@juspay/neurolink 9.10.0 → 9.11.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 +12 -0
- package/dist/adapters/video/videoAnalyzer.d.ts +3 -3
- package/dist/adapters/video/videoAnalyzer.js +39 -25
- package/dist/agent/directTools.d.ts +3 -3
- package/dist/cli/commands/config.d.ts +9 -9
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/constants/contextWindows.d.ts +6 -3
- package/dist/constants/contextWindows.js +30 -3
- package/dist/constants/index.d.ts +3 -3
- package/dist/constants/retry.d.ts +4 -4
- package/dist/constants/retry.js +1 -1
- package/dist/context/contextCompactor.d.ts +1 -1
- package/dist/context/contextCompactor.js +59 -1
- package/dist/context/summarizationEngine.d.ts +2 -2
- package/dist/context/summarizationEngine.js +44 -18
- package/dist/context/toolOutputLimits.d.ts +22 -13
- package/dist/context/toolOutputLimits.js +58 -64
- package/dist/core/baseProvider.d.ts +11 -2
- package/dist/core/baseProvider.js +30 -1
- package/dist/core/conversationMemoryManager.d.ts +13 -1
- package/dist/core/conversationMemoryManager.js +36 -5
- package/dist/core/modules/GenerationHandler.d.ts +6 -0
- package/dist/core/modules/GenerationHandler.js +192 -7
- package/dist/core/modules/MessageBuilder.js +42 -4
- package/dist/core/modules/TelemetryHandler.js +4 -1
- package/dist/core/redisConversationMemoryManager.d.ts +19 -3
- package/dist/core/redisConversationMemoryManager.js +253 -58
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/lib/adapters/video/videoAnalyzer.d.ts +3 -3
- package/dist/lib/adapters/video/videoAnalyzer.js +39 -25
- package/dist/lib/agent/directTools.d.ts +7 -7
- package/dist/lib/constants/contextWindows.d.ts +6 -3
- package/dist/lib/constants/contextWindows.js +30 -3
- package/dist/lib/constants/index.d.ts +3 -3
- package/dist/lib/constants/retry.d.ts +4 -4
- package/dist/lib/constants/retry.js +1 -1
- package/dist/lib/context/contextCompactor.d.ts +1 -1
- package/dist/lib/context/contextCompactor.js +59 -1
- package/dist/lib/context/summarizationEngine.d.ts +2 -2
- package/dist/lib/context/summarizationEngine.js +44 -18
- package/dist/lib/context/toolOutputLimits.d.ts +22 -13
- package/dist/lib/context/toolOutputLimits.js +58 -64
- package/dist/lib/core/baseProvider.d.ts +11 -2
- package/dist/lib/core/baseProvider.js +30 -1
- package/dist/lib/core/conversationMemoryManager.d.ts +13 -1
- package/dist/lib/core/conversationMemoryManager.js +36 -5
- package/dist/lib/core/modules/GenerationHandler.d.ts +6 -0
- package/dist/lib/core/modules/GenerationHandler.js +192 -7
- package/dist/lib/core/modules/MessageBuilder.js +42 -4
- package/dist/lib/core/modules/TelemetryHandler.js +4 -1
- package/dist/lib/core/redisConversationMemoryManager.d.ts +19 -3
- package/dist/lib/core/redisConversationMemoryManager.js +253 -58
- package/dist/lib/files/fileTools.d.ts +3 -3
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/mcp/externalServerManager.js +36 -1
- package/dist/lib/memory/memoryRetrievalTools.d.ts +166 -0
- package/dist/lib/memory/memoryRetrievalTools.js +145 -0
- package/dist/lib/neurolink.d.ts +35 -1
- package/dist/lib/neurolink.js +471 -16
- package/dist/lib/providers/amazonBedrock.d.ts +1 -1
- package/dist/lib/providers/amazonBedrock.js +78 -45
- package/dist/lib/providers/amazonSagemaker.d.ts +1 -1
- package/dist/lib/providers/amazonSagemaker.js +1 -1
- package/dist/lib/providers/anthropic.d.ts +1 -1
- package/dist/lib/providers/anthropic.js +7 -7
- package/dist/lib/providers/anthropicBaseProvider.d.ts +1 -1
- package/dist/lib/providers/anthropicBaseProvider.js +7 -6
- package/dist/lib/providers/azureOpenai.d.ts +1 -1
- package/dist/lib/providers/azureOpenai.js +1 -1
- package/dist/lib/providers/googleAiStudio.d.ts +1 -1
- package/dist/lib/providers/googleAiStudio.js +5 -5
- package/dist/lib/providers/googleVertex.d.ts +1 -1
- package/dist/lib/providers/googleVertex.js +74 -17
- package/dist/lib/providers/huggingFace.d.ts +1 -1
- package/dist/lib/providers/huggingFace.js +1 -1
- package/dist/lib/providers/litellm.d.ts +1 -1
- package/dist/lib/providers/litellm.js +18 -16
- package/dist/lib/providers/mistral.d.ts +1 -1
- package/dist/lib/providers/mistral.js +1 -1
- package/dist/lib/providers/ollama.d.ts +1 -1
- package/dist/lib/providers/ollama.js +8 -7
- package/dist/lib/providers/openAI.d.ts +1 -1
- package/dist/lib/providers/openAI.js +6 -6
- package/dist/lib/providers/openRouter.d.ts +1 -1
- package/dist/lib/providers/openRouter.js +6 -2
- package/dist/lib/providers/openaiCompatible.d.ts +1 -1
- package/dist/lib/providers/openaiCompatible.js +1 -1
- package/dist/lib/proxy/proxyFetch.js +291 -65
- package/dist/lib/server/utils/validation.d.ts +4 -4
- package/dist/lib/services/server/ai/observability/instrumentation.js +12 -3
- package/dist/lib/telemetry/telemetryService.d.ts +2 -1
- package/dist/lib/telemetry/telemetryService.js +8 -1
- package/dist/lib/types/contextTypes.d.ts +26 -2
- package/dist/lib/types/conversation.d.ts +72 -40
- package/dist/lib/types/conversationMemoryInterface.d.ts +5 -1
- package/dist/lib/types/generateTypes.d.ts +26 -0
- package/dist/lib/types/modelTypes.d.ts +2 -2
- package/dist/lib/types/multimodal.d.ts +2 -0
- package/dist/lib/types/observability.d.ts +10 -0
- package/dist/lib/types/sdkTypes.d.ts +1 -1
- package/dist/lib/utils/conversationMemory.d.ts +4 -3
- package/dist/lib/utils/conversationMemory.js +44 -6
- package/dist/lib/utils/errorHandling.d.ts +5 -0
- package/dist/lib/utils/errorHandling.js +7 -2
- package/dist/lib/utils/logger.d.ts +8 -0
- package/dist/lib/utils/logger.js +56 -1
- package/dist/lib/utils/messageBuilder.js +74 -4
- package/dist/lib/utils/redis.js +6 -1
- package/dist/lib/utils/tokenEstimation.d.ts +2 -2
- package/dist/lib/utils/tokenEstimation.js +16 -1
- package/dist/lib/utils/videoAnalysisProcessor.d.ts +2 -1
- package/dist/lib/utils/videoAnalysisProcessor.js +7 -2
- package/dist/lib/workflow/config.d.ts +110 -110
- package/dist/mcp/externalServerManager.js +36 -1
- package/dist/memory/memoryRetrievalTools.d.ts +166 -0
- package/dist/memory/memoryRetrievalTools.js +144 -0
- package/dist/neurolink.d.ts +35 -1
- package/dist/neurolink.js +471 -16
- package/dist/providers/amazonBedrock.d.ts +1 -1
- package/dist/providers/amazonBedrock.js +78 -45
- package/dist/providers/amazonSagemaker.d.ts +1 -1
- package/dist/providers/amazonSagemaker.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +7 -7
- package/dist/providers/anthropicBaseProvider.d.ts +1 -1
- package/dist/providers/anthropicBaseProvider.js +7 -6
- package/dist/providers/azureOpenai.d.ts +1 -1
- package/dist/providers/azureOpenai.js +1 -1
- package/dist/providers/googleAiStudio.d.ts +1 -1
- package/dist/providers/googleAiStudio.js +5 -5
- package/dist/providers/googleVertex.d.ts +1 -1
- package/dist/providers/googleVertex.js +74 -17
- package/dist/providers/huggingFace.d.ts +1 -1
- package/dist/providers/huggingFace.js +1 -1
- package/dist/providers/litellm.d.ts +1 -1
- package/dist/providers/litellm.js +18 -16
- package/dist/providers/mistral.d.ts +1 -1
- package/dist/providers/mistral.js +1 -1
- package/dist/providers/ollama.d.ts +1 -1
- package/dist/providers/ollama.js +8 -7
- package/dist/providers/openAI.d.ts +1 -1
- package/dist/providers/openAI.js +6 -6
- package/dist/providers/openRouter.d.ts +1 -1
- package/dist/providers/openRouter.js +6 -2
- package/dist/providers/openaiCompatible.d.ts +1 -1
- package/dist/providers/openaiCompatible.js +1 -1
- package/dist/proxy/proxyFetch.js +291 -65
- package/dist/services/server/ai/observability/instrumentation.js +12 -3
- package/dist/telemetry/telemetryService.d.ts +2 -1
- package/dist/telemetry/telemetryService.js +8 -1
- package/dist/types/contextTypes.d.ts +26 -2
- package/dist/types/conversation.d.ts +72 -40
- package/dist/types/conversationMemoryInterface.d.ts +5 -1
- package/dist/types/generateTypes.d.ts +26 -0
- package/dist/types/modelTypes.d.ts +10 -10
- package/dist/types/multimodal.d.ts +2 -0
- package/dist/types/observability.d.ts +10 -0
- package/dist/types/sdkTypes.d.ts +1 -1
- package/dist/utils/conversationMemory.d.ts +4 -3
- package/dist/utils/conversationMemory.js +44 -6
- package/dist/utils/errorHandling.d.ts +5 -0
- package/dist/utils/errorHandling.js +7 -2
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.js +56 -1
- package/dist/utils/messageBuilder.js +74 -4
- package/dist/utils/redis.js +6 -1
- package/dist/utils/tokenEstimation.d.ts +2 -2
- package/dist/utils/tokenEstimation.js +16 -1
- package/dist/utils/videoAnalysisProcessor.d.ts +2 -1
- package/dist/utils/videoAnalysisProcessor.js +7 -2
- package/dist/workflow/config.d.ts +12 -12
- package/package.json +1 -1
|
@@ -50,7 +50,7 @@ export declare class ConversationMemoryManager implements IConversationMemoryMan
|
|
|
50
50
|
* Returns messages from pointer onwards (or all if no pointer)
|
|
51
51
|
* Now consistently async to match Redis implementation
|
|
52
52
|
*/
|
|
53
|
-
buildContextMessages(sessionId: string): Promise<ChatMessage[]>;
|
|
53
|
+
buildContextMessages(sessionId: string, _userId?: string, _enableSummarization?: boolean, requestId?: string): Promise<ChatMessage[]>;
|
|
54
54
|
getSession(sessionId: string, _userId?: string): SessionMemory | undefined;
|
|
55
55
|
createSummarySystemMessage(content: string, summarizesFrom?: string, summarizesTo?: string): ChatMessage;
|
|
56
56
|
private ensureInitialized;
|
|
@@ -59,4 +59,16 @@ export declare class ConversationMemoryManager implements IConversationMemoryMan
|
|
|
59
59
|
getStats(): Promise<ConversationMemoryStats>;
|
|
60
60
|
clearSession(sessionId: string): Promise<boolean>;
|
|
61
61
|
clearAllSessions(): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Get the raw messages array for a session.
|
|
64
|
+
* Returns the full messages list without context filtering or summarization.
|
|
65
|
+
* Returns a deep copy to prevent external mutation of internal state.
|
|
66
|
+
*/
|
|
67
|
+
getSessionMessages(sessionId: string, _userId?: string): Promise<ChatMessage[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Replace the entire messages array for a session.
|
|
70
|
+
* Creates the session if it does not exist.
|
|
71
|
+
* Resets summary pointers since old pointers may reference messages that no longer exist.
|
|
72
|
+
*/
|
|
73
|
+
setSessionMessages(sessionId: string, messages: ChatMessage[], userId?: string): Promise<void>;
|
|
62
74
|
}
|
|
@@ -92,11 +92,12 @@ export class ConversationMemoryManager {
|
|
|
92
92
|
if (!this.summarizationInProgress.has(options.sessionId)) {
|
|
93
93
|
setImmediate(async () => {
|
|
94
94
|
try {
|
|
95
|
-
await this.checkAndSummarize(session, tokenThreshold);
|
|
95
|
+
await this.checkAndSummarize(session, tokenThreshold, options.requestId);
|
|
96
96
|
}
|
|
97
97
|
catch (error) {
|
|
98
98
|
logger.error("Background summarization failed", {
|
|
99
99
|
sessionId: session.sessionId,
|
|
100
|
+
requestId: options.requestId,
|
|
100
101
|
error: error instanceof Error ? error.message : String(error),
|
|
101
102
|
});
|
|
102
103
|
}
|
|
@@ -154,7 +155,7 @@ export class ConversationMemoryManager {
|
|
|
154
155
|
/**
|
|
155
156
|
* Check if summarization is needed based on token count
|
|
156
157
|
*/
|
|
157
|
-
async checkAndSummarize(session, threshold) {
|
|
158
|
+
async checkAndSummarize(session, threshold, requestId) {
|
|
158
159
|
// Acquire lock - if already in progress, skip
|
|
159
160
|
if (this.summarizationInProgress.has(session.sessionId)) {
|
|
160
161
|
logger.debug("[ConversationMemoryManager] Summarization already in progress, skipping", {
|
|
@@ -164,7 +165,7 @@ export class ConversationMemoryManager {
|
|
|
164
165
|
}
|
|
165
166
|
this.summarizationInProgress.add(session.sessionId);
|
|
166
167
|
try {
|
|
167
|
-
await this.summarizationEngine.checkAndSummarize(session, threshold, this.config, "[ConversationMemory]");
|
|
168
|
+
await this.summarizationEngine.checkAndSummarize(session, threshold, this.config, "[ConversationMemory]", requestId);
|
|
168
169
|
}
|
|
169
170
|
catch (error) {
|
|
170
171
|
logger.error("Token counting or summarization failed", {
|
|
@@ -195,9 +196,9 @@ export class ConversationMemoryManager {
|
|
|
195
196
|
* Returns messages from pointer onwards (or all if no pointer)
|
|
196
197
|
* Now consistently async to match Redis implementation
|
|
197
198
|
*/
|
|
198
|
-
async buildContextMessages(sessionId) {
|
|
199
|
+
async buildContextMessages(sessionId, _userId, _enableSummarization, requestId) {
|
|
199
200
|
const session = this.sessions.get(sessionId);
|
|
200
|
-
return session ? buildContextFromPointer(session) : [];
|
|
201
|
+
return session ? buildContextFromPointer(session, requestId) : [];
|
|
201
202
|
}
|
|
202
203
|
getSession(sessionId, _userId) {
|
|
203
204
|
return this.sessions.get(sessionId);
|
|
@@ -263,5 +264,35 @@ export class ConversationMemoryManager {
|
|
|
263
264
|
this.sessions.clear();
|
|
264
265
|
logger.info("All sessions cleared", { clearedCount: sessionIds.length });
|
|
265
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Get the raw messages array for a session.
|
|
269
|
+
* Returns the full messages list without context filtering or summarization.
|
|
270
|
+
* Returns a deep copy to prevent external mutation of internal state.
|
|
271
|
+
*/
|
|
272
|
+
async getSessionMessages(sessionId, _userId) {
|
|
273
|
+
await this.ensureInitialized();
|
|
274
|
+
const session = this.sessions.get(sessionId);
|
|
275
|
+
return session ? session.messages.map((msg) => ({ ...msg })) : [];
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Replace the entire messages array for a session.
|
|
279
|
+
* Creates the session if it does not exist.
|
|
280
|
+
* Resets summary pointers since old pointers may reference messages that no longer exist.
|
|
281
|
+
*/
|
|
282
|
+
async setSessionMessages(sessionId, messages, userId) {
|
|
283
|
+
await this.ensureInitialized();
|
|
284
|
+
let session = this.sessions.get(sessionId);
|
|
285
|
+
if (!session) {
|
|
286
|
+
session = this.createNewSession(sessionId, userId);
|
|
287
|
+
this.sessions.set(sessionId, session);
|
|
288
|
+
this.enforceSessionLimit();
|
|
289
|
+
}
|
|
290
|
+
session.messages = [...messages];
|
|
291
|
+
session.summarizedUpToMessageId = undefined;
|
|
292
|
+
session.summarizedMessage = undefined;
|
|
293
|
+
session.lastTokenCount = undefined;
|
|
294
|
+
session.lastCountedAt = undefined;
|
|
295
|
+
session.lastActivity = Date.now();
|
|
296
|
+
}
|
|
266
297
|
}
|
|
267
298
|
//# sourceMappingURL=conversationMemoryManager.js.map
|
|
@@ -38,6 +38,12 @@ export declare class GenerationHandler {
|
|
|
38
38
|
* Execute the generation with AI SDK
|
|
39
39
|
*/
|
|
40
40
|
executeGeneration(model: LanguageModelV1, messages: CoreMessage[], tools: Record<string, Tool>, options: TextGenerationOptions): Promise<Awaited<ReturnType<typeof generateText>>>;
|
|
41
|
+
/**
|
|
42
|
+
* Extract cache metrics from provider metadata (e.g. Anthropic's providerMetadata.anthropic)
|
|
43
|
+
* The Vercel AI SDK's LanguageModelUsage only has promptTokens/completionTokens/totalTokens.
|
|
44
|
+
* Cache metrics are surfaced via providerMetadata by provider-specific SDK adapters.
|
|
45
|
+
*/
|
|
46
|
+
private extractCacheMetricsFromProviderMetadata;
|
|
41
47
|
/**
|
|
42
48
|
* Log generation completion information
|
|
43
49
|
*/
|
|
@@ -14,8 +14,24 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { generateText, Output, NoObjectGeneratedError } from "ai";
|
|
16
16
|
import { logger } from "../../utils/logger.js";
|
|
17
|
-
import { extractTokenUsage } from "../../utils/tokenUtils.js";
|
|
17
|
+
import { extractTokenUsage, extractCacheCreationTokens, extractCacheReadTokens, calculateCacheSavingsPercent, } from "../../utils/tokenUtils.js";
|
|
18
18
|
import { DEFAULT_MAX_STEPS } from "../constants.js";
|
|
19
|
+
/**
|
|
20
|
+
* Safely preview-serialize a value for debug logging.
|
|
21
|
+
* Handles undefined, circular references, and non-serializable values.
|
|
22
|
+
*/
|
|
23
|
+
function safePreview(v) {
|
|
24
|
+
if (v === undefined) {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const text = typeof v === "string" ? v : JSON.stringify(v);
|
|
29
|
+
return (text ?? "").substring(0, 200);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return "[unserializable]";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
19
35
|
/**
|
|
20
36
|
* GenerationHandler class - Handles text generation operations for AI providers
|
|
21
37
|
*/
|
|
@@ -39,16 +55,41 @@ export class GenerationHandler {
|
|
|
39
55
|
async callGenerateText(model, messages, tools, options, shouldUseTools, includeStructuredOutput) {
|
|
40
56
|
// Check if this is a Google provider (for provider-specific options)
|
|
41
57
|
const isGoogleProvider = this.providerName === "google-ai" || this.providerName === "vertex";
|
|
42
|
-
// Check if this is an Anthropic provider
|
|
43
|
-
const isAnthropicProvider = this.providerName === "anthropic" ||
|
|
58
|
+
// Check if this is an Anthropic provider (includes Vertex+Claude)
|
|
59
|
+
const isAnthropicProvider = this.providerName === "anthropic" ||
|
|
60
|
+
this.providerName === "bedrock" ||
|
|
61
|
+
(this.providerName === "vertex" && this.modelName?.startsWith("claude-"));
|
|
44
62
|
const useStructuredOutput = includeStructuredOutput &&
|
|
45
63
|
!!options.schema &&
|
|
46
64
|
(options.output?.format === "json" ||
|
|
47
65
|
options.output?.format === "structured");
|
|
66
|
+
// Annotate the last tool with cache_control so the full tool-definition
|
|
67
|
+
// block becomes a cache breakpoint for Anthropic-family providers.
|
|
68
|
+
// Non-Anthropic providers harmlessly ignore unknown providerOptions.
|
|
69
|
+
// Note: The AI SDK Tool type doesn't yet include providerOptions, so we
|
|
70
|
+
// use a type assertion. The Anthropic adapter reads this at runtime.
|
|
71
|
+
const toolsWithCache = { ...tools };
|
|
72
|
+
if (isAnthropicProvider &&
|
|
73
|
+
shouldUseTools &&
|
|
74
|
+
Object.keys(toolsWithCache).length > 0) {
|
|
75
|
+
const toolNames = Object.keys(toolsWithCache);
|
|
76
|
+
const lastToolName = toolNames[toolNames.length - 1];
|
|
77
|
+
if (lastToolName && toolsWithCache[lastToolName]) {
|
|
78
|
+
const lastTool = toolsWithCache[lastToolName];
|
|
79
|
+
toolsWithCache[lastToolName] = {
|
|
80
|
+
...lastTool,
|
|
81
|
+
providerOptions: {
|
|
82
|
+
...(lastTool.providerOptions ?? {}),
|
|
83
|
+
anthropic: { cacheControl: { type: "ephemeral" } },
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
48
88
|
return await generateText({
|
|
49
89
|
model,
|
|
50
90
|
messages,
|
|
51
|
-
...(shouldUseTools &&
|
|
91
|
+
...(shouldUseTools &&
|
|
92
|
+
Object.keys(toolsWithCache).length > 0 && { tools: toolsWithCache }),
|
|
52
93
|
maxSteps: options.maxSteps ?? DEFAULT_MAX_STEPS,
|
|
53
94
|
...(shouldUseTools &&
|
|
54
95
|
options.toolChoice && { toolChoice: options.toolChoice }),
|
|
@@ -116,8 +157,90 @@ export class GenerationHandler {
|
|
|
116
157
|
const useStructuredOutput = !!options.schema &&
|
|
117
158
|
(options.output?.format === "json" ||
|
|
118
159
|
options.output?.format === "structured");
|
|
160
|
+
const requestId = options.requestId ||
|
|
161
|
+
options.context?.requestId ||
|
|
162
|
+
"unknown";
|
|
163
|
+
logger.info("[GenerationHandler] Calling generateText", {
|
|
164
|
+
requestId,
|
|
165
|
+
model: model.modelId || "unknown",
|
|
166
|
+
messageCount: messages.length,
|
|
167
|
+
toolCount: Object.keys(tools || {}).length,
|
|
168
|
+
maxSteps: options.maxSteps,
|
|
169
|
+
temperature: options.temperature,
|
|
170
|
+
});
|
|
171
|
+
if (logger.shouldLog("debug")) {
|
|
172
|
+
try {
|
|
173
|
+
logger.debug("[Observability] Full generateText parameters", {
|
|
174
|
+
requestId,
|
|
175
|
+
model: model.modelId || "unknown",
|
|
176
|
+
messageCount: messages.length,
|
|
177
|
+
messages: messages.map((msg, i) => ({
|
|
178
|
+
index: i,
|
|
179
|
+
role: msg.role,
|
|
180
|
+
contentLength: typeof msg.content === "string"
|
|
181
|
+
? msg.content.length
|
|
182
|
+
: safePreview(msg.content).length,
|
|
183
|
+
contentPreview: typeof msg.content === "string"
|
|
184
|
+
? msg.content.substring(0, 200)
|
|
185
|
+
: "[multimodal]",
|
|
186
|
+
})),
|
|
187
|
+
toolNames: Object.keys(tools || {}),
|
|
188
|
+
toolCount: Object.keys(tools || {}).length,
|
|
189
|
+
maxSteps: options.maxSteps,
|
|
190
|
+
temperature: options.temperature,
|
|
191
|
+
maxTokens: options.maxTokens,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// Ignore serialization errors in debug logging
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const genStartTime = Date.now();
|
|
119
199
|
try {
|
|
120
|
-
|
|
200
|
+
const result = await this.callGenerateText(model, messages, tools, options, shouldUseTools, true);
|
|
201
|
+
logger.info("[GenerationHandler] generateText returned", {
|
|
202
|
+
requestId,
|
|
203
|
+
durationMs: Date.now() - genStartTime,
|
|
204
|
+
finishReason: result.finishReason,
|
|
205
|
+
steps: result.steps?.length || 1,
|
|
206
|
+
toolCallsTotal: result.toolCalls?.length || 0,
|
|
207
|
+
responseChars: result.text?.length || 0,
|
|
208
|
+
});
|
|
209
|
+
if (logger.shouldLog("debug")) {
|
|
210
|
+
logger.debug("[Observability] Full LLM response", {
|
|
211
|
+
requestId,
|
|
212
|
+
finishReason: result.finishReason,
|
|
213
|
+
responseTextPreview: result.text?.substring(0, 200) || "",
|
|
214
|
+
responseTextLength: result.text?.length || 0,
|
|
215
|
+
toolCalls: result.toolCalls?.map((tc) => ({
|
|
216
|
+
toolName: tc.toolName,
|
|
217
|
+
argsPreview: safePreview(tc.args),
|
|
218
|
+
})),
|
|
219
|
+
toolResults: result.toolResults?.map((tr) => ({
|
|
220
|
+
toolName: tr.toolName,
|
|
221
|
+
resultPreview: safePreview(tr.result),
|
|
222
|
+
})),
|
|
223
|
+
steps: result.steps?.map((step, i) => ({
|
|
224
|
+
stepIndex: i,
|
|
225
|
+
stepType: step.stepType,
|
|
226
|
+
textPreview: step.text?.substring(0, 200),
|
|
227
|
+
textLength: step.text?.length || 0,
|
|
228
|
+
toolCalls: step.toolCalls?.map((tc) => ({
|
|
229
|
+
toolName: tc.toolName,
|
|
230
|
+
argsPreview: safePreview(tc.args),
|
|
231
|
+
})),
|
|
232
|
+
toolResults: step.toolResults?.map((tr) => ({
|
|
233
|
+
toolName: tr.toolName,
|
|
234
|
+
resultPreview: safePreview(tr.result),
|
|
235
|
+
})),
|
|
236
|
+
finishReason: step.finishReason,
|
|
237
|
+
})),
|
|
238
|
+
usage: result.usage,
|
|
239
|
+
providerMetadata: result.experimental_providerMetadata ||
|
|
240
|
+
result.providerMetadata,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
121
244
|
}
|
|
122
245
|
catch (error) {
|
|
123
246
|
// If NoObjectGeneratedError is thrown when using schema + tools together,
|
|
@@ -130,16 +253,50 @@ export class GenerationHandler {
|
|
|
130
253
|
});
|
|
131
254
|
// Retry without experimental_output - the formatEnhancedResult method
|
|
132
255
|
// will extract JSON from the text response
|
|
133
|
-
|
|
256
|
+
const result = await this.callGenerateText(model, messages, tools, options, shouldUseTools, false);
|
|
257
|
+
logger.info("[GenerationHandler] generateText returned (fallback)", {
|
|
258
|
+
requestId,
|
|
259
|
+
durationMs: Date.now() - genStartTime,
|
|
260
|
+
finishReason: result.finishReason,
|
|
261
|
+
steps: result.steps?.length || 1,
|
|
262
|
+
toolCallsTotal: result.toolCalls?.length || 0,
|
|
263
|
+
responseChars: result.text?.length || 0,
|
|
264
|
+
});
|
|
265
|
+
return result;
|
|
134
266
|
}
|
|
135
267
|
// Re-throw other errors
|
|
136
268
|
throw error;
|
|
137
269
|
}
|
|
138
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Extract cache metrics from provider metadata (e.g. Anthropic's providerMetadata.anthropic)
|
|
273
|
+
* The Vercel AI SDK's LanguageModelUsage only has promptTokens/completionTokens/totalTokens.
|
|
274
|
+
* Cache metrics are surfaced via providerMetadata by provider-specific SDK adapters.
|
|
275
|
+
*/
|
|
276
|
+
extractCacheMetricsFromProviderMetadata(generateResult) {
|
|
277
|
+
const providerMeta = generateResult
|
|
278
|
+
.providerMetadata ||
|
|
279
|
+
generateResult.experimental_providerMetadata;
|
|
280
|
+
if (!providerMeta) {
|
|
281
|
+
return {};
|
|
282
|
+
}
|
|
283
|
+
// Anthropic surfaces cache metrics under providerMetadata.anthropic
|
|
284
|
+
const anthropicMeta = providerMeta.anthropic;
|
|
285
|
+
if (anthropicMeta) {
|
|
286
|
+
const cacheCreationTokens = extractCacheCreationTokens(anthropicMeta);
|
|
287
|
+
const cacheReadTokens = extractCacheReadTokens(anthropicMeta);
|
|
288
|
+
return {
|
|
289
|
+
...(cacheCreationTokens !== undefined && { cacheCreationTokens }),
|
|
290
|
+
...(cacheReadTokens !== undefined && { cacheReadTokens }),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
return {};
|
|
294
|
+
}
|
|
139
295
|
/**
|
|
140
296
|
* Log generation completion information
|
|
141
297
|
*/
|
|
142
298
|
logGenerationComplete(generateResult) {
|
|
299
|
+
const cacheMetrics = this.extractCacheMetricsFromProviderMetadata(generateResult);
|
|
143
300
|
logger.debug(`generateText completed`, {
|
|
144
301
|
provider: this.providerName,
|
|
145
302
|
model: this.modelName,
|
|
@@ -147,6 +304,12 @@ export class GenerationHandler {
|
|
|
147
304
|
toolResultsCount: generateResult.toolResults?.length || 0,
|
|
148
305
|
finishReason: generateResult.finishReason,
|
|
149
306
|
usage: generateResult.usage,
|
|
307
|
+
...(cacheMetrics.cacheCreationTokens !== undefined && {
|
|
308
|
+
cacheCreationTokens: cacheMetrics.cacheCreationTokens,
|
|
309
|
+
}),
|
|
310
|
+
...(cacheMetrics.cacheReadTokens !== undefined && {
|
|
311
|
+
cacheReadTokens: cacheMetrics.cacheReadTokens,
|
|
312
|
+
}),
|
|
150
313
|
timestamp: Date.now(),
|
|
151
314
|
});
|
|
152
315
|
}
|
|
@@ -274,6 +437,28 @@ export class GenerationHandler {
|
|
|
274
437
|
// Note: The AI SDK bundles thinking tokens into promptTokens for Google models.
|
|
275
438
|
// Separate reasoningTokens tracking will work when/if the AI SDK adds support.
|
|
276
439
|
const usage = extractTokenUsage(generateResult.usage);
|
|
440
|
+
// Merge cache metrics from providerMetadata if not already present in usage
|
|
441
|
+
// The AI SDK's LanguageModelUsage doesn't include cache tokens; they come from
|
|
442
|
+
// provider-specific metadata (e.g. Anthropic's providerMetadata.anthropic)
|
|
443
|
+
if (usage.cacheCreationTokens === undefined ||
|
|
444
|
+
usage.cacheReadTokens === undefined) {
|
|
445
|
+
const cacheMetrics = this.extractCacheMetricsFromProviderMetadata(generateResult);
|
|
446
|
+
if (usage.cacheCreationTokens === undefined &&
|
|
447
|
+
cacheMetrics.cacheCreationTokens !== undefined) {
|
|
448
|
+
usage.cacheCreationTokens = cacheMetrics.cacheCreationTokens;
|
|
449
|
+
}
|
|
450
|
+
if (usage.cacheReadTokens === undefined &&
|
|
451
|
+
cacheMetrics.cacheReadTokens !== undefined) {
|
|
452
|
+
usage.cacheReadTokens = cacheMetrics.cacheReadTokens;
|
|
453
|
+
}
|
|
454
|
+
// Recalculate cache savings if we added cache metrics
|
|
455
|
+
if (usage.cacheReadTokens !== undefined) {
|
|
456
|
+
const savingsPercent = calculateCacheSavingsPercent(usage.cacheReadTokens, usage.input);
|
|
457
|
+
if (savingsPercent !== undefined) {
|
|
458
|
+
usage.cacheSavingsPercent = savingsPercent;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
277
462
|
return {
|
|
278
463
|
content,
|
|
279
464
|
usage,
|
|
@@ -309,7 +494,7 @@ export class GenerationHandler {
|
|
|
309
494
|
provider: this.providerName,
|
|
310
495
|
model: this.modelName,
|
|
311
496
|
responseTextLength: result.text?.length || 0,
|
|
312
|
-
responsePreview: result.text?.substring(0, 500)
|
|
497
|
+
responsePreview: result.text?.substring(0, 500) ?? "",
|
|
313
498
|
finishReason: result.finishReason,
|
|
314
499
|
usage: result.usage,
|
|
315
500
|
});
|
|
@@ -76,25 +76,44 @@ export class MessageBuilder {
|
|
|
76
76
|
messages = await buildMessagesArray(options);
|
|
77
77
|
}
|
|
78
78
|
// Convert messages to Vercel AI SDK format
|
|
79
|
+
// Preserve providerOptions (e.g. Anthropic cache_control) through conversion
|
|
79
80
|
return messages.map((msg) => {
|
|
81
|
+
const providerOptions = msg
|
|
82
|
+
.providerOptions;
|
|
80
83
|
if (typeof msg.content === "string") {
|
|
81
84
|
return {
|
|
82
85
|
role: msg.role,
|
|
83
86
|
content: msg.content,
|
|
87
|
+
...(providerOptions && { providerOptions }),
|
|
84
88
|
};
|
|
85
89
|
}
|
|
86
90
|
else {
|
|
87
91
|
return {
|
|
88
92
|
role: msg.role,
|
|
89
93
|
content: msg.content.map((item) => {
|
|
94
|
+
const itemProviderOptions = item
|
|
95
|
+
.providerOptions;
|
|
90
96
|
if (item.type === "text") {
|
|
91
|
-
return {
|
|
97
|
+
return {
|
|
98
|
+
type: "text",
|
|
99
|
+
text: item.text || "",
|
|
100
|
+
...(itemProviderOptions && {
|
|
101
|
+
providerOptions: itemProviderOptions,
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
92
104
|
}
|
|
93
105
|
else if (item.type === "image") {
|
|
94
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
type: "image",
|
|
108
|
+
image: item.image || "",
|
|
109
|
+
...(itemProviderOptions && {
|
|
110
|
+
providerOptions: itemProviderOptions,
|
|
111
|
+
}),
|
|
112
|
+
};
|
|
95
113
|
}
|
|
96
114
|
return item;
|
|
97
115
|
}),
|
|
116
|
+
...(providerOptions && { providerOptions }),
|
|
98
117
|
};
|
|
99
118
|
}
|
|
100
119
|
});
|
|
@@ -159,25 +178,44 @@ export class MessageBuilder {
|
|
|
159
178
|
messages = await buildMessagesArray(options);
|
|
160
179
|
}
|
|
161
180
|
// Convert messages to Vercel AI SDK format
|
|
181
|
+
// Preserve providerOptions (e.g. Anthropic cache_control) through conversion
|
|
162
182
|
return messages.map((msg) => {
|
|
183
|
+
const providerOptions = msg
|
|
184
|
+
.providerOptions;
|
|
163
185
|
if (typeof msg.content === "string") {
|
|
164
186
|
return {
|
|
165
187
|
role: msg.role,
|
|
166
188
|
content: msg.content,
|
|
189
|
+
...(providerOptions && { providerOptions }),
|
|
167
190
|
};
|
|
168
191
|
}
|
|
169
192
|
else {
|
|
170
193
|
return {
|
|
171
194
|
role: msg.role,
|
|
172
195
|
content: msg.content.map((item) => {
|
|
196
|
+
const itemProviderOptions = item
|
|
197
|
+
.providerOptions;
|
|
173
198
|
if (item.type === "text") {
|
|
174
|
-
return {
|
|
199
|
+
return {
|
|
200
|
+
type: "text",
|
|
201
|
+
text: item.text || "",
|
|
202
|
+
...(itemProviderOptions && {
|
|
203
|
+
providerOptions: itemProviderOptions,
|
|
204
|
+
}),
|
|
205
|
+
};
|
|
175
206
|
}
|
|
176
207
|
else if (item.type === "image") {
|
|
177
|
-
return {
|
|
208
|
+
return {
|
|
209
|
+
type: "image",
|
|
210
|
+
image: item.image || "",
|
|
211
|
+
...(itemProviderOptions && {
|
|
212
|
+
providerOptions: itemProviderOptions,
|
|
213
|
+
}),
|
|
214
|
+
};
|
|
178
215
|
}
|
|
179
216
|
return item;
|
|
180
217
|
}),
|
|
218
|
+
...(providerOptions && { providerOptions }),
|
|
181
219
|
};
|
|
182
220
|
}
|
|
183
221
|
});
|
|
@@ -17,6 +17,7 @@ import { nanoid } from "nanoid";
|
|
|
17
17
|
import { logger } from "../../utils/logger.js";
|
|
18
18
|
import { getPerformanceOptimizedProvider, recordProviderPerformanceFromMetrics, } from "../evaluationProviders.js";
|
|
19
19
|
import { modelConfig } from "../modelConfiguration.js";
|
|
20
|
+
import { TelemetryService } from "../../telemetry/telemetryService.js";
|
|
20
21
|
/**
|
|
21
22
|
* TelemetryHandler class - Handles analytics and telemetry for AI providers
|
|
22
23
|
*/
|
|
@@ -79,6 +80,8 @@ export class TelemetryHandler {
|
|
|
79
80
|
cost: actualCost,
|
|
80
81
|
success: true,
|
|
81
82
|
});
|
|
83
|
+
// Wire TelemetryService metrics so OTEL counters/histograms are populated
|
|
84
|
+
TelemetryService.getInstance().recordAIRequest(this.providerName, this.modelName, usage?.totalTokens || 0, responseTime, actualCost > 0 ? actualCost : undefined);
|
|
82
85
|
const optimizedProvider = getPerformanceOptimizedProvider("speed");
|
|
83
86
|
logger.debug(`🚀 Performance recorded for ${this.providerName}:`, {
|
|
84
87
|
responseTime: `${responseTime}ms`,
|
|
@@ -147,7 +150,7 @@ export class TelemetryHandler {
|
|
|
147
150
|
isEnabled: true,
|
|
148
151
|
functionId,
|
|
149
152
|
metadata,
|
|
150
|
-
recordInputs:
|
|
153
|
+
recordInputs: process.env.NEUROLINK_RECORD_INPUTS?.toLowerCase() === "true",
|
|
151
154
|
recordOutputs: true,
|
|
152
155
|
};
|
|
153
156
|
}
|
|
@@ -48,7 +48,13 @@ export declare class RedisConversationMemoryManager implements IConversationMemo
|
|
|
48
48
|
/**
|
|
49
49
|
* Get session by ID, reconstructing a SessionMemory from Redis storage.
|
|
50
50
|
*/
|
|
51
|
-
getSession(sessionId: string, userId?: string): Promise<SessionMemory | undefined>;
|
|
51
|
+
getSession(sessionId: string, userId?: string, requestId?: string): Promise<SessionMemory | undefined>;
|
|
52
|
+
/**
|
|
53
|
+
* Get raw session data without any filtering or transformation.
|
|
54
|
+
* Used by the memory retrieval tool and internal APIs that need
|
|
55
|
+
* access to full message data including unmodified tool outputs.
|
|
56
|
+
*/
|
|
57
|
+
getSessionRaw(sessionId: string, userId?: string): Promise<RedisConversationObject | null>;
|
|
52
58
|
/**
|
|
53
59
|
* Get all sessions for a specific user
|
|
54
60
|
*/
|
|
@@ -90,9 +96,9 @@ export declare class RedisConversationMemoryManager implements IConversationMemo
|
|
|
90
96
|
/**
|
|
91
97
|
* Build context messages for AI prompt injection (TOKEN-BASED)
|
|
92
98
|
* Returns messages from pointer onwards (or all if no pointer)
|
|
93
|
-
*
|
|
99
|
+
* Applies sendToolPreview toggle and hydrates result.result for backward compat
|
|
94
100
|
*/
|
|
95
|
-
buildContextMessages(sessionId: string, userId?: string, enableSummarization?: boolean): Promise<ChatMessage[]>;
|
|
101
|
+
buildContextMessages(sessionId: string, userId?: string, enableSummarization?: boolean, requestId?: string): Promise<ChatMessage[]>;
|
|
96
102
|
/**
|
|
97
103
|
* Get session metadata for a specific user session (optimized for listing)
|
|
98
104
|
* Fetches only essential metadata without heavy message arrays
|
|
@@ -131,6 +137,16 @@ export declare class RedisConversationMemoryManager implements IConversationMemo
|
|
|
131
137
|
* Create summary system message
|
|
132
138
|
*/
|
|
133
139
|
createSummarySystemMessage(content: string, summarizesFrom?: string, summarizesTo?: string): ChatMessage;
|
|
140
|
+
/**
|
|
141
|
+
* Get the raw messages array for a session.
|
|
142
|
+
* Returns the full messages list without context filtering or summarization.
|
|
143
|
+
*/
|
|
144
|
+
getSessionMessages(sessionId: string, userId?: string): Promise<ChatMessage[]>;
|
|
145
|
+
/**
|
|
146
|
+
* Replace the entire messages array for a session.
|
|
147
|
+
* The session must already exist in Redis.
|
|
148
|
+
*/
|
|
149
|
+
setSessionMessages(sessionId: string, messages: ChatMessage[], userId?: string): Promise<void>;
|
|
134
150
|
/**
|
|
135
151
|
* Close Redis connection
|
|
136
152
|
*/
|