@mariozechner/pi-ai 0.5.26 → 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 -2644
  13. package/dist/models.generated.d.ts.map +1 -1
  14. package/dist/models.generated.js +3063 -2648
  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,32 +1,16 @@
1
- import { FunctionCallingConfigMode, GoogleGenAI, } from "@google/genai";
1
+ import { FinishReason, FunctionCallingConfigMode, GoogleGenAI, } from "@google/genai";
2
+ import { QueuedGenerateStream } from "../generate.js";
2
3
  import { calculateCost } from "../models.js";
3
4
  import { transformMessages } from "./utils.js";
4
- export class GoogleLLM {
5
- client;
6
- modelInfo;
7
- constructor(model, apiKey) {
8
- if (!apiKey) {
9
- if (!process.env.GEMINI_API_KEY) {
10
- throw new Error("Gemini API key is required. Set GEMINI_API_KEY environment variable or pass it as an argument.");
11
- }
12
- apiKey = process.env.GEMINI_API_KEY;
13
- }
14
- this.client = new GoogleGenAI({ apiKey });
15
- this.modelInfo = model;
16
- }
17
- getModel() {
18
- return this.modelInfo;
19
- }
20
- getApi() {
21
- return "google-generative-ai";
22
- }
23
- async generate(context, options) {
5
+ export const streamGoogle = (model, context, options) => {
6
+ const stream = new QueuedGenerateStream();
7
+ (async () => {
24
8
  const output = {
25
9
  role: "assistant",
26
10
  content: [],
27
- api: this.getApi(),
28
- provider: this.modelInfo.provider,
29
- model: this.modelInfo.id,
11
+ api: "google-generative-ai",
12
+ provider: model.provider,
13
+ model: model.id,
30
14
  usage: {
31
15
  input: 0,
32
16
  output: 0,
@@ -37,109 +21,78 @@ export class GoogleLLM {
37
21
  stopReason: "stop",
38
22
  };
39
23
  try {
40
- const contents = this.convertMessages(context.messages);
41
- // Build generation config
42
- const generationConfig = {};
43
- if (options?.temperature !== undefined) {
44
- generationConfig.temperature = options.temperature;
45
- }
46
- if (options?.maxTokens !== undefined) {
47
- generationConfig.maxOutputTokens = options.maxTokens;
48
- }
49
- // Build the config object
50
- const config = {
51
- ...(Object.keys(generationConfig).length > 0 && generationConfig),
52
- ...(context.systemPrompt && { systemInstruction: context.systemPrompt }),
53
- ...(context.tools && { tools: this.convertTools(context.tools) }),
54
- };
55
- // Add tool config if needed
56
- if (context.tools && options?.toolChoice) {
57
- config.toolConfig = {
58
- functionCallingConfig: {
59
- mode: this.mapToolChoice(options.toolChoice),
60
- },
61
- };
62
- }
63
- // Add thinking config if enabled and model supports it
64
- if (options?.thinking?.enabled && this.modelInfo.reasoning) {
65
- config.thinkingConfig = {
66
- includeThoughts: true,
67
- ...(options.thinking.budgetTokens !== undefined && { thinkingBudget: options.thinking.budgetTokens }),
68
- };
69
- }
70
- // Abort signal
71
- if (options?.signal) {
72
- if (options.signal.aborted) {
73
- throw new Error("Request aborted");
74
- }
75
- config.abortSignal = options.signal;
76
- }
77
- // Build the request parameters
78
- const params = {
79
- model: this.modelInfo.id,
80
- contents,
81
- config,
82
- };
83
- const stream = await this.client.models.generateContentStream(params);
84
- options?.onEvent?.({ type: "start", model: this.modelInfo.id, provider: this.modelInfo.provider });
24
+ const client = createClient(options?.apiKey);
25
+ const params = buildParams(model, context, options);
26
+ const googleStream = await client.models.generateContentStream(params);
27
+ stream.push({ type: "start", partial: output });
85
28
  let currentBlock = null;
86
- for await (const chunk of stream) {
87
- // Extract parts from the chunk
29
+ for await (const chunk of googleStream) {
88
30
  const candidate = chunk.candidates?.[0];
89
31
  if (candidate?.content?.parts) {
90
32
  for (const part of candidate.content.parts) {
91
33
  if (part.text !== undefined) {
92
34
  const isThinking = part.thought === true;
93
- // Check if we need to switch blocks
94
35
  if (!currentBlock ||
95
36
  (isThinking && currentBlock.type !== "thinking") ||
96
37
  (!isThinking && currentBlock.type !== "text")) {
97
38
  if (currentBlock) {
98
39
  if (currentBlock.type === "text") {
99
- options?.onEvent?.({ type: "text_end", content: currentBlock.text });
40
+ stream.push({
41
+ type: "text_end",
42
+ content: currentBlock.text,
43
+ partial: output,
44
+ });
100
45
  }
101
46
  else {
102
- options?.onEvent?.({ type: "thinking_end", content: currentBlock.thinking });
47
+ stream.push({
48
+ type: "thinking_end",
49
+ content: currentBlock.thinking,
50
+ partial: output,
51
+ });
103
52
  }
104
53
  }
105
- // Start new block
106
54
  if (isThinking) {
107
55
  currentBlock = { type: "thinking", thinking: "", thinkingSignature: undefined };
108
- options?.onEvent?.({ type: "thinking_start" });
56
+ stream.push({ type: "thinking_start", partial: output });
109
57
  }
110
58
  else {
111
59
  currentBlock = { type: "text", text: "" };
112
- options?.onEvent?.({ type: "text_start" });
60
+ stream.push({ type: "text_start", partial: output });
113
61
  }
114
62
  output.content.push(currentBlock);
115
63
  }
116
- // Append content to current block
117
64
  if (currentBlock.type === "thinking") {
118
65
  currentBlock.thinking += part.text;
119
66
  currentBlock.thinkingSignature = part.thoughtSignature;
120
- options?.onEvent?.({
67
+ stream.push({
121
68
  type: "thinking_delta",
122
- content: currentBlock.thinking,
123
69
  delta: part.text,
70
+ partial: output,
124
71
  });
125
72
  }
126
73
  else {
127
74
  currentBlock.text += part.text;
128
- options?.onEvent?.({ type: "text_delta", content: currentBlock.text, delta: part.text });
75
+ stream.push({ type: "text_delta", delta: part.text, partial: output });
129
76
  }
130
77
  }
131
- // Handle function calls
132
78
  if (part.functionCall) {
133
79
  if (currentBlock) {
134
80
  if (currentBlock.type === "text") {
135
- options?.onEvent?.({ type: "text_end", content: currentBlock.text });
81
+ stream.push({
82
+ type: "text_end",
83
+ content: currentBlock.text,
84
+ partial: output,
85
+ });
136
86
  }
137
87
  else {
138
- options?.onEvent?.({ type: "thinking_end", content: currentBlock.thinking });
88
+ stream.push({
89
+ type: "thinking_end",
90
+ content: currentBlock.thinking,
91
+ partial: output,
92
+ });
139
93
  }
140
94
  currentBlock = null;
141
95
  }
142
- // Add tool call
143
96
  const toolCallId = part.functionCall.id || `${part.functionCall.name}_${Date.now()}`;
144
97
  const toolCall = {
145
98
  type: "toolCall",
@@ -148,19 +101,16 @@ export class GoogleLLM {
148
101
  arguments: part.functionCall.args,
149
102
  };
150
103
  output.content.push(toolCall);
151
- options?.onEvent?.({ type: "toolCall", toolCall });
104
+ stream.push({ type: "toolCall", toolCall, partial: output });
152
105
  }
153
106
  }
154
107
  }
155
- // Map finish reason
156
108
  if (candidate?.finishReason) {
157
- output.stopReason = this.mapStopReason(candidate.finishReason);
158
- // Check if we have tool calls in blocks
109
+ output.stopReason = mapStopReason(candidate.finishReason);
159
110
  if (output.content.some((b) => b.type === "toolCall")) {
160
111
  output.stopReason = "toolUse";
161
112
  }
162
113
  }
163
- // Capture usage metadata if available
164
114
  if (chunk.usageMetadata) {
165
115
  output.usage = {
166
116
  input: chunk.usageMetadata.promptTokenCount || 0,
@@ -175,163 +125,208 @@ export class GoogleLLM {
175
125
  total: 0,
176
126
  },
177
127
  };
178
- calculateCost(this.modelInfo, output.usage);
128
+ calculateCost(model, output.usage);
179
129
  }
180
130
  }
181
- // Finalize last block
182
131
  if (currentBlock) {
183
132
  if (currentBlock.type === "text") {
184
- options?.onEvent?.({ type: "text_end", content: currentBlock.text });
133
+ stream.push({ type: "text_end", content: currentBlock.text, partial: output });
185
134
  }
186
135
  else {
187
- options?.onEvent?.({ type: "thinking_end", content: currentBlock.thinking });
136
+ stream.push({ type: "thinking_end", content: currentBlock.thinking, partial: output });
188
137
  }
189
138
  }
190
- options?.onEvent?.({ type: "done", reason: output.stopReason, message: output });
191
- return output;
139
+ stream.push({ type: "done", reason: output.stopReason, message: output });
140
+ stream.end();
192
141
  }
193
142
  catch (error) {
194
143
  output.stopReason = "error";
195
144
  output.error = error instanceof Error ? error.message : JSON.stringify(error);
196
- options?.onEvent?.({ type: "error", error: output.error });
197
- return output;
145
+ stream.push({ type: "error", error: output.error, partial: output });
146
+ stream.end();
198
147
  }
148
+ })();
149
+ return stream;
150
+ };
151
+ function createClient(apiKey) {
152
+ if (!apiKey) {
153
+ if (!process.env.GEMINI_API_KEY) {
154
+ throw new Error("Gemini API key is required. Set GEMINI_API_KEY environment variable or pass it as an argument.");
155
+ }
156
+ apiKey = process.env.GEMINI_API_KEY;
199
157
  }
200
- convertMessages(messages) {
201
- const contents = [];
202
- // Transform messages for cross-provider compatibility
203
- const transformedMessages = transformMessages(messages, this.modelInfo, this.getApi());
204
- for (const msg of transformedMessages) {
205
- if (msg.role === "user") {
206
- // Handle both string and array content
207
- if (typeof msg.content === "string") {
208
- contents.push({
209
- role: "user",
210
- parts: [{ text: msg.content }],
211
- });
212
- }
213
- else {
214
- // Convert array content to Google format
215
- const parts = msg.content.map((item) => {
216
- if (item.type === "text") {
217
- return { text: item.text };
218
- }
219
- else {
220
- // Image content - Google uses inlineData
221
- return {
222
- inlineData: {
223
- mimeType: item.mimeType,
224
- data: item.data,
225
- },
226
- };
227
- }
228
- });
229
- const filteredParts = !this.modelInfo?.input.includes("image")
230
- ? parts.filter((p) => p.text !== undefined)
231
- : parts;
232
- contents.push({
233
- role: "user",
234
- parts: filteredParts,
235
- });
236
- }
158
+ return new GoogleGenAI({ apiKey });
159
+ }
160
+ function buildParams(model, context, options = {}) {
161
+ const contents = convertMessages(model, context);
162
+ const generationConfig = {};
163
+ if (options.temperature !== undefined) {
164
+ generationConfig.temperature = options.temperature;
165
+ }
166
+ if (options.maxTokens !== undefined) {
167
+ generationConfig.maxOutputTokens = options.maxTokens;
168
+ }
169
+ const config = {
170
+ ...(Object.keys(generationConfig).length > 0 && generationConfig),
171
+ ...(context.systemPrompt && { systemInstruction: context.systemPrompt }),
172
+ ...(context.tools && { tools: convertTools(context.tools) }),
173
+ };
174
+ if (context.tools && options.toolChoice) {
175
+ config.toolConfig = {
176
+ functionCallingConfig: {
177
+ mode: mapToolChoice(options.toolChoice),
178
+ },
179
+ };
180
+ }
181
+ if (options.thinking?.enabled && model.reasoning) {
182
+ config.thinkingConfig = {
183
+ includeThoughts: true,
184
+ ...(options.thinking.budgetTokens !== undefined && { thinkingBudget: options.thinking.budgetTokens }),
185
+ };
186
+ }
187
+ if (options.signal) {
188
+ if (options.signal.aborted) {
189
+ throw new Error("Request aborted");
190
+ }
191
+ config.abortSignal = options.signal;
192
+ }
193
+ const params = {
194
+ model: model.id,
195
+ contents,
196
+ config,
197
+ };
198
+ return params;
199
+ }
200
+ function convertMessages(model, context) {
201
+ const contents = [];
202
+ const transformedMessages = transformMessages(context.messages, model);
203
+ for (const msg of transformedMessages) {
204
+ if (msg.role === "user") {
205
+ if (typeof msg.content === "string") {
206
+ contents.push({
207
+ role: "user",
208
+ parts: [{ text: msg.content }],
209
+ });
237
210
  }
238
- else if (msg.role === "assistant") {
239
- const parts = [];
240
- // Process content blocks
241
- for (const block of msg.content) {
242
- if (block.type === "text") {
243
- parts.push({ text: block.text });
244
- }
245
- else if (block.type === "thinking") {
246
- const thinkingPart = {
247
- thought: true,
248
- thoughtSignature: block.thinkingSignature,
249
- text: block.thinking,
250
- };
251
- parts.push(thinkingPart);
211
+ else {
212
+ const parts = msg.content.map((item) => {
213
+ if (item.type === "text") {
214
+ return { text: item.text };
252
215
  }
253
- else if (block.type === "toolCall") {
254
- parts.push({
255
- functionCall: {
256
- id: block.id,
257
- name: block.name,
258
- args: block.arguments,
216
+ else {
217
+ return {
218
+ inlineData: {
219
+ mimeType: item.mimeType,
220
+ data: item.data,
259
221
  },
260
- });
222
+ };
261
223
  }
224
+ });
225
+ const filteredParts = !model.input.includes("image") ? parts.filter((p) => p.text !== undefined) : parts;
226
+ if (filteredParts.length === 0)
227
+ continue;
228
+ contents.push({
229
+ role: "user",
230
+ parts: filteredParts,
231
+ });
232
+ }
233
+ }
234
+ else if (msg.role === "assistant") {
235
+ const parts = [];
236
+ for (const block of msg.content) {
237
+ if (block.type === "text") {
238
+ parts.push({ text: block.text });
262
239
  }
263
- if (parts.length > 0) {
264
- contents.push({
265
- role: "model",
266
- parts,
240
+ else if (block.type === "thinking") {
241
+ const thinkingPart = {
242
+ thought: true,
243
+ thoughtSignature: block.thinkingSignature,
244
+ text: block.thinking,
245
+ };
246
+ parts.push(thinkingPart);
247
+ }
248
+ else if (block.type === "toolCall") {
249
+ parts.push({
250
+ functionCall: {
251
+ id: block.id,
252
+ name: block.name,
253
+ args: block.arguments,
254
+ },
267
255
  });
268
256
  }
269
257
  }
270
- else if (msg.role === "toolResult") {
271
- contents.push({
272
- role: "user",
273
- parts: [
274
- {
275
- functionResponse: {
276
- id: msg.toolCallId,
277
- name: msg.toolName,
278
- response: {
279
- result: msg.content,
280
- isError: msg.isError,
281
- },
258
+ if (parts.length === 0)
259
+ continue;
260
+ contents.push({
261
+ role: "model",
262
+ parts,
263
+ });
264
+ }
265
+ else if (msg.role === "toolResult") {
266
+ contents.push({
267
+ role: "user",
268
+ parts: [
269
+ {
270
+ functionResponse: {
271
+ id: msg.toolCallId,
272
+ name: msg.toolName,
273
+ response: {
274
+ result: msg.content,
275
+ isError: msg.isError,
282
276
  },
283
277
  },
284
- ],
285
- });
286
- }
278
+ },
279
+ ],
280
+ });
287
281
  }
288
- return contents;
289
- }
290
- convertTools(tools) {
291
- return [
292
- {
293
- functionDeclarations: tools.map((tool) => ({
294
- name: tool.name,
295
- description: tool.description,
296
- parameters: tool.parameters,
297
- })),
298
- },
299
- ];
300
282
  }
301
- mapToolChoice(choice) {
302
- switch (choice) {
303
- case "auto":
304
- return FunctionCallingConfigMode.AUTO;
305
- case "none":
306
- return FunctionCallingConfigMode.NONE;
307
- case "any":
308
- return FunctionCallingConfigMode.ANY;
309
- default:
310
- return FunctionCallingConfigMode.AUTO;
311
- }
283
+ return contents;
284
+ }
285
+ function convertTools(tools) {
286
+ return [
287
+ {
288
+ functionDeclarations: tools.map((tool) => ({
289
+ name: tool.name,
290
+ description: tool.description,
291
+ parameters: tool.parameters,
292
+ })),
293
+ },
294
+ ];
295
+ }
296
+ function mapToolChoice(choice) {
297
+ switch (choice) {
298
+ case "auto":
299
+ return FunctionCallingConfigMode.AUTO;
300
+ case "none":
301
+ return FunctionCallingConfigMode.NONE;
302
+ case "any":
303
+ return FunctionCallingConfigMode.ANY;
304
+ default:
305
+ return FunctionCallingConfigMode.AUTO;
312
306
  }
313
- mapStopReason(reason) {
314
- switch (reason) {
315
- case "STOP":
316
- return "stop";
317
- case "MAX_TOKENS":
318
- return "length";
319
- case "BLOCKLIST":
320
- case "PROHIBITED_CONTENT":
321
- case "SPII":
322
- case "SAFETY":
323
- case "IMAGE_SAFETY":
324
- return "safety";
325
- case "RECITATION":
326
- return "safety";
327
- case "FINISH_REASON_UNSPECIFIED":
328
- case "OTHER":
329
- case "LANGUAGE":
330
- case "MALFORMED_FUNCTION_CALL":
331
- case "UNEXPECTED_TOOL_CALL":
332
- return "error";
333
- default:
334
- return "stop";
307
+ }
308
+ function mapStopReason(reason) {
309
+ switch (reason) {
310
+ case FinishReason.STOP:
311
+ return "stop";
312
+ case FinishReason.MAX_TOKENS:
313
+ return "length";
314
+ case FinishReason.BLOCKLIST:
315
+ case FinishReason.PROHIBITED_CONTENT:
316
+ case FinishReason.SPII:
317
+ case FinishReason.SAFETY:
318
+ case FinishReason.IMAGE_SAFETY:
319
+ case FinishReason.RECITATION:
320
+ return "safety";
321
+ case FinishReason.FINISH_REASON_UNSPECIFIED:
322
+ case FinishReason.OTHER:
323
+ case FinishReason.LANGUAGE:
324
+ case FinishReason.MALFORMED_FUNCTION_CALL:
325
+ case FinishReason.UNEXPECTED_TOOL_CALL:
326
+ return "error";
327
+ default: {
328
+ const _exhaustive = reason;
329
+ throw new Error(`Unhandled stop reason: ${_exhaustive}`);
335
330
  }
336
331
  }
337
332
  }