docs(tools): align Code Mode docs with multi-language dispatch; py-types notes

Address ds-review-bot v5/v6 review round 3:
- Config.mode JSDoc and the regenerated config-catalog no longer claim Code
  Mode requires a TypeScript runtime; both now say a language with a
  registered SDK renderer.
- The active 2026-06-15-code-mode base note (both languages) follows shipped
  reality: the SDK renders the loaded runtime's language, dsh-tools accepts
  any language with a renderer and run_code flavor, and it cross-links the
  language-dispatch note.
- The language-dispatch note distinguishes the two Object.hasOwn guards'
  reachability and documents the peekRuntime no-runtime degrade vs the
  rejected silent fallback.
- SDK_RENDERERS comment: adding a language is two table entries, not one.
- py-types: document the deliberate PEP 586 deviation for float Literals;
  add oneOf-object-branch tests (named union classes and context-free
  degrade), keeping py-types.ts at 100% per-file coverage.
This commit is contained in:
Chinesezjc
2026-08-02 13:55:28 +08:00
parent 938ee4e2eb
commit 59affddfc5
10 changed files with 64 additions and 19 deletions

View File

@@ -217,6 +217,40 @@ describe('renderToolsSdkPy', () => {
expect(text.indexOf('class WorkflowArgs(TypedDict):')).toBeLessThan(text.indexOf('class Tools(Protocol):'))
})
it('renders a oneOf of object branches as a union of named TypedDicts declared before the parent', () => {
const tool: ToolSdkSchema = {
name: 'act',
description: 'Union output.',
parameters: { type: 'object', additionalProperties: false, properties: {} },
output: {
oneOf: [
{ type: 'object', additionalProperties: false, properties: { ok: { type: 'boolean' } }, required: ['ok'] },
{ type: 'object', additionalProperties: false, properties: { err: { type: 'string' } }, required: ['err'] },
],
},
}
const text = renderToolsSdkPy([tool])
// Each object branch becomes its own named class (`${base}Output1/2`),
// declared before the protocol references the union.
expect(text).toContain('class ActOutput1(TypedDict):')
expect(text).toContain('class ActOutput2(TypedDict):')
expect(text).toContain('-> ActOutput1 | ActOutput2')
expect(text.indexOf('class ActOutput1(TypedDict):')).toBeLessThan(text.indexOf('class Tools(Protocol):'))
expect(text.indexOf('class ActOutput2(TypedDict):')).toBeLessThan(text.indexOf('class Tools(Protocol):'))
})
it('degrades a context-free oneOf of object branches to a union of dict[str, Any]', () => {
// jsonSchemaToPy has no naming context, so each object branch degrades
// rather than declaring a class.
const type = jsonSchemaToPy({
oneOf: [
{ type: 'object', additionalProperties: false, properties: { ok: { type: 'boolean' } }, required: ['ok'] },
{ type: 'string' },
],
})
expect(type).toBe('dict[str, Any] | str')
})
it('suffixes a counter when two tools CamelCase to the same class base', () => {
const a: ToolSdkSchema = {
name: 'my-tool',