Files
pi-commandcode-provider/tests/models/models.test.ts
T
cat-shark 5947e133de 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.
2026-09-14 11:19:33 +08:00

189 lines
6.8 KiB
TypeScript

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)
})