diff --git a/.changeset/fn-6869-codex-pricing.md b/.changeset/fn-6869-codex-pricing.md new file mode 100644 index 0000000000..b15dde922c --- /dev/null +++ b/.changeset/fn-6869-codex-pricing.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +Add pricing entries for OpenAI Codex models used through the `openai-codex` provider, so Command Center token analytics can estimate costs for Codex runs instead of showing them as unavailable. + +This is marked minor because it expands the set of priced models surfaced by the published CLI/dashboard without changing existing pricing behavior. diff --git a/packages/core/src/__tests__/model-pricing.test.ts b/packages/core/src/__tests__/model-pricing.test.ts index ea116b31fe..4d6a4b3fc9 100644 --- a/packages/core/src/__tests__/model-pricing.test.ts +++ b/packages/core/src/__tests__/model-pricing.test.ts @@ -34,6 +34,34 @@ describe("model-pricing", () => { expect(result.usd).toBeCloseTo(10.0, 2); }); + it("prices OpenAI Codex GPT-5 models instead of reporting unavailable", () => { + // gpt-5-codex: input $1.25/1M, output $10/1M. + // 1,000,000 input + 200,000 output = 1.25 + 2.00 = 3.25 + const result = costFor( + { ...ZERO, inputTokens: 1_000_000, outputTokens: 200_000 }, + { provider: "openai-codex", model: "gpt-5-codex" }, + ); + expect(result.unavailable).toBe(false); + expect(result.usd).not.toBeNull(); + expect(result.usd).toBeCloseTo(3.25, 2); + }); + + it("prices Codex mini latest instead of reporting unavailable", () => { + // codex-mini-latest: input $1.50/1M, output $6/1M, cached input $0.375/1M. + const result = costFor( + { + ...ZERO, + inputTokens: 1_000_000, + outputTokens: 500_000, + cachedTokens: 1_000_000, + }, + { provider: "openai-codex", model: "codex-mini-latest" }, + ); + expect(result.unavailable).toBe(false); + expect(result.usd).not.toBeNull(); + expect(result.usd).toBeCloseTo(4.875, 3); + }); + it("returns unavailable + null usd for an unknown model (never guesses)", () => { const result = costFor( { ...ZERO, inputTokens: 1_000_000 }, @@ -134,6 +162,12 @@ describe("model-pricing", () => { ).toBe(MODEL_PRICING["openai:gpt-4o"]); }); + it("resolves OpenAI Codex models by explicit provider:model keys", () => { + expect( + lookupPricing({ provider: " OpenAI-Codex ", model: " GPT-5-Codex " }), + ).toBe(MODEL_PRICING["openai-codex:gpt-5-codex"]); + }); + it("falls back to a bare model id when provider is unset", () => { expect(lookupPricing({ model: "gemini-2.5-pro" })).toBe( MODEL_PRICING["google:gemini-2.5-pro"], @@ -147,12 +181,13 @@ describe("model-pricing", () => { }); }); - it("seeds Anthropic, OpenAI, and Google providers", () => { + it("seeds Anthropic, OpenAI Codex, OpenAI, and Google providers", () => { const providers = new Set( Object.keys(MODEL_PRICING).map((k) => k.split(":")[0]), ); expect(providers).toContain("anthropic"); expect(providers).toContain("openai"); + expect(providers).toContain("openai-codex"); expect(providers).toContain("google"); }); diff --git a/packages/core/src/model-pricing.ts b/packages/core/src/model-pricing.ts index 6f81eeafa7..76a2b10fa0 100644 --- a/packages/core/src/model-pricing.ts +++ b/packages/core/src/model-pricing.ts @@ -25,7 +25,7 @@ * The date the rates in {@link MODEL_PRICING} were last verified, ISO-8601. * Bump this whenever you edit a rate. Surfaced in the UI as "prices as of". */ -export const pricingAsOf = "2026-06-15"; +export const pricingAsOf = "2026-06-21"; /** * Pricing entries older than this (relative to a caller-supplied `now`) are @@ -233,6 +233,48 @@ export const MODEL_PRICING: Readonly> = { source: "openai.com/api/pricing", }, + // ── OpenAI Codex ──────────────────────────────────────────────────── + // OpenAI has no separate cache-write charge → cacheWrite = input rate. + /* + * FNXC:CommandCenter 2026-06-21-12:14: + * Codex runs store the `openai-codex` provider, so pricing must be keyed as `openai-codex:` instead of relying on the OpenAI provider or bare-model fallback. Keep these entries explicit so Command Center token cost does not show `unavailable`; rates mirror OpenAI GPT-5 Codex pricing, and `pricingAsOf` must be bumped on every rate edit. + */ + "openai-codex:gpt-5-codex": { + inputPer1M: 1.25, + outputPer1M: 10, + cacheReadPer1M: 0.125, + cacheWritePer1M: 1.25, + source: "openai.com/api/pricing", + }, + "openai-codex:gpt-5.1-codex": { + inputPer1M: 1.25, + outputPer1M: 10, + cacheReadPer1M: 0.125, + cacheWritePer1M: 1.25, + source: "openai.com/api/pricing", + }, + "openai-codex:gpt-5.2-codex": { + inputPer1M: 1.25, + outputPer1M: 10, + cacheReadPer1M: 0.125, + cacheWritePer1M: 1.25, + source: "openai.com/api/pricing", + }, + "openai-codex:gpt-5.3-codex": { + inputPer1M: 1.25, + outputPer1M: 10, + cacheReadPer1M: 0.125, + cacheWritePer1M: 1.25, + source: "openai.com/api/pricing", + }, + "openai-codex:codex-mini-latest": { + inputPer1M: 1.5, + outputPer1M: 6, + cacheReadPer1M: 0.375, + cacheWritePer1M: 1.5, + source: "openai.com/api/pricing", + }, + // ── Google Gemini ─────────────────────────────────────────────────── // No distinct cache-write token charge → cacheWrite = input rate. "google:gemini-2.5-pro": {