@wengine-ai/llms 2.3.2414 → 2.3.2416

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.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The canonical POST-/api-config runtime reload sequence, owned by the core
3
+ * and handed to plugins via PluginContext.host.reloadRuntime. Before this
4
+ * existed, every managed subscription provider carried its own
5
+ * copy — the plugin API exists to end exactly that kind of duplication.
6
+ */
7
+ export declare function reloadProviderRuntime(server: any, config: any, log: {
8
+ info(msg: string): void;
9
+ warn(msg: string): void;
10
+ error(msg: string): void;
11
+ }): void;
@@ -0,0 +1,33 @@
1
+ import { dirname } from "node:path";
2
+ import { type PluginRegistry } from "./registry";
3
+ export interface PluginLoadResult {
4
+ loaded: string[];
5
+ failed: Array<{
6
+ name: string;
7
+ error: string;
8
+ }>;
9
+ skipped: Array<{
10
+ name: string;
11
+ reason: string;
12
+ }>;
13
+ }
14
+ /** Reject symlinked directories, not just lexical traversal. */
15
+ export declare function resolvePluginDir(configDir: string, name: string): string | null;
16
+ /** Resolve the subscription-plugin dispatcher for one server instance: the
17
+ * explicit SUBSCRIPTION_PROXY_URL from the live config when a server/config
18
+ * service exists, else the initConfig env mirror (host-less loaders). Exported
19
+ * for tests — this is the exact closure the plugin host hands out. */
20
+ export declare function resolveSubscriptionDispatcher(server: unknown): unknown;
21
+ export declare function loadInstalledPlugins(deps: {
22
+ configDir: string;
23
+ log: {
24
+ info(msg: string): void;
25
+ warn(msg: string): void;
26
+ error(msg: string): void;
27
+ };
28
+ getServer: () => any;
29
+ registry?: PluginRegistry;
30
+ }): Promise<PluginLoadResult>;
31
+ /** Production must pass the host registry; default retained only for isolated loader tests. */
32
+ export declare function describeInstalledPlugins(configDir: string, registry?: PluginRegistry): Array<Record<string, unknown>>;
33
+ export { dirname };
@@ -0,0 +1,39 @@
1
+ import { type FastifyInstance } from "fastify";
2
+ import type { TransformerService } from "../services/transformer";
3
+ import type { LoadedPluginInfo, ManagedRuntime, PluginClassifiedError, PluginContext, PluginHostCapabilities, PluginManifest } from "./types";
4
+ import { PLUGIN_CORE_API } from "./types";
5
+ type PluginLog = PluginContext["log"];
6
+ /** One registry per Server. No production lookup falls back to the test registry. */
7
+ export declare class PluginRegistry {
8
+ private plugins;
9
+ private transformerServices;
10
+ private disposed;
11
+ private revision;
12
+ attachTransformerService(service: TransformerService): void;
13
+ beginActivation(manifest: PluginManifest, log: PluginLog, host: PluginHostCapabilities, directory?: string): {
14
+ context: PluginContext;
15
+ commit: (card?: Buffer, routeHost?: FastifyInstance) => Promise<void>;
16
+ readonly cardPath: string | undefined;
17
+ abort: () => void;
18
+ };
19
+ getManagedRuntimeFor(provider: any): ManagedRuntime | undefined;
20
+ getRegisteredRuntimes(): ManagedRuntime[];
21
+ applyConfigProtectors(next: any, prev: any): void;
22
+ listLoadedPlugins(): LoadedPluginInfo[];
23
+ getPublicCard(name: string, file: string): Buffer | undefined;
24
+ private prepareRoutes;
25
+ registerAllRoutes(app: FastifyInstance): Promise<void>;
26
+ dispose(): void;
27
+ private assertOpen;
28
+ }
29
+ export declare const getTestPluginRegistry: () => PluginRegistry;
30
+ export declare function clearPluginRegistry(): void;
31
+ export declare const getManagedRuntimeFor: (provider: any, registry?: PluginRegistry) => ManagedRuntime | undefined;
32
+ export declare const getRegisteredRuntimes: () => ManagedRuntime[];
33
+ export declare const listLoadedPlugins: () => LoadedPluginInfo[];
34
+ export declare const applyPluginConfigProtectors: (next: any, prev: any) => void;
35
+ export declare const registerAllPluginRoutes: (app: FastifyInstance) => Promise<void>;
36
+ /** Build the core-side thrown error from a plugin classification. */
37
+ export declare function pluginErrorFrom(runtimeName: string, info: PluginClassifiedError): Error & Record<string, any>;
38
+ export declare function isPluginClassifiedError(error: any): boolean;
39
+ export { PLUGIN_CORE_API };
@@ -0,0 +1,3 @@
1
+ import type { PluginContext } from "./types";
2
+ /** Explicit activation context; never a process-wide pointer to a live server. */
3
+ export declare function registerTransformerFromPlugin(context: PluginContext, cls: new () => any): void;
@@ -0,0 +1,236 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ import type { LLMProvider } from "../types/llm";
3
+ /**
4
+ * Provider plugin API (docs/design/provider-plugins.md).
5
+ *
6
+ * A plugin ships subscription-provider code out of the main package: a
7
+ * transformer, a managed runtime (upstream lock, token injection, error
8
+ * taxonomy), admin routes, a config protector and a UI card. The core only
9
+ * knows this interface — never the provider internals.
10
+ *
11
+ * Lives in `src/provider-plugins/` deliberately: `src/plugins/` is the older
12
+ * output-plugin system (token-speed) and must not mix namespaces.
13
+ *
14
+ * `coreApi` is a semver-ish compatibility marker. The core refuses plugins
15
+ * whose major does not match its own, so the public contract can evolve
16
+ * without silently breaking third parties.
17
+ */
18
+ export declare const PLUGIN_CORE_API = "1";
19
+ export interface PluginManifest {
20
+ name: string;
21
+ version: string;
22
+ coreApi: string;
23
+ description?: string;
24
+ }
25
+ /** A classified upstream error, shared shape across providers (contract §1.6). */
26
+ export interface PluginClassifiedError {
27
+ code: string;
28
+ message: string;
29
+ httpStatus: number;
30
+ cooldownMs?: number;
31
+ validationUrl?: string;
32
+ retryable?: boolean;
33
+ }
34
+ /**
35
+ * Input for a runtime-owned bounded dispatch wrapper (`dispatchRequest`). The
36
+ * core supplies its ordinary single-attempt dispatch as `attempt()` — every
37
+ * generic seam (ensureUsable/lockUpstream/signRequest/classifyError/
38
+ * noteUpstreamError, the 401-once refresh budget, response-error
39
+ * confidentiality) runs INSIDE each attempt; the wrapper only owns the loop
40
+ * around it (e.g. rotating local credentials). The generic same-model retry
41
+ * and fallback chains stay in the core and are never re-implemented by a
42
+ * wrapper.
43
+ */
44
+ export interface PluginDispatchInput {
45
+ /** One ordinary core dispatch attempt. May be called several times, always
46
+ * within the runtime's own hard bound (≤ its credential count and ≤ a
47
+ * constant cap). */
48
+ attempt(): Promise<Response>;
49
+ /** Finalized request body (body-derived account selection works). */
50
+ requestBody: any;
51
+ /** The per-provider request config; a wrapper may attach per-attempt state
52
+ * that lockUpstream/signRequest read back on the same object. */
53
+ config: any;
54
+ provider: LLMProvider;
55
+ /** The originating request object (shared identity across retry/fallback
56
+ * spreads) for per-request wrapper state. */
57
+ req?: any;
58
+ }
59
+ /**
60
+ * Outcome of a level-1 account-state check.
61
+ *
62
+ * `available: false` parks the provider without a network call; `parkedUntil`
63
+ * lets the plugin tell the core when the account frees up (e.g. a quota reset
64
+ * epoch), so the probe does not re-check before then.
65
+ */
66
+ export interface ManagedProbeVerdict {
67
+ available: boolean;
68
+ /** Epoch ms after which the account may be usable again. */
69
+ parkedUntil?: number;
70
+ /** Stable reason used for logging only — never shown to a client. */
71
+ reason?: string;
72
+ }
73
+ /** Input for a runtime-owned liveness probe. */
74
+ export interface ManagedProbeInput {
75
+ /** One ordinary core dispatch attempt (same seam as `PluginDispatchInput.attempt`). */
76
+ attempt(): Promise<Response>;
77
+ /** Model to probe with (the first circuit-open model of the provider). */
78
+ model: string;
79
+ provider: LLMProvider;
80
+ /** Per-attempt config; the core sets `TIMEOUT` so a probe cannot outlive its budget. */
81
+ config: any;
82
+ }
83
+ /**
84
+ * Managed-subscription semantics for one provider family. Everything the data
85
+ * plane needs to talk to a subscription upstream without the core knowing it:
86
+ * fixed upstream, token injection, error taxonomy, cooling circuit breaker.
87
+ */
88
+ export interface ManagedRuntime {
89
+ /** Provider family name, e.g. "codex". Used for logging and 401 bookkeeping keys. */
90
+ name: string;
91
+ isManaged(provider: LLMProvider): boolean;
92
+ /** Circuit breaker before any network touch (Gemini lesson: must run BEFORE token refresh). */
93
+ ensureUsable?(): Promise<void>;
94
+ /** Lock the upstream: mutate config.url / redirect / dispatcher. Runs once per attempt. */
95
+ lockUpstream?(config: any, provider: LLMProvider): Promise<void>;
96
+ /**
97
+ * Final header injection — called AFTER the request body is finalized, so
98
+ * body-derived headers (e.g. Codex session-id from prompt_cache_key) work.
99
+ * Returns the headers object to send. `config` is the per-attempt request
100
+ * config (optional for backward compatibility): a dispatch wrapper can
101
+ * select per-attempt credential material on it and the final header
102
+ * injection reads it back; hosts that predate the field leave wrappers to
103
+ * their own lockUpstream-saved state.
104
+ */
105
+ signRequest(input: {
106
+ headers: Record<string, string>;
107
+ requestBody: any;
108
+ provider: LLMProvider;
109
+ config?: any;
110
+ }): Promise<Record<string, string>>;
111
+ /**
112
+ * Classify an upstream failure into the shared taxonomy. Return null to let
113
+ * the next handler (built-in branches, then generic) deal with it.
114
+ */
115
+ classifyError?(status: number, body: unknown, systemText: string): PluginClassifiedError | null;
116
+ /** Persist a classified error (cooldown, lastError). Must never throw into the response path. */
117
+ noteUpstreamError?(info: PluginClassifiedError): Promise<void>;
118
+ /**
119
+ * Generic HTTP-200 hidden-error-envelope detector. When present, the core
120
+ * runs it against every parsed JSON 200 body and throws through the shared
121
+ * classification seam when it returns true — BEFORE the generic `error`
122
+ * field check, because some upstreams carry `{code,msg,data}` envelopes
123
+ * without an `error` key. This is a SECONDARY safety net for runtimes with
124
+ * a dispatch wrapper: rotation on such envelopes must happen inside the
125
+ * wrapper (before the response is returned); this hook only catches bodies
126
+ * a wrapper has already returned.
127
+ */
128
+ isErrorEnvelope?(body: unknown): boolean;
129
+ /**
130
+ * Optional bounded multi-attempt dispatcher (see PluginDispatchInput). When
131
+ * present the core calls it instead of performing its single attempt and
132
+ * forwards the returned response to the client. The wrapper MUST enforce a
133
+ * hard attempt bound and must never swallow client aborts.
134
+ */
135
+ dispatchRequest?(input: PluginDispatchInput): Promise<Response>;
136
+ /**
137
+ * Cheap account-state check used by the background recovery probe.
138
+ *
139
+ * Two-level probing exists because account state and reachability fail
140
+ * independently: a parked/over-quota account must NOT spend a network call
141
+ * (the Gemini lesson), while a healthy-but-unreachable account must not be
142
+ * restored to the model list on the strength of its state alone. The core
143
+ * calls this FIRST and only issues the (expensive) real request when it
144
+ * resolves.
145
+ *
146
+ * Resolve to use the `available` hint semantics below; throw a classified
147
+ * error (e.g. `*_account_cooling`) to report the account as unusable. When
148
+ * absent, the core falls back to `ensureUsable()` — which is the same check
149
+ * every real request already performs — so plugins that only implement the
150
+ * circuit breaker still work.
151
+ */
152
+ probeAccountState?(): Promise<ManagedProbeVerdict>;
153
+ /**
154
+ * Optional runtime-owned liveness probe (level 2).
155
+ *
156
+ * Called only after `probeAccountState`/`ensureUsable` reported the account as
157
+ * usable. Implementations should make the cheapest request that still proves
158
+ * the upstream answers for THIS account (a quota/status endpoint is ideal,
159
+ * otherwise a 1-token chat). Must honour the supplied signal/timeout and must
160
+ * not persist cooldowns that a probe is not allowed to set.
161
+ *
162
+ * When absent the core performs a generic 1-token request through
163
+ * `dispatchRequest` (see `PluginDispatchInput`).
164
+ */
165
+ probeLiveness?(input: ManagedProbeInput): Promise<Response>;
166
+ /**
167
+ * Opt-in 401 handling: when set, the core performs ONE forced refresh and
168
+ * re-dispatch on 401 before classifying. `refresh` must persist rotated
169
+ * refresh tokens before the in-memory identity switches.
170
+ */
171
+ authRefresh?: {
172
+ forceRefresh(): Promise<void>;
173
+ };
174
+ /** Suppress the request/response debug logs for this provider (token hygiene). */
175
+ suppressDebugLog?: boolean;
176
+ }
177
+ /**
178
+ * Host capabilities handed to a plugin at activation. Plugins must use THESE
179
+ * for anything stateful (config reads/writes, runtime reload, directories) —
180
+ * bundling a copy of a core module would fork its in-process state.
181
+ */
182
+ export interface PluginHostCapabilities {
183
+ /** CCR config dir ($CCR_CONFIG_DIR), for the plugin's private credential file. */
184
+ configDir: string;
185
+ readConfig(): Promise<any>;
186
+ writeConfig(next: any): Promise<void>;
187
+ /**
188
+ * Read config WITHOUT env-var interpolation. Managed providers must do their
189
+ * read-modify-write against this: `readConfig` interpolates $VAR placeholders,
190
+ * so writing it back would bake env values into config.json. Optional for
191
+ * backward compatibility; hosts that predate it only offer `readConfig`.
192
+ */
193
+ readRawConfig?(): Promise<any>;
194
+ /**
195
+ * Re-resolve providers/transformers after a config write (server reload).
196
+ * The config argument is the JUST-WRITTEN config; passing it avoids a reread
197
+ * race between write and reload. Optional parameter for backward compat.
198
+ */
199
+ reloadRuntime(config?: any): Promise<void>;
200
+ /** undici Agent that bypasses CCR/proxy env routing (for fixed subscription origins). */
201
+ getDirectDispatcher(): unknown;
202
+ /**
203
+ * Dispatcher for fixed subscription origins honoring the explicit
204
+ * SUBSCRIPTION_PROXY_URL opt-in: the direct agent when unset, a ProxyAgent
205
+ * for the configured URL otherwise. Optional for backward compatibility —
206
+ * hosts that predate it only offer getDirectDispatcher (fully direct).
207
+ */
208
+ getSubscriptionDispatcher?(): unknown;
209
+ }
210
+ export interface PluginContext {
211
+ readonly manifest: PluginManifest;
212
+ readonly log: {
213
+ info(msg: string): void;
214
+ warn(msg: string): void;
215
+ error(msg: string): void;
216
+ };
217
+ readonly host: PluginHostCapabilities;
218
+ registerTransformer(cls: new () => any): void;
219
+ registerManagedRuntime(rt: ManagedRuntime): void;
220
+ registerAdminRoutes(fn: (app: FastifyInstance) => void): void;
221
+ registerConfigProtector(fn: (next: any, prev: any) => void): void;
222
+ /** Path to the prebuilt card chunk, relative to the plugin directory. */
223
+ registerUiCard(def: {
224
+ cardPath: string;
225
+ }): void;
226
+ registerLocales(bundle: Record<string, Record<string, unknown>>): void;
227
+ }
228
+ export interface LoadedPluginInfo {
229
+ name: string;
230
+ version: string;
231
+ enabled: true;
232
+ /** Served by the core at this URL for the UI to dynamic-import. */
233
+ cardUrl?: string;
234
+ locales?: Record<string, Record<string, unknown>>;
235
+ error?: string;
236
+ }
package/dist/server.d.ts CHANGED
@@ -6,6 +6,7 @@ import { TokenizerService } from "./services/tokenizer";
6
6
  import { router, calculateTokenCount, searchProjectBySession } from "./utils/router";
7
7
  import { sessionUsageCache } from "./utils/cache";
8
8
  import { ActiveProbeService } from "./services/active-probe";
9
+ import { PluginRegistry } from "./provider-plugins/registry";
9
10
  import type { CcrPreHandlerCallbacks } from "./ccr/request-pipeline";
10
11
  declare module "fastify" {
11
12
  interface FastifyRequest {
@@ -27,11 +28,18 @@ export interface ServerOptions extends FastifyServerOptions {
27
28
  }
28
29
  declare class Server {
29
30
  app: FastifyInstance;
31
+ readonly pluginRegistry: PluginRegistry;
32
+ private namespaceProviders;
33
+ /** Reload in place: registered namespaces must not retain stale provider maps. */
34
+ reloadProviders(config: any): void;
35
+ /** Explicit namespace-owned config update; never merge root provider authority. */
36
+ reloadNamespaceProviders(name: string, providers: any[]): void;
30
37
  configService: ConfigService;
31
38
  providerService: ProviderService;
32
39
  transformerService: TransformerService;
33
40
  tokenizerService: TokenizerService;
34
41
  activeProbeService?: ActiveProbeService;
42
+ /** Gemini OAuth (Antigravity) managed-provider auth service. */
35
43
  recordUsage?: (data: any) => void;
36
44
  private readonly readiness;
37
45
  private mainNamespaceRegistered;
@@ -20,7 +20,7 @@ export interface ActiveProbeConfig {
20
20
  /** Providers to exclude from active probing (e.g., providers without models endpoint) */
21
21
  excludeProviders?: string[];
22
22
  }
23
- export type ProviderProbeStatus = 'healthy' | 'slow' | 'error' | 'timeout';
23
+ export type ProviderProbeStatus = 'healthy' | 'slow' | 'error' | 'timeout' | 'skipped';
24
24
  export type ProviderProbeErrorKind = 'timeout' | 'network' | 'http';
25
25
  export type ProviderProbeSource = 'health' | 'manual' | 'rate-limit-headers';
26
26
  export interface ProviderProbeTelemetry {
@@ -36,6 +36,7 @@ export interface ProviderProbeTelemetry {
36
36
  }
37
37
  export interface ManualProbeResult {
38
38
  success: boolean;
39
+ skipped?: boolean;
39
40
  latencyMs: number;
40
41
  status: ProviderProbeStatus;
41
42
  isSlow: boolean;
@@ -56,9 +57,13 @@ export declare class ActiveProbeService {
56
57
  private resolveProxyUrl?;
57
58
  private logger?;
58
59
  private getConfig?;
60
+ /** Resolves the installed plugin runtime that owns a managed provider. */
61
+ private resolveManagedRuntime?;
62
+ /** Periodic timer for managed-subscription recovery probing. */
63
+ private managedProbeTimer?;
59
64
  private running;
60
65
  private probeTelemetry;
61
- constructor(getProviders: () => LLMProvider[], config?: ActiveProbeConfig, resolveProxyUrl?: (provider: LLMProvider) => string | undefined, logger?: any, getConfig?: (key: string) => any);
66
+ constructor(getProviders: () => LLMProvider[], config?: ActiveProbeConfig, resolveProxyUrl?: (provider: LLMProvider) => string | undefined, logger?: any, getConfig?: (key: string) => any, resolveManagedRuntime?: (provider: LLMProvider) => any);
62
67
  /**
63
68
  * Record the latest reachability probe result for a provider.
64
69
  * Only /models-style reachability probes (health, manual, rate-limit header
@@ -101,6 +106,43 @@ export declare class ActiveProbeService {
101
106
  * Rate-limited models are NOT probed by runHealthProbe — they have a separate timer
102
107
  * so they can be checked more frequently (every 2 minutes) without affecting normal probes.
103
108
  */
109
+ /**
110
+ * Recover circuit-open MANAGED (subscription) providers with one real request.
111
+ *
112
+ * runHealthProbe deliberately skips managed providers: their credentials are
113
+ * plugin-owned, so an unauthenticated GET /v1/models would fail for reasons
114
+ * unrelated to health. The consequence was that a subscription provider whose
115
+ * circuit opened (exhausted quota, revoked token, upstream outage) had NO
116
+ * background path back to healthy — it stayed circuit-open and the only way it
117
+ * ever recovered was a real user request hitting it again, which is exactly the
118
+ * "keeps failing and keeps being called" behaviour.
119
+ *
120
+ * This closes that loop: for each managed provider with a circuit-open model
121
+ * whose probe interval has elapsed, issue one minimal request through the
122
+ * plugin's own dispatcher (so token refresh, account rotation and the plugin's
123
+ * error taxonomy all apply) and restore its models only when the upstream
124
+ * actually answered.
125
+ */
126
+ runManagedRecoveryProbe(): Promise<void>;
127
+ /**
128
+ * Level 1 of the managed recovery probe: is the ACCOUNT usable at all?
129
+ *
130
+ * Returns false when the account is parked (quota exhausted / verification
131
+ * needed / risk-blocked), in which case the caller must skip the liveness call
132
+ * entirely — testing the network for an account that cannot answer is pure
133
+ * waste and, for subscription providers, a rate-limit risk.
134
+ */
135
+ private probeManagedAccountState;
136
+ /**
137
+ * Level 2 of the managed recovery probe: does the upstream actually answer?
138
+ *
139
+ * Uses a runtime-owned `probeLiveness` when the plugin provides one (a quota or
140
+ * status endpoint costs no chat quota), otherwise performs a generic 1-token
141
+ * request through the runtime's dispatcher. The dispatcher input carries a real
142
+ * `attempt()` and a per-attempt `config` — both are REQUIRED by
143
+ * `PluginDispatchInput`, and omitting them made wrappers fail unconditionally.
144
+ */
145
+ private probeManagedLiveness;
104
146
  private runRateLimitProbe;
105
147
  /**
106
148
  * Run scheduled wake-up for configured providers
@@ -121,11 +163,11 @@ export declare class ActiveProbeService {
121
163
  /**
122
164
  * Initialize and get the active probe service
123
165
  */
124
- export declare function getActiveProbeService(getProviders: () => LLMProvider[], config?: ActiveProbeConfig, resolveProxyUrl?: (provider: LLMProvider) => string | undefined, logger?: any, getConfig?: (key: string) => any): ActiveProbeService;
166
+ export declare function getActiveProbeService(getProviders: () => LLMProvider[], config?: ActiveProbeConfig, resolveProxyUrl?: (provider: LLMProvider) => string | undefined, logger?: any, getConfig?: (key: string) => any, resolveManagedRuntime?: (provider: LLMProvider) => any): ActiveProbeService;
125
167
  /**
126
168
  * Start the active probe service (convenience function)
127
169
  */
128
- export declare function startActiveProbe(getProviders: () => LLMProvider[], config?: ActiveProbeConfig, resolveProxyUrl?: (provider: LLMProvider) => string | undefined, logger?: any, getConfig?: (key: string) => any): ActiveProbeService;
170
+ export declare function startActiveProbe(getProviders: () => LLMProvider[], config?: ActiveProbeConfig, resolveProxyUrl?: (provider: LLMProvider) => string | undefined, logger?: any, getConfig?: (key: string) => any, resolveManagedRuntime?: (provider: LLMProvider) => any): ActiveProbeService;
129
171
  /**
130
172
  * Stop the active probe service
131
173
  */
@@ -62,7 +62,7 @@ export declare class ProviderHealthStore {
62
62
  * Check if a provider/model is available for routing
63
63
  * closed = available, half-open = available (lower priority), open = unavailable
64
64
  */
65
- isAvailable(provider: string, model: string): boolean;
65
+ isAvailable(provider: string, model: string, allowRecoveryProbe?: boolean): boolean;
66
66
  /**
67
67
  * Get health status priority for sorting
68
68
  * Returns: 0 = closed (best), 1 = half-open (ok), 2 = open (worst)
@@ -76,6 +76,23 @@ export declare class ProviderHealthStore {
76
76
  * Get all models in fail pool (open status)
77
77
  */
78
78
  getFailPoolModels(): string[];
79
+ /**
80
+ * Stamp a background probe attempt without changing circuit state.
81
+ *
82
+ * Recovery probes need this: a failed probe must keep the model circuit-open
83
+ * but move `lastProbeTime` forward, otherwise the interval gate in
84
+ * isAvailable()/the probe loop would re-probe on every tick.
85
+ */
86
+ recordProbeAttempt(provider: string, model: string): void;
87
+ /**
88
+ * Park a model until the account's own reset epoch.
89
+ *
90
+ * A plugin that reports its quota window (e.g. `resets_at`) knows better than
91
+ * a fixed probe interval when the account can answer again. Setting
92
+ * `rateLimitUntil` keeps the model hidden from the list AND stops the recovery
93
+ * probe from spending network calls on a known-parked account.
94
+ */
95
+ markParkedUntil(provider: string, model: string, until: number): void;
79
96
  /**
80
97
  * Get all models in half-open state
81
98
  */
@@ -8,6 +8,8 @@ export declare class ProviderService {
8
8
  private providers;
9
9
  private modelRoutes;
10
10
  constructor(configService: ConfigService, transformerService: TransformerService, logger: any);
11
+ /** Preserve the object decorated into every Fastify namespace. */
12
+ reload(): void;
11
13
  private initializeCustomProviders;
12
14
  private initializeFromProvidersArray;
13
15
  registerProvider(request: RegisterProviderRequest): LLMProvider;
@@ -1,10 +1,16 @@
1
- import { ProxyAgent } from "undici";
1
+ import { Agent, ProxyAgent } from "undici";
2
2
  import type { ConfigService } from "./config";
3
3
  export interface ProxyAwareProvider {
4
4
  proxyEnabled?: boolean;
5
5
  proxy_enabled?: boolean;
6
6
  }
7
7
  export declare const PROXY_URL_KEYS: readonly ["HTTPS_PROXY", "https_proxy", "httpsProxy", "PROXY_URL"];
8
+ /** Explicit opt-in proxy for the subscription plugins' fixed origins (codex,
9
+ * gemini-oauth). Unlike PROXY_URL this is never applied implicitly and never
10
+ * read from the environment: unset keeps those plugins fully direct (the
11
+ * supported no-config deployment is an OS-level transparent proxy, e.g. TUN
12
+ * mode). Same http(s)-only contract as PROXY_URL. */
13
+ export declare const SUBSCRIPTION_PROXY_URL_KEY = "SUBSCRIPTION_PROXY_URL";
8
14
  export type ProxyUrlValidation = {
9
15
  ok: true;
10
16
  } | {
@@ -34,6 +40,18 @@ export declare function findInvalidProxyUrls(config: Record<string, unknown>): A
34
40
  key: string;
35
41
  error: string;
36
42
  }>;
43
+ /** A process-wide direct dispatcher, used by fixed-origin providers that must ignore configured proxies. */
44
+ export declare function getDirectDispatcher(): Agent;
45
+ /** Resolve the explicit subscription-proxy opt-in from live config. Only a
46
+ * string value counts: non-string config values (hand-edited booleans,
47
+ * numbers) mean "unset" — they must never become garbage proxy URLs. */
48
+ export declare function getSubscriptionProxyUrl(configService: Pick<ConfigService, "get">): string | undefined;
49
+ /** Dispatcher for the subscription plugins' fixed-origin calls: the shared
50
+ * direct agent when SUBSCRIPTION_PROXY_URL is unset, else the cached
51
+ * ProxyAgent for it. Unsupported protocols throw (same contract as
52
+ * getProxyDispatcher) so a typo fails loudly instead of silently sending
53
+ * subscription traffic directly. */
54
+ export declare function getSubscriptionDispatcher(proxyUrl: string | undefined): Agent | ProxyAgent;
37
55
  /**
38
56
  * Resolve the configured proxy URL using the compatibility key priority.
39
57
  */
@@ -5,6 +5,8 @@ export declare class TransformerService {
5
5
  private readonly logger;
6
6
  private transformers;
7
7
  constructor(configService: ConfigService, logger: any);
8
+ /** Construct without mutation so plugin activation can validate every service first. */
9
+ preparePluginTransformer(cls: new () => any): Transformer | TransformerConstructor;
8
10
  registerTransformer(name: string, transformer: Transformer): void;
9
11
  getTransformer(name: string): Transformer | TransformerConstructor | undefined;
10
12
  getAllTransformers(): Map<string, Transformer | TransformerConstructor>;
@@ -10,12 +10,6 @@ export declare class OpenAIResponsesTransformer implements Transformer {
10
10
  transformResponseOut(response: Response): Promise<Response>;
11
11
  private normalizeRequestContent;
12
12
  transformResponseIn(response: Response, context?: any): Promise<Response>;
13
- /**
14
- * Prepend a string chunk to the beginning of an existing ReadableStream.
15
- * Used when we've peeked at the first chunk and need to feed it back into
16
- * the converter along with the rest of the stream.
17
- */
18
- private prependToStream;
19
13
  /**
20
14
  * Convert an OpenAI Chat SSE stream to Responses API SSE events.
21
15
  * Handles choices[0].delta.content, choices[0].delta.tool_calls, and finish_reason.
@@ -199,6 +199,10 @@ export interface LLMProvider {
199
199
  wakeupEnabled?: boolean;
200
200
  wakeupTime?: string;
201
201
  wakeupModel?: string;
202
+ /** Set for CCR-managed subscription providers (config `ccr_managed`) so
203
+ * provider plugins can recognize their managed entries as one of their
204
+ * managed-provider signals (fixed upstream lock, own auth headers). */
205
+ pluginManaged?: boolean;
202
206
  }
203
207
  export type RegisterProviderRequest = LLMProvider;
204
208
  export interface ModelRoute {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wengine-ai/llms",
3
- "version": "2.3.2414",
3
+ "version": "2.3.2416",
4
4
  "description": "A universal LLM API transformation server",
5
5
  "main": "dist/cjs/server.cjs",
6
6
  "module": "dist/esm/server.mjs",
@@ -64,7 +64,7 @@
64
64
  "uuid": "^11.1.0",
65
65
  "@fastify/multipart": "^9.0.0",
66
66
  "@fastify/static": "^8.2.0",
67
- "@wengine-ai/claude-code-router-shared": "^2.3.2414",
67
+ "@wengine-ai/claude-code-router-shared": "^2.3.2416",
68
68
  "adm-zip": "^0.5.16",
69
69
  "better-sqlite3": "^12.10.0",
70
70
  "rotating-file-stream": "^3.2.7"
@@ -1 +0,0 @@
1
- {"root":["./src/server.ts","./src/api/middleware.ts","./src/api/routes.ts","./src/plugins/index.ts","./src/plugins/plugin-manager.ts","./src/plugins/token-speed.ts","./src/plugins/types.ts","./src/plugins/output/console-handler.ts","./src/plugins/output/index.ts","./src/plugins/output/output-manager.ts","./src/plugins/output/temp-file-handler.ts","./src/plugins/output/types.ts","./src/plugins/output/webhook-handler.ts","./src/services/active-probe.ts","./src/services/config.ts","./src/services/provider-health.ts","./src/services/provider.ts","./src/services/quota-adapters.ts","./src/services/quota-store.ts","./src/services/rate-limit.ts","./src/services/tokenizer.ts","./src/services/transformer.ts","./src/tokenizer/api-tokenizer.ts","./src/tokenizer/huggingface-tokenizer.ts","./src/tokenizer/tiktoken-tokenizer.ts","./src/transformer/anthropic.transformer.ts","./src/transformer/cerebras.transformer.ts","./src/transformer/cleancache.transformer.ts","./src/transformer/customparams.transformer.ts","./src/transformer/deepseek.transformer.ts","./src/transformer/enhancetool.transformer.ts","./src/transformer/forcereasoning.transformer.ts","./src/transformer/gemini.transformer.ts","./src/transformer/groq.transformer.ts","./src/transformer/index.ts","./src/transformer/kimi.transformer.ts","./src/transformer/maxcompletiontokens.transformer.ts","./src/transformer/maxtoken.transformer.ts","./src/transformer/openai.responses.transformer.ts","./src/transformer/openai.transformer.ts","./src/transformer/openrouter.transformer.ts","./src/transformer/reasoning.transformer.ts","./src/transformer/sampling.transformer.ts","./src/transformer/streamoptions.transformer.ts","./src/transformer/tooluse.transformer.ts","./src/transformer/vercel.transformer.ts","./src/transformer/vertex-claude.transformer.ts","./src/transformer/vertex-gemini.transformer.ts","./src/types/llm.ts","./src/types/tokenizer.d.ts","./src/types/transformer.ts","./src/utils/cache.ts","./src/utils/converter.ts","./src/utils/gemini.util.ts","./src/utils/image.ts","./src/utils/request.ts","./src/utils/router.ts","./src/utils/thinking.ts","./src/utils/toolargumentsparser.ts","./src/utils/vertex-claude.util.ts","./src/utils/sse/sseparser.transform.ts","./src/utils/sse/sseserializer.transform.ts","./src/utils/sse/index.ts","./src/utils/sse/rewritestream.ts"],"errors":true,"version":"5.9.3"}