fix(providers): correct Kiro model catalog to real upstream ids

Kiro's API (generateAssistantResponse) returns 400 "Invalid model. Please
select a different model" for any id it does not recognize. The registry
exposed fabricated ids (copied from OmniRoute's own Anthropic catalog) that
Kiro never serves, so every call to them 400'd. Live-verified on the VPS:

  Removed (400 Invalid model):
    - auto-kiro       (no "auto" model id — was sent verbatim upstream)
    - claude-fable-5  (Kiro offers no Fable)
    - claude-opus-4.8/4.7/4.6 (Kiro offers no Opus)
  Corrected:
    - claude-sonnet-4.6 -> claude-sonnet-4.5 (Kiro's Sonnet is 4.5; 4.5 -> 200)
  Kept:
    - claude-sonnet-5 (real Kiro model, plan-gated per account)
    - claude-haiku-4.5, deepseek-3.2, glm-5, minimax-m2.5/m2.1,
      qwen3-coder-next (all proven 200 on the VPS)

Aligns the free-model catalog and drops the orphaned auto-kiro price key.
Regression guard: tests/unit/kiro-catalog-real-models.test.ts (3/3).
Kiro cluster #6112/#6113/#6099.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-04 06:17:28 -03:00
parent 1e59d14143
commit b42e23cd76
5 changed files with 75 additions and 44 deletions

View File

@@ -245,11 +245,7 @@ export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [
{ provider: "kilo-gateway", modelId: "nvidia/nemotron-3-ultra-550b-a55b:free", displayName: "NVIDIA Nemotron 3 Ultra (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "kilo-gateway-free", tos: "caution" },
{ provider: "kilo-gateway", modelId: "nvidia/nemotron-3-super-120b-a12b:free", displayName: "NVIDIA Nemotron 3 Super (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "kilo-gateway-free", tos: "caution" },
{ provider: "kilo-gateway", modelId: "nex-agi/nex-n2-pro:free", displayName: "Nex-N2-Pro (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "kilo-gateway-free", tos: "caution" },
{ provider: "kiro", modelId: "auto-kiro", displayName: "Auto (Kiro picks best model)", monthlyTokens: 25000, creditTokens: 0, freeType: "recurring-monthly", poolKey: "kiro", tos: "avoid" },
{ provider: "kiro", modelId: "claude-opus-4.8", displayName: "Claude Opus 4.8", monthlyTokens: 25000, creditTokens: 0, freeType: "recurring-monthly", poolKey: "kiro", tos: "avoid" },
{ provider: "kiro", modelId: "claude-opus-4.7", displayName: "Claude Opus 4.7", monthlyTokens: 25000, creditTokens: 0, freeType: "recurring-monthly", poolKey: "kiro", tos: "avoid" },
{ provider: "kiro", modelId: "claude-opus-4.6", displayName: "Claude Opus 4.6", monthlyTokens: 25000, creditTokens: 0, freeType: "recurring-monthly", poolKey: "kiro", tos: "avoid" },
{ provider: "kiro", modelId: "claude-sonnet-4.6", displayName: "Claude Sonnet 4.6", monthlyTokens: 25000, creditTokens: 0, freeType: "recurring-monthly", poolKey: "kiro", tos: "avoid" },
{ provider: "kiro", modelId: "claude-sonnet-4.5", displayName: "Claude Sonnet 4.5", monthlyTokens: 25000, creditTokens: 0, freeType: "recurring-monthly", poolKey: "kiro", tos: "avoid" },
{ provider: "kiro", modelId: "claude-haiku-4.5", displayName: "Claude Haiku 4.5", monthlyTokens: 25000, creditTokens: 0, freeType: "recurring-monthly", poolKey: "kiro", tos: "avoid" },
{ provider: "kiro", modelId: "deepseek-3.2", displayName: "DeepSeek V3.2", monthlyTokens: 25000, creditTokens: 0, freeType: "recurring-monthly", poolKey: "kiro", tos: "avoid" },
{ provider: "kiro", modelId: "minimax-m2.5", displayName: "MiniMax M2.5", monthlyTokens: 25000, creditTokens: 0, freeType: "recurring-monthly", poolKey: "kiro", tos: "avoid" },

View File

@@ -15,32 +15,14 @@ export const kiroProvider: RegistryEntry = {
tokenUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken",
authUrl: "https://prod.us-east-1.auth.desktop.kiro.dev",
},
// Model IDs must match Kiro's real upstream catalog exactly — an unknown id
// makes Kiro return `400 "Invalid model. Please select a different model"`.
// Fabricated ids (auto-kiro, claude-opus-4.x, claude-fable-5, claude-sonnet-4.6)
// were removed after live VPS validation: Kiro offers no Opus/Fable, its Sonnet
// is 4.5 (not 4.6), and there is no "auto" model id (it was sent verbatim and
// 400'd). claude-sonnet-5 is a real Kiro model but plan-gated per account —
// kept so entitled accounts can use it. See kiro cluster #6112/#6113/#6099.
models: [
{ id: "auto-kiro", name: "Auto (Kiro picks best model)" },
{
id: "claude-fable-5",
name: "Claude Fable 5",
contextLength: 1000000,
maxOutputTokens: 128000,
},
{
id: "claude-opus-4.8",
name: "Claude Opus 4.8",
contextLength: 1000000,
maxOutputTokens: 128000,
},
{
id: "claude-opus-4.7",
name: "Claude Opus 4.7",
contextLength: 1000000,
maxOutputTokens: 128000,
},
{
id: "claude-opus-4.6",
name: "Claude Opus 4.6",
contextLength: 1000000,
maxOutputTokens: 128000,
},
{
id: "claude-sonnet-5",
name: "Claude Sonnet 5",
@@ -48,8 +30,8 @@ export const kiroProvider: RegistryEntry = {
maxOutputTokens: 128000,
},
{
id: "claude-sonnet-4.6",
name: "Claude Sonnet 4.6",
id: "claude-sonnet-4.5",
name: "Claude Sonnet 4.5",
contextLength: 200000,
maxOutputTokens: 64000,
},

View File

@@ -57,9 +57,9 @@ function convertMessages(messages, tools, model) {
let toolsAttached = false;
// Only Claude models support images in Kiro. Kiro also routes non-Claude
// models (deepseek, minimax, glm, qwen3-coder-next, auto-kiro) that do not
// accept image attachments — gate image extraction behind a Claude check so
// we never attach images those models would reject.
// models (deepseek, minimax, glm, qwen3-coder-next) that do not accept image
// attachments — gate image extraction behind a Claude check so we never
// attach images those models would reject.
const supportsImages = typeof model === "string" && model.toLowerCase().includes("claude");
const flushPending = () => {

View File

@@ -584,7 +584,10 @@ export const DEFAULT_PRICING_OAUTH = {
reasoning: 8.0,
cache_creation: 2.0,
},
// Kiro "Auto" model — routes to best available
// Kiro "Auto" pricing — retained as a fallback price for any legacy "auto"
// reference. The "auto-kiro" registry model was removed (Kiro's API has no
// "auto" model id — it 400'd "Invalid model"), so its dedicated price key
// was dropped with it. See kiro cluster #6112/#6113/#6099.
auto: {
input: 3.0,
output: 15.0,
@@ -592,13 +595,5 @@ export const DEFAULT_PRICING_OAUTH = {
reasoning: 15.0,
cache_creation: 3.0,
},
// Registry exposes the Auto model as id "auto-kiro" — keep both keys priced.
"auto-kiro": {
input: 3.0,
output: 15.0,
cached: 1.5,
reasoning: 15.0,
cache_creation: 3.0,
},
},
};

View File

@@ -0,0 +1,58 @@
import test from "node:test";
import assert from "node:assert/strict";
import { REGISTRY } from "@omniroute/open-sse/config/providers/index.ts";
import { FREE_MODEL_BUDGETS } from "@omniroute/open-sse/config/freeModelCatalog.data.ts";
// Kiro's upstream (`generateAssistantResponse`) returns 400 "Invalid model.
// Please select a different model" for any model id it does not recognize. The
// registry must therefore expose ONLY ids Kiro actually serves. These ids were
// fabricated (copied from OmniRoute's own Anthropic catalog) and live-verified
// to 400 on the VPS — they must never reappear. Regression guard for the kiro
// cluster (#6112/#6113/#6099).
const FABRICATED_KIRO_IDS = [
"auto-kiro", // no "auto" model id on Kiro — was sent verbatim and 400'd
"claude-fable-5", // Kiro offers no Fable
"claude-opus-4.8", // Kiro offers no Opus
"claude-opus-4.7",
"claude-opus-4.6",
"claude-sonnet-4.6", // Kiro's Sonnet is 4.5, not 4.6
];
// Ids proven to return 200 on the VPS (or a real, plan-gated Kiro model).
const REAL_KIRO_IDS = [
"claude-sonnet-5", // real model, plan-gated per account (kept)
"claude-sonnet-4.5", // proven 200 (replaces the fabricated 4.6)
"claude-haiku-4.5", // proven 200
"deepseek-3.2", // proven 200
"glm-5", // proven 200
"minimax-m2.5", // proven 200
"minimax-m2.1", // proven 200
"qwen3-coder-next", // proven 200
];
test("kiro registry exposes no fabricated model ids", () => {
const ids = new Set((REGISTRY.kiro?.models || []).map((m) => m.id));
for (const bad of FABRICATED_KIRO_IDS) {
assert.ok(!ids.has(bad), `kiro registry must not expose fabricated id "${bad}"`);
}
});
test("kiro registry exposes exactly the real Kiro model ids", () => {
const ids = (REGISTRY.kiro?.models || []).map((m) => m.id).sort();
assert.deepEqual(ids, [...REAL_KIRO_IDS].sort());
});
test("kiro free-model catalog carries no fabricated ids", () => {
const kiroCatalogIds = new Set(
FREE_MODEL_BUDGETS.filter((e) => e.provider === "kiro").map((e) => e.modelId)
);
for (const bad of FABRICATED_KIRO_IDS) {
assert.ok(!kiroCatalogIds.has(bad), `free catalog must not list fabricated kiro id "${bad}"`);
}
// Every kiro free-catalog entry must exist in the registry (no orphans).
const registryIds = new Set((REGISTRY.kiro?.models || []).map((m) => m.id));
for (const id of kiroCatalogIds) {
assert.ok(registryIds.has(id), `free catalog kiro id "${id}" is not in the registry`);
}
});