mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Add support for OpenRouter and AWS Bedrock
This commit is contained in:
34
src/api/anthropic.ts
Normal file
34
src/api/anthropic.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandler } from "."
|
||||
import { ApiHandlerOptions } from "../shared/api"
|
||||
|
||||
export class AnthropicHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: Anthropic
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.client = new Anthropic({ apiKey: this.options.apiKey })
|
||||
}
|
||||
|
||||
async createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
tools: Anthropic.Messages.Tool[]
|
||||
): Promise<Anthropic.Messages.Message> {
|
||||
return await this.client.messages.create(
|
||||
{
|
||||
model: "claude-3-5-sonnet-20240620", // https://docs.anthropic.com/en/docs/about-claude/models
|
||||
max_tokens: 8192, // beta max tokens
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
tools,
|
||||
tool_choice: { type: "auto" },
|
||||
},
|
||||
{
|
||||
// https://github.com/anthropics/anthropic-sdk-typescript?tab=readme-ov-file#default-headers
|
||||
headers: { "anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15" },
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
39
src/api/bedrock.ts
Normal file
39
src/api/bedrock.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import AnthropicBedrock from "@anthropic-ai/bedrock-sdk"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandlerOptions } from "../shared/api"
|
||||
import { ApiHandler } from "."
|
||||
|
||||
// https://docs.anthropic.com/en/api/claude-on-amazon-bedrock
|
||||
export class AwsBedrockHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: AnthropicBedrock
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.client = new AnthropicBedrock({
|
||||
// Authenticate by either providing the keys below or use the default AWS credential providers, such as
|
||||
// using ~/.aws/credentials or the "AWS_SECRET_ACCESS_KEY" and "AWS_ACCESS_KEY_ID" environment variables.
|
||||
awsAccessKey: this.options.awsAccessKey,
|
||||
awsSecretKey: this.options.awsSecretKey,
|
||||
|
||||
// awsRegion changes the aws region to which the request is made. By default, we read AWS_REGION,
|
||||
// and if that's not present, we default to us-east-1. Note that we do not read ~/.aws/config for the region.
|
||||
awsRegion: this.options.awsRegion,
|
||||
})
|
||||
}
|
||||
|
||||
async createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
tools: Anthropic.Messages.Tool[]
|
||||
): Promise<Anthropic.Messages.Message> {
|
||||
return await this.client.messages.create({
|
||||
model: "anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
max_tokens: 4096,
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
tools,
|
||||
tool_choice: { type: "auto" },
|
||||
})
|
||||
}
|
||||
}
|
||||
27
src/api/index.ts
Normal file
27
src/api/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiConfiguration } from "../shared/api"
|
||||
import { AnthropicHandler } from "./anthropic"
|
||||
import { AwsBedrockHandler } from "./bedrock"
|
||||
import { OpenRouterHandler } from "./openrouter"
|
||||
|
||||
export interface ApiHandler {
|
||||
createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
tools: Anthropic.Messages.Tool[]
|
||||
): Promise<Anthropic.Messages.Message>
|
||||
}
|
||||
|
||||
export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
||||
const { apiProvider, ...options } = configuration
|
||||
switch (apiProvider) {
|
||||
case "anthropic":
|
||||
return new AnthropicHandler(options)
|
||||
case "openrouter":
|
||||
return new OpenRouterHandler(options)
|
||||
case "bedrock":
|
||||
return new AwsBedrockHandler(options)
|
||||
default:
|
||||
throw new Error(`Unknown API provider: ${apiProvider}`)
|
||||
}
|
||||
}
|
||||
140
src/api/openrouter.ts
Normal file
140
src/api/openrouter.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandlerOptions } from "../shared/api"
|
||||
import { ApiHandler } from "."
|
||||
|
||||
export class OpenRouterHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://openrouter.ai/api/v1",
|
||||
apiKey: this.options.openRouterApiKey,
|
||||
defaultHeaders: {
|
||||
"HTTP-Referer": "https://github.com/saoudrizwan/claude-dev", // Optional, for including your app on openrouter.ai rankings.
|
||||
"X-Title": "claude-dev", // Optional. Shows in rankings on openrouter.ai.
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
tools: Anthropic.Messages.Tool[]
|
||||
): Promise<Anthropic.Messages.Message> {
|
||||
// Convert Anthropic messages to OpenAI format
|
||||
const openAIMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...messages.map((msg) => {
|
||||
const baseMessage = {
|
||||
content:
|
||||
typeof msg.content === "string"
|
||||
? msg.content
|
||||
: msg.content
|
||||
.map((part) => {
|
||||
if ("text" in part) {
|
||||
return part.text
|
||||
} else if ("source" in part) {
|
||||
return { type: "image_url" as const, image_url: { url: part.source.data } }
|
||||
}
|
||||
return ""
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
}
|
||||
|
||||
if (msg.role === "user") {
|
||||
return { ...baseMessage, role: "user" as const }
|
||||
} else if (msg.role === "assistant") {
|
||||
const assistantMessage: OpenAI.Chat.ChatCompletionAssistantMessageParam = {
|
||||
...baseMessage,
|
||||
role: "assistant" as const,
|
||||
}
|
||||
if ("tool_calls" in msg && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
||||
assistantMessage.tool_calls = msg.tool_calls.map((toolCall) => ({
|
||||
id: toolCall.id,
|
||||
type: "function",
|
||||
function: {
|
||||
name: toolCall.function.name,
|
||||
arguments: JSON.stringify(toolCall.function.arguments),
|
||||
},
|
||||
}))
|
||||
}
|
||||
return assistantMessage
|
||||
}
|
||||
throw new Error(`Unsupported message role: ${msg.role}`)
|
||||
}),
|
||||
]
|
||||
|
||||
// Convert Anthropic tools to OpenAI tools
|
||||
const openAITools: OpenAI.Chat.ChatCompletionTool[] = tools.map((tool) => ({
|
||||
type: "function",
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
parameters: tool.input_schema,
|
||||
},
|
||||
}))
|
||||
|
||||
const completion = await this.client.chat.completions.create({
|
||||
model: "anthropic/claude-3.5-sonnet:beta",
|
||||
max_tokens: 4096,
|
||||
messages: openAIMessages,
|
||||
tools: openAITools,
|
||||
tool_choice: "auto",
|
||||
})
|
||||
|
||||
// Convert OpenAI response to Anthropic format
|
||||
const openAIMessage = completion.choices[0].message
|
||||
const anthropicMessage: Anthropic.Messages.Message = {
|
||||
id: completion.id,
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: openAIMessage.content || "",
|
||||
},
|
||||
],
|
||||
model: completion.model,
|
||||
stop_reason: this.mapFinishReason(completion.choices[0].finish_reason),
|
||||
stop_sequence: null,
|
||||
usage: {
|
||||
input_tokens: completion.usage?.prompt_tokens || 0,
|
||||
output_tokens: completion.usage?.completion_tokens || 0,
|
||||
},
|
||||
}
|
||||
|
||||
if (openAIMessage.tool_calls && openAIMessage.tool_calls.length > 0) {
|
||||
anthropicMessage.content.push(
|
||||
...openAIMessage.tool_calls.map((toolCall) => ({
|
||||
type: "tool_use" as const,
|
||||
id: toolCall.id,
|
||||
name: toolCall.function.name,
|
||||
input: JSON.parse(toolCall.function.arguments || "{}"),
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
return anthropicMessage
|
||||
}
|
||||
|
||||
private mapFinishReason(
|
||||
finishReason: OpenAI.Chat.ChatCompletion.Choice["finish_reason"]
|
||||
): Anthropic.Messages.Message["stop_reason"] {
|
||||
switch (finishReason) {
|
||||
case "stop":
|
||||
return "end_turn"
|
||||
case "length":
|
||||
return "max_tokens"
|
||||
case "tool_calls":
|
||||
return "tool_use"
|
||||
case "content_filter":
|
||||
return null // Anthropic doesn't have an exact equivalent
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user