From f3591c1c4c987385d5d11820faa435c676b3c783 Mon Sep 17 00:00:00 2001 From: huangweiwen Date: Wed, 30 Jul 2025 16:04:30 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E6=B7=BB=E5=8A=A0anthropic=20passthrough?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants.ts | 4 ++++ src/index.ts | 3 +++ src/utils/index.ts | 4 ++++ src/utils/router.ts | 24 ++++++++++++++++++++++++ 4 files changed, 35 insertions(+) diff --git a/src/constants.ts b/src/constants.ts index 5b7f3bf0..ebe9a603 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -17,4 +17,8 @@ export const DEFAULT_CONFIG = { OPENAI_API_KEY: "", OPENAI_BASE_URL: "", OPENAI_MODEL: "", + Providers: [], + Router: { + default: "" + } }; diff --git a/src/index.ts b/src/index.ts index c52ac1a2..4e77e96d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { initConfig, initDir } from "./utils"; import { createServer } from "./server"; import { router } from "./utils/router"; import { apiKeyAuth } from "./middleware/auth"; +import { anthropicPassthrough } from "./middleware/anthropicPassthrough"; import { cleanupPidFile, isServiceRunning, @@ -97,6 +98,8 @@ async function run(options: RunOptions = {}) { server.addHook("preHandler", async (req, reply) => router(req, reply, config) ); + server.addHook("preHandler", anthropicPassthrough); + server.start(); } diff --git a/src/utils/index.ts b/src/utils/index.ts index ba0c3f14..6f827688 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -62,10 +62,14 @@ export const readConfigFile = async () => { const APIKEY = await question("Enter Provider API KEY: "); const baseUrl = await question("Enter Provider URL: "); const model = await question("Enter MODEL Name: "); + const providerType = await question("Enter Provider Type (openai/anthropic) [openai]: "); + const type = providerType.toLowerCase() === "anthropic" ? "anthropic" : "openai"; + const config = Object.assign({}, DEFAULT_CONFIG, { Providers: [ { name, + type, api_base_url: baseUrl, api_key: APIKEY, models: [model], diff --git a/src/utils/router.ts b/src/utils/router.ts index 87cca285..08a158c7 100644 --- a/src/utils/router.ts +++ b/src/utils/router.ts @@ -97,6 +97,19 @@ const getUseModel = async (req: any, tokenCount: number, config: any) => { return config.Router!.default; }; +const findProviderByModel = (modelString: string, config: any) => { + if (!modelString || !modelString.includes(",")) { + return null; + } + const [providerName] = modelString.split(","); + const provider = config.Providers?.find((p: any) => p.name === providerName); + if (provider) { + // Default to 'openai' type if not specified for backward compatibility + provider.type = provider.type || 'openai'; + } + return provider || null; +}; + export const router = async (req: any, _res: any, config: any) => { const { messages, system = [], tools }: MessageCreateParamsBase = req.body; try { @@ -118,6 +131,17 @@ export const router = async (req: any, _res: any, config: any) => { if (!model) { model = await getUseModel(req, tokenCount, config); } + + // Check if the selected model belongs to an anthropic provider + const provider = findProviderByModel(model, config); + if (provider && provider.type === "anthropic") { + log("Using anthropic provider for direct passthrough:", provider.name); + req.rawProvider = provider; + req.selectedModel = model.split(",")[1]; // Extract model name + // Don't modify req.body.model for anthropic providers + return; + } + req.body.model = model; } catch (error: any) { log("Error in router middleware:", error.message); From fe868447665a542c484381c2680b7efc3a5ba88f Mon Sep 17 00:00:00 2001 From: huangweiwen Date: Wed, 30 Jul 2025 16:05:01 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E6=B7=BB=E5=8A=A0anthorpic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/middleware/anthropicPassthrough.ts | 130 +++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/middleware/anthropicPassthrough.ts diff --git a/src/middleware/anthropicPassthrough.ts b/src/middleware/anthropicPassthrough.ts new file mode 100644 index 00000000..46a83945 --- /dev/null +++ b/src/middleware/anthropicPassthrough.ts @@ -0,0 +1,130 @@ +import { FastifyRequest, FastifyReply } from "fastify"; +import { log } from "../utils/log"; + +interface AnthropicRequest extends FastifyRequest { + rawProvider?: { + type: string; + name: string; + api_base_url: string; + api_key: string; + }; + selectedModel?: string; +} + +export const anthropicPassthrough = async (req: AnthropicRequest, reply: FastifyReply) => { + if (req.rawProvider && req.rawProvider.type === "anthropic") { + log("Forwarding to anthropic provider:", req.rawProvider.name); + + try { + // Smart URL construction - avoid duplicate /v1/messages + let baseUrl = req.rawProvider.api_base_url; + let requestPath = req.url; + + // If base URL already ends with /v1/messages, don't append the request path + if (baseUrl.endsWith('/v1/messages') && requestPath.startsWith('/v1/messages')) { + requestPath = ''; + } + + const targetUrl = `${baseUrl}${requestPath}`; + log("Target URL:", targetUrl); + + // Prepare headers for anthropic API + const headers: Record = { + "Content-Type": "application/json", + "anthropic-version": "2023-06-01", + "x-api-key": req.rawProvider.api_key, + }; + + // Forward user agent if present + if (req.headers["user-agent"]) { + headers["user-agent"] = req.headers["user-agent"]; + } + + // Override model in request body with selected model + const requestBody = { + ...(req.body as Record), + model: req.selectedModel + }; + + log("Request Body:", requestBody); + + const response = await fetch(targetUrl, { + method: req.method, + headers, + body: JSON.stringify(requestBody), + }); + + // Check if response is streaming (SSE) + const contentType = response.headers.get('content-type'); + const isStreaming = contentType?.includes('text/event-stream') || + contentType?.includes('text/plain') || + (requestBody as any).stream === true; + + log("Response Content-Type:", contentType); + log("Is Streaming:", isStreaming); + + // Forward all response headers except problematic ones for proxying + const responseHeaders: Record = {}; + response.headers.forEach((value, key) => { + const lowerKey = key.toLowerCase(); + // Only filter out headers that can cause proxying issues + if (!['transfer-encoding'].includes(lowerKey)) { + responseHeaders[key] = value; + } + }); + + reply.status(response.status); + + if (isStreaming && response.body) { + // Handle streaming response + log("Handling streaming response"); + + // Set response headers (keep original headers, especially content-type) + Object.entries(responseHeaders).forEach(([key, value]) => { + reply.header(key, value); + }); + + const reader = response.body.getReader(); + + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + log("Stream completed"); + break; + } + + // Direct binary transfer - no need to decode/encode + log("Streaming chunk size:", value.byteLength, "bytes"); + + reply.raw.write(value); + } + } catch (streamError: any) { + log("Stream error:", streamError.message); + } finally { + reader.releaseLock(); + reply.raw.end(); + } + } else { + // Handle non-streaming response + log("Handling non-streaming response"); + + // Set response headers + Object.entries(responseHeaders).forEach(([key, value]) => { + reply.header(key, value); + }); + + const responseData = await response.text(); + log("Response Data:", responseData); + reply.send(responseData); + } + + return reply; + } catch (error: any) { + log("Error forwarding to anthropic provider:", error.message); + reply.status(500).send({ error: "Failed to forward request to anthropic provider" }); + return reply; + } + } +}; \ No newline at end of file From 723774dbce33d1742f079dd3222ad12f5db401ab Mon Sep 17 00:00:00 2001 From: huangweiwen Date: Wed, 30 Jul 2025 16:10:06 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 34 ++++++++++++++++++++++++++++++++++ README_zh.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/README.md b/README.md index 71bb8569..32e0f313 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,40 @@ - **GitHub Actions Integration**: Trigger Claude Code tasks in your GitHub workflows. - **Plugin System**: Extend functionality with custom transformers. +## ✨ Anthropic Passthrough Support + +The router now supports two backend formats: +- **OpenAI Format**: Converts Anthropic requests to OpenAI-compatible format (default) +- **Anthropic Format**: Directly passes through original Anthropic requests (new feature) + +### Configuration + +Add `type` field to Provider configurations: +```json +{ + "Providers": [ + { + "name": "openai", + "type": "openai", + "api_base_url": "https://api.openai.com/v1", + "api_key": "your-openai-api-key", + "models": ["gpt-4o", "gpt-4o-mini"] + }, + { + "name": "anthropic-official", + "type": "anthropic", + "api_base_url": "https://api.anthropic.com", + "api_key": "your-anthropic-api-key", + "models": ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"] + } + ] +} +``` + +**API URL Configuration:** +- **Basic URL (recommended)**: `"https://api.anthropic.com"` → auto-adds `/v1/messages` +- **Full URL**: `"https://api.example.com/v1/messages"` → uses as-is + ## 🚀 Getting Started ### 1. Installation diff --git a/README_zh.md b/README_zh.md index 17113146..16374ac0 100644 --- a/README_zh.md +++ b/README_zh.md @@ -12,6 +12,43 @@ - **动态模型切换**: 在 Claude Code 中使用 `/model` 命令动态切换模型。 - **GitHub Actions 集成**: 在您的 GitHub 工作流程中触发 Claude Code 任务。 - **插件系统**: 使用自定义转换器扩展功能。 +- **Anthropic 直通**: 支持原始 Anthropic API 格式的直接透传,提供完整的流式体验。 + +## ✨ Anthropic 直通支持 + +路由器现在支持两种后端格式: +- **OpenAI 格式**: 将 Anthropic 请求转换为 OpenAI 兼容格式(默认) +- **Anthropic 格式**: 直接透传原始 Anthropic 请求到后端(新功能) + + +### 配置方法 + +在 Provider 配置中添加 `type` 字段: + +```json +{ + "Providers": [ + { + "name": "openai", + "type": "openai", + "api_base_url": "https://api.openai.com/v1", + "api_key": "your-openai-api-key", + "models": ["gpt-4o", "gpt-4o-mini"] + }, + { + "name": "anthropic-official", + "type": "anthropic", + "api_base_url": "https://api.anthropic.com", + "api_key": "your-anthropic-api-key", + "models": ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"] + } + ] +} +``` + +**API URL 配置:** +- **基础 URL(推荐)**: `"https://api.anthropic.com"` → 自动添加 `/v1/messages` +- **完整 URL**: `"https://api.example.com/v1/messages"` → 直接使用 ## 🚀 快速入门 From f07a9de9219bba01b94ae4d22a3b4013ea944d68 Mon Sep 17 00:00:00 2001 From: huangweiwen Date: Wed, 30 Jul 2025 23:23:36 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E4=BB=A5=E6=94=AF=E6=8C=81=20Anthropic=20=E7=9B=B4?= =?UTF-8?q?=E9=80=9A=E6=A8=A1=E5=BC=8F=EF=BC=8C=E7=A7=BB=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E4=BD=BF=E7=94=A8=E7=9A=84=E4=B8=AD=E9=97=B4=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E4=BC=98=E5=8C=96=E9=85=8D=E7=BD=AE=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 ++- README_zh.md | 11 ++- src/index.ts | 2 - src/middleware/anthropicPassthrough.ts | 130 ------------------------- src/utils/index.ts | 3 - src/utils/router.ts | 22 ----- 6 files changed, 13 insertions(+), 166 deletions(-) delete mode 100644 src/middleware/anthropicPassthrough.ts diff --git a/README.md b/README.md index 32e0f313..215ade07 100644 --- a/README.md +++ b/README.md @@ -23,23 +23,25 @@ The router now supports two backend formats: ### Configuration -Add `type` field to Provider configurations: +Use `anthropicpassthrough` transformer to enable Anthropic passthrough mode: + ```json { "Providers": [ { "name": "openai", - "type": "openai", "api_base_url": "https://api.openai.com/v1", "api_key": "your-openai-api-key", "models": ["gpt-4o", "gpt-4o-mini"] }, { "name": "anthropic-official", - "type": "anthropic", "api_base_url": "https://api.anthropic.com", "api_key": "your-anthropic-api-key", - "models": ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"] + "models": ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"], + "transformer": { + "use": ["anthropicpassthrough"] + } } ] } @@ -267,6 +269,7 @@ Transformers allow you to modify the request and response payloads to ensure com **Available Built-in Transformers:** +- `anthropicpassthrough`: Enables direct passthrough to Anthropic API without format conversion. - `deepseek`: Adapts requests/responses for DeepSeek API. - `gemini`: Adapts requests/responses for Gemini API. - `openrouter`: Adapts requests/responses for OpenRouter API. diff --git a/README_zh.md b/README_zh.md index 16374ac0..1f0b983c 100644 --- a/README_zh.md +++ b/README_zh.md @@ -20,27 +20,27 @@ - **OpenAI 格式**: 将 Anthropic 请求转换为 OpenAI 兼容格式(默认) - **Anthropic 格式**: 直接透传原始 Anthropic 请求到后端(新功能) - ### 配置方法 -在 Provider 配置中添加 `type` 字段: +使用 `anthropicpassthrough` transformer 来启用 Anthropic 直通模式: ```json { "Providers": [ { "name": "openai", - "type": "openai", "api_base_url": "https://api.openai.com/v1", "api_key": "your-openai-api-key", "models": ["gpt-4o", "gpt-4o-mini"] }, { "name": "anthropic-official", - "type": "anthropic", "api_base_url": "https://api.anthropic.com", "api_key": "your-anthropic-api-key", - "models": ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"] + "models": ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"], + "transformer": { + "use": ["anthropicpassthrough"] + } } ] } @@ -265,6 +265,7 @@ Transformers 允许您修改请求和响应负载,以确保与不同提供商 **可用的内置 Transformer:** +- `anthropicpassthrough`: 启用 Anthropic API 直通模式,不进行格式转换。 - `deepseek`: 适配 DeepSeek API 的请求/响应。 - `gemini`: 适配 Gemini API 的请求/响应。 - `openrouter`: 适配 OpenRouter API 的请求/响应。 diff --git a/src/index.ts b/src/index.ts index 4e77e96d..4c00e3cc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,6 @@ import { initConfig, initDir } from "./utils"; import { createServer } from "./server"; import { router } from "./utils/router"; import { apiKeyAuth } from "./middleware/auth"; -import { anthropicPassthrough } from "./middleware/anthropicPassthrough"; import { cleanupPidFile, isServiceRunning, @@ -98,7 +97,6 @@ async function run(options: RunOptions = {}) { server.addHook("preHandler", async (req, reply) => router(req, reply, config) ); - server.addHook("preHandler", anthropicPassthrough); server.start(); } diff --git a/src/middleware/anthropicPassthrough.ts b/src/middleware/anthropicPassthrough.ts deleted file mode 100644 index 46a83945..00000000 --- a/src/middleware/anthropicPassthrough.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { FastifyRequest, FastifyReply } from "fastify"; -import { log } from "../utils/log"; - -interface AnthropicRequest extends FastifyRequest { - rawProvider?: { - type: string; - name: string; - api_base_url: string; - api_key: string; - }; - selectedModel?: string; -} - -export const anthropicPassthrough = async (req: AnthropicRequest, reply: FastifyReply) => { - if (req.rawProvider && req.rawProvider.type === "anthropic") { - log("Forwarding to anthropic provider:", req.rawProvider.name); - - try { - // Smart URL construction - avoid duplicate /v1/messages - let baseUrl = req.rawProvider.api_base_url; - let requestPath = req.url; - - // If base URL already ends with /v1/messages, don't append the request path - if (baseUrl.endsWith('/v1/messages') && requestPath.startsWith('/v1/messages')) { - requestPath = ''; - } - - const targetUrl = `${baseUrl}${requestPath}`; - log("Target URL:", targetUrl); - - // Prepare headers for anthropic API - const headers: Record = { - "Content-Type": "application/json", - "anthropic-version": "2023-06-01", - "x-api-key": req.rawProvider.api_key, - }; - - // Forward user agent if present - if (req.headers["user-agent"]) { - headers["user-agent"] = req.headers["user-agent"]; - } - - // Override model in request body with selected model - const requestBody = { - ...(req.body as Record), - model: req.selectedModel - }; - - log("Request Body:", requestBody); - - const response = await fetch(targetUrl, { - method: req.method, - headers, - body: JSON.stringify(requestBody), - }); - - // Check if response is streaming (SSE) - const contentType = response.headers.get('content-type'); - const isStreaming = contentType?.includes('text/event-stream') || - contentType?.includes('text/plain') || - (requestBody as any).stream === true; - - log("Response Content-Type:", contentType); - log("Is Streaming:", isStreaming); - - // Forward all response headers except problematic ones for proxying - const responseHeaders: Record = {}; - response.headers.forEach((value, key) => { - const lowerKey = key.toLowerCase(); - // Only filter out headers that can cause proxying issues - if (!['transfer-encoding'].includes(lowerKey)) { - responseHeaders[key] = value; - } - }); - - reply.status(response.status); - - if (isStreaming && response.body) { - // Handle streaming response - log("Handling streaming response"); - - // Set response headers (keep original headers, especially content-type) - Object.entries(responseHeaders).forEach(([key, value]) => { - reply.header(key, value); - }); - - const reader = response.body.getReader(); - - try { - while (true) { - const { done, value } = await reader.read(); - - if (done) { - log("Stream completed"); - break; - } - - // Direct binary transfer - no need to decode/encode - log("Streaming chunk size:", value.byteLength, "bytes"); - - reply.raw.write(value); - } - } catch (streamError: any) { - log("Stream error:", streamError.message); - } finally { - reader.releaseLock(); - reply.raw.end(); - } - } else { - // Handle non-streaming response - log("Handling non-streaming response"); - - // Set response headers - Object.entries(responseHeaders).forEach(([key, value]) => { - reply.header(key, value); - }); - - const responseData = await response.text(); - log("Response Data:", responseData); - reply.send(responseData); - } - - return reply; - } catch (error: any) { - log("Error forwarding to anthropic provider:", error.message); - reply.status(500).send({ error: "Failed to forward request to anthropic provider" }); - return reply; - } - } -}; \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 6f827688..5723cf20 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -62,14 +62,11 @@ export const readConfigFile = async () => { const APIKEY = await question("Enter Provider API KEY: "); const baseUrl = await question("Enter Provider URL: "); const model = await question("Enter MODEL Name: "); - const providerType = await question("Enter Provider Type (openai/anthropic) [openai]: "); - const type = providerType.toLowerCase() === "anthropic" ? "anthropic" : "openai"; const config = Object.assign({}, DEFAULT_CONFIG, { Providers: [ { name, - type, api_base_url: baseUrl, api_key: APIKEY, models: [model], diff --git a/src/utils/router.ts b/src/utils/router.ts index 08a158c7..e20a0da6 100644 --- a/src/utils/router.ts +++ b/src/utils/router.ts @@ -97,18 +97,6 @@ const getUseModel = async (req: any, tokenCount: number, config: any) => { return config.Router!.default; }; -const findProviderByModel = (modelString: string, config: any) => { - if (!modelString || !modelString.includes(",")) { - return null; - } - const [providerName] = modelString.split(","); - const provider = config.Providers?.find((p: any) => p.name === providerName); - if (provider) { - // Default to 'openai' type if not specified for backward compatibility - provider.type = provider.type || 'openai'; - } - return provider || null; -}; export const router = async (req: any, _res: any, config: any) => { const { messages, system = [], tools }: MessageCreateParamsBase = req.body; @@ -132,16 +120,6 @@ export const router = async (req: any, _res: any, config: any) => { model = await getUseModel(req, tokenCount, config); } - // Check if the selected model belongs to an anthropic provider - const provider = findProviderByModel(model, config); - if (provider && provider.type === "anthropic") { - log("Using anthropic provider for direct passthrough:", provider.name); - req.rawProvider = provider; - req.selectedModel = model.split(",")[1]; // Extract model name - // Don't modify req.body.model for anthropic providers - return; - } - req.body.model = model; } catch (error: any) { log("Error in router middleware:", error.message); From 85f19ae47b7e6983e665e0148c69a2e737bed0bd Mon Sep 17 00:00:00 2001 From: huangweiwen Date: Thu, 31 Jul 2025 10:55:06 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20README=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E4=B8=AD=E7=9A=84=20`anthropicpassthrough`=20?= =?UTF-8?q?=E6=8B=BC=E5=86=99=E9=94=99=E8=AF=AF=EF=BC=8C=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E4=B8=BA=20`anthropicPassthrough`=EF=BC=8C=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=A4=BA=E4=BE=8B=E7=9A=84=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- README_zh.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 215ade07..b9d34f38 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The router now supports two backend formats: ### Configuration -Use `anthropicpassthrough` transformer to enable Anthropic passthrough mode: +Use `anthropicPassthrough` transformer to enable Anthropic passthrough mode: ```json { @@ -40,7 +40,7 @@ Use `anthropicpassthrough` transformer to enable Anthropic passthrough mode: "api_key": "your-anthropic-api-key", "models": ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"], "transformer": { - "use": ["anthropicpassthrough"] + "use": ["anthropicPassthrough"] } } ] diff --git a/README_zh.md b/README_zh.md index 1f0b983c..6bfd9817 100644 --- a/README_zh.md +++ b/README_zh.md @@ -22,7 +22,7 @@ ### 配置方法 -使用 `anthropicpassthrough` transformer 来启用 Anthropic 直通模式: +使用 `anthropicPassthrough` transformer 来启用 Anthropic 直通模式: ```json { @@ -39,7 +39,7 @@ "api_key": "your-anthropic-api-key", "models": ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"], "transformer": { - "use": ["anthropicpassthrough"] + "use": ["anthropicPassthrough"] } } ]