mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Merge pull request #886 from RooVetGit/ark_support
Support the Ark provider when used through OpenAI compatible
This commit is contained in:
commit
dc463349e8
4 changed files with 224 additions and 3 deletions
5
.changeset/spicy-pugs-guess.md
Normal file
5
.changeset/spicy-pugs-guess.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"roo-cline": patch
|
||||
---
|
||||
|
||||
Support Volcano Ark platform through OpenAI compatible
|
||||
|
|
@ -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 },
|
||||
}
|
||||
|
|
|
|||
138
src/api/transform/__tests__/simple-format.test.ts
Normal file
138
src/api/transform/__tests__/simple-format.test.ts
Normal 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]" },
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
67
src/api/transform/simple-format.ts
Normal file
67
src/api/transform/simple-format.ts
Normal 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),
|
||||
}))
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue