test(tools): pin the U+200C tail/head split; qualify the identifier-equivalence measurement

The docstring names ZWNJ/ZWJ as a skew instance but nothing checked the
predicate's tail-position accept branch; this is its first test, and it
also covers camelCase's Tool-prefix branch for a head that is
XID_Continue but not XID_Start.

The equivalence sentence pinned its evidence to Node 22.23.1 against
CPython 3.9.6 without saying the samples sit inside those two versions'
shared tables, next to five named characters where that same pair
diverges.
This commit is contained in:
Chinesezjc
2026-08-06 02:09:27 +08:00
parent eb3b635796
commit 4f8ba6c190
3 changed files with 38 additions and 8 deletions

View File

@@ -300,10 +300,10 @@ export interface RunCodeBridgeOptions {
/** Resolves `ctx.codeRuntime` or throws the loud misconfiguration error (shared with the registry's assembly-time checks). */
requireRuntime: () => CodeRuntime
/**
* Reads `ctx.codeRuntime` without throwing: `undefined` when none is
* mounted. Lets schema emission tell "no runtime" (degrade to TS; the
* readers that reach it are {@link resolveFlavor}'s) apart from "unknown
* language" (fail loud).
* Reads `ctx.codeRuntime` without throwing: `undefined` when none is mounted.
* Lets schema emission tell "no runtime" (degrade to TS; the readers that
* reach it are {@link resolveFlavor}'s) apart from "unknown language" (fail
* loud).
*/
peekRuntime: () => CodeRuntime | undefined
/** The run's overlap cap for parallel-classified sub-calls (the registry passes its validated `maxParallelSubCalls`). */

View File

@@ -42,10 +42,11 @@ const IDENTIFIER = /^[\p{XID_Start}_]\p{XID_Continue}*$/u
* take the subscript path, which carries their exact bytes.
*
* `IDENTIFIER`'s equivalence to `str.isidentifier()` was measured across 21
* samples with zero divergence, on Node 22.23.1 against CPython 3.9.6. The
* predicate as a whole is deliberately stricter than `isidentifier()`, which
* does not test NFKC stability: `'field'.isidentifier()` is True and this
* returns false.
* samples with zero divergence, on Node 22.23.1 against CPython 3.9.6 — every
* sample sits inside the two versions' shared tables, and the skew characters
* below are exactly where that pair diverges. The predicate as a whole is
* deliberately stricter than `isidentifier()`, which does not test NFKC
* stability: `'field'.isidentifier()` is True and this returns false.
*
* Both conditions are evaluated against the ENGINE's Unicode tables, and the
* two sides are versioned independently — `\p{XID_Start}`/`\p{XID_Continue}`

View File

@@ -466,6 +466,35 @@ describe('renderToolsSdkPy', () => {
expect(text).not.toContain('field:')
})
it('keeps U+200C in a name tail while rejecting it at a name head, per the two XID properties', () => {
// ZWNJ carries `XID_Continue` and not `XID_Start`, so the predicate splits
// on position: bare in a tail, subscripted at a head. Both verdicts are
// stable across the supported engines — the property arrives in Unicode
// 15.1 and the floor (Node 22.19.0, Unicode 16.0) is past it.
//
// The interpreter side is where this one skews, and it is the same skew the
// docstring's four other characters record, reached in a tail position
// instead of at a head: CPython reads XID_Continue out of the
// `DerivedCoreProperties.txt` of the UCD it was built against (13.0.0 on
// 3.9.6 and 15.0.0 on 3.12.13 both lack the row, and `'ab'.isidentifier()`
// is False on both, measured), so the field emitted bare here needs an
// interpreter with 15.1 tables or newer.
const of = (name: string): ToolSdkSchema => ({
name,
description: `Tool ${name}.`,
parameters: { type: 'object', additionalProperties: false, properties: { 'ab': { type: 'string' } } },
output: { type: 'string' },
})
const text = renderToolsSdkPy([of('ping'), of('b')])
expect(text).toContain('async def ping(self, args: PingArgs) -> str:')
expect(text).toContain(' ab: NotRequired[str]')
// A head that is XID_Continue but not XID_Start takes the subscript path,
// and `camelCase` prefixes `Tool` to make the class name start legally.
expect(text).toContain('# tools["b"](args: ToolbArgs) -> str')
expect(text).toContain('class ToolbArgs(TypedDict):')
expect(text).not.toContain('async def b')
})
it('subscripts a tool name that NFKC-normalizes to something else, while declaring a plain Unicode one', () => {
// Same split at the tool-name site: `路径` becomes an `async def`, the
// ligature name cannot, because `async def find` would define `find`. The