@juspay/neurolink 7.7.0 → 7.8.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 (83) hide show
  1. package/CHANGELOG.md +20 -2
  2. package/README.md +33 -2
  3. package/dist/cli/commands/config.d.ts +3 -3
  4. package/dist/cli/commands/sagemaker.d.ts +11 -0
  5. package/dist/cli/commands/sagemaker.js +778 -0
  6. package/dist/cli/factories/commandFactory.js +1 -0
  7. package/dist/cli/index.js +3 -0
  8. package/dist/cli/utils/interactiveSetup.js +28 -0
  9. package/dist/core/baseProvider.d.ts +2 -2
  10. package/dist/core/types.d.ts +1 -0
  11. package/dist/core/types.js +1 -0
  12. package/dist/factories/providerRegistry.js +5 -0
  13. package/dist/lib/core/baseProvider.d.ts +2 -2
  14. package/dist/lib/core/types.d.ts +1 -0
  15. package/dist/lib/core/types.js +1 -0
  16. package/dist/lib/factories/providerRegistry.js +5 -0
  17. package/dist/lib/providers/amazonSagemaker.d.ts +67 -0
  18. package/dist/lib/providers/amazonSagemaker.js +149 -0
  19. package/dist/lib/providers/index.d.ts +4 -0
  20. package/dist/lib/providers/index.js +4 -0
  21. package/dist/lib/providers/sagemaker/adaptive-semaphore.d.ts +86 -0
  22. package/dist/lib/providers/sagemaker/adaptive-semaphore.js +212 -0
  23. package/dist/lib/providers/sagemaker/client.d.ts +156 -0
  24. package/dist/lib/providers/sagemaker/client.js +462 -0
  25. package/dist/lib/providers/sagemaker/config.d.ts +73 -0
  26. package/dist/lib/providers/sagemaker/config.js +308 -0
  27. package/dist/lib/providers/sagemaker/detection.d.ts +176 -0
  28. package/dist/lib/providers/sagemaker/detection.js +596 -0
  29. package/dist/lib/providers/sagemaker/diagnostics.d.ts +37 -0
  30. package/dist/lib/providers/sagemaker/diagnostics.js +137 -0
  31. package/dist/lib/providers/sagemaker/error-constants.d.ts +78 -0
  32. package/dist/lib/providers/sagemaker/error-constants.js +227 -0
  33. package/dist/lib/providers/sagemaker/errors.d.ts +83 -0
  34. package/dist/lib/providers/sagemaker/errors.js +216 -0
  35. package/dist/lib/providers/sagemaker/index.d.ts +35 -0
  36. package/dist/lib/providers/sagemaker/index.js +67 -0
  37. package/dist/lib/providers/sagemaker/language-model.d.ts +182 -0
  38. package/dist/lib/providers/sagemaker/language-model.js +755 -0
  39. package/dist/lib/providers/sagemaker/parsers.d.ts +136 -0
  40. package/dist/lib/providers/sagemaker/parsers.js +625 -0
  41. package/dist/lib/providers/sagemaker/streaming.d.ts +39 -0
  42. package/dist/lib/providers/sagemaker/streaming.js +320 -0
  43. package/dist/lib/providers/sagemaker/structured-parser.d.ts +117 -0
  44. package/dist/lib/providers/sagemaker/structured-parser.js +625 -0
  45. package/dist/lib/providers/sagemaker/types.d.ts +456 -0
  46. package/dist/lib/providers/sagemaker/types.js +7 -0
  47. package/dist/lib/types/cli.d.ts +36 -1
  48. package/dist/lib/utils/providerConfig.js +6 -2
  49. package/dist/lib/utils/providerHealth.js +45 -9
  50. package/dist/providers/amazonSagemaker.d.ts +67 -0
  51. package/dist/providers/amazonSagemaker.js +149 -0
  52. package/dist/providers/index.d.ts +4 -0
  53. package/dist/providers/index.js +4 -0
  54. package/dist/providers/sagemaker/adaptive-semaphore.d.ts +86 -0
  55. package/dist/providers/sagemaker/adaptive-semaphore.js +212 -0
  56. package/dist/providers/sagemaker/client.d.ts +156 -0
  57. package/dist/providers/sagemaker/client.js +462 -0
  58. package/dist/providers/sagemaker/config.d.ts +73 -0
  59. package/dist/providers/sagemaker/config.js +308 -0
  60. package/dist/providers/sagemaker/detection.d.ts +176 -0
  61. package/dist/providers/sagemaker/detection.js +596 -0
  62. package/dist/providers/sagemaker/diagnostics.d.ts +37 -0
  63. package/dist/providers/sagemaker/diagnostics.js +137 -0
  64. package/dist/providers/sagemaker/error-constants.d.ts +78 -0
  65. package/dist/providers/sagemaker/error-constants.js +227 -0
  66. package/dist/providers/sagemaker/errors.d.ts +83 -0
  67. package/dist/providers/sagemaker/errors.js +216 -0
  68. package/dist/providers/sagemaker/index.d.ts +35 -0
  69. package/dist/providers/sagemaker/index.js +67 -0
  70. package/dist/providers/sagemaker/language-model.d.ts +182 -0
  71. package/dist/providers/sagemaker/language-model.js +755 -0
  72. package/dist/providers/sagemaker/parsers.d.ts +136 -0
  73. package/dist/providers/sagemaker/parsers.js +625 -0
  74. package/dist/providers/sagemaker/streaming.d.ts +39 -0
  75. package/dist/providers/sagemaker/streaming.js +320 -0
  76. package/dist/providers/sagemaker/structured-parser.d.ts +117 -0
  77. package/dist/providers/sagemaker/structured-parser.js +625 -0
  78. package/dist/providers/sagemaker/types.d.ts +456 -0
  79. package/dist/providers/sagemaker/types.js +7 -0
  80. package/dist/types/cli.d.ts +36 -1
  81. package/dist/utils/providerConfig.js +6 -2
  82. package/dist/utils/providerHealth.js +45 -9
  83. package/package.json +4 -1
@@ -0,0 +1,625 @@
1
+ /**
2
+ * SageMaker Streaming Response Parsers
3
+ *
4
+ * This module provides protocol-specific parsers for different streaming
5
+ * formats used by SageMaker endpoints (HuggingFace, LLaMA, custom models).
6
+ */
7
+ import { createStructuredOutputParser, isStructuredContent, } from "./structured-parser.js";
8
+ import { SageMakerError } from "./errors.js";
9
+ import { logger } from "../../utils/logger.js";
10
+ import { randomUUID } from "crypto";
11
+ /**
12
+ * Constants for JSON parsing and validation
13
+ */
14
+ const MIN_JSON_OBJECT_LENGTH = 2; // Minimum length for JSON object "{}"
15
+ /**
16
+ * Process a single character for bracket counting logic
17
+ * Shared utility to avoid code duplication between parsers
18
+ */
19
+ export function processBracketCharacter(char, state) {
20
+ if (state.escapeNext) {
21
+ state.escapeNext = false;
22
+ return { isValid: true };
23
+ }
24
+ if (char === "\\") {
25
+ state.escapeNext = true;
26
+ return { isValid: true };
27
+ }
28
+ if (char === '"' && !state.escapeNext) {
29
+ state.inString = !state.inString;
30
+ return { isValid: true };
31
+ }
32
+ // Only count brackets outside of strings
33
+ if (!state.inString) {
34
+ switch (char) {
35
+ case "{":
36
+ state.braceCount++;
37
+ break;
38
+ case "}":
39
+ state.braceCount--;
40
+ if (state.braceCount < 0) {
41
+ return { isValid: false, reason: "Unmatched closing brace" };
42
+ }
43
+ break;
44
+ case "[":
45
+ state.bracketCount++;
46
+ break;
47
+ case "]":
48
+ state.bracketCount--;
49
+ if (state.bracketCount < 0) {
50
+ return { isValid: false, reason: "Unmatched closing bracket" };
51
+ }
52
+ break;
53
+ }
54
+ }
55
+ return { isValid: true };
56
+ }
57
+ /**
58
+ * Utility function to validate JSON completeness using efficient bracket counting
59
+ * Extracted from parseArgumentsForToolCall for reusability
60
+ */
61
+ export function validateJSONCompleteness(jsonString) {
62
+ // Basic length check - minimum for empty object "{}"
63
+ if (jsonString.length < MIN_JSON_OBJECT_LENGTH) {
64
+ return { isComplete: false, reason: "Too short" };
65
+ }
66
+ // Must start and end with braces for object
67
+ if (!jsonString.startsWith("{") || !jsonString.endsWith("}")) {
68
+ return { isComplete: false, reason: "Missing object braces" };
69
+ }
70
+ // Use shared bracket counting logic
71
+ const state = {
72
+ braceCount: 0,
73
+ bracketCount: 0,
74
+ inString: false,
75
+ escapeNext: false,
76
+ };
77
+ for (let i = 0; i < jsonString.length; i++) {
78
+ const char = jsonString[i];
79
+ const result = processBracketCharacter(char, state);
80
+ if (!result.isValid) {
81
+ return { isComplete: false, reason: result.reason };
82
+ }
83
+ }
84
+ // Check for unterminated string
85
+ if (state.inString) {
86
+ return { isComplete: false, reason: "Unterminated string" };
87
+ }
88
+ // Check if all brackets are balanced
89
+ if (state.braceCount !== 0) {
90
+ return {
91
+ isComplete: false,
92
+ reason: `Unbalanced braces: ${state.braceCount}`,
93
+ };
94
+ }
95
+ if (state.bracketCount !== 0) {
96
+ return {
97
+ isComplete: false,
98
+ reason: `Unbalanced brackets: ${state.bracketCount}`,
99
+ };
100
+ }
101
+ return { isComplete: true };
102
+ }
103
+ /**
104
+ * Utility function to parse tool call arguments with robust validation
105
+ * Extracted from parseArgumentsForToolCall for reusability
106
+ */
107
+ export function parseToolCallArguments(args) {
108
+ const trimmedArgs = args.trim();
109
+ // Handle empty arguments
110
+ if (trimmedArgs.length === 0) {
111
+ return { arguments: "{}", complete: true };
112
+ }
113
+ // Use robust bracket counting for JSON validation
114
+ const validationResult = validateJSONCompleteness(trimmedArgs);
115
+ if (validationResult.isComplete) {
116
+ try {
117
+ const parsed = JSON.parse(trimmedArgs);
118
+ // Additional validation: ensure it's actually an object
119
+ if (typeof parsed === "object" &&
120
+ parsed !== null &&
121
+ !Array.isArray(parsed)) {
122
+ return { arguments: trimmedArgs, complete: true };
123
+ }
124
+ else {
125
+ // Not a valid object, treat as delta
126
+ return { argumentsDelta: trimmedArgs };
127
+ }
128
+ }
129
+ catch (parseError) {
130
+ // Log parsing error for debugging
131
+ logger.debug("JSON parsing failed for tool arguments", {
132
+ args: trimmedArgs.substring(0, 100),
133
+ error: formatErrorMessage(parseError),
134
+ });
135
+ // Not valid JSON despite looking complete, treat as delta
136
+ return { argumentsDelta: trimmedArgs };
137
+ }
138
+ }
139
+ else {
140
+ // String doesn't look like complete JSON, treat as delta
141
+ return { argumentsDelta: trimmedArgs };
142
+ }
143
+ }
144
+ /**
145
+ * Abstract base parser with common functionality
146
+ */
147
+ class BaseStreamingParser {
148
+ buffer = "";
149
+ isCompleted = false;
150
+ totalUsage;
151
+ structuredParser;
152
+ responseSchema;
153
+ isComplete(chunk) {
154
+ return chunk.done === true || chunk.finishReason !== undefined;
155
+ }
156
+ extractUsage(finalChunk) {
157
+ return finalChunk.usage || this.totalUsage;
158
+ }
159
+ reset() {
160
+ this.buffer = "";
161
+ this.isCompleted = false;
162
+ this.totalUsage = undefined;
163
+ this.structuredParser?.reset();
164
+ }
165
+ /**
166
+ * Enable structured output parsing with optional schema
167
+ */
168
+ enableStructuredOutput(schema) {
169
+ this.responseSchema = schema;
170
+ this.structuredParser = createStructuredOutputParser(schema);
171
+ }
172
+ /**
173
+ * Parse structured content if enabled
174
+ */
175
+ parseStructuredContent(content) {
176
+ if (!this.structuredParser || !isStructuredContent(content)) {
177
+ return undefined;
178
+ }
179
+ return this.structuredParser.parseChunk(content);
180
+ }
181
+ decodeChunk(chunk) {
182
+ return new TextDecoder().decode(chunk);
183
+ }
184
+ parseJSON(text) {
185
+ try {
186
+ return JSON.parse(text);
187
+ }
188
+ catch (error) {
189
+ logger.warn("Failed to parse JSON in streaming response", {
190
+ text,
191
+ error,
192
+ });
193
+ return null;
194
+ }
195
+ }
196
+ }
197
+ /**
198
+ * HuggingFace Transformers streaming parser (Server-Sent Events)
199
+ */
200
+ export class HuggingFaceStreamParser extends BaseStreamingParser {
201
+ getName() {
202
+ return "HuggingFace SSE Parser";
203
+ }
204
+ parse(chunk) {
205
+ const text = this.decodeChunk(chunk);
206
+ this.buffer += text;
207
+ const chunks = [];
208
+ const lines = this.buffer.split("\n");
209
+ // Keep the last potentially incomplete line in buffer
210
+ this.buffer = lines.pop() || "";
211
+ for (const line of lines) {
212
+ const trimmed = line.trim();
213
+ // Skip empty lines and comments
214
+ if (!trimmed || trimmed.startsWith(":")) {
215
+ continue;
216
+ }
217
+ // Parse Server-Sent Events format
218
+ if (trimmed.startsWith("data: ")) {
219
+ const data = trimmed.substring(6);
220
+ // Check for stream end
221
+ if (data === "[DONE]") {
222
+ chunks.push({
223
+ content: "",
224
+ done: true,
225
+ finishReason: "stop",
226
+ });
227
+ this.isCompleted = true;
228
+ continue;
229
+ }
230
+ // Parse JSON data
231
+ const parsed = this.parseJSON(data);
232
+ if (parsed && typeof parsed === "object" && parsed !== null) {
233
+ const chunk = this.parseHuggingFaceChunk(parsed);
234
+ if (chunk) {
235
+ chunks.push(chunk);
236
+ }
237
+ }
238
+ }
239
+ }
240
+ return chunks;
241
+ }
242
+ parseHuggingFaceChunk(data) {
243
+ // HuggingFace streaming format
244
+ if (data.token) {
245
+ const token = data.token;
246
+ return {
247
+ content: typeof token.text === "string" ? token.text : String(token),
248
+ done: false,
249
+ };
250
+ }
251
+ // Alternative format with generated_text
252
+ if (data.generated_text !== undefined) {
253
+ const details = data.details;
254
+ return {
255
+ content: String(data.generated_text),
256
+ done: details?.finish_reason !== undefined,
257
+ finishReason: this.mapFinishReason(details?.finish_reason),
258
+ usage: details ? this.parseHuggingFaceUsage(details) : undefined,
259
+ };
260
+ }
261
+ // Error format
262
+ if (data.error) {
263
+ const errorMessage = extractApiErrorMessage(data.error);
264
+ throw new SageMakerError(`HuggingFace streaming error: ${errorMessage}`, "MODEL_ERROR", 500);
265
+ }
266
+ return null;
267
+ }
268
+ parseHuggingFaceUsage(details) {
269
+ if (!details.tokens) {
270
+ return undefined;
271
+ }
272
+ const tokens = details.tokens;
273
+ return {
274
+ promptTokens: Number(tokens.input) || 0,
275
+ completionTokens: Number(tokens.generated) || 0,
276
+ totalTokens: Number(tokens.total) || 0,
277
+ };
278
+ }
279
+ mapFinishReason(reason) {
280
+ switch (reason) {
281
+ case "stop":
282
+ return "stop";
283
+ case "length":
284
+ return "length";
285
+ case "eos_token":
286
+ return "stop";
287
+ default:
288
+ return "unknown";
289
+ }
290
+ }
291
+ }
292
+ /**
293
+ * LLaMA/OpenAI-compatible streaming parser (JSON Lines)
294
+ */
295
+ export class LlamaStreamParser extends BaseStreamingParser {
296
+ getName() {
297
+ return "LLaMA JSONL Parser";
298
+ }
299
+ parse(chunk) {
300
+ const text = this.decodeChunk(chunk);
301
+ this.buffer += text;
302
+ const chunks = [];
303
+ const lines = this.buffer.split("\n");
304
+ // Keep the last potentially incomplete line in buffer
305
+ this.buffer = lines.pop() || "";
306
+ for (const line of lines) {
307
+ const trimmed = line.trim();
308
+ if (!trimmed) {
309
+ continue;
310
+ }
311
+ // Parse each line as JSON
312
+ const parsed = this.parseJSON(trimmed);
313
+ if (parsed && typeof parsed === "object" && parsed !== null) {
314
+ const chunk = this.parseLlamaChunk(parsed);
315
+ if (chunk) {
316
+ chunks.push(chunk);
317
+ }
318
+ }
319
+ }
320
+ return chunks;
321
+ }
322
+ parseLlamaChunk(data) {
323
+ // OpenAI-compatible format
324
+ if (Array.isArray(data.choices) && data.choices[0]) {
325
+ const choice = data.choices[0];
326
+ // Delta format (streaming)
327
+ if (choice.delta) {
328
+ const delta = choice.delta;
329
+ const content = String(delta.content || "");
330
+ const finishReason = choice.finish_reason;
331
+ const chunk = {
332
+ content,
333
+ done: finishReason !== null && finishReason !== undefined,
334
+ finishReason: this.mapFinishReason(finishReason || null),
335
+ usage: data.usage
336
+ ? this.parseLlamaUsage(data.usage)
337
+ : undefined,
338
+ };
339
+ // Phase 2.3: Handle structured output if enabled
340
+ if (content && this.structuredParser) {
341
+ chunk.structuredOutput = this.parseStructuredContent(content);
342
+ }
343
+ // Phase 2.3: Handle streaming tool calls
344
+ if (Array.isArray(delta.tool_calls) && delta.tool_calls[0]) {
345
+ const toolCall = delta.tool_calls[0];
346
+ chunk.toolCall = this.parseStreamingToolCall(toolCall);
347
+ // If tool call is complete and we have finish_reason, mark chunk complete
348
+ if (finishReason === "function_call" && chunk.toolCall.arguments) {
349
+ chunk.toolCall.complete = true;
350
+ }
351
+ }
352
+ return chunk;
353
+ }
354
+ // Text format (non-streaming fallback)
355
+ if (choice.text !== undefined) {
356
+ return {
357
+ content: String(choice.text),
358
+ done: choice.finish_reason !== null,
359
+ finishReason: this.mapFinishReason(choice.finish_reason),
360
+ usage: data.usage
361
+ ? this.parseLlamaUsage(data.usage)
362
+ : undefined,
363
+ };
364
+ }
365
+ }
366
+ // Direct content format
367
+ if (data.content !== undefined) {
368
+ return {
369
+ content: String(data.content),
370
+ done: Boolean(data.done),
371
+ finishReason: this.mapFinishReason(data.finish_reason),
372
+ usage: data.usage
373
+ ? this.parseLlamaUsage(data.usage)
374
+ : undefined,
375
+ };
376
+ }
377
+ // Error format
378
+ if (data.error) {
379
+ const errorData = data.error;
380
+ const errorMessage = extractApiErrorMessage(errorData);
381
+ throw new SageMakerError(`LLaMA streaming error: ${errorMessage}`, "MODEL_ERROR", 500);
382
+ }
383
+ return null;
384
+ }
385
+ /**
386
+ * Parse tool call arguments with robust validation and error handling
387
+ */
388
+ parseToolCallArguments(functionData, toolCall) {
389
+ if (typeof functionData.arguments === "string") {
390
+ const result = parseToolCallArguments(functionData.arguments);
391
+ if (result.complete) {
392
+ toolCall.arguments = result.arguments;
393
+ toolCall.complete = true;
394
+ }
395
+ else if (result.argumentsDelta !== undefined) {
396
+ toolCall.argumentsDelta = result.argumentsDelta;
397
+ }
398
+ }
399
+ else if (functionData.arguments !== undefined) {
400
+ // Handle non-string arguments (objects, numbers, etc.)
401
+ try {
402
+ toolCall.arguments = JSON.stringify(functionData.arguments);
403
+ toolCall.complete = true;
404
+ }
405
+ catch (stringifyError) {
406
+ logger.warn("Failed to stringify tool arguments", {
407
+ args: functionData.arguments,
408
+ error: formatErrorMessage(stringifyError),
409
+ });
410
+ toolCall.arguments = "{}";
411
+ toolCall.complete = true;
412
+ }
413
+ }
414
+ else {
415
+ // No arguments provided, default to empty object
416
+ toolCall.arguments = "{}";
417
+ toolCall.complete = true;
418
+ }
419
+ }
420
+ /**
421
+ * Parse streaming tool call from OpenAI-compatible format (Phase 2.3)
422
+ */
423
+ parseStreamingToolCall(toolCallData) {
424
+ const toolCall = {
425
+ id: String(toolCallData.id || `call_${randomUUID()}`),
426
+ type: "function",
427
+ };
428
+ // Handle function name (usually sent in first chunk)
429
+ const functionData = toolCallData.function;
430
+ if (functionData?.name) {
431
+ toolCall.name = String(functionData.name);
432
+ }
433
+ // Handle streaming arguments
434
+ if (functionData?.arguments) {
435
+ this.parseToolCallArguments(functionData, toolCall);
436
+ }
437
+ return toolCall;
438
+ }
439
+ parseLlamaUsage(usage) {
440
+ return {
441
+ promptTokens: Number(usage.prompt_tokens) || 0,
442
+ completionTokens: Number(usage.completion_tokens) || 0,
443
+ totalTokens: Number(usage.total_tokens) || 0,
444
+ };
445
+ }
446
+ mapFinishReason(reason) {
447
+ switch (reason) {
448
+ case "stop":
449
+ return "stop";
450
+ case "length":
451
+ return "length";
452
+ case "function_call":
453
+ return "tool-calls";
454
+ case "content_filter":
455
+ return "content-filter";
456
+ default:
457
+ return reason ? "unknown" : undefined;
458
+ }
459
+ }
460
+ }
461
+ /**
462
+ * Custom/Generic streaming parser (Chunked Transfer)
463
+ */
464
+ export class CustomStreamParser extends BaseStreamingParser {
465
+ expectedFormat = "json";
466
+ constructor(format = "json") {
467
+ super();
468
+ this.expectedFormat = format;
469
+ }
470
+ getName() {
471
+ return `Custom ${this.expectedFormat.toUpperCase()} Parser`;
472
+ }
473
+ parse(chunk) {
474
+ const text = this.decodeChunk(chunk);
475
+ this.buffer += text;
476
+ if (this.expectedFormat === "json") {
477
+ return this.parseJSONFormat();
478
+ }
479
+ else {
480
+ return this.parseTextFormat();
481
+ }
482
+ }
483
+ parseJSONFormat() {
484
+ const chunks = [];
485
+ // Try to parse complete JSON objects
486
+ let startIndex = 0;
487
+ while (startIndex < this.buffer.length) {
488
+ try {
489
+ const remaining = this.buffer.substring(startIndex);
490
+ const parsed = JSON.parse(remaining);
491
+ // Successfully parsed - this is probably the complete response
492
+ const chunk = {
493
+ content: parsed.text ||
494
+ parsed.generated_text ||
495
+ parsed.output ||
496
+ String(parsed),
497
+ done: true,
498
+ finishReason: "stop",
499
+ };
500
+ chunks.push(chunk);
501
+ this.buffer = "";
502
+ this.isCompleted = true;
503
+ break;
504
+ }
505
+ catch {
506
+ // JSON parsing failed - look for newline-separated JSON
507
+ const newlineIndex = this.buffer.indexOf("\n", startIndex);
508
+ if (newlineIndex === -1) {
509
+ // No complete line found, wait for more data
510
+ break;
511
+ }
512
+ const line = this.buffer.substring(startIndex, newlineIndex);
513
+ if (line.trim()) {
514
+ const parsed = this.parseJSON(line.trim());
515
+ if (parsed && typeof parsed === "object" && parsed !== null) {
516
+ const chunk = this.parseCustomChunk(parsed);
517
+ if (chunk) {
518
+ chunks.push(chunk);
519
+ }
520
+ }
521
+ }
522
+ startIndex = newlineIndex + 1;
523
+ }
524
+ }
525
+ // Clean processed content from buffer
526
+ if (startIndex > 0) {
527
+ this.buffer = this.buffer.substring(startIndex);
528
+ }
529
+ return chunks;
530
+ }
531
+ parseTextFormat() {
532
+ // Simple text streaming - treat each chunk as content
533
+ if (this.buffer) {
534
+ const content = this.buffer;
535
+ this.buffer = "";
536
+ return [
537
+ {
538
+ content,
539
+ done: false,
540
+ },
541
+ ];
542
+ }
543
+ return [];
544
+ }
545
+ parseCustomChunk(data) {
546
+ // Generic parsing for various custom formats
547
+ const content = data.text ||
548
+ data.generated_text ||
549
+ data.output ||
550
+ data.response ||
551
+ data.content ||
552
+ (typeof data === "string" ? data : JSON.stringify(data));
553
+ return {
554
+ content: String(content),
555
+ done: Boolean(data.done || data.finished || data.complete),
556
+ finishReason: data.finish_reason || data.status === "complete" ? "stop" : undefined,
557
+ usage: data.usage || data.tokens ? this.parseCustomUsage(data) : undefined,
558
+ };
559
+ }
560
+ parseCustomUsage(data) {
561
+ const usage = (data.usage || data.tokens || {});
562
+ return {
563
+ promptTokens: Number(usage.prompt_tokens || usage.input_tokens) || 0,
564
+ completionTokens: Number(usage.completion_tokens || usage.output_tokens) || 0,
565
+ totalTokens: Number(usage.total_tokens) || 0,
566
+ };
567
+ }
568
+ }
569
+ /**
570
+ * Parser factory to create appropriate parser for detected protocol
571
+ */
572
+ export class StreamingParserFactory {
573
+ static createParser(protocol, options) {
574
+ switch (protocol) {
575
+ case "sse":
576
+ return new HuggingFaceStreamParser();
577
+ case "jsonl":
578
+ return new LlamaStreamParser();
579
+ case "chunked": {
580
+ const format = options?.format;
581
+ return new CustomStreamParser(format || "json");
582
+ }
583
+ case "none":
584
+ default:
585
+ // Return a no-op parser that just converts complete responses
586
+ return new CustomStreamParser("text");
587
+ }
588
+ }
589
+ static getSupportedProtocols() {
590
+ return ["sse", "jsonl", "chunked", "text"];
591
+ }
592
+ }
593
+ /**
594
+ * Helper function to safely format error messages for logging and error handling
595
+ * Consolidates the duplicated error formatting pattern used throughout the parsers
596
+ */
597
+ function formatErrorMessage(error) {
598
+ if (error instanceof Error) {
599
+ return error.message;
600
+ }
601
+ return String(error);
602
+ }
603
+ /**
604
+ * Helper function to extract error messages from API response error data
605
+ * Handles both string and object error formats consistently
606
+ */
607
+ function extractApiErrorMessage(errorData) {
608
+ if (typeof errorData === "object" && errorData !== null) {
609
+ return errorData.message || String(errorData);
610
+ }
611
+ return String(errorData);
612
+ }
613
+ /**
614
+ * Utility function to estimate token usage when not provided
615
+ */
616
+ export function estimateTokenUsage(prompt, completion) {
617
+ // Rough estimation: ~4 characters per token for English text
618
+ const promptTokens = Math.ceil(prompt.length / 4);
619
+ const completionTokens = Math.ceil(completion.length / 4);
620
+ return {
621
+ promptTokens,
622
+ completionTokens,
623
+ totalTokens: promptTokens + completionTokens,
624
+ };
625
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Streaming response handling for Amazon SageMaker Provider (Phase 2)
3
+ *
4
+ * This module provides full streaming support with automatic protocol detection
5
+ * and model-specific parsing for various SageMaker deployment patterns.
6
+ */
7
+ import { ReadableStream } from "stream/web";
8
+ import type { SageMakerStreamChunk, SageMakerUsage, SageMakerConfig } from "./types.js";
9
+ /**
10
+ * Create a SageMaker streaming response with automatic protocol detection
11
+ *
12
+ * @param responseStream - Raw response stream from SageMaker endpoint
13
+ * @param endpointName - SageMaker endpoint name for capability detection
14
+ * @param config - SageMaker configuration
15
+ * @param options - Stream options and metadata
16
+ * @returns Promise resolving to ReadableStream compatible with AI SDK
17
+ */
18
+ export declare function createSageMakerStream(responseStream: AsyncIterable<Uint8Array>, endpointName: string, config: SageMakerConfig, options?: {
19
+ prompt?: string;
20
+ abortSignal?: AbortSignal;
21
+ onChunk?: (chunk: SageMakerStreamChunk) => void;
22
+ onComplete?: (usage: SageMakerUsage) => void;
23
+ onError?: (error: Error) => void;
24
+ }): Promise<ReadableStream<unknown>>;
25
+ /**
26
+ * Create a synthetic stream from complete text (for backward compatibility)
27
+ */
28
+ export declare function createSyntheticStream(text: string, usage: SageMakerUsage, options?: {
29
+ onChunk?: (chunk: SageMakerStreamChunk) => void;
30
+ onComplete?: (usage: SageMakerUsage) => void;
31
+ }): Promise<ReadableStream<unknown>>;
32
+ /**
33
+ * Estimate token usage from text content
34
+ *
35
+ * @param prompt - Input prompt text
36
+ * @param completion - Generated completion text
37
+ * @returns Estimated usage information
38
+ */
39
+ export declare function estimateTokenUsage(prompt: string, completion: string): SageMakerUsage;