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.
This commit is contained in:
@@ -134,9 +134,9 @@ On startup, the provider fetches:
|
|||||||
https://api.commandcode.ai/provider/v1/models
|
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 `<agent-dir>/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
|
## Pricing
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { AssistantMessageEventStream } from "@earendil-works/pi-ai"
|
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 { COMMAND_CODE_CLI_VERSION, createStreamCommandCode, DEFAULT_API_BASE } from "./src/core.ts"
|
||||||
import { calculateCommandCodeCost } from "./src/cost.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 API_BASE = process.env.COMMANDCODE_API_BASE ?? DEFAULT_API_BASE
|
||||||
const MODELS_URL = process.env.COMMANDCODE_MODELS_URL ?? DEFAULT_MODELS_URL
|
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 = {
|
type CommandCodeModelCost = {
|
||||||
input: number
|
input: number
|
||||||
|
|||||||
+4
-9
@@ -1,6 +1,5 @@
|
|||||||
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
||||||
import { homedir } from "node:os"
|
import { dirname } from "node:path"
|
||||||
import { dirname, join } from "node:path"
|
|
||||||
|
|
||||||
export const DEFAULT_MODELS_URL = "https://api.commandcode.ai/provider/v1/models"
|
export const DEFAULT_MODELS_URL = "https://api.commandcode.ai/provider/v1/models"
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ interface FetchCommandCodeModelsOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface LoadCommandCodeModelsOptions extends FetchCommandCodeModelsOptions {
|
interface LoadCommandCodeModelsOptions extends FetchCommandCodeModelsOptions {
|
||||||
cachePath?: string
|
cachePath: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadCommandCodeModelsResult {
|
export interface LoadCommandCodeModelsResult {
|
||||||
@@ -93,10 +92,6 @@ function errorMessage(error: unknown): string {
|
|||||||
return error instanceof Error ? error.message : String(error)
|
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[] {
|
export function commandCodeModelsFromApiResponse(value: unknown): readonly CommandCodeModel[] {
|
||||||
if (!isRecord(value)) throw new Error("Expected models response to be an object")
|
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'")
|
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(
|
export async function loadCommandCodeModels(
|
||||||
options: LoadCommandCodeModelsOptions = {},
|
options: LoadCommandCodeModelsOptions,
|
||||||
): Promise<LoadCommandCodeModelsResult> {
|
): Promise<LoadCommandCodeModelsResult> {
|
||||||
const cachePath = options.cachePath ?? defaultCommandCodeModelsCachePath()
|
const cachePath = options.cachePath
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const models = await fetchCommandCodeModels(options)
|
const models = await fetchCommandCodeModels(options)
|
||||||
|
|||||||
@@ -166,6 +166,9 @@ try {
|
|||||||
assert.match(listOutput, /commandcode/)
|
assert.match(listOutput, /commandcode/)
|
||||||
assert.match(listOutput, /deepseek\/deepseek-v4-flash/)
|
assert.match(listOutput, /deepseek\/deepseek-v4-flash/)
|
||||||
assert.equal(modelListRequestCount, 1)
|
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/)
|
assert.doesNotMatch(result.stdout + result.stderr, /Failed to load extension/)
|
||||||
|
|
||||||
console.log("[omp-compat] print mode through real extension and mock API")
|
console.log("[omp-compat] print mode through real extension and mock API")
|
||||||
|
|||||||
@@ -129,10 +129,10 @@ const env = {
|
|||||||
...process.env,
|
...process.env,
|
||||||
HOME: tempHome,
|
HOME: tempHome,
|
||||||
USERPROFILE: tempHome,
|
USERPROFILE: tempHome,
|
||||||
|
PI_CODING_AGENT_DIR: join(tempHome, "custom-pi-agent"),
|
||||||
COMMANDCODE_API_BASE: apiBase,
|
COMMANDCODE_API_BASE: apiBase,
|
||||||
COMMANDCODE_API_KEY: "mock-key",
|
COMMANDCODE_API_KEY: "mock-key",
|
||||||
COMMANDCODE_MODELS_URL: `${apiBase}/provider/v1/models`,
|
COMMANDCODE_MODELS_URL: `${apiBase}/provider/v1/models`,
|
||||||
COMMANDCODE_MODELS_CACHE: join(tempHome, "commandcode-models.json"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function runPi(args, timeoutMs = 30_000) {
|
function runPi(args, timeoutMs = 30_000) {
|
||||||
@@ -269,7 +269,8 @@ try {
|
|||||||
console.log("[pi-local] first offline start without a cache")
|
console.log("[pi-local] first offline start without a cache")
|
||||||
const onlineModelsUrl = env.COMMANDCODE_MODELS_URL
|
const onlineModelsUrl = env.COMMANDCODE_MODELS_URL
|
||||||
env.COMMANDCODE_MODELS_URL = "http://127.0.0.1:1/provider/v1/models"
|
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(
|
const firstOfflineList = await runPi(
|
||||||
["--no-extensions", "-e", EXT_PATH, "--list-models", "commandcode"],
|
["--no-extensions", "-e", EXT_PATH, "--list-models", "commandcode"],
|
||||||
20_000,
|
20_000,
|
||||||
@@ -289,6 +290,7 @@ try {
|
|||||||
assert.match(listOutput, /cc-offline-cache-model/)
|
assert.match(listOutput, /cc-offline-cache-model/)
|
||||||
assert.match(listOutput, /cc-second-model/)
|
assert.match(listOutput, /cc-second-model/)
|
||||||
assert.equal(modelListRequestCount, 1)
|
assert.equal(modelListRequestCount, 1)
|
||||||
|
assert.doesNotThrow(() => accessSync(modelsCachePath, constants.R_OK))
|
||||||
|
|
||||||
console.log("[pi-local] list cached models while model discovery is offline")
|
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"
|
env.COMMANDCODE_MODELS_URL = "http://127.0.0.1:1/provider/v1/models"
|
||||||
|
|||||||
Reference in New Issue
Block a user