dsh-plugin-subscriptions 0.5.2 → 0.6.0
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 +79 -5
- package/README.zh.md +78 -4
- package/lib/auth/rpc.d.ts +64 -13
- package/lib/auth/rpc.js +75 -10
- package/lib/auth/store.d.ts +75 -17
- package/lib/auth/store.js +148 -27
- package/lib/client/ImageGenerateToolview.d.ts +1 -1
- package/lib/client/SpeedSelect.d.ts +25 -2
- package/lib/client/SpeedSelect.js +10 -6
- package/lib/client/SubscriptionsSection.d.ts +83 -3
- package/lib/client/SubscriptionsSection.js +411 -62
- package/lib/client/VideoGenerateToolview.d.ts +1 -1
- package/lib/client/index.d.ts +1 -9
- package/lib/client/index.js +7 -4
- package/lib/client/locales.d.ts +46 -10
- package/lib/client/locales.js +46 -10
- package/lib/client.js +703 -132
- package/lib/client.js.map +1 -1
- package/lib/compat.d.ts +36 -0
- package/lib/compat.js +20 -0
- package/lib/index.d.ts +26 -1
- package/lib/index.js +2377 -309
- package/lib/model-defaults.d.ts +23 -0
- package/lib/model-defaults.js +237 -0
- package/lib/providers/accounts.d.ts +102 -0
- package/lib/providers/accounts.js +123 -0
- package/lib/providers/claude.d.ts +46 -7
- package/lib/providers/claude.js +125 -34
- package/lib/providers/codex.d.ts +45 -3
- package/lib/providers/codex.js +152 -26
- package/lib/providers/common.d.ts +87 -6
- package/lib/providers/common.js +185 -22
- package/lib/providers/copilot.d.ts +32 -3
- package/lib/providers/copilot.js +111 -19
- package/lib/providers/grok.d.ts +45 -4
- package/lib/providers/grok.js +136 -20
- package/lib/providers/pool-family.d.ts +56 -0
- package/lib/providers/pool-family.js +45 -0
- package/lib/providers/pool-health.d.ts +74 -0
- package/lib/providers/pool-health.js +148 -0
- package/lib/providers/pool-usage.d.ts +78 -0
- package/lib/providers/pool-usage.js +185 -0
- package/lib/providers/pool.d.ts +107 -0
- package/lib/providers/pool.js +371 -0
- package/lib/providers/rate-limit.d.ts +192 -0
- package/lib/providers/rate-limit.js +338 -0
- package/lib/tools/image-generate.d.ts +3 -3
- package/lib/tools/image-generate.js +2 -1
- package/lib/tools/video-generate.d.ts +2 -2
- package/lib/tools/video-generate.js +2 -1
- package/lib/tools/x-search.d.ts +2 -2
- package/lib/tools/x-search.js +2 -1
- package/lib/translate/anthropic.js +5 -4
- package/lib/translate/chat-completions.js +5 -4
- package/lib/translate/responses.js +5 -4
- package/package.json +21 -21
- package/lib/providers/antigravity.d.ts +0 -90
- package/lib/providers/antigravity.js +0 -392
- package/lib/translate/antigravity.d.ts +0 -110
- package/lib/translate/antigravity.js +0 -303
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate-limit window handling shared by the subscription adapters.
|
|
3
|
+
*
|
|
4
|
+
* A subscription plan is rate-limit shaped by design — a five-hour session
|
|
5
|
+
* window, a weekly window, and on some plans a per-model weekly one — so a 429
|
|
6
|
+
* is not a dead end: the window reopens at a time the provider discloses. This
|
|
7
|
+
* module turns that disclosure into the `providerRetryAfterMs` the optional
|
|
8
|
+
* `@deepseek-ai/dsh-llm-retry` plugin waits out, and resolves the retry policy
|
|
9
|
+
* whose `maxDelayMs` decides how long a route is allowed to hold the turn.
|
|
10
|
+
*
|
|
11
|
+
* The wait itself is provider-independent: adapters own the policy, the retry
|
|
12
|
+
* plugin executes it. Only the extraction of the reset instant differs, so each
|
|
13
|
+
* adapter contributes one {@link RateLimitResetReader} built from the parsing
|
|
14
|
+
* primitives here.
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-plugin-subscriptions/providers/rate-limit
|
|
17
|
+
*/
|
|
18
|
+
import { resolveRetryPolicy } from '@deepseek-ai/dsh-llm';
|
|
19
|
+
/**
|
|
20
|
+
* Extra time added to every provider-disclosed wait. Absorbs clock skew
|
|
21
|
+
* between the harness and the provider, so a retry does not land a moment
|
|
22
|
+
* before the window actually reopens and burn an attempt on a second 429.
|
|
23
|
+
*/
|
|
24
|
+
const RESET_GRACE_MS = 2_000;
|
|
25
|
+
/** Shortest wait ever scheduled, including for a reset instant already in the past. */
|
|
26
|
+
const MIN_WAIT_MS = 1_000;
|
|
27
|
+
/** Below this a bare number is a delay in seconds rather than an epoch stamp. */
|
|
28
|
+
const EPOCH_SECONDS_FLOOR = 1_000_000_000;
|
|
29
|
+
/** At or above this a bare epoch stamp is already in milliseconds. */
|
|
30
|
+
const EPOCH_MILLIS_FLOOR = 1_000_000_000_000;
|
|
31
|
+
/** Node's maximum timer delay; a longer wait cannot be scheduled at all. */
|
|
32
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
33
|
+
/** Default ceiling on a rate-limit wait: six hours covers a five-hour session window with slack. */
|
|
34
|
+
export const DEFAULT_RATE_LIMIT_MAX_WAIT_MS = 6 * 60 * 60 * 1_000;
|
|
35
|
+
/**
|
|
36
|
+
* Interpret a bare numeric rate-limit value, which providers write in three
|
|
37
|
+
* shapes: epoch milliseconds, epoch seconds, or a delay in seconds. The
|
|
38
|
+
* magnitude separates them unambiguously for any plausible value — an epoch in
|
|
39
|
+
* seconds is ~1.8e9 today, while a delay of even a full week is ~6e5.
|
|
40
|
+
* @param value - the raw numeric value.
|
|
41
|
+
* @param now - the current epoch milliseconds.
|
|
42
|
+
* @returns epoch milliseconds of the reset, or undefined when the value is unusable.
|
|
43
|
+
*/
|
|
44
|
+
export function resetInstantFromNumber(value, now) {
|
|
45
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
46
|
+
return undefined;
|
|
47
|
+
if (value >= EPOCH_MILLIS_FLOOR)
|
|
48
|
+
return value;
|
|
49
|
+
if (value >= EPOCH_SECONDS_FLOOR)
|
|
50
|
+
return value * 1_000;
|
|
51
|
+
// Provider body fields in this helper's allowlists are contracted as
|
|
52
|
+
// seconds. A provider that sends milliseconds here (for example,
|
|
53
|
+
// `retry_after: 30000`) would be interpreted as 30,000 seconds (~8.3 h),
|
|
54
|
+
// so such a field must be normalized by its provider reader first.
|
|
55
|
+
return now + value * 1_000;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse a Go-style duration (`6m0s`, `1h2m3.5s`, `150ms`) into milliseconds —
|
|
59
|
+
* the form OpenAI-compatible `x-ratelimit-reset-*` headers use.
|
|
60
|
+
* @param text - the raw header value.
|
|
61
|
+
* @returns the duration in milliseconds, or undefined when the text is not one.
|
|
62
|
+
*/
|
|
63
|
+
export function durationMs(text) {
|
|
64
|
+
const trimmed = text.trim();
|
|
65
|
+
if (trimmed.length === 0)
|
|
66
|
+
return undefined;
|
|
67
|
+
// Sticky: every component must abut the previous one, so trailing or
|
|
68
|
+
// interleaved junk ("6m0s later") fails the length check below.
|
|
69
|
+
const pattern = /(\d+(?:\.\d+)?)(ms|h|m|s)/y;
|
|
70
|
+
const units = { h: 3_600_000, m: 60_000, s: 1_000, ms: 1 };
|
|
71
|
+
let total = 0;
|
|
72
|
+
let matched = false;
|
|
73
|
+
// A failed sticky exec resets `lastIndex` to zero, so the reached offset is
|
|
74
|
+
// tracked separately rather than read back off the regex after the loop.
|
|
75
|
+
let index = 0;
|
|
76
|
+
// Components run strictly coarse to fine, the only order Go writes them in;
|
|
77
|
+
// a repeated or out-of-order unit ("1s2h", "1s1s") is not a duration and
|
|
78
|
+
// must not be silently summed into one.
|
|
79
|
+
let previousUnit = Number.POSITIVE_INFINITY;
|
|
80
|
+
for (;;) {
|
|
81
|
+
pattern.lastIndex = index;
|
|
82
|
+
const match = pattern.exec(trimmed);
|
|
83
|
+
if (match === null)
|
|
84
|
+
break;
|
|
85
|
+
const unit = units[match[2]];
|
|
86
|
+
if (unit >= previousUnit)
|
|
87
|
+
return undefined;
|
|
88
|
+
previousUnit = unit;
|
|
89
|
+
total += Number(match[1]) * unit;
|
|
90
|
+
index = pattern.lastIndex;
|
|
91
|
+
matched = true;
|
|
92
|
+
}
|
|
93
|
+
if (!matched || index !== trimmed.length)
|
|
94
|
+
return undefined;
|
|
95
|
+
// A zero duration ("0s") is not a disclosed reset — it is a bucket that has
|
|
96
|
+
// already rolled over — and reporting it as one would short-circuit the real
|
|
97
|
+
// signal behind it with a wait of `now`. The numeric path agrees:
|
|
98
|
+
// {@link resetInstantFromNumber} rejects zero too.
|
|
99
|
+
return total > 0 ? total : undefined;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Interpret any single rate-limit value — a number, a numeric string, a
|
|
103
|
+
* duration (`6m0s`), or a date — as the instant a window reopens. One reader
|
|
104
|
+
* for every shape, so a provider that changes the encoding of a field it
|
|
105
|
+
* already sends does not need a code change here.
|
|
106
|
+
* @param value - the raw header value or JSON field.
|
|
107
|
+
* @param now - the current epoch milliseconds.
|
|
108
|
+
* @returns epoch milliseconds of the reset, or undefined when the value is unusable.
|
|
109
|
+
*/
|
|
110
|
+
export function resetInstantFromValue(value, now) {
|
|
111
|
+
if (typeof value === 'number')
|
|
112
|
+
return resetInstantFromNumber(value, now);
|
|
113
|
+
if (typeof value !== 'string')
|
|
114
|
+
return undefined;
|
|
115
|
+
const trimmed = value.trim();
|
|
116
|
+
if (trimmed.length === 0)
|
|
117
|
+
return undefined;
|
|
118
|
+
const numeric = Number(trimmed);
|
|
119
|
+
if (Number.isFinite(numeric))
|
|
120
|
+
return resetInstantFromNumber(numeric, now);
|
|
121
|
+
const duration = durationMs(trimmed);
|
|
122
|
+
if (duration !== undefined)
|
|
123
|
+
return now + duration;
|
|
124
|
+
const parsed = Date.parse(trimmed);
|
|
125
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Read a header carrying any of the {@link resetInstantFromValue} shapes.
|
|
129
|
+
* @param response - the failed response.
|
|
130
|
+
* @param name - the header to read.
|
|
131
|
+
* @param now - the current epoch milliseconds.
|
|
132
|
+
* @returns epoch milliseconds of the reset, or undefined when absent or unusable.
|
|
133
|
+
*/
|
|
134
|
+
export function resetInstantFromHeader(response, name, now) {
|
|
135
|
+
return resetInstantFromValue(response.headers.get(name), now);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Read the RFC 7231 `retry-after` header in both its forms: a delay in seconds
|
|
139
|
+
* (never an epoch stamp, whatever its magnitude) or an HTTP-date.
|
|
140
|
+
* @param response - the failed response.
|
|
141
|
+
* @param now - the current epoch milliseconds.
|
|
142
|
+
* @returns epoch milliseconds of the reset, or undefined when absent or unusable.
|
|
143
|
+
*/
|
|
144
|
+
export function retryAfterInstant(response, now) {
|
|
145
|
+
const raw = response.headers.get('retry-after');
|
|
146
|
+
if (raw === null)
|
|
147
|
+
return undefined;
|
|
148
|
+
const trimmed = raw.trim();
|
|
149
|
+
if (trimmed.length === 0)
|
|
150
|
+
return undefined;
|
|
151
|
+
const seconds = Number(trimmed);
|
|
152
|
+
if (Number.isFinite(seconds))
|
|
153
|
+
return seconds > 0 ? now + seconds * 1_000 : undefined;
|
|
154
|
+
const parsed = Date.parse(trimmed);
|
|
155
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Parse a response body as JSON without throwing on the non-JSON bodies
|
|
159
|
+
* providers occasionally return under load (an HTML gateway page, say).
|
|
160
|
+
* @param body - the complete response body.
|
|
161
|
+
* @returns the parsed value, or undefined when the body is not JSON.
|
|
162
|
+
*/
|
|
163
|
+
export function jsonBody(body) {
|
|
164
|
+
if (body.length === 0)
|
|
165
|
+
return undefined;
|
|
166
|
+
try {
|
|
167
|
+
return JSON.parse(body);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Only swallow body parsing: header-derived signals still apply.
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** How deep {@link resetFromFields} walks; every observed payload nests one or two levels. */
|
|
175
|
+
const MAX_BODY_DEPTH = 4;
|
|
176
|
+
/**
|
|
177
|
+
* Find a reset instant under any of the named keys, anywhere in a parsed body.
|
|
178
|
+
*
|
|
179
|
+
* The search is by key rather than by path on purpose: providers move the same
|
|
180
|
+
* field between containers (`detail`, `error`, top level) across endpoints and
|
|
181
|
+
* versions, and a path-shaped reader silently stops working when they do. Only
|
|
182
|
+
* the key list is provider-specific.
|
|
183
|
+
* @param value - the parsed body, or any nested value.
|
|
184
|
+
* @param keys - field names this provider uses for a reset or delay.
|
|
185
|
+
* @param now - the current epoch milliseconds.
|
|
186
|
+
* @param depth - remaining recursion depth.
|
|
187
|
+
* @returns the earliest instant found, or undefined when no key matched.
|
|
188
|
+
*/
|
|
189
|
+
export function resetFromFields(value, keys, now, depth = MAX_BODY_DEPTH) {
|
|
190
|
+
if (depth <= 0 || value === null || typeof value !== 'object')
|
|
191
|
+
return undefined;
|
|
192
|
+
let earliest;
|
|
193
|
+
const consider = (candidate) => {
|
|
194
|
+
if (candidate !== undefined && (earliest === undefined || candidate < earliest))
|
|
195
|
+
earliest = candidate;
|
|
196
|
+
};
|
|
197
|
+
if (Array.isArray(value)) {
|
|
198
|
+
for (const item of value)
|
|
199
|
+
consider(resetFromFields(item, keys, now, depth - 1));
|
|
200
|
+
return earliest;
|
|
201
|
+
}
|
|
202
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
203
|
+
if (keys.includes(key))
|
|
204
|
+
consider(resetInstantFromValue(nested, now));
|
|
205
|
+
else
|
|
206
|
+
consider(resetFromFields(nested, keys, now, depth - 1));
|
|
207
|
+
}
|
|
208
|
+
return earliest;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The earliest of several candidate reset instants, ignoring absent ones. The
|
|
212
|
+
* earliest is the one that matters: it is the first moment any of the reported
|
|
213
|
+
* limits allows a request again.
|
|
214
|
+
* @param candidates - reset instants in no particular order.
|
|
215
|
+
* @returns the earliest instant, or undefined when every candidate is absent.
|
|
216
|
+
*/
|
|
217
|
+
export function earliestReset(...candidates) {
|
|
218
|
+
let earliest;
|
|
219
|
+
for (const candidate of candidates) {
|
|
220
|
+
if (candidate === undefined)
|
|
221
|
+
continue;
|
|
222
|
+
if (earliest === undefined || candidate < earliest)
|
|
223
|
+
earliest = candidate;
|
|
224
|
+
}
|
|
225
|
+
return earliest;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Turn a reset instant into the wait to report as `providerRetryAfterMs`.
|
|
229
|
+
*
|
|
230
|
+
* Deliberately not capped: a reset beyond the policy's `maxDelayMs` makes the
|
|
231
|
+
* retry plugin delegate immediately, failing the turn at once with the real
|
|
232
|
+
* reset in the message, rather than clamping the wait down and burning the
|
|
233
|
+
* retry budget against a window that is still closed.
|
|
234
|
+
* @param instant - epoch milliseconds the window reopens.
|
|
235
|
+
* @param now - the current epoch milliseconds.
|
|
236
|
+
* @returns the wait in milliseconds, never below {@link MIN_WAIT_MS}.
|
|
237
|
+
*/
|
|
238
|
+
export function waitFromReset(instant, now) {
|
|
239
|
+
return Math.max(MIN_WAIT_MS, instant - now + RESET_GRACE_MS);
|
|
240
|
+
}
|
|
241
|
+
/** Header names worth showing when a 429 disclosed no reset this code recognizes. */
|
|
242
|
+
const DIAGNOSTIC_HEADER = /rate-?limit|retry|reset|^x-codex-/i;
|
|
243
|
+
/**
|
|
244
|
+
* Render the rate-limit-shaped headers and the head of the body of a 429 whose
|
|
245
|
+
* reset instant nothing parsed. Emitted through the adapter's `onWarn`, this is
|
|
246
|
+
* how an unrecognized provider field gets named from live traffic instead of
|
|
247
|
+
* being guessed at.
|
|
248
|
+
*
|
|
249
|
+
* It is also where the per-bucket rollover snapshots land by design — no reader
|
|
250
|
+
* parks a turn on one, because on a 429 they cannot say which bucket refused —
|
|
251
|
+
* so the operator still sees what the provider disclosed.
|
|
252
|
+
* @param response - the failed response.
|
|
253
|
+
* @param body - the complete response body.
|
|
254
|
+
* @returns a one-line diagnostic.
|
|
255
|
+
*/
|
|
256
|
+
export function rateLimitDiagnostics(response, body) {
|
|
257
|
+
const headers = [];
|
|
258
|
+
response.headers.forEach((value, key) => {
|
|
259
|
+
if (DIAGNOSTIC_HEADER.test(key))
|
|
260
|
+
headers.push(`${key}: ${value}`);
|
|
261
|
+
});
|
|
262
|
+
headers.sort();
|
|
263
|
+
const rendered = headers.length > 0 ? headers.join('; ') : '(none)';
|
|
264
|
+
const head = body.slice(0, 200);
|
|
265
|
+
return `429 disclosed no reset time; headers [${rendered}]; body ${head.length > 0 ? head : '(empty)'}`;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The retry shape every subscription route starts from: Claude Code's own SDK
|
|
269
|
+
* numbers — ten retries after the first attempt, exponential backoff from 1s
|
|
270
|
+
* doubling per attempt, capped at 60s, plus 20% jitter.
|
|
271
|
+
*
|
|
272
|
+
* Shared across all four routes rather than kept to claude, because what these
|
|
273
|
+
* numbers are tuned for is the shape of a subscription endpoint — a consumer
|
|
274
|
+
* plan behind a session window, which sheds load in bursts and rewards an
|
|
275
|
+
* attempt that outlasts them — and that is the same on all four. The dsh-llm
|
|
276
|
+
* defaults (5 retries from 500ms to 10s) give up after about fifteen seconds,
|
|
277
|
+
* which is short for that.
|
|
278
|
+
*
|
|
279
|
+
* The 60s cap governs local backoff only: a disclosed rate-limit reset is
|
|
280
|
+
* accepted up to the configured wait ceiling instead.
|
|
281
|
+
*/
|
|
282
|
+
export const DEFAULT_RETRY = Object.freeze({
|
|
283
|
+
maxRetries: 10,
|
|
284
|
+
initialDelayMs: 1_000,
|
|
285
|
+
maxDelayMs: 60_000,
|
|
286
|
+
jitterRatio: 0.2,
|
|
287
|
+
});
|
|
288
|
+
/** Waiting behavior a route falls back to when the plugin passed none (waiting on, six-hour ceiling). */
|
|
289
|
+
export const DEFAULT_RATE_LIMIT_WAIT = Object.freeze({
|
|
290
|
+
wait: true,
|
|
291
|
+
maxWaitMs: DEFAULT_RATE_LIMIT_MAX_WAIT_MS,
|
|
292
|
+
});
|
|
293
|
+
/**
|
|
294
|
+
* Validate and default the rate-limit waiting config.
|
|
295
|
+
* @param config - the raw plugin config section, when present.
|
|
296
|
+
* @param path - diagnostic path naming the config that owns the value.
|
|
297
|
+
* @returns the resolved, immutable behavior.
|
|
298
|
+
*/
|
|
299
|
+
export function resolveRateLimitWait(config, path) {
|
|
300
|
+
const wait = config?.wait ?? true;
|
|
301
|
+
const maxWaitMs = config?.maxWaitMs ?? DEFAULT_RATE_LIMIT_MAX_WAIT_MS;
|
|
302
|
+
if (!Number.isFinite(maxWaitMs) || maxWaitMs <= 0) {
|
|
303
|
+
throw new Error(`${path}.maxWaitMs must be a positive finite number of milliseconds`);
|
|
304
|
+
}
|
|
305
|
+
if (maxWaitMs > MAX_TIMER_DELAY_MS) {
|
|
306
|
+
throw new Error(`${path}.maxWaitMs must be no greater than ${String(MAX_TIMER_DELAY_MS)} (the maximum schedulable delay)`);
|
|
307
|
+
}
|
|
308
|
+
return Object.freeze({ wait, maxWaitMs });
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Resolve one route's retry policy, widening the delay ceiling to the
|
|
312
|
+
* configured wait so a disclosed reset hours out is accepted rather than
|
|
313
|
+
* refused.
|
|
314
|
+
*
|
|
315
|
+
* The ceiling is shared with local exponential backoff, so widening it also
|
|
316
|
+
* raises how long an unrelated transient failure may back off for. That stays
|
|
317
|
+
* bounded by the finite retry budget — the claude route's ten retries reach
|
|
318
|
+
* 512 s per attempt at most — and it only governs when the provider disclosed
|
|
319
|
+
* nothing, which is exactly the case where a longer wait is the safer guess.
|
|
320
|
+
* @param defaults - the route's retry shape.
|
|
321
|
+
* @param rateLimit - resolved waiting behavior.
|
|
322
|
+
* @param path - diagnostic path naming the provider route.
|
|
323
|
+
* @returns the policy to report from `providerRetryPolicy`.
|
|
324
|
+
*/
|
|
325
|
+
export function subscriptionRetryPolicy(defaults, rateLimit, path) {
|
|
326
|
+
const maxDelayMs = rateLimit.wait
|
|
327
|
+
? Math.max(defaults.maxDelayMs, rateLimit.maxWaitMs)
|
|
328
|
+
: defaults.maxDelayMs;
|
|
329
|
+
return resolveRetryPolicy({
|
|
330
|
+
mode: 'normal',
|
|
331
|
+
maxRetries: defaults.maxRetries,
|
|
332
|
+
backoff: {
|
|
333
|
+
initialDelayMs: defaults.initialDelayMs,
|
|
334
|
+
maxDelayMs,
|
|
335
|
+
jitterRatio: defaults.jitterRatio,
|
|
336
|
+
},
|
|
337
|
+
}, path);
|
|
338
|
+
}
|
|
@@ -16,7 +16,7 @@ import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
|
16
16
|
import type { LlmRuntime } from '@deepseek-ai/dsh-llm';
|
|
17
17
|
import type { ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
18
18
|
import type { CodexSession, GrokSession } from '../auth/store.js';
|
|
19
|
-
import {
|
|
19
|
+
import { AccountTokenManager } from '../providers/accounts.js';
|
|
20
20
|
import type { FetchFn } from '../providers/common.js';
|
|
21
21
|
/** Endpoint the codex generation request is posted to. */
|
|
22
22
|
export declare const IMAGE_GENERATE_URL = "https://chatgpt.com/backend-api/codex/images/generations";
|
|
@@ -29,9 +29,9 @@ export declare const GROK_IMAGE_GENERATE_MODEL = "grok-imagine-image-2.0";
|
|
|
29
29
|
/** Dependencies of the `image_generate` tool. */
|
|
30
30
|
export interface ImageGenerateToolOptions {
|
|
31
31
|
/** Codex session source; the default preferred provider (`provider: 'gpt'`). */
|
|
32
|
-
codexTokens?:
|
|
32
|
+
codexTokens?: AccountTokenManager<CodexSession>;
|
|
33
33
|
/** Grok session source; preferred when the call passes `provider: 'grok'`. */
|
|
34
|
-
grokTokens?:
|
|
34
|
+
grokTokens?: AccountTokenManager<GrokSession>;
|
|
35
35
|
/** Fetch implementation (injectable for tests). */
|
|
36
36
|
fetchFn?: FetchFn;
|
|
37
37
|
/** Directory override for saved images (defaults under the harness home). */
|
|
@@ -17,7 +17,8 @@ import { basename, join } from 'node:path';
|
|
|
17
17
|
import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
|
|
18
18
|
import { AttachmentId } from '@deepseek-ai/dsh-attachment';
|
|
19
19
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
20
|
-
import { httpLlmError
|
|
20
|
+
import { httpLlmError } from '../providers/common.js';
|
|
21
|
+
import { AccountTokenManager } from '../providers/accounts.js';
|
|
21
22
|
import { proxiedFetch } from '../http.js';
|
|
22
23
|
/** Endpoint the codex generation request is posted to. */
|
|
23
24
|
export const IMAGE_GENERATE_URL = 'https://chatgpt.com/backend-api/codex/images/generations';
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
12
12
|
import type { GrokSession } from '../auth/store.js';
|
|
13
|
-
import {
|
|
13
|
+
import { AccountTokenManager } from '../providers/accounts.js';
|
|
14
14
|
import type { FetchFn } from '../providers/common.js';
|
|
15
15
|
/** Endpoint the generation request is posted to. */
|
|
16
16
|
export declare const VIDEO_GENERATE_URL = "https://api.x.ai/v1/videos/generations";
|
|
@@ -25,7 +25,7 @@ export declare const DEFAULT_MAX_WAIT_MS: number;
|
|
|
25
25
|
/** Dependencies of the `video_generate` tool. */
|
|
26
26
|
export interface VideoGenerateToolOptions {
|
|
27
27
|
/** Grok session source; a missing session throws the log-in hint. */
|
|
28
|
-
tokens:
|
|
28
|
+
tokens: AccountTokenManager<GrokSession>;
|
|
29
29
|
/** Fetch implementation (injectable for tests). */
|
|
30
30
|
fetchFn?: FetchFn;
|
|
31
31
|
/** Directory override for saved videos (defaults under the harness home). */
|
|
@@ -12,7 +12,8 @@ import { mkdir, writeFile } from 'node:fs/promises';
|
|
|
12
12
|
import { basename, join } from 'node:path';
|
|
13
13
|
import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
|
|
14
14
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
15
|
-
import { httpLlmError
|
|
15
|
+
import { httpLlmError } from '../providers/common.js';
|
|
16
|
+
import { AccountTokenManager } from '../providers/accounts.js';
|
|
16
17
|
import { proxiedFetch } from '../http.js';
|
|
17
18
|
/** Endpoint the generation request is posted to. */
|
|
18
19
|
export const VIDEO_GENERATE_URL = 'https://api.x.ai/v1/videos/generations';
|
package/lib/tools/x-search.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
8
8
|
import type { GrokSession } from '../auth/store.js';
|
|
9
|
-
import {
|
|
9
|
+
import { AccountTokenManager } from '../providers/accounts.js';
|
|
10
10
|
import type { FetchFn } from '../providers/common.js';
|
|
11
11
|
/** Endpoint the search request is posted to. */
|
|
12
12
|
export declare const X_SEARCH_URL = "https://api.x.ai/v1/responses";
|
|
@@ -15,7 +15,7 @@ export declare const X_SEARCH_MODEL = "grok-4";
|
|
|
15
15
|
/** Dependencies of the `x_search` tool. */
|
|
16
16
|
export interface XSearchToolOptions {
|
|
17
17
|
/** Grok session source; a missing session throws the log-in hint. */
|
|
18
|
-
tokens:
|
|
18
|
+
tokens: AccountTokenManager<GrokSession>;
|
|
19
19
|
/** Fetch implementation (injectable for tests). */
|
|
20
20
|
fetchFn?: FetchFn;
|
|
21
21
|
}
|
package/lib/tools/x-search.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* output is `{ answer, citations }`.
|
|
6
6
|
*/
|
|
7
7
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
|
-
import { httpLlmError
|
|
8
|
+
import { httpLlmError } from '../providers/common.js';
|
|
9
|
+
import { AccountTokenManager } from '../providers/accounts.js';
|
|
9
10
|
import { proxiedFetch } from '../http.js';
|
|
10
11
|
/** Endpoint the search request is posted to. */
|
|
11
12
|
export const X_SEARCH_URL = 'https://api.x.ai/v1/responses';
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* schema mapping, and a push-model SSE-event → StreamChunk state machine
|
|
5
5
|
* ({@link AnthropicStreamTranslator}) so tests need no streams.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmError, } from '@deepseek-ai/dsh-llm';
|
|
8
|
+
import { ToolCallId } from '../compat.js';
|
|
8
9
|
import { parseSse } from './sse.js';
|
|
9
10
|
/**
|
|
10
11
|
* The Claude Code identity block. The subscription endpoint rejects requests
|
|
@@ -252,7 +253,7 @@ function closeBlock(block) {
|
|
|
252
253
|
case 'tool-call':
|
|
253
254
|
return {
|
|
254
255
|
type: 'tool-call',
|
|
255
|
-
id:
|
|
256
|
+
id: ToolCallId(block.callId),
|
|
256
257
|
name: block.name ?? '',
|
|
257
258
|
arguments: block.text,
|
|
258
259
|
};
|
|
@@ -362,7 +363,7 @@ export class AnthropicStreamTranslator {
|
|
|
362
363
|
chunks.push({
|
|
363
364
|
type: 'tool-call-delta',
|
|
364
365
|
index: opened.index,
|
|
365
|
-
id:
|
|
366
|
+
id: ToolCallId(opened.callId),
|
|
366
367
|
...block.name === undefined ? {} : { name: block.name },
|
|
367
368
|
argumentsDelta: '',
|
|
368
369
|
});
|
|
@@ -393,7 +394,7 @@ export class AnthropicStreamTranslator {
|
|
|
393
394
|
chunks.push({
|
|
394
395
|
type: 'tool-call-delta',
|
|
395
396
|
index: block.index,
|
|
396
|
-
id:
|
|
397
|
+
id: ToolCallId(block.callId),
|
|
397
398
|
...block.name === undefined ? {} : { name: block.name },
|
|
398
399
|
argumentsDelta: delta.partial_json ?? '',
|
|
399
400
|
});
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* ({@link ChatCompletionsStreamTranslator}) mirroring the Responses
|
|
6
6
|
* translator, so tests need no streams.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm';
|
|
9
|
+
import { ToolCallId } from '../compat.js';
|
|
9
10
|
import { parseSse } from './sse.js';
|
|
10
11
|
/** Flatten a tool result's content to plain text for a `tool` message. */
|
|
11
12
|
function toolResultText(block) {
|
|
@@ -153,7 +154,7 @@ function closeBlock(block) {
|
|
|
153
154
|
case 'tool-call':
|
|
154
155
|
return {
|
|
155
156
|
type: 'tool-call',
|
|
156
|
-
id:
|
|
157
|
+
id: ToolCallId(block.callId),
|
|
157
158
|
name: block.name ?? '',
|
|
158
159
|
arguments: block.text,
|
|
159
160
|
};
|
|
@@ -285,7 +286,7 @@ export class ChatCompletionsStreamTranslator {
|
|
|
285
286
|
chunks.push({
|
|
286
287
|
type: 'tool-call-delta',
|
|
287
288
|
index: block.index,
|
|
288
|
-
id:
|
|
289
|
+
id: ToolCallId(block.callId),
|
|
289
290
|
...block.name === undefined ? {} : { name: block.name },
|
|
290
291
|
argumentsDelta: '',
|
|
291
292
|
});
|
|
@@ -295,7 +296,7 @@ export class ChatCompletionsStreamTranslator {
|
|
|
295
296
|
chunks.push({
|
|
296
297
|
type: 'tool-call-delta',
|
|
297
298
|
index: block.index,
|
|
298
|
-
id:
|
|
299
|
+
id: ToolCallId(block.callId),
|
|
299
300
|
argumentsDelta: call.function.arguments,
|
|
300
301
|
});
|
|
301
302
|
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* assembly, tool schema mapping, and a push-model SSE-event → StreamChunk
|
|
5
5
|
* state machine ({@link ResponsesStreamTranslator}) so tests need no streams.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE, } from '@deepseek-ai/dsh-llm';
|
|
8
|
+
import { ToolCallId } from '../compat.js';
|
|
8
9
|
import { parseSse } from './sse.js';
|
|
9
10
|
/** Flatten a tool result's content to plain text for `function_call_output`. */
|
|
10
11
|
function toolResultText(block) {
|
|
@@ -165,7 +166,7 @@ function closeBlock(block) {
|
|
|
165
166
|
case 'tool-call':
|
|
166
167
|
return {
|
|
167
168
|
type: 'tool-call',
|
|
168
|
-
id:
|
|
169
|
+
id: ToolCallId(block.callId),
|
|
169
170
|
name: block.name ?? '',
|
|
170
171
|
arguments: block.text,
|
|
171
172
|
};
|
|
@@ -251,7 +252,7 @@ export class ResponsesStreamTranslator {
|
|
|
251
252
|
chunks.push({
|
|
252
253
|
type: 'tool-call-delta',
|
|
253
254
|
index: block.index,
|
|
254
|
-
id:
|
|
255
|
+
id: ToolCallId(callId),
|
|
255
256
|
...item.name === undefined ? {} : { name: item.name },
|
|
256
257
|
argumentsDelta: '',
|
|
257
258
|
});
|
|
@@ -286,7 +287,7 @@ export class ResponsesStreamTranslator {
|
|
|
286
287
|
chunks.push({
|
|
287
288
|
type: 'tool-call-delta',
|
|
288
289
|
index: block.index,
|
|
289
|
-
id:
|
|
290
|
+
id: ToolCallId(block.callId),
|
|
290
291
|
...block.name === undefined ? {} : { name: block.name },
|
|
291
292
|
argumentsDelta: event.delta ?? '',
|
|
292
293
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-subscriptions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Use ChatGPT (Codex), Claude, Grok (X Premium), and GitHub Copilot subscriptions as DeepSeek Harness LLM providers, with OAuth login from the web Settings page",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"client": {
|
|
44
44
|
"platform": "web",
|
|
45
45
|
"inject": [
|
|
46
|
-
"@deepseek-ai/dsh-client-
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
47
47
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
48
48
|
"@deepseek-ai/dsh-client-locale"
|
|
49
49
|
]
|
|
@@ -57,28 +57,28 @@
|
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
60
|
-
"@deepseek-ai/dsh-attachment": "^0.1.1-rc.2",
|
|
61
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.1-rc.2",
|
|
62
|
-
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
63
|
-
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-attachment": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
61
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
62
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
63
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
64
64
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@deepseek-ai/cordis": "
|
|
68
|
-
"@deepseek-ai/dsh-api-remotes": "
|
|
69
|
-
"@deepseek-ai/dsh-attachment": "
|
|
70
|
-
"@deepseek-ai/dsh-client-connection": "
|
|
71
|
-
"@deepseek-ai/dsh-client-locale": "
|
|
72
|
-
"@deepseek-ai/dsh-client-
|
|
73
|
-
"@deepseek-ai/dsh-client-ui-
|
|
74
|
-
"@deepseek-ai/dsh-client-ui-
|
|
75
|
-
"@deepseek-ai/dsh-client-ui-
|
|
76
|
-
"@deepseek-ai/dsh-client-ui-
|
|
77
|
-
"@deepseek-ai/dsh-
|
|
78
|
-
"@deepseek-ai/dsh-
|
|
79
|
-
"@deepseek-ai/dsh-llm": "
|
|
80
|
-
"@deepseek-ai/dsh-tools": "
|
|
81
|
-
"@deepseek-ai/schemastery": "
|
|
67
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
68
|
+
"@deepseek-ai/dsh-api-remotes": "0.1.2-alpha.3",
|
|
69
|
+
"@deepseek-ai/dsh-attachment": "0.1.2-alpha.3",
|
|
70
|
+
"@deepseek-ai/dsh-client-connection": "0.1.2-alpha.3",
|
|
71
|
+
"@deepseek-ai/dsh-client-locale": "0.1.2-alpha.3",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.2-alpha.3",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.2-alpha.3",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-commands": "0.1.2-alpha.3",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-alpha.3",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.2-alpha.3",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.2-alpha.3",
|
|
78
|
+
"@deepseek-ai/dsh-home-paths": "0.1.2-alpha.3",
|
|
79
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.3",
|
|
80
|
+
"@deepseek-ai/dsh-tools": "0.1.2-alpha.3",
|
|
81
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
82
82
|
"@types/node": "^24.0.0",
|
|
83
83
|
"@types/react": "~18.3.1",
|
|
84
84
|
"react": "^18.2.0",
|