diff options
Diffstat (limited to 'packages/core')
| -rw-r--r-- | packages/core/src/credentials/claude.ts | 17 | ||||
| -rw-r--r-- | packages/core/src/credentials/index.ts | 1 | ||||
| -rw-r--r-- | packages/core/tests/credentials/wake-probe.test.ts | 26 |
3 files changed, 43 insertions, 1 deletions
diff --git a/packages/core/src/credentials/claude.ts b/packages/core/src/credentials/claude.ts index 432e403..7818222 100644 --- a/packages/core/src/credentials/claude.ts +++ b/packages/core/src/credentials/claude.ts @@ -483,6 +483,23 @@ export const ANTHROPIC_MODELS_FALLBACK = [ "claude-3-opus-20240229", ]; +/** + * Pick the model to use for a Claude "wake" probe from a list of model ids. + * + * The probe only needs a small/cheap model to register activity against the + * subscription, so we target Haiku. Model ids change over time (the old + * hardcoded `claude-3-5-haiku-20241022` started returning HTTP 404), so the + * caller fetches the live list from `/v1/models` and we resolve by substring. + * + * Selection: the FIRST id whose name contains "haiku" (case-insensitive). + * Anthropic's `/v1/models` returns models newest-first, so first-match + * naturally prefers the newest Haiku. Returns `null` when nothing matches so + * the caller can surface a clear error instead of probing an invalid model. + */ +export function selectHaikuModel(models: string[]): string | null { + return models.find((id) => id.toLowerCase().includes("haiku")) ?? null; +} + // ─── Credential Validation ──────────────────────────────────── export interface ClaudeProfile { diff --git a/packages/core/src/credentials/index.ts b/packages/core/src/credentials/index.ts index 46fa5b6..5221dc6 100644 --- a/packages/core/src/credentials/index.ts +++ b/packages/core/src/credentials/index.ts @@ -24,6 +24,7 @@ export { refreshAccountCredentials, refreshAccountCredentialsAsync, SYSTEM_IDENTITY, + selectHaikuModel, validateAccountCredentials, } from "./claude.js"; export { diff --git a/packages/core/tests/credentials/wake-probe.test.ts b/packages/core/tests/credentials/wake-probe.test.ts index 253efec..a97a00c 100644 --- a/packages/core/tests/credentials/wake-probe.test.ts +++ b/packages/core/tests/credentials/wake-probe.test.ts @@ -9,7 +9,7 @@ vi.mock("../../src/db/index.js", () => ({ }), })); -const { buildWakeProbeBody } = await import("../../src/credentials/claude.js"); +const { buildWakeProbeBody, selectHaikuModel } = await import("../../src/credentials/claude.js"); const IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude."; @@ -47,3 +47,27 @@ describe("buildWakeProbeBody", () => { expect(a).toEqual(b); }); }); +describe("selectHaikuModel", () => { + it("returns the id whose name contains 'haiku'", () => { + const models = ["claude-sonnet-4-20250514", "claude-haiku-4-5-20251001"]; + expect(selectHaikuModel(models)).toBe("claude-haiku-4-5-20251001"); + }); + + it("matches case-insensitively", () => { + expect(selectHaikuModel(["Claude-HAIKU-Latest"])).toBe("Claude-HAIKU-Latest"); + }); + + it("returns the FIRST match when several models contain 'haiku'", () => { + // `/v1/models` returns newest-first, so first-match prefers the newest. + const models = ["claude-haiku-4-5-20251001", "claude-3-5-haiku-20241022"]; + expect(selectHaikuModel(models)).toBe("claude-haiku-4-5-20251001"); + }); + + it("returns null when no model contains 'haiku'", () => { + expect(selectHaikuModel(["claude-sonnet-4-20250514", "claude-opus-4-20250514"])).toBeNull(); + }); + + it("returns null for an empty list", () => { + expect(selectHaikuModel([])).toBeNull(); + }); +}); |
