FN-7757: add missing openai-codex model pricing entries to fix empty estimated costs
Fixes the dashboard showing empty/dash estimated costs for runs on newer OpenAI Codex models, whose pricing was missing from the static model-pricing table. - Add pricing entries for gpt-5.3-codex-spark, gpt-5.4, gpt-5.4-mini, and gpt-5.5 under the openai-codex provider, sourced from the pinned pi-ai 0.80.5 model catalog. - Correct the previously-guessed gpt-5.6-luna/sol/terra rates to match their actual distinct published rates instead of all sharing the gpt-5.3-codex rate. - Update/add tests across model-pricing, team-analytics, token-analytics, and workflow-analytics to cover the new/corrected pricing entries. - Add dashboard tests (TaskSummaryTab prior attempts, CommandCenter mobile scroll, TokensArea, task-planner chat metrics) verifying costs render correctly instead of falling back to unavailable. - Add a patch changeset documenting the estimated-cost fix. Files changed: $(cat /tmp/diffstat.txt) Fusion-Task-Id: FN-7757 Fusion-Task-Lineage: e0f2e9d5-e922-4878-82d0-40c7d9899fdb Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7757-estimated-cost.md
Normal file
7
.changeset/fn-7757-estimated-cost.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Fix empty estimated cost on the dashboard so priced runs show a dollar amount.
|
||||||
|
category: fix
|
||||||
|
dev: Root-caused model-identity → pricing-key resolution; cost stays read-time derived in costFor/token-analytics.
|
||||||
@@ -65,17 +65,25 @@ describe("model-pricing", () => {
|
|||||||
expect(result.usd).toBeCloseTo(3.25, 2);
|
expect(result.usd).toBeCloseTo(3.25, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prices OpenAI Codex GPT-5.6 codenamed models instead of reporting unavailable", () => {
|
it("prices current OpenAI Codex catalog models instead of reporting unavailable", () => {
|
||||||
// FN-7742: gpt-5.6-luna / gpt-5.6-sol / gpt-5.6-terra mirror the gpt-5.3-codex rate
|
const cases = [
|
||||||
// ($1.25/1M input, $10/1M output). 1,000,000 input + 200,000 output = 1.25 + 2.00 = 3.25
|
["gpt-5.3-codex-spark", 4.55],
|
||||||
for (const model of ["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra"]) {
|
["gpt-5.4", 5.5],
|
||||||
|
["gpt-5.4-mini", 1.65],
|
||||||
|
["gpt-5.5", 11],
|
||||||
|
["gpt-5.6-luna", 2.2],
|
||||||
|
["gpt-5.6-sol", 11],
|
||||||
|
["gpt-5.6-terra", 5.5],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
for (const [model, expectedUsd] of cases) {
|
||||||
const result = costFor(
|
const result = costFor(
|
||||||
{ ...ZERO, inputTokens: 1_000_000, outputTokens: 200_000 },
|
{ ...ZERO, inputTokens: 1_000_000, outputTokens: 200_000 },
|
||||||
{ provider: "openai-codex", model },
|
{ provider: "openai-codex", model },
|
||||||
);
|
);
|
||||||
expect(result.unavailable).toBe(false);
|
expect(result.unavailable).toBe(false);
|
||||||
expect(result.usd).not.toBeNull();
|
expect(result.usd).not.toBeNull();
|
||||||
expect(result.usd).toBeCloseTo(3.25, 2);
|
expect(result.usd).toBeCloseTo(expectedUsd, 2);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ describe("team-analytics", () => {
|
|||||||
outputTokens: 1_000_000,
|
outputTokens: 1_000_000,
|
||||||
totalTokens: 2_000_000,
|
totalTokens: 2_000_000,
|
||||||
tokenUsageLastUsedAt: "2026-03-02T00:00:00.000Z",
|
tokenUsageLastUsedAt: "2026-03-02T00:00:00.000Z",
|
||||||
modelProvider: "openai",
|
modelProvider: "openai-codex",
|
||||||
modelId: "gpt-4o",
|
modelId: "gpt-5.5",
|
||||||
});
|
});
|
||||||
insertTask(db, {
|
insertTask(db, {
|
||||||
id: "a-done",
|
id: "a-done",
|
||||||
@@ -143,7 +143,7 @@ describe("team-analytics", () => {
|
|||||||
tasksInReview: 0,
|
tasksInReview: 0,
|
||||||
});
|
});
|
||||||
expect(byAgent.get("agent-a")?.tokens.totalTokens).toBe(2_000_000);
|
expect(byAgent.get("agent-a")?.tokens.totalTokens).toBe(2_000_000);
|
||||||
expect(byAgent.get("agent-a")?.cost).toEqual({ usd: 12.5, unavailable: false, stale: false });
|
expect(byAgent.get("agent-a")?.cost).toEqual({ usd: 35, unavailable: false, stale: false });
|
||||||
expect(byAgent.get("agent-b")).toMatchObject({
|
expect(byAgent.get("agent-b")).toMatchObject({
|
||||||
agentName: "Beta",
|
agentName: "Beta",
|
||||||
role: "reviewer",
|
role: "reviewer",
|
||||||
|
|||||||
@@ -660,6 +660,47 @@ describe("token-analytics", () => {
|
|||||||
expect(result.series?.[0].cost).toEqual({ usd: 12.5, unavailable: true, stale: false });
|
expect(result.series?.[0].cost).toEqual({ usd: 12.5, unavailable: true, stale: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prices current OpenAI Codex runtime identities across token analytics surfaces", () => {
|
||||||
|
const usage = { inputTokens: 1_000_000, outputTokens: 200_000, cachedTokens: 500_000, cacheWriteTokens: 100_000 };
|
||||||
|
const expected = costFor(usage, { provider: "openai-codex", model: "gpt-5.5" });
|
||||||
|
expect(expected).toEqual({ usd: 11.25, unavailable: false, stale: false });
|
||||||
|
|
||||||
|
insertTask(db, {
|
||||||
|
id: "codex-current",
|
||||||
|
inputTokens: usage.inputTokens,
|
||||||
|
outputTokens: usage.outputTokens,
|
||||||
|
cachedTokens: usage.cachedTokens,
|
||||||
|
cacheWriteTokens: usage.cacheWriteTokens,
|
||||||
|
totalTokens: 1_800_000,
|
||||||
|
lastUsedAt: "2026-03-01T00:00:00.000Z",
|
||||||
|
tokenUsageModelProvider: "openai-codex",
|
||||||
|
tokenUsageModelId: "gpt-5.5",
|
||||||
|
tokenUsagePerModel: [
|
||||||
|
{
|
||||||
|
modelProvider: "openai-codex",
|
||||||
|
modelId: "gpt-5.5",
|
||||||
|
...usage,
|
||||||
|
totalTokens: 1_800_000,
|
||||||
|
lastUsedAt: "2026-03-01T00:00:00.000Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
modelProvider: "openai",
|
||||||
|
modelId: "gpt-4o-mini",
|
||||||
|
nodeId: "node-codex",
|
||||||
|
agentId: "agent-codex",
|
||||||
|
});
|
||||||
|
|
||||||
|
const byModel = aggregateTokenAnalytics(db, { groupBy: "model" });
|
||||||
|
expect(byModel.cost).toEqual(expected);
|
||||||
|
expect(byModel.groups.find((group) => group.key === "gpt-5.5")?.cost).toEqual(expected);
|
||||||
|
|
||||||
|
const byProvider = aggregateTokenAnalytics(db, { groupBy: "provider" });
|
||||||
|
expect(byProvider.groups.find((group) => group.key === "openai-codex")?.cost).toEqual(expected);
|
||||||
|
|
||||||
|
const byDay = aggregateTokenAnalytics(db, { granularity: "day" });
|
||||||
|
expect(byDay.series?.[0].cost).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
it("prices resolved-model token usage costs from the usage snapshot across analytics surfaces", () => {
|
it("prices resolved-model token usage costs from the usage snapshot across analytics surfaces", () => {
|
||||||
const usage = { inputTokens: 1_000_000, outputTokens: 1_000_000, cachedTokens: 0, cacheWriteTokens: 0 };
|
const usage = { inputTokens: 1_000_000, outputTokens: 1_000_000, cachedTokens: 0, cacheWriteTokens: 0 };
|
||||||
const expected = costFor(usage, { provider: "openai", model: "gpt-4o" });
|
const expected = costFor(usage, { provider: "openai", model: "gpt-4o" });
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ describe("workflow-analytics", () => {
|
|||||||
outputTokens: 1_000_000,
|
outputTokens: 1_000_000,
|
||||||
totalTokens: 2_000_000,
|
totalTokens: 2_000_000,
|
||||||
tokenUsageLastUsedAt: "2026-03-02T00:00:00.000Z",
|
tokenUsageLastUsedAt: "2026-03-02T00:00:00.000Z",
|
||||||
modelProvider: "openai",
|
modelProvider: "openai-codex",
|
||||||
modelId: "gpt-4o",
|
modelId: "gpt-5.5",
|
||||||
modifiedFiles: ["src/custom.ts", "src/shared.ts"],
|
modifiedFiles: ["src/custom.ts", "src/shared.ts"],
|
||||||
updatedAt: "2026-03-02T00:00:00.000Z",
|
updatedAt: "2026-03-02T00:00:00.000Z",
|
||||||
});
|
});
|
||||||
@@ -149,7 +149,7 @@ describe("workflow-analytics", () => {
|
|||||||
tasksInReview: 0,
|
tasksInReview: 0,
|
||||||
});
|
});
|
||||||
expect(byWorkflow.get("WF-custom")?.tokens.totalTokens).toBe(2_000_000);
|
expect(byWorkflow.get("WF-custom")?.tokens.totalTokens).toBe(2_000_000);
|
||||||
expect(byWorkflow.get("WF-custom")?.cost).toEqual({ usd: 12.5, unavailable: false, stale: false });
|
expect(byWorkflow.get("WF-custom")?.cost).toEqual({ usd: 35, unavailable: false, stale: false });
|
||||||
expect(byWorkflow.get("builtin:quick-fix")).toMatchObject({
|
expect(byWorkflow.get("builtin:quick-fix")).toMatchObject({
|
||||||
workflowName: "Quick fix",
|
workflowName: "Quick fix",
|
||||||
isBuiltin: true,
|
isBuiltin: true,
|
||||||
|
|||||||
@@ -294,36 +294,56 @@ export const MODEL_PRICING: Readonly<Record<string, ModelPricing>> = {
|
|||||||
source: "openai.com/api/pricing",
|
source: "openai.com/api/pricing",
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* FNXC:CommandCenter 2026-07-09-00:00:
|
* FNXC:CommandCenter 2026-07-09-21:30:
|
||||||
* FN-7742 bumped the bundled pi SDK to 0.80.5 and inspected its generated model catalogs
|
* FN-7757 found that pi-ai 0.80.5 now persists OpenAI Codex model ids from the generated `openai-codex.models.js` catalog (`gpt-5.3-codex-spark`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, and codenamed GPT-5.6 variants). The old static table covered only earlier `gpt-5.x-codex` ids, so lookupPricing returned unavailable and every dashboard cost surface rendered `—`. Keep this block aligned with the pinned runtime catalog rates while preserving unknown-model `—` semantics.
|
||||||
* directly (node_modules/@earendil-works/pi-ai/dist/providers/{openai,openai-codex}.models.js)
|
|
||||||
* rather than guessing. There is no `gpt-5.6-codex` id in either catalog — OpenAI dropped the
|
|
||||||
* separate `-codex`-suffixed tier naming starting at the 5.4 generation. GPT-5.6 instead ships
|
|
||||||
* as three codenamed variants (`gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`) that are present
|
|
||||||
* under BOTH the `openai` and `openai-codex` provider catalogs, so they are the faithful
|
|
||||||
* equivalent of the prior `gpt-5.x-codex` entries. Rates mirror the existing gpt-5.3-codex rate
|
|
||||||
* (no confirmed different published OpenAI rate for these codenamed models) so Command Center
|
|
||||||
* token cost does not show `unavailable` for pi-sourced GPT-5.6 codex runs.
|
|
||||||
*/
|
*/
|
||||||
|
"openai-codex:gpt-5.3-codex-spark": {
|
||||||
|
inputPer1M: 1.75,
|
||||||
|
outputPer1M: 14,
|
||||||
|
cacheReadPer1M: 0.175,
|
||||||
|
cacheWritePer1M: 0,
|
||||||
|
source: "openai.com/api/pricing",
|
||||||
|
},
|
||||||
|
"openai-codex:gpt-5.4": {
|
||||||
|
inputPer1M: 2.5,
|
||||||
|
outputPer1M: 15,
|
||||||
|
cacheReadPer1M: 0.25,
|
||||||
|
cacheWritePer1M: 0,
|
||||||
|
source: "openai.com/api/pricing",
|
||||||
|
},
|
||||||
|
"openai-codex:gpt-5.4-mini": {
|
||||||
|
inputPer1M: 0.75,
|
||||||
|
outputPer1M: 4.5,
|
||||||
|
cacheReadPer1M: 0.075,
|
||||||
|
cacheWritePer1M: 0,
|
||||||
|
source: "openai.com/api/pricing",
|
||||||
|
},
|
||||||
|
"openai-codex:gpt-5.5": {
|
||||||
|
inputPer1M: 5,
|
||||||
|
outputPer1M: 30,
|
||||||
|
cacheReadPer1M: 0.5,
|
||||||
|
cacheWritePer1M: 0,
|
||||||
|
source: "openai.com/api/pricing",
|
||||||
|
},
|
||||||
"openai-codex:gpt-5.6-luna": {
|
"openai-codex:gpt-5.6-luna": {
|
||||||
inputPer1M: 1.25,
|
inputPer1M: 1,
|
||||||
outputPer1M: 10,
|
outputPer1M: 6,
|
||||||
cacheReadPer1M: 0.125,
|
cacheReadPer1M: 0.1,
|
||||||
cacheWritePer1M: 1.25,
|
cacheWritePer1M: 0,
|
||||||
source: "openai.com/api/pricing",
|
source: "openai.com/api/pricing",
|
||||||
},
|
},
|
||||||
"openai-codex:gpt-5.6-sol": {
|
"openai-codex:gpt-5.6-sol": {
|
||||||
inputPer1M: 1.25,
|
inputPer1M: 5,
|
||||||
outputPer1M: 10,
|
outputPer1M: 30,
|
||||||
cacheReadPer1M: 0.125,
|
cacheReadPer1M: 0.5,
|
||||||
cacheWritePer1M: 1.25,
|
cacheWritePer1M: 0,
|
||||||
source: "openai.com/api/pricing",
|
source: "openai.com/api/pricing",
|
||||||
},
|
},
|
||||||
"openai-codex:gpt-5.6-terra": {
|
"openai-codex:gpt-5.6-terra": {
|
||||||
inputPer1M: 1.25,
|
inputPer1M: 2.5,
|
||||||
outputPer1M: 10,
|
outputPer1M: 15,
|
||||||
cacheReadPer1M: 0.125,
|
cacheReadPer1M: 0.25,
|
||||||
cacheWritePer1M: 1.25,
|
cacheWritePer1M: 0,
|
||||||
source: "openai.com/api/pricing",
|
source: "openai.com/api/pricing",
|
||||||
},
|
},
|
||||||
"openai-codex:codex-mini-latest": {
|
"openai-codex:codex-mini-latest": {
|
||||||
|
|||||||
@@ -13,6 +13,42 @@ function doneTaskWithResults(workflowStepResults: unknown[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("TaskSummaryTab prior-attempts history (FN-7727)", () => {
|
describe("TaskSummaryTab prior-attempts history (FN-7727)", () => {
|
||||||
|
it("renders current OpenAI Codex token costs as dollars instead of the unavailable sentinel", () => {
|
||||||
|
render(
|
||||||
|
<TaskSummaryTab
|
||||||
|
task={makeTask({
|
||||||
|
column: "done",
|
||||||
|
tokenUsage: {
|
||||||
|
inputTokens: 1_000_000,
|
||||||
|
outputTokens: 200_000,
|
||||||
|
cachedTokens: 500_000,
|
||||||
|
cacheWriteTokens: 100_000,
|
||||||
|
totalTokens: 1_800_000,
|
||||||
|
firstUsedAt: "2026-07-01T10:00:00.000Z",
|
||||||
|
lastUsedAt: "2026-07-01T10:30:00.000Z",
|
||||||
|
perModel: [
|
||||||
|
{
|
||||||
|
modelProvider: "openai-codex",
|
||||||
|
modelId: "gpt-5.5",
|
||||||
|
inputTokens: 1_000_000,
|
||||||
|
outputTokens: 200_000,
|
||||||
|
cachedTokens: 500_000,
|
||||||
|
cacheWriteTokens: 100_000,
|
||||||
|
totalTokens: 1_800_000,
|
||||||
|
firstUsedAt: "2026-07-01T10:00:00.000Z",
|
||||||
|
lastUsedAt: "2026-07-01T10:30:00.000Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const section = screen.getByTestId("task-summary-token-cost-section");
|
||||||
|
expect(section).toHaveTextContent("$11.25");
|
||||||
|
expect(section).not.toHaveTextContent("—");
|
||||||
|
});
|
||||||
|
|
||||||
it("renders a collapsed prior-attempts affordance for a step with history", () => {
|
it("renders a collapsed prior-attempts affordance for a step with history", () => {
|
||||||
render(
|
render(
|
||||||
<TaskSummaryTab
|
<TaskSummaryTab
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ function populatedTokenFixture() {
|
|||||||
cost: { usd: 9, unavailable: false, stale: false },
|
cost: { usd: 9, unavailable: false, stale: false },
|
||||||
groups: [
|
groups: [
|
||||||
{
|
{
|
||||||
key: "gpt-4o",
|
key: "gpt-5.5",
|
||||||
inputTokens: 600,
|
inputTokens: 600,
|
||||||
outputTokens: 300,
|
outputTokens: 300,
|
||||||
cachedTokens: 100,
|
cachedTokens: 100,
|
||||||
@@ -413,6 +413,8 @@ describe("CommandCenter mobile scroll regression (FN-6595)", () => {
|
|||||||
render(<CommandCenter />);
|
render(<CommandCenter />);
|
||||||
|
|
||||||
await screen.findByTestId("command-center-overview-charts");
|
await screen.findByTestId("command-center-overview-charts");
|
||||||
|
expect(screen.getByTestId("command-center-stat-tokens")).toHaveTextContent("$9.00");
|
||||||
|
expect(screen.getByTestId("command-center-stat-tokens")).not.toHaveTextContent("—");
|
||||||
expect(screen.getByTestId("command-center-overview-chart-tokens")).toBeTruthy();
|
expect(screen.getByTestId("command-center-overview-chart-tokens")).toBeTruthy();
|
||||||
expect(screen.getByTestId("cc-overview-pie")).toBeTruthy();
|
expect(screen.getByTestId("cc-overview-pie")).toBeTruthy();
|
||||||
expect(screen.getByTestId("cc-overview-line")).toBeTruthy();
|
expect(screen.getByTestId("cc-overview-line")).toBeTruthy();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||||
import { render, screen, within } from "@testing-library/react";
|
import { render, screen, within } from "@testing-library/react";
|
||||||
|
import { costFor } from "@fusion/core";
|
||||||
import { TokensArea } from "../TokensArea";
|
import { TokensArea } from "../TokensArea";
|
||||||
import type { DateRange } from "../../DateRangePicker";
|
import type { DateRange } from "../../DateRangePicker";
|
||||||
|
|
||||||
@@ -179,6 +180,28 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("TokensArea provider model icons", () => {
|
describe("TokensArea provider model icons", () => {
|
||||||
|
it("renders current OpenAI Codex priced costs as dollars instead of the unavailable sentinel", async () => {
|
||||||
|
const usage = { inputTokens: 1_000_000, outputTokens: 200_000, cachedTokens: 500_000, cacheWriteTokens: 100_000 };
|
||||||
|
const cost = costFor(usage, { provider: "openai-codex", model: "gpt-5.5" });
|
||||||
|
expect(cost).toEqual({ usd: 11.25, unavailable: false, stale: false });
|
||||||
|
|
||||||
|
apiMock.mockResolvedValue({
|
||||||
|
from: "2026-06-08",
|
||||||
|
to: null,
|
||||||
|
groupBy: "model",
|
||||||
|
totals: { ...usage, totalTokens: 1_800_000, nTasks: 1 },
|
||||||
|
cost,
|
||||||
|
series: [{ bucket: "2026-06-18", ...usage, totalTokens: 1_800_000, nTasks: 1, cost }],
|
||||||
|
groups: [{ key: "gpt-5.5", ...usage, totalTokens: 1_800_000, nTasks: 1, cost }],
|
||||||
|
});
|
||||||
|
render(<TokensArea range={range7d} />);
|
||||||
|
|
||||||
|
await screen.findByTestId("cc-area-tokens");
|
||||||
|
expect(screen.getByTestId("cc-tokens-cost")).toHaveTextContent("$11.25");
|
||||||
|
expect(screen.getByTestId("cc-tokens-cost")).not.toHaveTextContent("—");
|
||||||
|
expect(screen.getByTestId("cc-tokens-row-gpt-5.5")).toHaveTextContent("$11.25");
|
||||||
|
});
|
||||||
|
|
||||||
it("renders inferred provider icons in the per-model table and Tokens by model bars", async () => {
|
it("renders inferred provider icons in the per-model table and Tokens by model bars", async () => {
|
||||||
render(<TokensArea range={range7d} />);
|
render(<TokensArea range={range7d} />);
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,27 @@ describe("formatTaskPlannerChatMetrics", () => {
|
|||||||
expect(result.summaryText).toContain("estimated cost $0.0085");
|
expect(result.summaryText).toContain("estimated cost $0.0085");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prices current OpenAI Codex runtime identities in task-planner chat metrics", () => {
|
||||||
|
const result = formatTaskPlannerChatMetrics(makeTask({
|
||||||
|
tokenUsage: {
|
||||||
|
inputTokens: 1_000_000,
|
||||||
|
outputTokens: 200_000,
|
||||||
|
cachedTokens: 500_000,
|
||||||
|
cacheWriteTokens: 100_000,
|
||||||
|
totalTokens: 1_800_000,
|
||||||
|
firstUsedAt: "2026-07-01T10:00:00.000Z",
|
||||||
|
lastUsedAt: "2026-07-01T10:30:00.000Z",
|
||||||
|
modelProvider: "openai-codex",
|
||||||
|
modelId: "gpt-5.5",
|
||||||
|
},
|
||||||
|
}), { nowMs: Date.parse("2026-07-01T10:31:00.000Z") });
|
||||||
|
|
||||||
|
expect(result.metrics.tokens.cost).toEqual({ usd: 11.25, costUnavailable: false, pricingStale: false });
|
||||||
|
expect(result.metrics.tokens.perModel[0].cost).toEqual({ usd: 11.25, costUnavailable: false, pricingStale: false });
|
||||||
|
expect(result.summaryText).toContain("estimated cost $11.2500");
|
||||||
|
expect(result.summaryText).not.toContain("cost unavailable");
|
||||||
|
});
|
||||||
|
|
||||||
it("marks unpriced and stale model costs unavailable instead of reporting zero", () => {
|
it("marks unpriced and stale model costs unavailable instead of reporting zero", () => {
|
||||||
const result = formatTaskPlannerChatMetrics(makeTask({
|
const result = formatTaskPlannerChatMetrics(makeTask({
|
||||||
tokenUsage: {
|
tokenUsage: {
|
||||||
|
|||||||
Reference in New Issue
Block a user