-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresponses-api-input.ts
More file actions
123 lines (119 loc) · 3.77 KB
/
Copy pathresponses-api-input.ts
File metadata and controls
123 lines (119 loc) · 3.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { Anthropic } from "@anthropic-ai/sdk"
/**
* Converts Anthropic-format messages to the OpenAI Responses API input format.
*
* Key differences from Chat Completions format:
* - Content parts use { type: "input_text" } instead of { type: "text" }
* - Images use { type: "input_image" } instead of { type: "image_url" }
* - Tool results use { type: "function_call_output", call_id } instead of { role: "tool", tool_call_id }
* - Tool uses become { type: "function_call", call_id, name, arguments } items
* - System prompt goes via the `instructions` parameter, not as a message
*
* @param messages - Array of Anthropic MessageParam objects
* @returns Array of Responses API input items
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function convertToResponsesApiInput(messages: Anthropic.Messages.MessageParam[]): any[] {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const input: any[] = []
for (const message of messages) {
if (typeof message.content === "string") {
if (message.role === "assistant") {
// Assistant messages use output_text in the Responses API format
input.push({
type: "message",
role: "assistant",
content: [{ type: "output_text", text: message.content }],
})
} else {
input.push({
role: message.role,
content: [{ type: "input_text", text: message.content }],
})
}
continue
}
if (message.role === "assistant") {
for (const part of message.content) {
switch (part.type) {
case "text":
input.push({
type: "message",
role: "assistant",
content: [{ type: "output_text", text: part.text }],
})
break
case "tool_use":
input.push({
type: "function_call",
call_id: part.id,
name: part.name,
arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input ?? {}),
})
break
case "thinking":
// Include reasoning if it has content
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((part as any).thinking && (part as any).thinking.trim().length > 0) {
input.push({
type: "message",
role: "assistant",
// eslint-disable-next-line @typescript-eslint/no-explicit-any
content: [{ type: "output_text", text: `[Thinking] ${(part as any).thinking}` }],
})
}
break
}
}
} else {
// User messages
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const contentParts: any[] = []
for (const part of message.content) {
switch (part.type) {
case "text":
contentParts.push({ type: "input_text", text: part.text })
break
case "image":
contentParts.push({
type: "input_image",
detail: "auto",
image_url: `data:${part.source.media_type};base64,${part.source.data}`,
})
break
case "tool_result": {
// Flush any pending user content before the tool result
if (contentParts.length > 0) {
input.push({ role: "user", content: [...contentParts] })
contentParts.length = 0
}
// Convert tool result content
let output: string
if (typeof part.content === "string") {
output = part.content || "(empty)"
} else if (Array.isArray(part.content)) {
output =
part.content
.filter((c): c is Anthropic.TextBlockParam => c.type === "text")
.map((c) => c.text)
.join("\n") || "(empty)"
} else {
output = "(empty)"
}
input.push({
type: "function_call_output",
call_id: part.tool_use_id,
output,
})
break
}
}
}
// Flush remaining user content
if (contentParts.length > 0) {
input.push({ role: "user", content: contentParts })
}
}
}
return input
}