@yourgpt/llm-sdk 2.1.5 → 2.1.6

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 (41) hide show
  1. package/dist/adapters/index.d.mts +11 -37
  2. package/dist/adapters/index.d.ts +11 -37
  3. package/dist/adapters/index.js +41 -192
  4. package/dist/adapters/index.mjs +42 -192
  5. package/dist/{base-5n-UuPfS.d.mts → base-D-U61JaB.d.mts} +22 -2
  6. package/dist/{base-Di31iy_8.d.ts → base-iGi9Va6Z.d.ts} +22 -2
  7. package/dist/fallback/index.d.mts +3 -3
  8. package/dist/fallback/index.d.ts +3 -3
  9. package/dist/index.d.mts +5 -5
  10. package/dist/index.d.ts +5 -5
  11. package/dist/index.js +96 -76
  12. package/dist/index.mjs +96 -76
  13. package/dist/providers/anthropic/index.d.mts +2 -2
  14. package/dist/providers/anthropic/index.d.ts +2 -2
  15. package/dist/providers/azure/index.d.mts +2 -2
  16. package/dist/providers/azure/index.d.ts +2 -2
  17. package/dist/providers/azure/index.js +4 -2
  18. package/dist/providers/azure/index.mjs +4 -2
  19. package/dist/providers/google/index.d.mts +2 -2
  20. package/dist/providers/google/index.d.ts +2 -2
  21. package/dist/providers/google/index.js +527 -339
  22. package/dist/providers/google/index.mjs +527 -339
  23. package/dist/providers/ollama/index.d.mts +3 -3
  24. package/dist/providers/ollama/index.d.ts +3 -3
  25. package/dist/providers/openai/index.d.mts +2 -2
  26. package/dist/providers/openai/index.d.ts +2 -2
  27. package/dist/providers/openai/index.js +34 -11
  28. package/dist/providers/openai/index.mjs +34 -11
  29. package/dist/providers/openrouter/index.d.mts +2 -2
  30. package/dist/providers/openrouter/index.d.ts +2 -2
  31. package/dist/providers/openrouter/index.js +34 -11
  32. package/dist/providers/openrouter/index.mjs +34 -11
  33. package/dist/providers/xai/index.d.mts +2 -2
  34. package/dist/providers/xai/index.d.ts +2 -2
  35. package/dist/providers/xai/index.js +355 -46
  36. package/dist/providers/xai/index.mjs +355 -46
  37. package/dist/{types-CNL8ZRne.d.ts → types-38yolWJn.d.ts} +1 -1
  38. package/dist/{types-C0vLXzuw.d.ts → types-BctsnC3g.d.ts} +1 -1
  39. package/dist/{types-BQl1suAv.d.mts → types-D4YfrQJR.d.mts} +1 -1
  40. package/dist/{types-VDgiUvH2.d.mts → types-DRqxMIjF.d.mts} +1 -1
  41. package/package.json +1 -1
@@ -333,329 +333,501 @@ ${stringifyForDebug(payload)}`
333
333
  );
334
334
  }
335
335
  }
336
-
337
- // src/adapters/google.ts
338
- function attachmentToGeminiPart(attachment) {
339
- if (!attachment.data) {
340
- console.warn(
341
- "Gemini adapter: URL-based attachments not supported, skipping"
336
+ function parameterToJsonSchema(param) {
337
+ const schema = {
338
+ type: param.type
339
+ };
340
+ if (param.description) {
341
+ schema.description = param.description;
342
+ }
343
+ if (param.enum) {
344
+ schema.enum = param.enum;
345
+ }
346
+ if (param.type === "array" && param.items) {
347
+ schema.items = parameterToJsonSchema(
348
+ param.items
342
349
  );
343
- return null;
344
350
  }
345
- if (attachment.type === "image") {
346
- let base64Data = attachment.data;
347
- if (base64Data.startsWith("data:")) {
348
- const commaIndex = base64Data.indexOf(",");
349
- if (commaIndex !== -1) {
350
- base64Data = base64Data.slice(commaIndex + 1);
351
- }
352
- }
351
+ if (param.type === "object" && param.properties) {
352
+ schema.properties = Object.fromEntries(
353
+ Object.entries(param.properties).map(([key, prop]) => [
354
+ key,
355
+ parameterToJsonSchema(
356
+ prop
357
+ )
358
+ ])
359
+ );
360
+ schema.additionalProperties = false;
361
+ }
362
+ return schema;
363
+ }
364
+ function normalizeObjectJsonSchema(schema) {
365
+ if (!schema || typeof schema !== "object") {
353
366
  return {
354
- inlineData: {
355
- mimeType: attachment.mimeType || "image/png",
356
- data: base64Data
357
- }
367
+ type: "object",
368
+ properties: {},
369
+ required: [],
370
+ additionalProperties: false
358
371
  };
359
372
  }
360
- if (attachment.type === "audio" || attachment.type === "video") {
361
- let base64Data = attachment.data;
362
- if (base64Data.startsWith("data:")) {
363
- const commaIndex = base64Data.indexOf(",");
364
- if (commaIndex !== -1) {
365
- base64Data = base64Data.slice(commaIndex + 1);
366
- }
373
+ const normalized = { ...schema };
374
+ const type = normalized.type;
375
+ if (type === "object") {
376
+ const properties = normalized.properties && typeof normalized.properties === "object" && !Array.isArray(normalized.properties) ? normalized.properties : {};
377
+ normalized.properties = Object.fromEntries(
378
+ Object.entries(properties).map(([key, value]) => [
379
+ key,
380
+ normalizeObjectJsonSchema(value)
381
+ ])
382
+ );
383
+ const propertyKeys = Object.keys(properties);
384
+ const required = Array.isArray(normalized.required) ? normalized.required.filter(
385
+ (value) => typeof value === "string"
386
+ ) : [];
387
+ normalized.required = Array.from(/* @__PURE__ */ new Set([...required, ...propertyKeys]));
388
+ if (normalized.additionalProperties === void 0) {
389
+ normalized.additionalProperties = false;
367
390
  }
368
- return {
369
- inlineData: {
370
- mimeType: attachment.mimeType || (attachment.type === "audio" ? "audio/mp3" : "video/mp4"),
371
- data: base64Data
372
- }
373
- };
391
+ } else if (type === "array" && normalized.items && typeof normalized.items === "object") {
392
+ normalized.items = normalizeObjectJsonSchema(
393
+ normalized.items
394
+ );
374
395
  }
375
- return null;
396
+ return normalized;
376
397
  }
377
- function messageToGeminiContent(msg) {
378
- if (msg.role === "system") return null;
379
- const parts = [];
380
- if (msg.role === "tool" && msg.tool_call_id) {
381
- let responseData;
382
- try {
383
- responseData = JSON.parse(msg.content || "{}");
384
- } catch {
385
- responseData = { result: msg.content || "" };
386
- }
387
- const toolName = msg.metadata?.toolName || "tool";
388
- parts.push({
389
- functionResponse: {
390
- name: toolName,
391
- response: responseData
398
+ function formatTools(actions) {
399
+ return actions.map((action) => ({
400
+ type: "function",
401
+ function: {
402
+ name: action.name,
403
+ description: action.description,
404
+ parameters: {
405
+ type: "object",
406
+ properties: action.parameters ? Object.fromEntries(
407
+ Object.entries(action.parameters).map(([key, param]) => [
408
+ key,
409
+ parameterToJsonSchema(param)
410
+ ])
411
+ ) : {},
412
+ required: action.parameters ? Object.entries(action.parameters).filter(([, param]) => param.required).map(([key]) => key) : [],
413
+ additionalProperties: false
392
414
  }
393
- });
394
- return { role: "user", parts };
415
+ }
416
+ }));
417
+ }
418
+ function hasImageAttachments(message) {
419
+ const attachments = message.metadata?.attachments;
420
+ return attachments?.some((a) => a.type === "image") ?? false;
421
+ }
422
+ function attachmentToOpenAIImage(attachment) {
423
+ if (attachment.type !== "image") return null;
424
+ let imageUrl;
425
+ if (attachment.url) {
426
+ imageUrl = attachment.url;
427
+ } else if (attachment.data) {
428
+ imageUrl = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType || "image/png"};base64,${attachment.data}`;
429
+ } else {
430
+ return null;
395
431
  }
396
- if (msg.content) {
397
- parts.push({ text: msg.content });
432
+ return {
433
+ type: "image_url",
434
+ image_url: {
435
+ url: imageUrl,
436
+ detail: "auto"
437
+ }
438
+ };
439
+ }
440
+ function messageToOpenAIContent(message) {
441
+ const attachments = message.metadata?.attachments;
442
+ const content = message.content ?? "";
443
+ if (!hasImageAttachments(message)) {
444
+ return content;
398
445
  }
399
- const attachments = msg.metadata?.attachments;
400
- if (attachments && Array.isArray(attachments)) {
446
+ const blocks = [];
447
+ if (content) {
448
+ blocks.push({ type: "text", text: content });
449
+ }
450
+ if (attachments) {
401
451
  for (const attachment of attachments) {
402
- const part = attachmentToGeminiPart(attachment);
403
- if (part) {
404
- parts.push(part);
452
+ const imageBlock = attachmentToOpenAIImage(attachment);
453
+ if (imageBlock) {
454
+ blocks.push(imageBlock);
405
455
  }
406
456
  }
407
457
  }
408
- if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
409
- for (const tc of msg.tool_calls) {
410
- let args = {};
411
- try {
412
- args = JSON.parse(tc.function.arguments);
413
- } catch {
458
+ return blocks;
459
+ }
460
+ function formatMessagesForOpenAI(messages, systemPrompt) {
461
+ const formatted = [];
462
+ if (systemPrompt) {
463
+ formatted.push({ role: "system", content: systemPrompt });
464
+ }
465
+ for (const msg of messages) {
466
+ if (msg.role === "system") {
467
+ formatted.push({ role: "system", content: msg.content ?? "" });
468
+ } else if (msg.role === "user") {
469
+ formatted.push({
470
+ role: "user",
471
+ content: messageToOpenAIContent(msg)
472
+ });
473
+ } else if (msg.role === "assistant") {
474
+ const hasToolCalls = msg.tool_calls && msg.tool_calls.length > 0;
475
+ const assistantMsg = {
476
+ role: "assistant",
477
+ // Gemini/xAI (OpenAI-compatible) reject content: "" on assistant messages with tool_calls
478
+ content: hasToolCalls ? msg.content || null : msg.content
479
+ };
480
+ if (hasToolCalls) {
481
+ assistantMsg.tool_calls = msg.tool_calls;
414
482
  }
415
- parts.push({
416
- functionCall: {
417
- name: tc.function.name,
418
- args
419
- }
483
+ formatted.push(assistantMsg);
484
+ } else if (msg.role === "tool" && msg.tool_call_id) {
485
+ formatted.push({
486
+ role: "tool",
487
+ content: msg.content ?? "",
488
+ tool_call_id: msg.tool_call_id
420
489
  });
421
490
  }
422
491
  }
423
- if (parts.length === 0) return null;
424
- return {
425
- role: msg.role === "assistant" ? "model" : "user",
426
- parts
427
- };
428
- }
429
- function formatToolsForGemini(actions) {
430
- if (!actions || actions.length === 0) return void 0;
431
- return {
432
- functionDeclarations: actions.map((action) => ({
433
- name: action.name,
434
- description: action.description,
435
- parameters: action.parameters ? {
436
- type: "object",
437
- properties: Object.fromEntries(
438
- Object.entries(action.parameters).map(([key, param]) => [
439
- key,
440
- {
441
- type: param.type,
442
- description: param.description,
443
- enum: param.enum
444
- }
445
- ])
446
- ),
447
- required: Object.entries(action.parameters).filter(([, param]) => param.required).map(([key]) => key)
448
- } : void 0
449
- }))
450
- };
492
+ return formatted;
451
493
  }
452
- var GoogleAdapter = class {
494
+
495
+ // src/adapters/openai.ts
496
+ var OpenAIAdapter = class _OpenAIAdapter {
453
497
  constructor(config) {
454
- this.provider = "google";
455
498
  this.config = config;
456
- this.model = config.model || "gemini-2.0-flash";
499
+ this.model = config.model || "gpt-4o";
500
+ this.provider = _OpenAIAdapter.resolveProviderName(config.baseUrl);
501
+ }
502
+ static resolveProviderName(baseUrl) {
503
+ if (!baseUrl) return "openai";
504
+ if (baseUrl.includes("generativelanguage.googleapis.com")) return "google";
505
+ if (baseUrl.includes("x.ai")) return "xai";
506
+ if (baseUrl.includes("azure")) return "azure";
507
+ return "openai";
457
508
  }
458
509
  async getClient() {
459
510
  if (!this.client) {
460
- const { GoogleGenerativeAI } = await import('@google/generative-ai');
461
- this.client = new GoogleGenerativeAI(this.config.apiKey);
511
+ const { default: OpenAI } = await import('openai');
512
+ this.client = new OpenAI({
513
+ apiKey: this.config.apiKey,
514
+ baseURL: this.config.baseUrl
515
+ });
462
516
  }
463
517
  return this.client;
464
518
  }
465
- async *stream(request) {
466
- const client = await this.getClient();
467
- const modelId = request.config?.model || this.model;
468
- const webSearchConfig = request.webSearch ?? this.config.webSearch;
469
- const model = client.getGenerativeModel({
470
- model: modelId,
471
- safetySettings: this.config.safetySettings
472
- });
473
- let contents = [];
474
- let systemInstruction;
475
- if (request.rawMessages && request.rawMessages.length > 0) {
476
- for (const msg of request.rawMessages) {
477
- if (msg.role === "system") {
478
- systemInstruction = (systemInstruction || "") + (msg.content || "");
479
- continue;
480
- }
481
- const content = messageToGeminiContent(msg);
519
+ shouldUseResponsesApi(request) {
520
+ return request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false && Array.isArray(request.toolDefinitions) && request.toolDefinitions.length > 0;
521
+ }
522
+ buildResponsesInput(request) {
523
+ const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
524
+ const input = [];
525
+ for (const message of sourceMessages) {
526
+ if (message.role === "system") {
527
+ continue;
528
+ }
529
+ if (message.role === "assistant") {
530
+ const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content : message.content ? JSON.stringify(message.content) : "";
482
531
  if (content) {
483
- contents.push(content);
532
+ input.push({
533
+ type: "message",
534
+ role: "assistant",
535
+ content
536
+ });
537
+ }
538
+ const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
539
+ for (const toolCall of toolCalls) {
540
+ input.push({
541
+ type: "function_call",
542
+ call_id: toolCall.id,
543
+ name: toolCall.function?.name,
544
+ arguments: toolCall.function?.arguments ?? "{}"
545
+ });
484
546
  }
547
+ continue;
485
548
  }
486
- if (request.systemPrompt && !systemInstruction) {
487
- systemInstruction = request.systemPrompt;
549
+ if (message.role === "tool") {
550
+ input.push({
551
+ type: "function_call_output",
552
+ call_id: message.tool_call_id,
553
+ output: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
554
+ });
555
+ continue;
488
556
  }
489
- } else {
490
- for (const msg of request.messages) {
491
- if (msg.role === "system") {
492
- systemInstruction = (systemInstruction || "") + (msg.content || "");
493
- continue;
557
+ input.push({
558
+ type: "message",
559
+ role: message.role === "developer" ? "developer" : "user",
560
+ content: typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content : JSON.stringify(message.content ?? "")
561
+ });
562
+ }
563
+ return input;
564
+ }
565
+ buildResponsesTools(tools) {
566
+ const nativeTools = tools.filter((tool) => tool.available !== false).map((tool) => ({
567
+ type: "function",
568
+ name: tool.name,
569
+ description: tool.description,
570
+ parameters: normalizeObjectJsonSchema(
571
+ tool.inputSchema ?? {
572
+ type: "object",
573
+ properties: {},
574
+ required: []
494
575
  }
495
- const content = messageToGeminiContent(msg);
496
- if (content) {
497
- contents.push(content);
576
+ ),
577
+ strict: true,
578
+ defer_loading: tool.deferLoading === true
579
+ }));
580
+ return [{ type: "tool_search" }, ...nativeTools];
581
+ }
582
+ parseResponsesResult(response) {
583
+ const content = typeof response?.output_text === "string" ? response.output_text : "";
584
+ const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
585
+ id: item.call_id ?? item.id ?? generateToolCallId(),
586
+ name: item.name,
587
+ args: (() => {
588
+ try {
589
+ return JSON.parse(item.arguments ?? "{}");
590
+ } catch {
591
+ return {};
498
592
  }
499
- }
500
- if (request.systemPrompt) {
501
- systemInstruction = request.systemPrompt;
593
+ })()
594
+ })) : [];
595
+ return {
596
+ content,
597
+ toolCalls,
598
+ usage: response?.usage ? {
599
+ promptTokens: response.usage.input_tokens ?? 0,
600
+ completionTokens: response.usage.output_tokens ?? 0,
601
+ totalTokens: response.usage.total_tokens ?? (response.usage.input_tokens ?? 0) + (response.usage.output_tokens ?? 0)
602
+ } : void 0,
603
+ rawResponse: response
604
+ };
605
+ }
606
+ async completeWithResponses(request) {
607
+ const client = await this.getClient();
608
+ const openaiToolOptions = request.providerToolOptions?.openai;
609
+ const payload = {
610
+ model: request.config?.model || this.model,
611
+ instructions: request.systemPrompt,
612
+ input: this.buildResponsesInput(request),
613
+ tools: this.buildResponsesTools(request.toolDefinitions ?? []),
614
+ tool_choice: openaiToolOptions?.toolChoice === "required" ? "required" : openaiToolOptions?.toolChoice === "auto" ? "auto" : void 0,
615
+ parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
616
+ temperature: request.config?.temperature ?? this.config.temperature,
617
+ max_output_tokens: request.config?.maxTokens ?? this.config.maxTokens,
618
+ stream: false
619
+ };
620
+ logProviderPayload("openai", "request payload", payload, request.debug);
621
+ const response = await client.responses.create(payload);
622
+ logProviderPayload("openai", "response payload", response, request.debug);
623
+ return this.parseResponsesResult(response);
624
+ }
625
+ async *stream(request) {
626
+ if (this.shouldUseResponsesApi(request)) {
627
+ const messageId2 = generateMessageId();
628
+ yield { type: "message:start", id: messageId2 };
629
+ try {
630
+ const result = await this.completeWithResponses(request);
631
+ if (result.content) {
632
+ yield { type: "message:delta", content: result.content };
633
+ }
634
+ for (const toolCall of result.toolCalls) {
635
+ yield {
636
+ type: "action:start",
637
+ id: toolCall.id,
638
+ name: toolCall.name
639
+ };
640
+ yield {
641
+ type: "action:args",
642
+ id: toolCall.id,
643
+ args: JSON.stringify(toolCall.args)
644
+ };
645
+ }
646
+ yield { type: "message:end" };
647
+ yield {
648
+ type: "done",
649
+ usage: result.usage ? {
650
+ prompt_tokens: result.usage.promptTokens,
651
+ completion_tokens: result.usage.completionTokens,
652
+ total_tokens: result.usage.totalTokens
653
+ } : void 0
654
+ };
655
+ return;
656
+ } catch (error) {
657
+ yield {
658
+ type: "error",
659
+ message: error instanceof Error ? error.message : "Unknown error",
660
+ code: "OPENAI_RESPONSES_ERROR"
661
+ };
662
+ return;
502
663
  }
503
664
  }
504
- if (contents.length === 0 || contents[0].role !== "user") {
505
- contents = [{ role: "user", parts: [{ text: "" }] }, ...contents];
506
- }
507
- const mergedContents = [];
508
- for (const content of contents) {
509
- const last = mergedContents[mergedContents.length - 1];
510
- if (last && last.role === content.role) {
511
- last.parts.push(...content.parts);
665
+ const client = await this.getClient();
666
+ let messages;
667
+ if (request.rawMessages && request.rawMessages.length > 0) {
668
+ const processedMessages = request.rawMessages.map((msg) => {
669
+ if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
670
+ return { ...msg, content: null };
671
+ }
672
+ const hasAttachments = msg.attachments && Array.isArray(msg.attachments) && msg.attachments.length > 0;
673
+ if (hasAttachments) {
674
+ const content = [];
675
+ if (msg.content) {
676
+ content.push({ type: "text", text: msg.content });
677
+ }
678
+ for (const attachment of msg.attachments) {
679
+ if (attachment.type === "image") {
680
+ let imageUrl;
681
+ if (attachment.url) {
682
+ imageUrl = attachment.url;
683
+ } else if (attachment.data) {
684
+ imageUrl = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType || "image/png"};base64,${attachment.data}`;
685
+ } else {
686
+ continue;
687
+ }
688
+ content.push({
689
+ type: "image_url",
690
+ image_url: { url: imageUrl, detail: "auto" }
691
+ });
692
+ }
693
+ }
694
+ return { ...msg, content, attachments: void 0 };
695
+ }
696
+ return msg;
697
+ });
698
+ if (request.systemPrompt) {
699
+ const hasSystem = processedMessages.some((m) => m.role === "system");
700
+ if (!hasSystem) {
701
+ messages = [
702
+ { role: "system", content: request.systemPrompt },
703
+ ...processedMessages
704
+ ];
705
+ } else {
706
+ messages = processedMessages;
707
+ }
512
708
  } else {
513
- mergedContents.push({ ...content, parts: [...content.parts] });
709
+ messages = processedMessages;
514
710
  }
711
+ } else {
712
+ messages = formatMessagesForOpenAI(
713
+ request.messages,
714
+ request.systemPrompt
715
+ );
515
716
  }
516
- const functionTools = formatToolsForGemini(request.actions);
517
- const toolsArray = [];
518
- if (functionTools) {
519
- toolsArray.push(functionTools);
520
- }
717
+ const tools = request.actions?.length ? formatTools(request.actions) : [];
718
+ const webSearchConfig = request.webSearch ?? this.config.webSearch;
521
719
  if (webSearchConfig) {
522
- toolsArray.push({
523
- google_search: {}
524
- });
720
+ const webSearchTool = {
721
+ type: "web_search_preview"
722
+ };
723
+ const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
724
+ if (wsConfig.userLocation) {
725
+ webSearchTool.search_context_size = "medium";
726
+ }
727
+ tools.push(webSearchTool);
525
728
  }
526
729
  const messageId = generateMessageId();
527
730
  yield { type: "message:start", id: messageId };
528
731
  try {
529
- logProviderPayload(
530
- "google",
531
- "request payload",
532
- {
533
- model: modelId,
534
- history: mergedContents.slice(0, -1),
535
- systemInstruction: systemInstruction ? { parts: [{ text: systemInstruction }] } : void 0,
536
- tools: toolsArray.length > 0 ? toolsArray : void 0,
537
- generationConfig: {
538
- temperature: request.config?.temperature ?? this.config.temperature,
539
- maxOutputTokens: request.config?.maxTokens ?? this.config.maxTokens
540
- },
541
- messageParts: mergedContents[mergedContents.length - 1]?.parts
542
- },
543
- request.debug
544
- );
545
- const chat = model.startChat({
546
- history: mergedContents.slice(0, -1),
547
- // All but the last message
548
- systemInstruction: systemInstruction ? { parts: [{ text: systemInstruction }] } : void 0,
549
- tools: toolsArray.length > 0 ? toolsArray : void 0,
550
- generationConfig: {
551
- temperature: request.config?.temperature ?? this.config.temperature,
552
- maxOutputTokens: request.config?.maxTokens ?? this.config.maxTokens
732
+ const openaiToolOptions = request.providerToolOptions?.openai;
733
+ const toolChoice = openaiToolOptions?.toolChoice && typeof openaiToolOptions.toolChoice === "object" ? {
734
+ type: "function",
735
+ function: {
736
+ name: openaiToolOptions.toolChoice.name
553
737
  }
554
- });
555
- const lastMessage = mergedContents[mergedContents.length - 1];
556
- const result = await chat.sendMessageStream(lastMessage.parts);
738
+ } : openaiToolOptions?.toolChoice;
739
+ const payload = {
740
+ model: request.config?.model || this.model,
741
+ messages,
742
+ tools: tools.length > 0 ? tools : void 0,
743
+ tool_choice: tools.length > 0 ? toolChoice : void 0,
744
+ parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
745
+ temperature: request.config?.temperature ?? this.config.temperature,
746
+ max_tokens: request.config?.maxTokens ?? this.config.maxTokens,
747
+ stream: true,
748
+ stream_options: { include_usage: true }
749
+ };
750
+ logProviderPayload("openai", "request payload", payload, request.debug);
751
+ const stream = await client.chat.completions.create(payload);
557
752
  let currentToolCall = null;
558
753
  const collectedCitations = [];
559
- for await (const chunk of result.stream) {
560
- logProviderPayload("google", "stream chunk", chunk, request.debug);
754
+ let citationIndex = 0;
755
+ let usage;
756
+ for await (const chunk of stream) {
757
+ logProviderPayload("openai", "stream chunk", chunk, request.debug);
561
758
  if (request.signal?.aborted) {
562
759
  break;
563
760
  }
564
- const candidate = chunk.candidates?.[0];
565
- if (!candidate?.content?.parts) continue;
566
- for (const part of candidate.content.parts) {
567
- if ("text" in part && part.text) {
568
- yield { type: "message:delta", content: part.text };
761
+ const delta = chunk.choices[0]?.delta;
762
+ const choice = chunk.choices[0];
763
+ if (delta?.content) {
764
+ yield { type: "message:delta", content: delta.content };
765
+ }
766
+ const annotations = delta?.annotations;
767
+ if (annotations && annotations.length > 0) {
768
+ for (const annotation of annotations) {
769
+ if (annotation.type === "url_citation" && annotation.url_citation?.url) {
770
+ citationIndex++;
771
+ const url = annotation.url_citation.url;
772
+ const domain = extractDomain(url);
773
+ collectedCitations.push({
774
+ index: citationIndex,
775
+ url,
776
+ title: annotation.url_citation.title || domain,
777
+ domain,
778
+ favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
779
+ });
780
+ }
569
781
  }
570
- if ("functionCall" in part && part.functionCall) {
571
- const fc = part.functionCall;
572
- const toolId = generateToolCallId();
573
- if (currentToolCall) {
782
+ }
783
+ if (delta?.tool_calls) {
784
+ for (const toolCall of delta.tool_calls) {
785
+ if (toolCall.id) {
786
+ if (currentToolCall) {
787
+ yield {
788
+ type: "action:args",
789
+ id: currentToolCall.id,
790
+ args: currentToolCall.arguments
791
+ };
792
+ }
793
+ const tcExtraContent = toolCall.extra_content;
794
+ currentToolCall = {
795
+ id: toolCall.id,
796
+ name: toolCall.function?.name || "",
797
+ arguments: toolCall.function?.arguments || "",
798
+ ...tcExtraContent ? { extra_content: tcExtraContent } : {}
799
+ };
574
800
  yield {
575
- type: "action:args",
801
+ type: "action:start",
576
802
  id: currentToolCall.id,
577
- args: JSON.stringify(currentToolCall.args)
803
+ name: currentToolCall.name,
804
+ ...currentToolCall.extra_content ? { extra_content: currentToolCall.extra_content } : {}
578
805
  };
806
+ } else if (currentToolCall && toolCall.function?.arguments) {
807
+ currentToolCall.arguments += toolCall.function.arguments;
579
808
  }
580
- currentToolCall = {
581
- id: toolId,
582
- name: fc.name,
583
- args: fc.args || {}
584
- };
585
- yield {
586
- type: "action:start",
587
- id: toolId,
588
- name: fc.name
589
- };
590
809
  }
591
810
  }
592
- if (candidate.finishReason) {
811
+ if (chunk.usage) {
812
+ usage = {
813
+ prompt_tokens: chunk.usage.prompt_tokens,
814
+ completion_tokens: chunk.usage.completion_tokens,
815
+ total_tokens: chunk.usage.total_tokens
816
+ };
817
+ }
818
+ if (choice?.finish_reason) {
593
819
  if (currentToolCall) {
594
820
  yield {
595
821
  type: "action:args",
596
822
  id: currentToolCall.id,
597
- args: JSON.stringify(currentToolCall.args)
823
+ args: currentToolCall.arguments
598
824
  };
599
825
  }
600
826
  }
601
- const groundingMetadata = candidate?.groundingMetadata;
602
- if (groundingMetadata?.groundingChunks) {
603
- for (const chunk2 of groundingMetadata.groundingChunks) {
604
- if (chunk2.web?.uri) {
605
- const url = chunk2.web.uri;
606
- const domain = extractDomain(url);
607
- if (!collectedCitations.some((c) => c.url === url)) {
608
- collectedCitations.push({
609
- index: collectedCitations.length + 1,
610
- url,
611
- title: chunk2.web.title || domain,
612
- domain,
613
- favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
614
- });
615
- }
616
- }
617
- }
618
- }
619
- }
620
- let usage;
621
- try {
622
- const response = await result.response;
623
- logProviderPayload(
624
- "google",
625
- "response payload",
626
- response,
627
- request.debug
628
- );
629
- if (response.usageMetadata) {
630
- usage = {
631
- prompt_tokens: response.usageMetadata.promptTokenCount || 0,
632
- completion_tokens: response.usageMetadata.candidatesTokenCount || 0,
633
- total_tokens: response.usageMetadata.totalTokenCount || 0
634
- };
635
- }
636
- const finalCandidate = response.candidates?.[0];
637
- const finalGrounding = finalCandidate?.groundingMetadata;
638
- if (finalGrounding?.groundingChunks) {
639
- for (const chunk of finalGrounding.groundingChunks) {
640
- if (chunk.web?.uri) {
641
- const url = chunk.web.uri;
642
- const domain = extractDomain(url);
643
- if (!collectedCitations.some((c) => c.url === url)) {
644
- collectedCitations.push({
645
- index: collectedCitations.length + 1,
646
- url,
647
- title: chunk.web.title || domain,
648
- domain,
649
- favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
650
- });
651
- }
652
- }
653
- }
654
- }
655
- } catch {
656
827
  }
657
828
  if (collectedCitations.length > 0) {
658
- yield { type: "citation", citations: collectedCitations };
829
+ const uniqueCitations = deduplicateCitations(collectedCitations);
830
+ yield { type: "citation", citations: uniqueCitations };
659
831
  }
660
832
  yield { type: "message:end" };
661
833
  yield { type: "done", usage };
@@ -663,100 +835,83 @@ var GoogleAdapter = class {
663
835
  yield {
664
836
  type: "error",
665
837
  message: error instanceof Error ? error.message : "Unknown error",
666
- code: "GOOGLE_ERROR"
838
+ code: `${this.provider.toUpperCase()}_ERROR`
667
839
  };
668
840
  }
669
841
  }
670
- /**
671
- * Non-streaming completion (optional, for debugging)
672
- */
673
842
  async complete(request) {
674
- const client = await this.getClient();
675
- const modelId = request.config?.model || this.model;
676
- const model = client.getGenerativeModel({
677
- model: modelId,
678
- safetySettings: this.config.safetySettings
679
- });
680
- let contents = [];
681
- let systemInstruction;
682
- for (const msg of request.messages) {
683
- if (msg.role === "system") {
684
- systemInstruction = (systemInstruction || "") + (msg.content || "");
685
- continue;
686
- }
687
- const content = messageToGeminiContent(msg);
688
- if (content) {
689
- contents.push(content);
690
- }
691
- }
692
- if (request.systemPrompt) {
693
- systemInstruction = request.systemPrompt;
694
- }
695
- if (contents.length === 0 || contents[0].role !== "user") {
696
- contents = [{ role: "user", parts: [{ text: "" }] }, ...contents];
843
+ if (this.shouldUseResponsesApi(request)) {
844
+ return this.completeWithResponses(request);
697
845
  }
698
- const mergedContents = [];
699
- for (const content of contents) {
700
- const last = mergedContents[mergedContents.length - 1];
701
- if (last && last.role === content.role) {
702
- last.parts.push(...content.parts);
846
+ const client = await this.getClient();
847
+ let messages;
848
+ if (request.rawMessages && request.rawMessages.length > 0) {
849
+ const sanitized = request.rawMessages.map((msg) => {
850
+ if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
851
+ return { ...msg, content: null };
852
+ }
853
+ return msg;
854
+ });
855
+ if (request.systemPrompt && !sanitized.some((message2) => message2.role === "system")) {
856
+ messages = [
857
+ { role: "system", content: request.systemPrompt },
858
+ ...sanitized
859
+ ];
703
860
  } else {
704
- mergedContents.push({ ...content, parts: [...content.parts] });
861
+ messages = sanitized;
705
862
  }
863
+ } else {
864
+ messages = formatMessagesForOpenAI(
865
+ request.messages,
866
+ request.systemPrompt
867
+ );
706
868
  }
707
- const tools = formatToolsForGemini(request.actions);
869
+ const tools = request.actions?.length ? formatTools(request.actions) : [];
870
+ const openaiToolOptions = request.providerToolOptions?.openai;
871
+ const toolChoice = openaiToolOptions?.toolChoice && typeof openaiToolOptions.toolChoice === "object" ? {
872
+ type: "function",
873
+ function: {
874
+ name: openaiToolOptions.toolChoice.name
875
+ }
876
+ } : openaiToolOptions?.toolChoice;
708
877
  const payload = {
709
- model: modelId,
710
- history: mergedContents.slice(0, -1),
711
- systemInstruction: systemInstruction ? { parts: [{ text: systemInstruction }] } : void 0,
712
- tools: tools ? [tools] : void 0,
713
- generationConfig: {
714
- temperature: request.config?.temperature ?? this.config.temperature,
715
- maxOutputTokens: request.config?.maxTokens ?? this.config.maxTokens
716
- },
717
- messageParts: mergedContents[mergedContents.length - 1]?.parts
878
+ model: request.config?.model || this.model,
879
+ messages,
880
+ tools: tools.length > 0 ? tools : void 0,
881
+ tool_choice: tools.length > 0 ? toolChoice : void 0,
882
+ parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
883
+ temperature: request.config?.temperature ?? this.config.temperature,
884
+ max_tokens: request.config?.maxTokens ?? this.config.maxTokens,
885
+ stream: false
718
886
  };
719
- logProviderPayload("google", "request payload", payload, request.debug);
720
- const chat = model.startChat({
721
- history: mergedContents.slice(0, -1),
722
- systemInstruction: systemInstruction ? { parts: [{ text: systemInstruction }] } : void 0,
723
- tools: tools ? [tools] : void 0,
724
- generationConfig: {
725
- temperature: request.config?.temperature ?? this.config.temperature,
726
- maxOutputTokens: request.config?.maxTokens ?? this.config.maxTokens
727
- }
728
- });
729
- const lastMessage = mergedContents[mergedContents.length - 1];
730
- const result = await chat.sendMessage(lastMessage.parts);
731
- const response = result.response;
732
- logProviderPayload("google", "response payload", response, request.debug);
733
- let textContent = "";
734
- const toolCalls = [];
735
- const candidate = response.candidates?.[0];
736
- if (candidate?.content?.parts) {
737
- for (const part of candidate.content.parts) {
738
- if ("text" in part && part.text) {
739
- textContent += part.text;
740
- }
741
- if ("functionCall" in part && part.functionCall) {
742
- toolCalls.push({
743
- id: generateToolCallId(),
744
- name: part.functionCall.name,
745
- args: part.functionCall.args || {}
746
- });
747
- }
748
- }
749
- }
887
+ logProviderPayload("openai", "request payload", payload, request.debug);
888
+ const response = await client.chat.completions.create(payload);
889
+ logProviderPayload("openai", "response payload", response, request.debug);
890
+ const choice = response.choices?.[0];
891
+ const message = choice?.message;
750
892
  return {
751
- content: textContent,
752
- toolCalls,
893
+ content: message?.content ?? "",
894
+ toolCalls: message?.tool_calls?.map((toolCall) => ({
895
+ id: toolCall.id ?? generateToolCallId(),
896
+ name: toolCall.function?.name ?? "",
897
+ args: (() => {
898
+ try {
899
+ return JSON.parse(toolCall.function?.arguments ?? "{}");
900
+ } catch {
901
+ return {};
902
+ }
903
+ })(),
904
+ ...toolCall.extra_content ? { extra_content: toolCall.extra_content } : {}
905
+ })) ?? [],
906
+ usage: response.usage ? {
907
+ promptTokens: response.usage.prompt_tokens,
908
+ completionTokens: response.usage.completion_tokens,
909
+ totalTokens: response.usage.total_tokens
910
+ } : void 0,
753
911
  rawResponse: response
754
912
  };
755
913
  }
756
914
  };
757
- function createGoogleAdapter(config) {
758
- return new GoogleAdapter(config);
759
- }
760
915
  function extractDomain(url) {
761
916
  try {
762
917
  const parsed = new URL(url);
@@ -765,6 +920,20 @@ function extractDomain(url) {
765
920
  return "";
766
921
  }
767
922
  }
923
+ function deduplicateCitations(citations) {
924
+ const seen = /* @__PURE__ */ new Map();
925
+ let index = 0;
926
+ for (const citation of citations) {
927
+ if (!seen.has(citation.url)) {
928
+ index++;
929
+ seen.set(citation.url, { ...citation, index });
930
+ }
931
+ }
932
+ return Array.from(seen.values());
933
+ }
934
+ function createOpenAIAdapter(config) {
935
+ return new OpenAIAdapter(config);
936
+ }
768
937
 
769
938
  // src/providers/types.ts
770
939
  function createCallableProvider(providerFn, properties) {
@@ -783,6 +952,7 @@ function createCallableProvider(providerFn, properties) {
783
952
  }
784
953
 
785
954
  // src/providers/google/index.ts
955
+ var GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/";
786
956
  var GOOGLE_MODELS2 = {
787
957
  // Gemini 2.0 series (latest)
788
958
  "gemini-2.0-flash": {
@@ -868,6 +1038,25 @@ var GOOGLE_MODELS2 = {
868
1038
  maxTokens: 1e6,
869
1039
  outputTokens: 8192
870
1040
  },
1041
+ // Gemini 3 series (thinking models)
1042
+ "gemini-3.1-flash-lite-preview": {
1043
+ vision: true,
1044
+ tools: true,
1045
+ audio: false,
1046
+ video: false,
1047
+ pdf: true,
1048
+ maxTokens: 1e6,
1049
+ outputTokens: 32768
1050
+ },
1051
+ "gemini-3.1-flash-preview": {
1052
+ vision: true,
1053
+ tools: true,
1054
+ audio: false,
1055
+ video: false,
1056
+ pdf: true,
1057
+ maxTokens: 1e6,
1058
+ outputTokens: 32768
1059
+ },
871
1060
  // Gemini 1.0 series (legacy)
872
1061
  "gemini-1.0-pro": {
873
1062
  vision: false,
@@ -882,11 +1071,10 @@ var GOOGLE_MODELS2 = {
882
1071
  function createGoogle(config = {}) {
883
1072
  const apiKey = config.apiKey ?? process.env.GOOGLE_API_KEY ?? "";
884
1073
  const providerFn = (modelId) => {
885
- return createGoogleAdapter({
1074
+ return createOpenAIAdapter({
886
1075
  apiKey,
887
1076
  model: modelId,
888
- baseUrl: config.baseUrl,
889
- safetySettings: config.safetySettings
1077
+ baseUrl: config.baseUrl || GEMINI_BASE_URL
890
1078
  });
891
1079
  };
892
1080
  const getCapabilities = (modelId) => {