@opengeni/codex 0.2.11 → 0.2.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/codex",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
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": {
package/src/fetch.ts CHANGED
@@ -510,17 +510,12 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
510
510
  authenticationAttempt === 0 ? requestId : `${requestId}:auth-${authenticationAttempt}`,
511
511
  );
512
512
  if (process.env.CODEX_DEBUG) {
513
- let keys: string[] = [];
514
- if (typeof nextInit.body === "string") {
515
- try {
516
- keys = Object.keys(JSON.parse(nextInit.body) as Record<string, unknown>);
517
- } catch {
518
- /* an unparseable body is already passed through unchanged above */
519
- }
520
- }
521
- console.error(
522
- `[codex-debug] POST ${rewritten} stream=${callerWantsStream} bodyKeys=[${keys.join(",")}]`,
523
- );
513
+ console.error("[codex-debug] request dispatched", {
514
+ method: "POST",
515
+ origin: "codex-subscription",
516
+ route: "codex_responses",
517
+ stream: callerWantsStream,
518
+ });
524
519
  }
525
520
  let res: Response;
526
521
  transportAttempt += 1;
@@ -587,12 +582,13 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
587
582
  ctx.onUsageHeaders?.(usage);
588
583
  }
589
584
  if (process.env.CODEX_DEBUG && !res.ok) {
590
- // Never log provider bodies: they can contain request-derived content or
591
- // account details. Status + request id is sufficient to correlate with
592
- // the structured worker failure telemetry.
593
- console.error(
594
- `[codex-debug] <- ${res.status} requestId=${res.headers.get("x-request-id") ?? "unknown"}`,
595
- );
585
+ // Never log provider bodies, identifiers, or headers: they can contain
586
+ // request-derived or account content. A bounded status is sufficient.
587
+ console.error("[codex-debug] request failed", {
588
+ origin: "codex-subscription",
589
+ route: "codex_responses",
590
+ status: res.status,
591
+ });
596
592
  }
597
593
  // The codex backend leaves the terminal event's response.output empty and
598
594
  // delivers the assistant items via output_item.done events instead. The
@@ -1016,9 +1012,9 @@ function codexSseFailureProjection(
1016
1012
  * A provider terminal carried inside an accepted HTTP-200 stream. The OpenAI
1017
1013
  * SDK cannot turn that late terminal into a non-2xx APIError because headers
1018
1014
  * have already been accepted, so the body transform throws this equivalent
1019
- * bounded shape. Provider-supplied message/param text is intentionally absent:
1020
- * the worker may persist Error.message, while identifiers/classifications are
1021
- * sufficient for retry, compaction, and incident diagnostics.
1015
+ * bounded shape. Provider-supplied message/param text remains exact within the
1016
+ * explicit terminal-field byte contract; retry classification is additive and
1017
+ * never substitutes for the source diagnostic.
1022
1018
  */
1023
1019
  export class CodexStreamingTerminalError extends Error {
1024
1020
  readonly status: number;
@@ -1030,8 +1026,8 @@ export class CodexStreamingTerminalError extends Error {
1030
1026
  readonly headers: Headers;
1031
1027
  readonly error: Record<string, unknown>;
1032
1028
 
1033
- constructor(projection: CodexSseFailureProjection, publicMessage: string) {
1034
- super(publicMessage);
1029
+ constructor(projection: CodexSseFailureProjection) {
1030
+ super(projection.error.message);
1035
1031
  this.name = "CodexStreamingTerminalError";
1036
1032
  this.status = projection.status;
1037
1033
  this.code = projection.error.code;
@@ -1049,6 +1045,8 @@ export class CodexStreamingTerminalError extends Error {
1049
1045
  this.error = {
1050
1046
  type: projection.error.type,
1051
1047
  code: projection.error.code,
1048
+ message: projection.error.message,
1049
+ ...(projection.error.param ? { param: projection.error.param } : {}),
1052
1050
  ...(projection.error.event_type ? { event_type: projection.error.event_type } : {}),
1053
1051
  ...(projection.error.response_id ? { response_id: projection.error.response_id } : {}),
1054
1052
  ...(projection.error.response_status
@@ -1072,7 +1070,6 @@ function codexSseFailureError(
1072
1070
  ): CodexStreamingTerminalError {
1073
1071
  return new CodexStreamingTerminalError(
1074
1072
  codexSseFailureProjection(source, rawError, fallbackCode, publicMessage, metadata),
1075
- publicMessage,
1076
1073
  );
1077
1074
  }
1078
1075
 
@@ -169,8 +169,10 @@ function sanitizeToolsInRpcMessage(
169
169
  * leaving the original content untouched.
170
170
  *
171
171
  * No-op when there is no `structuredContent` — so a tools/list response (or any
172
- * result without it) passes through unchanged. Runs alongside (after) the empty
173
- * outputSchema drop, which is what stops the MCP SDK -32602ing this same result.
172
+ * result without it) passes through unchanged. Valid object payloads stay intact
173
+ * for protocol-aware consumers. Invalid optional values (such as null) are removed
174
+ * after any useful value is copied into `content`, because the MCP client validates
175
+ * this field as an object before the tool can return. Runs after the outputSchema drop.
174
176
  */
175
177
  function inlineStructuredContentInRpcMessage(message: unknown): void {
176
178
  if (!message || typeof message !== "object") {
@@ -185,13 +187,15 @@ function inlineStructuredContentInRpcMessage(message: unknown): void {
185
187
  return;
186
188
  }
187
189
  const structured = record.structuredContent;
188
- if (structured === undefined || structured === null) {
189
- return;
190
+ if (structured !== undefined && structured !== null) {
191
+ const text = typeof structured === "string" ? structured : JSON.stringify(structured);
192
+ const content = Array.isArray(record.content) ? [...record.content] : [];
193
+ content.push({ type: "text", text });
194
+ record.content = content;
195
+ }
196
+ if (typeof structured !== "object" || structured === null || Array.isArray(structured)) {
197
+ delete record.structuredContent;
190
198
  }
191
- const text = typeof structured === "string" ? structured : JSON.stringify(structured);
192
- const content = Array.isArray(record.content) ? [...record.content] : [];
193
- content.push({ type: "text", text });
194
- record.content = content;
195
199
  }
196
200
 
197
201
  /** Sanitize a single JSON body (application/json MCP response). */