Merge pull request #886 from RooVetGit/ark_support

Support the Ark provider when used through OpenAI compatible
This commit is contained in:
Matt Rubens 2025-02-09 00:43:00 -05:00 committed by GitHub
commit dc463349e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 224 additions and 3 deletions

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Support Volcano Ark platform through OpenAI compatible

View file

@ -10,6 +10,7 @@ import {
import { ApiHandler, SingleCompletionHandler } from "../index"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { convertToR1Format } from "../transform/r1-format"
import { convertToSimpleMessages } from "../transform/simple-format"
import { ApiStream } from "../transform/stream"
export class OpenAiHandler implements ApiHandler, SingleCompletionHandler {
@ -46,21 +47,31 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler {
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const modelInfo = this.getModel().info
const modelUrl = this.options.openAiBaseUrl ?? ""
const modelId = this.options.openAiModelId ?? ""
const deepseekReasoner = modelId.includes("deepseek-reasoner")
const ark = modelUrl.includes(".volces.com")
if (this.options.openAiStreamingEnabled ?? true) {
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
role: "system",
content: systemPrompt,
}
let convertedMessages
if (deepseekReasoner) {
convertedMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
} else if (ark) {
convertedMessages = [systemMessage, ...convertToSimpleMessages(messages)]
} else {
convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)]
}
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: modelId,
temperature: 0,
messages: deepseekReasoner
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
: [systemMessage, ...convertToOpenAiMessages(messages)],
messages: convertedMessages,
stream: true as const,
stream_options: { include_usage: true },
}

View file

@ -0,0 +1,138 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { convertToSimpleContent, convertToSimpleMessages } from "../simple-format"
describe("simple-format", () => {
describe("convertToSimpleContent", () => {
it("returns string content as-is", () => {
const content = "Hello world"
expect(convertToSimpleContent(content)).toBe("Hello world")
})
it("extracts text from text blocks", () => {
const content = [
{ type: "text", text: "Hello" },
{ type: "text", text: "world" },
] as Anthropic.Messages.TextBlockParam[]
expect(convertToSimpleContent(content)).toBe("Hello\nworld")
})
it("converts image blocks to descriptive text", () => {
const content = [
{ type: "text", text: "Here's an image:" },
{
type: "image",
source: {
type: "base64",
media_type: "image/png",
data: "base64data",
},
},
] as Array<Anthropic.Messages.TextBlockParam | Anthropic.Messages.ImageBlockParam>
expect(convertToSimpleContent(content)).toBe("Here's an image:\n[Image: image/png]")
})
it("converts tool use blocks to descriptive text", () => {
const content = [
{ type: "text", text: "Using a tool:" },
{
type: "tool_use",
id: "tool-1",
name: "read_file",
input: { path: "test.txt" },
},
] as Array<Anthropic.Messages.TextBlockParam | Anthropic.Messages.ToolUseBlockParam>
expect(convertToSimpleContent(content)).toBe("Using a tool:\n[Tool Use: read_file]")
})
it("handles string tool result content", () => {
const content = [
{ type: "text", text: "Tool result:" },
{
type: "tool_result",
tool_use_id: "tool-1",
content: "Result text",
},
] as Array<Anthropic.Messages.TextBlockParam | Anthropic.Messages.ToolResultBlockParam>
expect(convertToSimpleContent(content)).toBe("Tool result:\nResult text")
})
it("handles array tool result content with text and images", () => {
const content = [
{
type: "tool_result",
tool_use_id: "tool-1",
content: [
{ type: "text", text: "Result 1" },
{
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: "base64data",
},
},
{ type: "text", text: "Result 2" },
],
},
] as Anthropic.Messages.ToolResultBlockParam[]
expect(convertToSimpleContent(content)).toBe("Result 1\n[Image: image/jpeg]\nResult 2")
})
it("filters out empty strings", () => {
const content = [
{ type: "text", text: "Hello" },
{ type: "text", text: "" },
{ type: "text", text: "world" },
] as Anthropic.Messages.TextBlockParam[]
expect(convertToSimpleContent(content)).toBe("Hello\nworld")
})
})
describe("convertToSimpleMessages", () => {
it("converts messages with string content", () => {
const messages = [
{ role: "user", content: "Hello" },
{ role: "assistant", content: "Hi there" },
] as Anthropic.Messages.MessageParam[]
expect(convertToSimpleMessages(messages)).toEqual([
{ role: "user", content: "Hello" },
{ role: "assistant", content: "Hi there" },
])
})
it("converts messages with complex content", () => {
const messages = [
{
role: "user",
content: [
{ type: "text", text: "Look at this:" },
{
type: "image",
source: {
type: "base64",
media_type: "image/png",
data: "base64data",
},
},
],
},
{
role: "assistant",
content: [
{ type: "text", text: "I see the image" },
{
type: "tool_use",
id: "tool-1",
name: "analyze_image",
input: { data: "base64data" },
},
],
},
] as Anthropic.Messages.MessageParam[]
expect(convertToSimpleMessages(messages)).toEqual([
{ role: "user", content: "Look at this:\n[Image: image/png]" },
{ role: "assistant", content: "I see the image\n[Tool Use: analyze_image]" },
])
})
})
})

View file

@ -0,0 +1,67 @@
import { Anthropic } from "@anthropic-ai/sdk"
/**
* Convert complex content blocks to simple string content
*/
export function convertToSimpleContent(
content:
| string
| Array<
| Anthropic.Messages.TextBlockParam
| Anthropic.Messages.ImageBlockParam
| Anthropic.Messages.ToolUseBlockParam
| Anthropic.Messages.ToolResultBlockParam
>,
): string {
if (typeof content === "string") {
return content
}
// Extract text from content blocks
return content
.map((block) => {
if (block.type === "text") {
return block.text
}
if (block.type === "image") {
return `[Image: ${block.source.media_type}]`
}
if (block.type === "tool_use") {
return `[Tool Use: ${block.name}]`
}
if (block.type === "tool_result") {
if (typeof block.content === "string") {
return block.content
}
if (Array.isArray(block.content)) {
return block.content
.map((part) => {
if (part.type === "text") {
return part.text
}
if (part.type === "image") {
return `[Image: ${part.source.media_type}]`
}
return ""
})
.join("\n")
}
return ""
}
return ""
})
.filter(Boolean)
.join("\n")
}
/**
* Convert Anthropic messages to simple format with string content
*/
export function convertToSimpleMessages(
messages: Anthropic.Messages.MessageParam[],
): Array<{ role: "user" | "assistant"; content: string }> {
return messages.map((message) => ({
role: message.role,
content: convertToSimpleContent(message.content),
}))
}