@yourgpt/llm-sdk 2.0.1 → 2.0.2-beta.2

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 (55) hide show
  1. package/dist/adapters/index.d.mts +27 -4
  2. package/dist/adapters/index.d.ts +27 -4
  3. package/dist/adapters/index.js +395 -25
  4. package/dist/adapters/index.js.map +1 -1
  5. package/dist/adapters/index.mjs +395 -25
  6. package/dist/adapters/index.mjs.map +1 -1
  7. package/dist/index.d.mts +24 -5
  8. package/dist/index.d.ts +24 -5
  9. package/dist/index.js +20 -4
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +20 -4
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/providers/anthropic/index.d.mts +1 -2
  14. package/dist/providers/anthropic/index.d.ts +1 -2
  15. package/dist/providers/anthropic/index.js +108 -12
  16. package/dist/providers/anthropic/index.js.map +1 -1
  17. package/dist/providers/anthropic/index.mjs +108 -12
  18. package/dist/providers/anthropic/index.mjs.map +1 -1
  19. package/dist/providers/azure/index.d.mts +1 -2
  20. package/dist/providers/azure/index.d.ts +1 -2
  21. package/dist/providers/azure/index.js.map +1 -1
  22. package/dist/providers/azure/index.mjs.map +1 -1
  23. package/dist/providers/google/index.d.mts +1 -2
  24. package/dist/providers/google/index.d.ts +1 -2
  25. package/dist/providers/google/index.js +61 -2
  26. package/dist/providers/google/index.js.map +1 -1
  27. package/dist/providers/google/index.mjs +61 -2
  28. package/dist/providers/google/index.mjs.map +1 -1
  29. package/dist/providers/ollama/index.d.mts +8 -3
  30. package/dist/providers/ollama/index.d.ts +8 -3
  31. package/dist/providers/ollama/index.js +227 -17
  32. package/dist/providers/ollama/index.js.map +1 -1
  33. package/dist/providers/ollama/index.mjs +227 -17
  34. package/dist/providers/ollama/index.mjs.map +1 -1
  35. package/dist/providers/openai/index.d.mts +1 -2
  36. package/dist/providers/openai/index.d.ts +1 -2
  37. package/dist/providers/openai/index.js +57 -3
  38. package/dist/providers/openai/index.js.map +1 -1
  39. package/dist/providers/openai/index.mjs +57 -3
  40. package/dist/providers/openai/index.mjs.map +1 -1
  41. package/dist/providers/openrouter/index.d.mts +56 -3
  42. package/dist/providers/openrouter/index.d.ts +56 -3
  43. package/dist/providers/openrouter/index.js +90 -276
  44. package/dist/providers/openrouter/index.js.map +1 -1
  45. package/dist/providers/openrouter/index.mjs +89 -277
  46. package/dist/providers/openrouter/index.mjs.map +1 -1
  47. package/dist/providers/xai/index.d.mts +1 -2
  48. package/dist/providers/xai/index.d.ts +1 -2
  49. package/dist/providers/xai/index.js.map +1 -1
  50. package/dist/providers/xai/index.mjs.map +1 -1
  51. package/dist/{base-DdxolpKP.d.mts → types-C_f95PKp.d.mts} +434 -3
  52. package/dist/{base-DdxolpKP.d.ts → types-C_f95PKp.d.ts} +434 -3
  53. package/package.json +1 -1
  54. package/dist/types-Ck25ZYma.d.mts +0 -323
  55. package/dist/types-Dsz8SpdB.d.ts +0 -323
@@ -359,29 +359,60 @@ var OpenAIAdapter = class {
359
359
  request.systemPrompt
360
360
  );
361
361
  }
362
- const tools = request.actions?.length ? formatTools(request.actions) : void 0;
362
+ const tools = request.actions?.length ? formatTools(request.actions) : [];
363
+ const webSearchConfig = request.webSearch ?? this.config.webSearch;
364
+ if (webSearchConfig) {
365
+ const webSearchTool = {
366
+ type: "web_search_preview"
367
+ };
368
+ const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
369
+ if (wsConfig.userLocation) {
370
+ webSearchTool.search_context_size = "medium";
371
+ }
372
+ tools.push(webSearchTool);
373
+ }
363
374
  const messageId = generateMessageId();
364
375
  yield { type: "message:start", id: messageId };
365
376
  try {
366
377
  const stream = await client.chat.completions.create({
367
378
  model: request.config?.model || this.model,
368
379
  messages,
369
- tools,
380
+ tools: tools.length > 0 ? tools : void 0,
370
381
  temperature: request.config?.temperature ?? this.config.temperature,
371
382
  max_tokens: request.config?.maxTokens ?? this.config.maxTokens,
372
383
  stream: true,
373
384
  stream_options: { include_usage: true }
374
385
  });
375
386
  let currentToolCall = null;
387
+ const collectedCitations = [];
388
+ let citationIndex = 0;
376
389
  let usage;
377
390
  for await (const chunk of stream) {
378
391
  if (request.signal?.aborted) {
379
392
  break;
380
393
  }
381
394
  const delta = chunk.choices[0]?.delta;
395
+ const choice = chunk.choices[0];
382
396
  if (delta?.content) {
383
397
  yield { type: "message:delta", content: delta.content };
384
398
  }
399
+ const annotations = delta?.annotations;
400
+ if (annotations && annotations.length > 0) {
401
+ for (const annotation of annotations) {
402
+ if (annotation.type === "url_citation" && annotation.url_citation?.url) {
403
+ citationIndex++;
404
+ const url = annotation.url_citation.url;
405
+ const domain = extractDomain(url);
406
+ collectedCitations.push({
407
+ index: citationIndex,
408
+ url,
409
+ title: annotation.url_citation.title || domain,
410
+ domain,
411
+ favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
412
+ });
413
+ }
414
+ }
415
+ }
385
416
  if (delta?.tool_calls) {
386
417
  for (const toolCall of delta.tool_calls) {
387
418
  if (toolCall.id) {
@@ -414,7 +445,7 @@ var OpenAIAdapter = class {
414
445
  total_tokens: chunk.usage.total_tokens
415
446
  };
416
447
  }
417
- if (chunk.choices[0]?.finish_reason) {
448
+ if (choice?.finish_reason) {
418
449
  if (currentToolCall) {
419
450
  yield {
420
451
  type: "action:args",
@@ -424,6 +455,10 @@ var OpenAIAdapter = class {
424
455
  }
425
456
  }
426
457
  }
458
+ if (collectedCitations.length > 0) {
459
+ const uniqueCitations = deduplicateCitations(collectedCitations);
460
+ yield { type: "citation", citations: uniqueCitations };
461
+ }
427
462
  yield { type: "message:end" };
428
463
  yield { type: "done", usage };
429
464
  } catch (error) {
@@ -435,6 +470,25 @@ var OpenAIAdapter = class {
435
470
  }
436
471
  }
437
472
  };
473
+ function extractDomain(url) {
474
+ try {
475
+ const parsed = new URL(url);
476
+ return parsed.hostname;
477
+ } catch {
478
+ return "";
479
+ }
480
+ }
481
+ function deduplicateCitations(citations) {
482
+ const seen = /* @__PURE__ */ new Map();
483
+ let index = 0;
484
+ for (const citation of citations) {
485
+ if (!seen.has(citation.url)) {
486
+ index++;
487
+ seen.set(citation.url, { ...citation, index });
488
+ }
489
+ }
490
+ return Array.from(seen.values());
491
+ }
438
492
  function createOpenAIAdapter(config) {
439
493
  return new OpenAIAdapter(config);
440
494
  }
@@ -633,14 +687,45 @@ var AnthropicAdapter = class {
633
687
  ) : {},
634
688
  required: action.parameters ? Object.entries(action.parameters).filter(([, param]) => param.required).map(([key]) => key) : []
635
689
  }
636
- }));
690
+ })) || [];
691
+ const webSearchConfig = request.webSearch ?? this.config.webSearch;
692
+ let serverToolConfiguration;
693
+ if (webSearchConfig) {
694
+ tools.push({
695
+ type: "web_search_20260209",
696
+ name: "web_search",
697
+ allowed_callers: ["direct"]
698
+ });
699
+ const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
700
+ const webSearchToolConfig = {};
701
+ if (wsConfig.maxUses !== void 0) {
702
+ webSearchToolConfig.max_uses = wsConfig.maxUses;
703
+ }
704
+ if (wsConfig.allowedDomains && wsConfig.allowedDomains.length > 0) {
705
+ webSearchToolConfig.allowed_domains = wsConfig.allowedDomains;
706
+ }
707
+ if (wsConfig.blockedDomains && wsConfig.blockedDomains.length > 0) {
708
+ webSearchToolConfig.blocked_domains = wsConfig.blockedDomains;
709
+ }
710
+ if (wsConfig.userLocation) {
711
+ webSearchToolConfig.user_location = wsConfig.userLocation;
712
+ }
713
+ if (Object.keys(webSearchToolConfig).length > 0) {
714
+ serverToolConfiguration = {
715
+ web_search: webSearchToolConfig
716
+ };
717
+ }
718
+ }
637
719
  const options = {
638
720
  model: request.config?.model || this.model,
639
721
  max_tokens: request.config?.maxTokens || this.config.maxTokens || 4096,
640
722
  system: systemMessage,
641
723
  messages,
642
- tools: tools?.length ? tools : void 0
724
+ tools: tools.length ? tools : void 0
643
725
  };
726
+ if (serverToolConfiguration) {
727
+ options.server_tool_configuration = serverToolConfiguration;
728
+ }
644
729
  if (this.config.thinking?.type === "enabled") {
645
730
  options.thinking = {
646
731
  type: "enabled",
@@ -696,6 +781,8 @@ var AnthropicAdapter = class {
696
781
  const stream = await client.messages.stream(options);
697
782
  let currentToolUse = null;
698
783
  let isInThinkingBlock = false;
784
+ const collectedCitations = [];
785
+ let citationIndex = 0;
699
786
  let usage;
700
787
  for await (const event of stream) {
701
788
  if (request.signal?.aborted) {
@@ -724,11 +811,30 @@ var AnthropicAdapter = class {
724
811
  name: event.content_block.name,
725
812
  input: ""
726
813
  };
727
- yield {
728
- type: "action:start",
729
- id: currentToolUse.id,
730
- name: currentToolUse.name
731
- };
814
+ if (currentToolUse.name !== "web_search") {
815
+ yield {
816
+ type: "action:start",
817
+ id: currentToolUse.id,
818
+ name: currentToolUse.name
819
+ };
820
+ }
821
+ } else if (event.content_block.type === "web_search_tool_result") {
822
+ const webSearchResult = event.content_block;
823
+ if (webSearchResult.content && Array.isArray(webSearchResult.content)) {
824
+ for (const result of webSearchResult.content) {
825
+ if (result.type === "web_search_result" && result.url) {
826
+ citationIndex++;
827
+ const domain = extractDomain2(result.url);
828
+ collectedCitations.push({
829
+ index: citationIndex,
830
+ url: result.url,
831
+ title: result.title || domain,
832
+ domain,
833
+ favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
834
+ });
835
+ }
836
+ }
837
+ }
732
838
  } else if (event.content_block.type === "thinking") {
733
839
  isInThinkingBlock = true;
734
840
  yield { type: "thinking:start" };
@@ -737,6 +843,20 @@ var AnthropicAdapter = class {
737
843
  case "content_block_delta":
738
844
  if (event.delta.type === "text_delta") {
739
845
  yield { type: "message:delta", content: event.delta.text };
846
+ } else if (event.delta.type === "citations_delta") {
847
+ const citationsDelta = event.delta;
848
+ if (citationsDelta.citation?.url) {
849
+ citationIndex++;
850
+ const domain = extractDomain2(citationsDelta.citation.url);
851
+ collectedCitations.push({
852
+ index: citationIndex,
853
+ url: citationsDelta.citation.url,
854
+ title: citationsDelta.citation.title || domain,
855
+ citedText: citationsDelta.citation.cited_text,
856
+ domain,
857
+ favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
858
+ });
859
+ }
740
860
  } else if (event.delta.type === "thinking_delta") {
741
861
  yield { type: "thinking:delta", content: event.delta.thinking };
742
862
  } else if (event.delta.type === "input_json_delta" && currentToolUse) {
@@ -745,11 +865,18 @@ var AnthropicAdapter = class {
745
865
  break;
746
866
  case "content_block_stop":
747
867
  if (currentToolUse) {
748
- yield {
749
- type: "action:args",
750
- id: currentToolUse.id,
751
- args: currentToolUse.input
752
- };
868
+ if (currentToolUse.name !== "web_search") {
869
+ yield {
870
+ type: "action:args",
871
+ id: currentToolUse.id,
872
+ args: currentToolUse.input
873
+ };
874
+ yield {
875
+ type: "action:end",
876
+ id: currentToolUse.id,
877
+ name: currentToolUse.name
878
+ };
879
+ }
753
880
  currentToolUse = null;
754
881
  }
755
882
  if (isInThinkingBlock) {
@@ -761,6 +888,10 @@ var AnthropicAdapter = class {
761
888
  break;
762
889
  }
763
890
  }
891
+ if (collectedCitations.length > 0) {
892
+ const uniqueCitations = deduplicateCitations2(collectedCitations);
893
+ yield { type: "citation", citations: uniqueCitations };
894
+ }
764
895
  yield { type: "message:end" };
765
896
  yield { type: "done", usage };
766
897
  } catch (error) {
@@ -772,11 +903,136 @@ var AnthropicAdapter = class {
772
903
  }
773
904
  }
774
905
  };
906
+ function extractDomain2(url) {
907
+ try {
908
+ const parsed = new URL(url);
909
+ return parsed.hostname;
910
+ } catch {
911
+ return "";
912
+ }
913
+ }
914
+ function deduplicateCitations2(citations) {
915
+ const seen = /* @__PURE__ */ new Map();
916
+ let index = 0;
917
+ for (const citation of citations) {
918
+ if (!seen.has(citation.url)) {
919
+ index++;
920
+ seen.set(citation.url, { ...citation, index });
921
+ }
922
+ }
923
+ return Array.from(seen.values());
924
+ }
775
925
  function createAnthropicAdapter(config) {
776
926
  return new AnthropicAdapter(config);
777
927
  }
778
928
 
779
929
  // src/adapters/ollama.ts
930
+ function extractBase64Data(data) {
931
+ if (data.startsWith("data:")) {
932
+ const commaIndex = data.indexOf(",");
933
+ if (commaIndex !== -1) {
934
+ return data.slice(commaIndex + 1);
935
+ }
936
+ }
937
+ return data;
938
+ }
939
+ async function downloadImageAsBase64(url) {
940
+ try {
941
+ const response = await fetch(url);
942
+ if (!response.ok) return null;
943
+ const buffer = await response.arrayBuffer();
944
+ const base64 = Buffer.from(buffer).toString("base64");
945
+ return base64;
946
+ } catch {
947
+ return null;
948
+ }
949
+ }
950
+ async function extractImagesFromAttachments(attachments) {
951
+ if (!attachments) return [];
952
+ const images = [];
953
+ for (const attachment of attachments) {
954
+ if (attachment.type !== "image") continue;
955
+ if (attachment.data) {
956
+ images.push(extractBase64Data(attachment.data));
957
+ } else if (attachment.url) {
958
+ const base64 = await downloadImageAsBase64(attachment.url);
959
+ if (base64) {
960
+ images.push(base64);
961
+ }
962
+ }
963
+ }
964
+ return images;
965
+ }
966
+ async function formatMessagesForOllama(messages, systemPrompt) {
967
+ const formatted = [];
968
+ if (systemPrompt) {
969
+ formatted.push({ role: "system", content: systemPrompt });
970
+ }
971
+ for (const msg of messages) {
972
+ const baseMessage = {
973
+ role: msg.role,
974
+ content: msg.content ?? ""
975
+ };
976
+ if (msg.role === "user" && msg.metadata?.attachments) {
977
+ const images = await extractImagesFromAttachments(
978
+ msg.metadata.attachments
979
+ );
980
+ if (images.length > 0) {
981
+ baseMessage.images = images;
982
+ }
983
+ }
984
+ if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
985
+ baseMessage.tool_calls = msg.tool_calls.map((tc) => ({
986
+ id: tc.id,
987
+ function: {
988
+ name: tc.function.name,
989
+ arguments: JSON.parse(tc.function.arguments)
990
+ }
991
+ }));
992
+ }
993
+ if (msg.role === "tool" && msg.tool_call_id) {
994
+ baseMessage.tool_call_id = msg.tool_call_id;
995
+ }
996
+ formatted.push(baseMessage);
997
+ }
998
+ return formatted;
999
+ }
1000
+ async function processRawMessagesForOllama(rawMessages, systemPrompt) {
1001
+ const processed = [];
1002
+ if (systemPrompt) {
1003
+ const hasSystem = rawMessages.some((m) => m.role === "system");
1004
+ if (!hasSystem) {
1005
+ processed.push({ role: "system", content: systemPrompt });
1006
+ }
1007
+ }
1008
+ for (const msg of rawMessages) {
1009
+ const baseMessage = {
1010
+ role: msg.role,
1011
+ content: msg.content ?? ""
1012
+ };
1013
+ if (msg.role === "user" && msg.attachments && Array.isArray(msg.attachments)) {
1014
+ const attachments = msg.attachments;
1015
+ const images = await extractImagesFromAttachments(attachments);
1016
+ if (images.length > 0) {
1017
+ baseMessage.images = images;
1018
+ }
1019
+ }
1020
+ if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
1021
+ baseMessage.tool_calls = msg.tool_calls.map((tc) => ({
1022
+ id: tc.id,
1023
+ function: {
1024
+ name: tc.function.name,
1025
+ arguments: typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments
1026
+ }
1027
+ }));
1028
+ }
1029
+ if (msg.tool_call_id) {
1030
+ baseMessage.tool_call_id = msg.tool_call_id;
1031
+ }
1032
+ processed.push(baseMessage);
1033
+ }
1034
+ return processed;
1035
+ }
780
1036
  var OllamaAdapter = class {
781
1037
  constructor(config = {}) {
782
1038
  this.provider = "ollama";
@@ -785,10 +1041,32 @@ var OllamaAdapter = class {
785
1041
  this.baseUrl = config.baseUrl || "http://localhost:11434";
786
1042
  }
787
1043
  async *stream(request) {
788
- const messages = formatMessages(request.messages, request.systemPrompt);
1044
+ let messages;
1045
+ if (request.rawMessages && request.rawMessages.length > 0) {
1046
+ messages = await processRawMessagesForOllama(
1047
+ request.rawMessages,
1048
+ request.systemPrompt
1049
+ );
1050
+ } else {
1051
+ messages = await formatMessagesForOllama(
1052
+ request.messages,
1053
+ request.systemPrompt
1054
+ );
1055
+ }
1056
+ const tools = request.actions?.length ? formatTools(request.actions) : void 0;
789
1057
  const messageId = generateMessageId();
790
1058
  yield { type: "message:start", id: messageId };
791
1059
  try {
1060
+ const ollamaOptions = {
1061
+ temperature: request.config?.temperature ?? this.config.temperature
1062
+ };
1063
+ const maxTokens = request.config?.maxTokens ?? this.config.maxTokens;
1064
+ if (maxTokens !== void 0) {
1065
+ ollamaOptions.num_predict = maxTokens;
1066
+ }
1067
+ if (this.config.options) {
1068
+ Object.assign(ollamaOptions, this.config.options);
1069
+ }
792
1070
  const response = await fetch(`${this.baseUrl}/api/chat`, {
793
1071
  method: "POST",
794
1072
  headers: {
@@ -797,16 +1075,15 @@ var OllamaAdapter = class {
797
1075
  body: JSON.stringify({
798
1076
  model: request.config?.model || this.model,
799
1077
  messages,
1078
+ tools,
800
1079
  stream: true,
801
- options: {
802
- temperature: request.config?.temperature ?? this.config.temperature,
803
- num_predict: request.config?.maxTokens ?? this.config.maxTokens
804
- }
1080
+ options: ollamaOptions
805
1081
  }),
806
1082
  signal: request.signal
807
1083
  });
808
1084
  if (!response.ok) {
809
- throw new Error(`Ollama API error: ${response.status}`);
1085
+ const errorText = await response.text().catch(() => "Unknown error");
1086
+ throw new Error(`Ollama API error: ${response.status} - ${errorText}`);
810
1087
  }
811
1088
  if (!response.body) {
812
1089
  throw new Error("No response body");
@@ -814,6 +1091,8 @@ var OllamaAdapter = class {
814
1091
  const reader = response.body.getReader();
815
1092
  const decoder = new TextDecoder();
816
1093
  let buffer = "";
1094
+ let hasEmittedToolCalls = false;
1095
+ const toolCallsInProgress = /* @__PURE__ */ new Map();
817
1096
  while (true) {
818
1097
  const { done, value } = await reader.read();
819
1098
  if (done) break;
@@ -827,6 +1106,28 @@ var OllamaAdapter = class {
827
1106
  if (chunk.message?.content) {
828
1107
  yield { type: "message:delta", content: chunk.message.content };
829
1108
  }
1109
+ if (chunk.message?.tool_calls && !hasEmittedToolCalls) {
1110
+ for (let i = 0; i < chunk.message.tool_calls.length; i++) {
1111
+ const toolCall = chunk.message.tool_calls[i];
1112
+ const toolCallId = toolCall.id || generateToolCallId();
1113
+ toolCallsInProgress.set(i, {
1114
+ id: toolCallId,
1115
+ name: toolCall.function?.name || "",
1116
+ arguments: toolCall.function?.arguments || {}
1117
+ });
1118
+ yield {
1119
+ type: "action:start",
1120
+ id: toolCallId,
1121
+ name: toolCall.function?.name || ""
1122
+ };
1123
+ yield {
1124
+ type: "action:args",
1125
+ id: toolCallId,
1126
+ args: JSON.stringify(toolCall.function?.arguments || {})
1127
+ };
1128
+ }
1129
+ hasEmittedToolCalls = true;
1130
+ }
830
1131
  if (chunk.done) {
831
1132
  break;
832
1133
  }
@@ -840,10 +1141,20 @@ var OllamaAdapter = class {
840
1141
  if (error.name === "AbortError") {
841
1142
  yield { type: "done" };
842
1143
  } else {
1144
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1145
+ let code = "OLLAMA_ERROR";
1146
+ let message = errorMessage;
1147
+ if (errorMessage.includes("ECONNREFUSED") || errorMessage.includes("fetch failed")) {
1148
+ message = `Cannot connect to Ollama at ${this.baseUrl}. Make sure Ollama is running (ollama serve).`;
1149
+ code = "OLLAMA_CONNECTION_ERROR";
1150
+ } else if (errorMessage.includes("model") && errorMessage.includes("not found")) {
1151
+ message = `Model "${this.model}" not found. Pull it with: ollama pull ${this.model}`;
1152
+ code = "OLLAMA_MODEL_NOT_FOUND";
1153
+ }
843
1154
  yield {
844
1155
  type: "error",
845
- message: error instanceof Error ? error.message : "Unknown error",
846
- code: "OLLAMA_ERROR"
1156
+ message,
1157
+ code
847
1158
  };
848
1159
  }
849
1160
  }
@@ -984,6 +1295,7 @@ var GoogleAdapter = class {
984
1295
  async *stream(request) {
985
1296
  const client = await this.getClient();
986
1297
  const modelId = request.config?.model || this.model;
1298
+ const webSearchConfig = request.webSearch ?? this.config.webSearch;
987
1299
  const model = client.getGenerativeModel({
988
1300
  model: modelId,
989
1301
  safetySettings: this.config.safetySettings
@@ -1031,7 +1343,16 @@ var GoogleAdapter = class {
1031
1343
  mergedContents.push({ ...content, parts: [...content.parts] });
1032
1344
  }
1033
1345
  }
1034
- const tools = formatToolsForGemini(request.actions);
1346
+ const functionTools = formatToolsForGemini(request.actions);
1347
+ const toolsArray = [];
1348
+ if (functionTools) {
1349
+ toolsArray.push(functionTools);
1350
+ }
1351
+ if (webSearchConfig) {
1352
+ toolsArray.push({
1353
+ google_search: {}
1354
+ });
1355
+ }
1035
1356
  const messageId = generateMessageId();
1036
1357
  yield { type: "message:start", id: messageId };
1037
1358
  try {
@@ -1039,7 +1360,7 @@ var GoogleAdapter = class {
1039
1360
  history: mergedContents.slice(0, -1),
1040
1361
  // All but the last message
1041
1362
  systemInstruction: systemInstruction ? { parts: [{ text: systemInstruction }] } : void 0,
1042
- tools: tools ? [tools] : void 0,
1363
+ tools: toolsArray.length > 0 ? toolsArray : void 0,
1043
1364
  generationConfig: {
1044
1365
  temperature: request.config?.temperature ?? this.config.temperature,
1045
1366
  maxOutputTokens: request.config?.maxTokens ?? this.config.maxTokens
@@ -1048,6 +1369,7 @@ var GoogleAdapter = class {
1048
1369
  const lastMessage = mergedContents[mergedContents.length - 1];
1049
1370
  const result = await chat.sendMessageStream(lastMessage.parts);
1050
1371
  let currentToolCall = null;
1372
+ const collectedCitations = [];
1051
1373
  for await (const chunk of result.stream) {
1052
1374
  if (request.signal?.aborted) {
1053
1375
  break;
@@ -1089,6 +1411,24 @@ var GoogleAdapter = class {
1089
1411
  };
1090
1412
  }
1091
1413
  }
1414
+ const groundingMetadata = candidate?.groundingMetadata;
1415
+ if (groundingMetadata?.groundingChunks) {
1416
+ for (const chunk2 of groundingMetadata.groundingChunks) {
1417
+ if (chunk2.web?.uri) {
1418
+ const url = chunk2.web.uri;
1419
+ const domain = extractDomain3(url);
1420
+ if (!collectedCitations.some((c) => c.url === url)) {
1421
+ collectedCitations.push({
1422
+ index: collectedCitations.length + 1,
1423
+ url,
1424
+ title: chunk2.web.title || domain,
1425
+ domain,
1426
+ favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
1427
+ });
1428
+ }
1429
+ }
1430
+ }
1431
+ }
1092
1432
  }
1093
1433
  let usage;
1094
1434
  try {
@@ -1100,8 +1440,30 @@ var GoogleAdapter = class {
1100
1440
  total_tokens: response.usageMetadata.totalTokenCount || 0
1101
1441
  };
1102
1442
  }
1443
+ const finalCandidate = response.candidates?.[0];
1444
+ const finalGrounding = finalCandidate?.groundingMetadata;
1445
+ if (finalGrounding?.groundingChunks) {
1446
+ for (const chunk of finalGrounding.groundingChunks) {
1447
+ if (chunk.web?.uri) {
1448
+ const url = chunk.web.uri;
1449
+ const domain = extractDomain3(url);
1450
+ if (!collectedCitations.some((c) => c.url === url)) {
1451
+ collectedCitations.push({
1452
+ index: collectedCitations.length + 1,
1453
+ url,
1454
+ title: chunk.web.title || domain,
1455
+ domain,
1456
+ favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
1457
+ });
1458
+ }
1459
+ }
1460
+ }
1461
+ }
1103
1462
  } catch {
1104
1463
  }
1464
+ if (collectedCitations.length > 0) {
1465
+ yield { type: "citation", citations: collectedCitations };
1466
+ }
1105
1467
  yield { type: "message:end" };
1106
1468
  yield { type: "done", usage };
1107
1469
  } catch (error) {
@@ -1189,6 +1551,14 @@ var GoogleAdapter = class {
1189
1551
  function createGoogleAdapter(config) {
1190
1552
  return new GoogleAdapter(config);
1191
1553
  }
1554
+ function extractDomain3(url) {
1555
+ try {
1556
+ const parsed = new URL(url);
1557
+ return parsed.hostname;
1558
+ } catch {
1559
+ return "";
1560
+ }
1561
+ }
1192
1562
 
1193
1563
  // src/adapters/xai.ts
1194
1564
  var XAI_BASE_URL = "https://api.x.ai/v1";