@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.
- package/README.md +2 -0
- package/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/api/empty-answer-guard.d.ts +66 -0
- package/dist/api/plugin-request-state.d.ts +22 -0
- package/dist/api/plugin-response-error.d.ts +5 -0
- package/dist/api/routes.d.ts +2 -0
- package/dist/ccr/models.d.ts +9 -1
- package/dist/ccr/plugin-install-routes.d.ts +79 -0
- package/dist/cjs/server.cjs +219 -214
- package/dist/cjs/server.cjs.map +4 -4
- package/dist/esm/server.mjs +221 -216
- package/dist/esm/server.mjs.map +4 -4
- package/dist/index.html +41 -41
- package/dist/provider-plugins/host.d.ts +11 -0
- package/dist/provider-plugins/loader.d.ts +33 -0
- package/dist/provider-plugins/registry.d.ts +39 -0
- package/dist/provider-plugins/transformer-hook.d.ts +3 -0
- package/dist/provider-plugins/types.d.ts +236 -0
- package/dist/server.d.ts +8 -0
- package/dist/services/active-probe.d.ts +46 -4
- package/dist/services/provider-health.d.ts +18 -1
- package/dist/services/provider.d.ts +2 -0
- package/dist/services/proxy.d.ts +19 -1
- package/dist/services/transformer.d.ts +2 -0
- package/dist/transformer/openai.responses.transformer.d.ts +0 -6
- package/dist/types/llm.d.ts +4 -0
- package/package.json +2 -2
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -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,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;
|
package/dist/services/proxy.d.ts
CHANGED
|
@@ -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.
|
package/dist/types/llm.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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"
|
package/tsconfig.tsbuildinfo
DELETED
|
@@ -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"}
|