@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
@@ -6,13 +6,26 @@ import { BedrockRuntime, ConverseRequest, ConverseResponse, ConverseStreamOutput
6
6
  import { S3Client } from "@aws-sdk/client-s3";
7
7
  import { AwsCredentialIdentity, Provider } from "@aws-sdk/types";
8
8
  import {
9
- AbstractDriver, AIModel, Completion, CompletionChunkObject, DataSource, DriverOptions, EmbeddingsOptions, EmbeddingsResult,
10
- ExecutionOptions, ExecutionTokenUsage, PromptSegment,
11
- TextFallbackOptions, ToolDefinition, ToolUse, TrainingJob, TrainingJobStatus, TrainingOptions,
12
- BedrockClaudeOptions, BedrockPalmyraOptions, BedrockGptOssOptions, getMaxTokensLimitBedrock, NovaCanvasOptions,
13
- modelModalitiesToArray, getModelCapabilities,
9
+ AbstractDriver, AIModel,
10
+ BedrockClaudeOptions,
11
+ BedrockGptOssOptions,
12
+ BedrockPalmyraOptions,
13
+ Completion, CompletionChunkObject, DataSource, DriverOptions, EmbeddingsOptions, EmbeddingsResult,
14
+ ExecutionOptions, ExecutionTokenUsage,
15
+ getMaxTokensLimitBedrock,
16
+ getModelCapabilities,
17
+ modelModalitiesToArray,
18
+ ModelOptions,
19
+ NovaCanvasOptions,
20
+ PromptSegment,
14
21
  StatelessExecutionOptions,
15
- ModelOptions
22
+ stripBinaryFromConversation,
23
+ truncateLargeTextInConversation,
24
+ deserializeBinaryFromStorage,
25
+ getConversationMeta,
26
+ incrementConversationTurn,
27
+ TextFallbackOptions, ToolDefinition, ToolUse, TrainingJob, TrainingJobStatus, TrainingOptions,
28
+ CompletionResult
16
29
  } from "@llumiverse/core";
17
30
  import { transformAsyncIterator } from "@llumiverse/core/async";
18
31
  import { formatNovaPrompt, NovaMessagesPrompt } from "@llumiverse/core/formatters";
@@ -22,9 +35,9 @@ import { formatNovaImageGenerationPayload, NovaImageGenerationTaskType } from ".
22
35
  import { forceUploadFile } from "./s3.js";
23
36
  import {
24
37
  formatTwelvelabsPegasusPrompt,
25
- TwelvelabsPegasusRequest,
26
38
  TwelvelabsMarengoRequest,
27
- TwelvelabsMarengoResponse
39
+ TwelvelabsMarengoResponse,
40
+ TwelvelabsPegasusRequest
28
41
  } from "./twelvelabs.js";
29
42
 
30
43
  const supportStreamingCache = new LRUCache<string, boolean>(4096);
@@ -114,7 +127,6 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
114
127
  this._executor = new BedrockRuntime({
115
128
  region: this.options.region,
116
129
  credentials: this.options.credentials,
117
-
118
130
  });
119
131
  }
120
132
  return this._executor;
@@ -350,6 +362,91 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
350
362
  return canStream;
351
363
  }
352
364
 
365
+ /**
366
+ * Build conversation context after streaming completion.
367
+ * Reconstructs the assistant message from accumulated results and applies stripping.
368
+ */
369
+ buildStreamingConversation(
370
+ prompt: BedrockPrompt,
371
+ result: unknown[],
372
+ toolUse: unknown[] | undefined,
373
+ options: ExecutionOptions
374
+ ): ConverseRequest | undefined {
375
+ // Only handle ConverseRequest prompts (not NovaMessagesPrompt or TwelvelabsPegasusRequest)
376
+ if (options.model.includes("canvas") || options.model.includes("twelvelabs.pegasus")) {
377
+ return undefined;
378
+ }
379
+
380
+ const conversePrompt = prompt as ConverseRequest;
381
+ const completionResults = result as CompletionResult[];
382
+
383
+ // Convert accumulated results to text content for assistant message
384
+ const textContent = completionResults
385
+ .map(r => {
386
+ switch (r.type) {
387
+ case 'text':
388
+ return r.value;
389
+ case 'json':
390
+ return typeof r.value === 'string' ? r.value : JSON.stringify(r.value);
391
+ case 'image':
392
+ // Skip images in conversation - they're in the result
393
+ return '';
394
+ default:
395
+ return String((r as any).value || '');
396
+ }
397
+ })
398
+ .join('');
399
+
400
+ // Deserialize any base64-encoded binary data back to Uint8Array
401
+ const incomingConversation = deserializeBinaryFromStorage(options.conversation) as ConverseRequest;
402
+
403
+ // Start with the conversation from options combined with the prompt
404
+ let conversation = updateConversation(incomingConversation, conversePrompt);
405
+
406
+ // Build assistant message content
407
+ const messageContent: any[] = [];
408
+ if (textContent) {
409
+ messageContent.push({ text: textContent });
410
+ }
411
+ // Add tool use blocks if present
412
+ if (toolUse && toolUse.length > 0) {
413
+ for (const tool of toolUse as ToolUse[]) {
414
+ messageContent.push({
415
+ toolUse: {
416
+ toolUseId: tool.id,
417
+ name: tool.tool_name,
418
+ input: tool.tool_input,
419
+ }
420
+ });
421
+ }
422
+ }
423
+
424
+ // Add assistant message
425
+ const assistantMessage: ConverseRequest = {
426
+ messages: [{
427
+ content: messageContent.length > 0 ? messageContent : [{ text: '' }],
428
+ role: "assistant"
429
+ }],
430
+ modelId: conversePrompt.modelId,
431
+ };
432
+ conversation = updateConversation(conversation, assistantMessage);
433
+
434
+ // Increment turn counter
435
+ conversation = incrementConversationTurn(conversation) as ConverseRequest;
436
+
437
+ // Apply stripping based on options
438
+ const currentTurn = getConversationMeta(conversation).turnNumber;
439
+ const stripOptions = {
440
+ keepForTurns: options.stripImagesAfterTurns ?? Infinity,
441
+ currentTurn,
442
+ textMaxTokens: options.stripTextMaxTokens
443
+ };
444
+ let processedConversation = stripBinaryFromConversation(conversation, stripOptions);
445
+ processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
446
+
447
+ return processedConversation as ConverseRequest;
448
+ }
449
+
353
450
  async requestTextCompletion(prompt: BedrockPrompt, options: ExecutionOptions): Promise<Completion> {
354
451
  // Handle Twelvelabs Pegasus models
355
452
  if (options.model.includes("twelvelabs.pegasus")) {
@@ -358,7 +455,10 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
358
455
 
359
456
  // Handle other Bedrock models that use Converse API
360
457
  const conversePrompt = prompt as ConverseRequest;
361
- let conversation = updateConversation(options.conversation as ConverseRequest, conversePrompt);
458
+
459
+ // Deserialize any base64-encoded binary data back to Uint8Array before API call
460
+ const incomingConversation = deserializeBinaryFromStorage(options.conversation) as ConverseRequest;
461
+ let conversation = updateConversation(incomingConversation, conversePrompt);
362
462
 
363
463
  const payload = this.preparePayload(conversation, options);
364
464
  const executor = this.getExecutor();
@@ -372,6 +472,9 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
372
472
  modelId: conversePrompt.modelId,
373
473
  });
374
474
 
475
+ // Increment turn counter for deferred stripping
476
+ conversation = incrementConversationTurn(conversation) as ConverseRequest;
477
+
375
478
  let tool_use: ToolUse[] | undefined = undefined;
376
479
  //Get tool requests, we check tool use regardless of finish reason, as you can hit length and still get a valid response.
377
480
  tool_use = res.output?.message?.content?.reduce((tools: ToolUse[], c) => {
@@ -389,10 +492,22 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
389
492
  tool_use = undefined;
390
493
  }
391
494
 
495
+ // Strip/serialize binary data based on options.stripImagesAfterTurns
496
+ const currentTurn = getConversationMeta(conversation).turnNumber;
497
+ const stripOptions = {
498
+ keepForTurns: options.stripImagesAfterTurns ?? Infinity,
499
+ currentTurn,
500
+ textMaxTokens: options.stripTextMaxTokens
501
+ };
502
+ let processedConversation = stripBinaryFromConversation(conversation, stripOptions);
503
+
504
+ // Truncate large text content if configured
505
+ processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
506
+
392
507
  const completion = {
393
508
  ...this.getExtractedExecution(res, conversePrompt, options),
394
509
  original_response: options.include_original_response ? res : undefined,
395
- conversation: conversation,
510
+ conversation: processedConversation,
396
511
  tool_use: tool_use,
397
512
  };
398
513
 
@@ -496,7 +611,13 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
496
611
 
497
612
  // Handle other Bedrock models that use Converse API
498
613
  const conversePrompt = prompt as ConverseRequest;
499
- const payload = this.preparePayload(conversePrompt, options);
614
+
615
+ // Include conversation history (same as non-streaming)
616
+ // Deserialize any base64-encoded binary data back to Uint8Array before API call
617
+ const incomingConversation = deserializeBinaryFromStorage(options.conversation) as ConverseRequest;
618
+ const conversation = updateConversation(incomingConversation, conversePrompt);
619
+
620
+ const payload = this.preparePayload(conversation, options);
500
621
  const executor = this.getExecutor();
501
622
  return executor.converseStream({
502
623
  ...payload,
@@ -642,22 +763,38 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
642
763
  prompt.messages = converseJSONprefill(prompt.messages);
643
764
  }
644
765
 
766
+ // Clean undefined values from additionalField since AWS Bedrock requires valid JSON
767
+ // and will throw an exception for unrecognized parameters
768
+ const cleanedAdditionalFields = removeUndefinedValues(additionalField);
769
+ const cleanedModelOptions = removeUndefinedValues({
770
+ maxTokens: model_options.max_tokens,
771
+ temperature: model_options.temperature,
772
+ topP: model_options.top_p,
773
+ stopSequences: model_options.stop_sequence,
774
+ } satisfies InferenceConfiguration);
775
+
776
+ //Construct the final request payload
777
+ // We only add fields that are defined to avoid AWS errors
645
778
  const request: ConverseRequest = {
646
- messages: prompt.messages,
647
- system: prompt.system,
648
779
  modelId: options.model,
649
- inferenceConfig: {
650
- maxTokens: model_options.max_tokens,
651
- temperature: model_options.temperature,
652
- topP: model_options.top_p,
653
- stopSequences: model_options.stop_sequence,
654
- } satisfies InferenceConfiguration,
655
- additionalModelRequestFields: {
656
- ...additionalField,
657
- }
658
780
  };
659
781
 
660
- //Only add tools if they are defined and not empty
782
+ if (prompt.messages) {
783
+ request.messages = prompt.messages;
784
+ }
785
+
786
+ if (prompt.system) {
787
+ request.system = prompt.system;
788
+ }
789
+
790
+ if (Object.keys(cleanedModelOptions).length > 0) {
791
+ request.inferenceConfig = cleanedModelOptions
792
+ }
793
+
794
+ if (Object.keys(cleanedAdditionalFields).length > 0) {
795
+ request.additionalModelRequestFields = cleanedAdditionalFields;
796
+ }
797
+
661
798
  if (tool_defs?.length) {
662
799
  request.toolConfig = {
663
800
  tools: tool_defs,
@@ -1087,6 +1224,33 @@ function getToolDefinition(tool: ToolDefinition): Tool.ToolSpecMember {
1087
1224
  }
1088
1225
  }
1089
1226
 
1227
+ /**
1228
+ * Recursively removes undefined values from an object.
1229
+ * AWS Bedrock's additionalModelRequestFields must be valid JSON, and undefined is not valid JSON.
1230
+ * Any unrecognized parameters will cause an exception.
1231
+ */
1232
+ function removeUndefinedValues<T extends Record<string, any>>(obj: T): Partial<T> {
1233
+ if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
1234
+ return obj;
1235
+ }
1236
+
1237
+ const cleaned: any = {};
1238
+ for (const [key, value] of Object.entries(obj)) {
1239
+ if (value !== undefined) {
1240
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
1241
+ const cleanedNested = removeUndefinedValues(value);
1242
+ // Only include nested objects if they have properties after cleaning
1243
+ if (Object.keys(cleanedNested).length > 0) {
1244
+ cleaned[key] = cleanedNested;
1245
+ }
1246
+ } else {
1247
+ cleaned[key] = value;
1248
+ }
1249
+ }
1250
+ }
1251
+ return cleaned;
1252
+ }
1253
+
1090
1254
  /**
1091
1255
  * Update the conversation messages
1092
1256
  * @param prompt
package/src/groq/index.ts CHANGED
@@ -3,9 +3,13 @@ import { transformAsyncIterator } from "@llumiverse/core/async";
3
3
  import { formatOpenAILikeMultimodalPrompt } from "../openai/openai_format.js";
4
4
 
5
5
  import Groq from "groq-sdk";
6
+ import type OpenAI from "openai";
6
7
  import type { ChatCompletionMessageParam, ChatCompletionTool } from "groq-sdk/resources/chat/completions";
7
8
  import type { FunctionParameters } from "groq-sdk/resources/shared";
8
9
 
10
+ type ResponseInputItem = OpenAI.Responses.ResponseInputItem;
11
+ type EasyInputMessage = OpenAI.Responses.EasyInputMessage;
12
+
9
13
  interface GroqDriverOptions extends DriverOptions {
10
14
  apiKey: string;
11
15
  endpoint_url?: string;
@@ -49,104 +53,13 @@ export class GroqDriver extends AbstractDriver<GroqDriverOptions, ChatCompletion
49
53
 
50
54
  protected async formatPrompt(segments: PromptSegment[], opts: ExecutionOptions): Promise<ChatCompletionMessageParam[]> {
51
55
  // Use OpenAI's multimodal formatter as base then convert to Groq types
52
- const openaiMessages = await formatOpenAILikeMultimodalPrompt(segments, {
56
+ const responseItems = await formatOpenAILikeMultimodalPrompt(segments, {
53
57
  ...opts,
54
58
  multimodal: true,
55
59
  });
56
60
 
57
- // Convert OpenAI ChatCompletionMessageParam[] to Groq ChatCompletionMessageParam[]
58
- // Handle differences between OpenAI and Groq SDK types
59
- const groqMessages: ChatCompletionMessageParam[] = openaiMessages.map(msg => {
60
- // Handle OpenAI developer messages - convert to system messages for Groq
61
- if (msg.role === 'developer' || msg.role === 'system') {
62
- const systemMsg: ChatCompletionMessageParam = {
63
- role: 'system',
64
- content: Array.isArray(msg.content)
65
- ? msg.content.map(part => part.text).join('\n')
66
- : msg.content,
67
- // Preserve name if present
68
- ...(msg.name && { name: msg.name })
69
- };
70
- return systemMsg;
71
- }
72
-
73
- // Handle user messages - filter content parts to only supported types
74
- if (msg.role === 'user') {
75
- let content: string | Array<{ type: 'text', text: string } | { type: 'image_url', image_url: { url: string, detail?: 'auto' | 'low' | 'high' } }> | undefined = undefined;
76
-
77
- if (typeof msg.content === 'string') {
78
- content = msg.content;
79
- } else if (Array.isArray(msg.content)) {
80
- // Filter to only text and image_url parts that Groq supports
81
- const supportedParts = msg.content.filter(part =>
82
- part.type === 'text' || part.type === 'image_url'
83
- ).map(part => {
84
- if (part.type === 'text') {
85
- return { type: 'text' as const, text: part.text };
86
- } else if (part.type === 'image_url') {
87
- return {
88
- type: 'image_url' as const,
89
- image_url: {
90
- url: part.image_url.url,
91
- ...(part.image_url.detail && { detail: part.image_url.detail })
92
- }
93
- };
94
- }
95
- return null;
96
- }).filter(Boolean) as Array<{ type: 'text', text: string } | { type: 'image_url', image_url: { url: string, detail?: 'auto' | 'low' | 'high' } }>;
97
-
98
- content = supportedParts.length > 0 ? supportedParts : 'Content not supported';
99
- }
100
-
101
- const userMsg: ChatCompletionMessageParam = {
102
- role: 'user',
103
- content: content ?? "",
104
- // Preserve name if present
105
- ...(msg.name && { name: msg.name })
106
- };
107
- return userMsg;
108
- }
109
-
110
- // Handle assistant messages - handle content arrays if needed
111
- if (msg.role === 'assistant') {
112
- const assistantMsg: ChatCompletionMessageParam = {
113
- role: 'assistant',
114
- content: Array.isArray(msg.content)
115
- ? msg.content.map(part => 'text' in part ? part.text : '').filter(Boolean).join('\n') || null
116
- : msg.content,
117
- // Preserve other assistant message properties
118
- ...(msg.tool_calls && { tool_calls: msg.tool_calls }),
119
- ...(msg.name && { name: msg.name })
120
- };
121
- return assistantMsg;
122
- }
123
-
124
- // For tool and function messages, they should be compatible
125
- if (msg.role === 'tool') {
126
- const toolMsg: ChatCompletionMessageParam = {
127
- role: 'tool',
128
- tool_call_id: msg.tool_call_id,
129
- content: Array.isArray(msg.content)
130
- ? msg.content.map(part => part.text).join('\n')
131
- : msg.content
132
- };
133
- return toolMsg;
134
- }
135
-
136
- if (msg.role === 'function') {
137
- const functionMsg: ChatCompletionMessageParam = {
138
- role: 'function',
139
- name: msg.name,
140
- content: msg.content
141
- };
142
- return functionMsg;
143
- }
144
-
145
- // Fallback - should not reach here but provides type safety
146
- throw new Error(`Unsupported message role: ${(msg as any).role}`);
147
- });
148
-
149
- return groqMessages;
61
+ // Convert ResponseInputItem[] to Groq ChatCompletionMessageParam[]
62
+ return convertResponseItemsToGroqMessages(responseItems);
150
63
  }
151
64
 
152
65
  private getToolDefinitions(tools: ToolDefinition[] | undefined): ChatCompletionTool[] | undefined {
@@ -342,4 +255,117 @@ function updateConversation(
342
255
  messages: ChatCompletionMessageParam[]
343
256
  ): ChatCompletionMessageParam[] {
344
257
  return (conversation || []).concat(messages);
258
+ }
259
+
260
+ /**
261
+ * Convert ResponseInputItem[] to Groq ChatCompletionMessageParam[]
262
+ */
263
+ function convertResponseItemsToGroqMessages(items: ResponseInputItem[]): ChatCompletionMessageParam[] {
264
+ const messages: ChatCompletionMessageParam[] = [];
265
+
266
+ for (const item of items) {
267
+ // Handle EasyInputMessage (has role and content)
268
+ if ('role' in item && 'content' in item) {
269
+ const msg = item as EasyInputMessage;
270
+ const role = msg.role;
271
+
272
+ // Handle system/developer messages
273
+ if (role === 'system' || role === 'developer') {
274
+ let content: string;
275
+ if (typeof msg.content === 'string') {
276
+ content = msg.content;
277
+ } else if (Array.isArray(msg.content)) {
278
+ content = msg.content
279
+ .filter((part): part is OpenAI.Responses.ResponseInputText => part.type === 'input_text')
280
+ .map(part => part.text)
281
+ .join('\n');
282
+ } else {
283
+ content = '';
284
+ }
285
+ messages.push({ role: 'system', content });
286
+ continue;
287
+ }
288
+
289
+ // Handle user messages
290
+ if (role === 'user') {
291
+ let content: string | Array<{ type: 'text', text: string } | { type: 'image_url', image_url: { url: string, detail?: 'auto' | 'low' | 'high' } }>;
292
+ if (typeof msg.content === 'string') {
293
+ content = msg.content;
294
+ } else if (Array.isArray(msg.content)) {
295
+ const parts: Array<{ type: 'text', text: string } | { type: 'image_url', image_url: { url: string, detail?: 'auto' | 'low' | 'high' } }> = [];
296
+ for (const part of msg.content) {
297
+ if (part.type === 'input_text') {
298
+ parts.push({ type: 'text', text: part.text });
299
+ } else if (part.type === 'input_image') {
300
+ const imgPart = part as OpenAI.Responses.ResponseInputImage;
301
+ if (imgPart.image_url) {
302
+ parts.push({
303
+ type: 'image_url',
304
+ image_url: {
305
+ url: imgPart.image_url,
306
+ ...(imgPart.detail && { detail: imgPart.detail })
307
+ }
308
+ });
309
+ }
310
+ }
311
+ }
312
+ content = parts.length > 0 ? parts : '';
313
+ } else {
314
+ content = '';
315
+ }
316
+ messages.push({ role: 'user', content });
317
+ continue;
318
+ }
319
+
320
+ // Handle assistant messages
321
+ if (role === 'assistant') {
322
+ let content: string | null;
323
+ if (typeof msg.content === 'string') {
324
+ content = msg.content;
325
+ } else if (Array.isArray(msg.content)) {
326
+ content = msg.content
327
+ .filter((part): part is OpenAI.Responses.ResponseInputText => part.type === 'input_text')
328
+ .map(part => part.text)
329
+ .join('\n') || null;
330
+ } else {
331
+ content = null;
332
+ }
333
+ messages.push({ role: 'assistant', content });
334
+ continue;
335
+ }
336
+ }
337
+
338
+ // Handle function_call_output (tool response)
339
+ if ('type' in item && item.type === 'function_call_output') {
340
+ const output = item as OpenAI.Responses.ResponseInputItem.FunctionCallOutput;
341
+ messages.push({
342
+ role: 'tool',
343
+ tool_call_id: output.call_id,
344
+ content: typeof output.output === 'string' ? output.output : JSON.stringify(output.output),
345
+ });
346
+ continue;
347
+ }
348
+
349
+ // Handle function_call (assistant tool call)
350
+ if ('type' in item && item.type === 'function_call') {
351
+ const call = item as OpenAI.Responses.ResponseFunctionToolCall;
352
+ // Groq expects tool_calls in assistant message, but we handle them separately
353
+ // This is a simplification - in practice tool_calls come from model responses
354
+ messages.push({
355
+ role: 'assistant',
356
+ content: null,
357
+ tool_calls: [{
358
+ id: call.call_id,
359
+ type: 'function',
360
+ function: {
361
+ name: call.name,
362
+ arguments: call.arguments,
363
+ }
364
+ }]
365
+ });
366
+ continue;
367
+ }
368
+ }
369
+
370
+ return messages;
345
371
  }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export * from "./huggingface_ie.js";
5
5
  export * from "./mistral/index.js";
6
6
  export * from "./openai/azure_openai.js";
7
7
  export * from "./openai/openai.js";
8
+ export * from "./openai/openai_compatible.js";
8
9
  export * from "./replicate.js";
9
10
  export * from "./test-driver/index.js";
10
11
  export * from "./togetherai/index.js";