feat(tools): render a Python SDK and dispatch Code Mode by runtime language

Code Mode generated only a TypeScript SDK and rejected any runtime whose
language was not "typescript". Add py-types.ts (jsonSchemaToPy /
renderToolsSdkPy) and select the SDK-section renderer and the run_code
schema flavor by ctx.codeRuntime.language through two parallel tables
(SDK_RENDERERS, RUN_CODE_FLAVORS), read with Object.hasOwn and failing
loud on a language with no renderer. The tool layer depends only on the
code-runtime seam's language field, so it lands independently of the
Python protocol and backend.
This commit is contained in:
Chinesezjc
2026-07-31 18:16:15 +08:00
parent d6853a667e
commit 4fdfa89d51
11 changed files with 1176 additions and 29 deletions
+31 -5
View File
@@ -24,6 +24,19 @@ import type { JsonSchemaNode } from './json-schema.ts'
import { createRunCodeTool, RUN_CODE_NAME, SDK_SECTION_ORDER } from './code-mode.ts'
import { renderToolsSdk } from './ts-types.ts'
import type { ToolSdkSchema } from './ts-types.ts'
import { renderToolsSdkPy } from './py-types.ts'
/**
* Language → SDK-section renderer. The registry looks up the loaded
* `ctx.codeRuntime.language` in this table when assembling the `tools:sdk`
* section under a non-native mode; a runtime whose language is not a key
* fails the assembly loudly (same idiom as `toolOrder` violations). Adding a
* new backend language is a table entry plus its renderer, nothing else.
*/
const SDK_RENDERERS: Record<string, (schemas: ToolSdkSchema[]) => string> = {
typescript: renderToolsSdk,
python: renderToolsSdkPy,
}
export {
defineTool,
@@ -65,6 +78,7 @@ export type { JsonValue } from '@deepseek-ai/dsh-session'
export { CodeRunFailedError, RUN_CODE_NAME } from './code-mode.ts'
export { jsonSchemaToTs, renderToolsSdk } from './ts-types.ts'
export { jsonSchemaToPy, renderToolsSdkPy } from './py-types.ts'
export { defineContentToolFixture, type ContentToolFixtureOptions } from './testing.ts'
// The render-intent vocabulary a tool declares via `presentCall`/`presentResult`
@@ -762,10 +776,21 @@ export class ToolRegistry extends Service {
ctx.systemPrompt.section({
name: 'tools:sdk',
order: SDK_SECTION_ORDER,
// Regenerate from the calling scope's visible tools in stable order.
// Regenerate from the calling scope's visible tools in stable order,
// picking the renderer that matches the loaded runtime's language.
// `requireCodeRuntime` already validated the language is in the
// table, so the fallback here is defense-in-depth against a caller
// that bypassed the guard (impossible under normal composition).
text: (context) => {
this.requireCodeRuntime()
return renderToolsSdk(this.sdkSchemas(context.scope))
const runtime = this.requireCodeRuntime()
// Own-property read: a language like `toString`/`constructor` would
// otherwise resolve an inherited Object.prototype member as a renderer.
const render = SDK_RENDERERS[runtime.language]
/* v8 ignore next 3 -- requireCodeRuntime rejects an unknown language before this ever runs. */
if (!Object.hasOwn(SDK_RENDERERS, runtime.language) || render === undefined) {
throw new Error(`dsh-tools: no SDK renderer registered for runtime language "${runtime.language}"`)
}
return render(this.sdkSchemas(context.scope))
},
})
}
@@ -804,8 +829,9 @@ export class ToolRegistry extends Service {
if (!runtime) {
throw new Error(`dsh-tools: mode "${this.mode}" requires a code runtime — load a ctx.codeRuntime implementation (e.g. @deepseek-ai/dsh-code-runtime-worker) or set tools mode to "native"`)
}
if (runtime.language !== 'typescript') {
throw new Error(`dsh-tools: mode "${this.mode}" generates a TypeScript SDK, but the loaded code runtime's language is "${runtime.language}"`)
if (!Object.hasOwn(SDK_RENDERERS, runtime.language)) {
const known = Object.keys(SDK_RENDERERS).map(name => JSON.stringify(name)).join(', ')
throw new Error(`dsh-tools: no SDK renderer registered for runtime language ${JSON.stringify(runtime.language)} (known: ${known})`)
}
return runtime
}