@yansigit/opencodex 2.33.0 → 2.35.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 (196) hide show
  1. package/README.md +3 -3
  2. package/gui/dist/assets/index-BjCaHxdz.js +112 -0
  3. package/gui/dist/assets/index-DLkXOXLC.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +1 -1
  6. package/src/adapters/anthropic.ts +79 -2
  7. package/src/adapters/command-code.ts +141 -23
  8. package/src/adapters/cursor/call-id.ts +44 -0
  9. package/src/adapters/cursor/checkpoint-store.ts +15 -10
  10. package/src/adapters/cursor/discovery.ts +60 -2
  11. package/src/adapters/cursor/effort-map.ts +79 -1
  12. package/src/adapters/cursor/envelope-echo.ts +162 -0
  13. package/src/adapters/cursor/live-models.ts +7 -2
  14. package/src/adapters/cursor/live-transport.ts +17 -1
  15. package/src/adapters/cursor/message-mapper.ts +4 -1
  16. package/src/adapters/cursor/native-exec-fs.ts +13 -12
  17. package/src/adapters/cursor/native-exec-network.ts +3 -5
  18. package/src/adapters/cursor/native-exec-policy.ts +47 -0
  19. package/src/adapters/cursor/native-exec-shell.ts +116 -31
  20. package/src/adapters/cursor/native-exec.ts +38 -10
  21. package/src/adapters/cursor/protobuf-events.ts +28 -2
  22. package/src/adapters/cursor/protobuf-request.ts +93 -41
  23. package/src/adapters/cursor/request-builder.ts +39 -10
  24. package/src/adapters/cursor/tool-definitions.ts +27 -3
  25. package/src/adapters/cursor/tool-result-normalize.ts +51 -6
  26. package/src/adapters/cursor/types.ts +23 -4
  27. package/src/adapters/cursor.ts +170 -29
  28. package/src/adapters/google-aistudio-parser.ts +49 -0
  29. package/src/adapters/google-antigravity-replay.ts +105 -25
  30. package/src/adapters/google-antigravity-wire.ts +5 -0
  31. package/src/adapters/google-errors.ts +41 -12
  32. package/src/adapters/google-http.ts +12 -11
  33. package/src/adapters/google.ts +219 -36
  34. package/src/adapters/image.ts +1 -1
  35. package/src/adapters/kiro-constants.ts +15 -0
  36. package/src/adapters/kiro-tools.ts +43 -15
  37. package/src/adapters/kiro.ts +54 -9
  38. package/src/adapters/openai-chat.ts +286 -242
  39. package/src/adapters/openai-responses.ts +335 -24
  40. package/src/adapters/run-turn-queue.ts +36 -1
  41. package/src/adapters/tool-catalog-nudge.ts +2 -2
  42. package/src/adapters/xai-tool-schema.ts +436 -0
  43. package/src/bridge.ts +67 -26
  44. package/src/chat/inbound.ts +29 -1
  45. package/src/chat/outbound.ts +15 -7
  46. package/src/claude/agents-inject.ts +8 -1
  47. package/src/claude/outbound.ts +10 -8
  48. package/src/cli/account-api.ts +27 -7
  49. package/src/cli/account-extended.ts +10 -3
  50. package/src/cli/account.ts +29 -5
  51. package/src/cli/alias.ts +66 -0
  52. package/src/cli/claude.ts +26 -1
  53. package/src/cli/dispatch.ts +13 -1
  54. package/src/cli/help.ts +1 -0
  55. package/src/cli/index.ts +6 -1
  56. package/src/cli/init.ts +1 -0
  57. package/src/cli/models-runtime.ts +95 -0
  58. package/src/cli/models.ts +13 -7
  59. package/src/cli/provider-runtime.ts +16 -2
  60. package/src/cli/registry.ts +6 -1
  61. package/src/cli/telemetry-commands.ts +25 -0
  62. package/src/cli/v2.ts +34 -10
  63. package/src/codex/account-pause.ts +2 -1
  64. package/src/codex/account-priority.ts +3 -2
  65. package/src/codex/app-server-processes.ts +80 -6
  66. package/src/codex/auth-api.ts +48 -8
  67. package/src/codex/auth-context.ts +21 -18
  68. package/src/codex/catalog/aggregation.ts +6 -0
  69. package/src/codex/catalog/model-metadata.ts +13 -1
  70. package/src/codex/catalog/native-models.ts +5 -2
  71. package/src/codex/catalog/parsing.ts +16 -0
  72. package/src/codex/catalog/provider-fetch.ts +20 -3
  73. package/src/codex/catalog/sync.ts +127 -2
  74. package/src/codex/catalog.ts +1 -1
  75. package/src/codex/codex-write-lock.ts +3 -1
  76. package/src/codex/convergence-types.ts +1 -1
  77. package/src/codex/convergence.ts +22 -2
  78. package/src/codex/desired-state.ts +2 -2
  79. package/src/codex/desktop-app-restart.ts +18 -5
  80. package/src/codex/inject-coordination.ts +83 -0
  81. package/src/codex/inject.ts +14 -1
  82. package/src/codex/log-guard/inspect.ts +22 -4
  83. package/src/codex/model-entitlements.ts +9 -2
  84. package/src/codex/prompt-layers.ts +371 -25
  85. package/src/codex/prompt-text-probe.ts +238 -0
  86. package/src/codex/quota.ts +123 -18
  87. package/src/codex/routing.ts +9 -0
  88. package/src/codex/subagent-model-fallback.ts +198 -27
  89. package/src/codex/transition-state.ts +107 -8
  90. package/src/combos/types.ts +10 -0
  91. package/src/compatibility/openai-responses.ts +33 -1
  92. package/src/config/autonomous-remediation.ts +21 -0
  93. package/src/config/provider-validation.ts +14 -0
  94. package/src/config/rebase-provenance.ts +68 -0
  95. package/src/config.ts +191 -17
  96. package/src/generated/compatibility-version.json +279 -159
  97. package/src/generated/model-metadata.ts +3 -0
  98. package/src/images/loop.ts +5 -4
  99. package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
  100. package/src/lab/fabric/producer-child.ts +1 -1
  101. package/src/lib/config-ownership.ts +20 -0
  102. package/src/lib/errors.ts +11 -2
  103. package/src/lib/package-tree-integrity.ts +101 -0
  104. package/src/oauth/aistudio-credentials.ts +65 -0
  105. package/src/oauth/aistudio-native-daemon.ts +116 -0
  106. package/src/oauth/aistudio-session-sync.ts +95 -0
  107. package/src/oauth/generic-account-failover.ts +231 -0
  108. package/src/oauth/google-aistudio-auth.ts +98 -0
  109. package/src/oauth/index.ts +57 -5
  110. package/src/oauth/key-providers.ts +18 -1
  111. package/src/oauth/kiro.ts +45 -0
  112. package/src/oauth/login-cli.ts +65 -1
  113. package/src/oauth/types.ts +15 -0
  114. package/src/providers/codex-capacity.ts +5 -2
  115. package/src/providers/command-code-efforts.ts +38 -6
  116. package/src/providers/context-cap.ts +4 -3
  117. package/src/providers/default-aliases.ts +65 -0
  118. package/src/providers/derive.ts +29 -1
  119. package/src/providers/fastwire.ts +7 -1
  120. package/src/providers/model-presets.ts +119 -0
  121. package/src/providers/new-model-policy.ts +146 -0
  122. package/src/providers/provider-id-rewrite.ts +2 -1
  123. package/src/providers/quota.ts +157 -46
  124. package/src/providers/registry.ts +184 -71
  125. package/src/providers/slug-codec.ts +52 -0
  126. package/src/responses/code-mode-helper-compat.ts +50 -0
  127. package/src/responses/custom-tool-compat.ts +34 -10
  128. package/src/responses/parser.ts +4 -0
  129. package/src/responses/schema.ts +5 -1
  130. package/src/responses/thought-signature-replay.ts +17 -0
  131. package/src/router.ts +43 -2
  132. package/src/routing/account-pool/cooldown.ts +8 -0
  133. package/src/routing/account-pool/index.ts +1 -0
  134. package/src/routing/analytics.ts +1 -0
  135. package/src/routing/quota.ts +10 -0
  136. package/src/server/auth-cors.ts +24 -0
  137. package/src/server/chat-completions.ts +26 -16
  138. package/src/server/chat-native-sse.ts +3 -3
  139. package/src/server/chat-native.ts +30 -11
  140. package/src/server/claude-messages.ts +1 -1
  141. package/src/server/effort-policy.ts +16 -0
  142. package/src/server/index.ts +180 -14
  143. package/src/server/lifecycle.ts +52 -1
  144. package/src/server/management/agent-settings-routes.ts +31 -15
  145. package/src/server/management/codex-prompt-routes.ts +570 -0
  146. package/src/server/management/combo-routes.ts +2 -1
  147. package/src/server/management/config-routes.ts +27 -9
  148. package/src/server/management/context.ts +9 -0
  149. package/src/server/management/logs-usage-routes.ts +11 -5
  150. package/src/server/management/model-routes.ts +266 -0
  151. package/src/server/management/oauth-account-routes.ts +13 -3
  152. package/src/server/management/provider-routes.ts +137 -3
  153. package/src/server/management/routing-profile-routes.ts +2 -2
  154. package/src/server/management-api.ts +2 -0
  155. package/src/server/port-reclaim.ts +19 -1
  156. package/src/server/relay-eager.ts +147 -20
  157. package/src/server/relay.ts +251 -19
  158. package/src/server/request-log-conversation.ts +33 -0
  159. package/src/server/request-log.ts +48 -21
  160. package/src/server/responses/collaboration.ts +42 -5
  161. package/src/server/responses/combo-stream-preflight.ts +10 -3
  162. package/src/server/responses/core.ts +575 -140
  163. package/src/server/responses/empty-completion-guard.ts +35 -0
  164. package/src/server/responses/fetch-helpers.ts +14 -6
  165. package/src/server/responses/input-admission.ts +3 -1
  166. package/src/server/responses/passthrough-error.ts +33 -9
  167. package/src/server/responses/policy-fallback.ts +1 -1
  168. package/src/server/responses/responses-field-backfill.ts +105 -13
  169. package/src/server/responses/ws-upstream.ts +35 -5
  170. package/src/server/responses-custom-tool-repair.ts +52 -7
  171. package/src/server/responses-terminal-repair.ts +25 -4
  172. package/src/server/sse-frame-buffer.ts +31 -4
  173. package/src/server/ws-bridge.ts +14 -2
  174. package/src/smoke/fingerprint-cache.ts +133 -0
  175. package/src/smoke/live-scenarios.ts +33 -0
  176. package/src/smoke/runner.ts +119 -0
  177. package/src/telemetry/dispatcher.ts +44 -0
  178. package/src/telemetry/fingerprint.ts +24 -0
  179. package/src/telemetry/hook.ts +43 -0
  180. package/src/telemetry/ledger.ts +54 -0
  181. package/src/telemetry/types.ts +23 -0
  182. package/src/types/config.ts +66 -14
  183. package/src/types/provider.ts +79 -1
  184. package/src/types/request.ts +18 -10
  185. package/src/types/tools.ts +30 -11
  186. package/src/types.ts +1 -0
  187. package/src/usage/command-code-manifest.ts +116 -0
  188. package/src/usage/cost.ts +2 -2
  189. package/src/usage/expected-prices.ts +126 -24
  190. package/src/usage/log.ts +18 -8
  191. package/src/usage/summary.ts +34 -12
  192. package/src/web-search/exa-executor.ts +40 -9
  193. package/src/web-search/index.ts +16 -8
  194. package/src/web-search/loop.ts +5 -4
  195. package/gui/dist/assets/index-DKLr4LTE.js +0 -102
  196. package/gui/dist/assets/index-DrSQdTRd.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 { adapterFailureFromMessage, classifyError, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, type OcxErrorPayload } from "./lib/errors";
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";
@@ -115,20 +124,19 @@ function toolCallArgumentsUsable(args: string): boolean {
115
124
  }
116
125
 
117
126
  function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "error" }>): { httpStatus: number; error: OcxErrorPayload } {
127
+ const message = redactSecretString(event.message);
118
128
  if (event.status === undefined && event.errorType === undefined && event.code === undefined) {
119
- return adapterFailureFromMessage(event.message);
129
+ return adapterFailureFromMessage(message);
120
130
  }
121
- const fallback = adapterFailureFromMessage(event.message);
131
+ const fallback = adapterFailureFromMessage(message);
122
132
  let httpStatus = event.status ?? fallback.httpStatus;
123
- const error = httpStatus === 400 && event.errorType === "upstream_error"
124
- ? { message: event.message, type: "upstream_error", code: event.code ?? null }
125
- : classifyError(httpStatus, event.errorType ?? fallback.error.type, event.message);
133
+ const error = classifyError(httpStatus, event.errorType ?? fallback.error.type, message);
126
134
  if (event.errorType !== undefined) error.type = event.errorType;
127
135
  if (event.code !== undefined) error.code = event.code;
128
136
  // Codex maps cyber_policy on HTTP 400 (body) or mid-stream code; never leave it as 502.
129
137
  if (isCyberPolicyCode(error.code) || isCyberPolicyCode(event.code)) {
130
138
  error.code = CYBER_POLICY_ERROR_CODE;
131
- error.type = "invalid_request_error";
139
+ error.type = cyberPolicyErrorType(event.errorType);
132
140
  httpStatus = 400;
133
141
  }
134
142
  return { httpStatus, error };
@@ -322,9 +330,14 @@ export function bridgeToResponsesSSE(
322
330
  // Freeform/custom tools (apply_patch, code-mode exec) carry their body in `input`; the
323
331
  // model is given a function with `{input:string}`, so unwrap it here when relaying back
324
332
  // as a custom_tool_call. Decorated apply_patch envelopes are repaired at this boundary.
325
- const freeformInput = (args: string, toolName: string, namespace?: string): string => (
326
- repairFreeformToolInput(args, toolName, namespace)
327
- );
333
+ const freeformInput = (
334
+ args: string,
335
+ toolName: string,
336
+ namespace?: string,
337
+ codeModeHelperName?: string,
338
+ ): string => codeModeHelperName
339
+ ? compileCodeModeHelperInput(args, codeModeHelperName)
340
+ : repairFreeformToolInput(args, toolName, namespace);
328
341
  // Best-effort unwrap of a PARTIAL freeform arg buffer for live input streaming
329
342
  // (`response.custom_tool_call_input.delta` — codex-rs uses it for UI preview only;
330
343
  // the completed custom_tool_call item stays authoritative). Compact `{"input":"...`
@@ -609,7 +622,7 @@ export function bridgeToResponsesSSE(
609
622
  // synthetic compaction item's payload on done.
610
623
  let compactionText = "";
611
624
  let compactionTextBytes = 0;
612
- 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;
625
+ 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;
613
626
  // Open native web-search cell (between begin and end). Holds the output index allocated on
614
627
  // begin so the matching done reuses it; closed as `failed` if the stream terminates early.
615
628
  let currentWebSearch: { itemId: string; eventId: string; outputIndex: number } | null = null;
@@ -728,7 +741,7 @@ export function bridgeToResponsesSSE(
728
741
  emit("response.custom_tool_call_input.done", {
729
742
  item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
730
743
  ...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
731
- input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace),
744
+ input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName),
732
745
  });
733
746
  }
734
747
  // Freeform tools serialize as custom_tool_call without extra_content; remember the
@@ -745,7 +758,7 @@ export function bridgeToResponsesSSE(
745
758
  type: "custom_tool_call", id: currentToolCall.itemId,
746
759
  call_id: currentToolCall.callId, name: currentToolCall.name,
747
760
  ...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
748
- input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "completed",
761
+ input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName), status: "completed",
749
762
  }
750
763
  : {
751
764
  type: "function_call", id: currentToolCall.itemId,
@@ -785,7 +798,7 @@ export function bridgeToResponsesSSE(
785
798
  type: "custom_tool_call", id: currentToolCall.itemId,
786
799
  call_id: currentToolCall.callId, name: currentToolCall.name,
787
800
  ...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
788
- input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "incomplete",
801
+ input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName), status: "incomplete",
789
802
  }
790
803
  : {
791
804
  type: "function_call", id: currentToolCall.itemId,
@@ -1142,6 +1155,9 @@ export function bridgeToResponsesSSE(
1142
1155
  }
1143
1156
  if (currentToolCall) closeCurrentToolCall();
1144
1157
  const effectiveName = normalizeDeclaredToolName(event.name, options?.declaredToolNames);
1158
+ const codeModeHelperName = effectiveName === "exec" && event.name !== effectiveName
1159
+ ? event.name
1160
+ : undefined;
1145
1161
  const mapped = toolNsMap?.get(effectiveName);
1146
1162
  const realName = mapped?.name ?? effectiveName;
1147
1163
  if (options?.declaredToolNames && !options.declaredToolNames.has(effectiveName)) {
@@ -1173,7 +1189,7 @@ export function bridgeToResponsesSSE(
1173
1189
  ? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, ...(ns ? { namespace: ns } : {}), input: "", status: "in_progress" }
1174
1190
  : { type: "function_call", id: itemId, call_id: event.id, name: realName, arguments: "", status: "in_progress", ...(ns ? { namespace: ns } : {}) };
1175
1191
  emit("response.output_item.added", { output_index: outputIndex, item });
1176
- currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, providerMetadata: event.providerMetadata };
1192
+ currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, codeModeHelperName, providerMetadata: event.providerMetadata };
1177
1193
  budget?.openCall(event.id);
1178
1194
  break;
1179
1195
  }
@@ -1192,7 +1208,7 @@ export function bridgeToResponsesSSE(
1192
1208
  delta: event.arguments,
1193
1209
  });
1194
1210
  }
1195
- if (currentToolCall.freeform) {
1211
+ if (currentToolCall.freeform && !currentToolCall.codeModeHelperName) {
1196
1212
  // Hold while the buffer is still an ambiguous prefix of the JSON wrapper,
1197
1213
  // then stream only the unwrapped input suffix (never rewind on mode flips).
1198
1214
  if (!FREEFORM_WRAP_PREFIX.startsWith(currentToolCall.args)) {
@@ -1395,7 +1411,9 @@ export function bridgeToResponsesSSE(
1395
1411
  ...(event.usage ? { usage: responsesUsage(event.usage) } : {}),
1396
1412
  error: failure.error,
1397
1413
  last_error: failure.error,
1398
- ...(event.retryable !== undefined ? { retryable: event.retryable } : {}),
1414
+ ...(isCyberPolicyCode(failure.error.code)
1415
+ ? { retryable: false }
1416
+ : event.retryable !== undefined ? { retryable: event.retryable } : {}),
1399
1417
  },
1400
1418
  });
1401
1419
  reportTerminal("failed");
@@ -1419,11 +1437,17 @@ export function bridgeToResponsesSSE(
1419
1437
  if (currentToolCall) failCurrentToolCall();
1420
1438
  if (currentWebSearch) closeCurrentWebSearch("failed", []);
1421
1439
  releasePendingWebSources();
1440
+ const failure = responseError(
1441
+ 500,
1442
+ "proxy_error",
1443
+ redactSecretString(err instanceof Error ? err.message : String(err)),
1444
+ );
1422
1445
  emit("response.failed", {
1423
1446
  response: {
1424
1447
  ...responseSnapshot("failed", finishedItems),
1425
- error: responseError(500, "proxy_error", err instanceof Error ? err.message : String(err)),
1426
- last_error: responseError(500, "proxy_error", err instanceof Error ? err.message : String(err)),
1448
+ error: failure,
1449
+ last_error: failure,
1450
+ ...(isCyberPolicyCode(failure.code) ? { retryable: false } : {}),
1427
1451
  },
1428
1452
  });
1429
1453
  reportTerminal("failed");
@@ -1476,6 +1500,8 @@ export function bridgeToResponsesSSE(
1476
1500
 
1477
1501
  const startStream = () => {
1478
1502
  emit("response.created", { response: responseSnapshot("in_progress", []) });
1503
+ // Responses spec parity: clients expect an explicit in_progress frame after created.
1504
+ emit("response.in_progress", { response: responseSnapshot("in_progress", []) });
1479
1505
  // The default ReadableStream strategy has HWM=1. Once one event's frames fill that
1480
1506
  // queue, pull stepping pauses; no custom FIFO or queuing strategy is layered on top.
1481
1507
  gated = true;
@@ -1667,15 +1693,21 @@ function buildResponseJSONWithBudget(
1667
1693
  let batchKiroRedactedBytes = 0;
1668
1694
  let currentToolCallId = "";
1669
1695
  let currentToolCallName = "";
1696
+ let currentToolCallCodeModeHelperName: string | undefined;
1670
1697
  let currentToolCallArgs = "";
1671
1698
  let currentToolCallProviderMetadata: OcxProviderOpaqueToolCallMetadata | undefined;
1672
1699
  let currentToolCallArgsBytes = 0;
1673
1700
  // Web-search citations awaiting the next assistant message (attached as url_citation annotations).
1674
1701
  let pendingWebSources: { url: string; title?: string }[] = [];
1675
1702
 
1676
- const freeformInput = (args: string, toolName: string, namespace?: string): string => (
1677
- repairFreeformToolInput(args, toolName, namespace)
1678
- );
1703
+ const freeformInput = (
1704
+ args: string,
1705
+ toolName: string,
1706
+ namespace?: string,
1707
+ codeModeHelperName?: string,
1708
+ ): string => codeModeHelperName
1709
+ ? compileCodeModeHelperInput(args, codeModeHelperName)
1710
+ : repairFreeformToolInput(args, toolName, namespace);
1679
1711
  const parseArgsObj = (args: string): Record<string, unknown> => {
1680
1712
  try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
1681
1713
  };
@@ -1777,7 +1809,7 @@ function buildResponseJSONWithBudget(
1777
1809
  type: "custom_tool_call", id: `ctc_${uuid()}`,
1778
1810
  call_id: currentToolCallId, name: realName,
1779
1811
  ...(ns ? { namespace: ns } : {}),
1780
- input: freeformInput(currentToolCallArgs, realName, ns), status,
1812
+ input: freeformInput(currentToolCallArgs, realName, ns, currentToolCallCodeModeHelperName), status,
1781
1813
  });
1782
1814
  } else {
1783
1815
  pushOutput({
@@ -1791,6 +1823,7 @@ function buildResponseJSONWithBudget(
1791
1823
  budget?.closeCall(currentToolCallId);
1792
1824
  currentToolCallId = "";
1793
1825
  currentToolCallName = "";
1826
+ currentToolCallCodeModeHelperName = undefined;
1794
1827
  currentToolCallProviderMetadata = undefined;
1795
1828
  currentToolCallArgs = "";
1796
1829
  currentToolCallArgsBytes = 0;
@@ -1905,6 +1938,9 @@ function buildResponseJSONWithBudget(
1905
1938
  currentToolCallId = e.id;
1906
1939
  budget?.openCall(e.id);
1907
1940
  currentToolCallName = effectiveName;
1941
+ currentToolCallCodeModeHelperName = effectiveName === "exec" && e.name !== effectiveName
1942
+ ? e.name
1943
+ : undefined;
1908
1944
  currentToolCallArgs = "";
1909
1945
  currentToolCallArgsBytes = 0;
1910
1946
  currentToolCallProviderMetadata = e.providerMetadata;
@@ -2051,7 +2087,9 @@ function buildResponseJSONWithBudget(
2051
2087
  model: modelId, output,
2052
2088
  ...(endTurn !== undefined ? { end_turn: endTurn } : {}),
2053
2089
  ...(failure ? { error: failure.error, last_error: failure.error } : {}),
2054
- ...(errorEvent?.retryable !== undefined ? { retryable: errorEvent.retryable } : {}),
2090
+ ...(failure && isCyberPolicyCode(failure.error.code)
2091
+ ? { retryable: false }
2092
+ : errorEvent?.retryable !== undefined ? { retryable: errorEvent.retryable } : {}),
2055
2093
  ...(incompleteEvent ? {
2056
2094
  incomplete_details: {
2057
2095
  reason: incompleteEvent.reason,
@@ -2080,12 +2118,15 @@ export function formatErrorResponse(
2080
2118
  const error = classifyError(status, type, message);
2081
2119
  if (isCyberPolicyCode(options?.code)) {
2082
2120
  error.code = CYBER_POLICY_ERROR_CODE;
2083
- error.type = "invalid_request_error";
2121
+ error.type = cyberPolicyErrorType(type);
2084
2122
  }
2085
2123
  const finalStatus = error.code === CYBER_POLICY_ERROR_CODE ? 400 : status;
2086
2124
  const headers = new Headers({ "Content-Type": "application/json" });
2087
2125
  const retryAfter = options?.retryAfter?.trim();
2088
- if (retryAfter && retryAfter.length > 0 && retryAfter.length <= 128) {
2126
+ if (error.code !== CYBER_POLICY_ERROR_CODE
2127
+ && retryAfter
2128
+ && retryAfter.length > 0
2129
+ && retryAfter.length <= 128) {
2089
2130
  headers.set("Retry-After", retryAfter);
2090
2131
  }
2091
2132
  return new Response(JSON.stringify({ error }), {