Skip to content

Commit 66bf677

Browse files
committed
Add Rust runtime lock settings
Expose typed model and context-tier locks on create and resume session configs and serialize them through the nested runtimeSettings wire payload. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a93a18e3-4a6d-4667-97fe-9252838f19cf
1 parent 6e3893c commit 66bf677

2 files changed

Lines changed: 139 additions & 4 deletions

File tree

rust/src/types.rs

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,38 @@ impl ProviderModelConfig {
15821582
}
15831583
}
15841584

1585+
/// Runtime settings applied when creating or resuming a session.
1586+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
1587+
#[serde(rename_all = "camelCase")]
1588+
#[non_exhaustive]
1589+
pub struct RuntimeSettings {
1590+
/// Restricts model selection to this model identifier.
1591+
#[serde(default, skip_serializing_if = "Option::is_none")]
1592+
pub model_lock: Option<String>,
1593+
/// Restricts context tier selection to this tier.
1594+
#[serde(default, skip_serializing_if = "Option::is_none")]
1595+
pub context_tier_lock: Option<String>,
1596+
}
1597+
1598+
impl RuntimeSettings {
1599+
/// Construct runtime settings with all fields unset.
1600+
pub fn new() -> Self {
1601+
Self::default()
1602+
}
1603+
1604+
/// Restrict model selection to the given model identifier.
1605+
pub fn with_model_lock(mut self, model_lock: impl Into<String>) -> Self {
1606+
self.model_lock = Some(model_lock.into());
1607+
self
1608+
}
1609+
1610+
/// Restrict context tier selection to the given tier.
1611+
pub fn with_context_tier_lock(mut self, context_tier_lock: impl Into<String>) -> Self {
1612+
self.context_tier_lock = Some(context_tier_lock.into());
1613+
self
1614+
}
1615+
}
1616+
15851617
/// Configuration for creating a new session via the `session.create` RPC.
15861618
///
15871619
/// All fields are optional — the CLI applies sensible defaults.
@@ -1640,6 +1672,8 @@ pub struct SessionConfig {
16401672
pub session_id: Option<SessionId>,
16411673
/// Model to use (e.g. `"gpt-4"`, `"claude-sonnet-4"`).
16421674
pub model: Option<String>,
1675+
/// Runtime settings applied to this session.
1676+
pub runtime_settings: Option<RuntimeSettings>,
16431677
/// Application name sent as `User-Agent` context.
16441678
pub client_name: Option<String>,
16451679
/// Reasoning effort level (e.g. `"low"`, `"medium"`, `"high"`).
@@ -1921,6 +1955,7 @@ impl std::fmt::Debug for SessionConfig {
19211955
f.debug_struct("SessionConfig")
19221956
.field("session_id", &self.session_id)
19231957
.field("model", &self.model)
1958+
.field("runtime_settings", &self.runtime_settings)
19241959
.field("client_name", &self.client_name)
19251960
.field("reasoning_effort", &self.reasoning_effort)
19261961
.field("reasoning_summary", &self.reasoning_summary)
@@ -2048,6 +2083,7 @@ impl Default for SessionConfig {
20482083
Self {
20492084
session_id: None,
20502085
model: None,
2086+
runtime_settings: None,
20512087
client_name: None,
20522088
reasoning_effort: None,
20532089
reasoning_summary: None,
@@ -2200,6 +2236,7 @@ impl SessionConfig {
22002236
let wire = crate::wire::SessionCreateWire {
22012237
session_id,
22022238
model: self.model,
2239+
runtime_settings: self.runtime_settings,
22032240
client_name: self.client_name,
22042241
reasoning_effort: self.reasoning_effort,
22052242
reasoning_summary: self.reasoning_summary,
@@ -2406,6 +2443,12 @@ impl SessionConfig {
24062443
self
24072444
}
24082445

2446+
/// Set runtime settings for this session.
2447+
pub fn with_runtime_settings(mut self, runtime_settings: RuntimeSettings) -> Self {
2448+
self.runtime_settings = Some(runtime_settings);
2449+
self
2450+
}
2451+
24092452
/// Set the application name sent as `User-Agent` context.
24102453
pub fn with_client_name(mut self, name: impl Into<String>) -> Self {
24112454
self.client_name = Some(name.into());
@@ -2870,6 +2913,8 @@ pub struct ResumeSessionConfig {
28702913
/// Model to use for this session (e.g. `"gpt-4"`, `"claude-sonnet-4"`).
28712914
/// Can change the model when resuming.
28722915
pub model: Option<String>,
2916+
/// Runtime settings applied to the resumed session.
2917+
pub runtime_settings: Option<RuntimeSettings>,
28732918
/// Application name sent as User-Agent context.
28742919
pub client_name: Option<String>,
28752920
/// Desired reasoning effort to apply after resuming the session.
@@ -3089,6 +3134,7 @@ impl std::fmt::Debug for ResumeSessionConfig {
30893134
f.debug_struct("ResumeSessionConfig")
30903135
.field("session_id", &self.session_id)
30913136
.field("model", &self.model)
3137+
.field("runtime_settings", &self.runtime_settings)
30923138
.field("client_name", &self.client_name)
30933139
.field("reasoning_effort", &self.reasoning_effort)
30943140
.field("reasoning_summary", &self.reasoning_summary)
@@ -3253,6 +3299,7 @@ impl ResumeSessionConfig {
32533299
let wire = crate::wire::SessionResumeWire {
32543300
session_id: self.session_id,
32553301
model: self.model,
3302+
runtime_settings: self.runtime_settings,
32563303
client_name: self.client_name,
32573304
reasoning_effort: self.reasoning_effort,
32583305
reasoning_summary: self.reasoning_summary,
@@ -3350,6 +3397,7 @@ impl ResumeSessionConfig {
33503397
Self {
33513398
session_id,
33523399
model: None,
3400+
runtime_settings: None,
33533401
client_name: None,
33543402
reasoning_effort: None,
33553403
reasoning_summary: None,
@@ -3527,6 +3575,12 @@ impl ResumeSessionConfig {
35273575
self
35283576
}
35293577

3578+
/// Set runtime settings for the resumed session.
3579+
pub fn with_runtime_settings(mut self, runtime_settings: RuntimeSettings) -> Self {
3580+
self.runtime_settings = Some(runtime_settings);
3581+
self
3582+
}
3583+
35303584
/// Set the application name sent as `User-Agent` context.
35313585
pub fn with_client_name(mut self, name: impl Into<String>) -> Self {
35323586
self.client_name = Some(name.into());
@@ -5404,9 +5458,9 @@ mod tests {
54045458
CustomAgentConfig, DeliveryMode, ExtensionInfo, GitHubReferenceType, InfiniteSessionConfig,
54055459
LargeToolOutputConfig, McpServerConfig, McpStdioServerConfig, MemoryConfiguration,
54065460
NamedProviderConfig, ProviderConfig, ProviderModelConfig, ReasoningSummary,
5407-
ResumeSessionConfig, SessionConfig, SessionEvent, SessionId, SystemMessageConfig, Tool,
5408-
ToolBinaryResult, ToolResult, ToolResultExpanded, ToolResultResponse,
5409-
ensure_attachment_display_names,
5461+
ResumeSessionConfig, RuntimeSettings, SessionConfig, SessionEvent, SessionId,
5462+
SystemMessageConfig, Tool, ToolBinaryResult, ToolResult, ToolResultExpanded,
5463+
ToolResultResponse, ensure_attachment_display_names,
54105464
};
54115465
use crate::generated::session_events::TypedSessionEvent;
54125466

@@ -5791,6 +5845,83 @@ mod tests {
57915845
assert_eq!(json["expAssignments"], assignments);
57925846
}
57935847

5848+
#[test]
5849+
fn runtime_settings_serializes_locks() {
5850+
let settings = RuntimeSettings::new()
5851+
.with_model_lock("gpt-5.6-sol")
5852+
.with_context_tier_lock("long_context");
5853+
assert_eq!(
5854+
serde_json::to_value(settings).unwrap(),
5855+
json!({
5856+
"modelLock": "gpt-5.6-sol",
5857+
"contextTierLock": "long_context"
5858+
})
5859+
);
5860+
assert_eq!(
5861+
serde_json::to_value(RuntimeSettings::default()).unwrap(),
5862+
json!({})
5863+
);
5864+
}
5865+
5866+
#[test]
5867+
fn session_config_into_wire_serializes_runtime_settings() {
5868+
let config = SessionConfig::default().with_runtime_settings(
5869+
RuntimeSettings::new()
5870+
.with_model_lock("gpt-5.6-sol")
5871+
.with_context_tier_lock("long_context"),
5872+
);
5873+
let (wire, _) = config
5874+
.into_wire(Some(SessionId::from("runtime-settings")))
5875+
.expect("no duplicate handlers");
5876+
let json = serde_json::to_value(wire).unwrap();
5877+
assert_eq!(
5878+
json["runtimeSettings"],
5879+
json!({
5880+
"modelLock": "gpt-5.6-sol",
5881+
"contextTierLock": "long_context"
5882+
})
5883+
);
5884+
5885+
let (wire, _) = SessionConfig::default()
5886+
.into_wire(Some(SessionId::from("no-runtime-settings")))
5887+
.expect("no duplicate handlers");
5888+
assert!(
5889+
serde_json::to_value(wire)
5890+
.unwrap()
5891+
.get("runtimeSettings")
5892+
.is_none()
5893+
);
5894+
}
5895+
5896+
#[test]
5897+
fn resume_session_config_into_wire_serializes_runtime_settings() {
5898+
let config = ResumeSessionConfig::new(SessionId::from("runtime-settings"))
5899+
.with_runtime_settings(
5900+
RuntimeSettings::new()
5901+
.with_model_lock("gpt-5.6-sol")
5902+
.with_context_tier_lock("long_context"),
5903+
);
5904+
let (wire, _) = config.into_wire().expect("no duplicate handlers");
5905+
let json = serde_json::to_value(wire).unwrap();
5906+
assert_eq!(
5907+
json["runtimeSettings"],
5908+
json!({
5909+
"modelLock": "gpt-5.6-sol",
5910+
"contextTierLock": "long_context"
5911+
})
5912+
);
5913+
5914+
let (wire, _) = ResumeSessionConfig::new(SessionId::from("no-runtime-settings"))
5915+
.into_wire()
5916+
.expect("no duplicate handlers");
5917+
assert!(
5918+
serde_json::to_value(wire)
5919+
.unwrap()
5920+
.get("runtimeSettings")
5921+
.is_none()
5922+
);
5923+
}
5924+
57945925
#[test]
57955926
#[allow(clippy::field_reassign_with_default)]
57965927
fn session_config_into_wire_serializes_bucket_b_fields() {

rust/src/wire.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::types::{
2727
CanvasProviderIdentity, CapiSessionOptions, CloudSessionOptions, CustomAgentConfig,
2828
DefaultAgentConfig, ExtensionInfo, InfiniteSessionConfig, LargeToolOutputConfig,
2929
McpServerConfig, MemoryConfiguration, NamedProviderConfig, ProviderConfig, ProviderModelConfig,
30-
SessionId, SessionLimitsConfig, SystemMessageConfig, Tool, ToolSearchConfig,
30+
RuntimeSettings, SessionId, SessionLimitsConfig, SystemMessageConfig, Tool, ToolSearchConfig,
3131
};
3232

3333
/// Wire representation of a slash command (name + description only). The
@@ -50,6 +50,8 @@ pub(crate) struct SessionCreateWire {
5050
#[serde(skip_serializing_if = "Option::is_none")]
5151
pub model: Option<String>,
5252
#[serde(skip_serializing_if = "Option::is_none")]
53+
pub runtime_settings: Option<RuntimeSettings>,
54+
#[serde(skip_serializing_if = "Option::is_none")]
5355
pub client_name: Option<String>,
5456
#[serde(skip_serializing_if = "Option::is_none")]
5557
pub reasoning_effort: Option<String>,
@@ -185,6 +187,8 @@ pub(crate) struct SessionResumeWire {
185187
#[serde(skip_serializing_if = "Option::is_none")]
186188
pub model: Option<String>,
187189
#[serde(skip_serializing_if = "Option::is_none")]
190+
pub runtime_settings: Option<RuntimeSettings>,
191+
#[serde(skip_serializing_if = "Option::is_none")]
188192
pub client_name: Option<String>,
189193
#[serde(skip_serializing_if = "Option::is_none")]
190194
pub reasoning_effort: Option<String>,

0 commit comments

Comments
 (0)