Skip to content

Commit cd3436f

Browse files
tclemCopilot
andcommitted
Mark remaining public config types non_exhaustive
Adds #[non_exhaustive] to the 10 remaining public configuration types that didn't already carry the attribute: - SessionConfig - ResumeSessionConfig - ClientOptions - ProviderConfig - McpServerConfig - Tool - CustomAgentConfig - InfiniteSessionConfig - SystemMessageConfig - ConnectionState HookEvent, HookOutput, MessageOptions, TelemetryConfig, SessionFsConfig, FsError, FileInfo, DirEntry, ToolInvocation, Error, Transport, and the new DeliveryMode were already marked. Closing the asymmetry now means adding fields to any of these post-1.0 is non-breaking on consumers that construct via Default::default() plus field assignment or the with_* builders. Tradeoff: external crates can no longer use struct-literal syntax for these types -- not even with ..Default::default(), which only works inside the defining crate. Tests, examples, and the tool_parameters doctest are migrated to the let-mut + field-assignment pattern. Callers porting from 0.1.0-* will see the same compile error and apply the same mechanical transform. CHANGELOG entry under Configuration parity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f3a5987 commit cd3436f

10 files changed

Lines changed: 84 additions & 78 deletions

File tree

rust/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ public surface.
127127
helper signatures are unchanged.
128128

129129
#### Configuration parity
130+
- All remaining public configuration types are now `#[non_exhaustive]`
131+
for forward-compatibility — adding fields post-1.0 is non-breaking on
132+
consumers that construct via `Default::default()` plus field
133+
assignment or the `with_*` builders. Affected: `SessionConfig`,
134+
`ResumeSessionConfig`, `ClientOptions`, `ProviderConfig`,
135+
`McpServerConfig`, `Tool`, `CustomAgentConfig`,
136+
`InfiniteSessionConfig`, `SystemMessageConfig`, `ConnectionState`.
137+
(`HookEvent`, `HookOutput`, `MessageOptions`, `TelemetryConfig`,
138+
`SessionFsConfig`, `FsError`, `FileInfo`, `DirEntry`, `ToolInvocation`,
139+
`Error`, `Transport`, `DeliveryMode` were already marked.) Callers
140+
using exhaustive struct literals must switch to
141+
`let mut x = Type::default(); x.field = ...;` or the available `with_*`
142+
builders; `..Default::default()` no longer compiles for these types
143+
outside the defining crate.
130144
- `MessageOptions::mode` is now typed `Option<DeliveryMode>` (was
131145
`Option<String>`). `DeliveryMode` is `#[non_exhaustive]` and serializes
132146
to the wire strings `"enqueue"` (default) and `"immediate"`. The prior

rust/examples/chat.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ fn read_line() -> Option<String> {
9090
async fn main() -> Result<(), github_copilot_sdk::Error> {
9191
let client = Client::start(ClientOptions::default()).await?;
9292

93-
let config = SessionConfig {
94-
streaming: Some(true),
95-
..Default::default()
96-
}
97-
.with_handler(Arc::new(ChatHandler));
93+
let config = {
94+
let mut cfg = SessionConfig::default();
95+
cfg.streaming = Some(true);
96+
cfg.with_handler(Arc::new(ChatHandler))
97+
};
9898
let session = client.create_session(config).await?;
9999

100100
println!(

rust/examples/hooks.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,10 @@ impl SessionHooks for AuditHooks {
101101
async fn main() -> Result<(), github_copilot_sdk::Error> {
102102
let client = Client::start(ClientOptions::default()).await?;
103103

104-
let config = SessionConfig {
105-
// hooks: true is set automatically when a hooks handler is provided.
106-
..Default::default()
107-
}
108-
.with_handler(Arc::new(ApproveAllHandler))
109-
.with_hooks(Arc::new(AuditHooks));
104+
// hooks: true is set automatically when a hooks handler is provided.
105+
let config = SessionConfig::default()
106+
.with_handler(Arc::new(ApproveAllHandler))
107+
.with_hooks(Arc::new(AuditHooks));
110108
let session = client.create_session(config).await?;
111109

112110
println!(

rust/examples/session_fs.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,14 @@ impl SessionFsProvider for InMemoryProvider {
116116
async fn main() -> Result<(), Box<dyn std::error::Error>> {
117117
let provider: Arc<dyn SessionFsProvider> = Arc::new(InMemoryProvider::new());
118118

119-
let options = ClientOptions {
120-
session_fs: Some(SessionFsConfig::new(
119+
let options = {
120+
let mut opts = ClientOptions::default();
121+
opts.session_fs = Some(SessionFsConfig::new(
121122
"/workspace",
122123
"/workspace/.copilot",
123124
SessionFsConventions::Posix,
124-
)),
125-
..Default::default()
125+
));
126+
opts
126127
};
127128

128129
let client = Client::start(options).await?;

rust/examples/tool_server.rs

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,11 @@ struct GetWeatherTool;
6060
#[async_trait]
6161
impl ToolHandler for GetWeatherTool {
6262
fn tool(&self) -> Tool {
63-
Tool {
64-
name: "get_weather".to_string(),
65-
namespaced_name: None,
66-
description: "Get the current weather for a city.".to_string(),
67-
parameters: tool_parameters(schema_for::<GetWeatherParams>()),
68-
instructions: None,
69-
..Default::default()
70-
}
63+
let mut tool = Tool::default();
64+
tool.name = "get_weather".to_string();
65+
tool.description = "Get the current weather for a city.".to_string();
66+
tool.parameters = tool_parameters(schema_for::<GetWeatherParams>());
67+
tool
7168
}
7269

7370
async fn call(&self, invocation: ToolInvocation) -> Result<ToolResult, Error> {
@@ -94,20 +91,17 @@ struct RollDiceTool;
9491
#[async_trait]
9592
impl ToolHandler for RollDiceTool {
9693
fn tool(&self) -> Tool {
97-
Tool {
98-
name: "roll_dice".to_string(),
99-
namespaced_name: None,
100-
description: "Roll one or more dice and return the total.".to_string(),
101-
parameters: tool_parameters(serde_json::json!({
102-
"type": "object",
103-
"properties": {
104-
"sides": { "type": "integer", "description": "Number of sides per die (default 6, max 1000)." },
105-
"count": { "type": "integer", "description": "Number of dice to roll (default 1, max 100)." }
106-
}
107-
})),
108-
instructions: None,
109-
..Default::default()
110-
}
94+
let mut tool = Tool::default();
95+
tool.name = "roll_dice".to_string();
96+
tool.description = "Roll one or more dice and return the total.".to_string();
97+
tool.parameters = tool_parameters(serde_json::json!({
98+
"type": "object",
99+
"properties": {
100+
"sides": { "type": "integer", "description": "Number of sides per die (default 6, max 1000)." },
101+
"count": { "type": "integer", "description": "Number of dice to roll (default 1, max 100)." }
102+
}
103+
}));
104+
tool
111105
}
112106

113107
async fn call(&self, invocation: ToolInvocation) -> Result<ToolResult, Error> {
@@ -160,11 +154,11 @@ async fn main() -> Result<(), github_copilot_sdk::Error> {
160154

161155
let client = Client::start(ClientOptions::default()).await?;
162156

163-
let config = SessionConfig {
164-
tools: Some(tools),
165-
..Default::default()
166-
}
167-
.with_handler(handler);
157+
let config = {
158+
let mut cfg = SessionConfig::default();
159+
cfg.tools = Some(tools);
160+
cfg.with_handler(handler)
161+
};
168162
let session = client.create_session(config).await?;
169163

170164
println!(

rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ impl From<PathBuf> for CliProgram {
307307
/// embedded CLI, and then the system PATH and common install locations.
308308
///
309309
/// Set `program` to [`CliProgram::Path`] to use an explicit binary.
310+
#[non_exhaustive]
310311
pub struct ClientOptions {
311312
/// How to locate the CLI binary.
312313
pub program: CliProgram,

rust/src/tool.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,10 @@ pub fn schema_for<T: schemars::JsonSchema>() -> serde_json::Value {
6969
/// use github_copilot_sdk::tool::tool_parameters;
7070
/// use github_copilot_sdk::Tool;
7171
///
72-
/// let tool = Tool {
73-
/// name: "ping".to_string(),
74-
/// namespaced_name: None,
75-
/// description: "ping the server".to_string(),
76-
/// parameters: tool_parameters(serde_json::json!({"type": "object"})),
77-
/// instructions: None,
78-
/// ..Default::default()
79-
/// };
72+
/// let mut tool = Tool::default();
73+
/// tool.name = "ping".to_string();
74+
/// tool.description = "ping the server".to_string();
75+
/// tool.parameters = tool_parameters(serde_json::json!({"type": "object"}));
8076
/// # let _ = tool;
8177
/// ```
8278
pub fn tool_parameters(schema: serde_json::Value) -> HashMap<String, serde_json::Value> {

rust/src/types.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use crate::transforms::SystemMessageTransform;
3131
/// unexpectedly.
3232
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
3333
#[serde(rename_all = "lowercase")]
34+
#[non_exhaustive]
3435
pub enum ConnectionState {
3536
/// No CLI process is attached or the process has exited cleanly.
3637
Disconnected,
@@ -303,6 +304,7 @@ impl PartialEq<&str> for RequestId {
303304
/// in the wire schema but are honored by the CLI.
304305
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
305306
#[serde(rename_all = "camelCase")]
307+
#[non_exhaustive]
306308
pub struct Tool {
307309
/// Tool identifier (e.g., `"bash"`, `"grep"`, `"str_replace_editor"`).
308310
pub name: String,
@@ -436,6 +438,7 @@ impl Serialize for CommandDefinition {
436438
/// when the session starts.
437439
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
438440
#[serde(rename_all = "camelCase")]
441+
#[non_exhaustive]
439442
pub struct CustomAgentConfig {
440443
/// Unique name of the custom agent.
441444
pub name: String,
@@ -483,6 +486,7 @@ pub struct DefaultAgentConfig {
483486
/// directory.
484487
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
485488
#[serde(rename_all = "camelCase")]
489+
#[non_exhaustive]
486490
pub struct InfiniteSessionConfig {
487491
/// Whether infinite sessions are enabled. Defaults to `true` on the CLI.
488492
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -534,6 +538,7 @@ pub struct InfiniteSessionConfig {
534538
/// ```
535539
#[derive(Debug, Clone, Serialize, Deserialize)]
536540
#[serde(tag = "type", rename_all = "lowercase")]
541+
#[non_exhaustive]
537542
pub enum McpServerConfig {
538543
/// Local MCP server launched as a subprocess and addressed over stdio.
539544
/// On the wire this serializes as `{"type": "stdio", ...}`. The CLI
@@ -601,6 +606,7 @@ pub struct McpHttpServerConfig {
601606
/// Copilot's default routing.
602607
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
603608
#[serde(rename_all = "camelCase")]
609+
#[non_exhaustive]
604610
pub struct ProviderConfig {
605611
/// Provider type: `"openai"`, `"azure"`, or `"anthropic"`. Defaults to
606612
/// `"openai"` on the CLI.
@@ -650,6 +656,7 @@ pub struct AzureProviderOptions {
650656
/// `availableTools`, `systemMessage`, etc.
651657
#[derive(Clone, Serialize, Deserialize)]
652658
#[serde(rename_all = "camelCase")]
659+
#[non_exhaustive]
653660
pub struct SessionConfig {
654661
/// Custom session ID. When unset, the CLI generates one.
655662
#[serde(skip_serializing_if = "Option::is_none")]
@@ -998,6 +1005,7 @@ impl SessionConfig {
9981005
/// See [`SessionConfig`] for the note on snake_case vs. camelCase field naming.
9991006
#[derive(Clone, Serialize, Deserialize)]
10001007
#[serde(rename_all = "camelCase")]
1008+
#[non_exhaustive]
10011009
pub struct ResumeSessionConfig {
10021010
/// ID of the session to resume.
10031011
pub session_id: SessionId,
@@ -1272,6 +1280,7 @@ impl ResumeSessionConfig {
12721280
/// section-level overrides.
12731281
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12741282
#[serde(rename_all = "camelCase")]
1283+
#[non_exhaustive]
12751284
pub struct SystemMessageConfig {
12761285
/// How content is applied: `"append"` (default), `"replace"`, or `"customize"`.
12771286
#[serde(skip_serializing_if = "Option::is_none")]

rust/tests/integration_test.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ use github_copilot_sdk::resolve::copilot_binary_with_source;
66
use github_copilot_sdk::{Client, ClientOptions, SDK_PROTOCOL_VERSION};
77

88
fn default_options() -> ClientOptions {
9-
ClientOptions {
10-
cwd: std::env::current_dir().expect("cwd"),
11-
..Default::default()
12-
}
9+
let mut opts = ClientOptions::default();
10+
opts.cwd = std::env::current_dir().expect("cwd");
11+
opts
1312
}
1413

1514
#[tokio::test]

rust/tests/session_test.rs

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,11 @@ async fn create_session_sends_correct_rpc() {
249249
let client = client.clone();
250250
async move {
251251
client
252-
.create_session(
253-
SessionConfig {
254-
model: Some("gpt-4".to_string()),
255-
..Default::default()
256-
}
257-
.with_handler(Arc::new(NoopHandler)),
258-
)
252+
.create_session({
253+
let mut cfg = SessionConfig::default();
254+
cfg.model = Some("gpt-4".to_string());
255+
cfg.with_handler(Arc::new(NoopHandler))
256+
})
259257
.await
260258
.unwrap()
261259
}
@@ -1870,13 +1868,7 @@ async fn request_elicitation_sent_in_create_params() {
18701868
let client = client.clone();
18711869
async move {
18721870
client
1873-
.create_session(
1874-
SessionConfig {
1875-
request_elicitation: Some(true),
1876-
..Default::default()
1877-
}
1878-
.with_handler(Arc::new(NoopHandler)),
1879-
)
1871+
.create_session(SessionConfig::default().with_handler(Arc::new(NoopHandler)))
18801872
.await
18811873
.unwrap()
18821874
}
@@ -2413,13 +2405,14 @@ fn session_config_serializes_bucket_b_fields() {
24132405

24142406
use github_copilot_sdk::{SessionConfig, SessionId};
24152407

2416-
let cfg = SessionConfig {
2417-
session_id: Some(SessionId::from("custom-id")),
2418-
config_dir: Some(PathBuf::from("/tmp/cfg")),
2419-
working_directory: Some(PathBuf::from("/tmp/work")),
2420-
github_token: Some("ghs_secret".to_string()),
2421-
include_sub_agent_streaming_events: Some(false),
2422-
..SessionConfig::default()
2408+
let cfg = {
2409+
let mut cfg = SessionConfig::default();
2410+
cfg.session_id = Some(SessionId::from("custom-id"));
2411+
cfg.config_dir = Some(PathBuf::from("/tmp/cfg"));
2412+
cfg.working_directory = Some(PathBuf::from("/tmp/work"));
2413+
cfg.github_token = Some("ghs_secret".to_string());
2414+
cfg.include_sub_agent_streaming_events = Some(false);
2415+
cfg
24232416
};
24242417
let json = serde_json::to_value(&cfg).unwrap();
24252418
assert_eq!(json["sessionId"], "custom-id");
@@ -2967,9 +2960,10 @@ async fn validate_session_fs_config_rejects_empty_initial_cwd() {
29672960
"/state",
29682961
SessionFsConventions::Posix,
29692962
);
2970-
let opts = github_copilot_sdk::ClientOptions {
2971-
session_fs: Some(cfg),
2972-
..Default::default()
2963+
let opts = {
2964+
let mut opts = github_copilot_sdk::ClientOptions::default();
2965+
opts.session_fs = Some(cfg);
2966+
opts
29732967
};
29742968
let err = github_copilot_sdk::Client::start(opts).await.err();
29752969
let err_string = format!("{err:?}");

0 commit comments

Comments
 (0)