Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions rust/src/generated/api_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30406,6 +30406,8 @@ pub enum ModelPickerCategory {
/// Powerful model category optimized for complex tasks.
#[serde(rename = "powerful")]
Powerful,
#[serde(rename = "experimental")]
Experimental,
/// Unknown variant for forward compatibility.
#[default]
#[serde(other)]
Expand Down
73 changes: 72 additions & 1 deletion rust/tests/session_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use github_copilot_sdk::handler::{
};
use github_copilot_sdk::rpc::{
CanvasProviderInvokeActionRequest, CanvasProviderOpenRequest, CanvasProviderOpenResult,
OpenCanvasInstance,
ModelPickerCategory, ModelsListResult, OpenCanvasInstance,
};
use github_copilot_sdk::session_events::{
ManagedSettingsResolvedSource, McpOauthRequiredData, ReasoningSummary, SessionLimitsConfig,
Expand Down Expand Up @@ -4106,6 +4106,77 @@ async fn rpc_namespace_client_models_list_dispatches_correctly() {
assert!(result.models.is_empty());
}

#[tokio::test]
async fn raw_client_call_supports_supplemental_model_compatibility() {
let (client, mut server_read, mut server_write) = make_client();

let supplemental_call = tokio::spawn({
let client = client.clone();
async move {
let value = client.call("models.listSupplemental", None).await.unwrap();
serde_json::from_value::<ModelsListResult>(value).unwrap()
}
});

let request = read_framed(&mut server_read).await;
assert_eq!(request["method"], "models.listSupplemental");
let response = serde_json::json!({
"jsonrpc": "2.0",
"id": request["id"],
"result": {
"models": [{
"id": "experimental-model",
"name": "Experimental Model",
"capabilities": {},
"modelPickerCategory": "experimental"
}, {
"id": "future-model",
"name": "Future Model",
"capabilities": {},
"modelPickerCategory": "future-category"
}, {
"id": "legacy-model",
"name": "Legacy Model",
"capabilities": {}
}]
}
});
write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await;

let result = timeout(TIMEOUT, supplemental_call).await.unwrap().unwrap();
assert_eq!(
result.models[0].model_picker_category,
Some(ModelPickerCategory::Experimental)
);
assert_eq!(
result.models[1].model_picker_category,
Some(ModelPickerCategory::Unknown)
);
assert_eq!(result.models[2].model_picker_category, None);

let unavailable_call = tokio::spawn(async move {
client
.call("models.listSupplemental", None)
.await
.unwrap_err()
});

let request = read_framed(&mut server_read).await;
let response = serde_json::json!({
"jsonrpc": "2.0",
"id": request["id"],
"error": {
"code": -32601,
"message": "Method not found"
}
});
write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await;

let error = timeout(TIMEOUT, unavailable_call).await.unwrap().unwrap();
assert_eq!(error.rpc_code(), Some(-32601));
assert!(!error.is_transport_failure());
}

#[tokio::test]
async fn client_stop_sends_session_destroy_for_each_active_session() {
// One client, two registered sessions. Client::stop must send
Expand Down
21 changes: 20 additions & 1 deletion scripts/codegen/rust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,25 @@ const STRING_NEWTYPE_OVERRIDES: Record<string, string> = {
requestId: "RequestId",
};

/**
* Keep the Rust model metadata lossless while supplemental models roll out
* ahead of the next published CLI schema.
*/
function addExperimentalModelPickerCategory(schema: ApiSchema): ApiSchema {
const definition = collectDefinitions(
schema as unknown as Record<string, unknown>,
).ModelPickerCategory;
if (!definition || typeof definition !== "object" || !Array.isArray(definition.enum)) {
throw new Error("ModelPickerCategory enum is missing from api.schema.json");
}

if (!definition.enum.includes("experimental")) {
definition.enum.push("experimental");
}

return schema;
}

// ── Naming helpers ──────────────────────────────────────────────────────────

function toPascalCase(s: string): string {
Expand Down Expand Up @@ -2235,7 +2254,7 @@ async function generate(): Promise<void> {
);
const apiSchema = propagateInternalVisibility(
postProcessSchema(
stripBooleanLiterals(apiRaw) as JSONSchema7,
stripBooleanLiterals(addExperimentalModelPickerCategory(apiRaw)) as JSONSchema7,
),
) as unknown as ApiSchema;

Expand Down
Loading