fix(catalog): retain computer capability provenance
This commit is contained in:
@@ -204,8 +204,8 @@ function hasModelHeaders(model: Model<Api>): boolean {
|
||||
* headers and reject/refetch dynamic-only cached models that need live headers.
|
||||
*/
|
||||
function toCachedModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi> {
|
||||
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. */
|
||||
|
||||
@@ -261,20 +261,21 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
||||
const latestUsableCacheModels = latestRestoredCache.models.filter(
|
||||
model => !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,
|
||||
|
||||
@@ -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<Record<string, unknown>>;
|
||||
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");
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user