FN-6869: add Codex model pricing

Add explicit OpenAI Codex pricing so token analytics can estimate Codex run costs.

- Add openai-codex pricing entries for GPT-5 Codex variants and codex-mini-latest.
- Bump the pricing verification date and document why Codex keys must use the openai-codex provider.
- Cover Codex pricing and lookup behavior in model-pricing tests.
- Add a changeset for the published Fusion package.

Files changed:
 .changeset/fn-6869-codex-pricing.md               |  7 ++++
 packages/core/src/__tests__/model-pricing.test.ts | 37 ++++++++++++++++++-
 packages/core/src/model-pricing.ts                | 44 ++++++++++++++++++++++-
 3 files changed, 86 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-6869
Fusion-Task-Lineage: 4bf119db-0401-4b18-b389-4a762e665cfc
This commit is contained in:
gsxdsm
2026-06-21 12:43:44 -07:00
parent 32fad6dfba
commit f80a785b95
3 changed files with 86 additions and 2 deletions

View File

@@ -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.

View File

@@ -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");
});

View File

@@ -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<Record<string, ModelPricing>> = {
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:<modelId>` 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": {