From 131d31f6aeccbb93bbba1b3d4624a6ffe9a1e95e Mon Sep 17 00:00:00 2001 From: can1357 Date: Mon, 22 Jun 2026 08:07:47 +0200 Subject: [PATCH] feat(catalog): added cache invalidation for static model metadata - Update `sakanaModelManagerOptions` to provide `dropCachedModelIdsOnStaticMismatch` configuration using identified static model IDs. - Add a test case to verify that stale cached model metadata is correctly cleared when bundled model specs are updated. --- .../src/provider-models/openai-compat.ts | 2 + packages/catalog/test/sakana-provider.test.ts | 52 ++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index 730919c8f..1716e9bbc 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -2582,6 +2582,7 @@ export const SAKANA_FUGU_STATIC_MODELS: readonly ModelSpec<"openai-responses">[] ]; const SAKANA_FUGU_STATIC_MODEL_BY_ID = new Map(SAKANA_FUGU_STATIC_MODELS.map(model => [model.id, model] as const)); +const SAKANA_FUGU_STATIC_MODEL_IDS = SAKANA_FUGU_STATIC_MODELS.map(model => model.id); export interface SakanaModelManagerConfig { apiKey?: string; @@ -2596,6 +2597,7 @@ export function sakanaModelManagerOptions(config?: SakanaModelManagerConfig): Mo return { providerId: "sakana", dynamicModelsAuthoritative: true, + dropCachedModelIdsOnStaticMismatch: SAKANA_FUGU_STATIC_MODEL_IDS, ...(apiKey && { fetchDynamicModels: () => fetchOpenAICompatibleModels({ diff --git a/packages/catalog/test/sakana-provider.test.ts b/packages/catalog/test/sakana-provider.test.ts index 3d2e124f8..f7b9d3ac2 100644 --- a/packages/catalog/test/sakana-provider.test.ts +++ b/packages/catalog/test/sakana-provider.test.ts @@ -1,11 +1,15 @@ import { afterEach, describe, expect, test, vi } from "bun:test"; +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; import { getOAuthProviders } from "@oh-my-pi/pi-ai/registry/oauth"; import { getEnvApiKey } from "@oh-my-pi/pi-ai/stream"; import { Effort } from "@oh-my-pi/pi-catalog/effort"; +import { resolveProviderModels } from "@oh-my-pi/pi-catalog/model-manager"; import { getBundledModels } from "@oh-my-pi/pi-catalog/models"; import { DEFAULT_MODEL_PER_PROVIDER, PROVIDER_DESCRIPTORS } from "@oh-my-pi/pi-catalog/provider-models/descriptors"; import { sakanaModelManagerOptions } from "@oh-my-pi/pi-catalog/provider-models/openai-compat"; -import type { FetchImpl, ResolvedOpenAIResponsesCompat } from "@oh-my-pi/pi-catalog/types"; +import type { FetchImpl, ModelSpec, ResolvedOpenAIResponsesCompat } from "@oh-my-pi/pi-catalog/types"; const ORIGINAL_ENV = { SAKANA_API_KEY: Bun.env.SAKANA_API_KEY, @@ -96,6 +100,52 @@ describe("Sakana AI provider support", () => { expect(fuguNext?.compat?.includeEncryptedReasoning).toBe(false); }); + test("drops stale cached Fugu rows when bundled context metadata changes", async () => { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-catalog-sakana-stale-cache-")); + const dbPath = path.join(tempDir, "models.db"); + const staleFugu: ModelSpec<"openai-responses"> = { + id: "fugu", + name: "Fugu", + api: "openai-responses", + provider: "sakana", + baseUrl: "https://api.sakana.ai/v1", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 272_000, + maxTokens: null, + }; + const correctedFugu: ModelSpec<"openai-responses"> = { ...staleFugu, contextWindow: 1_000_000 }; + + try { + await resolveProviderModels( + { + providerId: "sakana", + staticModels: [staleFugu], + dynamicModelsAuthoritative: true, + fetchDynamicModels: async () => [staleFugu], + cacheDbPath: dbPath, + }, + "online", + ); + + const offline = await resolveProviderModels( + { + ...sakanaModelManagerOptions({ + apiKey: "sakana-key", + fetch: async () => new Response(null, { status: 503 }), + }), + staticModels: [correctedFugu], + cacheDbPath: dbPath, + }, + "offline", + ); + + expect(offline.models.find(model => model.id === "fugu")?.contextWindow).toBe(1_000_000); + } finally { + await fs.rm(tempDir, { recursive: true, force: true }); + } + }); test("prefers explicit Sakana model-manager base URL over environment aliases", async () => { Bun.env.SAKANA_BASE_URL = "https://env.sakana.test"; const fetchMock: FetchImpl = vi.fn(