@opengeni/codex 0.2.23 → 0.2.24-canary.35585322809001

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.
@@ -0,0 +1,2 @@
1
+ /** Conservative progress evidence; reasoning/usage/empty terminal bookkeeping is not work. */
2
+ export declare function hasMeaningfulCodexOutput(output: unknown): boolean;
@@ -0,0 +1,16 @@
1
+ /** Durable control records; only providerData is sent by the SDK unknown-item adapter. */
2
+ export declare const REASONING_CONFIGURATION_FIELD = "opengeniReasoningConfiguration";
3
+ export type ConfigurationEffort = "low" | "medium" | "high" | "xhigh" | "max";
4
+ export declare function supportsReasoningConfiguration(model: string, effort: string): effort is ConfigurationEffort;
5
+ export type ReasoningConfiguration = {
6
+ version: 1;
7
+ baselineEffort: ConfigurationEffort;
8
+ effort: ConfigurationEffort;
9
+ turnId: string;
10
+ };
11
+ export declare function readReasoningConfiguration(item: Record<string, unknown>): ReasoningConfiguration | null;
12
+ export declare function reasoningConfigurationItem(state: ReasoningConfiguration): Record<string, unknown>;
13
+ export declare function latestReasoningConfiguration(items: readonly Record<string, unknown>[]): ReasoningConfiguration | null;
14
+ export declare function isReasoningConfigurationUpdate(item: Record<string, unknown>): boolean;
15
+ /** Never leak Astra controls to an incompatible model. Coalesce only adjacent controls. */
16
+ export declare function projectReasoningConfigurations(items: Record<string, unknown>[], enabled: boolean): Record<string, unknown>[];
@@ -48,6 +48,8 @@ export type CodexModelRequestEvent = {
48
48
  providerRequestId?: string;
49
49
  status?: number;
50
50
  willRetry?: boolean;
51
+ /** Only true for a successful response with substantive assistant/tool output. */
52
+ meaningfulOutput?: boolean;
51
53
  };
52
54
  export type CodexRequestOpaqueArtifacts = {
53
55
  requestId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/codex",
3
- "version": "0.2.23",
3
+ "version": "0.2.24-canary.35585322809001",
4
4
  "description": "ChatGPT/Codex subscription auth + transport: device-code login, token refresh, and the Responses-backend fetch. Pure HTTP + transforms; no database dependency.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -41,7 +41,7 @@
41
41
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
42
42
  },
43
43
  "dependencies": {
44
- "@opengeni/network": "^0.3.1",
44
+ "@opengeni/network": "^0.3.1-canary.35585322809001",
45
45
  "zod": "^4.2.1"
46
46
  },
47
47
  "engines": {
package/src/fetch.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  // Stream parsing is delegated to the SDK (SSE passthrough; spec §0(d)).
12
12
 
13
13
  import { randomUUID } from "node:crypto";
14
+ import { hasMeaningfulCodexOutput } from "./meaningful-output";
14
15
  import { CODEX_ORIGINATOR } from "./constants";
15
16
  import { normalizeCodexRequestBody } from "./normalize";
16
17
  import { opaqueProviderArtifactFingerprints } from "./opaque-artifact";
@@ -95,6 +96,9 @@ export type CodexEncryptedArtifactRejection = {
95
96
  kind: "encrypted_content_rejected";
96
97
  };
97
98
 
99
+ /** Exact Codex `error.code` for an opaque artifact the backend can no longer use. */
100
+ export const CODEX_ENCRYPTED_CONTENT_REJECTION_CODE = "invalid_encrypted_content";
101
+
98
102
  /**
99
103
  * Classify only the provider's definitive request rejection for an opaque
100
104
  * reasoning artifact that it can no longer decrypt/parse. A Codex transport
@@ -113,6 +117,14 @@ export function classifyCodexEncryptedArtifactRejection(
113
117
  ? (value.error as Record<string, unknown>)
114
118
  : null;
115
119
  const status = Number(value.status ?? body?.status);
120
+ // The exact provider error code names this family without a message
121
+ // match: production compaction requests have been rejected with it while
122
+ // the human-readable text varied. It is never a generic 400.
123
+ const exactCode =
124
+ typeof body?.code === "string" ? body.code : typeof value.code === "string" ? value.code : "";
125
+ if (status === 400 && exactCode === CODEX_ENCRYPTED_CONTENT_REJECTION_CODE) {
126
+ return { status: 400, kind: "encrypted_content_rejected" };
127
+ }
116
128
  const message = [
117
129
  typeof value.message === "string" ? value.message : "",
118
130
  typeof body?.message === "string" ? body.message : "",
@@ -221,8 +233,7 @@ type RequestTerminalOutcome = "completed" | "failed" | "timed_out";
221
233
 
222
234
  type SemanticTerminalState = {
223
235
  phase: "completed" | "failed" | null;
224
- /** Non-streaming callers must parse the complete SSE body before settling. */
225
- deferTransportTerminal: boolean;
236
+ meaningfulOutput?: boolean;
226
237
  };
227
238
 
228
239
  type CodexSseEvent = {
@@ -312,6 +323,13 @@ function markSemanticTerminal(state: SemanticTerminalState, phase: "completed" |
312
323
  }
313
324
  }
314
325
 
326
+ /** EOF and post-terminal cleanup must preserve the same recovery evidence. */
327
+ function semanticCompletionEvidence(state: SemanticTerminalState | undefined): {
328
+ meaningfulOutput?: boolean;
329
+ } {
330
+ return state?.phase === "completed" ? { meaningfulOutput: state.meaningfulOutput === true } : {};
331
+ }
332
+
315
333
  function terminalOutcomeForPhase(
316
334
  phase: CodexModelRequestEvent["phase"],
317
335
  ): RequestTerminalOutcome | null {
@@ -426,6 +444,7 @@ async function observedResponse(
426
444
  if (semanticTerminal) markSemanticTerminal(semanticTerminal, "failed");
427
445
  await emitRequestEvent(audit, {
428
446
  phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
447
+ ...semanticCompletionEvidence(semanticTerminal),
429
448
  responseObserved: true,
430
449
  status: res.status,
431
450
  ...(requestId ? { providerRequestId: requestId } : {}),
@@ -459,6 +478,7 @@ async function observedResponse(
459
478
  void reader.cancel(error).catch(() => undefined);
460
479
  void emitRequestEvent(audit, {
461
480
  phase,
481
+ ...semanticCompletionEvidence(semanticTerminal),
462
482
  responseObserved: true,
463
483
  ...(phase === "timed_out" ? { timeoutClass: klass } : {}),
464
484
  status: res.status,
@@ -486,6 +506,7 @@ async function observedResponse(
486
506
  void reader.cancel(reason).catch(() => undefined);
487
507
  void emitRequestEvent(audit, {
488
508
  phase: semanticTerminal?.phase ?? "failed",
509
+ ...semanticCompletionEvidence(semanticTerminal),
489
510
  responseObserved: true,
490
511
  status: res.status,
491
512
  ...(requestId ? { providerRequestId: requestId } : {}),
@@ -512,14 +533,12 @@ async function observedResponse(
512
533
  if (chunk.done) {
513
534
  terminal = true;
514
535
  clearTimers();
515
- if (semanticTerminal && semanticTerminal.phase === null) {
516
- if (!semanticTerminal.deferTransportTerminal) {
517
- markSemanticTerminal(semanticTerminal, "failed");
518
- }
519
- }
520
- if (!semanticTerminal?.deferTransportTerminal || semanticTerminal.phase !== null) {
536
+ // The SSE parser owns EOF classification: it may still have a final
537
+ // terminal block buffered without a blank separator.
538
+ if (!semanticTerminal || semanticTerminal.phase !== null) {
521
539
  await emitRequestEvent(audit, {
522
540
  phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
541
+ ...semanticCompletionEvidence(semanticTerminal),
523
542
  responseObserved: true,
524
543
  status: res.status,
525
544
  ...(requestId ? { providerRequestId: requestId } : {}),
@@ -555,6 +574,7 @@ async function observedResponse(
555
574
  }
556
575
  await emitRequestEvent(audit, {
557
576
  phase: semanticPhase ?? "failed",
577
+ ...semanticCompletionEvidence(semanticTerminal),
558
578
  responseObserved: true,
559
579
  status: res.status,
560
580
  ...(requestId ? { providerRequestId: requestId } : {}),
@@ -575,6 +595,7 @@ async function observedResponse(
575
595
  }
576
596
  await emitRequestEvent(audit, {
577
597
  phase: semanticTerminal?.phase ?? "failed",
598
+ ...semanticCompletionEvidence(semanticTerminal),
578
599
  responseObserved: true,
579
600
  status: res.status,
580
601
  ...(requestId ? { providerRequestId: requestId } : {}),
@@ -749,7 +770,6 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
749
770
  });
750
771
  const semanticTerminal: SemanticTerminalState = {
751
772
  phase: null,
752
- deferTransportTerminal: !callerWantsStream,
753
773
  };
754
774
  try {
755
775
  await ctx.beforeProviderDispatch?.();
@@ -835,9 +855,23 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
835
855
  return buffered;
836
856
  }
837
857
  if (callerWantsStream) {
838
- res = validateCodexStream(res, (phase) => {
839
- markSemanticTerminal(semanticTerminal, phase);
840
- });
858
+ res = validateCodexStream(
859
+ res,
860
+ (phase, meaningfulOutput) => {
861
+ markSemanticTerminal(semanticTerminal, phase);
862
+ semanticTerminal.meaningfulOutput = meaningfulOutput === true;
863
+ },
864
+ async () => {
865
+ const upstreamRequestId = providerRequestId(res.headers);
866
+ await emitRequestEvent(audit, {
867
+ phase: semanticTerminal.phase ?? "failed",
868
+ ...semanticCompletionEvidence(semanticTerminal),
869
+ responseObserved: true,
870
+ status: res.status,
871
+ ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
872
+ });
873
+ },
874
+ );
841
875
  } else {
842
876
  res = await sseToJsonResponse(res, audit, semanticTerminal);
843
877
  }
@@ -1081,6 +1115,7 @@ async function sseToJsonResponse(
1081
1115
  markSemanticTerminal(semanticTerminal, "completed");
1082
1116
  await emitRequestEvent(audit, {
1083
1117
  phase: "completed",
1118
+ meaningfulOutput: hasMeaningfulCodexOutput(final?.output),
1084
1119
  responseObserved: true,
1085
1120
  status: res.status,
1086
1121
  ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
@@ -1362,7 +1397,8 @@ function codexSseFailureError(
1362
1397
  */
1363
1398
  function validateCodexStream(
1364
1399
  res: Response,
1365
- onSemanticTerminal?: (phase: "completed" | "failed") => void,
1400
+ onSemanticTerminal?: (phase: "completed" | "failed", meaningfulOutput?: boolean) => void,
1401
+ onParsedEof?: () => Promise<void>,
1366
1402
  ): Response {
1367
1403
  if (!res.body) {
1368
1404
  onSemanticTerminal?.("failed");
@@ -1389,6 +1425,12 @@ function validateCodexStream(
1389
1425
  const encoder = new TextEncoder();
1390
1426
  let buffer = "";
1391
1427
  let successfulTerminalSeen = false;
1428
+ let meaningfulOutput = false;
1429
+ const observeOutput = (output: unknown) => {
1430
+ meaningfulOutput ||= hasMeaningfulCodexOutput(output);
1431
+ };
1432
+ const observeTerminal = (phase: "completed" | "failed") =>
1433
+ onSemanticTerminal?.(phase, phase === "completed" && meaningfulOutput);
1392
1434
  const emitCompleteBlocks = (
1393
1435
  controller: TransformStreamDefaultController<Uint8Array>,
1394
1436
  final: boolean,
@@ -1398,7 +1440,7 @@ function validateCodexStream(
1398
1440
  const block = buffer.slice(0, boundary.start);
1399
1441
  const separator = buffer.slice(boundary.start, boundary.end);
1400
1442
  buffer = buffer.slice(boundary.end);
1401
- successfulTerminalSeen ||= inspectCodexSseBlock(block, res, onSemanticTerminal);
1443
+ successfulTerminalSeen ||= inspectCodexSseBlock(block, res, observeTerminal, observeOutput);
1402
1444
  controller.enqueue(encoder.encode(`${block}${separator}`));
1403
1445
  boundary = findSseBlockBoundary(buffer, final);
1404
1446
  }
@@ -1408,21 +1450,34 @@ function validateCodexStream(
1408
1450
  buffer += decoder.decode(chunk, { stream: true });
1409
1451
  emitCompleteBlocks(controller, false);
1410
1452
  },
1411
- flush(controller) {
1412
- buffer += decoder.decode();
1413
- emitCompleteBlocks(controller, true);
1414
- if (buffer.length > 0) {
1415
- successfulTerminalSeen ||= inspectCodexSseBlock(buffer, res, onSemanticTerminal);
1416
- controller.enqueue(encoder.encode(buffer));
1417
- buffer = "";
1418
- }
1419
- if (!successfulTerminalSeen) {
1420
- throw codexSseFailureError(
1421
- res,
1422
- null,
1423
- "invalid_sse_terminal",
1424
- "The Codex response stream ended without a terminal response",
1425
- );
1453
+ async flush(controller) {
1454
+ try {
1455
+ buffer += decoder.decode();
1456
+ emitCompleteBlocks(controller, true);
1457
+ if (buffer.length > 0) {
1458
+ successfulTerminalSeen ||= inspectCodexSseBlock(
1459
+ buffer,
1460
+ res,
1461
+ observeTerminal,
1462
+ observeOutput,
1463
+ );
1464
+ controller.enqueue(encoder.encode(buffer));
1465
+ buffer = "";
1466
+ }
1467
+ if (!successfulTerminalSeen) {
1468
+ observeTerminal("failed");
1469
+ throw codexSseFailureError(
1470
+ res,
1471
+ null,
1472
+ "invalid_sse_terminal",
1473
+ "The Codex response stream ended without a terminal response",
1474
+ );
1475
+ }
1476
+ } finally {
1477
+ // Raw transport EOF can precede parsing a final block without a blank
1478
+ // separator. Settle only after that parse; the audit fence deduplicates
1479
+ // an earlier terminal already observed from a complete block.
1480
+ await onParsedEof?.();
1426
1481
  }
1427
1482
  },
1428
1483
  });
@@ -1475,14 +1530,15 @@ const CODEX_TERMINAL_TYPE_HINTS = [
1475
1530
  ] as const;
1476
1531
 
1477
1532
  /**
1478
- * Parse only blocks that can be terminal. Ordinary deltas and output items pass
1479
- * without object allocation; failed/error/incomplete terminals throw before the
1533
+ * Parse terminal blocks and completed output items (retaining only a progress
1534
+ * boolean). Ordinary deltas pass without object allocation; failure terminals throw before the
1480
1535
  * model can mistake them for an ordinary response_done event.
1481
1536
  */
1482
1537
  function inspectCodexSseBlock(
1483
1538
  block: string,
1484
1539
  source: Response,
1485
1540
  onSemanticTerminal?: (phase: "completed" | "failed") => void,
1541
+ onOutput?: (output: unknown) => void,
1486
1542
  ): boolean {
1487
1543
  const lines = block.split(/\r\n|\r|\n/);
1488
1544
  const dataStr = lines
@@ -1492,7 +1548,10 @@ function inspectCodexSseBlock(
1492
1548
  if (!dataStr || dataStr === "[DONE]") {
1493
1549
  return false;
1494
1550
  }
1495
- if (!CODEX_TERMINAL_TYPE_HINTS.some((terminalType) => dataStr.includes(terminalType))) {
1551
+ if (
1552
+ !dataStr.includes('"response.output_item.done"') &&
1553
+ !CODEX_TERMINAL_TYPE_HINTS.some((terminalType) => dataStr.includes(terminalType))
1554
+ ) {
1496
1555
  return false;
1497
1556
  }
1498
1557
  let ev: CodexSseEvent;
@@ -1502,6 +1561,7 @@ function inspectCodexSseBlock(
1502
1561
  return false;
1503
1562
  }
1504
1563
  const terminal = classifyCodexSseTerminal(ev);
1564
+ if (ev.type === "response.output_item.done") onOutput?.([ev.item]);
1505
1565
  if (terminal?.phase === "failed") {
1506
1566
  onSemanticTerminal?.("failed");
1507
1567
  throw codexSseFailureError(
@@ -1517,6 +1577,7 @@ function inspectCodexSseBlock(
1517
1577
  );
1518
1578
  }
1519
1579
  if (terminal?.phase === "completed") {
1580
+ onOutput?.(ev.response?.output);
1520
1581
  onSemanticTerminal?.("completed");
1521
1582
  return true;
1522
1583
  }
package/src/index.ts CHANGED
@@ -15,3 +15,4 @@ export * from "./opaque-artifact";
15
15
  export * from "./images";
16
16
  export * from "./realtime";
17
17
  export * from "./realtime-v3";
18
+ export * from "./reasoning-configuration";
@@ -0,0 +1,36 @@
1
+ /** Conservative progress evidence; reasoning/usage/empty terminal bookkeeping is not work. */
2
+ export function hasMeaningfulCodexOutput(output: unknown): boolean {
3
+ if (!Array.isArray(output)) return false;
4
+ return output.some((value) => {
5
+ if (!value || typeof value !== "object") return false;
6
+ const item = value as Record<string, unknown>;
7
+ if (item.status !== undefined && item.status !== "completed") return false;
8
+ if (item.type === "message" && item.role === "assistant" && Array.isArray(item.content)) {
9
+ return item.content.some((part: unknown) => {
10
+ if (!part || typeof part !== "object") return false;
11
+ const content = part as Record<string, unknown>;
12
+ return (
13
+ content.type === "output_text" &&
14
+ typeof content.text === "string" &&
15
+ content.text.trim().length > 0
16
+ );
17
+ });
18
+ }
19
+ if (item.type === "function_call" || item.type === "custom_tool_call") {
20
+ const payload = item.type === "function_call" ? item.arguments : item.input;
21
+ return (
22
+ typeof item.name === "string" &&
23
+ item.name.trim().length > 0 &&
24
+ typeof item.call_id === "string" &&
25
+ item.call_id.length > 0 &&
26
+ typeof payload === "string" &&
27
+ payload.trim().length > 0
28
+ );
29
+ }
30
+ return (
31
+ item.type === "image_generation_call" &&
32
+ typeof item.result === "string" &&
33
+ item.result.length > 0
34
+ );
35
+ });
36
+ }
@@ -0,0 +1,85 @@
1
+ /** Durable control records; only providerData is sent by the SDK unknown-item adapter. */
2
+ export const REASONING_CONFIGURATION_FIELD = "opengeniReasoningConfiguration";
3
+ export type ConfigurationEffort = "low" | "medium" | "high" | "xhigh" | "max";
4
+ const efforts = new Set<string>(["low", "medium", "high", "xhigh", "max"]);
5
+ export function supportsReasoningConfiguration(
6
+ model: string,
7
+ effort: string,
8
+ ): effort is ConfigurationEffort {
9
+ return model === "gpt-6-astra" && efforts.has(effort);
10
+ }
11
+ export type ReasoningConfiguration = {
12
+ version: 1;
13
+ baselineEffort: ConfigurationEffort;
14
+ effort: ConfigurationEffort;
15
+ turnId: string;
16
+ };
17
+ export function readReasoningConfiguration(
18
+ item: Record<string, unknown>,
19
+ ): ReasoningConfiguration | null {
20
+ const state = item[REASONING_CONFIGURATION_FIELD] as ReasoningConfiguration | undefined;
21
+ if (
22
+ !state ||
23
+ state.version !== 1 ||
24
+ !efforts.has(state.baselineEffort) ||
25
+ !efforts.has(state.effort) ||
26
+ typeof state.turnId !== "string"
27
+ )
28
+ return null;
29
+ const data = item.providerData as Record<string, unknown> | undefined;
30
+ if (
31
+ item.type !== "unknown" ||
32
+ data?.type !== "configuration_update" ||
33
+ (data.reasoning as { effort?: unknown })?.effort !== state.effort
34
+ )
35
+ return null;
36
+ return state;
37
+ }
38
+ export function reasoningConfigurationItem(state: ReasoningConfiguration): Record<string, unknown> {
39
+ return {
40
+ type: "unknown",
41
+ providerData: { type: "configuration_update", reasoning: { effort: state.effort } },
42
+ [REASONING_CONFIGURATION_FIELD]: state,
43
+ };
44
+ }
45
+ export function latestReasoningConfiguration(
46
+ items: readonly Record<string, unknown>[],
47
+ ): ReasoningConfiguration | null {
48
+ for (let i = items.length - 1; i >= 0; i--) {
49
+ const state = readReasoningConfiguration(items[i]!);
50
+ if (state) return state;
51
+ }
52
+ return null;
53
+ }
54
+ export function isReasoningConfigurationUpdate(item: Record<string, unknown>): boolean {
55
+ return (
56
+ item.type === "configuration_update" ||
57
+ (item.type === "unknown" &&
58
+ (item.providerData as { type?: unknown } | undefined)?.type === "configuration_update")
59
+ );
60
+ }
61
+ /** Never leak Astra controls to an incompatible model. Coalesce only adjacent controls. */
62
+ export function projectReasoningConfigurations(
63
+ items: Record<string, unknown>[],
64
+ enabled: boolean,
65
+ ): Record<string, unknown>[] {
66
+ const projected: Record<string, unknown>[] = [];
67
+ for (const item of items) {
68
+ if (!isReasoningConfigurationUpdate(item)) {
69
+ projected.push(item);
70
+ continue;
71
+ }
72
+ if (!enabled) continue;
73
+ if (projected.length && isReasoningConfigurationUpdate(projected[projected.length - 1]!))
74
+ projected.pop();
75
+ projected.push(
76
+ item.type === "unknown"
77
+ ? { type: "unknown", providerData: item.providerData }
78
+ : {
79
+ type: "unknown",
80
+ providerData: { type: "configuration_update", reasoning: item.reasoning },
81
+ },
82
+ );
83
+ }
84
+ return projected;
85
+ }
@@ -60,6 +60,8 @@ export type CodexModelRequestEvent = {
60
60
  providerRequestId?: string;
61
61
  status?: number;
62
62
  willRetry?: boolean;
63
+ /** Only true for a successful response with substantive assistant/tool output. */
64
+ meaningfulOutput?: boolean;
63
65
  };
64
66
 
65
67
  export type CodexRequestOpaqueArtifacts = {