Skip to content

Commit 17fe348

Browse files
taltasedelauna
andcommitted
Add Fable 5 support across Anthropic providers (#555)
* Add Fable 5 support to Anthropic providers * fix(providers): address PR #555 review feedback for Fable 5 --------- Co-authored-by: Elliott de Launay <edelauna@gmail.com>
1 parent 5b8eed5 commit 17fe348

23 files changed

Lines changed: 464 additions & 13 deletions

packages/types/src/providers/anthropic.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,23 @@ export const anthropicModels = {
126126
supportsReasoningBinary: true,
127127
supportsTemperature: false,
128128
},
129+
"claude-fable-5": {
130+
maxTokens: 128_000, // Overridden to 8k if `enableReasoningEffort` is false.
131+
contextWindow: 1_000_000,
132+
supportsImages: true,
133+
supportsPromptCache: true,
134+
inputPrice: 10.0, // $10 per million input tokens
135+
outputPrice: 50.0, // $50 per million output tokens
136+
cacheWritesPrice: 12.5, // $12.50 per million tokens
137+
cacheReadsPrice: 1.0, // $1.00 per million tokens
138+
// Fable 5 uses the same adaptive-thinking / binary-toggle convention as
139+
// Opus 4.7+ on the direct Anthropic provider path.
140+
supportsReasoningBudget: true,
141+
supportsReasoningBinary: true,
142+
supportsTemperature: false,
143+
description:
144+
"Claude Fable 5 is Anthropic's most capable widely released model for the most demanding reasoning and long-horizon agentic work.",
145+
},
129146
"claude-opus-4-5-20251101": {
130147
maxTokens: 32_000, // Overridden to 8k if `enableReasoningEffort` is false.
131148
contextWindow: 200_000,

packages/types/src/providers/bedrock.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,24 @@ export const bedrockModels = {
217217
},
218218
],
219219
},
220+
"anthropic.claude-fable-5": {
221+
maxTokens: 8192,
222+
contextWindow: 1_000_000,
223+
supportsImages: true,
224+
supportsPromptCache: true,
225+
supportsReasoningBudget: true,
226+
supportsReasoningBinary: true,
227+
supportsTemperature: false,
228+
inputPrice: 10.0,
229+
outputPrice: 50.0,
230+
cacheWritesPrice: 12.5,
231+
cacheReadsPrice: 1.0,
232+
minTokensPerCachePoint: 1024,
233+
maxCachePoints: 4,
234+
cachableFields: ["system", "messages", "tools"],
235+
description:
236+
"Claude Fable 5 is Anthropic's most capable widely released model for the most demanding reasoning and long-horizon agentic work.",
237+
},
220238
"anthropic.claude-opus-4-5-20251101-v1:0": {
221239
maxTokens: 8192,
222240
contextWindow: 200_000,
@@ -588,6 +606,7 @@ export const BEDROCK_1M_CONTEXT_MODEL_IDS = [
588606
// - Claude Opus 4.5
589607
// - Claude Opus 4.6
590608
// - Claude Opus 4.7
609+
// - Claude Fable 5 (cross-region inference only — can only be used through an inference profile)
591610
export const BEDROCK_GLOBAL_INFERENCE_MODEL_IDS = [
592611
"anthropic.claude-sonnet-4-20250514-v1:0",
593612
"anthropic.claude-sonnet-4-5-20250929-v1:0",
@@ -597,6 +616,7 @@ export const BEDROCK_GLOBAL_INFERENCE_MODEL_IDS = [
597616
"anthropic.claude-opus-4-6-v1",
598617
"anthropic.claude-opus-4-7",
599618
"anthropic.claude-opus-4-8",
619+
"anthropic.claude-fable-5",
600620
] as const
601621

602622
// Amazon Bedrock Service Tier types

packages/types/src/providers/openrouter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const OPEN_ROUTER_PROMPT_CACHING_MODELS = new Set([
4343
"anthropic/claude-opus-4.1",
4444
"anthropic/claude-opus-4.5",
4545
"anthropic/claude-opus-4.6",
46+
"anthropic/claude-fable-5",
4647
"anthropic/claude-haiku-4.5",
4748
"google/gemini-2.5-flash-preview",
4849
"google/gemini-2.5-flash-preview:thinking",
@@ -74,6 +75,7 @@ export const OPEN_ROUTER_REASONING_BUDGET_MODELS = new Set([
7475
"anthropic/claude-opus-4.1",
7576
"anthropic/claude-opus-4.5",
7677
"anthropic/claude-opus-4.6",
78+
"anthropic/claude-fable-5",
7779
"anthropic/claude-sonnet-4",
7880
"anthropic/claude-sonnet-4.5",
7981
"anthropic/claude-sonnet-4.6",

packages/types/src/providers/vercel-ai-gateway.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS = new Set([
1313
"anthropic/claude-opus-4.1",
1414
"anthropic/claude-opus-4.5",
1515
"anthropic/claude-opus-4.6",
16+
"anthropic/claude-fable-5",
1617
"anthropic/claude-sonnet-4",
1718
"anthropic/claude-sonnet-4.6",
1819
"openai/gpt-4.1",
@@ -55,6 +56,7 @@ export const VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS = new Set([
5556
"anthropic/claude-opus-4.1",
5657
"anthropic/claude-opus-4.5",
5758
"anthropic/claude-opus-4.6",
59+
"anthropic/claude-fable-5",
5860
"anthropic/claude-sonnet-4",
5961
"anthropic/claude-sonnet-4.6",
6062
"google/gemini-1.5-flash",

packages/types/src/providers/vertex.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,21 @@ export const vertexModels = {
434434
},
435435
],
436436
},
437+
"claude-fable-5": {
438+
maxTokens: 8192,
439+
contextWindow: 1_000_000,
440+
supportsImages: true,
441+
supportsPromptCache: true,
442+
inputPrice: 10.0,
443+
outputPrice: 50.0,
444+
cacheWritesPrice: 12.5,
445+
cacheReadsPrice: 1.0,
446+
supportsReasoningBudget: true,
447+
supportsReasoningBinary: true,
448+
supportsTemperature: false,
449+
description:
450+
"Claude Fable 5 is Anthropic's most capable widely released model for the most demanding reasoning and long-horizon agentic work.",
451+
},
437452
"claude-opus-4-5@20251101": {
438453
maxTokens: 8192,
439454
contextWindow: 200_000,

src/api/providers/__tests__/anthropic-vertex.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,23 @@ describe("VertexHandler", () => {
945945
expect(model.betas).toContain("context-1m-2025-08-07")
946946
})
947947

948+
it("should return Claude Fable 5 model info", () => {
949+
const handler = new AnthropicVertexHandler({
950+
apiModelId: "claude-fable-5",
951+
vertexProjectId: "test-project",
952+
vertexRegion: "us-central1",
953+
})
954+
955+
const model = handler.getModel()
956+
expect(model.id).toBe("claude-fable-5")
957+
expect(model.info.maxTokens).toBe(8192)
958+
expect(model.info.contextWindow).toBe(1_000_000)
959+
expect(model.info.supportsReasoningBinary).toBe(true)
960+
expect(model.info.supportsReasoningBudget).toBe(true)
961+
expect(model.info.supportsPromptCache).toBe(true)
962+
expect(model.info.supportsTemperature).toBe(false)
963+
})
964+
948965
it("should not enable 1M context when flag is disabled", () => {
949966
const handler = new AnthropicVertexHandler({
950967
apiModelId: VERTEX_1M_CONTEXT_MODEL_IDS[0],
@@ -1192,6 +1209,35 @@ describe("VertexHandler", () => {
11921209
expect(request.thinking).not.toHaveProperty("budget_tokens")
11931210
expect(request.temperature).toBeUndefined()
11941211
})
1212+
1213+
it("should use adaptive thinking for Claude Fable 5", async () => {
1214+
const fableHandler = new AnthropicVertexHandler({
1215+
apiModelId: "claude-fable-5",
1216+
vertexProjectId: "test-project",
1217+
vertexRegion: "us-central1",
1218+
enableReasoningEffort: true,
1219+
})
1220+
1221+
const mockCreate = vitest.fn().mockImplementation(async () => ({
1222+
async *[Symbol.asyncIterator]() {
1223+
yield { type: "message_start", message: { usage: { input_tokens: 10, output_tokens: 5 } } }
1224+
},
1225+
}))
1226+
;(fableHandler["client"].messages as any).create = mockCreate
1227+
1228+
await fableHandler.createMessage("You are a helpful assistant", [{ role: "user", content: "Hello" }]).next()
1229+
1230+
expect(mockCreate).toHaveBeenCalledWith(
1231+
expect.objectContaining({
1232+
thinking: { type: "adaptive" },
1233+
}),
1234+
undefined,
1235+
)
1236+
1237+
const request = mockCreate.mock.calls[0][0]
1238+
expect(request.thinking).not.toHaveProperty("budget_tokens")
1239+
expect(request.temperature).toBeUndefined()
1240+
})
11951241
})
11961242

11971243
describe("native tool calling", () => {

src/api/providers/__tests__/anthropic.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,33 @@ describe("AnthropicHandler", () => {
399399
expect(requestBody?.thinking).toEqual({ type: "adaptive" })
400400
expect(requestBody?.max_tokens).toBe(32768)
401401
})
402+
403+
it("should use adaptive thinking for Claude Fable 5 when reasoning is enabled", async () => {
404+
const fableHandler = new AnthropicHandler({
405+
apiKey: "test-api-key",
406+
apiModelId: "claude-fable-5",
407+
enableReasoningEffort: true,
408+
modelMaxTokens: 32768,
409+
})
410+
411+
const stream = fableHandler.createMessage(systemPrompt, [
412+
{
413+
role: "user",
414+
content: [{ type: "text" as const, text: "Hello" }],
415+
},
416+
])
417+
418+
for await (const _chunk of stream) {
419+
// Consume stream
420+
}
421+
422+
const requestBody = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[0]
423+
const requestOptions = mockCreate.mock.calls[mockCreate.mock.calls.length - 1]?.[1]
424+
expect(requestBody?.thinking).toEqual({ type: "adaptive" })
425+
expect(requestBody?.temperature).toBeUndefined()
426+
expect(requestBody?.max_tokens).toBe(32768)
427+
expect(requestOptions?.headers?.["anthropic-beta"]).toContain("prompt-caching-2024-07-31")
428+
})
402429
})
403430

404431
describe("completePrompt", () => {
@@ -543,6 +570,23 @@ describe("AnthropicHandler", () => {
543570
expect(model.reasoningBudget).toBeUndefined()
544571
})
545572

573+
it("should handle Claude Fable 5 model correctly", () => {
574+
const handler = new AnthropicHandler({
575+
apiKey: "test-api-key",
576+
apiModelId: "claude-fable-5",
577+
})
578+
const model = handler.getModel()
579+
expect(model.id).toBe("claude-fable-5")
580+
expect(model.info.maxTokens).toBe(128000)
581+
expect(model.info.contextWindow).toBe(1000000)
582+
expect(model.maxTokens).toBe(8192)
583+
expect(model.info.supportsReasoningBinary).toBe(true)
584+
expect(model.info.supportsReasoningBudget).toBe(true)
585+
expect(model.info.supportsPromptCache).toBe(true)
586+
expect(model.info.supportsTemperature).toBe(false)
587+
expect(model.reasoningBudget).toBeUndefined()
588+
})
589+
546590
it("should enable 1M context for Claude 4.5 Sonnet when beta flag is set", () => {
547591
const handler = new AnthropicHandler({
548592
apiKey: "test-api-key",

src/api/providers/__tests__/bedrock.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,37 @@ describe("AwsBedrockHandler", () => {
691691
expect(typeof model.info.supportsImages).toBe("boolean")
692692
expect(typeof model.info.supportsPromptCache).toBe("boolean")
693693
})
694+
695+
it("should return Claude Fable 5 model info", () => {
696+
const handler = new AwsBedrockHandler({
697+
apiModelId: "anthropic.claude-fable-5",
698+
awsAccessKey: "test",
699+
awsSecretKey: "test",
700+
awsRegion: "us-east-1",
701+
})
702+
703+
const model = handler.getModel()
704+
expect(model.id).toBe("anthropic.claude-fable-5")
705+
expect(model.info.contextWindow).toBe(1_000_000)
706+
expect(model.info.supportsReasoningBinary).toBe(true)
707+
expect(model.info.supportsReasoningBudget).toBe(true)
708+
expect(model.info.supportsPromptCache).toBe(true)
709+
expect(model.info.supportsTemperature).toBe(false)
710+
expect(model.maxTokens).toBe(8192)
711+
})
712+
713+
it("should apply global inference prefix for Claude Fable 5 when awsUseGlobalInference is true", () => {
714+
const handler = new AwsBedrockHandler({
715+
apiModelId: "anthropic.claude-fable-5",
716+
awsAccessKey: "test",
717+
awsSecretKey: "test",
718+
awsRegion: "us-east-1",
719+
awsUseGlobalInference: true,
720+
})
721+
722+
const model = handler.getModel()
723+
expect(model.id).toBe("global.anthropic.claude-fable-5")
724+
})
694725
})
695726

696727
describe("1M context beta feature", () => {
@@ -1524,13 +1555,15 @@ describe("AwsBedrockHandler", () => {
15241555
it("returns true for all adaptive-thinking model patterns (opus/sonnet 4.7 and 4.8)", () => {
15251556
expect(isAdaptiveThinkingModel("anthropic.claude-opus-4-7")).toBe(true)
15261557
expect(isAdaptiveThinkingModel("anthropic.claude-opus-4-8")).toBe(true)
1558+
expect(isAdaptiveThinkingModel("anthropic.claude-fable-5")).toBe(true)
15271559
// Future-proof Sonnet patterns — guarded even before a registry entry exists.
15281560
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-4-7")).toBe(true)
15291561
expect(isAdaptiveThinkingModel("anthropic.claude-sonnet-4-8")).toBe(true)
15301562
})
15311563

15321564
it("returns true when the id carries a cross-region or global prefix", () => {
15331565
expect(isAdaptiveThinkingModel("us.anthropic.claude-opus-4-8")).toBe(true)
1566+
expect(isAdaptiveThinkingModel("global.anthropic.claude-fable-5")).toBe(true)
15341567
expect(isAdaptiveThinkingModel("eu.anthropic.claude-sonnet-4-7")).toBe(true)
15351568
expect(isAdaptiveThinkingModel("global.anthropic.claude-opus-4-8")).toBe(true)
15361569
})

src/api/providers/__tests__/requesty.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ vitest.mock("../fetchers/modelCache", () => ({
3838
cacheReadsPrice: 0.3,
3939
description: "Claude 4 Sonnet",
4040
},
41+
"anthropic/claude-fable-5": {
42+
maxTokens: 128000,
43+
contextWindow: 1000000,
44+
supportsImages: true,
45+
supportsPromptCache: true,
46+
supportsReasoningBudget: true,
47+
supportsReasoningBinary: true,
48+
supportsTemperature: false,
49+
inputPrice: 10,
50+
outputPrice: 50,
51+
cacheWritesPrice: 12.5,
52+
cacheReadsPrice: 1,
53+
description: "Claude Fable 5",
54+
},
4155
})
4256
}),
4357
}))
@@ -193,6 +207,39 @@ describe("RequestyHandler", () => {
193207
)
194208
})
195209

210+
it("uses adaptive thinking for Claude Fable 5 when reasoning is enabled", async () => {
211+
const handler = new RequestyHandler({
212+
requestyApiKey: "test-key",
213+
requestyModelId: "anthropic/claude-fable-5",
214+
enableReasoningEffort: true,
215+
modelMaxTokens: 32768,
216+
})
217+
218+
const mockStream = {
219+
async *[Symbol.asyncIterator]() {
220+
yield {
221+
id: "test-id",
222+
choices: [{ delta: {} }],
223+
usage: { prompt_tokens: 10, completion_tokens: 20 },
224+
}
225+
},
226+
}
227+
228+
mockCreate.mockResolvedValue(mockStream)
229+
230+
const generator = handler.createMessage("test system prompt", [{ role: "user" as const, content: "test" }])
231+
await generator.next()
232+
233+
expect(mockCreate).toHaveBeenCalledWith(
234+
expect.objectContaining({
235+
model: "anthropic/claude-fable-5",
236+
max_tokens: 32768,
237+
thinking: { type: "adaptive" },
238+
temperature: undefined,
239+
}),
240+
)
241+
})
242+
196243
it("handles API errors", async () => {
197244
const handler = new RequestyHandler(mockOptions)
198245
const mockError = new Error("API Error")
@@ -367,6 +414,23 @@ describe("RequestyHandler", () => {
367414
})
368415
})
369416

417+
it("omits temperature for Claude Fable 5 in completePrompt", async () => {
418+
const handler = new RequestyHandler({
419+
requestyApiKey: "test-key",
420+
requestyModelId: "anthropic/claude-fable-5",
421+
})
422+
mockCreate.mockResolvedValue({ choices: [{ message: { content: "test completion" } }] })
423+
424+
await handler.completePrompt("test prompt")
425+
426+
expect(mockCreate).toHaveBeenCalledWith({
427+
model: "anthropic/claude-fable-5",
428+
max_tokens: 8192,
429+
messages: [{ role: "system", content: "test prompt" }],
430+
temperature: undefined,
431+
})
432+
})
433+
370434
it("handles API errors", async () => {
371435
const handler = new RequestyHandler(mockOptions)
372436
const mockError = new Error("API Error")

0 commit comments

Comments
 (0)