refactor(tools): pin the two language tables to one union, and name python at the seam

`SDK_RENDERERS` and `RUN_CODE_FLAVORS` had to stay in step by review alone:
the `Object.hasOwn` guards catch drift only once a runtime reporting the
half-added language exists, which is the one case that cannot arise. Both
tables are now `satisfies`-checked against a shared `CodeSdkLanguage` union,
so a missing or extra entry fails `typecheck`. The declared `Record<string, …>`
type stays, since `CodeRuntime.language` is an unconstrained `string`.

The code-runtime seam's own README row and `CodeRuntime.language` JSDoc still
named `'typescript'` as the sole well-known value; both now name `'python'`
too and say only `'typescript'` has a published backend.
This commit is contained in:
Chinesezjc
2026-08-05 23:25:42 +08:00
parent 99218ba41d
commit e14bcfb08a
9 changed files with 29 additions and 13 deletions

View File

@@ -100,11 +100,22 @@ const PYTHON_FLAVOR: RunCodeFlavor = {
codeDescription: 'The program: the body of an async Python function.',
}
/** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per `SDK_RENDERERS` language. */
/**
* The languages Code Mode ships a presentation for. Both per-language tables —
* {@link RUN_CODE_FLAVORS} here and `SDK_RENDERERS` in {@link ./index.ts} — are
* checked against this union with `satisfies`, so a language added to one and
* not the other fails `typecheck` instead of waiting for a runtime that reports
* it. The tables stay declared `Record<string, …>` because `CodeRuntime.language`
* is an unconstrained `string`: this union pins what the harness ships, while the
* `Object.hasOwn` guards reject what a mounted runtime may report.
*/
export type CodeSdkLanguage = 'typescript' | 'python'
/** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link CodeSdkLanguage}. */
const RUN_CODE_FLAVORS: Record<string, RunCodeFlavor> = {
typescript: TYPESCRIPT_FLAVOR,
python: PYTHON_FLAVOR,
}
} satisfies Record<CodeSdkLanguage, RunCodeFlavor>
/**
* The `description` parameter's model-facing description: language-independent

View File

@@ -22,6 +22,7 @@ import type { ToolCallView, ToolResultView } from './presentation.ts'
import { assertSupportedJsonSchema, validateJsonSchemaValue } from './json-schema.ts'
import type { JsonSchemaNode } from './json-schema.ts'
import { createRunCodeTool, RUN_CODE_NAME, SDK_SECTION_ORDER } from './code-mode.ts'
import type { CodeSdkLanguage } from './code-mode.ts'
import { renderToolsSdk } from './ts-types.ts'
import type { ToolSdkSchema } from './ts-types.ts'
import { renderToolsSdkPy } from './py-types.ts'
@@ -33,12 +34,15 @@ import { renderToolsSdkPy } from './py-types.ts'
* fails the assembly loudly (same idiom as `toolOrder` violations). Adding a
* new backend language is two table entries — an entry here and a
* `RUN_CODE_FLAVORS` entry in `code-mode.ts` for its `run_code` schema strings
* — plus the renderer function this table points at.
* — plus the renderer function this table points at. The `satisfies` clause
* pins this table's key set to {@link CodeSdkLanguage}, the same union the
* flavor table is checked against, so adding one entry without the other is a
* typecheck failure.
*/
const SDK_RENDERERS: Record<string, (schemas: ToolSdkSchema[]) => string> = {
typescript: renderToolsSdk,
python: renderToolsSdkPy,
}
} satisfies Record<CodeSdkLanguage, (schemas: ToolSdkSchema[]) => string>
export {
defineTool,