feat: rewrite the Command Code provider on pi's native provider API
Replace the previous implementation with one that registers the Provider API catalog through pi's own provider layer instead of shipping a custom transport, cache file, and hand-maintained pricing table. - models: derive the catalog from the published command-code CLI package (context windows, reasoning efforts, image input, output limits, rates) and keep it as the offline baseline; scripts/sync-catalog.mjs regenerates it and supports --check - refresh: use refreshModels plus context.publish so pi persists the live /provider/v1/models listing in models-store.json and restores it offline - auth: /login browser transfer through a localhost callback server with a pasted-key fallback; $COMMAND_CODE_API_KEY, --api-key and auth.json keep working - streaming: pi's native openai-completions and anthropic-messages adapters; the generate-transport fallback and Oh My Pi branches are gone - keep the context-overflow rewrite that enables pi's compaction retry and the /commandcode-quota command - tests: 51 cases under tests/<module>/ covering models, catalog sync, auth, the callback server, overflow handling, quota, and the extension factory Verified against the live API: chat, tool round trip, image input and --thinking max on deepseek/deepseek-v4.1-flash, quota output, and catalog persistence in an interactive session.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { test } from "node:test"
|
||||
|
||||
import {
|
||||
accountApiBase,
|
||||
apiForModelId,
|
||||
baseUrlForApi,
|
||||
fetchLiveCatalog,
|
||||
getModelsTimeoutMs,
|
||||
modelsFromCatalog,
|
||||
modelsFromLive,
|
||||
parseLiveCatalog,
|
||||
providerHeaders,
|
||||
thinkingLevelMapFor,
|
||||
toProviderModel,
|
||||
} from "../../src/models.ts"
|
||||
|
||||
const PROVIDER_API_BASE = "https://api.commandcode.ai/provider/v1"
|
||||
|
||||
/** Shape of the real GET /provider/v1/models response. */
|
||||
const liveCatalogResponse = {
|
||||
object: "list",
|
||||
data: [
|
||||
{ id: "deepseek/deepseek-v4.1-flash", object: "model", name: "DeepSeek V4.1 Flash", context_length: 1_000_000 },
|
||||
{ id: "claude-sonnet-4-6", object: "model", name: "Claude Sonnet 4.6", context_length: 1_000_000 },
|
||||
{ id: "vendor/brand-new-model", object: "model", name: "Brand New", context_length: 32_768 },
|
||||
],
|
||||
}
|
||||
|
||||
test("parseLiveCatalog reads id, name and context window", () => {
|
||||
const models = parseLiveCatalog(liveCatalogResponse)
|
||||
|
||||
assert.deepEqual(
|
||||
models.map((model) => model.id),
|
||||
["deepseek/deepseek-v4.1-flash", "claude-sonnet-4-6", "vendor/brand-new-model"],
|
||||
)
|
||||
assert.equal(models[2]?.contextWindow, 32_768)
|
||||
})
|
||||
|
||||
test("parseLiveCatalog rejects malformed catalogs", () => {
|
||||
assert.throws(() => parseLiveCatalog({ object: "list", data: [] }), /empty model catalog/)
|
||||
assert.throws(() => parseLiveCatalog({ object: "collection", data: [{}] }), /'list'/)
|
||||
assert.throws(
|
||||
() => parseLiveCatalog({ object: "list", data: [{ id: "x", name: "X" }] }),
|
||||
/context_length/,
|
||||
)
|
||||
})
|
||||
|
||||
test("modelsFromLive uses CLI metadata when the model is known", () => {
|
||||
const [deepseek, claude, unknown] = modelsFromLive(parseLiveCatalog(liveCatalogResponse))
|
||||
|
||||
assert.equal(deepseek?.reasoning, true)
|
||||
assert.deepEqual(deepseek?.efforts, ["low", "high", "max"])
|
||||
assert.deepEqual(deepseek?.input, ["text", "image"])
|
||||
assert.equal(deepseek?.cost.output, 0.6)
|
||||
assert.equal(claude?.api, "anthropic-messages")
|
||||
|
||||
// Unknown models stay usable but text-only and unpriced until the catalog syncs.
|
||||
assert.equal(unknown?.reasoning, false)
|
||||
assert.deepEqual(unknown?.input, ["text"])
|
||||
assert.deepEqual(unknown?.cost, { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 })
|
||||
assert.equal(unknown?.maxTokens, 32_768)
|
||||
})
|
||||
|
||||
test("modelsFromCatalog exposes every generated entry", async () => {
|
||||
const { CATALOG } = await import("../../src/catalog.ts")
|
||||
const models = modelsFromCatalog()
|
||||
|
||||
assert.equal(models.length, CATALOG.length)
|
||||
assert.ok(models.every((model) => model.maxTokens > 0 && model.contextWindow > 0))
|
||||
})
|
||||
|
||||
test("baseline models without a published context window fall back to a usable default", () => {
|
||||
// The CLI reference lists GLM-5.1 as "—" for context, and pi cannot run a model with a 0 window.
|
||||
const glm = modelsFromCatalog().find((model) => model.id === "zai-org/GLM-5.1")
|
||||
|
||||
assert.equal(glm?.contextWindow, 200_000)
|
||||
assert.ok((glm?.maxTokens ?? 0) > 0)
|
||||
})
|
||||
|
||||
test("modelsFromLive prefers the live context window over the CLI snapshot", () => {
|
||||
const [model] = modelsFromLive([
|
||||
{ id: "deepseek/deepseek-v4.1-flash", name: "DeepSeek V4.1 Flash", contextWindow: 512_000 },
|
||||
])
|
||||
|
||||
assert.equal(model?.contextWindow, 512_000)
|
||||
assert.equal(model?.maxTokens, 65_536)
|
||||
assert.deepEqual(model?.input, ["text", "image"])
|
||||
})
|
||||
|
||||
test("live context windows and DeepSeek V4.1 vision/effort metadata survive the merge", () => {
|
||||
const [model] = modelsFromLive([
|
||||
{ id: "deepseek/deepseek-v4.1-flash", name: "DeepSeek V4.1 Flash", contextWindow: 1_000_000 },
|
||||
])
|
||||
assert.ok(model)
|
||||
const config = toProviderModel(model, PROVIDER_API_BASE)
|
||||
|
||||
// Vision and the opt-in max effort are what the API actually serves for V4.1.
|
||||
assert.deepEqual(config.input, ["text", "image"])
|
||||
assert.deepEqual(config.thinkingLevelMap, {
|
||||
minimal: null,
|
||||
low: "low",
|
||||
medium: null,
|
||||
high: "high",
|
||||
xhigh: null,
|
||||
max: "max",
|
||||
})
|
||||
assert.equal(
|
||||
(config.compat as { supportsReasoningEffort?: boolean }).supportsReasoningEffort,
|
||||
true,
|
||||
)
|
||||
})
|
||||
|
||||
test("api and base URL follow the model family", () => {
|
||||
assert.equal(apiForModelId("claude-opus-5"), "anthropic-messages")
|
||||
assert.equal(apiForModelId("deepseek/deepseek-v4.1-flash"), "openai-completions")
|
||||
assert.equal(baseUrlForApi(PROVIDER_API_BASE, "openai-completions"), PROVIDER_API_BASE)
|
||||
// pi appends /v1/messages to the Anthropic base URL.
|
||||
assert.equal(baseUrlForApi(PROVIDER_API_BASE, "anthropic-messages"), "https://api.commandcode.ai/provider")
|
||||
})
|
||||
|
||||
test("accountApiBase strips the provider namespace", () => {
|
||||
assert.equal(accountApiBase(PROVIDER_API_BASE), "https://api.commandcode.ai")
|
||||
assert.equal(accountApiBase("https://example.test/provider/v1/"), "https://example.test")
|
||||
})
|
||||
|
||||
test("thinkingLevelMap hides levels the model does not offer", () => {
|
||||
assert.deepEqual(thinkingLevelMapFor(["low", "high", "max"]), {
|
||||
minimal: null,
|
||||
low: "low",
|
||||
medium: null,
|
||||
high: "high",
|
||||
xhigh: null,
|
||||
max: "max",
|
||||
})
|
||||
})
|
||||
|
||||
test("toProviderModel maps a Claude model onto the Anthropic adapter", () => {
|
||||
const [claude] = modelsFromLive(parseLiveCatalog(liveCatalogResponse)).slice(1)
|
||||
assert.ok(claude)
|
||||
const config = toProviderModel(claude, PROVIDER_API_BASE)
|
||||
|
||||
assert.equal(config.api, "anthropic-messages")
|
||||
assert.equal(config.baseUrl, "https://api.commandcode.ai/provider")
|
||||
assert.equal(config.reasoning, true)
|
||||
assert.deepEqual(config.thinkingLevelMap?.high, "high")
|
||||
assert.equal(
|
||||
(config.compat as { forceAdaptiveThinking?: boolean }).forceAdaptiveThinking,
|
||||
true,
|
||||
)
|
||||
assert.equal(config.cost.cacheWrite, 3.75)
|
||||
})
|
||||
|
||||
test("toProviderModel maps an OpenAI-compatible model onto the completions adapter", () => {
|
||||
const [deepseek] = modelsFromLive(parseLiveCatalog(liveCatalogResponse))
|
||||
assert.ok(deepseek)
|
||||
const config = toProviderModel(deepseek, PROVIDER_API_BASE)
|
||||
|
||||
assert.equal(config.api, "openai-completions")
|
||||
assert.equal(config.baseUrl, PROVIDER_API_BASE)
|
||||
assert.deepEqual(config.compat, {
|
||||
supportsStore: false,
|
||||
supportsDeveloperRole: false,
|
||||
supportsReasoningEffort: true,
|
||||
maxTokensField: "max_tokens",
|
||||
})
|
||||
assert.deepEqual(config.input, ["text", "image"])
|
||||
})
|
||||
|
||||
test("fetchLiveCatalog parses the response and surfaces HTTP failures", async () => {
|
||||
const ok = await fetchLiveCatalog({
|
||||
fetchImpl: async () => new Response(JSON.stringify(liveCatalogResponse), { status: 200 }),
|
||||
})
|
||||
assert.equal(ok.length, 3)
|
||||
|
||||
await assert.rejects(
|
||||
fetchLiveCatalog({ fetchImpl: async () => new Response("nope", { status: 503 }) }),
|
||||
/503/,
|
||||
)
|
||||
})
|
||||
|
||||
test("environment overrides for base URL, headers and timeout", () => {
|
||||
assert.deepEqual(providerHeaders({ CMD_ZDR: "1" }), { "x-cmd-zdr": "1" })
|
||||
assert.equal(providerHeaders({ COMMANDCODE_ZDR: "1" })?.["x-cmd-zdr"], "1")
|
||||
assert.equal(providerHeaders({}), undefined)
|
||||
assert.equal(getModelsTimeoutMs({ COMMANDCODE_MODELS_TIMEOUT_MS: "2500" }), 2500)
|
||||
assert.equal(getModelsTimeoutMs({ COMMANDCODE_MODELS_TIMEOUT_MS: "-1" }), 10_000)
|
||||
})
|
||||
@@ -0,0 +1,116 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { test } from "node:test"
|
||||
|
||||
import {
|
||||
buildCatalog,
|
||||
parseBundleMetadata,
|
||||
parseRates,
|
||||
parseReference,
|
||||
parseReferenceRow,
|
||||
sliceModelObject,
|
||||
} from "../../scripts/sync-catalog.mjs"
|
||||
|
||||
/** Mirrors one row of the CLI reference table, including the em-dash placeholders. */
|
||||
const referenceMarkdown = [
|
||||
"| Id (use EXACTLY this) | Name | Context | Efforts | $/1M in/out · cache read | Min plan | Best for |",
|
||||
"|---|---|---|---|---|---|---|",
|
||||
"| `deepseek/deepseek-v4.1-flash` | DeepSeek V4.1 Flash | 1M | low, high, max | $0.15/$0.6 · cache $0.003 | Go and above | reasoning with vision |",
|
||||
"| `moonshotai/Kimi-K2.7-Code` | Kimi K2.7 Code | 256K | — | $0.95/$4 · cache $0.19 | Go and above | long-horizon coding |",
|
||||
"| `claude-sonnet-4-6` | Claude Sonnet 4.6 | 1M | low, medium, high, xhigh, max | $3/$15 · cache $0.3 (write $3.75) | Pro and above | fast and capable |",
|
||||
].join("\n")
|
||||
|
||||
/** Trimmed shape of the minified CLI model literal. */
|
||||
const cliBundle = [
|
||||
"var $L={SONNET:{id:\"claude-sonnet-4-6\",inputModalities:[\"text\",\"image\"],provider:\"x\",spec:\"chatComplete\",label:\"Claude Sonnet 4.6\",reasoning:!0,reasoningEfforts:[\"low\",\"medium\",\"high\",\"xhigh\",\"max\"],contextWindow:1e6},",
|
||||
"FLASH:{id:\"deepseek/deepseek-v4.1-flash\",inputModalities:[\"text\",\"image\"],provider:\"y\",spec:\"chatComplete\",label:\"DeepSeek V4.1 Flash\",reasoning:!0,reasoningEfforts:[\"low\",\"high\",\"max\"],contextWindow:1e6},",
|
||||
"KIMI:{id:\"moonshotai/Kimi-K2.7-Code\",inputModalities:[\"text\",\"image\"],provider:\"z\",spec:\"chatComplete\",label:\"Kimi K2.7 Code\",reasoning:!0,contextWindow:262144}};",
|
||||
].join("")
|
||||
|
||||
/** Output shape of the generator, asserted field by field below. */
|
||||
interface CatalogEntry {
|
||||
id: string
|
||||
name: string
|
||||
contextWindow: number
|
||||
efforts: string[]
|
||||
reasoning: boolean
|
||||
input: string[]
|
||||
maxOutputTokens: number
|
||||
cost: { input: number; output: number; cacheRead: number; cacheWrite: number }
|
||||
}
|
||||
|
||||
test("parseReference reads ids, names, efforts and rates", () => {
|
||||
const models = parseReference(referenceMarkdown)
|
||||
|
||||
assert.deepEqual(models.map((model) => model.id), [
|
||||
"deepseek/deepseek-v4.1-flash",
|
||||
"moonshotai/Kimi-K2.7-Code",
|
||||
"claude-sonnet-4-6",
|
||||
])
|
||||
assert.deepEqual(models[0]?.efforts, ["low", "high", "max"])
|
||||
assert.deepEqual(models[1]?.efforts, [])
|
||||
assert.deepEqual(models[2]?.cost, { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 })
|
||||
})
|
||||
|
||||
test("parseReference ignores header rows and rejects unknown efforts", () => {
|
||||
assert.equal(parseReferenceRow("| Id (use EXACTLY this) | Name | Context | Efforts |"), undefined)
|
||||
assert.throws(
|
||||
() => parseReference("| `m` | M | 1M | turbo | $1/$2 · cache $0 | Go |"),
|
||||
/Unknown effort/,
|
||||
)
|
||||
})
|
||||
|
||||
test("parseRates handles free and cache-write pricing", () => {
|
||||
assert.deepEqual(parseRates("$0/$0 · cache $0"), { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 })
|
||||
assert.deepEqual(parseRates("$2/$6 · cache $0.25 (write $2.5)"), {
|
||||
input: 2,
|
||||
output: 6,
|
||||
cacheRead: 0.25,
|
||||
cacheWrite: 2.5,
|
||||
})
|
||||
})
|
||||
|
||||
test("sliceModelObject extracts one nested literal from the minified bundle", () => {
|
||||
const entry = sliceModelObject(cliBundle, "moonshotai/Kimi-K2.7-Code")
|
||||
|
||||
assert.ok(entry?.startsWith('{id:"moonshotai/Kimi-K2.7-Code",inputModalities:'))
|
||||
assert.ok(entry?.includes('contextWindow:262144}'))
|
||||
assert.ok(entry?.endsWith('contextWindow:262144}'))
|
||||
assert.equal(sliceModelObject(cliBundle, "missing/model"), undefined)
|
||||
})
|
||||
|
||||
test("parseBundleMetadata reports modalities, reasoning and limits", () => {
|
||||
const metadata = parseBundleMetadata(cliBundle, [
|
||||
"claude-sonnet-4-6",
|
||||
"moonshotai/Kimi-K2.7-Code",
|
||||
]) as Map<string, { input: string[]; reasoning: boolean; maxOutputTokens?: number; contextWindow?: number }>
|
||||
|
||||
assert.deepEqual(metadata.get("claude-sonnet-4-6")?.input, ["text", "image"])
|
||||
assert.equal(metadata.get("claude-sonnet-4-6")?.reasoning, true)
|
||||
assert.equal(metadata.get("claude-sonnet-4-6")?.contextWindow, 1_000_000)
|
||||
// Reason-only models carry no effort list but still report reasoning.
|
||||
assert.equal(metadata.get("moonshotai/Kimi-K2.7-Code")?.reasoning, true)
|
||||
assert.equal(metadata.get("moonshotai/Kimi-K2.7-Code")?.maxOutputTokens, undefined)
|
||||
})
|
||||
|
||||
test("buildCatalog merges the reference table with bundle metadata", () => {
|
||||
const reference = parseReference(referenceMarkdown)
|
||||
const metadata = parseBundleMetadata(
|
||||
cliBundle,
|
||||
reference.map((model: { id: string }) => model.id),
|
||||
)
|
||||
const catalog = buildCatalog("1.54.0", reference, metadata) as CatalogEntry[]
|
||||
|
||||
assert.deepEqual(catalog.map((model) => model.id), reference.map((model) => model.id))
|
||||
assert.deepEqual(catalog[0], {
|
||||
id: "deepseek/deepseek-v4.1-flash",
|
||||
name: "DeepSeek V4.1 Flash",
|
||||
contextWindow: 1_000_000,
|
||||
efforts: ["low", "high", "max"],
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
maxOutputTokens: 0,
|
||||
cost: { input: 0.15, output: 0.6, cacheRead: 0.003, cacheWrite: 0 },
|
||||
})
|
||||
assert.equal(catalog[1]?.reasoning, true)
|
||||
assert.equal(catalog[2]?.cost.cacheWrite, 3.75)
|
||||
})
|
||||
Reference in New Issue
Block a user