From 1e3ac8f69d8c666a74e26f911ecb271b5ed616b9 Mon Sep 17 00:00:00 2001 From: roboomp Date: Thu, 30 Jul 2026 15:32:16 +0000 Subject: [PATCH] fix(catalog): invalidated cached copilot grok route Drop cached grok-4.5 Chat Completions rows when the bundled Copilot catalog fingerprint changes, matching the existing MAI endpoint migration. Cover both cached endpoint migrations through the model manager's default online-if-uncached path. Fixes #7096 --- .../src/provider-models/openai-compat.ts | 2 +- .../test/github-copilot-model-limits.test.ts | 89 ++++++++++--------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index 87cdd17b6..44794ab69 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -4303,7 +4303,7 @@ export interface GithubCopilotModelManagerConfig { const COPILOT_ANTHROPIC_MODEL_PATTERN = /^claude-(haiku|sonnet|opus|fable|mythos)-\d/; const isCopilotResponsesModelId = (modelId: string): boolean => modelId === "grok-4.5" || modelId.startsWith("gpt-5") || modelId.startsWith("oswe") || modelId.startsWith("mai-"); -const COPILOT_CACHE_INVALIDATED_MODEL_IDS = ["mai-code-1-flash-picker"]; +const COPILOT_CACHE_INVALIDATED_MODEL_IDS = ["grok-4.5", "mai-code-1-flash-picker"]; function inferCopilotApi(modelId: string): Api { if (COPILOT_ANTHROPIC_MODEL_PATTERN.test(modelId)) { diff --git a/packages/catalog/test/github-copilot-model-limits.test.ts b/packages/catalog/test/github-copilot-model-limits.test.ts index cb0ec5f2f..992258afe 100644 --- a/packages/catalog/test/github-copilot-model-limits.test.ts +++ b/packages/catalog/test/github-copilot-model-limits.test.ts @@ -333,50 +333,55 @@ describe("github copilot model limits mapping", () => { expect(model).toBeDefined(); expect(model?.api).toBe("openai-responses"); }); - it("invalidates a cached MAI-Code completion route after the endpoint migration", async () => { - const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-ai-copilot-mai-cache-")); - const cacheDbPath = path.join(tempDir, "models.db"); - const cacheProviderId = "github-copilot-mai-cache-test"; - try { - const oldManager = createModelManager({ - providerId: "github-copilot", - cacheProviderId, - cacheDbPath, - staticModels: [], - fetchDynamicModels: async () => [ - { - id: "mai-code-1-flash-picker", - name: "MAI-Code-1-Flash", - api: "openai-completions" as const, - provider: "github-copilot", - baseUrl: "https://api.githubcopilot.com", - reasoning: true, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 256_000, - maxTokens: 128_000, - }, - ], - }); - await oldManager.refresh("online"); + for (const migration of [ + { id: "mai-code-1-flash-picker", name: "MAI-Code-1-Flash", expectedApi: "openai-responses" }, + { id: "grok-4.5", name: "Grok 4.5", expectedApi: undefined }, + ]) { + it(`invalidates a cached ${migration.name} completion route after the endpoint migration`, async () => { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), `pi-ai-copilot-${migration.id}-cache-`)); + const cacheDbPath = path.join(tempDir, "models.db"); + const cacheProviderId = `github-copilot-${migration.id}-cache-test`; + try { + const oldManager = createModelManager({ + providerId: "github-copilot", + cacheProviderId, + cacheDbPath, + staticModels: [], + fetchDynamicModels: async () => [ + { + id: migration.id, + name: migration.name, + api: "openai-completions" as const, + provider: "github-copilot", + baseUrl: "https://api.githubcopilot.com", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 256_000, + maxTokens: 128_000, + }, + ], + }); + await oldManager.refresh("online"); - const fetchMock = vi.fn(async () => { - throw new Error("a fresh cache must avoid discovery"); - }); - const manager = createModelManager({ - ...githubCopilotModelManagerOptions({ apiKey: "copilot-test-key", fetch: fetchMock }), - cacheProviderId, - cacheDbPath, - }); - const { models } = await manager.refresh("online-if-uncached"); - const model = models.find(candidate => candidate.id === "mai-code-1-flash-picker"); + const fetchMock = vi.fn(async () => { + throw new Error("a fresh cache must avoid discovery"); + }); + const manager = createModelManager({ + ...githubCopilotModelManagerOptions({ apiKey: "copilot-test-key", fetch: fetchMock }), + cacheProviderId, + cacheDbPath, + }); + const { models } = await manager.refresh("online-if-uncached"); + const model = models.find(candidate => candidate.id === migration.id); - expect(fetchMock).not.toHaveBeenCalled(); - expect(model?.api).toBe("openai-responses"); - } finally { - await fs.rm(tempDir, { recursive: true, force: true }); - } - }); + expect(fetchMock).not.toHaveBeenCalled(); + expect(model?.api).toBe(migration.expectedApi); + } finally { + await fs.rm(tempDir, { recursive: true, force: true }); + } + }); + } }); /**