From b7f1dd6d805b8308d1d5fa31052bf183231dd11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Fri, 17 Jul 2026 22:48:16 +0800 Subject: [PATCH] fix(server): include configured role models (#1024) --- src/server/__tests__/settings.test.ts | 34 +++++++++++++++++++++++++++ src/server/api/models.ts | 33 +++++++++++++++++++++----- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/server/__tests__/settings.test.ts b/src/server/__tests__/settings.test.ts index e7c1ec72..7474c48e 100644 --- a/src/server/__tests__/settings.test.ts +++ b/src/server/__tests__/settings.test.ts @@ -48,6 +48,7 @@ let originalAnthropicModel: string | undefined let originalAnthropicDefaultHaikuModel: string | undefined let originalAnthropicDefaultSonnetModel: string | undefined let originalAnthropicDefaultOpusModel: string | undefined +let originalAnthropicDefaultFableModel: string | undefined async function setup() { tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-test-')) @@ -66,6 +67,7 @@ async function setup() { originalAnthropicDefaultHaikuModel = process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL originalAnthropicDefaultSonnetModel = process.env.ANTHROPIC_DEFAULT_SONNET_MODEL originalAnthropicDefaultOpusModel = process.env.ANTHROPIC_DEFAULT_OPUS_MODEL + originalAnthropicDefaultFableModel = process.env.ANTHROPIC_DEFAULT_FABLE_MODEL process.env.CLAUDE_CONFIG_DIR = tmpDir process.env.HOME = tmpDir process.env.USERPROFILE = tmpDir @@ -77,6 +79,7 @@ async function setup() { delete process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL delete process.env.ANTHROPIC_DEFAULT_SONNET_MODEL delete process.env.ANTHROPIC_DEFAULT_OPUS_MODEL + delete process.env.ANTHROPIC_DEFAULT_FABLE_MODEL clearKeychainCache() primeKeychainCacheFromPrefetch(null) clearOpenAIOAuthTokenCache() @@ -162,6 +165,12 @@ async function teardown() { delete process.env.ANTHROPIC_DEFAULT_OPUS_MODEL } + if (originalAnthropicDefaultFableModel !== undefined) { + process.env.ANTHROPIC_DEFAULT_FABLE_MODEL = originalAnthropicDefaultFableModel + } else { + delete process.env.ANTHROPIC_DEFAULT_FABLE_MODEL + } + await fs.rm(tmpDir, { recursive: true, force: true }) } @@ -676,6 +685,31 @@ describe('Models API', () => { expect(ids.filter((id: string) => id === 'deepseek-v4-pro')).toHaveLength(1) }) + it('GET /api/models should merge user settings model roles with runtime env and include Fable', async () => { + await new SettingsService().updateUserSettings({ + env: { + ANTHROPIC_MODEL: 'claude-opus-4-8', + ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-haiku-4-5-20251001', + ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-6', + ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-8', + ANTHROPIC_DEFAULT_FABLE_MODEL: 'claude-fable-5', + }, + }) + process.env.ANTHROPIC_MODEL = 'claude-opus-4-8' + + const { req, url, segments } = makeRequest('GET', '/api/models') + const res = await handleModelsApi(req, url, segments) + + expect(res.status).toBe(200) + const body = await res.json() + expect(body.models.map((model: { id: string }) => model.id)).toEqual([ + 'claude-opus-4-8', + 'claude-haiku-4-5-20251001', + 'claude-sonnet-4-6', + 'claude-fable-5', + ]) + }) + it('GET /api/models/current should return default model when not set', async () => { const { req, url, segments } = makeRequest('GET', '/api/models/current') const res = await handleModelsApi(req, url, segments) diff --git a/src/server/api/models.ts b/src/server/api/models.ts index 050ebefb..026fcf70 100644 --- a/src/server/api/models.ts +++ b/src/server/api/models.ts @@ -95,6 +95,7 @@ function buildProviderModelList(models: { haiku: string sonnet: string opus: string + fable?: string }): ApiModelInfo[] { const modelList: ApiModelInfo[] = [] @@ -128,6 +129,14 @@ function buildProviderModelList(models: { context: '', } : null) + addUniqueModel(modelList, models.fable + ? { + id: models.fable, + name: models.fable, + description: 'Fable model', + context: '', + } + : null) return modelList } @@ -170,12 +179,20 @@ async function getGrokModelList(): Promise { })) } -function getEnvConfiguredAnthropicModels(): ApiModelInfo[] { +function getConfiguredAnthropicModels(settingsEnv: Record): ApiModelInfo[] { + const resolveModel = (key: string): string => { + const runtimeValue = process.env[key]?.trim() + if (runtimeValue) return runtimeValue + const settingsValue = settingsEnv[key] + return typeof settingsValue === 'string' ? settingsValue.trim() : '' + } + return buildProviderModelList({ - main: process.env.ANTHROPIC_MODEL?.trim() || '', - haiku: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL?.trim() || '', - sonnet: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL?.trim() || '', - opus: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL?.trim() || '', + main: resolveModel('ANTHROPIC_MODEL'), + haiku: resolveModel('ANTHROPIC_DEFAULT_HAIKU_MODEL'), + sonnet: resolveModel('ANTHROPIC_DEFAULT_SONNET_MODEL'), + opus: resolveModel('ANTHROPIC_DEFAULT_OPUS_MODEL'), + fable: resolveModel('ANTHROPIC_DEFAULT_FABLE_MODEL'), }) } @@ -188,7 +205,11 @@ async function getOpenAIAuthModels(): Promise { } async function getStandaloneModelList(): Promise { - const models = [...getEnvConfiguredAnthropicModels()] + const settings = await settingsService.getUserSettings() + const settingsEnv = settings.env && typeof settings.env === 'object' && !Array.isArray(settings.env) + ? settings.env as Record + : {} + const models = [...getConfiguredAnthropicModels(settingsEnv)] if (models.length === 0) { models.push(...DEFAULT_MODELS)