feat(providers): refresh curated model catalogs and retire Imagen 4 (#10537)

* feat(providers): refresh Gemini Flash catalogs and pricing

* fix(providers): refresh gemini-web Flash catalog

* chore(providers): eliminate Gemini 3.5/3.6 Flash models

* feat(providers): refresh Perplexity Web model mappings

* feat(providers): refresh PromptQL and Notion catalogs

* feat(providers): refresh KIE TinyCMS and Conol catalogs

* feat(providers): refresh OpenCode Zen catalog

* chore(providers): finish Gemini Flash cleanup

* chore(providers): retire Google Imagen 4
This commit is contained in:
backryun
2026-08-19 00:27:46 +09:00
committed by GitHub
parent 08860f5cae
commit df90591415
88 changed files with 818 additions and 1212 deletions

View File

@@ -557,7 +557,7 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
- **🧠 Memory you control** — off by default, opt-in int8 vector quantization + typed decay, per-request `x-omniroute-no-memory`. → [Memory](docs/frameworks/MEMORY.md)
- **🛡️ Security** — prompt-injection guard on every LLM route (red-team suite), opt-in credential-masking guardrail (redacts leaked API keys/secrets in both directions), free DuckDuckGo last-resort web search, and an optional OIDC login gate for the dashboard (password login always stays available). → [Guardrails](docs/security/GUARDRAILS.md)
- **🖼️ New endpoints** — `/v1/ocr` (Mistral OCR) and `/v1/audio/translations` (Whisper-style) round out the media surface. → [API Reference](docs/reference/API_REFERENCE.md)
- **🎨 Image / video / audio generation** — one API for media: xAI Grok Imagine & Novita AI video, ComfyUI, Freepik, Adobe Firefly, Microsoft Designer, Google Imagen, Segmind, EdgeTTS. → [API Reference](docs/reference/API_REFERENCE.md)
- **🎨 Image / video / audio generation** — one API for media: xAI Grok Imagine & Novita AI video, ComfyUI, Freepik, Adobe Firefly, Microsoft Designer, Segmind, EdgeTTS. → [API Reference](docs/reference/API_REFERENCE.md)
- **🌍 Deployment & ops** — reverse-proxy `basePath`, browser-language auto-detect, per-key device tracking, root-less MITM trust, zh-TW localization. → [Environment](docs/reference/ENVIRONMENT.md)
- **🤝 More providers & agents** — Cursor Cloud Agent, Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **340-provider catalog**. → [Providers](docs/reference/PROVIDER_REFERENCE.md)
- **📡 Routing transparency** — every response carries an `X-OmniRoute-Decision` header naming the strategy/provider/latency that served it, a new `cache-optimized` combo strategy + Auto-Combo `cacheAffinity` factor route repeat requests back to the connection holding the cached prefix, and a read-only `/v1/auto-combo/{channel}/candidates` endpoint exposes an `auto/*` channel's live candidate pool. → [Auto-Combo](docs/routing/AUTO-COMBO.md)

View File

@@ -2,8 +2,8 @@
//
// These models are pinned from the live `:fetchAvailableModels` endpoint
// (https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels) using a
// real `agy` consumer-OAuth token. The public catalog exposes the upstream Gemini 3.6
// and 3.5 Flash ids verbatim; the shared Antigravity executor dispatches them unchanged.
// real `agy` consumer-OAuth token. The public catalog exposes the upstream Gemini 3.7
// Flash ids verbatim; the shared Antigravity executor dispatches them unchanged.
//
// The `agy` provider reuses the `antigravity` executor/translator (identical backend),
// but keeps its own catalog so the CLI and IDE model surfaces can evolve independently.
@@ -12,11 +12,11 @@
// they are not chat-callable.
export const AGY_PUBLIC_MODELS = Object.freeze([
// Gemini 3.6 Flash tiers. The live endpoint selects High by default and advertises
// all three ids to both the IDE 2.1.1 and CLI 1.1.x clients.
// Gemini 3.7 Flash tiers. The live endpoint selects High by default and advertises
// all three ids to both the IDE 2.5.5 and CLI 1.1.x clients.
{
id: "gemini-3.6-flash-high",
name: "Gemini 3.6 Flash (High)",
id: "gemini-3.7-flash-high",
name: "Gemini 3.7 Flash (High)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
@@ -24,8 +24,8 @@ export const AGY_PUBLIC_MODELS = Object.freeze([
toolCalling: true,
},
{
id: "gemini-3.6-flash-medium",
name: "Gemini 3.6 Flash (Medium)",
id: "gemini-3.7-flash-medium",
name: "Gemini 3.7 Flash (Medium)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
@@ -33,27 +33,8 @@ export const AGY_PUBLIC_MODELS = Object.freeze([
toolCalling: true,
},
{
id: "gemini-3.6-flash-low",
name: "Gemini 3.6 Flash (Low)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
// Claude (Antigravity backend).
{
id: "claude-opus-4-6-thinking",
name: "Claude Opus 4.6 (Thinking)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "claude-sonnet-4-6",
name: "Claude Sonnet 4.6 (Thinking)",
id: "gemini-3.7-flash-low",
name: "Gemini 3.7 Flash (Low)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
@@ -79,45 +60,6 @@ export const AGY_PUBLIC_MODELS = Object.freeze([
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-3-flash-agent",
name: "Gemini 3.5 Flash (High)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-3.5-flash-low",
name: "Gemini 3.5 Flash (Medium)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-3.5-flash-extra-low",
name: "Gemini 3.5 Flash (Low)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
// Gemini 3.7 Flash: single callable public model (upstream exposes only
// gemini-3.7-flash-tiered; suffixed tier ids 404). One entry so it does not
// collide under the #3696 public-id uniqueness invariant.
{
id: "gemini-3.7-flash",
name: "Gemini 3.7 Flash",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-3.1-flash-lite",
name: "Gemini 3.1 Flash Lite",
@@ -125,27 +67,23 @@ export const AGY_PUBLIC_MODELS = Object.freeze([
maxOutputTokens: 65535,
toolCalling: true,
},
// Gemini 2.5
// Claude (Antigravity backend).
{
id: "gemini-2.5-flash-thinking",
name: "Gemini 2.5 Flash Thinking",
id: "claude-opus-4-6-thinking",
name: "Claude Opus 4.6 (Thinking)",
contextLength: 1048576,
maxOutputTokens: 65535,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-2.5-flash",
name: "Gemini 2.5 Flash",
id: "claude-sonnet-4-6",
name: "Claude Sonnet 4.6 (Thinking)",
contextLength: 1048576,
maxOutputTokens: 65535,
toolCalling: true,
},
{
id: "gemini-2.5-flash-lite",
name: "Gemini 2.5 Flash Lite",
contextLength: 1048576,
maxOutputTokens: 65535,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
// GPT-OSS
@@ -161,6 +99,21 @@ export const AGY_PUBLIC_MODELS = Object.freeze([
const AGY_PUBLIC_MODEL_IDS = new Set(AGY_PUBLIC_MODELS.map((model) => model.id));
const AGY_NON_CHAT_MODEL_IDS = new Set(["tab_flash_lite_preview", "tab_jump_flash_lite_preview"]);
const AGY_RETIRED_MODEL_IDS = new Set([
"gemini-3.6-flash-high",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-low",
"gemini-3-flash-agent",
"gemini-3.5-flash-extra-low",
"gemini-3.5-flash-low",
"gemini-3.5-flash-high",
"gemini-3.5-flash-medium",
"gemini-3.5-flash-preview",
"gemini-2.5-pro",
"gemini-2.5-flash-thinking",
"gemini-2.5-flash",
"gemini-2.5-flash-lite",
]);
const AGY_CLIENT_VISIBLE_MODEL_NAMES = Object.freeze(
AGY_PUBLIC_MODELS.reduce<Record<string, string>>((acc, model) => {
@@ -178,5 +131,5 @@ export function isUserCallableAgyModelId(modelId: string): boolean {
}
export function isDiscoverableAgyModelId(modelId: string): boolean {
return !!modelId && !AGY_NON_CHAT_MODEL_IDS.has(modelId);
return !!modelId && !AGY_NON_CHAT_MODEL_IDS.has(modelId) && !AGY_RETIRED_MODEL_IDS.has(modelId);
}

View File

@@ -1,7 +1,7 @@
export const ANTIGRAVITY_PUBLIC_MODELS = Object.freeze([
// Gemini 3.7 Flash tiers listed by the current official Antigravity model catalog
// alongside the existing Gemini 3.6 tiers. Keep the upstream model ids unchanged so
// discovery and execution address the same models selected by the native client.
// Gemini 3.7 Flash tiers listed by the current official Antigravity model catalog.
// Keep the upstream model ids unchanged so discovery and execution address the same
// models selected by the native client.
{
id: "gemini-3.7-flash-high",
name: "Gemini 3.7 Flash (High)",
@@ -20,51 +20,9 @@ export const ANTIGRAVITY_PUBLIC_MODELS = Object.freeze([
supportsVision: true,
toolCalling: true,
},
// Gemini 3.6 Flash tiers retained alongside the newer Gemini 3.7 tiers.
{
id: "gemini-3.6-flash-high",
name: "Gemini 3.6 Flash (High)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-3.6-flash-medium",
name: "Gemini 3.6 Flash (Medium)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-3.6-flash-low",
name: "Gemini 3.6 Flash (Low)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
// Claude (Antigravity backend). The `agy` provider already ships these from the live
// :fetchAvailableModels probe (see agyModels.ts) and discussion #3184 confirmed they
// are user-callable through the `antigravity` OAuth provider too — same backend.
// `antigravity/claude-opus-4-6-thinking` and `antigravity/claude-sonnet-4-6` both work.
// They are upstream IDs, so no alias remapping is required.
{
id: "claude-opus-4-6-thinking",
name: "Claude Opus 4.6 (Thinking)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "claude-sonnet-4-6",
name: "Claude Sonnet 4.6 (Thinking)",
id: "gemini-3.7-flash-low",
name: "Gemini 3.7 Flash (Low)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
@@ -92,50 +50,6 @@ export const ANTIGRAVITY_PUBLIC_MODELS = Object.freeze([
supportsVision: true,
toolCalling: true,
},
// Gemini 3.5 Flash tiers exposed by Antigravity's model selector. Public ids match
// fetchAvailableModels and are forwarded upstream unchanged:
// High -> gemini-3-flash-agent (displayName: Gemini 3.5 Flash (High))
// Medium -> gemini-3.5-flash-low (displayName: Gemini 3.5 Flash (Medium))
// Low -> gemini-3.5-flash-extra-low (displayName: Gemini 3.5 Flash (Low))
{
id: "gemini-3-flash-agent",
name: "Gemini 3.5 Flash (High)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-3.5-flash-low",
name: "Gemini 3.5 Flash (Medium)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-3.5-flash-extra-low",
name: "Gemini 3.5 Flash (Low)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
// Gemini 3.7 Flash: Antigravity's live catalog exposes a single upstream id
// gemini-3.7-flash-tiered; the suffixed tier ids 404 upstream. Kept as one
// callable public model so it does not collide with the #3696 uniqueness invariant.
{
id: "gemini-3.7-flash",
name: "Gemini 3.7 Flash",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-3.1-flash-lite",
name: "Gemini 3.1 Flash Lite",
@@ -143,25 +57,27 @@ export const ANTIGRAVITY_PUBLIC_MODELS = Object.freeze([
maxOutputTokens: 65535,
toolCalling: true,
},
// Claude (Antigravity backend). The `agy` provider already ships these from the live
// :fetchAvailableModels probe (see agyModels.ts) and discussion #3184 confirmed they
// are user-callable through the `antigravity` OAuth provider too — same backend.
// `antigravity/claude-opus-4-6-thinking` and `antigravity/claude-sonnet-4-6` both work.
// They are upstream IDs, so no alias remapping is required.
{
id: "gemini-2.5-flash-thinking",
name: "Gemini 2.5 Flash Thinking",
id: "claude-opus-4-6-thinking",
name: "Claude Opus 4.6 (Thinking)",
contextLength: 1048576,
maxOutputTokens: 65535,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-2.5-flash",
name: "Gemini 2.5 Flash",
id: "claude-sonnet-4-6",
name: "Claude Sonnet 4.6 (Thinking)",
contextLength: 1048576,
maxOutputTokens: 65535,
toolCalling: true,
},
{
id: "gemini-2.5-flash-lite",
name: "Gemini 2.5 Flash Lite",
contextLength: 1048576,
maxOutputTokens: 65535,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
@@ -175,12 +91,6 @@ export const ANTIGRAVITY_PUBLIC_MODELS = Object.freeze([
]);
export const ANTIGRAVITY_MODEL_ALIASES = Object.freeze({
// Gemini 3.7 Flash: the live catalog (fetchAvailableModels on daily-cloudcode-pa)
// exposes a single upstream id `gemini-3.7-flash-tiered`; the agy CLI maps all
// display tiers (high/medium/low) to it. Verified 200 OK with thinking_level and
// thinkingBudget configs. The suffixed ids 404 upstream ("Requested entity was not found").
// Exposed as ONE callable model (see #3696: public ids must be unique upstream ids).
"gemini-3.7-flash": "gemini-3.7-flash-tiered",
// gemini-3.1-pro-low is not aliased: the upstream accepts it verbatim.
// gemini-3.1-pro-high: the discovery slot returns HTTP 400 on v1internal;
// the live upstream id is gemini-pro-agent (see ANTIGRAVITY_PUBLIC_MODELS).
@@ -249,10 +159,19 @@ const ANTIGRAVITY_NON_CHAT_MODEL_IDS = new Set([
const ANTIGRAVITY_RETIRED_MODEL_IDS = new Set([
"gemini-3-pro-preview",
"gemini-3.1-pro",
"gemini-3.6-flash-high",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-low",
"gemini-3-flash-agent",
"gemini-3.5-flash-extra-low",
"gemini-3.5-flash-low",
"gemini-3.5-flash-high",
"gemini-3.5-flash-medium",
"gemini-3.5-flash-preview",
"gemini-2.5-pro",
"gemini-2.5-flash-thinking",
"gemini-2.5-flash",
"gemini-2.5-flash-lite",
"gemini-2.5-computer-use-preview-10-2025",
]);
@@ -281,7 +200,12 @@ const ANTIGRAVITY_DROPPED_QUOTA_BUCKETS = new Set<string>([
*/
export function toClientAntigravityQuotaModelId(modelId: string): string | null {
if (!modelId) return null;
if (ANTIGRAVITY_DROPPED_QUOTA_BUCKETS.has(modelId)) return null;
if (
ANTIGRAVITY_DROPPED_QUOTA_BUCKETS.has(modelId) ||
ANTIGRAVITY_RETIRED_MODEL_IDS.has(modelId)
) {
return null;
}
return toClientAntigravityModelId(modelId);
}

View File

@@ -16,27 +16,20 @@ import type { FreeModelBudget } from "./freeModelCatalog.ts";
* rewrites file timestamps on every deploy, which would report a months-old
* catalog as "updated today". Bump this whenever the entries below change.
*/
export const FREE_CATALOG_CURATED_AT = "2026-07-22";
export const FREE_CATALOG_CURATED_AT = "2026-08-16";
export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [
{ provider: "agentrouter", modelId: "claude-opus-4-8", displayName: "Claude Opus 4.8", monthlyTokens: 0, creditTokens: 200000000, freeType: "one-time-initial", poolKey: "agentrouter", tos: "caution" },
{ provider: "agentrouter", modelId: "claude-opus-5", displayName: "Claude Opus 5", monthlyTokens: 0, creditTokens: 200000000, freeType: "one-time-initial", poolKey: "agentrouter", tos: "caution" },
{ provider: "agentrouter", modelId: "gpt-5.6-sol", displayName: "GPT-5.6 Sol", monthlyTokens: 0, creditTokens: 200000000, freeType: "one-time-initial", poolKey: "agentrouter", tos: "caution" },
{ provider: "agy", modelId: "gemini-3.7-flash-high", displayName: "Gemini 3.7 Flash (High)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.7-flash-medium", displayName: "Gemini 3.7 Flash (Medium)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.7-flash-low", displayName: "Gemini 3.7 Flash (Low)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-pro-agent", displayName: "Gemini 3.1 Pro (High)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.1-pro-low", displayName: "Gemini 3.1 Pro (Low)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.1-flash-lite", displayName: "Gemini 3.1 Flash Lite", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "claude-opus-4-6-thinking", displayName: "Claude Opus 4.6 (Thinking)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6 (Thinking)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.1-pro-low", displayName: "Gemini 3.1 Pro (Low)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-pro-agent", displayName: "Gemini 3.1 Pro (High)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.7-flash", displayName: "Gemini 3.7 Flash", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.6-flash-high", displayName: "Gemini 3.6 Flash (High)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.6-flash-medium", displayName: "Gemini 3.6 Flash (Medium)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.6-flash-low", displayName: "Gemini 3.6 Flash (Low)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3-flash-agent", displayName: "Gemini 3.5 Flash (High)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.5-flash-low", displayName: "Gemini 3.5 Flash (Medium)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.5-flash-extra-low", displayName: "Gemini 3.5 Flash (Low)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-3.1-flash-lite", displayName: "Gemini 3.1 Flash Lite", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-2.5-flash", displayName: "Gemini 2.5 Flash", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-2.5-flash-thinking", displayName: "Gemini 2.5 Flash Thinking", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gemini-2.5-flash-lite", displayName: "Gemini 2.5 Flash Lite", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "agy", modelId: "gpt-oss-120b-medium", displayName: "GPT-OSS 120B (Medium)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "agy", tos: "avoid" },
{ provider: "ai21", modelId: "jamba-large-1.7", displayName: "jamba-large-1.7", monthlyTokens: 0, creditTokens: 10000000, freeType: "one-time-initial", poolKey: "ai21", tos: "avoid" },
{ provider: "ai21", modelId: "jamba-mini-2", displayName: "jamba-mini-2", monthlyTokens: 0, creditTokens: 10000000, freeType: "one-time-initial", poolKey: "ai21", tos: "avoid" },
@@ -188,7 +181,6 @@ export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [
{ provider: "gemini", modelId: "gemini-2.5-flash-lite", displayName: "Gemini 2.5 Flash-Lite", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "gemini-free", tos: "caution" },
{ provider: "gemini", modelId: "gemini-3-flash-preview", displayName: "Gemini 3 Flash Preview", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "gemini-free", tos: "caution" },
{ provider: "gemini", modelId: "gemini-3.1-flash-lite", displayName: "Gemini 3.1 Flash-Lite", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "gemini-free", tos: "caution" },
{ provider: "gemini", modelId: "gemini-3.5-flash", displayName: "Gemini 3.5 Flash", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "gemini-free", tos: "caution" },
{ provider: "glm-cn", modelId: "glm-4-flash", displayName: "GLM-4-Flash", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "zhipu-flash-free", tos: "ok" },
{ provider: "glm-cn", modelId: "glm-4.5-flash", displayName: "GLM-4.5-Flash", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "zhipu-flash-free", tos: "ok" },
{ provider: "glm-cn", modelId: "glm-4.7-flash", displayName: "GLM-4.7-Flash", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "zhipu-flash-free", tos: "ok" },
@@ -369,7 +361,7 @@ export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [
{ provider: "qoder", modelId: "deepseek-v4-pro", displayName: "DeepSeek-V4-Pro", monthlyTokens: 0, creditTokens: 1000000, freeType: "one-time-initial", poolKey: "qoder", tos: "caution" },
{ provider: "qoder", modelId: "deepseek-v4-flash", displayName: "DeepSeek-V4-Flash", monthlyTokens: 0, creditTokens: 1000000, freeType: "one-time-initial", poolKey: "qoder", tos: "caution" },
{ provider: "qoder", modelId: "minimax-m3", displayName: "MiniMax-M3", monthlyTokens: 0, creditTokens: 1000000, freeType: "one-time-initial", poolKey: "qoder", tos: "caution" },
{ provider: "qwen-web", modelId: "qwen3.8-max", displayName: "Qwen3.8 Max", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "qwen-web", tos: "avoid" },
{ provider: "qwen-web", modelId: "qwen3.8-max-preview", displayName: "Qwen3.8 Max Preview", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "qwen-web", tos: "avoid" },
{ provider: "qwen-web", modelId: "qwen3.7-max", displayName: "Qwen3.7 Max", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "qwen-web", tos: "avoid" },
{ provider: "qwen-web", modelId: "qwen3.7-plus", displayName: "Qwen3.7 Plus", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "qwen-web", tos: "avoid" },
{ provider: "qwen-web", modelId: "qwen3.6-plus", displayName: "Qwen3.6 Plus", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "qwen-web", tos: "avoid" },

View File

@@ -5,9 +5,6 @@
"gemini-2-flash-lite": { "rpm": 0, "rpd": 0, "tpm": 0 },
"gemini-2.5-flash-tts": { "rpm": 3, "rpd": 10, "tpm": 10000 },
"gemini-2.5-pro-tts": { "rpm": 0, "rpd": 0, "tpm": 0 },
"imagen-4-generate": { "rpm": -1, "rpd": 25, "tpm": -1 },
"imagen-4-ultra-generate": { "rpm": -1, "rpd": 25, "tpm": -1 },
"imagen-4-fast-generate": { "rpm": -1, "rpd": 25, "tpm": -1 },
"gemma-4-26b-it": { "rpm": 16000, "rpd": 14400, "tpm": 16000 },
"gemma-4-31b-it": { "rpm": 16000, "rpd": 14400, "tpm": 16000 },
"gemini-embedding-exp-03-07": { "rpm": 100, "rpd": 1000, "tpm": 30000 },

View File

@@ -10,7 +10,6 @@ import { SEGMIND_IMAGE_PROVIDER } from "./providers/registry/segmind/imageModels
import { KIE_IMAGE_MODELS } from "./providers/registry/kie/imageModels.ts";
import { FREEPIK_IMAGE_PROVIDER } from "./providers/registry/freepik/index.ts";
import { STABILITY_AI_IMAGE_MODELS } from "./providers/registry/stability-ai/imageModels.ts";
import { GEMINI_IMAGEN_PROVIDER } from "./providers/registry/gemini/imageModels.ts";
import { CHEAPERINFERENCE_IMAGE_PROVIDER } from "./providers/registry/cheaperinference/imageModels.ts";
import {
ADOBE_FIREFLY_IMAGE_ROUTING_ALIASES,
@@ -378,10 +377,6 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
supportedSizes: ["1024x1024"],
},
// Google AI Studio Imagen family — dedicated :predict endpoint, not generateContent.
// See providers/registry/gemini/imageModels.ts for the full rationale.
gemini: GEMINI_IMAGEN_PROVIDER,
//Curruntly no models serving
nebius: {
id: "nebius",

View File

@@ -107,7 +107,12 @@ export const CHEAPERINFERENCE_MODELS: RegistryModel[] = [
supportsReasoning: true,
toolCalling: true,
},
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", supportsReasoning: true, toolCalling: true },
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
supportsReasoning: true,
toolCalling: true,
},
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", supportsReasoning: true, toolCalling: true },
{
id: "gemini-2.5-flash",
@@ -117,8 +122,8 @@ export const CHEAPERINFERENCE_MODELS: RegistryModel[] = [
toolCalling: true,
},
{
id: "gemini-3-5-flash",
name: "Gemini 3.5 Flash",
id: "gemini-3.7-flash",
name: "Gemini 3.7 Flash",
supportsVision: true,
supportsReasoning: true,
toolCalling: true,

View File

@@ -140,22 +140,19 @@ export const cursorProvider: RegistryEntry = {
{ id: "claude-4.6-sonnet-medium", name: "Claude 4.6 Sonnet Medium" },
{ id: "claude-4.6-sonnet-medium-thinking", name: "Claude 4.6 Sonnet Medium Thinking" },
//
{ id: "claude-4.5-sonnet", name: "Claude 4.5 Sonnet" },
{ id: "claude-4.5-sonnet-thinking", name: "Claude 4.5 Sonnet Thinking" },
//
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
//
{ id: "gemini-3.7-flash", name: "Gemini 3.7 Flash" },
{ id: "gemini-3-flash", name: "Gemini 3 Flash" },
//
{ id: "grok-4.3", name: "Grok 4.3" },
{ id: "grok-4.6-medium", name: "Grok 4.6 Medium" },
{ id: "grok-4.6-fast-medium", name: "Grok 4.6 Fast Medium" },
{ id: "grok-4.6-high", name: "Grok 4.6 High" },
{ id: "grok-4.6-fast-high", name: "Grok 4.6 Fast High" },
{ id: "grok-4.6-xhigh", name: "Grok 4.6 XHigh" },
{ id: "grok-4.6-fast-xhigh", name: "Grok 4.6 Fast XHigh" },
//
{ id: "grok-4.5-medium", name: "Grok 4.5 Medium" },
{ id: "grok-4.5-fast-medium", name: "Grok 4.5 Fast Medium" },
{ id: "grok-4.5-high", name: "Grok 4.5 High" },
{ id: "grok-4.5-fast-high", name: "Grok 4.5 Fast High" },
{ id: "grok-4.5-xhigh", name: "Grok 4.5 XHigh" },
{ id: "grok-4.5-fast-xhigh", name: "Grok 4.5 Fast XHigh" },
//
{ id: "kimi-k2.5", name: "Kimi K2.5" },
{ id: "kimi-k3", name: "Kimi K3" },
{ id: "kimi-k2.7-code", name: "Kimi K2.7 Code" },
],
};

View File

@@ -84,14 +84,10 @@ export const DEVIN_MODEL_CATALOG: RegistryModel[] = [
// Gemini
{ id: "gemini-3-1-pro-high", name: "Gemini 3.1 Pro High", contextLength: 1048576 },
{ id: "gemini-3-1-pro-low", name: "Gemini 3.1 Pro Low", contextLength: 1048576 },
{ id: "gemini-3-6-flash-high", name: "Gemini 3.6 Flash High" },
{ id: "gemini-3-6-flash-medium", name: "Gemini 3.6 Flash Medium" },
{ id: "gemini-3-6-flash-low", name: "Gemini 3.6 Flash Low" },
{ id: "gemini-3-6-flash-minimal", name: "Gemini 3.6 Flash Minimal" },
{ id: "gemini-3-5-flash-high", name: "Gemini 3.5 Flash High", contextLength: 1048576 },
{ id: "gemini-3-5-flash-medium", name: "Gemini 3.5 Flash Medium", contextLength: 1048576 },
{ id: "gemini-3-5-flash-low", name: "Gemini 3.5 Flash Low", contextLength: 1048576 },
{ id: "gemini-3-5-flash-minimal", name: "Gemini 3.5 Flash Minimal", contextLength: 1048576 },
{ id: "gemini-3-7-flash-high", name: "Gemini 3.7 Flash High" },
{ id: "gemini-3-7-flash-medium", name: "Gemini 3.7 Flash Medium" },
{ id: "gemini-3-7-flash-low", name: "Gemini 3.7 Flash Low" },
{ id: "gemini-3-7-flash-minimal", name: "Gemini 3.7 Flash Minimal" },
// Grok
{ id: "grok-4-5-high", name: "Grok 4.5 High", contextLength: 500000 },
{ id: "grok-4-5-medium", name: "Grok 4.5 Medium", contextLength: 500000 },

View File

@@ -1,32 +0,0 @@
/**
* Google AI Studio (Gemini API) Imagen family image-generation provider entry.
*
* Uses the dedicated `:predict` endpoint (handled by format "google-imagen"), NOT
* generateContent — so only imagen-* models belong here; gemini flash-image /
* nano-banana route through /v1/chat/completions instead. The models are also
* surfaced live via ListModels; this seed makes them addressable on
* /v1/images/generations. Note: Imagen requires a billing-enabled Google project —
* free-tier keys get 403 / quota 0. The handler builds `{baseUrl}/{model}:predict`.
*
* Extracted out of imageRegistry.ts (which sits right at the 800-line file-size
* cap) so the catalog lives in its own semantic family module, following the same
* pattern as `providers/registry/stability-ai/imageModels.ts` and
* `providers/registry/segmind/imageModels.ts`. Co-located with the existing
* `gemini/index.ts` chat-provider entry — same provider id, different
* modality/consumer (chat registry vs image registry), mirroring the
* `kie/index.ts` + `kie/imageModels.ts` split.
*/
export const GEMINI_IMAGEN_PROVIDER = {
id: "gemini",
alias: "gemini",
baseUrl: "https://generativelanguage.googleapis.com/v1beta/models",
authType: "apikey",
authHeader: "x-goog-api-key",
format: "google-imagen",
models: [
{ id: "imagen-4.0-generate-001", name: "Imagen 4" },
{ id: "imagen-4.0-ultra-generate-001", name: "Imagen 4 Ultra" },
{ id: "imagen-4.0-fast-generate-001", name: "Imagen 4 Fast" },
],
supportedSizes: ["1024x1024", "1792x1024", "1024x1792"],
};

View File

@@ -22,14 +22,14 @@ export const geminiProvider: RegistryEntry = {
},
models: [
{
id: "gemini-3.1-pro-preview",
name: "Gemini 3.1 Pro Preview",
id: "gemini-3.7-flash",
name: "Gemini 3.7 Flash",
toolCalling: true,
supportsVision: true,
},
{
id: "gemini-3-flash-preview",
name: "Gemini 3 Flash Preview",
id: "gemini-3.1-pro-preview",
name: "Gemini 3.1 Pro Preview",
toolCalling: true,
supportsVision: true,
},
@@ -40,8 +40,8 @@ export const geminiProvider: RegistryEntry = {
supportsVision: true,
},
{
id: "gemini-3.5-flash",
name: "Gemini 3.5 Flash",
id: "gemini-3-flash-preview",
name: "Gemini 3 Flash Preview",
toolCalling: true,
supportsVision: true,
},

View File

@@ -24,8 +24,8 @@ export const gemini_webProvider: RegistryEntry = {
supportsReasoning: false,
},
{
id: "gemini-3.5-flash",
name: "Gemini 3.5 Flash",
id: "gemini-3.7-flash",
name: "Gemini 3.7 Flash",
toolCalling: false,
supportsReasoning: false,
},

View File

@@ -97,14 +97,29 @@ export const gheCopilotProvider: RegistryEntry = {
maxOutputTokens: 64000,
},
{
id: "gemini-3.5-flash",
name: "Gemini 3.5 Flash",
id: "gemini-3.7-flash",
name: "Gemini 3.7 Flash",
contextLength: 1000000,
maxOutputTokens: 64000,
},
{ id: "gpt-5.6-sol", name: "GPT-5.6 Sol", targetFormat: "openai-responses", maxOutputTokens: 128000 },
{ id: "gpt-5.6-terra", name: "GPT-5.6 Terra", targetFormat: "openai-responses", maxOutputTokens: 128000 },
{ id: "gpt-5.6-luna", name: "GPT-5.6 Luna", targetFormat: "openai-responses", maxOutputTokens: 128000 },
{
id: "gpt-5.6-sol",
name: "GPT-5.6 Sol",
targetFormat: "openai-responses",
maxOutputTokens: 128000,
},
{
id: "gpt-5.6-terra",
name: "GPT-5.6 Terra",
targetFormat: "openai-responses",
maxOutputTokens: 128000,
},
{
id: "gpt-5.6-luna",
name: "GPT-5.6 Luna",
targetFormat: "openai-responses",
maxOutputTokens: 128000,
},
{ id: "gpt-5.5", name: "GPT-5.5", ...GPT_5_5_CODEX_CAPABILITIES, maxOutputTokens: 128000 },
{
id: "gpt-5.4",

View File

@@ -117,8 +117,8 @@ export const githubProvider: RegistryEntry = {
maxOutputTokens: 64000,
},
{
id: "gemini-3.5-flash",
name: "Gemini 3.5 Flash",
id: "gemini-3.7-flash",
name: "Gemini 3.7 Flash",
contextLength: 1000000,
maxOutputTokens: 64000,
},

View File

@@ -23,9 +23,6 @@ export const KIE_IMAGE_MODELS: KieImageModelEntry[] = [
{ id: "seedream/5.0-lite-image-to-image", name: "Seedream 5.0 Lite I2I", isMarket: true },
{ id: "z-image/4.0-text-to-image", name: "Z-Image v4.0", isMarket: true },
{ id: "z-image/4.5-text-to-image", name: "Z-Image v4.5", isMarket: true },
{ id: "google-imagen/imagen4-fast", name: "Imagen 4 Fast", isMarket: true },
{ id: "google-imagen/imagen4-ultra", name: "Imagen 4 Ultra", isMarket: true },
{ id: "google-imagen/imagen4", name: "Imagen 4", isMarket: true },
{ id: "google-imagen/nano-banana-2", name: "Nano Banana 2", isMarket: true },
{ id: "google-imagen/nano-banana", name: "Nano Banana", isMarket: true },
{ id: "google-imagen/nano-banana-pro", name: "Nano Banana Pro", isMarket: true },

View File

@@ -12,16 +12,15 @@ export const kieProvider: RegistryEntry = {
models: [
// Sweep 2026-06-19: + current flagships the kie proxy surfaces. gemini-3-pro was
// skipped (registry already carries the newer gemini-3-1-pro).
{ id: "claude-opus-4-8", name: "Claude 4.8 Opus" },
{ id: "claude-opus-4-7", name: "Claude 4.7 Opus" },
{ id: "claude-sonnet-4-6", name: "Claude 4.6 Sonnet" },
{ id: "claude-fable-5", name: "Claude 5 Fable" },
{ id: "claude-opus-5", name: "Claude 5 Opus" },
{ id: "claude-sonnet-5", name: "Claude 5 Sonnet" },
{ id: "claude-haiku-4-5", name: "Claude 4.5 Haiku" },
{ id: "gpt-5-5", name: "GPT 5.5" },
{ id: "gpt-5-4", name: "GPT 5.4" },
{ id: "gpt-5-2", name: "GPT 5.2" },
{ id: "gpt-5-6-sol", name: "GPT 5.6 Sol" },
{ id: "gpt-5-6-terra", name: "GPT 5.6 Terra" },
{ id: "gpt-5-6-luna", name: "GPT 5.6 Luna" },
{ id: "gemini-3-1-pro", name: "Gemini 3.1 Pro" },
{ id: "gemini-2-5-pro", name: "Gemini 2.5 Pro" },
{ id: "gemini-3-flash", name: "Gemini 3 Flash" },
{ id: "gemini-3-5-flash", name: "Gemini 3.5 Flash" },
{ id: "gemini-3-7-flash", name: "Gemini 3.7 Flash" },
{ id: "grok-4-6", name: "Grok 4.6" },
],
};

View File

@@ -11,9 +11,6 @@ export const KIE_IMAGE_MODELS = [
{ id: "seedream/5.0-lite-image-to-image", name: "Seedream 5.0 Lite I2I", isMarket: true },
{ id: "z-image/4.0-text-to-image", name: "Z-Image v4.0", isMarket: true },
{ id: "z-image/4.5-text-to-image", name: "Z-Image v4.5", isMarket: true },
{ id: "google-imagen/imagen4-fast", name: "Imagen 4 Fast", isMarket: true },
{ id: "google-imagen/imagen4-ultra", name: "Imagen 4 Ultra", isMarket: true },
{ id: "google-imagen/imagen4", name: "Imagen 4", isMarket: true },
{ id: "google-imagen/nano-banana-2", name: "Nano Banana 2", isMarket: true },
{ id: "google-imagen/nano-banana", name: "Nano Banana", isMarket: true },
{ id: "google-imagen/nano-banana-pro", name: "Nano Banana Pro", isMarket: true },

View File

@@ -25,19 +25,20 @@ export const kilocodeProvider: RegistryEntry = {
},
models: [
{ id: "openrouter/free", name: "Free Models Router" },
{ id: "qwen/qwen3.6-plus", name: "Qwen3.6 Plus" },
{ id: "qwen/qwen3.5-397b-a17b", name: "Qwen3.5 397B A17B" },
{ id: "openai/gpt-5.5", name: "GPT-5.5" },
{ id: "openai/gpt-5.4-mini", name: "GPT-5.4 Mini" },
{ id: "anthropic/claude-opus-4.7", name: "Claude Opus 4.7" },
{ id: "anthropic/claude-sonnet-4.6", name: "Claude Sonnet 4.6" },
{ id: "openai/gpt-5.6-sol", name: "GPT-5.6 Sol" },
{ id: "openai/gpt-5.6-terra", name: "GPT-5.6 Terra" },
{ id: "openai/gpt-5.6-luna", name: "GPT-5.6 Luna" },
{ id: "anthropic/claude-opus-5", name: "Claude Opus 5" },
{ id: "anthropic/claude-sonnet-5", name: "Claude Sonnet 5" },
{ id: "anthropic/claude-haiku-4.5", name: "Claude Haiku 4.5" },
{ id: "google/gemini-3.1-pro-preview", name: "Gemini 3.1 Pro" },
{ id: "google/gemini-3-flash-preview", name: "Gemini 3 Flash" },
{ id: "google/gemini-3.1-flash-lite", name: "Gemini 3.1 Flash Lite" },
{ id: "deepseek/deepseek-v4-pro", name: "DeepSeek V4 Pro", supportsReasoning: true },
{ id: "deepseek/deepseek-v4-flash", name: "DeepSeek V4 Flash", supportsReasoning: true },
{ id: "moonshotai/kimi-k2.6", name: "Kimi K2.6" },
{ id: "google/gemini-3.7-flash", name: "Gemini 3.7 Flash" },
{ id: "google/gemini-3.5-flash-lite", name: "Gemini 3.5 Flash Lite" },
{ id: "qwen/qwen3.8-max", name: "Qwen3.8 Max" },
{ id: "qwen/qwen3.7-plus", name: "Qwen3.7 Plus" },
{ id: "deepseek/deepseek-v4-pro-0813", name: "DeepSeek V4 Pro", supportsReasoning: true },
{ id: "deepseek/deepseek-v4-flash-0731", name: "DeepSeek V4 Flash", supportsReasoning: true },
{ id: "moonshotai/kimi-k3", name: "Kimi K3" },
],
passthroughModels: true,
};

View File

@@ -90,10 +90,10 @@ export const LMARENA_DIRECT_MODEL_ENTRIES: readonly LmarenaDirectModelEntry[] =
category: "Text",
},
{
catalogId: "gemini-3.5-flash-high",
arenaId: "019f406f-fc33-7b9d-9571-7b8443bc7ca0",
publicName: "gemini-3.5-flash-high",
displayName: "gemini-3.5-flash-high",
catalogId: "gemini-3.6-flash",
arenaId: "019f90b1-c0ac-71ce-b295-487f261bf0f4",
publicName: "gemini-3.6-flash",
displayName: "gemini-3.6-flash",
organization: "google",
vision: true,
category: "Text",

View File

@@ -25,55 +25,48 @@ export const opencode_zenProvider: RegistryEntry = {
supportsReasoning: true,
interleavedField: "reasoning_content",
},
{ id: "gpt-5-nano", name: "GPT 5 Nano", contextLength: 400000 },
{ id: "gpt-5", name: "GPT 5" },
{ id: "gpt-5-codex", name: "GPT 5 Codex" },
{ id: "gpt-5.1", name: "GPT 5.1" },
{ id: "gpt-5.1-codex", name: "GPT 5.1 Codex" },
{ id: "gpt-5.1-codex-max", name: "GPT 5.1 Codex Max" },
{ id: "gpt-5.1-codex-mini", name: "GPT 5.1 Codex Mini" },
{ id: "gpt-5.2", name: "GPT 5.2" },
{ id: "gpt-5.2-codex", name: "GPT 5.2 Codex" },
{ id: "gpt-5.3-codex", name: "GPT 5.3 Codex" },
{ id: "gpt-5.3-codex-spark", name: "GPT 5.3 Codex Spark" },
{ id: "gpt-5.6-sol", name: "GPT 5.6 Sol" },
{ id: "gpt-5.6-terra", name: "GPT 5.6 Terra" },
{ id: "gpt-5.6-luna", name: "GPT 5.6 Luna" },
{ id: "gpt-5.4", name: "GPT 5.4" },
{ id: "gpt-5.4-mini", name: "GPT 5.4 Mini" },
{ id: "gpt-5.4-nano", name: "GPT 5.4 Nano" },
{ id: "gpt-5.4-pro", name: "GPT 5.4 Pro" },
{ id: "gpt-5.5", name: "GPT 5.5" },
{ id: "gpt-5.5-pro", name: "GPT 5.5 Pro" },
{ id: "gpt-5.3-codex-spark", name: "GPT 5.3 Codex Spark" },
{ id: "gpt-5.1", name: "GPT 5.1" },
// ── Claude ─────────────────────────────────────────────────
{ id: "claude-fable-5", name: "Claude Fable 5" },
{ id: "claude-opus-5", name: "Claude Opus 5" },
{ id: "claude-sonnet-5", name: "Claude Sonnet 5" },
{ id: "claude-haiku-4-5", name: "Claude Haiku 4.5" },
{ id: "claude-sonnet-4", name: "Claude Sonnet 4" },
{ id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" },
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
{ id: "claude-opus-4-1", name: "Claude Opus 4.1" },
{ id: "claude-opus-4-5", name: "Claude Opus 4.5" },
{ id: "claude-opus-4-6", name: "Claude Opus 4.6" },
{ id: "claude-opus-4-7", name: "Claude Opus 4.7" },
// ── Gemini ─────────────────────────────────────────────────
{ id: "gemini-3-flash", name: "Gemini 3 Flash" },
{ id: "gemini-3.7-flash", name: "Gemini 3.7 Flash" },
{ id: "gemini-3.5-flash-lite", name: "Gemini 3.5 Flash Lite" },
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
{ id: "gemini-3.5-flash", name: "Gemini 3.5 Flash" },
{ id: "gemini-3-flash", name: "Gemini 3 Flash" },
// ── Grok ───────────────────────────────────────────────────
{ id: "grok-build-0.1", name: "Grok Build 0.1" },
{ id: "grok-4.6", name: "Grok 4.6" },
// ── Muse ───────────────────────────────────────────────────
{ id: "muse-spark-1.2", name: "Muse Spark 1.2" },
// ── DeepSeek ────────────────────────────────────────────────
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro" },
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash" },
// ── GLM / Z.AI ─────────────────────────────────────────────
{ id: "glm-5", name: "GLM-5" },
{ id: "glm-5.1", name: "GLM-5.1" },
{ id: "glm-5.2", name: "GLM-5.2" },
// ── MiniMax ────────────────────────────────────────────────
// #3110: MiniMax M3 — frontier coding model with 1M context
{ id: "minimax-m3", name: "MiniMax M3", contextLength: 1048576, supportsVision: true },
{ id: "minimax-m2.5", name: "MiniMax M2.5" },
{ id: "minimax-m2.7", name: "MiniMax M2.7" },
// ── Kimi / Moonshot ────────────────────────────────────────
{ id: "kimi-k2.5", name: "Kimi K2.5" },
{ id: "kimi-k2.6", name: "Kimi K2.6" },
{ id: "kimi-k3", name: "Kimi K3" },
{ id: "kimi-k2.7-code", name: "Kimi K2.7 Code" },
// ── Qwen ───────────────────────────────────────────────────
// Issue #2292: Qwen models return Claude-format SSE bodies even
@@ -86,12 +79,14 @@ export const opencode_zenProvider: RegistryEntry = {
// ── Free Tier ──────────────────────────────────────────────
// #6998 (2026-07-14): upstream free tier rotated — minimax-m2.5-free,
// nemotron-3-super-free and qwen3.6-plus-free were delisted (401). Replaced
// by the 4 entries below with upstream-verified limits.
// nemotron-3-super-free and qwen3.6-plus-free were delisted (401).
// 2026-08-17 sync: north-mini-code-free delisted; nemotron-3.5-lightning-free
// and laguna-s-2.1-free added.
{ id: "deepseek-v4-flash-free", name: "DeepSeek V4 Flash Free", supportsReasoning: true },
{ id: "mimo-v2.5-free", name: "MiMo V2.5 Free", contextLength: 200000 },
{ id: "hy3-free", name: "HY3 Free", contextLength: 200000 },
{ id: "nemotron-3-ultra-free", name: "Nemotron 3 Ultra Free", contextLength: 1000000 },
{ id: "north-mini-code-free", name: "North Mini Code Free", contextLength: 200000 },
{ id: "nemotron-3.5-lightning-free", name: "Nemotron 3.5 Lightning Free" },
{ id: "laguna-s-2.1-free", name: "Laguna S 2.1 Free" },
],
};

View File

@@ -35,8 +35,8 @@ export const orcarouterProvider: RegistryEntry = {
maxOutputTokens: 128000,
},
{
id: "google/gemini-3.5-flash",
name: "Gemini 3.5 Flash",
id: "google/gemini-3.6-flash",
name: "Gemini 3.6 Flash",
toolCalling: true,
supportsReasoning: true,
supportsVision: true,

View File

@@ -13,12 +13,12 @@ export const perplexity_webProvider: RegistryEntry = {
{ id: "pplx-sonar", name: "Sonar 2 (via Perplexity)", toolCalling: false },
{ id: "pplx-gpt-5.6-terra", name: "GPT-5.6 Terra (via Perplexity)", toolCalling: false },
{ id: "pplx-gpt-5.6-sol", name: "GPT-5.6 Sol (via Perplexity)", toolCalling: false },
{ id: "pplx-gemini", name: "Gemini 3.1 Pro (via Perplexity)", toolCalling: false },
{ id: "pplx-gemini", name: "Gemini 3.7 Flash (via Perplexity)", toolCalling: false },
{ id: "pplx-sonnet", name: "Claude Sonnet 5.0 (via Perplexity)", toolCalling: false },
{ id: "pplx-opus", name: "Claude Opus 5.0 (via Perplexity)", toolCalling: false },
{ id: "pplx-glm", name: "GLM-5.2 (via Perplexity)", toolCalling: false },
{ id: "pplx-kimi", name: "Kimi K2.6 (via Perplexity)", toolCalling: false },
{ id: "pplx-grok-4.5", name: "Grok 4.5 (via Perplexity)", toolCalling: false },
{ id: "pplx-kimi", name: "Kimi K3 (via Perplexity)", toolCalling: false },
{ id: "pplx-grok-4.6", name: "Grok 4.6 (via Perplexity)", toolCalling: false },
{ id: "pplx-nemotron", name: "Nemotron 3 Ultra (via Perplexity)", toolCalling: false },
],
};

View File

@@ -19,15 +19,43 @@ export const raycastProvider: RegistryEntry = {
authHeader: "bearer",
defaultContextLength: 128000,
models: [
{ id: "openai-gpt-5-mini", name: "GPT-5 Mini" },
{ id: "openai-gpt-4o-mini", name: "GPT-4o Mini" },
{ id: "anthropic-claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
{ id: "google-gemini-2.5-flash", name: "Gemini 2.5 Flash" },
{ id: "raycast-ray1", name: "Ray1" },
{ id: "raycast-ray1-mini", name: "Ray1 Mini" },
{ id: "perplexity-sonar", name: "Sonar" },
//GPT
{ id: "openai-gpt-5.6-sol", name: "GPT-5.6 Sol" },
{ id: "openai-gpt-5.6-terra", name: "GPT-5.6 Terra" },
{ id: "openai-gpt-5.6-luna", name: "GPT-5.6 Luna" },
//Claude
{ id: "anthropic-claude-opus-5", name: "Claude Opus 5" },
{ id: "anthropic-claude-sonnet-5", name: "Claude Sonnet 5" },
{ id: "anthropic-claude-4-5-haiku-reasoning", name: "Claude 4.5 Haiku Reasoning" },
{ id: "anthropic-claude-4-5-haiku", name: "Claude 4.5 Haiku" },
//Gemini
{ id: "google-gemini-3.1-pro", name: "Gemini 3.1 Pro" },
{ id: "google-gemini-3.7-flash", name: "Gemini 3.7 Flash" },
{ id: "google-gemini-3.5-flash-lite", name: "Gemini 3.5 Flash Lite" },
//Perplexity
{ id: "perplexity-sonar-reasoning-pro", name: "Sonar Reasoning Pro" },
{ id: "perplexity-sonar-pro", name: "Sonar Pro" },
{ id: "perplexity-sonar", name: "Sonar" },
//Mistral
{ id: "mistral-mistral-large-latest", name: "Mistral Large" },
{ id: "mistral-mistral-medium-latest", name: "Mistral Medium" },
{ id: "mistral-mistral-small-latest", name: "Mistral Small" },
{ id: "mistral-codestral-latest", name: "Codestral" },
{ id: "mistral-open-mistral-nemo", name: "Mistral Nemo" },
{ id: "xai-grok-3-mini", name: "Grok 3 Mini" },
//Grok
{ id: "xai-grok-4.6", name: "Grok 4.6" },
//Opensource
{ id: "gateway-alibaba/qwen3.8-max", name: "Qwen 3.8 Max" },
{ id: "gateway-moonshotai/kimi-k3", name: "Kimi K3" },
{ id: "baseten-deepseek-ai/DeepSeek-V4-Pro", name: "DeepSeek V4 Pro" },
{ id: "gateway-deepseek/deepseek-v4-flash", name: "DeepSeek V4 Flash" },
{ id: "baseten-zai-org/GLM-5.2", name: "GLM 5.2" },
{ id: "gateway-thinkingmachines/inkling-1.0", name: "Inkling 1.0" },
{ id: "gateway-google/gemma-4-31b-it", name: "Gemma 4 31B" },
{ id: "groq-openai/gpt-oss-120b", name: "GPT-OSS 120B" },
{ id: "groq-openai/gpt-oss-20b", name: "GPT-OSS 20B" },
{ id: "groq-qwen/qwen3-32b", name: "Qwen 3 32B" },
{ id: "groq-llama-3.3-70b-versatile", name: "LLaMA 3.3 70B" },
{ id: "groq-llama-3.1-8b-instant", name: "LLaMA 3.1 8B" },
],
};

View File

@@ -18,22 +18,28 @@ export const tinycmsProvider: RegistryEntry = {
authType: "apikey",
authHeader: "uuid",
models: [
{ id: "gpt-5-free", name: "GPT 5 Free" },
{ id: "gpt-5.3-free", name: "GPT 5.3 Free (Multimodal/Vision)" },
{ id: "gpt-5.3-thinking-free", name: "GPT 5.3 Thinking Free", supportsReasoning: true },
{ id: "claude-fable-5", name: "Claude Fable 5" },
{ id: "claude-opus-5", name: "Claude Opus 5" },
{ id: "claude-sonnet-5", name: "Claude Sonnet 5" },
{ id: "gpt-5.6-sol", name: "GPT 5.6 Sol" },
{ id: "gpt-5.6-luna", name: "GPT 5.6 Luna" },
{ id: "gpt-5.5", name: "GPT 5.5" },
{ id: "gpt-5.4-mini", name: "GPT 5.4 Mini" },
{ id: "gpt-5.4-nano", name: "GPT 5.4 Nano" },
{ id: "gpt-5-nano", name: "GPT 5 Nano" },
{ id: "gemini-3.5-flash", name: "Gemini 3.5 Flash" },
{ id: "gemini-3-pro-preview", name: "Gemini 3 Pro Preview" },
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview" },
{ id: "grok-4.20-fast", name: "Grok 4.20 Fast" },
{ id: "grok-4.20", name: "Grok 4.20" },
{ id: "grok-imagine", name: "Grok Imagine (Image Gen)" },
{ id: "gpt-5.3-thinking-free", name: "GPT 5.3 Thinking Free", supportsReasoning: true },
{ id: "gpt-5.3-free", name: "GPT 5.3 Free (Multimodal/Vision)" },
{ id: "gpt-oss-120b", name: "GPT-OSS 120B" },
{ id: "gemini-3.6-flash", name: "Gemini 3.6 Flash" },
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro" },
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite" },
{ id: "grok-4.5", name: "Grok 4.5" },
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro" },
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash" },
{ id: "claude-sonnet-5", name: "Claude Sonnet 5" },
{ id: "gpt-image-2", name: "GPT Image 2 (Image Gen)" },
{ id: "kimi-k3", name: "Kimi K3" },
{ id: "glm-5.2", name: "GLM 5.2" },
{ id: "qwen3.6-plus", name: "Qwen 3.6 Plus" },
{ id: "mimo-v2.5-pro", name: "Mimo V2.5 Pro" },
{ id: "mimo-v2.5", name: "Mimo V2.5" },
],
};

View File

@@ -1,4 +1,5 @@
import { getExplicitModelOutputCap } from "@/lib/modelCapabilities";
import { isDiscoverableAntigravityModelId } from "../config/antigravityModelAliases";
/**
* Fallback ceiling on `generationConfig.maxOutputTokens` for Antigravity
@@ -13,7 +14,7 @@ import { getExplicitModelOutputCap } from "@/lib/modelCapabilities";
* Both of those models are catalogue-known today, so neither one reaches this
* constant anymore: they get their own declared limit via
* `resolveAntigravityOutputCap` (65536 and 65535, respectively). The higher
* limit holds against the live upstream. A gemini-3.6-flash-high request came
* limit holds against the live upstream. A gemini-3.7-flash-high request came
* back with completion_tokens 16754 and finish_reason "stop", which exceeds
* 16384 on its own and so cannot be an artifact of thinking-token accounting.
*
@@ -38,6 +39,10 @@ export const MAX_ANTIGRAVITY_OUTPUT_TOKENS = 16384;
export function resolveAntigravityOutputCap(modelId: string | null | undefined): number {
const id = typeof modelId === "string" ? modelId.trim() : "";
if (!id) return MAX_ANTIGRAVITY_OUTPUT_TOKENS;
// MODEL_SPECS is provider-neutral: other providers may continue serving old
// Gemini 3.5/3.6 ids after Antigravity retires them. Do not let those shared
// specs make a retired Antigravity id look active on this provider path.
if (!isDiscoverableAntigravityModelId(id)) return MAX_ANTIGRAVITY_OUTPUT_TOKENS;
try {
const declared = getExplicitModelOutputCap({ provider: "antigravity", model: id });
return typeof declared === "number" && Number.isFinite(declared) && declared > 0

View File

@@ -69,24 +69,26 @@ export const MODEL_MAP: Record<string, [string, string]> = {
"pplx-sonar": ["copilot", "turbo"],
"pplx-gpt-5.6-terra": ["copilot", "gpt56_terra"],
"pplx-gpt-5.6-sol": ["copilot", "gpt56_sol"],
"pplx-gemini": ["copilot", "gemini31pro_high"],
"pplx-gemini": ["copilot", "gemini37flash"],
"pplx-sonnet": ["copilot", "claude50sonnet"],
// Perplexity's catalog moved Opus to 5.0; claude48opus is still accepted but
// answers from the older model.
"pplx-opus": ["copilot", "claude50opus"],
"pplx-glm": ["copilot", "glm_5_2"],
"pplx-kimi": ["copilot", "kimik26instant"],
"pplx-grok-4.5": ["copilot", "grok45low"],
// The current Kimi K3 catalog entry only exposes its reasoning model.
"pplx-kimi": ["copilot", "kimik3thinking"],
"pplx-grok-4.6": ["copilot", "grok46low"],
"pplx-nemotron": ["copilot", "nv_nemotron_3_ultra"],
};
export const THINKING_MAP: Record<string, string> = {
"pplx-gpt-5.6-terra": "gpt56_terra_thinking",
"pplx-gpt-5.6-sol": "gpt56_sol_thinking",
"pplx-gemini": "gemini37flashthinking",
"pplx-sonnet": "claude50sonnetthinking",
"pplx-opus": "claude50opusthinking",
"pplx-kimi": "kimik26thinking",
"pplx-grok-4.5": "grok45medium",
"pplx-kimi": "kimik3thinking",
"pplx-grok-4.6": "grok46medium",
};
export const CITATION_RE = /\[\d+\]/g;

View File

@@ -41,7 +41,6 @@ import { handleHyperbolicImageGeneration } from "./imageGeneration/providers/hyp
import { handleHuggingFaceImageGeneration } from "./imageGeneration/providers/huggingface.ts";
import { handleComfyUIImageGeneration } from "./imageGeneration/providers/comfyUI.ts";
import { handleImagen3ImageGeneration } from "./imageGeneration/providers/imagen3.ts";
import { handleGoogleImagenGeneration } from "./imageGeneration/providers/googleImagen.ts";
import { handleIdeogramImageGeneration } from "./imageGeneration/providers/ideogram.ts";
import { handleHaiperImageGeneration } from "./imageGeneration/providers/haiper.ts";
import { handleLeonardoImageGeneration } from "./imageGeneration/providers/leonardo.ts";
@@ -383,17 +382,6 @@ export async function handleImageGeneration({
});
}
if (providerConfig.format === "google-imagen") {
return handleGoogleImagenGeneration({
model,
provider,
providerConfig,
body,
credentials,
log,
});
}
if (providerConfig.format === "hyperbolic") {
return handleHyperbolicImageGeneration({
model,

View File

@@ -1,147 +0,0 @@
// Google AI Studio (Gemini API) Imagen image generation.
//
// Unlike the antigravity "gemini-image" format (which wraps generateContent in a
// Cloud Code envelope), the Imagen family on generativelanguage.googleapis.com uses
// the dedicated ":predict" endpoint with an instances/parameters body and returns
// base64 image bytes under `predictions[].bytesBase64Encoded`.
//
// Docs: https://ai.google.dev/gemini-api/docs/imagen (Imagen requires a billing-
// enabled Google project; free-tier keys get 403 / quota 0.)
import { saveCallLog } from "@/lib/usageDb";
import { mapImageSize } from "../../../translator/image/sizeMapper.ts";
import { sanitizeErrorMessage } from "../../../utils/error.ts";
// Only the Imagen family routes through :predict. Other gemini image models
// (gemini-*-flash-image / nano-banana) use generateContent and belong on the chat
// route, so they must not be dispatched here.
export function isImagenModel(model) {
return /^imagen-/i.test(String(model || ""));
}
/**
* Build the Imagen :predict request body from an OpenAI-style image request.
* Pure — no I/O — so it can be unit-tested without live credentials.
*/
export function buildImagenPredictBody(body) {
const prompt = typeof body?.prompt === "string" ? body.prompt : String(body?.prompt ?? "");
const n = Number(body?.n);
const sampleCount = Number.isFinite(n) && n > 0 ? Math.min(Math.floor(n), 4) : 1;
return {
instances: [{ prompt }],
parameters: {
sampleCount,
aspectRatio: mapImageSize(body?.aspect_ratio || body?.size),
},
};
}
/**
* Normalize an Imagen :predict response into the OpenAI image-generation shape
* ({ created, data: [{ b64_json, revised_prompt }] }). Pure — unit-testable.
*/
export function parseImagenPredictResponse(data, prompt) {
const predictions = Array.isArray(data?.predictions) ? data.predictions : [];
const images = [];
for (const p of predictions) {
const b64 = p?.bytesBase64Encoded ?? p?.b64_json ?? p?.image ?? null;
if (typeof b64 === "string" && b64.length > 0) {
images.push({ b64_json: b64, revised_prompt: prompt });
}
}
return { created: Math.floor(Date.now() / 1000), data: images };
}
export async function handleGoogleImagenGeneration({
model,
provider,
providerConfig,
body,
credentials,
log,
}) {
const startTime = Date.now();
const token = credentials?.apiKey || credentials?.accessToken || "";
const prompt = typeof body.prompt === "string" ? body.prompt : String(body.prompt ?? "");
if (!isImagenModel(model)) {
return {
success: false,
status: 400,
error: `Model ${model} is not an Imagen model. Gemini flash-image models route through /v1/chat/completions, not /v1/images/generations.`,
};
}
const upstreamBody = buildImagenPredictBody(body);
// baseUrl is https://generativelanguage.googleapis.com/v1beta/models
const url = `${providerConfig.baseUrl.replace(/\/$/, "")}/${model}:predict`;
if (log) {
log.info(
"IMAGE",
`${provider}/${model} (google-imagen) | prompt: "${prompt.slice(0, 60)}..." | aspectRatio: ${upstreamBody.parameters.aspectRatio}`
);
}
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
// Key travels in the header, never the URL, so it stays out of logs.
"x-goog-api-key": token,
},
body: JSON.stringify(upstreamBody),
});
if (!response.ok) {
const errorText = await response.text();
const safeError = sanitizeErrorMessage(errorText);
if (log) log.error("IMAGE", `${provider} error ${response.status}: ${safeError.slice(0, 200)}`);
saveCallLog({
method: "POST",
path: "/v1/images/generations",
status: response.status,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: safeError.slice(0, 500),
}).catch(() => {});
return { success: false, status: response.status, error: safeError };
}
const data = await response.json();
const normalized = parseImagenPredictResponse(data, prompt);
saveCallLog({
method: "POST",
path: "/v1/images/generations",
status: 200,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
responseBody: { images_count: normalized.data.length },
}).catch(() => {});
return { success: true, data: normalized };
} catch (err) {
const errMsg = err instanceof Error ? err.message : String(err);
if (log) log.error("IMAGE", `${provider} fetch error: ${errMsg}`);
saveCallLog({
method: "POST",
path: "/v1/images/generations",
status: 502,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: errMsg,
}).catch(() => {});
return {
success: false,
status: 502,
error: `Image provider error: ${sanitizeErrorMessage(errMsg)}`,
};
}
}

View File

@@ -22,24 +22,24 @@ describe("Antigravity account quota-family cooldown", () => {
});
it("maps Gemini variants to Gemini family and Claude/Cloud variants to Claude family", () => {
expect(getAntigravityQuotaFamily("gemini-3.5-flash-medium")).toBe("gemini");
expect(getAntigravityQuotaFamily("google/gemini-3.5-flash-low")).toBe("gemini");
expect(getAntigravityQuotaFamily("agy/gemini-3.5-flash-medium")).toBe("gemini");
expect(getAntigravityQuotaFamily("gemini-3.7-flash-medium")).toBe("gemini");
expect(getAntigravityQuotaFamily("google/gemini-3.7-flash-low")).toBe("gemini");
expect(getAntigravityQuotaFamily("agy/gemini-3.7-flash-medium")).toBe("gemini");
expect(getAntigravityQuotaFamily("claude-sonnet-4")).toBe("claude");
expect(getAntigravityQuotaFamily("cloud/claude-opus-4")).toBe("claude");
expect(getAntigravityQuotaFamily("some-new-model")).toBe("other");
});
it("uses family-scoped lock key for Antigravity but preserves exact-model scope elsewhere", () => {
expect(getQuotaScopedModelForProvider("antigravity", "gemini-3.5-flash-medium")).toBe(
expect(getQuotaScopedModelForProvider("antigravity", "gemini-3.7-flash-medium")).toBe(
"family:gemini"
);
expect(getQuotaScopedModelForProvider("agy", "gemini-3.5-flash-medium")).toBe("family:gemini");
expect(getQuotaScopedModelForProvider(provider, "gemini-3.5-flash-low")).toBe("family:gemini");
expect(getQuotaScopedModelForProvider("agy", "gemini-3.7-flash-medium")).toBe("family:gemini");
expect(getQuotaScopedModelForProvider(provider, "gemini-3.7-flash-low")).toBe("family:gemini");
expect(getQuotaScopedModelForProvider(provider, "claude-sonnet-4")).toBe("family:claude");
expect(getQuotaScopedModelForProvider(provider, "unknown-model")).toBe("unknown-model");
expect(getQuotaScopedModelForProvider("openai", "gemini-3.5-flash-medium")).toBe(
"gemini-3.5-flash-medium"
expect(getQuotaScopedModelForProvider("openai", "gemini-3.7-flash-medium")).toBe(
"gemini-3.7-flash-medium"
);
});
@@ -47,7 +47,7 @@ describe("Antigravity account quota-family cooldown", () => {
recordModelLockoutFailure(
provider,
"account-a",
"gemini-3.5-flash-medium",
"gemini-3.7-flash-medium",
"rate_limited",
429,
60_000,
@@ -55,10 +55,10 @@ describe("Antigravity account quota-family cooldown", () => {
{ maxCooldownMs: 300_000 }
);
expect(isModelLocked(provider, "account-a", "gemini-3.5-flash-medium")).toBe(true);
expect(isModelLocked(provider, "account-a", "gemini-3.5-flash-low")).toBe(true);
expect(isModelLocked(provider, "account-a", "gemini-3.7-flash-medium")).toBe(true);
expect(isModelLocked(provider, "account-a", "gemini-3.7-flash-low")).toBe(true);
expect(isModelLocked(provider, "account-a", "claude-sonnet-4")).toBe(false);
expect(isModelLocked(provider, "account-b", "gemini-3.5-flash-low")).toBe(false);
expect(isModelLocked(provider, "account-b", "gemini-3.7-flash-low")).toBe(false);
});
it("keeps Claude/Cloud family distinct from Gemini", () => {
@@ -74,7 +74,7 @@ describe("Antigravity account quota-family cooldown", () => {
);
expect(isModelLocked(provider, "account-a", "cloud/claude-opus-4")).toBe(true);
expect(isModelLocked(provider, "account-a", "gemini-3.5-flash-low")).toBe(false);
expect(isModelLocked(provider, "account-a", "gemini-3.7-flash-low")).toBe(false);
});
it("can isolate a confirmed Antigravity quota exhaustion to one exact model", () => {
@@ -88,7 +88,7 @@ describe("Antigravity account quota-family cooldown", () => {
expect(isModelLocked(provider, "account-a", "claude-opus-4-6-thinking")).toBe(true);
expect(isModelLocked(provider, "account-a", "claude-sonnet-4-6-thinking")).toBe(false);
expect(isModelLocked(provider, "account-a", "gemini-3.5-flash-medium")).toBe(false);
expect(isModelLocked(provider, "account-a", "gemini-3.7-flash-medium")).toBe(false);
expect(clearModelLock(provider, "account-a", "claude-opus-4-6-thinking")).toBe(true);
expect(isModelLocked(provider, "account-a", "claude-opus-4-6-thinking")).toBe(false);
@@ -113,7 +113,7 @@ describe("Antigravity account quota-family cooldown", () => {
const upstream = recordModelLockoutFailure(
provider,
"account-a",
"gemini-3.5-flash-medium",
"gemini-3.7-flash-medium",
"rate_limited",
429,
1_000,
@@ -122,13 +122,13 @@ describe("Antigravity account quota-family cooldown", () => {
);
expect(upstream.cooldownMs).toBe(123_000);
expect(
getModelLockoutInfo(provider, "account-a", "gemini-3.5-flash-low")?.remainingMs
getModelLockoutInfo(provider, "account-a", "gemini-3.7-flash-low")?.remainingMs
).toBeGreaterThan(100_000);
const inferred = recordModelLockoutFailure(
provider,
"account-b",
"gemini-3.5-flash-medium",
"gemini-3.7-flash-medium",
"rate_limited",
429,
1_000,

View File

@@ -49,7 +49,6 @@ const FALLBACK_MODEL_SEEDS: FallbackModelSeed[] = [
{ id: "claude-opus-5", vision: true, efforts: EFFORTS_XHIGH },
{ id: "claude-opus-4-8", vision: true, efforts: EFFORTS_XHIGH },
{ id: "claude-fable-5", vision: true, efforts: EFFORTS_XHIGH },
{ id: "claude-opus-4-7", vision: true, efforts: EFFORTS_XHIGH },
{ id: "claude-sonnet-5", vision: true, efforts: EFFORTS_NO_XHIGH },
{ id: "claude-sonnet-4-6", vision: true, efforts: EFFORTS_NO_XHIGH },
{ id: "claude-haiku-4-5", vision: true, efforts: EFFORTS_STANDARD },
@@ -61,7 +60,6 @@ const FALLBACK_MODEL_SEEDS: FallbackModelSeed[] = [
{ id: "deepseek/deepseek-v4-pro", vision: false, efforts: EFFORTS_HIGH_ONLY },
{ id: "openrouter/fusion", vision: false, efforts: [] },
{ id: "z-ai/glm-5.2", vision: false, efforts: EFFORTS_STANDARD },
{ id: "z-ai/glm-5.1", vision: false, efforts: EFFORTS_STANDARD },
{ id: "tencent/hy3", vision: false, efforts: EFFORTS_STANDARD },
{ id: "moonshotai/kimi-k3", vision: true, efforts: EFFORTS_STANDARD },
{ id: "moonshotai/kimi-k2.7-code", vision: true, efforts: EFFORTS_STANDARD },
@@ -69,7 +67,7 @@ const FALLBACK_MODEL_SEEDS: FallbackModelSeed[] = [
{ id: "qwen/qwen3.7-max", vision: false, efforts: EFFORTS_STANDARD },
{ id: "minimax/minimax-m3", vision: true, efforts: EFFORTS_STANDARD },
{ id: "stepfun/step-3.7-flash", vision: true, efforts: EFFORTS_STANDARD },
{ id: "google/gemini-3.5-flash", vision: true, efforts: EFFORTS_STANDARD },
{ id: "google/gemini-3.7-flash", vision: true, efforts: EFFORTS_STANDARD },
{ id: "google/gemini-3.1-pro-preview", vision: true, efforts: EFFORTS_STANDARD },
{ id: "google/gemini-3.1-flash-lite", vision: true, efforts: EFFORTS_STANDARD },
{ id: "x-ai/grok-4.3", vision: true, efforts: EFFORTS_STANDARD },

View File

@@ -32,7 +32,7 @@ export const GITHUB_COPILOT_MODEL_ALLOWLIST = [
"claude-sonnet-4.5",
"claude-haiku-4.5",
"gemini-3.1-pro-preview",
"gemini-3.5-flash",
"gemini-3.7-flash",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",

View File

@@ -30,84 +30,96 @@ export type NotionDiscoveredModel = {
*/
export const NOTION_WEB_FALLBACK_MODELS: NotionDiscoveredModel[] = [
{ id: "notion-ai", name: "Notion AI (default)", owned_by: "notion" },
{ id: "gpt-5.6-sol", name: "GPT-5.6 Sol", owned_by: "openai", notionCodename: "orange-mousse" },
{
id: "gpt-5.6-sol",
name: "GPT-5.6 Sol",
owned_by: "openai",
supportsReasoning: true,
notionCodename: "orange-mousse",
},
{
id: "gpt-5.6-terra",
name: "GPT-5.6 Terra",
owned_by: "openai",
supportsReasoning: true,
notionCodename: "orchid-muffin",
},
{
id: "gpt-5.6-luna",
name: "GPT-5.6 Luna",
owned_by: "openai",
supportsReasoning: true,
notionCodename: "olive-jellyroll",
},
{ id: "gpt-5.2", name: "GPT-5.2", owned_by: "openai", notionCodename: "oatmeal-cookie" },
{ id: "gpt-5.4", name: "GPT-5.4", owned_by: "openai", notionCodename: "oval-kumquat-medium" },
{ id: "gpt-5.5", name: "GPT-5.5", owned_by: "openai", notionCodename: "opal-quince-medium" },
{
id: "gpt-5.4-mini",
name: "GPT-5.4 Mini",
owned_by: "openai",
supportsReasoning: true,
notionCodename: "oregon-grape-medium",
},
{
id: "gpt-5.4-nano",
name: "GPT-5.4 Nano",
owned_by: "openai",
supportsReasoning: true,
notionCodename: "otaheite-apple-medium",
},
{
id: "gemini-3.5-flash",
name: "Gemini 3.5 Flash",
id: "gemini-3.7-flash",
name: "Gemini 3.7 Flash",
owned_by: "gemini",
notionCodename: "vertex-gemini-3.5-flash",
},
{
id: "gemini-3-flash",
name: "Gemini 3 Flash",
owned_by: "gemini",
notionCodename: "gingerbread",
supportsReasoning: true,
notionCodename: "grapefruit-zeppole",
},
{
id: "gemini-3.1-pro",
name: "Gemini 3.1 Pro",
owned_by: "gemini",
supportsReasoning: true,
notionCodename: "galette-medium-thinking",
},
{
id: "sonnet-4.6",
name: "Sonnet 4.6",
id: "fable-5",
name: "Claude Fable 5",
owned_by: "anthropic",
notionCodename: "almond-croissant-low",
},
{ id: "sonnet-5", name: "Sonnet 5", owned_by: "anthropic", notionCodename: "angel-cake-high" },
{
id: "opus-4.6",
name: "Opus 4.6",
owned_by: "anthropic",
notionCodename: "avocado-froyo-medium",
supportsReasoning: true,
disabled: true,
notionCodename: "acai-budino-high",
},
{
id: "opus-4.7",
name: "Opus 4.7",
id: "opus-5",
name: "Claude Opus 5",
owned_by: "anthropic",
notionCodename: "apricot-sorbet-high",
supportsReasoning: true,
notionCodename: "agave-flan",
},
{
id: "sonnet-5",
name: "Claude Sonnet 5",
owned_by: "anthropic",
supportsReasoning: true,
notionCodename: "angel-cake-high",
},
{ id: "opus-4.8", name: "Opus 4.8", owned_by: "anthropic", notionCodename: "ambrosia-tart-high" },
{
id: "haiku-4.5",
name: "Haiku 4.5",
name: "Claude Haiku 4.5",
owned_by: "anthropic",
notionCodename: "anthropic-haiku-4.5",
},
{ id: "fable-5", name: "Fable 5", owned_by: "anthropic", notionCodename: "acai-budino-high" },
{
id: "kimi-k2.6",
name: "Kimi K2.6",
id: "grok-4.6",
name: "Grok 4.6",
owned_by: "xai",
supportsReasoning: true,
notionCodename: "soursop-shortcake",
},
{
id: "kimi-k3",
name: "Kimi K3",
owned_by: "mystery",
notionCodename: "fireworks-kimi-k2.6",
supportsReasoning: true,
notionCodename: "fireworks-kimi-k3",
},
{
id: "kimi-k2.7-code",
@@ -119,16 +131,14 @@ export const NOTION_WEB_FALLBACK_MODELS: NotionDiscoveredModel[] = [
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
owned_by: "mystery",
supportsReasoning: true,
notionCodename: "baseten-deepseek-v4-pro",
},
{ id: "glm-5.2", name: "GLM 5.2", owned_by: "mystery", notionCodename: "baseten-glm-5.2" },
{ id: "grok-4.3", name: "Grok 4.3", owned_by: "xai", notionCodename: "xigua-mochi-medium" },
{ id: "grok-4.5", name: "Grok 4.5", owned_by: "xai", notionCodename: "strawberry-whoopiepie" },
{
id: "grok-build-0.1",
name: "Grok Build 0.1",
owned_by: "xai",
notionCodename: "xinomavro-cake",
id: "glm-5.2",
name: "GLM 5.2",
owned_by: "mystery",
supportsReasoning: true,
notionCodename: "baseten-glm-5.2",
},
];

View File

@@ -2,7 +2,7 @@
* PromptQL (prompt.ql.app) model catalog helpers.
*
* Live catalog: GraphQL `FetchLlmConfigs` against the playground Hasura endpoint.
* Fallback: static seed captured 2026-07-20 (display_label / model_reference / model_id).
* Fallback: static seed captured 2026-08-17 (display_label / model_reference / model_id).
*/
export interface PromptQlModel {
@@ -21,95 +21,102 @@ export interface PromptQlModel {
/** Offline seed when discovery fails (from live FetchLlmConfigs capture). */
export const PROMPTQL_FALLBACK_MODELS: PromptQlModel[] = [
{
id: "vertex-claude-fable-5",
id: "bedrock-claude-fable-5",
name: "Claude Fable 5",
configId: "967e6517-1d6b-4e22-82fb-3463bab239c4",
modelId: "anthropic/claude-fable-5",
configId: "c47a1e57-2fca-4cfe-913a-5fb821079f50",
modelId: "us.anthropic.claude-fable-5",
supportsVision: true,
},
{
id: "bedrock-claude-opus-4-8",
name: "Claude Opus 4.8",
configId: "e97e7f50-9e4a-4685-bc14-1854f1f79782",
modelId: "us.anthropic.claude-opus-4-8",
id: "bedrock-claude-opus-5",
name: "Claude Opus 5",
configId: "8aed42aa-f7c8-48f9-8238-5046bbc0f4f7",
modelId: "us.anthropic.claude-opus-5",
supportsVision: true,
},
{
id: "bedrock-claude-sonnet-4-5",
name: "Claude Sonnet 4.5",
configId: "48105d83-9a45-4ec6-8b58-f3cf44094f92",
configId: "0abcbc61-dbef-4958-96b9-e0cde7e3ad8f",
modelId: "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
supportsVision: true,
},
{
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
configId: "5a23af33-b31b-4215-892c-20ef633a8848",
modelId: "accounts/fireworks/models/deepseek-v4-pro",
id: "deepseek-v4-pro-0813",
name: "DeepSeek V4 Pro 0813",
configId: "255de820-3615-4921-a5fe-85b4af9e37a4",
modelId: "accounts/fireworks/models/deepseek-v4-pro-0813",
supportsVision: true,
},
{
id: "deepseek-v4-flash-0731",
name: "DeepSeek V4 Flash 0731",
configId: "22d8bd9a-3c48-4e27-a4f8-dc67ad2242b7",
modelId: "accounts/fireworks/models/deepseek-v4-flash-0731",
supportsVision: true,
},
{
id: "gemini-3.1-pro-preview",
name: "Gemini 3.1 Pro Preview",
configId: "d2bda5cd-881b-4044-aeb9-02a83cc0ca27",
configId: "17703a97-41a4-469d-b5d4-7356f1c28948",
modelId: "google/gemini-3.1-pro-preview",
supportsVision: true,
},
{
id: "gemini-3.5-flash",
name: "Gemini 3.5 Flash",
configId: "c3a25aa0-ca48-4577-b52d-71282aacb687",
modelId: "google/gemini-3.5-flash",
},
{
id: "glm-5.2",
name: "GLM 5.2",
configId: "64a1fa3d-bf2e-4bb9-8c2b-fa76c218d636",
modelId: "accounts/fireworks/models/glm-5p2",
},
{
id: "gpt-5.5",
name: "GPT 5.5",
configId: "1762fbce-d5bf-4bf4-ba3d-8b1201f8e204",
modelId: "gpt-5.5",
},
{
id: "gpt-5.6-luna",
name: "GPT-5.6 Luna",
configId: "a9c45ba7-87fa-49a1-8165-76b0864c3a55",
modelId: "gpt-5.6-luna",
id: "gemini-3.7-flash",
name: "Gemini 3.7 Flash",
configId: "60754535-a5e8-4ae7-acf2-43d046771700",
modelId: "google/gemini-3.7-flash",
supportsVision: true,
},
{
id: "gpt-5.6-sol",
name: "GPT-5.6 Sol",
configId: "34c80712-def3-4db3-9e7a-f57b0324b43d",
configId: "4914e63d-ea29-45dc-9a85-c367b1ad0be5",
modelId: "gpt-5.6-sol",
supportsVision: true,
},
{
id: "gpt-5.6-terra",
name: "GPT-5.6 Terra",
configId: "04f1a08c-42b2-4371-b6d8-75c50b9bb990",
configId: "4e627eb9-a199-4a90-8050-b734b6ee5fda",
modelId: "gpt-5.6-terra",
supportsVision: true,
},
{
id: "xai-grok-4-5",
name: "Grok 4.5",
configId: "068b2ef2-e432-422b-98e5-5863a1852c47",
modelId: "grok-4.5",
id: "gpt-5.6-luna",
name: "GPT-5.6 Luna",
configId: "5eac2efb-7951-4da6-9a32-bfb31b1a7788",
modelId: "gpt-5.6-luna",
supportsVision: true,
},
{
id: "kimi-k2.6",
name: "Kimi K2.6",
configId: "placeholder-kimi-k2.6",
modelId: "accounts/fireworks/models/kimi-k2p6",
id: "xai-grok-4-6",
name: "Grok 4.6",
configId: "673e97ba-7b15-4213-8984-9b2477ee3409",
modelId: "grok-4.6",
supportsVision: true,
},
{
id: "kimi-k2.7-code",
name: "Kimi K2.7 Code",
configId: "placeholder-kimi-k2.7-code",
modelId: "accounts/fireworks/models/kimi-k2p7-code",
id: "kimi-k3",
name: "Kimi K3",
configId: "2a751e62-e281-4ab0-9be0-b05a6f8603db",
modelId: "accounts/fireworks/models/kimi-k3",
supportsVision: true,
},
{
id: "glm-5.2",
name: "GLM 5.2",
configId: "d2694d4d-4285-4d3c-ada5-aea5956375d4",
modelId: "accounts/fireworks/models/glm-5p2",
supportsVision: false,
},
{
id: "minimax-m3",
name: "Minimax M3",
configId: "placeholder-minimax-m3",
configId: "c4028069-0eb9-4a31-825c-a1cff9e5a085",
modelId: "accounts/fireworks/models/minimax-m3",
supportsVision: true,
supportsVision: false,
},
];

View File

@@ -2686,7 +2686,7 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
};
const FREE_STACK_PRESET_MODELS = [
{ model: "agy/gemini-3.5-flash-low", weight: 0 },
{ model: "agy/gemini-3.7-flash-low", weight: 0 },
{ model: "kr/claude-sonnet-4.5", weight: 0 },
{ model: "if/kimi-k2-thinking", weight: 0 },
{ model: "if/qwen3-coder-plus", weight: 0 },

View File

@@ -1768,8 +1768,8 @@ export async function GET(
// Vertex AI lists models from the Generative Language `v1beta/models` endpoint, which both
// Express-mode API keys (via ?key=) and Service Account JSON (via a minted OAuth Bearer
// token) can reach. This surfaces the full live catalog including image models
// (imagen-*, gemini-*-image) absent from the static registry list.
// token) can reach. This surfaces the live catalog, including gemini-*-image models
// absent from the static registry list.
const credential = (apiKey || "").trim();
let queryKey: string | null = null;
let bearerToken: string | null = null;

View File

@@ -3,15 +3,11 @@
*
* Each model's `supportedGenerationMethods` is mapped to OmniRoute endpoints:
* - generateContent / generateAnswer → "chat"
* - predict → "images" (Imagen image generation)
* - predictLongRunning → "video" (Veo video generation)
* - embedContent → "embeddings"
* - bidiGenerateContent → "audio" (Live real-time audio)
*
* Model-id heuristics refine the long-running bucket because Google exposes both
* Imagen and Veo via long-running methods on the same endpoint:
* - id contains "veo" → ensure "video"
* - id contains "imagen" → force "images" (never "video")
* Model-id heuristics ensure Veo models remain in the video bucket.
*
* Note: `gemini-*-image` models (e.g. gemini-3-pro-image) generate images via the
* regular `generateContent` path, so they stay "chat" (image output is a chat
@@ -25,7 +21,6 @@
const METHOD_TO_ENDPOINT: Record<string, string> = {
generateContent: "chat",
embedContent: "embeddings",
predict: "images",
predictLongRunning: "video",
bidiGenerateContent: "audio",
generateAnswer: "chat",
@@ -65,15 +60,10 @@ export function parseGeminiModelsList(data: any): GeminiDiscoveryModel[] {
const id = ((m.name as string) || (m.id as string) || "").replace(/^models\//, "");
const lowerId = id.toLowerCase();
// Google exposes Imagen (image) and Veo (video) via long-running methods; the
// method alone can't always distinguish them, so refine by model id.
// Keep Veo models in the video bucket even when the method list is incomplete.
if (lowerId.includes("veo")) {
endpoints.add("video");
}
if (lowerId.includes("imagen")) {
endpoints.delete("video");
endpoints.add("images");
}
if (endpoints.size === 0) endpoints.add("chat");

View File

@@ -100,7 +100,10 @@ export const CLI_TOOLS: Record<string, CliCatalogEntry> = {
baseUrlSupport: "none",
defaultCommand: "zcode",
notes: [
{ type: "info", text: "Uses the local ZCode app-server and its existing builtin:zai-coding-plan login." },
{
type: "info",
text: "Uses the local ZCode app-server and its existing builtin:zai-coding-plan login.",
},
{ type: "warning", text: "The response is buffered until the ZCode turn completes." },
],
},
@@ -239,29 +242,21 @@ export const CLI_TOOLS: Record<string, CliCatalogEntry> = {
acpSpawnable: false,
baseUrlSupport: "none",
modelAliases: [
"gemini-3.7-flash",
"gemini-3.6-flash-high",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-low",
"gemini-3.7-flash-high",
"gemini-3.7-flash-medium",
"gemini-3.7-flash-low",
"claude-opus-4-6-thinking",
"claude-sonnet-4-6",
"gemini-pro-agent",
"gemini-3.1-pro-low",
"gemini-3-flash-agent",
"gemini-3.5-flash-low",
"gemini-3.5-flash-extra-low",
"gpt-oss-120b-medium",
],
defaultModels: [
createCliModel("gemini-3.7-flash", "Gemini 3.7 Flash"),
createCliModel("gemini-3.6-flash-high", "Gemini 3.6 Flash High"),
createCliModel("gemini-3.6-flash-medium", "Gemini 3.6 Flash Medium"),
createCliModel("gemini-3.6-flash-low", "Gemini 3.6 Flash Low"),
createCliModel("gemini-3.7-flash-high", "Gemini 3.7 Flash High"),
createCliModel("gemini-3.7-flash-medium", "Gemini 3.7 Flash Medium"),
createCliModel("gemini-3.7-flash-low", "Gemini 3.7 Flash Low"),
createCliModel("gemini-pro-agent", "Gemini 3.1 Pro High"),
createCliModel("gemini-3.1-pro-low", "Gemini 3.1 Pro Low"),
createCliModel("gemini-3-flash-agent", "Gemini 3.5 Flash High"),
createCliModel("gemini-3.5-flash-low", "Gemini 3.5 Flash Medium"),
createCliModel("gemini-3.5-flash-extra-low", "Gemini 3.5 Flash Low"),
createCliModel("claude-sonnet-4-6", "Claude Sonnet 4.6"),
createCliModel("claude-opus-4-6-thinking", "Claude Opus 4.6 Thinking"),
createCliModel("gpt-oss-120b-medium", "GPT OSS 120B Medium"),

View File

@@ -153,7 +153,7 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
aliases: ["openai/gpt-4o"],
},
// ── Gemini 2.5 and 3.5 Flash series ──────────────────────────────
// ── Gemini 2.5 and provider-neutral 3.5 Flash series ─────────────
"gemini-2.5-flash": {
maxOutputTokens: 65536,
contextWindow: 1048576,
@@ -174,24 +174,17 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
thinkingBudgetCap: 0,
},
// ── Gemini 3.7 Flash (Antigravity) — collapsed live id ──────────
// Upstream (fetchAvailableModels on daily-cloudcode-pa) also serves this model as a
// single `gemini-3.7-flash-tiered` id via the `gemini-3.7-flash` alias in
// antigravityModelAliases.ts. Registered independently of the suffixed tier ids below
// (#3696 uniqueness invariant: each public id resolves to a distinct upstream id).
"gemini-3.7-flash": {
...GEMINI_35_FLASH_MODEL_SPEC,
defaultThinkingBudget: 8192,
thinkingBudgetCap: 65536,
supportsThinking: true,
},
// ── Gemini 3.7 / 3.6 Flash (Antigravity live tiers) ─────────────
// ── Gemini 3.7 Flash (current Antigravity/AGY live tiers) ─────────
// The model id itself selects the upstream 10k/4k/1k reasoning tier. Antigravity
// still rejects client-supplied thinking parameters, so keep the explicit-parameter
// capability aligned with the existing Gemini 3.5 tier ids.
// capability aligned with the existing Gemini Flash tier ids.
"gemini-3.7-flash-high": { ...GEMINI_35_FLASH_MODEL_SPEC },
"gemini-3.7-flash-medium": { ...GEMINI_35_FLASH_MODEL_SPEC },
"gemini-3.7-flash-low": { ...GEMINI_35_FLASH_MODEL_SPEC },
// Provider-neutral compatibility for providers that still serve Gemini 3.6.
// Antigravity/AGY availability is governed by their own provider catalogs and
// retirement filters; these shared specs must not be treated as an allowlist.
"gemini-3.6-flash-high": { ...GEMINI_35_FLASH_MODEL_SPEC },
"gemini-3.6-flash-medium": { ...GEMINI_35_FLASH_MODEL_SPEC },
"gemini-3.6-flash-low": { ...GEMINI_35_FLASH_MODEL_SPEC },

View File

@@ -3,6 +3,7 @@
* Pure data; merged by default-pricing.ts via spread (god-file decomposition; semantic split).
*/
import {
GEMINI_3_7_FLASH_PROMO_PRICING,
GPT_5_5_PRICING,
GPT_5_6_LUNA_PRICING,
GPT_5_6_SOL_PRICING,
@@ -226,6 +227,7 @@ export const DEFAULT_PRICING_FRONTIER = {
"claude-opus-4": CLAUDE_OPUS_4_PRICING,
},
gemini: {
"gemini-3.7-flash": GEMINI_3_7_FLASH_PROMO_PRICING,
// Gemini 3.1 Pro — novo flagship Google (2026-03-17)
// Context: 1.050.000 tokens | Max Output: 65.536
"gemini-3.1-pro": {

View File

@@ -140,12 +140,13 @@ export const DEFAULT_PRICING_INFERENCE = {
reasoning: 1.75,
cache_creation: 0.0583,
},
"gemini-3-5-flash": {
input: 1.05,
output: 6.3,
cached: 0.1085,
reasoning: 6.3,
cache_creation: 0.0602,
// Live public catalog rate checked 2026-08-16.
"gemini-3.7-flash": {
input: 0.525,
output: 2.625,
cached: 0.13125,
reasoning: 2.625,
cache_creation: 0.017708,
},
"gemini-3-flash-preview": {
input: 0.35,
@@ -218,7 +219,13 @@ export const DEFAULT_PRICING_INFERENCE = {
reasoning: 21.0,
cache_creation: 3.5,
},
"gpt-5.6-terra": { input: 1.0, output: 6.0, cached: 0.125, reasoning: 6.0, cache_creation: 1.0 },
"gpt-5.6-terra": {
input: 1.0,
output: 6.0,
cached: 0.125,
reasoning: 6.0,
cache_creation: 1.0,
},
"grok-4.5": { input: 1.4, output: 4.2, cached: 0.35, reasoning: 4.2, cache_creation: 1.4 },
"kimi-k3": { input: 2.1, output: 10.5, cached: 0.2625, reasoning: 10.5, cache_creation: 2.1 },
"minimax-m2.7": {
@@ -229,6 +236,15 @@ export const DEFAULT_PRICING_INFERENCE = {
cache_creation: 0.168,
},
},
orcarouter: {
"google/gemini-3.6-flash": {
input: 1.5,
output: 7.5,
cached: 0.15,
reasoning: 7.5,
cache_creation: 1.5,
},
},
synthetic: {
"hf:openai/gpt-oss-120b": {
input: 0.1,

View File

@@ -4,6 +4,7 @@
*/
import {
CLAUDE_OPUS_5_PRICING,
GEMINI_3_7_FLASH_PROMO_PRICING,
GPT_5_3_CODEX_PRICING,
GPT_5_5_PRICING,
GPT_5_6_LUNA_PRICING,
@@ -11,6 +12,12 @@ import {
GPT_5_6_TERRA_PRICING,
} from "./shared-tiers";
const ANTIGRAVITY_GEMINI_3_7_PRICING = {
"gemini-3.7-flash-low": GEMINI_3_7_FLASH_PROMO_PRICING,
"gemini-3.7-flash-medium": GEMINI_3_7_FLASH_PROMO_PRICING,
"gemini-3.7-flash-high": GEMINI_3_7_FLASH_PROMO_PRICING,
};
export const DEFAULT_PRICING_OAUTH = {
cc: {
"claude-fable-5": {
@@ -277,35 +284,6 @@ export const DEFAULT_PRICING_OAUTH = {
reasoning: 18.0,
cache_creation: 2.0,
},
// Antigravity 2.0.4+ exposes Gemini 3.5 Flash as three public client ids
// (see ANTIGRAVITY_PUBLIC_MODELS in open-sse/config/antigravityModelAliases.ts):
// gemini-3.5-flash-extra-low → "Gemini 3.5 Flash (Low)"
// gemini-3-flash-agent → "Gemini 3.5 Flash (High)"
// gemini-3.5-flash-low → "Gemini 3.5 Flash (Medium)"
// Without these rows, getPricingForModel("ag", id) returns null and downstream
// cost and quota calculations silently fall back to $0.
"gemini-3.5-flash-extra-low": {
input: 0.5,
output: 3.0,
cached: 0.03,
reasoning: 4.5,
cache_creation: 0.5,
},
"gemini-3-flash-agent": {
input: 0.5,
output: 3.0,
cached: 0.03,
reasoning: 4.5,
cache_creation: 0.5,
},
"gemini-3.5-flash-low": {
input: 0.5,
output: 3.0,
cached: 0.03,
reasoning: 4.5,
cache_creation: 0.5,
},
// `gemini-pro-agent` is the callable Antigravity id for Gemini 3.1 Pro (High).
"gemini-pro-agent": {
input: 4.0,
output: 18.0,
@@ -313,40 +291,7 @@ export const DEFAULT_PRICING_OAUTH = {
reasoning: 27.0,
cache_creation: 4.0,
},
// Gemini 3.6 Flash (released 2026-07-21) - three tier variants like 3.5 Flash
// (see ANTIGRAVITY_PUBLIC_MODELS / MODEL_SPECS which already carry the catalog
// entries). Without these rows, getPricingForModel("ag", id) returns null and
// downstream cost and quota calculations silently fall back to $0.
// Pricing: $1.50 input / $7.50 output / $0.15 cached per MTok. Thinking tokens
// billed at output rate.
"gemini-3.7-flash": {
input: 1.5,
output: 7.5,
cached: 0.15,
reasoning: 7.5,
cache_creation: 1.5,
},
"gemini-3.6-flash-low": {
input: 1.5,
output: 7.5,
cached: 0.15,
reasoning: 7.5,
cache_creation: 1.5,
},
"gemini-3.6-flash-medium": {
input: 1.5,
output: 7.5,
cached: 0.15,
reasoning: 7.5,
cache_creation: 1.5,
},
"gemini-3.6-flash-high": {
input: 1.5,
output: 7.5,
cached: 0.15,
reasoning: 7.5,
cache_creation: 1.5,
},
...ANTIGRAVITY_GEMINI_3_7_PRICING,
"claude-sonnet-4-6": {
input: 3.0,
output: 15.0,
@@ -369,6 +314,8 @@ export const DEFAULT_PRICING_OAUTH = {
cache_creation: 0.5,
},
},
antigravity: ANTIGRAVITY_GEMINI_3_7_PRICING,
agy: ANTIGRAVITY_GEMINI_3_7_PRICING,
gh: {
"claude-opus-5": CLAUDE_OPUS_5_PRICING,
"gpt-5": {
@@ -441,6 +388,7 @@ export const DEFAULT_PRICING_OAUTH = {
reasoning: 4.5,
cache_creation: 0.5,
},
"gemini-3.7-flash": GEMINI_3_7_FLASH_PROMO_PRICING,
"gemini-2.5-pro": {
input: 2.0,
output: 12.0,

View File

@@ -41,6 +41,17 @@ export const GPT_5_6_LUNA_PRICING = {
cache_creation: 1.25,
};
// Gemini 3.7 Flash introductory pricing through 2026-12-31. Google and
// GitHub Copilot both publish the same current token rates; standard pricing
// becomes $1.50/$7.50/$0.15 (input/output/cached input) on 2027-01-01.
export const GEMINI_3_7_FLASH_PROMO_PRICING = {
input: 0.75,
output: 3.75,
cached: 0.075,
reasoning: 3.75,
cache_creation: 0.75,
};
export const CLAUDE_FABLE_5_PRICING = {
input: 15.0,
output: 75.0,

View File

@@ -127,7 +127,7 @@ test("checkFallbackError locks Antigravity quota-reached 429 for the full reset
429,
message,
0,
"gemini-3-flash-agent",
"gemini-3.7-flash-high",
"antigravity",
null,
makeProfile({ useUpstreamRetryHints: true })
@@ -143,7 +143,7 @@ test("checkFallbackError locks Antigravity quota-reached 429 for the full reset
test("recordModelLockoutFailure honors a multi-day exactCooldownMs (under 30-day cap)", () => {
const provider = "antigravity";
const connectionId = "conn-quota-window";
const model = "gemini-3-flash-agent";
const model = "gemini-3.7-flash-high";
const exactCooldownMs = (164 * 3600 + 27 * 60 + 24) * 1000;
clearModelLock(provider, connectionId, model);

View File

@@ -51,12 +51,15 @@ test("agy ships its own live callable model catalog", () => {
const ids = REGISTRY.agy.models.map((m) => m.id);
assert.ok(ids.includes("claude-opus-4-6-thinking"), "must expose Claude Opus 4.6 Thinking");
assert.ok(ids.includes("claude-sonnet-4-6"), "must expose Claude Sonnet 4.6");
assert.ok(ids.includes("gemini-3.6-flash-low"), "must expose Gemini 3.6 Flash Low");
assert.ok(ids.includes("gemini-3.6-flash-medium"), "must expose Gemini 3.6 Flash Medium");
assert.ok(ids.includes("gemini-3.6-flash-high"), "must expose Gemini 3.6 Flash High");
assert.ok(ids.includes("gemini-3.5-flash-extra-low"), "must expose upstream Flash Low tier");
assert.ok(ids.includes("gemini-3.5-flash-low"), "must expose upstream Flash Medium tier");
assert.ok(ids.includes("gemini-3-flash-agent"), "must expose upstream Flash High tier");
assert.ok(ids.includes("gemini-3.7-flash-low"), "must expose Gemini 3.7 Flash Low");
assert.ok(ids.includes("gemini-3.7-flash-medium"), "must expose Gemini 3.7 Flash Medium");
assert.ok(ids.includes("gemini-3.7-flash-high"), "must expose Gemini 3.7 Flash High");
assert.ok(!ids.includes("gemini-3.6-flash-low"));
assert.ok(!ids.includes("gemini-3.6-flash-medium"));
assert.ok(!ids.includes("gemini-3.6-flash-high"));
assert.ok(!ids.includes("gemini-3.5-flash-extra-low"));
assert.ok(!ids.includes("gemini-3.5-flash-low"));
assert.ok(!ids.includes("gemini-3-flash-agent"));
assert.ok(!ids.includes("gemini-3.5-flash-medium"));
assert.ok(!ids.includes("gemini-3.5-flash-high"));
assert.ok(!ids.includes("gemini-3.5-flash-preview"));
@@ -64,8 +67,8 @@ test("agy ships its own live callable model catalog", () => {
assert.ok(!ids.includes("gemini-3.1-pro-high"), "must not expose rejected Pro High id");
assert.ok(ids.includes("gemini-pro-agent"), "must expose callable Pro High id");
assert.ok(!ids.includes("gemini-2.5-pro"), "must not expose unavailable Gemini 2.5 Pro");
assert.ok(ids.includes("gemini-2.5-flash"), "must retain working Gemini 2.5 Flash");
assert.ok(ids.includes("gemini-2.5-flash-lite"), "must retain working Gemini 2.5 Flash Lite");
assert.ok(!ids.includes("gemini-2.5-flash"));
assert.ok(!ids.includes("gemini-2.5-flash-lite"));
// Tab-completion models are not chat-callable and must be excluded.
assert.ok(!ids.includes("tab_flash_lite_preview"));
assert.ok(!ids.includes("tab_jump_flash_lite_preview"));
@@ -75,15 +78,18 @@ test("agy ships its own live callable model catalog", () => {
test("agy model helpers resolve catalog ids and display names", () => {
assert.equal(isUserCallableAgyModelId("claude-opus-4-6-thinking"), true);
assert.equal(isUserCallableAgyModelId("gemini-2.5-pro"), false);
assert.equal(isUserCallableAgyModelId("gemini-2.5-flash"), true);
assert.equal(isUserCallableAgyModelId("gemini-2.5-flash"), false);
assert.equal(isUserCallableAgyModelId("gemini-3.1-pro-high"), false);
assert.equal(isUserCallableAgyModelId("gemini-pro-agent"), true);
assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-low"), true);
assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-medium"), true);
assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-high"), true);
assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-extra-low"), true);
assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-low"), true);
assert.equal(isUserCallableAgyModelId("gemini-3-flash-agent"), true);
assert.equal(isUserCallableAgyModelId("gemini-3.7-flash-low"), true);
assert.equal(isUserCallableAgyModelId("gemini-3.7-flash-medium"), true);
assert.equal(isUserCallableAgyModelId("gemini-3.7-flash-high"), true);
assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-low"), false);
assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-medium"), false);
assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-high"), false);
assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-extra-low"), false);
assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-low"), false);
assert.equal(isUserCallableAgyModelId("gemini-3-flash-agent"), false);
assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-medium"), false);
assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-high"), false);
assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-preview"), false);
@@ -94,23 +100,20 @@ test("agy model helpers resolve catalog ids and display names", () => {
"Claude Opus 4.6 (Thinking)"
);
assert.equal(getClientVisibleAgyModelName("gemini-pro-agent"), "Gemini 3.1 Pro (High)");
assert.equal(getClientVisibleAgyModelName("gemini-3.7-flash-low"), "Gemini 3.7 Flash (Low)");
assert.equal(
getClientVisibleAgyModelName("gemini-3.5-flash-extra-low"),
"Gemini 3.5 Flash (Low)"
getClientVisibleAgyModelName("gemini-3.7-flash-medium"),
"Gemini 3.7 Flash (Medium)"
);
assert.equal(getClientVisibleAgyModelName("gemini-3.6-flash-low"), "Gemini 3.6 Flash (Low)");
assert.equal(
getClientVisibleAgyModelName("gemini-3.6-flash-medium"),
"Gemini 3.6 Flash (Medium)"
);
assert.equal(getClientVisibleAgyModelName("gemini-3.6-flash-high"), "Gemini 3.6 Flash (High)");
assert.equal(getClientVisibleAgyModelName("gemini-3.5-flash-low"), "Gemini 3.5 Flash (Medium)");
assert.equal(getClientVisibleAgyModelName("gemini-3-flash-agent"), "Gemini 3.5 Flash (High)");
assert.equal(getClientVisibleAgyModelName("gemini-3.7-flash-high"), "Gemini 3.7 Flash (High)");
assert.equal(getClientVisibleAgyModelName("unknown-model", "Fallback"), "Fallback");
});
test("agy live discovery accepts new chat models while excluding tab-completion models", () => {
assert.equal(isDiscoverableAgyModelId("gemini-new-live-tier"), true);
assert.equal(isDiscoverableAgyModelId("gemini-3.6-flash-high"), false);
assert.equal(isDiscoverableAgyModelId("gemini-3-flash-agent"), false);
assert.equal(isDiscoverableAgyModelId("gemini-2.5-flash"), false);
assert.equal(isDiscoverableAgyModelId("tab_flash_lite_preview"), false);
assert.equal(isDiscoverableAgyModelId("tab_jump_flash_lite_preview"), false);
assert.equal(isDiscoverableAgyModelId(""), false);

View File

@@ -18,7 +18,7 @@ test("getUsageForProvider routes agy through the Antigravity usage implementatio
new Response(
JSON.stringify({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: {
remainingFraction: 0.75,
resetTime: "2026-06-06T00:00:00Z",
@@ -48,7 +48,7 @@ test("getUsageForProvider routes agy through the Antigravity usage implementatio
);
assert.ok("quotas" in result, "agy should return quota data when upstream responds");
const quota = (result as { quotas: Record<string, any> }).quotas["gemini-3-flash-agent"];
const quota = (result as { quotas: Record<string, any> }).quotas["gemini-3.7-flash-high"];
assert.ok(quota, "should expose the upstream agy per-model quota");
assert.equal(quota.remainingPercentage, 75);
assert.equal(
@@ -65,7 +65,7 @@ test("parseQuotaData treats agy quota payloads like Antigravity", () => {
const parsed = providerLimitUtils.parseQuotaData("agy", {
quotas: {
credits: { remaining: 42 },
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
used: 250,
total: 1000,
remainingPercentage: 75,
@@ -83,7 +83,7 @@ test("parseQuotaData treats agy quota payloads like Antigravity", () => {
assert.ok(credits, "credits quota should be rendered");
assert.equal(credits.isCredits, true);
const modelQuota = parsed.find((quota: any) => quota.name === "gemini-3-flash-agent");
const modelQuota = parsed.find((quota: any) => quota.name === "gemini-3.7-flash-high");
assert.ok(modelQuota, "model quota should be rendered");
assert.equal(modelQuota.remainingPercentage, 75);
});

View File

@@ -7,7 +7,7 @@
* the fallback queries and asserts the flip — the regression guard for the id contract.
*
* Contract note: the fallback queries `usage_history WHERE model = <public model id>`
* (e.g. gemini-3-flash-agent), so the executor MUST log usage under that same model id
* (e.g. gemini-3.7-flash-high), so the executor MUST log usage under that same model id
* for the fallback to fire. This test pins exactly that join.
*/
import test from "node:test";
@@ -47,7 +47,7 @@ test("Antigravity fetchAvailableModels(used=0) → localUsageHistory when usage_
db.prepare(
`INSERT INTO usage_history (provider, model, connection_id, tokens_input, tokens_output, tokens_reasoning, success, timestamp)
VALUES (?, ?, ?, ?, ?, ?, 1, ?)`
).run("antigravity", "gemini-3-flash-agent", "conn-local-1", 1000, 1500, 500, seededTimestamp);
).run("antigravity", "gemini-3.7-flash-high", "conn-local-1", 1000, 1500, 500, seededTimestamp);
// Total seeded tokens = 3000 → ceil(3000/1000) = 3 units used.
globalThis.fetch = (async (input: any) => {
@@ -61,7 +61,7 @@ test("Antigravity fetchAvailableModels(used=0) → localUsageHistory when usage_
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: { remainingFraction: 1.0, resetTime },
},
},
@@ -79,8 +79,8 @@ test("Antigravity fetchAvailableModels(used=0) → localUsageHistory when usage_
const result = await getUsageForProvider(connection, { forceRefresh: true });
assert.ok(result && "quotas" in result, "should return quotas");
const quota = (result as any).quotas["gemini-3-flash-agent"];
assert.ok(quota, "should have the gemini-3-flash-agent quota");
const quota = (result as any).quotas["gemini-3.7-flash-high"];
assert.ok(quota, "should have the gemini-3.7-flash-high quota");
assert.equal(quota.quotaSource, "localUsageHistory", "stale full bucket replaced by local usage");
assert.equal(quota.used, 3, "3000 seeded tokens → 3 units used");
});
@@ -99,7 +99,7 @@ test("Antigravity stays fetchAvailableModels when usage_history has no matching
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": { quotaInfo: { remainingFraction: 1.0, resetTime } },
"gemini-3.7-flash-high": { quotaInfo: { remainingFraction: 1.0, resetTime } },
},
}),
} as Response;
@@ -114,7 +114,7 @@ test("Antigravity stays fetchAvailableModels when usage_history has no matching
};
const result = await getUsageForProvider(connection, { forceRefresh: true });
const quota = (result as any).quotas["gemini-3-flash-agent"];
const quota = (result as any).quotas["gemini-3.7-flash-high"];
assert.ok(quota, "should have the quota");
assert.equal(quota.quotaSource, "fetchAvailableModels", "no local rows → keep the catalog view");
assert.equal(quota.used, 0, "full bucket stays at 0 used");

View File

@@ -21,15 +21,16 @@ const EXPECTED_FLASH_TIERS = [
["gemini-3.7-flash", "Gemini 3.7 Flash"],
["gemini-3.7-flash-high", "Gemini 3.7 Flash (High)"],
["gemini-3.7-flash-medium", "Gemini 3.7 Flash (Medium)"],
["gemini-3.6-flash-low", "Gemini 3.6 Flash (Low)"],
["gemini-3.6-flash-medium", "Gemini 3.6 Flash (Medium)"],
["gemini-3.6-flash-high", "Gemini 3.6 Flash (High)"],
["gemini-3.5-flash-extra-low", "Gemini 3.5 Flash (Low)"],
["gemini-3.5-flash-low", "Gemini 3.5 Flash (Medium)"],
["gemini-3-flash-agent", "Gemini 3.5 Flash (High)"],
["gemini-3.7-flash-low", "Gemini 3.7 Flash (Low)"],
] as const;
const RETIRED_FLASH_IDS = [
"gemini-3.6-flash-low",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-high",
"gemini-3.5-flash-extra-low",
"gemini-3.5-flash-low",
"gemini-3-flash-agent",
"gemini-3.5-flash-medium",
"gemini-3.5-flash-high",
"gemini-3.5-flash-preview",
@@ -44,6 +45,9 @@ test("toClientAntigravityQuotaModelId preserves upstream Gemini Flash bucket IDs
// Retired preview buckets are dropped (hidden from clients).
assert.equal(toClientAntigravityQuotaModelId("gemini-3.5-flash-preview"), null);
assert.equal(toClientAntigravityQuotaModelId("gemini-3-flash-preview"), null);
for (const retiredId of RETIRED_FLASH_IDS) {
assert.equal(toClientAntigravityQuotaModelId(retiredId), null);
}
assert.equal(toClientAntigravityQuotaModelId(""), null);
});
@@ -86,9 +90,9 @@ test("isUserCallableAntigravityModelId only allows public chat-capable model IDs
}
assert.equal(isUserCallableAntigravityModelId("gemini-3.1-flash-lite"), true);
assert.equal(isUserCallableAntigravityModelId("gemini-2.5-pro"), false);
assert.equal(isUserCallableAntigravityModelId("gemini-2.5-flash"), true);
assert.equal(isUserCallableAntigravityModelId("gemini-2.5-flash-lite"), true);
assert.equal(isUserCallableAntigravityModelId("gemini-2.5-flash-thinking"), true);
assert.equal(isUserCallableAntigravityModelId("gemini-2.5-flash"), false);
assert.equal(isUserCallableAntigravityModelId("gemini-2.5-flash-lite"), false);
assert.equal(isUserCallableAntigravityModelId("gemini-2.5-flash-thinking"), false);
assert.equal(isUserCallableAntigravityModelId("gemini-pro-agent"), true);
// #3184: Claude IS user-callable through the Antigravity OAuth provider (same backend as
// `agy`, verified empirically). An earlier assumption that it was removed in Antigravity
@@ -109,6 +113,10 @@ test("isDiscoverableAntigravityModelId accepts new live chat models without a st
assert.equal(isDiscoverableAntigravityModelId("claude-sonnet-5"), true);
assert.equal(isDiscoverableAntigravityModelId("gemini-new-live-tier"), true);
for (const retiredId of RETIRED_FLASH_IDS) {
assert.equal(isDiscoverableAntigravityModelId(retiredId), false);
}
assert.equal(isDiscoverableAntigravityModelId("tab_flash_lite_preview"), false);
assert.equal(isDiscoverableAntigravityModelId("gemini-3.1-flash-image"), false);
assert.equal(isDiscoverableAntigravityModelId("gemini-3.1-flash-tts-preview"), false);
@@ -150,15 +158,6 @@ test("ANTIGRAVITY_PUBLIC_MODELS exposes current live names and capabilities", ()
for (const retiredId of RETIRED_FLASH_IDS) {
assert.equal(getPublicModel(retiredId), undefined);
}
assert.equal(getClientVisibleAntigravityModelName("gemini-2.5-flash"), "Gemini 2.5 Flash");
assert.equal(
getClientVisibleAntigravityModelName("gemini-2.5-flash-lite"),
"Gemini 2.5 Flash Lite"
);
assert.equal(
getClientVisibleAntigravityModelName("gemini-2.5-flash-thinking"),
"Gemini 2.5 Flash Thinking"
);
assert.deepEqual(getPublicModel("gpt-oss-120b-medium"), {
id: "gpt-oss-120b-medium",
name: "GPT-OSS 120B (Medium)",

View File

@@ -7,7 +7,7 @@
* part (usually carrying a `thoughtSignature`), so any tools request collected
* to an empty stream and was rewritten into a synthetic 502 "Provider returned
* empty content" — breaking Chatwit Captain Copilot / reply suggestions on
* agy/gemini-3.5-flash-low while plain text completions kept working.
* agy/gemini-3.7-flash-low while plain text completions kept working.
*/
import test from "node:test";
import assert from "node:assert/strict";

View File

@@ -82,6 +82,21 @@ test("an unknown model id falls back to the conservative ceiling", () => {
assert.equal(clampFor("no-such-model-xyz", 65535), MAX_ANTIGRAVITY_OUTPUT_TOKENS);
});
test("retired Antigravity Flash ids cannot inherit provider-neutral output caps", () => {
for (const modelId of [
"gemini-3.6-flash-low",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-high",
"gemini-3.5-flash-extra-low",
"gemini-3.5-flash-low",
"gemini-3.5-flash-medium",
"gemini-3.5-flash-high",
"gemini-3-flash-agent",
]) {
assert.equal(clampFor(modelId, 65536), MAX_ANTIGRAVITY_OUTPUT_TOKENS, modelId);
}
});
test("a missing or empty model id falls back too", () => {
assert.equal(clampFor(undefined, 65535), MAX_ANTIGRAVITY_OUTPUT_TOKENS);
assert.equal(clampFor(null, 65535), MAX_ANTIGRAVITY_OUTPUT_TOKENS);
@@ -211,7 +226,7 @@ test("a provider-prefixed model id resolves to the model's ceiling, not the fall
const cases: Array<[string, number]> = [
["agy/gemini-3.1-pro-high", 65535],
["antigravity/gemini-3.1-pro-high", 65535],
["agy/gemini-3.6-flash-high", 65536],
["agy/gemini-3.7-flash-high", 65536],
["agy/gpt-oss-120b-medium", 32768],
];

View File

@@ -89,7 +89,7 @@ test("#8965: quota reads use the runtime host (daily-cloudcode-pa), not cloudcod
json: async () => ({
buckets: [
{
modelId: "gemini-3-flash-agent",
modelId: "gemini-3.7-flash-high",
remainingFraction: 0.4,
resetTime: RESET_IN_2_HOURS,
},
@@ -103,10 +103,10 @@ test("#8965: quota reads use the runtime host (daily-cloudcode-pa), not cloudcod
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: { remainingFraction: 1.0, resetTime: RESET_IN_2_HOURS },
},
"gemini-3.5-flash-low": {
"gemini-3.7-flash-medium": {
quotaInfo: { remainingFraction: 0.8, resetTime: RESET_IN_2_HOURS },
},
},
@@ -148,9 +148,9 @@ test("#8965: quota reads use the runtime host (daily-cloudcode-pa), not cloudcod
// The per-model quota should come from retrieveUserQuota (the live source),
// NOT fetchAvailableModels (the stale catalog fallback).
assert.ok(quotas["gemini-3-flash-agent"], "gemini-3-flash-agent quota present");
assert.ok(quotas["gemini-3.7-flash-high"], "gemini-3.7-flash-high quota present");
assert.equal(
quotas["gemini-3-flash-agent"].quotaSource,
quotas["gemini-3.7-flash-high"].quotaSource,
"retrieveUserQuota",
"quota source is retrieveUserQuota (live), not fetchAvailableModels"
);
@@ -197,7 +197,7 @@ test("#8965 behavioral impact: live quota source + weekly bucket unreachable whe
json: async () => ({
buckets: [
{
modelId: "gemini-3-flash-agent",
modelId: "gemini-3.7-flash-high",
remainingFraction: 0.4,
resetTime: RESET_IN_2_HOURS,
},
@@ -211,7 +211,7 @@ test("#8965 behavioral impact: live quota source + weekly bucket unreachable whe
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: { remainingFraction: 1.0, resetTime: RESET_IN_2_HOURS },
},
},
@@ -250,9 +250,9 @@ test("#8965 behavioral impact: live quota source + weekly bucket unreachable whe
const quotas = (result as UsageResult).quotas;
// The per-model quota MUST come from retrieveUserQuota — the live signal.
assert.ok(quotas["gemini-3-flash-agent"], "gemini-3-flash-agent quota present");
assert.ok(quotas["gemini-3.7-flash-high"], "gemini-3.7-flash-high quota present");
assert.equal(
quotas["gemini-3-flash-agent"].quotaSource,
quotas["gemini-3.7-flash-high"].quotaSource,
"retrieveUserQuota",
"quota source is retrieveUserQuota (live), not fetchAvailableModels"
);
@@ -260,4 +260,4 @@ test("#8965 behavioral impact: live quota source + weekly bucket unreachable whe
// The weekly group quota MUST also be present because retrieveUserQuotaSummary
// was served by the runtime host.
assert.ok(quotas.gemini_weekly, "weekly group quota present");
});
});

View File

@@ -21,7 +21,7 @@ test("isQuotaExhaustedForRequest isolates Claude and Gemini quota families for a
// Simulate Claude Opus being exhausted, while Gemini is NOT.
quotaCache.setQuotaCache(connectionId, "antigravity", {
"claude-opus-4-6-thinking": { remainingPercentage: 0, resetAt: null },
"gemini-3.5-flash-high": { remainingPercentage: 100, resetAt: null },
"gemini-3.7-flash-high": { remainingPercentage: 100, resetAt: null },
});
// Verify that Claude models are considered exhausted.
@@ -49,7 +49,7 @@ test("isQuotaExhaustedForRequest isolates Claude and Gemini quota families for a
quotaCache.isQuotaExhaustedForRequest(
connectionId,
"antigravity",
"antigravity/gemini-3.5-flash-high"
"antigravity/gemini-3.7-flash-high"
),
false,
"Gemini Flash should NOT be exhausted"
@@ -58,7 +58,7 @@ test("isQuotaExhaustedForRequest isolates Claude and Gemini quota families for a
quotaCache.isQuotaExhaustedForRequest(
connectionId,
"antigravity",
"antigravity/gemini-2.5-pro"
"antigravity/gemini-pro-agent"
),
false,
"Gemini Pro should share Gemini family quota and NOT be exhausted"
@@ -68,7 +68,7 @@ test("isQuotaExhaustedForRequest isolates Claude and Gemini quota families for a
const connectionIdAgy = "conn-agy-test";
quotaCache.setQuotaCache(connectionIdAgy, "agy", {
"claude-opus-4-6-thinking": { remainingPercentage: 0, resetAt: null },
"gemini-3.5-flash-high": { remainingPercentage: 100, resetAt: null },
"gemini-3.7-flash-high": { remainingPercentage: 100, resetAt: null },
});
assert.equal(
@@ -77,7 +77,7 @@ test("isQuotaExhaustedForRequest isolates Claude and Gemini quota families for a
"Claude Opus under 'agy' should be exhausted"
);
assert.equal(
quotaCache.isQuotaExhaustedForRequest(connectionIdAgy, "agy", "agy/gemini-3.5-flash-high"),
quotaCache.isQuotaExhaustedForRequest(connectionIdAgy, "agy", "agy/gemini-3.7-flash-high"),
false,
"Gemini Flash under 'agy' should NOT be exhausted"
);
@@ -112,8 +112,8 @@ test("isQuotaExhaustedForRequest isolates Claude and Gemini quota families for a
test("isQuotaExhaustedForRequest scopes gemini exhaustion to the requested model, not sibling models", () => {
const connectionId = "conn-gemini-sibling-test";
quotaCache.setQuotaCache(connectionId, "antigravity", {
"gemini-3.6-flash-medium": { remainingPercentage: 0, resetAt: null },
"gemini-2.5-pro": { remainingPercentage: 100, resetAt: null },
"gemini-3.7-flash-medium": { remainingPercentage: 0, resetAt: null },
"gemini-pro-agent": { remainingPercentage: 100, resetAt: null },
gemini_weekly: { remainingPercentage: 0, resetAt: null },
});
@@ -121,33 +121,33 @@ test("isQuotaExhaustedForRequest scopes gemini exhaustion to the requested model
quotaCache.isQuotaExhaustedForRequest(
connectionId,
"antigravity",
"antigravity/gemini-3.6-flash-medium"
"antigravity/gemini-3.7-flash-medium"
),
true,
"gemini-3.6 at 0% should be exhausted even when gemini-2.5-pro still has quota"
"gemini-3.7 at 0% should be exhausted even when gemini-pro-agent still has quota"
);
assert.equal(
quotaCache.isQuotaExhaustedForRequest(
connectionId,
"antigravity",
"antigravity/gemini-2.5-pro"
"antigravity/gemini-pro-agent"
),
false,
"gemini-2.5-pro should remain available when only gemini-3.6 is depleted"
"gemini-pro-agent should remain available when only gemini-3.7 Flash is depleted"
);
});
test("isQuotaExhaustedForRequest treats near-zero remaining as exhausted at default threshold", () => {
const connectionId = "conn-near-zero-test";
quotaCache.setQuotaCache(connectionId, "antigravity", {
"gemini-3.6-flash-medium": { remainingPercentage: 0.00000167, resetAt: null },
"gemini-3.7-flash-medium": { remainingPercentage: 0.00000167, resetAt: null },
});
assert.equal(
quotaCache.isQuotaExhaustedForRequest(
connectionId,
"antigravity",
"antigravity/gemini-3.6-flash-medium"
"antigravity/gemini-3.7-flash-medium"
),
true,
"effectively-zero remaining should count as exhausted"

View File

@@ -17,44 +17,40 @@ import { CLI_TOOLS } from "../../src/shared/constants/cliTools.ts";
const RETIRED_PUBLIC_MODELS = [
"gemini-3-pro-preview",
"gemini-3.6-flash-high",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-low",
"gemini-3-flash-agent",
"gemini-3.5-flash-low",
"gemini-3.5-flash-extra-low",
"gemini-2.5-pro",
"gemini-2.5-flash-thinking",
"gemini-2.5-flash",
"gemini-2.5-flash-lite",
"gemini-2.5-computer-use-preview-10-2025",
] as const;
const EXPECTED_LEADING_MODEL_ORDER = [
"gemini-3.6-flash-high",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-low",
"claude-opus-4-6-thinking",
"claude-sonnet-4-6",
"gemini-pro-agent",
"gemini-3.1-pro-low",
"gemini-3-flash-agent",
"gemini-3.5-flash-low",
"gemini-3.5-flash-extra-low",
] as const;
const EXPECTED_ANTIGRAVITY_LEADING_MODEL_ORDER = [
"gemini-3.7-flash-high",
"gemini-3.7-flash-medium",
...EXPECTED_LEADING_MODEL_ORDER,
"gemini-3.7-flash-low",
"gemini-pro-agent",
"gemini-3.1-pro-low",
"gemini-3.1-flash-lite",
"claude-opus-4-6-thinking",
"claude-sonnet-4-6",
"gpt-oss-120b-medium",
] as const;
const ACTIVE_FLASH_MODEL_IDS = [
"gemini-3-flash-agent",
"gemini-3.5-flash-low",
"gemini-3.5-flash-extra-low",
] as const;
const CURRENT_36_FLASH_MODEL_IDS = [
"gemini-3.6-flash-high",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-low",
"gemini-3.7-flash-high",
"gemini-3.7-flash-medium",
"gemini-3.7-flash-low",
] as const;
test("Antigravity and AGY place their live Gemini Flash tiers first", () => {
for (const [provider, models, expectedOrder] of [
["antigravity", ANTIGRAVITY_PUBLIC_MODELS, EXPECTED_ANTIGRAVITY_LEADING_MODEL_ORDER],
["antigravity", ANTIGRAVITY_PUBLIC_MODELS, EXPECTED_LEADING_MODEL_ORDER],
["agy", AGY_PUBLIC_MODELS, EXPECTED_LEADING_MODEL_ORDER],
] as const) {
assert.deepEqual(
@@ -114,7 +110,7 @@ test("Antigravity and AGY expose gemini-pro-agent and gemini-3.1-pro-high as cal
);
});
test("Antigravity support catalogs expose every live Gemini 3.6 Flash tier", () => {
test("Antigravity support catalogs expose every live Gemini 3.7 Flash tier", () => {
const antigravityModelIds = new Set(ANTIGRAVITY_PUBLIC_MODELS.map((model) => model.id));
const agyModelIds = new Set(AGY_PUBLIC_MODELS.map((model) => model.id));
const cliAliases = new Set(CLI_TOOLS.antigravity.modelAliases);
@@ -123,7 +119,7 @@ test("Antigravity support catalogs expose every live Gemini 3.6 Flash tier", ()
FREE_MODEL_BUDGETS.filter((model) => model.provider === "agy").map((model) => model.modelId)
);
for (const modelId of CURRENT_36_FLASH_MODEL_IDS) {
for (const modelId of ACTIVE_FLASH_MODEL_IDS) {
assert.equal(antigravityModelIds.has(modelId), true, `${modelId} missing from Antigravity`);
assert.equal(agyModelIds.has(modelId), true, `${modelId} missing from AGY`);
assert.equal(cliAliases.has(modelId), true, `${modelId} missing from CLI aliases`);
@@ -142,7 +138,7 @@ test("Antigravity support catalogs no longer advertise or price the rejected Hig
assert.ok(pricing["gemini-pro-agent"]);
});
test("Antigravity and AGY support metadata excludes the retired Gemini 3 Flash id", () => {
test("Antigravity and AGY support metadata excludes retired Flash ids", () => {
const cliAliases = CLI_TOOLS.antigravity.modelAliases;
const cliModelIds = CLI_TOOLS.antigravity.defaultModels.map((model) => model.id);
const agyFreeModelIds = FREE_MODEL_BUDGETS.filter((model) => model.provider === "agy").map(

View File

@@ -10,7 +10,7 @@ import { FORMATS } from "../../open-sse/translator/formats.ts";
test("antigravity reasoning capabilities: Gemini and Claude models support reasoning", () => {
const geminiModels = [
"antigravity/gemini-3-flash-agent",
"antigravity/gemini-3.7-flash-high",
"antigravity/gemini-pro-agent",
"antigravity/gemini-3.1-pro-low",
"antigravity/claude-sonnet-4-6",

View File

@@ -35,7 +35,7 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: {
remainingFraction: undefined,
resetTime: "2026-05-26T00:00:00Z",
@@ -51,8 +51,8 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
assert.ok("quotas" in result, "should have quotas");
if ("quotas" in result) {
const quota = result.quotas["gemini-3-flash-agent"];
assert.ok(quota, "should have quota for gemini-3-flash-agent");
const quota = result.quotas["gemini-3.7-flash-high"];
assert.ok(quota, "should have quota for gemini-3.7-flash-high");
assert.equal(quota.remainingPercentage, 0, "remaining should be 0%");
assert.equal(quota.unlimited, false, "should not be unlimited");
assert.equal(quota.used > 0, true, "used should be > 0 when quota is exhausted");
@@ -69,7 +69,7 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: {
remainingFraction: 0,
resetTime: "2026-05-26T00:00:00Z",
@@ -88,8 +88,8 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
assert.ok("quotas" in result, "should have quotas");
if ("quotas" in result) {
const quota = result.quotas["gemini-3-flash-agent"];
assert.ok(quota, "should have quota for gemini-3-flash-agent");
const quota = result.quotas["gemini-3.7-flash-high"];
assert.ok(quota, "should have quota for gemini-3.7-flash-high");
assert.equal(quota.remainingPercentage, 0, "remaining should be 0%");
assert.equal(quota.unlimited, false, "should not be unlimited");
}
@@ -105,7 +105,7 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: {
remainingFraction: 1.0,
resetTime: "2026-05-26T00:00:00Z",
@@ -124,8 +124,8 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
assert.ok("quotas" in result, "should have quotas");
if ("quotas" in result) {
const quota = result.quotas["gemini-3-flash-agent"];
assert.ok(quota, "should have quota for gemini-3-flash-agent");
const quota = result.quotas["gemini-3.7-flash-high"];
assert.ok(quota, "should have quota for gemini-3.7-flash-high");
assert.equal(quota.remainingPercentage, 100, "remaining should be 100%");
assert.equal(quota.unlimited, false, "should not be unlimited (has resetTime)");
}
@@ -176,7 +176,7 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: {
remainingFraction: 0.5,
resetTime: "2026-05-26T00:00:00Z",
@@ -195,8 +195,8 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
assert.ok("quotas" in result, "should have quotas");
if ("quotas" in result) {
const quota = result.quotas["gemini-3-flash-agent"];
assert.ok(quota, "should have quota for gemini-3-flash-agent");
const quota = result.quotas["gemini-3.7-flash-high"];
assert.ok(quota, "should have quota for gemini-3.7-flash-high");
assert.equal(quota.remainingPercentage, 50, "remaining should be 50%");
assert.equal(quota.unlimited, false, "should not be unlimited");
}
@@ -212,7 +212,7 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: {
remainingFraction: 1.5,
resetTime: "2026-05-26T00:00:00Z",
@@ -231,8 +231,8 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
assert.ok("quotas" in result, "should have quotas");
if ("quotas" in result) {
const quota = result.quotas["gemini-3-flash-agent"];
assert.ok(quota, "should have quota for gemini-3-flash-agent");
const quota = result.quotas["gemini-3.7-flash-high"];
assert.ok(quota, "should have quota for gemini-3.7-flash-high");
assert.equal(quota.remainingPercentage, 100, "remaining should be clamped to 100%");
assert.equal(quota.unlimited, false, "should not be unlimited (has resetTime)");
}
@@ -248,7 +248,7 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: {
remainingFraction: -0.5,
resetTime: "2026-05-26T00:00:00Z",
@@ -267,8 +267,8 @@ describe("getUsageForProvider (antigravity in usage.ts)", () => {
assert.ok("quotas" in result, "should have quotas");
if ("quotas" in result) {
const quota = result.quotas["gemini-3-flash-agent"];
assert.ok(quota, "should have quota for gemini-3-flash-agent");
const quota = result.quotas["gemini-3.7-flash-high"];
assert.ok(quota, "should have quota for gemini-3.7-flash-high");
assert.equal(quota.remainingPercentage, 0, "remaining should be clamped to 0%");
assert.equal(quota.unlimited, false, "should not be unlimited");
}

View File

@@ -176,7 +176,7 @@ test("getUsageForProvider(antigravity) merges weekly quotas with the selected CL
json: async () => ({
buckets: [
{
modelId: "gemini-3-flash-agent",
modelId: "gemini-3.7-flash-high",
remainingFraction: 0.4,
resetTime: RESET_IN_2_HOURS,
},
@@ -190,7 +190,7 @@ test("getUsageForProvider(antigravity) merges weekly quotas with the selected CL
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: { remainingFraction: 1.0, resetTime: RESET_IN_2_HOURS },
},
},
@@ -211,8 +211,8 @@ test("getUsageForProvider(antigravity) merges weekly quotas with the selected CL
const quotas = (result as UsageResult).quotas;
// Existing per-model 5h quota is untouched.
assert.ok(quotas["gemini-3-flash-agent"], "per-model 5h quota still present");
assert.equal(quotas["gemini-3-flash-agent"].quotaSource, "retrieveUserQuota");
assert.ok(quotas["gemini-3.7-flash-high"], "per-model 5h quota still present");
assert.equal(quotas["gemini-3.7-flash-high"].quotaSource, "retrieveUserQuota");
// New weekly group quota is merged in alongside it.
assert.ok(quotas.gemini_weekly, "weekly group quota merged in");
@@ -235,7 +235,7 @@ test("getUsageForProvider(antigravity) is unaffected when retrieveUserQuotaSumma
ok: true,
json: async () => ({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: { remainingFraction: 1.0, resetTime: RESET_IN_2_HOURS },
},
},
@@ -253,6 +253,6 @@ test("getUsageForProvider(antigravity) is unaffected when retrieveUserQuotaSumma
const result = await getUsageForProvider(connection, { forceRefresh: true });
const quotas = (result as UsageResult).quotas;
assert.ok(quotas["gemini-3-flash-agent"], "per-model quota still present without weekly data");
assert.ok(quotas["gemini-3.7-flash-high"], "per-model quota still present without weekly data");
assert.equal(quotas.gemini_weekly, undefined, "no weekly key when the RPC is unavailable");
});

View File

@@ -11,7 +11,7 @@
* `chat.ts` routes through `resolveAutoRoutingState` → `createVirtualAutoCombo`
* (autoRouting.ts), NOT through `createBuiltinAutoCombo`. So the chat path
* skipped the tier filter entirely and `auto/best-free` behaved as plain
* `auto/cheap`, allowing paid models (e.g. antigravity/gemini-3.6-flash-high)
* `auto/cheap`, allowing paid models (e.g. antigravity/gemini-3.7-flash-high)
* to be selected from the full pool.
*
* classifyAutoModel() is module-private, so this exercises it through the public

View File

@@ -87,9 +87,9 @@ test("credentialed providers expose one logical candidate per visible registry m
);
for (const model of [
"antigravity/claude-sonnet-4-6",
"antigravity/gemini-3.6-flash-low",
"antigravity/gemini-3.6-flash-medium",
"antigravity/gemini-3.6-flash-high",
"antigravity/gemini-3.7-flash-low",
"antigravity/gemini-3.7-flash-medium",
"antigravity/gemini-3.7-flash-high",
]) {
assert.ok(modelStrings.includes(model), `${model} should be eligible for auto routing`);
}
@@ -121,15 +121,15 @@ test("candidate transparency expands a logical model into per-account rows", asy
});
test("connection model exclusions narrow only that model's account allowlist", async () => {
const { first, second } = await seedConnections(["gemini-3.6-*"]);
const { first, second } = await seedConnections(["gemini-3.7-*"]);
const combo = await virtualFactory.createVirtualAutoCombo(undefined);
const candidates = antigravityCandidates(combo);
const geminiCandidates = candidates.filter((candidate) =>
candidate.model.startsWith("antigravity/gemini-3.6-")
candidate.model.startsWith("antigravity/gemini-3.7-")
);
assert.ok(geminiCandidates.length >= 3, "Gemini 3.6 candidates should remain available");
assert.ok(geminiCandidates.length >= 3, "Gemini 3.7 candidates should remain available");
for (const candidate of geminiCandidates) {
assert.deepEqual(candidate.allowedConnectionIds, [second.id]);
}
@@ -150,9 +150,9 @@ test("hiding the first registry model does not drop the credentialed provider",
assert.equal(modelStrings.includes("antigravity/claude-sonnet-4-6"), false);
for (const model of [
"antigravity/gemini-3.6-flash-low",
"antigravity/gemini-3.6-flash-medium",
"antigravity/gemini-3.6-flash-high",
"antigravity/gemini-3.7-flash-low",
"antigravity/gemini-3.7-flash-medium",
"antigravity/gemini-3.7-flash-high",
]) {
assert.ok(modelStrings.includes(model), `${model} should remain after Sonnet is hidden`);
}

View File

@@ -181,16 +181,10 @@ test("Antigravity Gemini-family regression covers every current callable tier",
for (const model of [
"gemini-pro-agent",
"gemini-3.1-pro-low",
"gemini-3.6-flash-high",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-low",
"gemini-3-flash-agent",
"gemini-3.5-flash-low",
"gemini-3.5-flash-extra-low",
"gemini-3.7-flash-high",
"gemini-3.7-flash-medium",
"gemini-3.7-flash-low",
"gemini-3.1-flash-lite",
"gemini-2.5-flash-thinking",
"gemini-2.5-flash",
"gemini-2.5-flash-lite",
]) {
assert.ok(coveredModels.has(model), `expected regression coverage for ${model}`);
}

View File

@@ -210,7 +210,7 @@ test("priority combo: quota 429 on passthrough provider does not skip another mo
const combo = await combosDb.createCombo({
name: "passthrough-quota-scope",
strategy: "priority",
models: ["antigravity/claude-opus-4-6-thinking", "antigravity/gemini-3-flash-agent"],
models: ["antigravity/claude-opus-4-6-thinking", "antigravity/gemini-3.7-flash-high"],
});
const response = await handleComboChat({
@@ -235,7 +235,7 @@ test("priority combo: quota 429 on passthrough provider does not skip another mo
});
assert.equal(response.status, 200);
assert.equal(calls.at(-1), "antigravity/gemini-3-flash-agent");
assert.equal(calls.at(-1), "antigravity/gemini-3.7-flash-high");
assert.ok(
calls.includes("antigravity/claude-opus-4-6-thinking"),
"first passthrough model should be attempted before fallback"

View File

@@ -28,10 +28,10 @@ test("Devin CLI catalog includes the refreshed native model ids", () => {
"claude-opus-5-high",
"claude-opus-5-xhigh",
"claude-opus-5-max",
"gemini-3-6-flash-minimal",
"gemini-3-6-flash-low",
"gemini-3-6-flash-medium",
"gemini-3-6-flash-high",
"gemini-3-7-flash-minimal",
"gemini-3-7-flash-low",
"gemini-3-7-flash-medium",
"gemini-3-7-flash-high",
"kimi-k3-low",
"kimi-k3-high",
"kimi-k3-max",

View File

@@ -29,7 +29,7 @@ test("getExecutor('antigravity') returns AntigravityExecutor", () => {
test("getExecutor('agy') builds valid streaming URL", () => {
const executor = getExecutor("agy");
const url = executor.buildUrl("gemini-3.5-flash-high", true);
const url = executor.buildUrl("gemini-3.7-flash-high", true);
assert.ok(
url.includes("streamGenerateContent?alt=sse"),
`expected streaming endpoint URL, got: ${url}`
@@ -38,7 +38,7 @@ test("getExecutor('agy') builds valid streaming URL", () => {
test("getExecutor('agy') builds valid non-streaming URL", () => {
const executor = getExecutor("agy");
const url = executor.buildUrl("gemini-3.5-flash-high", false);
const url = executor.buildUrl("gemini-3.7-flash-high", false);
// Antigravity executor always uses streaming endpoint (buildUrl ignores stream flag)
assert.ok(
url.includes("streamGenerateContent?alt=sse"),

View File

@@ -516,7 +516,7 @@ test("AntigravityExecutor.collectStreamToResponse converts textual tool call SSE
const result = await executor.collectStreamToResponse(
response,
"gemini-3.5-flash-low",
"gemini-3.7-flash-low",
"https://example.com",
{ Authorization: "Bearer ag-token" },
{ request: {} }

View File

@@ -48,11 +48,27 @@ describe("NotionWebExecutor — registry consistency", () => {
it("registers a model catalog reachable via getModelsByProviderId", () => {
const models = getModelsByProviderId("notion-web");
assert.ok(models.length >= 1);
assert.ok(models.some((m) => m.id === "notion-ai"));
// Seed catalog uses real web-picker labels (fable-5 / gpt-5.6-sol), not food codenames.
assert.ok(
models.some((m) => m.id === "fable-5" || m.id === "gpt-5.6-sol" || m.id === "opus-4.8")
assert.deepEqual(
models.map((m) => m.id),
[
"notion-ai",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
"gpt-5.4-mini",
"gpt-5.4-nano",
"gemini-3.7-flash",
"gemini-3.1-pro",
"fable-5",
"opus-5",
"sonnet-5",
"haiku-4.5",
"grok-4.6",
"kimi-k3",
"kimi-k2.7-code",
"deepseek-v4-pro",
"glm-5.2",
]
);
assert.equal(
models.some(
@@ -103,6 +119,7 @@ const COOKIE_WITH_SPACE = "token_v2=xyz; space_id=space-1; notion_user_id=user-1
describe("NotionWebExecutor — upstream translation (mocked TLS fetch)", () => {
it("posts createThread + config/context/user and returns a chat.completion", async () => {
mod.__resetNotionThreadSessionsForTests();
const executor = new mod.NotionWebExecutor();
let capturedUrl = "";
let capturedHeaders: Record<string, string> = {};

View File

@@ -213,7 +213,7 @@ describe("PromptQl — helpers", () => {
assert.equal(models.clientFacingPromptQlModelId("pql/gpt-5.6-sol"), "gpt-5.6-sol");
const r = models.resolvePromptQlModel("Claude Fable 5");
assert.ok(r);
assert.equal(r!.id, "vertex-claude-fable-5");
assert.equal(r!.id, "bedrock-claude-fable-5");
});
it("converts credit micros to USD", () => {

View File

@@ -1,86 +0,0 @@
/**
* Google AI Studio (Gemini API) Imagen support on /v1/images/generations.
*
* Imagen uses the dedicated ":predict" endpoint (instances/parameters body,
* base64 predictions), NOT generateContent. Before this, `gemini/imagen-4.0-*`
* was advertised in /v1/models but unroutable — the image route rejected it with
* "Invalid image model" because `gemini` was not in the image registry.
*
* These cover the pure request-builder / response-parser and the registry wiring.
* The live Google call is not exercised (Imagen needs a billing-enabled key).
*/
import test from "node:test";
import assert from "node:assert/strict";
import { IMAGE_PROVIDERS, parseImageModel } from "../../open-sse/config/imageRegistry.ts";
import {
buildImagenPredictBody,
parseImagenPredictResponse,
isImagenModel,
} from "../../open-sse/handlers/imageGeneration/providers/googleImagen.ts";
test("gemini image provider is registered for the Imagen family via google-imagen format", () => {
const gemini = IMAGE_PROVIDERS.gemini;
assert.ok(gemini, "gemini image provider must exist");
assert.equal(gemini.format, "google-imagen");
assert.equal(gemini.authHeader, "x-goog-api-key");
assert.equal(gemini.baseUrl, "https://generativelanguage.googleapis.com/v1beta/models");
assert.deepEqual(
gemini.models.map((m) => m.id),
["imagen-4.0-generate-001", "imagen-4.0-ultra-generate-001", "imagen-4.0-fast-generate-001"]
);
});
test("parseImageModel resolves gemini/imagen-4.0-* to the gemini provider", () => {
assert.deepEqual(parseImageModel("gemini/imagen-4.0-generate-001"), {
provider: "gemini",
model: "imagen-4.0-generate-001",
});
});
test("isImagenModel gates only the Imagen family (flash-image belongs on the chat route)", () => {
assert.equal(isImagenModel("imagen-4.0-generate-001"), true);
assert.equal(isImagenModel("imagen-4.0-ultra-generate-001"), true);
assert.equal(isImagenModel("gemini-2.5-flash-image"), false);
assert.equal(isImagenModel("nano-banana-pro"), false);
assert.equal(isImagenModel(""), false);
assert.equal(isImagenModel(undefined), false);
});
test("buildImagenPredictBody produces the :predict instances/parameters shape", () => {
const body = buildImagenPredictBody({ prompt: "a red apple", n: 2, size: "1792x1024" });
assert.deepEqual(body, {
instances: [{ prompt: "a red apple" }],
parameters: { sampleCount: 2, aspectRatio: "16:9" },
});
});
test("buildImagenPredictBody clamps sampleCount to [1,4] and defaults aspectRatio to 1:1", () => {
assert.equal(buildImagenPredictBody({ prompt: "x" }).parameters.sampleCount, 1);
assert.equal(buildImagenPredictBody({ prompt: "x", n: 0 }).parameters.sampleCount, 1);
assert.equal(buildImagenPredictBody({ prompt: "x", n: 99 }).parameters.sampleCount, 4);
assert.equal(buildImagenPredictBody({ prompt: "x" }).parameters.aspectRatio, "1:1");
// Native aspect ratio passes through.
assert.equal(buildImagenPredictBody({ prompt: "x", aspect_ratio: "9:16" }).parameters.aspectRatio, "9:16");
});
test("parseImagenPredictResponse normalizes predictions[].bytesBase64Encoded to OpenAI shape", () => {
const out = parseImagenPredictResponse(
{
predictions: [
{ bytesBase64Encoded: "AAAA", mimeType: "image/png" },
{ bytesBase64Encoded: "BBBB", mimeType: "image/png" },
],
},
"a red apple"
);
assert.equal(out.data.length, 2);
assert.deepEqual(out.data[0], { b64_json: "AAAA", revised_prompt: "a red apple" });
assert.equal(typeof out.created, "number");
});
test("parseImagenPredictResponse tolerates empty/absent predictions", () => {
assert.deepEqual(parseImagenPredictResponse({}, "x").data, []);
assert.deepEqual(parseImagenPredictResponse({ predictions: [] }, "x").data, []);
assert.deepEqual(parseImagenPredictResponse({ predictions: [{}] }, "x").data, []);
});

View File

@@ -3,9 +3,7 @@ import assert from "node:assert/strict";
import { parseGeminiModelsList } from "../../src/lib/providerModels/geminiModelsParser";
// A representative slice of the live generativelanguage v1beta/models response — including the
// image models (gemini-*-image via generateContent, imagen-* via predict) that the Vertex catalog
// must surface dynamically.
// A representative slice of the live generativelanguage v1beta/models response.
const SAMPLE = {
models: [
{
@@ -21,11 +19,6 @@ const SAMPLE = {
displayName: "Gemini 3 Pro Image Preview",
supportedGenerationMethods: ["generateContent", "countTokens"],
},
{
name: "models/imagen-4.0-generate-001",
displayName: "Imagen 4.0",
supportedGenerationMethods: ["predict"],
},
{
name: "models/text-embedding-004",
displayName: "Text Embedding 004",
@@ -41,13 +34,6 @@ const SAMPLE = {
displayName: "Veo 3.0",
supportedGenerationMethods: ["predictLongRunning"],
},
{
// Defensive: an Imagen model exposed via a long-running method must stay
// "images", never "video".
name: "models/imagen-future-preview",
displayName: "Imagen Future",
supportedGenerationMethods: ["predictLongRunning"],
},
],
};
@@ -70,13 +56,6 @@ test("parseGeminiModelsList maps generateContent image models to the chat endpoi
assert.deepEqual(proImage!.supportedEndpoints, ["chat"]);
});
test("parseGeminiModelsList maps Imagen predict models to the images endpoint", () => {
const models = parseGeminiModelsList(SAMPLE);
const imagen = models.find((m) => m.id === "imagen-4.0-generate-001");
assert.ok(imagen, "imagen-4.0-generate-001 should be present");
assert.deepEqual(imagen!.supportedEndpoints, ["images"]);
});
test("parseGeminiModelsList maps embedContent and bidiGenerateContent", () => {
const models = parseGeminiModelsList(SAMPLE);
assert.deepEqual(models.find((m) => m.id === "text-embedding-004")!.supportedEndpoints, [
@@ -94,13 +73,6 @@ test("parseGeminiModelsList maps Veo predictLongRunning models to the video endp
assert.deepEqual(veo!.supportedEndpoints, ["video"]);
});
test("parseGeminiModelsList keeps Imagen as images even via a long-running method", () => {
const models = parseGeminiModelsList(SAMPLE);
const imagen = models.find((m) => m.id === "imagen-future-preview");
assert.ok(imagen, "imagen-future-preview should be present");
assert.deepEqual(imagen!.supportedEndpoints, ["images"]);
});
test("parseGeminiModelsList defaults to chat and tolerates empty/missing input", () => {
assert.deepEqual(parseGeminiModelsList({}), []);
assert.deepEqual(parseGeminiModelsList(null), []);

View File

@@ -171,7 +171,7 @@ test("Provider: gemini-web has correct models", async () => {
models.map((m: any) => [m.id, m.name]),
[
["gemini-3.1-pro", "Gemini 3.1 Pro"],
["gemini-3.5-flash", "Gemini 3.5 Flash"],
["gemini-3.7-flash", "Gemini 3.7 Flash"],
["gemini-3.1-flash-lite", "Gemini 3.1 Flash-Lite"],
]
);

View File

@@ -8,7 +8,8 @@
import test from "node:test";
import assert from "node:assert/strict";
const { gheCopilotProvider } = await import("../../open-sse/config/providers/registry/ghe-copilot/index.ts");
const { gheCopilotProvider } =
await import("../../open-sse/config/providers/registry/ghe-copilot/index.ts");
const { getModelsByProviderId } = await import("../../open-sse/config/providerModels.ts");
type ModelEntry = { id: string; targetFormat?: string; [k: string]: unknown };
@@ -30,7 +31,7 @@ const MUST_NOT_BE_RESPONSES = [
"claude-sonnet-4.5",
"claude-haiku-4.5",
"gemini-3.1-pro-preview",
"gemini-3.5-flash",
"gemini-3.7-flash",
];
for (const id of MUST_NOT_BE_RESPONSES) {

View File

@@ -140,7 +140,7 @@ test("curated Copilot allowlist contains the final approved model ids only", ()
"claude-sonnet-4.5",
"claude-haiku-4.5",
"gemini-3.1-pro-preview",
"gemini-3.5-flash",
"gemini-3.7-flash",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",

View File

@@ -154,7 +154,7 @@ test("unknown models keep maxOutputTokens null instead of using a generic defaul
);
});
test("Antigravity Gemini 3.5 upstream IDs share the Flash capability profile", () => {
test("provider-neutral Gemini 3.5 tier IDs retain their non-thinking capabilities", () => {
for (const modelId of [
"gemini-3.5-flash-extra-low",
"gemini-3.5-flash-low",
@@ -162,22 +162,21 @@ test("Antigravity Gemini 3.5 upstream IDs share the Flash capability profile", (
]) {
const spec = MODEL_SPECS[modelId];
assert.ok(spec, `missing exact MODEL_SPECS entry for ${modelId}`);
const capabilities = modelCapabilities.getResolvedModelCapabilities(`antigravity/${modelId}`);
const capabilities = modelCapabilities.getResolvedModelCapabilities(modelId);
assert.equal(capabilities.contextWindow, 1048576, modelId);
assert.equal(capabilities.maxOutputTokens, 65536, modelId);
assert.equal(capabilities.supportsThinking, true, modelId);
// These ids encode the upstream reasoning tier and do not accept a client-supplied effort.
assert.equal(capabilities.supportsThinking, false, modelId);
assert.equal(capabilities.supportsTools, true, modelId);
assert.equal(capabilities.supportsVision, true, modelId);
}
});
test("Antigravity Gemini 3.7 and 3.6 tier IDs share the Flash capability profile", () => {
test("Antigravity Gemini 3.7 tier IDs share the Flash capability profile", () => {
for (const modelId of [
"gemini-3.7-flash-high",
"gemini-3.7-flash-medium",
"gemini-3.6-flash-high",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-low",
"gemini-3.7-flash-low",
]) {
const spec = MODEL_SPECS[modelId];
assert.ok(spec, `missing exact MODEL_SPECS entry for ${modelId}`);

View File

@@ -696,12 +696,15 @@ test("v1 models catalog exposes current Antigravity aliases without retired mode
assert.equal(ids.has("antigravity/gemini-3.1-pro"), false);
assert.equal(ids.has("antigravity/gemini-2.5-computer-use-preview-10-2025"), false);
assert.equal(ids.has("antigravity/rev19-uic3-1p"), false);
assert.ok(ids.has("antigravity/gemini-3.6-flash-high"));
assert.ok(ids.has("antigravity/gemini-3.6-flash-medium"));
assert.ok(ids.has("antigravity/gemini-3.6-flash-low"));
assert.ok(ids.has("antigravity/gemini-3.5-flash-extra-low"));
assert.ok(ids.has("antigravity/gemini-3.5-flash-low"));
assert.ok(ids.has("antigravity/gemini-3-flash-agent"));
assert.ok(ids.has("antigravity/gemini-3.7-flash-high"));
assert.ok(ids.has("antigravity/gemini-3.7-flash-medium"));
assert.ok(ids.has("antigravity/gemini-3.7-flash-low"));
assert.equal(ids.has("antigravity/gemini-3.6-flash-high"), false);
assert.equal(ids.has("antigravity/gemini-3.6-flash-medium"), false);
assert.equal(ids.has("antigravity/gemini-3.6-flash-low"), false);
assert.equal(ids.has("antigravity/gemini-3.5-flash-extra-low"), false);
assert.equal(ids.has("antigravity/gemini-3.5-flash-low"), false);
assert.equal(ids.has("antigravity/gemini-3-flash-agent"), false);
assert.equal(ids.has("antigravity/gemini-3.5-flash-medium"), false);
assert.equal(ids.has("antigravity/gemini-3.5-flash-high"), false);
assert.equal(ids.has("antigravity/gemini-3.5-flash-preview"), false);

View File

@@ -98,7 +98,12 @@ test("#8353 catalog: aliases are NOT synthesized on opencode-zen", () => {
for (const { alias } of ISSUE_ALIASES) {
assert.equal(zenIds.has(alias), false, `opencode-zen must not expose ${alias}`);
}
// kimi-k3 became a live zen model in the 2026-08-17 registry sync (present in
// https://opencode.ai/zen/v1/models) — only the remaining Go-tier-only bases
// must stay absent from zen. The effort alias kimi-k3-max is still Go-only
// and remains covered by the ISSUE_ALIASES loop above.
for (const base of NEW_BASES) {
if (base === "kimi-k3") continue;
assert.equal(zenIds.has(base), false, `opencode-zen must not expose base ${base}`);
}
});

View File

@@ -27,9 +27,14 @@ test("Perplexity Web registers the refreshed model catalog", () => {
"pplx-sonar",
"pplx-kimi",
"pplx-glm",
"pplx-grok-4.5",
"pplx-grok-4.6",
];
assert.deepEqual([...modelIds].sort(), expectedModelIds.sort());
const modelNames = new Map(models.map((model) => [model.id, model.name]));
assert.equal(modelNames.get("pplx-gemini"), "Gemini 3.7 Flash (via Perplexity)");
assert.equal(modelNames.get("pplx-kimi"), "Kimi K3 (via Perplexity)");
assert.equal(modelNames.get("pplx-grok-4.6"), "Grok 4.6 (via Perplexity)");
});
test("every advertised Perplexity Web model has an explicit internal mapping", () => {
@@ -37,10 +42,16 @@ test("every advertised Perplexity Web model has an explicit internal mapping", (
assert.deepEqual(missing, []);
assert.deepEqual(MODEL_MAP["pplx-gpt-5.6-terra"], ["copilot", "gpt56_terra"]);
assert.deepEqual(MODEL_MAP["pplx-gpt-5.6-sol"], ["copilot", "gpt56_sol"]);
assert.deepEqual(MODEL_MAP["pplx-grok-4.5"], ["copilot", "grok45low"]);
assert.deepEqual(MODEL_MAP["pplx-gemini"], ["copilot", "gemini37flash"]);
assert.deepEqual(MODEL_MAP["pplx-kimi"], ["copilot", "kimik3thinking"]);
assert.deepEqual(MODEL_MAP["pplx-grok-4.6"], ["copilot", "grok46low"]);
assert.deepEqual(MODEL_MAP["pplx-opus"], ["copilot", "claude50opus"]);
assert.equal(THINKING_MAP["pplx-gemini"], "gemini37flashthinking");
assert.equal(THINKING_MAP["pplx-opus"], "claude50opusthinking");
assert.equal(THINKING_MAP["pplx-gpt-5.6-terra"], "gpt56_terra_thinking");
assert.equal(THINKING_MAP["pplx-gpt-5.6-sol"], "gpt56_sol_thinking");
assert.equal(THINKING_MAP["pplx-grok-4.5"], "grok45medium");
assert.equal(THINKING_MAP["pplx-kimi"], "kimik3thinking");
assert.equal(THINKING_MAP["pplx-grok-4.6"], "grok46medium");
assert.equal(MODEL_MAP["pplx-grok-4.5"], undefined);
assert.equal(THINKING_MAP["pplx-grok-4.5"], undefined);
});

View File

@@ -3,55 +3,52 @@ import assert from "node:assert/strict";
import { getDefaultPricing } from "../../src/shared/constants/pricing.ts";
// Antigravity exposes Gemini 3.5 Flash via three public client IDs in
// ANTIGRAVITY_PUBLIC_MODELS (`open-sse/config/antigravityModelAliases.ts`):
// - gemini-3.5-flash-extra-low → "Gemini 3.5 Flash (Low)" — upstream Low tier
// - gemini-3-flash-agent → "Gemini 3.5 Flash (High)" — upstream High tier
// - gemini-3.5-flash-low → "Gemini 3.5 Flash (Medium)" — upstream Medium tier
// - gemini-pro-agent → "Gemini 3.1 Pro (High)" — upstream Pro High alias
// All three were missing pricing rows in `ag` (DEFAULT_PRICING.ag), so
// getPricingForModel("ag", id) returned null and downstream cost / quota
// calculations silently fell back to $0. Each row matches its upstream quota tier.
const EXPECTED_GEMINI_3_7_PROMO_PRICING = {
input: 0.75,
output: 3.75,
cached: 0.075,
reasoning: 3.75,
cache_creation: 0.75,
};
for (const [modelId, tier] of [
["gemini-3.5-flash-extra-low", "Low"],
["gemini-3.5-flash-low", "Medium"],
["gemini-3-flash-agent", "High"],
] as const) {
test(`ag/${modelId} matches the Gemini 3.5 Flash (${tier}) tier`, () => {
const p = getDefaultPricing().ag[modelId];
assert.ok(p);
assert.equal(p.input, 0.5);
assert.equal(p.output, 3.0);
assert.equal(p.cached, 0.03);
assert.equal(p.reasoning, 4.5);
assert.equal(p.cache_creation, 0.5);
});
}
const ACTIVE_FLASH_IDS = [
"gemini-3.7-flash-low",
"gemini-3.7-flash-medium",
"gemini-3.7-flash-high",
] as const;
test("ag/gemini-pro-agent matches the Gemini 3.1 Pro (High) tier", () => {
const p = getDefaultPricing().ag["gemini-pro-agent"];
assert.equal(p.input, 4.0);
assert.equal(p.output, 18.0);
assert.equal(p.cached, 0.5);
assert.equal(p.reasoning, 27.0);
assert.equal(p.cache_creation, 4.0);
const RETIRED_ANTIGRAVITY_FLASH_IDS = [
"gemini-3.6-flash-low",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-high",
"gemini-3.5-flash-extra-low",
"gemini-3.5-flash-low",
"gemini-3.5-flash-medium",
"gemini-3.5-flash-high",
"gemini-3-flash-agent",
] as const;
test("Antigravity provider namespaces price every live Gemini 3.7 Flash tier", () => {
const pricing = getDefaultPricing() as Record<string, Record<string, unknown>>;
for (const provider of ["ag", "antigravity", "agy"] as const) {
for (const modelId of ACTIVE_FLASH_IDS) {
assert.deepEqual(pricing[provider][modelId], EXPECTED_GEMINI_3_7_PROMO_PRICING);
}
}
});
// Gemini 3.6 Flash (released 2026-07-21) ships three public client IDs in
// ANTIGRAVITY_PUBLIC_MODELS (`open-sse/config/antigravityModelAliases.ts`) and
// MODEL_SPECS (`src/shared/constants/modelSpecs.ts`), but was missing pricing
// rows in `ag` (DEFAULT_PRICING.ag) — cost / quota calculations silently fell
// back to $0. Pricing: $1.50 input / $7.50 output / $0.15 cached per MTok
// (Google's 2026-07-21 announcement). Thinking tokens billed at output rate.
for (const tier of ["low", "medium", "high"]) {
test(`ag/gemini-3.6-flash-${tier} has a non-null pricing row`, () => {
const p = getDefaultPricing().ag[`gemini-3.6-flash-${tier}`];
assert.ok(p, `expected a pricing row for ag/gemini-3.6-flash-${tier}`);
assert.equal(p.input, 1.5);
assert.equal(p.output, 7.5);
assert.equal(p.cached, 0.15);
assert.equal(p.reasoning, 7.5);
assert.equal(p.cache_creation, 1.5);
});
}
test("Antigravity pricing excludes retired Flash ids without affecting other providers", () => {
const pricing = getDefaultPricing() as Record<string, Record<string, unknown>>;
for (const provider of ["ag", "antigravity", "agy"] as const) {
for (const modelId of RETIRED_ANTIGRAVITY_FLASH_IDS) {
assert.equal(pricing[provider][modelId], undefined, `${provider}/${modelId}`);
}
}
assert.ok(
pricing.orcarouter["google/gemini-3.6-flash"],
"OrcaRouter must retain the Gemini 3.6 model it still serves"
);
});

View File

@@ -87,9 +87,9 @@ test("getProviderColumns: Antigravity falls back to dynamic schema (first 3 quot
"claude-opus-4-6-thinking": { used: 0, total: 100, remainingPercentage: 100 },
"claude-sonnet-4-6": { used: 0, total: 100, remainingPercentage: 100 },
"gemini-3.1-pro-low": { used: 0, total: 100, remainingPercentage: 100 },
"gemini-3.5-flash-low": { used: 0, total: 100, remainingPercentage: 100 },
"gemini-3.5-flash-medium": { used: 0, total: 100, remainingPercentage: 100 },
"gemini-3.5-flash-high": { used: 0, total: 100, remainingPercentage: 100 },
"gemini-3.7-flash-low": { used: 0, total: 100, remainingPercentage: 100 },
"gemini-3.7-flash-medium": { used: 0, total: 100, remainingPercentage: 100 },
"gemini-3.7-flash-high": { used: 0, total: 100, remainingPercentage: 100 },
},
});

View File

@@ -99,7 +99,7 @@ test("GitHub Copilot registry reflects the current supported model lineup", () =
// the OpenAI shape. Port of decolua/9router#2608.
assert.equal(getModelTargetFormat("gh", "claude-opus-4.8-fast"), "claude");
assert.equal(getModelTargetFormat("gh", "claude-sonnet-4.6"), "claude");
assert.equal(getModelTargetFormat("gh", "gemini-3.5-flash"), null);
assert.equal(getModelTargetFormat("gh", "gemini-3.7-flash"), null);
assert.equal(getModelTargetFormat("gh", "kimi-k2.7-code"), null);
assert.equal(ids.includes("gpt-4"), false);
assert.equal(ids.includes("gpt-4o"), false);

View File

@@ -982,13 +982,6 @@ test("provider models route retries Antigravity discovery endpoints before retur
{ id: "gemini-3.7-flash-high", name: "Gemini 3.7 Flash (High)" },
{ id: "gemini-3.7-flash-medium", name: "Gemini 3.7 Flash (Medium)" },
{ id: "gemini-3.8-flash-high", name: "Gemini 3.8 Flash High" },
{ id: "gemini-3.6-flash-high", name: "Gemini 3.6 Flash (High)" },
{ id: "gemini-3.6-flash-medium", name: "Gemini 3.6 Flash (Medium)" },
{ id: "gemini-3.6-flash-low", name: "Gemini 3.6 Flash (Low)" },
{ id: "gemini-3.5-flash-extra-low", name: "Gemini 3.5 Flash (Low)" },
{ id: "gemini-3.5-flash-low", name: "Gemini 3.5 Flash (Medium)" },
{ id: "gemini-3-flash-agent", name: "Gemini 3.5 Flash (High)" },
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
]);
});

View File

@@ -40,7 +40,7 @@ const MUST_NOT_BE_RESPONSES = [
"claude-sonnet-4.5",
"claude-haiku-4.5",
"gemini-3.1-pro-preview",
"gemini-3.5-flash",
"gemini-3.7-flash",
];
for (const id of MUST_NOT_BE_RESPONSES) {

View File

@@ -13,24 +13,12 @@
import test from "node:test";
import assert from "node:assert/strict";
const { openaiProvider } = await import(
"../../open-sse/config/providers/registry/openai/index.ts"
);
const { geminiProvider } = await import(
"../../open-sse/config/providers/registry/gemini/index.ts"
);
const { openaiProvider } = await import("../../open-sse/config/providers/registry/openai/index.ts");
const { geminiProvider } = await import("../../open-sse/config/providers/registry/gemini/index.ts");
const OPENAI_ADDED_IDS = [
"gpt-4.1-mini",
"gpt-4.1-nano",
"o3-mini",
"o4-mini",
] as const;
const OPENAI_ADDED_IDS = ["gpt-4.1-mini", "gpt-4.1-nano", "o3-mini", "o4-mini"] as const;
const GEMINI_ADDED_IDS = [
"gemini-3.1-flash-lite",
"gemini-2.5-flash-lite",
] as const;
const GEMINI_ADDED_IDS = ["gemini-3.1-flash-lite", "gemini-2.5-flash-lite"] as const;
test("openai registry exposes gpt-4.1 mini/nano and o3-mini/o4-mini reasoning variants", () => {
const ids = new Set(openaiProvider.models.map((m) => m.id));
@@ -69,7 +57,7 @@ test("port did not regress previously curated openai/gemini ids", () => {
"gemini-2.5-pro",
"gemini-2.5-flash",
"gemini-3.1-pro-preview",
"gemini-3.5-flash",
"gemini-3.7-flash",
] as const) {
assert.ok(geminiIds.has(id), `existing gemini model ${id} must remain`);
}

View File

@@ -74,15 +74,33 @@ test("tinycms-web is present in the provider REGISTRY with correct shape", () =>
test("tinycms-web registry has all expected models", () => {
const r = REGISTRY["tinycms-web"];
assert.ok(r.models && r.models.length > 0, "must have at least one model");
const ids = r.models.map((m) => m.id);
assert.ok(ids.includes("gpt-5-free"), "gpt-5-free must be registered");
assert.ok(ids.includes("gpt-5.3-free"), "gpt-5.3-free must be registered");
assert.ok(ids.includes("gpt-5.3-thinking-free"), "gpt-5.3-thinking-free must be registered");
assert.ok(ids.includes("deepseek-v4-flash"), "deepseek-v4-flash must be registered");
assert.ok(ids.includes("claude-sonnet-5"), "claude-sonnet-5 must be registered");
assert.ok(ids.includes("gemini-3.5-flash"), "gemini-3.5-flash must be registered");
assert.equal(r.models.length, 16, "must have exactly 16 models");
assert.deepEqual(
r.models.map((m) => m.id),
[
"claude-fable-5",
"claude-opus-5",
"claude-sonnet-5",
"gpt-5.6-sol",
"gpt-5.6-luna",
"gpt-5.5",
"gpt-5.4-mini",
"gpt-5.4-nano",
"gpt-5.3-thinking-free",
"gpt-5.3-free",
"gpt-oss-120b",
"gemini-3.6-flash",
"gemini-3.1-pro-preview",
"gemini-3.1-flash-lite-preview",
"grok-4.5",
"deepseek-v4-pro",
"deepseek-v4-flash",
"kimi-k3",
"glm-5.2",
"qwen3.6-plus",
"mimo-v2.5-pro",
"mimo-v2.5",
]
);
});
test("tinycms-web model names are human-readable strings", () => {

View File

@@ -60,7 +60,7 @@ function upstreamContextOverflowResponse() {
error: {
code: "context_length_exceeded",
message:
"Input exceeds the context window for opencode/north-mini-code-free: estimated 210724 input tokens, limit 200000. Reduce the prompt or route to a model with a larger context window.",
"Input exceeds the context window for opencode/mimo-v2.5-free: estimated 210724 input tokens, limit 200000. Reduce the prompt or route to a model with a larger context window.",
},
}),
{
@@ -71,8 +71,8 @@ function upstreamContextOverflowResponse() {
}
test("#8841 advertised vs compat-filter limit agree", () => {
const advertised = getTokenLimit("opencode-zen", "north-mini-code-free");
const caps = getResolvedModelCapabilities("opencode/north-mini-code-free");
const advertised = getTokenLimit("opencode-zen", "mimo-v2.5-free");
const caps = getResolvedModelCapabilities("opencode/mimo-v2.5-free");
assert.ok(advertised > 0);
assert.ok(
caps.contextWindow != null && caps.contextWindow > 0,
@@ -83,7 +83,7 @@ test("#8841 advertised vs compat-filter limit agree", () => {
test("#8841 oversized request rejected up front (no dispatch)", async () => {
const body = largeBody();
const pool = [
target("opencode/north-mini-code-free"),
target("opencode/mimo-v2.5-free"),
target("opencode/hy3-free"),
];
@@ -96,7 +96,7 @@ test("#8841 oversized request rejected up front (no dispatch)", async () => {
name: "pro-coding-repro-8841",
strategy: "priority",
models: [
"opencode/north-mini-code-free",
"opencode/mimo-v2.5-free",
"opencode/hy3-free",
],
},

View File

@@ -133,18 +133,22 @@ test("LEDGER-2: valid custom headers still pass", () => {
assert.equal(res.success, true, res.success ? "" : JSON.stringify(res.error?.issues));
});
// ── LEDGER-4: every minimax-m3 registry entry is flagged multimodal ──
test("LEDGER-4: all minimax-m3 registry entries set supportsVision (matches lite.ts)", () => {
const entries: { id: string; supportsVision?: boolean }[] = [];
for (const provider of Object.values(
// ── LEDGER-4: MiniMax M3 vision support is provider-specific ──
test("LEDGER-4: minimax-m3 vision metadata matches each provider", () => {
const entries: { provider: string; id: string; supportsVision?: boolean }[] = [];
for (const [provider, entry] of Object.entries(
REGISTRY as Record<string, { models?: { id: string; supportsVision?: boolean }[] }>
)) {
for (const m of provider.models || []) {
if (/minimax-m3/i.test(m.id)) entries.push(m);
for (const model of entry.models || []) {
if (/minimax-m3/i.test(model.id)) entries.push({ provider, ...model });
}
}
assert.ok(entries.length >= 6, `expected several minimax-m3 entries, got ${entries.length}`);
const unflagged = entries.filter((m) => m.supportsVision !== true).map((m) => m.id);
const promptql = entries.find((entry) => entry.provider === "promptql");
assert.notEqual(promptql?.supportsVision, true, "PromptQL MiniMax M3 is text-only");
const unflagged = entries
.filter((entry) => entry.provider !== "promptql" && entry.supportsVision !== true)
.map((entry) => `${entry.provider}/${entry.id}`);
assert.deepEqual(
unflagged,
[],

View File

@@ -47,11 +47,6 @@ test("getModelRpd strips gemma- prefix correctly for gemma models", () => {
assert.equal(getModelRpd("gemini/gemma-4-31b-it"), 14400);
});
test("getModelRpd handles image-generation models (no RPM value, -1)", () => {
// RPD is 25 for imagen models; RPM is -1 in the JSON
assert.equal(getModelRpd("imagen-4-generate"), 25);
});
test("getModelRpd handles models with unlimited RPD (-1)", () => {
// gemini-3.5-live-translate has rpd: -1
assert.equal(getModelRpd("gemini-3.5-live-translate"), 0);
@@ -207,8 +202,8 @@ test("getModelRpm returns 0 for empty string", () => {
assert.equal(getModelRpm(""), 0);
});
test("getModelRpm returns 0 for models with RPM=-1 (imagen)", () => {
assert.equal(getModelRpm("imagen-4-generate"), 0);
test("getModelRpm returns 0 for models with RPM=-1", () => {
assert.equal(getModelRpm("gemini-3.5-live-translate"), 0);
});
test("getModelRpm returns 0 for null input", () => {
@@ -288,9 +283,9 @@ test("isRpmExhausted returns false for unknown model", () => {
assert.equal(isRpmExhausted("gemini/unknown-model"), false);
});
test("isRpmExhausted returns false for model with RPM=-1 (imagen)", () => {
incrementRequestCount("imagen-4-generate");
assert.equal(isRpmExhausted("imagen-4-generate"), false);
test("isRpmExhausted returns false for model with RPM=-1", () => {
incrementRequestCount("gemini-3.5-live-translate");
assert.equal(isRpmExhausted("gemini-3.5-live-translate"), false);
});
test("isRpmExhausted works with gemini/ prefix", () => {

View File

@@ -14,10 +14,10 @@ test("T28: gemini AI Studio catalog includes current preview models", () => {
assert.ok(geminiIds.includes("gemini-3.1-pro-preview"));
assert.ok(geminiIds.includes("gemini-3-flash-preview"));
assert.ok(geminiIds.includes("gemini-3.1-flash-lite"));
assert.ok(geminiIds.includes("gemini-3.5-flash"));
assert.ok(geminiIds.includes("gemini-3.7-flash"));
assert.ok(geminiIds.includes("gemini-2.5-flash"));
assert.ok(geminiIds.includes("gemini-2.5-pro"));
assert.equal(geminiIds[0], "gemini-3.1-pro-preview", "preserve the existing Gemini default");
assert.equal(geminiIds[0], "gemini-3.7-flash", "use the current Gemini Flash default");
});
test("T28: antigravity static catalog exposes only callable Gemini tier IDs", () => {
@@ -26,12 +26,13 @@ test("T28: antigravity static catalog exposes only callable Gemini tier IDs", ()
assert.ok(!staticIds.includes("gemini-3-pro-preview"));
assert.ok(staticIds.includes("gemini-3.7-flash-high"));
assert.ok(staticIds.includes("gemini-3.7-flash-medium"));
assert.ok(staticIds.includes("gemini-3.6-flash-high"));
assert.ok(staticIds.includes("gemini-3.6-flash-medium"));
assert.ok(staticIds.includes("gemini-3.6-flash-low"));
assert.ok(staticIds.includes("gemini-3.5-flash-extra-low"));
assert.ok(staticIds.includes("gemini-3.5-flash-low"));
assert.ok(staticIds.includes("gemini-3-flash-agent"));
assert.ok(staticIds.includes("gemini-3.7-flash-low"));
assert.ok(!staticIds.includes("gemini-3.6-flash-high"));
assert.ok(!staticIds.includes("gemini-3.6-flash-medium"));
assert.ok(!staticIds.includes("gemini-3.6-flash-low"));
assert.ok(!staticIds.includes("gemini-3.5-flash-extra-low"));
assert.ok(!staticIds.includes("gemini-3.5-flash-low"));
assert.ok(!staticIds.includes("gemini-3-flash-agent"));
assert.ok(!staticIds.includes("gemini-3.5-flash-medium"));
assert.ok(!staticIds.includes("gemini-3.5-flash-high"));
assert.ok(staticIds.includes("gemini-3.1-pro-low"));
@@ -45,21 +46,21 @@ test("T28: antigravity static catalog exposes only callable Gemini tier IDs", ()
assert.ok(!staticIds.includes("gemini-claude-opus-4-5-thinking"));
});
test("T28: agy free-model metadata labels upstream Gemini 3.6 tier IDs", () => {
test("T28: agy free-model metadata labels upstream Gemini 3.7 tier IDs", () => {
const flashNames = Object.fromEntries(
FREE_MODEL_BUDGETS.filter(
(entry) =>
entry.provider === "agy" &&
["gemini-3.6-flash-low", "gemini-3.6-flash-medium", "gemini-3.6-flash-high"].includes(
["gemini-3.7-flash-low", "gemini-3.7-flash-medium", "gemini-3.7-flash-high"].includes(
entry.modelId
)
).map((entry) => [entry.modelId, entry.displayName])
);
assert.deepEqual(flashNames, {
"gemini-3.6-flash-low": "Gemini 3.6 Flash (Low)",
"gemini-3.6-flash-medium": "Gemini 3.6 Flash (Medium)",
"gemini-3.6-flash-high": "Gemini 3.6 Flash (High)",
"gemini-3.7-flash-low": "Gemini 3.7 Flash (Low)",
"gemini-3.7-flash-medium": "Gemini 3.7 Flash (Medium)",
"gemini-3.7-flash-high": "Gemini 3.7 Flash (High)",
});
});
@@ -90,7 +91,7 @@ test("T28: lmarena registry seeds Direct-chat Text/search; image models in IMAGE
// Chat registry: Text + Search only (not Image thrash)
assert.ok(lmarenaIds.length >= 40 && lmarenaIds.length < 60);
assert.ok(lmarenaIds.includes("gemini-3.1-pro-preview"));
assert.ok(lmarenaIds.includes("gemini-3.5-flash-high"));
assert.ok(lmarenaIds.includes("gemini-3.6-flash"));
assert.ok(lmarenaIds.includes("claude-sonnet-5"));
assert.ok(!lmarenaIds.includes("flux-2-pro"), "image models must not live in chat registry");

View File

@@ -21,7 +21,7 @@ test("T31: antigravity static catalog exposes client-visible Gemini preview IDs"
// catalog) retired the `gemini-3-pro-preview` alias, so assert the current
// client-visible top flash tier instead.
const staticIds = (getStaticModelsForProvider("antigravity") || []).map((m) => m.id);
assert.ok(staticIds.includes("gemini-3.6-flash-high"));
assert.ok(staticIds.includes("gemini-3.7-flash-high"));
assert.ok(!staticIds.includes("gemini-3-pro-preview"));
// #3303 (agy parity, discussion #3184): the Gemini + Claude budget tiers ARE
// client-visible on the Antigravity OAuth backend (Claude was never removed).

View File

@@ -23,7 +23,7 @@ test("checkFallbackError ignores body reset text when upstream retry hints are d
429,
ANTIGRAVITY_RESET_TEXT,
0,
"gemini-3-flash-agent",
"gemini-3.7-flash-high",
"antigravity",
null,
antigravityProfile(false)
@@ -40,7 +40,7 @@ test("checkFallbackError honors body reset text when upstream retry hints are en
429,
ANTIGRAVITY_RESET_TEXT,
0,
"gemini-3-flash-agent",
"gemini-3.7-flash-high",
"antigravity",
null,
antigravityProfile(true)

View File

@@ -259,7 +259,7 @@ test("usage service prefers Antigravity retrieveUserQuota over catalog quotaInfo
return new Response(
JSON.stringify({
models: {
"gemini-3-flash-agent": {
"gemini-3.7-flash-high": {
quotaInfo: {
remainingFraction: 1,
resetTime: new Date(Date.now() + 60_000).toISOString(),
@@ -276,7 +276,7 @@ test("usage service prefers Antigravity retrieveUserQuota over catalog quotaInfo
JSON.stringify({
buckets: [
{
modelId: "gemini-3-flash-agent",
modelId: "gemini-3.7-flash-high",
remainingFraction: 0.25,
resetTime: new Date(Date.now() + 60_000).toISOString(),
},
@@ -294,9 +294,9 @@ test("usage service prefers Antigravity retrieveUserQuota over catalog quotaInfo
accessToken: `ag-token-live-quota-${Date.now()}`,
});
assert.equal(usage.quotas["gemini-3-flash-agent"].remainingPercentage, 25);
assert.equal(usage.quotas["gemini-3-flash-agent"].used, 750);
assert.equal(usage.quotas["gemini-3-flash-agent"].quotaSource, "retrieveUserQuota");
assert.equal(usage.quotas["gemini-3.7-flash-high"].remainingPercentage, 25);
assert.equal(usage.quotas["gemini-3.7-flash-high"].used, 750);
assert.equal(usage.quotas["gemini-3.7-flash-high"].quotaSource, "retrieveUserQuota");
});
test("usage service preserves Antigravity upstream quota bucket ids", async () => {
@@ -317,10 +317,10 @@ test("usage service preserves Antigravity upstream quota bucket ids", async () =
return new Response(
JSON.stringify({
models: {
"gemini-3.5-flash-low": { quotaInfo: { remainingFraction: 1 } },
"gemini-3.5-flash-high": { quotaInfo: { remainingFraction: 1 } },
"gemini-3.7-flash-low": { quotaInfo: { remainingFraction: 1 } },
"gemini-3.7-flash-medium": { quotaInfo: { remainingFraction: 1 } },
"gemini-3.7-flash-high": { quotaInfo: { remainingFraction: 1 } },
"gemini-3-flash-agent": { quotaInfo: { remainingFraction: 1 } },
"gemini-3.5-flash-extra-low": { quotaInfo: { remainingFraction: 1 } },
},
}),
{ status: 200 }
@@ -331,8 +331,8 @@ test("usage service preserves Antigravity upstream quota bucket ids", async () =
return new Response(
JSON.stringify({
buckets: [
{ modelId: "gemini-3-flash-agent", remainingFraction: 0.5 },
{ modelId: "gemini-3.5-flash-extra-low", remainingFraction: 0.25 },
{ modelId: "gemini-3.7-flash-high", remainingFraction: 0.5 },
{ modelId: "gemini-3.7-flash-low", remainingFraction: 0.25 },
],
}),
{ status: 200 }
@@ -347,11 +347,10 @@ test("usage service preserves Antigravity upstream quota bucket ids", async () =
accessToken: `ag-token-legacy-buckets-${Date.now()}`,
});
assert.equal(usage.quotas["gemini-3-flash-agent"].remainingPercentage, 50);
assert.equal(usage.quotas["gemini-3.5-flash-extra-low"].remainingPercentage, 25);
assert.equal(usage.quotas["gemini-3.5-flash-low"].remainingPercentage, 100);
assert.equal(usage.quotas["gemini-3.5-flash-medium"], undefined);
assert.equal(usage.quotas["gemini-3.5-flash-high"], undefined);
assert.equal(usage.quotas["gemini-3.7-flash-high"].remainingPercentage, 50);
assert.equal(usage.quotas["gemini-3.7-flash-low"].remainingPercentage, 25);
assert.equal(usage.quotas["gemini-3.7-flash-medium"].remainingPercentage, 100);
assert.equal(usage.quotas["gemini-3-flash-agent"], undefined);
});
test("usage service retries Antigravity fetchAvailableModels across the shared fallback order", async () => {

View File

@@ -52,7 +52,6 @@ test("detectIntent returns 'video' for veo models", () => {
test("detectIntent returns 'image' for image models", () => {
assert.equal(detectIntent("image-gen"), "image");
assert.equal(detectIntent("banana"), "image");
assert.equal(detectIntent("imagen-4"), "image");
assert.equal(detectIntent("nano-banana"), "image");
});