inference-relay 1.6.12 → 1.6.14

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/dist/index.d.cts DELETED
@@ -1,949 +0,0 @@
1
- import { C as CreateParams, R as RelayResult, I as InferenceRelayConfig, P as ProbeResult, a as CostEstimate, b as ProviderHealth, c as RateLimitEvent, E as EngineEvent, S as SecurityState$1, A as AuditEvent, d as RoutingConfig } from './index-yGhLy31i.cjs';
2
- export { e as ConcurrencyConfig, f as ContentBlock, M as Message, g as ProviderConfig, h as RelayCapExceededError, i as SecureEnclaveConfig, j as SecureEnclaveCredentials, k as SegmentUsage } from './index-yGhLy31i.cjs';
3
- import { RelayStream } from './stream/index.cjs';
4
- export { createRelayStream } from './stream/index.cjs';
5
- import { EventEmitter } from 'node:events';
6
-
7
- /**
8
- * The relay's version and what its roads keep, for its caller (1.6.10, 9 Nov 2026).
9
- *
10
- * A caller that streams (a desktop app, for one) has to know, BEFORE it chooses the streamed road, whether the relay it
11
- * runs keeps the whole request there. Published 1.6.8's streamed road sent the model only the system prompt and the last message, and
12
- * returned tool names prefixed mcp__r__; 1.6.10's streamed road runs the queue route's own round (claude-cli streamRaw over executeImpl),
13
- * so every message, every tool_use and tool_result block, and the bare tool names survive.
14
- *
15
- * Read it with no call and no network:
16
- * import { RELAY_VERSION, RELAY_FEATURES, relayHello } from 'inference-relay';
17
- * RELAY_FEATURES.streamKeepsHistory === true // the one test a caller needs; undefined on 1.6.8 and earlier (no such export)
18
- * relayHello() // { name: 'inference-relay', version: '1.6.10', features: { streamKeepsHistory: true, ... } }
19
- * or on an instance: relay.version, relay.features, relay.hello() (and InferenceRelay.version).
20
- *
21
- * A caller that may run an OLDER relay (1.6.8 and earlier have none of these exports) must not import them by name: a static
22
- * `import { RELAY_FEATURES }` of a missing export is a hard error in ESM and in strict bundlers. Read them off the module instead, with
23
- * optional access, so an old relay reads as "does not keep history":
24
- * const ir = await import('inference-relay'); // or: import * as ir from 'inference-relay'
25
- * const keepsHistory = ir.RELAY_FEATURES?.streamKeepsHistory === true;
26
- *
27
- * The flags describe the claude-cli streamed road of the gateway bundle published with this library version: the library fetches the
28
- * bundle by its own version (gateway/lazy-provider.ts, LIBRARY_VERSION), so a 1.6.10 library always runs the 1.6.10 bundle.
29
- */
30
- declare const RELAY_VERSION: string;
31
- interface RelayFeatures {
32
- /** The streamed road sends the model every message of the request (earlier turns, a tool round's call and result), as the queue road does. */
33
- readonly streamKeepsHistory: boolean;
34
- /** tool_use and tool_result content blocks in the request survive on the streamed road. */
35
- readonly streamToolBlocks: boolean;
36
- /** Tool names on the streamed road come back as the caller declared them (no mcp__r__ prefix, no CLI caller field). */
37
- readonly streamBareToolNames: boolean;
38
- /** 1.6.12, the claude-cli provider only (kimi-cli unchanged): a caller that goes (its signal aborts, or a stream's reader stops) stops the CLI run within seconds on both roads, a Stop given
39
- * before the call starts no run, and an aborted call fails as an AbortError, never a cut answer handed back as finished. The signal is read
40
- * from create(params, { signal }) and also from create({ ...params, signal }). Undefined on 1.6.11 and earlier. */
41
- readonly stopsWhenCallerGoes: boolean;
42
- }
43
- declare const RELAY_FEATURES: RelayFeatures;
44
- interface RelayHello {
45
- readonly name: 'inference-relay';
46
- readonly version: string;
47
- readonly features: RelayFeatures;
48
- }
49
- /** One object a caller can log, send to its server, or put on a response header (JSON). */
50
- declare function relayHello(): RelayHello;
51
-
52
- interface LicenseValidation {
53
- valid: boolean;
54
- tier?: string;
55
- features?: string[];
56
- expiresAt?: number | null;
57
- /**
58
- * Effective cap: base cap plus any in-cycle top-up grants. Kept as the
59
- * `usageCap` field for backward compat — the library has always compared
60
- * against this.
61
- */
62
- usageCap?: number | null;
63
- /** Raw base cap (before top-ups). Optional for back-compat. */
64
- baseCap?: number | null;
65
- /** Sum of active top-up grants for the current cycle. */
66
- topUpGrants?: number;
67
- /**
68
- * Bumped by the backend on every top-up purchase. Library uses this to
69
- * short-circuit the normal validation cache: if the cached entry has an
70
- * older `lastCapChange` than the server, force a re-validate immediately.
71
- * Enables sub-30s recovery after a top-up.
72
- */
73
- lastCapChange?: number;
74
- usageThisMonth?: number;
75
- capExceeded?: boolean;
76
- error?: string;
77
- }
78
- /**
79
- * Validate a license key against the inference-relay backend.
80
- *
81
- * SECURITY: This is the SOLE authoritative implementation of license verification
82
- * in the entire monorepo. The Pro package re-exports this function rather than
83
- * reimplementing it. Do NOT copy this logic into other packages — single source
84
- * of truth for the asymmetric (RS256) trust chain.
85
- *
86
- * - Verifies JWS signatures from the Protocol Authority
87
- * - Rejects unsigned valid:true responses (MITM protection — fail-closed)
88
- * - Caches positive results for 24h, negative for 60s
89
- * - Falls back to cached result on network failure within 7-day staleness window
90
- */
91
- declare function validateLicense(key: string, endpoint?: string): Promise<LicenseValidation>;
92
- declare class InferenceRelay {
93
- /** This library's version (package.json at build time), e.g. '1.6.10'. Also exported as RELAY_VERSION. */
94
- static readonly version: string;
95
- /** This library's version, for a caller holding an instance. */
96
- get version(): string;
97
- /** What this relay's roads keep (src/version.ts). A caller lifts a one-message streaming guard only when streamKeepsHistory is true. */
98
- get features(): RelayFeatures;
99
- /** { name, version, features }: one object a caller can log or send on (no call, no network). */
100
- hello(): RelayHello;
101
- private providers;
102
- private config;
103
- private fallbackEngine;
104
- private telemetryReporter;
105
- private routingFn;
106
- private license;
107
- private licensePromise;
108
- private licenseValidatedAt;
109
- private callsSinceValidation;
110
- private manifestDecryptor;
111
- private capProbeTimer;
112
- private capProbeTicksRemaining;
113
- messages: {
114
- create(params: CreateParams & {
115
- stream?: false;
116
- }, options?: {
117
- signal?: AbortSignal;
118
- }): Promise<RelayResult>;
119
- create(params: CreateParams & {
120
- stream: true;
121
- }, options?: {
122
- signal?: AbortSignal;
123
- }): Promise<RelayStream>;
124
- create(params: CreateParams, options?: {
125
- signal?: AbortSignal;
126
- }): Promise<RelayResult | RelayStream>;
127
- };
128
- constructor(config: InferenceRelayConfig);
129
- /**
130
- * Start the High-Frequency Probe: background poll of /v1/validate every
131
- * 15 seconds for up to 5 minutes. Exits early when `lastCapChange` differs
132
- * from the cached value — meaning a top-up purchase has landed — at which
133
- * point the normal validation cache takes over and the next inference call
134
- * succeeds.
135
- *
136
- * Idempotent: a second call while a probe is already running is a no-op.
137
- * Properly cleaned up in dispose() so there are no timer leaks.
138
- */
139
- private startCapProbe;
140
- /**
141
- * Single validation call used by the cap probe loop. Extracted as a
142
- * method so tests can override it without going through the RS256 JWS
143
- * verification path. Production behavior delegates to the module-level
144
- * validateLicense() function, which is the sole authoritative source of
145
- * truth for the asymmetric trust chain.
146
- */
147
- private probeValidate;
148
- private stopCapProbe;
149
- static autoDetect(config?: Partial<InferenceRelayConfig>): Promise<InferenceRelay>;
150
- /**
151
- * Pre-flight: trigger the gateway bundle fetch + load now, so subsequent
152
- * `messages.create` / `stream` calls don't pay the cold-start latency
153
- * (and don't surface a gateway outage as a confusing chained provider
154
- * error). Throws `GatewayUnavailableError` on sustained outage so the
155
- * caller can early-skip a test cluster cleanly.
156
- *
157
- * No-op when the cascade has no claude-cli provider (API-only deployment).
158
- * On failure, the lazy provider's `loadingPromise` is reset so the next
159
- * call retries from scratch — `warmup()` doesn't poison subsequent calls.
160
- *
161
- * Added in 1.6.2.
162
- */
163
- warmup(): Promise<void>;
164
- /**
165
- * Clear all provider-health state on this client's fallback engine.
166
- *
167
- * Use between bench tasks, between user sessions, or after handling a
168
- * known-transient upstream incident (e.g., a subscription auth flap
169
- * that's been resolved). Equivalent to calling `markHealthy` for every
170
- * provider type — every provider's next call gets a fresh attempt.
171
- *
172
- * Added in 1.6.4. Replaces the deep-import workaround
173
- * `client.fallbackEngine.resetHealth()` (which relied on TypeScript
174
- * `private` being compile-time-only).
175
- */
176
- resetHealth(): void;
177
- /**
178
- * Clear the unhealthy mark for a single provider type. Useful when you
179
- * want to reset just (e.g.) `claude-cli` after a transient auth flap
180
- * without disturbing other providers' degraded/rate-limited state.
181
- *
182
- * Provider type strings: `claude-cli`, `anthropic-api`, `openai-api`,
183
- * `ollama`, plus any custom-provider type names you've registered.
184
- *
185
- * Added in 1.6.4.
186
- */
187
- markHealthy(providerType: string): void;
188
- dispose(): void;
189
- private resolveProviders;
190
- private executeMessage;
191
- private streamMessage;
192
- private createProvider;
193
- }
194
-
195
- declare abstract class BaseProvider {
196
- abstract readonly type: string;
197
- abstract readonly supportsStreaming: boolean;
198
- abstract readonly supportsMultimodal: boolean;
199
- abstract readonly platformRequirement: 'desktop' | 'web' | 'any';
200
- priority: number;
201
- abstract probe(): Promise<ProbeResult>;
202
- abstract execute(params: CreateParams, options?: {
203
- signal?: AbortSignal;
204
- }): Promise<RelayResult>;
205
- /**
206
- * Lower-level streaming entrypoint. Returns the raw provider event iterator
207
- * with NO RelayStream wrapping. The FallbackEngine consumes streamRaw()
208
- * directly so it can run mid-stream failover, accumulate state, and wrap
209
- * the final output in createRelayStream exactly once.
210
- *
211
- * Returns a Promise so eager setup (auth, spawn, fetch) happens BEFORE the
212
- * iterator is returned. This matches the previous stream() semantics — by
213
- * the time streamRaw() resolves, any side effects (subprocess spawn, HTTP
214
- * connection) have already started.
215
- *
216
- * Providers MUST override either streamRaw() OR stream() (preferably
217
- * streamRaw — stream() will be removed once all callers migrate).
218
- */
219
- streamRaw(_params: CreateParams, _options?: {
220
- signal?: AbortSignal;
221
- }): Promise<AsyncIterable<any>>;
222
- /**
223
- * Default RelayStream wrapper. Most providers should NOT override this —
224
- * implement streamRaw() instead. Kept as an instance method (not abstract)
225
- * for backward compatibility with callers that hit providers directly.
226
- */
227
- stream(params: CreateParams, options?: {
228
- signal?: AbortSignal;
229
- }): Promise<RelayStream>;
230
- abstract estimateCost(params: CreateParams): CostEstimate;
231
- abstract getHealth(): ProviderHealth;
232
- abstract reportRateLimit(event: RateLimitEvent): void;
233
- }
234
-
235
- interface AnthropicAPIOptions {
236
- apiKey: string;
237
- baseUrl?: string;
238
- /** Set true when running in a browser context */
239
- dangerousDirectBrowserAccess?: boolean;
240
- }
241
- declare class AnthropicAPIProvider extends BaseProvider {
242
- readonly type = "anthropic-api";
243
- readonly supportsStreaming = true;
244
- readonly supportsMultimodal = true;
245
- readonly platformRequirement: "any";
246
- private apiKey;
247
- private baseUrl;
248
- private dangerousDirectBrowserAccess;
249
- private health;
250
- constructor(options: AnthropicAPIOptions);
251
- probe(): Promise<ProbeResult>;
252
- execute(params: CreateParams, options?: {
253
- signal?: AbortSignal;
254
- }): Promise<RelayResult>;
255
- streamRaw(params: CreateParams, options?: {
256
- signal?: AbortSignal;
257
- }): Promise<AsyncIterable<any>>;
258
- estimateCost(params: CreateParams): CostEstimate;
259
- getHealth(): ProviderHealth;
260
- reportRateLimit(event: RateLimitEvent): void;
261
- private buildHeaders;
262
- private buildRequestBody;
263
- private handleErrorResponse;
264
- private recordSuccess;
265
- }
266
-
267
- interface OpenAIAPIOptions {
268
- apiKey: string;
269
- baseUrl?: string;
270
- }
271
- declare class OpenAIAPIProvider extends BaseProvider {
272
- readonly type = "openai-api";
273
- readonly supportsStreaming = true;
274
- readonly supportsMultimodal = true;
275
- readonly platformRequirement: "any";
276
- private apiKey;
277
- private baseUrl;
278
- private health;
279
- constructor(options: OpenAIAPIOptions);
280
- probe(): Promise<ProbeResult>;
281
- execute(params: CreateParams, options?: {
282
- signal?: AbortSignal;
283
- }): Promise<RelayResult>;
284
- streamRaw(params: CreateParams, options?: {
285
- signal?: AbortSignal;
286
- }): Promise<AsyncIterable<any>>;
287
- estimateCost(params: CreateParams): CostEstimate;
288
- getHealth(): ProviderHealth;
289
- reportRateLimit(event: RateLimitEvent): void;
290
- private buildHeaders;
291
- private buildRequestBody;
292
- private handleErrorResponse;
293
- private recordSuccess;
294
- }
295
-
296
- interface OllamaOptions {
297
- baseUrl?: string;
298
- }
299
- declare class OllamaProvider extends BaseProvider {
300
- readonly type = "ollama";
301
- readonly supportsStreaming = true;
302
- readonly supportsMultimodal = false;
303
- readonly platformRequirement: "desktop";
304
- private baseUrl;
305
- private health;
306
- constructor(options?: OllamaOptions);
307
- probe(): Promise<ProbeResult>;
308
- execute(params: CreateParams, options?: {
309
- signal?: AbortSignal;
310
- }): Promise<RelayResult>;
311
- streamRaw(params: CreateParams, options?: {
312
- signal?: AbortSignal;
313
- }): Promise<AsyncIterable<any>>;
314
- estimateCost(params: CreateParams): CostEstimate;
315
- getHealth(): ProviderHealth;
316
- reportRateLimit(event: RateLimitEvent): void;
317
- private buildRequestBody;
318
- private handleErrorResponse;
319
- private recordSuccess;
320
- }
321
-
322
- /**
323
- * Typed façade over Node's synchronous EventEmitter. Used by FallbackEngine
324
- * to emit lifecycle events that the TelemetryReporter buffers per-call and
325
- * flushes into a coherent AuditEvent at terminal stream_complete.
326
- *
327
- * MUST be synchronous (Node EventEmitter is). Do NOT replace with a custom
328
- * Promise/microtask-queued impl — audit-event ordering depends on synchronous
329
- * listener invocation so that lineage is fully buffered before flushLineageForCall
330
- * runs.
331
- */
332
- declare class EngineEventEmitter extends EventEmitter {
333
- constructor();
334
- emitTyped(event: EngineEvent): boolean;
335
- onTyped<K extends EngineEvent['kind']>(kind: K, listener: (event: Extract<EngineEvent, {
336
- kind: K;
337
- }>) => void): this;
338
- offTyped<K extends EngineEvent['kind']>(kind: K, listener: (event: Extract<EngineEvent, {
339
- kind: K;
340
- }>) => void): this;
341
- }
342
-
343
- interface FallbackOptions {
344
- signal?: AbortSignal;
345
- maxAttempts?: number;
346
- /** Maximum mid-stream failovers per call. Default: 2. */
347
- maxMidStreamFailovers?: number;
348
- /** Security state of the licence chain. SEC_DEGRADED enables jitter. */
349
- licenseState?: SecurityState$1;
350
- /**
351
- * Maximum same-provider retries on `transient` failures (e.g., Bun
352
- * crash on Windows). Default: 2. Set to 0 to disable retries (failure
353
- * immediately cascades to the next provider). Added 1.6.6.
354
- */
355
- maxSameProviderRetries?: number;
356
- /**
357
- * When true, refuses to cascade from `claude-cli` to any paid provider
358
- * (`anthropic-api`, `openai-api`). The cascade halts with
359
- * `subscription_only:cascade_halted` instead of falling through to a
360
- * paid leg. Billing safety net for users with stale ANTHROPIC_API_KEY
361
- * in env. Added 1.6.6.
362
- */
363
- subscriptionOnly?: boolean;
364
- }
365
- interface HealthRecord {
366
- status: ProviderHealth['status'];
367
- lastError?: string;
368
- updatedAt: number;
369
- /**
370
- * TTL override in milliseconds. Optional — when omitted,
371
- * `UNHEALTHY_TTL_DEFAULT_MS` is used. Set by `markUnhealthy` for
372
- * classifications that auto-recover faster than the default (1.6.4
373
- * adds 30s for `auth_failed`).
374
- */
375
- ttlMs?: number;
376
- }
377
- declare class RelayError extends Error {
378
- readonly code = "ALL_PROVIDERS_FAILED";
379
- readonly fallbackChain: string[];
380
- constructor(chain: string[]);
381
- }
382
- /**
383
- * Thrown when the cascade exhausts and every provider failure classified as
384
- * `request_too_large` — i.e., the prompt exceeded the upstream API's input
385
- * token ceiling. Subclass of {@link RelayError} so existing
386
- * `instanceof RelayError` matchers and chain-inspecting code keep working.
387
- *
388
- * Callers should branch on `instanceof RequestTooLargeError` and apply
389
- * domain-specific distillation (drop oldest messages, summarize step
390
- * trajectories, filter screenshots) before retrying. IR cannot distill the
391
- * payload itself — it has no semantic understanding of what content matters.
392
- *
393
- * Detection: {@link classifyError} matches stderr patterns like
394
- * `prompt is too long`, `input length`, `request too large`, and tokens-
395
- * exceed strings. When EVERY provider in the cascade failed with this
396
- * classification, the cascade throws {@link RequestTooLargeError} instead
397
- * of vanilla {@link RelayError}. If even one provider failed for a
398
- * different reason (e.g., auth, gateway), the cascade throws plain
399
- * `RelayError` and the size-related entries appear in `fallbackChain`.
400
- *
401
- * Added in 1.6.3.
402
- */
403
- declare class RequestTooLargeError extends RelayError {
404
- constructor(chain: string[]);
405
- }
406
- declare class FallbackEngine {
407
- /** Tracks provider health across calls. Keyed by provider type string. */
408
- private healthMap;
409
- /**
410
- * Public lifecycle event emitter. The TelemetryReporter subscribes to this
411
- * to buffer per-call lineage and flush a coherent AuditEvent at terminal
412
- * stream_complete. Synchronous semantics — see telemetry/engine-events.ts.
413
- */
414
- readonly engineEvents: EngineEventEmitter;
415
- /**
416
- * Default unhealthy TTL — appropriate for infrastructure outages
417
- * (`gateway_unavailable`, `provider_incompatible`) that typically take
418
- * minutes to resolve. Transient failure classes can override via the
419
- * `ttlMs` arg to {@link markUnhealthy}.
420
- */
421
- static readonly UNHEALTHY_TTL_DEFAULT_MS: number;
422
- /**
423
- * Shorter TTL for `auth_failed` (1.6.4). Token-rotation flaps and
424
- * brief auth-backend hiccups are typically sub-second; 5 minutes was
425
- * too aggressive a blast radius for what's usually transient. After
426
- * 1.6.4's auth-retry in claude-cli provider, anything that survives
427
- * the in-call retry is more likely to be a real auth problem — but
428
- * still recoverable in seconds-to-tens-of-seconds, not minutes.
429
- */
430
- static readonly UNHEALTHY_TTL_AUTH_MS: number;
431
- getHealthStatus(providerType: string): HealthRecord['status'];
432
- markUnhealthy(providerType: string, reason: string, ttlMs?: number): void;
433
- markDegraded(providerType: string, reason: string): void;
434
- markHealthy(providerType: string): void;
435
- resetHealth(): void;
436
- execute(params: CreateParams, providers: BaseProvider[], options?: FallbackOptions): Promise<RelayResult>;
437
- stream(params: CreateParams, providers: BaseProvider[], options?: FallbackOptions): Promise<RelayStream>;
438
- /**
439
- * Find the next provider in priority order after `current` that supports
440
- * streaming AND has not already been tried-and-failed in this call.
441
- *
442
- * IMPORTANT: this method intentionally does NOT consult `getHealthStatus`.
443
- * Type-health is keyed by provider type string, but the documented "two
444
- * API keys for redundancy" pattern uses two instances of the SAME type
445
- * (e.g. primary + backup AnthropicAPIProvider with different keys). If we
446
- * skipped by type-health, the first instance failing would poison the
447
- * second instance because they share the type string. Per-call exclusion
448
- * by INSTANCE preserves global health-tracking semantics for the next
449
- * call's connect-time loop while letting same-type siblings serve as
450
- * legitimate failover targets.
451
- */
452
- private nextHealthyProvider;
453
- /**
454
- * Internal emit shim — keeps the public emit API tight.
455
- */
456
- private emitEngineEvent;
457
- /**
458
- * Emit a stream_complete event on the terminal exhaustion path so the
459
- * reporter flushes its lineage buffer for this callId and we don't leak
460
- * Map entries forever (bug C3).
461
- */
462
- private emitTerminalExhaustion;
463
- }
464
-
465
- /**
466
- * Pre-flight input-token estimation for {@link CreateParams}.
467
- *
468
- * Lets callers gate distillation (drop screenshots, summarize trajectories,
469
- * filter DOM bits) BEFORE incurring the round-trip + reject cost of an
470
- * oversized prompt. The catch-it-after path is `instanceof
471
- * RequestTooLargeError`; this is the catch-it-before path.
472
- *
473
- * **Approximate, not exact.** Anthropic's BPE tokenizer is closed-source;
474
- * we use the standard 4-chars-per-token heuristic plus per-message
475
- * overhead. Within ±15% on typical English text. For budgeting against
476
- * a known ceiling, leave a 20% safety margin (e.g., target 100K when
477
- * the provider ceiling is 120K).
478
- *
479
- * Image content blocks count for `IMAGE_TOKENS_FLAT` each — Anthropic
480
- * bills images at a flat per-image rate that depends on dimensions, but
481
- * the size-vs-tokens function is non-linear. The flat estimate is
482
- * intentionally conservative (overcounts small images, slightly
483
- * undercounts very large ones) so callers planning around it lean safe.
484
- *
485
- * Added in 1.6.3.
486
- */
487
-
488
- /**
489
- * Estimate the input token count for a `messages.create` request.
490
- *
491
- * Counts: system prompt + every message content block (text + JSON-
492
- * serialized structured blocks) + per-message overhead + flat per-image
493
- * cost + request overhead. Excludes the `max_tokens` output reservation —
494
- * callers should add that themselves when budgeting against a context
495
- * window.
496
- *
497
- * @example
498
- * const inputTokens = estimateInputTokens(params);
499
- * if (inputTokens > 100_000) {
500
- * params = distill(params);
501
- * }
502
- * const result = await client.messages.create(params);
503
- *
504
- * @example Budget against a 200K context window:
505
- * const total = estimateInputTokens(params) + params.max_tokens;
506
- * if (total > 180_000) {
507
- * params = distill(params);
508
- * }
509
- */
510
- declare function estimateInputTokens(params: CreateParams): number;
511
-
512
- /**
513
- * Model Registry — Single Source of Truth
514
- *
515
- * v1.4.0: expanded from alias-only to a full registry with capabilities,
516
- * pricing, and provider ownership. The `equivalents` field (cross-provider
517
- * model mapping) is intentionally absent here — it lives in the gateway
518
- * bundle as trade-secret IP.
519
- *
520
- * Public consumers use the helpers at the bottom (findModel, providerForModel,
521
- * modelsForProvider) to enumerate and route. The backwards-compat
522
- * MODEL_ALIASES + normalizeModel() exports are preserved.
523
- */
524
- type ProviderType = 'anthropic-api' | 'openai-api' | 'ollama' | 'claude-cli';
525
- interface ModelEntry {
526
- /** Canonical short name customers use */
527
- canonical: string;
528
- /** Provider-specific dated/full ID (passed to the API) */
529
- apiId: string;
530
- /** Which provider class handles this model */
531
- provider: ProviderType;
532
- /** Capabilities the model supports */
533
- capabilities: {
534
- streaming: boolean;
535
- tools: boolean;
536
- multimodal: boolean;
537
- /** Max context window in tokens */
538
- contextWindow: number;
539
- };
540
- /** Pricing per million tokens [input, output] — undefined for local models */
541
- pricing?: [number, number];
542
- }
543
- declare const MODEL_REGISTRY: ModelEntry[];
544
- declare const MODEL_ALIASES: Record<string, string>;
545
- declare function normalizeModel(model: string): string;
546
- declare function findModel(canonical: string): ModelEntry | undefined;
547
- declare function providerForModel(canonical: string): ProviderType | undefined;
548
- declare function modelsForProvider(provider: ProviderType): ModelEntry[];
549
-
550
- /**
551
- * Detect Claude CLI installation status.
552
- *
553
- * This is the public, lightweight binary-presence check. It runs entirely
554
- * within the public package and only checks (a) is the binary installed
555
- * (b) what version (c) version-drift warning. It does NOT check credential
556
- * status — that requires reading OS keychain stores, which is gateway-only.
557
- *
558
- * The `authenticated` field returned here is `null` to signal "not yet
559
- * checked." The LazyClaudeCLIProvider's probe() method calls this AND
560
- * the gateway-loaded credential resolver to fill in the full picture.
561
- *
562
- * Customers who want the full probe (including authenticated status)
563
- * should use `relay.providers[0].probe()` once the relay is constructed.
564
- */
565
- declare function detectCLI(): Promise<ProbeResult>;
566
-
567
- /**
568
- * Validates an Anthropic API key by making a minimal request.
569
- * 200 -> valid, 401 -> invalid, 429/529 -> valid but rate limited/overloaded.
570
- */
571
- declare function validateAnthropicKey(apiKey: string): Promise<{
572
- valid: boolean;
573
- error?: string;
574
- }>;
575
-
576
- interface CLIManifest {
577
- version: string;
578
- cliVersion: string;
579
- eventTypes: {
580
- system: string;
581
- streamEvent: string;
582
- assistant: string;
583
- result: string;
584
- };
585
- fieldMappings: {
586
- sessionId: string;
587
- model: string;
588
- cliVersion: string;
589
- content: string;
590
- usage: string;
591
- inputTokens: string;
592
- outputTokens: string;
593
- stopReason: string;
594
- isError: string;
595
- totalCostUsd: string;
596
- durationMs: string;
597
- error: string;
598
- event: string;
599
- };
600
- syntheticModel: string;
601
- }
602
- type SecurityState = 'SEC_NOMINAL' | 'SEC_CACHE' | 'SEC_RECOVERY' | 'SEC_DEGRADED';
603
- declare class ManifestDecryptor {
604
- private verificationFailures;
605
- private cachedManifest;
606
- private currentSecurityState;
607
- /**
608
- * Verify a JWS-signed manifest from the backend.
609
- * The server signs with RS256 private key; we verify with the embedded public key.
610
- */
611
- verifyManifest(jwsString: string): Promise<{
612
- manifest: CLIManifest;
613
- securityState: SecurityState;
614
- }>;
615
- getSecurityState(): SecurityState;
616
- clearCache(): void;
617
- }
618
- declare function getLastKnownGoodManifest(): CLIManifest;
619
- /**
620
- * Path-based value accessor for manifest field mappings.
621
- * Zero-dependency alternative to lodash.get().
622
- * Prototype pollution guarded.
623
- */
624
- declare function getValue(obj: any, path: string): any;
625
-
626
- declare class TelemetryReporter {
627
- private endpoint;
628
- private enabled;
629
- private keyId;
630
- /**
631
- * Lineage buffer: per-call accumulation of EngineEvents from FallbackEngine.
632
- * Flushed into the AuditEvent.lineage field at terminal stream_complete.
633
- * Keyed by callId so concurrent calls don't cross-contaminate.
634
- */
635
- private lineageBuffer;
636
- /** Stored unsubscribe handlers so dispose() can detach cleanly. */
637
- private unsubscribers;
638
- constructor(options: {
639
- endpoint?: string;
640
- enabled?: boolean;
641
- keyId?: string;
642
- });
643
- /**
644
- * Subscribe to the FallbackEngine's lifecycle event stream. The reporter
645
- * buffers per-call lineage and flushes a coherent AuditEvent at terminal
646
- * stream_complete. Synchronous emit semantics from EngineEventEmitter
647
- * guarantee that all lineage events for a callId are buffered before
648
- * the stream_complete handler runs.
649
- */
650
- subscribeToEngine(engine: FallbackEngine): void;
651
- /** Detach all engine subscriptions. Called by InferenceRelay.dispose(). */
652
- unsubscribe(): void;
653
- private bufferLineageEvent;
654
- /**
655
- * Construct the final AuditEvent from the stream_complete payload + buffered
656
- * lineage and call report(). Privacy invariant: lineage carries IDs only,
657
- * never content.
658
- */
659
- private flushLineageForCall;
660
- report(event: AuditEvent): void;
661
- }
662
-
663
- type RoutingDecision = 'prefer-user' | 'prefer-app' | string;
664
- type RoutingFunction = (params: CreateParams, estimate: {
665
- estimatedCost: number;
666
- }) => RoutingDecision;
667
- /**
668
- * Apply routing decision to reorder providers.
669
- * 'prefer-user' puts subscription-backed providers first (claude-cli, then user API keys).
670
- * 'prefer-app' puts app-owned providers first (app API key).
671
- * A specific provider name puts that provider first.
672
- */
673
- declare function applyRouting(providers: BaseProvider[], decision: RoutingDecision): BaseProvider[];
674
- /**
675
- * Create a routing function from a RoutingConfig.
676
- */
677
- declare function createRoutingFunction(config: RoutingConfig): RoutingFunction;
678
-
679
- /**
680
- * Estimate the cost of a request before sending it.
681
- * Used by routing to decide which provider handles the call.
682
- */
683
- declare function estimateCost(params: CreateParams): CostEstimate;
684
-
685
- /**
686
- * Get pricing for a model. Falls back to Sonnet pricing for unknown models.
687
- */
688
- declare function getModelPricing(model: string): [number, number];
689
-
690
- /**
691
- * CredentialStore — secure local storage for user API keys.
692
- * Keys never leave the client. Never sent to inference-relay servers.
693
- */
694
- interface CredentialStore {
695
- get(key: string): Promise<string | null>;
696
- set(key: string, value: string): Promise<void>;
697
- delete(key: string): Promise<void>;
698
- has(key: string): Promise<boolean>;
699
- }
700
-
701
- /**
702
- * Electron safeStorage credential store.
703
- *
704
- * Uses Electron's safeStorage.encryptString() / decryptString() to protect
705
- * API keys at rest. Encrypted blobs are persisted as a JSON file in the
706
- * user's data directory (~/.inference-relay/credentials.enc by default).
707
- *
708
- * Falls back to an in-memory Map if safeStorage is unavailable (e.g. when
709
- * running outside Electron or before the app is ready).
710
- */
711
- declare class ElectronCredentialStore implements CredentialStore {
712
- private filePath;
713
- private safeStorage;
714
- private memoryFallback;
715
- constructor(options?: {
716
- storagePath?: string;
717
- });
718
- private readDisk;
719
- private writeDisk;
720
- get(key: string): Promise<string | null>;
721
- set(key: string, value: string): Promise<void>;
722
- delete(key: string): Promise<void>;
723
- has(key: string): Promise<boolean>;
724
- }
725
-
726
- /**
727
- * IndexedDB credential store with AES-GCM encryption via Web Crypto API.
728
- *
729
- * Encryption key is derived from a user-provided passphrase (PBKDF2) or,
730
- * if no passphrase is given, a random key is generated and held in
731
- * sessionStorage for the lifetime of the browser tab. This means
732
- * credentials survive page refreshes within the same session but are
733
- * wiped when the tab closes — a deliberate safety tradeoff.
734
- */
735
- declare class IndexedDBCredentialStore implements CredentialStore {
736
- private dbName;
737
- private storeName;
738
- private cryptoKey;
739
- private passphrase?;
740
- private initPromise;
741
- constructor(options?: {
742
- passphrase?: string;
743
- dbName?: string;
744
- });
745
- private ensureReady;
746
- private init;
747
- private openDB;
748
- private idbGet;
749
- private idbPut;
750
- private idbDelete;
751
- private idbHas;
752
- private getOrCreateSessionSalt;
753
- private deriveKey;
754
- private encrypt;
755
- private decrypt;
756
- get(key: string): Promise<string | null>;
757
- set(key: string, value: string): Promise<void>;
758
- delete(key: string): Promise<void>;
759
- has(key: string): Promise<boolean>;
760
- }
761
-
762
- /**
763
- * In-memory credential store. Volatile — cleared on process exit.
764
- * Suitable for testing, CLI apps, or as a fallback when no persistent
765
- * storage backend is available.
766
- */
767
- declare class MemoryCredentialStore implements CredentialStore {
768
- private store;
769
- get(key: string): Promise<string | null>;
770
- set(key: string, value: string): Promise<void>;
771
- delete(key: string): Promise<void>;
772
- has(key: string): Promise<boolean>;
773
- }
774
-
775
- /**
776
- * GatewayLoader — fetches the signed Native Subscription Gateway bundle
777
- * from the Protocol Authority on first use, verifies it against the
778
- * embedded RSA public key, caches it locally, and loads it into the
779
- * relay process via vm.Script.
780
- *
781
- * The bundle contains the implementation of the gateway providers
782
- * (subscription-bridged providers, stream parsers, credential resolvers).
783
- * It is NOT distributed via npm — it lives in Convex blob storage on the
784
- * Protocol Authority and is downloaded on demand by license holders.
785
- *
786
- * This file is bundled INTO the public npm package (no IP here). It is
787
- * the ONLY thing the public package needs to know about the gateway —
788
- * after `await GatewayLoader.load(licenseKey)`, the loader returns the
789
- * gateway module's exports as a plain object. Callers (specifically
790
- * gateway/lazy-provider.ts) treat that object as opaque.
791
- *
792
- * Threat model:
793
- * - Non-customer cannot fetch the bundle: the Protocol Authority's
794
- * /v1/gateway/fetch endpoint requires a valid license key. Bundles are
795
- * served via short-TTL pre-signed URLs.
796
- * - MITM attacker cannot substitute a malicious bundle: the bundle ships
797
- * with an RS256 signature over `{ version, sha256, purpose: 'gateway-bundle' }`,
798
- * verified against the embedded RSA public key. Cross-payload confusion
799
- * is prevented by the `purpose` field — a manifest signature cannot be
800
- * replayed as a gateway-bundle signature and vice versa.
801
- * - Customer who leaks bundle: contractual problem (covered by EULA).
802
- * Each bundle has a build identifier embedded; leaked copies are
803
- * identifiable.
804
- * - Local cache tampering: bundle SHA-256 is verified against the
805
- * server-asserted SHA-256 before loading. A tampered cache file fails
806
- * verification and triggers a fresh fetch.
807
- * - Gateway endpoint outage: fail closed. The relay cannot serve
808
- * claude-cli requests until the gateway is reachable. The user can
809
- * still configure direct API providers as a fallback.
810
- */
811
- /**
812
- * The shape of the gateway module's exports after loading. The lazy
813
- * proxies use these to instantiate concrete implementations without the
814
- * public package needing to know any internal details.
815
- */
816
- interface GatewayModule {
817
- ClaudeCLIProvider: new () => any;
818
- KimiCLIProvider?: new () => any;
819
- ProcessPool: new (options?: {
820
- maxSize?: number;
821
- idleTimeoutMs?: number;
822
- }) => any;
823
- _lsp?: (model: string, baseUrl: string) => Promise<boolean>;
824
- _lspClear?: () => void;
825
- _lt?: (tools: any[]) => any[];
826
- _lpc?: (toolCalls: any[]) => any[];
827
- _lpsc?: (chunk: any, state: any) => any[];
828
- _lcss?: () => any;
829
- _rfp?: (model: string, targetProvider: string) => string | undefined;
830
- _cfc?: (model: string) => string[];
831
- _ge?: (model: string) => Record<string, string> | undefined;
832
- _saf?: (input: any) => any | null;
833
- }
834
- /**
835
- * Errors thrown by the loader. Designed to be matched against by error
836
- * classification code in fallback/engine.ts and verify.ts so customer-
837
- * facing diagnostics stay structured.
838
- */
839
- declare class GatewayLoaderError extends Error {
840
- readonly code: 'GATEWAY_FETCH_FAILED' | 'LICENSE_REJECTED' | 'NETWORK_TIMEOUT' | 'GATEWAY_VERIFICATION_FAILED' | 'GATEWAY_CACHE_WRITE_FAILED' | 'GATEWAY_LOAD_FAILED';
841
- readonly cause?: unknown | undefined;
842
- constructor(message: string, code: 'GATEWAY_FETCH_FAILED' | 'LICENSE_REJECTED' | 'NETWORK_TIMEOUT' | 'GATEWAY_VERIFICATION_FAILED' | 'GATEWAY_CACHE_WRITE_FAILED' | 'GATEWAY_LOAD_FAILED', cause?: unknown | undefined);
843
- }
844
- /**
845
- * Thrown specifically when /v1/gateway/fetch exhausts the retry budget —
846
- * Protocol Authority unreachable, returning 5xx, or DNS/network errored.
847
- * Branch on `instanceof GatewayUnavailableError` to handle gateway outages
848
- * separately from license/verification errors (which stay as
849
- * `GatewayLoaderError`).
850
- *
851
- * Inherits `code: 'GATEWAY_FETCH_FAILED'` and `name: 'GatewayLoaderError'`
852
- * (parent's name is intentionally preserved) so every existing string-match,
853
- * code-match, and name-match consumer keeps working. The new typed-error
854
- * path is purely additive — switch to `instanceof GatewayUnavailableError`
855
- * at your leisure.
856
- */
857
- declare class GatewayUnavailableError extends GatewayLoaderError {
858
- constructor(message: string, cause?: unknown);
859
- }
860
- /**
861
- * Options for {@link GatewayLoader.load}. Added in 1.6.2 so callers can
862
- * extend the retry budget for sustained Cloudflare/Convex 502s (the default
863
- * `[0, 1000, 4000]` exhausts in ~5 seconds, which is too short for some
864
- * upstream incidents).
865
- */
866
- interface GatewayLoadOptions {
867
- /**
868
- * Per-attempt delay budget in milliseconds. Defaults to
869
- * `[0, 1000, 4000]` (3 attempts over ~5s). Env override:
870
- * `IR_GATEWAY_RETRY_DELAYS_MS=0,5000,15000,60000`.
871
- *
872
- * Explicit option wins over env; env wins over default. Malformed values
873
- * (non-array, non-numeric, negative) log a `debugLog` warning and fall
874
- * back to default — don't throw, so a typo doesn't brick startup.
875
- */
876
- retryDelaysMs?: number[];
877
- }
878
- declare class GatewayLoader {
879
- /**
880
- * Load (or resolve from cache) the gateway module for a given license
881
- * key. On first call, fetches from the Protocol Authority, verifies the
882
- * signature, writes to local cache, and instantiates the module.
883
- *
884
- * @param licenseKey - The customer's license key (`ir_live_…`)
885
- * @param libraryVersion - The library version, used as the cache namespace
886
- * @param baseUrl - Override the gateway endpoint (test fixture only)
887
- * @param options - Optional load-time tuning (e.g., retry budget). Added 1.6.2.
888
- */
889
- static load(licenseKey: string, libraryVersion: string, baseUrl?: string, options?: GatewayLoadOptions): Promise<GatewayModule>;
890
- /**
891
- * Test helper: bypass the network and load from a local file path.
892
- * Used by tests and by the airgapped pre-fetch flow.
893
- */
894
- static loadFromDisk(bundlePath: string): Promise<GatewayModule>;
895
- private static tryLoadFromDisk;
896
- private static fetchFromAuthority;
897
- /**
898
- * Verify the RS256 signature over
899
- * { libraryVersion, bundleVersion, sha256, purpose: 'gateway-bundle' }.
900
- *
901
- * The `purpose` field is mandatory — a payload signed for `manifest`
902
- * cannot be replayed here. The `libraryVersion` field is the
903
- * version-rollback guard: a 1.3.0 bundle's signature cannot be replayed
904
- * to satisfy a 1.4.0 client (the SHA-256 also pins the bytes, but
905
- * comparing libraryVersion catches the case where an attacker somehow
906
- * generates a SHA collision OR substitutes a different bundle whose
907
- * libraryVersion field doesn't match what the client requested).
908
- * The signature itself is a JWS compact form.
909
- */
910
- private static verifySignature;
911
- /**
912
- * Execute the gateway bundle as a CommonJS module.
913
- *
914
- * The bundle is a CJS module — it expects `module.exports = { ClaudeCLIProvider, ... }`
915
- * after evaluation. We use Node's `vm.compileFunction` API, which is
916
- * the same primitive Node's own CJS loader uses internally to evaluate
917
- * every .js file in a Node process. The gateway bundle gets called as
918
- * if it were a normal CJS module: with explicit `exports`, `require`,
919
- * `module`, `__filename`, and `__dirname` parameters.
920
- *
921
- * Why not vm.Script + createContext: that approach captures `require`
922
- * from the parent (loader.ts) module's scope, which under tsup ESM
923
- * compilation becomes a dynamic-require shim that throws inside the
924
- * sandbox. compileFunction passes `require` as an explicit named
925
- * argument, so the gateway bundle's bare `require()` calls resolve to
926
- * the function parameter, never to a captured outer-scope wrapper.
927
- *
928
- * The require we pass is built via createRequire() bound to a path
929
- * inside the local cache directory. Node built-in modules resolve
930
- * via this require regardless of the bound path; the path only
931
- * matters for relative file resolves, which the gateway bundle does
932
- * not perform (it's self-contained).
933
- *
934
- * Trust model: by accepting the EULA, the customer agrees that the
935
- * gateway bundle runs with the privileges of their Node process.
936
- * Removing the vm.Script sandbox does not change this — the prior
937
- * sandbox provided full `require` access anyway, so it was security
938
- * theater. compileFunction is more honest: the gateway runs with the
939
- * same Node API surface as any other CJS file in the host process.
940
- */
941
- private static loadIntoVm;
942
- /**
943
- * Test helper: clear the in-process module cache. Tests use this between
944
- * cases to force a fresh load.
945
- */
946
- static clearCache(): void;
947
- }
948
-
949
- export { AnthropicAPIProvider, AuditEvent, BaseProvider, type CLIManifest, CostEstimate, CreateParams, type CredentialStore, ElectronCredentialStore, EngineEvent, EngineEventEmitter, FallbackEngine, GatewayLoader, GatewayLoaderError, type GatewayModule, GatewayUnavailableError, IndexedDBCredentialStore, InferenceRelay, InferenceRelayConfig, type LicenseValidation, MODEL_ALIASES, MODEL_REGISTRY, ManifestDecryptor, MemoryCredentialStore, type ModelEntry, OllamaProvider, OpenAIAPIProvider, ProbeResult, ProviderHealth, type ProviderType, RELAY_FEATURES, RELAY_VERSION, RateLimitEvent, RelayError, type RelayFeatures, type RelayHello, RelayResult, RelayStream, RequestTooLargeError, RoutingConfig, type RoutingDecision, type RoutingFunction, SecurityState$1 as SecurityState, TelemetryReporter, applyRouting, createRoutingFunction, detectCLI, estimateCost, estimateInputTokens, findModel, getLastKnownGoodManifest, getModelPricing, getValue, modelsForProvider, normalizeModel, providerForModel, relayHello, validateAnthropicKey, validateLicense };