@bitkyc08/opencodex 2.33.0 → 2.34.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/README.md +3 -3
- package/gui/dist/assets/index-C4TMRloX.js +112 -0
- package/gui/dist/assets/index-DQ-Ie18T.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +79 -2
- package/src/adapters/command-code.ts +40 -3
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/discovery.ts +60 -2
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +162 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +29 -6
- package/src/adapters/cursor/tool-definitions.ts +11 -2
- package/src/adapters/cursor/tool-result-normalize.ts +32 -1
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +189 -37
- package/src/adapters/google-antigravity-replay.ts +102 -23
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +115 -16
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +15 -0
- package/src/adapters/kiro-tools.ts +43 -15
- package/src/adapters/kiro.ts +54 -9
- package/src/adapters/openai-chat.ts +286 -242
- package/src/adapters/openai-responses.ts +334 -24
- package/src/adapters/tool-catalog-nudge.ts +2 -2
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/bridge.ts +67 -24
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/outbound.ts +10 -8
- package/src/cli/account-api.ts +32 -4
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account.ts +29 -5
- package/src/cli/alias.ts +66 -0
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +13 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/init.ts +1 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +13 -7
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/registry.ts +6 -1
- package/src/cli/v2.ts +34 -10
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +80 -6
- package/src/codex/auth-api.ts +46 -8
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/catalog/aggregation.ts +6 -0
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +16 -0
- package/src/codex/catalog/provider-fetch.ts +20 -3
- package/src/codex/catalog/sync.ts +127 -2
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +22 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +18 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +14 -1
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +107 -8
- package/src/combos/types.ts +10 -0
- package/src/compatibility/openai-responses.ts +33 -1
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +107 -9
- package/src/generated/compatibility-version.json +191 -139
- package/src/images/loop.ts +5 -4
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +11 -2
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/index.ts +57 -5
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/types.ts +15 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +160 -68
- package/src/providers/slug-codec.ts +52 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +34 -10
- package/src/responses/parser.ts +4 -0
- package/src/responses/schema.ts +5 -1
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +4 -0
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +77 -11
- package/src/server/lifecycle.ts +52 -1
- package/src/server/management/agent-settings-routes.ts +31 -15
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +27 -9
- package/src/server/management/context.ts +9 -0
- package/src/server/management/model-routes.ts +266 -0
- package/src/server/management/provider-routes.ts +6 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management-api.ts +2 -0
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +21 -0
- package/src/server/request-log.ts +41 -15
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/core.ts +494 -114
- package/src/server/responses/empty-completion-guard.ts +35 -0
- package/src/server/responses/fetch-helpers.ts +2 -1
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses-custom-tool-repair.ts +52 -7
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/types/config.ts +53 -0
- package/src/types/provider.ts +64 -0
- package/src/types/request.ts +11 -3
- package/src/types/tools.ts +7 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +3 -0
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-23-Lf7jR.js +0 -102
- package/gui/dist/assets/index-DxJMDyOr.css +0 -1
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
function isSchemaObject(value: unknown): value is Record<string, unknown> {
|
|
4
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isXaiSchemaTarget(provider: Pick<OcxProviderConfig, "baseUrl">): boolean {
|
|
8
|
+
try {
|
|
9
|
+
// Public api.x.ai accepts native root object unions. Only the Grok CLI proxy
|
|
10
|
+
// 400s on a root oneOf/anyOf, so flattening/omitting is scoped to that host.
|
|
11
|
+
return new URL(provider.baseUrl).hostname === "cli-chat-proxy.grok.com";
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A tool this proxy had to omit is still named by `tool_choice`; the caller maps this to a 400. */
|
|
18
|
+
export class XaiToolSchemaCompatibilityError extends Error {}
|
|
19
|
+
|
|
20
|
+
function stringRequiredFields(value: unknown): string[] {
|
|
21
|
+
return Array.isArray(value)
|
|
22
|
+
? value.filter((item): item is string => typeof item === "string")
|
|
23
|
+
: [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Variant keys the merger can keep. Anything else is refused, not silently dropped. */
|
|
27
|
+
const XAI_VARIANT_MERGE_KEYS = new Set([
|
|
28
|
+
"type",
|
|
29
|
+
"properties",
|
|
30
|
+
"required",
|
|
31
|
+
"additionalProperties",
|
|
32
|
+
"description",
|
|
33
|
+
"title",
|
|
34
|
+
"$comment",
|
|
35
|
+
"$defs",
|
|
36
|
+
"definitions",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A root union expands combinatorially: n nested binary unions yield 2^n variants, and a
|
|
41
|
+
* diamond of `$ref`s amplifies node count the same way without ever cycling. Either one can
|
|
42
|
+
* exhaust memory before the schema is judged unflattenable, so depth, node, and variant
|
|
43
|
+
* budgets bound the walk. Exceeding any of them omits that one function — the same fallback
|
|
44
|
+
* an unflattenable schema already takes. Mirrors the node budgets in openai-chat.ts.
|
|
45
|
+
*/
|
|
46
|
+
const XAI_MAX_SCHEMA_DEPTH = 64;
|
|
47
|
+
const XAI_MAX_SCHEMA_NODES = 4_096;
|
|
48
|
+
const XAI_MAX_ROOT_VARIANTS = 256;
|
|
49
|
+
|
|
50
|
+
/** Mutable walk budget, shared across ref resolution and root-union expansion for one tool. */
|
|
51
|
+
interface XaiSchemaBudget {
|
|
52
|
+
remainingNodes: number;
|
|
53
|
+
remainingVariants: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One budget per tool: a large catalog must not let one schema spend another's allowance. */
|
|
57
|
+
function createXaiSchemaBudget(): XaiSchemaBudget {
|
|
58
|
+
return { remainingNodes: XAI_MAX_SCHEMA_NODES, remainingVariants: XAI_MAX_ROOT_VARIANTS };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function decodeJsonPointerToken(token: string): string {
|
|
62
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Resolve a local `#/`-rooted JSON Pointer against `root`; undefined when it does not resolve. */
|
|
66
|
+
export function lookupLocalJsonPointer(root: unknown, ref: string): unknown {
|
|
67
|
+
if (ref === "#" || ref === "#/") return root;
|
|
68
|
+
if (!ref.startsWith("#/")) return undefined;
|
|
69
|
+
let current: unknown = root;
|
|
70
|
+
for (const token of ref.slice(2).split("/").map(decodeJsonPointerToken)) {
|
|
71
|
+
if (!isSchemaObject(current) || !Object.hasOwn(current, token)) return undefined;
|
|
72
|
+
current = current[token];
|
|
73
|
+
}
|
|
74
|
+
return current;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Resolve local `#/` `$ref`s. Unresolvable, cyclic, or over-budget refs return undefined. */
|
|
78
|
+
function resolveXaiSchemaRefs(
|
|
79
|
+
schema: unknown,
|
|
80
|
+
root: Record<string, unknown>,
|
|
81
|
+
budget: XaiSchemaBudget,
|
|
82
|
+
stack: Set<string> = new Set(),
|
|
83
|
+
depth = 0,
|
|
84
|
+
): unknown | undefined {
|
|
85
|
+
if (!isSchemaObject(schema)) return schema;
|
|
86
|
+
if (depth >= XAI_MAX_SCHEMA_DEPTH || budget.remainingNodes <= 0) return undefined;
|
|
87
|
+
budget.remainingNodes -= 1;
|
|
88
|
+
if (typeof schema.$ref === "string") {
|
|
89
|
+
const ref = schema.$ref;
|
|
90
|
+
if (stack.has(ref)) return undefined;
|
|
91
|
+
const target = lookupLocalJsonPointer(root, ref);
|
|
92
|
+
if (target === undefined) return undefined;
|
|
93
|
+
stack.add(ref);
|
|
94
|
+
const resolvedTarget = resolveXaiSchemaRefs(target, root, budget, stack, depth + 1);
|
|
95
|
+
stack.delete(ref);
|
|
96
|
+
if (resolvedTarget === undefined) return undefined;
|
|
97
|
+
const rest: Record<string, unknown> = { ...schema };
|
|
98
|
+
delete rest.$ref;
|
|
99
|
+
if (Object.keys(rest).length === 0) return resolvedTarget;
|
|
100
|
+
const resolvedRest = resolveXaiSchemaRefs(rest, root, budget, stack, depth + 1);
|
|
101
|
+
if (resolvedRest === undefined || !isSchemaObject(resolvedTarget) || !isSchemaObject(resolvedRest)) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return composeXaiObjectSchemas(resolvedTarget, resolvedRest);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const resolved: Record<string, unknown> = {};
|
|
108
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
109
|
+
if ((key === "oneOf" || key === "anyOf") && Array.isArray(value)) {
|
|
110
|
+
const items: unknown[] = [];
|
|
111
|
+
for (const item of value) {
|
|
112
|
+
const next = resolveXaiSchemaRefs(item, root, budget, stack, depth + 1);
|
|
113
|
+
if (next === undefined) return undefined;
|
|
114
|
+
items.push(next);
|
|
115
|
+
}
|
|
116
|
+
resolved[key] = items;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (key === "properties" && isSchemaObject(value)) {
|
|
120
|
+
const properties: Record<string, unknown> = {};
|
|
121
|
+
for (const [name, property] of Object.entries(value)) {
|
|
122
|
+
const next = resolveXaiSchemaRefs(property, root, budget, stack, depth + 1);
|
|
123
|
+
if (next === undefined) return undefined;
|
|
124
|
+
properties[name] = next;
|
|
125
|
+
}
|
|
126
|
+
resolved[key] = properties;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
resolved[key] = value;
|
|
130
|
+
}
|
|
131
|
+
return resolved;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** A variant is mergeable only when it is an object whose every key the merger preserves. */
|
|
135
|
+
function xaiVariantIsConcreteObject(variant: Record<string, unknown>): boolean {
|
|
136
|
+
if (variant.type !== undefined && variant.type !== "object") return false;
|
|
137
|
+
return Object.keys(variant).every(key => XAI_VARIANT_MERGE_KEYS.has(key));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function variantProperties(variant: Record<string, unknown>): Record<string, unknown> {
|
|
141
|
+
return isSchemaObject(variant.properties) ? variant.properties : {};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Independent per-property anyOf is lossless only when every property name exists
|
|
146
|
+
* on every variant (absence is meaningful under xAI's default additionalProperties:
|
|
147
|
+
* false, and promoting a branch-local key also tightens explicit-true variants)
|
|
148
|
+
* and at most one property schema differs.
|
|
149
|
+
*/
|
|
150
|
+
function xaiPropertyMergeIsLossless(variants: Record<string, unknown>[]): boolean {
|
|
151
|
+
const names = new Set<string>();
|
|
152
|
+
const props = variants.map(variant => {
|
|
153
|
+
const properties = variantProperties(variant);
|
|
154
|
+
for (const name of Object.keys(properties)) names.add(name);
|
|
155
|
+
return properties;
|
|
156
|
+
});
|
|
157
|
+
let schemaConflicts = 0;
|
|
158
|
+
for (const name of names) {
|
|
159
|
+
const values = props.map(property => property[name]);
|
|
160
|
+
if (values.some(value => value === undefined)) return false;
|
|
161
|
+
if (values.some(value => JSON.stringify(value) !== JSON.stringify(values[0]))) schemaConflicts += 1;
|
|
162
|
+
}
|
|
163
|
+
return schemaConflicts <= 1;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function xaiRequiredSetsMatch(variants: Record<string, unknown>[]): boolean {
|
|
167
|
+
const serialized = variants.map(variant => [...stringRequiredFields(variant.required)].sort().join("\0"));
|
|
168
|
+
return serialized.every(value => value === serialized[0]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Values a schema pins through `const`/`enum`, or undefined when it pins none. */
|
|
172
|
+
function xaiLiteralValues(schema: unknown): unknown[] | undefined {
|
|
173
|
+
if (!isSchemaObject(schema)) return undefined;
|
|
174
|
+
if (Object.hasOwn(schema, "const")) return [schema.const];
|
|
175
|
+
if (Array.isArray(schema.enum)) return schema.enum;
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** JSON type name for a literal, so it can be compared against a `type` keyword. */
|
|
180
|
+
function xaiJsonTypeOf(value: unknown): string {
|
|
181
|
+
if (value === null) return "null";
|
|
182
|
+
if (Array.isArray(value)) return "array";
|
|
183
|
+
if (typeof value === "string") return "string";
|
|
184
|
+
if (typeof value === "boolean") return "boolean";
|
|
185
|
+
if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
|
|
186
|
+
return "object";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Types a schema declares, or undefined when it constrains none. */
|
|
190
|
+
function xaiDeclaredTypes(schema: unknown): Set<string> | undefined {
|
|
191
|
+
if (!isSchemaObject(schema)) return undefined;
|
|
192
|
+
const type = schema.type;
|
|
193
|
+
if (typeof type === "string") return new Set([type]);
|
|
194
|
+
if (Array.isArray(type) && type.every(item => typeof item === "string")) return new Set(type as string[]);
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** `integer` is a subset of `number`, so those two names overlap rather than exclude. */
|
|
199
|
+
function xaiTypesOverlap(left: string, right: string): boolean {
|
|
200
|
+
if (left === right) return true;
|
|
201
|
+
return (left === "integer" && right === "number") || (left === "number" && right === "integer");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Conservative mutual-exclusion test: true only when no instance can satisfy both schemas.
|
|
206
|
+
* Proof comes from disjoint literal sets or disjoint declared types; anything it cannot prove
|
|
207
|
+
* is reported as overlapping so the caller refuses the merge instead of widening the schema.
|
|
208
|
+
*/
|
|
209
|
+
function xaiSchemasAreProvablyDisjoint(left: unknown, right: unknown): boolean {
|
|
210
|
+
const leftValues = xaiLiteralValues(left);
|
|
211
|
+
const rightValues = xaiLiteralValues(right);
|
|
212
|
+
if (leftValues && rightValues) {
|
|
213
|
+
const seen = new Set(rightValues.map(value => JSON.stringify(value)));
|
|
214
|
+
return leftValues.every(value => !seen.has(JSON.stringify(value)));
|
|
215
|
+
}
|
|
216
|
+
const leftTypes = xaiDeclaredTypes(left);
|
|
217
|
+
const rightTypes = xaiDeclaredTypes(right);
|
|
218
|
+
const literalsExcludedByTypes = (values: unknown[], types: Set<string>): boolean =>
|
|
219
|
+
values.every(value => ![...types].some(type => xaiTypesOverlap(xaiJsonTypeOf(value), type)));
|
|
220
|
+
if (leftValues && rightTypes) return literalsExcludedByTypes(leftValues, rightTypes);
|
|
221
|
+
if (rightValues && leftTypes) return literalsExcludedByTypes(rightValues, leftTypes);
|
|
222
|
+
if (leftTypes && rightTypes) {
|
|
223
|
+
return ![...leftTypes].some(leftType => [...rightTypes].some(rightType => xaiTypesOverlap(leftType, rightType)));
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Every pair provably disjoint, so a union over them accepts each instance exactly once. */
|
|
229
|
+
function xaiSchemasArePairwiseDisjoint(schemas: unknown[]): boolean {
|
|
230
|
+
for (let i = 0; i < schemas.length; i += 1) {
|
|
231
|
+
for (let j = i + 1; j < schemas.length; j += 1) {
|
|
232
|
+
if (!xaiSchemasAreProvablyDisjoint(schemas[i], schemas[j])) return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Deduplicate schemas by serialized shape, preserving first-seen order. */
|
|
239
|
+
function uniqueXaiSchemas(values: unknown[]): unknown[] {
|
|
240
|
+
const unique: unknown[] = [];
|
|
241
|
+
const serialized = new Set<string>();
|
|
242
|
+
for (const value of values) {
|
|
243
|
+
const key = JSON.stringify(value);
|
|
244
|
+
if (serialized.has(key)) continue;
|
|
245
|
+
serialized.add(key);
|
|
246
|
+
unique.push(value);
|
|
247
|
+
}
|
|
248
|
+
return unique;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function mergeXaiAdditionalProperties(
|
|
252
|
+
variants: Record<string, unknown>[],
|
|
253
|
+
): { ok: true; value?: unknown } | { ok: false } {
|
|
254
|
+
const values = variants.map(variant => variant.additionalProperties);
|
|
255
|
+
const explicit = values.filter(value => value !== undefined);
|
|
256
|
+
if (explicit.length === 0) return { ok: true };
|
|
257
|
+
if (explicit.length !== values.length) return { ok: false };
|
|
258
|
+
const hasFalse = explicit.some(value => value === false);
|
|
259
|
+
const permissive = explicit.filter(value => value !== false);
|
|
260
|
+
if (hasFalse && permissive.length > 0) return { ok: false };
|
|
261
|
+
if (hasFalse) return { ok: true, value: false };
|
|
262
|
+
const unique = uniqueXaiSchemas(permissive);
|
|
263
|
+
if (unique.length !== 1) return { ok: false };
|
|
264
|
+
return { ok: true, value: unique[0] };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Compose root siblings into a branch so properties/required are not overwritten. */
|
|
268
|
+
function composeXaiObjectSchemas(
|
|
269
|
+
inherited: Record<string, unknown>,
|
|
270
|
+
branch: Record<string, unknown>,
|
|
271
|
+
): Record<string, unknown> {
|
|
272
|
+
const composed: Record<string, unknown> = { ...inherited, ...branch };
|
|
273
|
+
const inheritedProps = isSchemaObject(inherited.properties) ? inherited.properties : undefined;
|
|
274
|
+
const branchProps = isSchemaObject(branch.properties) ? branch.properties : undefined;
|
|
275
|
+
if (inheritedProps || branchProps) {
|
|
276
|
+
const properties: Record<string, unknown> = { ...(inheritedProps ?? {}) };
|
|
277
|
+
for (const [name, value] of Object.entries(branchProps ?? {})) {
|
|
278
|
+
const inheritedValue = inheritedProps?.[name];
|
|
279
|
+
properties[name] = inheritedValue !== undefined && JSON.stringify(inheritedValue) !== JSON.stringify(value)
|
|
280
|
+
? { allOf: [inheritedValue, value] }
|
|
281
|
+
: value;
|
|
282
|
+
}
|
|
283
|
+
composed.properties = properties;
|
|
284
|
+
}
|
|
285
|
+
const required = [...new Set([
|
|
286
|
+
...stringRequiredFields(inherited.required),
|
|
287
|
+
...stringRequiredFields(branch.required),
|
|
288
|
+
])];
|
|
289
|
+
if (required.length > 0) composed.required = required;
|
|
290
|
+
else delete composed.required;
|
|
291
|
+
return composed;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Flattened root variants, plus the shape of the union tree they came from. */
|
|
295
|
+
interface XaiRootExpansion {
|
|
296
|
+
variants: Record<string, unknown>[];
|
|
297
|
+
/** This node was itself a union, as opposed to a plain object leaf. */
|
|
298
|
+
isUnion: boolean;
|
|
299
|
+
/**
|
|
300
|
+
* True when a `oneOf` was expanded anywhere in the tree. `oneOf` rejects an instance that
|
|
301
|
+
* matches more than one branch, so its variants must stay mutually exclusive after merging;
|
|
302
|
+
* `anyOf` carries no such obligation.
|
|
303
|
+
*/
|
|
304
|
+
exclusive: boolean;
|
|
305
|
+
/**
|
|
306
|
+
* True when a branch was itself a union. Nested `anyOf` flattens associatively and stays
|
|
307
|
+
* exact, but a `oneOf` mixed into a nest no longer maps onto one flat variant list: its
|
|
308
|
+
* exclusivity binds only its own branch group, which a merged root cannot express.
|
|
309
|
+
*/
|
|
310
|
+
nestedUnion: boolean;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function expandXaiRootObjectSchemas(
|
|
314
|
+
schema: unknown,
|
|
315
|
+
budget: XaiSchemaBudget,
|
|
316
|
+
depth = 0,
|
|
317
|
+
): XaiRootExpansion | undefined {
|
|
318
|
+
if (!isSchemaObject(schema) || depth >= XAI_MAX_SCHEMA_DEPTH) return undefined;
|
|
319
|
+
const compositionKey = ["oneOf", "anyOf"].find(key => Array.isArray(schema[key]));
|
|
320
|
+
if (!compositionKey) {
|
|
321
|
+
if (schema.type !== undefined && schema.type !== "object") return undefined;
|
|
322
|
+
if (budget.remainingVariants <= 0) return undefined;
|
|
323
|
+
budget.remainingVariants -= 1;
|
|
324
|
+
return { variants: [{ ...schema, type: "object" }], isUnion: false, exclusive: false, nestedUnion: false };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const siblings = Object.fromEntries(Object.entries(schema).filter(([key]) => key !== compositionKey));
|
|
328
|
+
const branches = schema[compositionKey];
|
|
329
|
+
if (!Array.isArray(branches)) return undefined;
|
|
330
|
+
const expanded: Record<string, unknown>[] = [];
|
|
331
|
+
let exclusive = compositionKey === "oneOf";
|
|
332
|
+
let nestedUnion = false;
|
|
333
|
+
for (const branch of branches) {
|
|
334
|
+
const nested = expandXaiRootObjectSchemas(branch, budget, depth + 1);
|
|
335
|
+
if (!nested) return undefined;
|
|
336
|
+
exclusive ||= nested.exclusive;
|
|
337
|
+
nestedUnion ||= nested.isUnion || nested.nestedUnion;
|
|
338
|
+
for (const variant of nested.variants) expanded.push(composeXaiObjectSchemas(siblings, variant));
|
|
339
|
+
}
|
|
340
|
+
return expanded.length > 0 ? { variants: expanded, isUnion: true, exclusive, nestedUnion } : undefined;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The Grok CLI proxy rejects a function parameter schema whose root remains oneOf/anyOf.
|
|
345
|
+
* Flatten only when the merge is lossless: local $refs resolve inside the walk budget, every
|
|
346
|
+
* variant is a concrete object whose keys we can preserve, required sets match,
|
|
347
|
+
* additionalProperties does not change meaning, every property name exists on every variant,
|
|
348
|
+
* and at most one property schema differs.
|
|
349
|
+
*
|
|
350
|
+
* A `oneOf` carries one more obligation than `anyOf`: it rejects an instance matching several
|
|
351
|
+
* branches. Only the destination's ROOT rejects a union, so exclusivity survives by moving the
|
|
352
|
+
* union down onto the one differing property rather than by widening it — with every other
|
|
353
|
+
* property, the required set, and additionalProperties already identical across branches, a
|
|
354
|
+
* property-level `oneOf` accepts exactly what the root `oneOf` accepted. Branches that are
|
|
355
|
+
* provably disjoint keep emitting `anyOf`, since there the two keywords describe the same set.
|
|
356
|
+
* The remaining hole is an optional discriminator: absent, it matches every branch, which the
|
|
357
|
+
* root `oneOf` rejects and a per-property union would accept, so that property is promoted into
|
|
358
|
+
* `required`.
|
|
359
|
+
*
|
|
360
|
+
* Two deliberate widenings remain, and both widen only from the EMPTY set. A `oneOf` listing the
|
|
361
|
+
* same branch twice accepts nothing at all. Neither does a root `additionalProperties: false`
|
|
362
|
+
* placed over branches that declare properties the root itself does not: the restriction cannot
|
|
363
|
+
* see into an applicator, so it forbids the very keys those branches require (same for a `$ref`
|
|
364
|
+
* target's restriction against a sibling's properties). No author intends either, no root object
|
|
365
|
+
* schema can express "accepts nothing", and the emitted schema still carries the restriction — so
|
|
366
|
+
* the tool stays usable instead of disappearing over a source-schema bug. A union whose branch
|
|
367
|
+
* properties ARE declared on the root is satisfiable and is normalized losslessly, which is why
|
|
368
|
+
* the presence of `additionalProperties` alone is not grounds to refuse.
|
|
369
|
+
*/
|
|
370
|
+
export function normalizeXaiToolParameters(parameters: unknown): Record<string, unknown> | undefined {
|
|
371
|
+
if (!isSchemaObject(parameters)) return undefined;
|
|
372
|
+
const budget = createXaiSchemaBudget();
|
|
373
|
+
const resolved = resolveXaiSchemaRefs(parameters, parameters, budget);
|
|
374
|
+
if (!isSchemaObject(resolved)) return undefined;
|
|
375
|
+
|
|
376
|
+
const normalizedRoot = { ...resolved };
|
|
377
|
+
delete normalizedRoot.$schema;
|
|
378
|
+
|
|
379
|
+
const expansion = expandXaiRootObjectSchemas(normalizedRoot, budget);
|
|
380
|
+
if (!expansion) return undefined;
|
|
381
|
+
const { variants, exclusive, nestedUnion } = expansion;
|
|
382
|
+
if (variants.length === 1) {
|
|
383
|
+
return xaiVariantIsConcreteObject(variants[0]) ? variants[0] : undefined;
|
|
384
|
+
}
|
|
385
|
+
if (!variants.every(xaiVariantIsConcreteObject) || !xaiRequiredSetsMatch(variants)) return undefined;
|
|
386
|
+
const additionalProperties = mergeXaiAdditionalProperties(variants);
|
|
387
|
+
if (!additionalProperties.ok) return undefined;
|
|
388
|
+
if (!xaiPropertyMergeIsLossless(variants)) return undefined;
|
|
389
|
+
|
|
390
|
+
const metadata = Object.fromEntries(Object.entries(normalizedRoot).filter(([key]) => key !== "oneOf" && key !== "anyOf" && key !== "type"));
|
|
391
|
+
delete metadata.properties;
|
|
392
|
+
delete metadata.required;
|
|
393
|
+
delete metadata.additionalProperties;
|
|
394
|
+
|
|
395
|
+
const propertyValues = new Map<string, unknown[]>();
|
|
396
|
+
for (const variant of variants) {
|
|
397
|
+
if (!isSchemaObject(variant.properties)) continue;
|
|
398
|
+
for (const [name, value] of Object.entries(variant.properties)) {
|
|
399
|
+
const values = propertyValues.get(name) ?? [];
|
|
400
|
+
values.push(value);
|
|
401
|
+
propertyValues.set(name, values);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const properties: Record<string, unknown> = {};
|
|
406
|
+
const differingNames: string[] = [];
|
|
407
|
+
for (const [name, values] of propertyValues) {
|
|
408
|
+
const unique = uniqueXaiSchemas(values);
|
|
409
|
+
if (unique.length === 1) {
|
|
410
|
+
properties[name] = unique[0];
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
// A `oneOf` nested among other unions binds exclusivity to its own branch group only, so a
|
|
414
|
+
// single flat variant list cannot say what the original said — in either direction. Refuse.
|
|
415
|
+
if (exclusive && nestedUnion) return undefined;
|
|
416
|
+
differingNames.push(name);
|
|
417
|
+
// Disjoint branches make `anyOf` and `oneOf` describe the same set, so prefer the keyword
|
|
418
|
+
// already proven on this wire; overlapping branches need the exclusivity kept verbatim.
|
|
419
|
+
properties[name] = exclusive && !xaiSchemasArePairwiseDisjoint(unique)
|
|
420
|
+
? { oneOf: unique }
|
|
421
|
+
: { anyOf: unique };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
let required = stringRequiredFields(variants[0]?.required);
|
|
425
|
+
if (exclusive && differingNames.length > 0) {
|
|
426
|
+
required = [...new Set([...required, ...differingNames])];
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
...metadata,
|
|
431
|
+
type: "object",
|
|
432
|
+
properties,
|
|
433
|
+
...(required.length > 0 ? { required } : {}),
|
|
434
|
+
...("value" in additionalProperties ? { additionalProperties: additionalProperties.value } : {}),
|
|
435
|
+
};
|
|
436
|
+
}
|
package/src/bridge.ts
CHANGED
|
@@ -7,9 +7,18 @@ import type {
|
|
|
7
7
|
OcxUsage,
|
|
8
8
|
} from "./types";
|
|
9
9
|
import { coerceIntegerToolArguments } from "./lib/tool-argument-integers";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
adapterFailureFromMessage,
|
|
12
|
+
classifyError,
|
|
13
|
+
cyberPolicyErrorType,
|
|
14
|
+
CYBER_POLICY_ERROR_CODE,
|
|
15
|
+
isCyberPolicyCode,
|
|
16
|
+
type OcxErrorPayload,
|
|
17
|
+
} from "./lib/errors";
|
|
18
|
+
import { redactSecretString } from "./lib/redact";
|
|
11
19
|
import { repairFreeformToolInput } from "./responses/apply-patch-envelope";
|
|
12
20
|
import { encodeCompactionSummary } from "./responses/compaction";
|
|
21
|
+
import { compileCodeModeHelperInput } from "./responses/code-mode-helper-compat";
|
|
13
22
|
import { isTruncatedStopReason, truncationReasonFor } from "./responses/truncated-stop-reason";
|
|
14
23
|
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "./responses/reasoning-envelope";
|
|
15
24
|
import { rememberReasoningForCall } from "./responses/reasoning-replay-cache";
|
|
@@ -114,18 +123,19 @@ function toolCallArgumentsUsable(args: string): boolean {
|
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "error" }>): { httpStatus: number; error: OcxErrorPayload } {
|
|
126
|
+
const message = redactSecretString(event.message);
|
|
117
127
|
if (event.status === undefined && event.errorType === undefined && event.code === undefined) {
|
|
118
|
-
return adapterFailureFromMessage(
|
|
128
|
+
return adapterFailureFromMessage(message);
|
|
119
129
|
}
|
|
120
|
-
const fallback = adapterFailureFromMessage(
|
|
130
|
+
const fallback = adapterFailureFromMessage(message);
|
|
121
131
|
let httpStatus = event.status ?? fallback.httpStatus;
|
|
122
|
-
const error = classifyError(httpStatus, event.errorType ?? fallback.error.type,
|
|
132
|
+
const error = classifyError(httpStatus, event.errorType ?? fallback.error.type, message);
|
|
123
133
|
if (event.errorType !== undefined) error.type = event.errorType;
|
|
124
134
|
if (event.code !== undefined) error.code = event.code;
|
|
125
135
|
// Codex maps cyber_policy on HTTP 400 (body) or mid-stream code; never leave it as 502.
|
|
126
136
|
if (isCyberPolicyCode(error.code) || isCyberPolicyCode(event.code)) {
|
|
127
137
|
error.code = CYBER_POLICY_ERROR_CODE;
|
|
128
|
-
error.type =
|
|
138
|
+
error.type = cyberPolicyErrorType(event.errorType);
|
|
129
139
|
httpStatus = 400;
|
|
130
140
|
}
|
|
131
141
|
return { httpStatus, error };
|
|
@@ -235,9 +245,14 @@ export function bridgeToResponsesSSE(
|
|
|
235
245
|
// Freeform/custom tools (apply_patch, code-mode exec) carry their body in `input`; the
|
|
236
246
|
// model is given a function with `{input:string}`, so unwrap it here when relaying back
|
|
237
247
|
// as a custom_tool_call. Decorated apply_patch envelopes are repaired at this boundary.
|
|
238
|
-
const freeformInput = (
|
|
239
|
-
|
|
240
|
-
|
|
248
|
+
const freeformInput = (
|
|
249
|
+
args: string,
|
|
250
|
+
toolName: string,
|
|
251
|
+
namespace?: string,
|
|
252
|
+
codeModeHelperName?: string,
|
|
253
|
+
): string => codeModeHelperName
|
|
254
|
+
? compileCodeModeHelperInput(args, codeModeHelperName)
|
|
255
|
+
: repairFreeformToolInput(args, toolName, namespace);
|
|
241
256
|
// Best-effort unwrap of a PARTIAL freeform arg buffer for live input streaming
|
|
242
257
|
// (`response.custom_tool_call_input.delta` — codex-rs uses it for UI preview only;
|
|
243
258
|
// the completed custom_tool_call item stays authoritative). Compact `{"input":"...`
|
|
@@ -521,7 +536,7 @@ export function bridgeToResponsesSSE(
|
|
|
521
536
|
// synthetic compaction item's payload on done.
|
|
522
537
|
let compactionText = "";
|
|
523
538
|
let compactionTextBytes = 0;
|
|
524
|
-
let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; argsBytes: number; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
|
|
539
|
+
let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; argsBytes: number; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string; codeModeHelperName?: string; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
|
|
525
540
|
// Open native web-search cell (between begin and end). Holds the output index allocated on
|
|
526
541
|
// begin so the matching done reuses it; closed as `failed` if the stream terminates early.
|
|
527
542
|
let currentWebSearch: { itemId: string; eventId: string; outputIndex: number } | null = null;
|
|
@@ -640,7 +655,7 @@ export function bridgeToResponsesSSE(
|
|
|
640
655
|
emit("response.custom_tool_call_input.done", {
|
|
641
656
|
item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
|
|
642
657
|
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
643
|
-
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace),
|
|
658
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName),
|
|
644
659
|
});
|
|
645
660
|
}
|
|
646
661
|
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
@@ -657,7 +672,7 @@ export function bridgeToResponsesSSE(
|
|
|
657
672
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
658
673
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
659
674
|
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
660
|
-
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "completed",
|
|
675
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName), status: "completed",
|
|
661
676
|
}
|
|
662
677
|
: {
|
|
663
678
|
type: "function_call", id: currentToolCall.itemId,
|
|
@@ -697,7 +712,7 @@ export function bridgeToResponsesSSE(
|
|
|
697
712
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
698
713
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
699
714
|
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
700
|
-
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "incomplete",
|
|
715
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName), status: "incomplete",
|
|
701
716
|
}
|
|
702
717
|
: {
|
|
703
718
|
type: "function_call", id: currentToolCall.itemId,
|
|
@@ -1043,6 +1058,9 @@ export function bridgeToResponsesSSE(
|
|
|
1043
1058
|
}
|
|
1044
1059
|
if (currentToolCall) closeCurrentToolCall();
|
|
1045
1060
|
const effectiveName = normalizeDeclaredToolName(event.name, options?.declaredToolNames);
|
|
1061
|
+
const codeModeHelperName = effectiveName === "exec" && event.name !== effectiveName
|
|
1062
|
+
? event.name
|
|
1063
|
+
: undefined;
|
|
1046
1064
|
const mapped = toolNsMap?.get(effectiveName);
|
|
1047
1065
|
const realName = mapped?.name ?? effectiveName;
|
|
1048
1066
|
if (options?.declaredToolNames && !options.declaredToolNames.has(effectiveName)) {
|
|
@@ -1074,7 +1092,7 @@ export function bridgeToResponsesSSE(
|
|
|
1074
1092
|
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, ...(ns ? { namespace: ns } : {}), input: "", status: "in_progress" }
|
|
1075
1093
|
: { type: "function_call", id: itemId, call_id: event.id, name: realName, arguments: "", status: "in_progress", ...(ns ? { namespace: ns } : {}) };
|
|
1076
1094
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
1077
|
-
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, providerMetadata: event.providerMetadata };
|
|
1095
|
+
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, codeModeHelperName, providerMetadata: event.providerMetadata };
|
|
1078
1096
|
budget?.openCall(event.id);
|
|
1079
1097
|
break;
|
|
1080
1098
|
}
|
|
@@ -1093,7 +1111,7 @@ export function bridgeToResponsesSSE(
|
|
|
1093
1111
|
delta: event.arguments,
|
|
1094
1112
|
});
|
|
1095
1113
|
}
|
|
1096
|
-
if (currentToolCall.freeform) {
|
|
1114
|
+
if (currentToolCall.freeform && !currentToolCall.codeModeHelperName) {
|
|
1097
1115
|
// Hold while the buffer is still an ambiguous prefix of the JSON wrapper,
|
|
1098
1116
|
// then stream only the unwrapped input suffix (never rewind on mode flips).
|
|
1099
1117
|
if (!FREEFORM_WRAP_PREFIX.startsWith(currentToolCall.args)) {
|
|
@@ -1296,7 +1314,9 @@ export function bridgeToResponsesSSE(
|
|
|
1296
1314
|
...(event.usage ? { usage: responsesUsage(event.usage) } : {}),
|
|
1297
1315
|
error: failure.error,
|
|
1298
1316
|
last_error: failure.error,
|
|
1299
|
-
...(
|
|
1317
|
+
...(isCyberPolicyCode(failure.error.code)
|
|
1318
|
+
? { retryable: false }
|
|
1319
|
+
: event.retryable !== undefined ? { retryable: event.retryable } : {}),
|
|
1300
1320
|
},
|
|
1301
1321
|
});
|
|
1302
1322
|
reportTerminal("failed");
|
|
@@ -1320,11 +1340,17 @@ export function bridgeToResponsesSSE(
|
|
|
1320
1340
|
if (currentToolCall) failCurrentToolCall();
|
|
1321
1341
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1322
1342
|
releasePendingWebSources();
|
|
1343
|
+
const failure = responseError(
|
|
1344
|
+
500,
|
|
1345
|
+
"proxy_error",
|
|
1346
|
+
redactSecretString(err instanceof Error ? err.message : String(err)),
|
|
1347
|
+
);
|
|
1323
1348
|
emit("response.failed", {
|
|
1324
1349
|
response: {
|
|
1325
1350
|
...responseSnapshot("failed", finishedItems),
|
|
1326
|
-
error:
|
|
1327
|
-
last_error:
|
|
1351
|
+
error: failure,
|
|
1352
|
+
last_error: failure,
|
|
1353
|
+
...(isCyberPolicyCode(failure.code) ? { retryable: false } : {}),
|
|
1328
1354
|
},
|
|
1329
1355
|
});
|
|
1330
1356
|
reportTerminal("failed");
|
|
@@ -1377,6 +1403,8 @@ export function bridgeToResponsesSSE(
|
|
|
1377
1403
|
|
|
1378
1404
|
const startStream = () => {
|
|
1379
1405
|
emit("response.created", { response: responseSnapshot("in_progress", []) });
|
|
1406
|
+
// Responses spec parity: clients expect an explicit in_progress frame after created.
|
|
1407
|
+
emit("response.in_progress", { response: responseSnapshot("in_progress", []) });
|
|
1380
1408
|
// The default ReadableStream strategy has HWM=1. Once one event's frames fill that
|
|
1381
1409
|
// queue, pull stepping pauses; no custom FIFO or queuing strategy is layered on top.
|
|
1382
1410
|
gated = true;
|
|
@@ -1568,15 +1596,21 @@ function buildResponseJSONWithBudget(
|
|
|
1568
1596
|
let batchKiroRedactedBytes = 0;
|
|
1569
1597
|
let currentToolCallId = "";
|
|
1570
1598
|
let currentToolCallName = "";
|
|
1599
|
+
let currentToolCallCodeModeHelperName: string | undefined;
|
|
1571
1600
|
let currentToolCallArgs = "";
|
|
1572
1601
|
let currentToolCallProviderMetadata: OcxProviderOpaqueToolCallMetadata | undefined;
|
|
1573
1602
|
let currentToolCallArgsBytes = 0;
|
|
1574
1603
|
// Web-search citations awaiting the next assistant message (attached as url_citation annotations).
|
|
1575
1604
|
let pendingWebSources: { url: string; title?: string }[] = [];
|
|
1576
1605
|
|
|
1577
|
-
const freeformInput = (
|
|
1578
|
-
|
|
1579
|
-
|
|
1606
|
+
const freeformInput = (
|
|
1607
|
+
args: string,
|
|
1608
|
+
toolName: string,
|
|
1609
|
+
namespace?: string,
|
|
1610
|
+
codeModeHelperName?: string,
|
|
1611
|
+
): string => codeModeHelperName
|
|
1612
|
+
? compileCodeModeHelperInput(args, codeModeHelperName)
|
|
1613
|
+
: repairFreeformToolInput(args, toolName, namespace);
|
|
1580
1614
|
const parseArgsObj = (args: string): Record<string, unknown> => {
|
|
1581
1615
|
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
|
|
1582
1616
|
};
|
|
@@ -1678,7 +1712,7 @@ function buildResponseJSONWithBudget(
|
|
|
1678
1712
|
type: "custom_tool_call", id: `ctc_${uuid()}`,
|
|
1679
1713
|
call_id: currentToolCallId, name: realName,
|
|
1680
1714
|
...(ns ? { namespace: ns } : {}),
|
|
1681
|
-
input: freeformInput(currentToolCallArgs, realName, ns), status,
|
|
1715
|
+
input: freeformInput(currentToolCallArgs, realName, ns, currentToolCallCodeModeHelperName), status,
|
|
1682
1716
|
});
|
|
1683
1717
|
} else {
|
|
1684
1718
|
pushOutput({
|
|
@@ -1692,6 +1726,7 @@ function buildResponseJSONWithBudget(
|
|
|
1692
1726
|
budget?.closeCall(currentToolCallId);
|
|
1693
1727
|
currentToolCallId = "";
|
|
1694
1728
|
currentToolCallName = "";
|
|
1729
|
+
currentToolCallCodeModeHelperName = undefined;
|
|
1695
1730
|
currentToolCallProviderMetadata = undefined;
|
|
1696
1731
|
currentToolCallArgs = "";
|
|
1697
1732
|
currentToolCallArgsBytes = 0;
|
|
@@ -1806,6 +1841,9 @@ function buildResponseJSONWithBudget(
|
|
|
1806
1841
|
currentToolCallId = e.id;
|
|
1807
1842
|
budget?.openCall(e.id);
|
|
1808
1843
|
currentToolCallName = effectiveName;
|
|
1844
|
+
currentToolCallCodeModeHelperName = effectiveName === "exec" && e.name !== effectiveName
|
|
1845
|
+
? e.name
|
|
1846
|
+
: undefined;
|
|
1809
1847
|
currentToolCallArgs = "";
|
|
1810
1848
|
currentToolCallArgsBytes = 0;
|
|
1811
1849
|
currentToolCallProviderMetadata = e.providerMetadata;
|
|
@@ -1952,7 +1990,9 @@ function buildResponseJSONWithBudget(
|
|
|
1952
1990
|
model: modelId, output,
|
|
1953
1991
|
...(endTurn !== undefined ? { end_turn: endTurn } : {}),
|
|
1954
1992
|
...(failure ? { error: failure.error, last_error: failure.error } : {}),
|
|
1955
|
-
...(
|
|
1993
|
+
...(failure && isCyberPolicyCode(failure.error.code)
|
|
1994
|
+
? { retryable: false }
|
|
1995
|
+
: errorEvent?.retryable !== undefined ? { retryable: errorEvent.retryable } : {}),
|
|
1956
1996
|
...(incompleteEvent ? {
|
|
1957
1997
|
incomplete_details: {
|
|
1958
1998
|
reason: incompleteEvent.reason,
|
|
@@ -1981,12 +2021,15 @@ export function formatErrorResponse(
|
|
|
1981
2021
|
const error = classifyError(status, type, message);
|
|
1982
2022
|
if (isCyberPolicyCode(options?.code)) {
|
|
1983
2023
|
error.code = CYBER_POLICY_ERROR_CODE;
|
|
1984
|
-
error.type =
|
|
2024
|
+
error.type = cyberPolicyErrorType(type);
|
|
1985
2025
|
}
|
|
1986
2026
|
const finalStatus = error.code === CYBER_POLICY_ERROR_CODE ? 400 : status;
|
|
1987
2027
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1988
2028
|
const retryAfter = options?.retryAfter?.trim();
|
|
1989
|
-
if (
|
|
2029
|
+
if (error.code !== CYBER_POLICY_ERROR_CODE
|
|
2030
|
+
&& retryAfter
|
|
2031
|
+
&& retryAfter.length > 0
|
|
2032
|
+
&& retryAfter.length <= 128) {
|
|
1990
2033
|
headers.set("Retry-After", retryAfter);
|
|
1991
2034
|
}
|
|
1992
2035
|
return new Response(JSON.stringify({ error }), {
|