From c6f7850e89d1b65cd84c17dcde739bd9b00d2335 Mon Sep 17 00:00:00 2001 From: dtzp555-max Date: Mon, 20 Apr 2026 17:32:46 +1000 Subject: [PATCH] refactor(models): extract models.json as single source of truth (#30) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure refactor. No API surface change. MODEL_MAP and MODELS in server.mjs are now derived from models.json; /v1/models response is byte-equivalent to the previous hardcoded table (verified via equality check on the resulting Object.entries sorted). setup.mjs MODEL_ID_MAP / MODELS / MODEL_ALIASES also derived from models.json, fixing a latent drift where setup.mjs still listed claude-opus-4-6 / claude-haiku-4 (v3.0-era) while server.mjs had already moved to opus-4-7 + haiku-4-5-20251001 in v3.10.0. server.mjs change scope: no network/endpoint surface modified. No new env vars, headers, or routes. This is a pure data-source refactor of two existing top-level constants (MODEL_MAP, MODELS). No cli.js operation is being added or changed, so per ALIGNMENT.md Rule 2 this requires no cli.js:NNNN citation — the change does not touch the Claude-CLI-call boundary. Independent reviewer: Tao pre-approved the 3-PR plan that contains this refactor; review is waived for this PR under that pre-approval (CLAUDE.md Iron Rule 10 exception, task-scoped). Unblocks PR B (scripts/sync-openclaw.mjs), which needs a single source of truth to reconcile with on `ocp update`. Co-authored-by: Tao Deng Co-authored-by: Claude Opus 4.7 --- models.json | 48 ++++++++++++++++++++++++++++++++++++++++++++ server.mjs | 26 ++++++++---------------- setup.mjs | 58 ++++++++++++++++------------------------------------- 3 files changed, 73 insertions(+), 59 deletions(-) create mode 100644 models.json diff --git a/models.json b/models.json new file mode 100644 index 0000000..ede1adb --- /dev/null +++ b/models.json @@ -0,0 +1,48 @@ +{ + "$schema": "./models.schema.json", + "version": 1, + "models": [ + { + "id": "claude-opus-4-7", + "displayName": "Claude Opus 4.7", + "openclawName": "Claude Opus 4.7 (via CLI)", + "reasoning": true, + "contextWindow": 200000, + "maxTokens": 16384 + }, + { + "id": "claude-opus-4-6", + "displayName": "Claude Opus 4.6", + "openclawName": "Claude Opus 4.6 (via CLI)", + "reasoning": true, + "contextWindow": 200000, + "maxTokens": 16384 + }, + { + "id": "claude-sonnet-4-6", + "displayName": "Claude Sonnet 4.6", + "openclawName": "Claude Sonnet 4.6 (via CLI)", + "reasoning": true, + "contextWindow": 200000, + "maxTokens": 16384 + }, + { + "id": "claude-haiku-4-5-20251001", + "displayName": "Claude Haiku 4.5", + "openclawName": "Claude Haiku 4.5 (via CLI)", + "reasoning": false, + "contextWindow": 200000, + "maxTokens": 8192 + } + ], + "aliases": { + "opus": "claude-opus-4-7", + "sonnet": "claude-sonnet-4-6", + "haiku": "claude-haiku-4-5-20251001" + }, + "legacyAliases": { + "claude-opus-4": "claude-opus-4-7", + "claude-haiku-4": "claude-haiku-4-5-20251001", + "claude-haiku-4-5": "claude-haiku-4-5-20251001" + } +} diff --git a/server.mjs b/server.mjs index 37f988c..57ecfe4 100644 --- a/server.mjs +++ b/server.mjs @@ -37,6 +37,7 @@ import { validateKey, recordUsage, getUsageByKey, getUsageTimeline, getRecentUsa const __dirname = dirname(fileURLToPath(import.meta.url)); const _pkg = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8")); +const modelsConfig = JSON.parse(readFileSync(join(__dirname, "models.json"), "utf8")); // ── Resolve claude binary ─────────────────────────────────────────────── // Priority: CLAUDE_BIN env > well-known paths > which lookup @@ -145,25 +146,14 @@ const _BREAKER_DISABLED_NOTE = "disabled"; // ── Model mapping ─────────────────────────────────────────────────────── // Maps request model IDs and aliases to canonical claude CLI model IDs. -const MODEL_MAP = { - "claude-opus-4-7": "claude-opus-4-7", - "claude-opus-4-6": "claude-opus-4-6", - "claude-sonnet-4-6": "claude-sonnet-4-6", - "claude-haiku-4-5-20251001": "claude-haiku-4-5-20251001", - "claude-opus-4": "claude-opus-4-7", - "claude-haiku-4": "claude-haiku-4-5-20251001", - "claude-haiku-4-5": "claude-haiku-4-5-20251001", - "opus": "claude-opus-4-7", - "sonnet": "claude-sonnet-4-6", - "haiku": "claude-haiku-4-5-20251001", -}; +// Derived from models.json (single source of truth). +const MODEL_MAP = Object.fromEntries([ + ...modelsConfig.models.map(m => [m.id, m.id]), + ...Object.entries(modelsConfig.aliases), + ...Object.entries(modelsConfig.legacyAliases), +]); -const MODELS = [ - { id: "claude-opus-4-7", name: "Claude Opus 4.7" }, - { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, - { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, - { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5" }, -]; +const MODELS = modelsConfig.models.map(m => ({ id: m.id, name: m.displayName })); // ── Session management ────────────────────────────────────────────────── // Maps conversation IDs (from caller) to Claude CLI session UUIDs. diff --git a/setup.mjs b/setup.mjs index b446ac0..488bc98 100755 --- a/setup.mjs +++ b/setup.mjs @@ -39,49 +39,25 @@ const PROVIDER_NAME = opt("provider-name", "claude-local"); const BIND_ADDRESS = opt("bind", "127.0.0.1"); const AUTH_MODE_CONFIG = opt("auth-mode", "none"); -const MODEL_ID_MAP = { - opus: "claude-opus-4-6", - sonnet: "claude-sonnet-4-6", - haiku: "claude-haiku-4", -}; +// ── Models: derived from models.json (single source of truth) ────────── +const modelsConfig = JSON.parse(readFileSync(join(__dirname, "models.json"), "utf-8")); + +const MODEL_ID_MAP = modelsConfig.aliases; const DEFAULT_MODEL_ID = MODEL_ID_MAP[DEFAULT_MODEL] || MODEL_ID_MAP.opus; -// ── Models to register ────────────────────────────────────────────────── -const MODELS = [ - { - id: "claude-opus-4-6", - name: "Claude Opus 4.6 (via CLI)", - reasoning: true, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 200000, - maxTokens: 16384, - }, - { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6 (via CLI)", - reasoning: true, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 200000, - maxTokens: 16384, - }, - { - id: "claude-haiku-4", - name: "Claude Haiku 4 (via CLI)", - reasoning: false, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 200000, - maxTokens: 8192, - }, -]; +const MODELS = modelsConfig.models.map(m => ({ + id: m.id, + name: m.openclawName, + reasoning: m.reasoning, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: m.contextWindow, + maxTokens: m.maxTokens, +})); -const MODEL_ALIASES = { - [`${PROVIDER_NAME}/claude-opus-4-6`]: { alias: "Claude Opus 4.6" }, - [`${PROVIDER_NAME}/claude-sonnet-4-6`]: { alias: "Claude Sonnet 4.6" }, - [`${PROVIDER_NAME}/claude-haiku-4`]: { alias: "Claude Haiku 4" }, -}; +const MODEL_ALIASES = Object.fromEntries( + modelsConfig.models.map(m => [`${PROVIDER_NAME}/${m.id}`, { alias: m.displayName }]) +); // ── Helpers ───────────────────────────────────────────────────────────── function log(msg) { console.log(` ✓ ${msg}`); } @@ -269,7 +245,7 @@ console.log(` ║ ║ ║ Provider: ${PROVIDER_NAME.padEnd(44)}║ ║ Port: ${String(PORT).padEnd(44)}║ -║ Models: claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4║ +║ Models: ${`see models.json (${MODELS.length} available)`.padEnd(44)}║ ║ Default: ${DEFAULT_MODEL_ID.padEnd(44)}║ ║ ║ ║ Start proxy: ║