fix(omp): avoid unsupported calculateCost import
Fix OMP installation by removing the unsupported calculateCost runtime import from pi-ai.
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Fix Oh My Pi extension validation by avoiding the missing `calculateCost` export from OMP's legacy `pi-ai` shim.
|
||||
|
||||
## 0.4.1 - 2026-06-16
|
||||
|
||||
- Use the explicit `$COMMANDCODE_API_KEY` provider registration syntax expected by newer pi versions, removing the startup deprecation warning while keeping legacy placeholder compatibility.
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
* Models are fetched from Command Code's Provider API at startup.
|
||||
*/
|
||||
|
||||
import { AssistantMessageEventStream, calculateCost } from "@earendil-works/pi-ai"
|
||||
import { AssistantMessageEventStream } from "@earendil-works/pi-ai"
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
|
||||
|
||||
import { COMMAND_CODE_CLI_VERSION, createStreamCommandCode, DEFAULT_API_BASE } from "./src/core.ts"
|
||||
import { calculateCommandCodeCost } from "./src/cost.ts"
|
||||
import { DEFAULT_MODELS_URL, fetchCommandCodeModels } from "./src/models.ts"
|
||||
import { getApiKey, login, refreshToken } from "./src/oauth.ts"
|
||||
|
||||
@@ -69,7 +70,7 @@ const MODEL_COSTS: Record<string, CommandCodeModelCost> = {
|
||||
|
||||
const streamCommandCode = createStreamCommandCode({
|
||||
createStream: () => new AssistantMessageEventStream(),
|
||||
calculateCost,
|
||||
calculateCost: calculateCommandCodeCost,
|
||||
apiBase: API_BASE,
|
||||
})
|
||||
|
||||
|
||||
+3
-2
@@ -28,7 +28,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run typecheck && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-pricing.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-stream.ts && tsx tests/test-retry.ts && node tests/test-pi-local.mjs && node tests/test-omp-compat.mjs",
|
||||
"test": "npm run typecheck && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-pricing.ts && tsx tests/test-cost.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-stream.ts && tsx tests/test-retry.ts && node tests/test-pi-local.mjs && node tests/test-omp-compat.mjs",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"format:check": "prettier --check '**/*.{ts,mjs,json,md}'",
|
||||
"format": "prettier --write '**/*.{ts,mjs,json,md}'",
|
||||
@@ -40,7 +40,8 @@
|
||||
"test:stream": "tsx tests/test-stream.ts",
|
||||
"test:retry": "tsx tests/test-retry.ts",
|
||||
"test:pi-local": "node tests/test-pi-local.mjs",
|
||||
"test:smoke": "node tests/test-smoke.mjs"
|
||||
"test:smoke": "node tests/test-smoke.mjs",
|
||||
"test:cost": "tsx tests/test-cost.ts"
|
||||
},
|
||||
"pi": {
|
||||
"extensions": [
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Local cost calculation for Command Code usage.
|
||||
*
|
||||
* Mirrors pi-ai's `calculateCost` arithmetic exactly. The provider ships its
|
||||
* own copy because Oh My Pi's legacy pi-ai shim does not export
|
||||
* `calculateCost`, which broke extension installation there (issue #24).
|
||||
* `tests/test-cost.ts` locks this implementation to the pi-ai original.
|
||||
*/
|
||||
|
||||
import type { ModelLike, Usage } from "./types.ts"
|
||||
|
||||
export function calculateCommandCodeCost(model: ModelLike, usage: Usage): void {
|
||||
usage.cost.input = (model.cost.input / 1_000_000) * usage.input
|
||||
usage.cost.output = (model.cost.output / 1_000_000) * usage.output
|
||||
usage.cost.cacheRead = (model.cost.cacheRead / 1_000_000) * usage.cacheRead
|
||||
usage.cost.cacheWrite = (model.cost.cacheWrite / 1_000_000) * usage.cacheWrite
|
||||
usage.cost.total =
|
||||
usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite
|
||||
}
|
||||
@@ -50,11 +50,19 @@ export interface AssistantMessageLike {
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export interface ModelCost {
|
||||
input: number
|
||||
output: number
|
||||
cacheRead: number
|
||||
cacheWrite: number
|
||||
}
|
||||
|
||||
export interface ModelLike {
|
||||
id: string
|
||||
api: unknown
|
||||
provider: string
|
||||
maxTokens: number
|
||||
cost: ModelCost
|
||||
}
|
||||
|
||||
export interface MessageLike {
|
||||
|
||||
@@ -79,6 +79,7 @@ export function makeModel(overrides: Partial<ModelLike> = {}): ModelLike {
|
||||
api: "commandcode-custom",
|
||||
provider: "commandcode",
|
||||
maxTokens: 384_000,
|
||||
cost: { input: 0.14, output: 0.28, cacheRead: 0.028, cacheWrite: 0 },
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Regression test for the local cost calculation.
|
||||
*
|
||||
* The provider ships its own cost function because Oh My Pi's legacy pi-ai
|
||||
* shim does not export `calculateCost` (see issue #24). This test locks the
|
||||
* local implementation to pi-ai's upstream `calculateCost` so the two cannot
|
||||
* drift while pi remains the reference host.
|
||||
*/
|
||||
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
|
||||
import { calculateCost, type Model } from "@earendil-works/pi-ai"
|
||||
|
||||
import { calculateCommandCodeCost } from "../src/cost.ts"
|
||||
import type { Usage } from "../src/types.ts"
|
||||
|
||||
type CostTable = Model<"openai-completions">["cost"]
|
||||
|
||||
const COST_FIXTURES: Record<string, CostTable> = {
|
||||
"zero-cost-model": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
||||
"deepseek/deepseek-v4-pro": {
|
||||
input: 0.435,
|
||||
output: 0.87,
|
||||
cacheRead: 0.003625,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
"Qwen/Qwen3.7-Max": { input: 1.25, output: 3.75, cacheRead: 0.25, cacheWrite: 1.56 },
|
||||
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
||||
}
|
||||
|
||||
const USAGE_CASES = [
|
||||
{ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
{ input: 1, output: 1, cacheRead: 1, cacheWrite: 1 },
|
||||
{ input: 812, output: 187, cacheRead: 52_000, cacheWrite: 3_100 },
|
||||
{ input: 1_000_000, output: 65_536, cacheRead: 998_877, cacheWrite: 123_456 },
|
||||
{ input: 7, output: 999_999_999, cacheRead: 0.5, cacheWrite: 42 },
|
||||
]
|
||||
|
||||
function piAiModel(id: string, cost: CostTable): Model<"openai-completions"> {
|
||||
return {
|
||||
id,
|
||||
name: id,
|
||||
api: "openai-completions",
|
||||
provider: "commandcode",
|
||||
baseUrl: "https://api.commandcode.ai",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost,
|
||||
contextWindow: 1_000_000,
|
||||
maxTokens: 65_536,
|
||||
}
|
||||
}
|
||||
|
||||
function freshUsage(tokens: (typeof USAGE_CASES)[number]): Usage {
|
||||
return {
|
||||
...tokens,
|
||||
totalTokens: tokens.input + tokens.output + tokens.cacheRead + tokens.cacheWrite,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
describe("calculateCommandCodeCost()", () => {
|
||||
it("matches pi-ai calculateCost exactly for all cost fields", () => {
|
||||
for (const [id, cost] of Object.entries(COST_FIXTURES)) {
|
||||
const model = piAiModel(id, cost)
|
||||
|
||||
for (const tokens of USAGE_CASES) {
|
||||
const ours = freshUsage(tokens)
|
||||
const upstream = freshUsage(tokens)
|
||||
|
||||
calculateCommandCodeCost(model, ours)
|
||||
calculateCost(model, upstream)
|
||||
|
||||
for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"] as const) {
|
||||
assert.equal(
|
||||
ours.cost[key],
|
||||
upstream.cost[key],
|
||||
`${id} cost.${key} for tokens=${JSON.stringify(tokens)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("writes the total as the sum of all cost components", () => {
|
||||
const model = piAiModel("claude-sonnet-4-6", COST_FIXTURES["claude-sonnet-4-6"])
|
||||
const usage = freshUsage({ input: 1_000, output: 500, cacheRead: 10_000, cacheWrite: 2_000 })
|
||||
|
||||
calculateCommandCodeCost(model, usage)
|
||||
|
||||
assert.equal(
|
||||
usage.cost.total,
|
||||
usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite,
|
||||
)
|
||||
assert.ok(usage.cost.total > 0)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user