@llumiverse/drivers 0.23.0 → 0.24.0-dev.20260203.164053Z

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 (230) hide show
  1. package/README.md +141 -218
  2. package/package.json +12 -12
  3. package/src/azure/azure_foundry.ts +56 -7
  4. package/src/bedrock/index.ts +188 -24
  5. package/src/groq/index.ts +120 -94
  6. package/src/index.ts +1 -0
  7. package/src/openai/index.ts +363 -136
  8. package/src/openai/openai_compatible.ts +74 -0
  9. package/src/openai/openai_format.ts +44 -54
  10. package/src/vertexai/index.ts +186 -0
  11. package/src/vertexai/models/claude.ts +97 -2
  12. package/src/vertexai/models/gemini.ts +78 -27
  13. package/src/xai/index.ts +10 -17
  14. package/lib/cjs/adobe/firefly.js +0 -120
  15. package/lib/cjs/adobe/firefly.js.map +0 -1
  16. package/lib/cjs/azure/azure_foundry.js +0 -388
  17. package/lib/cjs/azure/azure_foundry.js.map +0 -1
  18. package/lib/cjs/bedrock/converse.js +0 -285
  19. package/lib/cjs/bedrock/converse.js.map +0 -1
  20. package/lib/cjs/bedrock/index.js +0 -966
  21. package/lib/cjs/bedrock/index.js.map +0 -1
  22. package/lib/cjs/bedrock/nova-image-payload.js +0 -207
  23. package/lib/cjs/bedrock/nova-image-payload.js.map +0 -1
  24. package/lib/cjs/bedrock/payloads.js +0 -3
  25. package/lib/cjs/bedrock/payloads.js.map +0 -1
  26. package/lib/cjs/bedrock/s3.js +0 -107
  27. package/lib/cjs/bedrock/s3.js.map +0 -1
  28. package/lib/cjs/bedrock/twelvelabs.js +0 -87
  29. package/lib/cjs/bedrock/twelvelabs.js.map +0 -1
  30. package/lib/cjs/groq/index.js +0 -293
  31. package/lib/cjs/groq/index.js.map +0 -1
  32. package/lib/cjs/huggingface_ie.js +0 -201
  33. package/lib/cjs/huggingface_ie.js.map +0 -1
  34. package/lib/cjs/index.js +0 -30
  35. package/lib/cjs/index.js.map +0 -1
  36. package/lib/cjs/mistral/index.js +0 -173
  37. package/lib/cjs/mistral/index.js.map +0 -1
  38. package/lib/cjs/mistral/types.js +0 -83
  39. package/lib/cjs/mistral/types.js.map +0 -1
  40. package/lib/cjs/openai/azure_openai.js +0 -72
  41. package/lib/cjs/openai/azure_openai.js.map +0 -1
  42. package/lib/cjs/openai/index.js +0 -469
  43. package/lib/cjs/openai/index.js.map +0 -1
  44. package/lib/cjs/openai/openai.js +0 -21
  45. package/lib/cjs/openai/openai.js.map +0 -1
  46. package/lib/cjs/openai/openai_format.js +0 -138
  47. package/lib/cjs/openai/openai_format.js.map +0 -1
  48. package/lib/cjs/package.json +0 -3
  49. package/lib/cjs/replicate.js +0 -275
  50. package/lib/cjs/replicate.js.map +0 -1
  51. package/lib/cjs/test-driver/TestErrorCompletionStream.js +0 -20
  52. package/lib/cjs/test-driver/TestErrorCompletionStream.js.map +0 -1
  53. package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js +0 -24
  54. package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
  55. package/lib/cjs/test-driver/index.js +0 -109
  56. package/lib/cjs/test-driver/index.js.map +0 -1
  57. package/lib/cjs/test-driver/utils.js +0 -30
  58. package/lib/cjs/test-driver/utils.js.map +0 -1
  59. package/lib/cjs/togetherai/index.js +0 -126
  60. package/lib/cjs/togetherai/index.js.map +0 -1
  61. package/lib/cjs/togetherai/interfaces.js +0 -3
  62. package/lib/cjs/togetherai/interfaces.js.map +0 -1
  63. package/lib/cjs/vertexai/debug.js +0 -12
  64. package/lib/cjs/vertexai/debug.js.map +0 -1
  65. package/lib/cjs/vertexai/embeddings/embeddings-image.js +0 -27
  66. package/lib/cjs/vertexai/embeddings/embeddings-image.js.map +0 -1
  67. package/lib/cjs/vertexai/embeddings/embeddings-text.js +0 -23
  68. package/lib/cjs/vertexai/embeddings/embeddings-text.js.map +0 -1
  69. package/lib/cjs/vertexai/index.js +0 -429
  70. package/lib/cjs/vertexai/index.js.map +0 -1
  71. package/lib/cjs/vertexai/models/claude.js +0 -399
  72. package/lib/cjs/vertexai/models/claude.js.map +0 -1
  73. package/lib/cjs/vertexai/models/gemini.js +0 -832
  74. package/lib/cjs/vertexai/models/gemini.js.map +0 -1
  75. package/lib/cjs/vertexai/models/imagen.js +0 -303
  76. package/lib/cjs/vertexai/models/imagen.js.map +0 -1
  77. package/lib/cjs/vertexai/models/llama.js +0 -183
  78. package/lib/cjs/vertexai/models/llama.js.map +0 -1
  79. package/lib/cjs/vertexai/models.js +0 -35
  80. package/lib/cjs/vertexai/models.js.map +0 -1
  81. package/lib/cjs/watsonx/index.js +0 -161
  82. package/lib/cjs/watsonx/index.js.map +0 -1
  83. package/lib/cjs/watsonx/interfaces.js +0 -3
  84. package/lib/cjs/watsonx/interfaces.js.map +0 -1
  85. package/lib/cjs/xai/index.js +0 -71
  86. package/lib/cjs/xai/index.js.map +0 -1
  87. package/lib/esm/adobe/firefly.js +0 -116
  88. package/lib/esm/adobe/firefly.js.map +0 -1
  89. package/lib/esm/azure/azure_foundry.js +0 -382
  90. package/lib/esm/azure/azure_foundry.js.map +0 -1
  91. package/lib/esm/bedrock/converse.js +0 -278
  92. package/lib/esm/bedrock/converse.js.map +0 -1
  93. package/lib/esm/bedrock/index.js +0 -962
  94. package/lib/esm/bedrock/index.js.map +0 -1
  95. package/lib/esm/bedrock/nova-image-payload.js +0 -203
  96. package/lib/esm/bedrock/nova-image-payload.js.map +0 -1
  97. package/lib/esm/bedrock/payloads.js +0 -2
  98. package/lib/esm/bedrock/payloads.js.map +0 -1
  99. package/lib/esm/bedrock/s3.js +0 -99
  100. package/lib/esm/bedrock/s3.js.map +0 -1
  101. package/lib/esm/bedrock/twelvelabs.js +0 -84
  102. package/lib/esm/bedrock/twelvelabs.js.map +0 -1
  103. package/lib/esm/groq/index.js +0 -286
  104. package/lib/esm/groq/index.js.map +0 -1
  105. package/lib/esm/huggingface_ie.js +0 -197
  106. package/lib/esm/huggingface_ie.js.map +0 -1
  107. package/lib/esm/index.js +0 -14
  108. package/lib/esm/index.js.map +0 -1
  109. package/lib/esm/mistral/index.js +0 -169
  110. package/lib/esm/mistral/index.js.map +0 -1
  111. package/lib/esm/mistral/types.js +0 -80
  112. package/lib/esm/mistral/types.js.map +0 -1
  113. package/lib/esm/openai/azure_openai.js +0 -68
  114. package/lib/esm/openai/azure_openai.js.map +0 -1
  115. package/lib/esm/openai/index.js +0 -464
  116. package/lib/esm/openai/index.js.map +0 -1
  117. package/lib/esm/openai/openai.js +0 -14
  118. package/lib/esm/openai/openai.js.map +0 -1
  119. package/lib/esm/openai/openai_format.js +0 -134
  120. package/lib/esm/openai/openai_format.js.map +0 -1
  121. package/lib/esm/replicate.js +0 -268
  122. package/lib/esm/replicate.js.map +0 -1
  123. package/lib/esm/test-driver/TestErrorCompletionStream.js +0 -16
  124. package/lib/esm/test-driver/TestErrorCompletionStream.js.map +0 -1
  125. package/lib/esm/test-driver/TestValidationErrorCompletionStream.js +0 -20
  126. package/lib/esm/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
  127. package/lib/esm/test-driver/index.js +0 -91
  128. package/lib/esm/test-driver/index.js.map +0 -1
  129. package/lib/esm/test-driver/utils.js +0 -25
  130. package/lib/esm/test-driver/utils.js.map +0 -1
  131. package/lib/esm/togetherai/index.js +0 -122
  132. package/lib/esm/togetherai/index.js.map +0 -1
  133. package/lib/esm/togetherai/interfaces.js +0 -2
  134. package/lib/esm/togetherai/interfaces.js.map +0 -1
  135. package/lib/esm/vertexai/debug.js +0 -6
  136. package/lib/esm/vertexai/debug.js.map +0 -1
  137. package/lib/esm/vertexai/embeddings/embeddings-image.js +0 -24
  138. package/lib/esm/vertexai/embeddings/embeddings-image.js.map +0 -1
  139. package/lib/esm/vertexai/embeddings/embeddings-text.js +0 -20
  140. package/lib/esm/vertexai/embeddings/embeddings-text.js.map +0 -1
  141. package/lib/esm/vertexai/index.js +0 -424
  142. package/lib/esm/vertexai/index.js.map +0 -1
  143. package/lib/esm/vertexai/models/claude.js +0 -394
  144. package/lib/esm/vertexai/models/claude.js.map +0 -1
  145. package/lib/esm/vertexai/models/gemini.js +0 -827
  146. package/lib/esm/vertexai/models/gemini.js.map +0 -1
  147. package/lib/esm/vertexai/models/imagen.js +0 -299
  148. package/lib/esm/vertexai/models/imagen.js.map +0 -1
  149. package/lib/esm/vertexai/models/llama.js +0 -179
  150. package/lib/esm/vertexai/models/llama.js.map +0 -1
  151. package/lib/esm/vertexai/models.js +0 -32
  152. package/lib/esm/vertexai/models.js.map +0 -1
  153. package/lib/esm/watsonx/index.js +0 -157
  154. package/lib/esm/watsonx/index.js.map +0 -1
  155. package/lib/esm/watsonx/interfaces.js +0 -2
  156. package/lib/esm/watsonx/interfaces.js.map +0 -1
  157. package/lib/esm/xai/index.js +0 -64
  158. package/lib/esm/xai/index.js.map +0 -1
  159. package/lib/types/adobe/firefly.d.ts +0 -30
  160. package/lib/types/adobe/firefly.d.ts.map +0 -1
  161. package/lib/types/azure/azure_foundry.d.ts +0 -50
  162. package/lib/types/azure/azure_foundry.d.ts.map +0 -1
  163. package/lib/types/bedrock/converse.d.ts +0 -9
  164. package/lib/types/bedrock/converse.d.ts.map +0 -1
  165. package/lib/types/bedrock/index.d.ts +0 -63
  166. package/lib/types/bedrock/index.d.ts.map +0 -1
  167. package/lib/types/bedrock/nova-image-payload.d.ts +0 -74
  168. package/lib/types/bedrock/nova-image-payload.d.ts.map +0 -1
  169. package/lib/types/bedrock/payloads.d.ts +0 -12
  170. package/lib/types/bedrock/payloads.d.ts.map +0 -1
  171. package/lib/types/bedrock/s3.d.ts +0 -23
  172. package/lib/types/bedrock/s3.d.ts.map +0 -1
  173. package/lib/types/bedrock/twelvelabs.d.ts +0 -50
  174. package/lib/types/bedrock/twelvelabs.d.ts.map +0 -1
  175. package/lib/types/groq/index.d.ts +0 -27
  176. package/lib/types/groq/index.d.ts.map +0 -1
  177. package/lib/types/huggingface_ie.d.ts +0 -35
  178. package/lib/types/huggingface_ie.d.ts.map +0 -1
  179. package/lib/types/index.d.ts +0 -14
  180. package/lib/types/index.d.ts.map +0 -1
  181. package/lib/types/mistral/index.d.ts +0 -25
  182. package/lib/types/mistral/index.d.ts.map +0 -1
  183. package/lib/types/mistral/types.d.ts +0 -132
  184. package/lib/types/mistral/types.d.ts.map +0 -1
  185. package/lib/types/openai/azure_openai.d.ts +0 -25
  186. package/lib/types/openai/azure_openai.d.ts.map +0 -1
  187. package/lib/types/openai/index.d.ts +0 -25
  188. package/lib/types/openai/index.d.ts.map +0 -1
  189. package/lib/types/openai/openai.d.ts +0 -15
  190. package/lib/types/openai/openai.d.ts.map +0 -1
  191. package/lib/types/openai/openai_format.d.ts +0 -19
  192. package/lib/types/openai/openai_format.d.ts.map +0 -1
  193. package/lib/types/replicate.d.ts +0 -48
  194. package/lib/types/replicate.d.ts.map +0 -1
  195. package/lib/types/test-driver/TestErrorCompletionStream.d.ts +0 -9
  196. package/lib/types/test-driver/TestErrorCompletionStream.d.ts.map +0 -1
  197. package/lib/types/test-driver/TestValidationErrorCompletionStream.d.ts +0 -9
  198. package/lib/types/test-driver/TestValidationErrorCompletionStream.d.ts.map +0 -1
  199. package/lib/types/test-driver/index.d.ts +0 -24
  200. package/lib/types/test-driver/index.d.ts.map +0 -1
  201. package/lib/types/test-driver/utils.d.ts +0 -5
  202. package/lib/types/test-driver/utils.d.ts.map +0 -1
  203. package/lib/types/togetherai/index.d.ts +0 -23
  204. package/lib/types/togetherai/index.d.ts.map +0 -1
  205. package/lib/types/togetherai/interfaces.d.ts +0 -96
  206. package/lib/types/togetherai/interfaces.d.ts.map +0 -1
  207. package/lib/types/vertexai/debug.d.ts +0 -2
  208. package/lib/types/vertexai/debug.d.ts.map +0 -1
  209. package/lib/types/vertexai/embeddings/embeddings-image.d.ts +0 -11
  210. package/lib/types/vertexai/embeddings/embeddings-image.d.ts.map +0 -1
  211. package/lib/types/vertexai/embeddings/embeddings-text.d.ts +0 -10
  212. package/lib/types/vertexai/embeddings/embeddings-text.d.ts.map +0 -1
  213. package/lib/types/vertexai/index.d.ts +0 -54
  214. package/lib/types/vertexai/index.d.ts.map +0 -1
  215. package/lib/types/vertexai/models/claude.d.ts +0 -20
  216. package/lib/types/vertexai/models/claude.d.ts.map +0 -1
  217. package/lib/types/vertexai/models/gemini.d.ts +0 -18
  218. package/lib/types/vertexai/models/gemini.d.ts.map +0 -1
  219. package/lib/types/vertexai/models/imagen.d.ts +0 -75
  220. package/lib/types/vertexai/models/imagen.d.ts.map +0 -1
  221. package/lib/types/vertexai/models/llama.d.ts +0 -20
  222. package/lib/types/vertexai/models/llama.d.ts.map +0 -1
  223. package/lib/types/vertexai/models.d.ts +0 -15
  224. package/lib/types/vertexai/models.d.ts.map +0 -1
  225. package/lib/types/watsonx/index.d.ts +0 -27
  226. package/lib/types/watsonx/index.d.ts.map +0 -1
  227. package/lib/types/watsonx/interfaces.d.ts +0 -65
  228. package/lib/types/watsonx/interfaces.d.ts.map +0 -1
  229. package/lib/types/xai/index.d.ts +0 -19
  230. package/lib/types/xai/index.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,6 +6,7 @@ import {
6
6
  AbstractDriver,
7
7
  Completion,
8
8
  CompletionChunkObject,
9
+ CompletionResult,
9
10
  DriverOptions,
10
11
  EmbeddingsOptions,
11
12
  EmbeddingsResult,
@@ -14,6 +15,11 @@ import {
14
15
  PromptSegment,
15
16
  getModelCapabilities,
16
17
  modelModalitiesToArray,
18
+ stripBase64ImagesFromConversation,
19
+ truncateLargeTextInConversation,
20
+ getConversationMeta,
21
+ incrementConversationTurn,
22
+ unwrapConversationArray,
17
23
  } from "@llumiverse/core";
18
24
  import { FetchClient } from "@vertesia/api-fetch-client";
19
25
  import { GoogleAuth, GoogleAuthOptions, AuthClient } from "google-auth-library";
@@ -251,6 +257,186 @@ export class VertexAIDriver extends AbstractDriver<VertexAIDriverOptions, Vertex
251
257
  return getModelDefinition(options.model).requestTextCompletionStream(this, prompt, options);
252
258
  }
253
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
+ parts.push({
309
+ functionCall: {
310
+ name: tool.tool_name,
311
+ args: tool.tool_input,
312
+ }
313
+ });
314
+ }
315
+ }
316
+
317
+ // Build assistant message in Gemini Content format
318
+ const assistantContent: Content = {
319
+ role: 'model',
320
+ parts: parts.length > 0 ? parts : [{ text: '' }]
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 + assistant response
328
+ let conversation: Content[] = [
329
+ ...existingConversation,
330
+ ...prompt.contents,
331
+ assistantContent
332
+ ];
333
+
334
+ // Increment turn counter
335
+ conversation = incrementConversationTurn(conversation) as Content[];
336
+
337
+ // Apply stripping based on options
338
+ const currentTurn = getConversationMeta(conversation).turnNumber;
339
+ const stripOptions = {
340
+ keepForTurns: options.stripImagesAfterTurns ?? Infinity,
341
+ currentTurn,
342
+ textMaxTokens: options.stripTextMaxTokens
343
+ };
344
+ let processedConversation = stripBase64ImagesFromConversation(conversation, stripOptions);
345
+ processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
346
+
347
+ return processedConversation as Content[];
348
+ }
349
+
350
+ /**
351
+ * Build conversation for Claude streaming.
352
+ * Creates assistant message with tool_use blocks in Claude's ContentBlock format.
353
+ */
354
+ private buildClaudeStreamingConversation(
355
+ prompt: { messages: unknown[]; system?: unknown[] },
356
+ result: unknown[],
357
+ toolUse: unknown[] | undefined,
358
+ options: ExecutionOptions
359
+ ): unknown {
360
+ const completionResults = result as CompletionResult[];
361
+
362
+ // Convert accumulated results to text content
363
+ const textContent = completionResults
364
+ .map(r => {
365
+ switch (r.type) {
366
+ case 'text':
367
+ return r.value;
368
+ case 'json':
369
+ return typeof r.value === 'string' ? r.value : JSON.stringify(r.value);
370
+ case 'image':
371
+ return '';
372
+ default:
373
+ return String((r as any).value || '');
374
+ }
375
+ })
376
+ .join('');
377
+
378
+ // Build Claude-style ContentBlock array for assistant message
379
+ const content: unknown[] = [];
380
+
381
+ // Add text block if there's text content
382
+ if (textContent) {
383
+ content.push({
384
+ type: 'text',
385
+ text: textContent
386
+ });
387
+ }
388
+
389
+ // Add tool_use blocks in Claude format
390
+ if (toolUse && toolUse.length > 0) {
391
+ for (const tool of toolUse as any[]) {
392
+ content.push({
393
+ type: 'tool_use',
394
+ id: tool.id,
395
+ name: tool.tool_name,
396
+ input: tool.tool_input ?? {}
397
+ });
398
+ }
399
+ }
400
+
401
+ // Build assistant message
402
+ const assistantMessage = {
403
+ role: 'assistant',
404
+ content: content.length > 0 ? content : [{ type: 'text', text: '' }]
405
+ };
406
+
407
+ // Get existing conversation or start fresh
408
+ const existingMessages = (options.conversation as any)?.messages ?? [];
409
+ const existingSystem = (options.conversation as any)?.system ?? prompt.system;
410
+
411
+ // Combine: existing conversation + new prompt messages + assistant response
412
+ const newMessages = [
413
+ ...existingMessages,
414
+ ...prompt.messages,
415
+ assistantMessage
416
+ ];
417
+
418
+ // Build the new conversation in ClaudePrompt format
419
+ const conversation = {
420
+ messages: newMessages,
421
+ system: existingSystem
422
+ };
423
+
424
+ // Increment turn counter
425
+ const withTurn = incrementConversationTurn(conversation);
426
+
427
+ // Apply stripping based on options
428
+ const currentTurn = getConversationMeta(withTurn).turnNumber;
429
+ const stripOptions = {
430
+ keepForTurns: options.stripImagesAfterTurns ?? Infinity,
431
+ currentTurn,
432
+ textMaxTokens: options.stripTextMaxTokens
433
+ };
434
+ let processedConversation = stripBase64ImagesFromConversation(withTurn, stripOptions);
435
+ processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
436
+
437
+ return processedConversation;
438
+ }
439
+
254
440
  async requestImageGeneration(
255
441
  _prompt: ImagenPrompt,
256
442
  _options: ExecutionOptions,
@@ -321,11 +321,17 @@ export class ClaudeModelDefinition implements ModelDefinition<ClaudePrompt> {
321
321
  driver.logger.warn({ options: options.model_options }, "Invalid model options");
322
322
  }
323
323
 
324
- const { payload, requestOptions } = getClaudePayload(options, prompt);
324
+ // Include conversation history (same as non-streaming)
325
+ const conversation = updateConversation(options.conversation as ClaudePrompt, prompt);
326
+
327
+ const { payload, requestOptions } = getClaudePayload(options, conversation);
325
328
  const streamingPayload: MessageStreamParams = { ...payload, stream: true };
326
329
 
327
330
  const response_stream = await client.messages.stream(streamingPayload, requestOptions);
328
331
 
332
+ // Track current tool use being built from streaming
333
+ let currentToolUse: { id: string; name: string; inputJson: string } | null = null;
334
+
329
335
  const stream = asyncMap(response_stream, async (streamEvent: RawMessageStreamEvent) => {
330
336
  switch (streamEvent.type) {
331
337
  case "message_start":
@@ -345,6 +351,22 @@ export class ClaudeModelDefinition implements ModelDefinition<ClaudePrompt> {
345
351
  finish_reason: claudeFinishReason(streamEvent.delta.stop_reason ?? undefined),
346
352
  } satisfies CompletionChunkObject;
347
353
  case "content_block_start":
354
+ // Handle tool_use blocks
355
+ if (streamEvent.content_block.type === "tool_use") {
356
+ currentToolUse = {
357
+ id: streamEvent.content_block.id,
358
+ name: streamEvent.content_block.name,
359
+ inputJson: ''
360
+ };
361
+ return {
362
+ result: [],
363
+ tool_use: [{
364
+ id: streamEvent.content_block.id,
365
+ tool_name: streamEvent.content_block.name,
366
+ tool_input: '' as any // Will be accumulated via input_json_delta
367
+ }]
368
+ } satisfies CompletionChunkObject;
369
+ }
348
370
  // Handle redacted thinking blocks
349
371
  if (streamEvent.content_block.type === "redacted_thinking" && model_options?.include_thoughts) {
350
372
  return {
@@ -359,6 +381,19 @@ export class ClaudeModelDefinition implements ModelDefinition<ClaudePrompt> {
359
381
  return {
360
382
  result: streamEvent.delta.text ? [{ type: "text", value: streamEvent.delta.text }] : []
361
383
  } satisfies CompletionChunkObject;
384
+ case "input_json_delta":
385
+ // Accumulate tool input JSON
386
+ if (currentToolUse && streamEvent.delta.partial_json) {
387
+ return {
388
+ result: [],
389
+ tool_use: [{
390
+ id: currentToolUse.id,
391
+ tool_name: '', // Name already sent in content_block_start
392
+ tool_input: streamEvent.delta.partial_json as any
393
+ }]
394
+ } satisfies CompletionChunkObject;
395
+ }
396
+ break;
362
397
  case "thinking_delta":
363
398
  if (model_options?.include_thoughts) {
364
399
  return {
@@ -377,6 +412,10 @@ export class ClaudeModelDefinition implements ModelDefinition<ClaudePrompt> {
377
412
  }
378
413
  break;
379
414
  case "content_block_stop":
415
+ // Reset current tool use tracking when block ends
416
+ if (currentToolUse) {
417
+ currentToolUse = null;
418
+ }
380
419
  // Handle the end of content blocks, for redacted thinking blocks
381
420
  if (model_options?.include_thoughts) {
382
421
  return {
@@ -406,6 +445,60 @@ function createPromptFromResponse(response: Message): ClaudePrompt {
406
445
  }
407
446
  }
408
447
 
448
+ /**
449
+ * Merge consecutive user messages in the conversation.
450
+ * This is required because Anthropic's API expects all tool_result blocks
451
+ * from a single assistant turn to be in one user message.
452
+ * When multiple tool results are added as separate user messages,
453
+ * we need to merge them before sending to the API.
454
+ */
455
+ export function mergeConsecutiveUserMessages(messages: MessageParam[]): MessageParam[] {
456
+ if (messages.length === 0) return [];
457
+
458
+ // Check if any merging is needed
459
+ const needsMerging = messages.some((msg, i) =>
460
+ i < messages.length - 1 &&
461
+ msg.role === 'user' &&
462
+ messages[i + 1].role === 'user'
463
+ );
464
+
465
+ if (!needsMerging) {
466
+ return messages;
467
+ }
468
+
469
+ const result: MessageParam[] = [];
470
+ let i = 0;
471
+
472
+ while (i < messages.length) {
473
+ const current = messages[i];
474
+
475
+ if (current.role === 'user') {
476
+ // Collect all consecutive user messages
477
+ const mergedContent: MessageParam['content'] = [];
478
+
479
+ while (i < messages.length && messages[i].role === 'user') {
480
+ const userMsg = messages[i];
481
+ if (Array.isArray(userMsg.content)) {
482
+ mergedContent.push(...userMsg.content);
483
+ } else if (typeof userMsg.content === 'string') {
484
+ mergedContent.push({ type: 'text', text: userMsg.content });
485
+ }
486
+ i++;
487
+ }
488
+
489
+ result.push({
490
+ role: 'user',
491
+ content: mergedContent
492
+ });
493
+ } else {
494
+ result.push(current);
495
+ i++;
496
+ }
497
+ }
498
+
499
+ return result;
500
+ }
501
+
409
502
  /**
410
503
  * Update the conversation messages
411
504
  * @param prompt
@@ -416,8 +509,10 @@ function updateConversation(conversation: ClaudePrompt | undefined | null, promp
416
509
  const baseSystemMessages = conversation?.system || [];
417
510
  const baseMessages = conversation?.messages || [];
418
511
  const system = baseSystemMessages.concat(prompt.system || []);
512
+ // Merge consecutive user messages to ensure tool_result blocks are properly grouped
513
+ const mergedMessages = mergeConsecutiveUserMessages(baseMessages.concat(prompt.messages || []));
419
514
  return {
420
- messages: baseMessages.concat(prompt.messages || []),
515
+ messages: mergedMessages,
421
516
  system: system.length > 0 ? system : undefined // If system is empty, set to undefined
422
517
  };
423
518
  }