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

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 +53 -1
  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 +225 -1
  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";
@@ -190,6 +192,143 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
190
192
  return await formatConversePrompt(segments, opts);
191
193
  }
192
194
 
195
+ /**
196
+ * Format AWS Bedrock errors into LlumiverseError with proper status codes and retryability.
197
+ *
198
+ * AWS SDK errors provide:
199
+ * - error.name: The exception type (e.g., "ThrottlingException")
200
+ * - error.$metadata.httpStatusCode: The HTTP status code
201
+ * - error.$metadata.requestId: The AWS request ID for tracking
202
+ * - error.$fault: "client" or "server" indicating error category
203
+ *
204
+ * @param error - The AWS SDK error
205
+ * @param context - Context about where the error occurred
206
+ * @returns A standardized LlumiverseError
207
+ */
208
+ public formatLlumiverseError(
209
+ error: unknown,
210
+ context: LlumiverseErrorContext
211
+ ): LlumiverseError {
212
+ // Check if it's an AWS SDK error with $metadata
213
+ const awsError = error as any;
214
+ const hasMetadata = awsError?.$metadata !== undefined;
215
+
216
+ if (!hasMetadata) {
217
+ // Not an AWS SDK error, use default handling
218
+ return super.formatLlumiverseError(error, context);
219
+ }
220
+
221
+ // Extract AWS-specific fields
222
+ const errorName = awsError.name || 'UnknownError';
223
+ const httpStatusCode = awsError.$metadata?.httpStatusCode;
224
+ const requestId = awsError.$metadata?.requestId;
225
+ const fault = awsError.$fault; // "client" or "server"
226
+
227
+ // Extract error message - handle both Error instances and plain objects
228
+ let message: string;
229
+ if (error instanceof Error) {
230
+ message = error.message;
231
+ } else if (typeof awsError.message === 'string') {
232
+ message = awsError.message;
233
+ } else {
234
+ message = String(error);
235
+ }
236
+
237
+ // Build user-facing message with error name and status code
238
+ let userMessage = message;
239
+
240
+ // Include status code in message if available (for end-user visibility)
241
+ if (httpStatusCode) {
242
+ userMessage = `[${httpStatusCode}] ${userMessage}`;
243
+ }
244
+
245
+ // Prefix with error name if it's meaningful (not just "Error")
246
+ if (errorName && errorName !== 'Error' && errorName !== 'UnknownError') {
247
+ userMessage = `${errorName}: ${userMessage}`;
248
+ }
249
+
250
+ // Add request ID if available (useful for AWS support)
251
+ if (requestId) {
252
+ userMessage += ` (Request ID: ${requestId})`;
253
+ }
254
+
255
+ // Determine retryability based on AWS error types
256
+ const retryable = this.isBedrockErrorRetryable(errorName, httpStatusCode, fault);
257
+
258
+ return new LlumiverseError(
259
+ `[${this.provider}] ${userMessage}`,
260
+ retryable,
261
+ context,
262
+ error,
263
+ httpStatusCode, // Only set code if we have numeric status code
264
+ errorName // Preserve AWS error name
265
+ );
266
+ }
267
+
268
+ /**
269
+ * Determine if a Bedrock error is retryable based on error type and status.
270
+ *
271
+ * Retryable errors:
272
+ * - ThrottlingException: Rate limit exceeded, retry with backoff
273
+ * - ServiceUnavailableException: Service temporarily down
274
+ * - InternalServerException: Server-side error
275
+ * - ServiceQuotaExceededException: Quota exhausted, may recover
276
+ * - 5xx status codes: Server errors
277
+ * - 429, 408 status codes: Rate limit, timeout
278
+ *
279
+ * Non-retryable errors:
280
+ * - ValidationException: Invalid request parameters
281
+ * - AccessDeniedException: Authentication/authorization failure
282
+ * - ResourceNotFoundException: Resource doesn't exist
283
+ * - ConflictException: Resource state conflict
284
+ * - ResourceInUseException: Resource locked by another operation
285
+ * - 4xx status codes (except 429, 408): Client errors
286
+ *
287
+ * @param errorName - The AWS error name (e.g., "ThrottlingException")
288
+ * @param httpStatusCode - The HTTP status code if available
289
+ * @param fault - The fault type ("client" or "server")
290
+ * @returns True if retryable, false if not retryable, undefined if unknown
291
+ */
292
+ private isBedrockErrorRetryable(
293
+ errorName: string,
294
+ httpStatusCode: number | undefined,
295
+ fault: string | undefined
296
+ ): boolean | undefined {
297
+ // Check specific AWS error types first
298
+ switch (errorName) {
299
+ // Retryable errors
300
+ case 'ThrottlingException':
301
+ case 'ServiceUnavailableException':
302
+ case 'InternalServerException':
303
+ case 'ServiceQuotaExceededException':
304
+ return true;
305
+
306
+ // Non-retryable errors
307
+ case 'ValidationException':
308
+ case 'AccessDeniedException':
309
+ case 'ResourceNotFoundException':
310
+ case 'ConflictException':
311
+ case 'ResourceInUseException':
312
+ case 'TooManyTagsException':
313
+ return false;
314
+ }
315
+
316
+ // If we have HTTP status code, use it
317
+ if (httpStatusCode !== undefined) {
318
+ if (httpStatusCode === 429 || httpStatusCode === 408) return true; // Rate limit, timeout
319
+ if (httpStatusCode === 529) return true; // Overloaded
320
+ if (httpStatusCode >= 500 && httpStatusCode < 600) return true; // Server errors
321
+ if (httpStatusCode >= 400 && httpStatusCode < 500) return false; // Client errors
322
+ }
323
+
324
+ // Fall back to fault type
325
+ if (fault === 'server') return true;
326
+ if (fault === 'client') return false;
327
+
328
+ // Unknown error type - let consumer decide retry strategy
329
+ return undefined;
330
+ }
331
+
193
332
  getExtractedExecution(result: ConverseResponse, _prompt?: BedrockPrompt, options?: ExecutionOptions): CompletionChunkObject {
194
333
  let resultText = "";
195
334
  let reasoning = "";
@@ -483,6 +622,10 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
483
622
  };
484
623
  let processedConversation = stripBinaryFromConversation(conversation, stripOptions);
485
624
  processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
625
+ processedConversation = stripHeartbeatsFromConversation(processedConversation, {
626
+ keepForTurns: options.stripHeartbeatsAfterTurns ?? 1,
627
+ currentTurn,
628
+ });
486
629
 
487
630
  return processedConversation as ConverseRequest;
488
631
  }
@@ -551,6 +694,12 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
551
694
  // Truncate large text content if configured
552
695
  processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
553
696
 
697
+ // Strip old heartbeat status messages
698
+ processedConversation = stripHeartbeatsFromConversation(processedConversation, {
699
+ keepForTurns: options.stripHeartbeatsAfterTurns ?? 1,
700
+ currentTurn,
701
+ });
702
+
554
703
  const completion = {
555
704
  ...this.getExtractedExecution(res, conversePrompt, options),
556
705
  original_response: options.include_original_response ? res : undefined,
@@ -852,6 +1001,12 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
852
1001
  request.toolConfig = {
853
1002
  tools: tool_defs,
854
1003
  }
1004
+ } else if (request.messages && messagesContainToolBlocks(request.messages)) {
1005
+ // Bedrock requires toolConfig when conversation contains toolUse/toolResult blocks.
1006
+ // When no tools are provided (e.g. checkpoint summary calls), convert tool blocks
1007
+ // to text representations so the conversation data is preserved while satisfying
1008
+ // Bedrock's API requirements without making tools callable.
1009
+ request.messages = convertToolBlocksToText(request.messages);
855
1010
  }
856
1011
 
857
1012
  return request;
@@ -1195,7 +1350,7 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
1195
1350
  const executor = this.getExecutor();
1196
1351
 
1197
1352
  // Prepare the request payload for TwelveLabs Marengo
1198
- let invokeBody: TwelvelabsMarengoRequest = {
1353
+ const invokeBody: TwelvelabsMarengoRequest = {
1199
1354
  inputType: "text"
1200
1355
  };
1201
1356
 
@@ -1285,6 +1440,75 @@ function getToolDefinition(tool: ToolDefinition): Tool.ToolSpecMember {
1285
1440
  }
1286
1441
  }
1287
1442
 
1443
+ /**
1444
+ * Checks whether any message contains toolUse or toolResult content blocks.
1445
+ */
1446
+ export function messagesContainToolBlocks(messages: Message[]): boolean {
1447
+ for (const msg of messages) {
1448
+ if (!msg.content) continue;
1449
+ for (const block of msg.content) {
1450
+ if ((block as ContentBlock.ToolUseMember).toolUse ||
1451
+ (block as ContentBlock.ToolResultMember).toolResult) {
1452
+ return true;
1453
+ }
1454
+ }
1455
+ }
1456
+ return false;
1457
+ }
1458
+
1459
+ /**
1460
+ * Converts toolUse and toolResult content blocks to text representations.
1461
+ * This preserves the tool call information in the conversation while removing
1462
+ * the structured tool blocks that require Bedrock's toolConfig to be set.
1463
+ *
1464
+ * Used when no tools are provided (e.g. checkpoint summary calls) but the
1465
+ * conversation history contains tool interactions from prior turns.
1466
+ */
1467
+ export function convertToolBlocksToText(messages: Message[]): Message[] {
1468
+ return messages.map(msg => {
1469
+ if (!msg.content) return msg;
1470
+ let hasToolBlocks = false;
1471
+ for (const block of msg.content) {
1472
+ if ((block as ContentBlock.ToolUseMember).toolUse ||
1473
+ (block as ContentBlock.ToolResultMember).toolResult) {
1474
+ hasToolBlocks = true;
1475
+ break;
1476
+ }
1477
+ }
1478
+ if (!hasToolBlocks) return msg;
1479
+
1480
+ const newContent: ContentBlock[] = [];
1481
+ for (const block of msg.content) {
1482
+ const toolUse = (block as ContentBlock.ToolUseMember).toolUse;
1483
+ const toolResult = (block as ContentBlock.ToolResultMember).toolResult;
1484
+ if (toolUse) {
1485
+ const inputStr = toolUse.input ? JSON.stringify(toolUse.input) : '';
1486
+ const truncatedInput = inputStr.length > 500 ? inputStr.substring(0, 500) + '...' : inputStr;
1487
+ newContent.push({
1488
+ text: `[Tool call: ${toolUse.name}(${truncatedInput})]`,
1489
+ } as ContentBlock.TextMember);
1490
+ } else if (toolResult) {
1491
+ const resultTexts: string[] = [];
1492
+ if (toolResult.content) {
1493
+ for (const c of toolResult.content) {
1494
+ if ((c as any).text) {
1495
+ const text = (c as any).text as string;
1496
+ resultTexts.push(text.length > 500 ? text.substring(0, 500) + '...' : text);
1497
+ }
1498
+ }
1499
+ }
1500
+ const resultStr = resultTexts.length > 0 ? resultTexts.join('\n') : 'No text content';
1501
+ newContent.push({
1502
+ text: `[Tool result: ${resultStr}]`,
1503
+ } as ContentBlock.TextMember);
1504
+ } else {
1505
+ newContent.push(block);
1506
+ }
1507
+ }
1508
+ return { ...msg, content: newContent };
1509
+ });
1510
+ }
1511
+
1288
1512
  /**
1289
1513
  * Recursively removes undefined values from an object.
1290
1514
  * AWS Bedrock's additionalModelRequestFields must be valid JSON, and undefined is not valid JSON.
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
  }