@llumiverse/drivers 0.23.0 → 0.24.0

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 (78) hide show
  1. package/README.md +141 -218
  2. package/lib/cjs/azure/azure_foundry.js +46 -2
  3. package/lib/cjs/azure/azure_foundry.js.map +1 -1
  4. package/lib/cjs/bedrock/index.js +140 -15
  5. package/lib/cjs/bedrock/index.js.map +1 -1
  6. package/lib/cjs/groq/index.js +115 -85
  7. package/lib/cjs/groq/index.js.map +1 -1
  8. package/lib/cjs/index.js +1 -0
  9. package/lib/cjs/index.js.map +1 -1
  10. package/lib/cjs/openai/index.js +310 -114
  11. package/lib/cjs/openai/index.js.map +1 -1
  12. package/lib/cjs/openai/openai_compatible.js +62 -0
  13. package/lib/cjs/openai/openai_compatible.js.map +1 -0
  14. package/lib/cjs/openai/openai_format.js +32 -39
  15. package/lib/cjs/openai/openai_format.js.map +1 -1
  16. package/lib/cjs/vertexai/index.js +147 -0
  17. package/lib/cjs/vertexai/index.js.map +1 -1
  18. package/lib/cjs/vertexai/models/claude.js +88 -2
  19. package/lib/cjs/vertexai/models/claude.js.map +1 -1
  20. package/lib/cjs/vertexai/models/gemini.js +59 -20
  21. package/lib/cjs/vertexai/models/gemini.js.map +1 -1
  22. package/lib/cjs/xai/index.js +10 -16
  23. package/lib/cjs/xai/index.js.map +1 -1
  24. package/lib/esm/azure/azure_foundry.js +46 -2
  25. package/lib/esm/azure/azure_foundry.js.map +1 -1
  26. package/lib/esm/bedrock/index.js +141 -16
  27. package/lib/esm/bedrock/index.js.map +1 -1
  28. package/lib/esm/groq/index.js +115 -85
  29. package/lib/esm/groq/index.js.map +1 -1
  30. package/lib/esm/index.js +1 -0
  31. package/lib/esm/index.js.map +1 -1
  32. package/lib/esm/openai/index.js +311 -115
  33. package/lib/esm/openai/index.js.map +1 -1
  34. package/lib/esm/openai/openai_compatible.js +55 -0
  35. package/lib/esm/openai/openai_compatible.js.map +1 -0
  36. package/lib/esm/openai/openai_format.js +32 -39
  37. package/lib/esm/openai/openai_format.js.map +1 -1
  38. package/lib/esm/vertexai/index.js +148 -1
  39. package/lib/esm/vertexai/index.js.map +1 -1
  40. package/lib/esm/vertexai/models/claude.js +87 -2
  41. package/lib/esm/vertexai/models/claude.js.map +1 -1
  42. package/lib/esm/vertexai/models/gemini.js +60 -21
  43. package/lib/esm/vertexai/models/gemini.js.map +1 -1
  44. package/lib/esm/xai/index.js +10 -16
  45. package/lib/esm/xai/index.js.map +1 -1
  46. package/lib/types/azure/azure_foundry.d.ts +7 -5
  47. package/lib/types/azure/azure_foundry.d.ts.map +1 -1
  48. package/lib/types/bedrock/index.d.ts +5 -0
  49. package/lib/types/bedrock/index.d.ts.map +1 -1
  50. package/lib/types/groq/index.d.ts.map +1 -1
  51. package/lib/types/index.d.ts +1 -0
  52. package/lib/types/index.d.ts.map +1 -1
  53. package/lib/types/openai/index.d.ts +13 -7
  54. package/lib/types/openai/index.d.ts.map +1 -1
  55. package/lib/types/openai/openai_compatible.d.ts +26 -0
  56. package/lib/types/openai/openai_compatible.d.ts.map +1 -0
  57. package/lib/types/openai/openai_format.d.ts +4 -2
  58. package/lib/types/openai/openai_format.d.ts.map +1 -1
  59. package/lib/types/vertexai/index.d.ts +11 -0
  60. package/lib/types/vertexai/index.d.ts.map +1 -1
  61. package/lib/types/vertexai/models/claude.d.ts +8 -0
  62. package/lib/types/vertexai/models/claude.d.ts.map +1 -1
  63. package/lib/types/vertexai/models/gemini.d.ts +1 -1
  64. package/lib/types/vertexai/models/gemini.d.ts.map +1 -1
  65. package/lib/types/xai/index.d.ts +2 -3
  66. package/lib/types/xai/index.d.ts.map +1 -1
  67. package/package.json +12 -12
  68. package/src/azure/azure_foundry.ts +56 -7
  69. package/src/bedrock/index.ts +188 -24
  70. package/src/groq/index.ts +120 -94
  71. package/src/index.ts +1 -0
  72. package/src/openai/index.ts +363 -136
  73. package/src/openai/openai_compatible.ts +74 -0
  74. package/src/openai/openai_format.ts +44 -54
  75. package/src/vertexai/index.ts +186 -0
  76. package/src/vertexai/models/claude.ts +97 -2
  77. package/src/vertexai/models/gemini.ts +78 -27
  78. package/src/xai/index.ts +10 -17
@@ -5,12 +5,20 @@ import {
5
5
  } from "@google/genai";
6
6
  import {
7
7
  AIModel, Completion, CompletionChunkObject, CompletionResult, ExecutionOptions,
8
- ExecutionTokenUsage, getMaxTokensLimitVertexAi, JSONObject, JSONSchema, ModelType, PromptOptions, PromptRole,
9
- PromptSegment, readStreamAsBase64, StatelessExecutionOptions, ToolDefinition, ToolUse,
8
+ ExecutionTokenUsage,
9
+ getConversationMeta,
10
+ getMaxTokensLimitVertexAi,
11
+ incrementConversationTurn,
12
+ JSONObject, JSONSchema, ModelType, PromptOptions, PromptRole,
13
+ PromptSegment, readStreamAsBase64, StatelessExecutionOptions,
14
+ stripBase64ImagesFromConversation,
15
+ ToolDefinition, ToolUse,
16
+ truncateLargeTextInConversation,
17
+ unwrapConversationArray,
10
18
  VertexAIGeminiOptions
11
19
  } from "@llumiverse/core";
12
20
  import { asyncMap } from "@llumiverse/core/async";
13
- import { VertexAIDriver, GenerateContentPrompt } from "../index.js";
21
+ import { GenerateContentPrompt, VertexAIDriver } from "../index.js";
14
22
  import { ModelDefinition } from "../models.js";
15
23
 
16
24
  function supportsStructuredOutput(options: PromptOptions): boolean {
@@ -467,11 +475,17 @@ function collectToolUseParts(content: Content): ToolUse[] | undefined {
467
475
  const parts = content.parts ?? [];
468
476
  for (const part of parts) {
469
477
  if (part.functionCall) {
470
- out.push({
478
+ const toolUse: ToolUse = {
471
479
  id: part.functionCall.name ?? '',
472
480
  tool_name: part.functionCall.name ?? '',
473
481
  tool_input: part.functionCall.args as JSONObject,
474
- });
482
+ };
483
+ // Capture thought_signature for Gemini thinking models (2.5+/3.0+)
484
+ // This must be passed back with the function response
485
+ if (part.thoughtSignature) {
486
+ toolUse.thought_signature = part.thoughtSignature;
487
+ }
488
+ out.push(toolUse);
475
489
  }
476
490
  }
477
491
  return out.length > 0 ? out : undefined;
@@ -545,7 +559,7 @@ function geminiThinkingConfig(option: StatelessExecutionOptions): ThinkingConfig
545
559
  const model_options = option.model_options as VertexAIGeminiOptions | undefined;
546
560
  const include_thoughts = model_options?.include_thoughts ?? false;
547
561
  if (model_options?.thinking_budget_tokens) {
548
- return {includeThoughts: include_thoughts, thinkingBudget: model_options.thinking_budget_tokens};
562
+ return { includeThoughts: include_thoughts, thinkingBudget: model_options.thinking_budget_tokens };
549
563
  }
550
564
 
551
565
  // Set minimum thinking level by default.
@@ -623,16 +637,18 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
623
637
  if (!msg.tool_use_id) {
624
638
  throw new Error("Tool response missing tool_use_id");
625
639
  }
640
+ // Build functionResponse part with optional thought_signature for Gemini thinking models
641
+ const functionResponsePart: Part = {
642
+ functionResponse: {
643
+ name: msg.tool_use_id,
644
+ response: formatFunctionResponse(msg.content || ''),
645
+ },
646
+ // Include thought_signature if provided (required for Gemini 2.5+/3.0+ thinking models)
647
+ thoughtSignature: msg.thought_signature,
648
+ };
626
649
  contents.push({
627
650
  role: 'user',
628
- parts: [
629
- {
630
- functionResponse: {
631
- name: msg.tool_use_id,
632
- response: formatFunctionResponse(msg.content || ''),
633
- }
634
- }
635
- ]
651
+ parts: [functionResponsePart]
636
652
  });
637
653
  } else { // PromptRole.user, PromptRole.assistant, PromptRole.safety
638
654
  const parts: Part[] = [];
@@ -646,14 +662,27 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
646
662
  // File content handling
647
663
  if (msg.files) {
648
664
  for (const f of msg.files) {
649
- const stream = await f.getStream();
650
- const data = await readStreamAsBase64(stream);
651
- parts.push({
652
- inlineData: {
653
- data,
654
- mimeType: f.mime_type
655
- }
656
- });
665
+ let fileUrl = await f.getURL();
666
+ const isGsUrl = fileUrl.startsWith('gs://') || fileUrl.startsWith('https://storage.googleapis.com/');
667
+
668
+ if (isGsUrl) {
669
+ parts.push({
670
+ fileData: {
671
+ fileUri: fileUrl,
672
+ mimeType: f.mime_type
673
+ }
674
+ });
675
+ } else {
676
+ // Inline data handling
677
+ const stream = await f.getStream();
678
+ const data = await readStreamAsBase64(stream);
679
+ parts.push({
680
+ inlineData: {
681
+ data,
682
+ mimeType: f.mime_type
683
+ }
684
+ });
685
+ }
657
686
  }
658
687
  }
659
688
 
@@ -742,7 +771,7 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
742
771
  const modelName = splits[splits.length - 1];
743
772
  options = { ...options, model: modelName };
744
773
 
745
- let conversation = updateConversation(options.conversation as Content[], prompt.contents);
774
+ let conversation = updateConversation(options.conversation, prompt.contents);
746
775
  prompt.contents = conversation;
747
776
 
748
777
  // TODO: Remove hack, use global endpoint manually if needed.
@@ -792,12 +821,27 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
792
821
  finish_reason = "tool_use";
793
822
  }
794
823
 
824
+ // Increment turn counter for deferred stripping
825
+ conversation = incrementConversationTurn(conversation) as Content[];
826
+
827
+ // Strip large base64 image data based on options.stripImagesAfterTurns
828
+ const currentTurn = getConversationMeta(conversation).turnNumber;
829
+ const stripOptions = {
830
+ keepForTurns: options.stripImagesAfterTurns ?? Infinity,
831
+ currentTurn,
832
+ textMaxTokens: options.stripTextMaxTokens
833
+ };
834
+ let processedConversation = stripBase64ImagesFromConversation(conversation, stripOptions);
835
+
836
+ // Truncate large text content if configured
837
+ processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
838
+
795
839
  return {
796
840
  result: result && result.length > 0 ? result : [{ type: "text" as const, value: '' }],
797
841
  token_usage: token_usage,
798
842
  finish_reason: finish_reason,
799
843
  original_response: options.include_original_response ? response : undefined,
800
- conversation,
844
+ conversation: processedConversation,
801
845
  tool_use
802
846
  } satisfies Completion;
803
847
  }
@@ -811,6 +855,10 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
811
855
  const modelName = splits[splits.length - 1];
812
856
  options = { ...options, model: modelName };
813
857
 
858
+ // Include conversation history in prompt contents (same as non-streaming)
859
+ const conversation = updateConversation(options.conversation, prompt.contents);
860
+ prompt.contents = conversation;
861
+
814
862
  if (options.model.includes("gemini-2.5-flash-image")) {
815
863
  region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
816
864
  }
@@ -897,16 +945,19 @@ function getToolFunction(tool: ToolDefinition): FunctionDeclaration {
897
945
  };
898
946
  }
899
947
 
900
-
901
948
  /**
902
949
  * Update the conversation messages
903
950
  * @param prompt
904
951
  * @param response
905
952
  * @returns
906
953
  */
907
- function updateConversation(conversation: Content[], prompt: Content[]): Content[] {
908
- return (conversation || [] satisfies Content[]).concat(prompt);
954
+ function updateConversation(conversation: unknown, prompt: Content[]): Content[] {
955
+ // Unwrap array if wrapped, otherwise treat as array
956
+ const unwrapped = unwrapConversationArray<Content>(conversation);
957
+ const convArray = unwrapped ?? (conversation as Content[] || []);
958
+ return convArray.concat(prompt);
909
959
  }
960
+
910
961
  /**
911
962
  *
912
963
  * Gemini supports JSON output in the response. so we test if the response is a valid JSON object. otherwise we treat the response as a string.
package/src/xai/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AIModel, Completion, DriverOptions, ExecutionOptions, PromptOptions, PromptSegment } from "@llumiverse/core";
1
+ import { AIModel, DriverOptions, PromptOptions, PromptSegment, Providers } from "@llumiverse/core";
2
2
  import { formatOpenAILikeMultimodalPrompt, OpenAIPromptFormatterOptions } from "../openai/openai_format.js";
3
3
  import { FetchClient } from "@vertesia/api-fetch-client";
4
4
  import OpenAI from "openai";
@@ -15,7 +15,7 @@ export interface xAiDriverOptions extends DriverOptions {
15
15
  export class xAIDriver extends BaseOpenAIDriver {
16
16
 
17
17
  service: OpenAI;
18
- provider: "xai";
18
+ readonly provider = Providers.xai;
19
19
  xai_service: FetchClient;
20
20
  DEFAULT_ENDPOINT = "https://api.x.ai/v1";
21
21
 
@@ -31,7 +31,6 @@ export class xAIDriver extends BaseOpenAIDriver {
31
31
  baseURL: opts.endpoint ?? this.DEFAULT_ENDPOINT,
32
32
  });
33
33
  this.xai_service = new FetchClient(opts.endpoint ?? this.DEFAULT_ENDPOINT).withAuthCallback(async () => `Bearer ${opts.apiKey}`);
34
- this.provider = "xai";
35
34
  //this.formatPrompt = this._formatPrompt; //TODO: fix xai prompt formatting
36
35
  }
37
36
 
@@ -49,17 +48,9 @@ export class xAIDriver extends BaseOpenAIDriver {
49
48
 
50
49
  }
51
50
 
52
- extractDataFromResponse(_options: ExecutionOptions, result: OpenAI.Chat.Completions.ChatCompletion): Completion {
53
- return {
54
- result: result.choices[0].message.content ? [{ type: "text", value: result.choices[0].message.content }] : [],
55
- finish_reason: result.choices[0].finish_reason,
56
- token_usage: {
57
- prompt: result.usage?.prompt_tokens,
58
- result: result.usage?.completion_tokens,
59
- total: result.usage?.total_tokens,
60
- }
61
- }
62
- }
51
+ // Note: We intentionally do NOT override extractDataFromResponse here.
52
+ // The base class implementation properly handles tool_calls extraction.
53
+ // xAI's API is OpenAI-compatible and returns tool_calls in the same format.
63
54
 
64
55
  async listModels(): Promise<AIModel[]> {
65
56
  const [lm, em] = await Promise.all([
@@ -76,10 +67,12 @@ export class xAIDriver extends BaseOpenAIDriver {
76
67
  return {
77
68
  id: model.id,
78
69
  provider: this.provider,
79
- name: model.object,
80
- description: model.object,
70
+ name: model.id,
71
+ description: `${model.id} by ${model.owned_by}`,
81
72
  is_multimodal: model.input_modalities.length > 1,
82
- tags: [...model.input_modalities.map(m => `ì:${m}`), ...model.output_modalities.map(m => `ì:${m}`)],
73
+ input_modalities: model.input_modalities,
74
+ output_modalities: model.output_modalities,
75
+ tags: [...model.input_modalities.map(m => `i:${m}`), ...model.output_modalities.map(m => `o:${m}`)],
83
76
  } satisfies AIModel;
84
77
  });
85
78