@juspay/neurolink 13.6.2 → 13.6.4

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.
@@ -86,6 +86,19 @@ export declare const hasNativeDoGenerate: (value: unknown) => value is {
86
86
  };
87
87
  /** A tool's inputSchema with its lazy (function) form, if any, resolved. */
88
88
  export declare const resolveToolInputSchema: (schema: Tool["inputSchema"]) => Exclude<Tool["inputSchema"], (...args: never[]) => unknown>;
89
+ /**
90
+ * The provider-neutral declaration of one tool: the shape every provider's
91
+ * `doGenerate` already converts to its own wire format. The loop uses it for
92
+ * the tools discovery loads mid-turn, and the providers that run the loop use
93
+ * it for their initial declarations, so a tool is declared the same way
94
+ * whether it is there at the start or joins later.
95
+ */
96
+ export declare const toNativeToolDeclaration: (name: string, tool: Tool) => {
97
+ type: "function";
98
+ name: string;
99
+ description: string;
100
+ inputSchema: object;
101
+ };
89
102
  /**
90
103
  * Spell a JSON Schema into the conversation's system turn.
91
104
  *
@@ -16,6 +16,7 @@
16
16
  import { observeGeneration } from "../telemetry/observation.js";
17
17
  import { StepSpans, withGeneration } from "../telemetry/withSpan.js";
18
18
  import { logger } from "../utils/logger.js";
19
+ import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
19
20
  import { guardToolExecutor } from "./toolExecutionGuards.js";
20
21
  import { resolveLiveTool } from "../tools/toolDiscovery.js";
21
22
  const DEFAULT_TOOL_CHOICE_STEPS = 1;
@@ -339,6 +340,35 @@ const parseToolInput = (raw) => {
339
340
  };
340
341
  /** A tool's inputSchema with its lazy (function) form, if any, resolved. */
341
342
  export const resolveToolInputSchema = (schema) => typeof schema === "function" ? resolveToolInputSchema(schema()) : schema;
343
+ /**
344
+ * The provider-neutral declaration of one tool: the shape every provider's
345
+ * `doGenerate` already converts to its own wire format. The loop uses it for
346
+ * the tools discovery loads mid-turn, and the providers that run the loop use
347
+ * it for their initial declarations, so a tool is declared the same way
348
+ * whether it is there at the start or joins later.
349
+ */
350
+ export const toNativeToolDeclaration = (name, tool) => ({
351
+ type: "function",
352
+ name,
353
+ description: tool.description ?? "",
354
+ inputSchema: declaredInputSchema(name, tool),
355
+ });
356
+ // A lazy schema that throws must not take the whole request down: SageMaker and
357
+ // Anthropic declared any function-valued schema as the empty object schema
358
+ // before the lazy form was resolved here, so a broken one falls back to that.
359
+ function declaredInputSchema(name, tool) {
360
+ const empty = { type: "object", properties: {} };
361
+ if (!tool.inputSchema) {
362
+ return empty;
363
+ }
364
+ try {
365
+ return convertZodToJsonSchema(resolveToolInputSchema(tool.inputSchema));
366
+ }
367
+ catch (error) {
368
+ logger.warn(`[nativeGenerateLoop] input schema of tool "${name}" could not be resolved; declaring it with an empty object schema`, { error: error instanceof Error ? error.message : String(error) });
369
+ return empty;
370
+ }
371
+ }
342
372
  /**
343
373
  * Validate parsed input against the tool's own schema when it exposes one.
344
374
  *
@@ -486,20 +516,23 @@ async function runGenerateSteps(args, toolExecutionSummaries, stepSpans) {
486
516
  // answer is that result — not a thrown turn.
487
517
  let preReask;
488
518
  const hasTools = Boolean(args.tools && args.tools.length > 0);
489
- // The declarations actually sent. Starts as the caller's list and grows when
490
- // a tool joins toolsRecord mid-turn (discovery loads tools into it).
491
- const liveTools = args.tools ? [...args.tools] : undefined;
519
+ // The declarations actually sent. It is the caller's own array and grows in
520
+ // place when a tool joins toolsRecord mid-turn (discovery loads tools into
521
+ // it), so anything the caller sized from it, such as the context guard's
522
+ // fixed-overhead estimate, counts the new tools too.
523
+ const liveTools = args.tools;
524
+ const declare = args.declareTool ?? toNativeToolDeclaration;
492
525
  const declaredNames = declaredToolRecord(args.tools);
493
526
  // Completed-step records for `prepareStep`; one per step that returned.
494
527
  const stepRecords = [];
495
528
  for (let step = 0; step < args.maxSteps; step++) {
496
529
  steps = step + 1;
497
530
  stepSpans.begin(steps);
498
- if (liveTools && args.declareTool) {
531
+ if (liveTools) {
499
532
  const loaded = [];
500
533
  for (const name of Object.keys(args.toolsRecord)) {
501
534
  if (!declaredNames[name]) {
502
- liveTools.push(args.declareTool(name, args.toolsRecord[name]));
535
+ liveTools.push(declare(name, args.toolsRecord[name]));
503
536
  declaredNames[name] = true;
504
537
  loaded.push(name);
505
538
  }
@@ -9,7 +9,7 @@ import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
9
9
  import { withProviderRetry } from "../utils/providerRetry.js";
10
10
  import { resolveToolChoice } from "../utils/toolChoice.js";
11
11
  import { DEFAULT_MAX_STEPS } from "../core/constants.js";
12
- import { hasNativeDoGenerate, runNativeGenerateLoop, } from "../core/nativeGenerateLoop.js";
12
+ import { hasNativeDoGenerate, runNativeGenerateLoop, toNativeToolDeclaration, } from "../core/nativeGenerateLoop.js";
13
13
  import { generationName } from "../telemetry/observationNames.js";
14
14
  import { observeGeneration } from "../telemetry/observation.js";
15
15
  import { tracers } from "../telemetry/tracers.js";
@@ -208,17 +208,7 @@ export class AmazonSageMakerProvider extends BaseProvider {
208
208
  const toolsRecord = shouldUseTools
209
209
  ? options.tools || {}
210
210
  : {};
211
- const v3Tools = Object.entries(toolsRecord).map(([name, t]) => {
212
- const tool = t;
213
- return {
214
- type: "function",
215
- name,
216
- description: tool.description ?? "",
217
- inputSchema: (tool.inputSchema
218
- ? convertZodToJsonSchema(tool.inputSchema)
219
- : { type: "object", properties: {} }),
220
- };
221
- });
211
+ const v3Tools = Object.entries(toolsRecord).map(([name, t]) => toNativeToolDeclaration(name, t));
222
212
  // Structured output was dropped entirely on this path: the schema never
223
213
  // reached the request, and nothing downstream re-imposed it, so a caller
224
214
  // asking for an object got whatever JSON coerceJsonToSchema could scrape
@@ -25,7 +25,7 @@ import { stringifyAnthropicToolOutput } from "./toolOutput.js";
25
25
  import { createAnthropicLoopAdapter } from "./loopAdapter.js";
26
26
  import { DEFAULT_BEFORE_STEP_TIMEOUT_MS } from "../../utils/parameterValidation.js";
27
27
  import { runAgenticLoop } from "../../core/loopEngine.js";
28
- import { hasNativeDoGenerate, resolveStepToolChoice, runNativeGenerateLoop, toPrepareStepRecord, } from "../../core/nativeGenerateLoop.js";
28
+ import { hasNativeDoGenerate, resolveStepToolChoice, runNativeGenerateLoop, toNativeToolDeclaration, toPrepareStepRecord, } from "../../core/nativeGenerateLoop.js";
29
29
  import { withProviderRetry } from "../../utils/providerRetry.js";
30
30
  import { resolveRequestKind } from "../../core/resolveRequestKind.js";
31
31
  import { resolveToolExecutionRecords, toolCallsFromSummaries, } from "../../core/toolExecutionRecorder.js";
@@ -1528,17 +1528,7 @@ export class AnthropicProvider extends BaseProvider {
1528
1528
  const toolsRecord = shouldUseTools
1529
1529
  ? options.tools || {}
1530
1530
  : {};
1531
- const v3Tools = Object.entries(toolsRecord).map(([name, t]) => {
1532
- const tool = t;
1533
- return {
1534
- type: "function",
1535
- name,
1536
- description: tool.description ?? "",
1537
- inputSchema: (tool.inputSchema
1538
- ? convertZodToJsonSchema(tool.inputSchema)
1539
- : { type: "object", properties: {} }),
1540
- };
1541
- });
1531
+ const v3Tools = Object.entries(toolsRecord).map(([name, t]) => toNativeToolDeclaration(name, t));
1542
1532
  const hasTools = v3Tools.length > 0;
1543
1533
  // Two structured-output routes, and doGenerate implements both. With no
1544
1534
  // tools it replaces the tool list with one forced json tool; with tools it
@@ -36,7 +36,7 @@ import { NoOutputGeneratedError } from "../utils/generationErrors.js";
36
36
  import { buildNoOutputSentinel, stampNoOutputSpan, } from "../utils/noOutputSentinel.js";
37
37
  import { composeAbortSignalsScoped, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../utils/timeout.js";
38
38
  import { resolveRequestKind } from "../core/resolveRequestKind.js";
39
- import { appendJsonSchemaInstruction, hasNativeDoGenerate, resolveToolInputSchema, resolveStepToolChoice, runNativeGenerateLoop, toPrepareStepRecord, } from "../core/nativeGenerateLoop.js";
39
+ import { appendJsonSchemaInstruction, hasNativeDoGenerate, resolveStepToolChoice, runNativeGenerateLoop, toNativeToolDeclaration, toPrepareStepRecord, } from "../core/nativeGenerateLoop.js";
40
40
  import { resolveToolExecutionRecords, toolCallsFromSummaries, } from "../core/toolExecutionRecorder.js";
41
41
  import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
42
42
  import { coerceJsonToSchema, recoverScalarRoot, schemaAccepts, } from "../utils/json/coerce.js";
@@ -925,19 +925,8 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
925
925
  ? options.tools || (await this.getAllTools())
926
926
  : {};
927
927
  // v3 tool shape — the same one doGenerate already converts internally.
928
- // Also handed to the loop, which declares tools discovery loads mid-turn.
929
- const declareTool = (name, tool) => {
930
- return {
931
- type: "function",
932
- name,
933
- description: tool.description ?? "",
934
- inputSchema: tool.inputSchema
935
- ? convertZodToJsonSchema(resolveToolInputSchema(tool.inputSchema))
936
- : { type: "object", properties: {} },
937
- };
938
- };
939
928
  const v3Tools = shouldUseTools
940
- ? Object.entries(toolsRecord).map(([name, t]) => declareTool(name, t))
929
+ ? Object.entries(toolsRecord).map(([name, t]) => toNativeToolDeclaration(name, t))
941
930
  : undefined;
942
931
  const hasTools = !!v3Tools && v3Tools.length > 0;
943
932
  // Structured output rides response_format, which is what Output.object did
@@ -965,14 +954,9 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
965
954
  // model has already given. Dropping them is what makes `response_format`
966
955
  // legal again on a vendor that refuses the two together.
967
956
  withTools = true) => {
968
- // What this pass sends: the initial declarations plus any the loop adds
969
- // for tools discovery loads mid-turn, so the guard counts them too.
957
+ // What this pass sends: the initial declarations, which the loop appends
958
+ // to for tools discovery loads mid-turn, so the guard counts them too.
970
959
  const passTools = withTools && v3Tools ? [...v3Tools] : undefined;
971
- const declarePassTool = (name, tool) => {
972
- const declaration = declareTool(name, tool);
973
- passTools?.push(declaration);
974
- return declaration;
975
- };
976
960
  return runNativeGenerateLoop({
977
961
  doGenerate,
978
962
  provider: this.providerName,
@@ -997,9 +981,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
997
981
  }), this.providerName),
998
982
  }),
999
983
  conversation: conv,
1000
- ...(passTools
1001
- ? { tools: [...passTools], declareTool: declarePassTool }
1002
- : {}),
984
+ ...(passTools ? { tools: passTools } : {}),
1003
985
  toolsRecord,
1004
986
  ...(withTools && hasTools && options.toolChoice
1005
987
  ? { toolChoice: resolveToolChoice(options, toolsRecord, true) }
@@ -1846,11 +1846,12 @@ export type NativeGenerateLoopArgs = {
1846
1846
  */
1847
1847
  toolsRecord: Record<string, Tool>;
1848
1848
  /**
1849
- * Builds the v3 declaration for one tool. When set, every step declares any
1850
- * tool that joined `toolsRecord` mid-turn (discovery's search_tools loads
1851
- * tools into it), so the model can call what it just loaded. Only the
1852
- * OpenAI-compatible `generate()` path supplies it; the other providers that
1853
- * run this loop do not declare a tool `search_tools` loaded mid-turn.
1849
+ * Builds the declaration for a tool that joined `toolsRecord` mid-turn
1850
+ * (discovery's search_tools loads tools into it). Optional: by default the
1851
+ * loop declares such a tool in the provider-neutral shape every provider's
1852
+ * `doGenerate` converts (`toNativeToolDeclaration`), for every provider that
1853
+ * runs this loop. Set it only to declare in another shape. The loop appends
1854
+ * the declarations to `tools` in place.
1854
1855
  */
1855
1856
  declareTool?: (name: string, tool: Tool) => Record<string, unknown>;
1856
1857
  /** The turn's resolved tool choice, applied per step via `resolveStepToolChoice`. */
@@ -2830,14 +2830,14 @@
2830
2830
  {"objectID":"dc8b9e9a2c66b1d2a60305a09a44263ceca755d40f04b6771e0a7c05a5237a64","title":"Perplexity, at its hosted endpoint","url":"/docs/features/decide-inference-type#perplexity-at-its-hosted-endpoint","content":"Perplexity's pplx-decider-v1-27b is a hosted API at a public endpoint, so a key\nalone configures it and there is no base URL to set. NeuroLink calls\n<base URL>/v1/decisions; a trailing /v1 on an override is accepted. The key\ncan equally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { perplexityDecider: { apiKey } } }), or per call;\nconfig set there counts when a decide() call picks the default decision\nprovider, exactly as the environment does. The classifier router's auto gate\ncounts them too, per-call credentials included, so a\ncredentials.perplexityDecider passed on one call can make auto pick jev.\nThe provider id is perplexity-decider: the perplexity provider is the\nSonar text provider, which serves generate() and stream() and not decide().\n\nPERPLEXITY_API_KEY is shared with that text provider. A host that set it\nonly to use Sonar has therefore also configured decide, and built-in features\nwill use Perplexity whenever none of TypeSafe, Laya, XOR or SubQ is configured.\nPerplexity comes after XOR and SubQ in descriptor order, so it never displaces\none that is. Context compaction's relevance stage and summary gate need no opt-in of their\nown, so earlier conversation text starts going to Perplexity the first time a\nconversation outgrows its budget; model routing, tool routing and RAG planning do\nnothing unless enabled. To keep the shared key from activating decisions, pass\nthe text provider's key as credentials.perplexity instead of through the\nenvironment, and keep it out of .env too, because the SDK and the CLI load that\nfile into the environment, or configure one of TypeSafe, Laya, XOR and SubQ, which then\nreceives those texts instead. The Perplexity provider guide lists\nwhat each consumer sends\nand the\nexact switches.\nIt reads images but no video; see Images and video.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Perplexity, at its hosted endpoint","lvl3":""}},
2831
2831
  {"objectID":"86d184ec9e501d4e94e973c833eaee71bb40744e683334ad36c497c3f625387f","title":"Cloudflare Clef, on Workers AI","url":"/docs/features/decide-inference-type#cloudflare-clef-on-workers-ai","content":"Cloudflare's clef and clef-flash are hosted on Workers AI at a public\nendpoint, so there is no base URL to set. The account id is part of the route, so\nClef counts as configured only with both the token and the account id. They can\nequally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { cloudflareClef: { apiKey, accountId } } })\n(baseURL is optional), or per call; config set there counts when a decide()\ncall picks the default decision provider, exactly as the environment does. The\nclassifier router's auto gate counts them too, per-call credentials included,\nso a credentials.cloudflareClef passed on one call can make auto pick jev.\nThe provider id is cloudflare-clef: the cloudflare provider is the Workers AI text\nprovider, which serves generate() and stream() and not decide(), and\ncredentials.cloudflare does not configure decide.\n\nThe Workers AI endpoint's state window changed during October 2026.\nCloudflare documents a 64K window; until 2026-10-04 text past about 2,048 tokens\nwas ignored without an error, and on 2026-10-07 states of at least 190,153 tokens\n(clef-flash) and 52,154 (clef) were read in full (hosted service or model:\nunknown). NeuroLink refuses a state it estimates at more than 16,000 tokens with\nmax_tokens_exceeded, which every built-in consumer treats as \"carry on as\nbefore\"; see Limits and gotchas. Latency grows with the\nstate, about 0.2 s per 1,000 tokens on large ones, so NeuroLink's default timeout\nadds 250 ms per 1,000 estimated tokens.\n\nCLOUDFLARE_API_KEY and CLOUDFLARE_ACCOUNT_ID are shared with that text\nprovider. A host that set them only to use Workers AI text has therefore also\nconfigured decide, and built-in features will use Clef whenever none of\nTypeSafe, Laya, XOR, SubQ or Perplexity is configured. Clef comes after\nTypeSafe, Laya, XOR, SubQ and Perplexity, so it never displaces one that is. No switch turns it off while the two\nvariables are set.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Cloudflare Clef, on Workers AI","lvl3":""}},
2832
2832
  {"objectID":"08fd87a8baa41fd742824c8457907e72804c393825cb2f67997a356c98b4211b","title":"OpenAI Decisions, at its hosted endpoint","url":"/docs/features/decide-inference-type#openai-decisions-at-its-hosted-endpoint","content":"OpenAI's Decisions API is a public beta at a public endpoint, so a key alone\nconfigures it and there is no base URL to set. NeuroLink calls\n<base URL>/v1/decisions; a trailing /v1 on an override is accepted. The key\ncan equally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { openaiDecisions: { apiKey } } }), or per call;\nconfig set there counts when a decide() call picks the default decision\nprovider, exactly as the environment does. The classifier router's auto gate\ncounts them too, per-call credentials included, so a credentials.openaiDecisions\npassed on one call can make auto pick jev. The provider id is openai-decisions: the openai provider is the chat\nprovider, which serves generate() and stream() and not decide(), and\ncredentials.openai does not configure decide. Its wire is OpenAI's own (named\nquestions in an array, an array of answers), so NeuroLink translates both ways;\nthe question types you write are unchanged. See the provider guide's\nwire mapping.\n\nOPENAI_API_KEY is shared with that chat provider. A host that set it only to\nuse OpenAI chat has therefore also configured decide, and built-in features will\nuse OpenAI Decisions whenever none of TypeSafe, Laya, XOR, SubQ, Perplexity or Cloudflare\nClef is configured. It comes after those six in the order, so it never displaces\none that is; only ScaleDown follows it.\nThe built-in consumers (model routing, tool routing, relevance compaction, the\nsummary gate, RAG planning) and the speech-to-text correction check then make paid\ndecision calls, though they still fail open. No switch turns it off while the key\nis set (short of a gateway OPENAI_BASE_URL, below). neurolink.decide() and tryDecide() build a fresh provider for every\ncall, so a key OpenAI rejects costs one failing request per decision site each\ntime, and the 401 latch never carries across calls. The provider guide explains\nwhen NeuroLink uses it\nand\nhow the two providers share the key.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"OpenAI Decisions, at its hosted endpoint","lvl3":""}},
2833
- {"objectID":"691b7e7dec264cbbdcf43939939e82963f66753635c1b5ca81b26b2be05c844b","title":"ScaleDown, at its hosted endpoint","url":"/docs/features/decide-inference-type#scaledown-at-its-hosted-endpoint","content":"ScaleDown's Classify models speak the same System One wire as TypeSafe, at a\npublic endpoint, so a key alone configures it and there is no base URL to set.\nNeuroLink calls <base URL>/v1/scaledown, drops trailing slashes from an\noverride, and sends the key in an x-api-key header and never as a Bearer token.\nThe key can equally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { scaledown: { apiKey } } }), or per call. The\nprovider id is scaledown; scaledown-classify names it too.\n\nScaleDown is the last decision provider, and its key is its own. Nothing\nelse reads SCALEDOWN_API_KEY, so setting it can only be meant for ScaleDown. It\nstill comes last in the order: a host with only that key gets ScaleDown for the\nbuilt-in features, and a host that also has any earlier provider configured\n(including an OPENAI_API_KEY, PERPLEXITY_API_KEY or Cloudflare pair set only for\na text provider) gets that earlier provider and reaches ScaleDown only by naming\nit. When ScaleDown is the default, the built-in consumers make paid decision\ncalls, and still fail open.\n\nIt reads text only, and the state always goes out as a string. A string state\nis sent unchanged, any other value as its JSON text, and a string that is empty or\nonly whitespace is refused before any request, as an invalid_request. {} and\n[] are sent as that text. Images and video are refused before any request. Two\noptional keys in the entry's decide block say this to the shared engine,\nauthHeader: \"x-api-key\" and stateEncoding: \"text\"; an entry that omits them\nbehaves as before. The provider guide has the\nrequest and the\nbilling note:\na choice question is scored once per option, so input tokens grow with options\ntimes state length.\n\nMuch about the endpoint is not measured, including its context size, latency,\nrate limits and the maximum number of options, questions and levels; the guide\nlists what is not measured.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"ScaleDown, at its hosted endpoint","lvl3":""}},
2833
+ {"objectID":"691b7e7dec264cbbdcf43939939e82963f66753635c1b5ca81b26b2be05c844b","title":"ScaleDown, at its hosted endpoint","url":"/docs/features/decide-inference-type#scaledown-at-its-hosted-endpoint","content":"ScaleDown's Classify models speak the same System One wire as TypeSafe, at a\npublic endpoint, so a key alone configures it and there is no base URL to set.\nNeuroLink calls <base URL>/v1/scaledown, drops trailing slashes from an\noverride, and sends the key in an x-api-key header and never as a Bearer token.\nThe key can equally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { scaledown: { apiKey } } }), or per call. The\nprovider id is scaledown; scaledown-classify names it too.\n\nScaleDown is the last decision provider, and its key is its own. Nothing\nelse reads SCALEDOWN_API_KEY, so setting it can only be meant for ScaleDown. It\nstill comes last in the order: a host with only that key gets ScaleDown for the\nbuilt-in features, and a host that also has any earlier provider configured\n(including an OPENAI_API_KEY, PERPLEXITY_API_KEY or Cloudflare pair set only for\na text provider) gets that earlier provider and reaches ScaleDown only by naming\nit. When ScaleDown is the default, the built-in consumers make paid decision\ncalls, and still fail open.\n\nIt reads text only, and the state always goes out as a string. A string state\nis sent unchanged, any other value as its JSON text, and a string that is empty or\nonly whitespace is refused before any request, as an invalid_request. {} and\n[] are sent as that text. Images and video are refused before any request. Two\noptional keys in the entry's decide block say this to the shared engine,\nauthHeader: \"x-api-key\" and stateEncoding: \"text\"; an entry that omits them\nbehaves as before. The provider guide has the\nrequest and the\nbilling note:\nScaleDown's documentation says a choice question is scored once per option, but\nthe input tokens charged in a live test did not grow with options times state\nlength (the same 20,000-character state cost 2,966, 3,024 and 3,137 tokens at 3, 6\nand 12 options); how ScaleDown counts them is not known.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"ScaleDown, at its hosted endpoint","lvl3":""}},
2834
2834
  {"objectID":"7990ffc08aca0afbeae4310cfdcd3758daf52da4d9edd1da90a82b683f4d7d41","title":"Two transports","url":"/docs/features/decide-inference-type#two-transports","content":"The same model is reachable two ways. Which one runs is decided once, in the\nconstructor:\n\n| | Direct | Vercel AI Gateway |\n| ------------------- | ------------------ | --------------------------------------------- |\n| Key | TYPESAFE_API_KEY | AI_GATEWAY_API_KEY |\n| Endpoint | api.typesafe.ai | ai-gateway.vercel.sh/v4/ai/evaluation-model |\n| Model named in | request body | ai-model-id header |\n| Question vocabulary | noul | boolean |\n| confidence | on each answer | on providerMetadata, not on the answer |\n| Billed by | TypeSafe | Vercel |\n\nHolding both keys keeps the direct transport, so the confidence figures a\nhost already sees do not shift underneath it when a second key appears. Both\ntransports report the vendor's calibrated confidence — the gateway simply puts\nit somewhere else, under providerMetadata.typesafe.confidence.<questionId>,\nleaving the answer objects without one. Set TYPESAFE_TRANSPORT=gateway (or\ncredentials.typesafe.transport) to override.\n\n⚠️ Read that field, not the distribution peak. It is tempting to take\nmax(probabilities) when an answer carries no confidence, and on a\nnear-certain answer the two agree. On an uncertain one they do not, and not by a\nlittle: a measured four-way choice returned probabilities\n{alpha 0.16, beta 0.28, gamma 0.33, delta 0.23} — a peak of 0.33 against a\nreported confidence of 0.10. That gap straddles the default\nminUpgradeConfidence of 0.3, so the derived number clears a bar the real one\nfails and a near-random pick gets acted on as a confident one. The peak stays as\nthe fallback when neither source reports a confidence, and it is genuinely a\ndifferent quantity: an even distribution over N options lands near 1/N, not 0.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Two transports","lvl3":""}},
2835
2835
  {"objectID":"e5df7db87d4cb31c67fcce769cf5a3573764cb358ed9ee4b41f7ce86cb3954b7","title":"Using it","url":"/docs/features/decide-inference-type#using-it","content":"readDecisionBoolean / readDecisionChoice / readDecisionScore validate at\nruntime and return undefined for a missing id or a mismatched type, so no\ncall site needs a type assertion.\n\nticketText is attacker-controlled, so the verdict is a hint: keep assignTo\nreversible or put a deterministic check in front of it (see the Untrusted input\nbullet under What it is bad at).\n\nUse decide() instead of tryDecide() when you want the failure to surface;\nit throws a ProviderError whose cause carries a typed kind\n(authentication, rate_limit, max_tokens_exceeded, …).\n\nMore questions than a provider takes. A provider that caps the questions in\none request (Laya and Cloudflare Clef take 64, Perplexity and XOR 128, OpenAI Decisions 200) refuses a longer map from decide() with max_tokens_exceeded, except\nOpenAI Decisions, whose row is advisory: its cap is reported and used to split,\nbut decide() sends a longer map whole and leaves the answer to the server.\ntryDecide(), which every built-in consumer calls, splits the map at the cap\ninstead, keeps the questions in the order given, runs\nup to four batches at a time and joins the answers into one result: usage is\nsummed, the latency is the slowest batch's, and requestId lists the batches'\nids. A batch that fails costs only its own questions, which then have no answer\n(the read* helpers return undefined for them, and every built-in consumer\ncarries on without that decision); tryDecide() returns null only when every\nbatch failed. The state and any media are sent with each batch, so a split\nrequest costs the state's tokens once for each batch. A provider with no cap\n(TypeSafe, SubQ, ScaleDown) always gets one request.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Using it","lvl3":""}},
2836
2836
  {"objectID":"7232a490e9e72e01dbe1a12de9470a38ed66ef91aa076b8c8cc745770eb03545","title":"From the CLI","url":"/docs/features/decide-inference-type#from-the-cli","content":"The same primitive is available as neurolink decide [state], which calls\ndecide() (not tryDecide()) and prints one line per answer:\n\nTo ask about an image, a video or an audio clip, add --image <path>\n(repeatable), --video <path> or --audio <path>. XOR reads one image and\naudio, Perplexity, Cloudflare Clef and OpenAI Decisions read images only,\nTypeSafe, Laya, SubQ and ScaleDown read neither, and no shipped provider reads\nvideo. With --audio, the state may be left out:\n\nSee the CLI command reference for the full flag\nlist, including --state-file, --questions-file, --image, --video,\n--audio and --format json.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"From the CLI","lvl3":""}},
2837
2837
  {"objectID":"3b801c727cb399feb7390c3ad67aba9ea5f77581096bb852b7aafb87b70998aa","title":"A choice answer is also a ranking","url":"/docs/features/decide-inference-type#a-choice-answer-is-also-a-ranking","content":"readDecisionChoice returns ranked — every option sorted by probability,\nhighest first. One choice question over N options therefore ranks all N in a\nsingle request. This is the basis for picking from a large catalogue.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"A choice answer is also a ranking","lvl3":""}},
2838
2838
  {"objectID":"e67682840a5ce9650dba2a1d437b0dfbba456cae60fa3f607127e209bf1ec3e7","title":"Images and video","url":"/docs/features/decide-inference-type#images-and-video","content":"A decision provider whose descriptor declares media limits reads images, and\npossibly one video or one audio clip, alongside state. XOR, Perplexity,\nCloudflare Clef and OpenAI Decisions read images; XOR also reads audio; TypeSafe,\nLaya, SubQ and ScaleDown read none. No shipped provider reads video: XOR's\ngateway drops a video without a word (a solid red clip was answered the same as\ngarbage bytes), so NeuroLink refuses one rather than return a blind answer.\nThree optional fields on the request carry media, for decide() and\ntryDecide() alike:\nimages — up to 8 for Perplexity and OpenAI Decisions (OpenAI's is\n NeuroLink's own allowance, not a limit OpenAI documents), 4 for Cloudflare\n Clef, 1 for XOR.\nvideo — one video, for a provider that declares video (none today).\naudio — one clip, for a provider that declares audio (XOR); see\n Audio below.\n\nEach takes the same three input forms:\na Buffer;\na local file path;\na data:image/…, data:video/… or data:audio/…;base64, URL.\n\nNeuroLink identifies a Buffer or a file from its bytes, not its extension — PNG,\nJPEG, WebP and GIF images, and MP4, MOV and WebM video —\nand sends it as a data: URL. A data: URL you pass yourself must hold base64\nimage or video content. Clef normalizes image/jpg to image/jpeg; other data URLs are sent as given. Perplexity and Cloudflare Clef read\nPNG, JPEG and WebP only, and refuse a GIF before any request. OpenAI Decisions\nsends every image as an inline data URL and checks no format list, because\nOpenAI's list is not established.\n\nWhat is refused.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Images and video","lvl3":""}},
2839
2839
  {"objectID":"011a9e1749ee443f7a2e40da4b793f6af01dbe491ce0951d27619c297867bdf5","title":"Audio","url":"/docs/features/decide-inference-type#audio","content":"XOR hears one clip per request, as WAV, FLAC, Ogg (Vorbis or Opus) or MP3,\nidentified from its bytes and sent as { data, format }. The state may be empty\n(state: \"\"; the CLI's --audio lets you leave it out), and an image may go\nwith it.\n\nThe model hears the first 30 seconds and drops the rest without a word, at\nabout 25 input tokens a second. So a WAV, FLAC or Ogg (Opus or Vorbis) clip that\nruns longer is refused before any request, its length read from the file; an\nMP3's length is not read, and only its first 30 seconds count. With no model\nnamed and XOR_MODEL unset, a request carrying media or a value question goes\nto xor-omni and is checked against its window (the catalog's\ndecide.capableModel). AAC/M4A is refused by name: it was\nmeasured decoding to nothing while the request was still answered. A WAV without\na complete fmt and data chunk is refused too.\n\nThe result carries mediaBytes, the encoded size of the media sent. The\ndecide spans carry decision.images.count, decision.audio.count and\ndecision.media.bytes, and\nnever any base64. A deployment started without OPENJEV_IMAGES=1 answers 200\nand silently ignores images, which NeuroLink cannot detect; see the\nXOR provider guide.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Audio","lvl3":""}},
2840
- {"objectID":"a6077cedd1ca3e03b757da9a2ef8c185a56d2f1032dd5ed43fe28bd71454836c","title":"The one rule: batch, never fan out","url":"/docs/features/decide-inference-type#the-one-rule-batch-never-fan-out","content":"This inverts the instinct you have from LLMs.\n\nOn TypeSafe, question count barely affects latency (measured against its live\nAPI):\n\n| questions | round trip | input tokens |\n| --------- | ---------- | ------------ |\n| 1 | 393 ms | 310 |\n| 10 | 390 ms | 481 |\n| 100 | 423 ms | 2 281 |\n| 400 | 465 ms | 8 581 |\n\nThese are TypeSafe's figures. perplexity-decider differs: about 0.3 to 1.1 s\nfor one question, about 65 ms for each further question (a request takes at\nmost 128), and an input time that grows faster than linearly with size: 3.9 s\nat 65,000 tokens, 10 s at 146,000 and 22.9 s at 251,000. See\nits latency section.\ncloudflare-clef takes 0.3 to 1.0 s for a small request and 1.1 to 1.3 s for\n64 questions (measured 2026-10-03). A 64-question request with the same\nquestions and a shorter state took 1.5 s on clef-flash and 2.3 s on clef on 2026-10-04; see\nits latency section.\nopenai-decisions has not been timed: its latency is not measured. Nor has\nscaledown: its latency is not measured, and a choice question is scored\nonce per option, so a request with many options may need a larger timeoutMs\nthan the 5-second default; see\nits timeouts note.\n\nOn TypeSafe, 400 questions cost ~70 ms more than one. Concurrent requests, by\ncontrast, queue: ten parallel calls take ~1.4 s wall with nine landing together\nat the end, while the server's own upstream time stays flat at 64–169 ms.\n\nSo on TypeSafe, 400 things in one request take ~465 ms; the same 400 as separate\nrequests take roughly a minute. Add every question you might need to the call\nyou are already making — speculative questions are nearly free on TypeSafe (on\nPerplexity each costs about 65 ms, up to 128 in a request; Cloudflare Clef takes\nup to 64), and a second round trip is not.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"The one rule: batch, never fan out","lvl3":""}},
2840
+ {"objectID":"a6077cedd1ca3e03b757da9a2ef8c185a56d2f1032dd5ed43fe28bd71454836c","title":"The one rule: batch, never fan out","url":"/docs/features/decide-inference-type#the-one-rule-batch-never-fan-out","content":"This inverts the instinct you have from LLMs.\n\nOn TypeSafe, question count barely affects latency (measured against its live\nAPI):\n\n| questions | round trip | input tokens |\n| --------- | ---------- | ------------ |\n| 1 | 393 ms | 310 |\n| 10 | 390 ms | 481 |\n| 100 | 423 ms | 2 281 |\n| 400 | 465 ms | 8 581 |\n\nThese are TypeSafe's figures. perplexity-decider differs: about 0.3 to 1.1 s\nfor one question, about 65 ms for each further question (a request takes at\nmost 128), and an input time that grows faster than linearly with size: 3.9 s\nat 65,000 tokens, 10 s at 146,000 and 22.9 s at 251,000. See\nits latency section.\ncloudflare-clef takes 0.3 to 1.0 s for a small request and 1.1 to 1.3 s for\n64 questions (measured 2026-10-03). A 64-question request with the same\nquestions and a shorter state took 1.5 s on clef-flash and 2.3 s on clef on 2026-10-04; see\nits latency section.\nopenai-decisions has not been timed: its latency is not measured. scaledown\nhas been timed only lightly: 265-token requests took 0.3 to 9.8 s on\n2026-10-20, and a choice question is scored once per option, so a request\nwith many options may need a larger timeoutMs than its 30-second default; see\nits timeouts note.\n\nOn TypeSafe, 400 questions cost ~70 ms more than one. Concurrent requests, by\ncontrast, queue: ten parallel calls take ~1.4 s wall with nine landing together\nat the end, while the server's own upstream time stays flat at 64–169 ms.\n\nSo on TypeSafe, 400 things in one request take ~465 ms; the same 400 as separate\nrequests take roughly a minute. Add every question you might need to the call\nyou are already making — speculative questions are nearly free on TypeSafe (on\nPerplexity each costs about 65 ms, up to 128 in a request; Cloudflare Clef takes\nup to 64), and a second round trip is not.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"The one rule: batch, never fan out","lvl3":""}},
2841
2841
  {"objectID":"0c1ce0b91d624674d6b4cc3b1f67fad986bcdfd379812b89e121784a94408ec3","title":"Model routing","url":"/docs/features/decide-inference-type#model-routing","content":"The classifier router gains a jev\nstrategy, and its default becomes auto — resolving to jev when a decision\nprovider is configured and heuristic when not.\n\nOne request asks for the difficulty tier, whether the task needs\nvision/tools/reasoning, whether carrying it out is risky, and which pool\nmember to use — all at once, in ~400 ms on TypeSafe's Jev.\n\n| | heuristic | llm | jev |\n| ---------------------- | ------------- | ------------------------------- | --------------------------------------------------- |\n| Added latency | 0 ms | ~1–8 s | ~400 ms on TypeSafe |\n| Cost per decision | none | a full LLM call | ~$0.00002 on TypeSafe |\n| Confidence | keyword score | self-reported (defaults to 0.7) | as the provider reports it (calibrated on TypeSafe) |\n| Picks a model directly | no | yes | yes |\n\nThe jev column is TypeSafe's figures; the\nPerplexity guide gives its\nlatency and the confidence it reports. The\nCloudflare Clef guide gives its\nlatency; a prompt NeuroLink estimates over 16,000 tokens is refused there and\nrouting falls back to the heuristic, as it does on any failure.\n\nThresholds are asymmetric, because the two mistakes do not cost the same:\nminUpgradeConfidence defaults to 0.3 (spending more on a wrong guess costs\nmoney) and minDowngradeConfidence to 0.6 (spending less on a wrong guess\nproduces a wrong answer).\n\nThe key upgrades routing; it does not switch routing on. The classifier\nrouter is still opt-in (classifierRouter.enabled) and still needs a pool,\nbecause NeuroLink cannot invent the set of models you are willing to route\nbetween. What the key changes is which classifier runs inside a router you\nalready enabled.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Model routing","lvl3":""}},
2842
2842
  {"objectID":"40f783ceca290c039cce7f0199ab5758d7a0719b0a91c335c1c6e490a72b1412","title":"The model catalogue","url":"/docs/features/decide-inference-type#the-model-catalogue","content":"Enabling classifierRouter.catalog widens the routable pool beyond what you\ndeclared by hand: candidates are built from the 64-model registry (7\nproviders — see the model catalogue\nfor which), intersected with the credentials this host actually holds, and\nranked by a deterministic formula whenever no decision provider is available\nto choose among them.\n\n| | Without catalog | With catalog.enabled |\n| -------------- | ------------------------ | ------------------------------------- |\n| Candidate pool | only the declared pool | declared pool plus registry matches |\n| Fallback pick | first pool member | tierScore()-ranked, tier-aware |\n| Cap | none needed | maxModels, default 120 |\n\nSee the model catalogue for how\ncandidates are filtered, rendered, and ranked.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"The model catalogue","lvl3":""}},
2843
2843
  {"objectID":"1362463e9ab1e8a456cb34a4e5ac2ec8493f18ddc2a8d277282de858c0420131","title":"Per-request context budget","url":"/docs/features/decide-inference-type#per-request-context-budget","content":"A per-request compactionThreshold option lowers the point at which history\ngets compacted, below the 0.8-of-window default. The jev strategy can fill\nit in automatically from a four-level scope rubric (current-message through\neverything) — and the mapping is a one-directional invariant: it can only\never lower the 0.8 default, never raise it, because over-filling a window is\nan unrecoverable provider error.\n\nSee per-request context budget for the\nrubric, the invariant, and how the threshold scales the compaction target.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Per-request context budget","lvl3":""}},
@@ -6506,28 +6506,28 @@
6506
6506
  {"objectID":"cd1de30168eb04f05817488311cca7b4f12bf8367f224d54df7d0b972af81d3e","title":"Verification status","url":"/docs/getting-started/providers/sarvam#verification-status","content":"Tier-2 onboarding requires evidence before a provider is accepted, and\npnpm run verify:provider-onboarding gates it in CI. This is what the\ncatalog records for Sarvam AI — and, unusually, all of it is credential-free:\n\n| Probe | Result |\n| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Roster | Unauthenticated GET https://api.sarvam.ai/v1/models, HTTP 200, 2026-09-28 — returned exactly sarvam-105b and sarvam-105b-conversations, no API key used. |\n| Auth rejection | Not probed live (would require a POST request, out of scope for credential-free onboarding). The 403 shape below is transcribed from Sarvam's own authentication docs, not observed. |\n| Wire compatibility | Documentary only — Sarvam's API reference explicitly describes an OpenAI-shaped messages / choices / finish_reason / object: \"chat.completion\" schema, tools/tool_choice, response_format (json_schema, json_object), and stream: true (SSE) for this endpoint. |\n| Live capability sweep | Not run. evidence.liveMatrix is null.","hierarchy":{"lvl0":"Getting Started","lvl1":"Sarvam AI Provider Guide","lvl2":"Verification status","lvl3":""}},
6507
6507
  {"objectID":"8e57df7483555e36212512809e787dc851cae1ddb71ca3758faab8f991880cad","title":"Troubleshooting","url":"/docs/getting-started/providers/sarvam#troubleshooting","content":"| Symptom | Cause | Fix |\n| ------------------------------------------ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |\n| Invalid Sarvam AI API key | SARVAM_API_KEY unset or wrong | Check the key at https://dashboard.sarvam.ai |\n| HTTP 403 on every call | Sarvam returns 403 (not 401) for both bad and forbidden keys | Confirm the key is current and has access; see error.code in the response |\n| Model not found | Only sarvam-105b and sarvam-105b-conversations exist | Use one of those two ids — the endpoint serves no others |\n| Empty/short replies at a small maxTokens | reasoning_effort defaults to medium, and reasoning_content is billed as output | Give reasoning prompts a generous maxTokens budget or lower reasoning_effort |","hierarchy":{"lvl0":"Getting Started","lvl1":"Sarvam AI Provider Guide","lvl2":"Troubleshooting","lvl3":""}},
6508
6508
  {"objectID":"49895306150970c4315329ad7d39d3141c62342bd1ca0ad4643cc8c3b63b1799","title":"See also","url":"/docs/getting-started/providers/sarvam#see-also","content":"Provider setup overview\nAll providers\nTier-2 onboarding — how this provider's JSON becomes a working integration\nProvider feature compatibility","hierarchy":{"lvl0":"Getting Started","lvl1":"Sarvam AI Provider Guide","lvl2":"See also","lvl3":""}},
6509
- {"objectID":"a5f27aad5d289af1b5605c874f662cf8feb10a9311baaef5577fa0788921a31c","title":"ScaleDown Provider Guide","url":"/docs/getting-started/providers/scaledown","content":"ScaleDown Provider Guide\n\nA provider of decide — the same typed boolean / choice / score\nanswers as TypeSafe's Jev, Laya, XOR,\nSubQ, Perplexity,\nCloudflare Clef and\nOpenAI Decisions, from ScaleDown's hosted Classify\nmodels. It emits no text at all.\n\nWhere the facts about ScaleDown come from. Everything this guide says about\nScaleDown's endpoint comes from ScaleDown's own documentation and from probes\nthat the marketing session made on 2026-10-15. NeuroLink's project did not\nprobe it: no request to ScaleDown was made in building this provider, and the\n200 replies in NeuroLink's tests are stand-ins written from the documented\nshape, and the error texts in the mocked tests are the ones the marketing\nsession reported.\nEach item that nobody measured is listed under\nNot measured.\n\nOverview\n\nclassify-1 is ScaleDown's decision model. You send one state plus named,\ntyped questions. The model answers every question in a single request and\nreturns a typed answer for each, with a probability instead of a sentence.\nScaleDown speaks the same System One wire as TypeSafe, so NeuroLink describes it\nwith one catalog file, src/lib/providers/catalog/scaledown.json, and no\nprovider code of its own. NeuroLink calls ScaleDown's public endpoint, so a key\nalone configures it: there is no base URL to set.\n\nKey Facts\n\n| | |\n| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Provider id | scaledown; the alias scaledown-classify names it too |\n| Inference type | decide only |\n| Models | classify-1 (the default) and classify, which ScaleDown describes as its latest Classify model. Any other name is refused by ScaleDown with HTTP 422. SCALEDOWN_MODEL sets one |\n| Key | SCALEDOWN_API_KEY, or credentials.scaledown.apiKey. The variable is ScaleDown's own: no text provider reads it |\n| Key header | x-api-key: <key>. NeuroLink never sends Authorization: Bearer to ScaleDown |\n| Endpoint | POST https://api.scaledown.xyz/v1/scaledown; SCALEDOWN_BASE_URL or credentials.scaledown.baseURL can name another base URL (see Base URL) |\n| State | text only: a string goes out unchanged, any other value as its JSON text; see How the state is sent |\n| Questions per request | no cap declared |\n| State window | none declared, so nothing is refused locally on size; ScaleDown's input ceiling is not measured |\n| Media | none. Images and video attached to a decide() call are refused before any request; see Images and video |\n| Default timeout | 5 seconds per attempt; timeoutMs or --timeout replaces it; see Timeouts |\n| Cost | priced per input token, input only; see Cost for the rate, its source and its date |\n| Precedence | the last of the eight: used automatically only when none of TypeSafe, Laya, XOR, SubQ, Perplexity, Cloudflare Clef or OpenAI Decisions is configured |\n| Status | NeuroLink lists both models as preview |\n\nQuick Start\nGet a key\n\nCreate an account and an API key by following\nScaleDown's documentation. Sign-up gives a free\nallowance; its expiry is not established. ScaleDown's documentation says a\nrequest made with no credit left returns HTTP 402.\nConfigure\n\nSet the key in the environment:\n\nor in the config passed to the SDK, exactly as for any other provider. Values\npassed per call override the constructor's, which override the environment:\n\ncredentials.scaledown is its own","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"","lvl3":""}},
6510
- {"objectID":"b9df3945bc87bb36e60b7b96008ffdf5fa0d81362bb0d14c8be9be28c055b127","title":"ScaleDown Provider Guide","url":"/docs/getting-started/providers/scaledown#scaledown-provider-guide","content":"A provider of decide — the same typed boolean / choice / score\nanswers as TypeSafe's Jev, Laya, XOR,\nSubQ, Perplexity,\nCloudflare Clef and\nOpenAI Decisions, from ScaleDown's hosted Classify\nmodels. It emits no text at all.\n\nWhere the facts about ScaleDown come from. Everything this guide says about\nScaleDown's endpoint comes from ScaleDown's own documentation and from probes\nthat the marketing session made on 2026-10-15. NeuroLink's project did not\nprobe it: no request to ScaleDown was made in building this provider, and the\n200 replies in NeuroLink's tests are stand-ins written from the documented\nshape, and the error texts in the mocked tests are the ones the marketing\nsession reported.\nEach item that nobody measured is listed under\nNot measured.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"ScaleDown Provider Guide","lvl3":""}},
6509
+ {"objectID":"a5f27aad5d289af1b5605c874f662cf8feb10a9311baaef5577fa0788921a31c","title":"ScaleDown Provider Guide","url":"/docs/getting-started/providers/scaledown","content":"ScaleDown Provider Guide\n\nA provider of decide — the same typed boolean / choice / score\nanswers as TypeSafe's Jev, Laya, XOR,\nSubQ, Perplexity,\nCloudflare Clef and\nOpenAI Decisions, from ScaleDown's hosted Classify\nmodels. It emits no text at all.\n\nWhere the facts about ScaleDown come from. Everything this guide says about\nScaleDown's endpoint comes from ScaleDown's own documentation and from probes\nthat the marketing session made on 2026-10-15. NeuroLink's project did not\nprobe it: no request to ScaleDown was made in building this provider, and the\n200 replies in NeuroLink's tests are stand-ins written from the documented\nshape, and the error texts in the mocked tests are the ones the marketing\nsession reported.\nThe passages on the 402, the 422, the default timeout and the billing of\noptions also use a live test of this provider that the marketing session ran on\n2026-10-20 (301 decide() calls, 295 answered); each says so where it does.\nEach item that nobody measured is listed under\nNot measured.\n\nOverview\n\nclassify-1 is ScaleDown's decision model. You send one state plus named,\ntyped questions. The model answers every question in a single request and\nreturns a typed answer for each, with a probability instead of a sentence.\nScaleDown speaks the same System One wire as TypeSafe, so NeuroLink describes it\nwith one catalog file, src/lib/providers/catalog/scaledown.json, and no\nprovider code of its own. NeuroLink calls ScaleDown's public endpoint, so a key\nalone configures it: there is no base URL to set.\n\nKey Facts\n\n| | |\n| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Provider id | scaledown; the alias scaledown-classify names it too |\n| Inference type | decide only |\n| Models | classify-1 (the default) and classify, which ScaleDown describes as its latest Classify model. Any other name is refused by ScaleDown with HTTP 422. SCALEDOWN_MODEL sets one |\n| Key | SCALEDOWN_API_KEY, or credentials.scaledown.apiKey. The variable is ScaleDown's own: no text provider reads it |\n| Key header | x-api-key: <key>. NeuroLink never sends Authorization: Bearer to ScaleDown |\n| Endpoint | POST https://api.scaledown.xyz/v1/scaledown; SCALEDOWN_BASE_URL or credentials.scaledown.baseURL can name another base URL (see Base URL) |\n| State | text only: a string goes out unchanged, any other value as its JSON text; see How the state is sent |\n| Questions per request | no cap declared |\n| State window | none declared, so nothing is refused locally on size; ScaleDown's input ceiling is not measured |\n| Media | none. Images and video attached to a decide() call are refused before any request; see Images and video |\n| Default timeout | 30 seconds per attempt, declared in the entry; timeoutMs or --timeout replaces it; see Timeouts |\n| Cost | priced per input token, input only; see Cost for the rate, its source and its date |\n| Precedence | the last of the eight: used automatically only when none of TypeSafe, Laya, XOR, SubQ, Perplexity, Cloudflare Clef or OpenAI Decisions is configured |\n| Status | NeuroLink lists both models as preview |\n\nQuick Start\nGet a key\n\nCreate an account and an API key by following\nScaleDown's documentation. Sign-up gives a free\nallowance; its expiry is not established. ScaleDown's documentation says a\nrequest made with no credit left returns HTTP 402, and","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"","lvl3":""}},
6510
+ {"objectID":"b9df3945bc87bb36e60b7b96008ffdf5fa0d81362bb0d14c8be9be28c055b127","title":"ScaleDown Provider Guide","url":"/docs/getting-started/providers/scaledown#scaledown-provider-guide","content":"A provider of decide — the same typed boolean / choice / score\nanswers as TypeSafe's Jev, Laya, XOR,\nSubQ, Perplexity,\nCloudflare Clef and\nOpenAI Decisions, from ScaleDown's hosted Classify\nmodels. It emits no text at all.\n\nWhere the facts about ScaleDown come from. Everything this guide says about\nScaleDown's endpoint comes from ScaleDown's own documentation and from probes\nthat the marketing session made on 2026-10-15. NeuroLink's project did not\nprobe it: no request to ScaleDown was made in building this provider, and the\n200 replies in NeuroLink's tests are stand-ins written from the documented\nshape, and the error texts in the mocked tests are the ones the marketing\nsession reported.\nThe passages on the 402, the 422, the default timeout and the billing of\noptions also use a live test of this provider that the marketing session ran on\n2026-10-20 (301 decide() calls, 295 answered); each says so where it does.\nEach item that nobody measured is listed under\nNot measured.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"ScaleDown Provider Guide","lvl3":""}},
6511
6511
  {"objectID":"5cd76a87b19e0ac4507befcfdbf6fe2a0b60672ef4b39ba8a48d7108d7e03641","title":"Overview","url":"/docs/getting-started/providers/scaledown#overview","content":"classify-1 is ScaleDown's decision model. You send one state plus named,\ntyped questions. The model answers every question in a single request and\nreturns a typed answer for each, with a probability instead of a sentence.\nScaleDown speaks the same System One wire as TypeSafe, so NeuroLink describes it\nwith one catalog file, src/lib/providers/catalog/scaledown.json, and no\nprovider code of its own. NeuroLink calls ScaleDown's public endpoint, so a key\nalone configures it: there is no base URL to set.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Overview","lvl3":""}},
6512
6512
  {"objectID":"1dc9cfb0502df137f78e02d2f0ce01f1c20021432f32b55e457f4af9aa8c28d3","title":"Key Facts","url":"/docs/getting-started/providers/scaledown#key-facts","content":"| | |\n| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Provider id | scaledown; the alias scaledown-classify names it too |\n| Inference type | decide only |\n| Models | classify-1 (the default) and classify, which ScaleDown describes as its latest Classify model. Any other name is refused by ScaleDown with HTTP 422. SCALEDOWN_MODEL sets one |\n| Key | SCALEDOWN_API_KEY, or credentials.scaledown.apiKey. The variable is ScaleDown's own: no text provider reads it |\n| Key header | x-api-key: <key>.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Key Facts","lvl3":""}},
6513
- {"objectID":"6e1271629d7848d082fcee808db1251840c95379629429d3af24aaacf4cbb453","title":"1. Get a key","url":"/docs/getting-started/providers/scaledown#1-get-a-key","content":"Create an account and an API key by following\nScaleDown's documentation. Sign-up gives a free\nallowance; its expiry is not established. ScaleDown's documentation says a\nrequest made with no credit left returns HTTP 402.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"1. Get a key","lvl3":""}},
6513
+ {"objectID":"6e1271629d7848d082fcee808db1251840c95379629429d3af24aaacf4cbb453","title":"1. Get a key","url":"/docs/getting-started/providers/scaledown#1-get-a-key","content":"Create an account and an API key by following\nScaleDown's documentation. Sign-up gives a free\nallowance; its expiry is not established. ScaleDown's documentation says a\nrequest made with no credit left returns HTTP 402, and a live test saw it return\n402 for a key it does not recognise too (see Errors).","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"1. Get a key","lvl3":""}},
6514
6514
  {"objectID":"59465c6b09139d6862d9a9a23650fd4ef6e24a036f6b80fd29b066d42d2044d2","title":"2. Configure","url":"/docs/getting-started/providers/scaledown#2-configure","content":"Set the key in the environment:\n\nor in the config passed to the SDK, exactly as for any other provider. Values\npassed per call override the constructor's, which override the environment:\n\ncredentials.scaledown is its own slice (apiKey and baseURL). A blank\nSCALEDOWN_API_KEY or credentials.scaledown.apiKey counts as no key.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"2. Configure","lvl3":""}},
6515
6515
  {"objectID":"55f2cd0ebb6971a4211f7f9d1221cf78ea3a539224d700f623346f317039322e","title":"Base URL","url":"/docs/getting-started/providers/scaledown#base-url","content":"SCALEDOWN_BASE_URL and credentials.scaledown.baseURL name the part of the URL\nin front of /v1/scaledown. It can be an origin, such as\nhttps://api.scaledown.xyz, or an origin with a path prefix, such as a gateway\nthat forwards https://gateway.example.com/scaledown to ScaleDown. NeuroLink\ndrops trailing slashes and appends /v1/scaledown, so a value that already ends\nin /v1 would send the request to /v1/v1/scaledown. A blank value is no\noverride. NeuroLink checks nothing else about the value before the request.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Base URL","lvl3":""}},
6516
6516
  {"objectID":"7d53a332341c36ef5f7392a98a61ec8bbf47e87a5d57ec9f47ad8ffdfdc79a18","title":"3. Use it","url":"/docs/getting-started/providers/scaledown#3-use-it","content":"result.model is the model ScaleDown's reply names, which can differ from the\none you asked for (classify is ScaleDown's name for its latest Classify model).\nresult.usage.inputTokens is the reply's usage.input_tokens.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"3. Use it","lvl3":""}},
6517
6517
  {"objectID":"6fa47bf20bb49567239e1e7aa2aaf60c0ddf14f94b5ecd5c9291325af97ee1cc","title":"From the CLI","url":"/docs/getting-started/providers/scaledown#from-the-cli","content":"The text output ends with a Cost: line, priced from the input tokens the reply\nreports (see Cost).","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"From the CLI","lvl3":""}},
6518
- {"objectID":"f0a725ba02edca902073a9425b5e6f11d56bb918130f79c1f4f75c017fb7ab37","title":"How a request is sent","url":"/docs/getting-started/providers/scaledown#how-a-request-is-sent","content":"ScaleDown's wire is System One's, so you write the same questions as for any\nother decide provider.\n\n| NeuroLink | Sent to /v1/scaledown |\n| --------- | ---------------------------------------------------------------------------------------------------------- |\n| the key | the header x-api-key: <key>, beside Content-Type: application/json; there is no Authorization header |\n| the model | model in the body (classify-1 unless you set another) |\n| state | state, always a string; see below |\n| boolean | a noul question with its instructions |\n| choice | a choice question; criteria maps each option name to its description |\n| score | a score question; criteria is an ordered list of level descriptions |\n\nThe reply has the System One shape, an answers object keyed by question id,\nplus model and usage (input_tokens, output_tokens and a cost in USD).\nA boolean answer carries a probability. A choice or score answer carries\nthe confidence the reply reports and a probability for each option or level; when\na reply omits the confidence, NeuroLink uses the highest probability in that\nanswer's distribution instead. Whether ScaleDown's confidence is calibrated is\nnot established, so tune a threshold on it against your own labelled data.\n\nScaleDown's documentation describes a score question's criteria as an ordered\nlist of 2 to 10 level descriptions. NeuroLink does not enforce that.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"How a request is sent","lvl3":""}},
6518
+ {"objectID":"f0a725ba02edca902073a9425b5e6f11d56bb918130f79c1f4f75c017fb7ab37","title":"How a request is sent","url":"/docs/getting-started/providers/scaledown#how-a-request-is-sent","content":"ScaleDown's wire is System One's, so you write the same questions as for any\nother decide provider.\n\n| NeuroLink | Sent to /v1/scaledown |\n| --------- | ---------------------------------------------------------------------------------------------------------- |\n| the key | the header x-api-key: <key>, beside Content-Type: application/json; there is no Authorization header |\n| the model | model in the body (classify-1 unless you set another) |\n| state | state, always a string; see below |\n| boolean | a noul question with its instructions |\n| choice | a choice question; criteria maps each option name to its description |\n| score | a score question; criteria is an ordered list of level descriptions |\n\nThe reply has the System One shape, an answers object keyed by question id,\nplus model and usage (input_tokens, output_tokens and a cost in USD).\nA boolean answer carries a probability. A choice or score answer carries\nthe confidence the reply reports and a probability for each option or level; when\na reply omits the confidence, NeuroLink uses the highest probability in that\nanswer's distribution instead. Whether ScaleDown's confidence is calibrated is\nnot established, so tune a threshold on it against your own labelled data.\n\nScaleDown's documentation describes a score question's criteria as an ordered\nlist of 2 to 10 level descriptions. NeuroLink does not enforce that; ScaleDown\ndoes: a list of 11 levels was refused with HTTP 422 in the live test of\n2026-10-20 ('criteria' must have between 2 and 10 levels, got 11).","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"How a request is sent","lvl3":""}},
6519
6519
  {"objectID":"019697cf87430bd6504ed998cc379cd4c9565713e8336341735eac9e28a05e95","title":"How the state is sent","url":"/docs/getting-started/providers/scaledown#how-the-state-is-sent","content":"ScaleDown's documentation says it reads only the text and document fields of\nthe state, and treats a string state as shorthand for { \"text\": <the string> }.\nNeuroLink therefore sends the state as a string, whatever you pass:\n\n| You pass | ScaleDown receives |\n| ----------------------------------------- | ----------------------------------------------- |\n| a string | the string, unchanged (not trimmed) |\n| an object, an array, a number, a boolean | its JSON text, as one string (JSON.stringify) |\n| {} or [] | the two-character text {} or [] |\n| a string that is empty or only whitespace | nothing: refused locally, before any request |\n\nTwo consequences:\nAn object state is classified as text. ScaleDown does not read an object's\n other keys, so { \"title\": \"...\", \"review\": \"...\" } reaches it as the JSON text\n of that object, and an object that has a text field is not unwrapped either.\n If you want it to read plain prose, pass a string.\n{} and [] are not refused. They are non-blank text, so they are sent\n and ScaleDown classifies the literal characters. ScaleDown itself answers HTTP\n 422 ('state.text' or 'state.document' must be provided) to an empty or\n text-less state object, as the marketing session saw on 2026-10-15, but\n NeuroLink never sends one. Check for an empty container yourself if you want it\n refused.\n\nA blank string is refused as an invalid_request, with a message that names\nScaleDown and the rule (needs non-blank state text) and never repeats the state.\ndecide() rejects with it, tryDecide() returns null, and no request is made.\nThis is a catalog setting, decide.stateEncoding: see\nHow the catalog entry says this.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"How the state is sent","lvl3":""}},
6520
- {"objectID":"909b8119299644649ccc89ea4d6942c9d088298200c8dde9487649c6a689140f","title":"Choice options and billing","url":"/docs/getting-started/providers/scaledown#choice-options-and-billing","content":"ScaleDown's documentation says every option of a choice question is scored by\na separate internal call, so the input tokens a request is charged for grow with\nthe number of options times the length of the state. The marketing session\nreported two data points on 2026-10-15, not measured here: about 1,829 input\ntokens for a 20-option choice and about 13,059 for a 255-option choice, each on a\none-sentence state.\n\nNeuroLink declares no cap on options. A probe by the marketing session saw a\n255-option choice answered and a 300-option choice refused with HTTP 502; the\nlimit between them is not mapped. When ScaleDown answers a request with HTTP 502,\nas it did to the 300-option probe, NeuroLink reports a server error, retries\nonce, and every built-in consumer then treats the decision as failed and falls\nback. Whether a refused request is charged is not established. Keep the number of\noptions of a choice question modest, and see Cost.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Choice options and billing","lvl3":""}},
6520
+ {"objectID":"909b8119299644649ccc89ea4d6942c9d088298200c8dde9487649c6a689140f","title":"Choice options and billing","url":"/docs/getting-started/providers/scaledown#choice-options-and-billing","content":"ScaleDown's documentation says every option of a choice question is scored by\na separate internal call. The input tokens a request was charged for did not\ngrow with the number of options times the length of the state when that was\nmeasured. In the live test of 2026-10-20 one choice question, whose options\neach had a description of about 20 characters, was sent three times with the\nsame 20,000-character state:\n\n| Options | Input tokens charged | If charged as options times state length, scaled from 3 options |\n| ------- | -------------------- | --------------------------------------------------------------- |\n| 3 | 2,966 | 2,966 |\n| 6 | 3,024 | 5,932 |\n| 12 | 3,137 | 11,864 |\n\nThat is the state once, plus about 19 tokens for each option added to the three\n(58 for three more, 171 for nine more). The same 12 options on a 55-character\nstate were charged 436 tokens.\n\nNot known: how ScaleDown counts these tokens, and so what the internal call\nper option adds to the count; whether the amount per option depends on the text\nof the options, the number of questions or the model; and whether the count holds\nbeyond 12 options or 20,000 characters. The marketing session's figures from\n2026-10-15, about 1,829 input tokens for a 20-option choice and about 13,059 for\na 255-option choice, each on a one-sentence state, were not measured again and\nare not reconciled with the line above.\n\nNeuroLink declares no cap on options. A probe by the marketing session saw a\n255-option choice answered and a 300-option choice refused with HTTP 502; the\nlimit between them is not mapped.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Choice options and billing","lvl3":""}},
6521
6521
  {"objectID":"0e9ffbaad88dadb6493a28fd7481227ff45f814672b9224e965f48bf3b8b9e0b","title":"When NeuroLink uses it","url":"/docs/getting-started/providers/scaledown#when-neurolink-uses-it","content":"Every built-in consumer of decide — model routing, relevance-driven\ncompaction, tool routing and RAG planning — asks for the default decision\nprovider. That is the first one that is configured, in the environment or in\nthe credentials passed to the SDK, in the order TypeSafe, Laya,\nXOR, SubQ, Perplexity,\nCloudflare Clef, OpenAI Decisions,\nthen ScaleDown. ScaleDown counts with its key alone. So:\nOnly a ScaleDown key: built-in features use ScaleDown, and each of their\n decisions is then a paid request (see Cost) that still falls back to\n what NeuroLink did before on any failure.\nA ScaleDown key, plus any earlier provider's key: built-in features use\n that earlier provider. ScaleDown runs only where a caller asks for\n provider: \"scaledown\".\nA blank SCALEDOWN_API_KEY: no key. ScaleDown is not configured.\nprovider: \"scaledown\" with no key: an authentication error and no\n request.\n\nScaleDown is last on purpose, and its key is not shared. Apart from ScaleDown, the providers that\ncome after SubQ in the order are the ones whose key is also read by a text\nprovider (Perplexity's, the Cloudflare pair and OpenAI's), placed behind it so\nthat a key set only for a text provider cannot displace a decision provider a\nhost set up on purpose. That reasoning does not cover ScaleDown, whose key\nbelongs to no other provider: it was placed last, after OpenAI Decisions, as a\ndeliberate choice and not by that rule. The effect is that an ambient OPENAI_API_KEY, PERPLEXITY_API_KEY or\nCloudflare pair in the environment outranks a SCALEDOWN_API_KEY set on purpose.\nThe one exception is a gateway's key: an OPENAI_API_KEY found only in the\nenvironment is skipped for the default while OPENAI_BASE_URL, OPENAI_API_BASE\nor credentials.openai.baseURL names a host other than https://api.openai.com,\nso with nothing earlier configured ScaleDown is the default.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"When NeuroLink uses it","lvl3":""}},
6522
6522
  {"objectID":"b06a4af3a93c004935822bcc4b8c3fa7b19a496460ca3d2a9f8ea72446d6c262","title":"How the catalog entry says this","url":"/docs/getting-started/providers/scaledown#how-the-catalog-entry-says-this","content":"scaledown.json needed two optional keys in its decide block that no earlier\nentry set, both read by the shared catalog engine for any entry that sets them.\nAn entry that omits them behaves exactly as before.\n\n| Key | Value | What it does |\n| --------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| authHeader | \"x-api-key\" | The API key is sent as x-api-key: <key> and no Authorization header is sent at all. \"bearer\", the default when the key is omitted, sends Authorization: Bearer <key>. This is the decide request's own setting; quirks.authHeaderStyle belongs to the chat path and is not read here |\n| stateEncoding | \"text\" | The state that goes out is always a string: a string is sent unchanged (not trimmed), any other value as its JSON text, and a state that is blank after trimming is refused before any request as an invalid_request. Omitted means the state goes out as given |\n\nThe rest of the entry is the same kind of data every decide entry carries: the\nsystem-one dialect, order 8, the endpoint {baseURL}/v1/scaledown, the model in\nthe body, trailing slashes dropped from the base URL, and the error rules in the\ntable below. The fields are also described in the generated API reference, under\nDecideBlock.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"How the catalog entry says this","lvl3":""}},
6523
6523
  {"objectID":"9f3c9d52f412bb381d8f770fa11e1de1c1f66ba5b56c324de62a11d5f6cf35bd","title":"Images and video","url":"/docs/getting-started/providers/scaledown#images-and-video","content":"ScaleDown's endpoint reads text only, and the catalog entry declares no media. An\nimage or a video attached to a decide() call for ScaleDown is refused before any\nrequest, as an invalid_request, and the catalog marks both models\nvision: false. This refusal comes before the blank-state check, so a call that\ncarries media is always refused for the media. To decide over images today, use\nXOR, Perplexity,\nCloudflare Clef or OpenAI Decisions.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Images and video","lvl3":""}},
6524
- {"objectID":"32661eaf2994628038e0d49fc81ee5e962c42273147ac024b6b6a66c0d2f2142","title":"Timeouts","url":"/docs/getting-started/providers/scaledown#timeouts","content":"The default is 5,000 ms per attempt (the shared decide default,\nDEFAULT_DECISION_TIMEOUT_MS). A choice question is scored once per option, and\nthe marketing session saw one ScaleDown 504 (Endpoint request timed out) after\nabout 29 seconds. ScaleDown's own latency is not measured here, so a caller with\nmany options or a long state should pass timeoutMs.\n\nA 504 that ScaleDown sends is classified timeout and is not retried. A timeout\nraised by NeuroLink's own clock is retried once.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Timeouts","lvl3":""}},
6524
+ {"objectID":"32661eaf2994628038e0d49fc81ee5e962c42273147ac024b6b6a66c0d2f2142","title":"Timeouts","url":"/docs/getting-started/providers/scaledown#timeouts","content":"The default is 30,000 ms per attempt. The entry declares it (timeouts.decideMs\nin scaledown.json). It is not the 5,000 ms of DEFAULT_DECISION_TIMEOUT_MS:\nthat constant comes after the provider's own default timeout, which is 30,000 ms\non the provider base class, so it is never reached. A provider that wants a\nshorter limit declares one, as TypeSafe, Laya and XOR declare 5,000 ms.\n\n30 seconds is not derived from a measurement of ScaleDown, but a 5-second limit\nwould have cut answers off. In the live test of 2026-10-20, with no timeoutMs, 10 identical\n265-token requests were answered in 0.3 to 9.8 s, and two of them (6.1 s and\n9.8 s) took longer than 5 s. A choice question is scored once per option, and the\nmarketing session saw one ScaleDown 504 (Endpoint request timed out) after\nabout 29 seconds. ScaleDown's own latency is otherwise not measured here, so a\ncaller with many options or a long state should pass timeoutMs.\n\nA 504 that ScaleDown sends is classified timeout and is not retried. A timeout\nraised by NeuroLink's own clock is retried once, so a request that never answers\nfails after about twice the limit (a loopback stand-in, not ScaleDown, that did\nnot answer in 30 s was cut off at 30,000 ms and retried, 60.4 s in all).","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Timeouts","lvl3":""}},
6525
6525
  {"objectID":"a09b830ff06ece8be11a2d4fb2093b734e19a11ba1ba8393b17cce73b3f653b7","title":"Cost","url":"/docs/getting-started/providers/scaledown#cost","content":"List price: USD 0.05 per million input tokens, with output tokens not\ncharged, from ScaleDown's launch post and from the usage.cost in a response, as\nreported to this project by the marketing session on 2026-10-15. This project has\nnot priced a call itself and has not checked the rate since, so check ScaleDown's\ndashboard for the current one.\n\nNeuroLink prices a decision from the input tokens the reply reports, at that one\nrate, and counts output tokens at nothing. It does not read the reply's\nusage.cost. result.usage and the CLI's Cost: line are therefore an estimate\nfrom the list price, not a bill. A reply that reports no usage is priced at zero.\nThe sign-up allowance is a free allowance whose expiry is not established.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Cost","lvl3":""}},
6526
- {"objectID":"9fe742ca7befe0c2159befcd8359f9831dcb2b94f4f462faff3e74b86b47fffc","title":"Errors","url":"/docs/getting-started/providers/scaledown#errors","content":"ScaleDown's documentation lists HTTP 402 (insufficient credits), 422 (invalid),\n502 (model service error) and 504 (timeout). The marketing session also saw, on\n2026-10-15, a 429 Max queue length has been reached (no rate-limit headers; it\ncleared after a few seconds) and a 504 Endpoint request timed out, and each\nwas answered when it retried by hand. NeuroLink shows the detail text of an\nerror body, whether it is a plain string or an object with a message. A body in\nany other form, such as a detail that is an array, gives only\nScaleDown request failed with HTTP <status>; the kind and the retry behaviour\ndo not change.\n\n| Reply | Kind | Retried |\n| ---------------------------------------------------- | ------------------------------------------------------------------ | --------- |\n| 401 | authentication — the provider instance stops retrying | no |\n| 402 | invalid_request — the account has no credit | no |\n| 422 | invalid_request, with ScaleDown's reason when the body shows one | no |\n| 429 | rate_limit | yes, once |\n| 502 | server | yes, once |\n| 504 | timeout | no |\n| other 5xx | server | yes, once |\n| any other status | invalid_request | no |\n| no","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Errors","lvl3":""}},
6526
+ {"objectID":"9fe742ca7befe0c2159befcd8359f9831dcb2b94f4f462faff3e74b86b47fffc","title":"Errors","url":"/docs/getting-started/providers/scaledown#errors","content":"ScaleDown's documentation lists HTTP 402 (insufficient credits), 422 (invalid),\n502 (model service error) and 504 (timeout). The marketing session also saw, on\n2026-10-15, a 429 Max queue length has been reached (no rate-limit headers; it\ncleared after a few seconds) and a 504 Endpoint request timed out, and each\nwas answered when it retried by hand. NeuroLink shows the detail text of an\nerror body, whether it is a plain string, an object with a message, or a list\nof validation errors. A list, which is the form both 422 bodies took in the live\ntest of 2026-10-20, is shown as Request failed validation — <where>: <reason>,\nfor up to five entries and without the input each entry echoes back (that is\nthe request, state included). A body in any other form gives only\nScaleDown request failed with HTTP <status>; the kind and the retry behaviour\ndo not change.\n\n| Reply | Kind | Retried |\n| ---------------------------------------------------- | ------------------------------------------------------------------------- | --------- |\n| 401 | authentication — the provider instance stops retrying | no |\n| 402 | invalid_request — no credit left, or a key ScaleDown does not recognise | no |\n| 422 | invalid_request, with ScaleDown's reason when the body shows one | no |\n| 429 | rate_limit | yes, once |\n| 502 | server | yes, once |\n| 504 | timeout","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Errors","lvl3":""}},
6527
6527
  {"objectID":"586e2eeaa1cb6c511d8eabf5f8e3aea68d19a0585eef1879b0a8432342bd93a7","title":"Data retention","url":"/docs/getting-started/providers/scaledown#data-retention","content":"NeuroLink has not read ScaleDown's data-handling terms. The state and the questions\nof every request go to api.scaledown.xyz, including the ones the built-in\nconsumers send for model routing, compaction, tool routing and RAG planning\nwhenever ScaleDown is the default decision provider. Read ScaleDown's terms before\nsending anything sensitive.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Data retention","lvl3":""}},
6528
- {"objectID":"4109a9812400e518479a4cdd59e777cb617fb3bff2364b69dae5e5fd397ecb04","title":"Not measured","url":"/docs/getting-started/providers/scaledown#not-measured","content":"Each item below is not measured by NeuroLink: nothing it was built or tested\nfrom says what the endpoint does beyond the marketing session's probes, and no\nrequest was made to ScaleDown for this project.\nContext size and input ceiling, for either model. The catalog records a\n contextWindow of 128000 and a maxOutputTokens of 1 for classify-1, and\n both are placeholders the catalog schema requires. 128000 is NeuroLink's own\n default window for a model it knows nothing about, and 1 is the figure SubQ's\n entry uses for a model that returns no text. Neither is a ScaleDown statement,\n and the entry declares no decisionLimits, so nothing is refused locally on\n the size of a state.\nLatency, for any size of state or number of options. The 5-second default\n is the shared decide default, not a measurement of ScaleDown.\nRate limits. A 429 Max queue length has been reached, with no\n rate-limit headers, was seen by the marketing session on 2026-10-15; no limit\n is mapped.\nThe maximum number of options in a choice, of questions per request, and\n of levels in a score, and what ScaleDown does with a list longer than the\n 2 to 10 levels its documentation describes.\nWhether classify moves to a newer model, and what that would change in an\n answer.\nAccuracy of its answers against any other decide provider, and whether its\n confidences are calibrated.\nHow long the free allowance lasts. Its expiry is not established.\nWhether a request ScaleDown refuses (an HTTP 502, 422 or 504) is charged.\nScaleDown's data-retention terms.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Not measured","lvl3":""}},
6529
- {"objectID":"ca6f431704d514c092d35d90635e8dc3bc58e2dc4c4e9d878edd8d0c9f95f466","title":"What has and has not been checked","url":"/docs/getting-started/providers/scaledown#what-has-and-has-not-been-checked","content":"Read or probed by the marketing session on 2026-10-15, not by this project:\n ScaleDown's documentation (the endpoint, the model names, the question kinds,\n the documented error statuses and the price); a request that sent the key as a\n Bearer token answered HTTP 401 {\"detail\":\"x-api-key header is required\"}; a\n model name other than classify or classify-1 answered HTTP 422; a state with\n no text or document answered HTTP 422; the 255-option and 300-option choice\n requests above; the 429 and the 504 above.\nIn this project: the whole behaviour of the entry is tested against stand-in\n replies: the mocked provider suite (the request headers, body and URL, every\n status above, the state rules, the order), the decide provider contract\n (test/fixtures/decide/scaledown/contract.json, whose rows are all stand-ins\n and none claims to have been seen live), and a CLI run against a local\n server on 127.0.0.1. None of them reaches ScaleDown.\nNot captured: any real reply from ScaleDown. The catalog entry records no\n live matrix.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"What has and has not been checked","lvl3":""}},
6530
- {"objectID":"3ea0ed3ccd27e74b56a4ebd0bf35dc7eeceb527711f8d862c00af089437485d9","title":"Troubleshooting","url":"/docs/getting-started/providers/scaledown#troubleshooting","content":"ScaleDown requires an API key — set SCALEDOWN_API_KEY, or pass\n credentials.scaledown.apiKey. A blank value counts as unset.\nA 401 — ScaleDown rejected the key, and the message carries its text. Check\n the key. neurolink.decide() builds a fresh provider for every call, so the\n next call asks again; only a provider instance you keep stops sending after a\n rejection.\nA 402 — ScaleDown's documentation says this means no credit is left. Add\n credit and call again; the same instance works again.\nA 422 — ScaleDown refused the request. The message carries its reason when the\n body shows one; a model name other than classify or classify-1 is one cause.\nScaleDown needs non-blank state text — the state was an empty string or\n only whitespace. Nothing was sent.\nScaleDown does not accept images or video — ScaleDown reads text only. Use\n a decision provider that reads images.\nA 502 on a choice question with very many options — a probe by the\n marketing session saw a 300-option choice refused this way. Send fewer options.\nA 504 / timeout — see Timeouts. Pass a larger timeoutMs.\nA request to /v1/v1/scaledown — SCALEDOWN_BASE_URL ends in /v1. Drop it.\nBuilt-in routing never uses ScaleDown — an earlier provider is also\n configured and takes precedence (see When NeuroLink uses it),\n or no key is set.\nA question is missing from result.answers — the reply left it out, or its\n answer could not be read. An answer that could not be read is logged as a\n warning that names the question and never quotes the reply; a question the\n reply leaves out entirely is not logged, so look for it in result.answers.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Troubleshooting","lvl3":""}},
6528
+ {"objectID":"4109a9812400e518479a4cdd59e777cb617fb3bff2364b69dae5e5fd397ecb04","title":"Not measured","url":"/docs/getting-started/providers/scaledown#not-measured","content":"Each item below is not measured by NeuroLink: nothing it was built or tested\nfrom says what the endpoint does beyond the marketing session's probes, and no\nrequest was made to ScaleDown for this project.\nContext size and input ceiling, for either model. The catalog records a\n contextWindow of 128000 and a maxOutputTokens of 1 for classify-1, and\n both are placeholders the catalog schema requires. 128000 is NeuroLink's own\n default window for a model it knows nothing about, and 1 is the figure SubQ's\n entry uses for a model that returns no text. Neither is a ScaleDown statement,\n and the entry declares no decisionLimits, so nothing is refused locally on\n the size of a state.\nLatency, for any size of state or number of options. The 30-second default\n is declared in the entry, not derived from a measurement of ScaleDown. The live\n test of 2026-10-20 saw 265-token requests answered in 0.3 to 9.8 s and states of\n 20,000 to 150,000 characters (3 options) answered in under 1.3 s.\nRate limits. A 429 Max queue length has been reached, with no\n rate-limit headers, was seen by the marketing session on 2026-10-15; no limit\n is mapped.\nThe maximum number of options in a choice, and of questions per request.\n (A score with 11 levels was refused with HTTP 422 and one with 3 was\n answered; the rest of the range was not probed.)\nWhether classify moves to a newer model, and what that would change in an\n answer.\nAccuracy of its answers against any other decide provider, and whether its\n confidences are calibrated.\nHow long the free allowance lasts. Its expiry is not established.\nWhether a request ScaleDown refuses (an HTTP 502, 422 or 504) is charged.\nScaleDown's data-retention terms.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Not measured","lvl3":""}},
6529
+ {"objectID":"ca6f431704d514c092d35d90635e8dc3bc58e2dc4c4e9d878edd8d0c9f95f466","title":"What has and has not been checked","url":"/docs/getting-started/providers/scaledown#what-has-and-has-not-been-checked","content":"Read or probed by the marketing session on 2026-10-15, not by this project:\n ScaleDown's documentation (the endpoint, the model names, the question kinds,\n the documented error statuses and the price); a request that sent the key as a\n Bearer token answered HTTP 401 {\"detail\":\"x-api-key header is required\"}; a\n model name other than classify or classify-1 answered HTTP 422; a state with\n no text or document answered HTTP 422; the 255-option and 300-option choice\n requests above; the 429 and the 504 above.\nLive test by the marketing session, 2026-10-20: 301 decide() calls\n through this provider (295 answered). It found the 402 for an unrecognised key,\n the list-shaped 422 body, the default timeout and the billing of options\n described above. Its two 422 bodies and the 402 text are what the mocked tests\n for those cases use.\nIn this project: the whole behaviour of the entry is tested against stand-in\n replies, except for the bodies just named: the mocked provider suite (the\n request headers, body and URL, every status above, the state rules, the order),\n the decide provider contract (test/fixtures/decide/scaledown/contract.json,\n whose rows are all stand-ins and none claims to have been seen live), and a CLI\n run against a local server on 127.0.0.1. None of them reaches ScaleDown.\nNot recorded in the catalog entry: a live matrix. evidence.liveMatrix is\n still empty.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"What has and has not been checked","lvl3":""}},
6530
+ {"objectID":"3ea0ed3ccd27e74b56a4ebd0bf35dc7eeceb527711f8d862c00af089437485d9","title":"Troubleshooting","url":"/docs/getting-started/providers/scaledown#troubleshooting","content":"ScaleDown requires an API key — set SCALEDOWN_API_KEY, or pass\n credentials.scaledown.apiKey. A blank value counts as unset.\nA 401 — ScaleDown rejected the key, and the message carries its text. Check\n the key. neurolink.decide() builds a fresh provider for every call, so the\n next call asks again; only a provider instance you keep stops sending after a\n rejection.\nA 402 — either no credit is left or ScaleDown does not recognise the key:\n both arrive as HTTP 402 Insufficient credits, and the message says so. Check\n SCALEDOWN_API_KEY (or the key you passed in credentials.scaledown.apiKey)\n first, then the balance. Fix whichever it is and call again; the same instance\n works again.\nA 422 — ScaleDown refused the request, and the message carries its reason.\n Two were recorded: a model name other than classify or classify-1, and a\n score question with more than 10 levels. For a score question the reason\n is the last of several entries; the ones before it are ScaleDown rejecting the\n other question types.\nScaleDown needs non-blank state text — the state was an empty string or\n only whitespace. Nothing was sent.\nScaleDown does not accept images or video — ScaleDown reads text only. Use\n a decision provider that reads images.\nA 502 on a choice question with very many options — a probe by the\n marketing session saw a 300-option choice refused this way. Send fewer options.\nA 504 / timeout — see Timeouts. Pass a larger timeoutMs.\nA request to /v1/v1/scaledown — SCALEDOWN_BASE_URL ends in /v1. Drop it.\nBuilt-in routing never uses ScaleDown — an earlier provider is also\n configured and takes precedence (see When NeuroLink uses it),\n or no key is set.\nA question is missing from result.answers — the reply left it out, or its\n answer could not be read. An answer that could not be read is logged as a\n warning that names the question and never quotes the reply; a question the\n reply leaves out entirely is not logged, so look for it in result.answers.","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"Troubleshooting","lvl3":""}},
6531
6531
  {"objectID":"00d81ba62df89546669e56393ea60060a6cda460a8332e4f932f98c4126a49cc","title":"See also","url":"/docs/getting-started/providers/scaledown#see-also","content":"The decide inference type\nTypeSafe (Jev) Provider Guide\nLaya Provider Guide\nXOR Provider Guide\nSubQ Provider Guide\nPerplexity Decisions Provider Guide\nCloudflare Clef Provider Guide\nOpenAI Decisions Provider Guide\nScaleDown's documentation","hierarchy":{"lvl0":"Getting Started","lvl1":"ScaleDown Provider Guide","lvl2":"See also","lvl3":""}},
6532
6532
  {"objectID":"ba0463bfd09a65fed9ca1abc4022a8e8e54cbe54859a04dd2d4ae6b46eb7118e","title":"Scaleway Provider Guide","url":"/docs/getting-started/providers/scaleway","content":"Scaleway Provider Guide\n\nScaleway is a Tier-2 catalog provider: an OpenAI-compatible endpoint\ndescribed by one JSON file (src/lib/providers/catalog/scaleway.json) rather\nthan hand-written code. That file is the source of truth for everything on this\npage.\n\nVerification status: this entry is docs-verified only, not yet\nlive-verified. Scaleway's GET /v1/models needs a key (an unauthenticated\nGET answers HTTP 401), so the model ids come from the vendor's public\nsupported-models page and the roster has not been checked against the API. No\naccount was created and no API key was used to build this entry.\nevidence.liveMatrix is null until someone runs the live capability matrix\nwith a real key (see Verification status below).\n\nKey Facts\nProvider id: scaleway\nProtocol: OpenAI-compatible (/chat/completions)\nBase URL: https://api.scaleway.ai/v1 (the\n Using Generative APIs page\n also shows a project-scoped form, https://api.scaleway.ai/<project_id>/v1)\nDefault model: mistral-small-3.2-24b-instruct-2506\nModels in catalog: 11 — the chat models whose \"Available in Serverless?\"\n cell on the\n supported-models page\n is Yes and that are not in that page's End of Life table. Left out: models\n whose cell is No or EOL, the audio transcription and embedding models, and\n six Serverless-Yes models that the End of Life table lists\n (qwen3-coder-30b-a3b-instruct, pixtral-12b-2409, gemma-3-27b-it,\n devstral-2-123b-instruct-2512, holo2-30b-a3b, voxtral-small-24b-2507)\nStreaming: supported — the\n Chat Completions reference\n documents stream with server-sent events and says the last chunk contains\n data: [DONE]\nTool calling: model-dependent — the\n function-calling page\n says \"All the chat models hosted by Scaleway support function calling.\", the\n supported-models page's attribute tables carry a \"Supports function calling\"\n row that reads Yes for each of the eleven catalog ids, and for\n gpt-oss-120b it says \"Currently, this model should be used through the\n Responses API, because the Chat Completions API does not yet support\n tool-calling for this model.\"\nTools while streaming: supported — the function-calling page has a \"Tool\n calling with stream mode\" section\nStructured output: supported — the\n structured-outputs page\n documents json_schema (schema mode) and json_object (JSON mode) and says\n \"All LLMs in the Scaleway library support Structured outputs and JSON mode.\"\nStructured output + tools together: not declared (false) — no combined\n probe was possible without credentials\nEmbeddings: not declared. Scaleway's compatibility page documents an\n Embeddings API and the supported-models page lists embedding models; this\n entry covers chat models\nThinking: not declared (false) — the supported-models page lists\n \"Supported reasoning efforts\" for eight of the catalog models (see the table\n below); the provider-level flag stays false\nBilling: free-with-card (NeuroLink's classification, not a Scaleway\n statement) — see Billing below\nKey format: none declared. The vendor's examples call the value\n SCW_SECRET_KEY\n\nQuick Start\nGet an API key\nVisit: https://account.scaleway.com/register?service=console and create a Scaleway account; the quickstart (https://www.scaleway.com/en/docs/generative-apis/quickstart/) lists \"A Scaleway account logged into the console\" and \"A valid API key\" as requirements\nCreate an API key (https://www.scaleway.com/en/docs/iam/credentials/create-api-keys/): click IAM & API keys on the top-right drop-down menu of the Scaleway console, open the API keys tab and click + Generate API key. The page says: \"A screen displays showing the access key and secret key for your new API key and reminding you that this is your only chance to securely save the secret key.\" The quickstart passes the secret key as the OpenAI client's api_key. Querying models requires one of the permission sets GenerativeApisModelAccess, GenerativeApisFullAccess or AllProductsFullAccess (https://www.scaleway.com/en/docs/generative-apis/api-cli/using-generative-apis/)\nBilling as the vendor states it: the pricing page (https://www.scaleway.com/en/pricing/model-as-a-service/) says \"Try out new models with our free tier: 1 million tokens and 60 minutes of audio transcription.\"; the FAQ (https://www.scaleway.com/en/docs/generative-apis/faq/) says \"After reaching this limit, you will be charged per million tokens processed and per minutes of audio processed.\"; the create-account page (https://www.scaleway.com/en/docs/account/how-to/create-an-account/) says \"Ordering Scaleway resources requires a valid credit card.\"; the payment-method page (https://www.scaleway.com/en/docs/billing/how-to/add-payment-method/) says \"Before you can order Scaleway resources, you must add a payment method to your account.\"; the rate-limits page (https://www.scaleway.com/en/docs/generative-apis/reference-content/rate-limits/) says \"Base limits apply if you registered a valid payment method, and they are increased automatically if you also verify your identit","hierarchy":{"lvl0":"Getting Started","lvl1":"Scaleway Provider Guide","lvl2":"","lvl3":""}},
6533
6533
  {"objectID":"080be05f3ab9716f18b156ceb2ac1cbc0522a29a2479d922c0e6c0033e977231","title":"Scaleway Provider Guide","url":"/docs/getting-started/providers/scaleway#scaleway-provider-guide","content":"Scaleway is a Tier-2 catalog provider: an OpenAI-compatible endpoint\ndescribed by one JSON file (src/lib/providers/catalog/scaleway.json) rather\nthan hand-written code. That file is the source of truth for everything on this\npage.\n\nVerification status: this entry is docs-verified only, not yet\nlive-verified. Scaleway's GET /v1/models needs a key (an unauthenticated\nGET answers HTTP 401), so the model ids come from the vendor's public\nsupported-models page and the roster has not been checked against the API. No\naccount was created and no API key was used to build this entry.\nevidence.liveMatrix is null until someone runs the live capability matrix\nwith a real key (see Verification status below).","hierarchy":{"lvl0":"Getting Started","lvl1":"Scaleway Provider Guide","lvl2":"Scaleway Provider Guide","lvl3":""}},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "13.6.2",
3
+ "version": "13.6.4",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
6
6
  "author": {