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:
7
.changeset/fn-6869-codex-pricing.md
Normal file
7
.changeset/fn-6869-codex-pricing.md
Normal 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.
|
||||||
@@ -34,6 +34,34 @@ describe("model-pricing", () => {
|
|||||||
expect(result.usd).toBeCloseTo(10.0, 2);
|
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)", () => {
|
it("returns unavailable + null usd for an unknown model (never guesses)", () => {
|
||||||
const result = costFor(
|
const result = costFor(
|
||||||
{ ...ZERO, inputTokens: 1_000_000 },
|
{ ...ZERO, inputTokens: 1_000_000 },
|
||||||
@@ -134,6 +162,12 @@ describe("model-pricing", () => {
|
|||||||
).toBe(MODEL_PRICING["openai:gpt-4o"]);
|
).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", () => {
|
it("falls back to a bare model id when provider is unset", () => {
|
||||||
expect(lookupPricing({ model: "gemini-2.5-pro" })).toBe(
|
expect(lookupPricing({ model: "gemini-2.5-pro" })).toBe(
|
||||||
MODEL_PRICING["google:gemini-2.5-pro"],
|
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(
|
const providers = new Set(
|
||||||
Object.keys(MODEL_PRICING).map((k) => k.split(":")[0]),
|
Object.keys(MODEL_PRICING).map((k) => k.split(":")[0]),
|
||||||
);
|
);
|
||||||
expect(providers).toContain("anthropic");
|
expect(providers).toContain("anthropic");
|
||||||
expect(providers).toContain("openai");
|
expect(providers).toContain("openai");
|
||||||
|
expect(providers).toContain("openai-codex");
|
||||||
expect(providers).toContain("google");
|
expect(providers).toContain("google");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
* The date the rates in {@link MODEL_PRICING} were last verified, ISO-8601.
|
* 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".
|
* 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
|
* 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",
|
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 ───────────────────────────────────────────────────
|
// ── Google Gemini ───────────────────────────────────────────────────
|
||||||
// No distinct cache-write token charge → cacheWrite = input rate.
|
// No distinct cache-write token charge → cacheWrite = input rate.
|
||||||
"google:gemini-2.5-pro": {
|
"google:gemini-2.5-pro": {
|
||||||
|
|||||||
Reference in New Issue
Block a user