@juspay/neurolink 12.49.6 → 12.49.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -2
- package/README.md +6 -5
- package/dist/browser/neurolink.min.js +361 -361
- package/dist/context/contextDecision.js +3 -3
- package/dist/core/toolRoutingDecision.js +9 -30
- package/dist/factories/providerDescriptors.js +26 -17
- package/dist/models/manifests/cloudflareClef.d.ts +4 -3
- package/dist/models/manifests/cloudflareClef.js +4 -3
- package/dist/providers/cloudflareClef.d.ts +4 -3
- package/dist/providers/cloudflareClef.js +20 -3
- package/dist/providers/systemOneDecision.d.ts +4 -2
- package/dist/providers/systemOneDecision.js +8 -5
- package/dist/utils/providerConfig.js +1 -1
- package/dist/voice/providers/CartesiaTTS.js +1 -1
- package/docs-site/static/search-index.json +23 -23
- package/package.json +1 -1
|
@@ -101,8 +101,8 @@ export async function selectIrrelevantMessages(messages, currentRequest, decide,
|
|
|
101
101
|
type: "boolean",
|
|
102
102
|
instructions: RELEVANCE_INSTRUCTIONS,
|
|
103
103
|
criteria: {
|
|
104
|
-
true: `The current request depends on
|
|
105
|
-
false:
|
|
104
|
+
true: `The current request depends on conversation[${position}].`,
|
|
105
|
+
false: `The current request does not depend on conversation[${position}].`,
|
|
106
106
|
},
|
|
107
107
|
};
|
|
108
108
|
});
|
|
@@ -230,7 +230,7 @@ export async function summaryPreservesContext(summary, replacedMessages, decide,
|
|
|
230
230
|
}
|
|
231
231
|
return true;
|
|
232
232
|
}
|
|
233
|
-
/** Bounded text
|
|
233
|
+
/** Bounded text stored once in decision state and referenced by position. */
|
|
234
234
|
function preview(message) {
|
|
235
235
|
return message.content.slice(0, MAX_MESSAGE_CHARS);
|
|
236
236
|
}
|
|
@@ -48,42 +48,21 @@ const MAX_SERVERS = 200;
|
|
|
48
48
|
/**
|
|
49
49
|
* The question wording, and why it is this wording.
|
|
50
50
|
*
|
|
51
|
-
* The
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* unrelated servers averaged p=0.31 and reached 0.80, so at the 0.6 drop bar
|
|
56
|
-
* only 12 of 39 unneeded servers were dropped.
|
|
57
|
-
*
|
|
58
|
-
* Naming the server, asking in the present tense about what carrying out the
|
|
59
|
-
* request *involves*, and splitting the bundled `false` criterion into a single
|
|
60
|
-
* claim moved unrelated servers to a mean of p=0.03 with a maximum of 0.35 —
|
|
61
|
-
* **37 of 39** dropped at the same bar, still with zero wrong drops.
|
|
62
|
-
*
|
|
63
|
-
* The lesson generalises: a decision model reads literally, and an `or` in a
|
|
64
|
-
* criterion is two questions wearing one coat.
|
|
51
|
+
* The server capability already exists once in structured state. Referencing
|
|
52
|
+
* that indexed field avoids copying the same long description into the
|
|
53
|
+
* instruction and both criteria while still binding each question to one
|
|
54
|
+
* concrete server.
|
|
65
55
|
*/
|
|
66
|
-
function serverQuestion(server) {
|
|
67
|
-
const does = uncapitalize(describeServer(server));
|
|
56
|
+
function serverQuestion(server, index) {
|
|
68
57
|
return {
|
|
69
58
|
type: "boolean",
|
|
70
|
-
instructions: `
|
|
59
|
+
instructions: `Does the request need the "${server.id}" server? Read available_servers[${index}].does.`,
|
|
71
60
|
criteria: {
|
|
72
|
-
true:
|
|
73
|
-
false:
|
|
61
|
+
true: "Needed.",
|
|
62
|
+
false: "Not needed.",
|
|
74
63
|
},
|
|
75
64
|
};
|
|
76
65
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Lowercase only the FIRST character. A plain `toLowerCase()` would turn
|
|
79
|
-
* "Read and write GitHub issues" into "…github issues", and the proper noun is
|
|
80
|
-
* often the strongest signal in the sentence.
|
|
81
|
-
*/
|
|
82
|
-
function uncapitalize(text) {
|
|
83
|
-
// charAt rather than an index: it returns "" for an empty string, so this
|
|
84
|
-
// needs no length guard and no non-null assertion.
|
|
85
|
-
return text.charAt(0).toLowerCase() + text.slice(1);
|
|
86
|
-
}
|
|
87
66
|
/**
|
|
88
67
|
* Ask one yes/no question per routable server and return the servers to keep.
|
|
89
68
|
*
|
|
@@ -98,7 +77,7 @@ export async function selectServersByDecision(userQuery, routableServers, decide
|
|
|
98
77
|
const servers = routableServers.slice(0, MAX_SERVERS);
|
|
99
78
|
const questions = {};
|
|
100
79
|
servers.forEach((server, index) => {
|
|
101
|
-
questions[decisionKey(SERVER_NAMESPACE, index)] = serverQuestion(server);
|
|
80
|
+
questions[decisionKey(SERVER_NAMESPACE, index)] = serverQuestion(server, index);
|
|
102
81
|
});
|
|
103
82
|
const result = await decide({
|
|
104
83
|
site: "toolRouting",
|
|
@@ -655,25 +655,34 @@ const HAND_DESCRIPTORS = [
|
|
|
655
655
|
// Measured 2026-10-03: 0.3 to 1.0 s for a small request, 1.1 s for 64 questions on
|
|
656
656
|
// clef-flash and 1.3 s on clef, and 2.2 s at most for any of 60 requests
|
|
657
657
|
// sent at once. On 2026-10-04, 64 questions took 1.5 s (flash) / 2.3 s (clef).
|
|
658
|
-
// 5s covers
|
|
659
|
-
//
|
|
658
|
+
// 5s covers a small request and keeps a fail-open consumer from waiting on a
|
|
659
|
+
// stuck call. The state adds time, so the provider adds 250 ms for each full
|
|
660
|
+
// 1,000 estimated state tokens (9s at the limit below). Measured 2026-10-07 at
|
|
661
|
+
// about 0.2 s per 1,000 input tokens from 50,000 tokens up on both models
|
|
662
|
+
// (clef-flash 4.9 s at 52,154 tokens, clef 11.8 s, clef-flash 19.2 s at
|
|
663
|
+
// 86,819 and 30.9 s at 158,153), and 1.7 to 1.9 s at 20,954 tokens.
|
|
660
664
|
timeouts: { decideMs: 5_000 },
|
|
661
|
-
//
|
|
662
|
-
// (hosted service or model: unknown),
|
|
663
|
-
// 1,500-
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
//
|
|
670
|
-
//
|
|
671
|
-
//
|
|
672
|
-
//
|
|
673
|
-
//
|
|
674
|
-
//
|
|
665
|
+
// On 2026-10-03 and 2026-10-04 the Workers AI endpoint ignored state text
|
|
666
|
+
// past about 2,048 tokens (hosted service or model: unknown), and the local
|
|
667
|
+
// limit was 1,500. On 2026-10-07 it no longer did: clef-flash read a fact at
|
|
668
|
+
// the very end of a 190,153-token digit array and of an 86,819-token prose
|
|
669
|
+
// state, and clef read a 52,154-token prose state, while clef answered
|
|
670
|
+
// HTTP 529 (code 5012, "Clef inference failed") after 15 s for the digit
|
|
671
|
+
// array. The limit is now 16,000 estimated tokens. The estimate runs 35%
|
|
672
|
+
// over real tokens for prose and 14% under for a digit array, whose commas
|
|
673
|
+
// cost a token each, so the limit is at most about 18,400 real tokens, 2.8
|
|
674
|
+
// times less than the 52,154 clef read in full. It also bounds how long a
|
|
675
|
+
// fail-open consumer can wait (the timeout above). The service changed twice
|
|
676
|
+
// in four days, so live canaries 19.10 and 19.10b read a fact at the end of a
|
|
677
|
+
// state at this limit on both models. Digits cost 1, ASCII symbols 0.75, BMP
|
|
678
|
+
// non-ASCII 1.5 and astral characters 3: the rates now approximate real
|
|
679
|
+
// tokens and no longer stand for a cut.
|
|
680
|
+
// The 2026-10-04 follow-up on the old cut (logs, number lists, digit arrays
|
|
681
|
+
// and compact JSON on both models; natural Chinese, Japanese, Korean and
|
|
682
|
+
// Hindi prose and emoji-rich English on clef; a many-key object) is in the
|
|
683
|
+
// guide; it explains where the rates come from, not what limit applies now.
|
|
675
684
|
decisionLimits: {
|
|
676
|
-
maxStateTokens:
|
|
685
|
+
maxStateTokens: 16_000,
|
|
677
686
|
maxQuestions: 64,
|
|
678
687
|
nonAsciiTokensPerChar: 1.5,
|
|
679
688
|
digitTokensPerChar: 1,
|
|
@@ -4,9 +4,10 @@ import type { ProviderModelManifest } from "../../types/index.js";
|
|
|
4
4
|
* generation, and not the Workers AI text models of the `cloudflare` provider.
|
|
5
5
|
*
|
|
6
6
|
* `contextWindow` is the figure Cloudflare documents (65,536 tokens). The
|
|
7
|
-
* Workers AI endpoint
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Workers AI endpoint ignored state text past about 2,048 tokens until
|
|
8
|
+
* 2026-10-04 and read far more on 2026-10-07 (hosted service or model:
|
|
9
|
+
* unknown), so NeuroLink enforces the limit on the descriptor's
|
|
10
|
+
* `decisionLimits`, not this. `vision` describes `generate()`
|
|
10
11
|
* input, which this provider does not serve; its image input is
|
|
11
12
|
* `DecisionRequest.images`.
|
|
12
13
|
*
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* generation, and not the Workers AI text models of the `cloudflare` provider.
|
|
4
4
|
*
|
|
5
5
|
* `contextWindow` is the figure Cloudflare documents (65,536 tokens). The
|
|
6
|
-
* Workers AI endpoint
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Workers AI endpoint ignored state text past about 2,048 tokens until
|
|
7
|
+
* 2026-10-04 and read far more on 2026-10-07 (hosted service or model:
|
|
8
|
+
* unknown), so NeuroLink enforces the limit on the descriptor's
|
|
9
|
+
* `decisionLimits`, not this. `vision` describes `generate()`
|
|
9
10
|
* input, which this provider does not serve; its image input is
|
|
10
11
|
* `DecisionRequest.images`.
|
|
11
12
|
*
|
|
@@ -13,9 +13,9 @@ import { SystemOneDecisionProvider } from "./systemOneDecision.js";
|
|
|
13
13
|
* reads (`CLOUDFLARE_API_KEY`, `CLOUDFLARE_ACCOUNT_ID`), so a host that has
|
|
14
14
|
* configured that provider has also configured this one.
|
|
15
15
|
*
|
|
16
|
-
* The Workers AI
|
|
17
|
-
*
|
|
18
|
-
* on the descriptor for the measured figures.
|
|
16
|
+
* The Workers AI state window changed in October 2026: about 2,048 tokens until
|
|
17
|
+
* 2026-10-04, far more on 2026-10-07 (hosted service or model: unknown). See
|
|
18
|
+
* `decisionLimits` on the descriptor for the measured figures.
|
|
19
19
|
*
|
|
20
20
|
* @see https://developers.cloudflare.com/workers-ai/models/clef/
|
|
21
21
|
*/
|
|
@@ -30,6 +30,7 @@ export declare class CloudflareClefProvider extends SystemOneDecisionProvider {
|
|
|
30
30
|
protected decisionApiKey(): string;
|
|
31
31
|
protected missingKeyMessage(): string;
|
|
32
32
|
protected missingConfigMessage(): string | undefined;
|
|
33
|
+
protected defaultTimeoutMs(_questionCount: number, stateTokens: number): number | undefined;
|
|
33
34
|
/** The model is part of the path, so the endpoint depends on the model asked for. */
|
|
34
35
|
protected decisionEndpoint(model: string): string;
|
|
35
36
|
protected decisionHeaders(): Record<string, string>;
|
|
@@ -5,6 +5,15 @@ import { getProviderModel } from "../utils/providerConfig.js";
|
|
|
5
5
|
import { isRecord, redactCredentials, SystemOneDecisionProvider, } from "./systemOneDecision.js";
|
|
6
6
|
const CLOUDFLARE_DEFAULT_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
7
7
|
const MODEL_PREFIX = "@cf/cloudflare/";
|
|
8
|
+
/**
|
|
9
|
+
* Added to the descriptor's `decideMs` for each full 1,000 estimated state
|
|
10
|
+
* tokens. Latency follows the input size: measured 2026-10-07 at about 0.2 s per
|
|
11
|
+
* 1,000 input tokens from 50,000 tokens up on both models, so a state at the
|
|
12
|
+
* descriptor's limit (16,000 estimated, about 18,400 real tokens at most) gets
|
|
13
|
+
* 4 s on top of the 5 s allowance, and a flat 5 s would cut off a request that
|
|
14
|
+
* is merely large and retry it into the same wait.
|
|
15
|
+
*/
|
|
16
|
+
const TIMEOUT_MS_PER_THOUSAND_STATE_TOKENS = 250;
|
|
8
17
|
/**
|
|
9
18
|
* Cloudflare account ids are 32 hex digits. The pattern is wider so that a
|
|
10
19
|
* change of format needs no release, but it still keeps `/`, `.`, `?` and
|
|
@@ -188,9 +197,9 @@ function cloudflareErrorKind(status, code) {
|
|
|
188
197
|
* reads (`CLOUDFLARE_API_KEY`, `CLOUDFLARE_ACCOUNT_ID`), so a host that has
|
|
189
198
|
* configured that provider has also configured this one.
|
|
190
199
|
*
|
|
191
|
-
* The Workers AI
|
|
192
|
-
*
|
|
193
|
-
* on the descriptor for the measured figures.
|
|
200
|
+
* The Workers AI state window changed in October 2026: about 2,048 tokens until
|
|
201
|
+
* 2026-10-04, far more on 2026-10-07 (hosted service or model: unknown). See
|
|
202
|
+
* `decisionLimits` on the descriptor for the measured figures.
|
|
194
203
|
*
|
|
195
204
|
* @see https://developers.cloudflare.com/workers-ai/models/clef/
|
|
196
205
|
*/
|
|
@@ -248,6 +257,14 @@ export class CloudflareClefProvider extends SystemOneDecisionProvider {
|
|
|
248
257
|
}
|
|
249
258
|
return baseURLProblem(this.baseURL);
|
|
250
259
|
}
|
|
260
|
+
defaultTimeoutMs(_questionCount, stateTokens) {
|
|
261
|
+
const allowance = this.getDescriptorDecideMs();
|
|
262
|
+
return allowance === undefined
|
|
263
|
+
? undefined
|
|
264
|
+
: allowance +
|
|
265
|
+
Math.floor(stateTokens / 1_000) *
|
|
266
|
+
TIMEOUT_MS_PER_THOUSAND_STATE_TOKENS;
|
|
267
|
+
}
|
|
251
268
|
/** The model is part of the path, so the endpoint depends on the model asked for. */
|
|
252
269
|
decisionEndpoint(model) {
|
|
253
270
|
return `${this.baseURL}/accounts/${encodeURIComponent(this.accountId)}/ai/run/${MODEL_PREFIX}${encodeURIComponent(wireModel(model) ?? model)}`;
|
|
@@ -82,9 +82,11 @@ export declare abstract class SystemOneDecisionProvider extends BaseProvider {
|
|
|
82
82
|
protected readRetryAfterMs(_headers: Headers): number | undefined;
|
|
83
83
|
/**
|
|
84
84
|
* The per-attempt timeout when the caller sets none. A provider whose latency
|
|
85
|
-
* grows with the number of questions
|
|
85
|
+
* grows with the number of questions or the size of the state scales the
|
|
86
|
+
* descriptor's allowance here. `stateTokens` is the local estimate, 0 when the
|
|
87
|
+
* descriptor sets no local limits.
|
|
86
88
|
*/
|
|
87
|
-
protected defaultTimeoutMs(_questionCount: number): number | undefined;
|
|
89
|
+
protected defaultTimeoutMs(_questionCount: number, _stateTokens: number): number | undefined;
|
|
88
90
|
/**
|
|
89
91
|
* The part of a successful response that holds `answers`, `usage` and
|
|
90
92
|
* `model`. The default is the response itself; a vendor that wraps every
|
|
@@ -230,9 +230,11 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
230
230
|
}
|
|
231
231
|
/**
|
|
232
232
|
* The per-attempt timeout when the caller sets none. A provider whose latency
|
|
233
|
-
* grows with the number of questions
|
|
233
|
+
* grows with the number of questions or the size of the state scales the
|
|
234
|
+
* descriptor's allowance here. `stateTokens` is the local estimate, 0 when the
|
|
235
|
+
* descriptor sets no local limits.
|
|
234
236
|
*/
|
|
235
|
-
defaultTimeoutMs(_questionCount) {
|
|
237
|
+
defaultTimeoutMs(_questionCount, _stateTokens) {
|
|
236
238
|
return this.getDescriptorDecideMs();
|
|
237
239
|
}
|
|
238
240
|
/**
|
|
@@ -327,7 +329,7 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
327
329
|
});
|
|
328
330
|
}
|
|
329
331
|
const resolvedModel = request.model ?? this.modelName ?? this.getDefaultModel();
|
|
330
|
-
this.assertWithinDecisionLimits(request, resolvedModel, questionEntries.length);
|
|
332
|
+
const stateTokens = this.assertWithinDecisionLimits(request, resolvedModel, questionEntries.length);
|
|
331
333
|
const wireQuestions = Object.fromEntries(questionEntries.map(([id, question]) => [
|
|
332
334
|
id,
|
|
333
335
|
this.encodeQuestion(question),
|
|
@@ -336,7 +338,7 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
336
338
|
const body = JSON.stringify(this.buildDecisionBody(request.state, wireQuestions, resolvedModel, media));
|
|
337
339
|
this.assertWithinRequestBytes(body);
|
|
338
340
|
const timeoutMs = request.timeoutMs ??
|
|
339
|
-
this.defaultTimeoutMs(questionEntries.length) ??
|
|
341
|
+
this.defaultTimeoutMs(questionEntries.length, stateTokens) ??
|
|
340
342
|
this.defaultTimeout ??
|
|
341
343
|
DEFAULT_DECISION_TIMEOUT_MS;
|
|
342
344
|
let lastError;
|
|
@@ -511,7 +513,7 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
511
513
|
? resolveDecisionLimitsReading(descriptor, model)
|
|
512
514
|
: null;
|
|
513
515
|
if (!limits || !limits.enforcedLocally) {
|
|
514
|
-
return;
|
|
516
|
+
return 0;
|
|
515
517
|
}
|
|
516
518
|
const label = this.vendorLabel();
|
|
517
519
|
if (limits.maxQuestions !== undefined &&
|
|
@@ -531,6 +533,7 @@ export class SystemOneDecisionProvider extends BaseProvider {
|
|
|
531
533
|
retryable: false,
|
|
532
534
|
});
|
|
533
535
|
}
|
|
536
|
+
return stateTokens;
|
|
534
537
|
}
|
|
535
538
|
/**
|
|
536
539
|
* Wrap a normalised decision failure in the provider error type, keeping
|
|
@@ -1391,7 +1391,7 @@ export function createCloudflareClefConfig() {
|
|
|
1391
1391
|
instructions: [
|
|
1392
1392
|
"1. Create an API token with the 'Workers AI: Read + Write' permission (https://dash.cloudflare.com/profile/api-tokens)",
|
|
1393
1393
|
"2. Set CLOUDFLARE_API_KEY to it, and CLOUDFLARE_ACCOUNT_ID to your account id (in the dashboard URL, or under 'Account ID'); they are the same settings the Cloudflare text provider reads, so setting them also lets decide() use Clef when no other decision provider is configured",
|
|
1394
|
-
"3.
|
|
1394
|
+
"3. NeuroLink refuses a state it estimates at more than 16,000 tokens (about 60,000 characters of prose); long states also take seconds, so Clef suits short decisions",
|
|
1395
1395
|
],
|
|
1396
1396
|
};
|
|
1397
1397
|
}
|
|
@@ -18,7 +18,7 @@ import { logger } from "../../utils/logger.js";
|
|
|
18
18
|
import { TTS_ERROR_CODES, TTSError } from "../../utils/ttsProcessor.js";
|
|
19
19
|
const DEFAULT_BASE_URL = "https://api.cartesia.ai";
|
|
20
20
|
const DEFAULT_API_VERSION = "2025-04-16";
|
|
21
|
-
const DEFAULT_MODEL = "sonic-
|
|
21
|
+
const DEFAULT_MODEL = "sonic-3.6";
|
|
22
22
|
// Same default voice as the streaming handler — a publicly available
|
|
23
23
|
// Cartesia voice id ("Bright Female"). Override per-call via TTSOptions.voice.
|
|
24
24
|
const DEFAULT_VOICE_ID = "694f9389-aac1-45b6-b726-9d9369183238";
|