@x1a0f3n9/dsh-failover-queue 0.1.11
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/LICENSE +21 -0
- package/README.md +64 -0
- package/README.zh.md +62 -0
- package/cordis.patch.yml +5 -0
- package/lib/client.js +2261 -0
- package/lib/client.js.map +1 -0
- package/lib/index.d.ts +274 -0
- package/lib/index.js +574 -0
- package/package.json +92 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
/** One provider+model slot in the failover queue. P1 is index 0. */
|
|
6
|
+
interface QueueRoute {
|
|
7
|
+
/** Adapter route key (`GenerateOptions.provider`). */
|
|
8
|
+
readonly provider: string;
|
|
9
|
+
/** Model id for that route. */
|
|
10
|
+
readonly model: string;
|
|
11
|
+
/** Optional display label; the chip falls back to `provider/model`. */
|
|
12
|
+
readonly label?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Closed = healthy, Open = skipped, HalfOpen = one probe. */
|
|
15
|
+
type CircuitState = 'closed' | 'open' | 'half_open';
|
|
16
|
+
/** Host-projected breaker badge for one queue route. Memory-only on the host. */
|
|
17
|
+
interface CircuitHealth {
|
|
18
|
+
readonly provider: string;
|
|
19
|
+
readonly model: string;
|
|
20
|
+
readonly state: CircuitState;
|
|
21
|
+
readonly failures: number;
|
|
22
|
+
}
|
|
23
|
+
/** Persisted failover document (settings namespace `dsh-failover-queue`). */
|
|
24
|
+
interface FailoverSettings {
|
|
25
|
+
/** When false, the plugin never overlays or retries across routes. */
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
/** Last picked queue index (P1 = 0). Clamped on read. Not a sticky pointer. */
|
|
28
|
+
currentIndex: number;
|
|
29
|
+
/** Ordered routes. Index 0 is P1. */
|
|
30
|
+
queue: QueueRoute[];
|
|
31
|
+
/** Live circuit badges. Host memory is the source of truth. */
|
|
32
|
+
circuits?: CircuitHealth[];
|
|
33
|
+
}
|
|
34
|
+
/** One advertised catalog row the panel can add. */
|
|
35
|
+
interface FailoverCandidate {
|
|
36
|
+
readonly provider: string;
|
|
37
|
+
readonly providerName: string;
|
|
38
|
+
readonly model: string;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/config.d.ts
|
|
43
|
+
/** Plugin config accepted from cordis.yml / the bundle patch. */
|
|
44
|
+
interface Config {
|
|
45
|
+
/** Milliseconds an Open circuit waits before a HalfOpen probe. Default 60000. */
|
|
46
|
+
cooldownMs?: number;
|
|
47
|
+
/** Consecutive failures that open a Closed breaker. Default 2. */
|
|
48
|
+
failureThreshold?: number;
|
|
49
|
+
/** Consecutive HalfOpen successes that close the breaker. Default 2. */
|
|
50
|
+
successThreshold?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Failure codes that skip remaining same-route retries, open the circuit
|
|
53
|
+
* immediately, and jump to the next P. Default `AUTH`, `RATE_LIMIT`, `NO_ADAPTER`.
|
|
54
|
+
*/
|
|
55
|
+
immediateCodes?: string[];
|
|
56
|
+
}
|
|
57
|
+
/** Config after schema defaults. */
|
|
58
|
+
interface ResolvedConfig {
|
|
59
|
+
cooldownMs: number;
|
|
60
|
+
failureThreshold: number;
|
|
61
|
+
successThreshold: number;
|
|
62
|
+
immediateCodes: readonly string[];
|
|
63
|
+
}
|
|
64
|
+
/** Runtime schema. */
|
|
65
|
+
declare const Config: z<Config>;
|
|
66
|
+
/**
|
|
67
|
+
* Apply schema defaults.
|
|
68
|
+
* @param config - raw plugin config, possibly partial.
|
|
69
|
+
*/
|
|
70
|
+
declare function resolveConfig(config?: Config): ResolvedConfig;
|
|
71
|
+
/** Settings namespace (hyphenated; settings forbids dots). */
|
|
72
|
+
declare const SETTINGS_NAMESPACE = "dsh-failover-queue";
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/queue.d.ts
|
|
75
|
+
/** Stable identity for cooldown and de-dupe. */
|
|
76
|
+
declare function routeKey(route: QueueRoute): string;
|
|
77
|
+
/**
|
|
78
|
+
* Clamp a queue index into `[0, length)` (or `0` when empty).
|
|
79
|
+
* @param index - requested index.
|
|
80
|
+
* @param length - queue length.
|
|
81
|
+
* @returns a usable index.
|
|
82
|
+
*/
|
|
83
|
+
declare function clampIndex(index: number, length: number): number;
|
|
84
|
+
/**
|
|
85
|
+
* Move one queue item from `from` to `to`.
|
|
86
|
+
* @param queue - current ordered routes.
|
|
87
|
+
* @param from - source index.
|
|
88
|
+
* @param to - destination index.
|
|
89
|
+
* @returns a new array.
|
|
90
|
+
*/
|
|
91
|
+
declare function reorderQueue(queue: readonly QueueRoute[], from: number, to: number): QueueRoute[];
|
|
92
|
+
/**
|
|
93
|
+
* Follow the same route after a drag, not the same slot.
|
|
94
|
+
* @param currentIndex - active index before the move.
|
|
95
|
+
* @param from - dragged index.
|
|
96
|
+
* @param to - drop index.
|
|
97
|
+
* @returns the index of the previously active route.
|
|
98
|
+
*/
|
|
99
|
+
declare function indexAfterReorder(currentIndex: number, from: number, to: number): number;
|
|
100
|
+
/**
|
|
101
|
+
* Next uncooled route after `currentIndex`, wrapping once.
|
|
102
|
+
* @param queue - ordered routes.
|
|
103
|
+
* @param currentIndex - failed slot.
|
|
104
|
+
* @param isCooled - true when this route should be skipped.
|
|
105
|
+
* @returns the next index, or `undefined` when nothing remains.
|
|
106
|
+
*/
|
|
107
|
+
declare function advanceIndex(queue: readonly QueueRoute[], currentIndex: number, isCooled: (route: QueueRoute) => boolean): number | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Drop duplicate provider+model pairs, keeping the first occurrence.
|
|
110
|
+
* @param queue - possibly messy user input.
|
|
111
|
+
*/
|
|
112
|
+
declare function dedupeQueue(queue: readonly QueueRoute[]): QueueRoute[];
|
|
113
|
+
/**
|
|
114
|
+
* Names the chip and queue rows show for one route.
|
|
115
|
+
* Prefers the live catalog's provider/model titles, then the stored label.
|
|
116
|
+
* @param route - queue slot.
|
|
117
|
+
* @param candidates - advertised catalog, possibly empty.
|
|
118
|
+
*/
|
|
119
|
+
declare function routeDisplay(route: QueueRoute, candidates?: readonly FailoverCandidate[]): {
|
|
120
|
+
provider: string;
|
|
121
|
+
model: string;
|
|
122
|
+
};
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/circuit.d.ts
|
|
125
|
+
/** Closed / Open / HalfOpen knobs. Chat-friendly defaults, CC Switch-shaped. */
|
|
126
|
+
interface CircuitConfig {
|
|
127
|
+
/** Consecutive failures that open a Closed breaker. */
|
|
128
|
+
readonly failureThreshold: number;
|
|
129
|
+
/** Consecutive HalfOpen successes that close the breaker. */
|
|
130
|
+
readonly successThreshold: number;
|
|
131
|
+
/** Milliseconds an Open breaker waits before becoming HalfOpen. */
|
|
132
|
+
readonly timeoutMs: number;
|
|
133
|
+
}
|
|
134
|
+
/** Result of taking a HalfOpen probe permit. */
|
|
135
|
+
interface ProbeDecision {
|
|
136
|
+
readonly allowed: boolean;
|
|
137
|
+
readonly halfOpen: boolean;
|
|
138
|
+
}
|
|
139
|
+
/** One queue pick: first Closed, else first HalfOpen. */
|
|
140
|
+
interface CircuitPick {
|
|
141
|
+
readonly index: number;
|
|
142
|
+
readonly route: QueueRoute;
|
|
143
|
+
readonly halfOpen: boolean;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* One route's Closed / Open / HalfOpen breaker.
|
|
147
|
+
*
|
|
148
|
+
* HalfOpen allows a single in-flight probe. Open becomes HalfOpen after
|
|
149
|
+
* `timeoutMs`. Immediate failures (AUTH / RATE_LIMIT / NO_ADAPTER) open on
|
|
150
|
+
* the first hit. Memory-only: a process restart starts Closed.
|
|
151
|
+
*/
|
|
152
|
+
declare class CircuitBreaker {
|
|
153
|
+
private readonly config;
|
|
154
|
+
private state;
|
|
155
|
+
private failures;
|
|
156
|
+
private successes;
|
|
157
|
+
private openedAt;
|
|
158
|
+
private halfOpenPermit;
|
|
159
|
+
/**
|
|
160
|
+
* @param config - thresholds and Open timeout.
|
|
161
|
+
*/
|
|
162
|
+
constructor(config: CircuitConfig);
|
|
163
|
+
/** Copy of counters for badges and tests. */
|
|
164
|
+
snapshot(): {
|
|
165
|
+
readonly state: CircuitState;
|
|
166
|
+
readonly failures: number;
|
|
167
|
+
readonly successes: number;
|
|
168
|
+
readonly openedAt: number | null;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Whether this route may be selected. Open → HalfOpen when the wait elapses.
|
|
172
|
+
* Does not consume the probe permit.
|
|
173
|
+
* @param now - epoch ms.
|
|
174
|
+
*/
|
|
175
|
+
isAvailable(now: number): boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Reserve this route for one request. HalfOpen consumes the single permit.
|
|
178
|
+
* @param now - epoch ms.
|
|
179
|
+
*/
|
|
180
|
+
allowProbe(now: number): ProbeDecision;
|
|
181
|
+
/** Probe succeeded. Two successes close a HalfOpen breaker. */
|
|
182
|
+
recordSuccess(): void;
|
|
183
|
+
/**
|
|
184
|
+
* Probe or Closed request failed.
|
|
185
|
+
* @param now - epoch ms used as Open timestamp.
|
|
186
|
+
* @param immediate - open on this hit regardless of `failureThreshold`.
|
|
187
|
+
*/
|
|
188
|
+
recordFailure(now: number, immediate?: boolean): void;
|
|
189
|
+
/** Drop the HalfOpen permit without changing health (cancel / abandon). */
|
|
190
|
+
releaseProbe(): void;
|
|
191
|
+
private maybeHalfOpen;
|
|
192
|
+
private transitionOpen;
|
|
193
|
+
private transitionClosed;
|
|
194
|
+
}
|
|
195
|
+
/** Per-route breaker map. Missing keys start Closed. */
|
|
196
|
+
declare class CircuitBank {
|
|
197
|
+
private readonly config;
|
|
198
|
+
private readonly breakers;
|
|
199
|
+
/**
|
|
200
|
+
* @param config - shared knobs for every route.
|
|
201
|
+
*/
|
|
202
|
+
constructor(config: CircuitConfig);
|
|
203
|
+
/**
|
|
204
|
+
* Breaker for one provider+model pair.
|
|
205
|
+
* @param key - {@link routeKey}.
|
|
206
|
+
*/
|
|
207
|
+
get(key: string): CircuitBreaker;
|
|
208
|
+
/**
|
|
209
|
+
* Badge rows for the live queue. Touches `isAvailable` so Open can show
|
|
210
|
+
* HalfOpen after the wait without consuming a permit.
|
|
211
|
+
* @param queue - ordered routes.
|
|
212
|
+
* @param now - epoch ms.
|
|
213
|
+
*/
|
|
214
|
+
health(queue: readonly QueueRoute[], now: number): CircuitHealth[];
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* First Closed route, else first HalfOpen with a free permit. Never sticky.
|
|
218
|
+
* @param queue - P1…Pn.
|
|
219
|
+
* @param bank - per-route breakers.
|
|
220
|
+
* @param now - epoch ms.
|
|
221
|
+
* @param skip - route key to ignore (the attempt that just failed).
|
|
222
|
+
*/
|
|
223
|
+
declare function pickFirstAvailable(queue: readonly QueueRoute[], bank: CircuitBank, now: number, skip?: string): CircuitPick | undefined;
|
|
224
|
+
/**
|
|
225
|
+
* Queue index of the first available route, without consuming a probe permit.
|
|
226
|
+
* @param queue - P1…Pn.
|
|
227
|
+
* @param bank - per-route breakers.
|
|
228
|
+
* @param now - epoch ms.
|
|
229
|
+
* @param skip - route key to ignore.
|
|
230
|
+
*/
|
|
231
|
+
declare function firstAvailableIndex(queue: readonly QueueRoute[], bank: CircuitBank, now: number, skip?: string): number | undefined;
|
|
232
|
+
/**
|
|
233
|
+
* Badge tone for one queue row.
|
|
234
|
+
* @param health - host snapshot for this route, if any.
|
|
235
|
+
*/
|
|
236
|
+
declare function circuitTone(health: CircuitHealth | undefined): 'ok' | 'probe' | 'open';
|
|
237
|
+
/**
|
|
238
|
+
* Match a queue route to a health row.
|
|
239
|
+
* @param route - queue slot.
|
|
240
|
+
* @param circuits - host snapshot.
|
|
241
|
+
*/
|
|
242
|
+
declare function healthFor(route: QueueRoute, circuits: readonly CircuitHealth[]): CircuitHealth | undefined;
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/command.d.ts
|
|
245
|
+
/** `/failover` verbs the host handler and tests share. */
|
|
246
|
+
type FailoverVerb = {
|
|
247
|
+
readonly kind: 'status';
|
|
248
|
+
} | {
|
|
249
|
+
readonly kind: 'on';
|
|
250
|
+
} | {
|
|
251
|
+
readonly kind: 'off';
|
|
252
|
+
} | {
|
|
253
|
+
readonly kind: 'candidates';
|
|
254
|
+
} | {
|
|
255
|
+
readonly kind: 'error';
|
|
256
|
+
readonly text: string;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* Parse `/failover` arguments.
|
|
260
|
+
* @param rawInput - text after the command name.
|
|
261
|
+
*/
|
|
262
|
+
declare function parseFailoverArg(rawInput: string): FailoverVerb;
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region src/index.d.ts
|
|
265
|
+
declare const name = "dsh-failover-queue";
|
|
266
|
+
declare const inject: string[];
|
|
267
|
+
/**
|
|
268
|
+
* Mount settings, `/failover`, and the request overlay.
|
|
269
|
+
* @param ctx - plugin context; requires `llm`.
|
|
270
|
+
* @param config - raw plugin config.
|
|
271
|
+
*/
|
|
272
|
+
declare function apply(ctx: Context, config?: Config): void;
|
|
273
|
+
//#endregion
|
|
274
|
+
export { CircuitBank, CircuitBreaker, type CircuitHealth, type CircuitState, Config, type Config as ConfigInput, type FailoverCandidate, type FailoverSettings, type QueueRoute, type ResolvedConfig, SETTINGS_NAMESPACE, advanceIndex, apply, circuitTone, clampIndex, dedupeQueue, firstAvailableIndex, healthFor, indexAfterReorder, inject, name, parseFailoverArg, pickFirstAvailable, reorderQueue, resolveConfig, routeDisplay, routeKey };
|