From b1a60da6b40be0db27b9702a37cf888a0b5034e5 Mon Sep 17 00:00:00 2001 From: Patrick Wozniak Date: Sun, 2 Aug 2026 01:37:34 +0200 Subject: [PATCH] fix(models): store cache in agent directory Resolve the Command Code model cache through the host's getAgentDir helper so pi, OMP, and PI_CODING_AGENT_DIR use their own agent state directory instead of the official Command Code client directory. --- README.md | 4 ++-- index.ts | 6 ++++-- src/models.ts | 13 ++++--------- tests/test-omp-compat.mjs | 3 +++ tests/test-pi-local.mjs | 6 ++++-- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f1073c9..865c6f5 100644 --- a/README.md +++ b/README.md @@ -134,9 +134,9 @@ On startup, the provider fetches: https://api.commandcode.ai/provider/v1/models ``` -The last successfully fetched catalog is cached at `~/.commandcode/pi-models.json`. If model discovery is temporarily unavailable, the provider uses this cached catalog so previously discovered Command Code models remain selectable. On a first offline start without a cache, pi still loads, but Command Code models remain unavailable until the connection is restored and `/reload` succeeds. +The last successfully fetched catalog is cached at `/commandcode-models.json` (`~/.pi/agent/commandcode-models.json` by default). The agent directory follows pi's `PI_CODING_AGENT_DIR` setting, so compatible hosts such as OMP keep the cache in their own agent directory. If model discovery is temporarily unavailable, the provider uses this cached catalog so previously discovered Command Code models remain selectable. On a first offline start without a cache, pi still loads, but Command Code models remain unavailable until the connection is restored and `/reload` succeeds. -For tests or local mocks, override the endpoint with `COMMANDCODE_MODELS_URL` and the cache location with `COMMANDCODE_MODELS_CACHE`. +For tests or local mocks, override the endpoint with `COMMANDCODE_MODELS_URL` and the cache file with `COMMANDCODE_MODELS_CACHE`. ## Pricing diff --git a/index.ts b/index.ts index 0ebe19c..4b67b15 100644 --- a/index.ts +++ b/index.ts @@ -13,7 +13,8 @@ */ import { AssistantMessageEventStream } from "@earendil-works/pi-ai" -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent" +import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent" +import { join } from "node:path" import { COMMAND_CODE_CLI_VERSION, createStreamCommandCode, DEFAULT_API_BASE } from "./src/core.ts" import { calculateCommandCodeCost } from "./src/cost.ts" @@ -22,7 +23,8 @@ import { getApiKey, login, refreshToken } from "./src/oauth.ts" const API_BASE = process.env.COMMANDCODE_API_BASE ?? DEFAULT_API_BASE const MODELS_URL = process.env.COMMANDCODE_MODELS_URL ?? DEFAULT_MODELS_URL -const MODELS_CACHE_PATH = process.env.COMMANDCODE_MODELS_CACHE +const MODELS_CACHE_PATH = + process.env.COMMANDCODE_MODELS_CACHE ?? join(getAgentDir(), "commandcode-models.json") type CommandCodeModelCost = { input: number diff --git a/src/models.ts b/src/models.ts index 237b003..604cf7c 100644 --- a/src/models.ts +++ b/src/models.ts @@ -1,6 +1,5 @@ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises" -import { homedir } from "node:os" -import { dirname, join } from "node:path" +import { dirname } from "node:path" export const DEFAULT_MODELS_URL = "https://api.commandcode.ai/provider/v1/models" @@ -27,7 +26,7 @@ interface FetchCommandCodeModelsOptions { } interface LoadCommandCodeModelsOptions extends FetchCommandCodeModelsOptions { - cachePath?: string + cachePath: string } export interface LoadCommandCodeModelsResult { @@ -93,10 +92,6 @@ function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error) } -export function defaultCommandCodeModelsCachePath(): string { - return join(homedir(), ".commandcode", "pi-models.json") -} - export function commandCodeModelsFromApiResponse(value: unknown): readonly CommandCodeModel[] { if (!isRecord(value)) throw new Error("Expected models response to be an object") if (value.object !== "list") throw new Error("Expected models response object to be 'list'") @@ -174,9 +169,9 @@ async function writeCommandCodeModelsCache( } export async function loadCommandCodeModels( - options: LoadCommandCodeModelsOptions = {}, + options: LoadCommandCodeModelsOptions, ): Promise { - const cachePath = options.cachePath ?? defaultCommandCodeModelsCachePath() + const cachePath = options.cachePath try { const models = await fetchCommandCodeModels(options) diff --git a/tests/test-omp-compat.mjs b/tests/test-omp-compat.mjs index b591675..af7c2bd 100644 --- a/tests/test-omp-compat.mjs +++ b/tests/test-omp-compat.mjs @@ -166,6 +166,9 @@ try { assert.match(listOutput, /commandcode/) assert.match(listOutput, /deepseek\/deepseek-v4-flash/) assert.equal(modelListRequestCount, 1) + assert.doesNotThrow(() => + accessSync(join(tempHome, ".omp", "agent", "commandcode-models.json"), constants.R_OK), + ) assert.doesNotMatch(result.stdout + result.stderr, /Failed to load extension/) console.log("[omp-compat] print mode through real extension and mock API") diff --git a/tests/test-pi-local.mjs b/tests/test-pi-local.mjs index ffe1687..18a8dd5 100644 --- a/tests/test-pi-local.mjs +++ b/tests/test-pi-local.mjs @@ -129,10 +129,10 @@ const env = { ...process.env, HOME: tempHome, USERPROFILE: tempHome, + PI_CODING_AGENT_DIR: join(tempHome, "custom-pi-agent"), COMMANDCODE_API_BASE: apiBase, COMMANDCODE_API_KEY: "mock-key", COMMANDCODE_MODELS_URL: `${apiBase}/provider/v1/models`, - COMMANDCODE_MODELS_CACHE: join(tempHome, "commandcode-models.json"), } function runPi(args, timeoutMs = 30_000) { @@ -269,7 +269,8 @@ try { console.log("[pi-local] first offline start without a cache") const onlineModelsUrl = env.COMMANDCODE_MODELS_URL env.COMMANDCODE_MODELS_URL = "http://127.0.0.1:1/provider/v1/models" - rmSync(env.COMMANDCODE_MODELS_CACHE, { force: true }) + const modelsCachePath = join(env.PI_CODING_AGENT_DIR, "commandcode-models.json") + rmSync(modelsCachePath, { force: true }) const firstOfflineList = await runPi( ["--no-extensions", "-e", EXT_PATH, "--list-models", "commandcode"], 20_000, @@ -289,6 +290,7 @@ try { assert.match(listOutput, /cc-offline-cache-model/) assert.match(listOutput, /cc-second-model/) assert.equal(modelListRequestCount, 1) + assert.doesNotThrow(() => accessSync(modelsCachePath, constants.R_OK)) console.log("[pi-local] list cached models while model discovery is offline") env.COMMANDCODE_MODELS_URL = "http://127.0.0.1:1/provider/v1/models"