@llumiverse/drivers 1.0.0-dev.20260224.234313Z → 1.0.0-dev.20260327.110720Z

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 (68) hide show
  1. package/lib/cjs/bedrock/converse.js +86 -12
  2. package/lib/cjs/bedrock/converse.js.map +1 -1
  3. package/lib/cjs/bedrock/index.js +208 -1
  4. package/lib/cjs/bedrock/index.js.map +1 -1
  5. package/lib/cjs/groq/index.js +7 -4
  6. package/lib/cjs/groq/index.js.map +1 -1
  7. package/lib/cjs/openai/index.js +457 -26
  8. package/lib/cjs/openai/index.js.map +1 -1
  9. package/lib/cjs/openai/openai_compatible.js +1 -0
  10. package/lib/cjs/openai/openai_compatible.js.map +1 -1
  11. package/lib/cjs/vertexai/index.js +42 -0
  12. package/lib/cjs/vertexai/index.js.map +1 -1
  13. package/lib/cjs/vertexai/models/claude.js +230 -2
  14. package/lib/cjs/vertexai/models/claude.js.map +1 -1
  15. package/lib/cjs/vertexai/models/gemini.js +261 -41
  16. package/lib/cjs/vertexai/models/gemini.js.map +1 -1
  17. package/lib/cjs/vertexai/models.js +1 -1
  18. package/lib/cjs/vertexai/models.js.map +1 -1
  19. package/lib/esm/bedrock/converse.js +80 -6
  20. package/lib/esm/bedrock/converse.js.map +1 -1
  21. package/lib/esm/bedrock/index.js +207 -2
  22. package/lib/esm/bedrock/index.js.map +1 -1
  23. package/lib/esm/groq/index.js +7 -4
  24. package/lib/esm/groq/index.js.map +1 -1
  25. package/lib/esm/openai/index.js +456 -27
  26. package/lib/esm/openai/index.js.map +1 -1
  27. package/lib/esm/openai/openai_compatible.js +1 -0
  28. package/lib/esm/openai/openai_compatible.js.map +1 -1
  29. package/lib/esm/vertexai/index.js +43 -1
  30. package/lib/esm/vertexai/index.js.map +1 -1
  31. package/lib/esm/vertexai/models/claude.js +229 -3
  32. package/lib/esm/vertexai/models/claude.js.map +1 -1
  33. package/lib/esm/vertexai/models/gemini.js +262 -43
  34. package/lib/esm/vertexai/models/gemini.js.map +1 -1
  35. package/lib/esm/vertexai/models.js +1 -1
  36. package/lib/esm/vertexai/models.js.map +1 -1
  37. package/lib/types/bedrock/converse.d.ts +1 -2
  38. package/lib/types/bedrock/converse.d.ts.map +1 -1
  39. package/lib/types/bedrock/index.d.ts +54 -2
  40. package/lib/types/bedrock/index.d.ts.map +1 -1
  41. package/lib/types/openai/index.d.ts +96 -1
  42. package/lib/types/openai/index.d.ts.map +1 -1
  43. package/lib/types/openai/openai_compatible.d.ts +5 -0
  44. package/lib/types/openai/openai_compatible.d.ts.map +1 -1
  45. package/lib/types/openai/openai_format.d.ts +1 -1
  46. package/lib/types/vertexai/index.d.ts +11 -1
  47. package/lib/types/vertexai/index.d.ts.map +1 -1
  48. package/lib/types/vertexai/models/claude.d.ts +64 -1
  49. package/lib/types/vertexai/models/claude.d.ts.map +1 -1
  50. package/lib/types/vertexai/models/gemini.d.ts +61 -1
  51. package/lib/types/vertexai/models/gemini.d.ts.map +1 -1
  52. package/lib/types/vertexai/models.d.ts +6 -1
  53. package/lib/types/vertexai/models.d.ts.map +1 -1
  54. package/package.json +9 -9
  55. package/src/bedrock/converse.ts +85 -10
  56. package/src/bedrock/error-handling.test.ts +352 -0
  57. package/src/bedrock/index.ts +228 -3
  58. package/src/groq/index.ts +9 -4
  59. package/src/openai/error-handling.test.ts +567 -0
  60. package/src/openai/index.ts +505 -29
  61. package/src/openai/openai_compatible.ts +7 -0
  62. package/src/openai/openai_format.ts +1 -1
  63. package/src/vertexai/index.ts +56 -5
  64. package/src/vertexai/models/claude-error-handling.test.ts +432 -0
  65. package/src/vertexai/models/claude.ts +273 -7
  66. package/src/vertexai/models/gemini-error-handling.test.ts +353 -0
  67. package/src/vertexai/models/gemini.ts +304 -48
  68. package/src/vertexai/models.ts +7 -2
@@ -20,12 +20,14 @@ import {
20
20
  getMaxTokensLimitBedrock,
21
21
  getModelCapabilities,
22
22
  incrementConversationTurn,
23
+ LlumiverseError, LlumiverseErrorContext,
23
24
  modelModalitiesToArray,
24
25
  ModelOptions,
25
26
  NovaCanvasOptions,
26
27
  PromptSegment,
27
28
  StatelessExecutionOptions,
28
29
  stripBinaryFromConversation,
30
+ stripHeartbeatsFromConversation,
29
31
  TextFallbackOptions, ToolDefinition, ToolUse, TrainingJob, TrainingJobStatus, TrainingOptions,
30
32
  truncateLargeTextInConversation
31
33
  } from "@llumiverse/core";
@@ -84,9 +86,10 @@ export interface BedrockDriverOptions extends DriverOptions {
84
86
  training_role_arn?: string;
85
87
 
86
88
  /**
87
- * The credentials to use to access AWS
89
+ * The credentials to use to access AWS (IAM access key + secret)
88
90
  */
89
91
  credentials?: AwsCredentialIdentity | Provider<AwsCredentialIdentity>;
92
+
90
93
  }
91
94
 
92
95
  //Used to get a max_token value when not specified in the model options. Claude requires it to be set.
@@ -190,6 +193,143 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
190
193
  return await formatConversePrompt(segments, opts);
191
194
  }
192
195
 
196
+ /**
197
+ * Format AWS Bedrock errors into LlumiverseError with proper status codes and retryability.
198
+ *
199
+ * AWS SDK errors provide:
200
+ * - error.name: The exception type (e.g., "ThrottlingException")
201
+ * - error.$metadata.httpStatusCode: The HTTP status code
202
+ * - error.$metadata.requestId: The AWS request ID for tracking
203
+ * - error.$fault: "client" or "server" indicating error category
204
+ *
205
+ * @param error - The AWS SDK error
206
+ * @param context - Context about where the error occurred
207
+ * @returns A standardized LlumiverseError
208
+ */
209
+ public formatLlumiverseError(
210
+ error: unknown,
211
+ context: LlumiverseErrorContext
212
+ ): LlumiverseError {
213
+ // Check if it's an AWS SDK error with $metadata
214
+ const awsError = error as any;
215
+ const hasMetadata = awsError?.$metadata !== undefined;
216
+
217
+ if (!hasMetadata) {
218
+ // Not an AWS SDK error, use default handling
219
+ return super.formatLlumiverseError(error, context);
220
+ }
221
+
222
+ // Extract AWS-specific fields
223
+ const errorName = awsError.name || 'UnknownError';
224
+ const httpStatusCode = awsError.$metadata?.httpStatusCode;
225
+ const requestId = awsError.$metadata?.requestId;
226
+ const fault = awsError.$fault; // "client" or "server"
227
+
228
+ // Extract error message - handle both Error instances and plain objects
229
+ let message: string;
230
+ if (error instanceof Error) {
231
+ message = error.message;
232
+ } else if (typeof awsError.message === 'string') {
233
+ message = awsError.message;
234
+ } else {
235
+ message = String(error);
236
+ }
237
+
238
+ // Build user-facing message with error name and status code
239
+ let userMessage = message;
240
+
241
+ // Include status code in message if available (for end-user visibility)
242
+ if (httpStatusCode) {
243
+ userMessage = `[${httpStatusCode}] ${userMessage}`;
244
+ }
245
+
246
+ // Prefix with error name if it's meaningful (not just "Error")
247
+ if (errorName && errorName !== 'Error' && errorName !== 'UnknownError') {
248
+ userMessage = `${errorName}: ${userMessage}`;
249
+ }
250
+
251
+ // Add request ID if available (useful for AWS support)
252
+ if (requestId) {
253
+ userMessage += ` (Request ID: ${requestId})`;
254
+ }
255
+
256
+ // Determine retryability based on AWS error types
257
+ const retryable = this.isBedrockErrorRetryable(errorName, httpStatusCode, fault);
258
+
259
+ return new LlumiverseError(
260
+ `[${this.provider}] ${userMessage}`,
261
+ retryable,
262
+ context,
263
+ error,
264
+ httpStatusCode, // Only set code if we have numeric status code
265
+ errorName // Preserve AWS error name
266
+ );
267
+ }
268
+
269
+ /**
270
+ * Determine if a Bedrock error is retryable based on error type and status.
271
+ *
272
+ * Retryable errors:
273
+ * - ThrottlingException: Rate limit exceeded, retry with backoff
274
+ * - ServiceUnavailableException: Service temporarily down
275
+ * - InternalServerException: Server-side error
276
+ * - ServiceQuotaExceededException: Quota exhausted, may recover
277
+ * - 5xx status codes: Server errors
278
+ * - 429, 408 status codes: Rate limit, timeout
279
+ *
280
+ * Non-retryable errors:
281
+ * - ValidationException: Invalid request parameters
282
+ * - AccessDeniedException: Authentication/authorization failure
283
+ * - ResourceNotFoundException: Resource doesn't exist
284
+ * - ConflictException: Resource state conflict
285
+ * - ResourceInUseException: Resource locked by another operation
286
+ * - 4xx status codes (except 429, 408): Client errors
287
+ *
288
+ * @param errorName - The AWS error name (e.g., "ThrottlingException")
289
+ * @param httpStatusCode - The HTTP status code if available
290
+ * @param fault - The fault type ("client" or "server")
291
+ * @returns True if retryable, false if not retryable, undefined if unknown
292
+ */
293
+ private isBedrockErrorRetryable(
294
+ errorName: string,
295
+ httpStatusCode: number | undefined,
296
+ fault: string | undefined
297
+ ): boolean | undefined {
298
+ // Check specific AWS error types first
299
+ switch (errorName) {
300
+ // Retryable errors
301
+ case 'ThrottlingException':
302
+ case 'ServiceUnavailableException':
303
+ case 'InternalServerException':
304
+ case 'ServiceQuotaExceededException':
305
+ return true;
306
+
307
+ // Non-retryable errors
308
+ case 'ValidationException':
309
+ case 'AccessDeniedException':
310
+ case 'ResourceNotFoundException':
311
+ case 'ConflictException':
312
+ case 'ResourceInUseException':
313
+ case 'TooManyTagsException':
314
+ return false;
315
+ }
316
+
317
+ // If we have HTTP status code, use it
318
+ if (httpStatusCode !== undefined) {
319
+ if (httpStatusCode === 429 || httpStatusCode === 408) return true; // Rate limit, timeout
320
+ if (httpStatusCode === 529) return true; // Overloaded
321
+ if (httpStatusCode >= 500 && httpStatusCode < 600) return true; // Server errors
322
+ if (httpStatusCode >= 400 && httpStatusCode < 500) return false; // Client errors
323
+ }
324
+
325
+ // Fall back to fault type
326
+ if (fault === 'server') return true;
327
+ if (fault === 'client') return false;
328
+
329
+ // Unknown error type - let consumer decide retry strategy
330
+ return undefined;
331
+ }
332
+
193
333
  getExtractedExecution(result: ConverseResponse, _prompt?: BedrockPrompt, options?: ExecutionOptions): CompletionChunkObject {
194
334
  let resultText = "";
195
335
  let reasoning = "";
@@ -483,6 +623,10 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
483
623
  };
484
624
  let processedConversation = stripBinaryFromConversation(conversation, stripOptions);
485
625
  processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
626
+ processedConversation = stripHeartbeatsFromConversation(processedConversation, {
627
+ keepForTurns: options.stripHeartbeatsAfterTurns ?? 1,
628
+ currentTurn,
629
+ });
486
630
 
487
631
  return processedConversation as ConverseRequest;
488
632
  }
@@ -551,6 +695,12 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
551
695
  // Truncate large text content if configured
552
696
  processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
553
697
 
698
+ // Strip old heartbeat status messages
699
+ processedConversation = stripHeartbeatsFromConversation(processedConversation, {
700
+ keepForTurns: options.stripHeartbeatsAfterTurns ?? 1,
701
+ currentTurn,
702
+ });
703
+
554
704
  const completion = {
555
705
  ...this.getExtractedExecution(res, conversePrompt, options),
556
706
  original_response: options.include_original_response ? res : undefined,
@@ -852,6 +1002,12 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
852
1002
  request.toolConfig = {
853
1003
  tools: tool_defs,
854
1004
  }
1005
+ } else if (request.messages && messagesContainToolBlocks(request.messages)) {
1006
+ // Bedrock requires toolConfig when conversation contains toolUse/toolResult blocks.
1007
+ // When no tools are provided (e.g. checkpoint summary calls), convert tool blocks
1008
+ // to text representations so the conversation data is preserved while satisfying
1009
+ // Bedrock's API requirements without making tools callable.
1010
+ request.messages = convertToolBlocksToText(request.messages);
855
1011
  }
856
1012
 
857
1013
  return request;
@@ -1195,7 +1351,7 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
1195
1351
  const executor = this.getExecutor();
1196
1352
 
1197
1353
  // Prepare the request payload for TwelveLabs Marengo
1198
- let invokeBody: TwelvelabsMarengoRequest = {
1354
+ const invokeBody: TwelvelabsMarengoRequest = {
1199
1355
  inputType: "text"
1200
1356
  };
1201
1357
 
@@ -1285,6 +1441,75 @@ function getToolDefinition(tool: ToolDefinition): Tool.ToolSpecMember {
1285
1441
  }
1286
1442
  }
1287
1443
 
1444
+ /**
1445
+ * Checks whether any message contains toolUse or toolResult content blocks.
1446
+ */
1447
+ export function messagesContainToolBlocks(messages: Message[]): boolean {
1448
+ for (const msg of messages) {
1449
+ if (!msg.content) continue;
1450
+ for (const block of msg.content) {
1451
+ if ((block as ContentBlock.ToolUseMember).toolUse ||
1452
+ (block as ContentBlock.ToolResultMember).toolResult) {
1453
+ return true;
1454
+ }
1455
+ }
1456
+ }
1457
+ return false;
1458
+ }
1459
+
1460
+ /**
1461
+ * Converts toolUse and toolResult content blocks to text representations.
1462
+ * This preserves the tool call information in the conversation while removing
1463
+ * the structured tool blocks that require Bedrock's toolConfig to be set.
1464
+ *
1465
+ * Used when no tools are provided (e.g. checkpoint summary calls) but the
1466
+ * conversation history contains tool interactions from prior turns.
1467
+ */
1468
+ export function convertToolBlocksToText(messages: Message[]): Message[] {
1469
+ return messages.map(msg => {
1470
+ if (!msg.content) return msg;
1471
+ let hasToolBlocks = false;
1472
+ for (const block of msg.content) {
1473
+ if ((block as ContentBlock.ToolUseMember).toolUse ||
1474
+ (block as ContentBlock.ToolResultMember).toolResult) {
1475
+ hasToolBlocks = true;
1476
+ break;
1477
+ }
1478
+ }
1479
+ if (!hasToolBlocks) return msg;
1480
+
1481
+ const newContent: ContentBlock[] = [];
1482
+ for (const block of msg.content) {
1483
+ const toolUse = (block as ContentBlock.ToolUseMember).toolUse;
1484
+ const toolResult = (block as ContentBlock.ToolResultMember).toolResult;
1485
+ if (toolUse) {
1486
+ const inputStr = toolUse.input ? JSON.stringify(toolUse.input) : '';
1487
+ const truncatedInput = inputStr.length > 500 ? inputStr.substring(0, 500) + '...' : inputStr;
1488
+ newContent.push({
1489
+ text: `[Tool call: ${toolUse.name}(${truncatedInput})]`,
1490
+ } as ContentBlock.TextMember);
1491
+ } else if (toolResult) {
1492
+ const resultTexts: string[] = [];
1493
+ if (toolResult.content) {
1494
+ for (const c of toolResult.content) {
1495
+ if ((c as any).text) {
1496
+ const text = (c as any).text as string;
1497
+ resultTexts.push(text.length > 500 ? text.substring(0, 500) + '...' : text);
1498
+ }
1499
+ }
1500
+ }
1501
+ const resultStr = resultTexts.length > 0 ? resultTexts.join('\n') : 'No text content';
1502
+ newContent.push({
1503
+ text: `[Tool result: ${resultStr}]`,
1504
+ } as ContentBlock.TextMember);
1505
+ } else {
1506
+ newContent.push(block);
1507
+ }
1508
+ }
1509
+ return { ...msg, content: newContent };
1510
+ });
1511
+ }
1512
+
1288
1513
  /**
1289
1514
  * Recursively removes undefined values from an object.
1290
1515
  * AWS Bedrock's additionalModelRequestFields must be valid JSON, and undefined is not valid JSON.
@@ -1447,4 +1672,4 @@ function formatAmazonModalities(modalities: ModelModality[]): string[] {
1447
1672
  }
1448
1673
  }
1449
1674
  return standardizedModalities;
1450
- }
1675
+ }
package/src/groq/index.ts CHANGED
@@ -299,12 +299,17 @@ function convertResponseItemsToGroqMessages(items: ResponseInputItem[]): ChatCom
299
299
  } else if (part.type === 'input_image') {
300
300
  const imgPart = part as OpenAI.Responses.ResponseInputImage;
301
301
  if (imgPart.image_url) {
302
+ const image_url: { url: string; detail?: 'auto' | 'low' | 'high' } = {
303
+ url: imgPart.image_url
304
+ };
305
+
306
+ if (imgPart.detail) {
307
+ image_url.detail = imgPart.detail as 'auto' | 'low' | 'high';
308
+ }
309
+
302
310
  parts.push({
303
311
  type: 'image_url',
304
- image_url: {
305
- url: imgPart.image_url,
306
- ...(imgPart.detail && { detail: imgPart.detail })
307
- }
312
+ image_url
308
313
  });
309
314
  }
310
315
  }