@juspay/neurolink 12.58.1 → 13.0.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.
- package/CHANGELOG.md +14 -3
- package/dist/auth/anthropicOAuth.js +3 -3
- package/dist/auth/middleware/AuthMiddleware.d.ts +1 -1
- package/dist/auth/sessionManager.js +4 -4
- package/dist/auth/tokenStore.js +4 -4
- package/dist/browser/neurolink.min.js +474 -473
- package/dist/cli/commands/observability.d.ts +1 -6
- package/dist/cli/commands/observability.js +0 -106
- package/dist/cli/commands/proxy.js +9 -18
- package/dist/cli/commands/telemetry.d.ts +3 -13
- package/dist/cli/commands/telemetry.js +7 -354
- package/dist/cli/loop/session.js +1 -1
- package/dist/client/httpClient.js +1 -1
- package/dist/client/sseClient.js +1 -1
- package/dist/client/streamingClient.js +2 -2
- package/dist/client/wsClient.js +1 -1
- package/dist/constants/langfuse.d.ts +25 -0
- package/dist/constants/langfuse.js +38 -0
- package/dist/constants/telemetry.d.ts +20 -0
- package/dist/constants/telemetry.js +34 -0
- package/dist/core/baseProvider.d.ts +2 -1
- package/dist/core/baseProvider.js +4 -11
- package/dist/core/conversationMemoryManager.js +5 -8
- package/dist/core/factory.js +1 -1
- package/dist/core/geminiLoopAdapter.d.ts +8 -1
- package/dist/core/geminiLoopAdapter.js +25 -1
- package/dist/core/loopEngine.js +69 -40
- package/dist/core/modules/MessageBuilder.js +2 -2
- package/dist/core/modules/StreamHandler.js +2 -2
- package/dist/core/modules/TelemetryHandler.d.ts +2 -18
- package/dist/core/modules/TelemetryHandler.js +1 -66
- package/dist/core/modules/ToolsManager.js +40 -31
- package/dist/core/nativeGenerateLoop.js +78 -8
- package/dist/core/redisConversationMemoryManager.js +8 -15
- package/dist/core/toolExecutionGuards.js +1 -13
- package/dist/index.d.ts +5 -12
- package/dist/index.js +5 -37
- package/dist/mcp/batching/requestBatcher.js +1 -1
- package/dist/mcp/externalServerManager.js +3 -21
- package/dist/mcp/toolDiscoveryService.js +4 -16
- package/dist/mcp/toolRegistry.js +2 -5
- package/dist/memory/memoryRetrievalTools.js +1 -1
- package/dist/neurolink.d.ts +16 -27
- package/dist/neurolink.js +788 -792
- package/dist/observability/exporterSpanProcessor.d.ts +50 -0
- package/dist/observability/exporterSpanProcessor.js +181 -0
- package/dist/observability/exporters/langfuseExporter.d.ts +13 -34
- package/dist/observability/exporters/langfuseExporter.js +101 -154
- package/dist/observability/index.d.ts +1 -0
- package/dist/observability/index.js +1 -0
- package/dist/observability/langfuseMasking.d.ts +8 -0
- package/dist/observability/langfuseMasking.js +47 -0
- package/dist/observability/otlpEncoding.d.ts +20 -0
- package/dist/observability/otlpEncoding.js +85 -0
- package/dist/providers/amazonBedrock/client.js +142 -176
- package/dist/providers/amazonBedrock/loopAdapter.js +14 -1
- package/dist/providers/amazonSagemaker.js +118 -80
- package/dist/providers/anthropic/client.js +3 -52
- package/dist/providers/anthropic/loopAdapter.js +24 -1
- package/dist/providers/anthropic/rateLimitCapture.js +9 -12
- package/dist/providers/googleAiStudio/client.d.ts +4 -5
- package/dist/providers/googleAiStudio/client.js +38 -40
- package/dist/providers/googleVertex/client.d.ts +2 -7
- package/dist/providers/googleVertex/client.js +282 -378
- package/dist/providers/googleVertex/constants.js +2 -2
- package/dist/providers/litellm/client.d.ts +2 -9
- package/dist/providers/litellm/client.js +1 -63
- package/dist/providers/openAI/client.d.ts +1 -8
- package/dist/providers/openAI/client.js +0 -65
- package/dist/providers/openaiChatCompletionsBase.d.ts +1 -0
- package/dist/providers/openaiChatCompletionsBase.js +61 -4
- package/dist/providers/replicate.js +22 -4
- package/dist/proxy/codexUsage.js +7 -0
- package/dist/proxy/proxyFetch.js +19 -25
- package/dist/proxy/proxyTracer.d.ts +14 -7
- package/dist/proxy/proxyTracer.js +129 -83
- package/dist/proxy/proxyTranslationEngine.js +0 -1
- package/dist/proxy/requestLogger.d.ts +1 -1
- package/dist/proxy/requestLogger.js +39 -45
- package/dist/server/middleware/common.js +1 -1
- package/dist/server/routes/agentRoutes.js +4 -4
- package/dist/server/routes/claudeProxyRoutes.js +99 -196
- package/dist/server/routes/codexProxyRoutes.js +621 -591
- package/dist/server/routes/healthRoutes.js +6 -6
- package/dist/server/routes/mcpRoutes.js +7 -7
- package/dist/server/routes/memoryRoutes.js +7 -7
- package/dist/server/routes/openApiRoutes.js +3 -3
- package/dist/server/routes/toolRoutes.js +5 -5
- package/dist/server/routes/transcribeRoutes.js +1 -1
- package/dist/server/utils/validation.d.ts +3 -3
- package/dist/services/server/ai/observability/instrumentation.d.ts +34 -201
- package/dist/services/server/ai/observability/instrumentation.js +220 -1267
- package/dist/services/server/ai/observability/langfuseSetup.d.ts +14 -0
- package/dist/services/server/ai/observability/langfuseSetup.js +55 -0
- package/dist/services/server/ai/observability/otlpSetup.d.ts +16 -0
- package/dist/services/server/ai/observability/otlpSetup.js +58 -0
- package/dist/session/globalSessionState.js +0 -11
- package/dist/telemetry/attributes.d.ts +23 -46
- package/dist/telemetry/attributes.js +76 -162
- package/dist/telemetry/chatMessages.d.ts +13 -0
- package/dist/telemetry/chatMessages.js +59 -0
- package/dist/telemetry/index.d.ts +5 -19
- package/dist/telemetry/index.js +5 -26
- package/dist/telemetry/observation.d.ts +37 -0
- package/dist/telemetry/observation.js +112 -0
- package/dist/telemetry/observationNames.d.ts +18 -0
- package/dist/telemetry/observationNames.js +24 -0
- package/dist/telemetry/spanSanitizer.d.ts +39 -0
- package/dist/telemetry/spanSanitizer.js +92 -0
- package/dist/telemetry/spanSerialization.d.ts +13 -0
- package/dist/telemetry/spanSerialization.js +46 -0
- package/dist/telemetry/tracers.d.ts +37 -18
- package/dist/telemetry/tracers.js +97 -19
- package/dist/telemetry/withSpan.d.ts +47 -25
- package/dist/telemetry/withSpan.js +158 -71
- package/dist/types/cli.d.ts +0 -11
- package/dist/types/decision.d.ts +3 -0
- package/dist/types/exporter.d.ts +54 -0
- package/dist/types/exporter.js +0 -4
- package/dist/types/generate.d.ts +4 -1
- package/dist/types/isolatedAgent.d.ts +5 -4
- package/dist/types/loopEngine.d.ts +18 -28
- package/dist/types/observability.d.ts +127 -160
- package/dist/types/providers.d.ts +15 -0
- package/dist/types/proxy.d.ts +41 -2
- package/dist/utils/anthropicTraceSanitizer.d.ts +2 -2
- package/dist/utils/anthropicTraceSanitizer.js +3 -1
- package/dist/utils/conversationMemory.js +4 -4
- package/dist/utils/fileDetector.js +1 -1
- package/dist/utils/messageBuilder.js +1 -1
- package/dist/utils/noOutputSentinel.d.ts +4 -16
- package/dist/utils/noOutputSentinel.js +12 -28
- package/dist/utils/providerRetry.js +4 -4
- package/dist/voice/livekit/brain.js +1 -1
- package/dist/voice/livekit/realtimeMemory.d.ts +1 -1
- package/dist/voice/livekit/realtimeMemory.js +66 -1
- package/dist/voice/livekit/realtimeVoiceAgent.js +1 -1
- package/docs-site/static/search-index.json +42 -63
- package/package.json +10 -4
- package/dist/telemetry/telemetryService.d.ts +0 -79
- package/dist/telemetry/telemetryService.js +0 -572
- package/dist/utils/observabilityHelpers.d.ts +0 -27
- package/dist/utils/observabilityHelpers.js +0 -47
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LangfuseAttributePolicy, OtlpKeyValue, OtlpSpan, SpanAttributes, SpanData } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* @description Encodes attributes as OTLP/JSON key-values, dropping `undefined` and `null` entries.
|
|
4
|
+
* @param attributes {SpanAttributes} - Attribute record to encode.
|
|
5
|
+
* @returns {OtlpKeyValue[]} One typed key-value per kept attribute.
|
|
6
|
+
*/
|
|
7
|
+
export declare function toOtlpAttributes(attributes: SpanAttributes): OtlpKeyValue[];
|
|
8
|
+
/**
|
|
9
|
+
* @description Encodes one span as OTLP/JSON, applying the exporter's attribute policy to its attributes.
|
|
10
|
+
* @param span {SpanData} - Span to encode.
|
|
11
|
+
* @param policy {LangfuseAttributePolicy} - Environment, release, redaction and mask settings.
|
|
12
|
+
* @returns {OtlpSpan} The span in the shape Langfuse's ingestion endpoint reads.
|
|
13
|
+
*/
|
|
14
|
+
export declare function toOtlpSpan(span: SpanData, policy: LangfuseAttributePolicy): OtlpSpan;
|
|
15
|
+
/**
|
|
16
|
+
* @description Splits encoded spans into request-sized batches, bounded by span count and serialized bytes.
|
|
17
|
+
* @param spans {OtlpSpan[]} - Encoded spans, in export order.
|
|
18
|
+
* @returns {OtlpSpan[][]} Batches that each fit one export request; a single oversized span still gets its own batch.
|
|
19
|
+
*/
|
|
20
|
+
export declare function chunkOtlpSpans(spans: OtlpSpan[]): OtlpSpan[][];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { MAX_BYTES_PER_REQUEST, MAX_SPANS_PER_REQUEST, OTLP_STATUS_CODES, } from "../constants/langfuse.js";
|
|
2
|
+
import { SpanStatus } from "../types/index.js";
|
|
3
|
+
import { protectAttributes } from "./langfuseMasking.js";
|
|
4
|
+
const SPAN_KIND_INTERNAL = 1;
|
|
5
|
+
function toNanos(iso) {
|
|
6
|
+
return (BigInt(Date.parse(iso)) * 1000000n).toString();
|
|
7
|
+
}
|
|
8
|
+
function toOtlpValue(value) {
|
|
9
|
+
if (typeof value === "boolean") {
|
|
10
|
+
return { boolValue: value };
|
|
11
|
+
}
|
|
12
|
+
if (typeof value === "number") {
|
|
13
|
+
return Number.isInteger(value)
|
|
14
|
+
? { intValue: String(value) }
|
|
15
|
+
: { doubleValue: value };
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return { arrayValue: { values: value.map(toOtlpValue) } };
|
|
19
|
+
}
|
|
20
|
+
return { stringValue: String(value) };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @description Encodes attributes as OTLP/JSON key-values, dropping `undefined` and `null` entries.
|
|
24
|
+
* @param attributes {SpanAttributes} - Attribute record to encode.
|
|
25
|
+
* @returns {OtlpKeyValue[]} One typed key-value per kept attribute.
|
|
26
|
+
*/
|
|
27
|
+
export function toOtlpAttributes(attributes) {
|
|
28
|
+
return Object.entries(attributes)
|
|
29
|
+
.filter(([, value]) => value !== undefined && value !== null)
|
|
30
|
+
.map(([key, value]) => ({ key, value: toOtlpValue(value) }));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* @description Encodes one span as OTLP/JSON, applying the exporter's attribute policy to its attributes.
|
|
34
|
+
* @param span {SpanData} - Span to encode.
|
|
35
|
+
* @param policy {LangfuseAttributePolicy} - Environment, release, redaction and mask settings.
|
|
36
|
+
* @returns {OtlpSpan} The span in the shape Langfuse's ingestion endpoint reads.
|
|
37
|
+
*/
|
|
38
|
+
export function toOtlpSpan(span, policy) {
|
|
39
|
+
const failed = span.status === SpanStatus.ERROR;
|
|
40
|
+
return {
|
|
41
|
+
traceId: span.traceId,
|
|
42
|
+
spanId: span.spanId,
|
|
43
|
+
...(span.parentSpanId && { parentSpanId: span.parentSpanId }),
|
|
44
|
+
name: span.name,
|
|
45
|
+
kind: SPAN_KIND_INTERNAL,
|
|
46
|
+
startTimeUnixNano: toNanos(span.startTime),
|
|
47
|
+
endTimeUnixNano: toNanos(span.endTime ?? span.startTime),
|
|
48
|
+
attributes: toOtlpAttributes(protectAttributes(span.attributes, policy)),
|
|
49
|
+
events: span.events.map((event) => ({
|
|
50
|
+
name: event.name,
|
|
51
|
+
timeUnixNano: toNanos(event.timestamp),
|
|
52
|
+
attributes: toOtlpAttributes(event.attributes ?? {}),
|
|
53
|
+
})),
|
|
54
|
+
status: {
|
|
55
|
+
code: OTLP_STATUS_CODES[span.status],
|
|
56
|
+
...(failed && span.statusMessage && { message: span.statusMessage }),
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* @description Splits encoded spans into request-sized batches, bounded by span count and serialized bytes.
|
|
62
|
+
* @param spans {OtlpSpan[]} - Encoded spans, in export order.
|
|
63
|
+
* @returns {OtlpSpan[][]} Batches that each fit one export request; a single oversized span still gets its own batch.
|
|
64
|
+
*/
|
|
65
|
+
export function chunkOtlpSpans(spans) {
|
|
66
|
+
const chunks = [];
|
|
67
|
+
let current = [];
|
|
68
|
+
let bytes = 0;
|
|
69
|
+
for (const span of spans) {
|
|
70
|
+
const size = Buffer.byteLength(JSON.stringify(span));
|
|
71
|
+
if (current.length > 0 &&
|
|
72
|
+
(current.length >= MAX_SPANS_PER_REQUEST ||
|
|
73
|
+
bytes + size > MAX_BYTES_PER_REQUEST)) {
|
|
74
|
+
chunks.push(current);
|
|
75
|
+
current = [];
|
|
76
|
+
bytes = 0;
|
|
77
|
+
}
|
|
78
|
+
current.push(span);
|
|
79
|
+
bytes += size;
|
|
80
|
+
}
|
|
81
|
+
if (current.length > 0) {
|
|
82
|
+
chunks.push(current);
|
|
83
|
+
}
|
|
84
|
+
return chunks;
|
|
85
|
+
}
|
|
@@ -601,10 +601,7 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
601
601
|
async executeSingleTool(tools, toolName, args, _toolUseId) {
|
|
602
602
|
return bedrockTracer.startActiveSpan("bedrock.tool.execute", {
|
|
603
603
|
kind: SpanKind.CLIENT,
|
|
604
|
-
attributes: {
|
|
605
|
-
"gen_ai.tool.name": toolName,
|
|
606
|
-
"gen_ai.system": "aws.bedrock",
|
|
607
|
-
},
|
|
604
|
+
attributes: { "tool.name": toolName },
|
|
608
605
|
}, async (span) => {
|
|
609
606
|
try {
|
|
610
607
|
logger.debug(`[AmazonBedrockProvider] Executing single tool: ${toolName}`, {
|
|
@@ -711,8 +708,9 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
711
708
|
if (disableTools) {
|
|
712
709
|
return {};
|
|
713
710
|
}
|
|
714
|
-
const aiTools = optionTools
|
|
715
|
-
|
|
711
|
+
const aiTools = optionTools
|
|
712
|
+
? this.toolsManager.wrapExternalToolsWithEvents(optionTools)
|
|
713
|
+
: await this.getAllTools();
|
|
716
714
|
return this.convertAISDKToolsToToolDefinitions(aiTools);
|
|
717
715
|
}
|
|
718
716
|
/**
|
|
@@ -896,177 +894,149 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
896
894
|
async executeStream(options) {
|
|
897
895
|
logger.debug("[TRACE] executeStream ENTRY - starting streaming attempt");
|
|
898
896
|
logger.info("[AmazonBedrockProvider] Attempting real streaming with ConverseStreamCommand");
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
options.input?.content?.length
|
|
914
|
-
options.input?.
|
|
915
|
-
options.input?.
|
|
916
|
-
options.input?.
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
});
|
|
930
|
-
// Same instance-default fold as the generate() path above.
|
|
931
|
-
this.applyToolReplayDefault(options);
|
|
932
|
-
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
933
|
-
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
934
|
-
// Convert to Bedrock format
|
|
935
|
-
this.conversationHistory =
|
|
936
|
-
this.convertToBedrockMessages(multimodalMessages);
|
|
937
|
-
}
|
|
938
|
-
else {
|
|
939
|
-
logger.debug(`[AmazonBedrockProvider] Text-only input, using simple message builder`);
|
|
940
|
-
// Add user message to conversation - simple text-only case
|
|
941
|
-
const userMessage = {
|
|
942
|
-
role: "user",
|
|
943
|
-
content: [{ text: options.input.text }],
|
|
944
|
-
};
|
|
945
|
-
this.conversationHistory.push(userMessage);
|
|
946
|
-
}
|
|
947
|
-
logger.debug(`[AmazonBedrockProvider] Starting streaming conversation with ${this.conversationHistory.length} message(s)`);
|
|
948
|
-
// Call the actual streaming implementation that already exists
|
|
949
|
-
logger.debug("[TRACE] executeStream - calling streamingConversationLoop NOW");
|
|
950
|
-
const result = await this.streamingConversationLoop(options, streamSpan);
|
|
951
|
-
logger.debug("[TRACE] executeStream - streamingConversationLoop SUCCESS, returning result");
|
|
952
|
-
streamSpan.setStatus({ code: SpanStatusCode.OK });
|
|
953
|
-
streamSpan.end();
|
|
954
|
-
return result;
|
|
897
|
+
try {
|
|
898
|
+
logger.debug("[TRACE] executeStream TRY block - about to call streamingConversationLoop");
|
|
899
|
+
// Clear conversation history for new streaming session
|
|
900
|
+
this.conversationHistory = [];
|
|
901
|
+
// Check for multimodal input (images, PDFs, CSVs, files)
|
|
902
|
+
const hasMultimodalInput = !!(options.input?.images?.length ||
|
|
903
|
+
options.input?.content?.length ||
|
|
904
|
+
options.input?.files?.length ||
|
|
905
|
+
options.input?.csvFiles?.length ||
|
|
906
|
+
options.input?.pdfFiles?.length);
|
|
907
|
+
if (hasMultimodalInput) {
|
|
908
|
+
logger.debug(`[AmazonBedrockProvider] Detected multimodal input, using multimodal message builder`, {
|
|
909
|
+
hasImages: !!options.input?.images?.length,
|
|
910
|
+
imageCount: options.input?.images?.length || 0,
|
|
911
|
+
hasContent: !!options.input?.content?.length,
|
|
912
|
+
contentCount: options.input?.content?.length || 0,
|
|
913
|
+
hasFiles: !!options.input?.files?.length,
|
|
914
|
+
fileCount: options.input?.files?.length || 0,
|
|
915
|
+
hasCSVFiles: !!options.input?.csvFiles?.length,
|
|
916
|
+
csvFileCount: options.input?.csvFiles?.length || 0,
|
|
917
|
+
hasPDFFiles: !!options.input?.pdfFiles?.length,
|
|
918
|
+
pdfFileCount: options.input?.pdfFiles?.length || 0,
|
|
919
|
+
});
|
|
920
|
+
// Same instance-default fold as the generate() path above.
|
|
921
|
+
this.applyToolReplayDefault(options);
|
|
922
|
+
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
923
|
+
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
924
|
+
// Convert to Bedrock format
|
|
925
|
+
this.conversationHistory =
|
|
926
|
+
this.convertToBedrockMessages(multimodalMessages);
|
|
955
927
|
}
|
|
956
|
-
|
|
957
|
-
logger.debug(
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
//
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
if (buffer.length > 0) {
|
|
928
|
+
else {
|
|
929
|
+
logger.debug(`[AmazonBedrockProvider] Text-only input, using simple message builder`);
|
|
930
|
+
// Add user message to conversation - simple text-only case
|
|
931
|
+
const userMessage = {
|
|
932
|
+
role: "user",
|
|
933
|
+
content: [{ text: options.input.text }],
|
|
934
|
+
};
|
|
935
|
+
this.conversationHistory.push(userMessage);
|
|
936
|
+
}
|
|
937
|
+
logger.debug(`[AmazonBedrockProvider] Starting streaming conversation with ${this.conversationHistory.length} message(s)`);
|
|
938
|
+
// Call the actual streaming implementation that already exists
|
|
939
|
+
logger.debug("[TRACE] executeStream - calling streamingConversationLoop NOW");
|
|
940
|
+
const result = await this.streamingConversationLoop(options);
|
|
941
|
+
logger.debug("[TRACE] executeStream - streamingConversationLoop SUCCESS, returning result");
|
|
942
|
+
return result;
|
|
943
|
+
}
|
|
944
|
+
catch (error) {
|
|
945
|
+
logger.debug("[TRACE] executeStream CATCH - error caught from streamingConversationLoop");
|
|
946
|
+
const errorObj = error;
|
|
947
|
+
// Check if error is related to streaming permissions. Gated on the
|
|
948
|
+
// real AWS SDK service-exception shape ($metadata) first: a
|
|
949
|
+
// middleware's wrapStream/transformParams can throw an arbitrary
|
|
950
|
+
// Error whose message happens to contain "streaming" or whose name
|
|
951
|
+
// happens to collide with an AWS exception name, and without this
|
|
952
|
+
// guard that error was silently swallowed and retried as if AWS
|
|
953
|
+
// itself had rejected the streaming call (see
|
|
954
|
+
// BedrockRuntimeServiceException in the AWS SDK, which carries
|
|
955
|
+
// $fault/$metadata on every genuine service exception).
|
|
956
|
+
const hasAwsServiceExceptionShape = typeof errorObj === "object" &&
|
|
957
|
+
errorObj !== null &&
|
|
958
|
+
"$metadata" in errorObj;
|
|
959
|
+
const isPermissionError = hasAwsServiceExceptionShape &&
|
|
960
|
+
(errorObj?.name === "AccessDeniedException" ||
|
|
961
|
+
errorObj?.name === "UnauthorizedOperation" ||
|
|
962
|
+
errorObj?.message?.includes("bedrock:InvokeModelWithResponseStream") ||
|
|
963
|
+
errorObj?.message?.includes("streaming") ||
|
|
964
|
+
errorObj?.message?.includes("ConverseStream"));
|
|
965
|
+
logger.debug("[TRACE] executeStream CATCH - checking if permission error");
|
|
966
|
+
logger.debug(`[TRACE] executeStream CATCH - isPermissionError=${isPermissionError}`);
|
|
967
|
+
if (isPermissionError) {
|
|
968
|
+
logger.debug("[TRACE] executeStream CATCH - PERMISSION ERROR DETECTED, starting fallback");
|
|
969
|
+
logger.warn(`[AmazonBedrockProvider] Streaming permissions not available, falling back to generate method: ${errorObj.message}`);
|
|
970
|
+
// Fallback to generate method and convert to streaming format
|
|
971
|
+
const generateResult = await this.generate({
|
|
972
|
+
prompt: options.input.text,
|
|
973
|
+
input: options.input,
|
|
974
|
+
maxTokens: options.maxTokens,
|
|
975
|
+
temperature: options.temperature,
|
|
976
|
+
systemPrompt: options.systemPrompt,
|
|
977
|
+
});
|
|
978
|
+
if (!generateResult) {
|
|
979
|
+
// eslint-disable-next-line preserve-caught-error
|
|
980
|
+
throw new Error("Generate method returned null result");
|
|
981
|
+
}
|
|
982
|
+
// Convert generate result to streaming format.
|
|
983
|
+
// Use whitespace-preserving split (matches BaseProvider's
|
|
984
|
+
// executeFakeStreaming) so newlines, tabs, indentation, code
|
|
985
|
+
// blocks, and markdown tables aren't collapsed to single spaces.
|
|
986
|
+
const stream = new ReadableStream({
|
|
987
|
+
start(controller) {
|
|
988
|
+
const responseText = generateResult.content || "";
|
|
989
|
+
const tokens = responseText.split(/(\s+)/);
|
|
990
|
+
let buffer = "";
|
|
991
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
992
|
+
buffer += tokens[i];
|
|
993
|
+
const shouldYield = i === tokens.length - 1 ||
|
|
994
|
+
buffer.length > 50 ||
|
|
995
|
+
/[.!?;,]\s*$/.test(buffer);
|
|
996
|
+
if (shouldYield && buffer.length > 0) {
|
|
1025
997
|
controller.enqueue({ content: buffer });
|
|
998
|
+
buffer = "";
|
|
1026
999
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1000
|
+
}
|
|
1001
|
+
if (buffer.length > 0) {
|
|
1002
|
+
controller.enqueue({ content: buffer });
|
|
1003
|
+
}
|
|
1004
|
+
controller.close();
|
|
1005
|
+
},
|
|
1006
|
+
});
|
|
1007
|
+
// Convert ReadableStream to AsyncIterable like streamingConversationLoop does
|
|
1008
|
+
const asyncIterable = {
|
|
1009
|
+
async *[Symbol.asyncIterator]() {
|
|
1010
|
+
const reader = stream.getReader();
|
|
1011
|
+
try {
|
|
1012
|
+
while (true) {
|
|
1013
|
+
const { done, value } = await reader.read();
|
|
1014
|
+
if (done) {
|
|
1015
|
+
break;
|
|
1041
1016
|
}
|
|
1017
|
+
yield value;
|
|
1042
1018
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
// Re-throw non-permission errors
|
|
1061
|
-
streamSpan.setStatus({
|
|
1062
|
-
code: SpanStatusCode.ERROR,
|
|
1063
|
-
message: errorObj instanceof Error ? errorObj.message : String(errorObj),
|
|
1064
|
-
});
|
|
1065
|
-
streamSpan.recordException(errorObj instanceof Error ? errorObj : new Error(String(errorObj)));
|
|
1066
|
-
streamSpan.end();
|
|
1067
|
-
throw error;
|
|
1019
|
+
}
|
|
1020
|
+
finally {
|
|
1021
|
+
reader.releaseLock();
|
|
1022
|
+
}
|
|
1023
|
+
},
|
|
1024
|
+
};
|
|
1025
|
+
return {
|
|
1026
|
+
stream: asyncIterable,
|
|
1027
|
+
// The generate() fallback already computed real usage — a
|
|
1028
|
+
// hardcoded zero object here threw it away.
|
|
1029
|
+
usage: generateResult.usage,
|
|
1030
|
+
model: this.modelName || this.getDefaultModel(),
|
|
1031
|
+
provider: this.getProviderName(),
|
|
1032
|
+
metadata: {
|
|
1033
|
+
fallback: true,
|
|
1034
|
+
},
|
|
1035
|
+
};
|
|
1068
1036
|
}
|
|
1069
|
-
|
|
1037
|
+
// Re-throw non-permission errors
|
|
1038
|
+
throw error;
|
|
1039
|
+
}
|
|
1070
1040
|
}
|
|
1071
1041
|
/**
|
|
1072
1042
|
* A multimodal turn skips model middleware (see modelBridge.ts's header).
|
|
@@ -1214,7 +1184,7 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
1214
1184
|
...(cacheWrite > 0 && { cacheCreationTokens: cacheWrite }),
|
|
1215
1185
|
};
|
|
1216
1186
|
}
|
|
1217
|
-
async streamingConversationLoop(options
|
|
1187
|
+
async streamingConversationLoop(options) {
|
|
1218
1188
|
const startTime = Date.now();
|
|
1219
1189
|
const maxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
1220
1190
|
// Resolved once for the whole turn. Bedrock has no mid-turn tool
|
|
@@ -1290,9 +1260,6 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
1290
1260
|
return stepResult;
|
|
1291
1261
|
},
|
|
1292
1262
|
};
|
|
1293
|
-
streamSpan.addEvent("stream.api_call", {
|
|
1294
|
-
"bedrock.tool_count": toolConfig?.tools?.length ?? 0,
|
|
1295
|
-
});
|
|
1296
1263
|
return runAgenticLoop(adapter, conversation, {
|
|
1297
1264
|
tools: this.toEngineTools(tools),
|
|
1298
1265
|
abortSignal: signal,
|
|
@@ -1397,7 +1364,6 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
1397
1364
|
// Read after drain by the facade's fallbackOnMaxSteps gate. Keep
|
|
1398
1365
|
// the engine's own outcome; raw tool_use alone is not a step cap.
|
|
1399
1366
|
metadata.stopReason = outcome.result.stopReason;
|
|
1400
|
-
streamSpan.setAttribute("gen_ai.response.stop_reason", outcome.result.rawStopReason ?? "unknown");
|
|
1401
1367
|
}
|
|
1402
1368
|
// Analytics is resolved off `settled` above, so it lands whether or
|
|
1403
1369
|
// not anyone drains the stream.
|
|
@@ -211,7 +211,20 @@ export function createBedrockLoopAdapter(config) {
|
|
|
211
211
|
// No toolFailureBreaker: Bedrock has never had strike counting — a failing
|
|
212
212
|
// tool becomes one error tool-result with no cross-step memory. Preserved.
|
|
213
213
|
buildStepRequest(conversation, step) {
|
|
214
|
-
|
|
214
|
+
const input = config.buildCommandInput(conversation, step);
|
|
215
|
+
return {
|
|
216
|
+
raw: input,
|
|
217
|
+
generation: {
|
|
218
|
+
provider: "bedrock",
|
|
219
|
+
model: input.modelId,
|
|
220
|
+
modelParameters: {
|
|
221
|
+
temperature: input.inferenceConfig?.temperature,
|
|
222
|
+
maxTokens: input.inferenceConfig?.maxTokens,
|
|
223
|
+
topP: input.inferenceConfig?.topP,
|
|
224
|
+
},
|
|
225
|
+
input: { system: input.system, messages: input.messages },
|
|
226
|
+
},
|
|
227
|
+
};
|
|
215
228
|
},
|
|
216
229
|
async executeStep(request, channel, signal) {
|
|
217
230
|
const commandInput = request.raw;
|