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 Zheng
2026-08-03 15:51:37 +08:00
committed by GitHub
parent c76a2c61bb
commit f3193ae628
17 changed files with 169 additions and 150 deletions
+3 -3
View File
@@ -86,11 +86,11 @@ Four Skill groups ship in the box ([docs](https://penguin.ooo/docs/skills)); age
| Model | Providers |
| ---------------- | -------------------------------------------------------------------------------- |
| DeepSeek V4 | DeepSeek, OpenRouter, Fireworks AI, SiliconFlow, Qwen Token Plan |
| DeepSeek V4 | DeepSeek, OpenRouter, Fireworks AI, SiliconFlow, Qwen Token Plan, Qwen Pay-As-You-Go |
| Kimi K3 | Moonshot AI, OpenRouter, Qwen Pay-As-You-Go |
| GLM 5.2 | Z.AI, OpenRouter, Fireworks AI, SiliconFlow, Qwen Token Plan, Qwen Pay-As-You-Go |
| Hunyuan 3 | OpenRouter |
| Qwen 3.8 Max | Qwen Token Plan (preview) |
| Qwen 3.8 Max | Qwen Token Plan, Qwen Pay-As-You-Go |
| GPT 5.6 | OpenRouter |
| Gemini 3.6 Flash | Google Gemini, OpenRouter |
| Claude 5 | Anthropic, OpenRouter |
@@ -174,7 +174,7 @@ cd penguin-install
The same engine, scriptable — made to be driven by agents (and agents building agents):
```bash
penguin config model add --provider deepseek --model-id deepseek-v4-pro --api-key sk-... --set-default
penguin config model add --provider deepseek --model-id deepseek-v4-flash --api-key sk-... --set-default
penguin run -m "Create hello.txt containing Hello, Penguin" # one-shot task
penguin chat # interactive REPL (/compact, /exit, Ctrl-C to interrupt)
penguin server # headless service (same API the Web App uses)
+3 -3
View File
@@ -86,11 +86,11 @@ https://github.com/user-attachments/assets/aec49ae9-b743-467b-b247-37bedfeaa36e
| 模型 | 可用供应商 |
| ---------------- | -------------------------------------------------------------------------------- |
| DeepSeek V4 | DeepSeek, OpenRouter, Fireworks AI, SiliconFlow, Qwen Token Plan |
| DeepSeek V4 | DeepSeek, OpenRouter, Fireworks AI, SiliconFlow, Qwen Token Plan, Qwen Pay-As-You-Go |
| Kimi K3 | Moonshot AI, OpenRouter, Qwen Pay-As-You-Go |
| GLM 5.2 | Z.AI, OpenRouter, Fireworks AI, SiliconFlow, Qwen Token Plan, Qwen Pay-As-You-Go |
| Hunyuan 3 | OpenRouter |
| Qwen 3.8 Max | Qwen Token Plan(预览) |
| Qwen 3.8 Max | Qwen Token Plan, Qwen Pay-As-You-Go |
| GPT 5.6 | OpenRouter |
| Gemini 3.6 Flash | Google Gemini, OpenRouter |
| Claude 5 | Anthropic, OpenRouter |
@@ -174,7 +174,7 @@ cd penguin-install
同一引擎、可脚本化——为被 Agent 驱动而生(以及让 Agent 构建 Agent):
```bash
penguin config model add --provider deepseek --model-id deepseek-v4-pro --api-key sk-... --set-default
penguin config model add --provider deepseek --model-id deepseek-v4-flash --api-key sk-... --set-default
penguin run -m "Create hello.txt containing Hello, Penguin" # 单次任务
penguin chat # 交互式 REPL(/compact、/exit、Ctrl-C 中断)
penguin server # 无界面服务(与 Web 应用同一套 API)
+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)
+2 -2
View File
@@ -67,11 +67,11 @@ Model entry (`[[models]]`) fields:
| `created_at` | Write timestamp of `api_key` (ISO 8601; a display field maintained by the interface layer) |
```toml
default_model = { provider = "deepseek", model_id = "deepseek-v4-pro" }
default_model = { provider = "deepseek", model_id = "deepseek-v4-flash" }
[[models]]
provider = "deepseek"
model_id = "deepseek-v4-pro"
model_id = "deepseek-v4-flash"
context_window = 1000000
vision = false
api_key = "sk-..."
+2 -2
View File
@@ -67,11 +67,11 @@ openrouter、fireworks、siliconflow、qwen-token-plan、qwen-pay-as-you-go 与
| `created_at` | `api_key` 写入时间(ISO 8601,界面维护的展示字段) |
```toml
default_model = { provider = "deepseek", model_id = "deepseek-v4-pro" }
default_model = { provider = "deepseek", model_id = "deepseek-v4-flash" }
[[models]]
provider = "deepseek"
model_id = "deepseek-v4-pro"
model_id = "deepseek-v4-flash"
context_window = 1000000
vision = false
api_key = "sk-..."
+5 -5
View File
@@ -29,17 +29,17 @@ Each Project's available models are recorded in the hidden `.project_config.toml
| `pricing` | Three price buckets (unit `usd_per_mtok`, USD per million tokens): `cache_read` / `cache_write` / `output` |
| `api_key` / `base_url` | Inlined credentials, both optional; when blank, AgentHub falls back to environment variables |
A fresh Project defaults to deepseek-v4-pro. A `vision_model` entry can additionally designate the proxy model that `describe_image` uses for text-only session models (see [Tools & Approval](/tools)); it is unset by default.
A fresh Project defaults to deepseek-v4-flash. A `vision_model` entry can additionally designate the proxy model that `describe_image` uses for text-only session models (see [Tools & Approval](/tools)); it is unset by default.
File shape (illustrative):
```toml
default_model = { provider = "deepseek", model_id = "deepseek-v4-pro" }
default_model = { provider = "deepseek", model_id = "deepseek-v4-flash" }
vision_model = { provider = "google", model_id = "gemini-3.1-pro-preview" }
[[models]]
provider = "deepseek"
model_id = "deepseek-v4-pro"
model_id = "deepseek-v4-flash"
context_window = 1000000
[[models]]
@@ -73,9 +73,9 @@ Built-in groups and their env-var fallbacks (catalog source: `packages/core/src/
The gateway groups (openrouter / fireworks / siliconflow / qwen-token-plan / qwen-pay-as-you-go) go through AgentHub's OpenAI client, so with blank credentials they read `OPENAI_API_KEY` — not a gateway-specific variable.
The preset catalog also carries OpenRouter's free tier: `:free` model variants (e.g. `inclusionai/ling-3.0-flash:free`, `poolside/laguna-m.1:free`) and the `openrouter/free` unified Free Models Router. They cost nothing, but are subject to OpenRouter's free-tier rate limits and data policy.
The preset catalog also carries OpenRouter's free tier: `:free` model variants (e.g. `inclusionai/ling-3.0-flash:free`, `nvidia/nemotron-3-ultra-550b-a55b:free`) and the `openrouter/free` unified Free Models Router. They cost nothing, but are subject to OpenRouter's free-tier rate limits and data policy.
Some models in the preset catalog: deepseek-v4-pro / deepseek-v4-flash, gemini-3.1-pro-preview, claude-opus-4-8 / claude-sonnet-4-6, gpt-5.5, glm-5.2, kimi-k2.6, qwen3.8-max-preview (not exhaustive).
Some models in the preset catalog: deepseek-v4-pro / deepseek-v4-flash, gemini-3.1-pro-preview, claude-opus-4-8 / claude-sonnet-4-6, gpt-5.5, glm-5.2, kimi-k2.6, qwen3.8-max (not exhaustive).
## Thinking levels
+5 -5
View File
@@ -29,17 +29,17 @@ description: 经 AgentHub 单一网关接入模型,以 (provider, model_id)
| `pricing` | 三档价格(单位 `usd_per_mtok`,USD 每百万 Token):`cache_read` / `cache_write` / `output` |
| `api_key` / `base_url` | 内联凭证,可留空;留空时 AgentHub 回退读环境变量 |
新建 Project 的默认模型是 deepseek-v4-pro。另可配置一条 `vision_model`,作为 text-only 模型使用 `describe_image` 时的代读模型(见 [工具与审批](/tools));默认不配置。
新建 Project 的默认模型是 deepseek-v4-flash。另可配置一条 `vision_model`,作为 text-only 模型使用 `describe_image` 时的代读模型(见 [工具与审批](/tools));默认不配置。
文件形态(示意):
```toml
default_model = { provider = "deepseek", model_id = "deepseek-v4-pro" }
default_model = { provider = "deepseek", model_id = "deepseek-v4-flash" }
vision_model = { provider = "google", model_id = "gemini-3.1-pro-preview" }
[[models]]
provider = "deepseek"
model_id = "deepseek-v4-pro"
model_id = "deepseek-v4-flash"
context_window = 1000000
[[models]]
@@ -73,9 +73,9 @@ api_key = "sk-..."
网关分组(openrouter / fireworks / siliconflow / qwen-token-plan / qwen-pay-as-you-go)经 AgentHub 的 OpenAI 客户端请求,因此凭证留空时读取的是 `OPENAI_API_KEY`,而非网关自己的变量名。
预置目录还收录了 OpenRouter 的免费档:`:free` 模型变体(如 `inclusionai/ling-3.0-flash:free`、`poolside/laguna-m.1:free`)与统一路由 `openrouter/free`(Free Models Router),零成本可用,但受 OpenRouter 免费档速率限制与数据政策约束。
预置目录还收录了 OpenRouter 的免费档:`:free` 模型变体(如 `inclusionai/ling-3.0-flash:free`、`nvidia/nemotron-3-ultra-550b-a55b:free`)与统一路由 `openrouter/free`(Free Models Router),零成本可用,但受 OpenRouter 免费档速率限制与数据政策约束。
预置目录中的部分模型:deepseek-v4-pro / deepseek-v4-flash、gemini-3.1-pro-preview、claude-opus-4-8 / claude-sonnet-4-6、gpt-5.5、glm-5.2、kimi-k2.6、qwen3.8-max-preview 等(非完整清单)。
预置目录中的部分模型:deepseek-v4-pro / deepseek-v4-flash、gemini-3.1-pro-preview、claude-opus-4-8 / claude-sonnet-4-6、gpt-5.5、glm-5.2、kimi-k2.6、qwen3.8-max 等(非完整清单)。
## 思考等级
+1 -1
View File
@@ -18,7 +18,7 @@ For other options (npm, from source), see [Installation](/installation).
PenguinHarness ships with no built-in model credentials, so configure a model first. Use the Models page in the Web UI, or the CLI:
```bash
penguin config model add --provider deepseek --model-id deepseek-v4-pro --api-key sk-... --set-default
penguin config model add --provider deepseek --model-id deepseek-v4-flash --api-key sk-... --set-default
```
- A model is always referenced as a `(provider, model_id)` pair, so `--provider` and `--model-id` are both required — the Provider is never inferred from the model id. See [Models & Providers](/models) for the built-in groups.
+1 -1
View File
@@ -18,7 +18,7 @@ curl -fsSL https://penguin.ooo/install.sh | sh
PenguinHarness 不内置任何模型凭据,使用前需要先配置一个模型。可以在 Web UI 的 Models 页面完成,也可以用 CLI:
```bash
penguin config model add --provider deepseek --model-id deepseek-v4-pro --api-key sk-... --set-default
penguin config model add --provider deepseek --model-id deepseek-v4-flash --api-key sk-... --set-default
```
- 模型引用始终是 `(provider, model_id)` 二元组,因此 `--provider` 与 `--model-id` 均为必填——Provider 绝不由模型 id 推断。内置分组见[模型与 Provider](/models)。
+1 -1
View File
@@ -79,7 +79,7 @@ describe("auth", () => {
);
expect(toml).toContain('name = "bob"');
expect(toml).toContain(
'default_model = { provider = "deepseek", model_id = "deepseek-v4-pro" }',
'default_model = { provider = "deepseek", model_id = "deepseek-v4-flash" }',
);
});
+5 -5
View File
@@ -97,7 +97,7 @@ describe("models preset & catalog enrichment", () => {
const res = await api.get(url());
expect(res.status).toBe(200);
const body = (await res.json()) as ModelsResponse;
expect(body.defaultModel).toEqual({ provider: "deepseek", modelId: "deepseek-v4-pro" });
expect(body.defaultModel).toEqual({ provider: "deepseek", modelId: "deepseek-v4-flash" });
expect(body.models.map(pairKey)).toEqual(catalogPairs);
const sonnet = pick(body, "anthropic", "claude-sonnet-4-6");
@@ -112,10 +112,10 @@ describe("models preset & catalog enrichment", () => {
expect(sonnet.credential).toBeUndefined();
expect(sonnet.clientType).toBeUndefined();
const deepseek = pick(body, "deepseek", "deepseek-v4-flash");
const deepseek = pick(body, "deepseek", "deepseek-v4-pro");
expect(deepseek.vision).toBe(false);
expect(deepseek.envKey).toBe("DEEPSEEK_API_KEY");
expect(pick(body, "deepseek", "deepseek-v4-pro").isDefault).toBe(true);
expect(pick(body, "deepseek", "deepseek-v4-flash").isDefault).toBe(true);
// OpenRouter gateway model: the upstream id contains `/`, but under column storage it's just a
// plain string; openai protocol + a preset base URL inlined on the entry (no secret).
@@ -255,7 +255,7 @@ describe("default_project presets", () => {
const res = await api.get("/api/projects/default_project/models");
expect(res.status).toBe(200);
const body = (await res.json()) as ModelsResponse;
expect(body.defaultModel).toEqual({ provider: "deepseek", modelId: "deepseek-v4-pro" });
expect(body.defaultModel).toEqual({ provider: "deepseek", modelId: "deepseek-v4-flash" });
expect(body.models.map(pairKey)).toEqual(catalogPairs);
// The point of presets is "works out of the box": creating a Session should succeed without passing a model ref.
@@ -266,7 +266,7 @@ describe("default_project presets", () => {
expect(created.status).toBe(201);
const { session } = (await created.json()) as SessionCreateResponse;
expect(session.provider).toBe("deepseek");
expect(session.modelId).toBe("deepseek-v4-pro");
expect(session.modelId).toBe("deepseek-v4-flash");
});
it("a default_project that already has models configured is left untouched (existing CLI config is not overwritten)", async () => {
@@ -3,7 +3,7 @@ name: agenthub-models
description: Call model APIs through @prismshadow/agenthub — streaming text generation, image generation, speech synthesis, embeddings and the supported-model registry with one client.
short_description: Call model APIs with one AgentHub client.
short_description_zh: 用一个 AgentHub 客户端调用模型 API。
version: 8
version: 9
updated: 2026-07-22T00:00:00Z
---
@@ -60,7 +60,7 @@ Use exact model ids. If an id is not in the table below and the user has not giv
| OpenAI embedding | `text-embedding-3-small`, `text-embedding-3-large` | — |
| Kimi K3 | `kimi-k3` | OpenRouter `moonshotai/kimi-k3` |
| Kimi K2.6 | `kimi-k2.6` | OpenRouter `moonshotai/kimi-k2.6`; SiliconFlow `Pro/moonshotai/Kimi-K2.6` |
| DeepSeek V4 | `deepseek-v4-pro`, `deepseek-v4-flash` | OpenRouter `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash`; SiliconFlow `deepseek-ai/DeepSeek-V4-Pro`, `deepseek-ai/DeepSeek-V4-Flash` |
| DeepSeek V4 | `deepseek-v4-pro`, `deepseek-v4-flash` | OpenRouter `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash`, `deepseek/deepseek-v4-flash-0731`; SiliconFlow `deepseek-ai/DeepSeek-V4-Pro`, `deepseek-ai/DeepSeek-V4-Flash` |
| GLM 5.2 | `glm-5.2` | OpenRouter `z-ai/glm-5.2`; SiliconFlow `zai-org/GLM-5.2` |
| GLM 5.1 | `glm-5.1` | OpenRouter `z-ai/glm-5.1`; SiliconFlow `Pro/zai-org/GLM-5.1` |
| Qwen 3.6 | — | OpenRouter `qwen/qwen3.6-35b-a3b`; SiliconFlow `Qwen/Qwen3.6-35B-A3B` |
+2 -2
View File
@@ -3,7 +3,7 @@ name: penguin-sdk
description: Build AI apps on the Penguin Harness SDK — self-contained projects, the createSession/run streaming loop with thinking and image messages, and a complete RAG recipe that ingests documents into a knowledge base and answers with citations behind a web UI.
short_description: Build AI and RAG apps on the Penguin Harness SDK.
short_description_zh: 基于 Penguin SDK 构建 AI 与 RAG 应用。
version: 17
version: 18
updated: 2026-07-30T11:10:00Z
---
@@ -56,7 +56,7 @@ If the package is not on your npm registry (it is developed in the PenguinHarnes
Configure a model for the app's data root, in this order — stop at the first that works:
1. `penguin config model add --root <data_dir> --provider <group> --model-id <id> --api-key <key> [--base-url <url>] [--client-type openai] --set-default` — prefer `--client-type openai --base-url <endpoint>` (works with any OpenAI-compatible endpoint; exact ids in the agenthub-models skill). `--provider` is required: a model is always the `(provider, model_id)` pair and the group is never inferred from the id (`custom` for an endpoint outside the built-in groups).
2. Environment variables cover the **credential only** (`DEEPSEEK_API_KEY`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, …) — model selection still comes from the project config, whose preset default is `deepseek-v4-pro`. Env-only setup therefore works out of the box only with `DEEPSEEK_API_KEY`; for another vendor either run the CLI command above or pass a configured `{ provider, modelId }` pair to `createSession`.
2. Environment variables cover the **credential only** (`DEEPSEEK_API_KEY`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, …) — model selection still comes from the project config, whose preset default is `deepseek-v4-flash`. Env-only setup therefore works out of the box only with `DEEPSEEK_API_KEY`; for another vendor either run the CLI command above or pass a configured `{ provider, modelId }` pair to `createSession`.
Keep model API keys **project-local**: configure them with the penguin CLI into the app's own data root under the working directory, so the project stays self-contained and movable. When building an AI app, **always pass `--root <data_dir>` pointing at the app's data directory inside the current working directory** (the same path you give `createAgent({ root })`, e.g. `./penguin_data`) — never run `penguin config ...` without `--root`, or it writes to the global `~/.penguin/data` instead of the project. Never read, copy or fall back to model keys stored in the user's global `~/.penguin` directory — that config belongs to the person running Penguin, not to the app you are building.