@juspay/neurolink 13.1.1 → 13.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -3
- package/README.md +71 -52
- package/dist/browser/neurolink.min.js +411 -411
- package/dist/cli/commands/decide.d.ts +2 -1
- package/dist/cli/commands/decide.js +2 -1
- package/dist/cli/commands/setup.d.ts +1 -1
- package/dist/cli/commands/setup.js +1 -1
- package/dist/cli/factories/commandFactory.js +1 -1
- package/dist/constants/contextWindows.d.ts +7 -0
- package/dist/constants/contextWindows.js +48 -0
- package/dist/constants/enums.d.ts +5 -0
- package/dist/constants/enums.js +6 -0
- package/dist/core/baseProvider.d.ts +0 -1
- package/dist/core/baseProvider.js +5 -2
- package/dist/factories/providerDescriptors.js +13 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/middleware/defaults.d.ts +68 -0
- package/dist/middleware/defaults.js +225 -0
- package/dist/middleware/factory.js +4 -15
- package/dist/middleware/presets.d.ts +12 -0
- package/dist/middleware/presets.js +29 -0
- package/dist/neurolink.d.ts +9 -0
- package/dist/neurolink.js +38 -1
- package/dist/providers/catalog/charm-hyper.json +6 -10
- package/dist/providers/catalog/index.generated.d.ts +1 -1
- package/dist/providers/catalog/index.generated.js +3 -0
- package/dist/providers/catalog/openai-decisions.json +1 -1
- package/dist/providers/catalog/provider-catalog.schema.json +8 -0
- package/dist/providers/catalog/scaledown.json +68 -0
- package/dist/providers/catalog/schema.js +2 -0
- package/dist/providers/configuredDecision.d.ts +5 -0
- package/dist/providers/configuredDecision.js +27 -5
- package/dist/types/classifierRouter.d.ts +10 -9
- package/dist/types/cli.d.ts +2 -2
- package/dist/types/config.d.ts +22 -0
- package/dist/types/providerCatalog.d.ts +22 -0
- package/dist/types/providerCatalog.generated.d.ts +2 -2
- package/dist/types/providers.d.ts +4 -0
- package/dist/utils/pricing.js +12 -0
- package/dist/utils/tokenLimits.js +20 -1
- package/docs-site/static/search-index.json +128 -100
- package/package.json +5 -2
package/dist/neurolink.js
CHANGED
|
@@ -94,6 +94,7 @@ import { hasLifecycleErrorFired, markLifecycleErrorFired, markLifecycleStreamCal
|
|
|
94
94
|
import { interleaveTTSStream } from "./utils/ttsStream.js";
|
|
95
95
|
import { resolveLifecycleTimeoutMs } from "./utils/lifecycleTimeout.js";
|
|
96
96
|
import { cloneOptionsForCallIsolation } from "./utils/cloneOptions.js";
|
|
97
|
+
import { getGlobalMiddleware, readEnvMiddlewarePreset, resolveMiddlewareOptions, snapshotMiddlewareOptions, } from "./middleware/defaults.js";
|
|
97
98
|
import { preserveLiveStreamAccessors } from "./utils/streamResultAccessors.js";
|
|
98
99
|
import { coerceJsonToSchema, recoverScalarRoot, schemaAccepts, } from "./utils/json/coerce.js";
|
|
99
100
|
// Factory processing imports
|
|
@@ -655,6 +656,12 @@ export class NeuroLink {
|
|
|
655
656
|
discoveryPins = new Map();
|
|
656
657
|
// Add orchestration property
|
|
657
658
|
enableOrchestration;
|
|
659
|
+
// Language-model middleware defaults, lowest precedence first: the layer
|
|
660
|
+
// read from NEUROLINK_MIDDLEWARE_PRESET, then the constructor's `middleware`
|
|
661
|
+
// option. The module-level global registry sits between them and is read on
|
|
662
|
+
// each call, so it can change after construction. See middleware/defaults.ts.
|
|
663
|
+
envMiddleware;
|
|
664
|
+
constructorMiddleware;
|
|
658
665
|
// Authentication provider for secure access control
|
|
659
666
|
authProvider;
|
|
660
667
|
pendingAuthConfig;
|
|
@@ -900,6 +907,18 @@ export class NeuroLink {
|
|
|
900
907
|
...tokenDerivedFields,
|
|
901
908
|
};
|
|
902
909
|
}
|
|
910
|
+
/**
|
|
911
|
+
* Fold the instance, global and environment middleware defaults beneath the
|
|
912
|
+
* call's own `middleware`. Runs before the lifecycle helpers so they layer
|
|
913
|
+
* onFinish/onError on top of the resolved config rather than replacing it.
|
|
914
|
+
* With no default set the options are left untouched.
|
|
915
|
+
*/
|
|
916
|
+
applyMiddlewareDefaults(options) {
|
|
917
|
+
const resolved = resolveMiddlewareOptions([this.envMiddleware, getGlobalMiddleware(), this.constructorMiddleware], options.middleware);
|
|
918
|
+
if (resolved !== options.middleware) {
|
|
919
|
+
options.middleware = resolved;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
903
922
|
applyGenerateLifecycleMiddleware(options) {
|
|
904
923
|
if (!options.onFinish && !options.onError) {
|
|
905
924
|
return;
|
|
@@ -1207,6 +1226,13 @@ export class NeuroLink {
|
|
|
1207
1226
|
return metricsTraceContextStorage.getStore() ?? null;
|
|
1208
1227
|
}
|
|
1209
1228
|
constructor(config) {
|
|
1229
|
+
// Validated first: an invalid default must throw before the constructor
|
|
1230
|
+
// starts telemetry or any other process-wide state.
|
|
1231
|
+
this.envMiddleware = readEnvMiddlewarePreset();
|
|
1232
|
+
this.constructorMiddleware =
|
|
1233
|
+
config?.middleware === undefined
|
|
1234
|
+
? undefined
|
|
1235
|
+
: snapshotMiddlewareOptions(config.middleware, "NeuroLink constructor");
|
|
1210
1236
|
if (config?.toolRegistry) {
|
|
1211
1237
|
// A supplied registry (a host's, shared with a worker) keeps its owner's
|
|
1212
1238
|
// roots; per-call executeTool() binds this instance's policy anyway.
|
|
@@ -4157,6 +4183,7 @@ Current user's request: ${currentInput}`;
|
|
|
4157
4183
|
options.input.text = piiResult.text;
|
|
4158
4184
|
}
|
|
4159
4185
|
this.enforceSessionBudget(options.maxBudgetUsd);
|
|
4186
|
+
this.applyMiddlewareDefaults(options);
|
|
4160
4187
|
this.applyGenerateLifecycleMiddleware(options);
|
|
4161
4188
|
await this.applyAuthenticatedRequestContext(options);
|
|
4162
4189
|
return { options, originalPrompt };
|
|
@@ -5470,6 +5497,9 @@ Current user's request: ${currentInput}`;
|
|
|
5470
5497
|
// NL-004: Resolve model aliases/deprecations before processing
|
|
5471
5498
|
isolated.model = resolveModel(isolated.model, this.modelAliasConfig);
|
|
5472
5499
|
bindFileToolRoots(isolated, this.resolveFileToolRoots(isolated.toolRoots));
|
|
5500
|
+
// generateText() reaches the same providers as generate() without passing
|
|
5501
|
+
// through its prelude, so the middleware defaults are folded in here too.
|
|
5502
|
+
this.applyMiddlewareDefaults(isolated);
|
|
5473
5503
|
// Use internal generation method directly
|
|
5474
5504
|
return await this.generateTextInternal(isolated);
|
|
5475
5505
|
}
|
|
@@ -8291,6 +8321,7 @@ Current user's request: ${currentInput}`;
|
|
|
8291
8321
|
this.enforceSessionBudget(options.maxBudgetUsd);
|
|
8292
8322
|
await this.applyAuthenticatedRequestContext(options);
|
|
8293
8323
|
this.emitStreamStartEvents(options, startTime);
|
|
8324
|
+
this.applyMiddlewareDefaults(options);
|
|
8294
8325
|
this.applyStreamLifecycleMiddleware(options);
|
|
8295
8326
|
}
|
|
8296
8327
|
async maybeHandleWorkflowStreamRequest(params) {
|
|
@@ -14298,7 +14329,13 @@ Current user's request: ${currentInput}`;
|
|
|
14298
14329
|
timeoutMs: options.timeoutMs,
|
|
14299
14330
|
});
|
|
14300
14331
|
const answerCount = Object.keys(result.answers).length;
|
|
14301
|
-
|
|
14332
|
+
// `providerName` is what the caller passed, so it can be an alias
|
|
14333
|
+
// (`jev`, `scaledown-classify`). The pricing table is keyed by the
|
|
14334
|
+
// canonical id and does not read the decide catalog, so an alias
|
|
14335
|
+
// priced the decision at 0.
|
|
14336
|
+
const pricedProvider = PROVIDER_ALIAS_INDEX.get(providerName.toLowerCase()) ??
|
|
14337
|
+
providerName;
|
|
14338
|
+
const cost = calculateCost(pricedProvider, result.model, {
|
|
14302
14339
|
input: result.usage.inputTokens,
|
|
14303
14340
|
output: result.usage.outputTokens,
|
|
14304
14341
|
total: result.usage.inputTokens + result.usage.outputTokens,
|
|
@@ -89,11 +89,10 @@
|
|
|
89
89
|
},
|
|
90
90
|
"vision": true,
|
|
91
91
|
"status": "production",
|
|
92
|
-
"description": "Moonshot Kimi K3 — vision-capable per the live roster; reasoning effort levels low/high/xhigh (default high). Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28)."
|
|
92
|
+
"description": "Moonshot Kimi K3 — vision-capable per the live roster; reasoning effort levels low/high/xhigh (default high). Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28). The 16000 max output tokens is Hyper's own cap for this model: Hyper's List Models docs (https://hyper.charm.land/docs/api/list-models.html) define `max_output_tokens` as the model's max output tokens, and the roster still reported 16000 on 2026-10-18. Moonshot's own API allows more (`max_completion_tokens` defaults to 131072 and can be set up to 1048576, per https://platform.kimi.ai/docs/guide/kimi-k3-quickstart), so the cap belongs to this gateway, not to the model."
|
|
93
93
|
},
|
|
94
94
|
"kimi-k2-thinking": {
|
|
95
95
|
"contextWindow": 262144,
|
|
96
|
-
"maxOutputTokens": 26214,
|
|
97
96
|
"pricingPerMTok": {
|
|
98
97
|
"input": 0.6,
|
|
99
98
|
"output": 2.5,
|
|
@@ -101,7 +100,7 @@
|
|
|
101
100
|
},
|
|
102
101
|
"vision": false,
|
|
103
102
|
"status": "production",
|
|
104
|
-
"description": "Moonshot Kimi K2 Thinking — reasoning-first Kimi variant; reasoning effort levels low/high/xhigh (default high). Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28)."
|
|
103
|
+
"description": "Moonshot Kimi K2 Thinking — reasoning-first Kimi variant; reasoning effort levels low/high/xhigh (default high). Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28). No maxOutputTokens is recorded: the roster reports 26214, which is a fixed fraction of the 262144-token context window and reads like a gateway default rather than a model limit."
|
|
105
104
|
},
|
|
106
105
|
"kimi-k2.7-code": {
|
|
107
106
|
"contextWindow": 256000,
|
|
@@ -113,7 +112,7 @@
|
|
|
113
112
|
},
|
|
114
113
|
"vision": true,
|
|
115
114
|
"status": "production",
|
|
116
|
-
"description": "Moonshot Kimi K2.7 Code — code-focused Kimi variant, vision-capable per the live roster; reasoning effort levels low/high/xhigh (default high). Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28)."
|
|
115
|
+
"description": "Moonshot Kimi K2.7 Code — code-focused Kimi variant, vision-capable per the live roster; reasoning effort levels low/high/xhigh (default high). Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28). The 16000 max output tokens is Hyper's own cap for this model: Hyper's List Models docs (https://hyper.charm.land/docs/api/list-models.html) define `max_output_tokens` as the model's max output tokens, and the roster still reported 16000 on 2026-10-18. Moonshot's own API defaults `max_tokens` to 32768 (https://platform.kimi.ai/docs/guide/kimi-k2-7-code-quickstart), so the cap belongs to this gateway, not to the model."
|
|
117
116
|
},
|
|
118
117
|
"glm-5.3": {
|
|
119
118
|
"contextWindow": 1000000,
|
|
@@ -165,7 +164,6 @@
|
|
|
165
164
|
},
|
|
166
165
|
"minimax-m2.7": {
|
|
167
166
|
"contextWindow": 262100,
|
|
168
|
-
"maxOutputTokens": 6553,
|
|
169
167
|
"pricingPerMTok": {
|
|
170
168
|
"input": 0.484,
|
|
171
169
|
"output": 1.852,
|
|
@@ -173,7 +171,7 @@
|
|
|
173
171
|
},
|
|
174
172
|
"vision": false,
|
|
175
173
|
"status": "production",
|
|
176
|
-
"description": "MiniMax M2.7 — prior MiniMax generation. Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28)."
|
|
174
|
+
"description": "MiniMax M2.7 — prior MiniMax generation. Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28). No maxOutputTokens is recorded: the roster reports 6553, which is a fixed fraction of the 262100-token context window and reads like a gateway default rather than a model limit."
|
|
177
175
|
},
|
|
178
176
|
"inkling": {
|
|
179
177
|
"contextWindow": 1048576,
|
|
@@ -273,7 +271,6 @@
|
|
|
273
271
|
},
|
|
274
272
|
"gpt-oss-120b": {
|
|
275
273
|
"contextWindow": 131072,
|
|
276
|
-
"maxOutputTokens": 13107,
|
|
277
274
|
"pricingPerMTok": {
|
|
278
275
|
"input": 0.178,
|
|
279
276
|
"output": 0.68,
|
|
@@ -281,11 +278,10 @@
|
|
|
281
278
|
},
|
|
282
279
|
"vision": false,
|
|
283
280
|
"status": "production",
|
|
284
|
-
"description": "OpenAI's open-weight gpt-oss-120b. Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28)."
|
|
281
|
+
"description": "OpenAI's open-weight gpt-oss-120b. Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28). No maxOutputTokens is recorded: the roster reports 13107, which is a fixed fraction of the 131072-token context window and reads like a gateway default rather than a model limit."
|
|
285
282
|
},
|
|
286
283
|
"gemma-4-26b-a4b-it": {
|
|
287
284
|
"contextWindow": 256000,
|
|
288
|
-
"maxOutputTokens": 25600,
|
|
289
285
|
"pricingPerMTok": {
|
|
290
286
|
"input": 0.098,
|
|
291
287
|
"output": 0.334,
|
|
@@ -293,7 +289,7 @@
|
|
|
293
289
|
},
|
|
294
290
|
"vision": false,
|
|
295
291
|
"status": "production",
|
|
296
|
-
"description": "Google Gemma 4 26B A4B, instruction-tuned. Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28)."
|
|
292
|
+
"description": "Google Gemma 4 26B A4B, instruction-tuned. Context window, max output tokens, vision and pricing per GET https://hyper.charm.land/v1/models (unauthenticated, 2026-09-28). No maxOutputTokens is recorded: the roster reports 25600, which is a fixed fraction of the 256000-token context window and reads like a gateway default rather than a model limit."
|
|
297
293
|
}
|
|
298
294
|
},
|
|
299
295
|
"topModels": [
|
|
@@ -2,4 +2,4 @@ import type { DecideCatalogJson, ProviderCatalogJson } from "../../types/index.j
|
|
|
2
2
|
export declare const CATALOG_JSON_ENTRIES: ProviderCatalogJson[];
|
|
3
3
|
export declare const CATALOG_PROVIDER_IDS: readonly ["a2agent", "abliteration", "above-dev", "ai21", "aiand", "aihubmix", "aionlabs", "ambient", "api-route", "arcee", "atlas-cloud", "avian-io", "baidu-qianfan", "baseten", "bee-heossi", "byteplus-modelark", "bytez", "cerebras", "charm-hyper", "chutes", "cloudflare", "crusoe", "dashscope", "deepinfra", "deepseek", "empiriolabs", "featherless-ai", "fireworks", "friendli", "gmicloud", "gradientai", "groq", "hetzner-inference", "huggingface", "inception-labs", "inceptron", "inco", "inference-net", "io-intelligence", "koscompute", "lemonfox-ai", "lilac", "llmtech", "longcat", "mancer", "meta-model-api", "minimax", "mistral", "moark", "modelscope", "moonshot-ai", "morph", "naver-clova-studio", "nebius", "neuralwatt", "novita", "ovhcloud", "parasail", "pareto-inference", "perplexity", "poolside", "prime-intellect", "reka", "runinfra", "sakana-ai", "sambanova", "sarvam", "scaleway", "siliconflow", "stackit", "stepfun", "subconscious", "synthetic", "telnyx", "thinking-machines", "tinfoil", "together-ai", "umans-ai", "upstage", "venice-ai", "vispark", "vultr-inference", "wafer", "wandb-inference", "xai", "xiaomi-mimo", "z-ai"];
|
|
4
4
|
export declare const DECIDE_CATALOG_JSON_ENTRIES: DecideCatalogJson[];
|
|
5
|
-
export declare const DECIDE_CATALOG_PROVIDER_IDS: readonly ["laya", "openai-decisions", "subq", "xor"];
|
|
5
|
+
export declare const DECIDE_CATALOG_PROVIDER_IDS: readonly ["laya", "openai-decisions", "scaledown", "subq", "xor"];
|
|
@@ -69,6 +69,7 @@ import runinfraJson from "./runinfra.json" with { type: "json" };
|
|
|
69
69
|
import sakanaAiJson from "./sakana-ai.json" with { type: "json" };
|
|
70
70
|
import sambanovaJson from "./sambanova.json" with { type: "json" };
|
|
71
71
|
import sarvamJson from "./sarvam.json" with { type: "json" };
|
|
72
|
+
import scaledownJson from "./scaledown.json" with { type: "json" };
|
|
72
73
|
import scalewayJson from "./scaleway.json" with { type: "json" };
|
|
73
74
|
import siliconflowJson from "./siliconflow.json" with { type: "json" };
|
|
74
75
|
import stackitJson from "./stackit.json" with { type: "json" };
|
|
@@ -280,12 +281,14 @@ export const CATALOG_PROVIDER_IDS = [
|
|
|
280
281
|
export const DECIDE_CATALOG_JSON_ENTRIES = [
|
|
281
282
|
layaJson,
|
|
282
283
|
openaiDecisionsJson,
|
|
284
|
+
scaledownJson,
|
|
283
285
|
subqJson,
|
|
284
286
|
xorJson,
|
|
285
287
|
].map(asDecideEntry);
|
|
286
288
|
export const DECIDE_CATALOG_PROVIDER_IDS = [
|
|
287
289
|
"laya",
|
|
288
290
|
"openai-decisions",
|
|
291
|
+
"scaledown",
|
|
289
292
|
"subq",
|
|
290
293
|
"xor",
|
|
291
294
|
];
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"instructions": [
|
|
61
61
|
"1. Create an API key at https://platform.openai.com/api-keys",
|
|
62
62
|
"2. Set {apiKeyEnvVar}; it is shared with the openai chat provider",
|
|
63
|
-
"3. With no other decision provider configured, {apiKeyEnvVar} alone makes openai-decisions the default decision provider (
|
|
63
|
+
"3. With no other decision provider configured, {apiKeyEnvVar} alone makes openai-decisions the default decision provider (a last resort, after every other one except ScaleDown, as PERPLEXITY_API_KEY does for perplexity-decider), unless OPENAI_BASE_URL, OPENAI_API_BASE or credentials.openai.baseURL names a host other than https://api.openai.com: the key is then taken to be a gateway's and is not used by default. To use it while another provider is configured, name it in a call (provider: \"openai-decisions\" or --provider openai-decisions), but only if the key is OpenAI's, because a named call sends the environment key to api.openai.com. Behind a gateway, route it on purpose with OPENAI_DECISIONS_BASE_URL or credentials.openaiDecisions, or do not use it",
|
|
64
64
|
"4. Optional: OPENAI_DECISIONS_BASE_URL (an origin) and OPENAI_DECISIONS_MODEL (default gpt-6-luna)"
|
|
65
65
|
]
|
|
66
66
|
},
|
|
@@ -142,6 +142,14 @@
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
|
+
"authHeader": {
|
|
146
|
+
"enum": ["bearer", "x-api-key"],
|
|
147
|
+
"description": "How the API key is sent. bearer, the default when omitted, sends Authorization: Bearer <key>. x-api-key sends x-api-key: <key> and no Authorization header at all. This is the decide request's own setting; quirks.authHeaderStyle belongs to the chat path and is not read here."
|
|
148
|
+
},
|
|
149
|
+
"stateEncoding": {
|
|
150
|
+
"enum": ["text"],
|
|
151
|
+
"description": "text makes the state that goes out always a string: a string is sent unchanged (not trimmed), any other value is sent 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."
|
|
152
|
+
},
|
|
145
153
|
"media": {
|
|
146
154
|
"type": "object",
|
|
147
155
|
"additionalProperties": false,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./provider-catalog.schema.json",
|
|
3
|
+
"id": "scaledown",
|
|
4
|
+
"displayName": "ScaleDown",
|
|
5
|
+
"enumTypeName": "ScaleDownModels",
|
|
6
|
+
"aliases": ["scaledown-classify"],
|
|
7
|
+
"inferenceKinds": ["decide"],
|
|
8
|
+
"wire": { "baseURL": "https://api.scaledown.xyz" },
|
|
9
|
+
"models": {
|
|
10
|
+
"default": "classify-1",
|
|
11
|
+
"topModels": ["classify-1", "classify"],
|
|
12
|
+
"catalog": {
|
|
13
|
+
"classify-1": {
|
|
14
|
+
"contextWindow": 128000,
|
|
15
|
+
"maxOutputTokens": 1,
|
|
16
|
+
"vision": false,
|
|
17
|
+
"status": "preview",
|
|
18
|
+
"description": "Recommended - ScaleDown Classify 1, decision model for boolean, choice and score questions; context window and output limit not established"
|
|
19
|
+
},
|
|
20
|
+
"classify": {
|
|
21
|
+
"vision": false,
|
|
22
|
+
"status": "preview",
|
|
23
|
+
"description": "ScaleDown's name for its latest Classify model; whether it moves to a newer model is not established"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"decide": {
|
|
28
|
+
"dialect": "system-one",
|
|
29
|
+
"order": 8,
|
|
30
|
+
"baseURLNormalize": ["strip-trailing-slash"],
|
|
31
|
+
"endpoint": "{baseURL}/v1/scaledown",
|
|
32
|
+
"model": { "placement": "body" },
|
|
33
|
+
"authHeader": "x-api-key",
|
|
34
|
+
"stateEncoding": "text",
|
|
35
|
+
"errors": {
|
|
36
|
+
"rules": [
|
|
37
|
+
{ "status": 401, "kind": "authentication" },
|
|
38
|
+
{ "status": [402, 422], "kind": "invalid_request" },
|
|
39
|
+
{ "status": 429, "kind": "rate_limit" },
|
|
40
|
+
{ "status": 502, "kind": "server" },
|
|
41
|
+
{ "status": 504, "kind": "timeout" },
|
|
42
|
+
{ "statusMin": 500, "kind": "server" }
|
|
43
|
+
],
|
|
44
|
+
"default": "invalid_request",
|
|
45
|
+
"bodyShapes": [{ "path": "detail.message" }, { "path": "detail" }]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"setup": {
|
|
49
|
+
"url": "https://docs.scaledown.ai",
|
|
50
|
+
"apiKeyFormat": null,
|
|
51
|
+
"billingPolicy": "promotional-credit",
|
|
52
|
+
"description": "API key from ScaleDown (see its documentation). Sign-up gives a free allowance; whether it expires is not established",
|
|
53
|
+
"instructions": [
|
|
54
|
+
"1. Create an account and an API key, following https://docs.scaledown.ai",
|
|
55
|
+
"2. Set {apiKeyEnvVar} in your .env file",
|
|
56
|
+
"3. ScaleDown's documentation says the key goes in an x-api-key header rather than Authorization: Bearer, and NeuroLink sends it that way",
|
|
57
|
+
"4. ScaleDown's documentation says a request made with no credit left returns HTTP 402"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"evidence": {
|
|
61
|
+
"rosterVerified": {
|
|
62
|
+
"date": "2026-10-15",
|
|
63
|
+
"method": "Checked by the marketing session, not in this change: ScaleDown's docs at docs.scaledown.ai were read and probes were made against api.scaledown.xyz. No request was made to ScaleDown in this change"
|
|
64
|
+
},
|
|
65
|
+
"liveMatrix": null,
|
|
66
|
+
"addedInPR": "https://github.com/juspay/neurolink/pull/2005"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -600,6 +600,8 @@ const decideBlockSchema = z.strictObject({
|
|
|
600
600
|
placement: z.enum(["body", "body-unless-alias"]),
|
|
601
601
|
alias: z.string().min(1).optional(),
|
|
602
602
|
}),
|
|
603
|
+
authHeader: z.enum(["bearer", "x-api-key"]).optional(),
|
|
604
|
+
stateEncoding: z.enum(["text"]).optional(),
|
|
603
605
|
media: decideMediaSchema.optional(),
|
|
604
606
|
ambientKeyGuard: ambientKeyGuardSchema.optional(),
|
|
605
607
|
requestIdHeaders: z.array(decideHeaderNameSchema).optional(),
|
|
@@ -15,6 +15,11 @@ export declare class ConfiguredDecisionProvider extends SystemOneDecisionProvide
|
|
|
15
15
|
protected missingConfigMessage(): string | undefined;
|
|
16
16
|
protected decisionEndpoint(): string;
|
|
17
17
|
protected decisionHeaders(): Record<string, string>;
|
|
18
|
+
/**
|
|
19
|
+
* The state the dialect is given. Only an entry that sets `stateEncoding`
|
|
20
|
+
* changes it, so every other entry sends exactly what the caller passed.
|
|
21
|
+
*/
|
|
22
|
+
private encodeState;
|
|
18
23
|
protected buildDecisionBody(state: DecisionState, questions: Record<string, Record<string, unknown>>, model: string, media?: DecisionPreparedMedia): Record<string, unknown>;
|
|
19
24
|
protected parseDecisionError(status: number, payload: unknown, requestId: string | undefined): DecisionError;
|
|
20
25
|
protected readRequestId(headers: Headers): string | undefined;
|
|
@@ -10,6 +10,9 @@ import { readPath } from "./decide/paths.js";
|
|
|
10
10
|
import { fillEndpointTemplate } from "./decide/template.js";
|
|
11
11
|
import { SystemOneDecisionProvider } from "./systemOneDecision.js";
|
|
12
12
|
const readText = (value) => typeof value === "string" ? value.trim() : "";
|
|
13
|
+
// JSON.stringify answers undefined for a value JSON cannot hold, such as a
|
|
14
|
+
// state a JavaScript caller left out, although its own return type says string.
|
|
15
|
+
const stateText = (state) => typeof state === "string" ? state : JSON.stringify(state);
|
|
13
16
|
/** A `decide` provider described entirely by its catalog entry. */
|
|
14
17
|
export class ConfiguredDecisionProvider extends SystemOneDecisionProvider {
|
|
15
18
|
entry;
|
|
@@ -68,15 +71,34 @@ export class ConfiguredDecisionProvider extends SystemOneDecisionProvider {
|
|
|
68
71
|
return this.endpoint;
|
|
69
72
|
}
|
|
70
73
|
decisionHeaders() {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
};
|
|
74
|
+
const credential = this.entry.decide.authHeader === "x-api-key"
|
|
75
|
+
? { "x-api-key": this.apiKey }
|
|
76
|
+
: { Authorization: `Bearer ${this.apiKey}` };
|
|
77
|
+
return { ...credential, "Content-Type": "application/json" };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The state the dialect is given. Only an entry that sets `stateEncoding`
|
|
81
|
+
* changes it, so every other entry sends exactly what the caller passed.
|
|
82
|
+
*/
|
|
83
|
+
encodeState(state) {
|
|
84
|
+
if (this.entry.decide.stateEncoding !== "text") {
|
|
85
|
+
return state;
|
|
86
|
+
}
|
|
87
|
+
const text = stateText(state);
|
|
88
|
+
if (text === undefined || text.trim() === "") {
|
|
89
|
+
throw this.decisionError({
|
|
90
|
+
kind: "invalid_request",
|
|
91
|
+
// Never the state itself, which can hold anything the caller was sent.
|
|
92
|
+
message: `${this.vendorLabel()} needs non-blank state text; the state was empty or only whitespace.`,
|
|
93
|
+
retryable: false,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return text;
|
|
75
97
|
}
|
|
76
98
|
buildDecisionBody(state, questions, model, media) {
|
|
77
99
|
const { placement, alias } = this.entry.decide.model;
|
|
78
100
|
return this.dialect().buildBody({
|
|
79
|
-
state,
|
|
101
|
+
state: this.encodeState(state),
|
|
80
102
|
questions,
|
|
81
103
|
model: placement === "body-unless-alias" && model === alias
|
|
82
104
|
? undefined
|
|
@@ -24,16 +24,16 @@ export type ClassifierDifficulty = "trivial" | "simple" | "moderate" | "hard" |
|
|
|
24
24
|
* - `heuristic` — keyword/length scoring. Deterministic, zero latency.
|
|
25
25
|
* - `llm` — a cheap classifier model via the injected `generate`.
|
|
26
26
|
* - `jev` — a decision model, whichever decision provider is configured
|
|
27
|
-
* (TypeSafe's Jev, Laya, XOR, SubQ, Perplexity, Cloudflare Clef
|
|
28
|
-
* Decisions); one round trip that returns the confidence the provider
|
|
27
|
+
* (TypeSafe's Jev, Laya, XOR, SubQ, Perplexity, Cloudflare Clef, OpenAI
|
|
28
|
+
* Decisions or ScaleDown); one round trip that returns the confidence the provider
|
|
29
29
|
* reports (*calibrated* on TypeSafe's Jev, ~400ms there) rather than a
|
|
30
30
|
* self-reported one.
|
|
31
31
|
* - `auto` — `jev` when a decision provider is configured, in the environment
|
|
32
32
|
* or in SDK credentials (`TYPESAFE_API_KEY`, `LAYA_API_KEY` with
|
|
33
33
|
* `LAYA_BASE_URL`, `XOR_API_KEY` with `XOR_BASE_URL`, `SUBQ_API_KEY`, `PERPLEXITY_API_KEY`,
|
|
34
|
-
* `CLOUDFLARE_API_KEY` with `CLOUDFLARE_ACCOUNT_ID`,
|
|
34
|
+
* `CLOUDFLARE_API_KEY` with `CLOUDFLARE_ACCOUNT_ID`, `OPENAI_API_KEY`,
|
|
35
35
|
* which does not count while `OPENAI_BASE_URL`, `OPENAI_API_BASE` or `credentials.openai.baseURL` names a
|
|
36
|
-
* host other than `https://api.openai.com`), otherwise `heuristic`.
|
|
36
|
+
* host other than `https://api.openai.com`, or `SCALEDOWN_API_KEY`), otherwise `heuristic`.
|
|
37
37
|
*/
|
|
38
38
|
export type ClassifierStrategyKind = "heuristic" | "llm" | "jev" | "auto";
|
|
39
39
|
/**
|
|
@@ -195,15 +195,16 @@ export type ClassifierRouterConfig = {
|
|
|
195
195
|
* decision provider is configured, in the environment or in SDK credentials
|
|
196
196
|
* (`TYPESAFE_API_KEY`, `LAYA_API_KEY` with `LAYA_BASE_URL`,
|
|
197
197
|
* `XOR_API_KEY` with `XOR_BASE_URL`, `SUBQ_API_KEY`, `PERPLEXITY_API_KEY`,
|
|
198
|
-
* `CLOUDFLARE_API_KEY` with `CLOUDFLARE_ACCOUNT_ID`,
|
|
199
|
-
* "heuristic" otherwise, so configuring one upgrades routing without any code
|
|
198
|
+
* `CLOUDFLARE_API_KEY` with `CLOUDFLARE_ACCOUNT_ID`, `OPENAI_API_KEY` or
|
|
199
|
+
* `SCALEDOWN_API_KEY`) and "heuristic" otherwise, so configuring one upgrades routing without any code
|
|
200
200
|
* change. Behaviour for a caller with none of these is unchanged. Three of them
|
|
201
201
|
* are shared with a text provider and count here too: `PERPLEXITY_API_KEY`
|
|
202
202
|
* (Perplexity's), the Cloudflare pair (Workers AI's) and `OPENAI_API_KEY` (the
|
|
203
203
|
* OpenAI chat provider's). A caller whose only key is `OPENAI_API_KEY` now
|
|
204
|
-
* resolves to "jev", answered by OpenAI Decisions
|
|
205
|
-
* provider
|
|
206
|
-
*
|
|
204
|
+
* resolves to "jev", answered by OpenAI Decisions (the default decision
|
|
205
|
+
* provider whenever none ahead of it in the order is configured); each
|
|
206
|
+
* classification is then a paid call that still falls back to the heuristic
|
|
207
|
+
* on any failure. The exception is a key that sits beside an
|
|
207
208
|
* `OPENAI_BASE_URL`, `OPENAI_API_BASE` or `credentials.openai.baseURL` naming a host other than
|
|
208
209
|
* `https://api.openai.com`: that key is a gateway's and does not count.
|
|
209
210
|
*/
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -1770,9 +1770,9 @@ export type CliClassifierRouterFlags = {
|
|
|
1770
1770
|
* (--classifier-strategy). "auto" resolves to "jev" when a decision
|
|
1771
1771
|
* provider is configured (TYPESAFE_API_KEY, LAYA_API_KEY with
|
|
1772
1772
|
* LAYA_BASE_URL, XOR_API_KEY with XOR_BASE_URL, SUBQ_API_KEY, PERPLEXITY_API_KEY,
|
|
1773
|
-
* CLOUDFLARE_API_KEY with CLOUDFLARE_ACCOUNT_ID,
|
|
1773
|
+
* CLOUDFLARE_API_KEY with CLOUDFLARE_ACCOUNT_ID, OPENAI_API_KEY, which does
|
|
1774
1774
|
* not count beside an OPENAI_BASE_URL, OPENAI_API_BASE or credentials.openai.baseURL naming a host other
|
|
1775
|
-
* than https://api.openai.com) and "heuristic" otherwise.
|
|
1775
|
+
* than https://api.openai.com, or SCALEDOWN_API_KEY) and "heuristic" otherwise.
|
|
1776
1776
|
*/
|
|
1777
1777
|
classifierStrategy?: string;
|
|
1778
1778
|
/**
|
package/dist/types/config.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { ContextRelevanceOptions } from "./context.js";
|
|
|
17
17
|
import type { DecisionHooks } from "./decision.js";
|
|
18
18
|
import type { SkillsConfig } from "./skills.js";
|
|
19
19
|
import type { KnowledgeGroundingConfig } from "./knowledge.js";
|
|
20
|
+
import type { MiddlewareFactoryOptions } from "./middleware.js";
|
|
20
21
|
/**
|
|
21
22
|
* Main NeuroLink configuration type
|
|
22
23
|
*/
|
|
@@ -184,6 +185,27 @@ export type NeurolinkConstructorConfig = {
|
|
|
184
185
|
* {@link KnowledgeGroundingConfig}.
|
|
185
186
|
*/
|
|
186
187
|
knowledgeGrounding?: KnowledgeGroundingConfig;
|
|
188
|
+
/**
|
|
189
|
+
* Language-model middleware applied to every generate()/stream() on this
|
|
190
|
+
* instance (guardrails, analytics, custom middleware). Spares each call from
|
|
191
|
+
* repeating the same `middleware` option.
|
|
192
|
+
*
|
|
193
|
+
* Precedence, lowest to highest: `NEUROLINK_MIDDLEWARE_PRESET`,
|
|
194
|
+
* `configureGlobalMiddleware()`, this option, then the call's own
|
|
195
|
+
* `middleware`. `middlewareConfig` merges per middleware id, and a key set
|
|
196
|
+
* by a higher layer replaces the lower layer's value; arrays are replaced,
|
|
197
|
+
* never concatenated. A call can switch off an inherited middleware with
|
|
198
|
+
* `disabledMiddleware` or `middlewareConfig.<id>.enabled: false`.
|
|
199
|
+
*
|
|
200
|
+
* Throws a TypeError at construction when this is not an object or its
|
|
201
|
+
* `preset` is not a built-in preset (`default`, `all` or `security`).
|
|
202
|
+
*
|
|
203
|
+
* Unset by default, in which case calls behave exactly as before. Note that
|
|
204
|
+
* `preset: "security"` only turns guardrails on: nothing is redacted until
|
|
205
|
+
* `middlewareConfig.guardrails.config` supplies `badWords` or `modelFilter`.
|
|
206
|
+
* See {@link MiddlewareFactoryOptions}.
|
|
207
|
+
*/
|
|
208
|
+
middleware?: MiddlewareFactoryOptions;
|
|
187
209
|
};
|
|
188
210
|
/**
|
|
189
211
|
* Configuration for MCP enhancement modules wired into generate()/stream() paths.
|
|
@@ -25,6 +25,14 @@ export type CatalogTimeouts = {
|
|
|
25
25
|
};
|
|
26
26
|
export type CatalogModelSpec = {
|
|
27
27
|
contextWindow?: number;
|
|
28
|
+
/**
|
|
29
|
+
* The model's own output-token limit. Enforced at runtime: for a provider
|
|
30
|
+
* with no PROVIDER_MAX_TOKENS row, getSafeMaxTokens clamps a larger
|
|
31
|
+
* requested maxTokens to this value. Record only a number the vendor states
|
|
32
|
+
* as a hard limit; omit it when the source is a roster field that looks
|
|
33
|
+
* derived from the context window, because an omitted value leaves the
|
|
34
|
+
* model uncapped while a wrong one silently truncates callers.
|
|
35
|
+
*/
|
|
28
36
|
maxOutputTokens?: number;
|
|
29
37
|
pricingPerMTok?: CatalogPricingPerMTok;
|
|
30
38
|
vision: boolean;
|
|
@@ -314,6 +322,20 @@ export type DecideBlock = {
|
|
|
314
322
|
/** Request URL; `{baseURL}` is the only placeholder. */
|
|
315
323
|
endpoint: string;
|
|
316
324
|
model: DecideModel;
|
|
325
|
+
/**
|
|
326
|
+
* How the API key is sent. `bearer`, the default when omitted, sends
|
|
327
|
+
* `Authorization: Bearer <key>`. `x-api-key` sends `x-api-key: <key>` and no
|
|
328
|
+
* `Authorization` header at all. This is the decide request's own setting;
|
|
329
|
+
* `quirks.authHeaderStyle` belongs to the chat path and is not read here.
|
|
330
|
+
*/
|
|
331
|
+
authHeader?: "bearer" | "x-api-key";
|
|
332
|
+
/**
|
|
333
|
+
* `text` makes the state that goes out always a string: a string is sent
|
|
334
|
+
* unchanged (not trimmed), any other value is sent as its JSON text, and a
|
|
335
|
+
* state that is blank after trimming is refused before any request, as an
|
|
336
|
+
* `invalid_request`. Omitted means the state goes out as given.
|
|
337
|
+
*/
|
|
338
|
+
stateEncoding?: "text";
|
|
317
339
|
/** Body keys for images and video, after `model`, `state` and `questions`. Omitted means the body never carries media. */
|
|
318
340
|
media?: DecideMedia;
|
|
319
341
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type CatalogProviderName = "a2agent" | "abliteration" | "above-dev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "api-route" | "arcee" | "atlas-cloud" | "avian-io" | "baidu-qianfan" | "baseten" | "bee-heossi" | "byteplus-modelark" | "bytez" | "cerebras" | "charm-hyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherless-ai" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetzner-inference" | "huggingface" | "inception-labs" | "inceptron" | "inco" | "inference-net" | "io-intelligence" | "koscompute" | "laya" | "lemonfox-ai" | "lilac" | "llmtech" | "longcat" | "mancer" | "meta-model-api" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshot-ai" | "morph" | "naver-clova-studio" | "nebius" | "neuralwatt" | "novita" | "openai-decisions" | "ovhcloud" | "parasail" | "pareto-inference" | "perplexity" | "poolside" | "prime-intellect" | "reka" | "runinfra" | "sakana-ai" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "subq" | "synthetic" | "telnyx" | "thinking-machines" | "tinfoil" | "together-ai" | "umans-ai" | "upstage" | "venice-ai" | "vispark" | "vultr-inference" | "wafer" | "wandb-inference" | "xai" | "xiaomi-mimo" | "xor" | "z-ai";
|
|
2
|
-
export type CatalogCredentialKey = "a2agent" | "abliteration" | "aboveDev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "apiRoute" | "arcee" | "atlasCloud" | "avianIo" | "baiduQianfan" | "baseten" | "beeHeossi" | "byteplusModelark" | "bytez" | "cerebras" | "charmHyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherlessAi" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetznerInference" | "huggingFace" | "inceptionLabs" | "inceptron" | "inco" | "inferenceNet" | "ioIntelligence" | "koscompute" | "laya" | "lemonfoxAi" | "lilac" | "llmtech" | "longcat" | "mancer" | "metaModelApi" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshotAi" | "morph" | "naverClovaStudio" | "nebius" | "neuralwatt" | "novita" | "openaiDecisions" | "ovhcloud" | "parasail" | "paretoInference" | "perplexity" | "poolside" | "primeIntellect" | "reka" | "runinfra" | "sakanaAi" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "subq" | "synthetic" | "telnyx" | "thinkingMachines" | "tinfoil" | "together" | "umansAi" | "upstage" | "veniceAi" | "vispark" | "vultrInference" | "wafer" | "wandbInference" | "xai" | "xiaomiMimo" | "xor" | "zAi";
|
|
1
|
+
export type CatalogProviderName = "a2agent" | "abliteration" | "above-dev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "api-route" | "arcee" | "atlas-cloud" | "avian-io" | "baidu-qianfan" | "baseten" | "bee-heossi" | "byteplus-modelark" | "bytez" | "cerebras" | "charm-hyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherless-ai" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetzner-inference" | "huggingface" | "inception-labs" | "inceptron" | "inco" | "inference-net" | "io-intelligence" | "koscompute" | "laya" | "lemonfox-ai" | "lilac" | "llmtech" | "longcat" | "mancer" | "meta-model-api" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshot-ai" | "morph" | "naver-clova-studio" | "nebius" | "neuralwatt" | "novita" | "openai-decisions" | "ovhcloud" | "parasail" | "pareto-inference" | "perplexity" | "poolside" | "prime-intellect" | "reka" | "runinfra" | "sakana-ai" | "sambanova" | "sarvam" | "scaledown" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "subq" | "synthetic" | "telnyx" | "thinking-machines" | "tinfoil" | "together-ai" | "umans-ai" | "upstage" | "venice-ai" | "vispark" | "vultr-inference" | "wafer" | "wandb-inference" | "xai" | "xiaomi-mimo" | "xor" | "z-ai";
|
|
2
|
+
export type CatalogCredentialKey = "a2agent" | "abliteration" | "aboveDev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "apiRoute" | "arcee" | "atlasCloud" | "avianIo" | "baiduQianfan" | "baseten" | "beeHeossi" | "byteplusModelark" | "bytez" | "cerebras" | "charmHyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherlessAi" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetznerInference" | "huggingFace" | "inceptionLabs" | "inceptron" | "inco" | "inferenceNet" | "ioIntelligence" | "koscompute" | "laya" | "lemonfoxAi" | "lilac" | "llmtech" | "longcat" | "mancer" | "metaModelApi" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshotAi" | "morph" | "naverClovaStudio" | "nebius" | "neuralwatt" | "novita" | "openaiDecisions" | "ovhcloud" | "parasail" | "paretoInference" | "perplexity" | "poolside" | "primeIntellect" | "reka" | "runinfra" | "sakanaAi" | "sambanova" | "sarvam" | "scaledown" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "subq" | "synthetic" | "telnyx" | "thinkingMachines" | "tinfoil" | "together" | "umansAi" | "upstage" | "veniceAi" | "vispark" | "vultrInference" | "wafer" | "wandbInference" | "xai" | "xiaomiMimo" | "xor" | "zAi";
|
package/dist/utils/pricing.js
CHANGED
|
@@ -812,6 +812,18 @@ const PRICING = {
|
|
|
812
812
|
// name, so a `"gpt-6-luna"` key would price that chat model at this rate.
|
|
813
813
|
_default: { input: 0.1 / 1_000_000, output: 0 },
|
|
814
814
|
},
|
|
815
|
+
scaledown: {
|
|
816
|
+
// $0.05 per million input tokens, output free: `output: 0` is a price, not
|
|
817
|
+
// a token count, so do not "correct" it from `usage.output_tokens`. A list
|
|
818
|
+
// price from ScaleDown's launch post and from `usage.cost` in a response,
|
|
819
|
+
// both as reported to this project on 2026-10-15; this project has not
|
|
820
|
+
// priced a call itself. Per ScaleDown's docs each choice option is a
|
|
821
|
+
// separate internal call, so input tokens grow with options times state
|
|
822
|
+
// length (not measured here). Only `_default`: LiteLLM, OpenRouter and
|
|
823
|
+
// openai-compatible search every table by model name, so a `"classify"` key
|
|
824
|
+
// would price an unrelated model at this rate.
|
|
825
|
+
_default: { input: 0.05 / 1_000_000, output: 0 },
|
|
826
|
+
},
|
|
815
827
|
"cloudflare-clef": {
|
|
816
828
|
// Workers AI bills Clef per million INPUT tokens ($0.24 for clef, $0.09 for
|
|
817
829
|
// clef-flash); no output price is listed. `output: 0` is a price, not a token
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Provider-specific token limit utilities
|
|
3
3
|
* Provides safe maxTokens values based on provider and model capabilities
|
|
4
4
|
*/
|
|
5
|
-
import { getRuntimeOutputCeiling } from "../constants/contextWindows.js";
|
|
5
|
+
import { getCatalogOutputCeiling, getRuntimeOutputCeiling, } from "../constants/contextWindows.js";
|
|
6
6
|
import { bedrockModelIdForLookup, stripBedrockGeoPrefix, } from "../constants/bedrockIdentifiers.js";
|
|
7
7
|
import { PROVIDER_MAX_TOKENS } from "../core/constants.js";
|
|
8
8
|
import { logger } from "./logger.js";
|
|
@@ -68,6 +68,25 @@ export function getSafeMaxTokens(provider, model, requestedMaxTokens) {
|
|
|
68
68
|
// Get provider-specific limits
|
|
69
69
|
const providerLimits = PROVIDER_MAX_TOKENS[canonicalProvider];
|
|
70
70
|
if (!providerLimits) {
|
|
71
|
+
// A JSON-catalog provider has no PROVIDER_MAX_TOKENS row, but its catalog
|
|
72
|
+
// entry can declare the model's own output ceiling. Only that per-model
|
|
73
|
+
// value counts: the provider-wide default is a 4096 placeholder for many
|
|
74
|
+
// providers. An over-large request is clamped. Unlike the runtime ceiling
|
|
75
|
+
// above, this ceiling is not injected as a default: an unset request stays
|
|
76
|
+
// unset, because a ceiling-sized default would shrink usable input on
|
|
77
|
+
// total-context backends.
|
|
78
|
+
const catalogCeiling = getCatalogOutputCeiling(provider, model);
|
|
79
|
+
if (catalogCeiling !== undefined) {
|
|
80
|
+
if (requestedMaxTokens === undefined || requestedMaxTokens === null) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
if (requestedMaxTokens > catalogCeiling) {
|
|
84
|
+
logger.warn(`Requested maxTokens ${requestedMaxTokens} exceeds the catalog output ceiling ` +
|
|
85
|
+
`of ${catalogCeiling} for ${provider}/${model}. Using ${catalogCeiling} instead.`);
|
|
86
|
+
return catalogCeiling;
|
|
87
|
+
}
|
|
88
|
+
return requestedMaxTokens;
|
|
89
|
+
}
|
|
71
90
|
logger.warn(`Unknown provider ${provider}, no token limits enforced`);
|
|
72
91
|
// Explicit undefined/null check so a caller-supplied 0 is preserved
|
|
73
92
|
// (truthy guard would silently drop 0 here as it does in the restricted branch).
|