@juspay/neurolink 9.70.7 → 9.71.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 +6 -0
- package/dist/browser/neurolink.min.js +344 -344
- package/dist/lib/neurolink.js +53 -16
- package/dist/lib/providers/googleVertex.js +257 -30
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +10 -1
- package/dist/lib/services/server/ai/observability/instrumentation.js +36 -1
- package/dist/lib/telemetry/attributes.d.ts +31 -0
- package/dist/lib/telemetry/attributes.js +46 -0
- package/dist/lib/telemetry/index.d.ts +1 -1
- package/dist/lib/telemetry/index.js +1 -1
- package/dist/lib/utils/anthropicTraceSanitizer.d.ts +7 -0
- package/dist/lib/utils/anthropicTraceSanitizer.js +26 -0
- package/dist/lib/utils/mcpErrorText.d.ts +16 -0
- package/dist/lib/utils/mcpErrorText.js +36 -0
- package/dist/neurolink.js +53 -16
- package/dist/providers/googleVertex.js +257 -30
- package/dist/services/server/ai/observability/instrumentation.d.ts +10 -1
- package/dist/services/server/ai/observability/instrumentation.js +36 -1
- package/dist/telemetry/attributes.d.ts +31 -0
- package/dist/telemetry/attributes.js +46 -0
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/anthropicTraceSanitizer.d.ts +7 -0
- package/dist/utils/anthropicTraceSanitizer.js +25 -0
- package/dist/utils/mcpErrorText.d.ts +16 -0
- package/dist/utils/mcpErrorText.js +36 -0
- package/package.json +2 -1
|
@@ -103,3 +103,34 @@ export declare const ATTR: {
|
|
|
103
103
|
readonly AR_DESCRIPTION: "autoresearch.description";
|
|
104
104
|
readonly AR_ERROR_CODE: "autoresearch.error_code";
|
|
105
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Langfuse observation/trace attribute names recognised by `@langfuse/otel`'s
|
|
108
|
+
* LangfuseSpanProcessor (already registered on the global TracerProvider). They
|
|
109
|
+
* let native (non-AI-SDK) provider paths emit spans that render as proper
|
|
110
|
+
* generation / tool observations — the same data the Vercel AI SDK's
|
|
111
|
+
* `experimental_telemetry` produced before providers moved to native SDKs.
|
|
112
|
+
*/
|
|
113
|
+
export declare const LANGFUSE_ATTR: {
|
|
114
|
+
readonly TRACE_NAME: "langfuse.trace.name";
|
|
115
|
+
readonly TRACE_INPUT: "langfuse.trace.input";
|
|
116
|
+
readonly TRACE_OUTPUT: "langfuse.trace.output";
|
|
117
|
+
readonly OBSERVATION_TYPE: "langfuse.observation.type";
|
|
118
|
+
readonly OBSERVATION_INPUT: "langfuse.observation.input";
|
|
119
|
+
readonly OBSERVATION_OUTPUT: "langfuse.observation.output";
|
|
120
|
+
readonly OBSERVATION_METADATA: "langfuse.observation.metadata";
|
|
121
|
+
readonly OBSERVATION_MODEL_NAME: "langfuse.observation.model.name";
|
|
122
|
+
readonly OBSERVATION_MODEL_PARAMETERS: "langfuse.observation.model.parameters";
|
|
123
|
+
readonly OBSERVATION_USAGE_DETAILS: "langfuse.observation.usage_details";
|
|
124
|
+
readonly OBSERVATION_LEVEL: "langfuse.observation.level";
|
|
125
|
+
readonly OBSERVATION_STATUS_MESSAGE: "langfuse.observation.status_message";
|
|
126
|
+
readonly OBSERVATION_COMPLETION_START_TIME: "langfuse.observation.completion_start_time";
|
|
127
|
+
};
|
|
128
|
+
/** Default ceiling for serialized span attribute values. */
|
|
129
|
+
export declare const SPAN_ATTRIBUTE_MAX_CHARS = 40000;
|
|
130
|
+
/**
|
|
131
|
+
* Serialize an arbitrary value for a span attribute, hard-capped at
|
|
132
|
+
* `maxChars` so a pathological prompt or tool result can't put megabytes
|
|
133
|
+
* on a single span. Strings pass through unserialized; everything else is
|
|
134
|
+
* JSON-stringified with a String() fallback for circular structures.
|
|
135
|
+
*/
|
|
136
|
+
export declare function spanJsonAttribute(value: unknown, maxChars?: number): string;
|
|
@@ -114,4 +114,50 @@ export const ATTR = {
|
|
|
114
114
|
AR_DESCRIPTION: "autoresearch.description",
|
|
115
115
|
AR_ERROR_CODE: "autoresearch.error_code",
|
|
116
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Langfuse observation/trace attribute names recognised by `@langfuse/otel`'s
|
|
119
|
+
* LangfuseSpanProcessor (already registered on the global TracerProvider). They
|
|
120
|
+
* let native (non-AI-SDK) provider paths emit spans that render as proper
|
|
121
|
+
* generation / tool observations — the same data the Vercel AI SDK's
|
|
122
|
+
* `experimental_telemetry` produced before providers moved to native SDKs.
|
|
123
|
+
*/
|
|
124
|
+
export const LANGFUSE_ATTR = {
|
|
125
|
+
TRACE_NAME: "langfuse.trace.name",
|
|
126
|
+
TRACE_INPUT: "langfuse.trace.input",
|
|
127
|
+
TRACE_OUTPUT: "langfuse.trace.output",
|
|
128
|
+
OBSERVATION_TYPE: "langfuse.observation.type",
|
|
129
|
+
OBSERVATION_INPUT: "langfuse.observation.input",
|
|
130
|
+
OBSERVATION_OUTPUT: "langfuse.observation.output",
|
|
131
|
+
OBSERVATION_METADATA: "langfuse.observation.metadata",
|
|
132
|
+
OBSERVATION_MODEL_NAME: "langfuse.observation.model.name",
|
|
133
|
+
OBSERVATION_MODEL_PARAMETERS: "langfuse.observation.model.parameters",
|
|
134
|
+
OBSERVATION_USAGE_DETAILS: "langfuse.observation.usage_details",
|
|
135
|
+
OBSERVATION_LEVEL: "langfuse.observation.level",
|
|
136
|
+
OBSERVATION_STATUS_MESSAGE: "langfuse.observation.status_message",
|
|
137
|
+
OBSERVATION_COMPLETION_START_TIME: "langfuse.observation.completion_start_time",
|
|
138
|
+
};
|
|
139
|
+
/** Default ceiling for serialized span attribute values. */
|
|
140
|
+
export const SPAN_ATTRIBUTE_MAX_CHARS = 40_000;
|
|
141
|
+
/**
|
|
142
|
+
* Serialize an arbitrary value for a span attribute, hard-capped at
|
|
143
|
+
* `maxChars` so a pathological prompt or tool result can't put megabytes
|
|
144
|
+
* on a single span. Strings pass through unserialized; everything else is
|
|
145
|
+
* JSON-stringified with a String() fallback for circular structures.
|
|
146
|
+
*/
|
|
147
|
+
export function spanJsonAttribute(value, maxChars = SPAN_ATTRIBUTE_MAX_CHARS) {
|
|
148
|
+
let serialized;
|
|
149
|
+
try {
|
|
150
|
+
serialized =
|
|
151
|
+
typeof value === "string"
|
|
152
|
+
? value
|
|
153
|
+
: (JSON.stringify(value) ?? String(value));
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
serialized = String(value);
|
|
157
|
+
}
|
|
158
|
+
if (serialized.length > maxChars) {
|
|
159
|
+
return `${serialized.slice(0, maxChars)}...[truncated ${serialized.length - maxChars} chars]`;
|
|
160
|
+
}
|
|
161
|
+
return serialized;
|
|
162
|
+
}
|
|
117
163
|
//# sourceMappingURL=attributes.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { TelemetryService } from "./telemetryService.js";
|
|
2
2
|
export { tracers } from "./tracers.js";
|
|
3
3
|
export { withSpan, withClientSpan, withStreamSpan, withClientStreamSpan, } from "./withSpan.js";
|
|
4
|
-
export { ATTR } from "./attributes.js";
|
|
4
|
+
export { ATTR, LANGFUSE_ATTR, SPAN_ATTRIBUTE_MAX_CHARS, spanJsonAttribute, } from "./attributes.js";
|
|
5
5
|
/**
|
|
6
6
|
* Initialize telemetry for NeuroLink
|
|
7
7
|
* Reuses an existing global TracerProvider when one is already registered,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export { TelemetryService } from "./telemetryService.js";
|
|
3
3
|
export { tracers } from "./tracers.js";
|
|
4
4
|
export { withSpan, withClientSpan, withStreamSpan, withClientStreamSpan, } from "./withSpan.js";
|
|
5
|
-
export { ATTR } from "./attributes.js";
|
|
5
|
+
export { ATTR, LANGFUSE_ATTR, SPAN_ATTRIBUTE_MAX_CHARS, spanJsonAttribute, } from "./attributes.js";
|
|
6
6
|
import { logger } from "../utils/logger.js";
|
|
7
7
|
/**
|
|
8
8
|
* Initialize telemetry for NeuroLink
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { VertexAnthropicMessage } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Strips base64 image/PDF payloads from Anthropic messages before they go on a
|
|
4
|
+
* trace attribute — one screenshot would otherwise be megabytes on a span.
|
|
5
|
+
* Other block types pass through; the serializer still applies its length cap.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sanitizeAnthropicMessagesForTrace(messages: VertexAnthropicMessage[]): Array<Record<string, unknown>>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strips base64 image/PDF payloads from Anthropic messages before they go on a
|
|
3
|
+
* trace attribute — one screenshot would otherwise be megabytes on a span.
|
|
4
|
+
* Other block types pass through; the serializer still applies its length cap.
|
|
5
|
+
*/
|
|
6
|
+
export function sanitizeAnthropicMessagesForTrace(messages) {
|
|
7
|
+
return messages.map((message) => {
|
|
8
|
+
if (typeof message.content === "string") {
|
|
9
|
+
return { role: message.role, content: message.content };
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
role: message.role,
|
|
13
|
+
content: message.content.map((block) => {
|
|
14
|
+
if (block.type === "image" || block.type === "document") {
|
|
15
|
+
return {
|
|
16
|
+
type: block.type,
|
|
17
|
+
media_type: block.source.media_type,
|
|
18
|
+
base64_chars: block.source.data.length,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return block;
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=anthropicTraceSanitizer.js.map
|
|
@@ -8,3 +8,19 @@
|
|
|
8
8
|
* must happen here and propagate to all three surfaces.
|
|
9
9
|
*/
|
|
10
10
|
export declare function extractMcpErrorText(raw: unknown): string;
|
|
11
|
+
/**
|
|
12
|
+
* MCP tools signal failure by RETURNING `{ isError: true, ... }`, not throwing,
|
|
13
|
+
* so execute()'s try/catch never sees it. Returns a capped status message for
|
|
14
|
+
* failures (undefined for success) for the caller to set the span error level.
|
|
15
|
+
*
|
|
16
|
+
* Generic over input shape: accepts either a result object or a JSON-stringified
|
|
17
|
+
* envelope (different providers hand back different shapes), mirroring
|
|
18
|
+
* `extractMcpErrorText`. A non-JSON string has no `isError` field, so it is
|
|
19
|
+
* correctly treated as "not an error" (→ undefined).
|
|
20
|
+
*
|
|
21
|
+
* Layered on `extractMcpErrorText`: this adds the `isError === true` gate and
|
|
22
|
+
* the human-readable "MCP tool returned isError: …" prefix, while the shared
|
|
23
|
+
* helper owns the content parsing and the 500-char cap. When `isError` is set
|
|
24
|
+
* but no readable text is present, falls back to a generic message.
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractMcpToolErrorMessage(result: unknown): string | undefined;
|
|
@@ -33,4 +33,40 @@ export function extractMcpErrorText(raw) {
|
|
|
33
33
|
.map((c) => c.text);
|
|
34
34
|
return texts.join(" ").substring(0, 500);
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* MCP tools signal failure by RETURNING `{ isError: true, ... }`, not throwing,
|
|
38
|
+
* so execute()'s try/catch never sees it. Returns a capped status message for
|
|
39
|
+
* failures (undefined for success) for the caller to set the span error level.
|
|
40
|
+
*
|
|
41
|
+
* Generic over input shape: accepts either a result object or a JSON-stringified
|
|
42
|
+
* envelope (different providers hand back different shapes), mirroring
|
|
43
|
+
* `extractMcpErrorText`. A non-JSON string has no `isError` field, so it is
|
|
44
|
+
* correctly treated as "not an error" (→ undefined).
|
|
45
|
+
*
|
|
46
|
+
* Layered on `extractMcpErrorText`: this adds the `isError === true` gate and
|
|
47
|
+
* the human-readable "MCP tool returned isError: …" prefix, while the shared
|
|
48
|
+
* helper owns the content parsing and the 500-char cap. When `isError` is set
|
|
49
|
+
* but no readable text is present, falls back to a generic message.
|
|
50
|
+
*/
|
|
51
|
+
export function extractMcpToolErrorMessage(result) {
|
|
52
|
+
let resultObj = result;
|
|
53
|
+
if (typeof resultObj === "string") {
|
|
54
|
+
try {
|
|
55
|
+
resultObj = JSON.parse(resultObj);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!resultObj || typeof resultObj !== "object") {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
if (resultObj.isError !== true) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const text = extractMcpErrorText(resultObj);
|
|
68
|
+
return text
|
|
69
|
+
? `MCP tool returned isError: ${text}`
|
|
70
|
+
: "MCP tool returned isError: true";
|
|
71
|
+
}
|
|
36
72
|
//# sourceMappingURL=mcpErrorText.js.map
|
package/dist/neurolink.js
CHANGED
|
@@ -55,7 +55,7 @@ import { createMemoryRetrievalTools } from "./memory/memoryRetrievalTools.js";
|
|
|
55
55
|
import { getMetricsAggregator, MetricsAggregator, } from "./observability/metricsAggregator.js";
|
|
56
56
|
import { SpanStatus, SpanType, CircuitBreakerOpenError, ConversationMemoryError, AuthenticationError, AuthorizationError, InvalidModelError, ModelAccessDeniedError, } from "./types/index.js";
|
|
57
57
|
import { SpanSerializer } from "./observability/utils/spanSerializer.js";
|
|
58
|
-
import { flushOpenTelemetry, getLangfuseHealthStatus, initializeOpenTelemetry, isOpenTelemetryInitialized, runWithCurrentLangfuseContext, setLangfuseContext, shutdownOpenTelemetry, } from "./services/server/ai/observability/instrumentation.js";
|
|
58
|
+
import { flushOpenTelemetry, getLangfuseContext, getLangfuseHealthStatus, initializeOpenTelemetry, isOpenTelemetryInitialized, runWithCurrentLangfuseContext, setLangfuseContext, shutdownOpenTelemetry, stampGuestRescueIdentity, } from "./services/server/ai/observability/instrumentation.js";
|
|
59
59
|
import { TaskManager } from "./tasks/taskManager.js";
|
|
60
60
|
import { createTaskTools } from "./tasks/tools/taskTools.js";
|
|
61
61
|
import { ATTR } from "./telemetry/attributes.js";
|
|
@@ -1378,11 +1378,8 @@ Current user's request: ${currentInput}`;
|
|
|
1378
1378
|
* Calls add(userId, content) which internally condenses old + new via LLM.
|
|
1379
1379
|
* Supports additional users with per-user prompt and maxWords overrides.
|
|
1380
1380
|
*/
|
|
1381
|
-
storeMemoryInBackground(originalPrompt, responseContent, userId, additionalUsers) {
|
|
1382
|
-
|
|
1383
|
-
// memory writes appear under the originating Langfuse trace instead of
|
|
1384
|
-
// becoming orphan spans.
|
|
1385
|
-
const wrappedMemoryWrite = runWithCurrentLangfuseContext(async () => {
|
|
1381
|
+
storeMemoryInBackground(originalPrompt, responseContent, userId, additionalUsers, langfuseIdentity) {
|
|
1382
|
+
const memoryWrite = async () => {
|
|
1386
1383
|
try {
|
|
1387
1384
|
const client = this.ensureMemoryReady();
|
|
1388
1385
|
if (!client) {
|
|
@@ -1408,7 +1405,21 @@ Current user's request: ${currentInput}`;
|
|
|
1408
1405
|
catch (error) {
|
|
1409
1406
|
logger.warn("Memory storage failed:", error);
|
|
1410
1407
|
}
|
|
1411
|
-
}
|
|
1408
|
+
};
|
|
1409
|
+
// Carry the turn's identity across the setImmediate boundary so the
|
|
1410
|
+
// condensation generate + redis spans don't orphan to "guest". Keep the
|
|
1411
|
+
// ambient store when it survived (generate path — carries conversationId,
|
|
1412
|
+
// metadata, …); re-establish from the caller only when it was lost (stream
|
|
1413
|
+
// path, which fires after the caller consumed the stream).
|
|
1414
|
+
const ambient = getLangfuseContext();
|
|
1415
|
+
const wrappedMemoryWrite = !(ambient?.traceName || ambient?.userId) &&
|
|
1416
|
+
(langfuseIdentity?.traceName || langfuseIdentity?.sessionId)
|
|
1417
|
+
? () => setLangfuseContext({
|
|
1418
|
+
userId,
|
|
1419
|
+
sessionId: langfuseIdentity.sessionId ?? null,
|
|
1420
|
+
traceName: langfuseIdentity.traceName ?? null,
|
|
1421
|
+
}, memoryWrite)
|
|
1422
|
+
: runWithCurrentLangfuseContext(memoryWrite);
|
|
1412
1423
|
setImmediate(wrappedMemoryWrite);
|
|
1413
1424
|
}
|
|
1414
1425
|
/**
|
|
@@ -2801,7 +2812,15 @@ Current user's request: ${currentInput}`;
|
|
|
2801
2812
|
}
|
|
2802
2813
|
const startedAt = Date.now();
|
|
2803
2814
|
try {
|
|
2804
|
-
return await this.runWithFallbackOrchestration(optionsOrPrompt, "generate", (opts) =>
|
|
2815
|
+
return await this.runWithFallbackOrchestration(optionsOrPrompt, "generate", (opts) => {
|
|
2816
|
+
// Capture root-ness before startActiveSpan makes generateSpan active.
|
|
2817
|
+
// The actual guest-rescue stamp is deferred to executeGenerateRequest,
|
|
2818
|
+
// AFTER prepareGenerateRequest merges auth/requestContext-derived
|
|
2819
|
+
// identity into options.context — otherwise an auth:{token} caller
|
|
2820
|
+
// with no pre-set context.userId would stamp the root span as guest.
|
|
2821
|
+
const generateIsRoot = !trace.getSpan(context.active());
|
|
2822
|
+
return tracers.sdk.startActiveSpan("neurolink.generate", { kind: SpanKind.INTERNAL }, (generateSpan) => this.executeGenerateWithMetricsContext(opts, generateSpan, generateIsRoot));
|
|
2823
|
+
});
|
|
2805
2824
|
}
|
|
2806
2825
|
catch (error) {
|
|
2807
2826
|
// Lifecycle middleware (wrapGenerate.catch in builtin/lifecycle.ts)
|
|
@@ -2973,14 +2992,17 @@ Current user's request: ${currentInput}`;
|
|
|
2973
2992
|
return { error };
|
|
2974
2993
|
}
|
|
2975
2994
|
}
|
|
2976
|
-
async executeGenerateWithMetricsContext(optionsOrPrompt, generateSpan) {
|
|
2977
|
-
return metricsTraceContextStorage.run(this.createMetricsTraceContext(), () => this.executeGenerateRequest(optionsOrPrompt, generateSpan));
|
|
2995
|
+
async executeGenerateWithMetricsContext(optionsOrPrompt, generateSpan, isRootSpan) {
|
|
2996
|
+
return metricsTraceContextStorage.run(this.createMetricsTraceContext(), () => this.executeGenerateRequest(optionsOrPrompt, generateSpan, isRootSpan));
|
|
2978
2997
|
}
|
|
2979
|
-
async executeGenerateRequest(optionsOrPrompt, generateSpan) {
|
|
2998
|
+
async executeGenerateRequest(optionsOrPrompt, generateSpan, isRootSpan) {
|
|
2980
2999
|
let resolvedOptions;
|
|
2981
3000
|
try {
|
|
2982
3001
|
const { options, originalPrompt } = await this.prepareGenerateRequest(optionsOrPrompt, generateSpan);
|
|
2983
3002
|
resolvedOptions = options;
|
|
3003
|
+
// Stamp now that prepareGenerateRequest has merged any auth/requestContext
|
|
3004
|
+
// identity into options.context (see capture of isRootSpan in generate()).
|
|
3005
|
+
stampGuestRescueIdentity(generateSpan, options.context, isRootSpan);
|
|
2984
3006
|
const earlyResult = await this.maybeHandleEarlyGenerateResult(options, generateSpan);
|
|
2985
3007
|
if (earlyResult) {
|
|
2986
3008
|
generateSpan.setStatus({ code: SpanStatusCode.OK });
|
|
@@ -3545,7 +3567,7 @@ Current user's request: ${currentInput}`;
|
|
|
3545
3567
|
// Memory storage
|
|
3546
3568
|
if (this.shouldWriteMemory(options.memory, options.context?.userId, generateResult.content) &&
|
|
3547
3569
|
options.context?.userId) {
|
|
3548
|
-
this.storeMemoryInBackground(originalPrompt ?? "", generateResult.content.trim(), options.context.userId, options.memory?.additionalUsers);
|
|
3570
|
+
this.storeMemoryInBackground(originalPrompt ?? "", generateResult.content.trim(), options.context.userId, options.memory?.additionalUsers, options.context);
|
|
3549
3571
|
}
|
|
3550
3572
|
}
|
|
3551
3573
|
/**
|
|
@@ -5531,10 +5553,20 @@ Current user's request: ${currentInput}`;
|
|
|
5531
5553
|
[ATTR.NL_PROVIDER]: options.provider || "default",
|
|
5532
5554
|
[ATTR.GEN_AI_MODEL]: options.model || "default",
|
|
5533
5555
|
[ATTR.NL_INPUT_LENGTH]: options.input?.text?.length || 0,
|
|
5534
|
-
|
|
5556
|
+
// Count registered custom tools too — chat hosts put their MCP tools
|
|
5557
|
+
// in the registry, so options.tools alone under-reports.
|
|
5558
|
+
[ATTR.NL_HAS_TOOLS]: !options.disableTools &&
|
|
5559
|
+
(!!(options.tools && Object.keys(options.tools).length > 0) ||
|
|
5560
|
+
this.getCustomTools().size > 0),
|
|
5535
5561
|
[ATTR.NL_STREAM_MODE]: true,
|
|
5536
5562
|
},
|
|
5537
5563
|
});
|
|
5564
|
+
// streamSpan isn't active yet, so context.active() is its parent — empty =
|
|
5565
|
+
// root. Capture root-ness here, but defer the actual guest-rescue stamp to
|
|
5566
|
+
// after validateStreamRequestOptions merges auth/requestContext identity
|
|
5567
|
+
// into options.context (below) — otherwise an auth:{token} caller with no
|
|
5568
|
+
// pre-set context.userId would stamp the root span as guest.
|
|
5569
|
+
const streamIsRoot = !trace.getSpan(context.active());
|
|
5538
5570
|
const spanStartTime = Date.now();
|
|
5539
5571
|
this._disableToolCacheForCurrentRequest = !!options.disableToolCache;
|
|
5540
5572
|
try {
|
|
@@ -5576,6 +5608,8 @@ Current user's request: ${currentInput}`;
|
|
|
5576
5608
|
const originalPrompt = options.input?.text ?? "";
|
|
5577
5609
|
options.fileRegistry = this.fileRegistry;
|
|
5578
5610
|
await this.validateStreamRequestOptions(options, startTime);
|
|
5611
|
+
// options.context now carries any auth/requestContext-derived identity.
|
|
5612
|
+
stampGuestRescueIdentity(streamSpan, options.context, streamIsRoot);
|
|
5579
5613
|
const workflowResult = await this.maybeHandleWorkflowStreamRequest({
|
|
5580
5614
|
options,
|
|
5581
5615
|
startTime,
|
|
@@ -5585,6 +5619,9 @@ Current user's request: ${currentInput}`;
|
|
|
5585
5619
|
if (workflowResult) {
|
|
5586
5620
|
return workflowResult;
|
|
5587
5621
|
}
|
|
5622
|
+
// Make neurolink.stream the active span so every provider span (generations,
|
|
5623
|
+
// tool calls) parents under it — one Langfuse trace per turn, not a forest.
|
|
5624
|
+
const streamSpanContext = trace.setSpan(context.active(), streamSpan);
|
|
5588
5625
|
// TTS Mode 2 deferred: stream() emits text first, then synthesizes the
|
|
5589
5626
|
// accumulated response into a single audio chunk at end-of-stream and
|
|
5590
5627
|
// resolves `streamResult.audio` with the same TTSResult. The resolver is
|
|
@@ -5599,7 +5636,7 @@ Current user's request: ${currentInput}`;
|
|
|
5599
5636
|
resolveStreamTtsAudio = resolve;
|
|
5600
5637
|
})
|
|
5601
5638
|
: undefined;
|
|
5602
|
-
const streamResult = await this.setLangfuseContextFromOptions(options, () => this.runStandardStreamRequest({
|
|
5639
|
+
const streamResult = await context.with(streamSpanContext, () => this.setLangfuseContextFromOptions(options, () => this.runStandardStreamRequest({
|
|
5603
5640
|
options,
|
|
5604
5641
|
streamSpan,
|
|
5605
5642
|
spanStartTime,
|
|
@@ -5608,7 +5645,7 @@ Current user's request: ${currentInput}`;
|
|
|
5608
5645
|
streamId,
|
|
5609
5646
|
originalPrompt,
|
|
5610
5647
|
ttsResolver: resolveStreamTtsAudio,
|
|
5611
|
-
}));
|
|
5648
|
+
})));
|
|
5612
5649
|
if (streamSttTranscription) {
|
|
5613
5650
|
streamResult.transcription = streamSttTranscription;
|
|
5614
5651
|
}
|
|
@@ -6512,7 +6549,7 @@ Current user's request: ${currentInput}`;
|
|
|
6512
6549
|
}
|
|
6513
6550
|
}
|
|
6514
6551
|
if (this.shouldWriteMemory(enhancedOptions.memory, enhancedOptions.context?.userId, accumulatedContent)) {
|
|
6515
|
-
this.storeMemoryInBackground(originalPrompt ?? "", accumulatedContent.trim(), enhancedOptions.context?.userId, enhancedOptions.memory?.additionalUsers);
|
|
6552
|
+
this.storeMemoryInBackground(originalPrompt ?? "", accumulatedContent.trim(), enhancedOptions.context?.userId, enhancedOptions.memory?.additionalUsers, enhancedOptions.context);
|
|
6516
6553
|
}
|
|
6517
6554
|
}
|
|
6518
6555
|
/**
|