@juspay/neurolink 12.35.1 → 12.35.3
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/dist/auth/index.d.ts +4 -14
- package/dist/auth/index.js +5 -18
- package/dist/browser/neurolink.min.js +400 -400
- package/dist/server/abstract/baseServerAdapter.d.ts +5 -1
- package/dist/server/abstract/baseServerAdapter.js +1 -1
- package/dist/server/adapters/honoAdapter.d.ts +5 -1
- package/dist/server/adapters/honoAdapter.js +21 -10
- package/dist/server/routes/claudeProxyRoutes.js +46 -10
- package/dist/utils/fileDetector.js +12 -3
- package/dist/utils/messageBuilder.js +5 -2
- package/dist/utils/redirectDispatcher.d.ts +6 -1
- package/dist/utils/redirectDispatcher.js +7 -2
- package/dist/utils/safeFetch.js +12 -3
- package/package.json +1 -1
|
@@ -28,8 +28,12 @@ export declare abstract class BaseServerAdapter extends EventEmitter {
|
|
|
28
28
|
constructor(neurolink: NeuroLink, config?: ServerAdapterConfig);
|
|
29
29
|
/**
|
|
30
30
|
* Initialize the underlying server framework
|
|
31
|
+
* May be async: adapters that dynamically import their framework package
|
|
32
|
+
* (e.g. Hono) return a Promise; adapters with a synchronous no-op stub that
|
|
33
|
+
* defer real setup to their own overridden `initialize()` (Express/Fastify/
|
|
34
|
+
* Koa) still return void.
|
|
31
35
|
*/
|
|
32
|
-
protected abstract initializeFramework(): void
|
|
36
|
+
protected abstract initializeFramework(): void | Promise<void>;
|
|
33
37
|
/**
|
|
34
38
|
* Register a route with the framework
|
|
35
39
|
*/
|
|
@@ -145,7 +145,7 @@ export class BaseServerAdapter extends EventEmitter {
|
|
|
145
145
|
const startTime = Date.now();
|
|
146
146
|
try {
|
|
147
147
|
// Initialize framework-specific setup
|
|
148
|
-
this.initializeFramework();
|
|
148
|
+
await this.initializeFramework();
|
|
149
149
|
// Register built-in middleware
|
|
150
150
|
this.registerBuiltInMiddleware();
|
|
151
151
|
// Register built-in routes
|
|
@@ -16,6 +16,8 @@ export declare class HonoServerAdapter extends BaseServerAdapter {
|
|
|
16
16
|
private rateLimitStore;
|
|
17
17
|
private rateLimitCleanupInterval?;
|
|
18
18
|
private requestContextStore;
|
|
19
|
+
private HTTPExceptionCtor;
|
|
20
|
+
private streamSSEFn;
|
|
19
21
|
constructor(neurolink: NeuroLink, config?: ServerAdapterConfig);
|
|
20
22
|
/**
|
|
21
23
|
* Create rate limiter middleware for Hono
|
|
@@ -27,8 +29,10 @@ export declare class HonoServerAdapter extends BaseServerAdapter {
|
|
|
27
29
|
private cleanupRateLimitStore;
|
|
28
30
|
/**
|
|
29
31
|
* Initialize Hono framework
|
|
32
|
+
* Dynamically imports Hono and its middleware so requiring only another
|
|
33
|
+
* adapter (Express/Fastify/Koa) never pulls Hono's import graph in.
|
|
30
34
|
*/
|
|
31
|
-
protected initializeFramework(): void
|
|
35
|
+
protected initializeFramework(): Promise<void>;
|
|
32
36
|
/**
|
|
33
37
|
* Register route with Hono
|
|
34
38
|
*/
|
|
@@ -3,13 +3,6 @@
|
|
|
3
3
|
* Primary server adapter implementation using Hono framework
|
|
4
4
|
* Hono is chosen for its performance, TypeScript-first design, and edge compatibility
|
|
5
5
|
*/
|
|
6
|
-
import { Hono } from "hono";
|
|
7
|
-
import { cors } from "hono/cors";
|
|
8
|
-
import { HTTPException } from "hono/http-exception";
|
|
9
|
-
import { logger as honoLogger } from "hono/logger";
|
|
10
|
-
import { secureHeaders } from "hono/secure-headers";
|
|
11
|
-
import { streamSSE } from "hono/streaming";
|
|
12
|
-
import { timeout } from "hono/timeout";
|
|
13
6
|
import { logger } from "../../utils/logger.js";
|
|
14
7
|
import { AlreadyRunningError, ServerStopError, wrapError } from "../errors.js";
|
|
15
8
|
import { BaseServerAdapter } from "../abstract/baseServerAdapter.js";
|
|
@@ -25,6 +18,11 @@ export class HonoServerAdapter extends BaseServerAdapter {
|
|
|
25
18
|
rateLimitCleanupInterval;
|
|
26
19
|
// Store context by request ID for sharing between middleware and route handlers
|
|
27
20
|
requestContextStore = new Map();
|
|
21
|
+
// Populated by initializeFramework's dynamic imports; used by methods that
|
|
22
|
+
// run after initialization (route registration, streaming) and so cannot
|
|
23
|
+
// rely on initializeFramework's own local closure variables.
|
|
24
|
+
HTTPExceptionCtor;
|
|
25
|
+
streamSSEFn;
|
|
28
26
|
constructor(neurolink, config = {}) {
|
|
29
27
|
super(neurolink, config);
|
|
30
28
|
}
|
|
@@ -113,8 +111,21 @@ export class HonoServerAdapter extends BaseServerAdapter {
|
|
|
113
111
|
}
|
|
114
112
|
/**
|
|
115
113
|
* Initialize Hono framework
|
|
114
|
+
* Dynamically imports Hono and its middleware so requiring only another
|
|
115
|
+
* adapter (Express/Fastify/Koa) never pulls Hono's import graph in.
|
|
116
116
|
*/
|
|
117
|
-
initializeFramework() {
|
|
117
|
+
async initializeFramework() {
|
|
118
|
+
const [{ Hono }, { cors }, { HTTPException }, { logger: honoLogger }, { secureHeaders }, { streamSSE }, { timeout },] = await Promise.all([
|
|
119
|
+
this.importFrameworkDependency("hono", "hono"),
|
|
120
|
+
this.importFrameworkDependency("hono/cors", "hono"),
|
|
121
|
+
this.importFrameworkDependency("hono/http-exception", "hono"),
|
|
122
|
+
this.importFrameworkDependency("hono/logger", "hono"),
|
|
123
|
+
this.importFrameworkDependency("hono/secure-headers", "hono"),
|
|
124
|
+
this.importFrameworkDependency("hono/streaming", "hono"),
|
|
125
|
+
this.importFrameworkDependency("hono/timeout", "hono"),
|
|
126
|
+
]);
|
|
127
|
+
this.HTTPExceptionCtor = HTTPException;
|
|
128
|
+
this.streamSSEFn = streamSSE;
|
|
118
129
|
this.app = new Hono();
|
|
119
130
|
// Add secure headers
|
|
120
131
|
this.app.use("*", secureHeaders());
|
|
@@ -284,7 +295,7 @@ export class HonoServerAdapter extends BaseServerAdapter {
|
|
|
284
295
|
route: route.path,
|
|
285
296
|
error: errorMessage,
|
|
286
297
|
});
|
|
287
|
-
throw new
|
|
298
|
+
throw new this.HTTPExceptionCtor(500, { message: errorMessage });
|
|
288
299
|
}
|
|
289
300
|
finally {
|
|
290
301
|
// Untrack connection when request completes
|
|
@@ -304,7 +315,7 @@ export class HonoServerAdapter extends BaseServerAdapter {
|
|
|
304
315
|
c.header(key, value);
|
|
305
316
|
}
|
|
306
317
|
}
|
|
307
|
-
return
|
|
318
|
+
return this.streamSSEFn(c, async (stream) => {
|
|
308
319
|
try {
|
|
309
320
|
// Get streaming result from handler
|
|
310
321
|
const result = await route.handler(ctx);
|
|
@@ -13,7 +13,6 @@ import { claudeOutputLogFields, observeClaudeJsonOutput, observeClaudeStreamOutp
|
|
|
13
13
|
import { access, readFile } from "node:fs/promises";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { join } from "node:path";
|
|
16
|
-
import { Agent } from "undici";
|
|
17
16
|
import { buildStableClaudeCodeBillingHeader, CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_OAUTH_BETAS, getOrCreateClaudeCodeIdentity, parseClaudeCodeUserId, } from "../../auth/anthropicOAuth.js";
|
|
18
17
|
import { clearAccountCooldown, loadAccountCooldowns, saveAccountCooldown, } from "../../proxy/accountCooldown.js";
|
|
19
18
|
import { anthropicAccountKeysEqual, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
|
|
@@ -184,6 +183,50 @@ function prepareAnthropicWire(ctx, bodyStr) {
|
|
|
184
183
|
};
|
|
185
184
|
}
|
|
186
185
|
let anthropicUpstreamDispatcher;
|
|
186
|
+
let anthropicUpstreamDispatcherPromise;
|
|
187
|
+
/**
|
|
188
|
+
* Lazily construct (once) and cache the upstream Agent, reused across every
|
|
189
|
+
* request rather than rebuilt per call.
|
|
190
|
+
*
|
|
191
|
+
* `undici` is imported dynamically here — rather than as a static top-level
|
|
192
|
+
* import — so that requiring this module (and transitively, the package
|
|
193
|
+
* entry that re-exports `createClaudeProxyRoutes`) does not force-load undici
|
|
194
|
+
* until an upstream Anthropic request actually dispatches. The construction
|
|
195
|
+
* itself is guarded by a cached promise, not a bare `??=` on the Agent, so
|
|
196
|
+
* concurrent first callers await the same in-flight construction instead of
|
|
197
|
+
* each racing past the `undefined` check and building a duplicate Agent. A
|
|
198
|
+
* rejected construction (a bad undici install, an Agent constructor throwing)
|
|
199
|
+
* clears the cached promise rather than leaving it permanently rejected, so
|
|
200
|
+
* the next request gets a fresh attempt instead of every later request
|
|
201
|
+
* failing forever on a cached failure. See
|
|
202
|
+
* test/continuous-test-suite-import-cost.ts.
|
|
203
|
+
*/
|
|
204
|
+
async function getAnthropicUpstreamDispatcher() {
|
|
205
|
+
if (anthropicUpstreamDispatcher) {
|
|
206
|
+
return anthropicUpstreamDispatcher;
|
|
207
|
+
}
|
|
208
|
+
anthropicUpstreamDispatcherPromise ??= (async () => {
|
|
209
|
+
try {
|
|
210
|
+
const { Agent: UndiciAgent } = await import("undici");
|
|
211
|
+
// Node's global fetch applies Undici's 300s default headers timeout
|
|
212
|
+
// before the route's 15-minute abort signal. Keep both transport
|
|
213
|
+
// deadlines aligned with the proxy contract.
|
|
214
|
+
const agent = new UndiciAgent({
|
|
215
|
+
headersTimeout: UPSTREAM_FETCH_TIMEOUT_MS,
|
|
216
|
+
bodyTimeout: UPSTREAM_FETCH_TIMEOUT_MS,
|
|
217
|
+
});
|
|
218
|
+
anthropicUpstreamDispatcher = agent;
|
|
219
|
+
return agent;
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
// Let a later request try again instead of every subsequent request
|
|
223
|
+
// failing forever against a cached rejection.
|
|
224
|
+
anthropicUpstreamDispatcherPromise = undefined;
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
return anthropicUpstreamDispatcherPromise;
|
|
229
|
+
}
|
|
187
230
|
async function fetchAnthropicUpstream(url, init, ctx, accountKey, dispatch) {
|
|
188
231
|
clearClaudeFallbackTerminalEvidence(ctx);
|
|
189
232
|
const prepared = dispatch.preparedContext;
|
|
@@ -203,15 +246,8 @@ async function fetchAnthropicUpstream(url, init, ctx, accountKey, dispatch) {
|
|
|
203
246
|
ctx.metadata.tokenBudget = lease.snapshot;
|
|
204
247
|
throw init.signal?.reason ?? ctx.abortSignal?.reason;
|
|
205
248
|
}
|
|
206
|
-
// Node's global fetch applies Undici's 300s default headers timeout before
|
|
207
|
-
// the route's 15-minute abort signal. Keep both transport deadlines aligned
|
|
208
|
-
// with the proxy contract and instantiate lazily so importing routes has no
|
|
209
|
-
// open transport handles.
|
|
210
|
-
anthropicUpstreamDispatcher ??= new Agent({
|
|
211
|
-
headersTimeout: UPSTREAM_FETCH_TIMEOUT_MS,
|
|
212
|
-
bodyTimeout: UPSTREAM_FETCH_TIMEOUT_MS,
|
|
213
|
-
});
|
|
214
249
|
try {
|
|
250
|
+
const upstreamDispatcher = await getAnthropicUpstreamDispatcher();
|
|
215
251
|
const dispatchedBody = String(init.body);
|
|
216
252
|
recordAttempt(dispatch.accountLabel, dispatch.accountType);
|
|
217
253
|
dispatch.tracer?.logUpstreamRequestHeaders(init.headers);
|
|
@@ -230,7 +266,7 @@ async function fetchAnthropicUpstream(url, init, ctx, accountKey, dispatch) {
|
|
|
230
266
|
const response = await fetch(url, {
|
|
231
267
|
...init,
|
|
232
268
|
body: dispatchedBody,
|
|
233
|
-
dispatcher:
|
|
269
|
+
dispatcher: upstreamDispatcher,
|
|
234
270
|
});
|
|
235
271
|
return observeAnthropicBudgetResponse(response, lease, () => {
|
|
236
272
|
ctx.metadata.tokenBudget = lease.snapshot;
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { open, readFile, realpath } from "fs/promises";
|
|
7
7
|
import { STATUS_CODES } from "node:http";
|
|
8
8
|
import { basename, isAbsolute as isAbsolutePath, relative as relativePath, resolve as resolvePath, sep, } from "path";
|
|
9
|
-
import { request } from "undici";
|
|
10
9
|
import { redirectFollowingDispatcher } from "./redirectDispatcher.js";
|
|
11
10
|
// Lazy-loaded processor singletons — avoids loading heavy media deps
|
|
12
11
|
// (mediabunny, fluent-ffmpeg, music-metadata, adm-zip) on every generate() call.
|
|
@@ -1609,8 +1608,13 @@ export class FileDetector {
|
|
|
1609
1608
|
const timeout = options?.timeout || FileDetector.DEFAULT_NETWORK_TIMEOUT;
|
|
1610
1609
|
return withRetry(async () => {
|
|
1611
1610
|
try {
|
|
1611
|
+
// Dynamic import keeps undici off the package's static import
|
|
1612
|
+
// graph — it is only needed once a URL-referenced attachment
|
|
1613
|
+
// actually has to be fetched. See
|
|
1614
|
+
// test/continuous-test-suite-import-cost.ts.
|
|
1615
|
+
const { request } = await import("undici");
|
|
1612
1616
|
const response = await request(url, {
|
|
1613
|
-
dispatcher: redirectFollowingDispatcher(5),
|
|
1617
|
+
dispatcher: await redirectFollowingDispatcher(5),
|
|
1614
1618
|
method: "GET",
|
|
1615
1619
|
headersTimeout: timeout,
|
|
1616
1620
|
bodyTimeout: timeout,
|
|
@@ -2375,8 +2379,13 @@ class MimeTypeStrategy {
|
|
|
2375
2379
|
// Wrap the whole HEAD (request + body drain) in withTimeout so a stalled
|
|
2376
2380
|
// dump() can't hang detection, per the project's async-timeout guideline.
|
|
2377
2381
|
contentType = await withTimeout((async () => {
|
|
2382
|
+
// Dynamic import keeps undici off the package's static import
|
|
2383
|
+
// graph — it is only needed once a URL-referenced attachment
|
|
2384
|
+
// actually has to be fetched. See
|
|
2385
|
+
// test/continuous-test-suite-import-cost.ts.
|
|
2386
|
+
const { request } = await import("undici");
|
|
2378
2387
|
const response = await request(input, {
|
|
2379
|
-
dispatcher: redirectFollowingDispatcher(5),
|
|
2388
|
+
dispatcher: await redirectFollowingDispatcher(5),
|
|
2380
2389
|
method: "HEAD",
|
|
2381
2390
|
headersTimeout: FileDetector.DEFAULT_HEAD_TIMEOUT,
|
|
2382
2391
|
bodyTimeout: FileDetector.DEFAULT_HEAD_TIMEOUT,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
2
2
|
import { open as openAsync, readFile as readFileAsync, stat as statAsync, } from "fs/promises";
|
|
3
3
|
import pLimit from "p-limit";
|
|
4
|
-
import { request } from "undici";
|
|
5
4
|
import { redirectFollowingDispatcher } from "./redirectDispatcher.js";
|
|
6
5
|
import { MultimodalLogger, ProviderImageAdapter, } from "../adapters/providerImageAdapter.js";
|
|
7
6
|
import { CONVERSATION_INSTRUCTIONS, STRUCTURED_OUTPUT_INSTRUCTIONS, } from "../config/conversationMemory.js";
|
|
@@ -1780,8 +1779,12 @@ async function downloadImageFromUrl(url) {
|
|
|
1780
1779
|
// Apply rate limiting only if cache missed
|
|
1781
1780
|
await urlDownloadRateLimiter.acquire();
|
|
1782
1781
|
try {
|
|
1782
|
+
// Dynamic import keeps undici off the package's static import graph —
|
|
1783
|
+
// it is only needed once a URL-referenced image actually has to be
|
|
1784
|
+
// fetched. See test/continuous-test-suite-import-cost.ts.
|
|
1785
|
+
const { request } = await import("undici");
|
|
1783
1786
|
const response = await request(url, {
|
|
1784
|
-
dispatcher: redirectFollowingDispatcher(5),
|
|
1787
|
+
dispatcher: await redirectFollowingDispatcher(5),
|
|
1785
1788
|
method: "GET",
|
|
1786
1789
|
headersTimeout: 10000, // 10 second timeout for headers
|
|
1787
1790
|
bodyTimeout: 30000, // 30 second timeout for body,
|
|
@@ -34,6 +34,11 @@ import type { Dispatcher } from "undici";
|
|
|
34
34
|
* requests. Losing a proxy on one Node version is recoverable; silently failing
|
|
35
35
|
* every redirecting download is not.
|
|
36
36
|
*
|
|
37
|
+
* `undici` itself is imported dynamically here (rather than at module scope)
|
|
38
|
+
* so that requiring this module — and transitively, messageBuilder.ts /
|
|
39
|
+
* fileDetector.ts — does not force-load undici until a URL attachment is
|
|
40
|
+
* actually fetched. See test/continuous-test-suite-import-cost.ts.
|
|
41
|
+
*
|
|
37
42
|
* @param maxRedirections How many redirects to follow before giving up.
|
|
38
43
|
*/
|
|
39
|
-
export declare function redirectFollowingDispatcher(maxRedirections: number): Dispatcher
|
|
44
|
+
export declare function redirectFollowingDispatcher(maxRedirections: number): Promise<Dispatcher>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Agent, getGlobalDispatcher, interceptors } from "undici";
|
|
2
1
|
/**
|
|
3
2
|
* The major of the `undici` this package depends on.
|
|
4
3
|
*
|
|
@@ -42,9 +41,15 @@ const NPM_UNDICI_MAJOR = 7;
|
|
|
42
41
|
* requests. Losing a proxy on one Node version is recoverable; silently failing
|
|
43
42
|
* every redirecting download is not.
|
|
44
43
|
*
|
|
44
|
+
* `undici` itself is imported dynamically here (rather than at module scope)
|
|
45
|
+
* so that requiring this module — and transitively, messageBuilder.ts /
|
|
46
|
+
* fileDetector.ts — does not force-load undici until a URL attachment is
|
|
47
|
+
* actually fetched. See test/continuous-test-suite-import-cost.ts.
|
|
48
|
+
*
|
|
45
49
|
* @param maxRedirections How many redirects to follow before giving up.
|
|
46
50
|
*/
|
|
47
|
-
export function redirectFollowingDispatcher(maxRedirections) {
|
|
51
|
+
export async function redirectFollowingDispatcher(maxRedirections) {
|
|
52
|
+
const { Agent, getGlobalDispatcher, interceptors } = await import("undici");
|
|
48
53
|
const builtinMajor = Number.parseInt(process.versions.undici?.split(".")[0] ?? "", 10);
|
|
49
54
|
const base = Number.isFinite(builtinMajor) && builtinMajor === NPM_UNDICI_MAJOR
|
|
50
55
|
? getGlobalDispatcher()
|
package/dist/utils/safeFetch.js
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @module utils/safeFetch
|
|
18
18
|
*/
|
|
19
|
-
import { Agent, fetch as undiciFetch } from "undici";
|
|
20
19
|
import { readBoundedBuffer } from "./sizeGuard.js";
|
|
21
20
|
import { validateAndResolveUrl } from "./ssrfGuard.js";
|
|
22
21
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
@@ -30,8 +29,14 @@ const DEFAULT_TIMEOUT_MS = 60_000;
|
|
|
30
29
|
* the OS resolver prefers AAAA, that broke every safeDownload (Replicate /
|
|
31
30
|
* Runway / Kling asset fetches) while plain curl of the same URL succeeded —
|
|
32
31
|
* curl races both families (Happy Eyeballs); a one-address pin cannot.
|
|
32
|
+
*
|
|
33
|
+
* `undici` is imported dynamically (this function is only ever called from
|
|
34
|
+
* the already-async `safeDownload`) so that requiring this module does not
|
|
35
|
+
* force-load undici until a download actually happens. See
|
|
36
|
+
* test/continuous-test-suite-import-cost.ts.
|
|
33
37
|
*/
|
|
34
|
-
function buildPinnedAgent(hostname, addresses) {
|
|
38
|
+
async function buildPinnedAgent(hostname, addresses) {
|
|
39
|
+
const { Agent } = await import("undici");
|
|
35
40
|
const primary = addresses[0];
|
|
36
41
|
if (!primary) {
|
|
37
42
|
throw new Error(`safeFetch: no validated addresses to pin for "${hostname}"`);
|
|
@@ -85,7 +90,11 @@ export async function safeDownload(url, options) {
|
|
|
85
90
|
const { url: validatedUrl, addresses } = await validateAndResolveUrl(url);
|
|
86
91
|
const parsed = new URL(validatedUrl);
|
|
87
92
|
const hostname = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
88
|
-
|
|
93
|
+
// Dynamic import keeps undici off the package's static import graph — it
|
|
94
|
+
// is only needed once a download actually happens. See
|
|
95
|
+
// test/continuous-test-suite-import-cost.ts.
|
|
96
|
+
const { fetch: undiciFetch } = await import("undici");
|
|
97
|
+
const agent = await buildPinnedAgent(hostname, addresses);
|
|
89
98
|
const timeoutCtrl = new AbortController();
|
|
90
99
|
const timeoutId = setTimeout(() => timeoutCtrl.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
91
100
|
const composedSignal = options.signal
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.35.
|
|
3
|
+
"version": "12.35.3",
|
|
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": {
|