@llumiverse/drivers 0.23.0-dev.20251121 → 0.24.0-dev.202601221707

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 (229) 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 +239 -19
  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 +211 -5
  17. package/lib/cjs/vertexai/index.js.map +1 -1
  18. package/lib/cjs/vertexai/models/claude.js +201 -3
  19. package/lib/cjs/vertexai/models/claude.js.map +1 -1
  20. package/lib/cjs/vertexai/models/gemini.js +74 -25
  21. package/lib/cjs/vertexai/models/gemini.js.map +1 -1
  22. package/lib/cjs/vertexai/models/imagen.js +0 -3
  23. package/lib/cjs/vertexai/models/imagen.js.map +1 -1
  24. package/lib/cjs/xai/index.js +10 -16
  25. package/lib/cjs/xai/index.js.map +1 -1
  26. package/lib/esm/azure/azure_foundry.js +46 -2
  27. package/lib/esm/azure/azure_foundry.js.map +1 -1
  28. package/lib/esm/bedrock/index.js +239 -20
  29. package/lib/esm/bedrock/index.js.map +1 -1
  30. package/lib/esm/groq/index.js +115 -85
  31. package/lib/esm/groq/index.js.map +1 -1
  32. package/lib/esm/index.js +1 -0
  33. package/lib/esm/index.js.map +1 -1
  34. package/lib/esm/openai/index.js +311 -115
  35. package/lib/esm/openai/index.js.map +1 -1
  36. package/lib/esm/openai/openai_compatible.js +55 -0
  37. package/lib/esm/openai/openai_compatible.js.map +1 -0
  38. package/lib/esm/openai/openai_format.js +32 -39
  39. package/lib/esm/openai/openai_format.js.map +1 -1
  40. package/lib/esm/vertexai/index.js +212 -6
  41. package/lib/esm/vertexai/index.js.map +1 -1
  42. package/lib/esm/vertexai/models/claude.js +199 -3
  43. package/lib/esm/vertexai/models/claude.js.map +1 -1
  44. package/lib/esm/vertexai/models/gemini.js +75 -26
  45. package/lib/esm/vertexai/models/gemini.js.map +1 -1
  46. package/lib/esm/vertexai/models/imagen.js +1 -4
  47. package/lib/esm/vertexai/models/imagen.js.map +1 -1
  48. package/lib/esm/xai/index.js +10 -16
  49. package/lib/esm/xai/index.js.map +1 -1
  50. package/lib/types/azure/azure_foundry.d.ts +7 -5
  51. package/lib/types/azure/azure_foundry.d.ts.map +1 -1
  52. package/lib/types/bedrock/index.d.ts +22 -1
  53. package/lib/types/bedrock/index.d.ts.map +1 -1
  54. package/lib/types/groq/index.d.ts.map +1 -1
  55. package/lib/types/index.d.ts +1 -0
  56. package/lib/types/index.d.ts.map +1 -1
  57. package/lib/types/openai/index.d.ts +13 -7
  58. package/lib/types/openai/index.d.ts.map +1 -1
  59. package/lib/types/openai/openai_compatible.d.ts +26 -0
  60. package/lib/types/openai/openai_compatible.d.ts.map +1 -0
  61. package/lib/types/openai/openai_format.d.ts +4 -2
  62. package/lib/types/openai/openai_format.d.ts.map +1 -1
  63. package/lib/types/vertexai/index.d.ts +16 -0
  64. package/lib/types/vertexai/index.d.ts.map +1 -1
  65. package/lib/types/vertexai/models/claude.d.ts +20 -0
  66. package/lib/types/vertexai/models/claude.d.ts.map +1 -1
  67. package/lib/types/vertexai/models/gemini.d.ts +1 -1
  68. package/lib/types/vertexai/models/gemini.d.ts.map +1 -1
  69. package/lib/types/vertexai/models/imagen.d.ts.map +1 -1
  70. package/lib/types/xai/index.d.ts +2 -3
  71. package/lib/types/xai/index.d.ts.map +1 -1
  72. package/package.json +17 -17
  73. package/src/azure/azure_foundry.ts +56 -7
  74. package/src/bedrock/index.ts +301 -29
  75. package/src/groq/index.ts +120 -94
  76. package/src/index.ts +1 -0
  77. package/src/openai/index.ts +363 -136
  78. package/src/openai/openai_compatible.ts +74 -0
  79. package/src/openai/openai_format.ts +44 -54
  80. package/src/vertexai/index.ts +255 -6
  81. package/src/vertexai/models/claude.ts +233 -3
  82. package/src/vertexai/models/gemini.ts +95 -32
  83. package/src/vertexai/models/imagen.ts +1 -5
  84. package/src/xai/index.ts +10 -17
  85. package/lib/cjs/test/TestErrorCompletionStream.js +0 -20
  86. package/lib/cjs/test/TestErrorCompletionStream.js.map +0 -1
  87. package/lib/cjs/test/TestValidationErrorCompletionStream.js +0 -24
  88. package/lib/cjs/test/TestValidationErrorCompletionStream.js.map +0 -1
  89. package/lib/cjs/test/index.js +0 -109
  90. package/lib/cjs/test/index.js.map +0 -1
  91. package/lib/cjs/test/utils.js +0 -30
  92. package/lib/cjs/test/utils.js.map +0 -1
  93. package/lib/esm/src/adobe/firefly.js +0 -116
  94. package/lib/esm/src/adobe/firefly.js.map +0 -1
  95. package/lib/esm/src/azure/azure_foundry.js +0 -382
  96. package/lib/esm/src/azure/azure_foundry.js.map +0 -1
  97. package/lib/esm/src/bedrock/converse.js +0 -278
  98. package/lib/esm/src/bedrock/converse.js.map +0 -1
  99. package/lib/esm/src/bedrock/index.js +0 -962
  100. package/lib/esm/src/bedrock/index.js.map +0 -1
  101. package/lib/esm/src/bedrock/nova-image-payload.js +0 -203
  102. package/lib/esm/src/bedrock/nova-image-payload.js.map +0 -1
  103. package/lib/esm/src/bedrock/payloads.js +0 -2
  104. package/lib/esm/src/bedrock/payloads.js.map +0 -1
  105. package/lib/esm/src/bedrock/s3.js +0 -99
  106. package/lib/esm/src/bedrock/s3.js.map +0 -1
  107. package/lib/esm/src/bedrock/twelvelabs.js +0 -84
  108. package/lib/esm/src/bedrock/twelvelabs.js.map +0 -1
  109. package/lib/esm/src/groq/index.js +0 -286
  110. package/lib/esm/src/groq/index.js.map +0 -1
  111. package/lib/esm/src/huggingface_ie.js +0 -197
  112. package/lib/esm/src/huggingface_ie.js.map +0 -1
  113. package/lib/esm/src/index.js +0 -14
  114. package/lib/esm/src/index.js.map +0 -1
  115. package/lib/esm/src/mistral/index.js +0 -169
  116. package/lib/esm/src/mistral/index.js.map +0 -1
  117. package/lib/esm/src/mistral/types.js +0 -80
  118. package/lib/esm/src/mistral/types.js.map +0 -1
  119. package/lib/esm/src/openai/azure_openai.js +0 -68
  120. package/lib/esm/src/openai/azure_openai.js.map +0 -1
  121. package/lib/esm/src/openai/index.js +0 -464
  122. package/lib/esm/src/openai/index.js.map +0 -1
  123. package/lib/esm/src/openai/openai.js +0 -14
  124. package/lib/esm/src/openai/openai.js.map +0 -1
  125. package/lib/esm/src/openai/openai_format.js +0 -134
  126. package/lib/esm/src/openai/openai_format.js.map +0 -1
  127. package/lib/esm/src/replicate.js +0 -268
  128. package/lib/esm/src/replicate.js.map +0 -1
  129. package/lib/esm/src/test/TestErrorCompletionStream.js +0 -16
  130. package/lib/esm/src/test/TestErrorCompletionStream.js.map +0 -1
  131. package/lib/esm/src/test/TestValidationErrorCompletionStream.js +0 -20
  132. package/lib/esm/src/test/TestValidationErrorCompletionStream.js.map +0 -1
  133. package/lib/esm/src/test/index.js +0 -91
  134. package/lib/esm/src/test/index.js.map +0 -1
  135. package/lib/esm/src/test/utils.js +0 -25
  136. package/lib/esm/src/test/utils.js.map +0 -1
  137. package/lib/esm/src/test-driver/TestErrorCompletionStream.js +0 -16
  138. package/lib/esm/src/test-driver/TestErrorCompletionStream.js.map +0 -1
  139. package/lib/esm/src/test-driver/TestValidationErrorCompletionStream.js +0 -20
  140. package/lib/esm/src/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
  141. package/lib/esm/src/test-driver/index.js +0 -91
  142. package/lib/esm/src/test-driver/index.js.map +0 -1
  143. package/lib/esm/src/test-driver/utils.js +0 -25
  144. package/lib/esm/src/test-driver/utils.js.map +0 -1
  145. package/lib/esm/src/togetherai/index.js +0 -122
  146. package/lib/esm/src/togetherai/index.js.map +0 -1
  147. package/lib/esm/src/togetherai/interfaces.js +0 -2
  148. package/lib/esm/src/togetherai/interfaces.js.map +0 -1
  149. package/lib/esm/src/vertexai/debug.js +0 -6
  150. package/lib/esm/src/vertexai/debug.js.map +0 -1
  151. package/lib/esm/src/vertexai/embeddings/embeddings-image.js +0 -24
  152. package/lib/esm/src/vertexai/embeddings/embeddings-image.js.map +0 -1
  153. package/lib/esm/src/vertexai/embeddings/embeddings-text.js +0 -20
  154. package/lib/esm/src/vertexai/embeddings/embeddings-text.js.map +0 -1
  155. package/lib/esm/src/vertexai/index.js +0 -383
  156. package/lib/esm/src/vertexai/index.js.map +0 -1
  157. package/lib/esm/src/vertexai/models/claude.js +0 -394
  158. package/lib/esm/src/vertexai/models/claude.js.map +0 -1
  159. package/lib/esm/src/vertexai/models/gemini.js +0 -817
  160. package/lib/esm/src/vertexai/models/gemini.js.map +0 -1
  161. package/lib/esm/src/vertexai/models/imagen.js +0 -302
  162. package/lib/esm/src/vertexai/models/imagen.js.map +0 -1
  163. package/lib/esm/src/vertexai/models/llama.js +0 -179
  164. package/lib/esm/src/vertexai/models/llama.js.map +0 -1
  165. package/lib/esm/src/vertexai/models.js +0 -32
  166. package/lib/esm/src/vertexai/models.js.map +0 -1
  167. package/lib/esm/src/watsonx/index.js +0 -157
  168. package/lib/esm/src/watsonx/index.js.map +0 -1
  169. package/lib/esm/src/watsonx/interfaces.js +0 -2
  170. package/lib/esm/src/watsonx/interfaces.js.map +0 -1
  171. package/lib/esm/src/xai/index.js +0 -64
  172. package/lib/esm/src/xai/index.js.map +0 -1
  173. package/lib/esm/test/TestErrorCompletionStream.js +0 -16
  174. package/lib/esm/test/TestErrorCompletionStream.js.map +0 -1
  175. package/lib/esm/test/TestValidationErrorCompletionStream.js +0 -20
  176. package/lib/esm/test/TestValidationErrorCompletionStream.js.map +0 -1
  177. package/lib/esm/test/index.js +0 -91
  178. package/lib/esm/test/index.js.map +0 -1
  179. package/lib/esm/test/utils.js +0 -25
  180. package/lib/esm/test/utils.js.map +0 -1
  181. package/lib/esm/tsconfig.tsbuildinfo +0 -1
  182. package/lib/types/src/adobe/firefly.d.ts +0 -29
  183. package/lib/types/src/azure/azure_foundry.d.ts +0 -49
  184. package/lib/types/src/bedrock/converse.d.ts +0 -8
  185. package/lib/types/src/bedrock/index.d.ts +0 -61
  186. package/lib/types/src/bedrock/nova-image-payload.d.ts +0 -73
  187. package/lib/types/src/bedrock/payloads.d.ts +0 -11
  188. package/lib/types/src/bedrock/s3.d.ts +0 -22
  189. package/lib/types/src/bedrock/twelvelabs.d.ts +0 -49
  190. package/lib/types/src/groq/index.d.ts +0 -26
  191. package/lib/types/src/huggingface_ie.d.ts +0 -34
  192. package/lib/types/src/index.d.ts +0 -13
  193. package/lib/types/src/mistral/index.d.ts +0 -24
  194. package/lib/types/src/mistral/types.d.ts +0 -131
  195. package/lib/types/src/openai/azure_openai.d.ts +0 -24
  196. package/lib/types/src/openai/index.d.ts +0 -24
  197. package/lib/types/src/openai/openai.d.ts +0 -14
  198. package/lib/types/src/openai/openai_format.d.ts +0 -18
  199. package/lib/types/src/replicate.d.ts +0 -47
  200. package/lib/types/src/test/TestErrorCompletionStream.d.ts +0 -8
  201. package/lib/types/src/test/TestValidationErrorCompletionStream.d.ts +0 -8
  202. package/lib/types/src/test/index.d.ts +0 -23
  203. package/lib/types/src/test/utils.d.ts +0 -4
  204. package/lib/types/src/test-driver/TestErrorCompletionStream.d.ts +0 -8
  205. package/lib/types/src/test-driver/TestValidationErrorCompletionStream.d.ts +0 -8
  206. package/lib/types/src/test-driver/index.d.ts +0 -23
  207. package/lib/types/src/test-driver/utils.d.ts +0 -4
  208. package/lib/types/src/togetherai/index.d.ts +0 -22
  209. package/lib/types/src/togetherai/interfaces.d.ts +0 -95
  210. package/lib/types/src/vertexai/debug.d.ts +0 -1
  211. package/lib/types/src/vertexai/embeddings/embeddings-image.d.ts +0 -10
  212. package/lib/types/src/vertexai/embeddings/embeddings-text.d.ts +0 -9
  213. package/lib/types/src/vertexai/index.d.ts +0 -52
  214. package/lib/types/src/vertexai/models/claude.d.ts +0 -19
  215. package/lib/types/src/vertexai/models/gemini.d.ts +0 -17
  216. package/lib/types/src/vertexai/models/imagen.d.ts +0 -74
  217. package/lib/types/src/vertexai/models/llama.d.ts +0 -19
  218. package/lib/types/src/vertexai/models.d.ts +0 -14
  219. package/lib/types/src/watsonx/index.d.ts +0 -26
  220. package/lib/types/src/watsonx/interfaces.d.ts +0 -64
  221. package/lib/types/src/xai/index.d.ts +0 -18
  222. package/lib/types/test/TestErrorCompletionStream.d.ts +0 -9
  223. package/lib/types/test/TestErrorCompletionStream.d.ts.map +0 -1
  224. package/lib/types/test/TestValidationErrorCompletionStream.d.ts +0 -9
  225. package/lib/types/test/TestValidationErrorCompletionStream.d.ts.map +0 -1
  226. package/lib/types/test/index.d.ts +0 -24
  227. package/lib/types/test/index.d.ts.map +0 -1
  228. package/lib/types/test/utils.d.ts +0 -5
  229. package/lib/types/test/utils.d.ts.map +0 -1
@@ -0,0 +1,74 @@
1
+ import { AIModel, DriverOptions, ModelType, Providers, getModelCapabilities, modelModalitiesToArray } from "@llumiverse/core";
2
+ import OpenAI from "openai";
3
+ import { BaseOpenAIDriver } from "./index.js";
4
+
5
+ export interface OpenAICompatibleDriverOptions extends DriverOptions {
6
+ /**
7
+ * The API key for the OpenAI-compatible service
8
+ */
9
+ apiKey: string;
10
+
11
+ /**
12
+ * The base URL of the OpenAI-compatible API endpoint
13
+ * Example: https://api.example.com/v1
14
+ */
15
+ endpoint: string;
16
+ }
17
+
18
+ /**
19
+ * A generic driver for OpenAI-compatible APIs.
20
+ * This can be used with any service that implements the OpenAI API spec,
21
+ * such as xAI (Grok), LM Studio, Ollama, vLLM, LocalAI, etc.
22
+ */
23
+ export class OpenAICompatibleDriver extends BaseOpenAIDriver {
24
+ service: OpenAI;
25
+ readonly provider = Providers.openai_compatible;
26
+
27
+ constructor(opts: OpenAICompatibleDriverOptions) {
28
+ super(opts);
29
+
30
+ if (!opts.apiKey) {
31
+ throw new Error("apiKey is required");
32
+ }
33
+
34
+ if (!opts.endpoint) {
35
+ throw new Error("endpoint is required for OpenAI-compatible driver");
36
+ }
37
+
38
+ this.service = new OpenAI({
39
+ apiKey: opts.apiKey,
40
+ baseURL: opts.endpoint,
41
+ });
42
+ }
43
+
44
+ async listModels(): Promise<AIModel[]> {
45
+ try {
46
+ const result = (await this.service.models.list()).data;
47
+
48
+ const models = result.map((m) => {
49
+ const modelCapability = getModelCapabilities(m.id, "openai");
50
+ let owner = m.owned_by;
51
+ if (owner === "system") {
52
+ owner = "unknown";
53
+ }
54
+ return {
55
+ id: m.id,
56
+ name: m.id,
57
+ provider: this.provider,
58
+ owner: owner,
59
+ type: ModelType.Text,
60
+ can_stream: true,
61
+ is_multimodal: false,
62
+ input_modalities: modelModalitiesToArray(modelCapability.input),
63
+ output_modalities: modelModalitiesToArray(modelCapability.output),
64
+ tool_support: modelCapability.tool_support,
65
+ } satisfies AIModel<string>;
66
+ }).sort((a, b) => a.id.localeCompare(b.id));
67
+
68
+ return models;
69
+ } catch (error) {
70
+ this.logger.warn({ error }, "[OpenAICompatible] Failed to list models, returning empty list");
71
+ return [];
72
+ }
73
+ }
74
+ }
@@ -3,16 +3,12 @@
3
3
 
4
4
  import { PromptRole, PromptOptions, PromptSegment } from "@llumiverse/common";
5
5
  import { readStreamAsBase64 } from "@llumiverse/core";
6
+ import type OpenAI from "openai";
6
7
 
7
- import type {
8
- ChatCompletionMessageParam,
9
- ChatCompletionContentPartText,
10
- ChatCompletionContentPartImage,
11
- ChatCompletionUserMessageParam,
12
- ChatCompletionSystemMessageParam,
13
- ChatCompletionAssistantMessageParam,
14
- ChatCompletionToolMessageParam
15
- } from 'openai/resources/chat/completions';
8
+ // Types for Response API
9
+ type ResponseInputItem = OpenAI.Responses.ResponseInputItem;
10
+ type ResponseInputContent = OpenAI.Responses.ResponseInputContent;
11
+ type EasyInputMessage = OpenAI.Responses.EasyInputMessage;
16
12
 
17
13
  export interface OpenAITextMessage {
18
14
  content: string;
@@ -47,14 +43,14 @@ export function formatOpenAILikeTextPrompt(segments: PromptSegment[]): OpenAITex
47
43
  }
48
44
 
49
45
 
50
- export async function formatOpenAILikeMultimodalPrompt(segments: PromptSegment[], opts: PromptOptions & OpenAIPromptFormatterOptions): Promise<ChatCompletionMessageParam[]> {
51
- const system: ChatCompletionMessageParam[] = [];
52
- const safety: ChatCompletionMessageParam[] = [];
53
- const others: ChatCompletionMessageParam[] = [];
46
+ export async function formatOpenAILikeMultimodalPrompt(segments: PromptSegment[], opts: PromptOptions & OpenAIPromptFormatterOptions): Promise<ResponseInputItem[]> {
47
+ const system: ResponseInputItem[] = [];
48
+ const safety: ResponseInputItem[] = [];
49
+ const others: ResponseInputItem[] = [];
54
50
 
55
51
  for (const msg of segments) {
56
52
 
57
- const parts: (ChatCompletionContentPartImage | ChatCompletionContentPartText)[] = [];
53
+ const parts: ResponseInputContent[] = [];
58
54
 
59
55
  //generate the parts based on PromptSegment
60
56
  if (msg.files) {
@@ -62,54 +58,56 @@ export async function formatOpenAILikeMultimodalPrompt(segments: PromptSegment[]
62
58
  const stream = await file.getStream();
63
59
  const data = await readStreamAsBase64(stream);
64
60
  parts.push({
65
- type: "image_url",
66
- image_url: {
67
- url: `data:${file.mime_type || "image/jpeg"};base64,${data}`,
68
- //detail: "auto" //This is modified just before execution to "low" | "high" | "auto"
69
- },
61
+ type: "input_image",
62
+ image_url: `data:${file.mime_type || "image/jpeg"};base64,${data}`,
63
+ detail: "auto",
70
64
  })
71
65
  }
72
66
  }
73
67
 
74
68
  if (msg.content) {
75
69
  parts.push({
70
+ type: "input_text",
76
71
  text: msg.content,
77
- type: "text"
78
72
  })
79
73
  }
80
74
 
81
75
 
82
76
  if (msg.role === PromptRole.system) {
83
77
  // For system messages, filter to only text parts
84
- const textParts = parts.filter((part): part is ChatCompletionContentPartText => part.type === 'text');
85
- const systemMsg: ChatCompletionSystemMessageParam = {
78
+ const textParts = parts.filter((part): part is OpenAI.Responses.ResponseInputText => part.type === 'input_text');
79
+ const textContent = textParts.length === 1 && !msg.files ? textParts[0].text : textParts;
80
+ const systemMsg: EasyInputMessage = {
86
81
  role: "system",
87
- content: textParts.length === 1 && !msg.files ? textParts[0].text : textParts
82
+ content: textContent,
88
83
  };
89
84
  system.push(systemMsg);
90
85
 
91
86
  if (opts.useToolForFormatting && opts.schema) {
92
87
  system.forEach(s => {
93
- if (typeof s.content === 'string') {
94
- s.content = "TOOL: " + s.content;
95
- } else if (Array.isArray(s.content)) {
96
- s.content.forEach((c: any) => {
97
- if (c.type === "text") c.text = "TOOL: " + c.text;
98
- });
88
+ if ((s as EasyInputMessage).role === 'system') {
89
+ const sysMsg = s as EasyInputMessage;
90
+ if (typeof sysMsg.content === 'string') {
91
+ sysMsg.content = "TOOL: " + sysMsg.content;
92
+ } else if (Array.isArray(sysMsg.content)) {
93
+ sysMsg.content.forEach((c: any) => {
94
+ if (c.type === "input_text") c.text = "TOOL: " + c.text;
95
+ });
96
+ }
99
97
  }
100
98
  });
101
99
  }
102
100
 
103
101
  } else if (msg.role === PromptRole.safety) {
104
- const textParts = parts.filter((part): part is ChatCompletionContentPartText => part.type === 'text');
105
- const safetyMsg: ChatCompletionSystemMessageParam = {
102
+ const textParts = parts.filter((part): part is OpenAI.Responses.ResponseInputText => part.type === 'input_text');
103
+ const safetyMsg: EasyInputMessage = {
106
104
  role: "system",
107
- content: textParts
105
+ content: textParts,
108
106
  };
109
107
 
110
108
  if (Array.isArray(safetyMsg.content)) {
111
109
  safetyMsg.content.forEach((c: any) => {
112
- if (c.type === "text") c.text = "DO NOT IGNORE - IMPORTANT: " + c.text;
110
+ if (c.type === "input_text") c.text = "DO NOT IGNORE - IMPORTANT: " + c.text;
113
111
  });
114
112
  }
115
113
 
@@ -118,35 +116,27 @@ export async function formatOpenAILikeMultimodalPrompt(segments: PromptSegment[]
118
116
  if (!msg.tool_use_id) {
119
117
  throw new Error("Tool use id is required for tool messages")
120
118
  }
121
- const toolMsg: ChatCompletionToolMessageParam = {
122
- role: "tool",
123
- tool_call_id: msg.tool_use_id,
124
- content: msg.content || ""
119
+ const toolOutputMsg: OpenAI.Responses.ResponseInputItem.FunctionCallOutput = {
120
+ type: "function_call_output",
121
+ call_id: msg.tool_use_id,
122
+ output: msg.content || ""
125
123
  };
126
- others.push(toolMsg);
124
+ others.push(toolOutputMsg);
127
125
  } else if (msg.role !== PromptRole.negative && msg.role !== PromptRole.mask) {
128
- if (msg.role === 'assistant') {
129
- const assistantMsg: ChatCompletionAssistantMessageParam = {
130
- role: 'assistant',
131
- content: parts as (ChatCompletionContentPartText)[]
132
- };
133
- others.push(assistantMsg);
134
- } else {
135
- const userMsg: ChatCompletionUserMessageParam = {
136
- role: 'user',
137
- content: parts
138
- };
139
- others.push(userMsg);
140
- }
126
+ const inputMsg: EasyInputMessage = {
127
+ role: msg.role === 'assistant' ? 'assistant' : 'user',
128
+ content: parts,
129
+ };
130
+ others.push(inputMsg);
141
131
  }
142
132
 
143
133
  }
144
134
 
145
135
  if (opts.result_schema && !opts.useToolForFormatting) {
146
- const schemaMsg: ChatCompletionSystemMessageParam = {
136
+ const schemaMsg: EasyInputMessage = {
147
137
  role: "system",
148
138
  content: [{
149
- type: "text",
139
+ type: "input_text",
150
140
  text: "IMPORTANT: only answer using JSON, and respecting the schema included below, between the <response_schema> tags. " + `<response_schema>${JSON.stringify(opts.result_schema)}</response_schema>`
151
141
  }]
152
142
  };
@@ -154,7 +144,7 @@ export async function formatOpenAILikeMultimodalPrompt(segments: PromptSegment[]
154
144
  }
155
145
 
156
146
  // put system messages first and safety last
157
- return ([] as ChatCompletionMessageParam[]).concat(system).concat(others).concat(safety);
147
+ return ([] as ResponseInputItem[]).concat(system).concat(others).concat(safety);
158
148
 
159
149
  }
160
150
 
@@ -6,18 +6,24 @@ import {
6
6
  AbstractDriver,
7
7
  Completion,
8
8
  CompletionChunkObject,
9
+ CompletionResult,
9
10
  DriverOptions,
10
11
  EmbeddingsOptions,
11
12
  EmbeddingsResult,
12
13
  ExecutionOptions,
13
- Modalities,
14
14
  ModelSearchPayload,
15
15
  PromptSegment,
16
16
  getModelCapabilities,
17
17
  modelModalitiesToArray,
18
+ stripBase64ImagesFromConversation,
19
+ truncateLargeTextInConversation,
20
+ getConversationMeta,
21
+ incrementConversationTurn,
22
+ unwrapConversationArray,
18
23
  } from "@llumiverse/core";
19
24
  import { FetchClient } from "@vertesia/api-fetch-client";
20
25
  import { GoogleAuth, GoogleAuthOptions, AuthClient } from "google-auth-library";
26
+ import type { ClientOptions as AnthropicVertexClientOptions } from "@anthropic-ai/vertex-sdk";
21
27
  import { getEmbeddingsForImages } from "./embeddings/embeddings-image.js";
22
28
  import { TextEmbeddingsOptions, getEmbeddingsForText } from "./embeddings/embeddings-text.js";
23
29
  import { getModelDefinition } from "./models.js";
@@ -155,7 +161,7 @@ export class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions, Vertex
155
161
  timeout: 20 * 60 * 10000, // Set to 20 minutes, 10 minute default, setting this disables long request error: https://github.com/anthropics/anthropic-sdk-typescript?#long-requests
156
162
  region: mappedRegion,
157
163
  projectId: this.options.project,
158
- authClient,
164
+ authClient: authClient as unknown as AnthropicVertexClientOptions["authClient"],
159
165
  });
160
166
  }
161
167
 
@@ -165,7 +171,7 @@ export class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions, Vertex
165
171
  timeout: 20 * 60 * 10000, // Set to 20 minutes, 10 minute default, setting this disables long request error: https://github.com/anthropics/anthropic-sdk-typescript?#long-requests
166
172
  region: mappedRegion,
167
173
  projectId: this.options.project,
168
- authClient,
174
+ authClient: authClient as unknown as AnthropicVertexClientOptions["authClient"],
169
175
  });
170
176
  }
171
177
  return this.anthropicClient;
@@ -224,14 +230,18 @@ export class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions, Vertex
224
230
  }
225
231
 
226
232
  protected canStream(options: ExecutionOptions): Promise<boolean> {
227
- if (options.output_modality == Modalities.image) {
233
+ if (this.isImageModel(options.model)) {
228
234
  return Promise.resolve(false);
229
235
  }
230
236
  return Promise.resolve(getModelDefinition(options.model).model.can_stream === true);
231
237
  }
232
238
 
239
+ protected isImageModel(model: string): boolean {
240
+ return model.includes("imagen");
241
+ }
242
+
233
243
  public createPrompt(segments: PromptSegment[], options: ExecutionOptions): Promise<VertexAIPrompt> {
234
- if (options.model.includes("imagen")) {
244
+ if (this.isImageModel(options.model)) {
235
245
  return new ImagenModelDefinition(options.model).createPrompt(this, segments, options);
236
246
  }
237
247
  return getModelDefinition(options.model).createPrompt(this, segments, options);
@@ -247,6 +257,196 @@ export class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions, Vertex
247
257
  return getModelDefinition(options.model).requestTextCompletionStream(this, prompt, options);
248
258
  }
249
259
 
260
+ /**
261
+ * Build conversation context after streaming completion.
262
+ * Reconstructs the assistant message from accumulated results and applies stripping.
263
+ * Handles both Gemini (Content[]) and Claude (ClaudePrompt) formats.
264
+ */
265
+ buildStreamingConversation(
266
+ prompt: VertexAIPrompt,
267
+ result: unknown[],
268
+ toolUse: unknown[] | undefined,
269
+ options: ExecutionOptions
270
+ ): Content[] | unknown | undefined {
271
+ // Handle Claude-style prompts (has 'messages' array)
272
+ if ('messages' in prompt && Array.isArray((prompt as any).messages)) {
273
+ return this.buildClaudeStreamingConversation(prompt as any, result, toolUse, options);
274
+ }
275
+
276
+ // Only handle Gemini-style prompts with contents array
277
+ if (!('contents' in prompt) || !Array.isArray(prompt.contents)) {
278
+ return undefined;
279
+ }
280
+
281
+ const completionResults = result as CompletionResult[];
282
+
283
+ // Convert accumulated results to text content for assistant message
284
+ const textContent = completionResults
285
+ .map(r => {
286
+ switch (r.type) {
287
+ case 'text':
288
+ return r.value;
289
+ case 'json':
290
+ return typeof r.value === 'string' ? r.value : JSON.stringify(r.value);
291
+ case 'image':
292
+ // Skip images in conversation - they're in the result
293
+ return '';
294
+ default:
295
+ return String((r as any).value || '');
296
+ }
297
+ })
298
+ .join('');
299
+
300
+ // Build parts array for assistant message
301
+ const parts: any[] = [];
302
+ if (textContent) {
303
+ parts.push({ text: textContent });
304
+ }
305
+ // Add function calls if present (Gemini format)
306
+ if (toolUse && toolUse.length > 0) {
307
+ for (const tool of toolUse as any[]) {
308
+ const functionCallPart: any = {
309
+ functionCall: {
310
+ name: tool.tool_name,
311
+ args: tool.tool_input,
312
+ }
313
+ };
314
+ // Include thought_signature for Gemini thinking models (2.5+/3.0+)
315
+ // This must be preserved in the conversation for subsequent API calls
316
+ if (tool.thought_signature) {
317
+ functionCallPart.thoughtSignature = tool.thought_signature;
318
+ }
319
+ parts.push(functionCallPart);
320
+ }
321
+ }
322
+
323
+ // Unwrap array if wrapped, otherwise treat as array
324
+ const unwrapped = unwrapConversationArray<Content>(options.conversation);
325
+ const existingConversation = unwrapped ?? (options.conversation as Content[] || []);
326
+
327
+ // Combine existing conversation + prompt contents
328
+ let conversation: Content[] = [
329
+ ...existingConversation,
330
+ ...prompt.contents,
331
+ ];
332
+
333
+ // Only add assistant message if there's actual content
334
+ // (Empty text parts can cause API errors)
335
+ if (parts.length > 0) {
336
+ conversation.push({
337
+ role: 'model',
338
+ parts: parts
339
+ });
340
+ }
341
+
342
+ // Increment turn counter
343
+ conversation = incrementConversationTurn(conversation) as Content[];
344
+
345
+ // Apply stripping based on options
346
+ const currentTurn = getConversationMeta(conversation).turnNumber;
347
+ const stripOptions = {
348
+ keepForTurns: options.stripImagesAfterTurns ?? Infinity,
349
+ currentTurn,
350
+ textMaxTokens: options.stripTextMaxTokens
351
+ };
352
+ let processedConversation = stripBase64ImagesFromConversation(conversation, stripOptions);
353
+ processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
354
+
355
+ return processedConversation as Content[];
356
+ }
357
+
358
+ /**
359
+ * Build conversation for Claude streaming.
360
+ * Creates assistant message with tool_use blocks in Claude's ContentBlock format.
361
+ */
362
+ private buildClaudeStreamingConversation(
363
+ prompt: { messages: unknown[]; system?: unknown[] },
364
+ result: unknown[],
365
+ toolUse: unknown[] | undefined,
366
+ options: ExecutionOptions
367
+ ): unknown {
368
+ const completionResults = result as CompletionResult[];
369
+
370
+ // Convert accumulated results to text content
371
+ const textContent = completionResults
372
+ .map(r => {
373
+ switch (r.type) {
374
+ case 'text':
375
+ return r.value;
376
+ case 'json':
377
+ return typeof r.value === 'string' ? r.value : JSON.stringify(r.value);
378
+ case 'image':
379
+ return '';
380
+ default:
381
+ return String((r as any).value || '');
382
+ }
383
+ })
384
+ .join('');
385
+
386
+ // Build Claude-style ContentBlock array for assistant message
387
+ const content: unknown[] = [];
388
+
389
+ // Add text block if there's text content
390
+ if (textContent) {
391
+ content.push({
392
+ type: 'text',
393
+ text: textContent
394
+ });
395
+ }
396
+
397
+ // Add tool_use blocks in Claude format
398
+ if (toolUse && toolUse.length > 0) {
399
+ for (const tool of toolUse as any[]) {
400
+ content.push({
401
+ type: 'tool_use',
402
+ id: tool.id,
403
+ name: tool.tool_name,
404
+ input: tool.tool_input ?? {}
405
+ });
406
+ }
407
+ }
408
+
409
+ // Get existing conversation or start fresh
410
+ const existingMessages = (options.conversation as any)?.messages ?? [];
411
+ const existingSystem = (options.conversation as any)?.system ?? prompt.system;
412
+
413
+ // Build the new messages array
414
+ const newMessages = [
415
+ ...existingMessages,
416
+ ...prompt.messages,
417
+ ];
418
+
419
+ // Only add assistant message if there's actual content
420
+ // (Claude API rejects empty text content blocks)
421
+ if (content.length > 0) {
422
+ newMessages.push({
423
+ role: 'assistant',
424
+ content: content
425
+ });
426
+ }
427
+
428
+ // Build the new conversation in ClaudePrompt format
429
+ const conversation = {
430
+ messages: newMessages,
431
+ system: existingSystem
432
+ };
433
+
434
+ // Increment turn counter
435
+ const withTurn = incrementConversationTurn(conversation);
436
+
437
+ // Apply stripping based on options
438
+ const currentTurn = getConversationMeta(withTurn).turnNumber;
439
+ const stripOptions = {
440
+ keepForTurns: options.stripImagesAfterTurns ?? Infinity,
441
+ currentTurn,
442
+ textMaxTokens: options.stripTextMaxTokens
443
+ };
444
+ let processedConversation = stripBase64ImagesFromConversation(withTurn, stripOptions);
445
+ processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
446
+
447
+ return processedConversation;
448
+ }
449
+
250
450
  async requestImageGeneration(
251
451
  _prompt: ImagenPrompt,
252
452
  _options: ExecutionOptions,
@@ -298,7 +498,7 @@ export class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions, Vertex
298
498
  //Used to exclude retired models that are still in the listing API but not available for use.
299
499
  //Or models we do not support yet
300
500
  const unsupportedModelsByPublisher = {
301
- google: ["gemini-pro", "gemini-ultra", "imagen-product-recontext-preview", "embedding"],
501
+ google: ["gemini-pro", "gemini-ultra", "imagen-product-recontext-preview", "embedding", "gemini-live-2.5-flash-preview-native-audio", "computer-use-preview"],
302
502
  anthropic: [],
303
503
  meta: [],
304
504
  };
@@ -380,6 +580,46 @@ export class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions, Vertex
380
580
  } satisfies AIModel<string>;
381
581
  }));
382
582
 
583
+ // Create global google gemini models for Gemini 2.5 and later, if missing from GenAI listing
584
+ if (publisher === 'google') {
585
+ const globalGeminiModels = response.filter((model) => {
586
+ const modelName = model.name ?? "";
587
+ if (retiredModels.some(retiredModel => modelName.includes(retiredModel))) {
588
+ return false;
589
+ }
590
+ if (modelFamily.some(family => modelName.includes(family))) {
591
+ const versionMatch = modelName.match(/gemini-(\d+(?:\.\d+)?)/);
592
+ if (versionMatch) {
593
+ const version = parseFloat(versionMatch[1]);
594
+ if (version >= 2.5) {
595
+ // Check if already present
596
+ const shortName = modelName.split('/').pop();
597
+ const globalName = "Global " + shortName;
598
+ if (models.some(m => m.name === globalName)) {
599
+ return false;
600
+ }
601
+ return true;
602
+ }
603
+ }
604
+ return false;
605
+ }
606
+ return false;
607
+ }).map(model => {
608
+ const modelCapability = getModelCapabilities(model.name ?? '', "vertexai");
609
+ return {
610
+ id: "locations/global/" + model.name,
611
+ name: "Global " + model.name?.split('/').pop(),
612
+ provider: 'vertexai',
613
+ owner: publisher,
614
+ input_modalities: modelModalitiesToArray(modelCapability.input),
615
+ output_modalities: modelModalitiesToArray(modelCapability.output),
616
+ tool_support: modelCapability.tool_support,
617
+ } satisfies AIModel<string>;
618
+ });
619
+
620
+ models = models.concat(globalGeminiModels);
621
+ }
622
+
383
623
  // Create global anthropic models for those not in NON_GLOBAL_ANTHROPIC_MODELS
384
624
  if (publisher === 'anthropic') {
385
625
  const globalAnthropicModels = response.filter((model) => {
@@ -452,6 +692,15 @@ export class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions, Vertex
452
692
  };
453
693
  return getEmbeddingsForText(this, text_options);
454
694
  }
695
+
696
+ /**
697
+ * Cleanup Google Cloud clients when the driver is evicted from the cache.
698
+ */
699
+ destroy(): void {
700
+ this.aiplatform?.close();
701
+ this.modelGarden?.close();
702
+ this.imagenClient?.close();
703
+ }
455
704
  }
456
705
 
457
706
  //'us-central1-aiplatform.googleapis.com',