Skip to content

Commit dc46334

Browse files
authored
Merge pull request #886 from RooVetGit/ark_support
Support the Ark provider when used through OpenAI compatible
2 parents ff7b3e0 + 2ddf54d commit dc46334

File tree

4 files changed

+224
-3
lines changed

4 files changed

+224
-3
lines changed

.changeset/spicy-pugs-guess.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Support Volcano Ark platform through OpenAI compatible

src/api/providers/openai.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { ApiHandler, SingleCompletionHandler } from "../index"
1111
import { convertToOpenAiMessages } from "../transform/openai-format"
1212
import { convertToR1Format } from "../transform/r1-format"
13+
import { convertToSimpleMessages } from "../transform/simple-format"
1314
import { ApiStream } from "../transform/stream"
1415

1516
export class OpenAiHandler implements ApiHandler, SingleCompletionHandler {
@@ -46,21 +47,31 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler {
4647

4748
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
4849
const modelInfo = this.getModel().info
50+
const modelUrl = this.options.openAiBaseUrl ?? ""
4951
const modelId = this.options.openAiModelId ?? ""
5052

5153
const deepseekReasoner = modelId.includes("deepseek-reasoner")
54+
const ark = modelUrl.includes(".volces.com")
5255

5356
if (this.options.openAiStreamingEnabled ?? true) {
5457
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
5558
role: "system",
5659
content: systemPrompt,
5760
}
61+
62+
let convertedMessages
63+
if (deepseekReasoner) {
64+
convertedMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
65+
} else if (ark) {
66+
convertedMessages = [systemMessage, ...convertToSimpleMessages(messages)]
67+
} else {
68+
convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)]
69+
}
70+
5871
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
5972
model: modelId,
6073
temperature: 0,
61-
messages: deepseekReasoner
62-
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
63-
: [systemMessage, ...convertToOpenAiMessages(messages)],
74+
messages: convertedMessages,
6475
stream: true as const,
6576
stream_options: { include_usage: true },
6677
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { Anthropic } from "@anthropic-ai/sdk"
2+
import { convertToSimpleContent, convertToSimpleMessages } from "../simple-format"
3+
4+
describe("simple-format", () => {
5+
describe("convertToSimpleContent", () => {
6+
it("returns string content as-is", () => {
7+
const content = "Hello world"
8+
expect(convertToSimpleContent(content)).toBe("Hello world")
9+
})
10+
11+
it("extracts text from text blocks", () => {
12+
const content = [
13+
{ type: "text", text: "Hello" },
14+
{ type: "text", text: "world" },
15+
] as Anthropic.Messages.TextBlockParam[]
16+
expect(convertToSimpleContent(content)).toBe("Hello\nworld")
17+
})
18+
19+
it("converts image blocks to descriptive text", () => {
20+
const content = [
21+
{ type: "text", text: "Here's an image:" },
22+
{
23+
type: "image",
24+
source: {
25+
type: "base64",
26+
media_type: "image/png",
27+
data: "base64data",
28+
},
29+
},
30+
] as Array<Anthropic.Messages.TextBlockParam | Anthropic.Messages.ImageBlockParam>
31+
expect(convertToSimpleContent(content)).toBe("Here's an image:\n[Image: image/png]")
32+
})
33+
34+
it("converts tool use blocks to descriptive text", () => {
35+
const content = [
36+
{ type: "text", text: "Using a tool:" },
37+
{
38+
type: "tool_use",
39+
id: "tool-1",
40+
name: "read_file",
41+
input: { path: "test.txt" },
42+
},
43+
] as Array<Anthropic.Messages.TextBlockParam | Anthropic.Messages.ToolUseBlockParam>
44+
expect(convertToSimpleContent(content)).toBe("Using a tool:\n[Tool Use: read_file]")
45+
})
46+
47+
it("handles string tool result content", () => {
48+
const content = [
49+
{ type: "text", text: "Tool result:" },
50+
{
51+
type: "tool_result",
52+
tool_use_id: "tool-1",
53+
content: "Result text",
54+
},
55+
] as Array<Anthropic.Messages.TextBlockParam | Anthropic.Messages.ToolResultBlockParam>
56+
expect(convertToSimpleContent(content)).toBe("Tool result:\nResult text")
57+
})
58+
59+
it("handles array tool result content with text and images", () => {
60+
const content = [
61+
{
62+
type: "tool_result",
63+
tool_use_id: "tool-1",
64+
content: [
65+
{ type: "text", text: "Result 1" },
66+
{
67+
type: "image",
68+
source: {
69+
type: "base64",
70+
media_type: "image/jpeg",
71+
data: "base64data",
72+
},
73+
},
74+
{ type: "text", text: "Result 2" },
75+
],
76+
},
77+
] as Anthropic.Messages.ToolResultBlockParam[]
78+
expect(convertToSimpleContent(content)).toBe("Result 1\n[Image: image/jpeg]\nResult 2")
79+
})
80+
81+
it("filters out empty strings", () => {
82+
const content = [
83+
{ type: "text", text: "Hello" },
84+
{ type: "text", text: "" },
85+
{ type: "text", text: "world" },
86+
] as Anthropic.Messages.TextBlockParam[]
87+
expect(convertToSimpleContent(content)).toBe("Hello\nworld")
88+
})
89+
})
90+
91+
describe("convertToSimpleMessages", () => {
92+
it("converts messages with string content", () => {
93+
const messages = [
94+
{ role: "user", content: "Hello" },
95+
{ role: "assistant", content: "Hi there" },
96+
] as Anthropic.Messages.MessageParam[]
97+
expect(convertToSimpleMessages(messages)).toEqual([
98+
{ role: "user", content: "Hello" },
99+
{ role: "assistant", content: "Hi there" },
100+
])
101+
})
102+
103+
it("converts messages with complex content", () => {
104+
const messages = [
105+
{
106+
role: "user",
107+
content: [
108+
{ type: "text", text: "Look at this:" },
109+
{
110+
type: "image",
111+
source: {
112+
type: "base64",
113+
media_type: "image/png",
114+
data: "base64data",
115+
},
116+
},
117+
],
118+
},
119+
{
120+
role: "assistant",
121+
content: [
122+
{ type: "text", text: "I see the image" },
123+
{
124+
type: "tool_use",
125+
id: "tool-1",
126+
name: "analyze_image",
127+
input: { data: "base64data" },
128+
},
129+
],
130+
},
131+
] as Anthropic.Messages.MessageParam[]
132+
expect(convertToSimpleMessages(messages)).toEqual([
133+
{ role: "user", content: "Look at this:\n[Image: image/png]" },
134+
{ role: "assistant", content: "I see the image\n[Tool Use: analyze_image]" },
135+
])
136+
})
137+
})
138+
})

src/api/transform/simple-format.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Anthropic } from "@anthropic-ai/sdk"
2+
3+
/**
4+
* Convert complex content blocks to simple string content
5+
*/
6+
export function convertToSimpleContent(
7+
content:
8+
| string
9+
| Array<
10+
| Anthropic.Messages.TextBlockParam
11+
| Anthropic.Messages.ImageBlockParam
12+
| Anthropic.Messages.ToolUseBlockParam
13+
| Anthropic.Messages.ToolResultBlockParam
14+
>,
15+
): string {
16+
if (typeof content === "string") {
17+
return content
18+
}
19+
20+
// Extract text from content blocks
21+
return content
22+
.map((block) => {
23+
if (block.type === "text") {
24+
return block.text
25+
}
26+
if (block.type === "image") {
27+
return `[Image: ${block.source.media_type}]`
28+
}
29+
if (block.type === "tool_use") {
30+
return `[Tool Use: ${block.name}]`
31+
}
32+
if (block.type === "tool_result") {
33+
if (typeof block.content === "string") {
34+
return block.content
35+
}
36+
if (Array.isArray(block.content)) {
37+
return block.content
38+
.map((part) => {
39+
if (part.type === "text") {
40+
return part.text
41+
}
42+
if (part.type === "image") {
43+
return `[Image: ${part.source.media_type}]`
44+
}
45+
return ""
46+
})
47+
.join("\n")
48+
}
49+
return ""
50+
}
51+
return ""
52+
})
53+
.filter(Boolean)
54+
.join("\n")
55+
}
56+
57+
/**
58+
* Convert Anthropic messages to simple format with string content
59+
*/
60+
export function convertToSimpleMessages(
61+
messages: Anthropic.Messages.MessageParam[],
62+
): Array<{ role: "user" | "assistant"; content: string }> {
63+
return messages.map((message) => ({
64+
role: message.role,
65+
content: convertToSimpleContent(message.content),
66+
}))
67+
}

0 commit comments

Comments
 (0)