Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions src/providers/openai-tiers-destination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type { OcxProviderConfig } from "../types";
import { openaiResponsesUrl } from "../adapters/openai-responses-url";

export const OPENAI_CODEX_PROVIDER_ID = "openai";
export const LEGACY_OPENAI_MULTI_PROVIDER_ID = "openai-multi";
export const OPENAI_API_PROVIDER_ID = "openai-apikey";
export const LEGACY_CHATGPT_PROVIDER_ID = "chatgpt";

export const CODEX_FORWARD_BASE_URL = "https://chatgpt.com/backend-api/codex";

function normalizedBaseUrl(value: string): string | undefined {
try {
const url = new URL(value.trim());
if (url.username || url.password || url.search || url.hash) return undefined;
const path = url.pathname.replace(/\/+$/, "");
return `${url.origin}${path}`;
} catch {
return undefined;
}
}

export function isCanonicalOpenAiForwardProvider(provider: OcxProviderConfig): boolean {
return provider.adapter === "openai-responses"
&& provider.authMode === "forward"
&& normalizedBaseUrl(provider.baseUrl) === CODEX_FORWARD_BASE_URL;
}

const OPENAI_API_ORIGIN = "https://api.openai.com";
const OPENAI_API_BASE_URL = `${OPENAI_API_ORIGIN}/v1`;
const OPENAI_API_RESPONSES_URL = `${OPENAI_API_BASE_URL}/responses`;

/**
* The Responses endpoint the adapter would actually POST key-auth traffic to, normalized.
*
* Mirrors the adapter's own construction (`src/adapters/openai-responses.ts`): a configured
* `responsesPath` is appended to the base verbatim, and only the default branch runs the
* `/v1/responses` suffix normalization. Classifying on the base URL alone would call
* `baseUrl: "https://api.openai.com"` with `responsesPath: "/other"` official even though that
* request never reaches the official Responses endpoint.
*/
function resolvedResponsesEndpoint(provider: OcxProviderConfig): string | undefined {
try {
const raw = provider.responsesPath === undefined
? openaiResponsesUrl(provider.baseUrl)
: `${provider.baseUrl.replace(/\/$/, "")}${provider.responsesPath}`;
return normalizedBaseUrl(raw);
} catch {
return undefined;
}
}

function isOfficialOpenAiResponsesDestination(provider: OcxProviderConfig): boolean {
// Exact normalized URL keeps lookalike/suffix hosts out of this set: `api.openai.com.evil.test`
// resolves to its own origin, never to the official one.
return resolvedResponsesEndpoint(provider) === OPENAI_API_RESPONSES_URL;
}

/**
* Whether this provider can serve `POST /responses/compact`. The canonical ChatGPT
* backend can, and so can the official OpenAI API — but an arbitrary gateway that
* merely speaks the Responses wire cannot, and calling it there fails compaction
* with an unhelpful error instead of falling back to a routed summary (#422).
*/
export function supportsNativeResponsesCompactEndpoint(
providerName: string,
provider: OcxProviderConfig,
): boolean {
if (isCanonicalOpenAiForwardProvider(provider)) return true;
return providerName === OPENAI_API_PROVIDER_ID
&& provider.adapter === "openai-responses"
&& normalizedBaseUrl(provider.baseUrl) === OPENAI_API_BASE_URL;
}

/**
* Whether this destination is an OpenAI-operated Responses backend — the canonical ChatGPT Codex
* surface or the official OpenAI API.
*
* Deliberately not keyed on `authMode === "forward"`: a noncanonical forward provider does not
* receive the caller's credentials (see the forward-header gate in the Responses adapter), so
* forward auth says nothing about which backend is on the other end.
*/
export function isOpenAiOperatedResponsesDestination(provider: OcxProviderConfig): boolean {
if (isCanonicalOpenAiForwardProvider(provider)) return true;
return provider.adapter === "openai-responses"
&& isOfficialOpenAiResponsesDestination(provider);
}

/**
* Whether this destination can decode a native (non-`ocx1:`) compaction blob.
*
* Only the backend that minted a blob can decode it. `authMode: "forward"` alone is not a signal:
* the adapter forwards caller credentials only to the canonical ChatGPT Codex surface, while a
* noncanonical forward provider receives no caller credentials and may point at any backend.
*
* Relay only to an OpenAI-operated destination or a destination whose operator explicitly opts in.
* Keyed by destination rather than provider id: a blob's issuer is the URL that produced it, not the
* local config key a replay travels under.
*/
export function destinationDecodesNativeCompactionBlob(provider: OcxProviderConfig): boolean {
return isOpenAiOperatedResponsesDestination(provider)
|| provider.decodesNativeCompactionBlobs === true;
}
101 changes: 2 additions & 99 deletions src/providers/openai-tiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@ import type { CodexAccountMode, OcxConfig, OcxProviderConfig, ProviderCostOverla
import { OPENAI_PROVIDER_TIER_VERSION } from "../types";
import { openaiResponsesUrl } from "../adapters/openai-responses-url";
import { MAX_COST4_RATE } from "../usage/expected-prices";
import { OPENAI_CODEX_PROVIDER_ID, LEGACY_OPENAI_MULTI_PROVIDER_ID, LEGACY_CHATGPT_PROVIDER_ID, CODEX_FORWARD_BASE_URL, isCanonicalOpenAiForwardProvider } from "./openai-tiers-destination";
export { OPENAI_CODEX_PROVIDER_ID, LEGACY_OPENAI_MULTI_PROVIDER_ID, OPENAI_API_PROVIDER_ID, LEGACY_CHATGPT_PROVIDER_ID, CODEX_FORWARD_BASE_URL, isCanonicalOpenAiForwardProvider, supportsNativeResponsesCompactEndpoint, isOpenAiOperatedResponsesDestination, destinationDecodesNativeCompactionBlob } from "./openai-tiers-destination";

export const OPENAI_CODEX_PROVIDER_ID = "openai";
export const LEGACY_OPENAI_MULTI_PROVIDER_ID = "openai-multi";
export const OPENAI_API_PROVIDER_ID = "openai-apikey";
export const LEGACY_CHATGPT_PROVIDER_ID = "chatgpt";

export const CODEX_FORWARD_BASE_URL = "https://chatgpt.com/backend-api/codex";
const LEGACY_OPENAI_MULTI_PREFIX = `${LEGACY_OPENAI_MULTI_PROVIDER_ID}/`;

function canonicalCodexForwardProvider(mode: CodexAccountMode): OcxProviderConfig {
Expand All @@ -20,99 +16,6 @@ function canonicalCodexForwardProvider(mode: CodexAccountMode): OcxProviderConfi
};
}

function normalizedBaseUrl(value: string): string | undefined {
try {
const url = new URL(value.trim());
if (url.username || url.password || url.search || url.hash) return undefined;
const path = url.pathname.replace(/\/+$/, "");
return `${url.origin}${path}`;
} catch {
return undefined;
}
}

export function isCanonicalOpenAiForwardProvider(provider: OcxProviderConfig): boolean {
return provider.adapter === "openai-responses"
&& provider.authMode === "forward"
&& normalizedBaseUrl(provider.baseUrl) === CODEX_FORWARD_BASE_URL;
}

const OPENAI_API_ORIGIN = "https://api.openai.com";
const OPENAI_API_BASE_URL = `${OPENAI_API_ORIGIN}/v1`;
const OPENAI_API_RESPONSES_URL = `${OPENAI_API_BASE_URL}/responses`;

/**
* The Responses endpoint the adapter would actually POST key-auth traffic to, normalized.
*
* Mirrors the adapter's own construction (`src/adapters/openai-responses.ts`): a configured
* `responsesPath` is appended to the base verbatim, and only the default branch runs the
* `/v1/responses` suffix normalization. Classifying on the base URL alone would call
* `baseUrl: "https://api.openai.com"` with `responsesPath: "/other"` official even though that
* request never reaches the official Responses endpoint.
*/
function resolvedResponsesEndpoint(provider: OcxProviderConfig): string | undefined {
try {
const raw = provider.responsesPath === undefined
? openaiResponsesUrl(provider.baseUrl)
: `${provider.baseUrl.replace(/\/$/, "")}${provider.responsesPath}`;
return normalizedBaseUrl(raw);
} catch {
return undefined;
}
}

function isOfficialOpenAiResponsesDestination(provider: OcxProviderConfig): boolean {
// Exact normalized URL keeps lookalike/suffix hosts out of this set: `api.openai.com.evil.test`
// resolves to its own origin, never to the official one.
return resolvedResponsesEndpoint(provider) === OPENAI_API_RESPONSES_URL;
}

/**
* Whether this provider can serve `POST /responses/compact`. The canonical ChatGPT
* backend can, and so can the official OpenAI API — but an arbitrary gateway that
* merely speaks the Responses wire cannot, and calling it there fails compaction
* with an unhelpful error instead of falling back to a routed summary (#422).
*/
export function supportsNativeResponsesCompactEndpoint(
providerName: string,
provider: OcxProviderConfig,
): boolean {
if (isCanonicalOpenAiForwardProvider(provider)) return true;
return providerName === OPENAI_API_PROVIDER_ID
&& provider.adapter === "openai-responses"
&& normalizedBaseUrl(provider.baseUrl) === OPENAI_API_BASE_URL;
}

/**
* Whether this destination is an OpenAI-operated Responses backend — the canonical ChatGPT Codex
* surface or the official OpenAI API.
*
* Deliberately not keyed on `authMode === "forward"`: a noncanonical forward provider does not
* receive the caller's credentials (see the forward-header gate in the Responses adapter), so
* forward auth says nothing about which backend is on the other end.
*/
export function isOpenAiOperatedResponsesDestination(provider: OcxProviderConfig): boolean {
if (isCanonicalOpenAiForwardProvider(provider)) return true;
return provider.adapter === "openai-responses"
&& isOfficialOpenAiResponsesDestination(provider);
}

/**
* Whether this destination can decode a native (non-`ocx1:`) compaction blob.
*
* Only the backend that minted a blob can decode it. `authMode: "forward"` alone is not a signal:
* the adapter forwards caller credentials only to the canonical ChatGPT Codex surface, while a
* noncanonical forward provider receives no caller credentials and may point at any backend.
*
* Relay only to an OpenAI-operated destination or a destination whose operator explicitly opts in.
* Keyed by destination rather than provider id: a blob's issuer is the URL that produced it, not the
* local config key a replay travels under.
*/
export function destinationDecodesNativeCompactionBlob(provider: OcxProviderConfig): boolean {
return isOpenAiOperatedResponsesDestination(provider)
|| provider.decodesNativeCompactionBlobs === true;
}

export interface OpenAiTierMigrationProjection {
config: OcxConfig;
changed: boolean;
Expand Down
13 changes: 13 additions & 0 deletions tests/adapters/openai/openai-provider-option.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { repoPath } from "../../helpers/repo-root";
import {
isCanonicalOpenAiForwardProvider as destinationIsCanonicalOpenAiForwardProvider,
OPENAI_CODEX_PROVIDER_ID as DESTINATION_OPENAI_CODEX_PROVIDER_ID,
} from "../../../src/providers/openai-tiers-destination";
import { getDefaultConfig } from "../../../src/config";
import { deriveInitProviders, deriveProviderPresets, listRegistryEntries, providerConfigSeed } from "../../../src/providers/derive";
import { getProviderRegistryEntry, providerCodexAccountMode } from "../../../src/providers/registry";
Expand Down Expand Up @@ -100,3 +106,10 @@ describe("OpenAI single-provider option foundation", () => {
expect(getDefaultConfig().providers.openai).toMatchObject({ codexAccountMode: "pool" });
});
});

test("destination leaf preserves facade bindings without importing the facade", () => {
expect(destinationIsCanonicalOpenAiForwardProvider).toBe(isCanonicalOpenAiForwardProvider);
expect(DESTINATION_OPENAI_CODEX_PROVIDER_ID).toBe(OPENAI_CODEX_PROVIDER_ID);
const source = readFileSync(repoPath("src/providers/openai-tiers-destination.ts"), "utf8");
expect(source).not.toMatch(/(?:from\s*|import\s*(?:\(\s*)?)["']\.\/openai-tiers(?:\.ts)?["']/);
});
Loading