@coseung2/opencodex 2.8.0-cs.15 → 2.8.0-cs.17

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 (61) hide show
  1. package/gui/dist/assets/{index-BhXIu7c0.js → index-Ch-99jy3.js} +2 -2
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +1 -1
  4. package/packages/ocx-notch/README.md +3 -1
  5. package/src/adapters/base.ts +12 -0
  6. package/src/adapters/identity.ts +1 -1
  7. package/src/adapters/kiro-calibration.ts +83 -0
  8. package/src/adapters/kiro-constants.ts +11 -2
  9. package/src/adapters/kiro-errors.ts +11 -0
  10. package/src/adapters/kiro-events.ts +19 -1
  11. package/src/adapters/kiro-thinking.ts +18 -2
  12. package/src/adapters/kiro-tools.ts +12 -3
  13. package/src/adapters/kiro.ts +300 -78
  14. package/src/adapters/openai-chat.ts +1 -42
  15. package/src/adapters/openai-responses.ts +126 -9
  16. package/src/adapters/xai-schema-analysis.ts +78 -0
  17. package/src/adapters/xai-tool-schema.ts +274 -0
  18. package/src/adapters/xai-web-search.ts +138 -0
  19. package/src/bridge.ts +61 -6
  20. package/src/cli/observe.ts +18 -3
  21. package/src/codex/app-server-processes.ts +3 -5
  22. package/src/codex/catalog/effort.ts +4 -2
  23. package/src/codex/catalog/metadata.ts +42 -9
  24. package/src/codex/catalog/parsing.ts +17 -2
  25. package/src/codex/catalog/provider-fetch.ts +9 -3
  26. package/src/codex/catalog/sync.ts +11 -5
  27. package/src/codex/data/upstream-models.json +169 -0
  28. package/src/grok/inject.ts +1 -1
  29. package/src/lib/errors.ts +18 -0
  30. package/src/lib/token-estimate.ts +42 -38
  31. package/src/lib/translator-budget.ts +34 -0
  32. package/src/oauth/index.ts +10 -4
  33. package/src/oauth/kiro.ts +71 -6
  34. package/src/oauth/store.ts +3 -1
  35. package/src/oauth/types.ts +4 -0
  36. package/src/providers/derive.ts +7 -5
  37. package/src/providers/opencode-go-transport.ts +59 -0
  38. package/src/providers/quota.ts +68 -60
  39. package/src/providers/registry.ts +41 -10
  40. package/src/providers/xai-transport.ts +10 -0
  41. package/src/responses/compaction.ts +8 -1
  42. package/src/responses/namespace-aliases.ts +56 -0
  43. package/src/responses/parser.ts +12 -0
  44. package/src/responses/reasoning-envelope.ts +9 -1
  45. package/src/responses/snapshot-policy.ts +108 -0
  46. package/src/responses/state.ts +23 -10
  47. package/src/responses/turn-termination.ts +108 -0
  48. package/src/responses/xai-custom-tool-compat.ts +237 -0
  49. package/src/server/grok-responses-snapshot-repair.ts +338 -0
  50. package/src/server/index.ts +2 -1
  51. package/src/server/relay-eager.ts +1 -0
  52. package/src/server/request-log-conversation.ts +8 -0
  53. package/src/server/request-log.ts +5 -4
  54. package/src/server/responses/core.ts +233 -16
  55. package/src/server/responses-image-gen-repair.ts +2 -2
  56. package/src/server/sse-payload-rewrite.ts +20 -3
  57. package/src/types.ts +10 -1
  58. package/src/usage/cost.ts +0 -0
  59. package/src/usage/expected-prices.ts +7 -0
  60. package/src/usage/log.ts +1 -2
  61. package/vendor/ocx-notch/win32-x64/ocx-notch.exe +0 -0
@@ -0,0 +1,338 @@
1
+ import type { TranslatorBudget } from "../lib/translator-budget";
2
+ import { MAX_COMPLETED_OUTPUT_ITEMS, MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES } from "./relay";
3
+ import type { SsePayloadRewrite } from "./sse-payload-rewrite";
4
+
5
+ interface OpenItemIdentity {
6
+ type: string;
7
+ id?: string;
8
+ sourceBytes: number;
9
+ }
10
+
11
+ interface CompletedItem {
12
+ item: Record<string, unknown>;
13
+ sourceBytes: number;
14
+ visibleToGrok: boolean;
15
+ }
16
+
17
+ const SUPPORTED_ITEM_TYPES = new Set([
18
+ "message",
19
+ "reasoning",
20
+ "function_call",
21
+ "custom_tool_call",
22
+ "web_search_call",
23
+ "code_interpreter_call",
24
+ "mcp_call",
25
+ ]);
26
+
27
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
28
+ return !!value && typeof value === "object" && !Array.isArray(value);
29
+ }
30
+
31
+ function validOptionalId(item: Record<string, unknown>): boolean {
32
+ return !("id" in item) || (typeof item.id === "string" && item.id.trim().length > 0);
33
+ }
34
+
35
+ function completedStatusWhenPresent(item: Record<string, unknown>): boolean {
36
+ return !("status" in item) || item.status === "completed";
37
+ }
38
+
39
+ function nullableString(value: unknown): boolean {
40
+ return value === null || typeof value === "string";
41
+ }
42
+
43
+ function backfillOutputTextPart(part: unknown): unknown {
44
+ if (!isPlainObject(part) || part.type !== "output_text" || Array.isArray(part.annotations)) return part;
45
+ return { ...part, annotations: [] };
46
+ }
47
+
48
+ function backfillMessageItem(item: unknown): unknown {
49
+ if (!isPlainObject(item) || item.type !== "message" || !Array.isArray(item.content)) return item;
50
+ let changed = false;
51
+ const content = item.content.map(part => {
52
+ const next = backfillOutputTextPart(part);
53
+ changed ||= next !== part;
54
+ return next;
55
+ });
56
+ return changed ? { ...item, content } : item;
57
+ }
58
+
59
+ function backfillGrokRequiredFields(payload: Record<string, unknown>): Record<string, unknown> {
60
+ let changed = false;
61
+ const next: Record<string, unknown> = { ...payload };
62
+ if (isPlainObject(payload.item)) {
63
+ const item = backfillMessageItem(payload.item);
64
+ if (item !== payload.item) { next.item = item; changed = true; }
65
+ }
66
+ if (isPlainObject(payload.part)) {
67
+ const part = backfillOutputTextPart(payload.part);
68
+ if (part !== payload.part) { next.part = part; changed = true; }
69
+ }
70
+ if (isPlainObject(payload.response) && Array.isArray(payload.response.output)) {
71
+ let outputChanged = false;
72
+ const output = payload.response.output.map(item => {
73
+ const repaired = backfillMessageItem(item);
74
+ outputChanged ||= repaired !== item;
75
+ return repaired;
76
+ });
77
+ if (outputChanged) {
78
+ next.response = { ...payload.response, output };
79
+ changed = true;
80
+ }
81
+ }
82
+ return changed ? next : payload;
83
+ }
84
+
85
+ function validOutputMessagePart(part: unknown): boolean {
86
+ if (!isPlainObject(part)) return false;
87
+ if (part.type === "output_text") {
88
+ return typeof part.text === "string"
89
+ && (!("annotations" in part) || Array.isArray(part.annotations))
90
+ && (!("logprobs" in part) || part.logprobs === null || Array.isArray(part.logprobs));
91
+ }
92
+ return part.type === "refusal" && typeof part.refusal === "string";
93
+ }
94
+
95
+ function validReasoningPart(part: unknown, type: "summary_text" | "reasoning_text"): boolean {
96
+ return isPlainObject(part) && part.type === type && typeof part.text === "string";
97
+ }
98
+
99
+ function validWebSearchAction(value: unknown): boolean {
100
+ if (!isPlainObject(value)) return false;
101
+ if (value.type === "search") {
102
+ return typeof value.query === "string"
103
+ && (!("sources" in value) || value.sources === null || (Array.isArray(value.sources)
104
+ && value.sources.every(source => isPlainObject(source)
105
+ && typeof source.type === "string" && typeof source.url === "string")));
106
+ }
107
+ if (value.type === "open_page") return !("url" in value) || nullableString(value.url);
108
+ if (value.type === "find" || value.type === "find_in_page") {
109
+ return typeof value.url === "string" && typeof value.pattern === "string";
110
+ }
111
+ return false;
112
+ }
113
+
114
+ function validCodeInterpreterOutput(value: unknown): boolean {
115
+ return isPlainObject(value)
116
+ && ((value.type === "logs" && typeof value.logs === "string")
117
+ || (value.type === "image" && typeof value.url === "string"));
118
+ }
119
+
120
+ /**
121
+ * Accept only semantic-complete items from a real output_item.done. Missing optional ids/status
122
+ * are tolerated, but content is never invented to justify a sparse terminal reconstruction.
123
+ */
124
+ function trustedCompletedItem(item: Record<string, unknown>): { visibleToGrok: boolean } | null {
125
+ if (!validOptionalId(item) || !completedStatusWhenPresent(item)) return null;
126
+
127
+ if (item.type === "message") {
128
+ if (item.role !== "assistant" || !Array.isArray(item.content)) return null;
129
+ if (!item.content.every(validOutputMessagePart)) return null;
130
+ if ("phase" in item && item.phase !== "commentary" && item.phase !== "final_answer") return null;
131
+ return {
132
+ visibleToGrok: item.content.some(part => isPlainObject(part)
133
+ && part.type === "output_text" && typeof part.text === "string" && part.text.length > 0),
134
+ };
135
+ }
136
+
137
+ if (item.type === "reasoning") {
138
+ if (!Array.isArray(item.summary) || !item.summary.every(part => validReasoningPart(part, "summary_text"))) return null;
139
+ if ("content" in item && item.content !== null
140
+ && (!Array.isArray(item.content) || !item.content.every(part => validReasoningPart(part, "reasoning_text")))) return null;
141
+ if ("encrypted_content" in item && !nullableString(item.encrypted_content)) return null;
142
+ return { visibleToGrok: false };
143
+ }
144
+
145
+ if (item.type === "function_call") {
146
+ if (typeof item.call_id !== "string" || item.call_id.trim().length === 0
147
+ || typeof item.name !== "string" || item.name.trim().length === 0
148
+ || typeof item.arguments !== "string") return null;
149
+ return { visibleToGrok: true };
150
+ }
151
+
152
+ if (item.type === "custom_tool_call") {
153
+ if (typeof item.call_id !== "string" || item.call_id.trim().length === 0
154
+ || typeof item.name !== "string" || item.name.trim().length === 0
155
+ || typeof item.input !== "string") return null;
156
+ // xAI restoration runs first: a client-executed function may already be a custom call.
157
+ return { visibleToGrok: true };
158
+ }
159
+
160
+ if (item.type === "web_search_call") {
161
+ if (item.status !== "completed" || !validWebSearchAction(item.action)) return null;
162
+ return { visibleToGrok: false };
163
+ }
164
+
165
+ if (item.type === "code_interpreter_call") {
166
+ if (item.status !== "completed"
167
+ || typeof item.container_id !== "string" || item.container_id.trim().length === 0
168
+ || ("code" in item && !nullableString(item.code))
169
+ || ("outputs" in item && item.outputs !== null
170
+ && (!Array.isArray(item.outputs) || !item.outputs.every(validCodeInterpreterOutput)))) return null;
171
+ return { visibleToGrok: false };
172
+ }
173
+
174
+ if (item.type === "mcp_call") {
175
+ if (typeof item.arguments !== "string"
176
+ || typeof item.name !== "string" || item.name.trim().length === 0
177
+ || typeof item.server_label !== "string" || item.server_label.trim().length === 0
178
+ || ("approval_request_id" in item && !nullableString(item.approval_request_id))
179
+ || ("error" in item && !nullableString(item.error))
180
+ || ("output" in item && !nullableString(item.output))) return null;
181
+ return { visibleToGrok: false };
182
+ }
183
+
184
+ return null;
185
+ }
186
+
187
+ function plausibleOpenItem(item: Record<string, unknown>): Omit<OpenItemIdentity, "sourceBytes"> | null {
188
+ const type = typeof item.type === "string" ? item.type : "";
189
+ if (!SUPPORTED_ITEM_TYPES.has(type) || !validOptionalId(item)) return null;
190
+ if ("status" in item && item.status !== "in_progress") return null;
191
+ if (type === "message") {
192
+ if ("role" in item && item.role !== "assistant") return null;
193
+ if ("content" in item && !Array.isArray(item.content)) return null;
194
+ }
195
+ return { type, ...(typeof item.id === "string" ? { id: item.id } : {}) };
196
+ }
197
+
198
+ /**
199
+ * Grok Build shows streaming deltas live but persists the final assistant turn from
200
+ * response.completed.response.output. Some native Responses streams leave that terminal array
201
+ * absent/empty while carrying complete items in output_item.done. Reconstruct only the sparse
202
+ * terminal case from unique, contiguous, bounded, validated done events; every ambiguity fails
203
+ * closed and leaves the provider payload byte-equivalent.
204
+ */
205
+ export function createGrokResponsesSparseTerminalPayloadRewrite(
206
+ budget?: TranslatorBudget,
207
+ ): SsePayloadRewrite {
208
+ const openItems = new Map<number, OpenItemIdentity>();
209
+ const completedItems = new Map<number, CompletedItem>();
210
+ let aggregateCompletedBytes = 0;
211
+ let aggregateOpenBytes = 0;
212
+ let tainted = false;
213
+ let hasVisibleOutput = false;
214
+
215
+ const clearRetained = (): void => {
216
+ const retained = aggregateCompletedBytes + aggregateOpenBytes;
217
+ if (retained > 0) budget?.releaseRetained(retained, { kind: "retained_collectors" });
218
+ openItems.clear();
219
+ completedItems.clear();
220
+ aggregateCompletedBytes = 0;
221
+ aggregateOpenBytes = 0;
222
+ hasVisibleOutput = false;
223
+ };
224
+ const reset = (): void => {
225
+ clearRetained();
226
+ tainted = false;
227
+ };
228
+ const taint = (): void => {
229
+ clearRetained();
230
+ tainted = true;
231
+ };
232
+
233
+ const closeOpen = (index: number): void => {
234
+ const open = openItems.get(index);
235
+ if (!open) return;
236
+ openItems.delete(index);
237
+ aggregateOpenBytes -= open.sourceBytes;
238
+ budget?.releaseRetained(open.sourceBytes, { kind: "retained_collectors" });
239
+ };
240
+
241
+ const rewrite = ((payload: string): string => {
242
+ if (payload === "[DONE]") {
243
+ reset();
244
+ return payload;
245
+ }
246
+ let decoded: unknown;
247
+ try { decoded = JSON.parse(payload); }
248
+ catch { taint(); return payload; }
249
+ if (!isPlainObject(decoded) || typeof decoded.type !== "string") {
250
+ taint();
251
+ return payload;
252
+ }
253
+ const parsed = backfillGrokRequiredFields(decoded);
254
+ const basePayload = parsed === decoded ? payload : JSON.stringify(parsed);
255
+
256
+ const outputIndex = Number.isInteger(parsed.output_index) && (parsed.output_index as number) >= 0
257
+ ? parsed.output_index as number
258
+ : undefined;
259
+
260
+ if (parsed.type === "response.output_item.added") {
261
+ const open = isPlainObject(parsed.item) ? plausibleOpenItem(parsed.item) : null;
262
+ if (outputIndex === undefined || !open || openItems.has(outputIndex) || completedItems.has(outputIndex)
263
+ || openItems.size >= MAX_COMPLETED_OUTPUT_ITEMS) {
264
+ taint();
265
+ } else if (!tainted) {
266
+ const sourceBytes = Buffer.byteLength(JSON.stringify(open), "utf8");
267
+ if (sourceBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES
268
+ || aggregateOpenBytes + sourceBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES) {
269
+ taint();
270
+ } else {
271
+ budget?.chargeRetained(sourceBytes, { kind: "retained_collectors" });
272
+ openItems.set(outputIndex, { ...open, sourceBytes });
273
+ aggregateOpenBytes += sourceBytes;
274
+ }
275
+ }
276
+ return basePayload;
277
+ }
278
+
279
+ if (parsed.type === "response.output_item.done") {
280
+ const item = isPlainObject(parsed.item) ? parsed.item : null;
281
+ const proof = item ? trustedCompletedItem(item) : null;
282
+ if (outputIndex === undefined || !proof || completedItems.has(outputIndex)) {
283
+ taint();
284
+ return basePayload;
285
+ }
286
+ const open = openItems.get(outputIndex);
287
+ const doneId = typeof item!.id === "string" ? item!.id : undefined;
288
+ if (open && (open.type !== item!.type || open.id !== doneId)) {
289
+ taint();
290
+ return basePayload;
291
+ }
292
+ closeOpen(outputIndex);
293
+ if (!tainted) {
294
+ const sourceBytes = Buffer.byteLength(JSON.stringify(item), "utf8");
295
+ if (sourceBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES
296
+ || completedItems.size >= MAX_COMPLETED_OUTPUT_ITEMS
297
+ || aggregateCompletedBytes + sourceBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES) {
298
+ taint();
299
+ } else {
300
+ budget?.chargeRetained(sourceBytes, { kind: "retained_collectors" });
301
+ completedItems.set(outputIndex, { item: item!, sourceBytes, visibleToGrok: proof.visibleToGrok });
302
+ aggregateCompletedBytes += sourceBytes;
303
+ hasVisibleOutput ||= proof.visibleToGrok;
304
+ }
305
+ }
306
+ return basePayload;
307
+ }
308
+
309
+ const terminal = parsed.type === "response.completed"
310
+ || parsed.type === "response.failed"
311
+ || parsed.type === "response.incomplete";
312
+ if (!terminal) return basePayload;
313
+
314
+ let rewritten = basePayload;
315
+ if (parsed.type === "response.completed" && !tainted && isPlainObject(parsed.response)) {
316
+ const response = parsed.response;
317
+ const output = response.output;
318
+ const statusConsistent = !("status" in response) || response.status === "completed";
319
+ const authoritative = Array.isArray(output) && output.length > 0;
320
+ const sparse = !("output" in response) || (Array.isArray(output) && output.length === 0);
321
+ if (!authoritative && sparse && statusConsistent && completedItems.size > 0
322
+ && openItems.size === 0 && hasVisibleOutput) {
323
+ const ordered = [...completedItems.entries()].sort(([a], [b]) => a - b);
324
+ if (ordered.every(([index], position) => index === position)) {
325
+ rewritten = JSON.stringify({
326
+ ...parsed,
327
+ response: { ...response, output: ordered.map(([, retained]) => retained.item) },
328
+ });
329
+ }
330
+ }
331
+ }
332
+ reset();
333
+ return rewritten;
334
+ }) as SsePayloadRewrite;
335
+
336
+ rewrite.dispose = reset;
337
+ return rewrite;
338
+ }
@@ -527,7 +527,7 @@ export function startServer(port?: number) {
527
527
  // Disabled natives stay in the catalog shape with visibility "hide" (mirrors the
528
528
  // on-disk sync; codex-rs keeps them out of the picker itself).
529
529
  const maMode = config.multiAgentMode === "v1" || config.multiAgentMode === "v2" ? config.multiAgentMode : "default";
530
- const entries = buildCatalogEntries(loadCatalogTemplate(), nativeSlugs, goOrdered, config.subagentModels, websocketsEnabled(config), maMode as "v1" | "default" | "v2", exactComboCatalogSlugs(config));
530
+ const entries = buildCatalogEntries(loadCatalogTemplate(), nativeSlugs, goOrdered, config.subagentModels, websocketsEnabled(config), maMode as "v1" | "default" | "v2", exactComboCatalogSlugs(config), config);
531
531
  return jsonResponse({ models: applyNativeVisibility(entries, disabledNativeSlugs(config)) }, 200, req, config);
532
532
  }
533
533
  // OpenAI list shape: native gpt bare + routed models namespaced "<provider>/<id>"
@@ -709,6 +709,7 @@ export function startServer(port?: number) {
709
709
  ...admissionFields(admission),
710
710
  inboundProtocol: "responses",
711
711
  };
712
+ if (req.headers.get("x-opencodex-grok") === "1") logCtx.surface = "grok";
712
713
  let logged = false;
713
714
  const finalizeNativePassthroughLog = (
714
715
  status: number,
@@ -283,6 +283,7 @@ export function relaySseEagerBounded(
283
283
  if (!cancelled) {
284
284
  try { controllerRef?.close(); } catch { /* already closed/errored */ }
285
285
  }
286
+ try { rewrite?.dispose?.(); } catch { /* rewrite teardown must not block lifecycle cleanup */ }
286
287
  try { hooks.disposeInspection?.(); } catch { /* inspection teardown must not block lifecycle cleanup */ }
287
288
  fireDone();
288
289
  }
@@ -61,6 +61,14 @@ export function sessionIdHeaderFromRequest(headers: Headers): string | null {
61
61
  return headers.get("session_id") ?? headers.get("session-id");
62
62
  }
63
63
 
64
+ export function sessionLaneIdFromRequest(headers: Headers): string | undefined {
65
+ const parent = headers.get("x-codex-parent-thread-id")?.trim();
66
+ const thread = headers.get("thread-id")?.trim();
67
+ const session = sessionIdHeaderFromRequest(headers)?.trim();
68
+ const lane = [parent, thread, session].filter(Boolean);
69
+ return lane.length > 0 ? lane.join("\0") : undefined;
70
+ }
71
+
64
72
  export function conversationIdFromResponsesRequest(input: {
65
73
  clientThreadId?: string;
66
74
  sessionIdHeader?: string | null;
@@ -4,6 +4,9 @@ import {
4
4
  classifyError,
5
5
  httpStatusFromTerminalError as httpStatusFromClassifiedTerminalError,
6
6
  isClientClosedMessage,
7
+ isCyberPolicyCode,
8
+ isCyberPolicyMessage,
9
+ upstreamErrorMessageFromPayload,
7
10
  } from "../lib/errors";
8
11
  import { CODEX_CONFIG_PATH, readRootTomlString } from "../codex/paths";
9
12
  import { readCodexCatalogPath } from "../codex/catalog";
@@ -674,9 +677,7 @@ function captureUpstreamErrorParsed(
674
677
  logCtx.terminalIncompleteReason = reason.trim();
675
678
  }
676
679
  if (logCtx.upstreamError) return;
677
- const message = json?.error?.message
678
- ?? json?.last_error?.message
679
- ?? json?.response?.error?.message;
680
+ const message = upstreamErrorMessageFromPayload(parsed);
680
681
  if (typeof message === "string" && message.trim()) {
681
682
  logCtx.upstreamError = redactSecretString(message).slice(0, 500);
682
683
  return;
@@ -939,7 +940,7 @@ function finalizedUsage(
939
940
  const usageFallback = !finalUsage && estimate !== undefined
940
941
  ? { inputTokens: estimate, outputTokens: 0, estimated: true }
941
942
  : undefined;
942
- const loggedUsage = finalUsage && estimate !== undefined
943
+ const loggedUsage = finalUsage?.estimated && estimate !== undefined
943
944
  ? {
944
945
  ...finalUsage,
945
946
  inputTokens: Math.max(finalUsage.inputTokens, estimate),