Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions rust/src/generated/api_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ pub mod rpc_methods {
pub const SESSION_MODEL_SETREASONINGEFFORT: &str = "session.model.setReasoningEffort";
/// `session.model.list`
pub const SESSION_MODEL_LIST: &str = "session.model.list";
/// `session.model.setAllowedModels`
pub const SESSION_MODEL_SETALLOWEDMODELS: &str = "session.model.setAllowedModels";
/// `session.mode.get`
pub const SESSION_MODE_GET: &str = "session.mode.get";
/// `session.mode.set`
Expand Down Expand Up @@ -21593,6 +21595,47 @@ pub struct WorkspacesWriteAutopilotObjectiveResult {
pub operation: String,
}

/// Host-supplied exact CAPI model IDs to allow for this running session. The runtime intersects the list with repository `.github/allowed_models.txt` policy. Omit or pass null to clear the host restriction; an explicit empty or disjoint list is rejected.
///
/// <div class="warning">
///
/// **Experimental.** This type is part of an experimental wire-protocol surface
/// and may change or be removed in future SDK or CLI releases.
///
/// </div>
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelSetAllowedModelsRequest {
/// Exact model IDs to permit, or null to clear the host restriction.
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_models: Option<Vec<String>>,
}

/// The applied host allowlist and effective session model policy after intersection.
///
/// <div class="warning">
///
/// **Experimental.** This type is part of an experimental wire-protocol surface
/// and may change or be removed in future SDK or CLI releases.
///
/// </div>
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelSetAllowedModelsResult {
/// Normalized host allowlist. Omitted when the host restriction was cleared.
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_models: Option<Vec<String>>,
/// Effective exact IDs or repository policy patterns after applying the host restriction. Omitted by relay clients whose AHP host applies the policy asynchronously.
#[serde(skip_serializing_if = "Option::is_none")]
pub effective_allowed_models: Option<Vec<String>>,
/// Effective deterministic fallback model, when the policy defines one.
#[serde(skip_serializing_if = "Option::is_none")]
pub fallback_model: Option<String>,
/// Selected session model after reconciling a now-disallowed concrete selection.
#[serde(skip_serializing_if = "Option::is_none")]
pub model_id: Option<String>,
}

/// List of Copilot models available to the resolved user, including capabilities and billing metadata.
///
/// <div class="warning">
Expand Down Expand Up @@ -23102,6 +23145,31 @@ pub struct SessionModelListResult {
pub quota_snapshots: Option<HashMap<String, serde_json::Value>>,
}

/// The applied host allowlist and effective session model policy after intersection.
///
/// <div class="warning">
///
/// **Experimental.** This type is part of an experimental wire-protocol surface
/// and may change or be removed in future SDK or CLI releases.
///
/// </div>
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionModelSetAllowedModelsResult {
/// Normalized host allowlist. Omitted when the host restriction was cleared.
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_models: Option<Vec<String>>,
/// Effective exact IDs or repository policy patterns after applying the host restriction. Omitted by relay clients whose AHP host applies the policy asynchronously.
#[serde(skip_serializing_if = "Option::is_none")]
pub effective_allowed_models: Option<Vec<String>>,
/// Effective deterministic fallback model, when the policy defines one.
#[serde(skip_serializing_if = "Option::is_none")]
pub fallback_model: Option<String>,
/// Selected session model after reconciling a now-disallowed concrete selection.
#[serde(skip_serializing_if = "Option::is_none")]
pub model_id: Option<String>,
}

/// Identifies the target session.
///
/// <div class="warning">
Expand Down
36 changes: 36 additions & 0 deletions rust/src/generated/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7621,6 +7621,42 @@ impl<'a> SessionRpcModel<'a> {
.await?;
Ok(serde_json::from_value(_value)?)
}

/// Replaces or clears the host-supplied model allowlist for a running session.
///
/// Wire method: `session.model.setAllowedModels`.
///
/// # Parameters
///
/// * `params` - Host-supplied exact CAPI model IDs to allow for this running session. The runtime intersects the list with repository `.github/allowed_models.txt` policy. Omit or pass null to clear the host restriction; an explicit empty or disjoint list is rejected.
///
/// # Returns
///
/// The applied host allowlist and effective session model policy after intersection.
///
/// <div class="warning">
///
/// **Experimental.** This API is part of an experimental wire-protocol surface
/// and may change or be removed in future SDK or CLI releases. Pin both the
/// SDK and CLI versions if your code depends on it.
///
/// </div>
pub async fn set_allowed_models(
&self,
params: ModelSetAllowedModelsRequest,
) -> Result<ModelSetAllowedModelsResult, Error> {
let mut wire_params = serde_json::to_value(params)?;
wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string());
let _value = self
.session
.client()
.call(
rpc_methods::SESSION_MODEL_SETALLOWEDMODELS,
Some(wire_params),
)
.await?;
Ok(serde_json::from_value(_value)?)
}
}

/// `session.name.*` RPCs.
Expand Down
91 changes: 91 additions & 0 deletions rust/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1939,6 +1939,9 @@ pub struct SessionConfig {
pub session_id: Option<SessionId>,
/// Model to use (e.g. `"gpt-4"`, `"claude-sonnet-4"`).
pub model: Option<String>,
/// Exact model identifiers permitted for this session. When unset, the SDK
/// does not restrict model selection.
pub allowed_models: Option<Vec<String>>,
/// Application name sent as `User-Agent` context.
pub client_name: Option<String>,
/// Reasoning effort level (e.g. `"low"`, `"medium"`, `"high"`).
Expand Down Expand Up @@ -2273,6 +2276,7 @@ impl std::fmt::Debug for SessionConfig {
f.debug_struct("SessionConfig")
.field("session_id", &self.session_id)
.field("model", &self.model)
.field("allowed_models", &self.allowed_models)
.field("client_name", &self.client_name)
.field("reasoning_effort", &self.reasoning_effort)
.field("reasoning_summary", &self.reasoning_summary)
Expand Down Expand Up @@ -2415,6 +2419,7 @@ impl Default for SessionConfig {
Self {
session_id: None,
model: None,
allowed_models: None,
client_name: None,
reasoning_effort: None,
reasoning_summary: None,
Expand Down Expand Up @@ -2584,6 +2589,7 @@ impl SessionConfig {
let wire = crate::wire::SessionCreateWire {
session_id,
model: self.model,
allowed_models: self.allowed_models,
client_name: self.client_name,
reasoning_effort: self.reasoning_effort,
reasoning_summary: self.reasoning_summary,
Expand Down Expand Up @@ -2807,6 +2813,16 @@ impl SessionConfig {
self
}

/// Set the exact model identifiers permitted for this session.
pub fn with_allowed_models<I, S>(mut self, allowed_models: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.allowed_models = Some(allowed_models.into_iter().map(Into::into).collect());
self
}

/// Set the application name sent as `User-Agent` context.
pub fn with_client_name(mut self, name: impl Into<String>) -> Self {
self.client_name = Some(name.into());
Expand Down Expand Up @@ -3347,6 +3363,9 @@ pub struct ResumeSessionConfig {
/// Model to use for this session (e.g. `"gpt-4"`, `"claude-sonnet-4"`).
/// Can change the model when resuming.
pub model: Option<String>,
/// Exact model identifiers permitted for the resumed session. When unset,
/// the SDK does not restrict model selection.
pub allowed_models: Option<Vec<String>>,
/// Application name sent as User-Agent context.
pub client_name: Option<String>,
/// Desired reasoning effort to apply after resuming the session.
Expand Down Expand Up @@ -3609,6 +3628,7 @@ impl std::fmt::Debug for ResumeSessionConfig {
f.debug_struct("ResumeSessionConfig")
.field("session_id", &self.session_id)
.field("model", &self.model)
.field("allowed_models", &self.allowed_models)
.field("client_name", &self.client_name)
.field("reasoning_effort", &self.reasoning_effort)
.field("reasoning_summary", &self.reasoning_summary)
Expand Down Expand Up @@ -3794,6 +3814,7 @@ impl ResumeSessionConfig {
let wire = crate::wire::SessionResumeWire {
session_id: self.session_id,
model: self.model,
allowed_models: self.allowed_models,
client_name: self.client_name,
reasoning_effort: self.reasoning_effort,
reasoning_summary: self.reasoning_summary,
Expand Down Expand Up @@ -3902,6 +3923,7 @@ impl ResumeSessionConfig {
Self {
session_id,
model: None,
allowed_models: None,
client_name: None,
reasoning_effort: None,
reasoning_summary: None,
Expand Down Expand Up @@ -4095,6 +4117,16 @@ impl ResumeSessionConfig {
self
}

/// Set the exact model identifiers permitted for the resumed session.
pub fn with_allowed_models<I, S>(mut self, allowed_models: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.allowed_models = Some(allowed_models.into_iter().map(Into::into).collect());
self
}

/// Set the application name sent as `User-Agent` context.
pub fn with_client_name(mut self, name: impl Into<String>) -> Self {
self.client_name = Some(name.into());
Expand Down Expand Up @@ -6347,6 +6379,65 @@ mod tests {
assert!(json.get("askUserVariant").is_none());
}

#[test]
fn session_config_allowed_models_builder_debug_and_wire() {
let default = SessionConfig::default();
assert_eq!(default.allowed_models, None);
assert!(format!("{default:?}").contains("allowed_models: None"));

let config = SessionConfig::default().with_allowed_models(["gpt-5", "claude-sonnet-5"]);
assert_eq!(
config.allowed_models.as_deref(),
Some(&["gpt-5".to_string(), "claude-sonnet-5".to_string()][..])
);
assert!(
format!("{config:?}")
.contains("allowed_models: Some([\"gpt-5\", \"claude-sonnet-5\"])")
);

let (wire, _) = config
.into_wire(Some(SessionId::from("allowed-models-create")))
.expect("allowed models do not add SDK validation");
let json = serde_json::to_value(&wire).unwrap();
assert_eq!(json["allowedModels"], json!(["gpt-5", "claude-sonnet-5"]));

let (default_wire, _) = SessionConfig::default()
.into_wire(Some(SessionId::from("unrestricted-create")))
.expect("default config has no duplicate handlers");
let default_json = serde_json::to_value(&default_wire).unwrap();
assert!(default_json.get("allowedModels").is_none());
}

#[test]
fn resume_session_config_allowed_models_builder_debug_and_wire() {
let default = ResumeSessionConfig::new(SessionId::from("unrestricted-resume"));
assert_eq!(default.allowed_models, None);
assert!(format!("{default:?}").contains("allowed_models: None"));

let config = ResumeSessionConfig::new(SessionId::from("allowed-models-resume"))
.with_allowed_models(vec!["gpt-5".to_string(), "claude-sonnet-5".to_string()]);
assert_eq!(
config.allowed_models.as_deref(),
Some(&["gpt-5".to_string(), "claude-sonnet-5".to_string()][..])
);
assert!(
format!("{config:?}")
.contains("allowed_models: Some([\"gpt-5\", \"claude-sonnet-5\"])")
);

let (wire, _) = config
.into_wire()
.expect("allowed models do not add SDK validation");
let json = serde_json::to_value(&wire).unwrap();
assert_eq!(json["allowedModels"], json!(["gpt-5", "claude-sonnet-5"]));

let (default_wire, _) = ResumeSessionConfig::new(SessionId::from("unrestricted-resume"))
.into_wire()
.expect("default resume config has no duplicate handlers");
let default_json = serde_json::to_value(&default_wire).unwrap();
assert!(default_json.get("allowedModels").is_none());
}

#[test]
fn custom_agents_local_only_serializes_on_create_and_resume() {
let (create_wire, _) = SessionConfig::default()
Expand Down
4 changes: 4 additions & 0 deletions rust/src/wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub(crate) struct SessionCreateWire {
pub session_id: Option<SessionId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(rename = "allowedModels", skip_serializing_if = "Option::is_none")]
pub allowed_models: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -210,6 +212,8 @@ pub(crate) struct SessionResumeWire {
pub session_id: SessionId,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(rename = "allowedModels", skip_serializing_if = "Option::is_none")]
pub allowed_models: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down
Loading
Loading