From c4bd9730992620b2c051c8ebfe9f5b7e4642db57 Mon Sep 17 00:00:00 2001 From: Eljo George Date: Fri, 7 Aug 2026 13:39:17 -0700 Subject: [PATCH 1/3] feat(rust): support cwd in model listing Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/src/generated/api_types.rs | 5 ++++- rust/tests/api_types_test.rs | 16 ++++++++++++++- rust/tests/session_test.rs | 35 ++++++++++++++++++++++++++++++++- scripts/codegen/rust.ts | 3 ++- scripts/codegen/utils.ts | 29 +++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 4 deletions(-) diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index c93f34cf64..18d4c7ed58 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -8484,7 +8484,7 @@ pub struct ModelSetReasoningEffortResult { pub reasoning_effort: String, } -/// Optional GitHub token used to list models for a specific user instead of the global auth context. +/// Optional GitHub token and working directory used to resolve available models. /// ///
/// @@ -8495,6 +8495,9 @@ pub struct ModelSetReasoningEffortResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ModelsListRequest { + /// Working directory used to apply repository model policy. When omitted, model availability is account-global. + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, /// GitHub token for per-user model listing. When provided, resolves this token to determine the user's Copilot plan and available models instead of using the global auth. #[serde(skip_serializing_if = "Option::is_none")] pub git_hub_token: Option, diff --git a/rust/tests/api_types_test.rs b/rust/tests/api_types_test.rs index 9b86b1367a..27c492916d 100644 --- a/rust/tests/api_types_test.rs +++ b/rust/tests/api_types_test.rs @@ -5,7 +5,8 @@ use github_copilot_sdk::rpc::{ Extension, ExtensionList, ExtensionSource, ExtensionStatus, ExtensionsDisableRequest, - ExtensionsEnableRequest, FleetStartRequest, FleetStartResult, TasksStartAgentRequest, + ExtensionsEnableRequest, FleetStartRequest, FleetStartResult, ModelsListRequest, + TasksStartAgentRequest, }; use github_copilot_sdk::session_events::{PermissionRequest, PermissionRequestedData}; @@ -104,6 +105,19 @@ fn permission_event_exposes_managed_approval_required() { assert_eq!(request.managed_approval_required, Some(true)); } +#[test] +fn models_list_request_serializes_repository_cwd() { + let request = ModelsListRequest { + cwd: Some("/workspace/repository".to_string()), + git_hub_token: None, + }; + + assert_eq!( + serde_json::to_value(request).unwrap(), + serde_json::json!({ "cwd": "/workspace/repository" }) + ); +} + fn running_extension(id: &str, name: &str) -> Extension { Extension { id: id.to_string(), diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 727911081d..bb74f4cae8 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -14,7 +14,7 @@ use github_copilot_sdk::handler::{ }; use github_copilot_sdk::rpc::{ CanvasProviderInvokeActionRequest, CanvasProviderOpenRequest, CanvasProviderOpenResult, - OpenCanvasInstance, + ModelsListRequest, OpenCanvasInstance, }; use github_copilot_sdk::session_events::{ ManagedSettingsResolvedSource, McpOauthRequiredData, ReasoningSummary, SessionLimitsConfig, @@ -4044,6 +4044,39 @@ async fn rpc_namespace_client_models_list_dispatches_correctly() { assert!(result.models.is_empty()); } +#[tokio::test] +async fn rpc_namespace_client_models_list_sends_repository_cwd() { + let (session, mut server) = create_session_pair().await; + let session = Arc::new(session); + + let client = session.client().clone(); + let handle = tokio::spawn(async move { + client + .rpc() + .models() + .list_with_params(ModelsListRequest { + cwd: Some("/workspace/repository".to_string()), + git_hub_token: None, + }) + .await + }); + + let request = server.read_request().await; + assert_eq!(request["method"], "models.list"); + assert_eq!( + request["params"], + serde_json::json!({ + "cwd": "/workspace/repository" + }) + ); + server + .respond(&request, serde_json::json!({ "models": [] })) + .await; + + let result = timeout(TIMEOUT, handle).await.unwrap().unwrap().unwrap(); + assert!(result.models.is_empty()); +} + #[tokio::test] async fn client_stop_sends_session_destroy_for_each_active_session() { // One client, two registered sessions. Client::stop must send diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index 4090318f05..1375af4eb1 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -17,6 +17,7 @@ import { fileURLToPath } from "url"; import { promisify } from "util"; import type { JSONSchema7, JSONSchema7Definition } from "json-schema"; import { + addCwdToModelsListRequest, addManagedApprovalRequiredToPermissionRequests, type ApiSchema, type DefinitionCollections, @@ -2219,7 +2220,7 @@ async function generate(): Promise { ); const apiSchema = propagateInternalVisibility( postProcessSchema( - stripBooleanLiterals(apiRaw) as JSONSchema7, + stripBooleanLiterals(addCwdToModelsListRequest(apiRaw)) as JSONSchema7, ), ) as unknown as ApiSchema; diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 42e78b9a07..b7f94fec90 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -496,6 +496,35 @@ export function addManagedApprovalRequiredToPermissionRequests(schema: T): T { + const cloned = cloneSchemaForCodegen(schema); + const property: JSONSchema7 = { + description: + "Working directory used to apply repository model policy. When omitted, model availability is account-global.", + type: ["string", "null"], + }; + (property as Record)["x-copilot-sdk-append-last"] = true; + + for (const definitions of [cloned.definitions, cloned.$defs]) { + if (!definitions) continue; + const definition = definitions.ModelsListRequest; + if (!definition || typeof definition !== "object") continue; + const objectDefinition = definition as JSONSchema7; + if (objectDefinition.properties?.cwd) continue; + objectDefinition.description = + "Optional GitHub token and working directory used to resolve available models."; + objectDefinition.properties = { + ...objectDefinition.properties, + cwd: cloneSchemaForCodegen(property), + }; + } + + return cloned; +} + export function getEnumValueDescriptions(schema: JSONSchema7 | null | undefined): EnumValueDescriptions | undefined { if (!schema || typeof schema !== "object") return undefined; From 77848a5428cc36128c33c1abaf8fa212fe05e7dc Mon Sep 17 00:00:00 2001 From: Eljo George Date: Fri, 7 Aug 2026 13:47:07 -0700 Subject: [PATCH 2/3] feat(rust): support model list cache refresh Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/src/generated/api_types.rs | 5 ++- rust/tests/api_types_test.rs | 12 +++++-- rust/tests/session_test.rs | 6 ++-- scripts/codegen/rust.ts | 4 +-- scripts/codegen/utils.ts | 57 ++++++++++++++++++++++++--------- 5 files changed, 62 insertions(+), 22 deletions(-) diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 18d4c7ed58..d3948c1959 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -8484,7 +8484,7 @@ pub struct ModelSetReasoningEffortResult { pub reasoning_effort: String, } -/// Optional GitHub token and working directory used to resolve available models. +/// Optional GitHub token, working directory, and cache controls used to resolve available models. /// ///
/// @@ -8501,6 +8501,9 @@ pub struct ModelsListRequest { /// GitHub token for per-user model listing. When provided, resolves this token to determine the user's Copilot plan and available models instead of using the global auth. #[serde(skip_serializing_if = "Option::is_none")] pub git_hub_token: Option, + /// When true, bypasses cached model data and refreshes the available model list. + #[serde(skip_serializing_if = "Option::is_none")] + pub skip_cache: Option, } /// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. diff --git a/rust/tests/api_types_test.rs b/rust/tests/api_types_test.rs index 27c492916d..c930228d96 100644 --- a/rust/tests/api_types_test.rs +++ b/rust/tests/api_types_test.rs @@ -106,15 +106,23 @@ fn permission_event_exposes_managed_approval_required() { } #[test] -fn models_list_request_serializes_repository_cwd() { +fn models_list_request_serializes_repository_options() { let request = ModelsListRequest { cwd: Some("/workspace/repository".to_string()), git_hub_token: None, + skip_cache: Some(true), }; assert_eq!( serde_json::to_value(request).unwrap(), - serde_json::json!({ "cwd": "/workspace/repository" }) + serde_json::json!({ + "cwd": "/workspace/repository", + "skipCache": true + }) + ); + assert_eq!( + serde_json::to_value(ModelsListRequest::default()).unwrap(), + serde_json::json!({}) ); } diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index bb74f4cae8..2ff6d742fe 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -4045,7 +4045,7 @@ async fn rpc_namespace_client_models_list_dispatches_correctly() { } #[tokio::test] -async fn rpc_namespace_client_models_list_sends_repository_cwd() { +async fn rpc_namespace_client_models_list_sends_repository_options() { let (session, mut server) = create_session_pair().await; let session = Arc::new(session); @@ -4057,6 +4057,7 @@ async fn rpc_namespace_client_models_list_sends_repository_cwd() { .list_with_params(ModelsListRequest { cwd: Some("/workspace/repository".to_string()), git_hub_token: None, + skip_cache: Some(true), }) .await }); @@ -4066,7 +4067,8 @@ async fn rpc_namespace_client_models_list_sends_repository_cwd() { assert_eq!( request["params"], serde_json::json!({ - "cwd": "/workspace/repository" + "cwd": "/workspace/repository", + "skipCache": true }) ); server diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index 1375af4eb1..fcf0826f85 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -17,8 +17,8 @@ import { fileURLToPath } from "url"; import { promisify } from "util"; import type { JSONSchema7, JSONSchema7Definition } from "json-schema"; import { - addCwdToModelsListRequest, addManagedApprovalRequiredToPermissionRequests, + addModelsListRequestOptions, type ApiSchema, type DefinitionCollections, EXCLUDED_EVENT_TYPES, @@ -2220,7 +2220,7 @@ async function generate(): Promise { ); const apiSchema = propagateInternalVisibility( postProcessSchema( - stripBooleanLiterals(addCwdToModelsListRequest(apiRaw)) as JSONSchema7, + stripBooleanLiterals(addModelsListRequestOptions(apiRaw)) as JSONSchema7, ), ) as unknown as ApiSchema; diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index b7f94fec90..61abf0649b 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -497,29 +497,56 @@ export function addManagedApprovalRequiredToPermissionRequests(schema: T): T { +export function addModelsListRequestOptions(schema: T): T { const cloned = cloneSchemaForCodegen(schema); - const property: JSONSchema7 = { - description: - "Working directory used to apply repository model policy. When omitted, model availability is account-global.", - type: ["string", "null"], + const properties: Record = { + cwd: { + description: + "Working directory used to apply repository model policy. When omitted, model availability is account-global.", + type: ["string", "null"], + }, + skipCache: { + description: + "When true, bypasses cached model data and refreshes the available model list.", + type: ["boolean", "null"], + }, }; - (property as Record)["x-copilot-sdk-append-last"] = true; + for (const property of Object.values(properties)) { + (property as Record)["x-copilot-sdk-append-last"] = true; + } for (const definitions of [cloned.definitions, cloned.$defs]) { if (!definitions) continue; const definition = definitions.ModelsListRequest; if (!definition || typeof definition !== "object") continue; - const objectDefinition = definition as JSONSchema7; - if (objectDefinition.properties?.cwd) continue; - objectDefinition.description = - "Optional GitHub token and working directory used to resolve available models."; - objectDefinition.properties = { - ...objectDefinition.properties, - cwd: cloneSchemaForCodegen(property), - }; + const requestDefinition = definition as JSONSchema7; + const objectDefinition = [ + requestDefinition, + ...(requestDefinition.anyOf ?? []), + ...(requestDefinition.oneOf ?? []), + ].find( + (candidate): candidate is JSONSchema7 => + typeof candidate === "object" && + candidate !== null && + (candidate.type === "object" || candidate.properties !== undefined), + ); + if (!objectDefinition) continue; + + let patched = false; + for (const [name, property] of Object.entries(properties)) { + if (objectDefinition.properties?.[name]) continue; + objectDefinition.properties = { + ...objectDefinition.properties, + [name]: cloneSchemaForCodegen(property), + }; + patched = true; + } + if (patched) { + requestDefinition.description = + "Optional GitHub token, working directory, and cache controls used to resolve available models."; + } } return cloned; From fbf084fecd75c1a5679c51d022962e7a7a688a93 Mon Sep 17 00:00:00 2001 From: Eljo George Date: Fri, 7 Aug 2026 13:52:52 -0700 Subject: [PATCH 3/3] fix(rust): align model list request contract Remove the unconfirmed skipCache option and keep cwd-only repository scoping. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/src/generated/api_types.rs | 5 +--- rust/tests/api_types_test.rs | 8 ++---- rust/tests/session_test.rs | 6 ++--- scripts/codegen/rust.ts | 4 +-- scripts/codegen/utils.ts | 44 +++++++++++---------------------- 5 files changed, 21 insertions(+), 46 deletions(-) diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index d3948c1959..18d4c7ed58 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -8484,7 +8484,7 @@ pub struct ModelSetReasoningEffortResult { pub reasoning_effort: String, } -/// Optional GitHub token, working directory, and cache controls used to resolve available models. +/// Optional GitHub token and working directory used to resolve available models. /// ///
/// @@ -8501,9 +8501,6 @@ pub struct ModelsListRequest { /// GitHub token for per-user model listing. When provided, resolves this token to determine the user's Copilot plan and available models instead of using the global auth. #[serde(skip_serializing_if = "Option::is_none")] pub git_hub_token: Option, - /// When true, bypasses cached model data and refreshes the available model list. - #[serde(skip_serializing_if = "Option::is_none")] - pub skip_cache: Option, } /// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. diff --git a/rust/tests/api_types_test.rs b/rust/tests/api_types_test.rs index c930228d96..c57272c2fc 100644 --- a/rust/tests/api_types_test.rs +++ b/rust/tests/api_types_test.rs @@ -106,19 +106,15 @@ fn permission_event_exposes_managed_approval_required() { } #[test] -fn models_list_request_serializes_repository_options() { +fn models_list_request_serializes_repository_cwd() { let request = ModelsListRequest { cwd: Some("/workspace/repository".to_string()), git_hub_token: None, - skip_cache: Some(true), }; assert_eq!( serde_json::to_value(request).unwrap(), - serde_json::json!({ - "cwd": "/workspace/repository", - "skipCache": true - }) + serde_json::json!({ "cwd": "/workspace/repository" }) ); assert_eq!( serde_json::to_value(ModelsListRequest::default()).unwrap(), diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 2ff6d742fe..bb74f4cae8 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -4045,7 +4045,7 @@ async fn rpc_namespace_client_models_list_dispatches_correctly() { } #[tokio::test] -async fn rpc_namespace_client_models_list_sends_repository_options() { +async fn rpc_namespace_client_models_list_sends_repository_cwd() { let (session, mut server) = create_session_pair().await; let session = Arc::new(session); @@ -4057,7 +4057,6 @@ async fn rpc_namespace_client_models_list_sends_repository_options() { .list_with_params(ModelsListRequest { cwd: Some("/workspace/repository".to_string()), git_hub_token: None, - skip_cache: Some(true), }) .await }); @@ -4067,8 +4066,7 @@ async fn rpc_namespace_client_models_list_sends_repository_options() { assert_eq!( request["params"], serde_json::json!({ - "cwd": "/workspace/repository", - "skipCache": true + "cwd": "/workspace/repository" }) ); server diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index fcf0826f85..1375af4eb1 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -17,8 +17,8 @@ import { fileURLToPath } from "url"; import { promisify } from "util"; import type { JSONSchema7, JSONSchema7Definition } from "json-schema"; import { + addCwdToModelsListRequest, addManagedApprovalRequiredToPermissionRequests, - addModelsListRequestOptions, type ApiSchema, type DefinitionCollections, EXCLUDED_EVENT_TYPES, @@ -2220,7 +2220,7 @@ async function generate(): Promise { ); const apiSchema = propagateInternalVisibility( postProcessSchema( - stripBooleanLiterals(addModelsListRequestOptions(apiRaw)) as JSONSchema7, + stripBooleanLiterals(addCwdToModelsListRequest(apiRaw)) as JSONSchema7, ), ) as unknown as ApiSchema; diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 61abf0649b..089065ed34 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -497,25 +497,16 @@ export function addManagedApprovalRequiredToPermissionRequests(schema: T): T { +export function addCwdToModelsListRequest(schema: T): T { const cloned = cloneSchemaForCodegen(schema); - const properties: Record = { - cwd: { - description: - "Working directory used to apply repository model policy. When omitted, model availability is account-global.", - type: ["string", "null"], - }, - skipCache: { - description: - "When true, bypasses cached model data and refreshes the available model list.", - type: ["boolean", "null"], - }, + const property: JSONSchema7 = { + description: + "Working directory used to apply repository model policy. When omitted, model availability is account-global.", + type: ["string", "null"], }; - for (const property of Object.values(properties)) { - (property as Record)["x-copilot-sdk-append-last"] = true; - } + (property as Record)["x-copilot-sdk-append-last"] = true; for (const definitions of [cloned.definitions, cloned.$defs]) { if (!definitions) continue; @@ -533,20 +524,13 @@ export function addModelsListRequestOptions(schema: T): T (candidate.type === "object" || candidate.properties !== undefined), ); if (!objectDefinition) continue; - - let patched = false; - for (const [name, property] of Object.entries(properties)) { - if (objectDefinition.properties?.[name]) continue; - objectDefinition.properties = { - ...objectDefinition.properties, - [name]: cloneSchemaForCodegen(property), - }; - patched = true; - } - if (patched) { - requestDefinition.description = - "Optional GitHub token, working directory, and cache controls used to resolve available models."; - } + if (objectDefinition.properties?.cwd) continue; + requestDefinition.description = + "Optional GitHub token and working directory used to resolve available models."; + objectDefinition.properties = { + ...objectDefinition.properties, + cwd: cloneSchemaForCodegen(property), + }; } return cloned;