Merge remote-tracking branch 'origin/farm/10fa7941/fix-openai-codex-luna-model'
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed OpenAI Codex turn requests to include the Codex `version` header, matching upstream Codex request metadata for newly gated models.
|
||||
- Fixed xAI SuperGrok multi-account rotation to correctly treat HTTP 403 credit exhaustion and spending limit errors as usage limits, triggering a credential rotation to a sibling account.
|
||||
- Fixed error classification for AWS credential-resolution failures (AwsCredentialsError) to correctly map them as authentication failures.
|
||||
- Fixed OpenAI-compatible chat-completions streams to preserve vLLM-style trailing cached-token usage chunks, ensuring accurate cacheRead and billable input session statistics.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as os from "node:os";
|
||||
import { scheduler } from "node:timers/promises";
|
||||
import { resolveCodexClientVersion } from "@oh-my-pi/pi-catalog/discovery/codex";
|
||||
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
|
||||
import {
|
||||
CODEX_BASE_URL,
|
||||
@@ -632,6 +633,7 @@ interface CodexRequestContext {
|
||||
baseUrl: string;
|
||||
url: string;
|
||||
requestHeaders: Record<string, string>;
|
||||
codexClientVersion: string;
|
||||
transportSessionId?: string;
|
||||
providerSessionState?: CodexProviderSessionState;
|
||||
isolatedTransportState?: CodexProviderSessionState;
|
||||
@@ -1200,6 +1202,7 @@ async function buildCodexRequestContext(
|
||||
const url = resolveCodexResponsesUrl(baseUrl);
|
||||
const promptCacheKey = normalizeOpenAIPromptCacheKey(options?.promptCacheKey ?? options?.sessionId);
|
||||
const transportSessionId = normalizeOpenAIPromptCacheKey(options?.sessionId);
|
||||
const codexClientVersion = await resolveCodexClientVersion(undefined, options?.fetch ?? fetch, options?.signal);
|
||||
const transformedBody = await buildTransformedCodexRequestBody(model, context, options, promptCacheKey);
|
||||
|
||||
const requestHeaders = { ...(model.headers ?? {}), ...(options?.headers ?? {}) };
|
||||
@@ -1275,6 +1278,7 @@ async function buildCodexRequestContext(
|
||||
websocketState,
|
||||
responsesLite,
|
||||
requestMetadata,
|
||||
codexClientVersion,
|
||||
transformedBody,
|
||||
rawRequestDump,
|
||||
};
|
||||
@@ -1427,6 +1431,7 @@ async function openCodexWebSocketTransport(
|
||||
requestContext.requestHeaders,
|
||||
requestContext.accountId,
|
||||
requestContext.apiKey,
|
||||
requestContext.codexClientVersion,
|
||||
requestContext.transportSessionId,
|
||||
"websocket",
|
||||
websocketState,
|
||||
@@ -1527,6 +1532,7 @@ async function openCodexSseTransport(
|
||||
wireBody,
|
||||
state,
|
||||
requestContext.responsesLite,
|
||||
requestContext.codexClientVersion,
|
||||
requestContext.requestMetadata,
|
||||
requestSetup.requestSignal,
|
||||
requestSetup.firstEventTimeoutMs,
|
||||
@@ -2496,6 +2502,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
|
||||
baseUrl: model.baseUrl || CODEX_BASE_URL,
|
||||
url: "",
|
||||
requestHeaders: {},
|
||||
codexClientVersion: packageJson.version,
|
||||
responsesLite: options?.responsesLite === true,
|
||||
transformedBody: { model: model.id },
|
||||
rawRequestDump: {
|
||||
@@ -2559,6 +2566,7 @@ export async function prewarmOpenAICodexResponses(
|
||||
transportSessionId ?? crypto.randomUUID(),
|
||||
providerSessionState,
|
||||
);
|
||||
const codexClientVersion = await resolveCodexClientVersion(undefined, fetch, options?.signal);
|
||||
const requestIdentity = createCodexCompatibilityIdentity(metadataSession);
|
||||
const headers = logger.time(
|
||||
"prewarmCodex:createHeaders",
|
||||
@@ -2566,6 +2574,7 @@ export async function prewarmOpenAICodexResponses(
|
||||
{ ...(model.headers ?? {}), ...(options?.headers ?? {}) },
|
||||
accountId,
|
||||
apiKey,
|
||||
codexClientVersion,
|
||||
promptCacheKey,
|
||||
"websocket",
|
||||
state,
|
||||
@@ -3685,6 +3694,7 @@ async function openCodexSseEventStream(
|
||||
body: RequestBody,
|
||||
state: CodexWebSocketSessionState | undefined,
|
||||
responsesLite: boolean,
|
||||
codexClientVersion: string,
|
||||
requestMetadata: CodexRequestMetadata | undefined,
|
||||
signal: AbortSignal | undefined,
|
||||
firstEventTimeoutMs: number | undefined,
|
||||
@@ -3695,6 +3705,7 @@ async function openCodexSseEventStream(
|
||||
requestHeaders,
|
||||
accountId,
|
||||
apiKey,
|
||||
codexClientVersion,
|
||||
sessionId,
|
||||
"sse",
|
||||
state,
|
||||
@@ -3756,6 +3767,7 @@ function createCodexHeaders(
|
||||
initHeaders: Record<string, string> | undefined,
|
||||
accountId: string | undefined,
|
||||
accessToken: string,
|
||||
codexClientVersion: string,
|
||||
sessionId?: string,
|
||||
transport: CodexTransport = "sse",
|
||||
state?: CodexWebSocketSessionState,
|
||||
@@ -3774,6 +3786,7 @@ function createCodexHeaders(
|
||||
headers.delete("openai-beta");
|
||||
headers.set(OPENAI_HEADERS.BETA, betaHeader);
|
||||
headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
|
||||
headers.set(OPENAI_HEADERS.VERSION, codexClientVersion);
|
||||
headers.set("User-Agent", `pi/${packageJson.version} (${os.platform()} ${os.release()}; ${os.arch()})`);
|
||||
if (sessionId) {
|
||||
headers.set(OPENAI_HEADERS.CONVERSATION_ID, sessionId);
|
||||
|
||||
@@ -100,6 +100,9 @@ function createCodexFetchMock(sse: string, onRequest: (captured: CapturedCodexRe
|
||||
if (url === "https://api.github.com/repos/openai/codex/releases/latest") {
|
||||
return new Response(JSON.stringify({ tag_name: "rust-v0.0.0" }), { status: 200 });
|
||||
}
|
||||
if (url === "https://registry.npmjs.org/@openai%2Fcodex/latest") {
|
||||
return new Response(JSON.stringify({ version: "0.144.1" }), { status: 200 });
|
||||
}
|
||||
if (url.startsWith("https://raw.githubusercontent.com/openai/codex/")) {
|
||||
return new Response("PROMPT", { status: 200, headers: { etag: '"etag"' } });
|
||||
}
|
||||
@@ -666,6 +669,7 @@ describe("openai-codex Responses Lite and client metadata wire format", () => {
|
||||
|
||||
expect(result.stopReason).toBe("stop");
|
||||
expect(captured?.headers.get("x-openai-internal-codex-responses-lite")).toBe("true");
|
||||
expect(captured?.headers.get("version")).toBe("0.144.1");
|
||||
expect(captured?.body.reasoning).toEqual({ context: "all_turns" });
|
||||
expect(captured?.body.instructions).toBeUndefined();
|
||||
expect(captured?.body.tools).toBeUndefined();
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
### Removed
|
||||
|
||||
- Removed the generated GPT-5.6 pro-reasoning aliases (`gpt-5.6-{luna,sol,terra}-pro`) from the `openai-codex` subscription provider — pro reasoning is not offered on subscriptions; the `openai` API-key aliases remain
|
||||
### Fixed
|
||||
|
||||
- Fixed OpenAI Codex model discovery to include the Codex `version` header alongside the `client_version` query parameter.
|
||||
|
||||
## [16.4.0] - 2026-07-10
|
||||
|
||||
|
||||
@@ -86,12 +86,12 @@ export async function fetchCodexModels(options: CodexModelDiscoveryOptions): Pro
|
||||
const fetchFn = discoveryFetch(options.fetchFn);
|
||||
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
||||
const paths = normalizePaths(options.paths);
|
||||
const headers = buildCodexHeaders(options);
|
||||
const clientVersion = await resolveCodexClientVersion(
|
||||
options.clientVersion,
|
||||
options.registryFetchFn ?? fetchFn,
|
||||
options.signal,
|
||||
);
|
||||
const headers = buildCodexHeaders(options, clientVersion);
|
||||
|
||||
let sawSuccessfulResponse = false;
|
||||
for (const path of paths) {
|
||||
@@ -156,7 +156,7 @@ function buildModelsUrl(baseUrl: string, path: string, clientVersion: string | u
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function buildCodexHeaders(options: CodexModelDiscoveryOptions): Headers {
|
||||
function buildCodexHeaders(options: CodexModelDiscoveryOptions, clientVersion: string): Headers {
|
||||
const headers = new Headers(options.headers);
|
||||
headers.set("Authorization", `Bearer ${options.accessToken}`);
|
||||
if (options.accountId && options.accountId.trim().length > 0) {
|
||||
@@ -164,11 +164,12 @@ function buildCodexHeaders(options: CodexModelDiscoveryOptions): Headers {
|
||||
}
|
||||
headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES);
|
||||
headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
|
||||
headers.set(OPENAI_HEADERS.VERSION, clientVersion);
|
||||
headers.set("accept", "application/json");
|
||||
return headers;
|
||||
}
|
||||
|
||||
async function resolveCodexClientVersion(
|
||||
export async function resolveCodexClientVersion(
|
||||
clientVersion: string | undefined,
|
||||
fetchFn: FetchImpl,
|
||||
signal: AbortSignal | undefined,
|
||||
|
||||
@@ -8,6 +8,7 @@ export const OPENAI_HEADERS = {
|
||||
BETA: "OpenAI-Beta",
|
||||
ACCOUNT_ID: "chatgpt-account-id",
|
||||
ORIGINATOR: "originator",
|
||||
VERSION: "version",
|
||||
SESSION_ID: "session_id",
|
||||
CONVERSATION_ID: "conversation_id",
|
||||
SCOPED_SESSION_ID: "session-id",
|
||||
|
||||
@@ -11,9 +11,11 @@ import type { ModelSpec } from "@oh-my-pi/pi-catalog/types";
|
||||
|
||||
describe("Codex model discovery", () => {
|
||||
it("marks discovered models for provider-native V2 compaction", async () => {
|
||||
let capturedHeaders: Headers | undefined;
|
||||
const fetchFn: typeof fetch = Object.assign(
|
||||
async () =>
|
||||
new Response(
|
||||
async (_input: string | URL | Request, init?: RequestInit) => {
|
||||
capturedHeaders = new Headers(init?.headers);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
models: [
|
||||
{
|
||||
@@ -28,7 +30,8 @@ describe("Codex model discovery", () => {
|
||||
],
|
||||
}),
|
||||
{ headers: { etag: "models-v1" } },
|
||||
),
|
||||
);
|
||||
},
|
||||
{ preconnect() {} },
|
||||
);
|
||||
const result = await fetchCodexModels({
|
||||
@@ -38,6 +41,7 @@ describe("Codex model discovery", () => {
|
||||
fetchFn,
|
||||
});
|
||||
|
||||
expect(capturedHeaders?.get("version")).toBe("0.99.0");
|
||||
expect(result?.etag).toBe("models-v1");
|
||||
expect(result?.models).toHaveLength(1);
|
||||
expect(result?.models[0]).toMatchObject({
|
||||
|
||||
Reference in New Issue
Block a user