Skip to content

Commit cd71ba1

Browse files
Workaround: patch model capabilities in Python models.list deserializer
TODO: Remove once the runtime schema correctly marks limits/supports and max_context_window_tokens as optional in ModelCapabilities. The generated Python deserializer (from quicktype) uses strict assert- based type checks that crash on null/missing fields. Go, C#, and TS deserializers silently zero-default these. This adds a targeted patch to models.list that supplies missing defaults before deserialization. Files to update when removing this workaround: - scripts/codegen/python.ts (remove _patch_model_capabilities helper and the string replacement logic) - python/copilot/generated/rpc.py (will be regenerated automatically) Co-authored-by: Copilot <[email protected]>
1 parent ca0f563 commit cd71ba1

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

python/copilot/generated/rpc.py

Lines changed: 22 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/codegen/python.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,27 @@ def _timeout_kwargs(timeout: float | None) -> dict:
400400
return {"timeout": timeout}
401401
return {}
402402
403+
def _patch_model_capabilities(data: dict) -> dict:
404+
"""Ensure model capabilities have required fields.
405+
406+
TODO: Remove once the runtime schema correctly marks these fields as optional.
407+
Some models (e.g. embedding models) may omit 'limits' or 'supports' in their
408+
capabilities, or omit 'max_context_window_tokens' within limits. The generated
409+
deserializer requires these fields, so we supply defaults here.
410+
"""
411+
for model in data.get("models", []):
412+
caps = model.get("capabilities")
413+
if caps is None:
414+
model["capabilities"] = {"supports": {}, "limits": {"max_context_window_tokens": 0}}
415+
continue
416+
if "supports" not in caps:
417+
caps["supports"] = {}
418+
if "limits" not in caps:
419+
caps["limits"] = {"max_context_window_tokens": 0}
420+
elif "max_context_window_tokens" not in caps["limits"]:
421+
caps["limits"]["max_context_window_tokens"] = 0
422+
return data
423+
403424
`);
404425

405426
// Emit RPC wrapper classes
@@ -413,7 +434,19 @@ def _timeout_kwargs(timeout: float | None) -> dict:
413434
emitClientSessionApiRegistration(lines, schema.clientSession, resolveType);
414435
}
415436

416-
const outPath = await writeGeneratedFile("python/copilot/generated/rpc.py", lines.join("\n"));
437+
// Patch models.list to normalize capabilities before deserialization
438+
let finalCode = lines.join("\n");
439+
finalCode = finalCode.replace(
440+
`ModelList.from_dict(await self._client.request("models.list"`,
441+
`ModelList.from_dict(_patch_model_capabilities(await self._client.request("models.list"`,
442+
);
443+
// Close the extra paren opened by _patch_model_capabilities(
444+
finalCode = finalCode.replace(
445+
/(_patch_model_capabilities\(await self\._client\.request\("models\.list",\s*\{[^)]*\)[^)]*\))/,
446+
"$1)",
447+
);
448+
449+
const outPath = await writeGeneratedFile("python/copilot/generated/rpc.py", finalCode);
417450
console.log(` ✓ ${outPath}`);
418451
}
419452

0 commit comments

Comments
 (0)