@opengeni/codex 0.2.10 → 0.2.11

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.
@@ -25,9 +25,10 @@ export declare function remapToolCallRequestBody(body: string, mapper: ToolNameM
25
25
  * to the MCP server's original. Only the POST request/response is buffered; the
26
26
  * long-lived GET notification SSE stream is passed through untouched.
27
27
  *
28
- * P4 (Part B.1): an optional `namespaceSink` Set accumulates the ORIGINAL-dotted
28
+ * An optional `namespaceSink` Set accumulates the ORIGINAL-dotted
29
29
  * connector namespaces seen across every tools/list this turn (captured before the
30
- * dot is sanitized away). The worker reads the (live, by-reference) Set after the
31
- * turn to cache the serving account's connector set — packages/codex stays db-free.
30
+ * dot is sanitized away). The runtime reads the live by-reference Set only to keep
31
+ * this turn's `tool_search` description accurate; it is never persisted or used
32
+ * for inference selection.
32
33
  */
33
34
  export declare function codexAppsSanitizingFetch(base?: FetchLike, namespaceSink?: Set<string>): FetchLike;
@@ -0,0 +1,4 @@
1
+ /** Stable, content-hiding identity for one opaque provider artifact. */
2
+ export declare function opaqueProviderArtifactFingerprint(item: unknown): string | null;
3
+ /** Exact opaque artifacts present in one normalized provider input array. */
4
+ export declare function opaqueProviderArtifactFingerprints(input: unknown): string[];
@@ -49,6 +49,10 @@ export type CodexModelRequestEvent = {
49
49
  status?: number;
50
50
  willRetry?: boolean;
51
51
  };
52
+ export type CodexRequestOpaqueArtifacts = {
53
+ requestId: string;
54
+ fingerprints: readonly string[];
55
+ };
52
56
  export type CodexRequestContext = {
53
57
  clientVersion: string;
54
58
  /**
@@ -81,6 +85,8 @@ export type CodexRequestContext = {
81
85
  responseTimeoutPolicy?: Partial<CodexResponseTimeoutPolicy>;
82
86
  /** Worker-owned durable audit sink; payloads never contain request bodies or auth. */
83
87
  onModelRequestEvent?: (event: CodexModelRequestEvent) => Promise<void> | void;
88
+ /** Exact opaque artifacts on the normalized wire request, never their ciphertext. */
89
+ onRequestOpaqueArtifacts?: (artifacts: CodexRequestOpaqueArtifacts) => void;
84
90
  /** Stable request identity supplied by the owning durable execution. */
85
91
  nextRequestId?: () => string;
86
92
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/codex",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
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
@@ -13,6 +13,7 @@
13
13
  import { randomUUID } from "node:crypto";
14
14
  import { CODEX_ORIGINATOR } from "./constants";
15
15
  import { normalizeCodexRequestBody } from "./normalize";
16
+ import { opaqueProviderArtifactFingerprints } from "./opaque-artifact";
16
17
  import {
17
18
  codexRequestStorage,
18
19
  type CodexModelRequestEvent,
@@ -489,6 +490,7 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
489
490
  // summarizer) still gets a single JSON Response back.
490
491
  let callerWantsStream = true;
491
492
  let model: string | undefined;
493
+ let requestOpaqueArtifacts: string[] = [];
492
494
  const nextInit: RequestInit = { ...init, headers };
493
495
  if (typeof init?.body === "string") {
494
496
  try {
@@ -497,10 +499,12 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
497
499
  const normalized = normalizeCodexRequestBody(parsed, ctx.resolveModel);
498
500
  model = typeof normalized.model === "string" ? normalized.model : undefined;
499
501
  nextInit.body = JSON.stringify(normalized);
502
+ requestOpaqueArtifacts = opaqueProviderArtifactFingerprints(normalized.input);
500
503
  } catch {
501
504
  /* leave unparseable bodies untouched (already copied from init) */
502
505
  }
503
506
  }
507
+ ctx.onRequestOpaqueArtifacts?.({ requestId, fingerprints: requestOpaqueArtifacts });
504
508
  headers.set(
505
509
  "Idempotency-Key",
506
510
  authenticationAttempt === 0 ? requestId : `${requestId}:auth-${authenticationAttempt}`,
package/src/index.ts CHANGED
@@ -11,5 +11,6 @@ export * from "./response-timeout";
11
11
  export * from "./fetch";
12
12
  export * from "./mcp-sanitize";
13
13
  export * from "./model-output-truncation";
14
+ export * from "./opaque-artifact";
14
15
  export * from "./realtime";
15
16
  export * from "./realtime-v3";
@@ -105,7 +105,7 @@ export class ToolNameMapper {
105
105
  /**
106
106
  * Drop bad outputSchemas + sanitize tool names on a JSON-RPC tools/list result, in place.
107
107
  *
108
- * P4 (Part B.1): when `namespaceSink` is provided, accumulate each tool's ORIGINAL
108
+ * When `namespaceSink` is provided, accumulate each tool's ORIGINAL
109
109
  * connector namespace (the segment BEFORE the first dot, e.g. `github` from
110
110
  * `github.create_issue`) into it — captured HERE because this pass sees the original
111
111
  * dotted name BEFORE mapper.sanitize rewrites the dot away. Only dotted names carry a
@@ -264,10 +264,11 @@ export function remapToolCallRequestBody(body: string, mapper: ToolNameMapper):
264
264
  * to the MCP server's original. Only the POST request/response is buffered; the
265
265
  * long-lived GET notification SSE stream is passed through untouched.
266
266
  *
267
- * P4 (Part B.1): an optional `namespaceSink` Set accumulates the ORIGINAL-dotted
267
+ * An optional `namespaceSink` Set accumulates the ORIGINAL-dotted
268
268
  * connector namespaces seen across every tools/list this turn (captured before the
269
- * dot is sanitized away). The worker reads the (live, by-reference) Set after the
270
- * turn to cache the serving account's connector set — packages/codex stays db-free.
269
+ * dot is sanitized away). The runtime reads the live by-reference Set only to keep
270
+ * this turn's `tool_search` description accurate; it is never persisted or used
271
+ * for inference selection.
271
272
  */
272
273
  export function codexAppsSanitizingFetch(
273
274
  base: FetchLike = globalThis.fetch,
package/src/normalize.ts CHANGED
@@ -69,7 +69,7 @@ export function normalizeCodexRequestBody(
69
69
  // strip every item id; PRESERVE call_id. spec §1.6 / verdict §0(b)
70
70
  // (This also covers tool_search items: the backend accepts an id-less
71
71
  // tool_search_call/output pair correlated by call_id — verified live — and
72
- // stripping the account-bound `tsc_…` id here sanitizes BOTH replay paths.)
72
+ // stripping the provider-stored `tsc_…` id here sanitizes BOTH replay paths.)
73
73
  if (Array.isArray(body.input)) {
74
74
  for (const item of body.input as unknown[]) {
75
75
  if (!item || typeof item !== "object") {
@@ -0,0 +1,29 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ /** Stable, content-hiding identity for one opaque provider artifact. */
4
+ export function opaqueProviderArtifactFingerprint(item: unknown): string | null {
5
+ if (!item || typeof item !== "object") return null;
6
+ const record = item as Record<string, unknown>;
7
+ if (record.type !== "reasoning" && record.type !== "compaction") return null;
8
+ const providerData =
9
+ record.providerData && typeof record.providerData === "object"
10
+ ? (record.providerData as Record<string, unknown>)
11
+ : null;
12
+ const ciphertext =
13
+ (typeof record.encrypted_content === "string" && record.encrypted_content) ||
14
+ (typeof record.encryptedContent === "string" && record.encryptedContent) ||
15
+ (typeof providerData?.encrypted_content === "string" && providerData.encrypted_content) ||
16
+ (typeof providerData?.encryptedContent === "string" && providerData.encryptedContent) ||
17
+ null;
18
+ if (!ciphertext) return null;
19
+ return `${record.type}:${createHash("sha256").update(ciphertext).digest("hex")}`;
20
+ }
21
+
22
+ /** Exact opaque artifacts present in one normalized provider input array. */
23
+ export function opaqueProviderArtifactFingerprints(input: unknown): string[] {
24
+ if (!Array.isArray(input)) return [];
25
+ return input.flatMap((item) => {
26
+ const fingerprint = opaqueProviderArtifactFingerprint(item);
27
+ return fingerprint ? [fingerprint] : [];
28
+ });
29
+ }
@@ -62,6 +62,11 @@ export type CodexModelRequestEvent = {
62
62
  willRetry?: boolean;
63
63
  };
64
64
 
65
+ export type CodexRequestOpaqueArtifacts = {
66
+ requestId: string;
67
+ fingerprints: readonly string[];
68
+ };
69
+
65
70
  export type CodexRequestContext = {
66
71
  clientVersion: string;
67
72
  /**
@@ -94,6 +99,8 @@ export type CodexRequestContext = {
94
99
  responseTimeoutPolicy?: Partial<CodexResponseTimeoutPolicy>;
95
100
  /** Worker-owned durable audit sink; payloads never contain request bodies or auth. */
96
101
  onModelRequestEvent?: (event: CodexModelRequestEvent) => Promise<void> | void;
102
+ /** Exact opaque artifacts on the normalized wire request, never their ciphertext. */
103
+ onRequestOpaqueArtifacts?: (artifacts: CodexRequestOpaqueArtifacts) => void;
97
104
  /** Stable request identity supplied by the owning durable execution. */
98
105
  nextRequestId?: () => string;
99
106
  /**