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:
Yaowei ZhengandClaude Fable 5 authored and GitHub committed 2026-08-03 15:51:37 +08:00
1 parent c76a2c61bb
commit f3193ae628
17 files changed
+169 -150

No files matched your search

+95 -81
View File
@@ -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>";
+1 -1
View File
@@ -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(),
};
}
+3 -3
View File
@@ -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();
}
+34 -29
View File
@@ -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(
+4 -4
View File
@@ -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)