-
Notifications
You must be signed in to change notification settings - Fork 1
chore: sync public mirror from internal #1335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -561,6 +561,11 @@ | |
| /// never credential values, and grants no Gateway authority. | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| model_binding: Option<crate::ManagedInferenceProviderBinding>, | ||
| /// The workspace writing policy and the sender's voice choice, | ||
| /// resolved by the authenticated host. Prompt data only; grants no | ||
| /// authority. Older turns carry none and render no voice. | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| voice: Option<crate::TurnVoice>, | ||
|
Check failure on line 568 in vendor/dex-loop/src/event.rs
|
||
| }, | ||
| /// Control: becomes a user message from `principal` before the next model | ||
| /// call. Calls the model then proposes act under `principal`. | ||
|
|
@@ -884,6 +889,41 @@ | |
| let event: Event = serde_json::from_value(input.clone()).expect("accepted message"); | ||
| let replay = serde_json::to_value(event).expect("durable event"); | ||
| assert_eq!(replay["model_binding"], input["model_binding"]); | ||
| assert!( | ||
| replay.get("voice").is_none(), | ||
| "older rows stay byte-identical" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn accepted_turn_voice_survives_event_round_trip() { | ||
| let input = serde_json::json!({ | ||
| "type": "user_message", "turn": "turn-1", "principal": "user-1", | ||
| "text": "hello", "attachments": [], | ||
| "voice": { | ||
| "policy": { | ||
| "guide_version": 3, | ||
| "voice": {"kind": "neutral"} | ||
| }, | ||
| "tone": ["formal"] | ||
| } | ||
| }); | ||
| let event: Event = serde_json::from_value(input.clone()).expect("accepted message"); | ||
| let Event::UserMessage { voice, .. } = &event else { | ||
| panic!("user message"); | ||
| }; | ||
| let voice = voice.as_ref().expect("voice"); | ||
| assert_eq!(voice.tone, vec![crate::ToneAdjustment::Formal]); | ||
| assert_eq!( | ||
| voice.policy.as_ref().map(|policy| &policy.voice), | ||
| Some(&crate::TurnVoiceChoice::Neutral) | ||
| ); | ||
| let replay = serde_json::to_value(event).expect("durable event"); | ||
| assert_eq!(replay["voice"]["tone"], input["voice"]["tone"]); | ||
| assert_eq!( | ||
| replay["voice"]["policy"]["voice"], | ||
| input["voice"]["policy"]["voice"] | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -923,6 +963,7 @@ | |
| attachments: vec![ArtifactRef::new("a1")], | ||
| authorized_tools: Vec::new(), | ||
| model_binding: None, | ||
| voice: None, | ||
| approval_mode: ApprovalMode::Interactive, | ||
| client_tools: vec![ClientToolSpec { | ||
| name: ToolName::new("browser.read_tab"), | ||
|
|
@@ -1036,6 +1077,7 @@ | |
| client_tools: vec![], | ||
| authorized_tools: Vec::new(), | ||
| model_binding: None, | ||
| voice: None, | ||
| approval_mode: ApprovalMode::Interactive, | ||
| }; | ||
| let json = serde_json::to_string(&event).expect("serialize"); | ||
|
|
@@ -1065,6 +1107,7 @@ | |
| client_tools: vec![], | ||
| authorized_tools: Vec::new(), | ||
| model_binding: None, | ||
| voice: None, | ||
| approval_mode: ApprovalMode::Interactive, | ||
| } | ||
| ); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| //! The writing policy one turn runs under: the workspace Content & AI policy | ||
| //! with the sender's voice choice already applied by the authenticated host. | ||
| //! | ||
| //! This is prompt data, never authority. It grants no tool, connector, or | ||
| //! model access, and the loop never reads it: the model port renders it into | ||
| //! the turn's stored context. Logged on the turn's `UserMessage`, so every | ||
| //! step, resume, and replica of the turn writes under the same policy even if | ||
| //! the workspace edits its style guide mid-turn. | ||
|
|
||
| use serde::{Deserialize, Serialize}; | ||
|
|
||
| /// One turn's resolved voice. Empty (no policy, no tone) renders nothing. | ||
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | ||
| pub struct TurnVoice { | ||
| /// The workspace Content & AI policy, present only when the workspace | ||
| /// has it enabled. | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| pub policy: Option<TurnContentPolicy>, | ||
| /// Short adjustments the sender asked for on top of the voice. | ||
| #[serde(default, skip_serializing_if = "Vec::is_empty")] | ||
| pub tone: Vec<ToneAdjustment>, | ||
| } | ||
|
|
||
| impl TurnVoice { | ||
| /// True when there is nothing to render for the turn. | ||
| pub fn is_empty(&self) -> bool { | ||
| self.policy.is_none() && self.tone.is_empty() | ||
| } | ||
| } | ||
|
|
||
| /// The workspace Content & AI policy snapshot admitted for one turn. | ||
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | ||
| pub struct TurnContentPolicy { | ||
| pub guide_version: u64, | ||
| #[serde(default)] | ||
| pub response_guidance: String, | ||
| #[serde(default)] | ||
| pub document_guidance: String, | ||
| #[serde(default)] | ||
| pub presentation_guidance: String, | ||
| #[serde(default)] | ||
| pub required_terms: Vec<String>, | ||
| #[serde(default)] | ||
| pub forbidden_terms: Vec<String>, | ||
| #[serde(default)] | ||
| pub require_citations: bool, | ||
| #[serde(default)] | ||
| pub allowed_citation_domains: Vec<String>, | ||
| #[serde(default)] | ||
| pub max_response_words: u32, | ||
| #[serde(default)] | ||
| pub voice: TurnVoiceChoice, | ||
| } | ||
|
|
||
| /// Which brand voice the turn writes in. | ||
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | ||
| #[serde(tag = "kind", rename_all = "snake_case")] | ||
| pub enum TurnVoiceChoice { | ||
| /// The workspace has no brand voice assigned. | ||
| #[default] | ||
| Unassigned, | ||
| /// The sender asked for no brand voice on this turn. The policy's | ||
| /// content rules still apply. | ||
| Neutral, | ||
| /// A workspace brand voice: the workspace default, or one the sender | ||
| /// picked. | ||
| Brand(TurnBrandVoice), | ||
| } | ||
|
|
||
| /// One workspace brand voice, copied at the version admitted for the turn. | ||
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | ||
| pub struct TurnBrandVoice { | ||
| pub voice_id: String, | ||
| pub name: String, | ||
| pub guidance: String, | ||
| pub version: u64, | ||
| /// True when the sender picked this voice rather than inheriting the | ||
| /// workspace default. | ||
| #[serde(default)] | ||
| pub explicit: bool, | ||
| } | ||
|
|
||
| /// A short tone adjustment the sender can add to one turn. | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] | ||
| #[serde(rename_all = "snake_case")] | ||
| pub enum ToneAdjustment { | ||
| Concise, | ||
| Formal, | ||
| Warmer, | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn empty_voice_serializes_to_an_empty_object_and_round_trips() { | ||
| let empty = TurnVoice::default(); | ||
| assert!(empty.is_empty()); | ||
| assert_eq!(serde_json::to_value(&empty).unwrap(), serde_json::json!({})); | ||
| let voice = TurnVoice { | ||
| policy: Some(TurnContentPolicy { | ||
| guide_version: 4, | ||
| voice: TurnVoiceChoice::Brand(TurnBrandVoice { | ||
| voice_id: "voice_exec".into(), | ||
| name: "Executive".into(), | ||
| guidance: "Lead with the decision.".into(), | ||
| version: 2, | ||
| explicit: true, | ||
| }), | ||
| ..TurnContentPolicy::default() | ||
| }), | ||
| tone: vec![ToneAdjustment::Concise, ToneAdjustment::Warmer], | ||
| }; | ||
| let json = serde_json::to_value(&voice).unwrap(); | ||
| assert_eq!(json["policy"]["voice"]["kind"], "brand"); | ||
| assert_eq!(json["tone"], serde_json::json!(["concise", "warmer"])); | ||
| assert_eq!(serde_json::from_value::<TurnVoice>(json).unwrap(), voice); | ||
| } | ||
|
|
||
| #[test] | ||
| fn neutral_choice_has_no_voice_fields() { | ||
| let json = serde_json::to_value(TurnVoiceChoice::Neutral).unwrap(); | ||
| assert_eq!(json, serde_json::json!({"kind": "neutral"})); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a
UserMessagecarries this field, it is persisted and rehydrated, but the in-repodex_loop::Modelimplementation never readsContext::voice():packages/dex-host-rs/src/model.rsbuilds the request fromctx.history()at lines 518-519, and itsRequestConfigonly usesself.systemat lines 103-116. A repository-wide search finds no othervoice()consumer, so policy guidance, required/forbidden terms, citation requirements, and tone have no effect onAiRsModelresponses. Include the resolved voice in the model prompt/request before treating it as an admitted turn policy.Useful? React with 👍 / 👎.