fix(tools): attach Python SDK docstrings to their own methods

A description was emitted above the `async def`, where Python treats the
first string as the `Tools` class docstring and every later one as a dead
expression — leaving each method undocumented in the model's only source of
tool semantics. Emit it as the first statement of the method body instead.

Also names the known languages in the run_code flavor guard (the reachable
rejection, symmetric with the SDK_RENDERERS guard) and corrects three doc
claims: the code-runtime group README no longer calls the generated SDK
TypeScript, the base Code Mode note states its serial dispatch in past
tense, and the tools README points at the rationale the language-dispatch
note actually carries.
This commit is contained in:
Chinesezjc
2026-08-05 14:02:47 +08:00
parent 975350c7b8
commit 7a178951d6
16 changed files with 60 additions and 23 deletions

View File

@@ -138,7 +138,8 @@ function resolveFlavor(peekRuntime: () => CodeRuntime | undefined): RunCodeFlavo
// resolve an inherited Object.prototype member as a flavor.
const flavor = RUN_CODE_FLAVORS[runtime.language]
if (!Object.hasOwn(RUN_CODE_FLAVORS, runtime.language) || flavor === undefined) {
throw new Error(`dsh-tools: no run_code schema flavor registered for runtime language ${JSON.stringify(runtime.language)}`)
const known = Object.keys(RUN_CODE_FLAVORS).map(name => JSON.stringify(name)).join(', ')
throw new Error(`dsh-tools: no run_code schema flavor registered for runtime language ${JSON.stringify(runtime.language)} (known: ${known})`)
}
return flavor
}

View File

@@ -482,8 +482,18 @@ export function renderToolsSdkPy(schemas: ToolSdkSchema[]): string {
const argType = renderType(schema.parameters, `${camelCase(schema.name)}Args`, state)
const outputType = renderType(schema.output, `${camelCase(schema.name)}Output`, state)
if (IDENTIFIER.test(schema.name) && !RESERVED.has(schema.name) && !schema.name.startsWith('_')) {
members.push(...docLines(schema.description, 1))
members.push(`${pad(1)}async def ${schema.name}(self, args: ${argType}) -> ${outputType}: ...`)
// A docstring only documents its method when it is the FIRST statement
// of that method's body. Emitted before the `async def` it would instead
// become the `Tools` class docstring (for the first tool) or a dead
// expression (for every later one), leaving every method undocumented —
// and this SDK is the model's only description of what a tool does. A
// docstring is a complete body, so the `...` stub is only for the
// description-less case.
const doc = docLines(schema.description, 2)
members.push(doc.length > 0
? `${pad(1)}async def ${schema.name}(self, args: ${argType}) -> ${outputType}:`
: `${pad(1)}async def ${schema.name}(self, args: ${argType}) -> ${outputType}: ...`)
members.push(...doc)
statements += 1
} else {
// Not a legal attribute name — the model reaches it via ``tools[name]``.