diff --git a/src/config/index.mjs b/src/config/index.mjs index abd174a67..5941704c7 100644 --- a/src/config/index.mjs +++ b/src/config/index.mjs @@ -91,6 +91,8 @@ export const customApiModelKeys = ['customModel'] export const ollamaApiModelKeys = ['ollamaModel'] export const azureOpenAiApiModelKeys = ['azureOpenAi'] export const claudeApiModelKeys = [ + 'claudeFable51Api', + 'claudeFable5Api', 'claudeOpus41Api', 'claudeOpus45Api', 'claudeOpus46Api', @@ -338,6 +340,14 @@ export const Models = { chatgptApi4_1_nano: { value: 'gpt-4.1-nano', desc: 'OpenAI (GPT-4.1 nano)' }, claude2WebFree: { value: '', desc: 'Claude.ai (Web)' }, + claudeFable51Api: { + value: 'claude-fable-5-1', + desc: 'Anthropic (Claude Fable 5.1)', + }, + claudeFable5Api: { + value: 'claude-fable-5', + desc: 'Anthropic (Claude Fable 5)', + }, claudeOpus41Api: { value: 'claude-opus-4-1-20250805', desc: 'Anthropic (Claude Opus 4.1)', @@ -751,6 +761,7 @@ export const defaultApiModeIds = [ 'chatgptApi5_6Luna', 'xaiGrok4_6', 'xaiGrok4_5', + 'claudeFable51Api', 'claudeOpus5Api', 'claudeSonnet5Api', 'claudeHaiku45Api', diff --git a/src/services/apis/temperature-params.mjs b/src/services/apis/temperature-params.mjs index fb9693e7e..69d7f96e7 100644 --- a/src/services/apis/temperature-params.mjs +++ b/src/services/apis/temperature-params.mjs @@ -3,6 +3,8 @@ const MODELS_WITHOUT_CUSTOM_TEMPERATURE = new Set([ 'claude-opus-4-8', 'claude-sonnet-5', 'claude-opus-5', + 'claude-fable-5', + 'claude-fable-5-1', ]) function normalizeModelId(model) { diff --git a/tests/unit/services/apis/claude-fable-api.test.mjs b/tests/unit/services/apis/claude-fable-api.test.mjs new file mode 100644 index 000000000..1daedfd61 --- /dev/null +++ b/tests/unit/services/apis/claude-fable-api.test.mjs @@ -0,0 +1,59 @@ +import assert from 'node:assert/strict' +import { beforeEach, test } from 'node:test' +import { generateAnswersWithClaudeApi } from '../../../../src/services/apis/claude-api.mjs' +import { createFakePort } from '../../helpers/port.mjs' +import { createMockSseResponse } from '../../helpers/sse-response.mjs' + +const setStorage = (values) => { + globalThis.__TEST_BROWSER_SHIM__.replaceStorage(values) +} + +beforeEach(() => { + globalThis.__TEST_BROWSER_SHIM__.clearStorage() +}) + +test('claude-api: supports Fable models with adaptive thinking defaults', async (t) => { + t.mock.method(console, 'debug', () => {}) + + for (const [modelName, model] of [ + ['claudeFable5Api', 'claude-fable-5'], + ['claudeFable51Api', 'claude-fable-5-1'], + ]) { + await t.test(modelName, async (t) => { + setStorage({ + customClaudeApiUrl: 'https://api.anthropic.com', + claudeApiKey: 'sk-ant-test', + maxConversationContextLength: 3, + maxResponseTokenLength: 1024, + temperatureOverrideEnabled: true, + temperature: 0.9, + }) + + const session = { + modelName, + conversationRecords: [], + isRetry: false, + } + const port = createFakePort() + + let capturedInit + t.mock.method(globalThis, 'fetch', async (_input, init) => { + capturedInit = init + return createMockSseResponse([ + 'data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"OK"}}\n\n', + 'data: {"type":"message_delta","delta":{"stop_reason":"end_turn"}}\n\n', + 'data: {"type":"message_stop"}\n\n', + ]) + }) + + await generateAnswersWithClaudeApi(port, 'Q', session) + + const body = JSON.parse(capturedInit.body) + assert.equal(body.model, model) + assert.equal(body.max_tokens, 1024) + assert.equal(body.stream, true) + assert.equal(Object.hasOwn(body, 'temperature'), false) + assert.equal(Object.hasOwn(body, 'thinking'), false) + }) + } +}) diff --git a/tests/unit/services/apis/temperature-params.test.mjs b/tests/unit/services/apis/temperature-params.test.mjs index 9358611e8..444552a61 100644 --- a/tests/unit/services/apis/temperature-params.test.mjs +++ b/tests/unit/services/apis/temperature-params.test.mjs @@ -35,6 +35,9 @@ test('temperature overrides omit known Anthropic models across provider ID forma 'claude-opus-4-8-20260801', 'claude-sonnet-5', 'claude-opus-5', + 'claude-fable-5', + 'claude-fable-5-1', + 'anthropic/claude-fable-5.1', ]) { assert.equal(canApplyTemperatureOverride(model), false, model) assert.deepEqual(