@coseung2/opencodex 2.8.0-cs.16 → 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 (52) hide show
  1. package/gui/dist/assets/{index-BZGMtkmp.js → index-Ch-99jy3.js} +1 -1
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +1 -1
  4. package/src/adapters/base.ts +12 -0
  5. package/src/adapters/kiro-calibration.ts +83 -0
  6. package/src/adapters/kiro-constants.ts +11 -2
  7. package/src/adapters/kiro-errors.ts +11 -0
  8. package/src/adapters/kiro-events.ts +19 -1
  9. package/src/adapters/kiro-thinking.ts +18 -2
  10. package/src/adapters/kiro-tools.ts +12 -3
  11. package/src/adapters/kiro.ts +300 -78
  12. package/src/adapters/openai-chat.ts +1 -42
  13. package/src/adapters/openai-responses.ts +93 -14
  14. package/src/adapters/xai-schema-analysis.ts +78 -0
  15. package/src/adapters/xai-tool-schema.ts +274 -0
  16. package/src/adapters/xai-web-search.ts +138 -0
  17. package/src/bridge.ts +61 -6
  18. package/src/codex/catalog/effort.ts +4 -2
  19. package/src/codex/catalog/metadata.ts +38 -9
  20. package/src/codex/catalog/parsing.ts +17 -2
  21. package/src/codex/catalog/provider-fetch.ts +9 -3
  22. package/src/codex/catalog/sync.ts +8 -5
  23. package/src/codex/data/upstream-models.json +169 -0
  24. package/src/grok/inject.ts +1 -1
  25. package/src/lib/token-estimate.ts +42 -38
  26. package/src/lib/translator-budget.ts +34 -0
  27. package/src/oauth/index.ts +10 -4
  28. package/src/oauth/kiro.ts +71 -6
  29. package/src/oauth/store.ts +3 -1
  30. package/src/oauth/types.ts +4 -0
  31. package/src/providers/derive.ts +7 -5
  32. package/src/providers/opencode-go-transport.ts +18 -0
  33. package/src/providers/registry.ts +34 -10
  34. package/src/providers/xai-transport.ts +10 -0
  35. package/src/responses/compaction.ts +8 -1
  36. package/src/responses/namespace-aliases.ts +56 -0
  37. package/src/responses/parser.ts +12 -0
  38. package/src/responses/reasoning-envelope.ts +9 -1
  39. package/src/responses/snapshot-policy.ts +108 -0
  40. package/src/responses/state.ts +23 -10
  41. package/src/responses/turn-termination.ts +108 -0
  42. package/src/responses/xai-custom-tool-compat.ts +237 -0
  43. package/src/server/grok-responses-snapshot-repair.ts +338 -0
  44. package/src/server/index.ts +2 -1
  45. package/src/server/relay-eager.ts +1 -0
  46. package/src/server/responses/core.ts +173 -13
  47. package/src/server/responses-image-gen-repair.ts +2 -2
  48. package/src/server/sse-payload-rewrite.ts +20 -3
  49. package/src/types.ts +10 -1
  50. package/src/usage/cost.ts +0 -0
  51. package/src/usage/expected-prices.ts +7 -0
  52. package/vendor/ocx-notch/win32-x64/ocx-notch.exe +0 -0
@@ -0,0 +1,108 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { OcxAssistantMessage, OcxMessage, OcxParsedRequest } from "../types";
3
+
4
+ const DELIVERED_FINAL_ANSWER_TTL_MS = 60 * 60 * 1_000;
5
+ const DELIVERED_FINAL_ANSWER_MAX_ENTRIES = 1_024;
6
+
7
+ interface DeliveredFinalAnswerRecord {
8
+ fingerprint: string;
9
+ createdAt: number;
10
+ }
11
+
12
+ const scopesByRequest = new WeakMap<OcxParsedRequest, string>();
13
+ const deliveredFinalAnswers = new Map<string, DeliveredFinalAnswerRecord>();
14
+
15
+ function pruneDeliveredFinalAnswers(at = Date.now()): void {
16
+ for (const [scope, record] of deliveredFinalAnswers) {
17
+ if (at - record.createdAt > DELIVERED_FINAL_ANSWER_TTL_MS) deliveredFinalAnswers.delete(scope);
18
+ }
19
+ while (deliveredFinalAnswers.size > DELIVERED_FINAL_ANSWER_MAX_ENTRIES) {
20
+ const oldest = deliveredFinalAnswers.keys().next().value;
21
+ if (oldest === undefined) break;
22
+ deliveredFinalAnswers.delete(oldest);
23
+ }
24
+ }
25
+
26
+ function textFingerprint(text: string): string {
27
+ return createHash("sha256").update(text, "utf8").digest("hex");
28
+ }
29
+
30
+ function assistantText(message: OcxAssistantMessage): string | undefined {
31
+ if (message.content.some(part => part.type === "toolCall")) return undefined;
32
+ const text = message.content
33
+ .filter((part): part is Extract<typeof part, { type: "text" }> => part.type === "text")
34
+ .map(part => part.text)
35
+ .join("");
36
+ return text.trim().length > 0 ? text : undefined;
37
+ }
38
+
39
+ function deliveredFinalAnswerText(response: unknown): string | undefined {
40
+ if (!response || typeof response !== "object" || Array.isArray(response)) return undefined;
41
+ const output = (response as { output?: unknown }).output;
42
+ if (!Array.isArray(output)) return undefined;
43
+ for (let index = output.length - 1; index >= 0; index -= 1) {
44
+ const item = output[index];
45
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
46
+ const message = item as { type?: unknown; role?: unknown; phase?: unknown; content?: unknown };
47
+ if (message.type !== "message" || message.role !== "assistant" || message.phase !== "final_answer") continue;
48
+ if (!Array.isArray(message.content)) return undefined;
49
+ const text = message.content
50
+ .filter(part => !!part && typeof part === "object" && !Array.isArray(part)
51
+ && (part as { type?: unknown }).type === "output_text"
52
+ && typeof (part as { text?: unknown }).text === "string")
53
+ .map(part => (part as { text: string }).text)
54
+ .join("");
55
+ return text.trim().length > 0 ? text : undefined;
56
+ }
57
+ return undefined;
58
+ }
59
+
60
+ /** Bind only the already-normalized per-conversation digest; raw client ids never enter this map. */
61
+ export function bindTurnTerminationScope(parsed: OcxParsedRequest, scope: string | undefined): void {
62
+ if (!scope || !/^[0-9a-f]{32}$/.test(scope)) return;
63
+ scopesByRequest.set(parsed, scope);
64
+ }
65
+
66
+ /** Remember only a final-answer message the proxy actually emitted for this exact conversation. */
67
+ export function rememberDeliveredFinalAnswer(parsed: OcxParsedRequest, response: unknown): void {
68
+ const scope = scopesByRequest.get(parsed);
69
+ if (!scope) return;
70
+ const text = deliveredFinalAnswerText(response);
71
+ if (!text) return;
72
+ const at = Date.now();
73
+ pruneDeliveredFinalAnswers(at);
74
+ deliveredFinalAnswers.delete(scope);
75
+ deliveredFinalAnswers.set(scope, { fingerprint: textFingerprint(text), createdAt: at });
76
+ pruneDeliveredFinalAnswers(at);
77
+ }
78
+
79
+ /**
80
+ * Match only when the remembered assistant answer is still the trailing content-bearing message.
81
+ * Any later user/tool-result message is new work and must reach Kiro.
82
+ */
83
+ export function hasRecordedTrailingDeliveredFinalAnswer(
84
+ parsed: OcxParsedRequest,
85
+ messages: readonly OcxMessage[],
86
+ ): boolean {
87
+ const scope = scopesByRequest.get(parsed);
88
+ if (!scope) return false;
89
+ pruneDeliveredFinalAnswers();
90
+ const record = deliveredFinalAnswers.get(scope);
91
+ if (!record) return false;
92
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
93
+ const message = messages[index];
94
+ if (message.role !== "assistant") return false;
95
+ const text = assistantText(message as OcxAssistantMessage);
96
+ if (text === undefined) {
97
+ if ((message as OcxAssistantMessage).content.some(part => part.type === "toolCall")) return false;
98
+ continue;
99
+ }
100
+ return textFingerprint(text) === record.fingerprint;
101
+ }
102
+ return false;
103
+ }
104
+
105
+ /** Test-only reset for deterministic cross-test isolation. */
106
+ export function clearDeliveredFinalAnswersForTests(): void {
107
+ deliveredFinalAnswers.clear();
108
+ }
@@ -0,0 +1,237 @@
1
+ import type { SsePayloadRewrite } from "../server/sse-payload-rewrite";
2
+
3
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
4
+ return !!value && typeof value === "object" && !Array.isArray(value);
5
+ }
6
+
7
+ function collectBareCustomToolNames(value: unknown, out: Set<string>): void {
8
+ if (Array.isArray(value)) {
9
+ for (const entry of value) collectBareCustomToolNames(entry, out);
10
+ return;
11
+ }
12
+ if (!isPlainObject(value)) return;
13
+ if (value.type === "custom" && typeof value.name === "string" && typeof value.namespace !== "string") {
14
+ out.add(value.name);
15
+ }
16
+ for (const entry of Object.values(value)) collectBareCustomToolNames(entry, out);
17
+ }
18
+
19
+ function collectConvertedCallIds(value: unknown, names: ReadonlySet<string>, out: Set<string>): void {
20
+ if (Array.isArray(value)) {
21
+ for (const entry of value) collectConvertedCallIds(entry, names, out);
22
+ return;
23
+ }
24
+ if (!isPlainObject(value)) return;
25
+ if (value.type === "custom_tool_call" && typeof value.name === "string" && names.has(value.name) && typeof value.call_id === "string") {
26
+ out.add(value.call_id);
27
+ }
28
+ for (const entry of Object.values(value)) collectConvertedCallIds(entry, names, out);
29
+ }
30
+
31
+ function rewriteToolChoiceForUpstream(value: unknown, names: ReadonlySet<string>): unknown {
32
+ if (!isPlainObject(value)) return value;
33
+ if ((value.type === "custom" || value.type === "function") && typeof value.name === "string" && names.has(value.name)) {
34
+ return value.type === "function" ? value : { ...value, type: "function" };
35
+ }
36
+ if (value.type === "allowed_tools" && Array.isArray(value.tools)) {
37
+ let changed = false;
38
+ const tools = value.tools.map(tool => {
39
+ if (!isPlainObject(tool) || tool.type !== "custom" || typeof tool.name !== "string" || !names.has(tool.name)) return tool;
40
+ changed = true;
41
+ return { ...tool, type: "function" };
42
+ });
43
+ return changed ? { ...value, tools } : value;
44
+ }
45
+ return value;
46
+ }
47
+
48
+ function rewriteForUpstream(value: unknown, names: ReadonlySet<string>, callIds: ReadonlySet<string>): unknown {
49
+ if (Array.isArray(value)) return value.map(entry => rewriteForUpstream(entry, names, callIds));
50
+ if (!isPlainObject(value)) return value;
51
+
52
+ if (value.type === "custom" && typeof value.name === "string" && names.has(value.name)) {
53
+ const { format: _format, ...rest } = value;
54
+ return {
55
+ ...rest,
56
+ type: "function",
57
+ parameters: {
58
+ type: "object",
59
+ properties: {
60
+ input: { type: "string", description: "Raw input for this client-executed custom tool." },
61
+ },
62
+ required: ["input"],
63
+ additionalProperties: false,
64
+ },
65
+ };
66
+ }
67
+
68
+ if (value.type === "custom_tool_call" && typeof value.name === "string" && names.has(value.name)) {
69
+ const { input, id: _id, ...rest } = value;
70
+ return {
71
+ ...rest,
72
+ type: "function_call",
73
+ arguments: JSON.stringify({ input: typeof input === "string" ? input : "" }),
74
+ };
75
+ }
76
+
77
+ if (value.type === "custom_tool_call_output" && typeof value.call_id === "string" && callIds.has(value.call_id)) {
78
+ return { ...value, type: "function_call_output" };
79
+ }
80
+
81
+ let changed = false;
82
+ const next: Record<string, unknown> = {};
83
+ for (const [key, entry] of Object.entries(value)) {
84
+ const rewritten = key === "tool_choice"
85
+ ? rewriteToolChoiceForUpstream(entry, names)
86
+ : rewriteForUpstream(entry, names, callIds);
87
+ next[key] = rewritten;
88
+ changed ||= rewritten !== entry;
89
+ }
90
+ return changed ? next : value;
91
+ }
92
+
93
+ /** Names of bare Responses custom tools that xAI must receive as ordinary functions. */
94
+ export function xaiResponsesCustomToolNames(body: unknown): Set<string> {
95
+ const names = new Set<string>();
96
+ collectBareCustomToolNames(body, names);
97
+ return names;
98
+ }
99
+
100
+ /** Lower bare Responses custom tools to functions for xAI, preserving enough metadata to restore calls. */
101
+ export function lowerXaiResponsesCustomTools(body: unknown): { body: unknown; names: Set<string> } {
102
+ const names = xaiResponsesCustomToolNames(body);
103
+ if (names.size === 0) return { body, names };
104
+ const callIds = new Set<string>();
105
+ collectConvertedCallIds(body, names, callIds);
106
+ return { body: rewriteForUpstream(body, names, callIds), names };
107
+ }
108
+
109
+ function customItemId(id: unknown): unknown {
110
+ return typeof id === "string" && id.startsWith("fc_") ? `ctc_${id.slice(3)}` : id;
111
+ }
112
+
113
+ function unwrapInput(argumentsText: unknown): string {
114
+ if (typeof argumentsText !== "string") return "";
115
+ try {
116
+ const parsed: unknown = JSON.parse(argumentsText);
117
+ if (isPlainObject(parsed) && typeof parsed.input === "string") return parsed.input;
118
+ } catch {
119
+ // Provider may return raw input; preserve it.
120
+ }
121
+ return argumentsText;
122
+ }
123
+
124
+ function restoreItem(item: unknown, names: ReadonlySet<string>): unknown {
125
+ if (!isPlainObject(item) || (item.type !== "function_call" && item.type !== "custom_tool_call")) return item;
126
+ if (typeof item.name !== "string" || !names.has(item.name)) return item;
127
+ const sourceInput = item.type === "function_call" ? item.arguments : item.input;
128
+ const restored: Record<string, unknown> = {
129
+ ...item,
130
+ type: "custom_tool_call",
131
+ id: customItemId(item.id),
132
+ input: unwrapInput(sourceInput),
133
+ };
134
+ delete restored.arguments;
135
+ return restored;
136
+ }
137
+
138
+ function restorePayload(value: unknown, names: ReadonlySet<string>): unknown {
139
+ if (!isPlainObject(value)) return value;
140
+ let changed = false;
141
+ const next: Record<string, unknown> = { ...value };
142
+
143
+ if (Array.isArray(value.output)) {
144
+ const output = value.output.map(item => {
145
+ const restored = restoreItem(item, names);
146
+ changed ||= restored !== item;
147
+ return restored;
148
+ });
149
+ if (changed) next.output = output;
150
+ }
151
+
152
+ if ((value.type === "response.output_item.added" || value.type === "response.output_item.done") && isPlainObject(value.item)) {
153
+ const restored = restoreItem(value.item, names);
154
+ if (restored !== value.item) {
155
+ next.item = restored;
156
+ changed = true;
157
+ }
158
+ }
159
+
160
+ if (typeof value.type === "string" && value.type.startsWith("response.") && isPlainObject(value.response)) {
161
+ const restored = restorePayload(value.response, names);
162
+ if (restored !== value.response) {
163
+ next.response = restored;
164
+ changed = true;
165
+ }
166
+ }
167
+ return changed ? next : value;
168
+ }
169
+
170
+ /** Restore converted calls in a non-streaming Responses JSON body. */
171
+ export function restoreXaiCustomCallsInJson(text: string, names: ReadonlySet<string>): string {
172
+ if (names.size === 0) return text;
173
+ try {
174
+ const parsed = JSON.parse(text) as unknown;
175
+ const restored = restorePayload(parsed, names);
176
+ return restored === parsed ? text : JSON.stringify(restored);
177
+ } catch {
178
+ return text;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Client-facing SSE rewrite. Argument deltas stay preview-only: until the `{input:string}` wrapper
184
+ * becomes valid JSON they are emitted as empty custom-input deltas, then the done frame and final
185
+ * item carry the authoritative raw input. This avoids leaking wrapper JSON while keeping the event
186
+ * sequence valid for Codex.
187
+ */
188
+ export function createXaiCustomToolPayloadRewrite(names: ReadonlySet<string>): SsePayloadRewrite | undefined {
189
+ if (names.size === 0) return undefined;
190
+ const itemIds = new Map<string, string>();
191
+ const convertedItemIds = new Set<string>();
192
+ return (payload: string): string => {
193
+ if (payload === "[DONE]") return payload;
194
+ let value: unknown;
195
+ try { value = JSON.parse(payload); } catch { return payload; }
196
+ if (!isPlainObject(value)) return payload;
197
+
198
+ if ((value.type === "response.output_item.added" || value.type === "response.output_item.done") && isPlainObject(value.item)) {
199
+ const item = value.item;
200
+ if ((item.type === "function_call" || item.type === "custom_tool_call") && typeof item.name === "string" && names.has(item.name)) {
201
+ const oldId = typeof item.id === "string" ? item.id : undefined;
202
+ const newId = customItemId(oldId);
203
+ if (oldId && typeof newId === "string") {
204
+ itemIds.set(oldId, newId);
205
+ convertedItemIds.add(oldId);
206
+ }
207
+ }
208
+ }
209
+
210
+ if (value.type === "response.function_call_arguments.delta") {
211
+ const itemId = typeof value.item_id === "string" ? value.item_id : undefined;
212
+ if (!itemId || !convertedItemIds.has(itemId)) return payload;
213
+ return JSON.stringify({
214
+ ...value,
215
+ type: "response.custom_tool_call_input.delta",
216
+ ...(itemId ? { item_id: itemIds.get(itemId) ?? customItemId(itemId) } : {}),
217
+ delta: "",
218
+ });
219
+ }
220
+ if (value.type === "response.function_call_arguments.done") {
221
+ const itemId = typeof value.item_id === "string" ? value.item_id : undefined;
222
+ if (!itemId || !convertedItemIds.has(itemId)) return payload;
223
+ const input = unwrapInput(value.arguments);
224
+ const next: Record<string, unknown> = {
225
+ ...value,
226
+ type: "response.custom_tool_call_input.done",
227
+ ...(itemId ? { item_id: itemIds.get(itemId) ?? customItemId(itemId) } : {}),
228
+ input,
229
+ };
230
+ delete next.arguments;
231
+ return JSON.stringify(next);
232
+ }
233
+
234
+ const restored = restorePayload(value, names);
235
+ return restored === value ? payload : JSON.stringify(restored);
236
+ };
237
+ }
@@ -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
+ }