@juspay/neurolink 10.3.0 → 10.4.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/browser/neurolink.min.js +430 -417
- package/dist/cli/loop/optionsSchema.js +4 -0
- package/dist/constants/contextWindows.d.ts +24 -0
- package/dist/constants/contextWindows.js +50 -0
- package/dist/context/errorDetection.d.ts +6 -0
- package/dist/context/errorDetection.js +18 -0
- package/dist/context/stepBudgetGuard.d.ts +17 -3
- package/dist/context/stepBudgetGuard.js +53 -14
- package/dist/core/baseProvider.d.ts +12 -0
- package/dist/core/baseProvider.js +18 -0
- package/dist/core/modules/GenerationHandler.js +208 -42
- package/dist/core/modules/ToolsManager.d.ts +17 -0
- package/dist/core/modules/ToolsManager.js +99 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/knowledge/context.d.ts +18 -0
- package/dist/knowledge/context.js +91 -0
- package/dist/knowledge/defaults.d.ts +24 -0
- package/dist/knowledge/defaults.js +29 -0
- package/dist/knowledge/engine.d.ts +35 -0
- package/dist/knowledge/engine.js +179 -0
- package/dist/knowledge/index.d.ts +15 -0
- package/dist/knowledge/index.js +15 -0
- package/dist/knowledge/indexCache.d.ts +19 -0
- package/dist/knowledge/indexCache.js +109 -0
- package/dist/knowledge/knowledgeIndex.d.ts +41 -0
- package/dist/knowledge/knowledgeIndex.js +204 -0
- package/dist/knowledge/normalize.d.ts +32 -0
- package/dist/knowledge/normalize.js +74 -0
- package/dist/knowledge/resolve.d.ts +18 -0
- package/dist/knowledge/resolve.js +156 -0
- package/dist/knowledge/retrieval.d.ts +16 -0
- package/dist/knowledge/retrieval.js +221 -0
- package/dist/lib/constants/contextWindows.d.ts +24 -0
- package/dist/lib/constants/contextWindows.js +50 -0
- package/dist/lib/context/errorDetection.d.ts +6 -0
- package/dist/lib/context/errorDetection.js +18 -0
- package/dist/lib/context/stepBudgetGuard.d.ts +17 -3
- package/dist/lib/context/stepBudgetGuard.js +53 -14
- package/dist/lib/core/baseProvider.d.ts +12 -0
- package/dist/lib/core/baseProvider.js +18 -0
- package/dist/lib/core/modules/GenerationHandler.js +208 -42
- package/dist/lib/core/modules/ToolsManager.d.ts +17 -0
- package/dist/lib/core/modules/ToolsManager.js +99 -1
- package/dist/lib/files/fileTools.d.ts +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/knowledge/context.d.ts +18 -0
- package/dist/lib/knowledge/context.js +92 -0
- package/dist/lib/knowledge/defaults.d.ts +24 -0
- package/dist/lib/knowledge/defaults.js +30 -0
- package/dist/lib/knowledge/engine.d.ts +35 -0
- package/dist/lib/knowledge/engine.js +180 -0
- package/dist/lib/knowledge/index.d.ts +15 -0
- package/dist/lib/knowledge/index.js +16 -0
- package/dist/lib/knowledge/indexCache.d.ts +19 -0
- package/dist/lib/knowledge/indexCache.js +110 -0
- package/dist/lib/knowledge/knowledgeIndex.d.ts +41 -0
- package/dist/lib/knowledge/knowledgeIndex.js +205 -0
- package/dist/lib/knowledge/normalize.d.ts +32 -0
- package/dist/lib/knowledge/normalize.js +75 -0
- package/dist/lib/knowledge/resolve.d.ts +18 -0
- package/dist/lib/knowledge/resolve.js +157 -0
- package/dist/lib/knowledge/retrieval.d.ts +16 -0
- package/dist/lib/knowledge/retrieval.js +222 -0
- package/dist/lib/mcp/toolDiscoveryService.js +59 -20
- package/dist/lib/neurolink.d.ts +14 -1
- package/dist/lib/neurolink.js +142 -12
- package/dist/lib/providers/litellm.d.ts +27 -19
- package/dist/lib/providers/litellm.js +171 -92
- package/dist/lib/providers/openaiChatCompletionsBase.d.ts +37 -1
- package/dist/lib/providers/openaiChatCompletionsBase.js +201 -33
- package/dist/lib/providers/openaiChatCompletionsClient.d.ts +23 -5
- package/dist/lib/providers/openaiChatCompletionsClient.js +94 -14
- package/dist/lib/proxy/proxyFetch.d.ts +17 -0
- package/dist/lib/proxy/proxyFetch.js +42 -4
- package/dist/lib/types/config.d.ts +12 -0
- package/dist/lib/types/conversation.d.ts +1 -1
- package/dist/lib/types/dynamic.d.ts +12 -0
- package/dist/lib/types/generate.d.ts +32 -2
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/types/knowledge.d.ts +342 -0
- package/dist/lib/types/knowledge.js +30 -0
- package/dist/lib/types/openaiCompatible.d.ts +2 -0
- package/dist/lib/types/providers.d.ts +9 -0
- package/dist/lib/types/stream.d.ts +14 -0
- package/dist/lib/utils/errorHandling.js +8 -2
- package/dist/lib/utils/schemaConversion.d.ts +16 -0
- package/dist/lib/utils/schemaConversion.js +165 -8
- package/dist/lib/utils/timeout.d.ts +22 -0
- package/dist/lib/utils/timeout.js +72 -12
- package/dist/lib/utils/tokenLimits.js +22 -0
- package/dist/lib/utils/toolCallRepair.d.ts +8 -0
- package/dist/lib/utils/toolCallRepair.js +4 -1
- package/dist/mcp/toolDiscoveryService.js +59 -20
- package/dist/neurolink.d.ts +14 -1
- package/dist/neurolink.js +142 -12
- package/dist/providers/litellm.d.ts +27 -19
- package/dist/providers/litellm.js +171 -92
- package/dist/providers/openaiChatCompletionsBase.d.ts +37 -1
- package/dist/providers/openaiChatCompletionsBase.js +201 -33
- package/dist/providers/openaiChatCompletionsClient.d.ts +23 -5
- package/dist/providers/openaiChatCompletionsClient.js +94 -14
- package/dist/proxy/proxyFetch.d.ts +17 -0
- package/dist/proxy/proxyFetch.js +42 -4
- package/dist/types/config.d.ts +12 -0
- package/dist/types/conversation.d.ts +1 -1
- package/dist/types/dynamic.d.ts +12 -0
- package/dist/types/generate.d.ts +32 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/knowledge.d.ts +342 -0
- package/dist/types/knowledge.js +29 -0
- package/dist/types/openaiCompatible.d.ts +2 -0
- package/dist/types/providers.d.ts +9 -0
- package/dist/types/stream.d.ts +14 -0
- package/dist/utils/errorHandling.js +8 -2
- package/dist/utils/schemaConversion.d.ts +16 -0
- package/dist/utils/schemaConversion.js +165 -8
- package/dist/utils/timeout.d.ts +22 -0
- package/dist/utils/timeout.js +72 -12
- package/dist/utils/tokenLimits.js +22 -0
- package/dist/utils/toolCallRepair.d.ts +8 -0
- package/dist/utils/toolCallRepair.js +4 -1
- package/package.json +4 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexical-first retrieval pipeline.
|
|
3
|
+
*
|
|
4
|
+
* For one turn: authorize/filter BEFORE ranking, resolve exact identifiers and
|
|
5
|
+
* reviewed aliases via query n-gram lookup, add field-aware BM25 over a bounded
|
|
6
|
+
* query+history window, combine the deterministic signals, take the top
|
|
7
|
+
* candidates, expand a bounded set of directly-related entries, and classify
|
|
8
|
+
* confidence. No LLM, no embeddings, no vector store.
|
|
9
|
+
*/
|
|
10
|
+
import { tokenize } from "./normalize.js";
|
|
11
|
+
/** Per-turn history text cap so a long prior turn cannot dominate the query. */
|
|
12
|
+
const HISTORY_TURN_CHARS = 400;
|
|
13
|
+
/** All contiguous token n-grams (length 1..maxLen), de-duplicated. */
|
|
14
|
+
const generateNGrams = (tokens, maxLen) => {
|
|
15
|
+
const grams = [];
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
const limit = Math.min(maxLen, tokens.length);
|
|
18
|
+
for (let size = 1; size <= limit; size += 1) {
|
|
19
|
+
for (let start = 0; start + size <= tokens.length; start += 1) {
|
|
20
|
+
const gram = tokens.slice(start, start + size).join(" ");
|
|
21
|
+
if (!seen.has(gram)) {
|
|
22
|
+
seen.add(gram);
|
|
23
|
+
grams.push(gram);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return grams;
|
|
28
|
+
};
|
|
29
|
+
/** Longest indexed exact/alias phrase length, derived from the active snapshot. */
|
|
30
|
+
const getMaxIndexedPhraseLen = (...indexes) => {
|
|
31
|
+
let maxLen = 0;
|
|
32
|
+
for (const index of indexes) {
|
|
33
|
+
for (const phrase of index.keys()) {
|
|
34
|
+
const length = phrase === "" ? 0 : phrase.split(" ").length;
|
|
35
|
+
maxLen = Math.max(maxLen, length);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return maxLen;
|
|
39
|
+
};
|
|
40
|
+
/** Look each n-gram up in a phrase index; returns entry id -> matched phrases. */
|
|
41
|
+
const lookupPhrases = (grams, index) => {
|
|
42
|
+
const hits = new Map();
|
|
43
|
+
for (const gram of grams) {
|
|
44
|
+
const ids = index.get(gram);
|
|
45
|
+
if (!ids) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
for (const id of ids) {
|
|
49
|
+
const matched = hits.get(id) ?? [];
|
|
50
|
+
matched.push(gram);
|
|
51
|
+
hits.set(id, matched);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return hits;
|
|
55
|
+
};
|
|
56
|
+
/** Tokens fed to BM25: the current query plus a bounded recent-turn window. */
|
|
57
|
+
const buildLexicalTokens = (request) => {
|
|
58
|
+
const parts = [request.query];
|
|
59
|
+
for (const turn of request.recentTurns) {
|
|
60
|
+
parts.push(turn.text.slice(0, HISTORY_TURN_CHARS));
|
|
61
|
+
}
|
|
62
|
+
return parts.flatMap((part) => tokenize(part));
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Authorization + metadata filter. Applied BEFORE ranking so restricted or
|
|
66
|
+
* disabled-integration content never enters candidates, traces, or model context.
|
|
67
|
+
*/
|
|
68
|
+
const isAuthorized = (entry, request, blockedDomains) => {
|
|
69
|
+
if (entry.status !== "active") {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (blockedDomains && blockedDomains.includes(entry.domain)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (entry.integrations.length > 0) {
|
|
76
|
+
const active = new Set();
|
|
77
|
+
for (const integration of request.enabledIntegrations) {
|
|
78
|
+
active.add(integration.toLowerCase());
|
|
79
|
+
}
|
|
80
|
+
if (!entry.integrations.some((integration) => active.has(integration.toLowerCase()))) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
};
|
|
86
|
+
const compareCandidates = (left, right) => {
|
|
87
|
+
if (right.score !== left.score) {
|
|
88
|
+
return right.score - left.score;
|
|
89
|
+
}
|
|
90
|
+
if (left.id < right.id) {
|
|
91
|
+
return -1;
|
|
92
|
+
}
|
|
93
|
+
if (left.id > right.id) {
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
return 0;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Confidence class. `high` requires an exact/alias hit at the top; otherwise a
|
|
100
|
+
* multi-field or dominant lexical top is `medium`, a single weak signal is
|
|
101
|
+
* `low`, and no candidates is `none`. Thresholds are pre-tuning heuristics.
|
|
102
|
+
*/
|
|
103
|
+
const classifyConfidence = (candidates) => {
|
|
104
|
+
if (candidates.length === 0) {
|
|
105
|
+
return "none";
|
|
106
|
+
}
|
|
107
|
+
const [top, second] = candidates;
|
|
108
|
+
if (top.exact || top.alias) {
|
|
109
|
+
return "high";
|
|
110
|
+
}
|
|
111
|
+
const fieldsMatched = Object.keys(top.fieldScores).length;
|
|
112
|
+
const dominant = second ? top.score >= 1.5 * second.score : false;
|
|
113
|
+
if (fieldsMatched >= 2 || dominant) {
|
|
114
|
+
return "medium";
|
|
115
|
+
}
|
|
116
|
+
return "low";
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Run retrieval against a ready snapshot. Returns the primary selection, a
|
|
120
|
+
* bounded relationship expansion, the scored candidate list (diagnostics), and
|
|
121
|
+
* a confidence class. Context assembly is a separate, later step.
|
|
122
|
+
*/
|
|
123
|
+
export const retrieve = (snapshot, request, config, blockedDomains) => {
|
|
124
|
+
const queryTokens = tokenize(request.query);
|
|
125
|
+
const maxPhraseLen = getMaxIndexedPhraseLen(snapshot.exactIndex, snapshot.aliasIndex);
|
|
126
|
+
const grams = generateNGrams(queryTokens, maxPhraseLen);
|
|
127
|
+
const exactHits = lookupPhrases(grams, snapshot.exactIndex);
|
|
128
|
+
const aliasHits = lookupPhrases(grams, snapshot.aliasIndex);
|
|
129
|
+
const eligibleEntryIds = new Set();
|
|
130
|
+
for (const [id, entry] of snapshot.entriesById) {
|
|
131
|
+
if (isAuthorized(entry, request, blockedDomains)) {
|
|
132
|
+
eligibleEntryIds.add(id);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const lexicalTokens = buildLexicalTokens(request);
|
|
136
|
+
const lexicalMatches = snapshot.lexical.search(lexicalTokens, config.candidateLimit * 2, eligibleEntryIds);
|
|
137
|
+
const lexicalById = new Map(lexicalMatches.map((match) => [match.id, match]));
|
|
138
|
+
const candidateIds = new Set();
|
|
139
|
+
for (const id of exactHits.keys()) {
|
|
140
|
+
if (eligibleEntryIds.has(id)) {
|
|
141
|
+
candidateIds.add(id);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const id of aliasHits.keys()) {
|
|
145
|
+
if (eligibleEntryIds.has(id)) {
|
|
146
|
+
candidateIds.add(id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const match of lexicalMatches) {
|
|
150
|
+
candidateIds.add(match.id);
|
|
151
|
+
}
|
|
152
|
+
const candidates = [];
|
|
153
|
+
for (const id of candidateIds) {
|
|
154
|
+
const entry = snapshot.entriesById.get(id);
|
|
155
|
+
if (!entry) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const exact = exactHits.has(id);
|
|
159
|
+
const alias = aliasHits.has(id);
|
|
160
|
+
const lexicalMatch = lexicalById.get(id);
|
|
161
|
+
const lexical = lexicalMatch?.score ?? 0;
|
|
162
|
+
let score = lexical;
|
|
163
|
+
if (exact) {
|
|
164
|
+
score += config.exactBoost;
|
|
165
|
+
}
|
|
166
|
+
if (alias) {
|
|
167
|
+
score += config.aliasBoost;
|
|
168
|
+
}
|
|
169
|
+
candidates.push({
|
|
170
|
+
id,
|
|
171
|
+
score,
|
|
172
|
+
exact,
|
|
173
|
+
alias,
|
|
174
|
+
lexical,
|
|
175
|
+
fieldScores: lexicalMatch?.fieldScores ?? {},
|
|
176
|
+
matchedPhrases: [
|
|
177
|
+
...(exactHits.get(id) ?? []),
|
|
178
|
+
...(aliasHits.get(id) ?? []),
|
|
179
|
+
],
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
candidates.sort(compareCandidates);
|
|
183
|
+
const trimmed = candidates.slice(0, config.candidateLimit);
|
|
184
|
+
const primary = [];
|
|
185
|
+
for (const candidate of trimmed.slice(0, config.resultLimit)) {
|
|
186
|
+
const entry = snapshot.entriesById.get(candidate.id);
|
|
187
|
+
if (entry) {
|
|
188
|
+
primary.push(entry);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const selectedIds = new Set(primary.map((entry) => entry.id));
|
|
192
|
+
const expanded = [];
|
|
193
|
+
for (const entry of primary) {
|
|
194
|
+
if (expanded.length >= config.relationLimit) {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
const relations = snapshot.relationIndex.get(entry.id) ?? [];
|
|
198
|
+
for (const relatedId of relations) {
|
|
199
|
+
if (expanded.length >= config.relationLimit) {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
if (selectedIds.has(relatedId)) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const relatedEntry = snapshot.entriesById.get(relatedId);
|
|
206
|
+
if (!relatedEntry ||
|
|
207
|
+
!isAuthorized(relatedEntry, request, blockedDomains)) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
selectedIds.add(relatedId);
|
|
211
|
+
expanded.push(relatedEntry);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
primary,
|
|
216
|
+
expanded,
|
|
217
|
+
candidates: trimmed,
|
|
218
|
+
confidence: classifyConfidence(trimmed),
|
|
219
|
+
candidateCount: candidates.length,
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
//# sourceMappingURL=retrieval.js.map
|
|
@@ -9,6 +9,7 @@ import { globalCircuitBreakerManager, CircuitBreakerOpenError, } from "./mcpCirc
|
|
|
9
9
|
import { isObject, isNullish } from "../utils/typeUtils.js";
|
|
10
10
|
import { validateToolName, validateToolDescription, } from "../utils/parameterValidation.js";
|
|
11
11
|
import { withTimeout } from "../utils/errorHandling.js";
|
|
12
|
+
import { coerceType } from "../utils/toolCallRepair.js";
|
|
12
13
|
import { extractMcpErrorText } from "../utils/mcpErrorText.js";
|
|
13
14
|
import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
14
15
|
import { tracers } from "../telemetry/tracers.js";
|
|
@@ -416,12 +417,16 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
416
417
|
if (!toolInfo.isAvailable) {
|
|
417
418
|
throw new Error(`Tool '${toolName}' is not available`);
|
|
418
419
|
}
|
|
419
|
-
// Validate input parameters if requested
|
|
420
|
+
// Validate input parameters if requested. Validation coerces
|
|
421
|
+
// recoverable mismatches (numeric strings for number params, "true"
|
|
422
|
+
// for booleans, JSON-encoded objects/arrays) instead of rejecting —
|
|
423
|
+
// a rejection here costs the agent loop a full model round-trip.
|
|
424
|
+
let effectiveParameters = parameters;
|
|
420
425
|
if (options.validateInput !== false) {
|
|
421
|
-
this.validateToolParameters(toolInfo, parameters);
|
|
426
|
+
effectiveParameters = this.validateToolParameters(toolInfo, parameters);
|
|
422
427
|
}
|
|
423
428
|
mcpLogger.debug(`[ToolDiscoveryService] Executing tool: ${toolName} on ${serverId}`, {
|
|
424
|
-
parameters,
|
|
429
|
+
parameters: effectiveParameters,
|
|
425
430
|
});
|
|
426
431
|
// Create circuit breaker for tool execution
|
|
427
432
|
const effectiveTimeout = options.timeout || DEFAULT_TOOL_TIMEOUT;
|
|
@@ -448,16 +453,22 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
448
453
|
"gen_ai.tool.name": toolName,
|
|
449
454
|
"gen_ai.request": safeJsonStringify({
|
|
450
455
|
name: toolName,
|
|
451
|
-
arguments: redactForPreview(
|
|
456
|
+
arguments: redactForPreview(effectiveParameters),
|
|
452
457
|
}, 2048),
|
|
453
458
|
},
|
|
454
459
|
}, async (callSpan) => {
|
|
455
460
|
try {
|
|
456
461
|
const timeout = effectiveTimeout;
|
|
462
|
+
// Pass the timeout as MCP RequestOptions too: without it the
|
|
463
|
+
// SDK applies its own DEFAULT_REQUEST_TIMEOUT_MSEC (60s), so a
|
|
464
|
+
// configured server timeout above 60s never took effect — the
|
|
465
|
+
// SDK aborted first. The SDK timeout also cancels the transport
|
|
466
|
+
// request and sends a cancellation notification, which the
|
|
467
|
+
// outer Promise.race below (kept as a backstop) cannot do.
|
|
457
468
|
const callResult = await withTimeout(client.callTool({
|
|
458
469
|
name: toolName,
|
|
459
|
-
arguments:
|
|
460
|
-
}), timeout, new Error(`Tool execution timeout: ${toolName}`));
|
|
470
|
+
arguments: effectiveParameters,
|
|
471
|
+
}, undefined, { timeout: effectiveTimeout }), timeout, new Error(`Tool execution timeout: ${toolName}`));
|
|
461
472
|
// Curator P0-1/P0-2: the MCP client does NOT throw on protocol
|
|
462
473
|
// errors — it returns { isError: true, content: [...] }. Detect
|
|
463
474
|
// that pattern so the span status reflects reality.
|
|
@@ -603,25 +614,45 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
603
614
|
*/
|
|
604
615
|
validateToolParameters(toolInfo, parameters) {
|
|
605
616
|
if (!toolInfo.inputSchema) {
|
|
606
|
-
return; // No schema to validate against
|
|
617
|
+
return parameters; // No schema to validate against
|
|
607
618
|
}
|
|
608
|
-
// Basic validation - check required properties
|
|
609
619
|
const schema = toolInfo.inputSchema;
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
//
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
620
|
+
const properties = schema.properties && typeof schema.properties === "object"
|
|
621
|
+
? schema.properties
|
|
622
|
+
: {};
|
|
623
|
+
const requiredProps = Array.isArray(schema.required)
|
|
624
|
+
? schema.required.filter((r) => typeof r === "string")
|
|
625
|
+
: [];
|
|
626
|
+
// The thrown message is fed back to the MODEL as the tool result, so it
|
|
627
|
+
// restates the full contract — a bare "missing X" made weaker models
|
|
628
|
+
// guess again and burn another loop step per attempt.
|
|
629
|
+
const contract = () => Object.entries(properties)
|
|
630
|
+
.map(([key, prop]) => `${key}${requiredProps.includes(key) ? "" : "?"}: ${prop.type ?? "any"}`)
|
|
631
|
+
.join(", ");
|
|
632
|
+
// Basic validation - check required properties
|
|
633
|
+
const missing = requiredProps.filter((prop) => !(prop in parameters));
|
|
634
|
+
if (missing.length > 0) {
|
|
635
|
+
throw new Error(`Missing required parameter${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}. ` +
|
|
636
|
+
`Expected arguments: { ${contract()} }; received keys: [${Object.keys(parameters).join(", ")}]`);
|
|
637
|
+
}
|
|
638
|
+
// Type validation for properties — coerce recoverable mismatches first
|
|
639
|
+
// (numeric strings, "true"/"false", JSON-encoded objects/arrays) so a
|
|
640
|
+
// sloppy-but-unambiguous call executes instead of failing back to the
|
|
641
|
+
// model. Only genuinely wrong types still throw.
|
|
642
|
+
let coerced;
|
|
643
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
644
|
+
if (propName in parameters) {
|
|
645
|
+
const originalValue = parameters[propName];
|
|
646
|
+
const coercedValue = coerceType(originalValue, propSchema);
|
|
647
|
+
if (coercedValue !== originalValue) {
|
|
648
|
+
mcpLogger.debug(`[ToolDiscoveryService] Coerced parameter '${propName}' for tool '${toolInfo.name}': ${typeof originalValue} → ${typeof coercedValue}`);
|
|
649
|
+
coerced = coerced ?? { ...parameters };
|
|
650
|
+
coerced[propName] = coercedValue;
|
|
622
651
|
}
|
|
652
|
+
this.validateParameterType(propName, (coerced ? coerced[propName] : originalValue), propSchema);
|
|
623
653
|
}
|
|
624
654
|
}
|
|
655
|
+
return coerced ?? parameters;
|
|
625
656
|
}
|
|
626
657
|
/**
|
|
627
658
|
* Validate parameter type
|
|
@@ -643,6 +674,14 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
643
674
|
throw new Error(`Parameter '${name}' must be a number, got ${actualType}`);
|
|
644
675
|
}
|
|
645
676
|
break;
|
|
677
|
+
case "integer":
|
|
678
|
+
// coerceType treats "integer" as distinct from "number"; without
|
|
679
|
+
// this case an uncoercible value ("3.7", "abc") passed through to
|
|
680
|
+
// the MCP server unvalidated.
|
|
681
|
+
if (actualType !== "number" || !Number.isInteger(value)) {
|
|
682
|
+
throw new Error(`Parameter '${name}' must be an integer, got ${actualType === "number" ? String(value) : actualType}`);
|
|
683
|
+
}
|
|
684
|
+
break;
|
|
646
685
|
case "boolean":
|
|
647
686
|
if (actualType !== "boolean") {
|
|
648
687
|
throw new Error(`Parameter '${name}' must be a boolean, got ${actualType}`);
|
package/dist/lib/neurolink.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Uses real MCP infrastructure for tool discovery and execution.
|
|
7
7
|
*/
|
|
8
8
|
import type { AgentDefinition, AgentNetworkConfig, NetworkExecutionInput, NetworkExecutionOptions, NetworkExecutionResult, NetworkStreamChunk } from "./types/index.js";
|
|
9
|
-
import type { CompactionConfig, CompactionResult, SpanData, ObservabilityConfig, MetricsSummary, MCPToolAnnotations, TraceView, AuthenticatedContext, AuthProvider, JsonObject, NeuroLinkEvents, TypedEventEmitter, MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig, ChatMessage, ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPToolInfo, GenerateOptions, GenerateResult, ProviderStatus, TextGenerationOptions, TextGenerationResult, MCPExecutableTool, MCPServerInfo, MCPStatus, StreamOptions, StreamResult, ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions, BatchOperationResult, StreamGenerationEndContext, ToolRoutingServerDescriptor, ToolDedupConfig, ToolConfig } from "./types/index.js";
|
|
9
|
+
import type { CompactionConfig, CompactionResult, SpanData, ObservabilityConfig, MetricsSummary, MCPToolAnnotations, TraceView, AuthenticatedContext, AuthProvider, JsonObject, NeuroLinkEvents, TypedEventEmitter, MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig, ChatMessage, ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPToolInfo, GenerateOptions, GenerateResult, ProviderStatus, TextGenerationOptions, TextGenerationResult, MCPExecutableTool, MCPServerInfo, MCPStatus, StreamOptions, StreamResult, ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions, BatchOperationResult, StreamGenerationEndContext, ToolRoutingServerDescriptor, ToolDedupConfig, ToolConfig, KnowledgeEngineStatus } from "./types/index.js";
|
|
10
10
|
import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
|
|
11
11
|
import type { RedisConversationMemoryManager } from "./core/redisConversationMemoryManager.js";
|
|
12
12
|
import { ExternalServerManager } from "./mcp/externalServerManager.js";
|
|
@@ -107,6 +107,7 @@ export declare class NeuroLink {
|
|
|
107
107
|
private toolRoutingConfig?;
|
|
108
108
|
private toolRoutingCacheInstance?;
|
|
109
109
|
private toolRoutingVectorCache?;
|
|
110
|
+
private knowledgeGroundingEngine?;
|
|
110
111
|
private toolDedupConfig?;
|
|
111
112
|
private toolsConfig?;
|
|
112
113
|
/** Session-scoped pins of tools discovered via search_tools (tools.discovery mode). */
|
|
@@ -629,6 +630,7 @@ export declare class NeuroLink {
|
|
|
629
630
|
* @param optionsOrPrompt.maxTokens - Maximum tokens to generate
|
|
630
631
|
* @param optionsOrPrompt.thinkingConfig - Extended thinking configuration (thinkingLevel: 'minimal'|'low'|'medium'|'high')
|
|
631
632
|
* @param optionsOrPrompt.context - Context with conversationId and userId for memory
|
|
633
|
+
* @param optionsOrPrompt.useKnowledgeGrounding - Whether to use the instance's configured knowledge for this call
|
|
632
634
|
* @returns Promise resolving to generation result with content and metadata
|
|
633
635
|
*
|
|
634
636
|
* @example Basic text generation
|
|
@@ -904,6 +906,7 @@ export declare class NeuroLink {
|
|
|
904
906
|
* @param options.enableEvaluation - Whether to include response quality evaluation
|
|
905
907
|
* @param options.context - Additional context for the request
|
|
906
908
|
* @param options.evaluationDomain - Domain for specialized evaluation
|
|
909
|
+
* @param options.useKnowledgeGrounding - Whether to use the instance's configured knowledge for this call
|
|
907
910
|
*
|
|
908
911
|
* @returns Promise resolving to StreamResult with an async iterable stream
|
|
909
912
|
*
|
|
@@ -989,6 +992,16 @@ export declare class NeuroLink {
|
|
|
989
992
|
* alone does not activate it.
|
|
990
993
|
*/
|
|
991
994
|
setToolRoutingServers(servers: ToolRoutingServerDescriptor[]): void;
|
|
995
|
+
/** Knowledge-grounding engine health (null when it was not configured). */
|
|
996
|
+
getKnowledgeStatus(): KnowledgeEngineStatus | null;
|
|
997
|
+
private appendKnowledgeGroundingBlockToSystemPrompt;
|
|
998
|
+
/**
|
|
999
|
+
* Retrieve knowledge grounding for a public generate/stream call without
|
|
1000
|
+
* mutating its options. The outer call boundary decides how to apply the
|
|
1001
|
+
* returned context. Returns undefined when the call does not opt in,
|
|
1002
|
+
* grounding is disabled/not applicable, or retrieval fails open.
|
|
1003
|
+
*/
|
|
1004
|
+
private retrieveKnowledgeGrounding;
|
|
992
1005
|
private validateStreamRequestOptions;
|
|
993
1006
|
private maybeHandleWorkflowStreamRequest;
|
|
994
1007
|
private runStandardStreamRequest;
|
package/dist/lib/neurolink.js
CHANGED
|
@@ -32,6 +32,7 @@ import { SYSTEM_LIMITS, DEFAULT_TOOL_ROUTING_TIMEOUT_MS, MIN_RECOVERY_TURN_BUDGE
|
|
|
32
32
|
import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
|
|
33
33
|
import { buildToolRoutingCatalog, buildRoutingQueryFromHistory, resolveToolRoutingExclusions, } from "./core/toolRouting.js";
|
|
34
34
|
import { ToolRoutingCache } from "./core/toolRoutingCache.js";
|
|
35
|
+
import { DEFAULT_RECENT_TURNS, KnowledgeGroundingEngine, } from "./knowledge/index.js";
|
|
35
36
|
import { AIProviderFactory } from "./core/factory.js";
|
|
36
37
|
import { createToolEventPayload } from "./core/toolEvents.js";
|
|
37
38
|
import { ProviderRegistry } from "./factories/providerRegistry.js";
|
|
@@ -405,6 +406,9 @@ export class NeuroLink {
|
|
|
405
406
|
// vectors are computed once. Cleared when the catalog changes via
|
|
406
407
|
// setToolRoutingServers() so stale vectors are never reused.
|
|
407
408
|
toolRoutingVectorCache;
|
|
409
|
+
// Knowledge grounding: lexical-first host-supplied retrieval engine. Built
|
|
410
|
+
// once from constructor config and undefined unless grounding is enabled.
|
|
411
|
+
knowledgeGroundingEngine;
|
|
408
412
|
// Opt-in tool-signature deduplication config.
|
|
409
413
|
toolDedupConfig;
|
|
410
414
|
toolsConfig;
|
|
@@ -837,6 +841,18 @@ export class NeuroLink {
|
|
|
837
841
|
// multiple NeuroLink instances.
|
|
838
842
|
this.toolRoutingConfig = { ...config.toolRouting };
|
|
839
843
|
}
|
|
844
|
+
const knowledgeGroundingConfig = config?.knowledgeGrounding;
|
|
845
|
+
if (knowledgeGroundingConfig?.enabled) {
|
|
846
|
+
if (Array.isArray(knowledgeGroundingConfig.sources) &&
|
|
847
|
+
knowledgeGroundingConfig.sources.length > 0) {
|
|
848
|
+
// The engine builds its immutable index asynchronously; eligible
|
|
849
|
+
// generate() and stream() calls await readiness before retrieval.
|
|
850
|
+
this.knowledgeGroundingEngine = new KnowledgeGroundingEngine(knowledgeGroundingConfig);
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
logger.warn("[KnowledgeGrounding] enabled but no sources were provided; grounding disabled for this instance");
|
|
854
|
+
}
|
|
855
|
+
}
|
|
840
856
|
if (config?.toolDedup) {
|
|
841
857
|
this.toolDedupConfig = { ...config.toolDedup };
|
|
842
858
|
}
|
|
@@ -853,7 +869,9 @@ export class NeuroLink {
|
|
|
853
869
|
// generate() (marked so it never recursively re-routes). Fails open.
|
|
854
870
|
this.classifierRouter = config?.classifierRouter?.enabled
|
|
855
871
|
? new ClassifierRouter(config.classifierRouter, {
|
|
856
|
-
generate: (genOptions) => this.generate(
|
|
872
|
+
generate: (genOptions) => this.generate({
|
|
873
|
+
...genOptions,
|
|
874
|
+
}),
|
|
857
875
|
logger: {
|
|
858
876
|
debug: (message, meta) => logger.debug(message, meta),
|
|
859
877
|
warn: (message, meta) => logger.warn(message, meta),
|
|
@@ -3045,6 +3063,7 @@ Current user's request: ${currentInput}`;
|
|
|
3045
3063
|
* @param optionsOrPrompt.maxTokens - Maximum tokens to generate
|
|
3046
3064
|
* @param optionsOrPrompt.thinkingConfig - Extended thinking configuration (thinkingLevel: 'minimal'|'low'|'medium'|'high')
|
|
3047
3065
|
* @param optionsOrPrompt.context - Context with conversationId and userId for memory
|
|
3066
|
+
* @param optionsOrPrompt.useKnowledgeGrounding - Whether to use the instance's configured knowledge for this call
|
|
3048
3067
|
* @returns Promise resolving to generation result with content and metadata
|
|
3049
3068
|
*
|
|
3050
3069
|
* @example Basic text generation
|
|
@@ -3131,9 +3150,22 @@ Current user's request: ${currentInput}`;
|
|
|
3131
3150
|
if (typeof optionsOrPrompt !== "string") {
|
|
3132
3151
|
optionsOrPrompt = cloneOptionsForCallIsolation(optionsOrPrompt);
|
|
3133
3152
|
}
|
|
3153
|
+
// Retrieve once at the public call boundary so fallback attempts reuse the
|
|
3154
|
+
// same grounding block and internal preparation cannot inject it twice.
|
|
3155
|
+
const groundingOptions = typeof optionsOrPrompt === "string"
|
|
3156
|
+
? { input: { text: optionsOrPrompt } }
|
|
3157
|
+
: optionsOrPrompt;
|
|
3158
|
+
const knowledgeOutcome = await this.retrieveKnowledgeGrounding(groundingOptions);
|
|
3159
|
+
if (knowledgeOutcome?.ephemeralContext) {
|
|
3160
|
+
const block = knowledgeOutcome.ephemeralContext.content;
|
|
3161
|
+
optionsOrPrompt = {
|
|
3162
|
+
...groundingOptions,
|
|
3163
|
+
systemPrompt: this.appendKnowledgeGroundingBlockToSystemPrompt(groundingOptions.systemPrompt, block),
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3134
3166
|
const startedAt = Date.now();
|
|
3135
3167
|
try {
|
|
3136
|
-
|
|
3168
|
+
const result = await this.runWithFallbackOrchestration(optionsOrPrompt, "generate", (opts) => {
|
|
3137
3169
|
// Capture root-ness before startActiveSpan makes generateSpan active.
|
|
3138
3170
|
// The actual guest-rescue stamp is deferred to executeGenerateRequest,
|
|
3139
3171
|
// AFTER prepareGenerateRequest merges auth/requestContext-derived
|
|
@@ -3142,6 +3174,10 @@ Current user's request: ${currentInput}`;
|
|
|
3142
3174
|
const generateIsRoot = !trace.getSpan(context.active());
|
|
3143
3175
|
return tracers.sdk.startActiveSpan("neurolink.generate", { kind: SpanKind.INTERNAL }, (generateSpan) => this.executeGenerateWithMetricsContext(opts, generateSpan, generateIsRoot));
|
|
3144
3176
|
});
|
|
3177
|
+
if (knowledgeOutcome) {
|
|
3178
|
+
result.knowledge = knowledgeOutcome.metadata;
|
|
3179
|
+
}
|
|
3180
|
+
return result;
|
|
3145
3181
|
}
|
|
3146
3182
|
catch (error) {
|
|
3147
3183
|
// Lifecycle middleware (wrapGenerate.catch in builtin/lifecycle.ts)
|
|
@@ -5190,12 +5226,22 @@ Current user's request: ${currentInput}`;
|
|
|
5190
5226
|
if (!generationContext) {
|
|
5191
5227
|
return null;
|
|
5192
5228
|
}
|
|
5229
|
+
// Provider construction runs BEFORE the budget check so runtime
|
|
5230
|
+
// model-limit discovery (ensureModelLimits) can register real context
|
|
5231
|
+
// windows first — ensureMCPGenerationBudget otherwise computes against
|
|
5232
|
+
// the static default window (see directProviderGeneration for the
|
|
5233
|
+
// lock-out this ordering prevents).
|
|
5234
|
+
const provider = await AIProviderFactory.createProvider(generationContext.providerName, options.model, !options.disableTools, this, options.region, this.resolveCredentials(options.credentials));
|
|
5235
|
+
provider.setTraceContext(this._metricsTraceContext);
|
|
5236
|
+
// Never rejects — discovery failure degrades to static defaults.
|
|
5237
|
+
await provider.ensureModelLimits?.();
|
|
5193
5238
|
const conversationMessages = await this.ensureMCPGenerationBudget(options, requestId, generationContext.providerName, generationContext.enhancedSystemPrompt, generationContext.availableTools, generationContext.conversationMessages);
|
|
5194
5239
|
return this.generateWithMCPProvider({
|
|
5195
5240
|
options,
|
|
5196
5241
|
requestId,
|
|
5197
5242
|
functionTag,
|
|
5198
5243
|
tryMCPStartTime,
|
|
5244
|
+
provider,
|
|
5199
5245
|
providerName: generationContext.providerName,
|
|
5200
5246
|
availableTools: generationContext.availableTools,
|
|
5201
5247
|
enhancedSystemPrompt: generationContext.enhancedSystemPrompt,
|
|
@@ -5479,9 +5525,7 @@ Current user's request: ${currentInput}`;
|
|
|
5479
5525
|
return compactedMessages;
|
|
5480
5526
|
}
|
|
5481
5527
|
async generateWithMCPProvider(context) {
|
|
5482
|
-
const { options, requestId, functionTag, tryMCPStartTime, providerName, availableTools, enhancedSystemPrompt, conversationMessages, } = context;
|
|
5483
|
-
const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, this, options.region, this.resolveCredentials(options.credentials));
|
|
5484
|
-
provider.setTraceContext(this._metricsTraceContext);
|
|
5528
|
+
const { options, requestId, functionTag, tryMCPStartTime, provider, providerName, availableTools, enhancedSystemPrompt, conversationMessages, } = context;
|
|
5485
5529
|
this.emitter.emit("connected");
|
|
5486
5530
|
this.emitter.emit("message", `${providerName} provider initialized successfully`);
|
|
5487
5531
|
provider.setupToolExecutor({
|
|
@@ -5760,6 +5804,21 @@ Current user's request: ${currentInput}`;
|
|
|
5760
5804
|
?.length
|
|
5761
5805
|
? optionsWithMessages.conversationMessages
|
|
5762
5806
|
: await getConversationMessages(this.conversationMemory, options);
|
|
5807
|
+
// Provider construction runs BEFORE the budget check so runtime
|
|
5808
|
+
// model-limit discovery (ensureModelLimits) can register real
|
|
5809
|
+
// context windows first. The previous order created a lock-out:
|
|
5810
|
+
// checkContextBudget ran against the static default window, its
|
|
5811
|
+
// pre-dispatch hard cap threw before the provider (whose
|
|
5812
|
+
// constructor owns the discovery) ever existed — so a blocked call
|
|
5813
|
+
// prevented the very discovery that would have unblocked it.
|
|
5814
|
+
const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, // Pass disableTools as inverse of enableMCP
|
|
5815
|
+
this, // Pass SDK instance
|
|
5816
|
+
options.region, // Pass region parameter
|
|
5817
|
+
this.resolveCredentials(options.credentials));
|
|
5818
|
+
// Propagate trace context for parent-child span hierarchy
|
|
5819
|
+
provider.setTraceContext(this._metricsTraceContext);
|
|
5820
|
+
// Never rejects — discovery failure degrades to static defaults.
|
|
5821
|
+
await provider.ensureModelLimits?.();
|
|
5763
5822
|
// Pre-generation budget check
|
|
5764
5823
|
const budgetCheck = checkContextBudget({
|
|
5765
5824
|
provider: providerName,
|
|
@@ -5915,12 +5974,6 @@ Current user's request: ${currentInput}`;
|
|
|
5915
5974
|
}
|
|
5916
5975
|
}
|
|
5917
5976
|
}
|
|
5918
|
-
const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, // Pass disableTools as inverse of enableMCP
|
|
5919
|
-
this, // Pass SDK instance
|
|
5920
|
-
options.region, // Pass region parameter
|
|
5921
|
-
this.resolveCredentials(options.credentials));
|
|
5922
|
-
// Propagate trace context for parent-child span hierarchy
|
|
5923
|
-
provider.setTraceContext(this._metricsTraceContext);
|
|
5924
5977
|
// ADD: Emit connection events for successful provider creation (Bedrock-compatible)
|
|
5925
5978
|
this.emitter.emit("connected");
|
|
5926
5979
|
this.emitter.emit("message", `${providerName} provider initialized successfully`);
|
|
@@ -6187,6 +6240,7 @@ Current user's request: ${currentInput}`;
|
|
|
6187
6240
|
* @param options.enableEvaluation - Whether to include response quality evaluation
|
|
6188
6241
|
* @param options.context - Additional context for the request
|
|
6189
6242
|
* @param options.evaluationDomain - Domain for specialized evaluation
|
|
6243
|
+
* @param options.useKnowledgeGrounding - Whether to use the instance's configured knowledge for this call
|
|
6190
6244
|
*
|
|
6191
6245
|
* @returns Promise resolving to StreamResult with an async iterable stream
|
|
6192
6246
|
*
|
|
@@ -6244,8 +6298,23 @@ Current user's request: ${currentInput}`;
|
|
|
6244
6298
|
// top-level keys and left `options.input` shared with the caller.
|
|
6245
6299
|
options = cloneOptionsForCallIsolation(options);
|
|
6246
6300
|
const startedAt = Date.now();
|
|
6301
|
+
// Retrieve once before provider/model fallback orchestration. Every fallback
|
|
6302
|
+
// receives the same explicitly enriched options, without the retrieval helper
|
|
6303
|
+
// mutating its input or injecting the same block more than once.
|
|
6304
|
+
const knowledgeOutcome = await this.retrieveKnowledgeGrounding(options);
|
|
6305
|
+
if (knowledgeOutcome?.ephemeralContext) {
|
|
6306
|
+
const block = knowledgeOutcome.ephemeralContext.content;
|
|
6307
|
+
options = {
|
|
6308
|
+
...options,
|
|
6309
|
+
systemPrompt: this.appendKnowledgeGroundingBlockToSystemPrompt(options.systemPrompt, block),
|
|
6310
|
+
};
|
|
6311
|
+
}
|
|
6247
6312
|
try {
|
|
6248
|
-
|
|
6313
|
+
const result = await this.streamWithIterationFallback(options);
|
|
6314
|
+
if (knowledgeOutcome) {
|
|
6315
|
+
result.knowledge = knowledgeOutcome.metadata;
|
|
6316
|
+
}
|
|
6317
|
+
return result;
|
|
6249
6318
|
}
|
|
6250
6319
|
catch (error) {
|
|
6251
6320
|
// Mirror generate(): fire consumer onError for failures that
|
|
@@ -6913,6 +6982,63 @@ Current user's request: ${currentInput}`;
|
|
|
6913
6982
|
// to recompute exclusions against the new server/tool set.
|
|
6914
6983
|
this.toolRoutingCacheInstance = undefined;
|
|
6915
6984
|
}
|
|
6985
|
+
/** Knowledge-grounding engine health (null when it was not configured). */
|
|
6986
|
+
getKnowledgeStatus() {
|
|
6987
|
+
return this.knowledgeGroundingEngine?.getStatus() ?? null;
|
|
6988
|
+
}
|
|
6989
|
+
appendKnowledgeGroundingBlockToSystemPrompt(systemPrompt, block) {
|
|
6990
|
+
if (typeof systemPrompt === "function") {
|
|
6991
|
+
const originalSystemPrompt = systemPrompt;
|
|
6992
|
+
return async (context) => {
|
|
6993
|
+
const resolved = await resolveDynamicArgument(originalSystemPrompt, context);
|
|
6994
|
+
return resolved.value ? `${resolved.value}\n\n${block}` : block;
|
|
6995
|
+
};
|
|
6996
|
+
}
|
|
6997
|
+
return systemPrompt ? `${systemPrompt}\n\n${block}` : block;
|
|
6998
|
+
}
|
|
6999
|
+
/**
|
|
7000
|
+
* Retrieve knowledge grounding for a public generate/stream call without
|
|
7001
|
+
* mutating its options. The outer call boundary decides how to apply the
|
|
7002
|
+
* returned context. Returns undefined when the call does not opt in,
|
|
7003
|
+
* grounding is disabled/not applicable, or retrieval fails open.
|
|
7004
|
+
*/
|
|
7005
|
+
async retrieveKnowledgeGrounding(options) {
|
|
7006
|
+
const engine = this.knowledgeGroundingEngine;
|
|
7007
|
+
if (!engine ||
|
|
7008
|
+
!engine.isEnabled() ||
|
|
7009
|
+
options.useKnowledgeGrounding !== true) {
|
|
7010
|
+
return undefined;
|
|
7011
|
+
}
|
|
7012
|
+
const query = options.input?.text;
|
|
7013
|
+
if (!query) {
|
|
7014
|
+
return undefined;
|
|
7015
|
+
}
|
|
7016
|
+
try {
|
|
7017
|
+
const conversationMessages = options.conversationMessages !== undefined
|
|
7018
|
+
? options.conversationMessages
|
|
7019
|
+
: await this.fetchRecentRoutingHistory(options);
|
|
7020
|
+
const recentTurns = conversationMessages
|
|
7021
|
+
.filter((message) => message.role === "user" || message.role === "assistant")
|
|
7022
|
+
.slice(-DEFAULT_RECENT_TURNS)
|
|
7023
|
+
.map((message) => ({
|
|
7024
|
+
role: message.role === "assistant"
|
|
7025
|
+
? "assistant"
|
|
7026
|
+
: "user",
|
|
7027
|
+
text: typeof message.content === "string" ? message.content : "",
|
|
7028
|
+
}));
|
|
7029
|
+
return await engine.ground({
|
|
7030
|
+
query,
|
|
7031
|
+
recentTurns,
|
|
7032
|
+
scope: options.knowledgeContext,
|
|
7033
|
+
});
|
|
7034
|
+
}
|
|
7035
|
+
catch (error) {
|
|
7036
|
+
logger.warn("[KnowledgeGrounding] grounding hook failed open", {
|
|
7037
|
+
error: String(error),
|
|
7038
|
+
});
|
|
7039
|
+
return undefined;
|
|
7040
|
+
}
|
|
7041
|
+
}
|
|
6916
7042
|
async validateStreamRequestOptions(options, startTime) {
|
|
6917
7043
|
await this.validateStreamInput(options);
|
|
6918
7044
|
// Input validation for stream
|
|
@@ -7892,6 +8018,10 @@ Current user's request: ${currentInput}`;
|
|
|
7892
8018
|
this, // Pass SDK instance
|
|
7893
8019
|
options.region, // Pass region parameter
|
|
7894
8020
|
this.resolveCredentials(options.credentials));
|
|
8021
|
+
// Runtime model-limit discovery must land BEFORE the stream budget
|
|
8022
|
+
// check below — otherwise it computes against the static default
|
|
8023
|
+
// window. Never rejects; failure degrades to static defaults.
|
|
8024
|
+
await provider.ensureModelLimits?.();
|
|
7895
8025
|
// Propagate trace context for parent-child span hierarchy
|
|
7896
8026
|
provider.setTraceContext(this._metricsTraceContext);
|
|
7897
8027
|
// Enable tool execution for the provider using BaseProvider method
|