diff --git a/CLAUDE.md b/CLAUDE.md index 5f806ee4..4fa2daa3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,13 +6,15 @@ ## Quick Start ```bash -npm install # Install deps (auto-generates .env from .env.example) -npm run dev # Dev server at http://localhost:20128 -npm run build # Production build (Next.js 16 standalone) -npm run lint # ESLint (0 errors expected; warnings are pre-existing) -npm run typecheck:core # TypeScript check (should be clean) -npm run test:coverage # Unit tests + coverage gate (60% min) -npm run check # lint + test combined +npm install # Install deps (auto-generates .env from .env.example) +npm run dev # Dev server at http://localhost:20128 +npm run build # Production build (Next.js 16 standalone) +npm run lint # ESLint (0 errors expected; warnings are pre-existing) +npm run typecheck:core # TypeScript check (should be clean) +npm run typecheck:noimplicit:core # Strict check (no implicit any) +npm run test:coverage # Unit tests + coverage gate (60% min) +npm run check # lint + test combined +npm run check:cycles # Detect circular dependencies ``` ### Running a Single Test @@ -172,6 +174,8 @@ Client → /v1/chat/completions (Next.js route) **PR rule**: If you change production code in `src/`, `open-sse/`, `electron/`, or `bin/`, you must include or update tests in the same PR. +**Test layer preference**: unit first → integration (multi-module or DB state) → e2e (UI/workflow only). Encode bug reproductions as automated tests before or alongside the fix. + --- ## Git Workflow diff --git a/Dockerfile b/Dockerfile index b36f4f13..f23fded2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ RUN apt-get update \ COPY package*.json ./ COPY scripts/postinstall.mjs ./scripts/postinstall.mjs +COPY scripts/postinstallSupport.mjs ./scripts/postinstallSupport.mjs COPY scripts/native-binary-compat.mjs ./scripts/native-binary-compat.mjs COPY scripts/postinstallSupport.mjs ./scripts/postinstallSupport.mjs RUN if [ -f package-lock.json ]; then npm ci --no-audit --no-fund; else npm install --no-audit --no-fund; fi diff --git a/open-sse/config/providerRegistry.ts b/open-sse/config/providerRegistry.ts index bfddee68..b48b3dc9 100644 --- a/open-sse/config/providerRegistry.ts +++ b/open-sse/config/providerRegistry.ts @@ -2149,3 +2149,22 @@ export function getProviderCategory(provider: string): "oauth" | "apikey" { if (!entry) return "apikey"; // Safe default for unknown providers return entry.authType === "apikey" ? "apikey" : "oauth"; } + +/** + * Derive the latest opus/sonnet/haiku model IDs from the `claude` registry entry. + * Picks the first model whose ID matches each family pattern — registry order + * determines precedence, so newer models should be listed first. + */ +export function getClaudeCodeDefaultModels(): { + opus: string; + sonnet: string; + haiku: string; +} { + const models = REGISTRY.claude?.models ?? []; + const find = (pattern: RegExp) => models.find((m) => pattern.test(m.id))?.id ?? ""; + return { + opus: find(/opus/i), + sonnet: find(/sonnet/i), + haiku: find(/haiku/i), + }; +} diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.tsx b/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.tsx index b572e171..80feaca5 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.tsx +++ b/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.tsx @@ -148,7 +148,7 @@ export default function ClaudeToolCard({ } tool.defaultModels.forEach((model) => { - const targetModel = modelMappings[model.alias]; + const targetModel = modelMappings[model.alias] || model.defaultValue || ""; if (targetModel && model.envKey) env[model.envKey] = targetModel; }); diff --git a/src/shared/constants/cliTools.ts b/src/shared/constants/cliTools.ts index 058446e2..d0b4f352 100644 --- a/src/shared/constants/cliTools.ts +++ b/src/shared/constants/cliTools.ts @@ -1,4 +1,8 @@ // CLI Tools configuration +import { getClaudeCodeDefaultModels } from "@omniroute/open-sse/config/providerRegistry"; + +const _cc = getClaudeCodeDefaultModels(); + export const CLI_TOOLS = { claude: { id: "claude", @@ -19,26 +23,42 @@ export const CLI_TOOLS = { settingsFile: "~/.claude/settings.json", defaultCommand: "claude", defaultModels: [ + { + id: "model", + name: "Default Model", + alias: "model", + envKey: "ANTHROPIC_MODEL", + defaultValue: _cc.sonnet ? `cc/${_cc.sonnet}` : "cc/claude-sonnet-4-5-20250929", + isTopLevel: true, + }, + { + id: "smallFast", + name: "Small Fast Model", + alias: "smallFast", + envKey: "ANTHROPIC_SMALL_FAST_MODEL", + defaultValue: _cc.haiku ? `cc/${_cc.haiku}` : "cc/claude-haiku-4-5-20251001", + isTopLevel: true, + }, { id: "opus", name: "Claude Opus", alias: "opus", envKey: "ANTHROPIC_DEFAULT_OPUS_MODEL", - defaultValue: "cc/claude-opus-4-5-20251101", + defaultValue: _cc.opus ? `cc/${_cc.opus}` : "cc/claude-opus-4-5-20251101", }, { id: "sonnet", name: "Claude Sonnet", alias: "sonnet", envKey: "ANTHROPIC_DEFAULT_SONNET_MODEL", - defaultValue: "cc/claude-sonnet-4-5-20250929", + defaultValue: _cc.sonnet ? `cc/${_cc.sonnet}` : "cc/claude-sonnet-4-5-20250929", }, { id: "haiku", name: "Claude Haiku", alias: "haiku", envKey: "ANTHROPIC_DEFAULT_HAIKU_MODEL", - defaultValue: "cc/claude-haiku-4-5-20251001", + defaultValue: _cc.haiku ? `cc/${_cc.haiku}` : "cc/claude-haiku-4-5-20251001", }, ], }, diff --git a/tests/unit/claude-cli-defaults.test.ts b/tests/unit/claude-cli-defaults.test.ts new file mode 100644 index 00000000..552b69b7 --- /dev/null +++ b/tests/unit/claude-cli-defaults.test.ts @@ -0,0 +1,23 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { getClaudeCodeDefaultModels } from "../../open-sse/config/providerRegistry.ts"; + +test("getClaudeCodeDefaultModels returns expected default models", () => { + const models = getClaudeCodeDefaultModels(); + + // They should be non-empty strings because providerRegistry is populated statically + assert.ok(typeof models.opus === "string"); + assert.ok(typeof models.sonnet === "string"); + assert.ok(typeof models.haiku === "string"); + + // Check that the returned IDs match the expected patterns + if (models.opus) { + assert.match(models.opus, /opus/i); + } + if (models.sonnet) { + assert.match(models.sonnet, /sonnet/i); + } + if (models.haiku) { + assert.match(models.haiku, /haiku/i); + } +});