dsh-cursor-subscription 0.5.3 → 0.5.5

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 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 are outside this plugin's scope.
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)与 DSH 工具调用;
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
- - 图片生成、联网搜索等 Cursor 内置能力不在本插件范围内;
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
- /** ConversationAction { user_message_action = 1 } → UserMessageAction { user_message = 1 } */
894
- function encodeUserMessageAction(userBytes) {
895
- const inner = new Writer().message(1, userBytes).finish(); // UserMessageAction
896
- return new Writer().message(1, inner).finish(); // ConversationAction
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 built = buildRunPayload(options, options.model, persisted);
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",
3
+ "version": "0.5.5",
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",
@@ -49,7 +49,11 @@
49
49
  "oauth",
50
50
  "pkce"
51
51
  ],
52
- "author": "dsh-cursor-subscription contributors",
52
+ "author": "orrinzeng",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/orrinzeng/dsh-cursor-subscription"
56
+ },
53
57
  "license": "MIT",
54
58
  "engines": {
55
59
  "node": "^22.19.0 || >=24.0.0"