fix(tools): complete the Literal parseability attribution and the soft-keyword positions
pyScalar's docstring named only the two code points CPython refuses anywhere in source. A bare quote, a trailing odd backslash, and a bare LF/CR break the Literal line just as fatally, and JSON.stringify is what covers those too. The argument also leaned on an unstated coincidence: every escape JSON.stringify can emit is a Python escape for the same character, which is why the emitted text both parses and decodes back to the declared value. Say both, and assert the second class. "statement head" does not describe `case`, whose clause block is not a statement. Split the positions three ways. Add the mode 'both' by python assembly, pinning the mode-by-language matrix rather than leaving it to the shared code path.
This commit is contained in:
@@ -350,6 +350,21 @@ describe('mode-aware wire contribution', () => {
|
||||
expect(sdk?.text).toContain('top-level `await`')
|
||||
})
|
||||
|
||||
it("assembles under a python runtime in mode 'both' as well, SDK and schema together", async () => {
|
||||
// `both` reaches the same wireSchemas/requireCodeRuntime/SDK-section code
|
||||
// as `code`, so this pins the mode-by-language matrix rather than a
|
||||
// separate path — including that `schemas()` under `both` projects the
|
||||
// Python flavor instead of hitting the flavor-table guard.
|
||||
const { ctx, systemPrompt } = await setup({ mode: 'both', runtime: { language: 'python' } })
|
||||
registerEcho(ctx)
|
||||
const assembly = await systemPrompt.assemble()
|
||||
expect(assembly.sections.find(section => section.name === 'tools:sdk')?.text).toContain('class Tools(Protocol):')
|
||||
const runCodeSchema = assembly.tools.find(tool => tool.name === RUN_CODE_NAME)
|
||||
expect(runCodeSchema?.description).toContain('Execute a Python program')
|
||||
// `both` keeps the native tools alongside run_code; `code` does not.
|
||||
expect(assembly.tools.map(tool => tool.name)).toContain('echo')
|
||||
})
|
||||
|
||||
it('emits a TypeScript-flavored run_code schema under a typescript runtime', async () => {
|
||||
const { ctx, systemPrompt } = await setup({ mode: 'code', runtime: { language: 'typescript' } })
|
||||
registerEcho(ctx)
|
||||
|
||||
@@ -52,12 +52,18 @@ describe('jsonSchemaToPy', () => {
|
||||
})
|
||||
|
||||
it('leans on JSON.stringify to keep a Literal parseable', () => {
|
||||
// The two code points CPython refuses in source reach this path as well,
|
||||
// and nothing here escapes them itself — `JSON.stringify` does, NUL as a
|
||||
// C0 control and a lone surrogate under ES2019 well-formed stringification.
|
||||
// Python decodes both escapes back to the value the schema declared.
|
||||
// Nothing here escapes anything itself; `JSON.stringify` carries both
|
||||
// classes of hazard. The two code points CPython refuses anywhere in
|
||||
// source: NUL, and a lone surrogate under ES2019 well-formed
|
||||
// stringification.
|
||||
expect(jsonSchemaToPy({ type: 'string', const: 'a\u0000b' })).toBe(String.raw`Literal["a\u0000b"]`)
|
||||
expect(jsonSchemaToPy({ type: 'string', enum: ['a\ud800b'] })).toBe(String.raw`Literal["a\ud800b"]`)
|
||||
// And the ones that break this line in particular: a bare quote closing
|
||||
// the literal early, a trailing backslash eating the closing quote, a bare
|
||||
// newline ending it before its terminator. Every escape it emits is also a
|
||||
// Python escape for the same character, so the value round-trips.
|
||||
expect(jsonSchemaToPy({ type: 'string', const: 'say "hi"\n' })).toBe(String.raw`Literal["say \"hi\"\n"]`)
|
||||
expect(jsonSchemaToPy({ type: 'string', const: 'ends\\' })).toBe(String.raw`Literal["ends\\"]`)
|
||||
})
|
||||
|
||||
it('emits exact digits for a beyond-safe-range integer literal', () => {
|
||||
|
||||
Reference in New Issue
Block a user