@dsh-cc/tools 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +201 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +209 -0
  4. package/README.zh.md +200 -0
  5. package/lib/abort-utils.d.ts +80 -0
  6. package/lib/abort-utils.d.ts.map +1 -0
  7. package/lib/abort-utils.js +186 -0
  8. package/lib/abort-utils.js.map +1 -0
  9. package/lib/cc-names.d.ts +100 -0
  10. package/lib/cc-names.d.ts.map +1 -0
  11. package/lib/cc-names.js +197 -0
  12. package/lib/cc-names.js.map +1 -0
  13. package/lib/code-mode.d.ts +46 -0
  14. package/lib/code-mode.d.ts.map +1 -0
  15. package/lib/code-mode.js +380 -0
  16. package/lib/code-mode.js.map +1 -0
  17. package/lib/define-tool.d.ts +103 -0
  18. package/lib/define-tool.d.ts.map +1 -0
  19. package/lib/define-tool.js +354 -0
  20. package/lib/define-tool.js.map +1 -0
  21. package/lib/index.d.ts +348 -0
  22. package/lib/index.d.ts.map +1 -0
  23. package/lib/index.js +289 -0
  24. package/lib/index.js.map +1 -0
  25. package/lib/invariant.d.ts +13 -0
  26. package/lib/invariant.d.ts.map +1 -0
  27. package/lib/invariant.js +123 -0
  28. package/lib/invariant.js.map +1 -0
  29. package/lib/json-render.d.ts +18 -0
  30. package/lib/json-render.d.ts.map +1 -0
  31. package/lib/json-render.js +111 -0
  32. package/lib/json-render.js.map +1 -0
  33. package/lib/json-schema-value.d.ts +18 -0
  34. package/lib/json-schema-value.d.ts.map +1 -0
  35. package/lib/json-schema-value.js +238 -0
  36. package/lib/json-schema-value.js.map +1 -0
  37. package/lib/json-schema.d.ts +101 -0
  38. package/lib/json-schema.d.ts.map +1 -0
  39. package/lib/json-schema.js +351 -0
  40. package/lib/json-schema.js.map +1 -0
  41. package/lib/presentation.d.ts +367 -0
  42. package/lib/presentation.d.ts.map +1 -0
  43. package/lib/presentation.js +8 -0
  44. package/lib/presentation.js.map +1 -0
  45. package/lib/py-names.d.ts +163 -0
  46. package/lib/py-names.d.ts.map +1 -0
  47. package/lib/py-names.js +236 -0
  48. package/lib/py-names.js.map +1 -0
  49. package/lib/py-render.d.ts +23 -0
  50. package/lib/py-render.d.ts.map +1 -0
  51. package/lib/py-render.js +434 -0
  52. package/lib/py-render.js.map +1 -0
  53. package/lib/py-sdk-doc.d.ts +29 -0
  54. package/lib/py-sdk-doc.d.ts.map +1 -0
  55. package/lib/py-sdk-doc.js +114 -0
  56. package/lib/py-sdk-doc.js.map +1 -0
  57. package/lib/py-types.d.ts +34 -0
  58. package/lib/py-types.d.ts.map +1 -0
  59. package/lib/py-types.js +40 -0
  60. package/lib/py-types.js.map +1 -0
  61. package/lib/run-code-defs.d.ts +77 -0
  62. package/lib/run-code-defs.d.ts.map +1 -0
  63. package/lib/run-code-defs.js +98 -0
  64. package/lib/run-code-defs.js.map +1 -0
  65. package/lib/runtime-code.d.ts +65 -0
  66. package/lib/runtime-code.d.ts.map +1 -0
  67. package/lib/runtime-code.js +129 -0
  68. package/lib/runtime-code.js.map +1 -0
  69. package/lib/runtime-core.d.ts +130 -0
  70. package/lib/runtime-core.d.ts.map +1 -0
  71. package/lib/runtime-core.js +33 -0
  72. package/lib/runtime-core.js.map +1 -0
  73. package/lib/runtime-execute.d.ts +82 -0
  74. package/lib/runtime-execute.d.ts.map +1 -0
  75. package/lib/runtime-execute.js +333 -0
  76. package/lib/runtime-execute.js.map +1 -0
  77. package/lib/runtime-registry.d.ts +131 -0
  78. package/lib/runtime-registry.d.ts.map +1 -0
  79. package/lib/runtime-registry.js +269 -0
  80. package/lib/runtime-registry.js.map +1 -0
  81. package/lib/runtime-results.d.ts +32 -0
  82. package/lib/runtime-results.d.ts.map +1 -0
  83. package/lib/runtime-results.js +163 -0
  84. package/lib/runtime-results.js.map +1 -0
  85. package/lib/runtime-schemas.d.ts +112 -0
  86. package/lib/runtime-schemas.d.ts.map +1 -0
  87. package/lib/runtime-schemas.js +222 -0
  88. package/lib/runtime-schemas.js.map +1 -0
  89. package/lib/scheduler.d.ts +22 -0
  90. package/lib/scheduler.d.ts.map +1 -0
  91. package/lib/scheduler.js +21 -0
  92. package/lib/scheduler.js.map +1 -0
  93. package/lib/schema-spec.d.ts +154 -0
  94. package/lib/schema-spec.d.ts.map +1 -0
  95. package/lib/schema-spec.js +9 -0
  96. package/lib/schema-spec.js.map +1 -0
  97. package/lib/schema.d.ts +5 -0
  98. package/lib/schema.d.ts.map +1 -0
  99. package/lib/schema.js +3 -0
  100. package/lib/schema.js.map +1 -0
  101. package/lib/testing.d.ts +25 -0
  102. package/lib/testing.d.ts.map +1 -0
  103. package/lib/testing.js +25 -0
  104. package/lib/testing.js.map +1 -0
  105. package/lib/tool-layer.d.ts +49 -0
  106. package/lib/tool-layer.d.ts.map +1 -0
  107. package/lib/tool-layer.js +57 -0
  108. package/lib/tool-layer.js.map +1 -0
  109. package/lib/tool-types.d.ts +385 -0
  110. package/lib/tool-types.d.ts.map +1 -0
  111. package/lib/tool-types.js +11 -0
  112. package/lib/tool-types.js.map +1 -0
  113. package/lib/ts-types.d.ts +37 -0
  114. package/lib/ts-types.d.ts.map +1 -0
  115. package/lib/ts-types.js +265 -0
  116. package/lib/ts-types.js.map +1 -0
  117. package/lib/types.d.ts +55 -0
  118. package/lib/types.d.ts.map +1 -0
  119. package/lib/types.js +7 -0
  120. package/lib/types.js.map +1 -0
  121. package/package.json +65 -0
@@ -0,0 +1,434 @@
1
+ /**
2
+ * The Python type-expression renderer: {@link renderType}, the explicit-stack
3
+ * walker that maps one validated JSON-Schema node to a Python type expression
4
+ * while threading a {@link RenderState} to collect the `TypedDict` class
5
+ * declarations and `typing` symbols a full render needs. Split out of
6
+ * `py-types.ts` for the line budget; `jsonSchemaToPy` and `renderToolsSdkPy`
7
+ * remain the public entries, re-exported from `py-types.ts`.
8
+ * @module @dsh-cc/tools/src/py-render
9
+ */
10
+ import { assertSupportedJsonSchema } from "./json-schema.js";
11
+ import { describe, camelCase, isBareIdentifier, pad, RESERVED } from "./py-names.js";
12
+ /** Class-name base cap keeping each emitted name — and total text — linear in schema depth. */
13
+ const MAX_CLASS_NAME_BASE = 120;
14
+ /**
15
+ * Deepest `list[…]` nesting emitted into one annotation before the item type
16
+ * degrades to `Any`. CPython's tokenizer rejects a logical line holding more
17
+ * than 200 simultaneously-open brackets (`MAXLEVEL`, `SyntaxError: too many
18
+ * nested parentheses`), so an array chain deeper than that would render an SDK
19
+ * block that is not valid Python at all — the same failure the docstring
20
+ * escaping in the SDK doc renderer exists to prevent. 180 leaves headroom for
21
+ * the few brackets an annotation can add around the chain, all of which count
22
+ * toward the same limit. Per emission site, counting brackets open at the
23
+ * chain's innermost point:
24
+ *
25
+ * - Return annotation, `async def f(self, args: X) -> chain:` — 180 `list[`
26
+ * plus an innermost `Literal[`. The parameter list's `(` closed at the `)`
27
+ * before the `->`, so it is NOT open here: 181.
28
+ * - TypedDict field, `field: NotRequired[chain]` — a class-body line with no
29
+ * other open bracket, and its children start at `listDepth: 1` to reserve
30
+ * the `NotRequired[`, so 179 `list[` plus `Literal[`: 181. Required fields
31
+ * share that start for uniformity, spending one level of representable depth
32
+ * on a bracket they never emit.
33
+ * - Argument annotation, `async def f(self, args: chain) -> Y:` — the `(` IS
34
+ * still open around it: 180 `list[` plus `Literal[` plus the paren, 182, the
35
+ * worst case. Reachable only through a raw `register()` whose `parameters`
36
+ * is an array reached from the root through `oneOf` arms alone — the root
37
+ * array itself, or one nested under any depth of unions, since an arm
38
+ * inherits the enclosing depth unchanged (`A | B` opens no bracket). An
39
+ * object ancestor takes it out of this case: its fields restart the chain at
40
+ * the 181 site. `defineTool` compiles an object root, so the annotation is a
41
+ * bare TypedDict class name or a one-bracket `dict[str, Any]` when that
42
+ * object degrades — never a chain.
43
+ *
44
+ * A CPython grammar limit, not a deployment choice, so it is fixed rather than
45
+ * configurable. The sibling `ts-types` renderer needs no counterpart: nothing
46
+ * in the TypeScript grammar bounds nesting, and its SDK block is never type-
47
+ * checked. Only bracket nesting counts — a `oneOf` renders as a flat `A | B`
48
+ * chain and nested objects render as separate `class` statements, so neither
49
+ * accumulates open brackets at any depth. The invariant this cap serves is
50
+ * grammatical validity; see the `oneOf` arm in {@link renderType} for the one
51
+ * interpreter limit deliberately left uncapped.
52
+ */
53
+ const MAX_LIST_NESTING = 180;
54
+ /**
55
+ * Cap a class-name base at {@link MAX_CLASS_NAME_BASE} (see the callers for
56
+ * why capping keeps the render linear). `slice` counts UTF-16 code units, so
57
+ * an astral character straddling the boundary would be cut in half and leave a
58
+ * lone surrogate — not an identifier character, and not even well-formed text;
59
+ * drop it rather than emit it.
60
+ */
61
+ function capClassNameBase(base) {
62
+ if (base.length <= MAX_CLASS_NAME_BASE)
63
+ return base;
64
+ const capped = base.slice(0, MAX_CLASS_NAME_BASE);
65
+ return /[\uD800-\uDBFF]$/.test(capped) ? capped.slice(0, -1) : capped;
66
+ }
67
+ /**
68
+ * Reserve a unique class name from a base, suffixing `2`, `3`, … on collision.
69
+ * The base is capped at {@link MAX_CLASS_NAME_BASE} first: child class names
70
+ * derive from their parent's allocated name (`ParentChild`), so an unbounded
71
+ * schema of single-field objects would otherwise grow each name by one field
72
+ * per level and the sum of all names to Θ(depth²). Capping the base keeps each
73
+ * name — and the total emitted text — linear in depth. Collisions resume from
74
+ * the per-base counter in `state.nextClassCounter` rather than rescanning from
75
+ * `2`, so a deep chain sharing one capped base stays O(1) per allocation
76
+ * (amortized) instead of Θ(depth²) in time.
77
+ */
78
+ function allocateClassName(base, state) {
79
+ const capped = capClassNameBase(base);
80
+ let name = capped;
81
+ if (state.usedClassNames.has(name)) {
82
+ let n = state.nextClassCounter.get(capped) ?? 2;
83
+ while (state.usedClassNames.has(`${capped}${n}`))
84
+ n++;
85
+ name = `${capped}${n}`;
86
+ state.nextClassCounter.set(capped, n + 1);
87
+ }
88
+ state.usedClassNames.add(name);
89
+ return name;
90
+ }
91
+ /**
92
+ * Append a child-name segment to a parent class-name base, capping the result
93
+ * at {@link MAX_CLASS_NAME_BASE}. Capping AT PROPAGATION (not only inside
94
+ * {@link allocateClassName}) keeps each level O(1): a deep `oneOf`- or
95
+ * object-chain would otherwise carry an ever-growing ConsString down the tree
96
+ * and re-materialize it (via `.length`/`.slice`) at every level — Θ(depth²).
97
+ * The bounded base plus the collision counter still yields unique names.
98
+ *
99
+ * The join is NFKC-normalized because both sides are separately normalized yet
100
+ * their concatenation need not be: a base ending in a Hangul L jamo or LV
101
+ * syllable composes with a following V or T jamo head (`가` + `ᆨ` gives `각`),
102
+ * so the emitted class name would differ from the symbol CPython compiles, and
103
+ * two byte-distinct names could fold onto one — `usedClassNames` dedupes by the
104
+ * raw bytes, so the collision counter would not see it. Normalizing costs
105
+ * O(cap + segment) per level, the same order as the `slice` it feeds. The other
106
+ * two join points need no counterpart: `Args`/`Output` start with `A`/`O` and
107
+ * {@link allocateClassName}'s suffix is digits, none of which compose backwards.
108
+ */
109
+ function childClassName(base, segment) {
110
+ return capClassNameBase(`${base}${segment}`.normalize('NFKC'));
111
+ }
112
+ /**
113
+ * Render one validated scalar as Python literal text (`True`/`False`,
114
+ * JSON-quoted strings, bare numbers). `null` cannot reach here: the `null`
115
+ * type renders directly as `None`, and the unified validator rejects a null
116
+ * `const`/`enum` entry on every other scalar type.
117
+ *
118
+ * A beyond-safe-range integral number takes `BigInt` digits rather than
119
+ * `String`: Python integers are arbitrary-precision, so the emitted digits ARE
120
+ * the value the model programs against, and `String` can give a different
121
+ * integer than the double holds (`2 ** 60` prints the rounded `...847000`, not
122
+ * the exact `...846976`) or no integer literal at all (`1e21` prints `1e+21`).
123
+ * `String`'s rounding is not a bug in it: `Number::toString` emits the shortest
124
+ * decimal string that re-reads to the same double, then pads to the exponent
125
+ * with zeros (1 significant digit for `1e20`, 16 for `2 ** 60`) — and when the
126
+ * shortest string is shorter than the double's exact value, those padded digits
127
+ * name an integer no double holds. Passing one back would have to cross the
128
+ * argument boundary as a JSON number — a double again — so the SDK would
129
+ * document a value no program can pass. `BigInt` needs no case split: where
130
+ * `String` is already exact (`2 ** 53`, `1e20`) the two agree byte for byte,
131
+ * and where it is not, `BigInt` is the exact one. The TS flavor needs no
132
+ * counterpart at all: its literal is re-read by a JS parser back into the same
133
+ * double.
134
+ *
135
+ * `JSON.stringify` is also what keeps this path's output parseable, and it is
136
+ * the only thing that does. It covers both classes of hazard: the two kinds of
137
+ * code point CPython refuses anywhere in source — NUL among the C0 controls,
138
+ * and the whole D800–DFFF unpaired-surrogate block, escaped under ES2019
139
+ * well-formed stringification, which the engines range guarantees — and the
140
+ * ones that break this line in particular, a bare `"` closing the literal
141
+ * early, a trailing odd backslash eating the closing quote, and a bare LF/CR
142
+ * ending it before its terminator. The `description` path carries
143
+ * the unprintable-character and lone-surrogate rules because nothing quotes it,
144
+ * and folds newlines in {@link describe}.
145
+ *
146
+ * That leans on a coincidence worth naming: every escape `JSON.stringify` can
147
+ * emit (`\"`, `\\`, `\b`, `\f`, `\n`, `\r`, `\t`, `\uXXXX`) is also a Python
148
+ * escape denoting the same character, so the emitted `Literal[...]` both
149
+ * parses and decodes back to the value the schema declared. DEL, the C1
150
+ * controls (NEL among them), and LS/PS (U+2028/U+2029) do reach it raw —
151
+ * legal but invisible, byte-for-byte as in the TS flavor; escaping them is a
152
+ * both-flavors change. Those last three are legal here for the reason
153
+ * the unprintable rule records: they are `str.splitlines()` boundaries, not
154
+ * tokenizer line terminators. The subscript tool-name comment quotes its name
155
+ * through its own call to the same `JSON.stringify`, never through this
156
+ * function, and inherits both halves — escapes and pass-throughs alike.
157
+ */
158
+ function pyScalar(value) {
159
+ if (value === true)
160
+ return 'True';
161
+ if (value === false)
162
+ return 'False';
163
+ if (typeof value === 'string')
164
+ return JSON.stringify(value);
165
+ if (typeof value === 'number' && Number.isInteger(value) && !Number.isSafeInteger(value)) {
166
+ return BigInt(value).toString();
167
+ }
168
+ return String(value);
169
+ }
170
+ /**
171
+ * Render a validated scalar `const`/`enum` as `Literal[...]`, falling back to
172
+ * the broad type. Deliberately deviates from PEP 586, which restricts `Literal`
173
+ * parameters to int/bool/str/bytes/enum/None: a non-integral number
174
+ * `const`/`enum` emits a float literal (`Literal[1.5]`) a strict checker would
175
+ * reject. An integral one does not deviate — {@link pyScalar} emits int digits,
176
+ * including for the beyond-safe-range values it widens through `BigInt`, and
177
+ * PEP 586 admits int parameters. Harmless either way — the stub is advisory
178
+ * prompt text, only required to parse — and keeping the exact value
179
+ * communicates the constraint to the model.
180
+ */
181
+ function renderConstrainedScalar(node, broad, state) {
182
+ if (node.const !== undefined) {
183
+ state.typing.add('Literal');
184
+ return `Literal[${pyScalar(node.const)}]`;
185
+ }
186
+ if (node.enum !== undefined) {
187
+ state.typing.add('Literal');
188
+ return `Literal[${node.enum.map(pyScalar).join(', ')}]`;
189
+ }
190
+ return broad;
191
+ }
192
+ /**
193
+ * Map one JSON-Schema node to a Python type expression, threading `state` to
194
+ * collect the `TypedDict` declarations and `typing` symbols a full render
195
+ * needs. `className` is the name to give an object node with properties (and
196
+ * the prefix for its nested objects). Handles every unified schema construct —
197
+ * `oneOf` (→ `X | Y`), `const`/`enum` (→ `Literal[...]`), `integer` (→ `int`),
198
+ * `null` (→ `None`) — and degrades an unsupported or malformed schema to `Any`
199
+ * without throwing, the same trusted-after-validation stance as the sibling
200
+ * {@link ./ts-types.ts | ts-types} renderer. `jsonSchemaToPy` is the
201
+ * context-free entry point; this is the collecting core.
202
+ */
203
+ export function renderType(schema, className, state) {
204
+ const newFrame = (schema, className, listDepth) => ({ schema, className, phase: 'start', listDepth, children: [], childIndex: 0, childTypes: [], entries: [] });
205
+ try {
206
+ // Validate the WHOLE tree once, then trust it — the same contract the
207
+ // sibling ts-types renderer follows at a typed same-process boundary. Every
208
+ // node past this point is a validated JSON-schema node, so the walk reads
209
+ // its fields without re-checking. An unsupported or malformed schema throws
210
+ // here (before anything is emitted) and degrades to `Any`, the Python
211
+ // counterpart of the TS flavor's `unknown`.
212
+ assertSupportedJsonSchema(schema);
213
+ const frames = [newFrame(schema, className, 0)];
214
+ let result;
215
+ /* jscpd:ignore-start -- the explicit-stack walk skeleton deliberately parallels
216
+ ts-types.ts's renderSupportedSchema; the two sibling renderers keep symmetric shapes. */
217
+ const finish = (type) => {
218
+ frames.pop();
219
+ const parent = frames.at(-1);
220
+ if (parent === undefined)
221
+ result = type;
222
+ else
223
+ parent.childTypes.push(type);
224
+ };
225
+ while (frames.length > 0) {
226
+ const frame = frames.at(-1);
227
+ /* v8 ignore next -- the loop condition guarantees a current frame. */
228
+ if (frame === undefined)
229
+ break;
230
+ if (frame.phase === 'children') {
231
+ if (frame.childIndex < frame.children.length) {
232
+ const child = frame.children[frame.childIndex];
233
+ /* v8 ignore next -- childIndex is bounded by children.length. */
234
+ if (child === undefined)
235
+ throw new Error('missing python render child');
236
+ frame.childIndex++;
237
+ frames.push(newFrame(child.schema, child.className, child.listDepth));
238
+ continue;
239
+ }
240
+ if (frame.kind === 'oneOf') {
241
+ // Concatenate incrementally (template literal, not `Array.join`): V8
242
+ // builds a lazy ConsString, so a deep oneOf chain materializes once
243
+ // at the root instead of re-materializing the accumulated string at
244
+ // every level (which `join` would, making it Θ(depth²)). This matches
245
+ // the array arm's template-literal laziness and ts-types' composable-
246
+ // document approach — the whole walk stays linear in schema depth.
247
+ let union = '';
248
+ for (const [index, childType] of frame.childTypes.entries()) {
249
+ union = index === 0 ? childType : `${union} | ${childType}`;
250
+ }
251
+ finish(union);
252
+ continue;
253
+ }
254
+ /* jscpd:ignore-end */
255
+ if (frame.kind === 'array') {
256
+ // `list[A | B]` needs no parentheses in Python. Array frames always
257
+ // schedule exactly one child, so its type is present.
258
+ /* v8 ignore next -- the ?? arm needs a childless array frame, which start never builds. */
259
+ finish(`list[${frame.childTypes[0] ?? 'Any'}]`);
260
+ continue;
261
+ }
262
+ // typeddict: assemble AFTER the children so any nested class this one
263
+ // references is already declared (declaration order = reference order).
264
+ const node = frame.node;
265
+ const name = frame.allocated;
266
+ /* v8 ignore next -- typeddict frames always set node and allocated at start. */
267
+ if (node === undefined || name === undefined)
268
+ throw new Error('missing typeddict frame state');
269
+ const required = new Set(node.required);
270
+ const lines = [`class ${name}(TypedDict):`];
271
+ for (let index = 0; index < frame.entries.length; index++) {
272
+ const entry = frame.entries[index];
273
+ const fieldType = frame.childTypes[index];
274
+ /* v8 ignore next -- entries and childTypes correspond one-to-one. */
275
+ if (entry === undefined || fieldType === undefined)
276
+ throw new Error('missing typeddict field type');
277
+ const [field, fieldSchema] = entry;
278
+ // The parent node passed assertSupportedJsonSchema, so every property
279
+ // value is a validated schema node.
280
+ const description = describe(fieldSchema);
281
+ if (description !== undefined)
282
+ lines.push(`${pad(1)}# ${description}`);
283
+ if (required.has(field)) {
284
+ lines.push(`${pad(1)}${field}: ${fieldType}`);
285
+ }
286
+ else {
287
+ state.typing.add('NotRequired');
288
+ lines.push(`${pad(1)}${field}: NotRequired[${fieldType}]`);
289
+ }
290
+ }
291
+ // TypedDict syntax cannot express openness, so an open object states it
292
+ // in-band: the annotation is advisory either way, and `mode: 'code'`
293
+ // omits the native schemas, making this line the model's only signal
294
+ // that extra keys are accepted.
295
+ if (node.additionalProperties !== false) {
296
+ lines.push(`${pad(1)}# Additional keys beyond those declared are allowed.`);
297
+ }
298
+ // A closed empty object still needs a class body (`pass`) to be valid
299
+ // Python; the declared emptiness is the information.
300
+ if (lines.length === 1)
301
+ lines.push(`${pad(1)}pass`);
302
+ state.classes.push(lines.join('\n'));
303
+ finish(name);
304
+ continue;
305
+ }
306
+ frame.phase = 'children';
307
+ const node = frame.schema;
308
+ if (node.oneOf !== undefined) {
309
+ frame.kind = 'oneOf';
310
+ // A union renders as `A | B` — no brackets of its own, so the branches
311
+ // inherit the enclosing depth unchanged.
312
+ //
313
+ // Union LENGTH is deliberately uncapped, unlike list nesting. The two
314
+ // limits are different in kind: >200 open brackets is a SyntaxError
315
+ // from the tokenizer, so the text is not Python; a long `A | B | …`
316
+ // chain is grammatically valid at any length and only defeats CPython's
317
+ // C-recursion when `compile()` walks the left-nested BinOp spine
318
+ // (measured: 1,000 branches compile, 5,000 raise RecursionError). This
319
+ // block is prompt text — nothing compiles it — so that limit costs
320
+ // nothing here, while capping would retire the deep-chain tests that
321
+ // pin the walk's linear time and the class-name propagation cap. The
322
+ // standard this renderer holds is grammatical validity, not
323
+ // compilability under one interpreter's stack.
324
+ frame.children = node.oneOf.map((branch, index) => ({ schema: branch, className: childClassName(frame.className, `${index + 1}`), listDepth: frame.listDepth }));
325
+ continue;
326
+ }
327
+ if (node.type === undefined) {
328
+ state.typing.add('Any');
329
+ finish('Any');
330
+ continue;
331
+ }
332
+ switch (node.type) {
333
+ case 'string':
334
+ finish(renderConstrainedScalar(node, 'str', state));
335
+ break;
336
+ case 'number':
337
+ finish(renderConstrainedScalar(node, 'float', state));
338
+ break;
339
+ case 'integer':
340
+ finish(renderConstrainedScalar(node, 'int', state));
341
+ break;
342
+ case 'boolean':
343
+ finish(renderConstrainedScalar(node, 'bool', state));
344
+ break;
345
+ case 'null':
346
+ finish('None');
347
+ break;
348
+ case 'array': {
349
+ if (node.items === undefined) {
350
+ state.typing.add('Any');
351
+ finish('list[Any]');
352
+ break;
353
+ }
354
+ // Past MAX_LIST_NESTING another `list[` would push the annotation
355
+ // beyond CPython's open-bracket limit and make the whole SDK block
356
+ // unparseable, so the chain degrades here instead — an unusable
357
+ // annotation either way, and this one is valid Python.
358
+ if (frame.listDepth >= MAX_LIST_NESTING) {
359
+ state.typing.add('Any');
360
+ finish('Any');
361
+ break;
362
+ }
363
+ // An array of objects names its item type after the array field.
364
+ frame.kind = 'array';
365
+ frame.children = [{ schema: node.items, className: frame.className, listDepth: frame.listDepth + 1 }];
366
+ break;
367
+ }
368
+ case 'object': {
369
+ // A missing `properties` is an empty property map, exactly as the
370
+ // unified validator and the TS renderer read it — NOT an unknown
371
+ // shape. The openness of the resulting empty object is decided below,
372
+ // so a closed empty object still declares an empty TypedDict rather
373
+ // than a permissive `dict[str, Any]`.
374
+ const entries = Object.entries(node.properties ?? {});
375
+ // An empty `className` marks the context-free `jsonSchemaToPy` entry:
376
+ // there is no naming context to declare into, so degrade. This reads
377
+ // the CALL's className, not `frame.className`: the marker belongs to
378
+ // the whole walk, and frames propagate a derived name (a `oneOf`
379
+ // branch of the context-free root gets the index-derived name `1` —
380
+ // `childClassName` concatenates and caps, it does not go through
381
+ // `camelCase`), so a per-frame read would declare classes the caller
382
+ // has no way to receive, under a name that is not even a legal
383
+ // identifier: `class 1(TypedDict):`. A field
384
+ // name that is not a legal Python attribute is inexpressible as a
385
+ // class-syntax `TypedDict` field, so such an object degrades whole.
386
+ // A leading-double-underscore non-dunder field (`__token`) would be
387
+ // NAME-MANGLED inside class syntax (`_ClassName__token`), describing a
388
+ // different JSON key than the registered schema — degrade like any
389
+ // other inexpressible field name.
390
+ if (className === '' || !entries.every(([name]) => isBareIdentifier(name) && !RESERVED.has(name) && !(name.startsWith('__') && !name.endsWith('__')))) {
391
+ state.typing.add('Any');
392
+ finish('dict[str, Any]');
393
+ break;
394
+ }
395
+ // An OPEN empty object is any dict; a CLOSED empty object declares an
396
+ // empty TypedDict so "no keys accepted" survives into the SDK.
397
+ if (entries.length === 0 && node.additionalProperties !== false) {
398
+ state.typing.add('Any');
399
+ finish('dict[str, Any]');
400
+ break;
401
+ }
402
+ frame.kind = 'typeddict';
403
+ frame.node = node;
404
+ frame.allocated = allocateClassName(frame.className, state);
405
+ state.typing.add('TypedDict');
406
+ frame.entries = entries;
407
+ // A field annotation is its own logical line, so nesting restarts —
408
+ // at 1, reserving the bracket an optional field's `NotRequired[…]`
409
+ // wraps around it. frame.allocated was assigned three statements up;
410
+ // the ?? arm is for the type system only.
411
+ /* v8 ignore next -- allocated is always set before children are built. */
412
+ frame.children = entries.map(([field, child]) => ({ schema: child, className: childClassName(frame.allocated ?? '', camelCase(field)), listDepth: 1 }));
413
+ break;
414
+ }
415
+ /* v8 ignore next 4 -- assertSupportedJsonSchema narrowed this closed type union. */
416
+ default: {
417
+ state.typing.add('Any');
418
+ finish('Any');
419
+ }
420
+ }
421
+ }
422
+ /* v8 ignore next -- every root frame produces one expression. */
423
+ return result ?? 'Any';
424
+ }
425
+ catch {
426
+ // An unsupported or malformed schema failed validation (before any
427
+ // emission), or an unreachable internal invariant tripped. Either degrades
428
+ // the node to `Any` rather than crashing prompt assembly — the Python
429
+ // counterpart of the TS flavor's `unknown` fallback.
430
+ state.typing.add('Any');
431
+ return 'Any';
432
+ }
433
+ }
434
+ //# sourceMappingURL=py-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"py-render.js","sourceRoot":"","sources":["../src/py-render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The Python SDK document renderer: `renderToolsSdkPy`, the `tools:sdk` prompt
3
+ * section rendered under `runtime.language === 'python'` — the fixed usage
4
+ * instructions plus one named `TypedDict` per tool argument or output object
5
+ * and one awaitable method per visible tool on a `Tools` protocol. Split out
6
+ * of `py-types.ts` for the line budget; re-exported from `py-types.ts`.
7
+ * @module @dsh-cc/tools/src/py-sdk-doc
8
+ */
9
+ import type { ToolSdkSchema } from './ts-types.ts';
10
+ /**
11
+ * Render the full `tools:sdk` prompt section under `runtime.language ===
12
+ * 'python'`: the Python-flavored usage instructions plus one named `TypedDict`
13
+ * per tool argument or output object (and per nested object) and one awaitable
14
+ * method per visible tool on a `Tools` protocol — typed args in, the tool's
15
+ * canonical output value out — with a `tools: Tools` singleton the model calls
16
+ * into. The `typing` import line lists exactly the symbols the render used.
17
+ * Deterministic — tools are emitted in lexicographic name order, and class
18
+ * declarations precede the protocol in that same order (nested classes before
19
+ * the parent that references them), so an unchanged tool set produces
20
+ * byte-identical text across assemblies. The sort is not a total order on
21
+ * byte-equal names, so two schemas sharing a name would render in argument
22
+ * order; the caller's visible-capability map is keyed by name, so the input
23
+ * never carries a duplicate.
24
+ * @param schemas - the tool schemas plus canonical output schemas to declare
25
+ * (the caller excludes `run_code` itself).
26
+ * @returns the complete section text.
27
+ */
28
+ export declare function renderToolsSdkPy(schemas: ToolSdkSchema[]): string;
29
+ //# sourceMappingURL=py-sdk-doc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"py-sdk-doc.d.ts","sourceRoot":"","sources":["../src/py-sdk-doc.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAkClD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CAuDjE"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The Python SDK document renderer: `renderToolsSdkPy`, the `tools:sdk` prompt
3
+ * section rendered under `runtime.language === 'python'` — the fixed usage
4
+ * instructions plus one named `TypedDict` per tool argument or output object
5
+ * and one awaitable method per visible tool on a `Tools` protocol. Split out
6
+ * of `py-types.ts` for the line budget; re-exported from `py-types.ts`.
7
+ * @module @dsh-cc/tools/src/py-sdk-doc
8
+ */
9
+ import { describe, camelCase, isBareIdentifier, pad, RESERVED } from "./py-names.js";
10
+ import { renderType } from "./py-render.js";
11
+ /** `typing` symbols this module may emit, in the deterministic import order. */
12
+ const TYPING_ORDER = ['Any', 'Literal', 'NotRequired', 'Protocol', 'TypedDict'];
13
+ /**
14
+ * One-line docstring for a tool `description`, or no lines when there is none.
15
+ * Backslashes are doubled first, every quote is escaped, and a trailing
16
+ * backslash cannot survive: a description ending in `"` or an odd backslash
17
+ * would otherwise merge with (or escape) the closing triple quote and make
18
+ * the generated block — Code Mode's only SDK — syntactically invalid Python.
19
+ */
20
+ function docLines(description, indent) {
21
+ const collapsed = describe({ description });
22
+ if (collapsed === undefined)
23
+ return [];
24
+ const escaped = collapsed.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
25
+ return [`${pad(indent)}"""${escaped}"""`];
26
+ }
27
+ /** The fixed model-facing usage contract rendered above the declarations. */
28
+ const SDK_INSTRUCTIONS = `## Writing code for run_code
29
+
30
+ \`run_code\` takes two required arguments: \`code\` — the body of an async Python function (top-level \`await\` and \`return\` both work) — and \`description\`, a short summary of what the program does. At run time exactly two of the names declared below are bound: \`tools\` and \`ToolCallError\`. Everything else is a STATIC STUB describing argument and return types — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tools.name({"field": 1})\`, never \`FooArgs(field=1)\`, which raises \`NameError\`. Inside the program:
31
+
32
+ - Call tools as \`await tools.name(args)\` — subscript access for exotic, reserved, or underscore-leading names: \`await tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value (each method's return type below). Tool arguments must be lossless JSON.
33
+ - A FAILED tool call raises \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose message is human-readable — wrap in \`try/except\` to handle and continue.
34
+ - Independent read-only calls MAY overlap under \`asyncio.gather\` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with \`await\`.
35
+ - Emit the run's answer with \`print(...)\` and/or a top-level \`return <value>\`; the returned value must be lossless JSON. ONLY what you print and the returned value come back — intermediate tool results never enter the conversation, so extract just what you need.
36
+
37
+ The available tools:`;
38
+ /**
39
+ * Render the full `tools:sdk` prompt section under `runtime.language ===
40
+ * 'python'`: the Python-flavored usage instructions plus one named `TypedDict`
41
+ * per tool argument or output object (and per nested object) and one awaitable
42
+ * method per visible tool on a `Tools` protocol — typed args in, the tool's
43
+ * canonical output value out — with a `tools: Tools` singleton the model calls
44
+ * into. The `typing` import line lists exactly the symbols the render used.
45
+ * Deterministic — tools are emitted in lexicographic name order, and class
46
+ * declarations precede the protocol in that same order (nested classes before
47
+ * the parent that references them), so an unchanged tool set produces
48
+ * byte-identical text across assemblies. The sort is not a total order on
49
+ * byte-equal names, so two schemas sharing a name would render in argument
50
+ * order; the caller's visible-capability map is keyed by name, so the input
51
+ * never carries a duplicate.
52
+ * @param schemas - the tool schemas plus canonical output schemas to declare
53
+ * (the caller excludes `run_code` itself).
54
+ * @returns the complete section text.
55
+ */
56
+ export function renderToolsSdkPy(schemas) {
57
+ const sorted = [...schemas].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
58
+ const state = { classes: [], usedClassNames: new Set(), nextClassCounter: new Map(), typing: new Set(['Protocol']) };
59
+ // ONE ordered member stream, matching the documented lexicographic contract
60
+ // and the TypeScript flavor (which quotes exotic keys in place rather than
61
+ // partitioning them out). Interleaving is free here: a comment line between
62
+ // two `async def` lines is not a statement, so it changes nothing about how
63
+ // the class body parses.
64
+ const members = [];
65
+ let statements = 0;
66
+ for (const schema of sorted) {
67
+ const argType = renderType(schema.parameters, `${camelCase(schema.name)}Args`, state);
68
+ const outputType = renderType(schema.output, `${camelCase(schema.name)}Output`, state);
69
+ if (isBareIdentifier(schema.name) && !RESERVED.has(schema.name) && !schema.name.startsWith('_')) {
70
+ // A docstring only documents its method when it is the FIRST statement
71
+ // of that method's body. Emitted before the `async def` it would instead
72
+ // become the `Tools` class docstring (for the first tool) or a dead
73
+ // expression (for every later one), leaving every method undocumented —
74
+ // and under `mode: 'code'` this SDK is the model's only description of
75
+ // what a tool does. A docstring is a complete body, so the `...` stub is
76
+ // only for the description-less case.
77
+ const doc = docLines(schema.description, 2);
78
+ members.push(doc.length > 0
79
+ ? `${pad(1)}async def ${schema.name}(self, args: ${argType}) -> ${outputType}:`
80
+ : `${pad(1)}async def ${schema.name}(self, args: ${argType}) -> ${outputType}: ...`);
81
+ members.push(...doc);
82
+ statements += 1;
83
+ }
84
+ else {
85
+ // Not reachable as ``tools.name`` — the model reaches it via
86
+ // ``tools[name]``. Exotic names and hard keywords are not legal
87
+ // attributes at all; an underscore-leading name (``_foo``) IS a legal
88
+ // attribute and is routed here anyway, because the forms that break
89
+ // split three ways — a non-dunder ``__token`` name-mangles at the CALL
90
+ // site, a dunder that exists on ``object``/``type`` (``__class__``,
91
+ // ``__doc__``) resolves before ``__getattr__`` ever runs, and implicit
92
+ // special-method lookup skips the hook entirely — and one rule over the
93
+ // whole family costs nothing while a per-form rule would have to
94
+ // enumerate them (see {@link RESERVED}). The stub lists it as a subscript comment
95
+ // (referencing the named TypedDicts too) so a reader sees what is
96
+ // accessible; runtime resolution goes through the proxy's __getitem__.
97
+ members.push(`${pad(1)}# tools[${JSON.stringify(schema.name)}](args: ${argType}) -> ${outputType}`);
98
+ const description = describe(schema);
99
+ if (description !== undefined)
100
+ members.push(`${pad(1)}# ${description}`);
101
+ }
102
+ }
103
+ // Subscript entries are COMMENTS, not statements: a class body of only
104
+ // comments fails to parse, so `pass` is required whenever no method was
105
+ // emitted — including the subscript-only tool set.
106
+ const bodyLines = statements > 0 ? members : [`${pad(1)}pass`, ...members];
107
+ const body = bodyLines.join('\n');
108
+ const imports = TYPING_ORDER.filter(symbol => state.typing.has(symbol));
109
+ const classBlock = state.classes.length > 0 ? `${state.classes.join('\n\n')}\n\n` : '';
110
+ const errorDeclaration = 'class ToolCallError(Exception):\n toolName: str';
111
+ const declaration = `from typing import ${imports.join(', ')}\n\n${errorDeclaration}\n\n${classBlock}class Tools(Protocol):\n${body}\n\ntools: Tools`;
112
+ return `${SDK_INSTRUCTIONS}\n\n\`\`\`python\n${declaration}\n\`\`\``;
113
+ }
114
+ //# sourceMappingURL=py-sdk-doc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"py-sdk-doc.js","sourceRoot":"","sources":["../src/py-sdk-doc.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAEpF,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,gFAAgF;AAChF,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,CAAU,CAAA;AAExF;;;;;;GAMG;AACH,SAAS,QAAQ,CAAC,WAAoB,EAAE,MAAc;IACpD,MAAM,SAAS,GAAG,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC,CAAA;IAC3C,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,EAAE,CAAA;IACtC,MAAM,OAAO,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IACzE,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,OAAO,KAAK,CAAC,CAAA;AAC3C,CAAC;AAED,6EAA6E;AAC7E,MAAM,gBAAgB,GAAG;;;;;;;;;qBASJ,CAAA;AAErB;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAwB;IACvD,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1F,MAAM,KAAK,GAAgB,EAAE,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,IAAI,GAAG,EAAE,EAAE,gBAAgB,EAAE,IAAI,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,CAAA;IACjI,4EAA4E;IAC5E,2EAA2E;IAC3E,4EAA4E;IAC5E,4EAA4E;IAC5E,yBAAyB;IACzB,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,KAAK,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QACrF,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;QACtF,IAAI,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAChG,uEAAuE;YACvE,yEAAyE;YACzE,oEAAoE;YACpE,wEAAwE;YACxE,uEAAuE;YACvE,yEAAyE;YACzE,sCAAsC;YACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;YAC3C,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC;gBACzB,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,aAAa,MAAM,CAAC,IAAI,gBAAgB,OAAO,QAAQ,UAAU,GAAG;gBAC/E,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,aAAa,MAAM,CAAC,IAAI,gBAAgB,OAAO,QAAQ,UAAU,OAAO,CAAC,CAAA;YACtF,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAA;YACpB,UAAU,IAAI,CAAC,CAAA;QACjB,CAAC;aAAM,CAAC;YACN,6DAA6D;YAC7D,gEAAgE;YAChE,sEAAsE;YACtE,oEAAoE;YACpE,uEAAuE;YACvE,oEAAoE;YACpE,uEAAuE;YACvE,wEAAwE;YACxE,iEAAiE;YACjE,kFAAkF;YAClF,kEAAkE;YAClE,uEAAuE;YACvE,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,OAAO,QAAQ,UAAU,EAAE,CAAC,CAAA;YACnG,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;YACpC,IAAI,WAAW,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,WAAW,EAAE,CAAC,CAAA;QAC5E,CAAC;IACH,CAAC;IACD,uEAAuE;IACvE,wEAAwE;IACxE,mDAAmD;IACnD,MAAM,SAAS,GAAG,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,CAAA;IAC1E,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;IACvE,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAA;IACtF,MAAM,gBAAgB,GAAG,oDAAoD,CAAA;IAC7E,MAAM,WAAW,GAAG,sBAAsB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,gBAAgB,OAAO,UAAU,2BAA2B,IAAI,kBAAkB,CAAA;IACrJ,OAAO,GAAG,gBAAgB,qBAAqB,WAAW,UAAU,CAAA;AACtE,CAAC"}