dsh-cursor-subscription 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README_zh.md +2 -2
- package/lib/index.js +54 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Sign in to your Cursor account and use your Cursor subscription directly from De
|
|
|
11
11
|
- Use a Cursor subscription directly in DSH through browser-based PKCE sign-in—no API key required.
|
|
12
12
|
- Store credentials in DSH's local credential store and refresh access tokens automatically.
|
|
13
13
|
- Discover models available to the current account dynamically through Cursor's `GetUsableModels`, with a built-in fallback list if discovery fails.
|
|
14
|
-
- Stream conversations with reasoning output and DSH tool calls.
|
|
14
|
+
- Stream conversations with reasoning output, image attachments, and DSH tool calls.
|
|
15
15
|
- View sign-in status and token expiration in the settings page.
|
|
16
16
|
- View and manually refresh subscription usage, including requests, Auto + Composer usage, other-model (API) usage, per-model spend, on-demand spend, and billing cycle.
|
|
17
17
|
- View and manually refresh the models available to the current account.
|
|
@@ -72,7 +72,7 @@ If DSH is running, restart it manually after installation or an update.
|
|
|
72
72
|
|
|
73
73
|
## Scope and Support
|
|
74
74
|
|
|
75
|
-
- Cursor-native features such as image generation and web search
|
|
75
|
+
- Image *input* (user-uploaded attachments) is supported. Cursor-native features such as image generation and web search remain outside this plugin's scope.
|
|
76
76
|
- Report problems through the repository's Issues page.
|
|
77
77
|
|
|
78
78
|
[MIT](LICENSE)
|
package/README_zh.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
- 在 DSH 中直接使用 Cursor 订阅(浏览器 PKCE 登录,无 API Key);
|
|
17
17
|
- 登录凭据保存在本机 DSH credential 存储中,自动刷新访问令牌;
|
|
18
18
|
- 通过 Cursor 的 `GetUsableModels` 动态发现当前账户可用的模型(失败时回退到内置列表);
|
|
19
|
-
- 流式对话,支持思考过程(reasoning
|
|
19
|
+
- 流式对话,支持思考过程(reasoning)、用户图片附件与 DSH 工具调用;
|
|
20
20
|
- 设置页可查看登录状态与令牌有效期;
|
|
21
21
|
- 设置页可查询订阅用量(包含请求、Auto + Composer、其他模型占比、各模型消费、按需消费、账单周期)并手动刷新;
|
|
22
22
|
- 设置页可查看当前账户可用的模型列表并手动刷新;
|
|
@@ -85,7 +85,7 @@ dsh plugin --profile web remove dsh-cursor-subscription # 卸载
|
|
|
85
85
|
|
|
86
86
|
## 边界与支持
|
|
87
87
|
|
|
88
|
-
-
|
|
88
|
+
- 支持用户上传图片作为输入。图片生成、联网搜索等 Cursor 内置能力仍不在本插件范围内;
|
|
89
89
|
- 问题反馈请在仓库 Issues 中提交。
|
|
90
90
|
|
|
91
91
|
[MIT](LICENSE)
|
package/lib/index.js
CHANGED
|
@@ -102,7 +102,7 @@ function sanitizeHeaders(headers) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
function makeResponse(status, res, data) {
|
|
105
|
-
const text = () => data.toString("utf8");
|
|
105
|
+
const text = async () => data.toString("utf8");
|
|
106
106
|
return {
|
|
107
107
|
ok: status >= 200 && status < 300,
|
|
108
108
|
status,
|
|
@@ -111,7 +111,7 @@ function makeResponse(status, res, data) {
|
|
|
111
111
|
get: (name) => res.headers[String(name).toLowerCase()] ?? null,
|
|
112
112
|
},
|
|
113
113
|
text: async () => text(),
|
|
114
|
-
json: async () => JSON.parse(text()),
|
|
114
|
+
json: async () => JSON.parse(await text()),
|
|
115
115
|
arrayBuffer: async () => data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength),
|
|
116
116
|
};
|
|
117
117
|
}
|
|
@@ -890,10 +890,30 @@ function encodeModelDetails(modelId) {
|
|
|
890
890
|
return writer.finish();
|
|
891
891
|
}
|
|
892
892
|
|
|
893
|
-
/**
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
893
|
+
/**
|
|
894
|
+
* ConversationHistoryImageContent { data = 1, mime_type = 2 }
|
|
895
|
+
* wrapped as ConversationHistoryMessage.user.content[].image
|
|
896
|
+
*/
|
|
897
|
+
function encodeConversationHistoryImage({ data, mimeType }) {
|
|
898
|
+
const image = new Writer().string(1, data);
|
|
899
|
+
if (mimeType) image.string(2, mimeType);
|
|
900
|
+
const content = new Writer().message(2, image.finish()).finish(); // UserContent.image
|
|
901
|
+
const user = new Writer().message(1, content).finish(); // UserMessage.content
|
|
902
|
+
return new Writer().message(1, user).finish(); // HistoryMessage.user
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/** ConversationHistory { messages = 1 } with one history message per image. */
|
|
906
|
+
function encodeConversationHistory(images) {
|
|
907
|
+
const writer = new Writer();
|
|
908
|
+
for (const image of images) writer.message(1, encodeConversationHistoryImage(image));
|
|
909
|
+
return writer.finish();
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/** ConversationAction { user_message_action = 1 } → UserMessageAction { user_message = 1, conversation_history = 7 } */
|
|
913
|
+
function encodeUserMessageAction(userBytes, images = []) {
|
|
914
|
+
const inner = new Writer().message(1, userBytes); // UserMessageAction.user_message
|
|
915
|
+
if (images.length > 0) inner.message(7, encodeConversationHistory(images));
|
|
916
|
+
return new Writer().message(1, inner.finish()).finish(); // ConversationAction
|
|
897
917
|
}
|
|
898
918
|
|
|
899
919
|
/** ConversationStateStructure — the durable conversation payload. */
|
|
@@ -1573,6 +1593,26 @@ function sha256(bytes) {
|
|
|
1573
1593
|
return createHash("sha256").update(bytes).digest();
|
|
1574
1594
|
}
|
|
1575
1595
|
|
|
1596
|
+
function collectImageAttachments(options) {
|
|
1597
|
+
const refs = new Map();
|
|
1598
|
+
const visit = (blocks) => {
|
|
1599
|
+
for (const block of blocks ?? []) {
|
|
1600
|
+
if (block.type === "image") refs.set(block.attachment.attachmentId, block.attachment);
|
|
1601
|
+
else if (block.type === "tool-result") visit(block.content);
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
for (const message of options.messages ?? []) if (message.role === "user") visit(message.content);
|
|
1605
|
+
return [...refs.values()];
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
export async function prepareCursorImages(options, attachments) {
|
|
1609
|
+
const refs = collectImageAttachments(options);
|
|
1610
|
+
if (refs.length === 0) return [];
|
|
1611
|
+
if (attachments === undefined) throw new LlmError("Cursor image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
1612
|
+
const stored = await Promise.all(refs.map((ref) => attachments.readImage(ref, options.signal)));
|
|
1613
|
+
return stored.map(({ ref, data }) => ({ data: Buffer.from(data).toString("base64"), mimeType: ref.mediaType }));
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1576
1616
|
function flattenBlocks(content) {
|
|
1577
1617
|
let text = "";
|
|
1578
1618
|
for (const block of content ?? []) {
|
|
@@ -1892,7 +1932,7 @@ export function buildConversationState(options) {
|
|
|
1892
1932
|
* conversation state and its referenced blobs are served from the persisted
|
|
1893
1933
|
* blob store. Otherwise the state is built from the DSH message history.
|
|
1894
1934
|
*/
|
|
1895
|
-
export function buildRunPayload(options, modelId, persisted) {
|
|
1935
|
+
export function buildRunPayload(options, modelId, persisted, images = []) {
|
|
1896
1936
|
let conversationState;
|
|
1897
1937
|
let blobStore;
|
|
1898
1938
|
let actionText;
|
|
@@ -1912,7 +1952,7 @@ export function buildRunPayload(options, modelId, persisted) {
|
|
|
1912
1952
|
actionText = coldStartActionText(options);
|
|
1913
1953
|
}
|
|
1914
1954
|
const userBytes = encodeUserMessage({ text: actionText, messageId: randomUUID() });
|
|
1915
|
-
const action = encodeUserMessageAction(userBytes);
|
|
1955
|
+
const action = encodeUserMessageAction(userBytes, images);
|
|
1916
1956
|
const modelDetails = encodeModelDetails(modelId);
|
|
1917
1957
|
const runRequest = encodeRunRequest({
|
|
1918
1958
|
conversationState,
|
|
@@ -2333,6 +2373,7 @@ export class CursorAdapter extends LlmAdapter {
|
|
|
2333
2373
|
});
|
|
2334
2374
|
this.settings = options.settings ?? (() => fallbackSettings);
|
|
2335
2375
|
this.createAgentRun = options.createAgentRun ?? ((access) => new AgentRun(access));
|
|
2376
|
+
this.resolveAttachments = options.resolveAttachments;
|
|
2336
2377
|
this.sleep = options.sleep ?? abortableDelay;
|
|
2337
2378
|
this.now = options.now ?? Date.now;
|
|
2338
2379
|
this.#modelsCache = { at: 0, models: undefined };
|
|
@@ -2367,7 +2408,7 @@ export class CursorAdapter extends LlmAdapter {
|
|
|
2367
2408
|
const result = source.map((model) => ({
|
|
2368
2409
|
id: model.id,
|
|
2369
2410
|
name: model.name || model.id,
|
|
2370
|
-
inputModalities: ["text"],
|
|
2411
|
+
inputModalities: ["text", "image"],
|
|
2371
2412
|
}));
|
|
2372
2413
|
this.#modelsCache = { at: now, models: result };
|
|
2373
2414
|
return result;
|
|
@@ -2400,7 +2441,7 @@ export class CursorAdapter extends LlmAdapter {
|
|
|
2400
2441
|
provider,
|
|
2401
2442
|
id: model,
|
|
2402
2443
|
name,
|
|
2403
|
-
inputModalities: ["text"],
|
|
2444
|
+
inputModalities: ["text", "image"],
|
|
2404
2445
|
context: { contextWindow: DEFAULT_CONTEXT_WINDOW },
|
|
2405
2446
|
defaultMaxTokens: DEFAULT_MAX_TOKENS,
|
|
2406
2447
|
};
|
|
@@ -2460,7 +2501,8 @@ export class CursorAdapter extends LlmAdapter {
|
|
|
2460
2501
|
persisted.blobs.clear();
|
|
2461
2502
|
}
|
|
2462
2503
|
const access = await this.auth.accessToken({ signal: upstream });
|
|
2463
|
-
const
|
|
2504
|
+
const images = await prepareCursorImages(options, this.resolveAttachments?.());
|
|
2505
|
+
const built = buildRunPayload(options, options.model, persisted, images);
|
|
2464
2506
|
blobStore = built.blobStore;
|
|
2465
2507
|
let retriesUsed = 0;
|
|
2466
2508
|
for (;;) {
|
|
@@ -2864,7 +2906,7 @@ export function apply(ctx, config = {}) {
|
|
|
2864
2906
|
};
|
|
2865
2907
|
const store = new CursorCredentialStore(ctx.credentials, CREDENTIAL_REF);
|
|
2866
2908
|
const auth = new CursorAuthService(store, { logger: ctx.logger });
|
|
2867
|
-
const adapter = new CursorAdapter({ auth, settings: readSettings });
|
|
2909
|
+
const adapter = new CursorAdapter({ auth, settings: readSettings, resolveAttachments: () => ctx.get("attachments") });
|
|
2868
2910
|
ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
2869
2911
|
const usageReader = new CursorUsageReader(auth, { logger: ctx.logger });
|
|
2870
2912
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cursor-subscription",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"packageManager": "pnpm@11.19.0",
|
|
5
5
|
"description": "Cursor subscription for DeepSeek Harness with browser login, token refresh, model discovery, and the Cursor Agent chat protocol",
|
|
6
6
|
"type": "module",
|