@juspay/neurolink 12.0.3 → 12.0.5
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 +2 -2
- package/dist/browser/neurolink.min.js +308 -304
- package/dist/neurolink.js +118 -24
- package/dist/rag/ragIntegration.js +42 -4
- package/package.json +1 -1
package/dist/neurolink.js
CHANGED
|
@@ -5548,7 +5548,15 @@ Current user's request: ${currentInput}`;
|
|
|
5548
5548
|
: "empty",
|
|
5549
5549
|
hasCustomSystemPrompt: !!options.systemPrompt,
|
|
5550
5550
|
});
|
|
5551
|
-
|
|
5551
|
+
// Caller-supplied conversationMessages win — mirroring
|
|
5552
|
+
// directProviderGeneration. getConversationMessages() returns [] whenever
|
|
5553
|
+
// no memory manager / session context is configured, which silently
|
|
5554
|
+
// dropped an inline history on the MCP-first path (the default path for
|
|
5555
|
+
// any bare `new NeuroLink()` with tools enabled) while the direct path
|
|
5556
|
+
// honored it.
|
|
5557
|
+
const conversationMessages = (options.conversationMessages?.length
|
|
5558
|
+
? options.conversationMessages
|
|
5559
|
+
: await getConversationMessages(this.conversationMemory, options));
|
|
5552
5560
|
this.logMCPConversationSummary(requestId, conversationMessages);
|
|
5553
5561
|
logger.debug("[Observability] Available tools for LLM", {
|
|
5554
5562
|
requestId,
|
|
@@ -6062,32 +6070,118 @@ Current user's request: ${currentInput}`;
|
|
|
6062
6070
|
// oversized case. When the budget check shows the request is
|
|
6063
6071
|
// over budget but there's nothing to compact (no memory + no
|
|
6064
6072
|
// inline messages — e.g. a huge prompt or huge tool definitions
|
|
6065
|
-
// alone),
|
|
6073
|
+
// alone), recover by WINDOWING the prompt when the prompt is what
|
|
6074
|
+
// blew the budget: keep its head and tail around an elision marker
|
|
6075
|
+
// and dispatch. Agentic callers (Yama's session loop) carry their
|
|
6076
|
+
// whole tool transcript in the prompt; the previous
|
|
6077
|
+
// unconditional throw dead-ended every such turn — observed live
|
|
6078
|
+
// as an unbounded retry loop (93K→299K tokens, no verdict, ever).
|
|
6079
|
+
// The throw remains for the truly unrecoverable case: system
|
|
6080
|
+
// prompt + tool definitions alone exceed the budget.
|
|
6081
|
+
let promptWindowRecovered = false;
|
|
6066
6082
|
if (!budgetCheck.withinBudget && !dpgHasCompactableMessages) {
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6083
|
+
const fixedOverhead = (budgetCheck.breakdown?.systemPrompt ?? 0) +
|
|
6084
|
+
(budgetCheck.breakdown?.toolDefinitions ?? 0) +
|
|
6085
|
+
(budgetCheck.breakdown?.fileAttachments ?? 0);
|
|
6086
|
+
// 3% margin against estimator drift; 1024-token floor — below
|
|
6087
|
+
// that, a windowed prompt carries too little to answer from.
|
|
6088
|
+
const promptBudget = Math.floor((budgetCheck.availableInputTokens - fixedOverhead) * 0.97);
|
|
6089
|
+
const promptText = typeof options.prompt === "string" ? options.prompt : undefined;
|
|
6090
|
+
if (promptText &&
|
|
6091
|
+
promptBudget >= 1024 &&
|
|
6092
|
+
(budgetCheck.breakdown?.currentPrompt ?? 0) > promptBudget) {
|
|
6093
|
+
const marker = "\n\n[... middle of this prompt elided by NeuroLink to fit the model's context window ...]\n\n";
|
|
6094
|
+
// Proportional char budget from the observed chars-per-token of
|
|
6095
|
+
// THIS text, re-checked and shrunk until the estimator agrees.
|
|
6096
|
+
let charBudget = Math.floor(promptText.length *
|
|
6097
|
+
(promptBudget /
|
|
6098
|
+
Math.max(budgetCheck.breakdown?.currentPrompt ?? 1, 1)));
|
|
6099
|
+
let windowed = promptText;
|
|
6100
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
6101
|
+
const headChars = Math.floor(charBudget * 0.6);
|
|
6102
|
+
const tailChars = Math.max(charBudget - headChars - marker.length, 0);
|
|
6103
|
+
windowed =
|
|
6104
|
+
promptText.slice(0, headChars) +
|
|
6105
|
+
marker +
|
|
6106
|
+
(tailChars > 0 ? promptText.slice(-tailChars) : "");
|
|
6107
|
+
const recheck = checkContextBudget({
|
|
6108
|
+
provider: providerName,
|
|
6109
|
+
model: options.model,
|
|
6110
|
+
maxTokens: options.maxTokens,
|
|
6111
|
+
systemPrompt: options.systemPrompt,
|
|
6112
|
+
conversationMessages: [],
|
|
6113
|
+
currentPrompt: windowed,
|
|
6114
|
+
toolDefinitions: options.tools
|
|
6115
|
+
? Object.values(options.tools)
|
|
6116
|
+
: undefined,
|
|
6117
|
+
});
|
|
6118
|
+
if (recheck.withinBudget) {
|
|
6119
|
+
break;
|
|
6120
|
+
}
|
|
6121
|
+
charBudget = Math.floor(charBudget * 0.8);
|
|
6122
|
+
if (attempt === 3) {
|
|
6123
|
+
windowed = promptText; // give up — fall through to the throw
|
|
6124
|
+
}
|
|
6125
|
+
}
|
|
6126
|
+
if (windowed !== promptText) {
|
|
6127
|
+
logger.warn("[NeuroLink] Prompt exceeded the model's context budget with nothing to compact — " +
|
|
6128
|
+
"windowed the prompt (head+tail kept, middle elided) to fit.", {
|
|
6129
|
+
provider: providerName,
|
|
6130
|
+
model: options.model,
|
|
6131
|
+
estimatedTokens: budgetCheck.estimatedInputTokens,
|
|
6132
|
+
budget: budgetCheck.availableInputTokens,
|
|
6133
|
+
originalPromptChars: promptText.length,
|
|
6134
|
+
windowedPromptChars: windowed.length,
|
|
6135
|
+
});
|
|
6136
|
+
try {
|
|
6137
|
+
this.emitter.emit("compaction.applied", {
|
|
6138
|
+
stagesAttempted: ["pre-dispatch prompt window"],
|
|
6139
|
+
finalTokens: budgetCheck.availableInputTokens,
|
|
6140
|
+
budget: budgetCheck.availableInputTokens,
|
|
6141
|
+
provider: providerName,
|
|
6142
|
+
model: options.model,
|
|
6143
|
+
phase: "pre-dispatch-prompt-window",
|
|
6144
|
+
timestamp: Date.now(),
|
|
6145
|
+
});
|
|
6146
|
+
}
|
|
6147
|
+
catch {
|
|
6148
|
+
/* listener errors are non-fatal */
|
|
6149
|
+
}
|
|
6150
|
+
options.prompt = windowed;
|
|
6151
|
+
const inputHolder = options
|
|
6152
|
+
.input;
|
|
6153
|
+
if (inputHolder && typeof inputHolder.text === "string") {
|
|
6154
|
+
inputHolder.text = windowed;
|
|
6155
|
+
}
|
|
6156
|
+
promptWindowRecovered = true;
|
|
6157
|
+
}
|
|
6077
6158
|
}
|
|
6078
|
-
|
|
6079
|
-
|
|
6159
|
+
if (!promptWindowRecovered) {
|
|
6160
|
+
try {
|
|
6161
|
+
this.emitter.emit("compaction.insufficient", {
|
|
6162
|
+
stagesAttempted: ["pre-dispatch hard cap"],
|
|
6163
|
+
finalTokens: budgetCheck.estimatedInputTokens,
|
|
6164
|
+
budget: budgetCheck.availableInputTokens,
|
|
6165
|
+
provider: providerName,
|
|
6166
|
+
model: options.model,
|
|
6167
|
+
phase: "pre-dispatch-no-recovery",
|
|
6168
|
+
timestamp: Date.now(),
|
|
6169
|
+
});
|
|
6170
|
+
}
|
|
6171
|
+
catch {
|
|
6172
|
+
/* listener errors are non-fatal */
|
|
6173
|
+
}
|
|
6174
|
+
throw new ContextBudgetExceededError(`Context exceeds model budget and no compaction is possible ` +
|
|
6175
|
+
`(no conversationMemory, no inline conversationMessages — only ` +
|
|
6176
|
+
`prompt + tools). Estimated: ${budgetCheck.estimatedInputTokens} ` +
|
|
6177
|
+
`tokens, budget: ${budgetCheck.availableInputTokens} tokens. ` +
|
|
6178
|
+
`Reduce prompt or tool-definition size, or trim the request.`, {
|
|
6179
|
+
estimatedTokens: budgetCheck.estimatedInputTokens,
|
|
6180
|
+
availableTokens: budgetCheck.availableInputTokens,
|
|
6181
|
+
stagesUsed: [],
|
|
6182
|
+
breakdown: budgetCheck.breakdown,
|
|
6183
|
+
});
|
|
6080
6184
|
}
|
|
6081
|
-
throw new ContextBudgetExceededError(`Context exceeds model budget and no compaction is possible ` +
|
|
6082
|
-
`(no conversationMemory, no inline conversationMessages — only ` +
|
|
6083
|
-
`prompt + tools). Estimated: ${budgetCheck.estimatedInputTokens} ` +
|
|
6084
|
-
`tokens, budget: ${budgetCheck.availableInputTokens} tokens. ` +
|
|
6085
|
-
`Reduce prompt or tool-definition size, or trim the request.`, {
|
|
6086
|
-
estimatedTokens: budgetCheck.estimatedInputTokens,
|
|
6087
|
-
availableTokens: budgetCheck.availableInputTokens,
|
|
6088
|
-
stagesUsed: [],
|
|
6089
|
-
breakdown: budgetCheck.breakdown,
|
|
6090
|
-
});
|
|
6091
6185
|
}
|
|
6092
6186
|
if (budgetCheck.shouldCompact &&
|
|
6093
6187
|
(this.conversationMemory || dpgHasInlineMessages) &&
|
|
@@ -235,9 +235,47 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
|
|
|
235
235
|
const EMBEDDING_DIMENSION = 128;
|
|
236
236
|
const vectorStore = new InMemoryVectorStore();
|
|
237
237
|
const indexName = "rag-index";
|
|
238
|
+
// When the caller configured an embedding provider/model, embed BOTH the
|
|
239
|
+
// index chunks and (below) the queries through that provider — previously
|
|
240
|
+
// those config fields had no runtime effect and retrieval always used the
|
|
241
|
+
// deterministic hash embedding, which is a lexical fingerprint rather than
|
|
242
|
+
// a semantic space. Index and query must share one embedding space, so the
|
|
243
|
+
// provider path replaces the hash path wholesale; any provider failure
|
|
244
|
+
// falls back to the hash for both sides.
|
|
245
|
+
const wantProviderEmbeddings = Boolean(embeddingProvider || embeddingModel);
|
|
246
|
+
const embedProviderName = embeddingProvider || fallbackProvider || "vertex";
|
|
247
|
+
const embedModelName = embeddingModel || "gemini-2.5-flash";
|
|
248
|
+
let embedFn = (text) => Promise.resolve(generateSimpleEmbedding(text, EMBEDDING_DIMENSION));
|
|
249
|
+
if (wantProviderEmbeddings) {
|
|
250
|
+
try {
|
|
251
|
+
const { AIProviderFactory } = await import("../core/factory.js");
|
|
252
|
+
const embedderProvider = (await AIProviderFactory.createProvider(embedProviderName, embedModelName));
|
|
253
|
+
if (typeof embedderProvider.embed === "function") {
|
|
254
|
+
const providerEmbed = embedderProvider.embed.bind(embedderProvider);
|
|
255
|
+
embedFn = (text) => providerEmbed(text, embedModelName);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
logger.warn(`[RAG] Embedding provider '${embedProviderName}' has no embed(); falling back to hash embeddings`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
logger.warn("[RAG] Failed to create embedding provider; falling back to hash embeddings", { error: error instanceof Error ? error.message : String(error) });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
let chunkVectors;
|
|
266
|
+
try {
|
|
267
|
+
chunkVectors = await Promise.all(allChunks.map((chunk) => embedFn(chunk.text)));
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
// One failed chunk must not leave a mixed-space index — flip the whole
|
|
271
|
+
// index AND all queries back to the hash space together.
|
|
272
|
+
logger.warn("[RAG] Provider embedding failed mid-index; falling back to hash embeddings for index and queries", { error: error instanceof Error ? error.message : String(error) });
|
|
273
|
+
embedFn = (text) => Promise.resolve(generateSimpleEmbedding(text, EMBEDDING_DIMENSION));
|
|
274
|
+
chunkVectors = allChunks.map((chunk) => generateSimpleEmbedding(chunk.text, EMBEDDING_DIMENSION));
|
|
275
|
+
}
|
|
238
276
|
const items = allChunks.map((chunk, i) => ({
|
|
239
277
|
id: `rag-chunk-${i}`,
|
|
240
|
-
vector:
|
|
278
|
+
vector: chunkVectors[i],
|
|
241
279
|
metadata: {
|
|
242
280
|
text: chunk.text,
|
|
243
281
|
...chunk.metadata,
|
|
@@ -274,9 +312,9 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
|
|
|
274
312
|
"rag.top_k": topK ?? 5,
|
|
275
313
|
},
|
|
276
314
|
}, async (span) => {
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
const queryEmbedding = generateSimpleEmbedding(query, EMBEDDING_DIMENSION);
|
|
315
|
+
// Query through the same embedding space the index was built in —
|
|
316
|
+
// provider embeddings when configured (and healthy), else the hash.
|
|
317
|
+
const queryEmbedding = await embedFn(query).catch(() => generateSimpleEmbedding(query, EMBEDDING_DIMENSION));
|
|
280
318
|
// Fetch more candidates than needed so diversity can select across files
|
|
281
319
|
const fetchK = fileContents.length > 1 ? topK * 3 : topK;
|
|
282
320
|
const rawResults = await vectorStore.query({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.0.
|
|
3
|
+
"version": "12.0.5",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|