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