@juspay/neurolink 12.47.14 → 12.48.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 +3 -3
- package/dist/auth/middleware/AuthMiddleware.d.ts +1 -1
- package/dist/browser/neurolink.min.js +578 -565
- package/dist/cli/factories/commandFactory.d.ts +7 -0
- package/dist/cli/factories/commandFactory.js +26 -0
- package/dist/cli/loop/optionsSchema.js +9 -0
- package/dist/context/budgetChecker.js +54 -2
- package/dist/context/contextDecision.js +2 -0
- package/dist/context/toolPairRepair.d.ts +12 -0
- package/dist/context/toolPairRepair.js +29 -3
- package/dist/core/baseProvider.d.ts +92 -3
- package/dist/core/baseProvider.js +254 -10
- package/dist/core/conversationMemoryManager.js +17 -2
- package/dist/core/loopEngine.js +1 -1
- package/dist/core/modules/MessageBuilder.js +11 -3
- package/dist/core/modules/TelemetryHandler.d.ts +2 -2
- package/dist/core/modules/ToolsManager.d.ts +35 -0
- package/dist/core/modules/ToolsManager.js +159 -55
- package/dist/core/nativeGenerateLoop.d.ts +58 -1
- package/dist/core/nativeGenerateLoop.js +277 -3
- package/dist/core/redisConversationMemoryManager.js +10 -3
- package/dist/core/resolveRequestKind.d.ts +24 -4
- package/dist/core/resolveRequestKind.js +31 -4
- package/dist/core/toolExecutionRecorder.d.ts +6 -0
- package/dist/core/toolExecutionRecorder.js +24 -2
- package/dist/core/toolRoutingDecision.js +1 -0
- package/dist/factories/mediaHandlerCatalog.js +5 -0
- package/dist/factories/providerDescriptors.js +15 -0
- package/dist/files/fileTools.d.ts +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +9 -2
- package/dist/memory/condensationPrompt.d.ts +8 -0
- package/dist/memory/condensationPrompt.js +29 -0
- package/dist/memory/hippocampusInitializer.d.ts +14 -2
- package/dist/memory/hippocampusInitializer.js +64 -3
- package/dist/neurolink.d.ts +252 -3
- package/dist/neurolink.js +898 -43
- package/dist/providers/amazonBedrock/client.js +6 -0
- package/dist/providers/amazonSagemaker.js +14 -1
- package/dist/providers/anthropic/client.js +148 -19
- package/dist/providers/anthropic/loopAdapter.js +2 -2
- package/dist/providers/googleAiStudio/client.js +2 -1
- package/dist/providers/openaiChatCompletionsBase.js +103 -23
- package/dist/providers/systemOneDecision.d.ts +6 -0
- package/dist/providers/systemOneDecision.js +14 -63
- package/dist/providers/typesafe.d.ts +6 -0
- package/dist/providers/typesafe.js +6 -0
- package/dist/rag/retrieval/searchDecision.js +1 -0
- package/dist/routing/classifierRouter.js +6 -5
- package/dist/routing/classifierStrategies.js +8 -1
- package/dist/types/classifierRouter.d.ts +16 -3
- package/dist/types/common.d.ts +2 -0
- package/dist/types/config.d.ts +9 -0
- package/dist/types/context.d.ts +8 -1
- package/dist/types/conversation.d.ts +87 -0
- package/dist/types/decision.d.ts +210 -1
- package/dist/types/decision.js +9 -1
- package/dist/types/dispatch.d.ts +11 -4
- package/dist/types/generate.d.ts +118 -54
- package/dist/types/loopEngine.d.ts +7 -2
- package/dist/types/memory.d.ts +177 -9
- package/dist/types/openaiCompatible.d.ts +11 -3
- package/dist/types/stream.d.ts +13 -16
- package/dist/types/stt.d.ts +58 -1
- package/dist/types/tools.d.ts +14 -0
- package/dist/types/tools.js +8 -0
- package/dist/types/tts.d.ts +108 -0
- package/dist/types/voice.d.ts +41 -4
- package/dist/utils/decisionAnswers.d.ts +31 -1
- package/dist/utils/decisionAnswers.js +114 -0
- package/dist/utils/decisionLimits.d.ts +39 -0
- package/dist/utils/decisionLimits.js +116 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.js +9 -0
- package/dist/utils/messageBuilder.d.ts +45 -1
- package/dist/utils/messageBuilder.js +393 -16
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
- package/dist/utils/multimodalOptionsBuilder.js +1 -0
- package/dist/utils/speechText.d.ts +41 -0
- package/dist/utils/speechText.js +364 -0
- package/dist/utils/toolResultStatus.d.ts +26 -0
- package/dist/utils/toolResultStatus.js +73 -0
- package/dist/utils/ttsProcessor.d.ts +29 -0
- package/dist/utils/ttsProcessor.js +84 -2
- package/dist/voice/index.d.ts +1 -0
- package/dist/voice/index.js +3 -0
- package/dist/voice/providers/ElevenLabsSTT.d.ts +42 -0
- package/dist/voice/providers/ElevenLabsSTT.js +351 -0
- package/dist/voice/providers/ElevenLabsTTS.d.ts +81 -1
- package/dist/voice/providers/ElevenLabsTTS.js +415 -74
- package/docs-site/static/search-index.json +48 -33
- package/package.json +5 -2
- package/dist/utils/toolEndEmitter.d.ts +0 -25
- package/dist/utils/toolEndEmitter.js +0 -64
|
@@ -133,6 +133,12 @@ export declare abstract class SystemOneDecisionProvider extends BaseProvider {
|
|
|
133
133
|
* Refuse a request the model cannot read in full. An encoder cuts the state
|
|
134
134
|
* off past its window without saying so, so without this a decision would
|
|
135
135
|
* be made on input the model never saw — and reported as if it had.
|
|
136
|
+
*
|
|
137
|
+
* Advisory limits (`decisionLimits.advisory`) are skipped: they publish a
|
|
138
|
+
* server's own ceiling for `NeuroLink.decisionLimits()` to report, and the
|
|
139
|
+
* server enforces them itself. The estimator and the flattening are the
|
|
140
|
+
* same functions that method uses, so a host that sizes a state against the
|
|
141
|
+
* reading measures exactly what is checked here.
|
|
136
142
|
*/
|
|
137
143
|
private assertWithinDecisionLimits;
|
|
138
144
|
/**
|
|
@@ -6,7 +6,7 @@ import { ProviderError } from "../types/index.js";
|
|
|
6
6
|
import { prepareDecisionMedia } from "../utils/decisionMedia.js";
|
|
7
7
|
import { logger } from "../utils/logger.js";
|
|
8
8
|
import { redactUrlsInText } from "../utils/logSanitize.js";
|
|
9
|
-
import {
|
|
9
|
+
import { estimateDecisionStateTokens, resolveDecisionLimitsReading, } from "../utils/decisionLimits.js";
|
|
10
10
|
/**
|
|
11
11
|
* Generous enough for a cold start (measured at 2.0–2.7s after idle on Jev)
|
|
12
12
|
* while still bounded. Every internal caller is fail-open, so this only bites
|
|
@@ -171,59 +171,6 @@ const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
|
171
171
|
}
|
|
172
172
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
173
173
|
});
|
|
174
|
-
/**
|
|
175
|
-
* `estimateTokens` assumes ~4 characters per token. That holds for English
|
|
176
|
-
* and is several times too generous for other scripts on an English
|
|
177
|
-
* tokenizer, so when a provider declares a rate, non-ASCII characters are
|
|
178
|
-
* counted at it instead. ASCII text is estimated exactly as before.
|
|
179
|
-
*/
|
|
180
|
-
function estimateDecisionStateTokens(text, nonAsciiTokensPerChar, rates = {}) {
|
|
181
|
-
const { digit, symbol, astral } = rates;
|
|
182
|
-
if (nonAsciiTokensPerChar === undefined &&
|
|
183
|
-
digit === undefined &&
|
|
184
|
-
symbol === undefined &&
|
|
185
|
-
astral === undefined) {
|
|
186
|
-
return estimateTokens(text);
|
|
187
|
-
}
|
|
188
|
-
// Each class of character is counted once, at its own rate. A class with no
|
|
189
|
-
// declared rate stays in the ordinary four-characters-a-token estimate (or,
|
|
190
|
-
// for non-ASCII, at that estimate's per-character rate), so a provider that
|
|
191
|
-
// declares none of the extra rates is estimated exactly as before.
|
|
192
|
-
const isDigit = (c) => c >= "0" && c <= "9";
|
|
193
|
-
const isSymbol = (c) => /[!-/:-@[-`{-~]/.test(c);
|
|
194
|
-
let digits = 0;
|
|
195
|
-
let symbols = 0;
|
|
196
|
-
let astrals = 0;
|
|
197
|
-
let nonAscii = 0;
|
|
198
|
-
const rest = [];
|
|
199
|
-
for (const c of text) {
|
|
200
|
-
const code = c.codePointAt(0) ?? 0;
|
|
201
|
-
if (code > 0xffff && astral !== undefined) {
|
|
202
|
-
astrals += 1;
|
|
203
|
-
}
|
|
204
|
-
else if (code > 0x7f) {
|
|
205
|
-
nonAscii += 1;
|
|
206
|
-
}
|
|
207
|
-
else if (digit !== undefined && isDigit(c)) {
|
|
208
|
-
digits += 1;
|
|
209
|
-
}
|
|
210
|
-
else if (symbol !== undefined && isSymbol(c)) {
|
|
211
|
-
symbols += 1;
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
rest.push(c);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
// Digits, punctuation and astral characters (emoji) are charged separately
|
|
218
|
-
// only when the provider declares a rate for them: a tokenizer that reads each
|
|
219
|
-
// digit, and most punctuation, on its own makes a number-heavy or JSON-heavy
|
|
220
|
-
// state several times longer than four characters a token suggests.
|
|
221
|
-
return (estimateTokens(rest.join("")) +
|
|
222
|
-
Math.ceil(digits * (digit ?? 0)) +
|
|
223
|
-
Math.ceil(symbols * (symbol ?? 0)) +
|
|
224
|
-
Math.ceil(astrals * (astral ?? 0)) +
|
|
225
|
-
Math.ceil(nonAscii * (nonAsciiTokensPerChar ?? 1 / CHARS_PER_TOKEN)));
|
|
226
|
-
}
|
|
227
174
|
/**
|
|
228
175
|
* The shared half of every "System One" decision provider — a model that takes
|
|
229
176
|
* one `state` plus typed questions and returns one typed answer each, with no
|
|
@@ -551,10 +498,19 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
551
498
|
* Refuse a request the model cannot read in full. An encoder cuts the state
|
|
552
499
|
* off past its window without saying so, so without this a decision would
|
|
553
500
|
* be made on input the model never saw — and reported as if it had.
|
|
501
|
+
*
|
|
502
|
+
* Advisory limits (`decisionLimits.advisory`) are skipped: they publish a
|
|
503
|
+
* server's own ceiling for `NeuroLink.decisionLimits()` to report, and the
|
|
504
|
+
* server enforces them itself. The estimator and the flattening are the
|
|
505
|
+
* same functions that method uses, so a host that sizes a state against the
|
|
506
|
+
* reading measures exactly what is checked here.
|
|
554
507
|
*/
|
|
555
508
|
assertWithinDecisionLimits(request, model, questionCount) {
|
|
556
|
-
const
|
|
557
|
-
|
|
509
|
+
const descriptor = PROVIDER_DESCRIPTORS_BY_NAME.get(this.providerName);
|
|
510
|
+
const limits = descriptor
|
|
511
|
+
? resolveDecisionLimitsReading(descriptor, model)
|
|
512
|
+
: null;
|
|
513
|
+
if (!limits || !limits.enforcedLocally) {
|
|
558
514
|
return;
|
|
559
515
|
}
|
|
560
516
|
const label = this.vendorLabel();
|
|
@@ -566,13 +522,8 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
566
522
|
retryable: false,
|
|
567
523
|
});
|
|
568
524
|
}
|
|
569
|
-
const
|
|
570
|
-
const
|
|
571
|
-
const stateTokens = estimateDecisionStateTokens(serializeForEstimate(request.state), modelLimits?.nonAsciiTokensPerChar ?? limits.nonAsciiTokensPerChar, {
|
|
572
|
-
digit: limits.digitTokensPerChar,
|
|
573
|
-
symbol: limits.symbolTokensPerChar,
|
|
574
|
-
astral: limits.astralTokensPerChar,
|
|
575
|
-
});
|
|
525
|
+
const { maxStateTokens } = limits;
|
|
526
|
+
const stateTokens = estimateDecisionStateTokens(request.state, limits);
|
|
576
527
|
if (stateTokens > maxStateTokens) {
|
|
577
528
|
throw this.decisionError({
|
|
578
529
|
kind: "max_tokens_exceeded",
|
|
@@ -4,6 +4,12 @@ import { SystemOneDecisionProvider } from "./systemOneDecision.js";
|
|
|
4
4
|
* The binding limit in practice: `state` plus the SINGLE LONGEST question.
|
|
5
5
|
* Measured by bisection at single-character resolution — 33,002 reported
|
|
6
6
|
* input tokens accepted, 33,003 not.
|
|
7
|
+
*
|
|
8
|
+
* The same figure sits on the TypeSafe descriptor as an ADVISORY
|
|
9
|
+
* `decisionLimits.maxStateTokens`, which is what `NeuroLink.decisionLimits()`
|
|
10
|
+
* reads; the server enforces it, NeuroLink only reports it. The descriptor
|
|
11
|
+
* cannot import this constant without a cycle, so the decide suite pins them
|
|
12
|
+
* equal instead.
|
|
7
13
|
*/
|
|
8
14
|
export declare const TYPESAFE_MAX_STATE_TOKENS = 33000;
|
|
9
15
|
/**
|
|
@@ -27,6 +27,12 @@ const GATEWAY_SPEC_VERSION = "4";
|
|
|
27
27
|
* The binding limit in practice: `state` plus the SINGLE LONGEST question.
|
|
28
28
|
* Measured by bisection at single-character resolution — 33,002 reported
|
|
29
29
|
* input tokens accepted, 33,003 not.
|
|
30
|
+
*
|
|
31
|
+
* The same figure sits on the TypeSafe descriptor as an ADVISORY
|
|
32
|
+
* `decisionLimits.maxStateTokens`, which is what `NeuroLink.decisionLimits()`
|
|
33
|
+
* reads; the server enforces it, NeuroLink only reports it. The descriptor
|
|
34
|
+
* cannot import this constant without a cycle, so the decide suite pins them
|
|
35
|
+
* equal instead.
|
|
30
36
|
*/
|
|
31
37
|
export const TYPESAFE_MAX_STATE_TOKENS = 33_000;
|
|
32
38
|
/**
|
|
@@ -75,6 +75,7 @@ export async function decideSearchPlan(query, decide, capabilities) {
|
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
const result = await decide({
|
|
78
|
+
site: "ragPlan",
|
|
78
79
|
state: { query: query.slice(0, 4000) },
|
|
79
80
|
// The map is assembled dynamically above; its members are all valid
|
|
80
81
|
// question shapes, which the cast asserts once here rather than at four
|
|
@@ -98,7 +98,7 @@ export class ClassifierRouter {
|
|
|
98
98
|
try {
|
|
99
99
|
// Both the LLM and Jev strategies can pick a model straight out of the
|
|
100
100
|
// pool — the generic path for custom/registry-less models.
|
|
101
|
-
const strategy = this.resolveStrategy();
|
|
101
|
+
const strategy = this.resolveStrategy(input);
|
|
102
102
|
const usesCandidates = (strategy === "jev" && !!this.deps.decide) ||
|
|
103
103
|
(strategy === "llm" && !!this.deps.generate);
|
|
104
104
|
const built = usesCandidates ? this.buildCandidates() : undefined;
|
|
@@ -138,7 +138,7 @@ export class ClassifierRouter {
|
|
|
138
138
|
* moment a decision provider is configured; without one the behaviour is
|
|
139
139
|
* exactly what it was before Jev existed.
|
|
140
140
|
*/
|
|
141
|
-
resolveStrategy() {
|
|
141
|
+
resolveStrategy(input) {
|
|
142
142
|
const configured = this.config.classifier ?? "auto";
|
|
143
143
|
if (configured !== "auto") {
|
|
144
144
|
return configured;
|
|
@@ -146,10 +146,11 @@ export class ClassifierRouter {
|
|
|
146
146
|
// A decision provider that is both registered AND configured is the whole
|
|
147
147
|
// activation condition — this is where "if somebody sets it we start
|
|
148
148
|
// using it" lives for routing. The caller's check also counts the
|
|
149
|
-
// credentials it was constructed with
|
|
149
|
+
// credentials it was constructed with and the request's own per-call
|
|
150
|
+
// credentials, which the decision call itself will use.
|
|
150
151
|
const hasProvider = this.deps.hasDecisionProvider
|
|
151
|
-
? this.deps.hasDecisionProvider()
|
|
152
|
-
: resolveDefaultDecisionProvider() !== undefined;
|
|
152
|
+
? this.deps.hasDecisionProvider(input.credentials)
|
|
153
|
+
: resolveDefaultDecisionProvider(input.credentials) !== undefined;
|
|
153
154
|
return this.deps.decide && hasProvider ? "jev" : "heuristic";
|
|
154
155
|
}
|
|
155
156
|
/** Run the chosen strategy; every one falls back to heuristic on failure. */
|
|
@@ -297,8 +297,15 @@ export async function classifyJev(input, decide, timeoutMs, candidates, threshol
|
|
|
297
297
|
// short conversational one.
|
|
298
298
|
// Continuity is sent as `session_bound` / `prior_messages`, never as the
|
|
299
299
|
// raw sessionId: the id is an opaque identifier that tells a third-party
|
|
300
|
-
// model nothing, so it stays in-process
|
|
300
|
+
// model nothing, so it stays in-process — as do the site stamp, the
|
|
301
|
+
// per-call credentials and the ids below, which the caller reads and the
|
|
302
|
+
// wire never sees.
|
|
301
303
|
const result = await decide({
|
|
304
|
+
site: "routing",
|
|
305
|
+
credentials: input.credentials,
|
|
306
|
+
signal: input.signal,
|
|
307
|
+
sessionId: input.sessionId,
|
|
308
|
+
requestId: input.requestId,
|
|
302
309
|
state: {
|
|
303
310
|
request: (input.prompt ?? "").slice(0, 8000),
|
|
304
311
|
estimated_input_tokens: input.estimatedInputTokens ?? 0,
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import type { ValidationSchema } from "./aliases.js";
|
|
17
17
|
import type { DecisionCallerFn } from "./decision.js";
|
|
18
|
+
import type { NeurolinkCredentials } from "./providers.js";
|
|
18
19
|
/** Coarse difficulty buckets the classifier maps a request into. */
|
|
19
20
|
export type ClassifierDifficulty = "trivial" | "simple" | "moderate" | "hard" | "expert";
|
|
20
21
|
/**
|
|
@@ -307,6 +308,16 @@ export type ClassifierRouterInput = {
|
|
|
307
308
|
sessionBound?: boolean;
|
|
308
309
|
/** Number of prior conversation messages the caller supplied, when known. */
|
|
309
310
|
priorMessageCount?: number;
|
|
311
|
+
/**
|
|
312
|
+
* The outer request's per-call credentials, forwarded to the decision call
|
|
313
|
+
* so it reaches the caller's provider account rather than the instance's.
|
|
314
|
+
* Never part of the state sent to the model.
|
|
315
|
+
*/
|
|
316
|
+
credentials?: NeurolinkCredentials;
|
|
317
|
+
/** The outer request's abort signal; an abandoned turn abandons its routing call. */
|
|
318
|
+
signal?: AbortSignal;
|
|
319
|
+
/** The outer request's id, for hook and event correlation. Never sent to the model. */
|
|
320
|
+
requestId?: string;
|
|
310
321
|
};
|
|
311
322
|
/** Enriched per-model metadata used while ranking pool members. */
|
|
312
323
|
export type ClassifierModelMeta = {
|
|
@@ -359,9 +370,11 @@ export type ClassifierRouterDeps = {
|
|
|
359
370
|
decide?: ClassifierDecideFn;
|
|
360
371
|
/**
|
|
361
372
|
* Whether a decision provider is configured for this caller, counting the
|
|
362
|
-
* credentials it was given as well as the environment
|
|
363
|
-
*
|
|
373
|
+
* credentials it was given as well as the environment — and the request's
|
|
374
|
+
* own per-call `credentials`, passed in from `ClassifierRouterInput`, so a
|
|
375
|
+
* caller that holds no decision key itself can still route a request that
|
|
376
|
+
* carries one. Omit to check the environment alone.
|
|
364
377
|
*/
|
|
365
|
-
hasDecisionProvider?: () => boolean;
|
|
378
|
+
hasDecisionProvider?: (callCredentials?: NeurolinkCredentials) => boolean;
|
|
366
379
|
logger?: ClassifierLogger;
|
|
367
380
|
};
|
package/dist/types/common.d.ts
CHANGED
|
@@ -102,6 +102,8 @@ export type NeuroLinkEvents = {
|
|
|
102
102
|
"stream:error": unknown;
|
|
103
103
|
"generation:start": unknown;
|
|
104
104
|
"generation:end": unknown;
|
|
105
|
+
"decision:before": unknown;
|
|
106
|
+
"decision:after": unknown;
|
|
105
107
|
"response:start": unknown;
|
|
106
108
|
"response:end": unknown;
|
|
107
109
|
"externalMCP:serverConnected": unknown;
|
package/dist/types/config.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { ModelPoolConfig } from "./modelPool.js";
|
|
|
14
14
|
import type { RequestRouter } from "./requestRouter.js";
|
|
15
15
|
import type { ClassifierRouterConfig } from "./classifierRouter.js";
|
|
16
16
|
import type { ContextRelevanceOptions } from "./context.js";
|
|
17
|
+
import type { DecisionHooks } from "./decision.js";
|
|
17
18
|
import type { SkillsConfig } from "./skills.js";
|
|
18
19
|
import type { KnowledgeGroundingConfig } from "./knowledge.js";
|
|
19
20
|
/**
|
|
@@ -155,6 +156,14 @@ export type NeurolinkConstructorConfig = {
|
|
|
155
156
|
* which is the same activation rule every other decision consumer uses.
|
|
156
157
|
*/
|
|
157
158
|
contextRelevance?: ContextRelevanceOptions;
|
|
159
|
+
/**
|
|
160
|
+
* Ride along on the decision calls NeuroLink already makes for routing,
|
|
161
|
+
* tool routing, compaction and RAG planning: add your own questions to the
|
|
162
|
+
* same round trip and read the answers back. Observe-only with respect to
|
|
163
|
+
* NeuroLink's behaviour, fail-open, and inert until a decision provider is
|
|
164
|
+
* configured. See {@link DecisionHooks}.
|
|
165
|
+
*/
|
|
166
|
+
decisionHooks?: DecisionHooks;
|
|
158
167
|
/**
|
|
159
168
|
* Native skills: versioned, discoverable instruction packs (SOPs,
|
|
160
169
|
* playbooks) with progressive disclosure. When enabled, each
|
package/dist/types/context.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { ExecutionContext } from "../types/tools.js";
|
|
6
6
|
import type { JsonObject } from "./common.js";
|
|
7
|
-
import type { ChatMessage, ConversationMemoryConfig } from "./conversation.js";
|
|
7
|
+
import type { ChatMessage, ConversationMemoryConfig, ToolReplayMode } from "./conversation.js";
|
|
8
8
|
import type { DecisionCallerFn } from "./decision.js";
|
|
9
9
|
/**
|
|
10
10
|
* Base context type for all AI operations
|
|
@@ -237,6 +237,13 @@ export type BudgetCheckParams = {
|
|
|
237
237
|
}>;
|
|
238
238
|
/** Compaction trigger threshold (0.0-1.0). Default: 0.80 */
|
|
239
239
|
compactionThreshold?: number;
|
|
240
|
+
/**
|
|
241
|
+
* How this request replays stored `tool_call` / `tool_result` rows
|
|
242
|
+
* (`replayToolSteps`). When set, tool rows are estimated at the size they
|
|
243
|
+
* reach the model in that mode rather than at stored size; when omitted
|
|
244
|
+
* they count at stored size.
|
|
245
|
+
*/
|
|
246
|
+
toolReplayMode?: ToolReplayMode;
|
|
240
247
|
};
|
|
241
248
|
/** A file prepared for potential summarization. */
|
|
242
249
|
export type FileForSummarization = {
|
|
@@ -44,12 +44,99 @@ export type { ModelMessage, SystemModelMessage, UserModelMessage, AssistantModel
|
|
|
44
44
|
* uniqueness checks see.
|
|
45
45
|
*/
|
|
46
46
|
export type StorageConfig = HippocampusStorageConfig;
|
|
47
|
+
/**
|
|
48
|
+
* How stored `tool_call` / `tool_result` rows are replayed into the prompt
|
|
49
|
+
* on later turns of a session (see `ConversationMemoryConfig.replayToolSteps`).
|
|
50
|
+
*
|
|
51
|
+
* - `"full"`: replayed as real tool-call / tool-result turns, so the model
|
|
52
|
+
* sees the exact call and its output.
|
|
53
|
+
* - `"marker"`: a compact text line per tool call
|
|
54
|
+
* (`[called <tool> → ok]` / `[called <tool> → error]`) folded into the
|
|
55
|
+
* assistant turn, so the model knows what it did without the payload.
|
|
56
|
+
* - `"off"`: tool rows are dropped from the prompt, as before this option.
|
|
57
|
+
*/
|
|
58
|
+
export type ToolReplayMode = "full" | "marker" | "off";
|
|
59
|
+
/**
|
|
60
|
+
* The fields the tool-step replay reads off a stored history row. Structural
|
|
61
|
+
* rather than `ChatMessage` because the multimodal builder's history is typed
|
|
62
|
+
* as `{ role: string; content: string }` while carrying the same rows.
|
|
63
|
+
*/
|
|
64
|
+
export type ToolHistoryRow = {
|
|
65
|
+
/** Row id; a `repair-*` prefix marks a placeholder `repairToolPairs` invented. */
|
|
66
|
+
id?: string;
|
|
67
|
+
role: string;
|
|
68
|
+
content?: unknown;
|
|
69
|
+
tool?: string;
|
|
70
|
+
toolCallId?: string;
|
|
71
|
+
args?: Record<string, unknown>;
|
|
72
|
+
result?: {
|
|
73
|
+
success?: boolean;
|
|
74
|
+
error?: string;
|
|
75
|
+
};
|
|
76
|
+
metadata?: {
|
|
77
|
+
stepIndex?: number;
|
|
78
|
+
isSummary?: boolean;
|
|
79
|
+
isSkill?: boolean;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* What a replayed step tells the model about the call's outcome: `"ok"`,
|
|
84
|
+
* `"error"` (the result was a failure — thrown, rejected, or an `isError`
|
|
85
|
+
* payload), or `"unknown"` when the real result was lost to compaction and
|
|
86
|
+
* `repairToolPairs` filled the gap with a placeholder.
|
|
87
|
+
*/
|
|
88
|
+
export type ToolReplayOutcome = "ok" | "error" | "unknown";
|
|
89
|
+
/** One paired call/result from a session's history, ready to replay. */
|
|
90
|
+
export type ToolReplayStep = {
|
|
91
|
+
callId: string;
|
|
92
|
+
toolName: string;
|
|
93
|
+
args: Record<string, unknown>;
|
|
94
|
+
output: string;
|
|
95
|
+
outcome: ToolReplayOutcome;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* One tool call as handed to conversation-memory storage after a loop step.
|
|
99
|
+
* The stores read `args` (Redis also `arguments` / `parameters`), so the
|
|
100
|
+
* field is named for what they read — an `input` key here is silently
|
|
101
|
+
* persisted as `{}`, which is exactly the bug this type exists to prevent.
|
|
102
|
+
*/
|
|
103
|
+
export type MemoryToolCallRecord = {
|
|
104
|
+
toolCallId?: string;
|
|
105
|
+
toolName?: string;
|
|
106
|
+
args?: Record<string, unknown>;
|
|
107
|
+
/** Zero-based loop step, persisted as `metadata.stepIndex` by stores that keep it. */
|
|
108
|
+
stepIndex?: number;
|
|
109
|
+
/** Gemini 3 thought signature riding on the step's first call. */
|
|
110
|
+
thoughtSignature?: string;
|
|
111
|
+
timestamp?: Date;
|
|
112
|
+
};
|
|
113
|
+
/** One tool result as handed to conversation-memory storage after a loop step. */
|
|
114
|
+
export type MemoryToolResultRecord = {
|
|
115
|
+
toolCallId?: string;
|
|
116
|
+
toolName?: string;
|
|
117
|
+
output?: unknown;
|
|
118
|
+
/** Legacy alias of `output`; stores read whichever is present. */
|
|
119
|
+
result?: unknown;
|
|
120
|
+
/** Set when the execution failed; persisted as `result.error` and `success: false`. */
|
|
121
|
+
error?: string;
|
|
122
|
+
stepIndex?: number;
|
|
123
|
+
timestamp?: Date;
|
|
124
|
+
};
|
|
47
125
|
/**
|
|
48
126
|
* Configuration for conversation memory feature
|
|
49
127
|
*/
|
|
50
128
|
export type ConversationMemoryConfig = {
|
|
51
129
|
/** Enable conversation memory feature */
|
|
52
130
|
enabled: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* How a session's stored tool steps are replayed into later prompts.
|
|
133
|
+
* Default `"marker"`. `"full"` replays real tool-call / tool-result turns
|
|
134
|
+
* (costs tokens per replayed step and, on Anthropic, changes the prompt
|
|
135
|
+
* prefix ahead of the cached system breakpoint); `"off"` restores the
|
|
136
|
+
* previous behaviour of dropping them. Overridable per request via
|
|
137
|
+
* `replayToolSteps` on the generate / stream options.
|
|
138
|
+
*/
|
|
139
|
+
replayToolSteps?: ToolReplayMode;
|
|
53
140
|
/** Maximum number of sessions to keep in memory (default: 50) */
|
|
54
141
|
maxSessions?: number;
|
|
55
142
|
/** Enable automatic summarization */
|
package/dist/types/decision.d.ts
CHANGED
|
@@ -182,6 +182,56 @@ export type DecisionLimits = {
|
|
|
182
182
|
}>>;
|
|
183
183
|
/** What the provider accepts besides text. Absent means text only. */
|
|
184
184
|
media?: DecisionMediaLimits;
|
|
185
|
+
/**
|
|
186
|
+
* True when these figures describe the provider's own server-side ceiling,
|
|
187
|
+
* published so callers can plan against it, and NeuroLink does NOT refuse
|
|
188
|
+
* locally — the server does, with its own `max_tokens_exceeded`. Absent or
|
|
189
|
+
* false means NeuroLink refuses an over-limit request before any network
|
|
190
|
+
* call. `NeuroLink.decisionLimits()` reports the distinction as
|
|
191
|
+
* `enforcedLocally`.
|
|
192
|
+
*/
|
|
193
|
+
advisory?: boolean;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* A provider's decision limits resolved for one model — the figure the
|
|
197
|
+
* pre-flight check compares against, flattened from a descriptor's
|
|
198
|
+
* `decisionLimits` with the per-model override applied.
|
|
199
|
+
*
|
|
200
|
+
* Returned by `NeuroLink.decisionLimits()` so a host extending NeuroLink's
|
|
201
|
+
* questions (see {@link DecisionHooks}) can size its additions before the
|
|
202
|
+
* call, instead of discovering the cap when the whole request is refused.
|
|
203
|
+
*/
|
|
204
|
+
export type DecisionLimitsReading = {
|
|
205
|
+
provider: string;
|
|
206
|
+
model: string;
|
|
207
|
+
maxStateTokens: number;
|
|
208
|
+
/**
|
|
209
|
+
* Absent when the provider caps a request by tokens (or bytes) rather
|
|
210
|
+
* than by question count — TypeSafe and XOR; `Infinity` is never used.
|
|
211
|
+
*/
|
|
212
|
+
maxQuestions?: number;
|
|
213
|
+
nonAsciiTokensPerChar?: number;
|
|
214
|
+
/** Tokens charged per ASCII digit; see {@link DecisionLimits.digitTokensPerChar}. */
|
|
215
|
+
digitTokensPerChar?: number;
|
|
216
|
+
/** Tokens charged per ASCII punctuation/symbol; see {@link DecisionLimits.symbolTokensPerChar}. */
|
|
217
|
+
symbolTokensPerChar?: number;
|
|
218
|
+
/** Tokens charged per astral character; see {@link DecisionLimits.astralTokensPerChar}. */
|
|
219
|
+
astralTokensPerChar?: number;
|
|
220
|
+
/** What the provider accepts besides text; absent means text only. */
|
|
221
|
+
media?: DecisionMediaLimits;
|
|
222
|
+
/**
|
|
223
|
+
* Whether NeuroLink itself refuses an over-limit request before any network
|
|
224
|
+
* call (Laya), or merely reports the server's ceiling (TypeSafe).
|
|
225
|
+
*/
|
|
226
|
+
enforcedLocally: boolean;
|
|
227
|
+
};
|
|
228
|
+
/** What `NeuroLink.decisionLimits()` accepts: the same selection `decide()` takes. */
|
|
229
|
+
export type DecisionLimitsQuery = {
|
|
230
|
+
/** Provider name or alias; defaults to the configured decision provider. */
|
|
231
|
+
provider?: string;
|
|
232
|
+
/** Defaults to the provider's configured model, then its default model. */
|
|
233
|
+
model?: string;
|
|
234
|
+
credentials?: NeurolinkCredentials;
|
|
185
235
|
};
|
|
186
236
|
export type DecisionMediaLimits = {
|
|
187
237
|
/** Most images one request may carry. */
|
|
@@ -287,6 +337,162 @@ export type DecisionOptions = DecisionRequest & {
|
|
|
287
337
|
/** Per-call credential overrides, as `generate()` takes. */
|
|
288
338
|
credentials?: NeurolinkCredentials;
|
|
289
339
|
};
|
|
340
|
+
/**
|
|
341
|
+
* The built-in consumers of the `decide` inference type — one name per place
|
|
342
|
+
* NeuroLink asks a decision model something on its own behalf. Each consumer
|
|
343
|
+
* stamps its request with its site, which is what lets a host's
|
|
344
|
+
* {@link DecisionHooks} tell the calls apart and lets telemetry attribute them.
|
|
345
|
+
*
|
|
346
|
+
* - `routing` — the classifier router (difficulty, capabilities, risk, model, context scope)
|
|
347
|
+
* - `toolRouting` — one yes/no per MCP server
|
|
348
|
+
* - `contextRelevance` — one yes/no per earlier message, compaction stage 0
|
|
349
|
+
* - `summaryGate` — accept or reject a generated summary, compaction stage 3
|
|
350
|
+
* - `ragPlan` — per-query retrieval plan (`RAGPipeline` only)
|
|
351
|
+
*/
|
|
352
|
+
export type DecisionSite = "routing" | "toolRouting" | "contextRelevance" | "summaryGate" | "ragPlan";
|
|
353
|
+
/**
|
|
354
|
+
* Options an internal decision site hands its caller: the public
|
|
355
|
+
* {@link DecisionOptions} plus in-process context that never reaches the
|
|
356
|
+
* wire. A bound `NeuroLink.tryDecide` accepts it unchanged; a host wrapping
|
|
357
|
+
* one can read `site` to see which consumer is asking.
|
|
358
|
+
*/
|
|
359
|
+
export type DecisionCallerOptions = DecisionOptions & {
|
|
360
|
+
/** Which built-in consumer is asking. Unset on a host's own `tryDecide` call. */
|
|
361
|
+
site?: DecisionSite;
|
|
362
|
+
/** The conversation the outer request belongs to. Never sent to the model. */
|
|
363
|
+
sessionId?: string;
|
|
364
|
+
/** The outer request's id. Never sent to the model. */
|
|
365
|
+
requestId?: string;
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* What an internal decision site inherits from the `generate()` / `stream()`
|
|
369
|
+
* call it runs inside: the per-call credentials (so the decision goes to the
|
|
370
|
+
* caller's provider account, not the instance's), the abort signal, and the
|
|
371
|
+
* ids a host's {@link DecisionHooks} may want to correlate on.
|
|
372
|
+
*/
|
|
373
|
+
export type DecisionSiteContext = {
|
|
374
|
+
credentials?: NeurolinkCredentials;
|
|
375
|
+
signal?: AbortSignal;
|
|
376
|
+
sessionId?: string;
|
|
377
|
+
requestId?: string;
|
|
378
|
+
};
|
|
379
|
+
/** What a site call adds to its decision span beyond the request itself. */
|
|
380
|
+
export type DecisionSiteAttributes = {
|
|
381
|
+
site: DecisionSite;
|
|
382
|
+
/** Questions a host's `extendQuestions` hook added; 0 without hooks. */
|
|
383
|
+
hostQuestionCount: number;
|
|
384
|
+
};
|
|
385
|
+
/**
|
|
386
|
+
* What a {@link DecisionHooks} callback sees before the call: the site, the
|
|
387
|
+
* state NeuroLink is about to send, and NeuroLink's own questions — as
|
|
388
|
+
* copies, so a hook that edits them cannot reach the wire request.
|
|
389
|
+
*/
|
|
390
|
+
export type DecisionHookContext = {
|
|
391
|
+
site: DecisionSite;
|
|
392
|
+
state: DecisionState;
|
|
393
|
+
/** NeuroLink's own questions for this site, keyed by its own ids. */
|
|
394
|
+
questions: DecisionQuestionMap;
|
|
395
|
+
sessionId?: string;
|
|
396
|
+
requestId?: string;
|
|
397
|
+
};
|
|
398
|
+
/** What `onAnswers` receives once the decision has returned. */
|
|
399
|
+
export type DecisionHookAnswersContext = DecisionHookContext & {
|
|
400
|
+
/**
|
|
401
|
+
* The host's answers, keyed by the ids the host used in `extendQuestions`
|
|
402
|
+
* — the namespacing applied on the wire is undone here. Empty when the
|
|
403
|
+
* host added no questions.
|
|
404
|
+
*/
|
|
405
|
+
answers: DecisionAnswerMap;
|
|
406
|
+
/** The whole result, including NeuroLink's own answers under its own ids. */
|
|
407
|
+
result: DecisionResult;
|
|
408
|
+
};
|
|
409
|
+
/**
|
|
410
|
+
* Ride along on the decision calls NeuroLink already makes.
|
|
411
|
+
*
|
|
412
|
+
* Latency on a decision model is flat in question count, so a host with its
|
|
413
|
+
* own yes/no or choice questions about the same request pays nothing to ask
|
|
414
|
+
* them in the round trip NeuroLink is making anyway — one request per
|
|
415
|
+
* {@link DecisionSite}, never a second. The host's questions are namespaced
|
|
416
|
+
* on the wire so they can never collide with NeuroLink's, capped so they can
|
|
417
|
+
* never push the request past the provider's question limit (which would
|
|
418
|
+
* refuse the whole call and degrade NeuroLink's own routing), and answered
|
|
419
|
+
* back under the host's original ids.
|
|
420
|
+
*
|
|
421
|
+
* Both hooks are fail-open: a hook that throws is logged and the decision
|
|
422
|
+
* proceeds with NeuroLink's own questions, and neither can change what
|
|
423
|
+
* NeuroLink does with its own answers — every value a hook or listener is
|
|
424
|
+
* handed is a copy. Without hooks, the decision payload (state and
|
|
425
|
+
* questions) is exactly what the consumer built; the outer request's
|
|
426
|
+
* per-call `credentials` are still forwarded, so the account and base URL
|
|
427
|
+
* on the wire can differ from the instance's. Hooks are inert without a
|
|
428
|
+
* decision provider: neither runs, and no event fires.
|
|
429
|
+
*/
|
|
430
|
+
export type DecisionHooks = {
|
|
431
|
+
/**
|
|
432
|
+
* Return extra questions to send with this site's call, or `undefined` to
|
|
433
|
+
* add none. An invalid question is dropped with a warning; questions past
|
|
434
|
+
* the provider's cap are dropped from the end, also with a warning.
|
|
435
|
+
*/
|
|
436
|
+
extendQuestions?: (context: DecisionHookContext) => DecisionQuestionMap | undefined | Promise<DecisionQuestionMap | undefined>;
|
|
437
|
+
/**
|
|
438
|
+
* Called with the host's answers (under the host's ids) and the full result
|
|
439
|
+
* whenever a site call returns one. Observe-only: it receives a copy, so
|
|
440
|
+
* mutating it cannot reach the answers NeuroLink's own consumer reads.
|
|
441
|
+
*/
|
|
442
|
+
onAnswers?: (context: DecisionHookAnswersContext) => void | Promise<void>;
|
|
443
|
+
/**
|
|
444
|
+
* Upper bound, in milliseconds, on each hook call. Both hooks sit on the
|
|
445
|
+
* request path (routing, tool routing, compaction, RAG planning), so a
|
|
446
|
+
* hook that hangs would stall the turn; past this bound the call proceeds
|
|
447
|
+
* as if the hook had returned nothing, with a warning. Default 2000. Must
|
|
448
|
+
* be a finite number from 1 to {@link MAX_DECISION_HOOK_TIMEOUT_MS};
|
|
449
|
+
* anything else (including `Infinity`) falls back to the default with one
|
|
450
|
+
* warning, since a timer given such a delay fires immediately.
|
|
451
|
+
*/
|
|
452
|
+
hookTimeoutMs?: number;
|
|
453
|
+
};
|
|
454
|
+
/** Default bound on a `DecisionHooks` callback, in milliseconds. */
|
|
455
|
+
export declare const DEFAULT_DECISION_HOOK_TIMEOUT_MS = 2000;
|
|
456
|
+
/**
|
|
457
|
+
* Largest `hookTimeoutMs` a timer honours (2^31 − 1 ms, about 24.8 days).
|
|
458
|
+
* A delay past it — or `Infinity`, `NaN`, 0, a negative — fires at once,
|
|
459
|
+
* so such a value falls back to the default instead of timing every hook
|
|
460
|
+
* out immediately.
|
|
461
|
+
*/
|
|
462
|
+
export declare const MAX_DECISION_HOOK_TIMEOUT_MS = 2147483647;
|
|
463
|
+
/**
|
|
464
|
+
* Payload of the `decision:before` event, emitted once per site call. `state`
|
|
465
|
+
* and `questions` are copies: a listener that edits them changes neither
|
|
466
|
+
* the request on the wire nor the consumer's own question objects.
|
|
467
|
+
*/
|
|
468
|
+
export type DecisionBeforeEvent = {
|
|
469
|
+
site: DecisionSite;
|
|
470
|
+
state: DecisionState;
|
|
471
|
+
/** Everything about to be sent, NeuroLink's own questions plus the host's (namespaced). */
|
|
472
|
+
questions: DecisionQuestionMap;
|
|
473
|
+
hostQuestionCount: number;
|
|
474
|
+
sessionId?: string;
|
|
475
|
+
requestId?: string;
|
|
476
|
+
};
|
|
477
|
+
/**
|
|
478
|
+
* Payload of the `decision:after` event, emitted once per site call whether
|
|
479
|
+
* or not it produced a result — a decision path that stopped working must
|
|
480
|
+
* stay observable. `result` is null when an emitted call failed; with no
|
|
481
|
+
* decision provider configured, no event fires at all.
|
|
482
|
+
*/
|
|
483
|
+
export type DecisionAfterEvent = {
|
|
484
|
+
site: DecisionSite;
|
|
485
|
+
/** NeuroLink's own answers, under its own ids. Empty when `result` is null. */
|
|
486
|
+
answers: DecisionAnswerMap;
|
|
487
|
+
/** The host's answers, under the host's original ids. Empty when `result` is null. */
|
|
488
|
+
hostAnswers: DecisionAnswerMap;
|
|
489
|
+
latencyMs: number;
|
|
490
|
+
provider?: string;
|
|
491
|
+
model?: string;
|
|
492
|
+
result: DecisionResult | null;
|
|
493
|
+
sessionId?: string;
|
|
494
|
+
requestId?: string;
|
|
495
|
+
};
|
|
290
496
|
/**
|
|
291
497
|
* Injected fail-open decision caller — typically a bound `NeuroLink.tryDecide`,
|
|
292
498
|
* which returns `null` on any failure rather than throwing.
|
|
@@ -297,8 +503,11 @@ export type DecisionOptions = DecisionRequest & {
|
|
|
297
503
|
* makes "no decision provider configured" indistinguishable from "the call
|
|
298
504
|
* failed" at every call site: both are `null`, and both mean *carry on as
|
|
299
505
|
* before*.
|
|
506
|
+
*
|
|
507
|
+
* The options carry a {@link DecisionSite} stamp; a bound `tryDecide` routes
|
|
508
|
+
* a stamped request through the host's {@link DecisionHooks}.
|
|
300
509
|
*/
|
|
301
|
-
export type DecisionCallerFn = (options:
|
|
510
|
+
export type DecisionCallerFn = (options: DecisionCallerOptions) => Promise<DecisionResult | null>;
|
|
302
511
|
/**
|
|
303
512
|
* Shared gate for acting on a yes/no answer.
|
|
304
513
|
*
|
package/dist/types/decision.js
CHANGED
|
@@ -18,4 +18,12 @@
|
|
|
18
18
|
* `src/lib/types/` (CLAUDE.md rule 9). Note `evaluate`/`Evaluation*` is a
|
|
19
19
|
* different, unrelated feature — scoring a generated response with RAGAS.
|
|
20
20
|
*/
|
|
21
|
-
|
|
21
|
+
/** Default bound on a `DecisionHooks` callback, in milliseconds. */
|
|
22
|
+
export const DEFAULT_DECISION_HOOK_TIMEOUT_MS = 2000;
|
|
23
|
+
/**
|
|
24
|
+
* Largest `hookTimeoutMs` a timer honours (2^31 − 1 ms, about 24.8 days).
|
|
25
|
+
* A delay past it — or `Infinity`, `NaN`, 0, a negative — fires at once,
|
|
26
|
+
* so such a value falls back to the default instead of timing every hook
|
|
27
|
+
* out immediately.
|
|
28
|
+
*/
|
|
29
|
+
export const MAX_DECISION_HOOK_TIMEOUT_MS = 2_147_483_647;
|