test(tools): pin the argument-annotation nesting cap, the worst of the three sites

The 182 the cap is chosen against had no direct case: the existing tests
cover the root chain and the TypedDict field, both of which start one
bracket lower. An array-rooted parameters schema reaches it from a plain
ToolSdkSchema literal, no raw register() needed. Exactly 180 arrays over
a const scalar is the worst case itself -- the root frame starts at
listDepth 0, so every list[ still emits and the innermost Literal[ is
reached rather than degraded; one deeper is where the item degrades.

Name the subscript tool-name comment in pyScalar's docstring: it quotes
through the same JSON.stringify call and inherits the same escapes and
the same pass-throughs.
This commit is contained in:
Chinesezjc
2026-08-05 19:07:10 +08:00
parent dbefb2fa90
commit f3c8695fd6
2 changed files with 29 additions and 1 deletions

View File

@@ -277,7 +277,9 @@ function childClassName(base: string, segment: string): string {
* escape denoting the same character, so the emitted `Literal[...]` both
* parses and decodes back to the value the schema declared. DEL and the C1
* controls do reach it raw — legal but invisible, byte-for-byte as in the TS
* flavor; escaping them is a both-flavors change.
* flavor; escaping them is a both-flavors change. The subscript tool-name
* comment quotes its name through the same call and inherits both halves,
* escapes and pass-throughs alike.
*/
function pyScalar(value: JsonSchemaScalar): string {
if (value === true) return 'True'

View File

@@ -571,6 +571,32 @@ describe('renderToolsSdkPy', () => {
expect(renderToolsSdkPy([tool])).toContain(` rows: ${'list['.repeat(179)}str${']'.repeat(179)}`)
})
it('caps the argument annotation, the site whose enclosing paren stays open', () => {
// The worst of the three emission sites: the parameter list's `(` is still
// open around this annotation, so 180 `list[` plus the innermost bracket
// plus that paren is 182 of CPython's 200. Only a raw `register()` reaches
// it — `defineTool` compiles an object root, whose annotation is a bare
// TypedDict name that opens nothing.
const rooted = (depth: number): ToolSdkSchema => {
let schema: Record<string, unknown> = { type: 'string', const: 'x' }
for (let i = 0; i < depth; i++) schema = { type: 'array', items: schema }
return { name: 'rooted', description: 'Array-rooted parameters.', parameters: schema, output: { type: 'string' } }
}
// Exactly at the cap with a scalar underneath is the worst case itself: the
// chain's root frame starts at `listDepth: 0` here, so all 180 `list[`
// still emit and the innermost `Literal[` is reached rather than degraded.
const worst = renderToolsSdkPy([rooted(180)])
expect(worst).toContain(`async def rooted(self, args: ${'list['.repeat(180)}Literal["x"]${']'.repeat(180)}) -> str:`)
const annotation = worst.split('async def rooted(self, args: ')[1]!.split(') -> str:')[0]!
// 181 brackets on the annotation plus the still-open parameter-list paren,
// the 182 the cap is chosen against.
expect(annotation.split('[').length - 1).toBe(181)
// One array deeper is where the degradation lands, and it lands on the item
// rather than on another `list[`, so the count cannot grow past that.
expect(renderToolsSdkPy([rooted(181)]))
.toContain(`async def rooted(self, args: ${'list['.repeat(180)}Any${']'.repeat(180)}) -> str:`)
})
it('renders a deeply nested oneOf chain in linear time (no per-level re-materialization)', () => {
// Each level is a two-branch oneOf whose first branch recurses; joining the
// accumulated union string at every level would be Theta(depth^2). At this