forked from EduCraft/curriculum-project-hub
fix(hub): default to OpenRouter Sonnet model
This commit is contained in:
+4
-1
@@ -10,7 +10,10 @@ ANTHROPIC_BASE_URL="https://openrouter.ai/api"
|
|||||||
ANTHROPIC_AUTH_TOKEN=""
|
ANTHROPIC_AUTH_TOKEN=""
|
||||||
ANTHROPIC_API_KEY=""
|
ANTHROPIC_API_KEY=""
|
||||||
|
|
||||||
# Model override (optional). Defaults to Claude Sonnet. To use GLM:
|
# Model override (optional). Defaults to anthropic/claude-sonnet-5.
|
||||||
|
# Use an OpenRouter model slug, for example:
|
||||||
|
# ANTHROPIC_DEFAULT_SONNET_MODEL="~anthropic/claude-sonnet-latest"
|
||||||
|
# To use GLM:
|
||||||
# ANTHROPIC_DEFAULT_SONNET_MODEL="z-ai/glm-4.7"
|
# ANTHROPIC_DEFAULT_SONNET_MODEL="z-ai/glm-4.7"
|
||||||
# ANTHROPIC_DEFAULT_OPUS_MODEL="z-ai/glm-4.7"
|
# ANTHROPIC_DEFAULT_OPUS_MODEL="z-ai/glm-4.7"
|
||||||
# ANTHROPIC_DEFAULT_HAIKU_MODEL="z-ai/glm-4.6"
|
# ANTHROPIC_DEFAULT_HAIKU_MODEL="z-ai/glm-4.6"
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { InMemoryModelRegistry } from "./models.js";
|
||||||
|
|
||||||
|
type Env = Readonly<Record<string, string | undefined>>;
|
||||||
|
|
||||||
|
const DEFAULT_SONNET_MODEL = "anthropic/claude-sonnet-5";
|
||||||
|
const DEFAULT_SONNET_LABEL = "Claude Sonnet 5";
|
||||||
|
|
||||||
|
function readEnv(env: Env, name: string): string | undefined {
|
||||||
|
const value = env[name]?.trim();
|
||||||
|
return value === undefined || value === "" ? undefined : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultSonnetModel(env: Env = process.env): string {
|
||||||
|
return readEnv(env, "ANTHROPIC_DEFAULT_SONNET_MODEL") ?? DEFAULT_SONNET_MODEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDefaultModelRegistry(env: Env = process.env): InMemoryModelRegistry {
|
||||||
|
const sonnetModel = defaultSonnetModel(env);
|
||||||
|
const sonnetLabel =
|
||||||
|
readEnv(env, "ANTHROPIC_DEFAULT_SONNET_MODEL_NAME") ??
|
||||||
|
(sonnetModel === DEFAULT_SONNET_MODEL ? DEFAULT_SONNET_LABEL : sonnetModel);
|
||||||
|
|
||||||
|
return new InMemoryModelRegistry(
|
||||||
|
[
|
||||||
|
{ id: sonnetModel, label: sonnetLabel, toolCapable: true },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ id: "draft", label: "草稿", defaultModel: sonnetModel },
|
||||||
|
{ id: "review", label: "审校", defaultModel: sonnetModel },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
+5
-13
@@ -8,12 +8,12 @@
|
|||||||
* loop are ADR-0003-authorized (chat must match binding).
|
* loop are ADR-0003-authorized (chat must match binding).
|
||||||
*
|
*
|
||||||
* Env (see .env.example):
|
* Env (see .env.example):
|
||||||
* DATABASE_URL, OPENROUTER_API_KEY, FEISHU_APP_ID/SECRET/BOT_OPEN_ID, PORT
|
* DATABASE_URL, ANTHROPIC_AUTH_TOKEN, FEISHU_APP_ID/SECRET/BOT_OPEN_ID, PORT
|
||||||
*/
|
*/
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import Fastify from "fastify";
|
import Fastify from "fastify";
|
||||||
import { prisma } from "./db.js";
|
import { prisma } from "./db.js";
|
||||||
import { InMemoryModelRegistry } from "./agent/models.js";
|
import { createDefaultModelRegistry } from "./agent/defaultModels.js";
|
||||||
import { createLarkClient, startFeishuListenerWithClient, type FeishuRuntime } from "./feishu/client.js";
|
import { createLarkClient, startFeishuListenerWithClient, type FeishuRuntime } from "./feishu/client.js";
|
||||||
import { makeTriggerHandler } from "./feishu/trigger.js";
|
import { makeTriggerHandler } from "./feishu/trigger.js";
|
||||||
|
|
||||||
@@ -54,17 +54,9 @@ async function main(): Promise<void> {
|
|||||||
|
|
||||||
app.get("/api/healthz", async () => ({ ok: true, ts: Date.now() }));
|
app.get("/api/healthz", async () => ({ ok: true, ts: Date.now() }));
|
||||||
|
|
||||||
// Model registry (role-as-data, ADR-0017). Claude Code SDK uses model
|
// Model registry (role-as-data, ADR-0017). Defaults are OpenRouter model
|
||||||
// aliases like 'sonnet', 'haiku' — the registry maps roles to these.
|
// slugs; ANTHROPIC_DEFAULT_SONNET_MODEL can override the role default.
|
||||||
const models = new InMemoryModelRegistry(
|
const models = createDefaultModelRegistry();
|
||||||
[
|
|
||||||
{ id: "claude-sonnet-4-20250514", label: "Claude Sonnet", toolCapable: true },
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{ id: "draft", label: "草稿", defaultModel: "claude-sonnet-4-20250514" },
|
|
||||||
{ id: "review", label: "审校", defaultModel: "claude-sonnet-4-20250514" },
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- Feishu listener ---
|
// --- Feishu listener ---
|
||||||
const feishuConfig = { appId: feishuAppId, appSecret: feishuAppSecret, botOpenId: feishuBotOpenId };
|
const feishuConfig = { appId: feishuAppId, appSecret: feishuAppSecret, botOpenId: feishuBotOpenId };
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { createDefaultModelRegistry } from "../../src/agent/defaultModels.js";
|
||||||
|
|
||||||
|
describe("default model registry", () => {
|
||||||
|
it("uses OpenRouter model slugs for Sonnet defaults", () => {
|
||||||
|
const models = createDefaultModelRegistry({});
|
||||||
|
|
||||||
|
expect(models.listModels()[0]).toMatchObject({
|
||||||
|
id: "anthropic/claude-sonnet-5",
|
||||||
|
label: "Claude Sonnet 5",
|
||||||
|
toolCapable: true,
|
||||||
|
});
|
||||||
|
expect(models.resolve(undefined, "draft")).toBe("anthropic/claude-sonnet-5");
|
||||||
|
expect(models.resolve(undefined, "review")).toBe("anthropic/claude-sonnet-5");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("honors ANTHROPIC_DEFAULT_SONNET_MODEL when configured", () => {
|
||||||
|
const models = createDefaultModelRegistry({
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL: "z-ai/glm-4.7",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(models.listModels()[0]?.id).toBe("z-ai/glm-4.7");
|
||||||
|
expect(models.resolve(undefined, "draft")).toBe("z-ai/glm-4.7");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user