deepcodex 0.1.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 (43) hide show
  1. package/.codex-plugin/plugin.json +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +178 -0
  4. package/bin/opencodex.js +49 -0
  5. package/config/desktop.json +9 -0
  6. package/config/pilot.json +16 -0
  7. package/config/worker.json +78 -0
  8. package/node_modules/smol-toml/LICENSE +24 -0
  9. package/node_modules/smol-toml/README.md +418 -0
  10. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  11. package/node_modules/smol-toml/dist/date.js +127 -0
  12. package/node_modules/smol-toml/dist/error.d.ts +38 -0
  13. package/node_modules/smol-toml/dist/error.js +63 -0
  14. package/node_modules/smol-toml/dist/extract.js +69 -0
  15. package/node_modules/smol-toml/dist/index.cjs +734 -0
  16. package/node_modules/smol-toml/dist/index.d.ts +43 -0
  17. package/node_modules/smol-toml/dist/index.js +33 -0
  18. package/node_modules/smol-toml/dist/parse.d.ts +36 -0
  19. package/node_modules/smol-toml/dist/parse.js +149 -0
  20. package/node_modules/smol-toml/dist/primitive.js +238 -0
  21. package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
  22. package/node_modules/smol-toml/dist/stringify.js +181 -0
  23. package/node_modules/smol-toml/dist/struct.js +179 -0
  24. package/node_modules/smol-toml/dist/util.d.ts +38 -0
  25. package/node_modules/smol-toml/dist/util.js +89 -0
  26. package/node_modules/smol-toml/package.json +68 -0
  27. package/package.json +47 -0
  28. package/prompts/worker.md +20 -0
  29. package/scripts/credentials.js +102 -0
  30. package/scripts/desktop.js +199 -0
  31. package/scripts/pilot-router.js +241 -0
  32. package/scripts/pilot.js +242 -0
  33. package/scripts/toml.js +6 -0
  34. package/scripts/worker.js +637 -0
  35. package/skills/delegate-flash/SKILL.md +63 -0
  36. package/vendor/codex-router/LICENSE +21 -0
  37. package/vendor/codex-router/deepseek-responses.js +55 -0
  38. package/vendor/codex-router/json-number-rewrite.js +58 -0
  39. package/vendor/codex-router/namespace-relay.js +4294 -0
  40. package/vendor/codex-router/sse-prefix.js +115 -0
  41. package/vendor/codex-router/subagent-completion.js +261 -0
  42. package/vendor/codex-router/tool-arguments.js +111 -0
  43. package/vendor/codex-router/tool-schema-root.js +1008 -0
@@ -0,0 +1,1008 @@
1
+ // Vendored from duolahypercho/codex-router at 63ec1f3602c28f2a28ccb7e9edaf7b4f7d191c6c.
2
+ // Source: src/tool-schema-root.js; MIT license in LICENSE.
3
+ import { isDeepStrictEqual } from "node:util";
4
+
5
+ // xAI rejects any tool whose parameter schema does not have an object at the
6
+ // root: "tool parameter root must be an object type (root schema is an
7
+ // anyOf/oneOf union with a non-object branch)". The rejection fails the whole
8
+ // request, not the one tool, so a single union-rooted definition makes every
9
+ // turn on that provider a 400.
10
+ //
11
+ // Codex ships exactly such a tool: `codex_app__automation_update` roots its
12
+ // schema in a `oneOf` over the view/create/update/delete shapes. The router
13
+ // relays the app toolset to routed providers verbatim, which is how a Grok
14
+ // session that never touches automations still dies on its first message.
15
+ //
16
+ // Flattening keeps the tool callable: the branches are merged into one object
17
+ // so the model still sees every field it may send, with `required` narrowed to
18
+ // the fields every branch demands (usually none, because the branches are
19
+ // alternatives). Validation of which combination is legal stays where it
20
+ // already was -- the Codex app executes these calls and checks its own
21
+ // arguments.
22
+
23
+ const MAX_DEPTH = 8;
24
+
25
+ function isPlainObject(value) {
26
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27
+ }
28
+
29
+ // Resolves only local URI-fragment JSON Pointers: `#` and `#/...`. RFC 6901
30
+ // fragment decoding happens before `~1` / `~0` token decoding. Object own keys
31
+ // and canonical in-range array indexes are traversable; malformed fragments,
32
+ // anchors such as `#node`, and unsupported targets remain unresolved. The
33
+ // cycle repair deliberately does not infer semantics for `$dynamicRef` or
34
+ // `$recursiveRef` -- only an actual `$ref` crosses this boundary.
35
+ function resolveRef(ref, root) {
36
+ if (typeof ref !== "string" || !ref.startsWith("#")) return undefined;
37
+ let pointer;
38
+ try {
39
+ pointer = decodeURIComponent(ref.slice(1));
40
+ } catch {
41
+ return undefined;
42
+ }
43
+ if (pointer === "") return isPlainObject(root) ? root : undefined;
44
+ if (!pointer.startsWith("/")) return undefined;
45
+
46
+ let node = root;
47
+ for (const rawSegment of pointer.slice(1).split("/")) {
48
+ if (/~(?:[^01]|$)/.test(rawSegment)) return undefined;
49
+ const segment = rawSegment.replace(/~1/g, "/").replace(/~0/g, "~");
50
+ if (Array.isArray(node)) {
51
+ if (!/^(?:0|[1-9]\d*)$/.test(segment)) return undefined;
52
+ const index = Number(segment);
53
+ if (!Number.isSafeInteger(index) || index >= node.length || !(index in node)) {
54
+ return undefined;
55
+ }
56
+ node = node[index];
57
+ continue;
58
+ }
59
+ if (!isPlainObject(node) || !Object.hasOwn(node, segment)) return undefined;
60
+ node = node[segment];
61
+ }
62
+ return isPlainObject(node) ? node : undefined;
63
+ }
64
+
65
+ // OpenCode's Responses-compatible surfaces reject recursive local refs in a
66
+ // tool schema before the model sees the request. Keep definitions and every
67
+ // acyclic, boolean, or unresolved ref intact: expanding a shared ref DAG can
68
+ // grow exponentially, while deleting `$defs` leaves those refs dangling.
69
+ //
70
+ // A graph DFS marks only ref occurrences whose targets are still on the active
71
+ // stack. It follows only JSON Schema keywords that actually contain schemas;
72
+ // `$ref` strings inside const/default/examples/enum objects are literal data.
73
+ // Removing the marked back edges makes the reference graph acyclic. The second
74
+ // pass clones once and removes `$ref` only from the marked occurrence,
75
+ // preserving any sibling constraints. Both passes are iterative so a valid
76
+ // deeply nested tool schema cannot exhaust the JavaScript call stack. Schemas
77
+ // with no cycle keep identity.
78
+ const REF_SCHEMA_MAP_KEYWORDS = [
79
+ "$defs",
80
+ "definitions",
81
+ "properties",
82
+ "patternProperties",
83
+ "dependentSchemas",
84
+ ];
85
+ const REF_SCHEMA_ARRAY_KEYWORDS = ["allOf", "anyOf", "oneOf", "prefixItems"];
86
+ const REF_SCHEMA_CHILD_KEYWORDS = [
87
+ "additionalItems",
88
+ "additionalProperties",
89
+ "contains",
90
+ "contentSchema",
91
+ "else",
92
+ "if",
93
+ "items",
94
+ "not",
95
+ "propertyNames",
96
+ "then",
97
+ "unevaluatedItems",
98
+ "unevaluatedProperties",
99
+ ];
100
+
101
+ function schemaEdges(node, root) {
102
+ const edges = [];
103
+ const resolved = resolveRef(node.$ref, root);
104
+ if (isPlainObject(resolved)) edges.push({ node: resolved, ref: true });
105
+
106
+ for (const keyword of REF_SCHEMA_MAP_KEYWORDS) {
107
+ const schemas = node[keyword];
108
+ if (!isPlainObject(schemas)) continue;
109
+ for (const schema of Object.values(schemas)) {
110
+ if (isPlainObject(schema)) edges.push({ node: schema, ref: false });
111
+ }
112
+ }
113
+ for (const keyword of REF_SCHEMA_ARRAY_KEYWORDS) {
114
+ const schemas = node[keyword];
115
+ if (!Array.isArray(schemas)) continue;
116
+ for (const schema of schemas) {
117
+ if (isPlainObject(schema)) edges.push({ node: schema, ref: false });
118
+ }
119
+ }
120
+ for (const keyword of REF_SCHEMA_CHILD_KEYWORDS) {
121
+ const schema = node[keyword];
122
+ if (Array.isArray(schema)) {
123
+ // Drafts before 2020-12 allowed tuple schemas directly under `items`.
124
+ for (const entry of schema) {
125
+ if (isPlainObject(entry)) edges.push({ node: entry, ref: false });
126
+ }
127
+ } else if (isPlainObject(schema)) {
128
+ edges.push({ node: schema, ref: false });
129
+ }
130
+ }
131
+ // Draft-07 `dependencies` mixes property-name arrays with schema values.
132
+ const dependencies = node.dependencies;
133
+ if (isPlainObject(dependencies)) {
134
+ for (const schema of Object.values(dependencies)) {
135
+ if (isPlainObject(schema)) edges.push({ node: schema, ref: false });
136
+ }
137
+ }
138
+ return edges;
139
+ }
140
+
141
+ function cycleClosingLocalRefs(schema) {
142
+ const state = new WeakMap();
143
+ const closing = new WeakSet();
144
+ let count = 0;
145
+ state.set(schema, 1);
146
+ const stack = [{ node: schema, edges: schemaEdges(schema, schema), index: 0 }];
147
+ while (stack.length) {
148
+ const frame = stack.at(-1);
149
+ if (frame.index >= frame.edges.length) {
150
+ state.set(frame.node, 2);
151
+ stack.pop();
152
+ continue;
153
+ }
154
+ const edge = frame.edges[frame.index];
155
+ frame.index += 1;
156
+ const targetState = state.get(edge.node);
157
+ if (targetState === 1) {
158
+ if (edge.ref && !closing.has(frame.node)) {
159
+ closing.add(frame.node);
160
+ count += 1;
161
+ }
162
+ continue;
163
+ }
164
+ if (targetState === 2) continue;
165
+ state.set(edge.node, 1);
166
+ stack.push({ node: edge.node, edges: schemaEdges(edge.node, schema), index: 0 });
167
+ }
168
+ return { closing, count };
169
+ }
170
+
171
+ // The type a blanked cycle-closing `$ref` was declaring, read out of the target
172
+ // it named rather than inferred from context. A definition that is a pure alias
173
+ // for another is followed, with visited pointers tracked so a `$defs` cycle made
174
+ // only of references terminates.
175
+ function closingRefType(ref, root) {
176
+ const seen = new Set();
177
+ let node = resolveRef(ref, root);
178
+ while (isPlainObject(node) && !("type" in node) && typeof node.$ref === "string") {
179
+ if (seen.has(node.$ref)) return undefined;
180
+ seen.add(node.$ref);
181
+ node = resolveRef(node.$ref, root);
182
+ }
183
+ if (!isPlainObject(node)) return undefined;
184
+ const type = node.type;
185
+ if (typeof type === "string") return type;
186
+ if (Array.isArray(type) && type.length && type.every((entry) => typeof entry === "string")) {
187
+ return [...type];
188
+ }
189
+ return undefined;
190
+ }
191
+
192
+ function cloneWithoutClosingRefs(root, closing, keepTypes) {
193
+ const clones = new WeakMap();
194
+ const rootCopy = {};
195
+ clones.set(root, rootCopy);
196
+ const stack = [{ source: root, target: rootCopy }];
197
+ while (stack.length) {
198
+ const { source, target } = stack.pop();
199
+ const entries = Array.isArray(source)
200
+ ? source.map((value, index) => [index, value])
201
+ : Object.entries(source);
202
+ let blankedRef;
203
+ for (const [key, value] of entries) {
204
+ if (key === "$ref" && closing.has(source)) {
205
+ blankedRef = value;
206
+ continue;
207
+ }
208
+ if (!Array.isArray(value) && !isPlainObject(value)) {
209
+ target[key] = value;
210
+ continue;
211
+ }
212
+ let copy = clones.get(value);
213
+ if (!copy) {
214
+ copy = Array.isArray(value) ? [] : {};
215
+ clones.set(value, copy);
216
+ stack.push({ source: value, target: copy });
217
+ }
218
+ target[key] = copy;
219
+ }
220
+ // Infer from the original siblings: nested clones are not populated yet.
221
+ // A mixed enum/union may imply no single type, so do not fall back to the
222
+ // reference target when any type-bearing sibling is present.
223
+ if (keepTypes && blankedRef !== undefined && !("type" in source)) {
224
+ const { $ref, ...siblings } = source;
225
+ const hasTypeKeywords = ["enum", "const", "items", "prefixItems",
226
+ "properties", "required", "patternProperties", "anyOf", "oneOf", "allOf"]
227
+ .some((key) => key in siblings);
228
+ const recovered = hasTypeKeywords
229
+ ? inferredType(siblings)
230
+ : closingRefType(blankedRef, root);
231
+ if (recovered !== undefined) target.type = recovered;
232
+ }
233
+ }
234
+ return rootCopy;
235
+ }
236
+
237
+ export function nonRecursiveToolSchema(schema, options = {}) {
238
+ const { keepBlankedTypes = false } = options ?? {};
239
+ if (!isPlainObject(schema)) return schema;
240
+ const { closing, count } = cycleClosingLocalRefs(schema);
241
+ if (!count) return schema;
242
+ return cloneWithoutClosingRefs(schema, closing, keepBlankedTypes);
243
+ }
244
+
245
+ // Some strict upstream JSON-Schema validators reject Codex's private
246
+ // `encrypted` annotation. It is metadata on a schema node, not a JSON-Schema
247
+ // keyword and not the same thing as a user property whose name is
248
+ // "encrypted". Walk only positions that JSON Schema defines as child schemas;
249
+ // never recurse into const/default/examples/enum or arbitrary extension data.
250
+ //
251
+ // Copy-on-write keeps an ordinary schema byte-shape identical. The depth cap
252
+ // makes a hostile hand-built object bounded; a node beyond it is left intact,
253
+ // which fails closed at the strict upstream instead of broadening the schema.
254
+ const MAX_ANNOTATION_DEPTH = 32;
255
+
256
+ export function stripCodexEncryptedSchemaAnnotation(schema) {
257
+ const active = new WeakSet();
258
+
259
+ const visit = (node, depth) => {
260
+ if (!isPlainObject(node) || depth > MAX_ANNOTATION_DEPTH || active.has(node)) return node;
261
+ active.add(node);
262
+ let next = node;
263
+ const replace = (key, value) => {
264
+ if (next === node) next = { ...node };
265
+ next[key] = value;
266
+ };
267
+
268
+ if (Object.hasOwn(node, "encrypted")) {
269
+ const { encrypted: _annotation, ...withoutAnnotation } = node;
270
+ next = withoutAnnotation;
271
+ }
272
+
273
+ for (const keyword of [...REF_SCHEMA_MAP_KEYWORDS, "dependencies"]) {
274
+ const schemas = node[keyword];
275
+ if (!isPlainObject(schemas)) continue;
276
+ let changed = false;
277
+ const rewritten = { ...schemas };
278
+ for (const [name, child] of Object.entries(schemas)) {
279
+ if (!isPlainObject(child)) continue;
280
+ const repaired = visit(child, depth + 1);
281
+ if (repaired !== child) {
282
+ rewritten[name] = repaired;
283
+ changed = true;
284
+ }
285
+ }
286
+ if (changed) replace(keyword, rewritten);
287
+ }
288
+
289
+ for (const keyword of REF_SCHEMA_ARRAY_KEYWORDS) {
290
+ const schemas = node[keyword];
291
+ if (!Array.isArray(schemas)) continue;
292
+ let changed = false;
293
+ const rewritten = schemas.map((child) => {
294
+ if (!isPlainObject(child)) return child;
295
+ const repaired = visit(child, depth + 1);
296
+ if (repaired !== child) changed = true;
297
+ return repaired;
298
+ });
299
+ if (changed) replace(keyword, rewritten);
300
+ }
301
+
302
+ for (const keyword of REF_SCHEMA_CHILD_KEYWORDS) {
303
+ const child = node[keyword];
304
+ if (Array.isArray(child)) {
305
+ let changed = false;
306
+ const rewritten = child.map((entry) => {
307
+ if (!isPlainObject(entry)) return entry;
308
+ const repaired = visit(entry, depth + 1);
309
+ if (repaired !== entry) changed = true;
310
+ return repaired;
311
+ });
312
+ if (changed) replace(keyword, rewritten);
313
+ } else if (isPlainObject(child)) {
314
+ const repaired = visit(child, depth + 1);
315
+ if (repaired !== child) replace(keyword, repaired);
316
+ }
317
+ }
318
+
319
+ active.delete(node);
320
+ return next;
321
+ };
322
+
323
+ return visit(schema, 0);
324
+ }
325
+
326
+ // Moonshot validates every `$ref` a tool schema carries. It accepts only pure
327
+ // pointers into `#/$defs/`, rejecting the whole request -- not the one tool --
328
+ // over other pointers and over a `$ref` that carries sibling keywords. Codex App
329
+ // connector tools break the first rule routinely: Wego `_flights_search` points
330
+ // one property at a *sibling* property,
331
+ // `#/properties/filters/properties/priceRange`, so a kimi session that never
332
+ // searches a flight still dies on its first message (issue #353). Codex's
333
+ // zod-generated dynamic tools break the second when a `$defs` entry combines a
334
+ // reference with `type`, `format`, or validation constraints.
335
+ //
336
+ // Inlining replaces such a node with its resolved target merged under the
337
+ // node's own siblings. Nothing is invented: the target is the schema the client
338
+ // itself pointed at, and a `description` or `default` declared beside the `$ref`
339
+ // still wins over whatever the target says. Pure `#/$defs/` pointers are left
340
+ // exactly as they are -- they are the form Moonshot asks for -- while a
341
+ // definition reference carrying siblings is expanded only on a route that asks
342
+ // for this strict validation flavor.
343
+ //
344
+ // Three bounds keep the walk finite. `seen` holds the refs on the current
345
+ // expansion path, so a self-referential or mutually recursive schema stops at
346
+ // the edge that would close the cycle and keeps that one `$ref` rather than
347
+ // expanding forever. MAX_DEPTH caps how many ref hops a single path may take.
348
+ // MAX_INLINE_DEPTH caps structural nesting so a pathological schema cannot
349
+ // exhaust the JavaScript call stack.
350
+ //
351
+ // An unresolvable pointer -- an anchor, a dangling path, a target this module
352
+ // cannot traverse -- is left alone. Guessing at it or deleting it would change
353
+ // what the tool accepts, and the client may well have meant something the
354
+ // upstream resolves for itself.
355
+ //
356
+ // Expanding a shared ref DAG can grow exponentially, which is why the cycle
357
+ // repair above deliberately does not do it. Two budgets make it affordable
358
+ // here: expansions are counted while walking, and the finished copy is measured
359
+ // once. Exceeding either returns the *original* schema, so the worst case is
360
+ // the rejection this repair exists to avoid rather than a multi-megabyte tool
361
+ // list. Copy-on-write throughout: a schema with no foreign ref keeps identity,
362
+ // and the client's object is never mutated.
363
+ const DEFS_REF_PREFIX = "#/$defs/";
364
+ const REF_OVERRIDE_ANNOTATIONS = new Set([
365
+ "$comment",
366
+ "default",
367
+ "deprecated",
368
+ "description",
369
+ "examples",
370
+ "readOnly",
371
+ "title",
372
+ "writeOnly",
373
+ ]);
374
+ const MAX_INLINE_DEPTH = 32;
375
+ const MAX_INLINE_EXPANSIONS = 512;
376
+ const MAX_INLINE_BYTES = 256 * 1024;
377
+
378
+ function inlineChildRefs(node, root, state, seen, depth, refDepth, inlineDefsWithSiblings) {
379
+ let next = node;
380
+ const replace = (key, value) => {
381
+ if (next === node) next = { ...node };
382
+ next[key] = value;
383
+ };
384
+ const inlineChild = (schema) =>
385
+ isPlainObject(schema)
386
+ ? inlineNodeRefs(
387
+ schema,
388
+ root,
389
+ state,
390
+ seen,
391
+ depth + 1,
392
+ refDepth,
393
+ inlineDefsWithSiblings,
394
+ )
395
+ : schema;
396
+
397
+ // `dependencies` is draft-07's mixed map: array values list property names
398
+ // rather than schemas, and `inlineChild` passes those through untouched.
399
+ for (const keyword of [...REF_SCHEMA_MAP_KEYWORDS, "dependencies"]) {
400
+ const schemas = node[keyword];
401
+ if (!isPlainObject(schemas)) continue;
402
+ let changed = false;
403
+ const rewritten = {};
404
+ for (const [name, schema] of Object.entries(schemas)) {
405
+ const inlined = inlineChild(schema);
406
+ if (inlined !== schema) changed = true;
407
+ rewritten[name] = inlined;
408
+ }
409
+ if (changed) replace(keyword, rewritten);
410
+ }
411
+
412
+ for (const keyword of [...REF_SCHEMA_ARRAY_KEYWORDS, ...REF_SCHEMA_CHILD_KEYWORDS]) {
413
+ const schemas = node[keyword];
414
+ if (Array.isArray(schemas)) {
415
+ let changed = false;
416
+ const rewritten = schemas.map((schema) => {
417
+ const inlined = inlineChild(schema);
418
+ if (inlined !== schema) changed = true;
419
+ return inlined;
420
+ });
421
+ if (changed) replace(keyword, rewritten);
422
+ continue;
423
+ }
424
+ if (!isPlainObject(schemas)) continue;
425
+ const inlined = inlineChild(schemas);
426
+ if (inlined !== schemas) replace(keyword, inlined);
427
+ }
428
+
429
+ return next;
430
+ }
431
+
432
+ function inlineNodeRefs(
433
+ node,
434
+ root,
435
+ state,
436
+ seen,
437
+ depth,
438
+ refDepth,
439
+ inlineDefsWithSiblings,
440
+ resolveDefsAliases = false,
441
+ ) {
442
+ if (!isPlainObject(node) || depth > MAX_INLINE_DEPTH || state.exceeded) return node;
443
+ const ref = node.$ref;
444
+ const hasSiblings = Object.keys(node).some((key) => key !== "$ref");
445
+ const isDefsRef = typeof ref === "string" && ref.startsWith(DEFS_REF_PREFIX);
446
+ const foreignRef = typeof ref === "string" && !ref.startsWith(DEFS_REF_PREFIX);
447
+ const defsRefWithSiblings =
448
+ inlineDefsWithSiblings &&
449
+ isDefsRef &&
450
+ hasSiblings;
451
+ // A decorated definition can point through a chain of pure aliases. Resolve
452
+ // those only while expanding that decorated reference; an ordinary pure
453
+ // definition elsewhere remains the valid untouched form Moonshot accepts.
454
+ const pureDefsAlias = inlineDefsWithSiblings && resolveDefsAliases && isDefsRef;
455
+ const expandable =
456
+ (foreignRef || defsRefWithSiblings || pureDefsAlias) &&
457
+ !seen.has(ref) &&
458
+ refDepth < MAX_DEPTH;
459
+ const target = expandable ? resolveRef(ref, root) : undefined;
460
+ if (!isPlainObject(target)) {
461
+ return inlineChildRefs(
462
+ node,
463
+ root,
464
+ state,
465
+ seen,
466
+ depth,
467
+ refDepth,
468
+ inlineDefsWithSiblings,
469
+ );
470
+ }
471
+
472
+ state.expansions += 1;
473
+ if (state.expansions > MAX_INLINE_EXPANSIONS) {
474
+ state.exceeded = true;
475
+ return node;
476
+ }
477
+ seen.add(ref);
478
+ // The target takes the node's place rather than nesting inside it, so the
479
+ // structural depth does not grow; the ref hop is what is charged.
480
+ const expanded = inlineNodeRefs(
481
+ target,
482
+ root,
483
+ state,
484
+ seen,
485
+ depth,
486
+ refDepth + 1,
487
+ inlineDefsWithSiblings,
488
+ isDefsRef,
489
+ );
490
+ seen.delete(ref);
491
+ if (state.exceeded) return node;
492
+ state.inlined = true;
493
+ const { $ref: _inlined, ...siblings } = node;
494
+ // Only the siblings still need walking: the target came back already inlined,
495
+ // and re-walking it would re-expand the very edges `seen` just protected.
496
+ const rewrittenSiblings = inlineChildRefs(
497
+ siblings,
498
+ root,
499
+ state,
500
+ seen,
501
+ depth,
502
+ refDepth,
503
+ inlineDefsWithSiblings,
504
+ );
505
+ const strictDefsRef = inlineDefsWithSiblings && isDefsRef;
506
+ // A definition expansion that still carries its own reference reached a
507
+ // cycle. Removing this node's siblings would weaken the original schema, so
508
+ // keep the decorated node intact and let the provider fail closed if it
509
+ // cannot represent that conjunction.
510
+ if (strictDefsRef && expanded.$ref !== undefined) return node;
511
+ if (foreignRef) {
512
+ const conflicts = Object.keys(rewrittenSiblings).some((key) => (
513
+ !REF_OVERRIDE_ANNOTATIONS.has(key) &&
514
+ Object.hasOwn(expanded, key) &&
515
+ !isDeepStrictEqual(rewrittenSiblings[key], expanded[key])
516
+ ));
517
+ // `$ref` siblings are conjunctive. Object spread is lossless when the
518
+ // assertions are distinct or identical, but a different value for the
519
+ // same assertion would overwrite one side and can widen the schema.
520
+ if (conflicts) return node;
521
+ }
522
+ if (strictDefsRef) {
523
+ const conflicts = Object.keys(rewrittenSiblings).some((key) => (
524
+ Object.hasOwn(expanded, key) && !isDeepStrictEqual(rewrittenSiblings[key], expanded[key])
525
+ ));
526
+ // Overwriting either assertion would weaken one side of the conjunction.
527
+ // Keep the original decorated node instead; this malformed case has no
528
+ // lossless expansion.
529
+ if (conflicts) return node;
530
+ }
531
+ return { ...expanded, ...rewrittenSiblings };
532
+ }
533
+
534
+ function jsonByteLength(value) {
535
+ try {
536
+ return Buffer.byteLength(JSON.stringify(value) ?? "");
537
+ } catch {
538
+ return Number.POSITIVE_INFINITY;
539
+ }
540
+ }
541
+
542
+ export function inlineForeignRefs(schema) {
543
+ if (!isPlainObject(schema)) return schema;
544
+ const state = { expansions: 0, exceeded: false, inlined: false };
545
+ const inlined = inlineNodeRefs(schema, schema, state, new Set(), 0, 0, true);
546
+ if (state.exceeded || !state.inlined || inlined === schema) return schema;
547
+ if (jsonByteLength(inlined) > MAX_INLINE_BYTES) return schema;
548
+ return inlined;
549
+ }
550
+
551
+ // Zillow's connector stores MinMaxInt in request.$defs but refers to it as
552
+ // #/$defs/MinMaxInt. Repair missing direct root names using their enclosing
553
+ // definitions, without reinterpreting valid root refs or other pointer forms.
554
+ // This is a compatibility heuristic for malformed schemas, not JSON Schema
555
+ // reference resolution. Resource boundaries, cycles and exhausted budgets
556
+ // return the original schema. Only annotation siblings may be merged.
557
+ export function inlineDanglingNestedDefsRefs(schema) {
558
+ if (!isPlainObject(schema)) return schema;
559
+ const state = { expansions: 0, nodes: 0, bytes: jsonByteLength(schema), exceeded: false, inlined: false };
560
+ if (state.bytes > MAX_INLINE_BYTES) return schema;
561
+ const activeTargets = new WeakSet();
562
+ const contexts = new WeakMap();
563
+
564
+ // Index lexical scopes before moving anything. A borrowed definition's own
565
+ // refs must not bind to same-named definitions at the expansion site.
566
+ const index = (node, scopes, depth) => {
567
+ if (state.exceeded) return;
568
+ state.nodes += 1;
569
+ if (depth > MAX_INLINE_DEPTH || state.nodes > 8192 || contexts.has(node)) {
570
+ state.exceeded = true;
571
+ return;
572
+ }
573
+ if (
574
+ (node !== schema && ["$id", "id", "$schema"].some((key) => Object.hasOwn(node, key))) ||
575
+ ["$anchor", "$dynamicAnchor", "$dynamicRef", "$recursiveAnchor", "$recursiveRef"]
576
+ .some((key) => Object.hasOwn(node, key))
577
+ ) {
578
+ state.exceeded = true;
579
+ return;
580
+ }
581
+ const nextScopes = isPlainObject(node.$defs) ? [node.$defs, ...scopes] : scopes;
582
+ contexts.set(node, nextScopes);
583
+ // A null root yields only structural edges, never reference edges.
584
+ for (const edge of schemaEdges(node, null)) index(edge.node, nextScopes, depth + 1);
585
+ };
586
+ index(schema, [], 0);
587
+ if (state.exceeded) return schema;
588
+
589
+ const definitionName = (ref) => {
590
+ if (typeof ref !== "string") return undefined;
591
+ let decoded;
592
+ try { decoded = decodeURIComponent(ref); } catch { return undefined; }
593
+ const match = /^#\/\$defs\/([^/]+)$/.exec(decoded);
594
+ if (!match || /~(?:[^01]|$)/.test(match[1])) return undefined;
595
+ return match[1].replace(/~1/g, "/").replace(/~0/g, "~");
596
+ };
597
+
598
+ const visit = (node, depth) => {
599
+ if (!isPlainObject(node) || state.exceeded) return node;
600
+ if (depth > MAX_INLINE_DEPTH) {
601
+ state.exceeded = true;
602
+ return node;
603
+ }
604
+ const name = definitionName(node.$ref);
605
+
606
+ if (
607
+ name !== undefined &&
608
+ !(isPlainObject(schema.$defs) && Object.hasOwn(schema.$defs, name))
609
+ ) {
610
+ let target;
611
+ for (const defs of contexts.get(node) ?? []) {
612
+ if (!Object.hasOwn(defs, name)) continue;
613
+ target = defs[name];
614
+ break;
615
+ }
616
+ const { $ref: _ref, ...siblings } = node;
617
+ // Distinct validation keywords can interact (e.g. properties and
618
+ // additionalProperties). Even a conflict-free object spread is unsafe.
619
+ if (isPlainObject(target) && Object.keys(siblings).every((key) => REF_OVERRIDE_ANNOTATIONS.has(key))) {
620
+ if (activeTargets.has(target)) {
621
+ state.exceeded = true;
622
+ return node;
623
+ }
624
+ state.expansions += 1;
625
+ // Charge before expansion so repeated large targets cannot allocate a
626
+ // huge output before the final serialized-size check.
627
+ state.bytes += jsonByteLength(target);
628
+ if (state.expansions > MAX_INLINE_EXPANSIONS || state.bytes > MAX_INLINE_BYTES) {
629
+ state.exceeded = true;
630
+ return node;
631
+ }
632
+ activeTargets.add(target);
633
+ const expanded = visit(target, depth);
634
+ activeTargets.delete(target);
635
+ if (!state.exceeded) {
636
+ state.inlined = true;
637
+ return { ...expanded, ...siblings };
638
+ }
639
+ }
640
+ }
641
+
642
+ let next = node;
643
+ const replace = (key, value) => {
644
+ if (next === node) next = { ...node };
645
+ next[key] = value;
646
+ };
647
+ for (const keyword of [...REF_SCHEMA_MAP_KEYWORDS, "dependencies"]) {
648
+ const children = node[keyword];
649
+ if (!isPlainObject(children)) continue;
650
+ let changed = false;
651
+ const rewritten = { ...children };
652
+ for (const [name, child] of Object.entries(children)) {
653
+ if (!isPlainObject(child)) continue;
654
+ const repaired = visit(child, depth + 1);
655
+ if (repaired !== child) {
656
+ rewritten[name] = repaired;
657
+ changed = true;
658
+ }
659
+ }
660
+ if (changed) replace(keyword, rewritten);
661
+ }
662
+ for (const keyword of [...REF_SCHEMA_ARRAY_KEYWORDS, ...REF_SCHEMA_CHILD_KEYWORDS]) {
663
+ const children = node[keyword];
664
+ if (Array.isArray(children)) {
665
+ let changed = false;
666
+ const rewritten = children.map((child) => {
667
+ if (!isPlainObject(child)) return child;
668
+ const repaired = visit(child, depth + 1);
669
+ if (repaired !== child) changed = true;
670
+ return repaired;
671
+ });
672
+ if (changed) replace(keyword, rewritten);
673
+ } else if (isPlainObject(children)) {
674
+ const repaired = visit(children, depth + 1);
675
+ if (repaired !== children) replace(keyword, repaired);
676
+ }
677
+ }
678
+ return next;
679
+ };
680
+
681
+ const inlined = visit(schema, 0);
682
+ if (state.exceeded || !state.inlined || inlined === schema) return schema;
683
+ if (jsonByteLength(inlined) > MAX_INLINE_BYTES) return schema;
684
+ return inlined;
685
+ }
686
+
687
+ // Every object-typed leaf reachable from `schema` through unions and local
688
+ // refs. `seen` guards the self-referential `$defs` Codex generates.
689
+ function objectBranches(schema, root, seen, depth = 0) {
690
+ if (!isPlainObject(schema) || depth > MAX_DEPTH) return [];
691
+ if (typeof schema.$ref === "string") {
692
+ if (seen.has(schema.$ref)) return [];
693
+ seen.add(schema.$ref);
694
+ return objectBranches(resolveRef(schema.$ref, root), root, seen, depth + 1);
695
+ }
696
+ const branches = [];
697
+ for (const keyword of ["anyOf", "oneOf", "allOf"]) {
698
+ if (!Array.isArray(schema[keyword])) continue;
699
+ for (const branch of schema[keyword]) {
700
+ branches.push(...objectBranches(branch, root, seen, depth + 1));
701
+ }
702
+ }
703
+ if (schema.type === "object" || isPlainObject(schema.properties)) branches.push(schema);
704
+ return branches;
705
+ }
706
+
707
+ const UNION_KEYWORDS = ["anyOf", "oneOf", "allOf"];
708
+
709
+ function hasRootUnion(schema) {
710
+ return UNION_KEYWORDS.some((keyword) => Array.isArray(schema[keyword]));
711
+ }
712
+
713
+ // xAI's rule is about the root *keywords*, not the declared type: a schema may
714
+ // say `type: "object"` and still be rejected for carrying a `oneOf` beside it.
715
+ // Checking only `type`/`properties` here is what let the live client's
716
+ // `automation_update` -- which sends both -- through untouched.
717
+ //
718
+ // A declared type is read literally, which is why a nullable root is not an
719
+ // object root. `type: ["object", "null"]` is a legal JSON Schema object that
720
+ // also permits null, and xAI rejects it with the same
721
+ // `tool parameter root must be an object type` as a union -- verified against
722
+ // the live backend. Only the exact string passes; a declared type that is
723
+ // anything else sends the schema down the rewrite path, and the `properties`
724
+ // fallback is left for schemas that declare no type at all.
725
+ export function hasObjectRoot(schema) {
726
+ if (!isPlainObject(schema)) return false;
727
+ if (hasRootUnion(schema)) return false;
728
+ if (schema.type !== undefined) return schema.type === "object";
729
+ return isPlainObject(schema.properties);
730
+ }
731
+
732
+ // Returns `schema` unchanged when its root is already a plain object, so the
733
+ // common case costs one type check and no copy.
734
+ export function objectRootToolSchema(schema) {
735
+ if (!isPlainObject(schema)) return { type: "object", properties: {} };
736
+ if (hasObjectRoot(schema)) return schema;
737
+
738
+ const branches = objectBranches(schema, schema, new Set());
739
+ const properties = {};
740
+ // Root-level properties apply to every branch, so they win over branch
741
+ // definitions of the same name.
742
+ if (isPlainObject(schema.properties)) Object.assign(properties, schema.properties);
743
+ for (const branch of branches) {
744
+ if (!isPlainObject(branch.properties)) continue;
745
+ for (const [name, property] of Object.entries(branch.properties)) {
746
+ if (!(name in properties)) properties[name] = property;
747
+ }
748
+ }
749
+ // Required only where every branch requires it: a field the view branch
750
+ // demands is optional for the delete branch, and marking it required would
751
+ // reject calls the app accepts. Root-level requirements are separate -- they
752
+ // bind every branch, so they survive whatever the branches disagree about.
753
+ const rootRequired = Array.isArray(schema.required) ? schema.required : [];
754
+ const unionBranches = branches.filter((branch) => branch !== schema);
755
+ const shared = unionBranches.length
756
+ ? unionBranches
757
+ .map((branch) => (Array.isArray(branch.required) ? branch.required : []))
758
+ .reduce((left, right) => left.filter((name) => right.includes(name)))
759
+ : [];
760
+ const required = [...new Set([...rootRequired, ...shared])];
761
+
762
+ return {
763
+ ...(schema.$schema ? { $schema: schema.$schema } : {}),
764
+ ...(schema.$defs ? { $defs: schema.$defs } : {}),
765
+ ...(schema.definitions ? { definitions: schema.definitions } : {}),
766
+ ...(typeof schema.description === "string" ? { description: schema.description } : {}),
767
+ type: "object",
768
+ properties,
769
+ ...(required.length ? { required } : {}),
770
+ // The merged object cannot describe which branch a call belongs to, so it
771
+ // must not reject fields that only one branch declares. A root that was
772
+ // rewritten without merging anything -- a nullable `type: ["object","null"]`
773
+ // becoming plain `"object"` -- has no such ambiguity, so it keeps whatever
774
+ // it declared rather than being quietly opened up.
775
+ ...(unionBranches.length || schema.additionalProperties === undefined
776
+ ? { additionalProperties: true }
777
+ : { additionalProperties: schema.additionalProperties }),
778
+ };
779
+ }
780
+
781
+ // Moonshot validates every enum/const literal against the type its own node
782
+ // declares, and rejects the whole request -- not the one tool -- on a mismatch:
783
+ //
784
+ // tools.function.parameters is not a valid moonshot flavored json schema,
785
+ // details: <At path 'properties.appTaskLane.properties.enabled.enum':
786
+ // enum value (true) does not match any type in [string]>
787
+ //
788
+ // The contradiction is the client's: mergeCodexAppTools lets client-provided
789
+ // definitions win, so it cannot be repaired in the bundled snapshot. Drop the
790
+ // offending literal rather than coercing it. A literal that contradicts its own
791
+ // declared type could never validate, so dropping it cannot change a well-formed
792
+ // schema; coercing `true` to `"true"` would instead tell the model to send a
793
+ // value the app never asked for.
794
+
795
+ const MAX_LITERAL_DEPTH = 32;
796
+ const SCHEMA_MAP_KEYWORDS = ["properties", "patternProperties", "$defs", "definitions"];
797
+ const SCHEMA_LIST_KEYWORDS = ["anyOf", "oneOf", "allOf", "prefixItems"];
798
+ const SCHEMA_CHILD_KEYWORDS = [
799
+ "items",
800
+ "additionalProperties",
801
+ "contains",
802
+ "not",
803
+ "if",
804
+ "then",
805
+ "else",
806
+ "propertyNames",
807
+ ];
808
+
809
+ function jsonTypeOf(value) {
810
+ if (value === null) return "null";
811
+ if (Array.isArray(value)) return "array";
812
+ if (typeof value === "boolean") return "boolean";
813
+ if (typeof value === "string") return "string";
814
+ if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
815
+ if (typeof value === "object") return "object";
816
+ return undefined;
817
+ }
818
+
819
+ function declaredTypes(schema) {
820
+ if (typeof schema.type === "string") return [schema.type];
821
+ if (Array.isArray(schema.type)) return schema.type.filter((entry) => typeof entry === "string");
822
+ return [];
823
+ }
824
+
825
+ function matchesDeclaredType(value, types) {
826
+ const actual = jsonTypeOf(value);
827
+ if (actual === undefined) return false;
828
+ if (types.includes(actual)) return true;
829
+ // JSON Schema counts every integer as a number.
830
+ return actual === "integer" && types.includes("number");
831
+ }
832
+
833
+ // Returns `schema` by identity when nothing contradicts, so a clean toolset
834
+ // costs one walk and no copy, and the client's object is never mutated.
835
+ export function normalizeSchemaLiterals(schema, depth = 0) {
836
+ if (!isPlainObject(schema) || depth > MAX_LITERAL_DEPTH) return schema;
837
+ let next = schema;
838
+ const replace = (key, value) => {
839
+ if (next === schema) next = { ...schema };
840
+ if (value === undefined) delete next[key];
841
+ else next[key] = value;
842
+ };
843
+
844
+ const types = declaredTypes(schema);
845
+ if (types.length) {
846
+ if (Array.isArray(schema.enum)) {
847
+ const kept = schema.enum.filter((value) => matchesDeclaredType(value, types));
848
+ if (kept.length !== schema.enum.length) replace("enum", kept.length ? kept : undefined);
849
+ }
850
+ if ("const" in schema && !matchesDeclaredType(schema.const, types)) {
851
+ replace("const", undefined);
852
+ }
853
+ }
854
+
855
+ for (const keyword of SCHEMA_MAP_KEYWORDS) {
856
+ const node = schema[keyword];
857
+ if (!isPlainObject(node)) continue;
858
+ let changed = false;
859
+ const rewritten = {};
860
+ for (const [name, child] of Object.entries(node)) {
861
+ const sanitized = normalizeSchemaLiterals(child, depth + 1);
862
+ if (sanitized !== child) changed = true;
863
+ rewritten[name] = sanitized;
864
+ }
865
+ if (changed) replace(keyword, rewritten);
866
+ }
867
+
868
+ for (const keyword of [...SCHEMA_LIST_KEYWORDS, ...SCHEMA_CHILD_KEYWORDS]) {
869
+ const node = schema[keyword];
870
+ if (Array.isArray(node)) {
871
+ let changed = false;
872
+ const rewritten = node.map((child) => {
873
+ const sanitized = normalizeSchemaLiterals(child, depth + 1);
874
+ if (sanitized !== child) changed = true;
875
+ return sanitized;
876
+ });
877
+ if (changed) replace(keyword, rewritten);
878
+ continue;
879
+ }
880
+ if (!isPlainObject(node)) continue;
881
+ const sanitized = normalizeSchemaLiterals(node, depth + 1);
882
+ if (sanitized !== node) replace(keyword, sanitized);
883
+ }
884
+
885
+ return next;
886
+ }
887
+
888
+ // Moonshot's validator rejects a schema node that declares no `type` inside a
889
+ // union, naming it as "tools.function.parameters missing type in anyOf
890
+ // properties" (#641). Nothing else in the pipeline supplies one:
891
+ // `normalizeSchemaLiterals` only removes literals that contradict a type a node
892
+ // already declares. A nullable leaf written the ordinary way --
893
+ // `{"anyOf":[{"type":"string"},{"type":"null"}]}` -- therefore reaches Moonshot
894
+ // exactly as the client wrote it and loses the turn.
895
+ //
896
+ // Only declare a type the node already implies. Inferring one from `not`/`if`/
897
+ // `then`/`else`, or guessing for a `$ref` whose target carries the type, would
898
+ // narrow a schema the client meant to leave open, which is worse than the 400.
899
+ // Returns `schema` by identity when every node already says what it is.
900
+ function inferredType(schema) {
901
+ if ("type" in schema || "$ref" in schema) return undefined;
902
+ if ("properties" in schema || "required" in schema || "patternProperties" in schema) {
903
+ return "object";
904
+ }
905
+ if ("items" in schema || "prefixItems" in schema) return "array";
906
+ if (Array.isArray(schema.enum) && schema.enum.length) {
907
+ const types = [...new Set(schema.enum.map(jsonTypeOf))];
908
+ if (types.length === 1 && types[0] !== undefined) return types[0];
909
+ return undefined;
910
+ }
911
+ if ("const" in schema) return jsonTypeOf(schema.const);
912
+ // A union says what it is only when every branch does.
913
+ for (const keyword of ["anyOf", "oneOf"]) {
914
+ const branches = schema[keyword];
915
+ if (!Array.isArray(branches) || !branches.length) continue;
916
+ const types = [];
917
+ for (const branch of branches) {
918
+ if (!isPlainObject(branch)) return undefined;
919
+ const declared = declaredTypes(branch);
920
+ if (declared.length !== 1) return undefined;
921
+ if (!types.includes(declared[0])) types.push(declared[0]);
922
+ }
923
+ return types.length === 1 ? types[0] : types;
924
+ }
925
+ return undefined;
926
+ }
927
+
928
+ export function declareSchemaTypes(schema, depth = 0) {
929
+ if (!isPlainObject(schema) || depth > MAX_LITERAL_DEPTH) return schema;
930
+ let next = schema;
931
+ const replace = (key, value) => {
932
+ if (next === schema) next = { ...schema };
933
+ next[key] = value;
934
+ };
935
+
936
+ for (const keyword of SCHEMA_MAP_KEYWORDS) {
937
+ const node = schema[keyword];
938
+ if (!isPlainObject(node)) continue;
939
+ let changed = false;
940
+ const rewritten = {};
941
+ for (const [name, child] of Object.entries(node)) {
942
+ const declared = declareSchemaTypes(child, depth + 1);
943
+ if (declared !== child) changed = true;
944
+ rewritten[name] = declared;
945
+ }
946
+ if (changed) replace(keyword, rewritten);
947
+ }
948
+
949
+ for (const keyword of [...SCHEMA_LIST_KEYWORDS, ...SCHEMA_CHILD_KEYWORDS]) {
950
+ const node = schema[keyword];
951
+ if (Array.isArray(node)) {
952
+ let changed = false;
953
+ const rewritten = node.map((child) => {
954
+ const declared = declareSchemaTypes(child, depth + 1);
955
+ if (declared !== child) changed = true;
956
+ return declared;
957
+ });
958
+ if (changed) replace(keyword, rewritten);
959
+ continue;
960
+ }
961
+ if (!isPlainObject(node)) continue;
962
+ const declared = declareSchemaTypes(node, depth + 1);
963
+ if (declared !== node) replace(keyword, declared);
964
+ }
965
+
966
+ // After the children, so a union reads the types its branches just gained.
967
+ const inferred = inferredType(next);
968
+ if (inferred !== undefined) replace("type", inferred);
969
+ return next;
970
+ }
971
+
972
+ // The one provider-facing normalization: literals aligned with the type their
973
+ // own node declares, and a union root merged into a plain object. Returns
974
+ // `schema` unchanged when neither applies.
975
+ //
976
+ // Deliberately narrower than objectRootToolSchema alone. That function collapses
977
+ // *any* root it cannot recognize -- an array root, a bare `type: "string"`, an
978
+ // empty object -- into `{type:"object", properties:{}}`, discarding the real
979
+ // schema. That is the right trade for xAI, which rejects every non-object root
980
+ // outright. It is the wrong trade here: this runs on every namespace and MCP
981
+ // tool, where a server-defined schema that merely looks unusual would be
982
+ // silently replaced with one accepting anything. Only a union root is the
983
+ // documented strict-upstream rejection, so only a union root is rewritten.
984
+ // A root `type` array that offers "object" among others -- the nullable object
985
+ // root. Narrow on purpose: an array that cannot be an object at all is left
986
+ // alone, because collapsing it would replace a real schema with one accepting
987
+ // anything, which is the trade this function exists to avoid.
988
+ function hasNullableObjectRoot(schema) {
989
+ return Array.isArray(schema.type) && schema.type.includes("object");
990
+ }
991
+
992
+ export function providerToolSchema(schema) {
993
+ const normalized = normalizeSchemaLiterals(schema);
994
+ if (!isPlainObject(normalized)) return normalized;
995
+ // Two independent upstreams reject a nullable object root by name, which is
996
+ // what promotes it from "unusual" to documented alongside the union:
997
+ //
998
+ // xAI: tool parameter root must be an object type
999
+ // DeepSeek: schema must be a JSON Schema of 'type: "object"',
1000
+ // got 'type: ["object","null"]'
1001
+ //
1002
+ // Both were reproduced live. The repair is lossless here -- properties,
1003
+ // required and additionalProperties all survive -- because only the root's
1004
+ // own `null` alternative is dropped, and a tool call whose entire argument
1005
+ // object is null is not a call any of these providers can dispatch.
1006
+ if (!hasRootUnion(normalized) && !hasNullableObjectRoot(normalized)) return normalized;
1007
+ return objectRootToolSchema(normalized);
1008
+ }