dsh-subscriptions 0.2.3 → 0.3.1
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 +9 -21
- package/lib/auth/rpc.d.ts +2 -20
- package/lib/auth/rpc.js +3 -49
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +86 -110
- package/lib/client/ProviderModelEditor.d.ts +1 -1
- package/lib/client/ProviderModelEditor.js +2 -6
- package/lib/client/SubscriptionUsageBadge.d.ts +8 -2
- package/lib/client/SubscriptionUsageBadge.js +24 -17
- package/lib/client/SubscriptionsSection.js +1 -2
- package/lib/client/locales.d.ts +2 -32
- package/lib/client/locales.js +2 -32
- package/lib/client/model-filter.js +1 -1
- package/lib/client/subscription-card-styles.d.ts +1 -1
- package/lib/client/subscription-card-styles.js +1 -1
- package/lib/client.js +234 -518
- package/lib/client.js.map +1 -1
- package/lib/index.js +208 -3305
- package/lib/providers/claude.d.ts +3 -1
- package/lib/providers/claude.js +2 -2
- package/lib/providers/ollama-settings.d.ts +1 -1
- package/lib/providers/ollama-settings.js +1 -1
- package/lib/providers/pool-usage.d.ts +10 -9
- package/lib/providers/pool-usage.js +30 -19
- package/lib/providers/pool.d.ts +6 -6
- package/lib/providers/pool.js +24 -12
- package/package.json +1 -7
- package/lib/client/CursorCard.d.ts +0 -9
- package/lib/client/CursorCard.js +0 -151
- package/lib/providers/cursor-adapter.d.ts +0 -50
- package/lib/providers/cursor-adapter.js +0 -304
- package/lib/providers/cursor-auth.d.ts +0 -43
- package/lib/providers/cursor-auth.js +0 -221
- package/lib/providers/cursor-model-settings.d.ts +0 -15
- package/lib/providers/cursor-model-settings.js +0 -60
- package/lib/providers/cursor-usage.d.ts +0 -9
- package/lib/providers/cursor-usage.js +0 -131
- package/vendor/cursor/LICENSE +0 -21
- package/vendor/cursor/index.d.ts +0 -18
- package/vendor/cursor/index.js +0 -3574
- package/vendor/cursor/proto.js +0 -254
|
@@ -172,7 +172,9 @@ export declare const CLAUDE_REQUEST_IMAGE_BUDGET: number;
|
|
|
172
172
|
* five-minute mark can still read an entry a one-hour mark wrote.
|
|
173
173
|
* @returns the JSON body to POST.
|
|
174
174
|
*/
|
|
175
|
-
export declare function claudeRequestBody(options: GenerateOptions,
|
|
175
|
+
export declare function claudeRequestBody(options: Omit<GenerateOptions, 'purpose'> & {
|
|
176
|
+
purpose?: string;
|
|
177
|
+
}, messages: readonly TranslatableMessage[], maxTokens: number, thinking?: Record<string, unknown>, effort?: string, cacheTtl?: PromptCacheTtl): Record<string, unknown>;
|
|
176
178
|
/** Claude wire adapter: one instance serves the `claude` provider route. */
|
|
177
179
|
export declare class ClaudeAdapter extends LlmAdapter {
|
|
178
180
|
private readonly options;
|
package/lib/providers/claude.js
CHANGED
|
@@ -580,8 +580,8 @@ const CLAUDE_MODALITIES = ['text', 'image'];
|
|
|
580
580
|
* @returns the JSON body to POST.
|
|
581
581
|
*/
|
|
582
582
|
export function claudeRequestBody(options, messages, maxTokens, thinking, effort, cacheTtl) {
|
|
583
|
-
//
|
|
584
|
-
//
|
|
583
|
+
// Auxiliary callers own this extension even when a host's GenerateOptions
|
|
584
|
+
// omits or reserves `purpose`; ordinary turns retain the configured TTL.
|
|
585
585
|
const purpose = options.purpose;
|
|
586
586
|
const ttl = purpose === undefined ? cacheTtl : undefined;
|
|
587
587
|
const anthropicMessages = toAnthropicMessages(messages);
|
|
@@ -59,7 +59,7 @@ export interface OllamaSettings {
|
|
|
59
59
|
export declare function validateOllamaBaseURL(value: unknown): string;
|
|
60
60
|
/**
|
|
61
61
|
* Validate one settings document, dropping unknown keys and duplicate model
|
|
62
|
-
* ids (first wins
|
|
62
|
+
* ids (first wins).
|
|
63
63
|
* @param input - the parsed store document or an RPC payload.
|
|
64
64
|
* @throws when a known field has the wrong type or an unusable value.
|
|
65
65
|
*/
|
|
@@ -106,7 +106,7 @@ function readModel(entry, index) {
|
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
108
|
* Validate one settings document, dropping unknown keys and duplicate model
|
|
109
|
-
* ids (first wins
|
|
109
|
+
* ids (first wins).
|
|
110
110
|
* @param input - the parsed store document or an RPC payload.
|
|
111
111
|
* @throws when a known field has the wrong type or an unusable value.
|
|
112
112
|
*/
|
|
@@ -19,8 +19,10 @@ export declare const QUOTA_FULL_PERCENT = 95;
|
|
|
19
19
|
export declare const USAGE_TTL_MS: number;
|
|
20
20
|
/** The scheduling view of one member's quota. */
|
|
21
21
|
export interface MemberQuota {
|
|
22
|
-
/**
|
|
22
|
+
/** Near-full gate: false at 95% or when the login is gone; may still serve as a fallback. */
|
|
23
23
|
available: boolean;
|
|
24
|
+
/** True at 100%: never spend this member's subscription quota or paid fallback. */
|
|
25
|
+
exhausted: boolean;
|
|
24
26
|
/** Required burn rate (fraction of window per ms); 0 when unknown. */
|
|
25
27
|
urgency: number;
|
|
26
28
|
/** Epoch ms of the snapshot this was computed from; 0 when none. */
|
|
@@ -51,15 +53,12 @@ export declare class PoolUsageTracker {
|
|
|
51
53
|
* (member selection must never block on the network mid-conversation). A
|
|
52
54
|
* failure still cooling down degrades immediately with no network call.
|
|
53
55
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `snapshotFor`'s stale-display concern (the Settings page, the composer
|
|
59
|
-
* badge) has no such downside — showing an old percentage beats showing
|
|
60
|
-
* nothing.
|
|
56
|
+
* A failed refresh does not reuse stale urgency for routing order, but it
|
|
57
|
+
* does retain a stale snapshot's known exhaustion bit. That one-way safety
|
|
58
|
+
* guard prevents a failed Settings refresh from re-enabling paid-credit
|
|
59
|
+
* fallback; an account with no successful usage sample still fails open.
|
|
61
60
|
* @param member - the pool member to score (account resolved).
|
|
62
|
-
* @returns availability
|
|
61
|
+
* @returns availability, known exhaustion, and the urgency score.
|
|
63
62
|
*/
|
|
64
63
|
quotaFor(member: ConcretePoolMember): Promise<MemberQuota>;
|
|
65
64
|
/**
|
|
@@ -93,6 +92,8 @@ export declare class PoolUsageTracker {
|
|
|
93
92
|
* waiting out a stale cooldown.
|
|
94
93
|
*/
|
|
95
94
|
private refresh;
|
|
95
|
+
/** Degrade ordering while retaining only a cached safety exhaustion bit. */
|
|
96
|
+
private degraded;
|
|
96
97
|
/** Score one member against a snapshot's windows. */
|
|
97
98
|
private score;
|
|
98
99
|
}
|
|
@@ -49,21 +49,18 @@ export class PoolUsageTracker {
|
|
|
49
49
|
* (member selection must never block on the network mid-conversation). A
|
|
50
50
|
* failure still cooling down degrades immediately with no network call.
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* `snapshotFor`'s stale-display concern (the Settings page, the composer
|
|
57
|
-
* badge) has no such downside — showing an old percentage beats showing
|
|
58
|
-
* nothing.
|
|
52
|
+
* A failed refresh does not reuse stale urgency for routing order, but it
|
|
53
|
+
* does retain a stale snapshot's known exhaustion bit. That one-way safety
|
|
54
|
+
* guard prevents a failed Settings refresh from re-enabling paid-credit
|
|
55
|
+
* fallback; an account with no successful usage sample still fails open.
|
|
59
56
|
* @param member - the pool member to score (account resolved).
|
|
60
|
-
* @returns availability
|
|
57
|
+
* @returns availability, known exhaustion, and the urgency score.
|
|
61
58
|
*/
|
|
62
59
|
async quotaFor(member) {
|
|
63
60
|
const key = `${member.provider}/${member.account}`;
|
|
64
61
|
const fetcher = this.fetcherFor(member.provider, member.account);
|
|
65
62
|
if (fetcher === undefined)
|
|
66
|
-
return { available: true, urgency: 0, fetchedAt: 0 };
|
|
63
|
+
return { available: true, exhausted: false, urgency: 0, fetchedAt: 0 };
|
|
67
64
|
const entry = this.entries.get(key);
|
|
68
65
|
if (entry !== undefined) {
|
|
69
66
|
const fresh = Date.now() - entry.at < (entry.cooldownMs ?? this.ttlMs);
|
|
@@ -73,7 +70,7 @@ export class PoolUsageTracker {
|
|
|
73
70
|
return this.score(member, entry);
|
|
74
71
|
}
|
|
75
72
|
if (fresh)
|
|
76
|
-
return
|
|
73
|
+
return this.degraded(member, entry.error, entry.lastSnapshot);
|
|
77
74
|
// The cooldown expired: fall through to a fresh, blocking attempt.
|
|
78
75
|
}
|
|
79
76
|
try {
|
|
@@ -81,7 +78,8 @@ export class PoolUsageTracker {
|
|
|
81
78
|
return this.score(member, { snapshot, at: Date.now() });
|
|
82
79
|
}
|
|
83
80
|
catch (error) {
|
|
84
|
-
|
|
81
|
+
const failed = this.entries.get(key);
|
|
82
|
+
return this.degraded(member, error, failed?.snapshot ?? failed?.lastSnapshot);
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
/**
|
|
@@ -214,29 +212,42 @@ export class PoolUsageTracker {
|
|
|
214
212
|
}
|
|
215
213
|
return pending;
|
|
216
214
|
}
|
|
215
|
+
/** Degrade ordering while retaining only a cached safety exhaustion bit. */
|
|
216
|
+
degraded(member, error, snapshot) {
|
|
217
|
+
const cached = snapshot === undefined
|
|
218
|
+
? undefined
|
|
219
|
+
: this.score(member, { snapshot, at: 0 });
|
|
220
|
+
return degradedQuota(error, cached?.exhausted === true);
|
|
221
|
+
}
|
|
217
222
|
/** Score one member against a snapshot's windows. */
|
|
218
223
|
score(member, entry) {
|
|
219
224
|
const windows = (entry.snapshot.windows ?? []).filter((window) => windowApplies(window, member.model));
|
|
220
225
|
let available = true;
|
|
226
|
+
let exhausted = false;
|
|
221
227
|
let urgency = 0;
|
|
222
228
|
for (const window of windows) {
|
|
223
229
|
if (window.usedPercent >= QUOTA_FULL_PERCENT)
|
|
224
230
|
available = false;
|
|
231
|
+
if (window.usedPercent >= 100)
|
|
232
|
+
exhausted = true;
|
|
225
233
|
urgency = Math.max(urgency, windowUrgency(window));
|
|
226
234
|
}
|
|
227
|
-
return { available, urgency, fetchedAt: entry.at };
|
|
235
|
+
return { available, exhausted, urgency, fetchedAt: entry.at };
|
|
228
236
|
}
|
|
229
237
|
}
|
|
230
238
|
/**
|
|
231
239
|
* The routing view of a fetch failure. Logged out: the member cannot serve
|
|
232
|
-
* at all. Any other failure (network, endpoint rate limit)
|
|
233
|
-
*
|
|
234
|
-
*
|
|
240
|
+
* at all. Any other failure (network, endpoint rate limit) has a zero urgency;
|
|
241
|
+
* without a successful sample it stays available, while known exhaustion
|
|
242
|
+
* remains unavailable so stale telemetry cannot re-enable paid fallback.
|
|
235
243
|
*/
|
|
236
|
-
function degradedQuota(error) {
|
|
237
|
-
return
|
|
238
|
-
|
|
239
|
-
|
|
244
|
+
function degradedQuota(error, exhausted = false) {
|
|
245
|
+
return {
|
|
246
|
+
available: !isMissingOrInvalidCredential(error) && !exhausted,
|
|
247
|
+
exhausted,
|
|
248
|
+
urgency: 0,
|
|
249
|
+
fetchedAt: 0,
|
|
250
|
+
};
|
|
240
251
|
}
|
|
241
252
|
/** How long to hold a failure in the negative cache: the endpoint's own `retry-after`, or the default TTL. */
|
|
242
253
|
function cooldownFor(error, defaultTtlMs) {
|
package/lib/providers/pool.d.ts
CHANGED
|
@@ -95,12 +95,12 @@ export declare class PoolAdapter extends LlmAdapter {
|
|
|
95
95
|
*/
|
|
96
96
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
97
97
|
/**
|
|
98
|
-
* Order the candidates for one request. Health
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* `switchMargin`.
|
|
98
|
+
* Order the candidates for one request. Health and known exhaustion filter
|
|
99
|
+
* both strategies: an applicable window at 100% must never become a paid
|
|
100
|
+
* fallback. `quota_aware` then ranks by urgency (members without telemetry,
|
|
101
|
+
* e.g. copilot, score zero and sink to the bottom of their class), while
|
|
102
|
+
* near-full members stay as a last-resort tail in urgency order. The sticky
|
|
103
|
+
* member keeps its lead unless a challenger out-scores it by `switchMargin`.
|
|
104
104
|
*/
|
|
105
105
|
private select;
|
|
106
106
|
/** Pin the serving member to the session (with bounded memory). */
|
package/lib/providers/pool.js
CHANGED
|
@@ -287,31 +287,43 @@ export class PoolAdapter extends LlmAdapter {
|
|
|
287
287
|
throw this.exhaustedWithWarn(options.model, members, failures);
|
|
288
288
|
}
|
|
289
289
|
/**
|
|
290
|
-
* Order the candidates for one request. Health
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* `switchMargin`.
|
|
290
|
+
* Order the candidates for one request. Health and known exhaustion filter
|
|
291
|
+
* both strategies: an applicable window at 100% must never become a paid
|
|
292
|
+
* fallback. `quota_aware` then ranks by urgency (members without telemetry,
|
|
293
|
+
* e.g. copilot, score zero and sink to the bottom of their class), while
|
|
294
|
+
* near-full members stay as a last-resort tail in urgency order. The sticky
|
|
295
|
+
* member keeps its lead unless a challenger out-scores it by `switchMargin`.
|
|
296
296
|
*/
|
|
297
297
|
async select(poolId, members, sessionId) {
|
|
298
298
|
const usable = members.filter((member) => this.options.adapters[member.provider] !== undefined &&
|
|
299
299
|
this.options.health.isMemberAvailable(member.provider, member.account, member.model));
|
|
300
300
|
if (usable.length === 0)
|
|
301
301
|
return [];
|
|
302
|
+
const quotas = new Map(await Promise.all(usable.map(async (member) => [member, await this.options.usage.quotaFor(member)])));
|
|
303
|
+
const candidates = usable.filter((member) => quotas.get(member)?.exhausted !== true);
|
|
304
|
+
if (candidates.length === 0) {
|
|
305
|
+
const message = `${poolId}: all currently usable accounts have exhausted subscription quota; ` +
|
|
306
|
+
'the request was stopped before spending paid credits. After an external reset, ' +
|
|
307
|
+
'use Settings > Subscriptions > Refresh to re-check usage.';
|
|
308
|
+
this.warnOnce(message);
|
|
309
|
+
throw new LlmError(message, QUOTA_EXCEEDED_CODE);
|
|
310
|
+
}
|
|
302
311
|
const stickyMember = sessionId === undefined
|
|
303
312
|
? undefined
|
|
304
|
-
:
|
|
313
|
+
: candidates.find((member) => memberKey(member.provider, member.account, member.model) ===
|
|
305
314
|
this.sticky.get(stickyKey(poolId, sessionId)));
|
|
306
315
|
if (this.options.strategy === 'priority') {
|
|
307
316
|
return stickyMember === undefined
|
|
308
|
-
?
|
|
309
|
-
: [
|
|
317
|
+
? candidates
|
|
318
|
+
: [
|
|
319
|
+
stickyMember,
|
|
320
|
+
...candidates.filter((member) => member !== stickyMember),
|
|
321
|
+
];
|
|
310
322
|
}
|
|
311
|
-
const
|
|
312
|
-
const
|
|
313
|
-
const quotaFull = usable.filter((member) => quotas.get(member)?.available === false);
|
|
323
|
+
const scored = candidates.filter((member) => quotas.get(member)?.available === true);
|
|
324
|
+
const quotaFull = candidates.filter((member) => quotas.get(member)?.available === false);
|
|
314
325
|
scored.sort((a, b) => (quotas.get(b)?.urgency ?? 0) - (quotas.get(a)?.urgency ?? 0));
|
|
326
|
+
quotaFull.sort((a, b) => (quotas.get(b)?.urgency ?? 0) - (quotas.get(a)?.urgency ?? 0));
|
|
315
327
|
if (stickyMember !== undefined && scored.includes(stickyMember)) {
|
|
316
328
|
const best = scored[0];
|
|
317
329
|
const stickyUrgency = quotas.get(stickyMember)?.urgency ?? 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-subscriptions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Unified subscription providers and usage dashboard for DeepSeek Harness",
|
|
5
5
|
"icon": "./icon.svg",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"grok",
|
|
25
25
|
"github-copilot",
|
|
26
26
|
"google-antigravity",
|
|
27
|
-
"cursor",
|
|
28
27
|
"opencode-go",
|
|
29
28
|
"kimi-code"
|
|
30
29
|
],
|
|
@@ -40,16 +39,11 @@
|
|
|
40
39
|
"types": "./lib/client/index.d.ts",
|
|
41
40
|
"default": "./lib/client.js"
|
|
42
41
|
},
|
|
43
|
-
"./cursor-transport": {
|
|
44
|
-
"types": "./vendor/cursor/index.d.ts",
|
|
45
|
-
"default": "./vendor/cursor/index.js"
|
|
46
|
-
},
|
|
47
42
|
"./package.json": "./package.json",
|
|
48
43
|
"./locale/*.json": "./locale/*.json"
|
|
49
44
|
},
|
|
50
45
|
"files": [
|
|
51
46
|
"lib",
|
|
52
|
-
"vendor/cursor",
|
|
53
47
|
"cordis.patch.yml",
|
|
54
48
|
"locale/*.json",
|
|
55
49
|
"icon.svg"
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
|
|
2
|
-
import type { SubscriptionsSectionInjected } from './SubscriptionsSection.js';
|
|
3
|
-
type Translate = SubscriptionsSectionInjected['t'];
|
|
4
|
-
/** Cursor's single connected account, rendered with the same card and Manage dialog as the other providers. */
|
|
5
|
-
export declare function CursorCard({ rpc, t, }: {
|
|
6
|
-
rpc: ConnectionHandle['rpc'];
|
|
7
|
-
t: Translate;
|
|
8
|
-
}): import("react").JSX.Element;
|
|
9
|
-
export {};
|
package/lib/client/CursorCard.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { callSubscriptionsAuth } from './subscriptions-rpc.js';
|
|
4
|
-
import { ProviderAccountManager } from './ProviderAccountManager.js';
|
|
5
|
-
import { USAGE_BADGE_REFRESH_EVENT } from './usage-badge-preferences.js';
|
|
6
|
-
import { subscriptionCardStyles as styles } from './subscription-card-styles.js';
|
|
7
|
-
import { UsageMeter } from './UsageMeter.js';
|
|
8
|
-
import { displayUsedPercent } from './usage-pace.js';
|
|
9
|
-
function message(error) {
|
|
10
|
-
return error instanceof Error ? error.message : String(error);
|
|
11
|
-
}
|
|
12
|
-
/** Cursor's single connected account, rendered with the same card and Manage dialog as the other providers. */
|
|
13
|
-
export function CursorCard({ rpc, t, }) {
|
|
14
|
-
const [status, setStatus] = useState();
|
|
15
|
-
const [usage, setUsage] = useState();
|
|
16
|
-
const [observedAt, setObservedAt] = useState();
|
|
17
|
-
const [usageStale, setUsageStale] = useState(false);
|
|
18
|
-
const [authorizeUrl, setAuthorizeUrl] = useState();
|
|
19
|
-
const [error, setError] = useState();
|
|
20
|
-
const [loading, setLoading] = useState(false);
|
|
21
|
-
const [manage, setManage] = useState(false);
|
|
22
|
-
const authenticated = useRef();
|
|
23
|
-
const refreshStatus = useCallback(async () => {
|
|
24
|
-
try {
|
|
25
|
-
const value = await callSubscriptionsAuth(rpc, 'cursorStatus', {});
|
|
26
|
-
setStatus(value);
|
|
27
|
-
if (authenticated.current !== value.authenticated) {
|
|
28
|
-
authenticated.current = value.authenticated;
|
|
29
|
-
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
30
|
-
}
|
|
31
|
-
if (value.authenticated)
|
|
32
|
-
setAuthorizeUrl(undefined);
|
|
33
|
-
if (value.error)
|
|
34
|
-
setError(value.error);
|
|
35
|
-
}
|
|
36
|
-
catch (failure) {
|
|
37
|
-
setError(message(failure));
|
|
38
|
-
}
|
|
39
|
-
}, [rpc]);
|
|
40
|
-
const refreshUsage = useCallback(async () => {
|
|
41
|
-
setLoading(true);
|
|
42
|
-
try {
|
|
43
|
-
const value = await callSubscriptionsAuth(rpc, 'cursorUsage', {});
|
|
44
|
-
setUsage(value);
|
|
45
|
-
// Prefer the server's observation time; a cached reading must not look fresh.
|
|
46
|
-
setObservedAt(value.observedAt ?? Date.now());
|
|
47
|
-
setUsageStale(value.stale === true);
|
|
48
|
-
setError(undefined);
|
|
49
|
-
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
50
|
-
}
|
|
51
|
-
catch (failure) {
|
|
52
|
-
setError(message(failure));
|
|
53
|
-
setUsageStale(true);
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
setLoading(false);
|
|
57
|
-
}
|
|
58
|
-
}, [rpc]);
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
void refreshStatus();
|
|
61
|
-
}, [refreshStatus]);
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
if (status?.busy !== true)
|
|
64
|
-
return;
|
|
65
|
-
const timer = setInterval(() => {
|
|
66
|
-
void refreshStatus();
|
|
67
|
-
}, 2000);
|
|
68
|
-
return () => clearInterval(timer);
|
|
69
|
-
}, [status?.busy, refreshStatus]);
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
if (status?.authenticated === true)
|
|
72
|
-
void refreshUsage();
|
|
73
|
-
}, [status?.authenticated, refreshUsage]);
|
|
74
|
-
const login = async () => {
|
|
75
|
-
setError(undefined);
|
|
76
|
-
try {
|
|
77
|
-
const value = await callSubscriptionsAuth(rpc, 'cursorLogin', {});
|
|
78
|
-
setAuthorizeUrl(value.authorizeUrl);
|
|
79
|
-
window.open(value.authorizeUrl, '_blank', 'noopener');
|
|
80
|
-
await refreshStatus();
|
|
81
|
-
}
|
|
82
|
-
catch (failure) {
|
|
83
|
-
setError(message(failure));
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
const cancel = async () => {
|
|
87
|
-
try {
|
|
88
|
-
await callSubscriptionsAuth(rpc, 'cursorCancel', {});
|
|
89
|
-
setAuthorizeUrl(undefined);
|
|
90
|
-
await refreshStatus();
|
|
91
|
-
}
|
|
92
|
-
catch (failure) {
|
|
93
|
-
setError(message(failure));
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
const logout = async () => {
|
|
97
|
-
if (!window.confirm(t('logoutAccountConfirm', { account: 'Cursor', provider: 'Cursor' })))
|
|
98
|
-
return;
|
|
99
|
-
try {
|
|
100
|
-
await callSubscriptionsAuth(rpc, 'cursorLogout', {});
|
|
101
|
-
setUsage(undefined);
|
|
102
|
-
setObservedAt(undefined);
|
|
103
|
-
setUsageStale(false);
|
|
104
|
-
await refreshStatus();
|
|
105
|
-
}
|
|
106
|
-
catch (failure) {
|
|
107
|
-
setError(message(failure));
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
const connected = status?.authenticated === true;
|
|
111
|
-
return (_jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.header, children: [_jsx("span", { style: {
|
|
112
|
-
...styles.dot,
|
|
113
|
-
background: status?.busy
|
|
114
|
-
? 'var(--dsw-alias-state-warn-label)'
|
|
115
|
-
: connected
|
|
116
|
-
? 'var(--dsw-alias-state-success-primary)'
|
|
117
|
-
: 'var(--dsw-alias-label-dimmed)',
|
|
118
|
-
} }), _jsx("span", { style: styles.name, children: t('cursorTitle') })] }), _jsx("p", { style: styles.status, children: status === undefined
|
|
119
|
-
? t('checking')
|
|
120
|
-
: status.busy
|
|
121
|
-
? t('loginInProgress')
|
|
122
|
-
: connected
|
|
123
|
-
? t('loggedInCount', { count: 1 })
|
|
124
|
-
: t('notLoggedIn') }), error !== undefined && _jsx("p", { style: styles.error, children: error }), connected && (_jsxs("div", { style: styles.account, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("span", { style: styles.defaultStar, title: t('defaultBadge'), children: "\u2605" }), _jsx("span", { style: styles.accountName, children: "Cursor" }), status.expiresAt !== undefined && (_jsx("span", { style: styles.status, children: t('accountExpires', {
|
|
125
|
-
date: new Date(status.expiresAt).toLocaleString(),
|
|
126
|
-
}) })), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: () => {
|
|
127
|
-
void logout();
|
|
128
|
-
}, children: t('logout') })] }), _jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && (_jsx("span", { style: styles.status, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: styles.usageRefresh, disabled: loading, onClick: () => {
|
|
129
|
-
void refreshUsage();
|
|
130
|
-
}, children: t('usageRefresh') })] }), loading && usage === undefined && (_jsx("p", { style: styles.status, children: t('usageLoading') })), usage?.windows?.length === 0 && (_jsx("p", { style: styles.status, children: t('usageEmpty') })), usage?.windows?.map((window, index) => {
|
|
131
|
-
const percent = displayUsedPercent(window.usedPercent);
|
|
132
|
-
const label = window.scope === 'Included'
|
|
133
|
-
? t('cursorIncluded')
|
|
134
|
-
: window.scope === 'Cursor Models'
|
|
135
|
-
? t('cursorModels')
|
|
136
|
-
: window.scope === 'Other Models'
|
|
137
|
-
? t('cursorOtherModels')
|
|
138
|
-
: window.scope === 'Included requests'
|
|
139
|
-
? t('cursorIncludedRequests')
|
|
140
|
-
: (window.scope ?? t('usageWindow'));
|
|
141
|
-
return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: label }), _jsxs("span", { children: [percent === undefined
|
|
142
|
-
? t('usageMeterInvalid')
|
|
143
|
-
: `${percent}%`, window.resetsAt === undefined
|
|
144
|
-
? ''
|
|
145
|
-
: ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx(UsageMeter, { window: window, t: t, observedAt: observedAt, stale: usageStale })] }, index));
|
|
146
|
-
})] })] })), _jsxs("div", { style: styles.actions, children: [status?.authenticated === false && !status.busy && (_jsx("button", { type: "button", style: styles.button, onClick: () => {
|
|
147
|
-
void login();
|
|
148
|
-
}, children: t('login') })), _jsx("button", { type: "button", style: styles.button, "aria-haspopup": "dialog", onClick: () => setManage(true), children: t('accountsManage') }), status?.busy === true && (_jsx("button", { type: "button", style: styles.button, onClick: () => {
|
|
149
|
-
void cancel();
|
|
150
|
-
}, children: t('cancel') }))] }), status?.busy === true && authorizeUrl !== undefined && (_jsx("a", { href: authorizeUrl, target: "_blank", rel: "noopener noreferrer", children: t('cursorOpenLogin') })), manage && (_jsx(ProviderAccountManager, { provider: "cursor-subscription", name: "Cursor", rpc: rpc, t: t, onClose: () => setManage(false) }))] }));
|
|
151
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
2
|
-
import { CursorAdapter } from 'dsh-subscriptions/cursor-transport';
|
|
3
|
-
/**
|
|
4
|
-
* The imported Cursor transport still reads tool results as user-role content
|
|
5
|
-
* blocks. DSH 0.1.7 carries them as first-class role=tool messages instead.
|
|
6
|
-
*/
|
|
7
|
-
export declare function projectCursorMessages(messages: GenerateOptions['messages']): unknown[];
|
|
8
|
-
/**
|
|
9
|
-
* Cursor adapter for the DSH host.
|
|
10
|
-
*
|
|
11
|
-
* Failure text contract: `finish/error` messages that originate from the
|
|
12
|
-
* vendor stream and can quote provider-controlled text (the response content
|
|
13
|
-
* type, trailers, end-stream errors, a frame-reader or other error whose message
|
|
14
|
-
* is not one of the literals below, and a transport-coded failure that is not a
|
|
15
|
-
* socket-layer error) are replaced by `<code> [provider response text omitted]`. Failures that are
|
|
16
|
-
* wholly local keep their own message and code:
|
|
17
|
-
*
|
|
18
|
-
* - anything the auth service threw (not signed in, sign-in expired, token
|
|
19
|
-
* refresh status), and a host attachment read failure;
|
|
20
|
-
* - the vendor's own request validation, by code (`UNSUPPORTED_OPTION`,
|
|
21
|
-
* `UNSUPPORTED_CONTENT`, `TOOL_LIMIT`);
|
|
22
|
-
* - the vendor's fixed-literal plain errors, by exact message: the HTTP status
|
|
23
|
-
* (`Cursor agent returned HTTP 429`), the response, idle and progress
|
|
24
|
-
* timeouts, a closed bridge or stream, an unreadable compressed frame, and
|
|
25
|
-
* an invalid Cursor setting;
|
|
26
|
-
* - a network error raised by the operating system (`ECONNREFUSED`,
|
|
27
|
-
* `ENOTFOUND`, ...), recognised from the thrown error's own code, not from
|
|
28
|
-
* the vendor's classification.
|
|
29
|
-
*
|
|
30
|
-
* Not a provider-origin class: a network error such as `ECONNREFUSED` is a
|
|
31
|
-
* local failure (no network, daemon down, DNS), and is kept. What is still
|
|
32
|
-
* replaced although it is probably local is a transport failure with no
|
|
33
|
-
* socket-layer errno: a TLS or certificate failure and an HTTP/2 protocol
|
|
34
|
-
* error. The vendor's `TRANSPORT` code cannot decide those, because
|
|
35
|
-
* `classifyCursorError` derives it from message text (`network`, `connection`,
|
|
36
|
-
* `socket`, `fetch`, `ECONN`, `http2`) and a provider's end-stream message or
|
|
37
|
-
* `grpc-message` can contain any of them; so the code is never trusted alone.
|
|
38
|
-
* Telling these apart would need a check on the thrown error object, as the
|
|
39
|
-
* errno check above does, extended to the TLS and HTTP/2 error codes. That is
|
|
40
|
-
* not done here and is not verified.
|
|
41
|
-
*/
|
|
42
|
-
export declare class CursorCompatAdapter extends CursorAdapter {
|
|
43
|
-
private readonly visibleModels;
|
|
44
|
-
private readonly localFailures;
|
|
45
|
-
constructor(options: ConstructorParameters<typeof CursorAdapter>[0] & {
|
|
46
|
-
visibleModels?: () => readonly string[] | undefined;
|
|
47
|
-
});
|
|
48
|
-
listModels(provider: string): Promise<import("@deepseek-ai/dsh-llm").LlmModelInfo[]>;
|
|
49
|
-
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
50
|
-
}
|