@mariozechner/pi-ai 0.5.27 → 0.5.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +355 -275
- package/dist/generate.d.ts +22 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +204 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -12
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +10 -71
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +3056 -2659
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +3063 -2663
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +17 -59
- package/dist/models.js.map +1 -1
- package/dist/providers/anthropic.d.ts +5 -18
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +254 -227
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/google.d.ts +3 -14
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +215 -220
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/openai-completions.d.ts +4 -14
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +247 -215
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-responses.d.ts +6 -13
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +242 -244
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/utils.d.ts +2 -14
- package/dist/providers/utils.d.ts.map +1 -1
- package/dist/providers/utils.js +2 -15
- package/dist/providers/utils.js.map +1 -1
- package/dist/types.d.ts +39 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,60 +1,16 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { QueuedGenerateStream } from "../generate.js";
|
|
2
3
|
import { calculateCost } from "../models.js";
|
|
3
4
|
import { transformMessages } from "./utils.js";
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
isOAuthToken = false;
|
|
8
|
-
constructor(model, apiKey) {
|
|
9
|
-
if (!apiKey) {
|
|
10
|
-
if (!process.env.ANTHROPIC_API_KEY) {
|
|
11
|
-
throw new Error("Anthropic API key is required. Set ANTHROPIC_API_KEY environment variable or pass it as an argument.");
|
|
12
|
-
}
|
|
13
|
-
apiKey = process.env.ANTHROPIC_API_KEY;
|
|
14
|
-
}
|
|
15
|
-
if (apiKey.includes("sk-ant-oat")) {
|
|
16
|
-
const defaultHeaders = {
|
|
17
|
-
accept: "application/json",
|
|
18
|
-
"anthropic-dangerous-direct-browser-access": "true",
|
|
19
|
-
"anthropic-beta": "oauth-2025-04-20,fine-grained-tool-streaming-2025-05-14",
|
|
20
|
-
};
|
|
21
|
-
// Clear the env var if we're in Node.js to prevent SDK from using it
|
|
22
|
-
if (typeof process !== "undefined" && process.env) {
|
|
23
|
-
process.env.ANTHROPIC_API_KEY = undefined;
|
|
24
|
-
}
|
|
25
|
-
this.client = new Anthropic({
|
|
26
|
-
apiKey: null,
|
|
27
|
-
authToken: apiKey,
|
|
28
|
-
baseURL: model.baseUrl,
|
|
29
|
-
defaultHeaders,
|
|
30
|
-
dangerouslyAllowBrowser: true,
|
|
31
|
-
});
|
|
32
|
-
this.isOAuthToken = true;
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
const defaultHeaders = {
|
|
36
|
-
accept: "application/json",
|
|
37
|
-
"anthropic-dangerous-direct-browser-access": "true",
|
|
38
|
-
"anthropic-beta": "fine-grained-tool-streaming-2025-05-14",
|
|
39
|
-
};
|
|
40
|
-
this.client = new Anthropic({ apiKey, baseURL: model.baseUrl, dangerouslyAllowBrowser: true, defaultHeaders });
|
|
41
|
-
this.isOAuthToken = false;
|
|
42
|
-
}
|
|
43
|
-
this.modelInfo = model;
|
|
44
|
-
}
|
|
45
|
-
getModel() {
|
|
46
|
-
return this.modelInfo;
|
|
47
|
-
}
|
|
48
|
-
getApi() {
|
|
49
|
-
return "anthropic-messages";
|
|
50
|
-
}
|
|
51
|
-
async generate(context, options) {
|
|
5
|
+
export const streamAnthropic = (model, context, options) => {
|
|
6
|
+
const stream = new QueuedGenerateStream();
|
|
7
|
+
(async () => {
|
|
52
8
|
const output = {
|
|
53
9
|
role: "assistant",
|
|
54
10
|
content: [],
|
|
55
|
-
api:
|
|
56
|
-
provider:
|
|
57
|
-
model:
|
|
11
|
+
api: "anthropic-messages",
|
|
12
|
+
provider: model.provider,
|
|
13
|
+
model: model.id,
|
|
58
14
|
usage: {
|
|
59
15
|
input: 0,
|
|
60
16
|
output: 0,
|
|
@@ -65,67 +21,12 @@ export class AnthropicLLM {
|
|
|
65
21
|
stopReason: "stop",
|
|
66
22
|
};
|
|
67
23
|
try {
|
|
68
|
-
const
|
|
69
|
-
const params =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
max_tokens: options?.maxTokens || 4096,
|
|
73
|
-
stream: true,
|
|
74
|
-
};
|
|
75
|
-
// For OAuth tokens, we MUST include Claude Code identity
|
|
76
|
-
if (this.isOAuthToken) {
|
|
77
|
-
params.system = [
|
|
78
|
-
{
|
|
79
|
-
type: "text",
|
|
80
|
-
text: "You are Claude Code, Anthropic's official CLI for Claude.",
|
|
81
|
-
cache_control: {
|
|
82
|
-
type: "ephemeral",
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
];
|
|
86
|
-
if (context.systemPrompt) {
|
|
87
|
-
params.system.push({
|
|
88
|
-
type: "text",
|
|
89
|
-
text: context.systemPrompt,
|
|
90
|
-
cache_control: {
|
|
91
|
-
type: "ephemeral",
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
else if (context.systemPrompt) {
|
|
97
|
-
params.system = context.systemPrompt;
|
|
98
|
-
}
|
|
99
|
-
if (options?.temperature !== undefined) {
|
|
100
|
-
params.temperature = options?.temperature;
|
|
101
|
-
}
|
|
102
|
-
if (context.tools) {
|
|
103
|
-
params.tools = this.convertTools(context.tools);
|
|
104
|
-
}
|
|
105
|
-
// Only enable thinking if the model supports it
|
|
106
|
-
if (options?.thinking?.enabled && this.modelInfo.reasoning) {
|
|
107
|
-
params.thinking = {
|
|
108
|
-
type: "enabled",
|
|
109
|
-
budget_tokens: options.thinking.budgetTokens || 1024,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
if (options?.toolChoice) {
|
|
113
|
-
if (typeof options.toolChoice === "string") {
|
|
114
|
-
params.tool_choice = { type: options.toolChoice };
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
params.tool_choice = options.toolChoice;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
const stream = this.client.messages.stream({
|
|
121
|
-
...params,
|
|
122
|
-
stream: true,
|
|
123
|
-
}, {
|
|
124
|
-
signal: options?.signal,
|
|
125
|
-
});
|
|
126
|
-
options?.onEvent?.({ type: "start", model: this.modelInfo.id, provider: this.modelInfo.provider });
|
|
24
|
+
const { client, isOAuthToken } = createClient(model, options?.apiKey);
|
|
25
|
+
const params = buildParams(model, context, isOAuthToken, options);
|
|
26
|
+
const anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: options?.signal });
|
|
27
|
+
stream.push({ type: "start", partial: output });
|
|
127
28
|
let currentBlock = null;
|
|
128
|
-
for await (const event of
|
|
29
|
+
for await (const event of anthropicStream) {
|
|
129
30
|
if (event.type === "content_block_start") {
|
|
130
31
|
if (event.content_block.type === "text") {
|
|
131
32
|
currentBlock = {
|
|
@@ -133,7 +34,7 @@ export class AnthropicLLM {
|
|
|
133
34
|
text: "",
|
|
134
35
|
};
|
|
135
36
|
output.content.push(currentBlock);
|
|
136
|
-
|
|
37
|
+
stream.push({ type: "text_start", partial: output });
|
|
137
38
|
}
|
|
138
39
|
else if (event.content_block.type === "thinking") {
|
|
139
40
|
currentBlock = {
|
|
@@ -142,10 +43,10 @@ export class AnthropicLLM {
|
|
|
142
43
|
thinkingSignature: "",
|
|
143
44
|
};
|
|
144
45
|
output.content.push(currentBlock);
|
|
145
|
-
|
|
46
|
+
stream.push({ type: "thinking_start", partial: output });
|
|
146
47
|
}
|
|
147
48
|
else if (event.content_block.type === "tool_use") {
|
|
148
|
-
// We wait for the full tool use to be streamed
|
|
49
|
+
// We wait for the full tool use to be streamed
|
|
149
50
|
currentBlock = {
|
|
150
51
|
type: "toolCall",
|
|
151
52
|
id: event.content_block.id,
|
|
@@ -159,16 +60,20 @@ export class AnthropicLLM {
|
|
|
159
60
|
if (event.delta.type === "text_delta") {
|
|
160
61
|
if (currentBlock && currentBlock.type === "text") {
|
|
161
62
|
currentBlock.text += event.delta.text;
|
|
162
|
-
|
|
63
|
+
stream.push({
|
|
64
|
+
type: "text_delta",
|
|
65
|
+
delta: event.delta.text,
|
|
66
|
+
partial: output,
|
|
67
|
+
});
|
|
163
68
|
}
|
|
164
69
|
}
|
|
165
70
|
else if (event.delta.type === "thinking_delta") {
|
|
166
71
|
if (currentBlock && currentBlock.type === "thinking") {
|
|
167
72
|
currentBlock.thinking += event.delta.thinking;
|
|
168
|
-
|
|
73
|
+
stream.push({
|
|
169
74
|
type: "thinking_delta",
|
|
170
|
-
content: currentBlock.thinking,
|
|
171
75
|
delta: event.delta.thinking,
|
|
76
|
+
partial: output,
|
|
172
77
|
});
|
|
173
78
|
}
|
|
174
79
|
}
|
|
@@ -187,10 +92,18 @@ export class AnthropicLLM {
|
|
|
187
92
|
else if (event.type === "content_block_stop") {
|
|
188
93
|
if (currentBlock) {
|
|
189
94
|
if (currentBlock.type === "text") {
|
|
190
|
-
|
|
95
|
+
stream.push({
|
|
96
|
+
type: "text_end",
|
|
97
|
+
content: currentBlock.text,
|
|
98
|
+
partial: output,
|
|
99
|
+
});
|
|
191
100
|
}
|
|
192
101
|
else if (currentBlock.type === "thinking") {
|
|
193
|
-
|
|
102
|
+
stream.push({
|
|
103
|
+
type: "thinking_end",
|
|
104
|
+
content: currentBlock.thinking,
|
|
105
|
+
partial: output,
|
|
106
|
+
});
|
|
194
107
|
}
|
|
195
108
|
else if (currentBlock.type === "toolCall") {
|
|
196
109
|
const finalToolCall = {
|
|
@@ -200,150 +113,264 @@ export class AnthropicLLM {
|
|
|
200
113
|
arguments: JSON.parse(currentBlock.partialJson),
|
|
201
114
|
};
|
|
202
115
|
output.content.push(finalToolCall);
|
|
203
|
-
|
|
116
|
+
stream.push({
|
|
117
|
+
type: "toolCall",
|
|
118
|
+
toolCall: finalToolCall,
|
|
119
|
+
partial: output,
|
|
120
|
+
});
|
|
204
121
|
}
|
|
205
122
|
currentBlock = null;
|
|
206
123
|
}
|
|
207
124
|
}
|
|
208
125
|
else if (event.type === "message_delta") {
|
|
209
126
|
if (event.delta.stop_reason) {
|
|
210
|
-
output.stopReason =
|
|
127
|
+
output.stopReason = mapStopReason(event.delta.stop_reason);
|
|
211
128
|
}
|
|
212
129
|
output.usage.input += event.usage.input_tokens || 0;
|
|
213
130
|
output.usage.output += event.usage.output_tokens || 0;
|
|
214
131
|
output.usage.cacheRead += event.usage.cache_read_input_tokens || 0;
|
|
215
132
|
output.usage.cacheWrite += event.usage.cache_creation_input_tokens || 0;
|
|
216
|
-
calculateCost(
|
|
133
|
+
calculateCost(model, output.usage);
|
|
217
134
|
}
|
|
218
135
|
}
|
|
219
|
-
options?.
|
|
220
|
-
|
|
136
|
+
if (options?.signal?.aborted) {
|
|
137
|
+
throw new Error("Request was aborted");
|
|
138
|
+
}
|
|
139
|
+
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
140
|
+
stream.end();
|
|
221
141
|
}
|
|
222
142
|
catch (error) {
|
|
223
143
|
output.stopReason = "error";
|
|
224
144
|
output.error = error instanceof Error ? error.message : JSON.stringify(error);
|
|
225
|
-
|
|
226
|
-
|
|
145
|
+
stream.push({ type: "error", error: output.error, partial: output });
|
|
146
|
+
stream.end();
|
|
147
|
+
}
|
|
148
|
+
})();
|
|
149
|
+
return stream;
|
|
150
|
+
};
|
|
151
|
+
function createClient(model, apiKey) {
|
|
152
|
+
if (apiKey.includes("sk-ant-oat")) {
|
|
153
|
+
const defaultHeaders = {
|
|
154
|
+
accept: "application/json",
|
|
155
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
156
|
+
"anthropic-beta": "oauth-2025-04-20,fine-grained-tool-streaming-2025-05-14",
|
|
157
|
+
};
|
|
158
|
+
// Clear the env var if we're in Node.js to prevent SDK from using it
|
|
159
|
+
if (typeof process !== "undefined" && process.env) {
|
|
160
|
+
process.env.ANTHROPIC_API_KEY = undefined;
|
|
161
|
+
}
|
|
162
|
+
const client = new Anthropic({
|
|
163
|
+
apiKey: null,
|
|
164
|
+
authToken: apiKey,
|
|
165
|
+
baseURL: model.baseUrl,
|
|
166
|
+
defaultHeaders,
|
|
167
|
+
dangerouslyAllowBrowser: true,
|
|
168
|
+
});
|
|
169
|
+
return { client, isOAuthToken: true };
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const defaultHeaders = {
|
|
173
|
+
accept: "application/json",
|
|
174
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
175
|
+
"anthropic-beta": "fine-grained-tool-streaming-2025-05-14",
|
|
176
|
+
};
|
|
177
|
+
const client = new Anthropic({
|
|
178
|
+
apiKey,
|
|
179
|
+
baseURL: model.baseUrl,
|
|
180
|
+
dangerouslyAllowBrowser: true,
|
|
181
|
+
defaultHeaders,
|
|
182
|
+
});
|
|
183
|
+
return { client, isOAuthToken: false };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function buildParams(model, context, isOAuthToken, options) {
|
|
187
|
+
const params = {
|
|
188
|
+
model: model.id,
|
|
189
|
+
messages: convertMessages(context.messages, model),
|
|
190
|
+
max_tokens: options?.maxTokens || model.maxTokens,
|
|
191
|
+
stream: true,
|
|
192
|
+
};
|
|
193
|
+
// For OAuth tokens, we MUST include Claude Code identity
|
|
194
|
+
if (isOAuthToken) {
|
|
195
|
+
params.system = [
|
|
196
|
+
{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: "You are Claude Code, Anthropic's official CLI for Claude.",
|
|
199
|
+
cache_control: {
|
|
200
|
+
type: "ephemeral",
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
if (context.systemPrompt) {
|
|
205
|
+
params.system.push({
|
|
206
|
+
type: "text",
|
|
207
|
+
text: context.systemPrompt,
|
|
208
|
+
cache_control: {
|
|
209
|
+
type: "ephemeral",
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else if (context.systemPrompt) {
|
|
215
|
+
params.system = context.systemPrompt;
|
|
216
|
+
}
|
|
217
|
+
if (options?.temperature !== undefined) {
|
|
218
|
+
params.temperature = options.temperature;
|
|
219
|
+
}
|
|
220
|
+
if (context.tools) {
|
|
221
|
+
params.tools = convertTools(context.tools);
|
|
222
|
+
}
|
|
223
|
+
if (options?.thinkingEnabled && model.reasoning) {
|
|
224
|
+
params.thinking = {
|
|
225
|
+
type: "enabled",
|
|
226
|
+
budget_tokens: options.thinkingBudgetTokens || 1024,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (options?.toolChoice) {
|
|
230
|
+
if (typeof options.toolChoice === "string") {
|
|
231
|
+
params.tool_choice = { type: options.toolChoice };
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
params.tool_choice = options.toolChoice;
|
|
227
235
|
}
|
|
228
236
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
+
return params;
|
|
238
|
+
}
|
|
239
|
+
// Sanitize tool call IDs to match Anthropic's required pattern: ^[a-zA-Z0-9_-]+$
|
|
240
|
+
function sanitizeToolCallId(id) {
|
|
241
|
+
// Replace any character that isn't alphanumeric, underscore, or hyphen with underscore
|
|
242
|
+
return id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
243
|
+
}
|
|
244
|
+
function convertMessages(messages, model) {
|
|
245
|
+
const params = [];
|
|
246
|
+
// Transform messages for cross-provider compatibility
|
|
247
|
+
const transformedMessages = transformMessages(messages, model);
|
|
248
|
+
for (const msg of transformedMessages) {
|
|
249
|
+
if (msg.role === "user") {
|
|
250
|
+
if (typeof msg.content === "string") {
|
|
251
|
+
if (msg.content.trim().length > 0) {
|
|
237
252
|
params.push({
|
|
238
253
|
role: "user",
|
|
239
254
|
content: msg.content,
|
|
240
255
|
});
|
|
241
256
|
}
|
|
242
|
-
else {
|
|
243
|
-
// Convert array content to Anthropic format
|
|
244
|
-
const blocks = msg.content.map((item) => {
|
|
245
|
-
if (item.type === "text") {
|
|
246
|
-
return {
|
|
247
|
-
type: "text",
|
|
248
|
-
text: item.text,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
// Image content
|
|
253
|
-
return {
|
|
254
|
-
type: "image",
|
|
255
|
-
source: {
|
|
256
|
-
type: "base64",
|
|
257
|
-
media_type: item.mimeType,
|
|
258
|
-
data: item.data,
|
|
259
|
-
},
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
const filteredBlocks = !this.modelInfo?.input.includes("image")
|
|
264
|
-
? blocks.filter((b) => b.type !== "image")
|
|
265
|
-
: blocks;
|
|
266
|
-
params.push({
|
|
267
|
-
role: "user",
|
|
268
|
-
content: filteredBlocks,
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
257
|
}
|
|
272
|
-
else
|
|
273
|
-
const blocks =
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
blocks.push({
|
|
258
|
+
else {
|
|
259
|
+
const blocks = msg.content.map((item) => {
|
|
260
|
+
if (item.type === "text") {
|
|
261
|
+
return {
|
|
277
262
|
type: "text",
|
|
278
|
-
text:
|
|
279
|
-
}
|
|
263
|
+
text: item.text,
|
|
264
|
+
};
|
|
280
265
|
}
|
|
281
|
-
else
|
|
282
|
-
|
|
283
|
-
type: "
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
266
|
+
else {
|
|
267
|
+
return {
|
|
268
|
+
type: "image",
|
|
269
|
+
source: {
|
|
270
|
+
type: "base64",
|
|
271
|
+
media_type: item.mimeType,
|
|
272
|
+
data: item.data,
|
|
273
|
+
},
|
|
274
|
+
};
|
|
287
275
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
input: block.arguments,
|
|
294
|
-
});
|
|
276
|
+
});
|
|
277
|
+
let filteredBlocks = !model?.input.includes("image") ? blocks.filter((b) => b.type !== "image") : blocks;
|
|
278
|
+
filteredBlocks = filteredBlocks.filter((b) => {
|
|
279
|
+
if (b.type === "text") {
|
|
280
|
+
return b.text.trim().length > 0;
|
|
295
281
|
}
|
|
296
|
-
|
|
297
|
-
params.push({
|
|
298
|
-
role: "assistant",
|
|
299
|
-
content: blocks,
|
|
282
|
+
return true;
|
|
300
283
|
});
|
|
301
|
-
|
|
302
|
-
|
|
284
|
+
if (filteredBlocks.length === 0)
|
|
285
|
+
continue;
|
|
303
286
|
params.push({
|
|
304
287
|
role: "user",
|
|
305
|
-
content:
|
|
306
|
-
{
|
|
307
|
-
type: "tool_result",
|
|
308
|
-
tool_use_id: msg.toolCallId,
|
|
309
|
-
content: msg.content,
|
|
310
|
-
is_error: msg.isError,
|
|
311
|
-
},
|
|
312
|
-
],
|
|
288
|
+
content: filteredBlocks,
|
|
313
289
|
});
|
|
314
290
|
}
|
|
315
291
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
292
|
+
else if (msg.role === "assistant") {
|
|
293
|
+
const blocks = [];
|
|
294
|
+
for (const block of msg.content) {
|
|
295
|
+
if (block.type === "text") {
|
|
296
|
+
if (block.text.trim().length === 0)
|
|
297
|
+
continue;
|
|
298
|
+
blocks.push({
|
|
299
|
+
type: "text",
|
|
300
|
+
text: block.text,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
else if (block.type === "thinking") {
|
|
304
|
+
if (block.thinking.trim().length === 0)
|
|
305
|
+
continue;
|
|
306
|
+
blocks.push({
|
|
307
|
+
type: "thinking",
|
|
308
|
+
thinking: block.thinking,
|
|
309
|
+
signature: block.thinkingSignature || "",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
else if (block.type === "toolCall") {
|
|
313
|
+
blocks.push({
|
|
314
|
+
type: "tool_use",
|
|
315
|
+
id: sanitizeToolCallId(block.id),
|
|
316
|
+
name: block.name,
|
|
317
|
+
input: block.arguments,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (blocks.length === 0)
|
|
322
|
+
continue;
|
|
323
|
+
params.push({
|
|
324
|
+
role: "assistant",
|
|
325
|
+
content: blocks,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
else if (msg.role === "toolResult") {
|
|
329
|
+
params.push({
|
|
330
|
+
role: "user",
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
type: "tool_result",
|
|
334
|
+
tool_use_id: sanitizeToolCallId(msg.toolCallId),
|
|
335
|
+
content: msg.content,
|
|
336
|
+
is_error: msg.isError,
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
});
|
|
340
|
+
}
|
|
330
341
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
342
|
+
return params;
|
|
343
|
+
}
|
|
344
|
+
function convertTools(tools) {
|
|
345
|
+
if (!tools)
|
|
346
|
+
return [];
|
|
347
|
+
return tools.map((tool) => ({
|
|
348
|
+
name: tool.name,
|
|
349
|
+
description: tool.description,
|
|
350
|
+
input_schema: {
|
|
351
|
+
type: "object",
|
|
352
|
+
properties: tool.parameters.properties || {},
|
|
353
|
+
required: tool.parameters.required || [],
|
|
354
|
+
},
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
function mapStopReason(reason) {
|
|
358
|
+
switch (reason) {
|
|
359
|
+
case "end_turn":
|
|
360
|
+
return "stop";
|
|
361
|
+
case "max_tokens":
|
|
362
|
+
return "length";
|
|
363
|
+
case "tool_use":
|
|
364
|
+
return "toolUse";
|
|
365
|
+
case "refusal":
|
|
366
|
+
return "safety";
|
|
367
|
+
case "pause_turn": // Stop is good enough -> resubmit
|
|
368
|
+
return "stop";
|
|
369
|
+
case "stop_sequence":
|
|
370
|
+
return "stop"; // We don't supply stop sequences, so this should never happen
|
|
371
|
+
default: {
|
|
372
|
+
const _exhaustive = reason;
|
|
373
|
+
throw new Error(`Unhandled stop reason: ${_exhaustive}`);
|
|
347
374
|
}
|
|
348
375
|
}
|
|
349
376
|
}
|