@opengeni/codex 0.2.25 → 0.2.26-canary.36199476632001

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.
@@ -18,6 +18,7 @@
18
18
  * runtime request seam and the database history boundary call the same pure
19
19
  * function, so replayed conversation truth is identical to live model input.
20
20
  */
21
+ import { type HostedCallStatusItemType } from "./hosted-call-status.js";
21
22
  export { MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL } from "./oversized-image-card.js";
22
23
  export type ModelHistoryItem = Record<string, unknown>;
23
24
  type WithoutOutputOnlyProviderDataFields<T> = T extends ModelHistoryItem ? Omit<T, "status"> : T;
@@ -28,15 +29,18 @@ type WithoutOutputOnlyProviderDataField<T extends ModelHistoryItem> = "providerD
28
29
  } : {
29
30
  providerData: WithoutOutputOnlyProviderDataFields<T["providerData"]>;
30
31
  } : object;
31
- type WithoutOutputOnlyHistoryItemFields<T extends ModelHistoryItem> = T extends unknown ? Omit<T, "status" | "providerData"> & WithoutOutputOnlyProviderDataField<T> : never;
32
+ type WithoutOutputOnlyHistoryItemFields<T extends ModelHistoryItem> = T extends unknown ? T extends {
33
+ type: HostedCallStatusItemType;
34
+ } ? T : Omit<T, "status" | "providerData"> & WithoutOutputOnlyProviderDataField<T> : never;
32
35
  /**
33
36
  * Responses output items carry `status` (`in_progress` / `completed` /
34
37
  * `incomplete`). That field is not conversation meaning — pairing is `call_id`
35
38
  * — and Codex's input schema 400s it (`Unknown parameter: 'input[N].status'`).
36
39
  * SuperGrok accepts items with or without it. The SDK also nests `status` on
37
40
  * `providerData` (reasoning items) and flattens it back onto the request.
38
- * Canonical history therefore omits both at persist so portable sessions can
39
- * cross Responses providers.
41
+ * Canonical history omits both for those items so portable sessions can cross
42
+ * Responses providers. Hosted tool calls are different: the provider requires
43
+ * their status on replay, and the SDK reads it to reconstruct the wire item.
40
44
  */
41
45
  export declare function omitOutputOnlyHistoryItemFields<T extends ModelHistoryItem>(item: T): WithoutOutputOnlyHistoryItemFields<T>;
42
46
  /** Persist/replay boundary: drop output-only fields, then bound tool output. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/codex",
3
- "version": "0.2.25",
3
+ "version": "0.2.26-canary.36199476632001",
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,8 @@
41
41
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
42
42
  },
43
43
  "dependencies": {
44
- "@opengeni/network": "^0.3.1",
44
+ "@opengeni/contracts": "^5.1.1-canary.36199476632001",
45
+ "@opengeni/network": "^0.3.1-canary.36199476632001",
45
46
  "zod": "^4.2.1"
46
47
  },
47
48
  "engines": {
@@ -0,0 +1,17 @@
1
+ /** Hosted Responses calls require status on replay, unlike function/message annotations. */
2
+ export type HostedCallStatusItemType =
3
+ | "hosted_tool_call"
4
+ | "web_search_call"
5
+ | "file_search_call"
6
+ | "code_interpreter_call"
7
+ | "image_generation_call";
8
+
9
+ export function preservesHostedCallStatus(type: unknown): type is HostedCallStatusItemType {
10
+ return (
11
+ type === "hosted_tool_call" ||
12
+ type === "web_search_call" ||
13
+ type === "file_search_call" ||
14
+ type === "code_interpreter_call" ||
15
+ type === "image_generation_call"
16
+ );
17
+ }
@@ -20,6 +20,8 @@
20
20
  */
21
21
 
22
22
  import { MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL } from "./oversized-image-card";
23
+ import { RetainedArtifactMetadataSchema } from "@opengeni/contracts";
24
+ import { preservesHostedCallStatus, type HostedCallStatusItemType } from "./hosted-call-status";
23
25
 
24
26
  export { MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL } from "./oversized-image-card";
25
27
 
@@ -42,7 +44,9 @@ type WithoutOutputOnlyProviderDataField<T extends ModelHistoryItem> = "providerD
42
44
  : object;
43
45
 
44
46
  type WithoutOutputOnlyHistoryItemFields<T extends ModelHistoryItem> = T extends unknown
45
- ? Omit<T, "status" | "providerData"> & WithoutOutputOnlyProviderDataField<T>
47
+ ? T extends { type: HostedCallStatusItemType }
48
+ ? T
49
+ : Omit<T, "status" | "providerData"> & WithoutOutputOnlyProviderDataField<T>
46
50
  : never;
47
51
 
48
52
  /**
@@ -51,8 +55,9 @@ type WithoutOutputOnlyHistoryItemFields<T extends ModelHistoryItem> = T extends
51
55
  * — and Codex's input schema 400s it (`Unknown parameter: 'input[N].status'`).
52
56
  * SuperGrok accepts items with or without it. The SDK also nests `status` on
53
57
  * `providerData` (reasoning items) and flattens it back onto the request.
54
- * Canonical history therefore omits both at persist so portable sessions can
55
- * cross Responses providers.
58
+ * Canonical history omits both for those items so portable sessions can cross
59
+ * Responses providers. Hosted tool calls are different: the provider requires
60
+ * their status on replay, and the SDK reads it to reconstruct the wire item.
56
61
  */
57
62
  export function omitOutputOnlyHistoryItemFields<T extends ModelHistoryItem>(
58
63
  item: T,
@@ -60,6 +65,9 @@ export function omitOutputOnlyHistoryItemFields<T extends ModelHistoryItem>(
60
65
  if (!item || typeof item !== "object") {
61
66
  return item as unknown as WithoutOutputOnlyHistoryItemFields<T>;
62
67
  }
68
+ if (preservesHostedCallStatus(item.type)) {
69
+ return item as unknown as WithoutOutputOnlyHistoryItemFields<T>;
70
+ }
63
71
  const providerData =
64
72
  item.providerData && typeof item.providerData === "object"
65
73
  ? (item.providerData as Record<string, unknown>)
@@ -402,6 +410,16 @@ function boundTextLeaves(
402
410
  return bounded;
403
411
  }
404
412
  if (!value || typeof value !== "object") return value;
413
+ // Screenshot receipts are image protocol, not model-visible text. They are
414
+ // compact, schema-bounded references whose fields must survive a zero text
415
+ // budget so a resumed turn can materialize the image from durable storage.
416
+ if (
417
+ !Array.isArray(value) &&
418
+ (value as Record<string, unknown>).type === "retained_artifact" &&
419
+ RetainedArtifactMetadataSchema.safeParse((value as Record<string, unknown>).artifact).success
420
+ ) {
421
+ return value;
422
+ }
405
423
  if (depth >= MODEL_TOOL_OUTPUT_MAX_DEPTH) return DEPTH_OMISSION_MARKER;
406
424
  if (state.seen.has(value)) return CYCLE_OMISSION_MARKER;
407
425
  state.seen.add(value);
package/src/normalize.ts CHANGED
@@ -7,7 +7,8 @@
7
7
  // - strip max_output_tokens / max_completion_tokens
8
8
  // - reasoning effort minimal -> low
9
9
  // - normalize the model slug (longest-prefix against the live catalog)
10
- // - strip every item `id` and `status` but PRESERVE `call_id`
10
+ // - strip every item `id` and output-only `status` but PRESERVE `call_id`
11
+ // and the required status of hosted tool calls
11
12
  // We do NOT filter item_reference (the SDK never emits it) and do NOT convert
12
13
  // orphaned tool outputs (the SDK's runner already prunes by call_id).
13
14
  //
@@ -16,6 +17,8 @@
16
17
  // schema 400s `Unknown parameter: 'input[N].status'` — observed live on a
17
18
  // portable SuperGrok → Codex switch. Pairing uses `call_id`, never `status`.
18
19
 
20
+ import { preservesHostedCallStatus } from "./hosted-call-status";
21
+
19
22
  const MINIMAL = "minimal";
20
23
 
21
24
  // The ChatGPT/Codex backend is a STRICT ALLOWLIST: it 400s on ANY top-level field
@@ -71,13 +74,13 @@ export function normalizeCodexRequestBody(
71
74
  body.model = resolveModel(body.model);
72
75
  }
73
76
 
74
- // strip every item id and status; PRESERVE call_id. spec §1.6 / verdict §0(b)
77
+ // Strip item ids and output-only status; preserve call_id and hosted status.
75
78
  // (This also covers tool_search items: the backend accepts an id-less
76
79
  // tool_search_call/output pair correlated by call_id — verified live — and
77
80
  // stripping the provider-stored `tsc_…` id here sanitizes BOTH replay paths.)
78
- // `status` is output-only on Codex input items. New rows omit it at persist;
79
- // this wire strip remains defense for already-stored SuperGrok rows and
80
- // mid-turn SDK items.
81
+ // Hosted web/file search, code interpreter and image-generation items require
82
+ // their actual status on replay. Never turn an absent status into a fabricated
83
+ // completion. Other items retain the portable-history compatibility strip.
81
84
  if (Array.isArray(body.input)) {
82
85
  for (const item of body.input as unknown[]) {
83
86
  if (!item || typeof item !== "object") {
@@ -87,7 +90,7 @@ export function normalizeCodexRequestBody(
87
90
  if ("id" in record) {
88
91
  delete record.id;
89
92
  }
90
- if ("status" in record) {
93
+ if ("status" in record && !preservesHostedCallStatus(record.type)) {
91
94
  delete record.status;
92
95
  }
93
96
  // A replayed tool_search_call must carry `arguments` as an OBJECT — the