@juspay/neurolink 12.18.0 → 12.19.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 +2 -2
- package/README.md +277 -75
- package/dist/browser/neurolink.min.js +396 -396
- package/dist/cli/commands/setup.js +2 -1
- package/dist/cli/factories/commandFactory.d.ts +22 -2
- package/dist/cli/factories/commandFactory.js +14 -2
- package/dist/cli/loop/optionsSchema.js +4 -0
- package/dist/cli/utils/classifierRouterFlags.js +39 -1
- package/dist/constants/enums.d.ts +13 -0
- package/dist/constants/enums.js +14 -0
- package/dist/context/budgetChecker.d.ts +3 -1
- package/dist/context/budgetChecker.js +20 -4
- package/dist/context/contextCompactor.d.ts +43 -3
- package/dist/context/contextCompactor.js +157 -41
- package/dist/context/contextDecision.d.ts +56 -0
- package/dist/context/contextDecision.js +234 -0
- package/dist/core/baseProvider.d.ts +27 -1
- package/dist/core/baseProvider.js +39 -0
- package/dist/core/toolRouting.d.ts +8 -0
- package/dist/core/toolRouting.js +52 -1
- package/dist/core/toolRoutingDecision.d.ts +35 -0
- package/dist/core/toolRoutingDecision.js +194 -0
- package/dist/factories/providerDescriptors.d.ts +15 -0
- package/dist/factories/providerDescriptors.js +57 -1
- package/dist/factories/providerRegistry.js +9 -1
- package/dist/index.d.ts +57 -1
- package/dist/index.js +60 -1
- package/dist/models/manifestRegistry.js +2 -0
- package/dist/models/manifests/typesafe.d.ts +16 -0
- package/dist/models/manifests/typesafe.js +42 -0
- package/dist/neurolink.d.ts +57 -1
- package/dist/neurolink.js +254 -10
- package/dist/observability/exporters/laminarExporter.js +3 -0
- package/dist/observability/exporters/posthogExporter.js +3 -0
- package/dist/observability/utils/spanSerializer.js +4 -0
- package/dist/providers/catalog/schema.d.ts +1 -1
- package/dist/providers/typesafe.d.ts +112 -0
- package/dist/providers/typesafe.js +654 -0
- package/dist/proxy/proxyHistoryTruncation.js +15 -10
- package/dist/rag/pipeline/RAGPipeline.d.ts +3 -1
- package/dist/rag/pipeline/RAGPipeline.js +65 -4
- package/dist/rag/retrieval/searchDecision.d.ts +27 -0
- package/dist/rag/retrieval/searchDecision.js +124 -0
- package/dist/routing/classifierRouter.d.ts +74 -2
- package/dist/routing/classifierRouter.js +232 -32
- package/dist/routing/classifierStrategies.d.ts +33 -1
- package/dist/routing/classifierStrategies.js +237 -0
- package/dist/routing/index.d.ts +1 -1
- package/dist/routing/index.js +1 -1
- package/dist/routing/modelCatalog.d.ts +79 -0
- package/dist/routing/modelCatalog.js +382 -0
- package/dist/telemetry/tracers.d.ts +1 -0
- package/dist/telemetry/tracers.js +1 -0
- package/dist/types/classifierRouter.d.ts +160 -4
- package/dist/types/cli.d.ts +17 -1
- package/dist/types/config.d.ts +12 -0
- package/dist/types/context.d.ts +55 -1
- package/dist/types/decision.d.ts +200 -0
- package/dist/types/decision.js +21 -0
- package/dist/types/generate.d.ts +20 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/providers.d.ts +59 -1
- package/dist/types/providers.js +5 -0
- package/dist/types/rag.d.ts +48 -0
- package/dist/types/span.d.ts +11 -0
- package/dist/types/span.js +11 -0
- package/dist/types/stream.d.ts +13 -0
- package/dist/types/toolRouting.d.ts +56 -0
- package/dist/utils/decisionAnswers.d.ts +56 -0
- package/dist/utils/decisionAnswers.js +95 -0
- package/dist/utils/modelChoices.js +13 -1
- package/dist/utils/pricing.js +11 -0
- package/dist/utils/providerConfig.d.ts +4 -0
- package/dist/utils/providerConfig.js +17 -0
- package/docs-site/static/search-index.json +1 -1
- package/package.json +16 -3
|
@@ -5,6 +5,7 @@ import type { UnknownRecord, JsonValue, StreamingCapability } from "./common.js"
|
|
|
5
5
|
import type { NeuroLink } from "../neurolink.js";
|
|
6
6
|
import { AIProviderName, AnthropicModels, BedrockModels, DeepSeekModels, GoogleAIModels, LlamaCppModels, LMStudioModels, NvidiaNimModels, OpenAIModels, VertexModels } from "../constants/enums.js";
|
|
7
7
|
import type { ValidationSchema } from "./aliases.js";
|
|
8
|
+
import type { DecisionRequest, DecisionResult } from "./decision.js";
|
|
8
9
|
import type { EnhancedGenerateResult, TextGenerationOptions } from "./generate.js";
|
|
9
10
|
import type { MultimodalAudioEntry } from "./file.js";
|
|
10
11
|
import type { StreamOptions, StreamResult } from "./stream.js";
|
|
@@ -273,6 +274,27 @@ export type NeurolinkCredentials = {
|
|
|
273
274
|
apiKey?: string;
|
|
274
275
|
baseURL?: string;
|
|
275
276
|
};
|
|
277
|
+
/** TypeSafe (Jev) — the `decide` inference type, not text generation. */
|
|
278
|
+
typesafe?: {
|
|
279
|
+
apiKey?: string;
|
|
280
|
+
baseURL?: string;
|
|
281
|
+
/**
|
|
282
|
+
* Which transport carries the decision request.
|
|
283
|
+
*
|
|
284
|
+
* - `"direct"` — TypeSafe's own API, `POST /v1/systemone`.
|
|
285
|
+
* - `"gateway"` — Vercel's AI Gateway evaluation-model endpoint, which
|
|
286
|
+
* bills through an existing Vercel account instead of a TypeSafe one.
|
|
287
|
+
*
|
|
288
|
+
* Omitted resolves automatically: `gateway` when only a gateway key is
|
|
289
|
+
* present, `direct` otherwise. The two differ on the wire — the gateway
|
|
290
|
+
* already uses the neutral `boolean`/`probability` vocabulary, carries
|
|
291
|
+
* the model in a header rather than the body, and omits `confidence`
|
|
292
|
+
* entirely — but a caller sees the identical `DecisionResult` either way.
|
|
293
|
+
*/
|
|
294
|
+
transport?: "direct" | "gateway";
|
|
295
|
+
/** Vercel AI Gateway key. Defaults to `AI_GATEWAY_API_KEY`. */
|
|
296
|
+
gatewayApiKey?: string;
|
|
297
|
+
};
|
|
276
298
|
};
|
|
277
299
|
/**
|
|
278
300
|
* Voyage AI /embeddings response shape.
|
|
@@ -767,6 +789,16 @@ export type AIProvider = {
|
|
|
767
789
|
* Generate embedding vectors for multiple text inputs in batch.
|
|
768
790
|
*/
|
|
769
791
|
embedMany(texts: string[], modelName?: string): Promise<number[][]>;
|
|
792
|
+
/**
|
|
793
|
+
* Evaluate a state against a batch of typed questions — the `decide`
|
|
794
|
+
* inference type. Implemented by BaseProvider (throws by default, as
|
|
795
|
+
* `embed` does); only providers whose descriptor declares `"decide"` in
|
|
796
|
+
* `inferenceKinds` override it.
|
|
797
|
+
*
|
|
798
|
+
* Optional so external AIProvider implementations still compile; callers
|
|
799
|
+
* must treat absence as "this provider cannot decide".
|
|
800
|
+
*/
|
|
801
|
+
decide?(request: DecisionRequest): Promise<DecisionResult>;
|
|
770
802
|
setupToolExecutor(sdk: {
|
|
771
803
|
customTools: Map<string, unknown>;
|
|
772
804
|
executeTool: (toolName: string, params: unknown) => Promise<unknown>;
|
|
@@ -1887,11 +1919,22 @@ export type ProviderRegistration = {
|
|
|
1887
1919
|
aliases?: string[];
|
|
1888
1920
|
descriptor?: ProviderDescriptor;
|
|
1889
1921
|
};
|
|
1922
|
+
/**
|
|
1923
|
+
* The kinds of inference a provider can serve.
|
|
1924
|
+
*
|
|
1925
|
+
* `generate` and `stream` both produce text. `decide` produces typed,
|
|
1926
|
+
* calibrated judgements and no text at all — see src/lib/types/decision.ts.
|
|
1927
|
+
* They are peers: a provider may serve any subset.
|
|
1928
|
+
*/
|
|
1929
|
+
export type InferenceKind = "generate" | "stream" | "decide";
|
|
1930
|
+
/** What a descriptor means when it declares no `inferenceKinds`. */
|
|
1931
|
+
export declare const DEFAULT_INFERENCE_KINDS: readonly InferenceKind[];
|
|
1890
1932
|
/**
|
|
1891
1933
|
* Single source of truth for one AI provider's static identity: how it's
|
|
1892
1934
|
* addressed (name/aliases), how it's authenticated (credentialsKey/envVars),
|
|
1893
1935
|
* what it defaults to (defaultModel), and how the rest of the codebase
|
|
1894
|
-
* should treat it (toolSupport/localRuntime/healthCheck).
|
|
1936
|
+
* should treat it (toolSupport/localRuntime/healthCheck/inferenceKinds).
|
|
1937
|
+
* Every consumer
|
|
1895
1938
|
* that used to hand-maintain its own provider table (CLI choices,
|
|
1896
1939
|
* CREDENTIAL_KEY_MAP, env-var checks, health-check dispatch, auto-select
|
|
1897
1940
|
* priority, PROMPT_ONLY_TOOL_PROVIDERS) derives from PROVIDER_DESCRIPTORS
|
|
@@ -1902,6 +1945,20 @@ export type ProviderDescriptor = {
|
|
|
1902
1945
|
name: AIProviderName;
|
|
1903
1946
|
/** Alternate spellings accepted by the CLI and the alias index (kebab-case, shorthand, legacy names). Does not include `name` itself. */
|
|
1904
1947
|
aliases: readonly string[];
|
|
1948
|
+
/**
|
|
1949
|
+
* Which inference types this provider actually serves. Omitted means
|
|
1950
|
+
* `["generate", "stream"]`, which is what every text provider is — so every
|
|
1951
|
+
* existing descriptor keeps its meaning untouched.
|
|
1952
|
+
*
|
|
1953
|
+
* This is the one declarative statement of a provider's modality. Before it
|
|
1954
|
+
* existed, modality was only implied (by `toolSupport`, `healthCheck` and
|
|
1955
|
+
* the auto-select ranks), which is why embedding-only providers like Voyage
|
|
1956
|
+
* and Jina have to implement `getAISDKModel()` as a throw: nothing could
|
|
1957
|
+
* express that they are not text providers. Consumers that build a
|
|
1958
|
+
* generation fallback chain, run the health sweep, or offer CLI model
|
|
1959
|
+
* choices should filter on this rather than special-case a provider name.
|
|
1960
|
+
*/
|
|
1961
|
+
inferenceKinds?: readonly InferenceKind[];
|
|
1905
1962
|
/** Key into NeurolinkCredentials for per-call/per-instance credential overrides. */
|
|
1906
1963
|
credentialsKey: keyof NeurolinkCredentials;
|
|
1907
1964
|
/** Environment variables this provider reads at runtime. */
|
|
@@ -1959,6 +2016,7 @@ export type ProviderDescriptor = {
|
|
|
1959
2016
|
timeouts?: {
|
|
1960
2017
|
generateMs?: number;
|
|
1961
2018
|
streamMs?: number;
|
|
2019
|
+
decideMs?: number;
|
|
1962
2020
|
};
|
|
1963
2021
|
/** Ascending priority (1 = tried first) in the auto-select fallback chain used by getBestProvider(). Undefined = not part of the auto-select chain. */
|
|
1964
2022
|
autoSelectPriority?: number;
|
package/dist/types/providers.js
CHANGED
|
@@ -109,3 +109,8 @@ export const DEFAULT_PROVIDER_CONFIGS = [
|
|
|
109
109
|
models: [OpenAIModels.GPT_4O, OpenAIModels.GPT_4O_MINI],
|
|
110
110
|
},
|
|
111
111
|
];
|
|
112
|
+
/** What a descriptor means when it declares no `inferenceKinds`. */
|
|
113
|
+
export const DEFAULT_INFERENCE_KINDS = [
|
|
114
|
+
"generate",
|
|
115
|
+
"stream",
|
|
116
|
+
];
|
package/dist/types/rag.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Tool } from "./tools.js";
|
|
2
2
|
import type { EmbedInput } from "./providers.js";
|
|
3
|
+
import type { DecisionCallerFn } from "./decision.js";
|
|
3
4
|
/**
|
|
4
5
|
* RAG Type Definitions
|
|
5
6
|
*
|
|
@@ -234,6 +235,16 @@ export type RAGPipelineConfig = {
|
|
|
234
235
|
rerankingModel?: EmbeddingModelConfig;
|
|
235
236
|
/** Multi-modal RAG configuration (image + text embeddings) */
|
|
236
237
|
multiModal?: MultiModalRAGConfig;
|
|
238
|
+
/**
|
|
239
|
+
* Fail-open decision caller — typically a bound `NeuroLink.tryDecide`.
|
|
240
|
+
*
|
|
241
|
+
* When supplied AND a decision provider is configured, each query gets its
|
|
242
|
+
* own retrieval plan (`topK`, `hybrid`, `graph`, `rerank`) instead of the
|
|
243
|
+
* four static config values. An explicit per-call `QueryOptions` field
|
|
244
|
+
* always wins, and a capability this config did not enable can never be
|
|
245
|
+
* turned on by the plan.
|
|
246
|
+
*/
|
|
247
|
+
decide?: DecisionCallerFn;
|
|
237
248
|
};
|
|
238
249
|
/**
|
|
239
250
|
* Ingestion options
|
|
@@ -272,6 +283,12 @@ export type QueryOptions = {
|
|
|
272
283
|
systemPrompt?: string;
|
|
273
284
|
/** Temperature for generation */
|
|
274
285
|
temperature?: number;
|
|
286
|
+
/**
|
|
287
|
+
* Set false to skip per-query retrieval planning even when a decision
|
|
288
|
+
* provider is configured. Fields passed explicitly here already override
|
|
289
|
+
* the plan, so this is for turning the extra round trip off entirely.
|
|
290
|
+
*/
|
|
291
|
+
plan?: boolean;
|
|
275
292
|
};
|
|
276
293
|
/**
|
|
277
294
|
* Query response
|
|
@@ -294,6 +311,8 @@ export type RAGResponse = {
|
|
|
294
311
|
retrievalMethod: string;
|
|
295
312
|
chunksRetrieved: number;
|
|
296
313
|
reranked: boolean;
|
|
314
|
+
/** The per-query retrieval plan, when one was decided. */
|
|
315
|
+
plan?: SearchPlanResult;
|
|
297
316
|
};
|
|
298
317
|
};
|
|
299
318
|
/**
|
|
@@ -1474,3 +1493,32 @@ export type ImageLoaderOptions = {
|
|
|
1474
1493
|
/** Custom headers for URL fetches */
|
|
1475
1494
|
headers?: Record<string, string>;
|
|
1476
1495
|
};
|
|
1496
|
+
/**
|
|
1497
|
+
* What a pipeline is *able* to do, handed to the per-search planner so it
|
|
1498
|
+
* never suggests a capability that is not configured.
|
|
1499
|
+
*/
|
|
1500
|
+
export type SearchPlanCapabilities = {
|
|
1501
|
+
/** The configured topK a breadth reading is scaled against. */
|
|
1502
|
+
defaultTopK: number;
|
|
1503
|
+
canHybrid: boolean;
|
|
1504
|
+
canGraph: boolean;
|
|
1505
|
+
canRerank: boolean;
|
|
1506
|
+
/** Per-call timeout override for the decision request. */
|
|
1507
|
+
timeoutMs?: number;
|
|
1508
|
+
};
|
|
1509
|
+
/**
|
|
1510
|
+
* A suggested retrieval plan for one query. Every field is optional and means
|
|
1511
|
+
* "use this unless the caller passed an explicit option".
|
|
1512
|
+
*/
|
|
1513
|
+
export type SearchPlanResult = {
|
|
1514
|
+
topK?: number;
|
|
1515
|
+
hybrid?: boolean;
|
|
1516
|
+
graph?: boolean;
|
|
1517
|
+
rerank?: boolean;
|
|
1518
|
+
/** Index into the breadth rubric the topK was derived from. */
|
|
1519
|
+
breadthLevel?: number;
|
|
1520
|
+
/** Resolved decision model id, for telemetry. */
|
|
1521
|
+
model: string;
|
|
1522
|
+
/** Round trip in milliseconds. */
|
|
1523
|
+
latencyMs: number;
|
|
1524
|
+
};
|
package/dist/types/span.d.ts
CHANGED
|
@@ -15,6 +15,17 @@ export declare enum SpanType {
|
|
|
15
15
|
TOOL_CALL = "tool.call",
|
|
16
16
|
/** LLM generation request */
|
|
17
17
|
MODEL_GENERATION = "model.generation",
|
|
18
|
+
/**
|
|
19
|
+
* Decision-model request (the `decide` inference type).
|
|
20
|
+
*
|
|
21
|
+
* Deliberately NOT folded into MODEL_GENERATION. A decision emits no text,
|
|
22
|
+
* so it has no output tokens, no finish reason and a latency distribution
|
|
23
|
+
* roughly an order of magnitude tighter; counting it as a generation would
|
|
24
|
+
* distort generation counts, p50/p95 latency and the output-token
|
|
25
|
+
* aggregate simultaneously, and would make a cost dashboard attribute
|
|
26
|
+
* fractions of a cent to the same bucket as a frontier model.
|
|
27
|
+
*/
|
|
28
|
+
MODEL_DECISION = "model.decision",
|
|
18
29
|
/** Embedding generation (reserved for future embedding API) */
|
|
19
30
|
EMBEDDING = "embedding",
|
|
20
31
|
/** Retrieval operation (reserved for future RAG support) */
|
package/dist/types/span.js
CHANGED
|
@@ -16,6 +16,17 @@ export var SpanType;
|
|
|
16
16
|
SpanType["TOOL_CALL"] = "tool.call";
|
|
17
17
|
/** LLM generation request */
|
|
18
18
|
SpanType["MODEL_GENERATION"] = "model.generation";
|
|
19
|
+
/**
|
|
20
|
+
* Decision-model request (the `decide` inference type).
|
|
21
|
+
*
|
|
22
|
+
* Deliberately NOT folded into MODEL_GENERATION. A decision emits no text,
|
|
23
|
+
* so it has no output tokens, no finish reason and a latency distribution
|
|
24
|
+
* roughly an order of magnitude tighter; counting it as a generation would
|
|
25
|
+
* distort generation counts, p50/p95 latency and the output-token
|
|
26
|
+
* aggregate simultaneously, and would make a cost dashboard attribute
|
|
27
|
+
* fractions of a cent to the same bucket as a frontier model.
|
|
28
|
+
*/
|
|
29
|
+
SpanType["MODEL_DECISION"] = "model.decision";
|
|
19
30
|
/** Embedding generation (reserved for future embedding API) */
|
|
20
31
|
SpanType["EMBEDDING"] = "embedding";
|
|
21
32
|
/** Retrieval operation (reserved for future RAG support) */
|
package/dist/types/stream.d.ts
CHANGED
|
@@ -293,6 +293,19 @@ export type ExecutionControlOptions = {
|
|
|
293
293
|
beforeStepTimeoutMs?: number;
|
|
294
294
|
};
|
|
295
295
|
export type StreamOptions = {
|
|
296
|
+
/**
|
|
297
|
+
* Fraction of the model's context window at which compaction runs for this
|
|
298
|
+
* request, replacing the 0.8 default.
|
|
299
|
+
*
|
|
300
|
+
* A request that needs only the current message does not need the whole
|
|
301
|
+
* window kept warm for it, and compacting earlier is free; a request that
|
|
302
|
+
* depends on the entire conversation should compact as late as possible.
|
|
303
|
+
* When the classifier router runs with a decision model it fills this in
|
|
304
|
+
* per request — always at or below the default, never above, because a
|
|
305
|
+
* request that overflows the window fails hard and `ModelPool` records that
|
|
306
|
+
* failure as a permanent cooldown.
|
|
307
|
+
*/
|
|
308
|
+
compactionThreshold?: number;
|
|
296
309
|
/**
|
|
297
310
|
* Opt this stream call into the knowledge grounding configured on the
|
|
298
311
|
* NeuroLink instance. Defaults to `false` when omitted.
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* identical to routing being disabled.
|
|
17
17
|
*/
|
|
18
18
|
import type { GenerateOptions, GenerateResult } from "./generate.js";
|
|
19
|
+
import type { DecisionCallerFn } from "./decision.js";
|
|
19
20
|
/** One routable server as declared by the host application. */
|
|
20
21
|
export type ToolRoutingServerDescriptor = {
|
|
21
22
|
/**
|
|
@@ -171,6 +172,17 @@ export type ToolRoutingConfig = {
|
|
|
171
172
|
* off (or fails) the granularity falls back to `"server"` automatically.
|
|
172
173
|
*/
|
|
173
174
|
granularity?: "server" | "tool";
|
|
175
|
+
/**
|
|
176
|
+
* How confidently a decision model must rule a server OUT before its tools
|
|
177
|
+
* are withheld. Default 0.6.
|
|
178
|
+
*
|
|
179
|
+
* Only consulted when a decision provider is configured, in which case one
|
|
180
|
+
* calibrated yes/no question per server replaces the generative router. The
|
|
181
|
+
* bar is deliberately asymmetric and high: keeping an unneeded server costs
|
|
182
|
+
* a few hundred tokens, while dropping a needed one costs the turn, because
|
|
183
|
+
* the model cannot call — or even ask for — a tool it was never shown.
|
|
184
|
+
*/
|
|
185
|
+
minDropConfidence?: number;
|
|
174
186
|
};
|
|
175
187
|
/** Catalog entry pairing a server descriptor with its registered tool names. */
|
|
176
188
|
export type ToolRoutingCatalogEntry = {
|
|
@@ -248,6 +260,12 @@ export type ToolRoutingDecision = {
|
|
|
248
260
|
* applied (outcome === "applied").
|
|
249
261
|
*/
|
|
250
262
|
granularity?: "server" | "tool";
|
|
263
|
+
/**
|
|
264
|
+
* Which strategy produced this decision. Absent on skip/fail-open paths that
|
|
265
|
+
* never reached one. "decision" is the calibrated per-server router,
|
|
266
|
+
* "embedding" the L2 retriever, "llm" the generative router.
|
|
267
|
+
*/
|
|
268
|
+
strategy?: "decision" | "embedding" | "llm";
|
|
251
269
|
};
|
|
252
270
|
/** Parameters for `resolveToolRoutingExclusions()`. */
|
|
253
271
|
export type ToolRoutingResolutionParams = {
|
|
@@ -265,6 +283,25 @@ export type ToolRoutingResolutionParams = {
|
|
|
265
283
|
timeoutMs: number;
|
|
266
284
|
/** Invokes the router LLM — `NeuroLink.generate` bound by the caller. */
|
|
267
285
|
generateFn: (options: GenerateOptions) => Promise<GenerateResult>;
|
|
286
|
+
/**
|
|
287
|
+
* Invokes a decision model — `NeuroLink.tryDecide` bound by the caller.
|
|
288
|
+
* When supplied AND a decision provider is configured, one yes/no question
|
|
289
|
+
* per server replaces the router LLM call: ~400ms and ~$0.00002 instead of
|
|
290
|
+
* a generative call on a 15-second budget, and a calibrated probability per
|
|
291
|
+
* server instead of a list that cannot express doubt.
|
|
292
|
+
*
|
|
293
|
+
* Omitting it reproduces the LLM-only behaviour exactly, and so does
|
|
294
|
+
* supplying it with no decision provider configured — the caller's bound
|
|
295
|
+
* `tryDecide` returns null and the LLM path runs.
|
|
296
|
+
*/
|
|
297
|
+
decideFn?: DecisionCallerFn;
|
|
298
|
+
/**
|
|
299
|
+
* How confidently the decision router must answer "no" before a server's
|
|
300
|
+
* tools are withheld. Default 0.6. Deliberately asymmetric: a wrongly
|
|
301
|
+
* dropped server breaks the turn, a wrongly kept one costs a few hundred
|
|
302
|
+
* tokens.
|
|
303
|
+
*/
|
|
304
|
+
decisionMinDropConfidence?: number;
|
|
268
305
|
/**
|
|
269
306
|
* Optional callback invoked once per resolution with a structured summary of
|
|
270
307
|
* the routing decision. Called on every return path (applied, skipped,
|
|
@@ -348,3 +385,22 @@ export type ToolRetrievalSelectOptions = {
|
|
|
348
385
|
*/
|
|
349
386
|
timeoutMs?: number;
|
|
350
387
|
};
|
|
388
|
+
/**
|
|
389
|
+
* What the decision-model tool router concluded. Distinct from
|
|
390
|
+
* {@link ToolRoutingDecision}, which is the telemetry record for a routing
|
|
391
|
+
* turn regardless of which strategy produced it.
|
|
392
|
+
*/
|
|
393
|
+
export type ToolRoutingDecisionOutcome = {
|
|
394
|
+
/** Servers whose tools stay available. */
|
|
395
|
+
selectedServerIds: string[];
|
|
396
|
+
/** Servers the model confidently ruled out. */
|
|
397
|
+
excludedServerIds: string[];
|
|
398
|
+
/** Flattened tool names to add to the request denylist. */
|
|
399
|
+
excludedToolNames: string[];
|
|
400
|
+
/** How many servers came back with a usable, confident answer. */
|
|
401
|
+
answeredCount: number;
|
|
402
|
+
/** Resolved decision model id, for telemetry. */
|
|
403
|
+
model: string;
|
|
404
|
+
/** Round trip in milliseconds. */
|
|
405
|
+
latencyMs: number;
|
|
406
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readers for a decision model's answer map.
|
|
3
|
+
*
|
|
4
|
+
* Every reader validates at runtime and returns `undefined` for a missing id
|
|
5
|
+
* **or** a type mismatch, so no call site needs a type assertion (CLAUDE.md
|
|
6
|
+
* rule 14) and "no opinion" stays distinguishable from "an opinion of 0".
|
|
7
|
+
*
|
|
8
|
+
* @module utils/decisionAnswers
|
|
9
|
+
*/
|
|
10
|
+
import type { DecisionAnswerMap, DecisionBooleanGate, DecisionChoiceReading, DecisionScoreReading, ProviderDescriptor } from "../types/index.js";
|
|
11
|
+
/**
|
|
12
|
+
* Read a yes/no answer as a probability.
|
|
13
|
+
*
|
|
14
|
+
* Returns undefined when the id is absent or the answer was a different
|
|
15
|
+
* type — a caller can therefore tell "not answered" from "answered 0".
|
|
16
|
+
*/
|
|
17
|
+
export declare function readDecisionBoolean(answers: DecisionAnswerMap, id: string): number | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Read a choice answer, including the full ranking.
|
|
20
|
+
*
|
|
21
|
+
* `ranked` is the distribution sorted highest-first. This is what makes one
|
|
22
|
+
* choice question over N options a ranking of all N — the basis for picking
|
|
23
|
+
* from a large catalogue in a single request.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readDecisionChoice(answers: DecisionAnswerMap, id: string): DecisionChoiceReading | undefined;
|
|
26
|
+
export declare function readDecisionScore(answers: DecisionAnswerMap, id: string): DecisionScoreReading | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* A yes/no answer carries no confidence of its own, so certainty has to be
|
|
29
|
+
* inferred from how far the probability sits from a coin flip. 0.5 → 0, and
|
|
30
|
+
* 0 or 1 → 1.
|
|
31
|
+
*/
|
|
32
|
+
export declare function decisionBooleanConfidence(probability: number): number;
|
|
33
|
+
/** Whether a descriptor declares support for a given inference type. */
|
|
34
|
+
export declare function servesInferenceKind(descriptor: Pick<ProviderDescriptor, "inferenceKinds">, kind: "generate" | "stream" | "decide"): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Read a yes/no answer as an actionable decision, or `undefined` when there
|
|
37
|
+
* is not enough signal to act.
|
|
38
|
+
*
|
|
39
|
+
* Three outcomes, and keeping them distinct is the whole point: `true` (a
|
|
40
|
+
* confident yes), `false` (a confident no), and `undefined` (unanswered, the
|
|
41
|
+
* wrong type, or too close to a coin flip). Every consumer of the `decide`
|
|
42
|
+
* inference type needs exactly this, so the bars live here rather than being
|
|
43
|
+
* re-invented — inconsistently — at each call site.
|
|
44
|
+
*/
|
|
45
|
+
export declare function gateDecisionBoolean(answers: DecisionAnswerMap, id: string, gate?: DecisionBooleanGate): boolean | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Build a question id that survives a round trip.
|
|
48
|
+
*
|
|
49
|
+
* Question ids are the only thing tying an answer back to what it was asked
|
|
50
|
+
* about, and a batch mixes heterogeneous questions (one per server, one per
|
|
51
|
+
* message, plus gates), so the namespace prefix is what demultiplexes them.
|
|
52
|
+
* The index — not the subject's own name — is the key, because ids from the
|
|
53
|
+
* wild (server ids, model ids, file paths) are not guaranteed to be distinct
|
|
54
|
+
* after any normalisation the wire might apply.
|
|
55
|
+
*/
|
|
56
|
+
export declare function decisionKey(namespace: string, index: number): string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readers for a decision model's answer map.
|
|
3
|
+
*
|
|
4
|
+
* Every reader validates at runtime and returns `undefined` for a missing id
|
|
5
|
+
* **or** a type mismatch, so no call site needs a type assertion (CLAUDE.md
|
|
6
|
+
* rule 14) and "no opinion" stays distinguishable from "an opinion of 0".
|
|
7
|
+
*
|
|
8
|
+
* @module utils/decisionAnswers
|
|
9
|
+
*/
|
|
10
|
+
import { DEFAULT_INFERENCE_KINDS } from "../types/index.js";
|
|
11
|
+
/**
|
|
12
|
+
* Read a yes/no answer as a probability.
|
|
13
|
+
*
|
|
14
|
+
* Returns undefined when the id is absent or the answer was a different
|
|
15
|
+
* type — a caller can therefore tell "not answered" from "answered 0".
|
|
16
|
+
*/
|
|
17
|
+
export function readDecisionBoolean(answers, id) {
|
|
18
|
+
const answer = answers[id];
|
|
19
|
+
return answer?.type === "boolean" ? answer.probability : undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read a choice answer, including the full ranking.
|
|
23
|
+
*
|
|
24
|
+
* `ranked` is the distribution sorted highest-first. This is what makes one
|
|
25
|
+
* choice question over N options a ranking of all N — the basis for picking
|
|
26
|
+
* from a large catalogue in a single request.
|
|
27
|
+
*/
|
|
28
|
+
export function readDecisionChoice(answers, id) {
|
|
29
|
+
const answer = answers[id];
|
|
30
|
+
if (answer?.type !== "choice") {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const { choice, confidence, probabilities } = answer;
|
|
34
|
+
const ranked = Object.entries(probabilities)
|
|
35
|
+
.map(([name, probability]) => ({ name, probability }))
|
|
36
|
+
.sort((a, b) => b.probability - a.probability);
|
|
37
|
+
return { choice, confidence, probabilities, ranked };
|
|
38
|
+
}
|
|
39
|
+
export function readDecisionScore(answers, id) {
|
|
40
|
+
const answer = answers[id];
|
|
41
|
+
if (answer?.type !== "score") {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const { score, confidence, legend, probabilities } = answer;
|
|
45
|
+
return { score, confidence, legend, probabilities };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A yes/no answer carries no confidence of its own, so certainty has to be
|
|
49
|
+
* inferred from how far the probability sits from a coin flip. 0.5 → 0, and
|
|
50
|
+
* 0 or 1 → 1.
|
|
51
|
+
*/
|
|
52
|
+
export function decisionBooleanConfidence(probability) {
|
|
53
|
+
return Math.abs(probability - 0.5) * 2;
|
|
54
|
+
}
|
|
55
|
+
/** Whether a descriptor declares support for a given inference type. */
|
|
56
|
+
export function servesInferenceKind(descriptor, kind) {
|
|
57
|
+
return (descriptor.inferenceKinds ?? DEFAULT_INFERENCE_KINDS).includes(kind);
|
|
58
|
+
}
|
|
59
|
+
/** Default bars for {@link gateDecisionBoolean}. */
|
|
60
|
+
const DEFAULT_BOOLEAN_MIN_PROBABILITY = 0.5;
|
|
61
|
+
const DEFAULT_BOOLEAN_MIN_CONFIDENCE = 0.4;
|
|
62
|
+
/**
|
|
63
|
+
* Read a yes/no answer as an actionable decision, or `undefined` when there
|
|
64
|
+
* is not enough signal to act.
|
|
65
|
+
*
|
|
66
|
+
* Three outcomes, and keeping them distinct is the whole point: `true` (a
|
|
67
|
+
* confident yes), `false` (a confident no), and `undefined` (unanswered, the
|
|
68
|
+
* wrong type, or too close to a coin flip). Every consumer of the `decide`
|
|
69
|
+
* inference type needs exactly this, so the bars live here rather than being
|
|
70
|
+
* re-invented — inconsistently — at each call site.
|
|
71
|
+
*/
|
|
72
|
+
export function gateDecisionBoolean(answers, id, gate) {
|
|
73
|
+
const probability = readDecisionBoolean(answers, id);
|
|
74
|
+
if (probability === undefined) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const minConfidence = gate?.minConfidence ?? DEFAULT_BOOLEAN_MIN_CONFIDENCE;
|
|
78
|
+
if (decisionBooleanConfidence(probability) < minConfidence) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return (probability >= (gate?.minProbability ?? DEFAULT_BOOLEAN_MIN_PROBABILITY));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build a question id that survives a round trip.
|
|
85
|
+
*
|
|
86
|
+
* Question ids are the only thing tying an answer back to what it was asked
|
|
87
|
+
* about, and a batch mixes heterogeneous questions (one per server, one per
|
|
88
|
+
* message, plus gates), so the namespace prefix is what demultiplexes them.
|
|
89
|
+
* The index — not the subject's own name — is the key, because ids from the
|
|
90
|
+
* wild (server ids, model ids, file paths) are not guaranteed to be distinct
|
|
91
|
+
* after any normalisation the wire might apply.
|
|
92
|
+
*/
|
|
93
|
+
export function decisionKey(namespace, index) {
|
|
94
|
+
return `${namespace}__${index}`;
|
|
95
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Centralized model choices for CLI commands
|
|
3
3
|
* Derives choices from model enums to ensure consistency
|
|
4
4
|
*/
|
|
5
|
-
import { AIProviderName, OpenAIModels, AnthropicModels, GoogleAIModels, BedrockModels, VertexModels, OllamaModels, AzureOpenAIModels, LiteLLMModels, HuggingFaceModels, SageMakerModels, OpenRouterModels, DeepSeekModels, NvidiaNimModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, ReplicateModels, } from "../constants/enums.js";
|
|
5
|
+
import { AIProviderName, OpenAIModels, AnthropicModels, GoogleAIModels, BedrockModels, VertexModels, OllamaModels, AzureOpenAIModels, LiteLLMModels, HuggingFaceModels, SageMakerModels, OpenRouterModels, DeepSeekModels, NvidiaNimModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, TypeSafeModels, ReplicateModels, } from "../constants/enums.js";
|
|
6
6
|
import { getCatalogJsonEntries } from "../providers/catalog/loader.js";
|
|
7
7
|
/**
|
|
8
8
|
* Looks up the JSON catalog entry for a provider, if it is one of the 9
|
|
@@ -393,6 +393,16 @@ const TOP_MODELS_CONFIG = {
|
|
|
393
393
|
},
|
|
394
394
|
{ model: RecraftModels.RECRAFT_V2, description: "V2" },
|
|
395
395
|
],
|
|
396
|
+
[AIProviderName.TYPESAFE]: [
|
|
397
|
+
{
|
|
398
|
+
model: TypeSafeModels.JEV_LATEST,
|
|
399
|
+
description: "Recommended - Jev (decisions, not text)",
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
model: TypeSafeModels.JEV_PREVIEW,
|
|
403
|
+
description: "Preview build of Jev",
|
|
404
|
+
},
|
|
405
|
+
],
|
|
396
406
|
[AIProviderName.AUTO]: [],
|
|
397
407
|
};
|
|
398
408
|
/**
|
|
@@ -432,6 +442,7 @@ export const DEFAULT_MODELS = {
|
|
|
432
442
|
[AIProviderName.STABILITY]: StabilityModels.STABLE_IMAGE_ULTRA,
|
|
433
443
|
[AIProviderName.IDEOGRAM]: IdeogramModels.IDEOGRAM_V3,
|
|
434
444
|
[AIProviderName.RECRAFT]: RecraftModels.RECRAFT_V3,
|
|
445
|
+
[AIProviderName.TYPESAFE]: TypeSafeModels.JEV_LATEST,
|
|
435
446
|
};
|
|
436
447
|
/**
|
|
437
448
|
* Model enum mappings for getAllModels.
|
|
@@ -464,6 +475,7 @@ const MODEL_ENUMS = {
|
|
|
464
475
|
[AIProviderName.STABILITY]: StabilityModels,
|
|
465
476
|
[AIProviderName.IDEOGRAM]: IdeogramModels,
|
|
466
477
|
[AIProviderName.RECRAFT]: RecraftModels,
|
|
478
|
+
[AIProviderName.TYPESAFE]: TypeSafeModels,
|
|
467
479
|
[AIProviderName.AUTO]: null,
|
|
468
480
|
};
|
|
469
481
|
/**
|
package/dist/utils/pricing.js
CHANGED
|
@@ -634,6 +634,17 @@ const PRICING = {
|
|
|
634
634
|
output: 0,
|
|
635
635
|
},
|
|
636
636
|
},
|
|
637
|
+
typesafe: {
|
|
638
|
+
// TypeSafe bills per million INPUT tokens only. Output is REPORTED but
|
|
639
|
+
// not billed: measured live, a decision returns roughly 17 output tokens
|
|
640
|
+
// per question (1 question → 21, 20 questions → 354), and the rate on
|
|
641
|
+
// them is zero. So `output: 0` is a price, not a token count — do not
|
|
642
|
+
// "correct" it by reading a non-zero `usage.output_tokens` off a
|
|
643
|
+
// response. A three-question decision costs ~$0.000013 in total.
|
|
644
|
+
// Rates from typesafe.ai/pricing (checked 2026-09-20).
|
|
645
|
+
_default: { input: 0.042 / 1_000_000, output: 0 },
|
|
646
|
+
"jev-latest": { input: 0.042 / 1_000_000, output: 0 },
|
|
647
|
+
},
|
|
637
648
|
stability: {
|
|
638
649
|
// Stability AI bills per image; symbolic per-token rate.
|
|
639
650
|
_default: { input: 0, output: 0.04 / 1_000 },
|
|
@@ -396,3 +396,7 @@ export declare function resolveOpenAICompatConfig(entry: OpenAICompatConfigInput
|
|
|
396
396
|
apiKey: string;
|
|
397
397
|
baseURL: string;
|
|
398
398
|
};
|
|
399
|
+
/**
|
|
400
|
+
* TypeSafe (Jev) — the `decide` inference type. Not a text provider.
|
|
401
|
+
*/
|
|
402
|
+
export declare function createTypeSafeConfig(): ProviderConfigOptions;
|
|
@@ -1307,3 +1307,20 @@ export function resolveOpenAICompatConfig(entry, credentials) {
|
|
|
1307
1307
|
}
|
|
1308
1308
|
return { apiKey, baseURL };
|
|
1309
1309
|
}
|
|
1310
|
+
/**
|
|
1311
|
+
* TypeSafe (Jev) — the `decide` inference type. Not a text provider.
|
|
1312
|
+
*/
|
|
1313
|
+
export function createTypeSafeConfig() {
|
|
1314
|
+
return {
|
|
1315
|
+
providerName: "TypeSafe (Jev)",
|
|
1316
|
+
envVarName: "TYPESAFE_API_KEY",
|
|
1317
|
+
setupUrl: "https://console.typesafe.ai/keys",
|
|
1318
|
+
description: "API key",
|
|
1319
|
+
instructions: [
|
|
1320
|
+
"1. Visit: https://console.typesafe.ai/keys",
|
|
1321
|
+
"2. Sign in to your TypeSafe account",
|
|
1322
|
+
"3. Create a new API key",
|
|
1323
|
+
"4. Set TYPESAFE_API_KEY in your .env file",
|
|
1324
|
+
],
|
|
1325
|
+
};
|
|
1326
|
+
}
|