feat(models): refresh qianwenai lineup, add flash-0731 and gpt-5.6-luna, default to flash (#160)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c76a2c61bb
commit
f3193ae628
17 files changed
+169
-150
No files matched your search
@@ -219,32 +219,27 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
pricing: cny(0.025, 3, 6),
|
||||
supportsVision: false,
|
||||
},
|
||||
// -- OpenRouter (gateway: OpenAI-compatible protocol, preset base URL). Entries added
|
||||
// 2026-07-20 list no cache pricing on their OpenRouter pages, so cache_read carries the
|
||||
// standard input price; the :free tier and the openrouter/free Free Models Router store a
|
||||
// genuine $0 price (not "unknown"), so costs correctly compute to 0. GPT models are
|
||||
// uniformly vision-capable (OpenAI product-line policy) even where the gateway page omits
|
||||
// the modality.
|
||||
// Two cache_read conventions coexist in this block: rows whose upstream publishes a
|
||||
// cache-hit price store that real price (the google/gemini-3.6-flash and
|
||||
// google/gemini-3.5-flash-lite entries below), while the 2026-07-20 rows still repeat the
|
||||
// input price. Several of those older rows do have a published cache price upstream and
|
||||
// should be re-read in one pass; until then treat their cache_read as an upper bound. --
|
||||
// -- OpenRouter (gateway: OpenAI-compatible protocol, preset base URL). Prices re-read in
|
||||
// one pass on 2026-08-03 from the models API (/api/v1/models): cache_read stores the
|
||||
// published input_cache_read (falling back to the input price for the few rows without
|
||||
// one — qwen3.6-35b-a3b and the :free rows); cache_write stores input_cache_write only
|
||||
// when it is a genuine per-token write premium (the Anthropic and GPT rows, 1.25x input) —
|
||||
// Gemini's field is an hourly cache-STORAGE rate, not a per-token price, so those rows
|
||||
// keep the input price — and otherwise also carries the input price. The :free tier and
|
||||
// the openrouter/free Free Models Router store a genuine $0 price (not "unknown"), so
|
||||
// costs correctly compute to 0. GPT models are uniformly vision-capable (OpenAI
|
||||
// product-line policy) even where the gateway page omits the modality. --
|
||||
{
|
||||
modelId: "anthropic/claude-fable-5",
|
||||
displayName: "Claude Fable 5",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(10, 10, 50),
|
||||
pricing: usd(1, 12.5, 50),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
},
|
||||
{
|
||||
// Upstream publishes full cache pricing for this row (2026-07-24, per the OpenRouter
|
||||
// models API: $0.50/mtok cache hit, $6.25 cache write = 1.25 x input, $5 input, $25
|
||||
// output), so cache_read stores the real discounted price — same convention as the
|
||||
// Gemini rows below — instead of repeating the input price.
|
||||
modelId: "anthropic/claude-opus-5",
|
||||
displayName: "Claude Opus 5",
|
||||
provider: "openrouter",
|
||||
@@ -259,7 +254,7 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "Claude Opus 4.8",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(5, 5, 25),
|
||||
pricing: usd(0.5, 6.25, 25),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -269,7 +264,7 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "Claude Opus 4.7",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(5, 5, 25),
|
||||
pricing: usd(0.5, 6.25, 25),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -279,17 +274,27 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "Claude Sonnet 5",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(2, 2, 10),
|
||||
pricing: usd(0.2, 2.5, 10),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
},
|
||||
{
|
||||
modelId: "deepseek/deepseek-v4-flash-0731",
|
||||
displayName: "DeepSeek V4 Flash 0731",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(0.018, 0.09, 0.18),
|
||||
supportsVision: false,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
},
|
||||
{
|
||||
modelId: "deepseek/deepseek-v4-flash",
|
||||
displayName: "DeepSeek V4 Flash",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(0.09, 0.09, 0.18),
|
||||
pricing: usd(0.028, 0.14, 0.28),
|
||||
supportsVision: false,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -299,19 +304,16 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "DeepSeek V4 Pro",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(0.435, 0.435, 0.87),
|
||||
pricing: usd(0.003625, 0.435, 0.87),
|
||||
supportsVision: false,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
},
|
||||
{
|
||||
// Unlike the older OpenRouter entries above, upstream **does** publish a cache-hit price
|
||||
// for the Gemini rows (2026-07-22: $0.15/mtok here, agreed by the OpenRouter models API
|
||||
// and AgentHub's own supported-model registry), so cache_read stores the real discounted
|
||||
// price rather than repeating the input price: cache_read is billed as its own bucket in
|
||||
// the cost center, and an input-priced cache_read overstates cache-heavy spend 10x.
|
||||
// cache_write repeats the input price (no separate per-token cache-write fee), matching
|
||||
// the direct-vendor Gemini rows below.
|
||||
// cache_read is billed as its own bucket in the cost center, and an input-priced
|
||||
// cache_read would overstate cache-heavy Gemini spend 10x; cache_write repeats the input
|
||||
// price (see the block comment — Gemini publishes storage-per-hour, not per-token write),
|
||||
// matching the direct-vendor Gemini rows below.
|
||||
modelId: "google/gemini-3.6-flash",
|
||||
displayName: "Gemini 3.6 Flash",
|
||||
provider: "openrouter",
|
||||
@@ -326,7 +328,7 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "Gemini 3.5 Flash",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1048576,
|
||||
pricing: usd(1.5, 1.5, 9),
|
||||
pricing: usd(0.15, 1.5, 9),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -372,7 +374,7 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "Kimi K3",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(3, 3, 15),
|
||||
pricing: usd(0.3, 3, 15),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -382,7 +384,7 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "Kimi K2.6",
|
||||
provider: "openrouter",
|
||||
contextWindow: 262144,
|
||||
pricing: usd(0.144, 0.684, 3.42),
|
||||
pricing: usd(0.2, 0.6, 3.41),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -397,12 +399,25 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
},
|
||||
{
|
||||
// The page shows the $0.10/$0.60 rate under a "50% off" banner; the models API bills the
|
||||
// same numbers (with real hit/write prices), so this row stores them — re-read when the
|
||||
// promotion ends.
|
||||
modelId: "openai/gpt-5.6-luna",
|
||||
displayName: "GPT-5.6 Luna",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(0.01, 0.125, 0.6),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
},
|
||||
{
|
||||
modelId: "openai/gpt-5.6-sol",
|
||||
displayName: "GPT-5.6 Sol",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(5, 5, 30),
|
||||
pricing: usd(0.5, 6.25, 30),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -412,7 +427,7 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "GPT-5.6 Terra",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(2.5, 2.5, 15),
|
||||
pricing: usd(0.1, 1.25, 6),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -422,7 +437,7 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "GPT-5.5",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(5, 5, 30),
|
||||
pricing: usd(0.5, 5, 30),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -444,19 +459,6 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
},
|
||||
{
|
||||
// Poolside's free tier of Laguna M.1, its flagship coding-agent model (agentic coding
|
||||
// workflows with tool calling and reasoning), text-only; context and $0 pricing per the
|
||||
// OpenRouter models API (2026-07-24).
|
||||
modelId: "poolside/laguna-m.1:free",
|
||||
displayName: "Laguna M.1 (free)",
|
||||
provider: "openrouter",
|
||||
contextWindow: 262144,
|
||||
pricing: usd(0, 0, 0),
|
||||
supportsVision: false,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
},
|
||||
{
|
||||
// Neither the OpenRouter page nor AgentHub's registry publishes a cache price for this
|
||||
// model, so cache_read repeats the input price (no discount assumed).
|
||||
@@ -485,7 +487,7 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "Hy3",
|
||||
provider: "openrouter",
|
||||
contextWindow: 262144,
|
||||
pricing: usd(0.035, 0.14, 0.58),
|
||||
pricing: usd(0.033, 0.132, 0.528),
|
||||
supportsVision: false,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -495,7 +497,7 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "Grok 4.5",
|
||||
provider: "openrouter",
|
||||
contextWindow: 500000,
|
||||
pricing: usd(2, 2, 6),
|
||||
pricing: usd(0.3, 2, 6),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -515,7 +517,7 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "GLM-5.2",
|
||||
provider: "openrouter",
|
||||
contextWindow: 1000000,
|
||||
pricing: usd(0.93, 0.93, 3),
|
||||
pricing: usd(0.221, 1.19, 3.74),
|
||||
supportsVision: false,
|
||||
clientType: "openai",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
@@ -634,16 +636,17 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
clientType: "openai",
|
||||
baseUrl: SILICONFLOW_BASE_URL,
|
||||
},
|
||||
// The three Pro/ and Qwen/ entries below carry no pricing: AgentHub's registry publishes
|
||||
// none for them, and SiliconFlow's price list sits behind an authenticated API (the public
|
||||
// /v1/models endpoint returns 401 and the console page is client-rendered). Rather than
|
||||
// invent a rate, the entries ship unpriced — the same state as qwen3.8-max-preview, so their
|
||||
// cost reads as 0 until a published price can be filled in.
|
||||
// The Pro/ and Qwen/ entries below were unpriced until 2026-08-03 (SiliconFlow's price
|
||||
// list sits behind an authenticated console); prices below are its official CNY list
|
||||
// prices. GLM-5.1 bills in two input-length tiers ([0, 32k) and [32k, +inf) for hit/input/
|
||||
// output alike); the catalog stores one number per bucket, so these rows keep the LOWER
|
||||
// tier — treat its cost as a floor for long-context use.
|
||||
{
|
||||
modelId: "Pro/moonshotai/Kimi-K2.6",
|
||||
displayName: "Kimi K2.6",
|
||||
provider: "siliconflow",
|
||||
contextWindow: 262144,
|
||||
pricing: cny(1.1, 6.5, 27),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: SILICONFLOW_BASE_URL,
|
||||
@@ -653,15 +656,18 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
displayName: "GLM-5.1",
|
||||
provider: "siliconflow",
|
||||
contextWindow: 200000,
|
||||
pricing: cny(1.3, 6, 24),
|
||||
supportsVision: false,
|
||||
clientType: "openai",
|
||||
baseUrl: SILICONFLOW_BASE_URL,
|
||||
},
|
||||
{
|
||||
// No cache-hit price on the list, so cache_read carries the input price.
|
||||
modelId: "Qwen/Qwen3.6-35B-A3B",
|
||||
displayName: "Qwen 3.6 35B A3B",
|
||||
provider: "siliconflow",
|
||||
contextWindow: 262144,
|
||||
pricing: cny(1.8, 1.8, 10.8),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: SILICONFLOW_BASE_URL,
|
||||
@@ -679,9 +685,18 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
// -- Qwen Token Plan (subscription gateway; vision flags per the plan's supported-model
|
||||
// table). Pricing and context windows from each model's page at
|
||||
// www.qianwenai.com/models/<id> (official CNY list prices; limited-time promotions such as
|
||||
// the 20%/50% off discounts are not stored). qwen3.8-max-preview is preview-only with a
|
||||
// quota-multiplier promotion and publishes no per-token list price nor a context window, so
|
||||
// it carries no pricing and uses its family's 1M window. --
|
||||
// the 20%/50% off discounts are not stored). Lineup updated 2026-08-03: qwen3.8-max and
|
||||
// deepseek-v4-flash-0731 join; qwen3.8-max-preview and qwen3.7-max leave the plan. --
|
||||
{
|
||||
modelId: "deepseek-v4-flash-0731",
|
||||
displayName: "DeepSeek V4 Flash 0731",
|
||||
provider: "qwen-token-plan",
|
||||
contextWindow: 1000000,
|
||||
pricing: cny(0.2, 1, 2),
|
||||
supportsVision: false,
|
||||
clientType: "openai",
|
||||
baseUrl: QWEN_TOKEN_PLAN_BASE_URL,
|
||||
},
|
||||
{
|
||||
modelId: "deepseek-v4-pro",
|
||||
displayName: "DeepSeek V4 Pro",
|
||||
@@ -703,24 +718,15 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
baseUrl: QWEN_TOKEN_PLAN_BASE_URL,
|
||||
},
|
||||
{
|
||||
modelId: "qwen3.8-max-preview",
|
||||
displayName: "Qwen 3.8 Max Preview",
|
||||
modelId: "qwen3.8-max",
|
||||
displayName: "Qwen 3.8 Max",
|
||||
provider: "qwen-token-plan",
|
||||
contextWindow: 1000000,
|
||||
pricing: cny(1.5, 12, 36),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: QWEN_TOKEN_PLAN_BASE_URL,
|
||||
},
|
||||
{
|
||||
modelId: "qwen3.7-max",
|
||||
displayName: "Qwen 3.7 Max",
|
||||
provider: "qwen-token-plan",
|
||||
contextWindow: 1000000,
|
||||
pricing: cny(2.4, 12, 36),
|
||||
supportsVision: false,
|
||||
clientType: "openai",
|
||||
baseUrl: QWEN_TOKEN_PLAN_BASE_URL,
|
||||
},
|
||||
{
|
||||
modelId: "qwen3.7-plus",
|
||||
displayName: "Qwen 3.7 Plus",
|
||||
@@ -733,7 +739,18 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
},
|
||||
// -- Qwen Pay-As-You-Go (DashScope's OpenAI-compatible pay-per-token marketplace; official
|
||||
// CNY list prices and specs from each model's page at www.qianwenai.com/models/<id> —
|
||||
// resold third-party models keep their vendor-prefixed upstream ids) --
|
||||
// resold third-party models keep their upstream ids exactly as the page lists them: kimi/
|
||||
// and ZHIPU/ carry vendor prefixes, DeepSeek is listed bare) --
|
||||
{
|
||||
modelId: "deepseek-v4-flash-0731",
|
||||
displayName: "DeepSeek V4 Flash 0731",
|
||||
provider: "qwen-pay-as-you-go",
|
||||
contextWindow: 1000000,
|
||||
pricing: cny(0.2, 1, 2),
|
||||
supportsVision: false,
|
||||
clientType: "openai",
|
||||
baseUrl: QWEN_PAYG_BASE_URL,
|
||||
},
|
||||
{
|
||||
modelId: "kimi/kimi-k3",
|
||||
displayName: "Kimi K3",
|
||||
@@ -745,12 +762,12 @@ export const MODEL_CATALOG: ModelCatalogEntry[] = [
|
||||
baseUrl: QWEN_PAYG_BASE_URL,
|
||||
},
|
||||
{
|
||||
modelId: "qwen3.7-max",
|
||||
displayName: "Qwen 3.7 Max",
|
||||
modelId: "qwen3.8-max",
|
||||
displayName: "Qwen 3.8 Max",
|
||||
provider: "qwen-pay-as-you-go",
|
||||
contextWindow: 1000000,
|
||||
pricing: cny(2.4, 12, 36),
|
||||
supportsVision: false,
|
||||
pricing: cny(1.5, 12, 36),
|
||||
supportsVision: true,
|
||||
clientType: "openai",
|
||||
baseUrl: QWEN_PAYG_BASE_URL,
|
||||
},
|
||||
@@ -1044,16 +1061,13 @@ export function presetModelEntries(): ModelEntry[] {
|
||||
/**
|
||||
* The model's own homepage/detail page for the frontend's model-card link. Gateway groups
|
||||
* have a stable per-model URL pattern (works for user-added ids in those groups too);
|
||||
* direct-vendor models link to the vendor's model list/docs page; the Token Plan preview
|
||||
* model has no dedicated page and links to the plan's model overview; custom and
|
||||
* user-defined groups have no page to vouch for.
|
||||
* direct-vendor models link to the vendor's model list/docs page; custom and user-defined
|
||||
* groups have no page to vouch for.
|
||||
*/
|
||||
export function modelHomepageUrl(provider: string, modelId: string): string | undefined {
|
||||
if (provider === "openrouter") return `https://openrouter.ai/${modelId}`;
|
||||
if (provider === "qwen-token-plan") {
|
||||
return modelId === "qwen3.8-max-preview"
|
||||
? providerInfo(provider)?.modelsUrl
|
||||
: `https://www.qianwenai.com/models/${modelId}`;
|
||||
return `https://www.qianwenai.com/models/${modelId}`;
|
||||
}
|
||||
if (provider === "fireworks") {
|
||||
// API id "accounts/<owner>/models/<slug>" -> page "app.fireworks.ai/models/<owner>/<slug>";
|
||||
|
||||
@@ -133,7 +133,7 @@ export interface ProjectConfig {
|
||||
*/
|
||||
export function defaultProjectConfig(): ProjectConfig {
|
||||
return {
|
||||
default_model: { provider: "deepseek", model_id: "deepseek-v4-pro" },
|
||||
default_model: { provider: "deepseek", model_id: "deepseek-v4-flash" },
|
||||
models: presetModelEntries(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -169,9 +169,9 @@ describe("Agent.createSession model reference ((provider, model_id) pair)", () =
|
||||
// (same source that Trace writes).
|
||||
const meta = session.metaMessage.payload as { provider: string; model_id: string };
|
||||
expect(meta.provider).toBe("deepseek");
|
||||
expect(meta.model_id).toBe("deepseek-v4-pro");
|
||||
expect(meta.model_id).toBe("deepseek-v4-flash");
|
||||
expect(session.provider).toBe("deepseek");
|
||||
expect(session.modelId).toBe("deepseek-v4-pro");
|
||||
expect(session.modelId).toBe("deepseek-v4-flash");
|
||||
} finally {
|
||||
session.dispose();
|
||||
}
|
||||
@@ -229,7 +229,7 @@ describe("Agent.createSession model reference ((provider, model_id) pair)", () =
|
||||
const session = await agent.createSession({ workspaceDir: ws });
|
||||
try {
|
||||
expect(session.provider).toBe("deepseek");
|
||||
expect(session.modelId).toBe("deepseek-v4-pro");
|
||||
expect(session.modelId).toBe("deepseek-v4-flash");
|
||||
} finally {
|
||||
session.dispose();
|
||||
}
|
||||
|
||||
@@ -68,22 +68,12 @@ describe("model-catalog", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("price buckets are positive (preview models without a list price omit pricing); context_window is a positive integer", () => {
|
||||
// Models with no obtainable published price. qwen3.8-max-preview: the plan runs a
|
||||
// quota-multiplier promotion instead of a per-token list price. The three SiliconFlow
|
||||
// entries: AgentHub's registry publishes no pricing for them and SiliconFlow's price list
|
||||
// is only reachable with an authenticated token, so no number can be sourced. All of them
|
||||
// carry no pricing and their costs read as 0, same as unpriced user models.
|
||||
const UNPRICED = new Set([
|
||||
"qwen-token-plan\0qwen3.8-max-preview",
|
||||
"siliconflow\0Pro/moonshotai/Kimi-K2.6",
|
||||
"siliconflow\0Pro/zai-org/GLM-5.1",
|
||||
"siliconflow\0Qwen/Qwen3.6-35B-A3B",
|
||||
]);
|
||||
it("every entry is priced (free-tier rows store a genuine $0); context_window is a positive integer", () => {
|
||||
// As of 2026-08-03 no catalog entry ships unpriced: the last holdouts (the three
|
||||
// SiliconFlow Pro//Qwen/ rows) got their official CNY list prices. Unpriced remains a
|
||||
// legal state for user-added models only.
|
||||
for (const m of MODEL_CATALOG) {
|
||||
if (UNPRICED.has(`${m.provider}\0${m.modelId}`)) {
|
||||
expect(m.pricing, m.modelId).toBeUndefined();
|
||||
} else if (m.modelId.endsWith(":free") || m.modelId === "openrouter/free") {
|
||||
if (m.modelId.endsWith(":free") || m.modelId === "openrouter/free") {
|
||||
// Free-tier gateway model (:free variants and the openrouter/free router): a genuine
|
||||
// $0 price (not "unknown"), so costs compute to 0.
|
||||
expect(m.pricing, m.modelId).toBeDefined();
|
||||
@@ -111,8 +101,8 @@ describe("model-catalog", () => {
|
||||
// It matches on (group, upstream id) pairs, so an identically named upstream id never
|
||||
// matches across the wrong group. There is no bare-id lookup at all: a gateway reselling a
|
||||
// vendor model keeps the vendor's upstream id, so a bare id names no single catalog entry
|
||||
// and the catalog never offers to pick one (`glm-5.2`, `qwen3.7-max`, `qwen3.7-plus` and
|
||||
// `deepseek-v4-pro` each appear under two groups).
|
||||
// and the catalog never offers to pick one (`glm-5.2`, `deepseek-v4-pro`, `qwen3.8-max`,
|
||||
// `qwen3.7-plus` and `deepseek-v4-flash-0731` each appear under two groups).
|
||||
expect(catalogEntryFor("anthropic", "claude-sonnet-4-6")?.displayName).toBe(
|
||||
"Claude Sonnet 4.6",
|
||||
);
|
||||
@@ -121,6 +111,14 @@ describe("model-catalog", () => {
|
||||
expect(catalogEntryFor("openrouter", "xiaomi/mimo-v2.5")?.displayName).toBe("MiMo-V2.5");
|
||||
expect(catalogEntryFor("custom", "my-own")).toBeUndefined();
|
||||
// Each group's entry for a resold id is reached only through that group.
|
||||
expect(catalogEntryFor("qwen-token-plan", "qwen3.8-max")?.provider).toBe("qwen-token-plan");
|
||||
expect(catalogEntryFor("qwen-pay-as-you-go", "qwen3.8-max")?.provider).toBe(
|
||||
"qwen-pay-as-you-go",
|
||||
);
|
||||
// The bare resold id never leaks into the vendor's own group (deepseek's flash revision is
|
||||
// sold there as deepseek-v4-flash, without the date suffix).
|
||||
expect(catalogEntryFor("deepseek", "deepseek-v4-flash-0731")).toBeUndefined();
|
||||
expect(catalogEntryFor("deepseek", "deepseek-v4-flash")?.provider).toBe("deepseek");
|
||||
expect(catalogEntryFor("zhipu", "glm-5.2")?.contextWindow).toBe(1000000);
|
||||
expect(catalogEntryFor("qwen-token-plan", "glm-5.2")?.contextWindow).toBe(1048576);
|
||||
expect(catalogEntryFor("deepseek", "deepseek-v4-pro")?.provider).toBe("deepseek");
|
||||
@@ -157,6 +155,7 @@ describe("model-catalog", () => {
|
||||
"anthropic/claude-opus-4.8",
|
||||
"anthropic/claude-opus-4.7",
|
||||
"anthropic/claude-sonnet-5",
|
||||
"deepseek/deepseek-v4-flash-0731",
|
||||
"deepseek/deepseek-v4-flash",
|
||||
"deepseek/deepseek-v4-pro",
|
||||
"google/gemini-3.6-flash",
|
||||
@@ -167,11 +166,11 @@ describe("model-catalog", () => {
|
||||
"moonshotai/kimi-k3",
|
||||
"moonshotai/kimi-k2.6",
|
||||
"nvidia/nemotron-3-ultra-550b-a55b:free",
|
||||
"openai/gpt-5.6-luna",
|
||||
"openai/gpt-5.6-sol",
|
||||
"openai/gpt-5.6-terra",
|
||||
"openai/gpt-5.5",
|
||||
"openrouter/free",
|
||||
"poolside/laguna-m.1:free",
|
||||
"qwen/qwen3.6-35b-a3b",
|
||||
"stepfun/step-3.7-flash",
|
||||
"tencent/hy3",
|
||||
@@ -216,28 +215,29 @@ describe("model-catalog", () => {
|
||||
}
|
||||
const qtp = MODEL_CATALOG.filter((m) => m.provider === "qwen-token-plan");
|
||||
expect(qtp.map((m) => m.modelId)).toEqual([
|
||||
"deepseek-v4-flash-0731",
|
||||
"deepseek-v4-pro",
|
||||
"glm-5.2",
|
||||
"qwen3.8-max-preview",
|
||||
"qwen3.7-max",
|
||||
"qwen3.8-max",
|
||||
"qwen3.7-plus",
|
||||
]);
|
||||
for (const m of qtp) {
|
||||
expect(m.clientType).toBe("openai");
|
||||
expect(m.baseUrl).toBe("https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1");
|
||||
}
|
||||
// Vision flags per the plan's supported-model table: 3.8-max-preview and 3.7-plus see images.
|
||||
// Vision flags per the plan's supported-model table: 3.8-max and 3.7-plus see images.
|
||||
expect(qtp.map((m) => [m.modelId, m.supportsVision])).toEqual([
|
||||
["deepseek-v4-flash-0731", false],
|
||||
["deepseek-v4-pro", false],
|
||||
["glm-5.2", false],
|
||||
["qwen3.8-max-preview", true],
|
||||
["qwen3.7-max", false],
|
||||
["qwen3.8-max", true],
|
||||
["qwen3.7-plus", true],
|
||||
]);
|
||||
const qpayg = MODEL_CATALOG.filter((m) => m.provider === "qwen-pay-as-you-go");
|
||||
expect(qpayg.map((m) => [m.modelId, m.supportsVision])).toEqual([
|
||||
["deepseek-v4-flash-0731", false],
|
||||
["kimi/kimi-k3", true],
|
||||
["qwen3.7-max", false],
|
||||
["qwen3.8-max", true],
|
||||
["qwen3.7-plus", true],
|
||||
["ZHIPU/GLM-5.2", false],
|
||||
]);
|
||||
@@ -280,11 +280,16 @@ describe("model-catalog", () => {
|
||||
}
|
||||
}
|
||||
const gateway = [...or, ...fw, ...sf, ...qtp, ...qpayg];
|
||||
// Pricing (USD): MiMo v2.5 and Hy3.
|
||||
// Pricing (USD, per the 2026-08-03 models-API re-read): MiMo v2.5 and Hy3 publish a real
|
||||
// cache-hit price and no per-token write premium, so cache_write carries the input price.
|
||||
const mimo = MODEL_CATALOG.find((m) => m.modelId === "xiaomi/mimo-v2.5")!.pricing!;
|
||||
expect([mimo.cache_read, mimo.cache_write, mimo.output]).toEqual([0.0028, 0.14, 0.28]);
|
||||
const hy3 = MODEL_CATALOG.find((m) => m.modelId === "tencent/hy3")!.pricing!;
|
||||
expect([hy3.cache_read, hy3.cache_write, hy3.output]).toEqual([0.035, 0.14, 0.58]);
|
||||
expect([hy3.cache_read, hy3.cache_write, hy3.output]).toEqual([0.033, 0.132, 0.528]);
|
||||
// Anthropic/GPT rows publish a genuine 1.25x per-token cache-write premium; it is stored
|
||||
// as-is (cache_write > input would be wrong to collapse back to input).
|
||||
const sonnet5 = catalogEntryFor("openrouter", "anthropic/claude-sonnet-5")!.pricing!;
|
||||
expect([sonnet5.cache_read, sonnet5.cache_write, sonnet5.output]).toEqual([0.2, 2.5, 10]);
|
||||
// Gemini 3.6 Flash and 3.5 Flash Lite: upstream publishes a cache-hit price, so cache_read
|
||||
// stores the real discounted price (not the input price) — cache_read is its own billing
|
||||
// bucket in the cost center. cache_write repeats input (no per-token cache-write fee).
|
||||
@@ -453,9 +458,9 @@ describe("resolveModelEnv (PRN-021: env fallback resolved by AgentHub routing ru
|
||||
expect(modelHomepageUrl("qwen-pay-as-you-go", "ZHIPU/GLM-5.2")).toBe(
|
||||
"https://www.qianwenai.com/models/ZHIPU%2FGLM-5.2",
|
||||
);
|
||||
// The preview model has no dedicated page: falls back to the plan's model overview.
|
||||
expect(modelHomepageUrl("qwen-token-plan", "qwen3.8-max-preview")).toBe(
|
||||
providerInfo("qwen-token-plan")!.modelsUrl,
|
||||
// Token Plan models link to their qianwenai model page (bare ids, no encoding needed).
|
||||
expect(modelHomepageUrl("qwen-token-plan", "qwen3.8-max")).toBe(
|
||||
"https://www.qianwenai.com/models/qwen3.8-max",
|
||||
);
|
||||
// Direct vendors link to the vendor's model docs page.
|
||||
expect(modelHomepageUrl("deepseek", "deepseek-v4-pro")).toBe(
|
||||
|
||||
@@ -1143,9 +1143,9 @@ describe("project-config round trip", () => {
|
||||
expect(entry?.vision).toBeUndefined();
|
||||
});
|
||||
|
||||
it("default config presets the full model catalog (default = deepseek deepseek-v4-pro)", () => {
|
||||
it("default config presets the full model catalog (default = deepseek deepseek-v4-flash)", () => {
|
||||
const cfg = defaultProjectConfig();
|
||||
expect(cfg.default_model).toEqual({ provider: "deepseek", model_id: "deepseek-v4-pro" });
|
||||
expect(cfg.default_model).toEqual({ provider: "deepseek", model_id: "deepseek-v4-flash" });
|
||||
// The catalog is presented in full: provider and model_id are separate columns, model_id
|
||||
// being the plain upstream id (vision is only persisted as false for models that don't
|
||||
// support images).
|
||||
@@ -1157,8 +1157,8 @@ describe("project-config round trip", () => {
|
||||
(c) => c.provider === entry.provider && c.modelId === entry.model_id,
|
||||
)!;
|
||||
expect(entry.vision).toBe(cat.supportsVision ? undefined : false);
|
||||
// A catalog entry without a list price (the Token Plan preview model) presets no
|
||||
// pricing; every other catalog entry stores USD pricing.
|
||||
// A catalog entry without a list price would preset no pricing (none currently);
|
||||
// every priced catalog entry stores USD pricing.
|
||||
if (cat.pricing === undefined) expect(entry.pricing).toBeUndefined();
|
||||
else expect(entry.pricing?.unit).toBe("usd_per_mtok");
|
||||
// A model that auto-routes leaves client_type unset; a gateway model (OpenRouter)
|
||||
|
||||
Reference in new issue
Block a user