pi-openai-toolkit 0.23.2 → 0.24.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.
@@ -20,9 +20,7 @@ export type NativeFallbackFailureReason =
20
20
  | "auth-failed"
21
21
  | "aborted"
22
22
  | "empty-summary"
23
- | "compact-failed"
24
- | "public-runtime-unavailable"
25
- | "missing-model";
23
+ | "compact-failed";
26
24
 
27
25
  export type NativeFallbackResult =
28
26
  | {
@@ -63,10 +61,8 @@ function toErrorMessage(error: unknown): string {
63
61
  * active model cannot use remote v2 at all. This function never inspects `remoteCompactModel`
64
62
  * itself, so a model that cannot compact remotely can never inherit the remote producer.
65
63
  *
66
- * Ordinary no-boundary defaults stay with Pi. When a window constrains input,
67
- * useCurrentModel runs the same public native primitive with the public registry
68
- * stream, current thinking and auth, rather than reopening unscoped host history.
69
- * Host-private custom stream/retry callbacks are not exposed by ExtensionContext.
64
+ * Unset or same-current-model defaults stay with Pi. This helper does not
65
+ * select a session model or take ownership of Pi's scheduler.
70
66
  */
71
67
  export async function runNativeFallbackCompaction(args: {
72
68
  ctx: ExtensionContext;
@@ -75,10 +71,7 @@ export async function runNativeFallbackCompaction(args: {
75
71
  /** Model spec supplied by the caller for this specific fallback reason. */
76
72
  modelSpec?: string;
77
73
  compactFn?: NativeCompactFn;
78
- /** Scoped input cannot be handed back to an unscoped host default. */
79
- scoped?: boolean;
80
- /** Run current-model native policy, independently of Toolkit's fallback config. */
81
- useCurrentModel?: boolean;
74
+ /** Reject results if the originating session/branch/selection changed. */
82
75
  isCurrent?: () => boolean;
83
76
  }): Promise<NativeFallbackResult> {
84
77
  const { ctx, event, config } = args;
@@ -87,21 +80,16 @@ export async function runNativeFallbackCompaction(args: {
87
80
  const isCurrent = () => !event.signal.aborted && (args.isCurrent?.() ?? true);
88
81
  if (!isCurrent()) return { ok: false, reason: "aborted" };
89
82
 
90
- if (!args.useCurrentModel && !nativeFallback.enabled) return { ok: false, reason: "disabled" };
91
- const spec = args.useCurrentModel ? (ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "") : args.modelSpec?.trim() ?? "";
92
- if (!spec) return { ok: false, reason: args.useCurrentModel ? "missing-model" : "no-model-configured" };
83
+ if (!nativeFallback.enabled) return { ok: false, reason: "disabled" };
84
+ const spec = args.modelSpec?.trim() ?? "";
85
+ if (!spec) return { ok: false, reason: "no-model-configured" };
93
86
  const parsed = parseModelSpec(spec);
94
87
  if (!parsed) return { ok: false, reason: "invalid-model-spec", modelSpec: spec };
95
- const model = args.useCurrentModel ? ctx.model : ctx.modelRegistry.find(parsed.provider, parsed.modelId);
88
+ const model = ctx.modelRegistry.find(parsed.provider, parsed.modelId);
96
89
  if (!model) return { ok: false, reason: "model-not-found", modelSpec: spec };
97
- if (!args.useCurrentModel && ctx.model && ctx.model.provider === model.provider && ctx.model.id === model.id) {
90
+ if (ctx.model && ctx.model.provider === model.provider && ctx.model.id === model.id) {
98
91
  return { ok: false, reason: "same-as-current-model", modelSpec: spec };
99
92
  }
100
- // The public registry preserves provider-native stream/auth wiring. Do not
101
- // silently use a compat-only transport for scoped current-model compression.
102
- if (args.scoped && typeof ctx.modelRegistry.streamSimple !== "function" && !args.compactFn) {
103
- return { ok: false, reason: "public-runtime-unavailable", modelSpec: spec };
104
- }
105
93
 
106
94
  // Pi owns prompt construction, tool-result truncation and split-turn requests.
107
95
  // A chars/4 estimate is not target-model token accounting and must not veto
@@ -126,8 +114,8 @@ export async function runNativeFallbackCompaction(args: {
126
114
  mergeProviderHeaders(auth.headers),
127
115
  event.customInstructions,
128
116
  event.signal,
129
- args.useCurrentModel ? ctx.thinkingLevel : nativeFallback.thinkingLevel,
130
- args.scoped && typeof ctx.modelRegistry.streamSimple === "function" ? ctx.modelRegistry.streamSimple.bind(ctx.modelRegistry) : undefined,
117
+ nativeFallback.thinkingLevel,
118
+ undefined,
131
119
  auth.env,
132
120
  );
133
121
 
@@ -91,7 +91,7 @@ export function buildCodexCliUserAgent(version: string): string {
91
91
 
92
92
  /**
93
93
  * Codex client identity used by synthetic gateway requests in their User-Agent.
94
- * Native live and context-management requests do not generate a `version`
94
+ * Native live and shared synthetic requests do not generate a `version`
95
95
  * header; explicitly configured provider values remain caller-owned.
96
96
  */
97
97
  export const CODEX_CLIENT_VERSION = "0.153.0";
@@ -0,0 +1,35 @@
1
+ /** Reserved persisted identifiers, retained only to refuse retired sessions. */
2
+ export const RETIRED_REMOTE_WINDOW_TYPE = "codex-context-window" as const;
3
+ export const RETIRED_MANUAL_COMPACT_TYPE = "pi-openai-toolkit:manual-compact" as const;
4
+ export const RETIRED_REMOTE_WINDOW_SUMMARY =
5
+ "[Pi Codex context-window boundary; no conversation summary was generated.]";
6
+
7
+ export type RetiredRemoteWindowsReason =
8
+ | "window-message"
9
+ | "window-compaction"
10
+ | "window-summary"
11
+ | "manual-compact-record";
12
+
13
+ export const RETIRED_REMOTE_WINDOWS_MESSAGE =
14
+ "Remote Windows has been removed. This branch contains retired window or manual-compaction records and cannot continue. Start a new session and explicitly choose the model you want to use. Existing history is unchanged; no model or thinking level was restored.";
15
+
16
+ function isRecord(value: unknown): value is Record<string, unknown> {
17
+ return !!value && typeof value === "object" && !Array.isArray(value);
18
+ }
19
+
20
+ /**
21
+ * Inspect only the supplied active branch. Reserved identifiers suffice even
22
+ * when their data is damaged, terminal or superseded by a newer checkpoint.
23
+ * This deliberately interprets no ownership, recovery or migration state.
24
+ */
25
+ export function detectRetiredRemoteWindows(entries: readonly unknown[]): RetiredRemoteWindowsReason | undefined {
26
+ for (const entry of entries) {
27
+ if (!isRecord(entry)) continue;
28
+ if (entry.type === "custom_message" && entry.customType === RETIRED_REMOTE_WINDOW_TYPE) return "window-message";
29
+ if (entry.type === "custom" && entry.customType === RETIRED_MANUAL_COMPACT_TYPE) return "manual-compact-record";
30
+ if (entry.type !== "compaction") continue;
31
+ if (isRecord(entry.details) && entry.details.strategy === RETIRED_REMOTE_WINDOW_TYPE) return "window-compaction";
32
+ if (entry.summary === RETIRED_REMOTE_WINDOW_SUMMARY) return "window-summary";
33
+ }
34
+ return undefined;
35
+ }
package/src/runtime.ts CHANGED
@@ -784,7 +784,8 @@ export async function resolveRemoteCompactionExecution(
784
784
  options: NativeCompactionSupportOptions = {},
785
785
  remoteModelSpec?: string,
786
786
  ): Promise<RemoteCompactionExecutionResolution> {
787
- const consumerResolution = await resolveNativeCompactionEnvironment(ctx, options);
787
+ const selectedModel = ctx.model;
788
+ const consumerResolution = await resolveNativeCompactionEnvironmentForModel(ctx, selectedModel, options);
788
789
  if (!consumerResolution.ok) {
789
790
  return consumerResolution;
790
791
  }
@@ -809,6 +810,16 @@ export async function resolveRemoteCompactionExecution(
809
810
  };
810
811
  }
811
812
 
813
+ const consumer = consumerResolution.runtime;
814
+ const matches = (model: { provider: string; id: string } | undefined) =>
815
+ model?.provider === parsed.provider && model.id === parsed.modelId;
816
+ // Reuse this operation's physical/auth snapshot for either the current
817
+ // logical selection or its resolved physical identity. Do not resolve a
818
+ // virtual selection again or look up/authenticate an identical producer.
819
+ if (matches(selectedModel) || matches(consumer.currentModel)) {
820
+ return { ok: true, execution: { consumer, compactor: consumer } };
821
+ }
822
+
812
823
  const compactorModel = ctx.modelRegistry.find(parsed.provider, parsed.modelId);
813
824
  if (!compactorModel) {
814
825
  return {
@@ -832,7 +843,6 @@ export async function resolveRemoteCompactionExecution(
832
843
  };
833
844
  }
834
845
 
835
- const consumer = consumerResolution.runtime;
836
846
  const compactor = compactorResolution.runtime;
837
847
  if (compactor.baseUrl !== consumer.baseUrl) {
838
848
  return {
package/src/types.ts CHANGED
@@ -19,8 +19,8 @@ export const RESPONSES_COMPACT_CAPABLE_APIS = [
19
19
  ] as const;
20
20
  /**
21
21
  * Bare model ID used by the independent Astra effort compatibility layer.
22
- * Remote Context eligibility is API/provider based with an exact gateway opt-in;
23
- * this constant does not verify backend support or grant that opt-in.
22
+ * Gateway transport is an independent exact-model opt-in; this constant
23
+ * does not verify backend support or grant that opt-in.
24
24
  */
25
25
  export const ASTRA_MODEL_ID = "gpt-6-astra";
26
26
  export const LEGACY_NATIVE_COMPACTION_STRATEGY = "openai-native-compact-v1";
@@ -60,8 +60,6 @@ export type DebugArtifactKind =
60
60
  | "compaction-event"
61
61
  | "lifecycle";
62
62
 
63
- export type ContextManagementMode = "off" | "remote";
64
-
65
63
  /** How Remote V2 obtains the history it sends to the compaction endpoint. */
66
64
  export type RemoteV2ContextSource = "pi-context-hook" | "legacy";
67
65
 
@@ -79,12 +77,6 @@ export type NativeFallbackConfig = {
79
77
  thinkingLevel: ThinkingLevel;
80
78
  };
81
79
 
82
- /**
83
- * Legacy compatibility setting. Remote Context history is no longer bulk-compacted when
84
- * selecting a non-window model; local projection and a no-summary boundary own that handoff.
85
- */
86
- export type LeaveManagedModePolicy = "warn" | "compact";
87
-
88
80
  export type DebugConfig = {
89
81
  debug: boolean;
90
82
  notifyOnLoad: boolean;
@@ -96,13 +88,6 @@ export type DebugConfig = {
96
88
 
97
89
  export type CompactionConfig = {
98
90
  enabled: boolean;
99
- /**
100
- * Legacy compatibility setting. Non-window consumers never receive retired Remote Context
101
- * history, regardless of this value; retained config files continue to parse it.
102
- */
103
- leaveManagedMode: LeaveManagedModePolicy;
104
- /** Optional Codex Remote Context management. Disabled by default for compatibility. */
105
- contextManagement: ContextManagementMode;
106
91
  /**
107
92
  * Allow a Responses session whose latest compaction was not created by this extension
108
93
  * to restart native compaction from Pi's current serialized session context.
@@ -125,16 +110,10 @@ export type CompactionConfig = {
125
110
  */
126
111
  responsesApis: string[];
127
112
  /**
128
- * Exact "provider/model" keys allowed to use Codex Remote Context on the
129
- * `openai-responses` gateway wire. The native `openai-codex` route ignores
130
- * this list; gateway coverage is opt-in per model.
113
+ * Legacy alias for exact "provider/model" Codex gateway transport opt-ins.
114
+ * Shared by compaction and image generation; not a window capability.
131
115
  */
132
116
  gatewayContextModels: string[];
133
- /**
134
- * Percentage of the context window that, when remaining tokens drop below it,
135
- * triggers a checkpoint/new_context reminder (0-100; 0 disables reminders).
136
- */
137
- contextReminderThresholdPercent: number;
138
117
  notifyOnLoad: boolean;
139
118
  debug: boolean;
140
119
  logProviderPayloads: boolean;
@@ -526,7 +505,6 @@ export const DEFAULT_NATIVE_FALLBACK_CONFIG: NativeFallbackConfig = {
526
505
 
527
506
  export const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {
528
507
  enabled: true,
529
- contextManagement: "off",
530
508
  allowCompactionContinuityBreak: false,
531
509
  remoteCompactModel: undefined,
532
510
  remoteV2ContextSource: "legacy",
@@ -534,8 +512,6 @@ export const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {
534
512
  nativeFallback: { ...DEFAULT_NATIVE_FALLBACK_CONFIG },
535
513
  responsesApis: [...RESPONSES_COMPACT_CAPABLE_APIS],
536
514
  gatewayContextModels: [],
537
- leaveManagedMode: "warn",
538
- contextReminderThresholdPercent: 5,
539
515
  notifyOnLoad: false,
540
516
  debug: false,
541
517
  logProviderPayloads: false,
@@ -1,270 +0,0 @@
1
- import type { Api, Model, ProviderHeaders } from "@earendil-works/pi-ai";
2
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
- import { mergeProviderHeaders } from "../provider-headers";
4
- import {
5
- CODEX_AFFINITY_SCOPE,
6
- CODEX_CLIENT_VERSION,
7
- CODEX_GATEWAY_ORIGINATOR,
8
- buildCodexCliUserAgent,
9
- filterCodexGatewayHeaders,
10
- } from "../responses-headers";
11
- export { CODEX_AFFINITY_SCOPE } from "../responses-headers";
12
- import { normalizeBaseUrl, resolveEffectiveModel, VIRTUAL_MODEL_API } from "../runtime";
13
- import { isExactModelAllowed } from "../model-scope";
14
- import {
15
- isNonEmptyString,
16
- type CodexContextProvider,
17
- type CodexContextProviderResolution,
18
- } from "./types";
19
-
20
- const CODEX_PROVIDER = "openai-codex";
21
- const CODEX_API = "openai-codex-responses";
22
- const GATEWAY_API = "openai-responses";
23
- const CODEX_TOOL_ORIGINATOR = CODEX_GATEWAY_ORIGINATOR;
24
- const CODEX_TOOL_VERSION = "0.0.0";
25
- const CODEX_SESSION_ID_MAX_LENGTH = 64;
26
-
27
- export const CODEX_CONTEXT_PROVIDER_ERROR =
28
- "Remote Context management requires a configured Codex-compatible backend";
29
-
30
- export function isNativeCodexModel(
31
- model: ExtensionContext["model"] | undefined,
32
- ): boolean {
33
- return model?.provider === CODEX_PROVIDER && model.api === CODEX_API;
34
- }
35
-
36
- export function isCodexGatewayModel(
37
- model: ExtensionContext["model"] | undefined,
38
- gatewayModels: readonly string[] = [],
39
- ): boolean {
40
- // Gateway coverage is an operator allowlist of exact "provider/model" keys
41
- // (compaction.gatewayContextModels). The backend relay must actually pass
42
- // hosted window markers through; models outside the list stay on remote
43
- // compaction v2.
44
- if (!model || model.api !== GATEWAY_API) return false;
45
- return isExactModelAllowed(model, gatewayModels);
46
- }
47
-
48
- export function normalizeCodexBackendBaseUrl(baseUrl: string | undefined | null): string | undefined {
49
- const normalized = normalizeBaseUrl(baseUrl);
50
- if (!normalized) return undefined;
51
- try {
52
- const url = new URL(normalized);
53
- const pathname = url.pathname.replace(/\/+$/, "");
54
- if (pathname.endsWith("/codex/responses")) {
55
- url.pathname = pathname.slice(0, -"/responses".length);
56
- return url.toString().replace(/\/$/, "");
57
- }
58
- if (pathname.endsWith("/codex")) return normalized;
59
- if (pathname.endsWith("/backend-api") || pathname.endsWith("/api")) {
60
- return `${normalized}/codex`;
61
- }
62
- if (pathname === "") return `${normalized}/api/codex`;
63
- if (pathname === "/") return `${normalized}/api/codex`;
64
- return undefined;
65
- } catch {
66
- return undefined;
67
- }
68
- }
69
-
70
- function headerValue(headers: ProviderHeaders | undefined, name: string): string | undefined {
71
- const wanted = name.toLowerCase();
72
- for (const [key, value] of Object.entries(headers ?? {})) {
73
- if (key.toLowerCase() === wanted && typeof value === "string" && value.trim()) {
74
- return value.trim();
75
- }
76
- }
77
- return undefined;
78
- }
79
-
80
- function bearerToken(value: string | undefined): string | undefined {
81
- if (!value) return undefined;
82
- const match = value.trim().match(/^Bearer\s+(.+)$/i);
83
- return (match?.[1] ?? value).trim() || undefined;
84
- }
85
-
86
- function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
87
- const parts = token.split(".");
88
- if (parts.length !== 3) return undefined;
89
- try {
90
- const value: unknown = JSON.parse(Buffer.from(parts[1]!, "base64url").toString("utf8"));
91
- return value && typeof value === "object" && !Array.isArray(value)
92
- ? value as Record<string, unknown>
93
- : undefined;
94
- } catch {
95
- return undefined;
96
- }
97
- }
98
-
99
- function accountIdFromToken(token: string): string | undefined {
100
- const payload = decodeJwtPayload(token);
101
- const direct = payload?.chatgpt_account_id;
102
- if (isNonEmptyString(direct)) return direct.trim();
103
- const auth = payload?.["https://api.openai.com/auth"];
104
- if (!auth || typeof auth !== "object" || Array.isArray(auth)) return undefined;
105
- const nested = (auth as Record<string, unknown>).chatgpt_account_id;
106
- return isNonEmptyString(nested) ? nested.trim() : undefined;
107
- }
108
-
109
- function userAgent(version: string): string {
110
- return buildCodexCliUserAgent(version);
111
- }
112
-
113
- function clampSessionId(value: string | undefined): string | undefined {
114
- if (!value) return undefined;
115
- const chars = Array.from(value.trim());
116
- if (chars.length === 0) return undefined;
117
- return chars.length <= CODEX_SESSION_ID_MAX_LENGTH
118
- ? chars.join("")
119
- : chars.slice(0, CODEX_SESSION_ID_MAX_LENGTH).join("");
120
- }
121
-
122
- function resolveContextSessionId(ctx: ExtensionContext): string | undefined {
123
- try {
124
- return clampSessionId(ctx.sessionManager.getSessionId());
125
- } catch {
126
- return undefined;
127
- }
128
- }
129
-
130
- export type CodexContextProviderHeaderOptions = {
131
- sessionId?: string;
132
- clientRequestId?: string;
133
- };
134
-
135
- export function codexContextProviderHeaders(
136
- provider: CodexContextProvider,
137
- options: CodexContextProviderHeaderOptions = {},
138
- ): Headers {
139
- const headers = new Headers();
140
- for (const [name, value] of Object.entries(provider.headers)) {
141
- if (typeof value === "string") headers.set(name, value);
142
- }
143
-
144
- if (provider.kind === "native-codex") {
145
- headers.set("Authorization", `Bearer ${provider.token}`);
146
- headers.set("ChatGPT-Account-ID", provider.accountId);
147
- headers.set("originator", CODEX_TOOL_ORIGINATOR);
148
- headers.set("User-Agent", userAgent(CODEX_TOOL_VERSION));
149
- } else {
150
- headers.set("Authorization", `Bearer ${provider.apiKey}`);
151
- headers.delete("ChatGPT-Account-ID");
152
- headers.delete("Cookie");
153
- headers.set("originator", CODEX_GATEWAY_ORIGINATOR);
154
- headers.set("User-Agent", userAgent(CODEX_CLIENT_VERSION));
155
- // Let the gateway own its version policy; preserve an explicitly configured
156
- // version already copied from provider.headers above.
157
- headers.set("X-Codex-Affinity-Scope", CODEX_AFFINITY_SCOPE);
158
- headers.set("X-Codex-Model", provider.model);
159
- }
160
-
161
- const sessionId = clampSessionId(options.sessionId);
162
- if (sessionId) {
163
- headers.set("Session-Id", sessionId);
164
- headers.set("X-Client-Request-Id", clampSessionId(options.clientRequestId) ?? sessionId);
165
- }
166
- headers.set("content-type", "application/json");
167
- return headers;
168
- }
169
-
170
- export async function resolveCodexContextProvider(
171
- ctx: ExtensionContext,
172
- modelOverride: ExtensionContext["model"] = ctx.model,
173
- gatewayModels: readonly string[] = [],
174
- ): Promise<CodexContextProviderResolution> {
175
- const selectedModel = modelOverride;
176
- const selectedDescriptor = {
177
- provider: selectedModel?.provider,
178
- api: selectedModel?.api,
179
- model: selectedModel?.id,
180
- baseUrl: normalizeBaseUrl(selectedModel?.baseUrl),
181
- };
182
- const effective = await resolveEffectiveModel(ctx, selectedModel);
183
- if (!effective.ok) return { ok: false, reason: "unsupported-model", ...selectedDescriptor };
184
- const model = effective.model;
185
- const descriptor = {
186
- provider: model.provider,
187
- api: model.api,
188
- model: model.id,
189
- baseUrl: normalizeBaseUrl(model.baseUrl),
190
- };
191
-
192
- const isNative = isNativeCodexModel(model);
193
- const isLogicalVirtualGateway = selectedModel?.api === VIRTUAL_MODEL_API && isExactModelAllowed(selectedModel, gatewayModels);
194
- const isGateway = isCodexGatewayModel(model, gatewayModels) ||
195
- (isLogicalVirtualGateway && model.api === GATEWAY_API);
196
- if (!isNative && !isGateway) {
197
- return { ok: false, reason: "unsupported-model", ...descriptor };
198
- }
199
- if (!isNative && model.api !== GATEWAY_API) {
200
- return { ok: false, reason: "unsupported-api", ...descriptor };
201
- }
202
- if (isNative && model.api !== CODEX_API) {
203
- return { ok: false, reason: "unsupported-api", ...descriptor };
204
- }
205
-
206
- let auth: Awaited<ReturnType<typeof ctx.modelRegistry.getApiKeyAndHeaders>>;
207
- try {
208
- auth = await ctx.modelRegistry.getApiKeyAndHeaders(model as Model<Api>);
209
- } catch {
210
- return { ok: false, reason: "auth-resolution-failed", ...descriptor };
211
- }
212
- if (!auth.ok) return { ok: false, reason: "auth-resolution-failed", ...descriptor };
213
-
214
- const rawBaseUrl = normalizeBaseUrl(auth.baseUrl) ?? normalizeBaseUrl(model.baseUrl);
215
- if (!rawBaseUrl) return { ok: false, reason: "missing-base-url", ...descriptor };
216
-
217
- if (isGateway) {
218
- if (!resolveContextSessionId(ctx)) {
219
- return { ok: false, reason: "missing-session-id", ...descriptor, baseUrl: rawBaseUrl };
220
- }
221
- const apiKey = bearerToken(auth.apiKey) ?? bearerToken(headerValue(auth.headers, "authorization"));
222
- if (!apiKey) return { ok: false, reason: "missing-api-key", ...descriptor, baseUrl: rawBaseUrl };
223
- return {
224
- ok: true,
225
- provider: {
226
- kind: "codex-gateway",
227
- route: "codex-gateway",
228
- provider: model.provider,
229
- api: GATEWAY_API,
230
- model: model.id,
231
- baseUrl: rawBaseUrl,
232
- apiKey,
233
- headers: filterCodexGatewayHeaders(mergeProviderHeaders(model.headers, auth.headers)),
234
- },
235
- };
236
- }
237
-
238
- const token = bearerToken(auth.apiKey) ?? bearerToken(headerValue(auth.headers, "authorization"));
239
- if (!token) return { ok: false, reason: "missing-token", ...descriptor };
240
- const accountId = headerValue(auth.headers, "chatgpt-account-id") ?? accountIdFromToken(token);
241
- if (!accountId) return { ok: false, reason: "missing-account-id", ...descriptor };
242
- const baseUrl = normalizeCodexBackendBaseUrl(rawBaseUrl);
243
- if (!baseUrl) return { ok: false, reason: "unsupported-backend", ...descriptor };
244
-
245
- const headers = mergeProviderHeaders(model.headers, auth.headers);
246
- return {
247
- ok: true,
248
- provider: {
249
- kind: "native-codex",
250
- route: "openai-codex",
251
- provider: CODEX_PROVIDER,
252
- api: CODEX_API,
253
- model: model.id,
254
- baseUrl,
255
- token,
256
- accountId,
257
- headers,
258
- },
259
- };
260
- }
261
-
262
- export const _codexProviderTest = {
263
- accountIdFromToken,
264
- bearerToken,
265
- clampSessionId,
266
- decodeJwtPayload,
267
- headerValue,
268
- isCodexGatewayModel,
269
- userAgent,
270
- };