@oai404iao/pi-codex-minimal-tools 1.3.0 → 1.4.0

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/src/index.ts CHANGED
@@ -225,6 +225,8 @@ function registerTools(
225
225
  }) as never);
226
226
  pi.registerTool(createWebSearchToolDefinition({
227
227
  getCurrentTurnId: (sessionId) => providerController?.getCurrentTurnId(sessionId),
228
+ getRequestIdentity: (sessionId) =>
229
+ providerController?.getRequestIdentity?.(sessionId),
228
230
  }) as never);
229
231
  pi.registerTool({
230
232
  renderShell: "self",
@@ -40,11 +40,15 @@ import { createCodexReservedNamespaceTool } from "./codex-reserved-tools.js";
40
40
  import { rewriteNativeOpenAiTools } from "./provider-native-tools.js";
41
41
  import {
42
42
  captureCodexTurnState,
43
- codexInstallationIdFor,
44
43
  codexTurnStateFor,
44
+ currentCodexTurn,
45
+ isUuidV7,
46
+ resolveCodexRequestIdentity,
45
47
  resolveCodexWireIdentity,
48
+ type CodexRequestIdentity,
46
49
  uuidV7,
47
50
  } from "./codex-wire-identity.js";
51
+ import { installCodexIdentityLifecycle } from "./codex-identity-extension.js";
48
52
  import { applyFastModeServiceTier } from "./fast-mode.js";
49
53
  import {
50
54
  hasCodexRequestAuth,
@@ -217,14 +221,20 @@ interface SessionStartupPrewarmTask {
217
221
  }
218
222
 
219
223
  interface WebSocketRequestMetadata {
224
+ /** Legacy Pi session lookup key used by exported test helpers. */
220
225
  sessionId?: string;
221
226
  threadId?: string;
222
227
  turnId: string;
223
228
  requestKind?: "turn" | "compaction" | "prewarm";
229
+ identity?: CodexRequestIdentity;
224
230
  }
225
231
 
226
232
  export interface OpenAIResponsesProviderController {
227
233
  getCurrentTurnId(sessionId: string | undefined): string | undefined;
234
+ getRequestIdentity?(
235
+ sessionId: string | undefined,
236
+ requestKind?: CodexRequestIdentity["requestKind"],
237
+ ): CodexRequestIdentity | undefined;
228
238
  }
229
239
 
230
240
  let fsPromisesPromise: Promise<typeof import("node:fs/promises")> | undefined;
@@ -568,6 +578,14 @@ function headersToRecord(headers: Headers): Record<string, string> {
568
578
  return Object.fromEntries(headers.entries());
569
579
  }
570
580
 
581
+ function providerHeadersToHeaders(headers: ProviderHeaders): Headers {
582
+ const result = new Headers();
583
+ for (const [name, value] of Object.entries(headers)) {
584
+ if (typeof value === "string") result.set(name, value);
585
+ }
586
+ return result;
587
+ }
588
+
571
589
  function createCodexRequestId(): string {
572
590
  if (typeof globalThis.crypto?.randomUUID === "function") {
573
591
  return globalThis.crypto.randomUUID();
@@ -617,16 +635,39 @@ function applyWireIdentityHeaders(
617
635
  headers: Headers,
618
636
  sessionId: string | undefined,
619
637
  threadId: string | undefined,
638
+ requestIdentity?: CodexRequestIdentity,
620
639
  ): void {
621
- const wire = wireIdentityFor(sessionId, threadId);
640
+ const wire = requestIdentity ?? wireIdentityFor(sessionId, threadId);
622
641
  if (!wire) return;
623
642
  setProviderGeneratedHeader(headers, "session-id", wire.sessionId);
624
643
  setProviderGeneratedHeader(headers, "thread-id", wire.threadId);
625
644
  setProviderGeneratedHeader(headers, "x-codex-window-id", wire.windowId);
626
645
  setProviderGeneratedHeader(headers, "x-client-request-id", wire.threadId);
627
- if (sessionId) {
628
- const turnState = codexTurnStateFor(sessionId);
629
- if (turnState) setProviderGeneratedHeader(headers, "x-codex-turn-state", turnState);
646
+ const turnState = requestIdentity?.turnState
647
+ ?? (sessionId ? codexTurnStateFor(sessionId) : undefined);
648
+ if (turnState) {
649
+ setProviderGeneratedHeader(headers, "x-codex-turn-state", turnState);
650
+ }
651
+ if (requestIdentity?.parentThreadId) {
652
+ setProviderGeneratedHeader(
653
+ headers,
654
+ "x-codex-parent-thread-id",
655
+ requestIdentity.parentThreadId,
656
+ );
657
+ }
658
+ if (requestIdentity?.subagentKind) {
659
+ setProviderGeneratedHeader(
660
+ headers,
661
+ "x-openai-subagent",
662
+ requestIdentity.subagentKind,
663
+ );
664
+ }
665
+ if (requestIdentity) {
666
+ setProviderGeneratedHeader(
667
+ headers,
668
+ "x-codex-turn-metadata",
669
+ buildCodexTurnMetadataJson(requestIdentity),
670
+ );
630
671
  }
631
672
  }
632
673
 
@@ -639,6 +680,7 @@ export function buildSSEHeaders(
639
680
  sessionId: string | undefined,
640
681
  profile: CodexRequestProfile,
641
682
  threadId?: string,
683
+ requestIdentity?: CodexRequestIdentity,
642
684
  ): Headers {
643
685
  const headers = buildBaseCodexHeaders(modelHeaders, additionalHeaders, accountId, token);
644
686
  setProviderDefaultHeader(headers, "OpenAI-Beta", "responses=experimental");
@@ -648,7 +690,7 @@ export function buildSSEHeaders(
648
690
  setProviderDefaultHeader(headers, X_OPENAI_INTERNAL_CODEX_RESPONSES_LITE, "true");
649
691
  }
650
692
 
651
- applyWireIdentityHeaders(headers, sessionId, threadId);
693
+ applyWireIdentityHeaders(headers, sessionId, threadId, requestIdentity);
652
694
 
653
695
  return headers;
654
696
  }
@@ -684,6 +726,7 @@ export function buildWebSocketHeaders(
684
726
  token: string,
685
727
  sessionId: string,
686
728
  threadId = sessionId,
729
+ requestIdentity?: CodexRequestIdentity,
687
730
  ): Headers {
688
731
  const headers = buildBaseCodexHeaders(modelHeaders, additionalHeaders, accountId, token);
689
732
  headers.delete("accept");
@@ -694,13 +737,34 @@ export function buildWebSocketHeaders(
694
737
  setProviderDefaultHeader(headers, "x-client-request-id", threadId);
695
738
  setProviderDefaultHeader(headers, "session-id", sessionId);
696
739
  setProviderDefaultHeader(headers, "thread-id", threadId);
697
- const wire = wireIdentityFor(sessionId, threadId);
740
+ const wire = requestIdentity ?? wireIdentityFor(sessionId, threadId);
698
741
  if (wire) {
699
742
  setProviderGeneratedHeader(headers, "session-id", wire.sessionId);
700
743
  setProviderGeneratedHeader(headers, "thread-id", wire.threadId);
701
744
  setProviderGeneratedHeader(headers, "x-codex-window-id", wire.windowId);
702
745
  setProviderGeneratedHeader(headers, "x-client-request-id", wire.threadId);
703
746
  }
747
+ if (requestIdentity?.parentThreadId) {
748
+ setProviderGeneratedHeader(
749
+ headers,
750
+ "x-codex-parent-thread-id",
751
+ requestIdentity.parentThreadId,
752
+ );
753
+ }
754
+ if (requestIdentity?.subagentKind) {
755
+ setProviderGeneratedHeader(
756
+ headers,
757
+ "x-openai-subagent",
758
+ requestIdentity.subagentKind,
759
+ );
760
+ }
761
+ if (requestIdentity) {
762
+ setProviderGeneratedHeader(
763
+ headers,
764
+ "x-codex-turn-metadata",
765
+ buildCodexTurnMetadataJson(requestIdentity),
766
+ );
767
+ }
704
768
  return headers;
705
769
  }
706
770
 
@@ -872,26 +936,52 @@ export function withResponsesLiteWebSocketMetadata<T extends { client_metadata?:
872
936
  * this blob (privacy normalization); once it honors client-supplied values,
873
937
  * this keeps the shape identical to the CLI.
874
938
  */
875
- function buildCodexTurnMetadataJson(
876
- sessionId: string,
877
- threadId: string | undefined,
878
- turnId: string,
879
- requestKind: "turn" | "compaction" | "prewarm",
880
- ): string {
881
- const wire = wireIdentityFor(sessionId, threadId);
882
- if (!wire) return "";
939
+ function buildCodexTurnMetadataJson(identity: CodexRequestIdentity): string {
883
940
  const payload: Record<string, string | number> = {
884
- installation_id: codexInstallationIdFor(sessionId),
885
- session_id: wire.sessionId,
886
- thread_id: wire.threadId,
887
- turn_id: turnId,
888
- window_id: wire.windowId,
889
- request_kind: requestKind,
890
- turn_started_at_unix_ms: Date.now(),
941
+ installation_id: identity.installationId,
942
+ session_id: identity.sessionId,
943
+ thread_id: identity.threadId,
944
+ turn_id: identity.turnId,
945
+ window_id: identity.windowId,
946
+ request_kind: identity.requestKind,
947
+ ...(identity.turnStartedAtMs !== undefined
948
+ ? { turn_started_at_unix_ms: identity.turnStartedAtMs }
949
+ : {}),
950
+ ...(identity.agentName ? { agent_name: identity.agentName } : {}),
951
+ ...(identity.forkedFromThreadId
952
+ ? { forked_from_thread_id: identity.forkedFromThreadId }
953
+ : {}),
954
+ ...(identity.parentThreadId
955
+ ? { parent_thread_id: identity.parentThreadId }
956
+ : {}),
957
+ ...(identity.parentTurnId
958
+ ? { parent_turn_id: identity.parentTurnId }
959
+ : {}),
960
+ ...(identity.rootTurnId
961
+ ? { root_turn_id: identity.rootTurnId }
962
+ : {}),
963
+ ...(identity.subagentKind
964
+ ? { subagent_kind: identity.subagentKind }
965
+ : {}),
891
966
  };
892
967
  return JSON.stringify(payload);
893
968
  }
894
969
 
970
+ function identityForRequestMetadata(
971
+ metadata: WebSocketRequestMetadata,
972
+ ): CodexRequestIdentity | undefined {
973
+ if (metadata.identity) return metadata.identity;
974
+ const explicit: Record<string, unknown> = {
975
+ turn_id: metadata.turnId,
976
+ };
977
+ if (isUuidV7(metadata.threadId)) explicit.thread_id = metadata.threadId;
978
+ return resolveCodexRequestIdentity(
979
+ metadata.sessionId,
980
+ explicit,
981
+ metadata.requestKind ?? "turn",
982
+ );
983
+ }
984
+
895
985
  /**
896
986
  * Inject the Codex-compatible `client_metadata` for an SSE request, sourced
897
987
  * from the same (session, thread, turn) metadata as the WebSocket path. The
@@ -899,56 +989,78 @@ function buildCodexTurnMetadataJson(
899
989
  * Lite flag) stay out: SSE carries the Lite header and turn-state header.
900
990
  */
901
991
  export function withSseRequestMetadata(body: ResponsesBody, metadata: WebSocketRequestMetadata): ResponsesBody {
902
- const wire = wireIdentityFor(metadata.sessionId, metadata.threadId);
903
- if (!metadata.sessionId || !wire) return body;
904
- const turnMetadata = buildCodexTurnMetadataJson(
905
- metadata.sessionId,
906
- metadata.threadId,
907
- metadata.turnId,
908
- metadata.requestKind ?? "turn",
909
- );
992
+ const identity = identityForRequestMetadata(metadata);
993
+ if (!identity) return body;
994
+ const turnMetadata = buildCodexTurnMetadataJson(identity);
910
995
  return {
911
996
  ...body,
912
997
  client_metadata: {
913
998
  ...body.client_metadata,
914
- session_id: wire.sessionId,
915
- thread_id: wire.threadId,
916
- "x-codex-window-id": wire.windowId,
917
- turn_id: metadata.turnId,
918
- "x-codex-installation-id": codexInstallationIdFor(metadata.sessionId),
999
+ session_id: identity.sessionId,
1000
+ thread_id: identity.threadId,
1001
+ "x-codex-window-id": identity.windowId,
1002
+ turn_id: identity.turnId,
1003
+ "x-codex-installation-id": identity.installationId,
1004
+ ...(identity.parentThreadId
1005
+ ? { "x-codex-parent-thread-id": identity.parentThreadId }
1006
+ : {}),
1007
+ ...(identity.parentTurnId
1008
+ ? { parent_turn_id: identity.parentTurnId }
1009
+ : {}),
1010
+ ...(identity.rootTurnId
1011
+ ? { root_turn_id: identity.rootTurnId }
1012
+ : {}),
1013
+ ...(identity.subagentKind
1014
+ ? { "x-openai-subagent": identity.subagentKind }
1015
+ : {}),
919
1016
  ...(turnMetadata ? { "x-codex-turn-metadata": turnMetadata } : {}),
920
1017
  },
921
1018
  };
922
1019
  }
923
1020
 
924
1021
  function withWebSocketRequestMetadata(body: ResponsesBody, metadata: WebSocketRequestMetadata): ResponsesBody {
925
- const wire = wireIdentityFor(metadata.sessionId, metadata.threadId);
926
- const turnState = metadata.sessionId ? codexTurnStateFor(metadata.sessionId) : undefined;
927
- const turnMetadata = metadata.sessionId
928
- ? buildCodexTurnMetadataJson(
929
- metadata.sessionId,
930
- metadata.threadId,
931
- metadata.turnId,
932
- metadata.requestKind ?? "turn",
933
- )
934
- : "";
1022
+ const identity = identityForRequestMetadata(metadata);
1023
+ const turnMetadata = identity ? buildCodexTurnMetadataJson(identity) : "";
935
1024
  return {
936
1025
  ...body,
937
1026
  client_metadata: {
938
1027
  ...body.client_metadata,
939
- ...(metadata.sessionId ? { session_id: wire?.sessionId ?? metadata.sessionId } : {}),
940
- ...(metadata.threadId ? { thread_id: wire?.threadId ?? metadata.threadId } : {}),
941
- ...(wire ? { "x-codex-window-id": wire.windowId } : {}),
942
- turn_id: metadata.turnId,
943
- ...(metadata.sessionId ? { "x-codex-installation-id": codexInstallationIdFor(metadata.sessionId) } : {}),
1028
+ ...(identity ? { session_id: identity.sessionId } : {}),
1029
+ ...(identity ? { thread_id: identity.threadId } : {}),
1030
+ ...(identity ? { "x-codex-window-id": identity.windowId } : {}),
1031
+ turn_id: identity?.turnId ?? metadata.turnId,
1032
+ ...(identity
1033
+ ? { "x-codex-installation-id": identity.installationId }
1034
+ : {}),
1035
+ ...(identity?.parentThreadId
1036
+ ? { "x-codex-parent-thread-id": identity.parentThreadId }
1037
+ : {}),
1038
+ ...(identity?.parentTurnId
1039
+ ? { parent_turn_id: identity.parentTurnId }
1040
+ : {}),
1041
+ ...(identity?.rootTurnId
1042
+ ? { root_turn_id: identity.rootTurnId }
1043
+ : {}),
1044
+ ...(identity?.subagentKind
1045
+ ? { "x-openai-subagent": identity.subagentKind }
1046
+ : {}),
944
1047
  ...(turnMetadata ? { "x-codex-turn-metadata": turnMetadata } : {}),
945
- ...(turnState ? { "x-codex-turn-state": turnState } : {}),
1048
+ ...(identity?.turnState
1049
+ ? { "x-codex-turn-state": identity.turnState }
1050
+ : {}),
946
1051
  [WS_STREAM_REQUEST_START_MS_CLIENT_METADATA_KEY]: Date.now().toString(),
947
1052
  },
948
1053
  };
949
1054
  }
950
1055
 
951
1056
  export function buildRequestBody<TApi extends Api>(model: Model<TApi>, context: Context, profile: CodexRequestProfile, options?: SimpleStreamOptions): ResponsesBody {
1057
+ const requestIdentity = resolveCodexRequestIdentity(
1058
+ options?.sessionId,
1059
+ options?.metadata as Record<string, unknown> | undefined,
1060
+ // Only the session-scoped prompt cache key is needed here. Do not
1061
+ // synthesize a logical turn while constructing startup/prewarm bodies.
1062
+ "prewarm",
1063
+ );
952
1064
  const messages = convertResponsesMessages(model, context, new Set([...CODEX_TOOL_CALL_PROVIDERS, model.provider]), {
953
1065
  includeSystemPrompt: false,
954
1066
  });
@@ -997,7 +1109,7 @@ export function buildRequestBody<TApi extends Api>(model: Model<TApi>, context:
997
1109
  input: [],
998
1110
  text: { verbosity: ((options as { textVerbosity?: string } | undefined)?.textVerbosity ?? "low") as string },
999
1111
  include: ["reasoning.encrypted_content"],
1000
- prompt_cache_key: wireIdentityFor(options?.sessionId)?.sessionId ?? options?.sessionId,
1112
+ prompt_cache_key: requestIdentity?.sessionId ?? options?.sessionId,
1001
1113
  tool_choice: "auto",
1002
1114
  parallel_tool_calls: profile.supportsParallelTools,
1003
1115
  };
@@ -2131,8 +2243,14 @@ function isPreviousResponseNotFoundError(error: unknown): boolean {
2131
2243
  }
2132
2244
 
2133
2245
  function webSocketHeaderIdentity(headers: Headers): string {
2246
+ const requestScoped = new Set([
2247
+ "x-codex-turn-metadata",
2248
+ "x-codex-turn-state",
2249
+ "x-codex-window-id",
2250
+ ]);
2134
2251
  return shortHash(
2135
2252
  [...headers.entries()]
2253
+ .filter(([name]) => !requestScoped.has(name.toLowerCase()))
2136
2254
  .sort(([left], [right]) => left.localeCompare(right))
2137
2255
  .map(([name, value]) => `${name}:${value}`)
2138
2256
  .join("\n"),
@@ -2535,11 +2653,17 @@ function buildJsonHeaders(
2535
2653
  accountId: string | undefined,
2536
2654
  apiKey: string,
2537
2655
  sessionId?: string,
2656
+ requestIdentity?: CodexRequestIdentity,
2538
2657
  ): Headers {
2539
2658
  const headers = buildBaseCodexHeaders(modelHeaders, additionalHeaders, accountId, apiKey);
2540
2659
  setProviderDefaultHeader(headers, "accept", "application/json");
2541
2660
  setProviderDefaultHeader(headers, "content-type", "application/json");
2542
- applyWireIdentityHeaders(headers, sessionId, sessionId);
2661
+ applyWireIdentityHeaders(
2662
+ headers,
2663
+ sessionId,
2664
+ requestIdentity?.threadId ?? sessionId,
2665
+ requestIdentity,
2666
+ );
2543
2667
  return headers;
2544
2668
  }
2545
2669
 
@@ -2929,6 +3053,7 @@ async function requestCodexCompactionTriggerWithTransport(
2929
3053
  options: {
2930
3054
  sessionId?: string;
2931
3055
  turnId?: string;
3056
+ requestIdentity?: CodexRequestIdentity;
2932
3057
  signal?: AbortSignal;
2933
3058
  settings: ResolvedCodexModelSettings;
2934
3059
  maxRetries?: number;
@@ -2939,9 +3064,17 @@ async function requestCodexCompactionTriggerWithTransport(
2939
3064
  const responsesMode = resolveCodexRequestProfile(options.settings.requestProfile).responsesMode;
2940
3065
  const sseUrl = resolveCodexUrl(model.baseUrl, { apiKeyMode: options.settings.apiKeyMode });
2941
3066
  const requestMetadata: WebSocketRequestMetadata = {
2942
- ...(options.sessionId ? { sessionId: options.sessionId, threadId: options.sessionId } : {}),
2943
- turnId: options.turnId || createPiTurnId(),
3067
+ ...(options.sessionId ? { sessionId: options.sessionId } : {}),
3068
+ ...(options.requestIdentity?.threadId
3069
+ ? { threadId: options.requestIdentity.threadId }
3070
+ : {}),
3071
+ turnId: options.requestIdentity?.turnId
3072
+ || options.turnId
3073
+ || createPiTurnId(),
2944
3074
  requestKind: "compaction",
3075
+ ...(options.requestIdentity
3076
+ ? { identity: options.requestIdentity }
3077
+ : {}),
2945
3078
  };
2946
3079
  if (transport === "sse") {
2947
3080
  return requestCodexCompactionTrigger(
@@ -3083,6 +3216,11 @@ export async function requestOpenAINativeCompaction(
3083
3216
  auth,
3084
3217
  apiKeyMode: settings.apiKeyMode,
3085
3218
  });
3219
+ const requestIdentity = resolveCodexRequestIdentity(
3220
+ options.sessionId,
3221
+ options.turnId ? { turn_id: options.turnId } : undefined,
3222
+ "compaction",
3223
+ );
3086
3224
  let body = applyFastModeServiceTier(buildRequestBody(model, context, profile, {
3087
3225
  apiKey: options.apiKey,
3088
3226
  headers: options.headers,
@@ -3116,8 +3254,12 @@ export async function requestOpenAINativeCompaction(
3116
3254
  options.apiKey,
3117
3255
  options.sessionId,
3118
3256
  profile,
3257
+ requestIdentity?.threadId,
3258
+ requestIdentity,
3119
3259
  ), settings, model);
3120
- const requestId = options.sessionId || createCodexRequestId();
3260
+ const requestId = requestIdentity?.threadId
3261
+ ?? options.sessionId
3262
+ ?? createCodexRequestId();
3121
3263
  const websocketHeaders = applyConfiguredResponsesFeatureHeaders(buildWebSocketHeaders(
3122
3264
  model.headers,
3123
3265
  options.headers,
@@ -3125,6 +3267,7 @@ export async function requestOpenAINativeCompaction(
3125
3267
  options.apiKey,
3126
3268
  requestId,
3127
3269
  requestId,
3270
+ requestIdentity,
3128
3271
  ), settings, model);
3129
3272
  const item = await requestCodexCompactionTriggerWithTransport(
3130
3273
  model,
@@ -3133,6 +3276,7 @@ export async function requestOpenAINativeCompaction(
3133
3276
  {
3134
3277
  sessionId: options.sessionId,
3135
3278
  turnId: options.turnId,
3279
+ requestIdentity,
3136
3280
  signal: options.signal,
3137
3281
  settings,
3138
3282
  maxRetries: options.maxRetries,
@@ -3148,6 +3292,7 @@ export async function requestOpenAINativeCompaction(
3148
3292
  accountId,
3149
3293
  options.apiKey,
3150
3294
  options.sessionId,
3295
+ requestIdentity,
3151
3296
  );
3152
3297
  if (profile.responsesMode === "lite") {
3153
3298
  setProviderGeneratedHeader(headers, X_OPENAI_INTERNAL_CODEX_RESPONSES_LITE, "true");
@@ -3688,15 +3833,16 @@ function createCodexStream<TApi extends Api>(
3688
3833
  auth,
3689
3834
  apiKeyMode: apiKeyTransport,
3690
3835
  });
3836
+ const requestIdentity = resolveCodexRequestIdentity(
3837
+ options?.sessionId,
3838
+ options?.metadata as Record<string, unknown> | undefined,
3839
+ "turn",
3840
+ );
3691
3841
  let body = applyFastModeServiceTier(
3692
3842
  buildRequestBody(model, context, requestProfile, options),
3693
3843
  settings,
3694
3844
  model,
3695
3845
  );
3696
- const nextBody = await options?.onPayload?.(body, model);
3697
- if (nextBody !== undefined) {
3698
- body = nextBody as ResponsesBody;
3699
- }
3700
3846
  if (settings.nativeProviderTools) {
3701
3847
  const webSearch = settings.modelProfile.effective.tools.webSearch;
3702
3848
  body = rewriteNativeOpenAiTools(body, {
@@ -3710,46 +3856,59 @@ function createCodexStream<TApi extends Api>(
3710
3856
  : false,
3711
3857
  }).payload;
3712
3858
  }
3859
+ const nextBody = await options?.onPayload?.(body, model);
3860
+ if (nextBody !== undefined) {
3861
+ body = nextBody as ResponsesBody;
3862
+ }
3713
3863
  options = withRequestServiceTier(options, body.service_tier);
3714
3864
  ensureWebSearchDetailsIncluded(body);
3715
3865
 
3716
- const optionMetadata = options?.metadata;
3717
- const metadataString = (key: string): string | undefined => {
3718
- const value = optionMetadata?.[key];
3719
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
3720
- };
3721
- const websocketSessionId = metadataString("session_id") ?? options?.sessionId;
3722
- const websocketThreadId = metadataString("thread_id") ?? options?.sessionId;
3723
- const websocketTurnId = metadataString("turn_id")
3724
- ?? deps.getCurrentTurnId?.(options?.sessionId)
3725
- ?? createPiTurnId();
3726
- const websocketRequestId = websocketThreadId || websocketSessionId || createCodexRequestId();
3866
+ const websocketSessionId = requestIdentity?.sessionId ?? options?.sessionId;
3867
+ const websocketThreadId = requestIdentity?.threadId ?? options?.sessionId;
3868
+ const websocketTurnId = requestIdentity?.turnId
3869
+ || deps.getCurrentTurnId?.(options?.sessionId)
3870
+ || createPiTurnId();
3871
+ const websocketRequestId = websocketThreadId
3872
+ || websocketSessionId
3873
+ || createCodexRequestId();
3727
3874
  const websocketRequestMetadata: WebSocketRequestMetadata = {
3728
- ...(websocketSessionId ? { sessionId: websocketSessionId } : {}),
3875
+ ...(options?.sessionId ? { sessionId: options.sessionId } : {}),
3729
3876
  ...(websocketThreadId ? { threadId: websocketThreadId } : {}),
3730
3877
  turnId: websocketTurnId,
3878
+ ...(requestIdentity ? { identity: requestIdentity } : {}),
3731
3879
  };
3732
- const sseHeaders = applyConfiguredResponsesFeatureHeaders(
3880
+ let sseHeaders = applyConfiguredResponsesFeatureHeaders(
3733
3881
  buildSSEHeaders(
3734
3882
  model.headers,
3735
3883
  requestHeaders,
3736
3884
  accountId,
3737
3885
  apiKey,
3738
- websocketSessionId || options?.sessionId,
3886
+ options?.sessionId,
3739
3887
  requestProfile,
3740
- websocketThreadId || websocketRequestId,
3888
+ websocketThreadId,
3889
+ requestIdentity,
3741
3890
  ),
3742
3891
  settings,
3743
3892
  model as Model<Api>,
3744
3893
  );
3745
- const websocketHeaders = applyConfiguredResponsesFeatureHeaders(buildWebSocketHeaders(
3894
+ let websocketHeaders = applyConfiguredResponsesFeatureHeaders(buildWebSocketHeaders(
3746
3895
  model.headers,
3747
3896
  requestHeaders,
3748
3897
  accountId,
3749
3898
  apiKey,
3750
- websocketSessionId || websocketRequestId,
3751
- websocketThreadId || websocketRequestId,
3899
+ options?.sessionId ?? websocketRequestId,
3900
+ websocketThreadId ?? websocketRequestId,
3901
+ requestIdentity,
3752
3902
  ), settings, model as Model<Api>);
3903
+ const transformHeaders = (
3904
+ options as
3905
+ | (SimpleStreamOptions & {
3906
+ transformHeaders?: (
3907
+ headers: ProviderHeaders,
3908
+ ) => ProviderHeaders | Promise<ProviderHeaders>;
3909
+ })
3910
+ | undefined
3911
+ )?.transformHeaders;
3753
3912
  const bodyJson = JSON.stringify(withSseRequestMetadata(body, websocketRequestMetadata));
3754
3913
  const responseHeaderTimeoutMs = responseHeaderTimeoutMsFromOptions(options);
3755
3914
  const configuredTransport: ProviderTransport = settings.openaiTransport;
@@ -3771,6 +3930,13 @@ function createCodexStream<TApi extends Api>(
3771
3930
  && websocketHttpFallbackSessions.has(fallbackKey);
3772
3931
 
3773
3932
  if (transport !== "sse" && !sessionFellBackToHttp) {
3933
+ if (transformHeaders) {
3934
+ websocketHeaders = providerHeadersToHeaders(
3935
+ await transformHeaders(
3936
+ headersToRecord(websocketHeaders),
3937
+ ),
3938
+ );
3939
+ }
3774
3940
  const startupPrewarmTask = options?.sessionId
3775
3941
  ? deps.getStartupPrewarm?.(options.sessionId, model as Model<Api>)
3776
3942
  : undefined;
@@ -3860,6 +4026,11 @@ function createCodexStream<TApi extends Api>(
3860
4026
  let lastError: Error | undefined;
3861
4027
  const sseUrl = resolveCodexUrl(model.baseUrl, { apiKeyMode: apiKeyTransport });
3862
4028
  const sseDispatcher = await proxyDispatcherForUrl(sseUrl);
4029
+ if (transformHeaders) {
4030
+ sseHeaders = providerHeadersToHeaders(
4031
+ await transformHeaders(headersToRecord(sseHeaders)),
4032
+ );
4033
+ }
3863
4034
 
3864
4035
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
3865
4036
  if (options?.signal?.aborted) {
@@ -3962,9 +4133,9 @@ export function registerOpenAIResponsesProviders(
3962
4133
  pi: ExtensionAPI,
3963
4134
  options: { getCurrentCwd: () => string },
3964
4135
  ): OpenAIResponsesProviderController {
4136
+ installCodexIdentityLifecycle(pi);
3965
4137
  const pendingActivities: PendingActivity[] = [];
3966
4138
  const imagePreviewCache = new Map<string, CachedImagePreview>();
3967
- const activeTurnIds = new Map<string, string>();
3968
4139
  const startupPrewarms = new Map<string, StartupPrewarmState>();
3969
4140
  const sessionStartupPrewarmTasks = new Map<string, SessionStartupPrewarmTask>();
3970
4141
  let sessionGeneration = 0;
@@ -4015,6 +4186,11 @@ export function registerOpenAIResponsesProviders(
4015
4186
  }
4016
4187
 
4017
4188
  const profile = resolveCodexRequestProfile(settings.requestProfile);
4189
+ const requestIdentity = resolveCodexRequestIdentity(
4190
+ sessionId,
4191
+ undefined,
4192
+ "prewarm",
4193
+ );
4018
4194
  // Match Codex startup prewarm: snapshot only stable request context.
4019
4195
  // Conversation history and the first user message are sent by the first
4020
4196
  // real request as an incremental continuation from this response.
@@ -4069,7 +4245,8 @@ export function registerOpenAIResponsesProviders(
4069
4245
  }),
4070
4246
  auth.apiKey ?? "",
4071
4247
  sessionId,
4072
- sessionId,
4248
+ requestIdentity?.threadId ?? sessionId,
4249
+ requestIdentity,
4073
4250
  ), settings, model);
4074
4251
  const cacheKey = webSocketCacheKey(
4075
4252
  sessionId,
@@ -4105,9 +4282,10 @@ export function registerOpenAIResponsesProviders(
4105
4282
  body,
4106
4283
  requestMetadata: {
4107
4284
  sessionId,
4108
- threadId: sessionId,
4109
- turnId: createPiTurnId(),
4285
+ threadId: requestIdentity?.threadId ?? sessionId,
4286
+ turnId: requestIdentity?.turnId ?? "",
4110
4287
  requestKind: "prewarm",
4288
+ ...(requestIdentity ? { identity: requestIdentity } : {}),
4111
4289
  },
4112
4290
  signal: abortController.signal,
4113
4291
  connectTimeoutMs: WEBSOCKET_PREWARM_TIMEOUT_MS,
@@ -4179,7 +4357,8 @@ export function registerOpenAIResponsesProviders(
4179
4357
  }
4180
4358
  return createCodexStream(model, context, streamOptions, {
4181
4359
  getCurrentCwd: options.getCurrentCwd,
4182
- getCurrentTurnId: (sessionId) => sessionId ? activeTurnIds.get(sessionId) : undefined,
4360
+ getCurrentTurnId: (sessionId) =>
4361
+ currentCodexTurn(sessionId)?.turnId,
4183
4362
  getStartupPrewarm: (sessionId, requestModel) => {
4184
4363
  const task = sessionStartupPrewarmTasks.get(sessionId);
4185
4364
  return task
@@ -4227,7 +4406,6 @@ export function registerOpenAIResponsesProviders(
4227
4406
  sessionGeneration++;
4228
4407
  abortStartupPrewarms();
4229
4408
  ensureProviderShimForModel(ctx?.model as Model<Api> | undefined, ctx?.cwd);
4230
- activeTurnIds.clear();
4231
4409
  clearPendingMessages();
4232
4410
  const generation = sessionGeneration;
4233
4411
  // Do not block session startup. The first provider request naturally
@@ -4252,34 +4430,26 @@ export function registerOpenAIResponsesProviders(
4252
4430
  ensureProviderShimForModel(ctx?.model as Model<Api> | undefined, ctx?.cwd);
4253
4431
  });
4254
4432
 
4255
- pi.on("session_shutdown", async () => {
4433
+ pi.on("session_shutdown", async (_event, ctx) => {
4256
4434
  sessionGeneration++;
4257
4435
  abortStartupPrewarms();
4258
4436
  if (pendingActivities.length > 0) {
4259
4437
  flushPendingMessages();
4260
4438
  }
4261
- activeTurnIds.clear();
4262
- closeProviderWebSocketSessions();
4439
+ closeProviderWebSocketSessions(
4440
+ ctx?.sessionManager?.getSessionId?.(),
4441
+ );
4263
4442
  clearPendingMessages();
4264
4443
  });
4265
4444
 
4266
4445
  pi.on("before_agent_start", async (_event, ctx) => {
4267
4446
  ensureProviderShimForModel(ctx.model as Model<Api> | undefined, ctx.cwd);
4268
- const sessionId = ctx?.sessionManager?.getSessionId();
4269
- if (!sessionId) return;
4270
- const turnId = createPiTurnId();
4271
- activeTurnIds.set(sessionId, turnId);
4272
4447
  });
4273
4448
 
4274
4449
  pi.on("agent_end", async (_event, ctx) => {
4275
4450
  schedulePendingMessageFlush();
4276
4451
  });
4277
4452
 
4278
- pi.on("agent_settled", async (_event, ctx) => {
4279
- const sessionId = ctx?.sessionManager?.getSessionId();
4280
- if (sessionId) activeTurnIds.delete(sessionId);
4281
- });
4282
-
4283
4453
  pi.registerMessageRenderer<ImageDisplayMessageDetails>(IMAGE_SAVE_DISPLAY_MESSAGE_TYPE, (message, options, theme) => {
4284
4454
  const savedImage = message.details?.savedImages?.[0];
4285
4455
  const textContent = typeof message.content === "string"
@@ -4332,7 +4502,10 @@ export function registerOpenAIResponsesProviders(
4332
4502
 
4333
4503
  return {
4334
4504
  getCurrentTurnId(sessionId) {
4335
- return sessionId ? activeTurnIds.get(sessionId) : undefined;
4505
+ return currentCodexTurn(sessionId)?.turnId;
4506
+ },
4507
+ getRequestIdentity(sessionId, requestKind = "turn") {
4508
+ return resolveCodexRequestIdentity(sessionId, undefined, requestKind);
4336
4509
  },
4337
4510
  };
4338
4511
  }