From 7682348bf1ba6cead28ba7304f3edfa5acb42d0e Mon Sep 17 00:00:00 2001 From: David Fowler <95136+davidfowl@users.noreply.github.com> Date: Tue, 6 Oct 2026 12:59:05 +0000 Subject: [PATCH] feat(rust): add original-session remote policy inputs Apply the canonical machine-generated Rust RPC and DTO delta from runtime eaec03ae972f75282fe5122bebd54cba3c6cae01 (schema producer 9d3047795c634841ce94718d66759e3b7ca15d23). Preserve the standalone CLI pin and unrelated SDK snapshot differences. api.schema.json SHA256: 7f2f51e8ea294ec8fa116da77ff58bf0e1fea4d8fb0b4bcfd460195e2fd667f6 session-events.schema.json SHA256: 1e363c28ca3b2197ea4baf2ba25980138eda63213cd8a6585151deeb111a882d Cover Content-Length framed transport with the original session ID, all boolean combinations, malformed required fields, and RPC error propagation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- rust/README.md | 13 +++ rust/src/generated/api_types.rs | 59 +++++++++++++ rust/src/generated/rpc.rs | 28 ++++++ rust/tests/session_test.rs | 150 +++++++++++++++++++++++++++++++- 4 files changed, 249 insertions(+), 1 deletion(-) diff --git a/rust/README.md b/rust/README.md index e2dcdb087e..9d3986f20c 100644 --- a/rust/README.md +++ b/rust/README.md @@ -517,6 +517,19 @@ New RPCs land in the namespace immediately as the schema regenerates; helpers are added on top only when an ergonomic story is worth the maintenance. +The experimental `session.rpc().remote().get_policy_inputs().await?` returns +`github_copilot_sdk::rpc::RemotePolicyInputs` for that original live session, +using its existing subscription. Its four required booleans are +`managed_remote_control_setting`, `managed_remote_control_staff_override`, +`owner_adc_sandbox`, and `owner_codespaces`. The managed flags include native +defaults; the environment booleans describe the session's owning runtime +process (`ADC_SANDBOX_ID` nonempty and `CODESPACES` exactly `"true"`). +The call does not enable export or steering, apply managed policy, or expose +raw flags or environment values. Missing or unattached sessions, unsupported +runtimes, and malformed responses return errors rather than inferred inputs. +Use a matching runtime and its emitted schemas for this unreleased API; the +SDK change alone does not add support to an older pinned CLI. + #### Typed MCP installation and removal payloads (breaking change) Three payloads in the experimental MCP installation and removal workflow are now typed diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 32c6e7f6ca..c8a919a7c5 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -973,6 +973,8 @@ pub mod rpc_methods { pub const SESSION_REMOTE_ENABLE: &str = "session.remote.enable"; /// `session.remote.disable` pub const SESSION_REMOTE_DISABLE: &str = "session.remote.disable"; + /// `session.remote.getPolicyInputs` + pub const SESSION_REMOTE_GETPOLICYINPUTS: &str = "session.remote.getPolicyInputs"; /// `session.remote.notifySteerableChanged` pub const SESSION_REMOTE_NOTIFYSTEERABLECHANGED: &str = "session.remote.notifySteerableChanged"; /// `session.visibility.get` @@ -20120,6 +20122,27 @@ pub struct RemoteNotifySteerableChangedRequest { #[serde(rename_all = "camelCase")] pub struct RemoteNotifySteerableChangedResult {} +/// Read-only remote-policy inputs from the original live session and its owning runtime process. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RemotePolicyInputs { + /// Resolved MANAGED_REMOTE_CONTROL_SETTING flag, including the runtime default when no boolean override exists. + pub managed_remote_control_setting: bool, + /// Resolved MANAGED_REMOTE_CONTROL_SETTING_STAFF_OVERRIDE flag, including the runtime default when no boolean override exists. + pub managed_remote_control_staff_override: bool, + /// Whether ADC_SANDBOX_ID is nonempty in the original session's owning runtime process. + pub owner_adc_sandbox: bool, + /// Whether CODESPACES is the literal string "true" in the original session's owning runtime process. + pub owner_codespaces: bool, +} + /// Remote session connection result. /// ///
@@ -36534,6 +36557,42 @@ pub struct SessionRemoteDisableParams { pub session_id: SessionId, } +/// Identifies the target session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionRemoteGetPolicyInputsParams { + /// Target session identifier + pub session_id: SessionId, +} + +/// Read-only remote-policy inputs from the original live session and its owning runtime process. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionRemoteGetPolicyInputsResult { + /// Resolved MANAGED_REMOTE_CONTROL_SETTING flag, including the runtime default when no boolean override exists. + pub managed_remote_control_setting: bool, + /// Resolved MANAGED_REMOTE_CONTROL_SETTING_STAFF_OVERRIDE flag, including the runtime default when no boolean override exists. + pub managed_remote_control_staff_override: bool, + /// Whether ADC_SANDBOX_ID is nonempty in the original session's owning runtime process. + pub owner_adc_sandbox: bool, + /// Whether CODESPACES is the literal string "true" in the original session's owning runtime process. + pub owner_codespaces: bool, +} + /// Persist a steerability change as a `session.remote_steerable_changed` event. Used by the host (CLI / SDK consumer) when it has just finished enabling or disabling steering on a remote exporter that the runtime does not directly own. /// ///
diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index 766e1908a2..9e8452ad1d 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -13168,6 +13168,34 @@ impl<'a> SessionRpcRemote<'a> { Ok(()) } + /// Reads the original live session's resolved remote-policy feature flags and owner-process environment booleans without changing remote export or steering. Requires the caller's already-established session subscription. + /// + /// Wire method: `session.remote.getPolicyInputs`. + /// + /// # Returns + /// + /// Read-only remote-policy inputs from the original live session and its owning runtime process. + /// + ///
+ /// + /// **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. + /// + ///
+ pub async fn get_policy_inputs(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_REMOTE_GETPOLICYINPUTS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Persists a remote-steerability change emitted by the host as a session event. /// /// Wire method: `session.remote.notifySteerableChanged`. diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 23a254d9e9..52b4438628 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -24,7 +24,7 @@ use github_copilot_sdk::rpc::{ ConnectorConnectResult, ConnectorContinueRequest, ConnectorDisconnectResult, ConnectorMcpStatus, ConnectorReconcileOptions, ConnectorReconcileRequest, ConnectorSessionAccount, ConnectorStatus, ModelSetAllowedModelsRequest, OpenCanvasInstance, - SendAgentMode, SendMode, SendRequest, SessionRpcConnectors, + RemotePolicyInputs, SendAgentMode, SendMode, SendRequest, SessionRpcConnectors, }; use github_copilot_sdk::session_events::{ ManagedSettingsResolvedSource, McpOauthRequiredData, ReasoningSummary, SessionLimitsConfig, @@ -7420,6 +7420,154 @@ async fn rpc_namespace_session_connectors_dispatches_all_methods() { timeout(TIMEOUT, server_handle).await.unwrap().unwrap(); } +#[tokio::test] +async fn remote_policy_inputs_preserves_original_session_and_all_boolean_combinations() { + let (session, mut server) = + create_session_pair_with_config(|cfg| cfg.with_session_id("original-policy-session")).await; + let session = Arc::new(session); + assert_eq!(session.id().as_str(), "original-policy-session"); + + for bits in 0..16 { + let expected = serde_json::json!({ + "managedRemoteControlSetting": bits & 1 != 0, + "managedRemoteControlStaffOverride": bits & 2 != 0, + "ownerAdcSandbox": bits & 4 != 0, + "ownerCodespaces": bits & 8 != 0, + }); + let handle = tokio::spawn({ + let session = session.clone(); + async move { session.rpc().remote().get_policy_inputs().await } + }); + let request = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(request["method"], "session.remote.getPolicyInputs"); + assert_eq!( + request["params"], + serde_json::json!({ "sessionId": "original-policy-session" }) + ); + server.respond(&request, expected.clone()).await; + + let result: RemotePolicyInputs = timeout(TIMEOUT, handle).await.unwrap().unwrap().unwrap(); + assert_eq!(result.managed_remote_control_setting, bits & 1 != 0); + assert_eq!(result.managed_remote_control_staff_override, bits & 2 != 0); + assert_eq!(result.owner_adc_sandbox, bits & 4 != 0); + assert_eq!(result.owner_codespaces, bits & 8 != 0); + assert_eq!(serde_json::to_value(result).unwrap(), expected); + } +} + +#[tokio::test] +async fn remote_policy_inputs_rejects_missing_and_malformed_required_booleans() { + let (session, mut server) = create_session_pair().await; + let session = Arc::new(session); + let valid = serde_json::json!({ + "managedRemoteControlSetting": false, + "managedRemoteControlStaffOverride": false, + "ownerAdcSandbox": false, + "ownerCodespaces": false, + }); + let mut malformed = vec![Value::Null, serde_json::json!([]), serde_json::json!({})]; + for field in [ + "managedRemoteControlSetting", + "managedRemoteControlStaffOverride", + "ownerAdcSandbox", + "ownerCodespaces", + ] { + let mut missing = valid.clone(); + missing.as_object_mut().unwrap().remove(field); + malformed.push(missing); + for value in [ + Value::Null, + serde_json::json!("false"), + serde_json::json!(0), + serde_json::json!([]), + serde_json::json!({}), + ] { + let mut response = valid.clone(); + response[field] = value; + malformed.push(response); + } + } + + for response in malformed { + let handle = tokio::spawn({ + let session = session.clone(); + async move { session.rpc().remote().get_policy_inputs().await } + }); + let request = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(request["method"], "session.remote.getPolicyInputs"); + assert_eq!( + request["params"], + serde_json::json!({ "sessionId": server.session_id }) + ); + server.respond(&request, response.clone()).await; + + let error = timeout(TIMEOUT, handle) + .await + .unwrap() + .unwrap() + .expect_err("malformed policy inputs must not default to false"); + assert_eq!(error.kind(), &ErrorKind::Json, "response: {response}"); + } +} + +#[tokio::test] +async fn remote_policy_inputs_propagates_rpc_errors_without_fallback() { + let (session, mut server) = create_session_pair().await; + let session = Arc::new(session); + for (code, message) in [ + (-32601, "Method not found"), + (-32000, "Original session not found"), + (-32000, "Original session is not attached"), + ] { + let handle = tokio::spawn({ + let session = session.clone(); + async move { session.rpc().remote().get_policy_inputs().await } + }); + let request = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(request["method"], "session.remote.getPolicyInputs"); + assert_eq!( + request["params"], + serde_json::json!({ "sessionId": server.session_id }) + ); + let data = serde_json::json!({ "sessionId": server.session_id }); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": request["id"], + "error": { "code": code, "message": message, "data": data }, + }); + write_framed(&mut server.write, &serde_json::to_vec(&response).unwrap()).await; + + let error = timeout(TIMEOUT, handle) + .await + .unwrap() + .unwrap() + .expect_err("RPC rejection must propagate"); + assert_eq!(error.kind(), &ErrorKind::Rpc { code }); + assert_eq!(error.message(), Some(message)); + assert_eq!(error.rpc_data(), Some(&data)); + } + + let handle = tokio::spawn(async move { session.rpc().remote().get_policy_inputs().await }); + let request = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(request["method"], "session.remote.getPolicyInputs"); + assert_eq!( + request["params"], + serde_json::json!({ "sessionId": server.session_id }) + ); + server + .respond( + &request, + serde_json::json!({ + "managedRemoteControlSetting": false, + "managedRemoteControlStaffOverride": false, + "ownerAdcSandbox": false, + "ownerCodespaces": false, + }), + ) + .await; + timeout(TIMEOUT, handle).await.unwrap().unwrap().unwrap(); +} + #[tokio::test] async fn rpc_namespace_client_models_list_dispatches_correctly() { let (session, mut server) = create_session_pair().await;