From e33f665e4ce39d44ed9795716f8a3e4143bc160b Mon Sep 17 00:00:00 2001 From: usr-bin-roygbiv Date: Sat, 25 Jul 2026 02:50:10 +0000 Subject: [PATCH] fix(catalog): retain computer capability provenance --- packages/catalog/src/model-cache.ts | 4 +- packages/catalog/src/model-manager.ts | 23 +++--- packages/catalog/test/build.test.ts | 74 +++++++++++++++++++ .../test/cli-computer-lazy.test.ts | 2 +- 4 files changed, 89 insertions(+), 14 deletions(-) diff --git a/packages/catalog/src/model-cache.ts b/packages/catalog/src/model-cache.ts index b426530aa..8e6a3ab14 100644 --- a/packages/catalog/src/model-cache.ts +++ b/packages/catalog/src/model-cache.ts @@ -204,8 +204,8 @@ function hasModelHeaders(model: Model): boolean { * headers and reject/refetch dynamic-only cached models that need live headers. */ function toCachedModelSpec(model: Model): ModelSpec { - const { headers: _headers, compatConfig, ...rest } = model; - return { ...rest, compat: compatConfig }; + const { headers: _headers, compatConfig, supportsComputerUseConfig, ...rest } = model; + return { ...rest, supportsComputerUse: supportsComputerUseConfig, compat: compatConfig }; } /** Whether two in-memory header records are byte-for-byte equivalent. */ diff --git a/packages/catalog/src/model-manager.ts b/packages/catalog/src/model-manager.ts index 1fa5a3b7c..4660b4e85 100644 --- a/packages/catalog/src/model-manager.ts +++ b/packages/catalog/src/model-manager.ts @@ -261,20 +261,21 @@ export async function resolveProviderModels !latestRestoredCache.unresolvedModelIds.has(model.id), ); + const fallbackSnapshotModels = collapseBuiltModelVariants( + mergeDynamicModels( + mergeModelSources(staticModels, modelsDevModels), + prepareCacheModelsForStaticMismatch( + latestUsableCacheModels, + staticModels, + cacheFingerprintMatches, + options.dropCachedModelIdsOnStaticMismatch, + ), + ), + ); writeModelCache( cacheProviderId, now(), - collapseBuiltModelVariants( - mergeDynamicModels( - mergeModelSources(staticModels, modelsDevModels), - prepareCacheModelsForStaticMismatch( - latestUsableCacheModels, - staticModels, - cacheFingerprintMatches, - options.dropCachedModelIdsOnStaticMismatch, - ), - ), - ), + fallbackSnapshotModels, false, staticFingerprint, dbPath, diff --git a/packages/catalog/test/build.test.ts b/packages/catalog/test/build.test.ts index ee92a5eff..9ab751315 100644 --- a/packages/catalog/test/build.test.ts +++ b/packages/catalog/test/build.test.ts @@ -687,6 +687,80 @@ describe("model cache spec round trip", () => { } }); + it("preserves computer-use provenance across cache restarts and endpoint reroutes", async () => { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-catalog-computer-use-cache-")); + const dbPath = path.join(tempDir, "models.db"); + const common = { + name: "GPT-5.4", + requestModelId: "gpt-5.4", + api: "openai-responses" as const, + reasoning: true, + input: ["text", "image"] as Array<"text" | "image">, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 400_000, + maxTokens: 128_000, + }; + const direct = { + ...common, + id: "inferred-direct", + provider: "openai", + baseUrl: "https://api.openai.com/v1", + } satisfies ModelSpec<"openai-responses">; + const proxy = { + ...common, + id: "inferred-proxy", + provider: "gpt-proxy", + baseUrl: "https://gateway.example/v1", + } satisfies ModelSpec<"openai-responses">; + const explicitTrue = { ...direct, id: "explicit-true", supportsComputerUse: true }; + const explicitFalse = { ...proxy, id: "explicit-false", supportsComputerUse: false }; + const reroute = (model: Model<"openai-responses">, provider: string, baseUrl: string) => + buildModel({ ...model, provider, baseUrl, compat: model.compatConfig } as ModelSpec<"openai-responses">); + + try { + await resolveProviderModels<"openai-responses">( + { + providerId: "computer-use-cache-test", + staticModels: [], + cacheDbPath: dbPath, + fetchDynamicModels: async () => [direct, proxy, explicitTrue, explicitFalse], + }, + "online", + ); + + const db = new Database(dbPath, { readonly: true }); + const row = db + .query<{ models: string }, [string]>("SELECT models FROM model_cache WHERE provider_id = ?") + .get("computer-use-cache-test"); + db.close(); + const persisted = JSON.parse(row?.models ?? "[]") as Array>; + expect(persisted.find(model => model.id === direct.id)).not.toHaveProperty("supportsComputerUse"); + expect(persisted.find(model => model.id === proxy.id)).not.toHaveProperty("supportsComputerUse"); + expect(persisted.find(model => model.id === explicitTrue.id)?.supportsComputerUse).toBe(true); + expect(persisted.find(model => model.id === explicitFalse.id)?.supportsComputerUse).toBe(false); + + const offline = await resolveProviderModels<"openai-responses">( + { providerId: "computer-use-cache-test", staticModels: [], cacheDbPath: dbPath }, + "offline", + ); + const byId = new Map(offline.models.map(model => [model.id, model])); + const cachedDirect = byId.get(direct.id); + const cachedProxy = byId.get(proxy.id); + const cachedExplicitTrue = byId.get(explicitTrue.id); + const cachedExplicitFalse = byId.get(explicitFalse.id); + expect(cachedDirect?.supportsComputerUseConfig).toBeUndefined(); + expect(cachedProxy?.supportsComputerUseConfig).toBeUndefined(); + expect(reroute(cachedDirect!, "gpt-proxy", proxy.baseUrl).supportsComputerUse).toBe(false); + expect(reroute(cachedProxy!, "openai", direct.baseUrl).supportsComputerUse).toBe(true); + expect(cachedExplicitTrue?.supportsComputerUseConfig).toBe(true); + expect(cachedExplicitFalse?.supportsComputerUseConfig).toBe(false); + expect(reroute(cachedExplicitTrue!, "gpt-proxy", proxy.baseUrl).supportsComputerUse).toBe(true); + expect(reroute(cachedExplicitFalse!, "openai", direct.baseUrl).supportsComputerUse).toBe(false); + } finally { + await fs.rm(tempDir, { recursive: true, force: true }); + } + }); + it("uses current static limits for same-id cache rows when the static fingerprint changed", async () => { const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-catalog-static-fingerprint-")); const dbPath = path.join(tempDir, "models.db"); diff --git a/packages/coding-agent/test/cli-computer-lazy.test.ts b/packages/coding-agent/test/cli-computer-lazy.test.ts index 7a46d1f9a..51e6e9ac2 100644 --- a/packages/coding-agent/test/cli-computer-lazy.test.ts +++ b/packages/coding-agent/test/cli-computer-lazy.test.ts @@ -9,7 +9,7 @@ test("normal CLI startup keeps computer worker modules lazy", async () => { await Bun.write( probePath, [ - `const { runCli } = await import(${JSON.stringify(cliUrl)});`, + `import { runCli } from ${JSON.stringify(cliUrl)};`, "process.stdout.write = () => true;", 'await runCli(["--version"]);', ].join("\n"),