pi-openai-codex-compat 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.0.8 - 2026-08-16
6
+
7
+ ### Fixed
8
+
9
+ - Match Codex tool-call commit semantics by trusting only `response.output_item.done`, ignoring terminal output snapshots and terminal-only calls, executing the completed subset of mixed batches, and preserving completed calls across incomplete or failed response handling.
10
+
5
11
  ## 0.0.7 - 2026-08-16
6
12
 
7
13
  ### Added
@@ -115,6 +115,7 @@ type ActiveAgentTurn = {
115
115
  turnId: string;
116
116
  startedAtUnixMs: number;
117
117
  turnState: CodexTurnState;
118
+ pendingPostToolDisposition?: CodexPostToolDisposition;
118
119
  };
119
120
 
120
121
  type CodexCompat = {
@@ -130,25 +131,27 @@ type CodexTerminalState = {
130
131
 
131
132
  type CodexAttemptCapture = {
132
133
  streamedItems: ResponsesItem[];
133
- streamedToolCallKeys: Set<string>;
134
- streamedCompletedToolCallKeys: Set<string>;
135
- terminalItems?: ResponsesItem[];
134
+ streamedToolCallIndexes: Set<number>;
135
+ streamedCompletedToolCallIndexes: Set<number>;
136
136
  };
137
137
 
138
138
  type CodexToolCallAssessment = {
139
- allComplete: boolean;
140
- authoritativeCount: number;
141
- hasToolCalls: boolean;
142
- omittedStreamedCount: number;
143
- terminalCount: number;
139
+ completedCount: number;
140
+ discardedPartialCount: number;
141
+ hasCompletedCalls: boolean;
142
+ };
143
+
144
+ type CodexPostToolDisposition = {
145
+ callIds: string[];
146
+ response?: JsonRecord;
147
+ retryAttempt: number;
148
+ terminalType: "response.incomplete" | "response.failed";
149
+ type: "error" | "retry";
144
150
  };
145
151
 
146
152
  type CodexResponseDecision =
147
153
  | "continue_no_tools"
148
- | "preserve_terminal_error"
149
- | "reject_terminal_stream_mismatch"
150
154
  | "retry_original_input"
151
- | "return_length_incomplete_call"
152
155
  | "return_terminal"
153
156
  | "return_tool_use";
154
157
 
@@ -217,62 +220,22 @@ function isToolCallItem(item: ResponsesItem): boolean {
217
220
  return item.type === "function_call" || item.type === "custom_tool_call";
218
221
  }
219
222
 
220
- function toolCallKey(item: ResponsesItem): string {
221
- const callId = typeof item["call_id"] === "string" ? item["call_id"] : undefined;
222
- const itemId = typeof item.id === "string" ? item.id : undefined;
223
- return `${String(item.type)}:${itemId ? `item:${itemId}` : `call:${callId ?? "missing"}`}`;
224
- }
225
-
226
- function hasValidToolCallPayload(item: ResponsesItem): boolean {
227
- if (
228
- typeof item.id !== "string" ||
229
- typeof item["call_id"] !== "string" ||
230
- typeof item.name !== "string"
231
- ) {
232
- return false;
233
- }
234
- if (item.type === "custom_tool_call") return typeof item.input === "string";
235
- if (item.type !== "function_call" || typeof item.arguments !== "string") return false;
236
- try {
237
- return isObject(JSON.parse(item.arguments));
238
- } catch {
239
- return false;
240
- }
241
- }
242
-
243
- function toolCallIsComplete(
244
- item: ResponsesItem,
245
- capture: CodexAttemptCapture,
246
- terminalType: CodexTerminalState["type"],
247
- ): boolean {
248
- if (!hasValidToolCallPayload(item)) return false;
249
- const status = typeof item["status"] === "string" ? item["status"] : undefined;
250
- if (status !== undefined) return status === "completed";
251
- if (capture.streamedCompletedToolCallKeys.has(toolCallKey(item))) return true;
252
- return terminalType === "response.completed";
223
+ function eventOutputIndex(event: JsonRecord): number {
224
+ return typeof event["output_index"] === "number" ? event["output_index"] : 0;
253
225
  }
254
226
 
255
227
  function assessAttemptToolCalls(
256
228
  items: readonly ResponsesItem[],
257
229
  capture: CodexAttemptCapture,
258
- terminalType: CodexTerminalState["type"],
259
230
  ): CodexToolCallAssessment {
260
- const calls = items.filter(isToolCallItem);
261
- const authoritativeKeys = new Set(calls.map(toolCallKey));
262
- const omittedStreamedCount = [...capture.streamedToolCallKeys].filter(
263
- (key) => !authoritativeKeys.has(key),
231
+ const completedCount = items.filter(isToolCallItem).length;
232
+ const discardedPartialCount = [...capture.streamedToolCallIndexes].filter(
233
+ (index) => !capture.streamedCompletedToolCallIndexes.has(index),
264
234
  ).length;
265
- const hasToolCalls = calls.length > 0 || capture.streamedToolCallKeys.size > 0;
266
235
  return {
267
- hasToolCalls,
268
- allComplete:
269
- hasToolCalls &&
270
- omittedStreamedCount === 0 &&
271
- calls.length > 0 &&
272
- calls.every((item) => toolCallIsComplete(item, capture, terminalType)),
273
- authoritativeCount: calls.length,
274
- omittedStreamedCount,
275
- terminalCount: capture.terminalItems?.filter(isToolCallItem).length ?? 0,
236
+ completedCount,
237
+ discardedPartialCount,
238
+ hasCompletedCalls: completedCount > 0,
276
239
  };
277
240
  }
278
241
 
@@ -291,6 +254,7 @@ function responseDecisionDiagnostic(options: {
291
254
  capture: CodexAttemptCapture;
292
255
  decision: CodexResponseDecision;
293
256
  incompleteReason?: string;
257
+ postToolDisposition?: "continue" | "error" | "retry";
294
258
  terminalState: CodexTerminalState;
295
259
  toolCalls: CodexToolCallAssessment;
296
260
  }): AssistantMessageDiagnostic | undefined {
@@ -302,8 +266,7 @@ function responseDecisionDiagnostic(options: {
302
266
  options.attempt > 1 ||
303
267
  options.terminalState.type !== "response.completed" ||
304
268
  endTurn === false ||
305
- options.toolCalls.hasToolCalls ||
306
- options.toolCalls.omittedStreamedCount > 0;
269
+ options.capture.streamedToolCallIndexes.size > 0;
307
270
  if (!nontrivial) return undefined;
308
271
 
309
272
  return {
@@ -314,14 +277,12 @@ function responseDecisionDiagnostic(options: {
314
277
  terminalType: options.terminalState.type ?? "missing",
315
278
  ...(options.incompleteReason ? { incompleteReason: options.incompleteReason } : {}),
316
279
  ...(endTurn === undefined ? {} : { endTurn }),
317
- itemSource: options.capture.terminalItems ? "terminal" : "stream-fallback",
318
280
  outputItemTypes: outputItemTypeCounts(options.attemptItems),
319
- streamedCallsStarted: options.capture.streamedToolCallKeys.size,
320
- streamedCallsCompleted: options.capture.streamedCompletedToolCallKeys.size,
321
- terminalCalls: options.toolCalls.terminalCount,
322
- authoritativeCalls: options.toolCalls.authoritativeCount,
323
- terminalOmittedStreamedCalls: options.toolCalls.omittedStreamedCount,
324
- allCallsComplete: options.toolCalls.allComplete,
281
+ streamedCallsStarted: options.capture.streamedToolCallIndexes.size,
282
+ streamedCallsDone: options.capture.streamedCompletedToolCallIndexes.size,
283
+ returnedCalls: options.toolCalls.completedCount,
284
+ discardedPartialCalls: options.toolCalls.discardedPartialCount,
285
+ ...(options.postToolDisposition ? { postToolDisposition: options.postToolDisposition } : {}),
325
286
  decision: options.decision,
326
287
  },
327
288
  };
@@ -340,28 +301,17 @@ function captureRawEvents(
340
301
  isResponsesItem(event.item) &&
341
302
  isToolCallItem(event.item)
342
303
  ) {
343
- capture.streamedToolCallKeys.add(toolCallKey(event.item));
304
+ capture.streamedToolCallIndexes.add(eventOutputIndex(event));
344
305
  }
345
306
  if (event.type === "response.output_item.done" && isResponsesItem(event.item)) {
346
307
  const item = structuredClone(event.item);
347
308
  capture.streamedItems.push(item);
348
309
  if (isToolCallItem(item)) {
349
- const key = toolCallKey(item);
350
- capture.streamedToolCallKeys.add(key);
351
- capture.streamedCompletedToolCallKeys.add(key);
310
+ const index = eventOutputIndex(event);
311
+ capture.streamedToolCallIndexes.add(index);
312
+ capture.streamedCompletedToolCallIndexes.add(index);
352
313
  }
353
314
  }
354
- if (
355
- (event.type === "response.completed" ||
356
- event.type === "response.incomplete" ||
357
- event.type === "response.failed") &&
358
- isObject(event.response) &&
359
- Array.isArray(event.response["output"])
360
- ) {
361
- capture.terminalItems = event.response["output"]
362
- .filter(isResponsesItem)
363
- .map((item) => structuredClone(item));
364
- }
365
315
  if (
366
316
  terminalState &&
367
317
  (event.type === "response.completed" ||
@@ -462,6 +412,48 @@ function retryableResponseFailure(response: JsonRecord | undefined): boolean {
462
412
  );
463
413
  }
464
414
 
415
+ function terminalErrorMessage(disposition: CodexPostToolDisposition): string {
416
+ if (disposition.terminalType === "response.failed") {
417
+ const error = isObject(disposition.response?.["error"])
418
+ ? disposition.response["error"]
419
+ : undefined;
420
+ return typeof error?.["message"] === "string" ? error["message"] : "Codex response failed";
421
+ }
422
+ const details = isObject(disposition.response?.["incomplete_details"])
423
+ ? disposition.response["incomplete_details"]
424
+ : undefined;
425
+ const reason = typeof details?.["reason"] === "string" ? details["reason"] : undefined;
426
+ return reason
427
+ ? `Response incomplete: ${reason}`
428
+ : "Response incomplete without a provider reason";
429
+ }
430
+
431
+ function completedAttemptToolCallIds(
432
+ message: AssistantMessage,
433
+ attempt: CodexStreamAttemptState,
434
+ ): string[] {
435
+ return [...attempt.completedContentIndexes]
436
+ .sort((left, right) => left - right)
437
+ .flatMap((index) => {
438
+ const block = message.content[index];
439
+ return block?.type === "toolCall" ? [block.id] : [];
440
+ });
441
+ }
442
+
443
+ function assertLinkedToolOutputs(context: Context, disposition: CodexPostToolDisposition): void {
444
+ const outputIds = new Set(
445
+ context.messages.flatMap((message) =>
446
+ message.role === "toolResult" ? [message.toolCallId] : [],
447
+ ),
448
+ );
449
+ const missing = disposition.callIds.filter((callId) => !outputIds.has(callId));
450
+ if (missing.length > 0) {
451
+ throw new Error(
452
+ `Codex cannot process the ${disposition.terminalType} response until Pi records tool output for: ${missing.join(", ")}`,
453
+ );
454
+ }
455
+ }
456
+
465
457
  function responseRetryDelayMs(baseDelayMs: number, attempt: number): number {
466
458
  if (baseDelayMs <= 0) return 0;
467
459
  const exponential = baseDelayMs * 2 ** Math.max(0, attempt - 1);
@@ -1169,6 +1161,26 @@ export class CodexProviderRuntime {
1169
1161
  requestOptions.cacheRetention === "none" ? undefined : codexCacheKey(runtimeSessionId);
1170
1162
  const agentTurn = this.agentTurn(runtimeSessionId);
1171
1163
  const responsesLiteEnabled = this.responsesLiteEnabled(runtimeSessionId);
1164
+ let carriedResponseRetries = 0;
1165
+ const pendingPostToolDisposition = agentTurn.pendingPostToolDisposition;
1166
+ if (pendingPostToolDisposition) {
1167
+ assertLinkedToolOutputs(context, pendingPostToolDisposition);
1168
+ delete agentTurn.pendingPostToolDisposition;
1169
+ if (
1170
+ pendingPostToolDisposition.type === "error" ||
1171
+ pendingPostToolDisposition.retryAttempt > this.responseRetryPolicy.maxRetries
1172
+ ) {
1173
+ throw new Error(terminalErrorMessage(pendingPostToolDisposition));
1174
+ }
1175
+ carriedResponseRetries = pendingPostToolDisposition.retryAttempt;
1176
+ await waitForResponseRetry(
1177
+ responseRetryDelayMs(
1178
+ this.responseRetryPolicy.baseDelayMs,
1179
+ pendingPostToolDisposition.retryAttempt,
1180
+ ),
1181
+ requestOptions.signal,
1182
+ );
1183
+ }
1172
1184
  const grammarToolInputProperties = createGrammarToolInputProperties(
1173
1185
  context.tools,
1174
1186
  (model.compat as CodexCompat | undefined)?.supportsOpenAIGrammarTools ?? false,
@@ -1255,20 +1267,19 @@ export class CodexProviderRuntime {
1255
1267
  },
1256
1268
  };
1257
1269
  let responseRequests = 0;
1258
- let responseRetries = 0;
1270
+ let responseRetries = carriedResponseRetries;
1259
1271
  while (true) {
1260
1272
  responseRequests += 1;
1261
1273
  const attemptCapture: CodexAttemptCapture = {
1262
1274
  streamedItems: [],
1263
- streamedToolCallKeys: new Set(),
1264
- streamedCompletedToolCallKeys: new Set(),
1275
+ streamedToolCallIndexes: new Set(),
1276
+ streamedCompletedToolCallIndexes: new Set(),
1265
1277
  };
1266
1278
  const terminalState: CodexTerminalState = {};
1267
1279
  const attemptState: CodexStreamAttemptState = {
1268
1280
  startedContentIndexes: new Set(),
1269
1281
  completedContentIndexes: new Set(),
1270
1282
  };
1271
- const contentLengthBeforeAttempt = output.content.length;
1272
1283
  const usageBeforeAttempt = structuredClone(output.usage);
1273
1284
  output.usage = emptyUsage();
1274
1285
  try {
@@ -1300,14 +1311,10 @@ export class CodexProviderRuntime {
1300
1311
  throw error;
1301
1312
  }
1302
1313
  output.usage = accumulateUsage(usageBeforeAttempt, output.usage);
1303
- // A terminal response.output is the complete provider snapshot when present.
1304
- // Stream-completed items are the fallback for transports that omit that field.
1305
- const attemptItems = attemptCapture.terminalItems ?? attemptCapture.streamedItems;
1306
- const toolCalls = assessAttemptToolCalls(
1307
- attemptItems,
1308
- attemptCapture,
1309
- terminalState.type,
1310
- );
1314
+ // `response.output_item.done` is Codex's item-level commit point. Terminal
1315
+ // response.output snapshots are deliberately ignored.
1316
+ const attemptItems = attemptCapture.streamedItems;
1317
+ const toolCalls = assessAttemptToolCalls(attemptItems, attemptCapture);
1311
1318
  const incompleteDetails = isObject(terminalState.response?.["incomplete_details"])
1312
1319
  ? terminalState.response["incomplete_details"]
1313
1320
  : undefined;
@@ -1315,67 +1322,55 @@ export class CodexProviderRuntime {
1315
1322
  typeof incompleteDetails?.["reason"] === "string"
1316
1323
  ? incompleteDetails["reason"]
1317
1324
  : undefined;
1318
- const maxOutputIncomplete =
1319
- terminalState.type === "response.incomplete" &&
1320
- incompleteReason === "max_output_tokens";
1321
- const recordDecision = (decision: CodexResponseDecision): void => {
1325
+ const recordDecision = (
1326
+ decision: CodexResponseDecision,
1327
+ postToolDisposition?: "continue" | "error" | "retry",
1328
+ ): void => {
1322
1329
  const diagnostic = responseDecisionDiagnostic({
1323
1330
  attempt: responseRequests,
1324
1331
  attemptItems,
1325
1332
  capture: attemptCapture,
1326
1333
  decision,
1327
1334
  ...(incompleteReason ? { incompleteReason } : {}),
1335
+ ...(postToolDisposition ? { postToolDisposition } : {}),
1328
1336
  terminalState,
1329
1337
  toolCalls,
1330
1338
  });
1331
1339
  if (diagnostic) output.diagnostics = [...(output.diagnostics ?? []), diagnostic];
1332
1340
  };
1333
1341
 
1334
- // Tool calls create a client-execution boundary. Never append them to an
1335
- // internal provider continuation before Pi can return the linked outputs.
1336
- if (toolCalls.hasToolCalls) {
1337
- if (
1338
- toolCalls.allComplete &&
1339
- ((terminalState.type === "response.completed" && output.stopReason === "toolUse") ||
1340
- maxOutputIncomplete)
1341
- ) {
1342
- discardIncompleteAttemptContent(output, attemptState);
1343
- rawItems.push(...attemptItems.map((item) => structuredClone(item)));
1344
- output.stopReason = "toolUse";
1345
- delete output.errorMessage;
1346
- recordDecision("return_tool_use");
1347
- break;
1342
+ // Return each done tool call before processing an unsuccessful response
1343
+ // terminal. Pi will execute the completed subset; started-only siblings
1344
+ // are discarded and never enter provider history.
1345
+ if (toolCalls.hasCompletedCalls) {
1346
+ const callIds = completedAttemptToolCallIds(output, attemptState);
1347
+ if (callIds.length !== toolCalls.completedCount) {
1348
+ throw new Error("Codex completed tool-call items could not be mapped to Pi calls.");
1348
1349
  }
1349
-
1350
- output.content.splice(contentLengthBeforeAttempt);
1350
+ let postToolDisposition: "continue" | "error" | "retry" = "continue";
1351
1351
  if (
1352
- terminalState.type === "response.failed" &&
1353
- retryableResponseFailure(terminalState.response) &&
1354
- responseRetries < this.responseRetryPolicy.maxRetries
1352
+ terminalState.type === "response.incomplete" ||
1353
+ terminalState.type === "response.failed"
1355
1354
  ) {
1356
- responseRetries += 1;
1357
- output.stopReason = "pending";
1358
- delete output.errorMessage;
1359
- delete output.rawStopReason;
1360
- recordDecision("retry_original_input");
1361
- await waitForResponseRetry(
1362
- responseRetryDelayMs(this.responseRetryPolicy.baseDelayMs, responseRetries),
1363
- requestOptions.signal,
1364
- );
1365
- continue;
1366
- }
1367
- if (terminalState.type === "response.completed" && output.stopReason !== "error") {
1368
- output.stopReason = "length";
1369
- output.rawStopReason = "incomplete.tool_call";
1370
- delete output.errorMessage;
1355
+ const retryable =
1356
+ terminalState.type === "response.incomplete" ||
1357
+ retryableResponseFailure(terminalState.response);
1358
+ postToolDisposition = retryable ? "retry" : "error";
1359
+ agentTurn.pendingPostToolDisposition = {
1360
+ callIds,
1361
+ ...(terminalState.response
1362
+ ? { response: structuredClone(terminalState.response) }
1363
+ : {}),
1364
+ retryAttempt: retryable ? responseRetries + 1 : responseRetries,
1365
+ terminalType: terminalState.type,
1366
+ type: postToolDisposition,
1367
+ };
1371
1368
  }
1372
- recordDecision(
1373
- toolCalls.omittedStreamedCount > 0
1374
- ? "reject_terminal_stream_mismatch"
1375
- : output.stopReason === "length"
1376
- ? "return_length_incomplete_call"
1377
- : "preserve_terminal_error",
1378
- );
1369
+ discardIncompleteAttemptContent(output, attemptState);
1370
+ rawItems.push(...attemptItems.map((item) => structuredClone(item)));
1371
+ output.stopReason = "toolUse";
1372
+ delete output.errorMessage;
1373
+ recordDecision("return_tool_use", postToolDisposition);
1379
1374
  break;
1380
1375
  }
1381
1376
 
@@ -1396,7 +1391,9 @@ export class CodexProviderRuntime {
1396
1391
  output.stopReason = "pending";
1397
1392
  delete output.errorMessage;
1398
1393
  delete output.rawStopReason;
1399
- recordDecision("continue_no_tools");
1394
+ recordDecision(
1395
+ attemptItems.length === 0 ? "retry_original_input" : "continue_no_tools",
1396
+ );
1400
1397
  await waitForResponseRetry(
1401
1398
  responseRetryDelayMs(this.responseRetryPolicy.baseDelayMs, responseRetries),
1402
1399
  requestOptions.signal,
@@ -141,11 +141,6 @@ function appendCustomInput(
141
141
  return delta;
142
142
  }
143
143
 
144
- function responseItems(value: unknown): JsonRecord[] {
145
- if (!Array.isArray(value)) return [];
146
- return value.filter(isObject);
147
- }
148
-
149
144
  function itemContentText(item: JsonRecord): string {
150
145
  if (!Array.isArray(item.content)) return "";
151
146
  return item.content
@@ -210,7 +205,7 @@ export async function processCodexStream(
210
205
  let terminal = false;
211
206
  const slots = new Map<number, OutputSlot>();
212
207
  const completedOutputItems = new Set<string>();
213
- const reasoningById = new Map<string, ThinkingContent>();
208
+ const completedToolCallContentIndexes = new Set<number>();
214
209
  const applyMessagePhaseStopReason = (item: JsonRecord): void => {
215
210
  if (item.type === "message" && item["phase"] === "final_answer") {
216
211
  output.stopReason = "stop";
@@ -336,7 +331,6 @@ export async function processCodexStream(
336
331
  if (item.type === "reasoning" && slot?.type === "thinking") {
337
332
  slot.block.thinking = reasoningText(item) || slot.block.thinking;
338
333
  slot.block.thinkingSignature = JSON.stringify(item);
339
- if (typeof item.id === "string") reasoningById.set(item.id, slot.block);
340
334
  stream.push({
341
335
  type: "thinking_end",
342
336
  contentIndex: slot.contentIndex,
@@ -375,6 +369,7 @@ export async function processCodexStream(
375
369
  partial: output,
376
370
  });
377
371
  trackCompleted(slot);
372
+ completedToolCallContentIndexes.add(slot.contentIndex);
378
373
  slots.delete(index);
379
374
  } else if (item.type === "custom_tool_call" && slot?.type === "toolCall") {
380
375
  slot.block.name = piToolCallName(item);
@@ -388,6 +383,7 @@ export async function processCodexStream(
388
383
  partial: output,
389
384
  });
390
385
  trackCompleted(slot);
386
+ completedToolCallContentIndexes.add(slot.contentIndex);
391
387
  slots.delete(index);
392
388
  }
393
389
  };
@@ -424,20 +420,6 @@ export async function processCodexStream(
424
420
  output.usage,
425
421
  typeof response.service_tier === "string" ? response.service_tier : undefined,
426
422
  );
427
- const terminalItems = responseItems(response["output"]);
428
- terminalItems.forEach((item, index) => completeOutputItem(index, item));
429
- for (const item of terminalItems) {
430
- if (item.type !== "reasoning" || typeof item.id !== "string") continue;
431
- const block = reasoningById.get(item.id);
432
- if (!block?.thinkingSignature || typeof item.encrypted_content !== "string") continue;
433
- const stored = JSON.parse(block.thinkingSignature) as JsonRecord;
434
- if (typeof stored.encrypted_content !== "string") {
435
- block.thinkingSignature = JSON.stringify({
436
- ...stored,
437
- encrypted_content: item.encrypted_content,
438
- });
439
- }
440
- }
441
423
  const status = normalizeCodexStatus(response["status"]);
442
424
  const incompleteDetails = isObject(response["incomplete_details"])
443
425
  ? response["incomplete_details"]
@@ -452,7 +434,12 @@ export async function processCodexStream(
452
434
  output.stopReason = mappedStop.stopReason;
453
435
  if (mappedStop.errorMessage === undefined) delete output.errorMessage;
454
436
  else output.errorMessage = mappedStop.errorMessage;
455
- if (output.stopReason === "stop" && output.content.some((block) => block.type === "toolCall")) {
437
+ if (
438
+ output.stopReason === "stop" &&
439
+ [...completedToolCallContentIndexes].some(
440
+ (contentIndex) => output.content[contentIndex]?.type === "toolCall",
441
+ )
442
+ ) {
456
443
  output.stopReason = "toolUse";
457
444
  }
458
445
  };
@@ -1910,16 +1910,6 @@ async function* requestWebSocket(
1910
1910
  responseCompleted = true;
1911
1911
  }
1912
1912
  if (typeof event.response.id === "string") responseId = event.response.id;
1913
- if (Array.isArray(event.response["output"])) {
1914
- const terminalItems = event.response["output"].filter(isObject);
1915
- if (terminalItems.length > 0) {
1916
- responseItems.splice(
1917
- 0,
1918
- responseItems.length,
1919
- ...terminalItems.map((item) => structuredClone(item)),
1920
- );
1921
- }
1922
- }
1923
1913
  }
1924
1914
  const normalized = normalizeEvent(event);
1925
1915
  if (!normalized) continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-openai-codex-compat",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "OpenAI Codex compatibility for Pi with native compaction, fast mode, and Codex-optimized capabilities",
5
5
  "keywords": [
6
6
  "pi-package"