langfx.js 0.1.0-alpha.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.
- package/LICENSE +202 -0
- package/NOTICE +11 -0
- package/README.md +107 -0
- package/dist/agentic.d.ts +128 -0
- package/dist/agentic.js +265 -0
- package/dist/agentic.js.map +1 -0
- package/dist/cache.d.ts +32 -0
- package/dist/cache.js +135 -0
- package/dist/cache.js.map +1 -0
- package/dist/cancellation.d.ts +9 -0
- package/dist/cancellation.js +70 -0
- package/dist/cancellation.js.map +1 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.js +44 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/json-stream.d.ts +73 -0
- package/dist/json-stream.js +222 -0
- package/dist/json-stream.js.map +1 -0
- package/dist/langfunc.d.ts +20 -0
- package/dist/langfunc.js +28 -0
- package/dist/langfunc.js.map +1 -0
- package/dist/language-model.d.ts +78 -0
- package/dist/language-model.js +218 -0
- package/dist/language-model.js.map +1 -0
- package/dist/llms/anthropic.d.ts +30 -0
- package/dist/llms/anthropic.js +365 -0
- package/dist/llms/anthropic.js.map +1 -0
- package/dist/llms/gemini.d.ts +34 -0
- package/dist/llms/gemini.js +380 -0
- package/dist/llms/gemini.js.map +1 -0
- package/dist/llms/images.d.ts +10 -0
- package/dist/llms/images.js +43 -0
- package/dist/llms/images.js.map +1 -0
- package/dist/llms/index.d.ts +7 -0
- package/dist/llms/index.js +4 -0
- package/dist/llms/index.js.map +1 -0
- package/dist/llms/openai.d.ts +30 -0
- package/dist/llms/openai.js +398 -0
- package/dist/llms/openai.js.map +1 -0
- package/dist/llms/transport.d.ts +4 -0
- package/dist/llms/transport.js +90 -0
- package/dist/llms/transport.js.map +1 -0
- package/dist/mapping.d.ts +71 -0
- package/dist/mapping.js +190 -0
- package/dist/mapping.js.map +1 -0
- package/dist/message.d.ts +56 -0
- package/dist/message.js +86 -0
- package/dist/message.js.map +1 -0
- package/dist/python-preview.d.ts +24 -0
- package/dist/python-preview.js +368 -0
- package/dist/python-preview.js.map +1 -0
- package/dist/python-stream.d.ts +15 -0
- package/dist/python-stream.js +29 -0
- package/dist/python-stream.js.map +1 -0
- package/dist/python.d.ts +85 -0
- package/dist/python.js +728 -0
- package/dist/python.js.map +1 -0
- package/dist/query.d.ts +31 -0
- package/dist/query.js +151 -0
- package/dist/query.js.map +1 -0
- package/dist/retry.d.ts +12 -0
- package/dist/retry.js +56 -0
- package/dist/retry.js.map +1 -0
- package/dist/schema/zod.d.ts +4 -0
- package/dist/schema/zod.js +7 -0
- package/dist/schema/zod.js.map +1 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.js +7 -0
- package/dist/schema.js.map +1 -0
- package/dist/template.d.ts +14 -0
- package/dist/template.js +75 -0
- package/dist/template.js.map +1 -0
- package/dist/testing/index.d.ts +33 -0
- package/dist/testing/index.js +56 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tool-call.d.ts +27 -0
- package/dist/tool-call.js +16 -0
- package/dist/tool-call.js.map +1 -0
- package/dist/tools.d.ts +43 -0
- package/dist/tools.js +155 -0
- package/dist/tools.js.map +1 -0
- package/docs/ANTHROPIC.md +43 -0
- package/docs/API_DESIGN.md +200 -0
- package/docs/GEMINI.md +68 -0
- package/docs/IMPLEMENTATION_STATUS.md +77 -0
- package/docs/LIVE_TESTING.md +24 -0
- package/docs/MAPPING.md +54 -0
- package/docs/OPENAI.md +40 -0
- package/docs/PORTING_PLAN.md +135 -0
- package/docs/PROMPT_PARITY.md +379 -0
- package/docs/PYTHON_PROTOCOL.md +109 -0
- package/docs/PYTHON_PROTOCOL_PARITY.md +964 -0
- package/docs/PYTHON_SCHEMA_EVALUATION.md +93 -0
- package/docs/PYTHON_STREAMING_PARITY.md +59 -0
- package/docs/RELEASING.md +25 -0
- package/docs/RETRIES_AND_CACHE.md +56 -0
- package/docs/SESSION_EVENTS.md +40 -0
- package/docs/SOURCE_AUDIT.md +133 -0
- package/docs/STREAMING.md +76 -0
- package/docs/TOOL_STREAMING.md +31 -0
- package/package.json +95 -0
package/dist/python.js
ADDED
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
import { resolveSchema as resolveExternalSchema } from './schema.js';
|
|
2
|
+
const CALL = Symbol('python-call');
|
|
3
|
+
const DICT = Symbol('python-dict');
|
|
4
|
+
const TUPLE = Symbol('python-tuple');
|
|
5
|
+
const LIST = Symbol('python-list');
|
|
6
|
+
/** Explicit marker for a value the model cannot provide. */
|
|
7
|
+
export const UNKNOWN = Symbol('python-unknown');
|
|
8
|
+
class FloatLiteral {
|
|
9
|
+
value;
|
|
10
|
+
constructor(value) {
|
|
11
|
+
this.value = value;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function unbox(value) { return value instanceof FloatLiteral ? value.value : value; }
|
|
15
|
+
function codec(schema) {
|
|
16
|
+
const result = resolveSchema(schema).python;
|
|
17
|
+
if (!result)
|
|
18
|
+
throw new TypeError("Python protocol requires a Python-capable schema. Use protocol: 'json' for JSON Schema/Zod schemas.");
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
const rawSchemas = new WeakMap();
|
|
22
|
+
function resolveSchema(value) {
|
|
23
|
+
const schema = 'schema' in value ? value.schema : value;
|
|
24
|
+
return rawSchemas.has(schema) ? schema : resolveExternalSchema(value);
|
|
25
|
+
}
|
|
26
|
+
function references(codecs) {
|
|
27
|
+
const result = Object.create(null);
|
|
28
|
+
for (const c of codecs)
|
|
29
|
+
for (const [name, resolve] of Object.entries(c.references ?? {})) {
|
|
30
|
+
if (result[name] && result[name] !== resolve)
|
|
31
|
+
throw new TypeError(`Conflicting recursive schema ${name}.`);
|
|
32
|
+
result[name] = resolve;
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
function exampleStyles(codecs) {
|
|
37
|
+
const styles = Object.create(null);
|
|
38
|
+
for (const c of codecs)
|
|
39
|
+
for (const [name, style] of Object.entries(c.exampleStyles ?? {})) {
|
|
40
|
+
if (styles[name] && styles[name] !== style)
|
|
41
|
+
throw new TypeError(`Conflicting example style for ${name}.`);
|
|
42
|
+
styles[name] = style;
|
|
43
|
+
}
|
|
44
|
+
return Object.freeze(styles);
|
|
45
|
+
}
|
|
46
|
+
function make(jsonSchema, python) {
|
|
47
|
+
const schema = {
|
|
48
|
+
get jsonSchema() {
|
|
49
|
+
const pending = Object.entries(python.references ?? {});
|
|
50
|
+
if (!pending.length)
|
|
51
|
+
return jsonSchema;
|
|
52
|
+
const defs = Object.create(null), resolvers = new Map();
|
|
53
|
+
for (let i = 0; i < pending.length; i++) {
|
|
54
|
+
const [name, resolve] = pending[i];
|
|
55
|
+
if (resolvers.has(name)) {
|
|
56
|
+
if (resolvers.get(name) !== resolve)
|
|
57
|
+
throw new TypeError('Conflicting recursive schema.');
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
resolvers.set(name, resolve);
|
|
61
|
+
const target = resolveSchema(resolve());
|
|
62
|
+
defs[name] = rawSchemas.get(target) ?? target.jsonSchema;
|
|
63
|
+
pending.push(...Object.entries(target.python?.references ?? {}));
|
|
64
|
+
}
|
|
65
|
+
return { ...jsonSchema, $defs: defs };
|
|
66
|
+
}, python: Object.freeze(python), parse: (value) => python.decode(value),
|
|
67
|
+
};
|
|
68
|
+
rawSchemas.set(schema, jsonSchema);
|
|
69
|
+
return Object.freeze(schema);
|
|
70
|
+
}
|
|
71
|
+
function floatRepr(value) {
|
|
72
|
+
if (Object.is(value, -0))
|
|
73
|
+
return '-0.0';
|
|
74
|
+
return Number.isInteger(value) && Math.abs(value) < 1e21 ? `${value}.0` : String(value);
|
|
75
|
+
}
|
|
76
|
+
function stringRepr(value) {
|
|
77
|
+
const quote = value.includes("'") && !value.includes('"') ? '"' : "'";
|
|
78
|
+
return quote + value.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, c => '\\x' + c.charCodeAt(0).toString(16).padStart(2, '0')).split(quote).join('\\' + quote) + quote;
|
|
79
|
+
}
|
|
80
|
+
function primitive(type, jsonType, valid) {
|
|
81
|
+
return make({ type: jsonType }, { type, declarations: {}, decode(value) { if (type === 'int' && value instanceof FloatLiteral)
|
|
82
|
+
throw new TypeError('Expected int, received float literal.'); value = unbox(value); if (!valid(value))
|
|
83
|
+
throw new TypeError(`Expected ${type}.`); return value; }, encode(value) { if (!valid(value))
|
|
84
|
+
throw new TypeError(`Expected ${type}.`); if (type === 'float' && typeof value === 'number')
|
|
85
|
+
return floatRepr(value); return value === null ? 'None' : typeof value === 'boolean' ? value ? 'True' : 'False' : JSON.stringify(value); } });
|
|
86
|
+
}
|
|
87
|
+
export const str = primitive('str', 'string', (v) => typeof v === 'string');
|
|
88
|
+
export const int = primitive('int', 'integer', (v) => typeof v === 'number' && Number.isSafeInteger(v));
|
|
89
|
+
export const float = primitive('float', 'number', (v) => typeof v === 'number' && Number.isFinite(v));
|
|
90
|
+
export const bool = primitive('bool', 'boolean', (v) => typeof v === 'boolean');
|
|
91
|
+
export const none = primitive('NoneType', 'null', (v) => v === null);
|
|
92
|
+
export function list(item, options = {}) {
|
|
93
|
+
options = { ...options };
|
|
94
|
+
const c = codec(item);
|
|
95
|
+
const checkSize = (value) => { if (options.minSize !== undefined && value.length < options.minSize || options.maxSize !== undefined && value.length > options.maxSize)
|
|
96
|
+
throw new TypeError('List length constraint failed.'); };
|
|
97
|
+
for (const n of [options.minSize, options.maxSize])
|
|
98
|
+
if (n !== undefined && (!Number.isSafeInteger(n) || n < 0))
|
|
99
|
+
throw new TypeError('Invalid list size constraint.');
|
|
100
|
+
if ((options.minSize ?? 0) > (options.maxSize ?? Infinity))
|
|
101
|
+
throw new TypeError('Invalid list size range.');
|
|
102
|
+
return make({ type: 'array', items: (rawSchemas.get(resolveSchema(item)) ?? resolveSchema(item).jsonSchema), ...(options.minSize !== undefined ? { minItems: options.minSize } : {}), ...(options.maxSize !== undefined ? { maxItems: options.maxSize } : {}) }, { type: `list[${c.type}]`, get declarations() { return c.declarations; }, get exampleStyles() { return exampleStyles([c]); }, references: references([c]),
|
|
103
|
+
decode(value) { if (!Array.isArray(value) || TUPLE in value)
|
|
104
|
+
throw new TypeError('Expected list.'); checkSize(value); return value.map(v => c.decode(v)); },
|
|
105
|
+
encode(value) { if (!Array.isArray(value) || TUPLE in value)
|
|
106
|
+
throw new TypeError('Expected list.'); checkSize(value); return `[${value.map(v => c.encode(v)).join(', ')}]`; } });
|
|
107
|
+
}
|
|
108
|
+
export function dict(item) {
|
|
109
|
+
const c = codec(item);
|
|
110
|
+
return make({ type: 'object', additionalProperties: (rawSchemas.get(resolveSchema(item)) ?? resolveSchema(item).jsonSchema) }, { type: `dict[str, ${c.type}]`, get declarations() { return c.declarations; }, get exampleStyles() { return exampleStyles([c]); }, references: references([c]),
|
|
111
|
+
decode(value) { if (!value || typeof value !== 'object' || Array.isArray(value) || value instanceof Set || CALL in value || value instanceof FloatLiteral)
|
|
112
|
+
throw new TypeError('Expected dictionary.'); return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, c.decode(v)])); },
|
|
113
|
+
encode(value) { if (!value || typeof value !== 'object' || Array.isArray(value) || value instanceof Set)
|
|
114
|
+
throw new TypeError('Expected dictionary.'); return `{${Object.entries(value).map(([k, v]) => `${JSON.stringify(k)}: ${c.encode(v)}`).join(', ')}}`; } });
|
|
115
|
+
}
|
|
116
|
+
function declarations(codecs) {
|
|
117
|
+
const result = Object.create(null);
|
|
118
|
+
for (const c of codecs)
|
|
119
|
+
for (const [name, declaration] of Object.entries(c.declarations)) {
|
|
120
|
+
if (Object.hasOwn(result, name) && result[name] !== declaration) {
|
|
121
|
+
// An explicit subclass relationship enriches an otherwise identical definition.
|
|
122
|
+
const plain = (text) => text.replace(/^(class [A-Za-z][A-Za-z0-9_]*)\([A-Za-z][A-Za-z0-9_]*\):/, '$1:');
|
|
123
|
+
if (plain(result[name]) !== plain(declaration) || (result[name] !== plain(result[name]) && declaration !== plain(declaration)))
|
|
124
|
+
throw new TypeError(`Conflicting Python class ${name}.`);
|
|
125
|
+
if (declaration === plain(declaration))
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
result[name] = declaration;
|
|
129
|
+
}
|
|
130
|
+
return Object.freeze(result);
|
|
131
|
+
}
|
|
132
|
+
/** Try declared alternatives in order, matching Python union validation. */
|
|
133
|
+
export function union(...schemas) {
|
|
134
|
+
const codecs = schemas.map(s => codec(s));
|
|
135
|
+
if (new Set(codecs.map(c => c.type)).size !== codecs.length)
|
|
136
|
+
throw new TypeError('Duplicate Python union alternative.');
|
|
137
|
+
return make({ anyOf: schemas.map(s => (rawSchemas.get(resolveSchema(s)) ?? resolveSchema(s).jsonSchema)) }, { type: codecs.length === 2 && codecs.some(c => c.type === 'NoneType') ? `${codecs.find(c => c.type !== 'NoneType').type} | None` : `Union[${codecs.map(c => c.type === 'NoneType' ? 'None' : c.type).join(', ')}]`, get declarations() { return declarations(codecs); }, get exampleStyles() { return exampleStyles(codecs); }, references: references(codecs),
|
|
138
|
+
decode(value) {
|
|
139
|
+
for (const c of codecs) {
|
|
140
|
+
try {
|
|
141
|
+
return c.decode(value);
|
|
142
|
+
}
|
|
143
|
+
catch { /* Try the declared alternatives. */ }
|
|
144
|
+
}
|
|
145
|
+
throw new TypeError('No matching union alternative.');
|
|
146
|
+
},
|
|
147
|
+
encode(value) {
|
|
148
|
+
for (const c of codecs) {
|
|
149
|
+
try {
|
|
150
|
+
return c.encode(value);
|
|
151
|
+
}
|
|
152
|
+
catch { /* Try alternatives. */ }
|
|
153
|
+
}
|
|
154
|
+
throw new TypeError('No matching union example alternative.');
|
|
155
|
+
} });
|
|
156
|
+
}
|
|
157
|
+
const identifier = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
158
|
+
const reserved = new Set('False None True and as assert async await break class continue def del elif else except finally for from global if import in is lambda nonlocal not or pass raise return try while with yield output set UNKNOWN'.split(' '));
|
|
159
|
+
export function classSchema(options) {
|
|
160
|
+
const { name, create, isInstance, decompose } = options;
|
|
161
|
+
const defaults = { ...options.defaults };
|
|
162
|
+
const excluded = new Set(options.excludeFromPrompt ?? []);
|
|
163
|
+
for (const key of Object.keys(defaults))
|
|
164
|
+
if (!Object.hasOwn(options.fields, key))
|
|
165
|
+
throw new TypeError('Unknown default field.');
|
|
166
|
+
for (const key of excluded)
|
|
167
|
+
if (!Object.hasOwn(defaults, key))
|
|
168
|
+
throw new TypeError('Excluded fields require a default.');
|
|
169
|
+
if (!identifier.test(name) || reserved.has(name))
|
|
170
|
+
throw new TypeError('Invalid Python class name.');
|
|
171
|
+
const entries = Object.entries(options.fields).map(([key, schema]) => {
|
|
172
|
+
if (!identifier.test(key) || reserved.has(key))
|
|
173
|
+
throw new TypeError('Invalid Python field name.');
|
|
174
|
+
return { key, schema: resolveSchema(schema), codec: codec(schema) };
|
|
175
|
+
});
|
|
176
|
+
const extraSchema = options.extraFields ? resolveSchema(options.extraFields) : undefined;
|
|
177
|
+
const allCodecs = [...entries.map(e => e.codec), ...(options.extraFields ? [codec(options.extraFields)] : [])];
|
|
178
|
+
const lines = [];
|
|
179
|
+
if (options.description)
|
|
180
|
+
lines.push(' ' + '\"\"\"' + options.description.replaceAll('\"\"\"', '\\"\\"\\"').replaceAll('\n', '\n ') + '\"\"\"');
|
|
181
|
+
for (const e of entries) {
|
|
182
|
+
if (excluded.has(e.key))
|
|
183
|
+
continue;
|
|
184
|
+
const description = options.fieldDescriptions?.[e.key];
|
|
185
|
+
if (description)
|
|
186
|
+
lines.push(...description.split('\n').filter(Boolean).map(line => ` # ${line}`));
|
|
187
|
+
lines.push(` ${e.key}: ${e.codec.type}`);
|
|
188
|
+
}
|
|
189
|
+
const declaration = `class ${name}:\n${lines.length ? lines.join('\n') : ' pass'}`;
|
|
190
|
+
return make({ type: 'object', properties: Object.fromEntries(entries.map(e => [e.key, (rawSchemas.get(e.schema) ?? e.schema.jsonSchema)])), required: entries.filter(e => !Object.hasOwn(defaults, e.key)).map(e => e.key), additionalProperties: extraSchema ? (rawSchemas.get(extraSchema) ?? extraSchema.jsonSchema) : false }, {
|
|
191
|
+
type: name,
|
|
192
|
+
get declarations() {
|
|
193
|
+
const declared = { ...declarations(allCodecs) };
|
|
194
|
+
if (declared[name] !== undefined && declared[name] !== declaration)
|
|
195
|
+
throw new TypeError('Conflicting Python class name.');
|
|
196
|
+
declared[name] = declaration;
|
|
197
|
+
return Object.freeze(declared);
|
|
198
|
+
}, get exampleStyles() { return exampleStyles([{ exampleStyles: { [name]: options.exampleFormat ?? 'expanded' } }, ...allCodecs]); }, references: references(allCodecs),
|
|
199
|
+
decode(value) {
|
|
200
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || value instanceof Set || value instanceof FloatLiteral)
|
|
201
|
+
throw new TypeError(`Expected ${name}.`);
|
|
202
|
+
let fields;
|
|
203
|
+
if (CALL in value) {
|
|
204
|
+
if (value[CALL] !== name)
|
|
205
|
+
throw new TypeError(`Expected ${name}.`);
|
|
206
|
+
fields = value.fields;
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
if (DICT in value)
|
|
210
|
+
throw new TypeError(`Expected ${name} constructor, received dictionary.`);
|
|
211
|
+
fields = value;
|
|
212
|
+
}
|
|
213
|
+
const extra = Object.keys(fields).filter(key => !entries.some(e => e.key === key));
|
|
214
|
+
if (extra.length && !options.extraFields)
|
|
215
|
+
throw new TypeError(`Unexpected fields for ${name}.`);
|
|
216
|
+
const parsed = Object.create(null);
|
|
217
|
+
for (const e of entries) {
|
|
218
|
+
if (!Object.hasOwn(fields, e.key)) {
|
|
219
|
+
if (!Object.hasOwn(defaults, e.key))
|
|
220
|
+
throw new TypeError(`Missing ${e.key}.`);
|
|
221
|
+
parsed[e.key] = e.codec.decode(new Parser(e.codec.encode(defaults[e.key])).parse());
|
|
222
|
+
}
|
|
223
|
+
else
|
|
224
|
+
parsed[e.key] = e.codec.decode(fields[e.key]);
|
|
225
|
+
}
|
|
226
|
+
if (options.extraFields)
|
|
227
|
+
for (const key of extra)
|
|
228
|
+
parsed[key] = codec(options.extraFields).decode(fields[key]);
|
|
229
|
+
return create(parsed);
|
|
230
|
+
},
|
|
231
|
+
encode(value) {
|
|
232
|
+
if (!isInstance(value))
|
|
233
|
+
throw new TypeError(`Expected ${name} instance.`);
|
|
234
|
+
const fields = decompose(value);
|
|
235
|
+
const args = entries.map(e => `${e.key}=${e.codec.encode(fields[e.key])}`);
|
|
236
|
+
if (options.extraFields)
|
|
237
|
+
for (const key of Object.keys(fields)) {
|
|
238
|
+
if (!entries.some(e => e.key === key)) {
|
|
239
|
+
if (!identifier.test(key) || reserved.has(key))
|
|
240
|
+
throw new TypeError('Invalid extra field name.');
|
|
241
|
+
args.push(`${key}=${codec(options.extraFields).encode(fields[key])}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return `${name}(${args.join(', ')})`;
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
export function schemaText(schema) {
|
|
249
|
+
const c = codec(schema);
|
|
250
|
+
const definitions = Object.values(c.declarations);
|
|
251
|
+
return definitions.length ? c.type + '\n\n```python\n' + definitions.join('\n\n') + '\n```' : c.type === 'NoneType' ? 'NoneType' : c.type;
|
|
252
|
+
}
|
|
253
|
+
export function format(schema, value) { return codec(schema).encode(value); }
|
|
254
|
+
export function parse(schema, text, limits = {}) { return codec(schema).decode(new Parser(text, limits).parse()); }
|
|
255
|
+
/** Restricted data grammar: no eval, attribute access, positional calls, imports or operators. */
|
|
256
|
+
class Parser {
|
|
257
|
+
i = 0;
|
|
258
|
+
text;
|
|
259
|
+
maxDepth;
|
|
260
|
+
constructor(text, limits = {}) {
|
|
261
|
+
const maxChars = limits.maxChars ?? 1_048_576;
|
|
262
|
+
this.maxDepth = limits.maxDepth ?? 64;
|
|
263
|
+
for (const [name, value, cap] of [['maxChars', maxChars, 1_048_576], ['maxDepth', this.maxDepth, 64]]) {
|
|
264
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > cap)
|
|
265
|
+
throw new RangeError(`${name} must be a positive integer no greater than ${cap}.`);
|
|
266
|
+
}
|
|
267
|
+
if (text.length > maxChars)
|
|
268
|
+
throw new SyntaxError('Python output exceeds size limit.');
|
|
269
|
+
this.text = text.trim();
|
|
270
|
+
if (this.text.startsWith('```')) {
|
|
271
|
+
const match = /^```(?:python)?\s*\n([\s\S]*)\n```$/.exec(this.text);
|
|
272
|
+
if (!match)
|
|
273
|
+
throw new SyntaxError('Invalid Python fence.');
|
|
274
|
+
this.text = match[1];
|
|
275
|
+
}
|
|
276
|
+
this.text = this.text.replace(/^\s*output\s*=\s*/, '');
|
|
277
|
+
}
|
|
278
|
+
space() { while (/\s/.test(this.text[this.i] ?? '') && this.i < this.text.length)
|
|
279
|
+
this.i++; }
|
|
280
|
+
take(token) { this.space(); if (this.text.startsWith(token, this.i)) {
|
|
281
|
+
this.i += token.length;
|
|
282
|
+
return true;
|
|
283
|
+
} return false; }
|
|
284
|
+
expect(token) { if (!this.take(token))
|
|
285
|
+
throw new SyntaxError(`Expected ${token}.`); }
|
|
286
|
+
name() { this.space(); const m = /^[A-Za-z][A-Za-z0-9_]*/.exec(this.text.slice(this.i)); if (!m)
|
|
287
|
+
throw new SyntaxError('Expected identifier.'); this.i += m[0].length; return m[0]; }
|
|
288
|
+
parse() { const value = this.value(0); this.space(); if (this.i !== this.text.length)
|
|
289
|
+
throw new SyntaxError('Unexpected trailing code.'); return value; }
|
|
290
|
+
value(depth) {
|
|
291
|
+
if (depth > this.maxDepth)
|
|
292
|
+
throw new SyntaxError('Python output exceeds depth limit.');
|
|
293
|
+
this.space();
|
|
294
|
+
const ch = this.text[this.i];
|
|
295
|
+
if (ch === '"' || ch === "'" || /[rR]/.test(ch ?? '') && ['"', "'"].includes(this.text[this.i + 1] ?? '')) {
|
|
296
|
+
let result = '';
|
|
297
|
+
do {
|
|
298
|
+
const raw = /[rR]/.test(this.text[this.i] ?? '');
|
|
299
|
+
if (raw)
|
|
300
|
+
this.i++;
|
|
301
|
+
const quote = this.text[this.i++];
|
|
302
|
+
const triple = this.text.slice(this.i, this.i + 2) === quote.repeat(2);
|
|
303
|
+
if (triple)
|
|
304
|
+
this.i += 2;
|
|
305
|
+
const end = quote.repeat(triple ? 3 : 1);
|
|
306
|
+
let closed = false;
|
|
307
|
+
while (this.i < this.text.length) {
|
|
308
|
+
if (this.text.startsWith(end, this.i)) {
|
|
309
|
+
this.i += end.length;
|
|
310
|
+
closed = true;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
const c = this.text[this.i++];
|
|
314
|
+
if (c === '\0' || !triple && (c === '\n' || c === '\r'))
|
|
315
|
+
throw new SyntaxError('Invalid string character.');
|
|
316
|
+
if (c !== '\\') {
|
|
317
|
+
result += c;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const escape = this.text[this.i++];
|
|
321
|
+
if (escape === undefined)
|
|
322
|
+
throw new SyntaxError('Unterminated string escape.');
|
|
323
|
+
if (raw) {
|
|
324
|
+
result += '\\' + escape;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const simple = { a: '\x07', v: '\v', n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', '\\': '\\', '"': '"', "'": "'" };
|
|
328
|
+
if (Object.hasOwn(simple, escape))
|
|
329
|
+
result += simple[escape];
|
|
330
|
+
else if (escape === '\n') { /* Python line continuation. */ }
|
|
331
|
+
else if (['x', 'u', 'U'].includes(escape)) {
|
|
332
|
+
const length = escape === 'x' ? 2 : escape === 'u' ? 4 : 8;
|
|
333
|
+
const digits = this.text.slice(this.i, this.i + length);
|
|
334
|
+
if (digits.length !== length || !/^[\da-fA-F]+$/.test(digits))
|
|
335
|
+
throw new SyntaxError('Invalid Unicode escape.');
|
|
336
|
+
this.i += length;
|
|
337
|
+
const point = parseInt(digits, 16);
|
|
338
|
+
if (point > 0x10ffff)
|
|
339
|
+
throw new SyntaxError('Invalid Unicode code point.');
|
|
340
|
+
result += String.fromCodePoint(point);
|
|
341
|
+
}
|
|
342
|
+
else if (/[0-7]/.test(escape)) {
|
|
343
|
+
const rest = /^[0-7]{0,2}/.exec(this.text.slice(this.i))[0];
|
|
344
|
+
this.i += rest.length;
|
|
345
|
+
result += String.fromCodePoint(parseInt(escape + rest, 8));
|
|
346
|
+
}
|
|
347
|
+
else
|
|
348
|
+
throw new SyntaxError('Unsupported string escape.');
|
|
349
|
+
}
|
|
350
|
+
if (!closed)
|
|
351
|
+
throw new SyntaxError('Unterminated string.');
|
|
352
|
+
this.space();
|
|
353
|
+
} while (['"', "'"].includes(this.text[this.i] ?? '') || /[rR]/.test(this.text[this.i] ?? '') && ['"', "'"].includes(this.text[this.i + 1] ?? ''));
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
if (this.take('(')) {
|
|
357
|
+
const values = [];
|
|
358
|
+
Object.defineProperty(values, TUPLE, { value: true });
|
|
359
|
+
if (this.take(')'))
|
|
360
|
+
return values;
|
|
361
|
+
values.push(this.value(depth + 1));
|
|
362
|
+
if (this.take(')'))
|
|
363
|
+
return values[0]; // Parenthesized scalar.
|
|
364
|
+
this.expect(',');
|
|
365
|
+
while (!this.take(')')) {
|
|
366
|
+
values.push(this.value(depth + 1));
|
|
367
|
+
if (this.take(')'))
|
|
368
|
+
return values;
|
|
369
|
+
this.expect(',');
|
|
370
|
+
}
|
|
371
|
+
return values;
|
|
372
|
+
}
|
|
373
|
+
if (this.take('[')) {
|
|
374
|
+
const values = [];
|
|
375
|
+
Object.defineProperty(values, LIST, { value: true });
|
|
376
|
+
if (this.take(']'))
|
|
377
|
+
return values;
|
|
378
|
+
do {
|
|
379
|
+
values.push(this.value(depth + 1));
|
|
380
|
+
if (this.take(']'))
|
|
381
|
+
return values;
|
|
382
|
+
this.expect(',');
|
|
383
|
+
} while (!this.take(']'));
|
|
384
|
+
return values;
|
|
385
|
+
}
|
|
386
|
+
if (this.take('{')) {
|
|
387
|
+
const values = Object.create(null);
|
|
388
|
+
Object.defineProperty(values, DICT, { value: true });
|
|
389
|
+
if (this.take('}'))
|
|
390
|
+
return values;
|
|
391
|
+
const first = this.value(depth + 1);
|
|
392
|
+
if (!this.take(':')) {
|
|
393
|
+
const elements = new Set();
|
|
394
|
+
setElement(first);
|
|
395
|
+
elements.add(first);
|
|
396
|
+
while (!this.take('}')) {
|
|
397
|
+
this.expect(',');
|
|
398
|
+
if (this.take('}'))
|
|
399
|
+
break;
|
|
400
|
+
const element = this.value(depth + 1);
|
|
401
|
+
setElement(element);
|
|
402
|
+
elements.add(element);
|
|
403
|
+
}
|
|
404
|
+
return elements;
|
|
405
|
+
}
|
|
406
|
+
if (typeof first !== 'string')
|
|
407
|
+
throw new SyntaxError('Dictionary keys must be unique strings.');
|
|
408
|
+
values[first] = this.value(depth + 1);
|
|
409
|
+
while (!this.take('}')) {
|
|
410
|
+
this.expect(',');
|
|
411
|
+
if (this.take('}'))
|
|
412
|
+
break;
|
|
413
|
+
const key = this.value(depth + 1);
|
|
414
|
+
if (typeof key !== 'string' || Object.hasOwn(values, key))
|
|
415
|
+
throw new SyntaxError('Dictionary keys must be unique strings.');
|
|
416
|
+
this.expect(':');
|
|
417
|
+
values[key] = this.value(depth + 1);
|
|
418
|
+
}
|
|
419
|
+
return values;
|
|
420
|
+
}
|
|
421
|
+
const based = /^[+-]?0(?:[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*|[oO][0-7](?:_?[0-7])*|[bB][01](?:_?[01])*)/.exec(this.text.slice(this.i));
|
|
422
|
+
if (based) {
|
|
423
|
+
this.i += based[0].length;
|
|
424
|
+
const token = based[0].replaceAll('_', '');
|
|
425
|
+
const sign = token.startsWith('-') ? -1 : 1;
|
|
426
|
+
const unsigned = token.replace(/^[+-]/, '');
|
|
427
|
+
return sign * Number(unsigned);
|
|
428
|
+
}
|
|
429
|
+
const number = /^[+-]?(?:\d(?:_?\d)*(?:\.(?:\d(?:_?\d)*)?)?|\.\d(?:_?\d)*)(?:[eE][+-]?\d(?:_?\d)*)?/.exec(this.text.slice(this.i));
|
|
430
|
+
if (number) {
|
|
431
|
+
this.i += number[0].length;
|
|
432
|
+
const token = number[0].replaceAll('_', '');
|
|
433
|
+
const floating = /[.eE]/.test(number[0]);
|
|
434
|
+
if (!floating && /^[+-]?0[0-9]*[1-9]/.test(token))
|
|
435
|
+
throw new SyntaxError('Leading zeros are invalid in decimal integers.');
|
|
436
|
+
const value = Number(token);
|
|
437
|
+
if (!Number.isFinite(value))
|
|
438
|
+
throw new SyntaxError('Nonfinite number.');
|
|
439
|
+
return floating ? new FloatLiteral(value) : value;
|
|
440
|
+
}
|
|
441
|
+
const name = this.name();
|
|
442
|
+
if (name === 'True')
|
|
443
|
+
return true;
|
|
444
|
+
if (name === 'False')
|
|
445
|
+
return false;
|
|
446
|
+
if (name === 'None')
|
|
447
|
+
return null;
|
|
448
|
+
if (name === 'UNKNOWN')
|
|
449
|
+
return UNKNOWN;
|
|
450
|
+
if (name === 'set') {
|
|
451
|
+
this.expect('(');
|
|
452
|
+
this.expect(')');
|
|
453
|
+
return new Set();
|
|
454
|
+
}
|
|
455
|
+
this.expect('(');
|
|
456
|
+
const fields = Object.create(null);
|
|
457
|
+
if (!this.take(')'))
|
|
458
|
+
do {
|
|
459
|
+
const key = this.name();
|
|
460
|
+
if (Object.hasOwn(fields, key))
|
|
461
|
+
throw new SyntaxError('Duplicate keyword.');
|
|
462
|
+
this.expect('=');
|
|
463
|
+
fields[key] = this.value(depth + 1);
|
|
464
|
+
if (this.take(')'))
|
|
465
|
+
break;
|
|
466
|
+
this.expect(',');
|
|
467
|
+
} while (!this.take(')'));
|
|
468
|
+
return { [CALL]: name, fields };
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/** Python v2's fenced, assigned, expanded few-shot representation. */
|
|
472
|
+
export function formatExample(schema, value) {
|
|
473
|
+
const data = new Parser(format(schema, value)).parse();
|
|
474
|
+
const styles = codec(schema).exampleStyles ?? {};
|
|
475
|
+
function pretty(value, level = 0, compact = false) {
|
|
476
|
+
const pad = ' '.repeat(level), child = ' '.repeat(level + 1);
|
|
477
|
+
if (value === UNKNOWN)
|
|
478
|
+
return 'UNKNOWN';
|
|
479
|
+
if (value instanceof Set)
|
|
480
|
+
return value.size ? '{' + [...value].map(v => pretty(v, level + 1, compact)).join(', ') + '}' : 'set()';
|
|
481
|
+
if (value === null)
|
|
482
|
+
return 'None';
|
|
483
|
+
if (typeof value === 'boolean')
|
|
484
|
+
return value ? 'True' : 'False';
|
|
485
|
+
if (typeof value === 'string') {
|
|
486
|
+
return stringRepr(value);
|
|
487
|
+
}
|
|
488
|
+
if (value instanceof FloatLiteral)
|
|
489
|
+
return floatRepr(value.value);
|
|
490
|
+
if (typeof value === 'number')
|
|
491
|
+
return String(value);
|
|
492
|
+
if (Array.isArray(value)) {
|
|
493
|
+
const body = value.map(v => pretty(v, level + 1, compact)).join(', ');
|
|
494
|
+
return TUPLE in value ? '(' + body + (value.length === 1 ? ',' : '') + ')' : `[${body}]`;
|
|
495
|
+
}
|
|
496
|
+
if (value && typeof value === 'object') {
|
|
497
|
+
const call = CALL in value ? value : undefined;
|
|
498
|
+
const entries = Object.entries(call ? call.fields : value);
|
|
499
|
+
compact ||= !!call && styles[call[CALL]] === 'compact';
|
|
500
|
+
const start = call ? call[CALL] + '(' : '{', end = call ? ')' : '}';
|
|
501
|
+
if (compact)
|
|
502
|
+
return start + entries.map(([k, v]) => (call ? k + '=' : pretty(k) + ': ') + pretty(v, level + 1, true)).join(', ') + end;
|
|
503
|
+
return entries.length ? `${start}\n${entries.map(([k, v]) => child + (call ? k + '=' : pretty(k) + ': ') + pretty(v, level + 1, compact)).join(',\n')}\n${pad}${end}` : start + end;
|
|
504
|
+
}
|
|
505
|
+
throw new TypeError('Unsupported example value.');
|
|
506
|
+
}
|
|
507
|
+
return '```python\noutput = ' + pretty(data) + '\n```';
|
|
508
|
+
}
|
|
509
|
+
/** Literal choices preserve their values in both the prompt and local validation. */
|
|
510
|
+
export function literal(...values) {
|
|
511
|
+
if (!values.length)
|
|
512
|
+
throw new TypeError('At least one literal is required.');
|
|
513
|
+
for (const v of values)
|
|
514
|
+
if (typeof v === 'number' && !Number.isFinite(v))
|
|
515
|
+
throw new TypeError('Literal numbers must be finite.');
|
|
516
|
+
const repr = (v) => typeof v === 'string' ? stringRepr(v) : v === null ? 'None' : typeof v === 'boolean' ? v ? 'True' : 'False' : Object.is(v, -0) ? '-0.0' : String(v);
|
|
517
|
+
const validate = (v) => { v = unbox(v); if (!values.some(x => Object.is(x, v)))
|
|
518
|
+
throw new TypeError('Value is not a declared literal.'); return v; };
|
|
519
|
+
return make({ enum: [...values] }, { type: `Literal[${values.map(repr).join(', ')}]`, declarations: {}, decode: validate, encode: v => repr(validate(v)) });
|
|
520
|
+
}
|
|
521
|
+
export function integer(options = {}) { return boundedNumber(int, options); }
|
|
522
|
+
export function number(options = {}) { return boundedNumber(float, options); }
|
|
523
|
+
function boundedNumber(base, options) {
|
|
524
|
+
options = { ...options };
|
|
525
|
+
for (const n of [options.min, options.max])
|
|
526
|
+
if (n !== undefined && (!Number.isFinite(n) || base === int && !Number.isSafeInteger(n)))
|
|
527
|
+
throw new TypeError('Invalid numeric bound.');
|
|
528
|
+
if ((options.min ?? -Infinity) > (options.max ?? Infinity))
|
|
529
|
+
throw new TypeError('Invalid numeric range.');
|
|
530
|
+
const check = (v) => { const n = base.python.decode(v); if (options.min !== undefined && n < options.min || options.max !== undefined && n > options.max)
|
|
531
|
+
throw new TypeError('Numeric constraint failed.'); return n; };
|
|
532
|
+
const args = [options.min !== undefined ? `min=${options.min}` : '', options.max !== undefined ? `max=${options.max}` : ''].filter(Boolean);
|
|
533
|
+
return make({ ...base.jsonSchema, ...(options.min !== undefined ? { minimum: options.min } : {}), ...(options.max !== undefined ? { maximum: options.max } : {}) }, {
|
|
534
|
+
type: base.python.type + (args.length ? `(${args.join(', ')})` : ''), declarations: {}, decode: check, encode: value => base.python.encode(check(value)),
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
/** Uses JavaScript regex syntax; portable patterns match Python's start-anchored re.match behavior. */
|
|
538
|
+
export function string(options = {}) {
|
|
539
|
+
const pattern = options.pattern === undefined ? undefined : new RegExp(`^(?:${options.pattern})`, 'u');
|
|
540
|
+
const check = (v) => { const s = str.python.decode(v); if (pattern && !pattern.test(s))
|
|
541
|
+
throw new TypeError('String pattern constraint failed.'); return s; };
|
|
542
|
+
return make({ type: 'string', ...(options.pattern !== undefined ? { pattern: `^(?:${options.pattern})` } : {}) }, {
|
|
543
|
+
type: options.pattern === undefined ? 'str' : `str(regex=${literal(options.pattern).python.encode(options.pattern)})`, declarations: {}, decode: check, encode: value => str.python.encode(check(value)),
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
export function record(fields) {
|
|
547
|
+
const entries = Object.entries(fields).map(([key, s]) => ({ key, schema: resolveSchema(s), codec: codec(s) }));
|
|
548
|
+
const check = (v) => { if (!v || typeof v !== 'object' || Array.isArray(v) || v instanceof Set || CALL in v || v instanceof FloatLiteral)
|
|
549
|
+
throw new TypeError('Expected record.'); const result = v; if (Object.keys(result).length !== entries.length || entries.some(e => !Object.hasOwn(result, e.key)))
|
|
550
|
+
throw new TypeError('Record fields mismatch.'); return result; };
|
|
551
|
+
return make({ type: 'object', properties: Object.fromEntries(entries.map(e => [e.key, (rawSchemas.get(e.schema) ?? e.schema.jsonSchema)])), required: entries.map(e => e.key), additionalProperties: false }, {
|
|
552
|
+
type: `{${entries.map(e => `${literal(e.key).python.encode(e.key)}: ${e.codec.type}`).join(', ')}}`, get declarations() { return declarations(entries.map(e => e.codec)); }, get exampleStyles() { return exampleStyles(entries.map(e => e.codec)); }, references: references(entries.map(e => e.codec)),
|
|
553
|
+
decode(v) { const value = check(v); return Object.fromEntries(entries.map(e => [e.key, e.codec.decode(value[e.key])])); },
|
|
554
|
+
encode(v) { const value = check(v); return `{${entries.map(e => `${str.python.encode(e.key)}: ${e.codec.encode(value[e.key])}`).join(', ')}}`; },
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
export function tuple(...items) {
|
|
558
|
+
const codecs = items.map(s => codec(s));
|
|
559
|
+
return make({ type: 'array', prefixItems: items.map(s => (rawSchemas.get(resolveSchema(s)) ?? resolveSchema(s).jsonSchema)), minItems: items.length, maxItems: items.length }, {
|
|
560
|
+
type: `tuple[${codecs.map(c => c.type).join(', ')}]`, get declarations() { return declarations(codecs); }, get exampleStyles() { return exampleStyles(codecs); }, references: references(codecs),
|
|
561
|
+
decode(v) { if (!Array.isArray(v) || LIST in v || v.length !== items.length)
|
|
562
|
+
throw new TypeError('Expected fixed-length tuple literal.'); return v.map((x, i) => codecs[i].decode(x)); },
|
|
563
|
+
encode(v) { if (!Array.isArray(v) || v.length !== items.length)
|
|
564
|
+
throw new TypeError('Tuple length mismatch.'); return '(' + v.map((x, i) => codecs[i].encode(x)).join(', ') + (v.length === 1 ? ',' : '') + ')'; },
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
/** Explicit allowed subtypes; unlike Python there is no process-wide class registry. */
|
|
568
|
+
export function withSubclasses(base, ...subtypes) {
|
|
569
|
+
const all = [base, ...subtypes].map(s => codec(s));
|
|
570
|
+
return make({ anyOf: [base, ...subtypes].map(s => (rawSchemas.get(resolveSchema(s)) ?? resolveSchema(s).jsonSchema)) }, {
|
|
571
|
+
type: all[0].type,
|
|
572
|
+
get declarations() {
|
|
573
|
+
const root = all[0];
|
|
574
|
+
const result = { ...declarations(all) };
|
|
575
|
+
if (!result[root.type]?.startsWith(`class ${root.type}:`))
|
|
576
|
+
throw new TypeError('Subclass base must be a named class schema.');
|
|
577
|
+
const names = new Set([root.type]);
|
|
578
|
+
for (const child of all.slice(1)) {
|
|
579
|
+
if (names.has(child.type))
|
|
580
|
+
throw new TypeError('Duplicate subclass registration.');
|
|
581
|
+
names.add(child.type);
|
|
582
|
+
const text = result[child.type];
|
|
583
|
+
if (!text?.startsWith(`class ${child.type}:`))
|
|
584
|
+
throw new TypeError('Subtype must be a named class schema.');
|
|
585
|
+
result[child.type] = text.replace(`class ${child.type}:`, `class ${child.type}(${root.type}):`);
|
|
586
|
+
}
|
|
587
|
+
return Object.freeze(result);
|
|
588
|
+
}, get exampleStyles() { return exampleStyles(all); }, references: references(all),
|
|
589
|
+
decode(value) { for (const c of all) {
|
|
590
|
+
try {
|
|
591
|
+
return c.decode(value);
|
|
592
|
+
}
|
|
593
|
+
catch { /* Try allowed subtype. */ }
|
|
594
|
+
} throw new TypeError('No allowed class matched.'); },
|
|
595
|
+
encode(value) { for (const c of [...all].reverse()) {
|
|
596
|
+
try {
|
|
597
|
+
return c.encode(value);
|
|
598
|
+
}
|
|
599
|
+
catch { /* Prefer specific subtype. */ }
|
|
600
|
+
} throw new TypeError('No allowed class matched.'); },
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
/** Arbitrary data literals only: no constructors or executable values. */
|
|
604
|
+
export const any = make({}, {
|
|
605
|
+
type: 'Any', declarations: {},
|
|
606
|
+
decode(value) {
|
|
607
|
+
if (value === UNKNOWN)
|
|
608
|
+
return UNKNOWN;
|
|
609
|
+
if (value instanceof Set)
|
|
610
|
+
return set().python.decode(value);
|
|
611
|
+
if (value instanceof FloatLiteral)
|
|
612
|
+
return value.value;
|
|
613
|
+
if (value === null || ['string', 'boolean'].includes(typeof value))
|
|
614
|
+
return value;
|
|
615
|
+
if (typeof value === 'number' && Number.isFinite(value) && (!Number.isInteger(value) || Number.isSafeInteger(value)))
|
|
616
|
+
return value;
|
|
617
|
+
if (Array.isArray(value))
|
|
618
|
+
return value.map(v => any.python.decode(v));
|
|
619
|
+
if (value && typeof value === 'object' && !(CALL in value))
|
|
620
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, any.python.decode(v)]));
|
|
621
|
+
throw new TypeError('Expected data literal.');
|
|
622
|
+
},
|
|
623
|
+
encode(value) {
|
|
624
|
+
if (value === UNKNOWN)
|
|
625
|
+
return 'UNKNOWN';
|
|
626
|
+
if (value instanceof Set)
|
|
627
|
+
return set().python.encode(value);
|
|
628
|
+
if (value === null)
|
|
629
|
+
return 'None';
|
|
630
|
+
if (typeof value === 'string')
|
|
631
|
+
return str.python.encode(value);
|
|
632
|
+
if (typeof value === 'boolean')
|
|
633
|
+
return bool.python.encode(value);
|
|
634
|
+
if (typeof value === 'number')
|
|
635
|
+
return (Number.isInteger(value) ? int : float).python.encode(value);
|
|
636
|
+
if (Array.isArray(value))
|
|
637
|
+
return '[' + value.map(v => any.python.encode(v)).join(', ') + ']';
|
|
638
|
+
if (value && typeof value === 'object' && [Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
639
|
+
return '{' + Object.entries(value).map(([k, v]) => `${str.python.encode(k)}: ${any.python.encode(v)}`).join(', ') + '}';
|
|
640
|
+
throw new TypeError('Expected data literal.');
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
/** Named forward reference. Declare the target before rendering/parsing the schema. */
|
|
644
|
+
export function lazy(name, resolve) {
|
|
645
|
+
if (!identifier.test(name) || reserved.has(name))
|
|
646
|
+
throw new TypeError('Invalid lazy schema name.');
|
|
647
|
+
let rendering = false, renderingStyles = false;
|
|
648
|
+
const target = () => { const c = codec(resolve()); if (c.type !== name)
|
|
649
|
+
throw new TypeError('Lazy schema name mismatch.'); return c; };
|
|
650
|
+
return make({ $ref: `#/$defs/${name}` }, {
|
|
651
|
+
type: name, references: { [name]: resolve },
|
|
652
|
+
get exampleStyles() {
|
|
653
|
+
if (renderingStyles)
|
|
654
|
+
return {};
|
|
655
|
+
renderingStyles = true;
|
|
656
|
+
try {
|
|
657
|
+
return target().exampleStyles ?? {};
|
|
658
|
+
}
|
|
659
|
+
finally {
|
|
660
|
+
renderingStyles = false;
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
get declarations() {
|
|
664
|
+
if (rendering)
|
|
665
|
+
return {};
|
|
666
|
+
rendering = true;
|
|
667
|
+
try {
|
|
668
|
+
return target().declarations;
|
|
669
|
+
}
|
|
670
|
+
finally {
|
|
671
|
+
rendering = false;
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
decode(value) { return target().decode(value); }, encode(value) { return target().encode(value); },
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
/** Opt into UNKNOWN without silently widening all typed query results. */
|
|
678
|
+
export function allowUnknown(schema) {
|
|
679
|
+
const c = codec(schema), resolved = resolveSchema(schema);
|
|
680
|
+
// UNKNOWN has no JSON representation; the underlying JSON contract is unchanged.
|
|
681
|
+
return make(rawSchemas.get(resolved) ?? resolved.jsonSchema, {
|
|
682
|
+
type: c.type, get declarations() { return c.declarations; }, get exampleStyles() { return exampleStyles([c]); }, references: references([c]),
|
|
683
|
+
decode(value) { return value === UNKNOWN ? UNKNOWN : c.decode(value); },
|
|
684
|
+
encode(value) { return value === UNKNOWN ? 'UNKNOWN' : c.encode(value); },
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
function setElement(value) {
|
|
688
|
+
value = unbox(value);
|
|
689
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
690
|
+
return value;
|
|
691
|
+
if (typeof value === 'number' && Number.isFinite(value) && (!Number.isInteger(value) || Number.isSafeInteger(value)))
|
|
692
|
+
return value;
|
|
693
|
+
throw new TypeError('Sets support finite, safe primitive elements only.');
|
|
694
|
+
}
|
|
695
|
+
/** Primitive-valued sets. An explicit item schema adds local element validation. */
|
|
696
|
+
export function set(item) {
|
|
697
|
+
const c = item ? codec(item) : any.python;
|
|
698
|
+
const convert = (values) => {
|
|
699
|
+
const result = new Set(), seen = new Set();
|
|
700
|
+
for (const value of values) {
|
|
701
|
+
const decoded = c.decode(value);
|
|
702
|
+
setElement(decoded);
|
|
703
|
+
// Python treats True/1 and False/0 as equal keys. Keep the first representative.
|
|
704
|
+
const key = typeof decoded === 'boolean' ? Number(decoded) : decoded;
|
|
705
|
+
if (!seen.has(key)) {
|
|
706
|
+
seen.add(key);
|
|
707
|
+
result.add(decoded);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return result;
|
|
711
|
+
};
|
|
712
|
+
const resolved = item ? resolveSchema(item) : undefined;
|
|
713
|
+
return make({ type: 'array', uniqueItems: true, items: resolved ? (rawSchemas.get(resolved) ?? resolved.jsonSchema) : {} }, {
|
|
714
|
+
type: 'set', get declarations() { return c.declarations; }, get exampleStyles() { return exampleStyles([c]); }, references: references([c]),
|
|
715
|
+
decode(value) {
|
|
716
|
+
if (!(value instanceof Set) && (!Array.isArray(value) || LIST in value || TUPLE in value))
|
|
717
|
+
throw new TypeError('Expected set.');
|
|
718
|
+
return convert(value);
|
|
719
|
+
},
|
|
720
|
+
encode(value) {
|
|
721
|
+
if (!(value instanceof Set))
|
|
722
|
+
throw new TypeError('Expected Set instance.');
|
|
723
|
+
const encoded = [...convert(value)].map(v => c.encode(v));
|
|
724
|
+
return encoded.length ? '{' + encoded.join(', ') + '}' : 'set()';
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
//# sourceMappingURL=python.js.map
|