@mariozechner/pi-ai 0.5.27 → 0.5.28

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.
Files changed (42) hide show
  1. package/README.md +355 -275
  2. package/dist/generate.d.ts +22 -0
  3. package/dist/generate.d.ts.map +1 -0
  4. package/dist/generate.js +204 -0
  5. package/dist/generate.js.map +1 -0
  6. package/dist/index.d.ts +7 -8
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +7 -12
  9. package/dist/index.js.map +1 -1
  10. package/dist/models.d.ts +10 -71
  11. package/dist/models.d.ts.map +1 -1
  12. package/dist/models.generated.d.ts +3056 -2659
  13. package/dist/models.generated.d.ts.map +1 -1
  14. package/dist/models.generated.js +3063 -2663
  15. package/dist/models.generated.js.map +1 -1
  16. package/dist/models.js +17 -59
  17. package/dist/models.js.map +1 -1
  18. package/dist/providers/anthropic.d.ts +5 -18
  19. package/dist/providers/anthropic.d.ts.map +1 -1
  20. package/dist/providers/anthropic.js +249 -227
  21. package/dist/providers/anthropic.js.map +1 -1
  22. package/dist/providers/google.d.ts +3 -14
  23. package/dist/providers/google.d.ts.map +1 -1
  24. package/dist/providers/google.js +215 -220
  25. package/dist/providers/google.js.map +1 -1
  26. package/dist/providers/openai-completions.d.ts +4 -14
  27. package/dist/providers/openai-completions.d.ts.map +1 -1
  28. package/dist/providers/openai-completions.js +247 -215
  29. package/dist/providers/openai-completions.js.map +1 -1
  30. package/dist/providers/openai-responses.d.ts +6 -13
  31. package/dist/providers/openai-responses.d.ts.map +1 -1
  32. package/dist/providers/openai-responses.js +242 -244
  33. package/dist/providers/openai-responses.js.map +1 -1
  34. package/dist/providers/utils.d.ts +2 -14
  35. package/dist/providers/utils.d.ts.map +1 -1
  36. package/dist/providers/utils.js +2 -15
  37. package/dist/providers/utils.js.map +1 -1
  38. package/dist/types.d.ts +39 -16
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/types.js +1 -0
  41. package/dist/types.js.map +1 -1
  42. 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 class AnthropicLLM {
5
- client;
6
- modelInfo;
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: this.getApi(),
56
- provider: this.modelInfo.provider,
57
- model: this.modelInfo.id,
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 messages = this.convertMessages(context.messages);
69
- const params = {
70
- model: this.modelInfo.id,
71
- messages,
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 stream) {
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
- options?.onEvent?.({ type: "text_start" });
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
- options?.onEvent?.({ type: "thinking_start" });
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 to send the event
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
- options?.onEvent?.({ type: "text_delta", content: currentBlock.text, delta: event.delta.text });
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
- options?.onEvent?.({
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
- options?.onEvent?.({ type: "text_end", content: currentBlock.text });
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
- options?.onEvent?.({ type: "thinking_end", content: currentBlock.thinking });
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,259 @@ export class AnthropicLLM {
200
113
  arguments: JSON.parse(currentBlock.partialJson),
201
114
  };
202
115
  output.content.push(finalToolCall);
203
- options?.onEvent?.({ type: "toolCall", toolCall: finalToolCall });
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 = this.mapStopReason(event.delta.stop_reason);
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(this.modelInfo, output.usage);
133
+ calculateCost(model, output.usage);
217
134
  }
218
135
  }
219
- options?.onEvent?.({ type: "done", reason: output.stopReason, message: output });
220
- return output;
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
- options?.onEvent?.({ type: "error", error: output.error });
226
- return output;
145
+ stream.push({ type: "error", error: output.error, partial: output });
146
+ stream.end();
227
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;
228
219
  }
229
- convertMessages(messages) {
230
- const params = [];
231
- // Transform messages for cross-provider compatibility
232
- const transformedMessages = transformMessages(messages, this.modelInfo, this.getApi());
233
- for (const msg of transformedMessages) {
234
- if (msg.role === "user") {
235
- // Handle both string and array content
236
- if (typeof msg.content === "string") {
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;
235
+ }
236
+ }
237
+ return params;
238
+ }
239
+ function convertMessages(messages, model) {
240
+ const params = [];
241
+ // Transform messages for cross-provider compatibility
242
+ const transformedMessages = transformMessages(messages, model);
243
+ for (const msg of transformedMessages) {
244
+ if (msg.role === "user") {
245
+ if (typeof msg.content === "string") {
246
+ if (msg.content.trim().length > 0) {
237
247
  params.push({
238
248
  role: "user",
239
249
  content: msg.content,
240
250
  });
241
251
  }
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
252
  }
272
- else if (msg.role === "assistant") {
273
- const blocks = [];
274
- for (const block of msg.content) {
275
- if (block.type === "text") {
276
- blocks.push({
253
+ else {
254
+ const blocks = msg.content.map((item) => {
255
+ if (item.type === "text") {
256
+ return {
277
257
  type: "text",
278
- text: block.text,
279
- });
258
+ text: item.text,
259
+ };
280
260
  }
281
- else if (block.type === "thinking") {
282
- blocks.push({
283
- type: "thinking",
284
- thinking: block.thinking,
285
- signature: block.thinkingSignature || "",
286
- });
261
+ else {
262
+ return {
263
+ type: "image",
264
+ source: {
265
+ type: "base64",
266
+ media_type: item.mimeType,
267
+ data: item.data,
268
+ },
269
+ };
287
270
  }
288
- else if (block.type === "toolCall") {
289
- blocks.push({
290
- type: "tool_use",
291
- id: block.id,
292
- name: block.name,
293
- input: block.arguments,
294
- });
271
+ });
272
+ let filteredBlocks = !model?.input.includes("image") ? blocks.filter((b) => b.type !== "image") : blocks;
273
+ filteredBlocks = filteredBlocks.filter((b) => {
274
+ if (b.type === "text") {
275
+ return b.text.trim().length > 0;
295
276
  }
296
- }
297
- params.push({
298
- role: "assistant",
299
- content: blocks,
277
+ return true;
300
278
  });
301
- }
302
- else if (msg.role === "toolResult") {
279
+ if (filteredBlocks.length === 0)
280
+ continue;
303
281
  params.push({
304
282
  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
- ],
283
+ content: filteredBlocks,
313
284
  });
314
285
  }
315
286
  }
316
- return params;
317
- }
318
- convertTools(tools) {
319
- if (!tools)
320
- return [];
321
- return tools.map((tool) => ({
322
- name: tool.name,
323
- description: tool.description,
324
- input_schema: {
325
- type: "object",
326
- properties: tool.parameters.properties || {},
327
- required: tool.parameters.required || [],
328
- },
329
- }));
287
+ else if (msg.role === "assistant") {
288
+ const blocks = [];
289
+ for (const block of msg.content) {
290
+ if (block.type === "text") {
291
+ if (block.text.trim().length === 0)
292
+ continue;
293
+ blocks.push({
294
+ type: "text",
295
+ text: block.text,
296
+ });
297
+ }
298
+ else if (block.type === "thinking") {
299
+ if (block.thinking.trim().length === 0)
300
+ continue;
301
+ blocks.push({
302
+ type: "thinking",
303
+ thinking: block.thinking,
304
+ signature: block.thinkingSignature || "",
305
+ });
306
+ }
307
+ else if (block.type === "toolCall") {
308
+ blocks.push({
309
+ type: "tool_use",
310
+ id: block.id,
311
+ name: block.name,
312
+ input: block.arguments,
313
+ });
314
+ }
315
+ }
316
+ if (blocks.length === 0)
317
+ continue;
318
+ params.push({
319
+ role: "assistant",
320
+ content: blocks,
321
+ });
322
+ }
323
+ else if (msg.role === "toolResult") {
324
+ params.push({
325
+ role: "user",
326
+ content: [
327
+ {
328
+ type: "tool_result",
329
+ tool_use_id: msg.toolCallId,
330
+ content: msg.content,
331
+ is_error: msg.isError,
332
+ },
333
+ ],
334
+ });
335
+ }
330
336
  }
331
- mapStopReason(reason) {
332
- switch (reason) {
333
- case "end_turn":
334
- return "stop";
335
- case "max_tokens":
336
- return "length";
337
- case "tool_use":
338
- return "toolUse";
339
- case "refusal":
340
- return "safety";
341
- case "pause_turn": // Stop is good enough -> resubmit
342
- return "stop";
343
- case "stop_sequence":
344
- return "stop"; // We don't supply stop sequences, so this should never happen
345
- default:
346
- return "stop";
337
+ return params;
338
+ }
339
+ function convertTools(tools) {
340
+ if (!tools)
341
+ return [];
342
+ return tools.map((tool) => ({
343
+ name: tool.name,
344
+ description: tool.description,
345
+ input_schema: {
346
+ type: "object",
347
+ properties: tool.parameters.properties || {},
348
+ required: tool.parameters.required || [],
349
+ },
350
+ }));
351
+ }
352
+ function mapStopReason(reason) {
353
+ switch (reason) {
354
+ case "end_turn":
355
+ return "stop";
356
+ case "max_tokens":
357
+ return "length";
358
+ case "tool_use":
359
+ return "toolUse";
360
+ case "refusal":
361
+ return "safety";
362
+ case "pause_turn": // Stop is good enough -> resubmit
363
+ return "stop";
364
+ case "stop_sequence":
365
+ return "stop"; // We don't supply stop sequences, so this should never happen
366
+ default: {
367
+ const _exhaustive = reason;
368
+ throw new Error(`Unhandled stop reason: ${_exhaustive}`);
347
369
  }
348
370
  }
349
371
  }