diff --git a/src/stats/pricing.ts b/src/stats/pricing.ts index 4aa528f..46f8a4a 100644 --- a/src/stats/pricing.ts +++ b/src/stats/pricing.ts @@ -117,6 +117,7 @@ export function isOpenRouterModelsUrl(url: string): boolean { /** * 对比用的当前档:每个家族只留最新一档(DeepSeek / Claude / GLM / Gemini / GPT / Grok)。 + * Gemini 另留非 flash/pro 的代号档(如 gemini-4-argon);Pro 若只有 preview 也算当前档。 * 同一档有多个 id 时留 created 更新的那个。 */ export function usualOpenRouterBucket(modelId: string): string | null { @@ -132,7 +133,11 @@ export function usualOpenRouterBucket(modelId: string): string | null { return null; } const parts = name.split(/[^a-z0-9.]+/); - if (parts.some((part) => USUAL_SKIP_PARTS.has(part))) return null; + const skipped = parts.filter((part) => USUAL_SKIP_PARTS.has(part)); + // Gemini 当前 Pro 在 OpenRouter 上仍带 preview,不能整档丢掉。 + const geminiPreviewOnly = + name.includes("gemini") && skipped.length > 0 && skipped.every((part) => part === "preview"); + if (skipped.length && !geminiPreviewOnly) return null; if (name.includes("claude-haiku")) return "claude-haiku"; if (name.includes("claude-sonnet")) return "claude-sonnet"; if (name.includes("claude-opus")) return "claude-opus"; @@ -148,6 +153,7 @@ export function usualOpenRouterBucket(modelId: string): string | null { } if (name.includes("gemini") && name.includes("pro")) return "gemini-pro"; if (name.includes("gemini") && name.includes("flash")) return "gemini-flash"; + if (name.includes("gemini")) return "gemini"; if (name.startsWith("gpt-") && name.includes("luna-pro")) return "gpt-luna-pro"; if (name.startsWith("gpt-") && name.includes("luna")) return "gpt-luna"; if (name.startsWith("gpt-") && name.includes("sol-pro")) return "gpt-sol-pro"; diff --git a/tests/pricing.test.ts b/tests/pricing.test.ts index c13a033..78a40ad 100644 --- a/tests/pricing.test.ts +++ b/tests/pricing.test.ts @@ -255,6 +255,9 @@ describe("trimOpenRouterUsualCatalog", () => { ); expect(usualOpenRouterBucket("google/gemini-3.8-flash")).toBe("gemini-flash"); expect(usualOpenRouterBucket("google/gemini-2.5-pro")).toBe("gemini-pro"); + expect(usualOpenRouterBucket("google/gemini-3.1-pro-preview")).toBe("gemini-pro"); + expect(usualOpenRouterBucket("google/gemini-4-argon")).toBe("gemini"); + expect(usualOpenRouterBucket("google/gemini-3.5-flash-lite")).toBeNull(); expect(usualOpenRouterBucket("openai/gpt-4o:batch")).toBeNull(); const trimmed = JSON.parse( trimOpenRouterUsualCatalog( @@ -293,6 +296,39 @@ describe("trimOpenRouterUsualCatalog", () => { "anthropic/claude-haiku-5.5", "openai/gpt-6-luna", ]); + const gemini = JSON.parse( + trimOpenRouterUsualCatalog( + JSON.stringify({ + data: [ + { + id: "google/gemini-2.5-pro", + created: 10, + pricing: { prompt: "0.000001", completion: "0.000005" }, + }, + { + id: "google/gemini-3.1-pro-preview", + created: 20, + pricing: { prompt: "0.000002", completion: "0.000008" }, + }, + { + id: "google/gemini-3.8-flash", + created: 30, + pricing: { prompt: "0.0000003", completion: "0.000001" }, + }, + { + id: "google/gemini-4-argon", + created: 40, + pricing: { prompt: "0.000003", completion: "0.000012" }, + }, + ], + }), + ), + ) as { data: Array<{ id: string }> }; + expect(gemini.data.map((row) => row.id).sort()).toEqual([ + "google/gemini-3.1-pro-preview", + "google/gemini-3.8-flash", + "google/gemini-4-argon", + ]); const pinned = JSON.parse( trimOpenRouterUsualCatalog( JSON.stringify({