Skip to content

Commit 578782e

Browse files
Rust SDK: PR #1367 review follow-ups (#1382)
1 parent 24d5ff6 commit 578782e

6 files changed

Lines changed: 421 additions & 368 deletions

File tree

rust/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ Tools are named types (not closures) — visible in stack traces and navigable v
375375

376376
Tools without an attached handler (`Tool::with_handler` never called) are declaration-only: the SDK advertises them on the wire but doesn't dispatch invocations to anything. Useful when another connected client services the tool.
377377

378-
For trivial tools that don't need a named type, [`define_tool`](crate::tool::define_tool) collapses the definition to a single expression and returns a fully-formed `Tool` with handler attached:
378+
For trivial tools that don't need a named type, the `define_tool` helper function (available with the `derive` feature) collapses the definition to a single expression and returns a fully-formed `Tool` with handler attached:
379379

380380
```rust,ignore
381381
use github_copilot_sdk::tool::{define_tool, JsonSchema};
@@ -672,7 +672,7 @@ ergonomics the dynamically-typed SDKs don't.
672672
`Session` value to thread in, and the SDK already prefers traits over
673673
boxed closures for handler-shaped APIs (`PermissionHandler`, `ToolHandler`,
674674
`SessionHooks`,
675-
`ToolHandler`).
675+
`SystemMessageTransform`).
676676

677677
```rust,ignore
678678
use std::sync::Arc;

rust/src/lib.rs

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -937,26 +937,20 @@ impl Client {
937937
// to the server. For Tcp, the SDK auto-generates one when the
938938
// caller leaves it unset so the loopback listener is safe by
939939
// default.
940-
let (mut options, effective_connection_token) = {
941-
let mut options = options;
942-
let effective = match &mut options.transport {
943-
Transport::Stdio => None,
944-
Transport::Tcp {
945-
connection_token, ..
946-
} => {
947-
if connection_token.is_none() {
948-
*connection_token = Some(generate_connection_token());
949-
}
950-
connection_token.clone()
951-
}
952-
Transport::External {
953-
connection_token, ..
954-
} => connection_token.clone(),
955-
};
956-
(options, effective)
940+
let mut options = options;
941+
let effective_connection_token: Option<String> = match &mut options.transport {
942+
Transport::Stdio => None,
943+
Transport::Tcp {
944+
connection_token, ..
945+
} => Some(
946+
connection_token
947+
.get_or_insert_with(generate_connection_token)
948+
.clone(),
949+
),
950+
Transport::External {
951+
connection_token, ..
952+
} => connection_token.clone(),
957953
};
958-
let _ = &mut options;
959-
let effective_connection_token: Option<String> = effective_connection_token;
960954
let session_fs_config = options.session_fs.clone();
961955
let session_fs_sqlite_declared = session_fs_config
962956
.as_ref()

rust/src/session.rs

Lines changed: 35 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl Drop for PendingSessionRegistration {
122122
/// Owns an internal event loop that dispatches events to the per-callback
123123
/// handlers installed on the session config.
124124
///
125-
/// Protocol methods (`send`, `get_messages`, `abort`, etc.) automatically
125+
/// Protocol methods (`send`, `get_events`, `abort`, etc.) automatically
126126
/// inject the session ID into RPC params.
127127
///
128128
/// Call [`destroy`](Self::destroy) for graceful cleanup (RPC + local). If dropped
@@ -788,45 +788,27 @@ impl Client {
788788
if let Some(transforms) = config.system_message_transform.clone() {
789789
inject_transform_sections(&mut config, transforms.as_ref());
790790
}
791-
let wire = config.to_wire(session_id.clone());
791+
let (wire, mut runtime) = config.into_wire(session_id.clone())?;
792792

793793
let permission_handler = crate::permission::resolve_handler(
794-
config.permission_handler.take(),
795-
config.permission_policy.take(),
794+
runtime.permission_handler.take(),
795+
runtime.permission_policy.take(),
796796
);
797-
let elicitation_handler = config.elicitation_handler.take();
798-
let user_input_handler = config.user_input_handler.take();
799-
let exit_plan_mode_handler = config.exit_plan_mode_handler.take();
800-
let auto_mode_switch_handler = config.auto_mode_switch_handler.take();
801-
let mut tool_map: HashMap<String, Arc<dyn crate::tool::ToolHandler>> = HashMap::new();
802-
if let Some(tools) = config.tools.as_mut() {
803-
for tool in tools.iter_mut() {
804-
if let Some(handler) = tool.handler.take() {
805-
if tool_map.contains_key(&tool.name) {
806-
return Err(Error::InvalidConfig(format!(
807-
"duplicate tool handler registered for name {:?}",
808-
tool.name
809-
)));
810-
}
811-
tool_map.insert(tool.name.clone(), handler);
812-
}
813-
}
814-
}
815797
let handlers = SessionHandlers {
816798
permission: permission_handler,
817-
elicitation: elicitation_handler,
818-
user_input: user_input_handler,
819-
exit_plan_mode: exit_plan_mode_handler,
820-
auto_mode_switch: auto_mode_switch_handler,
821-
tools: Arc::new(tool_map),
799+
elicitation: runtime.elicitation_handler.take(),
800+
user_input: runtime.user_input_handler.take(),
801+
exit_plan_mode: runtime.exit_plan_mode_handler.take(),
802+
auto_mode_switch: runtime.auto_mode_switch_handler.take(),
803+
tools: Arc::new(std::mem::take(&mut runtime.tool_handlers)),
822804
};
823-
let hooks = config.hooks_handler.take();
824-
let transforms = config.system_message_transform.take();
825-
let tools_count = config.tools.as_ref().map_or(0, Vec::len);
826-
let commands_count = config.commands.as_ref().map_or(0, Vec::len);
805+
let hooks = runtime.hooks_handler.take();
806+
let transforms = runtime.system_message_transform.take();
807+
let tools_count = wire.tools.as_ref().map_or(0, Vec::len);
808+
let commands_count = runtime.commands.as_ref().map_or(0, Vec::len);
827809
let has_hooks = hooks.is_some();
828-
let command_handlers = build_command_handler_map(config.commands.as_deref());
829-
let session_fs_provider = config.session_fs_provider.take();
810+
let command_handlers = build_command_handler_map(runtime.commands.as_deref());
811+
let session_fs_provider = runtime.session_fs_provider.take();
830812
if self.inner.session_fs_configured && session_fs_provider.is_none() {
831813
return Err(Error::Session(SessionError::SessionFsProviderRequired));
832814
}
@@ -943,45 +925,27 @@ impl Client {
943925
if let Some(transforms) = config.system_message_transform.clone() {
944926
inject_transform_sections_resume(&mut config, transforms.as_ref());
945927
}
946-
let wire = config.to_wire();
928+
let (wire, mut runtime) = config.into_wire()?;
947929

948930
let permission_handler = crate::permission::resolve_handler(
949-
config.permission_handler.take(),
950-
config.permission_policy.take(),
931+
runtime.permission_handler.take(),
932+
runtime.permission_policy.take(),
951933
);
952-
let elicitation_handler = config.elicitation_handler.take();
953-
let user_input_handler = config.user_input_handler.take();
954-
let exit_plan_mode_handler = config.exit_plan_mode_handler.take();
955-
let auto_mode_switch_handler = config.auto_mode_switch_handler.take();
956-
let mut tool_map: HashMap<String, Arc<dyn crate::tool::ToolHandler>> = HashMap::new();
957-
if let Some(tools) = config.tools.as_mut() {
958-
for tool in tools.iter_mut() {
959-
if let Some(handler) = tool.handler.take() {
960-
if tool_map.contains_key(&tool.name) {
961-
return Err(Error::InvalidConfig(format!(
962-
"duplicate tool handler registered for name {:?}",
963-
tool.name
964-
)));
965-
}
966-
tool_map.insert(tool.name.clone(), handler);
967-
}
968-
}
969-
}
970934
let handlers = SessionHandlers {
971935
permission: permission_handler,
972-
elicitation: elicitation_handler,
973-
user_input: user_input_handler,
974-
exit_plan_mode: exit_plan_mode_handler,
975-
auto_mode_switch: auto_mode_switch_handler,
976-
tools: Arc::new(tool_map),
936+
elicitation: runtime.elicitation_handler.take(),
937+
user_input: runtime.user_input_handler.take(),
938+
exit_plan_mode: runtime.exit_plan_mode_handler.take(),
939+
auto_mode_switch: runtime.auto_mode_switch_handler.take(),
940+
tools: Arc::new(std::mem::take(&mut runtime.tool_handlers)),
977941
};
978-
let hooks = config.hooks_handler.take();
979-
let transforms = config.system_message_transform.take();
980-
let tools_count = config.tools.as_ref().map_or(0, Vec::len);
981-
let commands_count = config.commands.as_ref().map_or(0, Vec::len);
942+
let hooks = runtime.hooks_handler.take();
943+
let transforms = runtime.system_message_transform.take();
944+
let tools_count = wire.tools.as_ref().map_or(0, Vec::len);
945+
let commands_count = runtime.commands.as_ref().map_or(0, Vec::len);
982946
let has_hooks = hooks.is_some();
983-
let command_handlers = build_command_handler_map(config.commands.as_deref());
984-
let session_fs_provider = config.session_fs_provider.take();
947+
let command_handlers = build_command_handler_map(runtime.commands.as_deref());
948+
let session_fs_provider = runtime.session_fs_provider.take();
985949
if self.inner.session_fs_configured && session_fs_provider.is_none() {
986950
return Err(Error::Session(SessionError::SessionFsProviderRequired));
987951
}
@@ -1464,12 +1428,12 @@ async fn handle_notification(
14641428
);
14651429
tokio::spawn(
14661430
async move {
1467-
if data.tool_call_id.is_empty() || data.tool_name.is_empty() {
1468-
let error_msg = if data.tool_call_id.is_empty() {
1469-
"Missing toolCallId"
1470-
} else {
1471-
"Missing toolName"
1472-
};
1431+
// `tool_name.is_empty()` would have produced a `None`
1432+
// lookup in `handlers.tools` and short-circuited at the
1433+
// outer guard above, so only the tool_call_id check is
1434+
// reachable here.
1435+
if data.tool_call_id.is_empty() {
1436+
let error_msg = "Missing toolCallId";
14731437
let rpc_start = Instant::now();
14741438
let _ = client
14751439
.call(

rust/src/tool.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,13 @@ pub fn schema_for<T: schemars::JsonSchema>() -> serde_json::Value {
5757
}
5858

5959
/// Convert a JSON Schema [`Value`](serde_json::Value) into the
60-
/// [`Tool::parameters`] map shape expected by the protocol.
60+
/// [`Tool::parameters`](crate::types::Tool::parameters) map shape
61+
/// expected by the protocol.
6162
///
6263
/// Panics if the input is not a JSON object — tool parameter schemas
6364
/// are always top-level objects (`{"type": "object", ...}`). Pair with
64-
/// [`schema_for`] or a `serde_json::json!(...)` literal.
65+
/// `schema_for` (available with the `derive` feature) or a
66+
/// `serde_json::json!(...)` literal.
6567
///
6668
/// Use [`try_tool_parameters`] when the schema comes from dynamic input and
6769
/// should return a recoverable error instead of panicking.
@@ -179,13 +181,15 @@ pub fn convert_mcp_call_tool_result(value: &serde_json::Value) -> Option<ToolRes
179181
///
180182
/// Implement this trait when you want to bind a Rust function to a tool
181183
/// name and have the SDK dispatch matching `external_tool.requested`
182-
/// broadcasts to it. Attach the impl to a [`Tool`] via [`Tool::with_handler`].
184+
/// broadcasts to it. Attach the impl to a [`Tool`](crate::types::Tool)
185+
/// via [`Tool::with_handler`](crate::types::Tool::with_handler).
183186
///
184187
/// Named handler types (e.g. `struct MyTool;`) are visible in stack
185188
/// traces and navigable via "go to definition", which is preferable to
186189
/// closure-based alternatives for non-trivial tools. For trivial tools,
187-
/// [`define_tool`] wraps a free `async fn` or closure into a [`Tool`]
188-
/// with the handler already attached.
190+
/// the `define_tool` helper function (available with the `derive`
191+
/// feature) wraps a free `async fn` or closure into a [`Tool`](crate::types::Tool) with
192+
/// the handler already attached.
189193
///
190194
/// # Example
191195
///

0 commit comments

Comments
 (0)