dsh-subscriptions 0.2.3 → 0.3.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 +7 -19
- 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 +1 -1
- package/lib/client/SubscriptionUsageBadge.js +1 -13
- package/lib/client/SubscriptionsSection.js +1 -2
- package/lib/client/locales.d.ts +0 -32
- package/lib/client/locales.js +0 -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 +175 -489
- package/lib/client.js.map +1 -1
- package/lib/index.js +165 -3275
- package/lib/providers/ollama-settings.d.ts +1 -1
- package/lib/providers/ollama-settings.js +1 -1
- 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
|
@@ -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
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-subscriptions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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
|
-
}
|
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import { CursorAdapter } from 'dsh-subscriptions/cursor-transport';
|
|
2
|
-
/**
|
|
3
|
-
* The imported Cursor transport still reads tool results as user-role content
|
|
4
|
-
* blocks. DSH 0.1.7 carries them as first-class role=tool messages instead.
|
|
5
|
-
*/
|
|
6
|
-
export function projectCursorMessages(messages) {
|
|
7
|
-
return messages.map((message) => message.role === 'tool'
|
|
8
|
-
? {
|
|
9
|
-
...message,
|
|
10
|
-
role: 'user',
|
|
11
|
-
content: [
|
|
12
|
-
{
|
|
13
|
-
type: 'tool-result',
|
|
14
|
-
toolCallId: message.toolCallId,
|
|
15
|
-
isError: message.isError,
|
|
16
|
-
content: message.content,
|
|
17
|
-
},
|
|
18
|
-
],
|
|
19
|
-
}
|
|
20
|
-
: message);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Vendor `LlmError` codes whose whole message is a local literal (no response
|
|
24
|
-
* header, trailer, body or end-stream text). Provider-derived failures can only
|
|
25
|
-
* carry a `classifyCursorError` code or `CURSOR_PROTOCOL`, never these, so the
|
|
26
|
-
* code is a sound discriminator for them.
|
|
27
|
-
*/
|
|
28
|
-
const LOCAL_VENDOR_CODES = new Set([
|
|
29
|
-
'UNSUPPORTED_OPTION',
|
|
30
|
-
'UNSUPPORTED_CONTENT',
|
|
31
|
-
'TOOL_LIMIT',
|
|
32
|
-
]);
|
|
33
|
-
/**
|
|
34
|
-
* Plain-error messages the vendor throws as fixed literals (`vendor/cursor/
|
|
35
|
-
* index.js`: `waitForResponse`, the request write, the tool-continuation write,
|
|
36
|
-
* the stream `close` handler, the frame reader, the stall watchdog and the
|
|
37
|
-
* settings validation). None
|
|
38
|
-
* interpolates response text, so they are matched by exact message, the same way
|
|
39
|
-
* the auth messages are. A provider that echoed one of these sentences would
|
|
40
|
-
* only make the user read a local literal.
|
|
41
|
-
*/
|
|
42
|
-
const LOCAL_VENDOR_MESSAGES = new Set([
|
|
43
|
-
'Cursor HTTP response timeout',
|
|
44
|
-
'Cursor HTTP stream closed before response',
|
|
45
|
-
'Cursor agent bridge closed before accepting the request',
|
|
46
|
-
'Cursor tool continuation bridge closed before accepting the result',
|
|
47
|
-
'Cursor sent an unreadable compressed frame',
|
|
48
|
-
'Cursor stream idle timeout',
|
|
49
|
-
// `resolveCursorSettings`, run at the top of every stream.
|
|
50
|
-
'cursor-subscription: maxToolRounds must be an integer between 1 and 1000',
|
|
51
|
-
'cursor-subscription: retryCount must be an integer between 0 and 10',
|
|
52
|
-
'cursor-subscription: retryIntervalMs must be an integer between 0 and 300000',
|
|
53
|
-
'cursor-subscription: retryHttpStatusCodes must contain only HTTP status integers from 400 to 599',
|
|
54
|
-
'cursor-subscription: retryHttpStatusCodes must not contain duplicates',
|
|
55
|
-
'cursor-subscription: replayHistoryEachStep must be a boolean',
|
|
56
|
-
]);
|
|
57
|
-
/**
|
|
58
|
-
* The same, for the two literals that embed a number the vendor computes: the
|
|
59
|
-
* response status (`Number(headers[':status'])`, so digits or `NaN`) and the
|
|
60
|
-
* configured progress timeout.
|
|
61
|
-
*/
|
|
62
|
-
const LOCAL_VENDOR_PATTERNS = [
|
|
63
|
-
/^Cursor agent returned HTTP \d{3}$/,
|
|
64
|
-
/^Cursor stream progress timeout: no content for \d+ms$/,
|
|
65
|
-
];
|
|
66
|
-
/**
|
|
67
|
-
* Operating-system network error codes. An `Error` carrying one of these on
|
|
68
|
-
* itself, or on its `cause` (Node wraps a refused connection in
|
|
69
|
-
* `ERR_HTTP2_STREAM_CANCEL`), was produced by the local socket layer, so its
|
|
70
|
-
* message is local: `connect ECONNREFUSED 127.0.0.1:443`, `getaddrinfo ENOTFOUND
|
|
71
|
-
* host`. This is a property of the thrown object, which a provider cannot
|
|
72
|
-
* forge. It is deliberately not a test on the vendor's `TRANSPORT` code: the
|
|
73
|
-
* vendor derives that code from message text, so a provider's end-stream
|
|
74
|
-
* message or `grpc-message` containing "connection" or "socket" gets it too.
|
|
75
|
-
* Transport failures with no such code (TLS and certificate errors, HTTP/2
|
|
76
|
-
* protocol errors) are not recognised and stay replaced.
|
|
77
|
-
*/
|
|
78
|
-
const LOCAL_SYSTEM_ERRNO = new Set([
|
|
79
|
-
'ECONNREFUSED',
|
|
80
|
-
'ECONNRESET',
|
|
81
|
-
'ECONNABORTED',
|
|
82
|
-
'ENOTFOUND',
|
|
83
|
-
'EAI_AGAIN',
|
|
84
|
-
'ETIMEDOUT',
|
|
85
|
-
'ENETUNREACH',
|
|
86
|
-
'ENETDOWN',
|
|
87
|
-
'EHOSTUNREACH',
|
|
88
|
-
'EHOSTDOWN',
|
|
89
|
-
'EPIPE',
|
|
90
|
-
]);
|
|
91
|
-
function errorCode(value) {
|
|
92
|
-
return typeof value === 'object' && value !== null
|
|
93
|
-
? value.code
|
|
94
|
-
: undefined;
|
|
95
|
-
}
|
|
96
|
-
function isLocalSystemError(error) {
|
|
97
|
-
if (!(error instanceof Error))
|
|
98
|
-
return false;
|
|
99
|
-
const own = errorCode(error);
|
|
100
|
-
const cause = errorCode(error.cause);
|
|
101
|
-
return ((typeof own === 'string' && LOCAL_SYSTEM_ERRNO.has(own)) ||
|
|
102
|
-
(typeof cause === 'string' && LOCAL_SYSTEM_ERRNO.has(cause)));
|
|
103
|
-
}
|
|
104
|
-
function messageOf(error) {
|
|
105
|
-
return error instanceof Error ? error.message : String(error);
|
|
106
|
-
}
|
|
107
|
-
/** Bound on remembered local failure messages; they are short and few. */
|
|
108
|
-
const MAX_LOCAL_MESSAGES = 64;
|
|
109
|
-
/**
|
|
110
|
-
* Remembers the messages of failures raised by this machine rather than read
|
|
111
|
-
* from the provider: whatever the auth service threw, whatever the host
|
|
112
|
-
* attachment store threw while reading an image, and a socket-layer network
|
|
113
|
-
* error seen on the run. The vendor stream turns each of these into
|
|
114
|
-
* `finish/error` carrying exactly `error.message`, so a failure whose message
|
|
115
|
-
* equals a remembered one originated locally and may keep its text. The match
|
|
116
|
-
* is by exact text: a provider that echoed a remembered message would only
|
|
117
|
-
* display a local literal.
|
|
118
|
-
*/
|
|
119
|
-
class LocalFailureMessages {
|
|
120
|
-
messages = new Set();
|
|
121
|
-
/** Wrap `auth` so its failures are remembered and rethrown unchanged. */
|
|
122
|
-
wrap(auth) {
|
|
123
|
-
return {
|
|
124
|
-
accessToken: async (options) => {
|
|
125
|
-
try {
|
|
126
|
-
return await auth.accessToken(options);
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
this.remember(messageOf(error));
|
|
130
|
-
throw error;
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Wrap the host attachment store the vendor reads images through, so a read
|
|
137
|
-
* failure keeps its own message. The store is a local service; its errors
|
|
138
|
-
* carry no provider text. Anything that is not a store passes through.
|
|
139
|
-
*/
|
|
140
|
-
wrapAttachments(resolve) {
|
|
141
|
-
return () => {
|
|
142
|
-
const store = resolve();
|
|
143
|
-
const readImage = store
|
|
144
|
-
?.readImage;
|
|
145
|
-
if (typeof readImage !== 'function')
|
|
146
|
-
return store;
|
|
147
|
-
return Object.create(store, {
|
|
148
|
-
readImage: {
|
|
149
|
-
value: async (...args) => {
|
|
150
|
-
try {
|
|
151
|
-
return await readImage.apply(store, args);
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
this.remember(messageOf(error));
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Observe a vendor run's two failure channels, the response promise and the
|
|
164
|
-
* frame reader, and remember the message of an error the socket layer raised.
|
|
165
|
-
* The run's behavior is unchanged: the same error is rethrown.
|
|
166
|
-
*/
|
|
167
|
-
observeRun(run) {
|
|
168
|
-
if (typeof run !== 'object' || run === null)
|
|
169
|
-
return run;
|
|
170
|
-
const target = run;
|
|
171
|
-
const observe = (error) => {
|
|
172
|
-
if (isLocalSystemError(error))
|
|
173
|
-
this.remember(messageOf(error));
|
|
174
|
-
throw error;
|
|
175
|
-
};
|
|
176
|
-
const wait = target.waitForResponse;
|
|
177
|
-
if (typeof wait === 'function') {
|
|
178
|
-
target.waitForResponse = (...args) => Promise.resolve(wait.apply(target, args)).catch(observe);
|
|
179
|
-
}
|
|
180
|
-
const frames = target.frames;
|
|
181
|
-
const next = frames?.next;
|
|
182
|
-
if (frames !== undefined && typeof next === 'function') {
|
|
183
|
-
frames.next = (...args) => Promise.resolve(next.apply(frames, args)).catch(observe);
|
|
184
|
-
}
|
|
185
|
-
return run;
|
|
186
|
-
}
|
|
187
|
-
has(message) {
|
|
188
|
-
return this.messages.has(message);
|
|
189
|
-
}
|
|
190
|
-
remember(message) {
|
|
191
|
-
// An empty message displays nothing; remembering it would exempt every
|
|
192
|
-
// empty failure from the boundary for no benefit.
|
|
193
|
-
if (message === '')
|
|
194
|
-
return;
|
|
195
|
-
this.messages.delete(message);
|
|
196
|
-
this.messages.add(message);
|
|
197
|
-
if (this.messages.size > MAX_LOCAL_MESSAGES) {
|
|
198
|
-
const oldest = this.messages.values().next().value;
|
|
199
|
-
if (oldest !== undefined)
|
|
200
|
-
this.messages.delete(oldest);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function isVendorLocalMessage(message) {
|
|
205
|
-
return (LOCAL_VENDOR_MESSAGES.has(message) ||
|
|
206
|
-
LOCAL_VENDOR_PATTERNS.some((pattern) => pattern.test(message)));
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Cursor adapter for the DSH host.
|
|
210
|
-
*
|
|
211
|
-
* Failure text contract: `finish/error` messages that originate from the
|
|
212
|
-
* vendor stream and can quote provider-controlled text (the response content
|
|
213
|
-
* type, trailers, end-stream errors, a frame-reader or other error whose message
|
|
214
|
-
* is not one of the literals below, and a transport-coded failure that is not a
|
|
215
|
-
* socket-layer error) are replaced by `<code> [provider response text omitted]`. Failures that are
|
|
216
|
-
* wholly local keep their own message and code:
|
|
217
|
-
*
|
|
218
|
-
* - anything the auth service threw (not signed in, sign-in expired, token
|
|
219
|
-
* refresh status), and a host attachment read failure;
|
|
220
|
-
* - the vendor's own request validation, by code (`UNSUPPORTED_OPTION`,
|
|
221
|
-
* `UNSUPPORTED_CONTENT`, `TOOL_LIMIT`);
|
|
222
|
-
* - the vendor's fixed-literal plain errors, by exact message: the HTTP status
|
|
223
|
-
* (`Cursor agent returned HTTP 429`), the response, idle and progress
|
|
224
|
-
* timeouts, a closed bridge or stream, an unreadable compressed frame, and
|
|
225
|
-
* an invalid Cursor setting;
|
|
226
|
-
* - a network error raised by the operating system (`ECONNREFUSED`,
|
|
227
|
-
* `ENOTFOUND`, ...), recognised from the thrown error's own code, not from
|
|
228
|
-
* the vendor's classification.
|
|
229
|
-
*
|
|
230
|
-
* Not a provider-origin class: a network error such as `ECONNREFUSED` is a
|
|
231
|
-
* local failure (no network, daemon down, DNS), and is kept. What is still
|
|
232
|
-
* replaced although it is probably local is a transport failure with no
|
|
233
|
-
* socket-layer errno: a TLS or certificate failure and an HTTP/2 protocol
|
|
234
|
-
* error. The vendor's `TRANSPORT` code cannot decide those, because
|
|
235
|
-
* `classifyCursorError` derives it from message text (`network`, `connection`,
|
|
236
|
-
* `socket`, `fetch`, `ECONN`, `http2`) and a provider's end-stream message or
|
|
237
|
-
* `grpc-message` can contain any of them; so the code is never trusted alone.
|
|
238
|
-
* Telling these apart would need a check on the thrown error object, as the
|
|
239
|
-
* errno check above does, extended to the TLS and HTTP/2 error codes. That is
|
|
240
|
-
* not done here and is not verified.
|
|
241
|
-
*/
|
|
242
|
-
export class CursorCompatAdapter extends CursorAdapter {
|
|
243
|
-
visibleModels;
|
|
244
|
-
localFailures;
|
|
245
|
-
constructor(options) {
|
|
246
|
-
const localFailures = new LocalFailureMessages();
|
|
247
|
-
super({
|
|
248
|
-
...options,
|
|
249
|
-
auth: localFailures.wrap(options.auth),
|
|
250
|
-
...(options.resolveAttachments === undefined
|
|
251
|
-
? {}
|
|
252
|
-
: {
|
|
253
|
-
resolveAttachments: localFailures.wrapAttachments(options.resolveAttachments),
|
|
254
|
-
}),
|
|
255
|
-
});
|
|
256
|
-
this.localFailures = localFailures;
|
|
257
|
-
this.visibleModels = options.visibleModels;
|
|
258
|
-
// The vendor assigns `createAgentRun` (the injected one, or its own
|
|
259
|
-
// `AgentRun` factory) in its constructor but does not type it as a member.
|
|
260
|
-
const vendor = this;
|
|
261
|
-
const createRun = vendor.createAgentRun;
|
|
262
|
-
vendor.createAgentRun = (access) => localFailures.observeRun(createRun(access));
|
|
263
|
-
}
|
|
264
|
-
async listModels(provider) {
|
|
265
|
-
const models = await super.listModels(provider);
|
|
266
|
-
const visible = this.visibleModels?.();
|
|
267
|
-
return visible === undefined
|
|
268
|
-
? models
|
|
269
|
-
: models.filter((model) => visible.includes(model.id));
|
|
270
|
-
}
|
|
271
|
-
async *stream(options) {
|
|
272
|
-
for await (const chunk of super.stream({
|
|
273
|
-
...options,
|
|
274
|
-
// Existing DSH sessions may still hold this retired selection.
|
|
275
|
-
model: options.model === 'composer-2' ? 'composer-2.5' : options.model,
|
|
276
|
-
messages: projectCursorMessages(options.messages),
|
|
277
|
-
})) {
|
|
278
|
-
if (chunk.type === 'finish' &&
|
|
279
|
-
chunk.reason.kind === 'error' &&
|
|
280
|
-
!LOCAL_VENDOR_CODES.has(chunk.reason.failure.code) &&
|
|
281
|
-
!isVendorLocalMessage(chunk.reason.failure.message) &&
|
|
282
|
-
!this.localFailures.has(chunk.reason.failure.message)) {
|
|
283
|
-
// The vendor uses raw headers, trailers and end-stream errors for protocol
|
|
284
|
-
// checks and classification before this boundary. Keep its code and retry
|
|
285
|
-
// decisions; do not reclassify the replacement text. "Safe" here means no
|
|
286
|
-
// provider-controlled free text is displayed in failures, not that no
|
|
287
|
-
// secret can exist anywhere internally.
|
|
288
|
-
yield {
|
|
289
|
-
...chunk,
|
|
290
|
-
reason: {
|
|
291
|
-
...chunk.reason,
|
|
292
|
-
failure: {
|
|
293
|
-
...chunk.reason.failure,
|
|
294
|
-
message: `${chunk.reason.failure.code} [provider response text omitted]`,
|
|
295
|
-
},
|
|
296
|
-
},
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
yield chunk;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/** Cursor OAuth session and browser-login lifecycle. Credentials stay in DSH. */
|
|
2
|
-
export declare const CURSOR_CREDENTIAL_REF: import("@deepseek-ai/dsh-credentials").CredentialRef;
|
|
3
|
-
export interface CursorCredential {
|
|
4
|
-
type: 'oauth';
|
|
5
|
-
access: string;
|
|
6
|
-
refresh: string;
|
|
7
|
-
expires: number;
|
|
8
|
-
}
|
|
9
|
-
export interface CursorAuthStatus {
|
|
10
|
-
authenticated: boolean;
|
|
11
|
-
busy: boolean;
|
|
12
|
-
expiresAt?: number;
|
|
13
|
-
error?: string;
|
|
14
|
-
}
|
|
15
|
-
export interface CursorCredentialService {
|
|
16
|
-
resolve(ref: typeof CURSOR_CREDENTIAL_REF): Promise<{
|
|
17
|
-
value: string;
|
|
18
|
-
} | undefined>;
|
|
19
|
-
set(ref: typeof CURSOR_CREDENTIAL_REF, value: string): Promise<void>;
|
|
20
|
-
unset(ref: typeof CURSOR_CREDENTIAL_REF): Promise<void>;
|
|
21
|
-
}
|
|
22
|
-
export declare class CursorAuth {
|
|
23
|
-
private readonly credentials;
|
|
24
|
-
private readonly http;
|
|
25
|
-
private readonly now;
|
|
26
|
-
private loginTask;
|
|
27
|
-
private error;
|
|
28
|
-
private refreshTask;
|
|
29
|
-
constructor(credentials: CursorCredentialService, http?: typeof fetch, now?: () => number);
|
|
30
|
-
private read;
|
|
31
|
-
status(): Promise<CursorAuthStatus>;
|
|
32
|
-
accessToken({ signal, }?: {
|
|
33
|
-
signal?: AbortSignal;
|
|
34
|
-
}): Promise<string>;
|
|
35
|
-
private refresh;
|
|
36
|
-
/** Return a Cursor browser URL immediately; polling continues on the host. */
|
|
37
|
-
login(): Promise<{
|
|
38
|
-
authorizeUrl: string;
|
|
39
|
-
}>;
|
|
40
|
-
private pollLogin;
|
|
41
|
-
cancel(): Promise<void>;
|
|
42
|
-
logout(): Promise<void>;
|
|
43
|
-
}
|