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.
Files changed (41) hide show
  1. package/README.md +9 -21
  2. package/lib/auth/rpc.d.ts +2 -20
  3. package/lib/auth/rpc.js +3 -49
  4. package/lib/client/ProviderAccountManager.d.ts +1 -1
  5. package/lib/client/ProviderAccountManager.js +86 -110
  6. package/lib/client/ProviderModelEditor.d.ts +1 -1
  7. package/lib/client/ProviderModelEditor.js +2 -6
  8. package/lib/client/SubscriptionUsageBadge.d.ts +8 -2
  9. package/lib/client/SubscriptionUsageBadge.js +24 -17
  10. package/lib/client/SubscriptionsSection.js +1 -2
  11. package/lib/client/locales.d.ts +2 -32
  12. package/lib/client/locales.js +2 -32
  13. package/lib/client/model-filter.js +1 -1
  14. package/lib/client/subscription-card-styles.d.ts +1 -1
  15. package/lib/client/subscription-card-styles.js +1 -1
  16. package/lib/client.js +234 -518
  17. package/lib/client.js.map +1 -1
  18. package/lib/index.js +208 -3305
  19. package/lib/providers/claude.d.ts +3 -1
  20. package/lib/providers/claude.js +2 -2
  21. package/lib/providers/ollama-settings.d.ts +1 -1
  22. package/lib/providers/ollama-settings.js +1 -1
  23. package/lib/providers/pool-usage.d.ts +10 -9
  24. package/lib/providers/pool-usage.js +30 -19
  25. package/lib/providers/pool.d.ts +6 -6
  26. package/lib/providers/pool.js +24 -12
  27. package/package.json +1 -7
  28. package/lib/client/CursorCard.d.ts +0 -9
  29. package/lib/client/CursorCard.js +0 -151
  30. package/lib/providers/cursor-adapter.d.ts +0 -50
  31. package/lib/providers/cursor-adapter.js +0 -304
  32. package/lib/providers/cursor-auth.d.ts +0 -43
  33. package/lib/providers/cursor-auth.js +0 -221
  34. package/lib/providers/cursor-model-settings.d.ts +0 -15
  35. package/lib/providers/cursor-model-settings.js +0 -60
  36. package/lib/providers/cursor-usage.d.ts +0 -9
  37. package/lib/providers/cursor-usage.js +0 -131
  38. package/vendor/cursor/LICENSE +0 -21
  39. package/vendor/cursor/index.d.ts +0 -18
  40. package/vendor/cursor/index.js +0 -3574
  41. package/vendor/cursor/proto.js +0 -254
package/lib/index.js CHANGED
@@ -1,20 +1,17 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import * as llm from "@deepseek-ai/dsh-llm";
3
- import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, QUOTA_EXCEEDED_CODE, ReasoningEffortId, ToolCallId, attributionHeaders, errorChain, isContextWindowExceededError, isQuotaExceededError, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
4
- import http, { createServer } from "node:http";
3
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, QUOTA_EXCEEDED_CODE, ReasoningEffortId, attributionHeaders, errorChain, isContextWindowExceededError, isQuotaExceededError, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
4
+ import { createServer } from "node:http";
5
5
  import { createHash, randomBytes, randomUUID } from "node:crypto";
6
6
  import { getDefaultAutoSelectFamilyAttemptTimeout, setDefaultAutoSelectFamilyAttemptTimeout } from "node:net";
7
7
  import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
8
8
  import { basename, dirname, join, resolve } from "node:path";
9
9
  import { execFileSync } from "node:child_process";
10
- import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { AttachmentId } from "@deepseek-ai/dsh-attachment";
13
13
  import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
14
14
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
15
- import http2 from "node:http2";
16
- import https from "node:https";
17
- import { gunzipSync } from "node:zlib";
18
15
  import { WebError } from "@deepseek-ai/dsh-web";
19
16
  import { defineTool } from "@deepseek-ai/dsh-tools";
20
17
 
@@ -1786,13 +1783,13 @@ function parseStore(text, path) {
1786
1783
  console.warn(`subscriptions auth store: entry "${provider}" is not an object; skipped`);
1787
1784
  continue;
1788
1785
  }
1789
- const record$4 = entry;
1790
- if (typeof record$4.accessToken === "string") {
1791
- if (!isValidSessionShape(record$4)) {
1786
+ const record$2 = entry;
1787
+ if (typeof record$2.accessToken === "string") {
1788
+ if (!isValidSessionShape(record$2)) {
1792
1789
  console.warn(`subscriptions auth store: legacy entry "${provider}" has no usable tokens; skipped`);
1793
1790
  continue;
1794
1791
  }
1795
- const session = record$4;
1792
+ const session = record$2;
1796
1793
  const key = provider === "codex" ? session.accountId : accountKeyOf(provider, session);
1797
1794
  store[provider] = {
1798
1795
  default: key,
@@ -1800,12 +1797,12 @@ function parseStore(text, path) {
1800
1797
  };
1801
1798
  continue;
1802
1799
  }
1803
- const accounts = record$4.accounts;
1800
+ const accounts = record$2.accounts;
1804
1801
  if (typeof accounts !== "object" || accounts === null || Array.isArray(accounts)) {
1805
1802
  console.warn(`subscriptions auth store: entry "${provider}" has no accounts map; skipped`);
1806
1803
  continue;
1807
1804
  }
1808
- if (record$4.default !== void 0 && typeof record$4.default !== "string") {
1805
+ if (record$2.default !== void 0 && typeof record$2.default !== "string") {
1809
1806
  console.warn(`subscriptions auth store: entry "${provider}" default is not a string; skipped`);
1810
1807
  continue;
1811
1808
  }
@@ -1813,9 +1810,9 @@ function parseStore(text, path) {
1813
1810
  for (const [account, session] of Object.entries(accounts)) if (isValidSessionShape(session)) kept[account] = session;
1814
1811
  else console.warn(`subscriptions auth store: entry "${provider}/${account}" has no usable accessToken/refreshToken/expiresAt; skipped`);
1815
1812
  if (Object.keys(kept).length === 0) continue;
1816
- const validDefault = record$4.default === void 0 || record$4.default in kept ? record$4.default : Object.keys(kept)[0];
1813
+ const validDefault = record$2.default === void 0 || record$2.default in kept ? record$2.default : Object.keys(kept)[0];
1817
1814
  store[provider] = {
1818
- ...record$4,
1815
+ ...record$2,
1819
1816
  default: validDefault,
1820
1817
  accounts: kept
1821
1818
  };
@@ -2011,9 +2008,9 @@ function usedFromCounts(total, count) {
2011
2008
  * @throws when the key is pay-as-you-go, the endpoint or its business status
2012
2009
  * fails, the body has no `model_remains`, or no window is a finite bar.
2013
2010
  */
2014
- async function fetchMiniMaxUsage(key, region, http$1 = fetch, signal) {
2011
+ async function fetchMiniMaxUsage(key, region, http = fetch, signal) {
2015
2012
  if (key.startsWith("sk-api-")) throw new Error("MiniMax requires a subscription key, not a pay-as-you-go key");
2016
- const response = await http$1(`https://www.minimax.${region === "cn" ? "cn" : "io"}/v1/token_plan/remains`, {
2013
+ const response = await http(`https://www.minimax.${region === "cn" ? "cn" : "io"}/v1/token_plan/remains`, {
2017
2014
  headers: {
2018
2015
  authorization: `Bearer ${key}`,
2019
2016
  "content-type": "application/json"
@@ -2093,7 +2090,7 @@ function kimiReset(value) {
2093
2090
  if (seconds !== void 0 && seconds > 0) return Date.now() + seconds * 1e3;
2094
2091
  }
2095
2092
  }
2096
- function record$3(value) {
2093
+ function record$1(value) {
2097
2094
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2098
2095
  }
2099
2096
  function percent(value) {
@@ -2104,8 +2101,8 @@ function resetTime$1(value) {
2104
2101
  const parsed = Date.parse(value);
2105
2102
  return Number.isFinite(parsed) ? parsed : void 0;
2106
2103
  }
2107
- async function getUsage(url, apiKey, http$1, signal) {
2108
- const response = await http$1(url, {
2104
+ async function getUsage(url, apiKey, http, signal) {
2105
+ const response = await http(url, {
2109
2106
  headers: {
2110
2107
  accept: "application/json",
2111
2108
  authorization: `Bearer ${apiKey}`
@@ -2116,8 +2113,8 @@ async function getUsage(url, apiKey, http$1, signal) {
2116
2113
  return parseProviderJson(response, "external usage");
2117
2114
  }
2118
2115
  /** Read the three account-wide OpenCode Go windows from its Go gateway. */
2119
- async function fetchOpenCodeGoUsage(apiKey, http$1 = fetch, signal) {
2120
- const usage = record$3(record$3(await getUsage("https://opencode.ai/zen/go/v1/usage", apiKey, http$1, signal))?.usage);
2116
+ async function fetchOpenCodeGoUsage(apiKey, http = fetch, signal) {
2117
+ const usage = record$1(record$1(await getUsage("https://opencode.ai/zen/go/v1/usage", apiKey, http, signal))?.usage);
2121
2118
  if (usage === void 0) throw new Error("OpenCode Go usage response has no usage object");
2122
2119
  const windows = [];
2123
2120
  for (const [field, kind, scope] of [
@@ -2137,7 +2134,7 @@ async function fetchOpenCodeGoUsage(apiKey, http$1 = fetch, signal) {
2137
2134
  "Monthly"
2138
2135
  ]
2139
2136
  ]) {
2140
- const value = record$3(usage[field]);
2137
+ const value = record$1(usage[field]);
2141
2138
  const usedPercent = percent(value?.percent);
2142
2139
  if (usedPercent === void 0) continue;
2143
2140
  const resetsAt = resetTime$1(value?.resetsAt);
@@ -2165,11 +2162,11 @@ async function fetchOpenCodeGoUsage(apiKey, http$1 = fetch, signal) {
2165
2162
  };
2166
2163
  }
2167
2164
  /** Read official CLI count windows and retain compatibility with older ratio pools. */
2168
- async function fetchKimiCodeUsage(apiKey, http$1 = fetch, signal) {
2169
- const body = record$3(await getUsage("https://api.kimi.com/coding/v1/usages", apiKey, http$1, signal));
2165
+ async function fetchKimiCodeUsage(apiKey, http = fetch, signal) {
2166
+ const body = record$1(await getUsage("https://api.kimi.com/coding/v1/usages", apiKey, http, signal));
2170
2167
  if (body === void 0) throw new Error("Kimi Code usage response is not an object");
2171
2168
  const modern = [];
2172
- const rows = [...record$3(body.usage) ? [{
2169
+ const rows = [...record$1(body.usage) ? [{
2173
2170
  raw: { detail: body.usage },
2174
2171
  summary: true
2175
2172
  }] : [], ...Array.isArray(body.limits) ? body.limits.map((raw) => ({
@@ -2177,15 +2174,15 @@ async function fetchKimiCodeUsage(apiKey, http$1 = fetch, signal) {
2177
2174
  summary: false
2178
2175
  })) : []];
2179
2176
  for (const { raw, summary } of rows) {
2180
- const item = record$3(raw);
2177
+ const item = record$1(raw);
2181
2178
  if (!item) continue;
2182
- const detail = record$3(item.detail) ?? item;
2179
+ const detail = record$1(item.detail) ?? item;
2183
2180
  const limit = numeric(detail.limit);
2184
2181
  const used = numeric(detail.used) ?? (limit !== void 0 && numeric(detail.remaining) !== void 0 ? limit - numeric(detail.remaining) : void 0);
2185
2182
  if (limit === void 0 || limit <= 0 || used === void 0) continue;
2186
2183
  const usedPercent = percent(used / limit * 100);
2187
2184
  if (usedPercent === void 0) continue;
2188
- const window = record$3(item.window) ?? item;
2185
+ const window = record$1(item.window) ?? item;
2189
2186
  const duration = numeric(window.duration ?? item.duration ?? detail.duration);
2190
2187
  const unit = String(window.timeUnit ?? item.timeUnit ?? detail.timeUnit ?? "").toUpperCase();
2191
2188
  const multiplier = unit.includes("MINUTE") ? 6e4 : unit.includes("HOUR") ? 36e5 : unit.includes("DAY") ? 864e5 : unit.includes("SECOND") ? 1e3 : void 0;
@@ -2210,7 +2207,7 @@ async function fetchKimiCodeUsage(apiKey, http$1 = fetch, signal) {
2210
2207
  windows: modern,
2211
2208
  plan: "Kimi Code"
2212
2209
  };
2213
- const usages = record$3(body.usages);
2210
+ const usages = record$1(body.usages);
2214
2211
  if (usages === void 0) throw new Error("Kimi Code usage response has no valid windows");
2215
2212
  const windows = [];
2216
2213
  for (const [field, kind, scope] of [
@@ -2230,7 +2227,7 @@ async function fetchKimiCodeUsage(apiKey, http$1 = fetch, signal) {
2230
2227
  "Monthly"
2231
2228
  ]
2232
2229
  ]) {
2233
- const value = record$3(usages[field]);
2230
+ const value = record$1(usages[field]);
2234
2231
  const ratio = value?.used_ratio ?? value?.usedRatio;
2235
2232
  if (typeof ratio !== "number" || !Number.isFinite(ratio) || ratio < 0 || ratio > 1) continue;
2236
2233
  const resetsAt = resetTime$1(value?.reset_time ?? value?.resetAt);
@@ -2260,9 +2257,9 @@ async function fetchKimiCodeUsage(apiKey, http$1 = fetch, signal) {
2260
2257
  * reported period. A 404 means a self-hosted endpoint without the
2261
2258
  * surface: not an error, the card renders an unsupported note instead.
2262
2259
  */
2263
- async function fetchOllamaUsage(apiKey, baseURL, http$1 = fetch, signal) {
2260
+ async function fetchOllamaUsage(apiKey, baseURL, http = fetch, signal) {
2264
2261
  const combined = signal === void 0 ? AbortSignal.timeout(15e3) : AbortSignal.any([AbortSignal.timeout(15e3), signal]);
2265
- const response = await http$1(`${baseURL.replace(/\/+$/, "")}/balance`, {
2262
+ const response = await http(`${baseURL.replace(/\/+$/, "")}/balance`, {
2266
2263
  headers: {
2267
2264
  accept: "application/json",
2268
2265
  authorization: `Bearer ${apiKey}`,
@@ -2274,11 +2271,11 @@ async function fetchOllamaUsage(apiKey, baseURL, http$1 = fetch, signal) {
2274
2271
  if (signal?.aborted) throw signal.reason;
2275
2272
  if (response.status === 404) return { supported: false };
2276
2273
  if (!response.ok) throw new Error(`Usage endpoint returned HTTP ${response.status}`);
2277
- const included = record$3(record$3(await readBoundedJson(response, "usage", 1 * 1024 * 1024))?.included);
2274
+ const included = record$1(record$1(await readBoundedJson(response, "usage", 1 * 1024 * 1024))?.included);
2278
2275
  const allowance = numeric(included?.allowance_usd);
2279
2276
  const balance = numeric(included?.balance_usd);
2280
2277
  if (included === void 0 || allowance === void 0 || allowance <= 0 || balance === void 0) throw new Error("Ollama balance response has no usable included allowance");
2281
- const period = record$3(included.period);
2278
+ const period = record$1(included.period);
2282
2279
  const resetsAt = period === void 0 ? void 0 : resetTime$1(period.until);
2283
2280
  const startsAt = period === void 0 ? void 0 : resetTime$1(period.from);
2284
2281
  return {
@@ -2474,7 +2471,7 @@ async function resolveImages(messages, attachments, signal, limit) {
2474
2471
  //#endregion
2475
2472
  //#region src/compat.ts
2476
2473
  /** Brand a string as a tool-call id: alpha's `ToolCallId`, rc.2's `CallId`. */
2477
- const ToolCallId$1 = (() => {
2474
+ const ToolCallId = (() => {
2478
2475
  const exports = llm;
2479
2476
  return exports["ToolCallId"] ?? exports["CallId"];
2480
2477
  })();
@@ -2692,7 +2689,7 @@ function closeBlock$2(block) {
2692
2689
  };
2693
2690
  case "tool-call": return {
2694
2691
  type: "tool-call",
2695
- id: ToolCallId$1(block.callId),
2692
+ id: ToolCallId(block.callId),
2696
2693
  name: block.name ?? "",
2697
2694
  arguments: block.text
2698
2695
  };
@@ -2846,7 +2843,7 @@ var ChatCompletionsStreamTranslator = class {
2846
2843
  chunks.push({
2847
2844
  type: "tool-call-delta",
2848
2845
  index: block.index,
2849
- id: ToolCallId$1(block.callId),
2846
+ id: ToolCallId(block.callId),
2850
2847
  ...block.name === void 0 ? {} : { name: block.name },
2851
2848
  argumentsDelta: ""
2852
2849
  });
@@ -2856,7 +2853,7 @@ var ChatCompletionsStreamTranslator = class {
2856
2853
  chunks.push({
2857
2854
  type: "tool-call-delta",
2858
2855
  index: block.index,
2859
- id: ToolCallId$1(block.callId),
2856
+ id: ToolCallId(block.callId),
2860
2857
  argumentsDelta: call.function.arguments
2861
2858
  });
2862
2859
  }
@@ -3165,8 +3162,8 @@ async function fetchOllamaModels(options) {
3165
3162
  const seen = /* @__PURE__ */ new Set();
3166
3163
  const entries = [];
3167
3164
  for (const entry of listed) {
3168
- const record$4 = recordOf$1(entry);
3169
- const raw = record$4 === void 0 ? void 0 : typeof record$4["model"] === "string" ? record$4["model"] : typeof record$4["name"] === "string" ? record$4["name"] : void 0;
3165
+ const record$2 = recordOf$1(entry);
3166
+ const raw = record$2 === void 0 ? void 0 : typeof record$2["model"] === "string" ? record$2["model"] : typeof record$2["name"] === "string" ? record$2["name"] : void 0;
3170
3167
  if (raw === void 0 || raw.length === 0) continue;
3171
3168
  const wire = normalizeOllamaModelId(raw, options.baseURL);
3172
3169
  if (seen.has(wire)) continue;
@@ -3384,9 +3381,9 @@ const DEFAULT_REFS = {
3384
3381
  var ExternalUsageController = class {
3385
3382
  refs;
3386
3383
  ollamaBaseURL;
3387
- constructor(resolveCredential, http$1 = fetch, refs = {}, ollamaBaseURL = DEFAULT_OLLAMA_BASE_URL) {
3384
+ constructor(resolveCredential, http = fetch, refs = {}, ollamaBaseURL = DEFAULT_OLLAMA_BASE_URL) {
3388
3385
  this.resolveCredential = resolveCredential;
3389
- this.http = http$1;
3386
+ this.http = http;
3390
3387
  this.refs = {
3391
3388
  ...DEFAULT_REFS,
3392
3389
  ...refs
@@ -3468,17 +3465,17 @@ function readEfforts(value, field) {
3468
3465
  }
3469
3466
  function readModel(entry, index) {
3470
3467
  if (entry === null || typeof entry !== "object" || Array.isArray(entry)) fail(`models[${index}] must be an object`);
3471
- const record$4 = entry;
3472
- const model = { id: readNonEmptyString(record$4.id, `models[${index}].id`).trim() };
3473
- if (record$4.name !== void 0) model.name = readNonEmptyString(record$4.name, `models[${index}].name`);
3474
- if (record$4.contextWindow !== void 0) model.contextWindow = readPositiveInteger(record$4.contextWindow, `models[${index}].contextWindow`);
3475
- if (record$4.contextOverride !== void 0) model.contextOverride = readPositiveInteger(record$4.contextOverride, `models[${index}].contextOverride`);
3476
- if (record$4.maxTokens !== void 0) model.maxTokens = readPositiveInteger(record$4.maxTokens, `models[${index}].maxTokens`);
3477
- if (record$4.vision !== void 0) model.vision = readBoolean(record$4.vision, `models[${index}].vision`);
3478
- if (record$4.thinking !== void 0) model.thinking = readBoolean(record$4.thinking, `models[${index}].thinking`);
3479
- if (record$4.efforts !== void 0) model.efforts = readEfforts(record$4.efforts, `models[${index}].efforts`);
3480
- if (record$4.defaultEffort !== void 0) {
3481
- const effort = readNonEmptyString(record$4.defaultEffort, `models[${index}].defaultEffort`);
3468
+ const record$2 = entry;
3469
+ const model = { id: readNonEmptyString(record$2.id, `models[${index}].id`).trim() };
3470
+ if (record$2.name !== void 0) model.name = readNonEmptyString(record$2.name, `models[${index}].name`);
3471
+ if (record$2.contextWindow !== void 0) model.contextWindow = readPositiveInteger(record$2.contextWindow, `models[${index}].contextWindow`);
3472
+ if (record$2.contextOverride !== void 0) model.contextOverride = readPositiveInteger(record$2.contextOverride, `models[${index}].contextOverride`);
3473
+ if (record$2.maxTokens !== void 0) model.maxTokens = readPositiveInteger(record$2.maxTokens, `models[${index}].maxTokens`);
3474
+ if (record$2.vision !== void 0) model.vision = readBoolean(record$2.vision, `models[${index}].vision`);
3475
+ if (record$2.thinking !== void 0) model.thinking = readBoolean(record$2.thinking, `models[${index}].thinking`);
3476
+ if (record$2.efforts !== void 0) model.efforts = readEfforts(record$2.efforts, `models[${index}].efforts`);
3477
+ if (record$2.defaultEffort !== void 0) {
3478
+ const effort = readNonEmptyString(record$2.defaultEffort, `models[${index}].defaultEffort`);
3482
3479
  if (model.efforts !== void 0 && !model.efforts.includes(effort)) fail(`models[${index}].defaultEffort must be one of its efforts`);
3483
3480
  model.defaultEffort = effort;
3484
3481
  }
@@ -3486,19 +3483,19 @@ function readModel(entry, index) {
3486
3483
  }
3487
3484
  /**
3488
3485
  * Validate one settings document, dropping unknown keys and duplicate model
3489
- * ids (first wins, matching the Cursor store's visible-model handling).
3486
+ * ids (first wins).
3490
3487
  * @param input - the parsed store document or an RPC payload.
3491
3488
  * @throws when a known field has the wrong type or an unusable value.
3492
3489
  */
3493
3490
  function validateOllamaSettings(input) {
3494
3491
  if (input === null || typeof input !== "object" || Array.isArray(input)) fail("Ollama settings must be an object");
3495
- const record$4 = input;
3492
+ const record$2 = input;
3496
3493
  const settings = {};
3497
- if (record$4.models !== void 0) {
3498
- if (!Array.isArray(record$4.models)) fail("models must be an array");
3494
+ if (record$2.models !== void 0) {
3495
+ if (!Array.isArray(record$2.models)) fail("models must be an array");
3499
3496
  const seen = /* @__PURE__ */ new Set();
3500
3497
  const models = [];
3501
- for (const [index, entry] of record$4.models.entries()) {
3498
+ for (const [index, entry] of record$2.models.entries()) {
3502
3499
  const model = readModel(entry, index);
3503
3500
  if (seen.has(model.id)) continue;
3504
3501
  seen.add(model.id);
@@ -3506,9 +3503,9 @@ function validateOllamaSettings(input) {
3506
3503
  }
3507
3504
  settings.models = models;
3508
3505
  }
3509
- if (record$4.web !== void 0) settings.web = readBoolean(record$4.web, "web");
3510
- if (record$4.baseURL !== void 0) settings.baseURL = validateOllamaBaseURL(record$4.baseURL);
3511
- if (record$4.apiKeyRef !== void 0) settings.apiKeyRef = readApiKeyRef(record$4.apiKeyRef);
3506
+ if (record$2.web !== void 0) settings.web = readBoolean(record$2.web, "web");
3507
+ if (record$2.baseURL !== void 0) settings.baseURL = validateOllamaBaseURL(record$2.baseURL);
3508
+ if (record$2.apiKeyRef !== void 0) settings.apiKeyRef = readApiKeyRef(record$2.apiKeyRef);
3512
3509
  return settings;
3513
3510
  }
3514
3511
  /**
@@ -3597,14 +3594,6 @@ const SUBSCRIPTIONS_AUTH_ENDPOINTS = [
3597
3594
  "usage",
3598
3595
  "externalStatus",
3599
3596
  "externalUsage",
3600
- "cursorStatus",
3601
- "cursorLogin",
3602
- "cursorCancel",
3603
- "cursorLogout",
3604
- "cursorUsage",
3605
- "cursorModels",
3606
- "cursorSettings",
3607
- "cursorSetSettings",
3608
3597
  "ollamaSettings",
3609
3598
  "ollamaSetSettings",
3610
3599
  "ollamaModels",
@@ -3630,13 +3619,13 @@ const VIDEO_NAME_PATTERN = /^[\w.-]+\.mp4$/;
3630
3619
  var BadRequest = class extends Error {};
3631
3620
  function readEnvelope(body) {
3632
3621
  if (typeof body !== "object" || body === null) return void 0;
3633
- const record$4 = body;
3634
- if (record$4.type !== "client-request" || typeof record$4.rpcId !== "string" || typeof record$4.method !== "string") return void 0;
3622
+ const record$2 = body;
3623
+ if (record$2.type !== "client-request" || typeof record$2.rpcId !== "string" || typeof record$2.method !== "string") return void 0;
3635
3624
  return {
3636
3625
  type: "client-request",
3637
- rpcId: record$4.rpcId,
3638
- method: record$4.method,
3639
- payload: record$4.payload
3626
+ rpcId: record$2.rpcId,
3627
+ method: record$2.method,
3628
+ payload: record$2.payload
3640
3629
  };
3641
3630
  }
3642
3631
  function serverResponse(rpcId, result) {
@@ -3740,11 +3729,11 @@ function readOptionalString(payload, field) {
3740
3729
  function readModelDefaultInput(payload) {
3741
3730
  const provider = readProvider(payload);
3742
3731
  const model = readString(payload, "model");
3743
- const record$4 = payload;
3732
+ const record$2 = payload;
3744
3733
  let effort;
3745
- if (record$4.effort !== void 0) {
3746
- if (typeof record$4.effort !== "string" || record$4.effort.length === 0) throw new BadRequest("payload.effort must be a non-empty string when present");
3747
- effort = record$4.effort;
3734
+ if (record$2.effort !== void 0) {
3735
+ if (typeof record$2.effort !== "string" || record$2.effort.length === 0) throw new BadRequest("payload.effort must be a non-empty string when present");
3736
+ effort = record$2.effort;
3748
3737
  }
3749
3738
  return {
3750
3739
  provider,
@@ -3769,27 +3758,27 @@ function readSpeedTier(payload) {
3769
3758
  /** Validate the `image` endpoint's payload into a full attachment reference. */
3770
3759
  function readImageRef(payload) {
3771
3760
  if (typeof payload !== "object" || payload === null) throw new BadRequest("payload must be an object");
3772
- const record$4 = payload;
3773
- const attachmentId = record$4.attachmentId;
3761
+ const record$2 = payload;
3762
+ const attachmentId = record$2.attachmentId;
3774
3763
  if (typeof attachmentId !== "string" || attachmentId.length === 0) throw new BadRequest("payload.attachmentId must be a non-empty string");
3775
- const mediaType = record$4.mediaType;
3764
+ const mediaType = record$2.mediaType;
3776
3765
  if (typeof mediaType !== "string" || !IMAGE_MEDIA_TYPES.includes(mediaType)) throw new BadRequest(`payload.mediaType must be one of ${IMAGE_MEDIA_TYPES.join(", ")}`);
3777
3766
  for (const field of [
3778
3767
  "bytes",
3779
3768
  "width",
3780
3769
  "height"
3781
3770
  ]) {
3782
- const value = record$4[field];
3771
+ const value = record$2[field];
3783
3772
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new BadRequest(`payload.${field} must be a positive integer`);
3784
3773
  }
3785
- const name$1 = record$4.name;
3774
+ const name$1 = record$2.name;
3786
3775
  if (name$1 !== void 0 && typeof name$1 !== "string") throw new BadRequest("payload.name must be a string when present");
3787
3776
  return {
3788
3777
  attachmentId: AttachmentId(attachmentId),
3789
3778
  mediaType,
3790
- bytes: record$4.bytes,
3791
- width: record$4.width,
3792
- height: record$4.height,
3779
+ bytes: record$2.bytes,
3780
+ width: record$2.width,
3781
+ height: record$2.height,
3793
3782
  ...name$1 === void 0 ? {} : { name: name$1 }
3794
3783
  };
3795
3784
  }
@@ -3817,7 +3806,7 @@ function readSessionId(payload) {
3817
3806
  if (typeof payload !== "object" || payload === null) throw new BadRequest("payload must be an object");
3818
3807
  return readString(payload, "sessionId");
3819
3808
  }
3820
- async function dispatch(controller, speed, modelDefaults, endpoint, payload, signal, providerSettings, externalUsage, cursor, ollama) {
3809
+ async function dispatch(controller, speed, modelDefaults, endpoint, payload, signal, providerSettings, externalUsage, ollama) {
3821
3810
  switch (endpoint) {
3822
3811
  case "providerSettings":
3823
3812
  if (!providerSettings) throw new BadRequest("provider settings are unavailable");
@@ -3877,3044 +3866,79 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
3877
3866
  if (!EXTERNAL_USAGE_SOURCES.includes(source)) throw new BadRequest(`payload.source must be one of ${EXTERNAL_USAGE_SOURCES.join(", ")}`);
3878
3867
  return ok(await externalUsage.usage(source, signal));
3879
3868
  }
3880
- case "cursorStatus":
3881
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3882
- return ok(await cursor.status());
3883
- case "cursorLogin":
3884
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3885
- return ok(await cursor.login());
3886
- case "cursorCancel":
3887
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3888
- await cursor.cancel();
3889
- return ok({ ok: true });
3890
- case "cursorLogout":
3891
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3892
- await cursor.logout();
3893
- return ok({ ok: true });
3894
- case "cursorUsage":
3895
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3896
- return ok(await cursor.usage(signal));
3897
- case "cursorModels":
3898
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3899
- return ok(await cursor.models(readForce(payload), signal));
3900
- case "cursorSettings":
3901
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3902
- return ok(await cursor.settings(readForce(payload), signal));
3903
- case "cursorSetSettings":
3904
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3905
- if (payload === null || typeof payload !== "object") throw new BadRequest("payload must be an object");
3906
- await cursor.setSettings(payload.settings);
3907
- return ok({ ok: true });
3908
- case "ollamaSettings":
3909
- if (!ollama) throw new BadRequest("Ollama settings are unavailable");
3910
- return ok(await ollama.settings());
3911
- case "ollamaSetSettings":
3912
- if (!ollama) throw new BadRequest("Ollama settings are unavailable");
3913
- if (payload === null || typeof payload !== "object") throw new BadRequest("payload must be an object");
3914
- await ollama.setSettings(payload.settings);
3915
- return ok({ ok: true });
3916
- case "ollamaModels": {
3917
- if (!ollama) throw new BadRequest("Ollama settings are unavailable");
3918
- const apiKey = readOptionalString(payload, "apiKey");
3919
- const rawBaseURL = readOptionalString(payload, "baseURL");
3920
- let baseURL;
3921
- if (rawBaseURL !== void 0) try {
3922
- baseURL = validateOllamaBaseURL(rawBaseURL);
3923
- } catch (error) {
3924
- throw new BadRequest(error instanceof Error ? error.message : String(error));
3925
- }
3926
- return ok(await ollama.models({
3927
- ...apiKey === void 0 ? {} : { apiKey },
3928
- ...baseURL === void 0 ? {} : { baseURL }
3929
- }, signal));
3930
- }
3931
- case "prepareReset":
3932
- if (!controller.prepareReset) throw new BadRequest("Reset redemption unavailable");
3933
- return ok(await controller.prepareReset(readString(payload, "account"), signal));
3934
- case "consumeReset":
3935
- if (!controller.consumeReset) throw new BadRequest("Reset redemption unavailable");
3936
- await controller.consumeReset(readString(payload, "account"), readString(payload, "ticket"), signal);
3937
- return ok({ ok: true });
3938
- case "image": return ok(await controller.readImage(readImageRef(payload), signal));
3939
- case "video": return ok(await controller.readVideo(readVideoName(payload), signal));
3940
- case "speed": return ok(await speed.speed(readSessionId(payload), payload.model === void 0 ? void 0 : readString(payload, "model")));
3941
- case "setSpeed":
3942
- await speed.setSpeed(readSessionId(payload), readSpeedTier(payload));
3943
- return ok({ ok: true });
3944
- case "modelDefaults":
3945
- if (modelDefaults === void 0) throw new BadRequest("model defaults are unavailable");
3946
- return ok(await modelDefaults.catalog(readForce(payload)));
3947
- case "setModelDefault":
3948
- if (modelDefaults === void 0) throw new BadRequest("model defaults are unavailable");
3949
- {
3950
- const input = readModelDefaultInput(payload);
3951
- await modelDefaults.set(input.provider, input.model, input.effort);
3952
- }
3953
- return ok({ ok: true });
3954
- default: throw new BadRequest(`unknown /subscriptions-auth endpoint "${endpoint}"`);
3955
- }
3956
- }
3957
- /**
3958
- * Register the `/subscriptions-auth` RPC channel when a host connection exists.
3959
- * @param ctx - the plugin context (headless profiles have no `connection`).
3960
- * @param controller - the auth operations backing the endpoints.
3961
- * @param speed - the per-session speed-tier state backing the Speed toggle.
3962
- * @param modelDefaults - optional per-model default-effort state backing `modelDefaults`/`setModelDefault`.
3963
- * @param providerSettings - optional OAuth provider/account preferences.
3964
- * @param externalUsage - optional credential-backed quota readers.
3965
- * @param cursor - optional Cursor account and model-visibility actions.
3966
- * @param ollama - optional Ollama Cloud catalog and connection settings.
3967
- */
3968
- function registerAuthRpc(ctx, controller, speed, modelDefaults = void 0, providerSettings = void 0, externalUsage = void 0, cursor = void 0, ollama = void 0) {
3969
- ctx.inject(["connection"], (ctx$1) => {
3970
- const connection = ctx$1.get("connection");
3971
- const handler = async (endpoint, payload, signal) => {
3972
- try {
3973
- return await dispatch(controller, speed, modelDefaults, endpoint, payload, signal, providerSettings, externalUsage, cursor, ollama);
3974
- } catch (error) {
3975
- return failure(error);
3976
- }
3977
- };
3978
- const register = connection.fetch.register;
3979
- for (const endpoint of SUBSCRIPTIONS_AUTH_ENDPOINTS) ctx$1.effect(() => register(fetchRouteFor(endpoint, handler)), `dsh-subscriptions: /api/${SUBSCRIPTIONS_AUTH_PREFIX}${endpoint} route`);
3980
- });
3981
- }
3982
-
3983
- //#endregion
3984
- //#region src/providers/cursor-auth.ts
3985
- const CURSOR_CREDENTIAL_REF = credentialRef("CURSOR_SUBSCRIPTION_OAUTH");
3986
- const API_ORIGIN = "https://api2.cursor.sh";
3987
- const REFRESH_AHEAD_MS$1 = 5 * 6e4;
3988
- async function parseCredential(value) {
3989
- if (value === void 0 || value.length === 0) return void 0;
3990
- const parsed = await parseProviderJson(value, "Cursor stored credential");
3991
- if (parsed === null || typeof parsed !== "object") throw new Error("Cursor credential is malformed");
3992
- const item = parsed;
3993
- if (item.type !== "oauth" || typeof item.access !== "string" || item.access.length === 0 || typeof item.refresh !== "string" || typeof item.expires !== "number" || !Number.isFinite(item.expires)) throw new Error("Cursor credential is malformed");
3994
- return {
3995
- type: "oauth",
3996
- access: item.access,
3997
- refresh: item.refresh,
3998
- expires: item.expires
3999
- };
4000
- }
4001
- function tokenExpiry(token, now) {
4002
- try {
4003
- const payload = token.split(".")[1];
4004
- if (payload === void 0) return now + 1440 * 6e4;
4005
- const exp = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"))?.exp;
4006
- if (typeof exp === "number" && Number.isFinite(exp)) return exp * 1e3;
4007
- } catch {}
4008
- return now + 1440 * 6e4;
4009
- }
4010
- function abortableDelay$2(ms, signal) {
4011
- if (signal.aborted) return Promise.reject(signal.reason);
4012
- return new Promise((resolve$2, reject) => {
4013
- const timer = setTimeout(() => {
4014
- signal.removeEventListener("abort", aborted);
4015
- resolve$2();
4016
- }, ms);
4017
- function aborted() {
4018
- clearTimeout(timer);
4019
- reject(signal.reason);
4020
- }
4021
- signal.addEventListener("abort", aborted, { once: true });
4022
- });
4023
- }
4024
- var CursorAuth = class {
4025
- loginTask;
4026
- error;
4027
- refreshTask;
4028
- constructor(credentials, http$1 = fetch, now = Date.now) {
4029
- this.credentials = credentials;
4030
- this.http = http$1;
4031
- this.now = now;
4032
- }
4033
- async read() {
4034
- return parseCredential((await this.credentials.resolve(CURSOR_CREDENTIAL_REF))?.value);
4035
- }
4036
- async status() {
4037
- const current$1 = await this.read();
4038
- return {
4039
- authenticated: current$1 !== void 0,
4040
- busy: this.loginTask !== void 0,
4041
- ...current$1 === void 0 ? {} : { expiresAt: current$1.expires },
4042
- ...this.error === void 0 ? {} : { error: this.error }
4043
- };
4044
- }
4045
- async accessToken({ signal } = {}) {
4046
- signal?.throwIfAborted();
4047
- const current$1 = await this.read();
4048
- if (current$1 === void 0) throw new Error("Cursor is not signed in");
4049
- if (current$1.expires - this.now() > REFRESH_AHEAD_MS$1) return current$1.access;
4050
- if (current$1.refresh.length === 0) throw new Error("Cursor sign-in needs to be renewed");
4051
- if (this.refreshTask === void 0) {
4052
- const pending = this.refresh(current$1, signal);
4053
- this.refreshTask = pending;
4054
- pending.finally(() => {
4055
- if (this.refreshTask === pending) this.refreshTask = void 0;
4056
- }).catch(() => void 0);
4057
- }
4058
- return (await this.refreshTask).access;
4059
- }
4060
- async refresh(current$1, signal) {
4061
- const response = await this.http(`${API_ORIGIN}/auth/exchange_user_api_key`, {
4062
- method: "POST",
4063
- redirect: "error",
4064
- headers: {
4065
- authorization: `Bearer ${current$1.refresh}`,
4066
- accept: "application/json",
4067
- "content-type": "application/json"
4068
- },
4069
- body: "{}",
4070
- ...signal === void 0 ? {} : { signal }
4071
- });
4072
- if (!response.ok) throw new Error(`Cursor token refresh failed (HTTP ${response.status})`);
4073
- const body = await parseProviderJson(response, "Cursor token refresh");
4074
- if (typeof body.accessToken !== "string" || body.accessToken.length === 0) throw new Error("Cursor token refresh returned no access token");
4075
- const next = {
4076
- type: "oauth",
4077
- access: body.accessToken,
4078
- refresh: typeof body.refreshToken === "string" && body.refreshToken.length > 0 ? body.refreshToken : current$1.refresh,
4079
- expires: tokenExpiry(body.accessToken, this.now())
4080
- };
4081
- const latest = await this.read();
4082
- if (latest === void 0) throw new Error("Cursor signed out during token refresh");
4083
- if (latest.refresh !== current$1.refresh) return latest;
4084
- await this.credentials.set(CURSOR_CREDENTIAL_REF, JSON.stringify(next));
4085
- return next;
4086
- }
4087
- /** Return a Cursor browser URL immediately; polling continues on the host. */
4088
- async login() {
4089
- await this.cancel();
4090
- this.error = void 0;
4091
- const verifier = randomBytes(96).toString("base64url");
4092
- const challenge = createHash("sha256").update(verifier).digest("base64url");
4093
- const uuid = randomUUID();
4094
- const url = new URL("https://cursor.com/loginDeepControl");
4095
- url.search = new URLSearchParams({
4096
- challenge,
4097
- uuid,
4098
- mode: "login",
4099
- redirectTarget: "cli"
4100
- }).toString();
4101
- const abort = new AbortController();
4102
- this.loginTask = {
4103
- abort,
4104
- promise: this.pollLogin(uuid, verifier, abort.signal).catch((error) => {
4105
- if (!abort.signal.aborted) this.error = error instanceof Error ? error.message : String(error);
4106
- }).finally(() => {
4107
- if (this.loginTask?.abort === abort) this.loginTask = void 0;
4108
- })
4109
- };
4110
- return { authorizeUrl: url.href };
4111
- }
4112
- async pollLogin(uuid, verifier, signal) {
4113
- let delay = 1e3;
4114
- for (let attempt = 0; attempt < 150; attempt++) {
4115
- await abortableDelay$2(delay, signal);
4116
- const url = `${API_ORIGIN}/auth/poll?uuid=${encodeURIComponent(uuid)}&verifier=${encodeURIComponent(verifier)}`;
4117
- const response = await this.http(url, {
4118
- headers: { accept: "application/json" },
4119
- redirect: "error",
4120
- signal
4121
- });
4122
- if (response.status === 404) {
4123
- delay = Math.min(Math.ceil(delay * 1.2), 1e4);
4124
- continue;
4125
- }
4126
- if (!response.ok) throw new Error(`Cursor login failed (HTTP ${response.status})`);
4127
- const body = await parseProviderJson(response, "Cursor login poll");
4128
- signal.throwIfAborted();
4129
- if (typeof body.accessToken !== "string" || body.accessToken.length === 0) throw new Error("Cursor login returned no access token");
4130
- if (typeof body.refreshToken !== "string" || body.refreshToken.length === 0) throw new Error("Cursor login returned no refresh token");
4131
- const value = {
4132
- type: "oauth",
4133
- access: body.accessToken,
4134
- refresh: body.refreshToken,
4135
- expires: tokenExpiry(body.accessToken, this.now())
4136
- };
4137
- await this.credentials.set(CURSOR_CREDENTIAL_REF, JSON.stringify(value));
4138
- return;
4139
- }
4140
- throw new Error("Cursor login timed out");
4141
- }
4142
- async cancel() {
4143
- const task = this.loginTask;
4144
- task?.abort.abort(/* @__PURE__ */ new Error("Cursor login cancelled"));
4145
- await task?.promise;
4146
- if (this.loginTask === task) this.loginTask = void 0;
4147
- this.error = void 0;
4148
- }
4149
- async logout() {
4150
- await this.cancel();
4151
- await this.credentials.unset(CURSOR_CREDENTIAL_REF);
4152
- }
4153
- };
4154
-
4155
- //#endregion
4156
- //#region src/providers/cursor-usage.ts
4157
- const ORIGIN = "https://cursor.com";
4158
- function record$2(value) {
4159
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4160
- }
4161
- function number(value) {
4162
- if (typeof value === "number" && Number.isFinite(value)) return value;
4163
- if (typeof value === "string" && value.trim() !== "") {
4164
- const parsed = Number(value);
4165
- if (Number.isFinite(parsed)) return parsed;
4166
- }
4167
- }
4168
- /** Cursor's WorkOS user id from the access JWT, without the identity prefix. */
4169
- function cursorUserId(token) {
4170
- try {
4171
- const parts = token.split(".");
4172
- if (parts.length !== 3 || !parts[1]) return void 0;
4173
- const raw = record$2(JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8")))?.sub;
4174
- if (typeof raw !== "string" || raw.length === 0) return void 0;
4175
- const id = raw.includes("|") ? raw.split("|").at(-1) : raw;
4176
- return id !== void 0 && /^[A-Za-z0-9_-]+$/.test(id) ? id : void 0;
4177
- } catch {
4178
- return;
4179
- }
4180
- }
4181
- async function dashboardJson(url, cookie, http$1, signal) {
4182
- const response = await http$1(url, {
4183
- headers: {
4184
- accept: "application/json",
4185
- cookie,
4186
- origin: ORIGIN,
4187
- referer: `${ORIGIN}/dashboard`
4188
- },
4189
- ...signal === void 0 ? {} : { signal }
4190
- });
4191
- if (!response.ok) throw new Error(`Cursor usage endpoint returned HTTP ${response.status}`);
4192
- return parseProviderJson(response, "Cursor usage");
4193
- }
4194
- function legacyRequestWindow(body, resetsAt) {
4195
- const usage = record$2(body);
4196
- if (usage === void 0) return void 0;
4197
- const gpt4 = record$2(usage["gpt-4"]);
4198
- const candidate = gpt4 !== void 0 && number(gpt4.numRequests) !== void 0 ? gpt4 : Object.values(usage).map(record$2).filter((value) => value !== void 0).sort((a, b) => (number(b.maxRequestUsage) ?? 0) - (number(a.maxRequestUsage) ?? 0))[0];
4199
- const used = number(candidate?.numRequests);
4200
- const limit = number(candidate?.maxRequestUsage);
4201
- if (used === void 0 || limit === void 0 || limit <= 0) return void 0;
4202
- return {
4203
- kind: "other",
4204
- scope: "Included requests",
4205
- usedPercent: Math.max(0, Math.min(100, used / limit * 100)),
4206
- ...resetsAt === void 0 ? {} : { resetsAt }
4207
- };
4208
- }
4209
- /**
4210
- * Read Cursor's individual dashboard quota using an OAuth access token.
4211
- * The dashboard cookie is request-local; no token enters ProviderUsage.
4212
- */
4213
- async function fetchCursorUsage(accessToken, http$1 = fetch, signal) {
4214
- const userId = cursorUserId(accessToken);
4215
- if (userId === void 0) throw new Error("Cursor session token has no usable user identity");
4216
- const cookie = `WorkosCursorSessionToken=${userId}::${accessToken}`;
4217
- const [summaryResult, legacyResult] = await Promise.allSettled([dashboardJson(`${ORIGIN}/api/usage-summary`, cookie, http$1, signal), dashboardJson(`${ORIGIN}/api/usage?user=${encodeURIComponent(userId)}`, cookie, http$1, signal)]);
4218
- if (summaryResult.status === "rejected" && legacyResult.status === "rejected") throw summaryResult.reason instanceof Error ? summaryResult.reason : /* @__PURE__ */ new Error("Cursor usage is unavailable");
4219
- const summary = summaryResult.status === "fulfilled" ? record$2(summaryResult.value) : void 0;
4220
- const plan = record$2(record$2(summary?.individualUsage)?.plan) ?? record$2(record$2(summary?.teamUsage)?.plan);
4221
- const resetValue = summary?.billingCycleEnd;
4222
- const parsedReset = typeof resetValue === "string" ? Date.parse(resetValue) : NaN;
4223
- const resetsAt = Number.isFinite(parsedReset) ? parsedReset : void 0;
4224
- const startValue = summary?.billingCycleStart;
4225
- const parsedStart = typeof startValue === "string" ? Date.parse(startValue) : NaN;
4226
- const timing = {
4227
- ...resetsAt === void 0 ? {} : { resetsAt },
4228
- ...Number.isFinite(parsedStart) ? { startsAt: parsedStart } : {}
4229
- };
4230
- const windows = [];
4231
- for (const [field, scope] of [
4232
- ["totalPercentUsed", "Included"],
4233
- ["autoPercentUsed", "Cursor Models"],
4234
- ["apiPercentUsed", "Other Models"]
4235
- ]) {
4236
- const usedPercent = number(plan?.[field]);
4237
- if (usedPercent === void 0 || usedPercent < 0 || usedPercent > 100) continue;
4238
- windows.push({
4239
- kind: "other",
4240
- scope,
4241
- usedPercent,
4242
- ...timing
4243
- });
4244
- }
4245
- const legacy = legacyRequestWindow(legacyResult.status === "fulfilled" ? legacyResult.value : void 0, resetsAt);
4246
- if (legacy !== void 0) windows.push({
4247
- ...legacy,
4248
- ...timing
4249
- });
4250
- if (windows.length === 0 && summary?.isUnlimited !== true) throw new Error("Cursor usage response has no quota windows");
4251
- const membershipType = summary?.membershipType;
4252
- return {
4253
- supported: true,
4254
- windows,
4255
- ...typeof membershipType === "string" && membershipType.length > 0 ? { plan: membershipType } : {}
4256
- };
4257
- }
4258
-
4259
- //#endregion
4260
- //#region vendor/cursor/proto.js
4261
- /**
4262
- * Minimal protobuf wire-format helpers for the Cursor Agent protocol.
4263
- *
4264
- * Only the subset of protobuf needed by `dsh-cursor-subscription` is
4265
- * implemented: varints, length-delimited fields, fixed64/double, and nested
4266
- * messages. Map fields are encoded as repeated entry messages, exactly like
4267
- * the official protobuf runtime.
4268
- *
4269
- * @module dsh-cursor-subscription/proto
4270
- */
4271
- /** Encode one unsigned varint into a Uint8Array. */
4272
- function varintEncode(value) {
4273
- const out = [];
4274
- let n = Math.trunc(value);
4275
- while (n > 127) {
4276
- out.push(n & 127 | 128);
4277
- n = Math.floor(n / 128);
4278
- }
4279
- out.push(n);
4280
- return Uint8Array.from(out);
4281
- }
4282
- /** Combine byte arrays. */
4283
- function concatBytes(parts) {
4284
- let total = 0;
4285
- for (const part of parts) total += part.length;
4286
- const out = new Uint8Array(total);
4287
- let offset = 0;
4288
- for (const part of parts) {
4289
- out.set(part, offset);
4290
- offset += part.length;
4291
- }
4292
- return out;
4293
- }
4294
- /** A streaming protobuf message writer. */
4295
- var Writer = class {
4296
- constructor() {
4297
- this.parts = [];
4298
- }
4299
- /** Append a raw field tag. */
4300
- tag(field, wireType) {
4301
- this.parts.push(varintEncode(field << 3 | wireType));
4302
- return this;
4303
- }
4304
- /** Append a length-delimited payload. */
4305
- bytes(field, data) {
4306
- this.tag(field, 2);
4307
- this.parts.push(varintEncode(data.length));
4308
- this.parts.push(data);
4309
- return this;
4310
- }
4311
- /** Append a UTF-8 string field. */
4312
- string(field, value) {
4313
- return this.bytes(field, new TextEncoder().encode(value));
4314
- }
4315
- /** Append a nested message field. */
4316
- message(field, inner) {
4317
- return this.bytes(field, inner);
4318
- }
4319
- /** Append a varint field (uint32/int32/bool/enum). */
4320
- varint(field, value) {
4321
- this.tag(field, 0);
4322
- this.parts.push(varintEncode(Math.trunc(value)));
4323
- return this;
4324
- }
4325
- /** Append a double (fixed64, little-endian) field. */
4326
- double(field, value) {
4327
- this.tag(field, 1);
4328
- const buffer = /* @__PURE__ */ new ArrayBuffer(8);
4329
- new DataView(buffer).setFloat64(0, value, true);
4330
- this.parts.push(new Uint8Array(buffer));
4331
- return this;
4332
- }
4333
- finish() {
4334
- return concatBytes(this.parts);
4335
- }
4336
- };
4337
- /** A streaming protobuf message reader. */
4338
- var Reader = class {
4339
- constructor(bytes) {
4340
- this.data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
4341
- this.pos = 0;
4342
- }
4343
- get done() {
4344
- return this.pos >= this.data.length;
4345
- }
4346
- varint() {
4347
- let result = 0;
4348
- let shift = 0;
4349
- while (true) {
4350
- if (this.pos >= this.data.length) throw new Error("protobuf: truncated varint");
4351
- const byte = this.data[this.pos++];
4352
- result += (byte & 127) * 2 ** shift;
4353
- if ((byte & 128) === 0) break;
4354
- shift += 7;
4355
- if (shift > 63) throw new Error("protobuf: varint too long");
4356
- }
4357
- return result;
4358
- }
4359
- /** Read the tag and return { field, wireType }. */
4360
- tag() {
4361
- const raw = this.varint();
4362
- return {
4363
- field: Math.floor(raw / 8),
4364
- wireType: raw % 8
4365
- };
4366
- }
4367
- bytes() {
4368
- const length = this.varint();
4369
- if (this.pos + length > this.data.length) throw new Error("protobuf: truncated bytes");
4370
- const out = this.data.subarray(this.pos, this.pos + length);
4371
- this.pos += length;
4372
- return out;
4373
- }
4374
- string() {
4375
- return new TextDecoder().decode(this.bytes());
4376
- }
4377
- double() {
4378
- if (this.pos + 8 > this.data.length) throw new Error("protobuf: truncated double");
4379
- const view = new DataView(this.data.buffer, this.data.byteOffset + this.pos, 8);
4380
- this.pos += 8;
4381
- return view.getFloat64(0, true);
4382
- }
4383
- /** Skip a field of the given wire type (length-delimited or varint or fixed64). */
4384
- skip(wireType) {
4385
- if (wireType === 0) this.varint();
4386
- else if (wireType === 1) {
4387
- if (this.pos + 8 > this.bytes.length) throw new Error("protobuf: truncated fixed64");
4388
- this.pos += 8;
4389
- } else if (wireType === 2) this.bytes();
4390
- else if (wireType === 5) {
4391
- if (this.pos + 4 > this.bytes.length) throw new Error("protobuf: truncated fixed32");
4392
- this.pos += 4;
4393
- } else throw new Error(`protobuf: unsupported wire type ${wireType}`);
4394
- }
4395
- };
4396
- /**
4397
- * Encode a JSON value as `google.protobuf.Value` (the wire format Cursor uses
4398
- * for MCP tool input schemas and MCP argument values).
4399
- */
4400
- function encodeValue(value) {
4401
- const writer = new Writer();
4402
- if (value === null || value === void 0) writer.varint(1, 0);
4403
- else if (typeof value === "boolean") writer.varint(4, value ? 1 : 0);
4404
- else if (typeof value === "number") writer.double(2, value);
4405
- else if (typeof value === "string") writer.string(3, value);
4406
- else if (Array.isArray(value)) {
4407
- const list = new Writer();
4408
- for (const item of value) list.message(1, encodeValue(item));
4409
- writer.message(6, list.finish());
4410
- } else if (typeof value === "object") {
4411
- const struct = new Writer();
4412
- for (const [key, item] of Object.entries(value)) {
4413
- const entry = new Writer();
4414
- entry.string(1, key);
4415
- entry.message(2, encodeValue(item));
4416
- struct.message(1, entry.finish());
4417
- }
4418
- writer.message(5, struct.finish());
4419
- } else throw new Error(`cannot encode ${typeof value} as google.protobuf.Value`);
4420
- return writer.finish();
4421
- }
4422
- /**
4423
- * Decode `google.protobuf.Value` bytes back into a JSON value.
4424
- * @param {Uint8Array} bytes - serialized google.protobuf.Value message.
4425
- * @returns {unknown} the decoded JSON value.
4426
- */
4427
- function decodeValue(bytes) {
4428
- const reader = new Reader(bytes);
4429
- while (!reader.done) {
4430
- const { field, wireType } = reader.tag();
4431
- if (field === 1 && wireType === 0) {
4432
- reader.varint();
4433
- return null;
4434
- }
4435
- if (field === 2 && wireType === 1) return reader.double();
4436
- if (field === 3 && wireType === 2) return reader.string();
4437
- if (field === 4 && wireType === 0) return reader.varint() !== 0;
4438
- if (field === 5 && wireType === 2) return decodeStruct(reader.bytes());
4439
- if (field === 6 && wireType === 2) return decodeList(reader.bytes());
4440
- reader.skip(wireType);
4441
- }
4442
- return null;
4443
- }
4444
- function decodeStruct(bytes) {
4445
- const reader = new Reader(bytes);
4446
- const result = {};
4447
- while (!reader.done) {
4448
- const { field, wireType } = reader.tag();
4449
- if (field === 1 && wireType === 2) {
4450
- const entry = new Reader(reader.bytes());
4451
- let key = "";
4452
- let value = null;
4453
- while (!entry.done) {
4454
- const tag = entry.tag();
4455
- if (tag.field === 1 && tag.wireType === 2) key = entry.string();
4456
- else if (tag.field === 2 && tag.wireType === 2) value = decodeValue(entry.bytes());
4457
- else entry.skip(tag.wireType);
4458
- }
4459
- result[key] = value;
4460
- } else reader.skip(wireType);
4461
- }
4462
- return result;
4463
- }
4464
- function decodeList(bytes) {
4465
- const reader = new Reader(bytes);
4466
- const result = [];
4467
- while (!reader.done) {
4468
- const { field, wireType } = reader.tag();
4469
- if (field === 1 && wireType === 2) result.push(decodeValue(reader.bytes()));
4470
- else reader.skip(wireType);
4471
- }
4472
- return result;
4473
- }
4474
-
4475
- //#endregion
4476
- //#region vendor/cursor/index.js
4477
- /**
4478
- * Read this package's own version from the manifest beside `lib/`.
4479
- * @returns the manifest version, or undefined when it cannot be read.
4480
- */
4481
- function readVersion() {
4482
- try {
4483
- return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
4484
- } catch {
4485
- return;
4486
- }
4487
- }
4488
- /** Build version the settings panel prints; undefined when the manifest is unreadable. */
4489
- const VERSION = readVersion();
4490
- /**
4491
- * DSH web replaces the ambient `fetch` with an MCP `createFetchWithInit`
4492
- * wrapper whose `baseFetch` is undefined in the host process, so any consumer
4493
- * of the global `fetch` throws `baseFetch is not a function`. Cursor usage /
4494
- * refresh / model discovery go through HTTP, so this module uses Node's own
4495
- * `http`/`https` to talk to the upstream directly instead of trusting the
4496
- * host-injected `fetch`. It exposes the small Fetch subset these callers use.
4497
- */
4498
- async function nativeFetch(url, init = {}) {
4499
- const target = new URL(url);
4500
- const client = target.protocol === "https:" ? https : http;
4501
- const method = (init.method ?? "GET").toUpperCase();
4502
- const body = init.body === void 0 ? void 0 : isBytes(init.body) ? Buffer.from(init.body) : String(init.body);
4503
- const request = (u$1, redirect) => new Promise((resolve$2, reject) => {
4504
- const req = client.request(u$1, {
4505
- method,
4506
- headers: sanitizeHeaders(init.headers),
4507
- signal: init.signal
4508
- }, (res) => {
4509
- const chunks = [];
4510
- res.on("data", (chunk) => chunks.push(chunk));
4511
- res.on("end", () => {
4512
- const data = Buffer.concat(chunks);
4513
- const status = res.statusCode ?? 0;
4514
- if ((redirect === "error" || redirect === "manual") && status >= 300 && status < 400) {
4515
- reject(/* @__PURE__ */ new Error(`HTTP ${status} from ${u$1.origin}${u$1.pathname}`));
4516
- return;
4517
- }
4518
- const location = res.headers.location;
4519
- if ((redirect ?? "follow") === "follow" && location && status >= 300 && status < 400) {
4520
- resolve$2({ redirect: new URL(location, u$1) });
4521
- return;
4522
- }
4523
- resolve$2({ response: makeResponse(status, res, data) });
4524
- });
4525
- });
4526
- req.on("error", reject);
4527
- if (body !== void 0) req.write(body);
4528
- req.end();
4529
- });
4530
- let u = target;
4531
- for (let hops = 0;; hops++) {
4532
- const result = await request(u, init.redirect);
4533
- if (result.redirect !== void 0) {
4534
- if (hops >= 6) throw new Error(`too many redirects from ${target.href}`);
4535
- u = result.redirect;
4536
- continue;
4537
- }
4538
- return result.response;
4539
- }
4540
- }
4541
- function isBytes(value) {
4542
- return value instanceof Uint8Array || typeof value !== "string" && ArrayBuffer.isView(value);
4543
- }
4544
- function sanitizeHeaders(headers) {
4545
- if (headers === void 0) return void 0;
4546
- const out = {};
4547
- for (const [key, value] of Object.entries(headers)) {
4548
- if (value === void 0) continue;
4549
- out[key] = value;
4550
- }
4551
- return out;
4552
- }
4553
- function makeResponse(status, res, data) {
4554
- const text = async () => data.toString("utf8");
4555
- return {
4556
- ok: status >= 200 && status < 300,
4557
- status,
4558
- statusText: res.statusMessage ?? "",
4559
- headers: { get: (name$1) => res.headers[String(name$1).toLowerCase()] ?? null },
4560
- text: async () => text(),
4561
- json: async () => JSON.parse(await text()),
4562
- arrayBuffer: async () => data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)
4563
- };
4564
- }
4565
- const CREDENTIAL_REF = credentialRef("CURSOR_SUBSCRIPTION_OAUTH");
4566
- /** Connection registry options: accept the account channel from loopback Hosts only. */
4567
- const LOOPBACK_RPC = Object.freeze({ authority: "loopback" });
4568
- const CURSOR_BASE_URL = "https://api2.cursor.sh";
4569
- const CURSOR_RUN_PATH = "/agent.v1.AgentService/Run";
4570
- const CURSOR_MODELS_PATH = "/agent.v1.AgentService/GetUsableModels";
4571
- /** Cursor dashboard usage endpoints (authenticated with the session cookie). */
4572
- const USAGE_API_ORIGIN = "https://cursor.com";
4573
- const USAGE_URL = `${USAGE_API_ORIGIN}/api/usage`;
4574
- const USAGE_SUMMARY_URL = `${USAGE_API_ORIGIN}/api/usage-summary`;
4575
- const USAGE_TEAMS_URL = `${USAGE_API_ORIGIN}/api/dashboard/teams`;
4576
- const USAGE_AGGREGATED_URL = `${USAGE_API_ORIGIN}/api/dashboard/get-aggregated-usage-events`;
4577
- const USAGE_TTL_MS$1 = 60 * 1e3;
4578
- /** Client version reported to the Agent service; bump when Cursor requires it. */
4579
- const CURSOR_CLIENT_VERSION = "cli-2026.02.13-41ac335";
4580
- /** Keep-alive heartbeats while an agent run is streaming. */
4581
- const HEARTBEAT_INTERVAL_MS = 5e3;
4582
- /** How long an agent run may stay completely silent before we abort. */
4583
- const STREAM_IDLE_TIMEOUT_MS = 120 * 1e3;
4584
- /** How long a run may keep sending heartbeats without any real content. */
4585
- const STREAM_PROGRESS_TIMEOUT_MS = 60 * 1e3;
4586
- /** Retain checkpoints/live tool bridges for inactive DSH sessions. */
4587
- const SESSION_STATE_TTL_MS = 1800 * 1e3;
4588
- /** Stop one Cursor Run before an unconstrained agent can loop forever. */
4589
- const MAX_TOOL_ROUNDS = 200;
4590
- /**
4591
- * How long a tool-call step keeps reading after `mcpArgs` for the frames that
4592
- * close the burst (sibling calls, blob writes, checkpoint). Sibling calls of
4593
- * one burst arrive within ~100 ms of each other.
4594
- */
4595
- const TOOL_CALL_SETTLE_MS = 500;
4596
- const DEFAULT_RETRY_COUNT = 0;
4597
- const DEFAULT_RETRY_INTERVAL_MS = 1e3;
4598
- const DEFAULT_RETRY_HTTP_STATUS_CODES = Object.freeze([
4599
- 408,
4600
- 425,
4601
- 429,
4602
- 500,
4603
- 502,
4604
- 503,
4605
- 504
4606
- ]);
4607
- /**
4608
- * Rebuild the Cursor conversation from the DSH history on every step instead of
4609
- * resuming the conversation Cursor keeps server-side.
4610
- *
4611
- * A plugin whose transport re-sends its own message history — as the standard
4612
- * function-calling ones do — hands the model the same explicit history at every
4613
- * step. Delegating that history to Cursor's server instead is what a Cursor turn
4614
- * was observed to re-derive from: the step re-stated its goal, re-read its goal
4615
- * and rewrote its plan while its tool calls kept succeeding. Off by default: a
4616
- * rebuild re-sends the whole transcript each step, and Cursor has no
4617
- * provider-side compaction to bound it.
4618
- */
4619
- const DEFAULT_REPLAY_HISTORY_EACH_STEP = false;
4620
- /**
4621
- * Editable runtime settings. Every field is `.volatile()`: the Loader parses a
4622
- * volatile field into a live reference that is updated in place when the
4623
- * settings page saves, which is what carries a saved value into a Cursor run
4624
- * that is already in flight. On DSH 0.1.7 a Config without a volatile field
4625
- * produces no settings section at all, so none of these may lose the marker.
4626
- */
4627
- const Config$1 = z.object({
4628
- maxToolRounds: z.number().step(1).min(1).max(1e3).default(MAX_TOOL_ROUNDS).volatile(),
4629
- retryCount: z.number().step(1).min(0).max(10).default(DEFAULT_RETRY_COUNT).volatile(),
4630
- retryIntervalMs: z.number().step(1).min(0).max(3e5).default(DEFAULT_RETRY_INTERVAL_MS).volatile(),
4631
- retryHttpStatusCodes: z.array(z.number().step(1).min(400).max(599)).default([...DEFAULT_RETRY_HTTP_STATUS_CODES]).volatile(),
4632
- replayHistoryEachStep: z.boolean().default(DEFAULT_REPLAY_HISTORY_EACH_STEP).volatile()
4633
- });
4634
- /** Refresh the access token this early before its JWT expiry. */
4635
- const REFRESH_AHEAD_MS = 300 * 1e3;
4636
- /** Default access-token lifetime when the JWT has no usable exp claim. */
4637
- const DEFAULT_TOKEN_LIFETIME_MS = 1440 * 60 * 1e3;
4638
- const DEFAULT_CONTEXT_WINDOW = 2e5;
4639
- const DEFAULT_MAX_TOKENS = 64e3;
4640
- /**
4641
- * Read one Config field. DSH 0.1.7 delivers a `.volatile()` field to the plugin
4642
- * as a reference whose `get()` returns the value the settings page last saved —
4643
- * the Loader keeps that one object and writes into it, so reading it per call
4644
- * is what makes a saved edit visible. Tests and the frozen fallback pass
4645
- * ordinary values, so both shapes are accepted here.
4646
- * @param value Config field as delivered by the Loader, or a plain value.
4647
- * @returns The current plain value.
4648
- */
4649
- function readConfigField(value) {
4650
- return typeof value?.get === "function" ? value.get() : value;
4651
- }
4652
- function resolveCursorSettings(input = {}) {
4653
- const maxToolRounds = readConfigField(input.maxToolRounds) ?? MAX_TOOL_ROUNDS;
4654
- const retryCount = readConfigField(input.retryCount) ?? DEFAULT_RETRY_COUNT;
4655
- const retryIntervalMs = readConfigField(input.retryIntervalMs) ?? DEFAULT_RETRY_INTERVAL_MS;
4656
- const retryHttpStatusCodes = readConfigField(input.retryHttpStatusCodes) ?? DEFAULT_RETRY_HTTP_STATUS_CODES;
4657
- const replayHistoryEachStep = readConfigField(input.replayHistoryEachStep) ?? DEFAULT_REPLAY_HISTORY_EACH_STEP;
4658
- if (!Number.isSafeInteger(maxToolRounds) || maxToolRounds < 1 || maxToolRounds > 1e3) throw new Error("cursor-subscription: maxToolRounds must be an integer between 1 and 1000");
4659
- if (!Number.isSafeInteger(retryCount) || retryCount < 0 || retryCount > 10) throw new Error("cursor-subscription: retryCount must be an integer between 0 and 10");
4660
- if (!Number.isSafeInteger(retryIntervalMs) || retryIntervalMs < 0 || retryIntervalMs > 3e5) throw new Error("cursor-subscription: retryIntervalMs must be an integer between 0 and 300000");
4661
- if (!Array.isArray(retryHttpStatusCodes) || retryHttpStatusCodes.some((status) => !Number.isSafeInteger(status) || status < 400 || status > 599)) throw new Error("cursor-subscription: retryHttpStatusCodes must contain only HTTP status integers from 400 to 599");
4662
- if (new Set(retryHttpStatusCodes).size !== retryHttpStatusCodes.length) throw new Error("cursor-subscription: retryHttpStatusCodes must not contain duplicates");
4663
- if (typeof replayHistoryEachStep !== "boolean") throw new Error("cursor-subscription: replayHistoryEachStep must be a boolean");
4664
- return Object.freeze({
4665
- maxToolRounds,
4666
- retryCount,
4667
- retryIntervalMs,
4668
- retryHttpStatusCodes: Object.freeze([...retryHttpStatusCodes]),
4669
- replayHistoryEachStep
4670
- });
4671
- }
4672
- function shouldRetryHttpStatus(status, retriesUsed, settings) {
4673
- return retriesUsed < settings.retryCount && settings.retryHttpStatusCodes.includes(status);
4674
- }
4675
- function isSuccessfulAgentResponse(status, contentType) {
4676
- return status === 200 && /^application\/connect\+proto(?:\s*;|\s*$)/i.test(contentType ?? "");
4677
- }
4678
- function abortableDelay$1(ms, signal) {
4679
- if (signal.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Cursor retry aborted"));
4680
- if (ms === 0) return Promise.resolve();
4681
- return new Promise((resolve$2, reject) => {
4682
- const timer = setTimeout(done, ms);
4683
- timer.unref?.();
4684
- function done() {
4685
- signal.removeEventListener("abort", aborted);
4686
- resolve$2();
4687
- }
4688
- function aborted() {
4689
- clearTimeout(timer);
4690
- signal.removeEventListener("abort", aborted);
4691
- reject(signal.reason ?? /* @__PURE__ */ new Error("Cursor retry aborted"));
4692
- }
4693
- signal.addEventListener("abort", aborted, { once: true });
4694
- });
4695
- }
4696
- /**
4697
- * Cursor's `GetUsableModels` answers in its own order (newest family first),
4698
- * and that order moves between releases, so the picker would reshuffle on its
4699
- * own. Sort by display name instead, with the id as a tiebreak.
4700
- *
4701
- * The collation is pinned to `"en"` so one build lists the same order on every
4702
- * host locale, and `numeric` so families keep their natural sequence
4703
- * (`GPT-5` before `GPT-10`, `Composer 2` after `Composer 1.5`).
4704
- */
4705
- const MODEL_NAME_COLLATOR = new Intl.Collator("en", {
4706
- numeric: true,
4707
- sensitivity: "base"
4708
- });
4709
- /** Order model entries by name then id, without mutating the input. */
4710
- function sortModelsByName(models) {
4711
- return [...models].sort((a, b) => {
4712
- const byName = MODEL_NAME_COLLATOR.compare(a?.name ?? "", b?.name ?? "");
4713
- if (byName !== 0) return byName;
4714
- return MODEL_NAME_COLLATOR.compare(a?.id ?? "", b?.id ?? "");
4715
- });
4716
- }
4717
- const textEncoder = new TextEncoder();
4718
- function bytesOf(value) {
4719
- return value instanceof Uint8Array ? value : new Uint8Array(value);
4720
- }
4721
- function encodeUserMessage({ text, messageId }) {
4722
- const writer = new Writer();
4723
- if (typeof text === "string" && text.length > 0) writer.string(1, text);
4724
- if (typeof messageId === "string" && messageId.length > 0) writer.string(2, messageId);
4725
- return writer.finish();
4726
- }
4727
- /** ModelDetails { model_id = 1, display_model_id = 3, display_name = 4 } */
4728
- function encodeModelDetails(modelId) {
4729
- const writer = new Writer();
4730
- writer.string(1, modelId);
4731
- writer.string(3, modelId);
4732
- writer.string(4, modelId);
4733
- return writer.finish();
4734
- }
4735
- /**
4736
- * ConversationHistoryImageContent { data = 1, mime_type = 2 }
4737
- * wrapped as ConversationHistoryMessage.user.content[].image
4738
- */
4739
- function encodeConversationHistoryImage({ data, mimeType }) {
4740
- const image = new Writer().string(1, data);
4741
- if (mimeType) image.string(2, mimeType);
4742
- const content = new Writer().message(2, image.finish()).finish();
4743
- const user = new Writer().message(1, content).finish();
4744
- return new Writer().message(1, user).finish();
4745
- }
4746
- /** ConversationHistory { messages = 1 } with one history message per image. */
4747
- function encodeConversationHistory(images) {
4748
- const writer = new Writer();
4749
- for (const image of images) writer.message(1, encodeConversationHistoryImage(image));
4750
- return writer.finish();
4751
- }
4752
- /** ConversationAction { user_message_action = 1 } → UserMessageAction { user_message = 1, conversation_history = 7 } */
4753
- function encodeUserMessageAction(userBytes, images = []) {
4754
- const inner = new Writer().message(1, userBytes);
4755
- if (images.length > 0) inner.message(7, encodeConversationHistory(images));
4756
- return new Writer().message(1, inner.finish()).finish();
4757
- }
4758
- /** ConversationStateStructure — the durable conversation payload. */
4759
- function encodeConversationState({ rootPromptBlobIds, turns }) {
4760
- const writer = new Writer();
4761
- for (const id of rootPromptBlobIds) writer.bytes(1, id);
4762
- for (const turn of turns) writer.bytes(8, turn);
4763
- return writer.finish();
4764
- }
4765
- /** AgentRunRequest { conversation_state=1, action=2, model_details=3, conversation_id=5 } */
4766
- function encodeRunRequest({ conversationState, action, modelDetails, conversationId }) {
4767
- const writer = new Writer();
4768
- writer.message(1, conversationState);
4769
- writer.message(2, action);
4770
- writer.message(3, modelDetails);
4771
- if (conversationId) writer.string(5, conversationId);
4772
- return writer.finish();
4773
- }
4774
- /** AgentClientMessage { run_request = 1 } */
4775
- function encodeRunMessage(runRequestBytes) {
4776
- return new Writer().message(1, runRequestBytes).finish();
4777
- }
4778
- /** AgentClientMessage { client_heartbeat = 7 } */
4779
- function encodeHeartbeat() {
4780
- return new Writer().message(7, new Uint8Array(0)).finish();
4781
- }
4782
- /** KvClientMessage { id=1, get_blob_result=2 { blob_data=1 } } */
4783
- function encodeGetBlobResult(id, blobData) {
4784
- const inner = blobData === void 0 ? new Uint8Array(0) : blobData;
4785
- const result = new Writer().bytes(1, inner).finish();
4786
- const writer = new Writer();
4787
- writer.varint(1, id);
4788
- writer.message(2, result);
4789
- return writer.finish();
4790
- }
4791
- /** AgentClientMessage { kv_client_message = 3 } */
4792
- function encodeKvClientMessage(kvBytes) {
4793
- return new Writer().message(3, kvBytes).finish();
4794
- }
4795
- /** KvClientMessage { id=1, set_blob_result=3 } — acknowledge a server blob write. */
4796
- function encodeSetBlobResult(id) {
4797
- const writer = new Writer();
4798
- writer.varint(1, id);
4799
- writer.message(3, new Uint8Array(0));
4800
- return writer.finish();
4801
- }
4802
- /** McpResult.success with one text content item. */
4803
- function encodeMcpResult({ content, isError = false }) {
4804
- return encodeMcpResultSuccess(String(content ?? ""), isError);
4805
- }
4806
- /** ExecClientMessage { id=1, exec_id=15, message=... } */
4807
- function encodeExecClientMessage(id, execId, messageField, messageBytes) {
4808
- const writer = new Writer();
4809
- writer.varint(1, id);
4810
- if (typeof execId === "string" && execId.length > 0) writer.string(15, execId);
4811
- if (messageBytes !== void 0) writer.message(messageField, messageBytes);
4812
- return writer.finish();
4813
- }
4814
- /** AgentClientMessage { exec_client_message = 2 } */
4815
- function encodeExecClientMessageEnvelope(execBytes) {
4816
- return new Writer().message(2, execBytes).finish();
4817
- }
4818
- /** RequestContextResult { success=1 { request_context=1 } } with tools. */
4819
- function encodeRequestContextResult(tools) {
4820
- const context = new Writer();
4821
- for (const tool of tools) context.message(7, tool);
4822
- const success = new Writer().message(1, context.finish()).finish();
4823
- return new Writer().message(1, success).finish();
4824
- }
4825
- /** McpToolDefinition { name=1, description=2, input_schema=3, provider_identifier=4, tool_name=5 } */
4826
- function encodeMcpToolDefinition({ name: name$1, description, inputSchema, providerIdentifier, toolName }) {
4827
- const writer = new Writer();
4828
- writer.string(1, name$1);
4829
- writer.string(2, description ?? "");
4830
- writer.bytes(3, bytesOf(inputSchema));
4831
- writer.string(4, providerIdentifier ?? "dsh-cursor-subscription");
4832
- writer.string(5, toolName ?? name$1);
4833
- return writer.finish();
4834
- }
4835
- /** McpTextContent { text = 1 } */
4836
- function encodeMcpTextContent(text) {
4837
- return new Writer().string(1, text).finish();
4838
- }
4839
- /** McpToolResultContentItem { text = 1 } */
4840
- function encodeMcpToolResultContentItem(text) {
4841
- return new Writer().message(1, encodeMcpTextContent(text)).finish();
4842
- }
4843
- /** McpSuccess { content=1, is_error=2 } */
4844
- function encodeMcpSuccess(text, isError) {
4845
- const writer = new Writer();
4846
- writer.message(1, encodeMcpToolResultContentItem(text));
4847
- writer.varint(2, isError ? 1 : 0);
4848
- return writer.finish();
4849
- }
4850
- /** McpResult { success=1 | error=2 } */
4851
- function encodeMcpResultSuccess(text, isError) {
4852
- return new Writer().message(1, encodeMcpSuccess(text, isError)).finish();
4853
- }
4854
- function encodeMcpError(error) {
4855
- return new Writer().message(2, new Writer().string(1, error).finish()).finish();
4856
- }
4857
- /** ReadResult { rejected=3 { path=1, reason=2 } } */
4858
- function encodeReadRejected(path, reason) {
4859
- const rejected = new Writer().string(1, path ?? "").string(2, reason).finish();
4860
- return new Writer().message(3, rejected).finish();
4861
- }
4862
- /** LsResult { rejected=3 { path=1, reason=2 } } */
4863
- function encodeLsRejected(path, reason) {
4864
- const rejected = new Writer().string(1, path ?? "").string(2, reason).finish();
4865
- return new Writer().message(3, rejected).finish();
4866
- }
4867
- /** GrepResult { error=2 { error=1 } } */
4868
- function encodeGrepError(error) {
4869
- return new Writer().message(2, new Writer().string(1, error).finish()).finish();
4870
- }
4871
- /** WriteResult { rejected = 6 } (oneof: success=1, permission_denied=3, no_space=4, error=5, rejected=6) */
4872
- function encodeWriteRejected(path, reason) {
4873
- const rejected = new Writer().string(1, path ?? "").string(2, reason).finish();
4874
- return new Writer().message(6, rejected).finish();
4875
- }
4876
- /** DeleteResult { rejected = 6 } (oneof: success=1, file_not_found=2, not_file=3, permission_denied=4, file_busy=5, rejected=6) */
4877
- function encodeDeleteRejected(path, reason) {
4878
- const rejected = new Writer().string(1, path ?? "").string(2, reason).finish();
4879
- return new Writer().message(6, rejected).finish();
4880
- }
4881
- /** ShellRejected { command=1, working_directory=2, reason=3, is_readonly=4 } */
4882
- function encodeShellRejected(command, workingDirectory, reason) {
4883
- const writer = new Writer();
4884
- writer.string(1, command ?? "");
4885
- writer.string(2, workingDirectory ?? "");
4886
- writer.string(3, reason);
4887
- writer.varint(4, 0);
4888
- return writer.finish();
4889
- }
4890
- /** ShellResult { rejected = 4 } (oneof: success=1, failure=2, timeout=3, rejected=4) */
4891
- function encodeShellRejectedResult(command, workingDirectory, reason) {
4892
- return new Writer().message(4, encodeShellRejected(command, workingDirectory, reason)).finish();
4893
- }
4894
- /**
4895
- * ShellStream { rejected = 5 } — the reply type for `shellStreamArgs` execs
4896
- * (streaming shell), carried by ExecClientMessage.shell_stream (field 14).
4897
- */
4898
- function encodeShellStreamRejected(command, workingDirectory, reason) {
4899
- return new Writer().message(5, encodeShellRejected(command, workingDirectory, reason)).finish();
4900
- }
4901
- /** BackgroundShellSpawnResult { rejected = 3 } (oneof: success=1, error=2, rejected=3, permission_denied=4) */
4902
- function encodeBackgroundShellRejectedResult(command, workingDirectory, reason) {
4903
- return new Writer().message(3, encodeShellRejected(command, workingDirectory, reason)).finish();
4904
- }
4905
- /** FetchResult { error=2 { url=1, error=2 } } */
4906
- function encodeFetchError(url, error) {
4907
- const inner = new Writer().string(1, url ?? "").string(2, error).finish();
4908
- return new Writer().message(2, inner).finish();
4909
- }
4910
- /** WriteShellStdinResult { error=2 { error=1 } } */
4911
- function encodeWriteShellStdinError(error) {
4912
- return new Writer().message(2, new Writer().string(1, error).finish()).finish();
4913
- }
4914
- /** DiagnosticsResult { success = 1 } — empty success keeps the exec well-formed. */
4915
- function encodeDiagnosticsResult() {
4916
- return new Writer().message(1, new Uint8Array(0)).finish();
4917
- }
4918
- new TextDecoder();
4919
- /** AgentServerMessage { interaction_update=1, exec_server_message=2, kv_server_message=4, ... } */
4920
- function decodeAgentServerMessage(bytes) {
4921
- const reader = new Reader(bytes);
4922
- while (!reader.done) {
4923
- const { field, wireType } = reader.tag();
4924
- if (wireType !== 2) {
4925
- reader.skip(wireType);
4926
- continue;
4927
- }
4928
- const payload = reader.bytes();
4929
- if (field === 1) return {
4930
- case: "interactionUpdate",
4931
- value: decodeInteractionUpdate(payload)
4932
- };
4933
- if (field === 2) return {
4934
- case: "execServerMessage",
4935
- value: decodeExecServerMessage(payload)
4936
- };
4937
- if (field === 3) return {
4938
- case: "conversationCheckpointUpdate",
4939
- value: payload
4940
- };
4941
- if (field === 4) return {
4942
- case: "kvServerMessage",
4943
- value: decodeKvServerMessage(payload)
4944
- };
4945
- }
4946
- return {
4947
- case: "unknown",
4948
- value: void 0
4949
- };
4950
- }
4951
- /** Field numbers inside one protobuf message; used to describe frames we do not decode. */
4952
- function messageFields(bytes) {
4953
- const reader = new Reader(bytes);
4954
- const fields = [];
4955
- while (!reader.done) {
4956
- const { field, wireType } = reader.tag();
4957
- fields.push(field);
4958
- reader.skip(wireType);
4959
- }
4960
- return fields.join(",");
4961
- }
4962
- /** Field numbers of a nested message (`wanted` inside `bytes`). */
4963
- function nestedMessageFields(bytes, wanted) {
4964
- const reader = new Reader(bytes);
4965
- while (!reader.done) {
4966
- const { field, wireType } = reader.tag();
4967
- if (wireType !== 2) {
4968
- reader.skip(wireType);
4969
- continue;
4970
- }
4971
- const payload = reader.bytes();
4972
- if (field === wanted) return messageFields(payload);
4973
- }
4974
- return "";
4975
- }
4976
- /**
4977
- * One-line description of a server frame for the stall trace. Undecoded
4978
- * frames report their protobuf field numbers so a server message the adapter
4979
- * ignores is still visible when a run goes silent.
4980
- */
4981
- function describeServerFrame(frame) {
4982
- if ((frame.flags & CONNECT_END_STREAM_FLAG) !== 0) return `endstream(${Buffer.from(frame.payload).toString("utf8").replace(/\s+/g, " ").slice(0, 60)})`;
4983
- try {
4984
- const message = decodeAgentServerMessage(frame.payload);
4985
- if (message.case === "interactionUpdate") {
4986
- const update = message.value;
4987
- if (update.type === "textDelta") return `text(${update.text.length})`;
4988
- if (update.type === "thinkingDelta") return `thinking(${update.text.length})`;
4989
- if (update.type === "tokenDelta") return `tokens(${update.tokens})`;
4990
- if (update.type === "partialToolCall") return "partialToolCall";
4991
- if (update.type === "unknown") return `interaction#${nestedMessageFields(frame.payload, 1)}`;
4992
- return update.type;
4993
- }
4994
- if (message.case === "conversationCheckpointUpdate") return `checkpoint(${message.value.length}B)`;
4995
- if (message.case === "kvServerMessage") return `kv:${message.value.case}`;
4996
- if (message.case === "execServerMessage") {
4997
- const exec = message.value;
4998
- return exec.case === "unknown" ? `exec:unknown#${exec.field ?? "?"}[${(exec.seen ?? []).join(",")}]` : `exec:${exec.case}`;
4999
- }
5000
- return `message#${messageFields(frame.payload)}`;
5001
- } catch (error) {
5002
- return `undecodable(${(error instanceof Error ? error.message : String(error)).slice(0, 40)})`;
5003
- }
5004
- }
5005
- /**
5006
- * Prompt tokens Cursor reports for the conversation it is holding.
5007
- *
5008
- * `ConversationStateStructure.token_details.used_tokens` rides in every
5009
- * `conversation_checkpoint_update`, so the run that resumes that checkpoint can
5010
- * report the real prompt size. Without it the adapter reported a placeholder
5011
- * zero, and the context gauge showed nothing however large the model's
5012
- * conversation had grown.
5013
- *
5014
- * @param checkpoint Raw checkpoint bytes from the last update, if any.
5015
- * @returns Used tokens, or undefined when the field is absent or unreadable.
5016
- */
5017
- function decodeCheckpointUsedTokens(checkpoint) {
5018
- if (checkpoint === void 0) return void 0;
5019
- try {
5020
- const reader = new Reader(checkpoint);
5021
- while (!reader.done) {
5022
- const { field, wireType } = reader.tag();
5023
- if (field === 5 && wireType === 2) {
5024
- const details = new Reader(reader.bytes());
5025
- while (!details.done) {
5026
- const tag = details.tag();
5027
- if (tag.field === 1 && tag.wireType === 0) return details.varint();
5028
- details.skip(tag.wireType);
5029
- }
5030
- return;
5031
- }
5032
- reader.skip(wireType);
5033
- }
5034
- } catch {}
5035
- }
5036
- /** InteractionUpdate { text_delta=1, thinking_delta=4, token_delta=8, turn_ended=14, ... } */
5037
- function decodeInteractionUpdate(bytes) {
5038
- const reader = new Reader(bytes);
5039
- while (!reader.done) {
5040
- const { field, wireType } = reader.tag();
5041
- if (wireType !== 2) {
5042
- reader.skip(wireType);
5043
- continue;
5044
- }
5045
- const payload = reader.bytes();
5046
- if (field === 1) return {
5047
- type: "textDelta",
5048
- text: decodeTextDelta(payload)
5049
- };
5050
- if (field === 4) return {
5051
- type: "thinkingDelta",
5052
- text: decodeTextDelta(payload)
5053
- };
5054
- if (field === 5) return {
5055
- type: "thinkingCompleted",
5056
- durationMs: decodeThinkingDuration(payload)
5057
- };
5058
- if (field === 8) return {
5059
- type: "tokenDelta",
5060
- tokens: decodeTokenDelta(payload)
5061
- };
5062
- if (field === 14) return { type: "turnEnded" };
5063
- if (field === 2) return { type: "toolCallStarted" };
5064
- if (field === 3) return { type: "toolCallCompleted" };
5065
- if (field === 7) return {
5066
- type: "partialToolCall",
5067
- ...decodePartialToolCall(payload)
5068
- };
5069
- if (field === 15) return { type: "toolCallDelta" };
5070
- if (field === 13) return { type: "heartbeat" };
5071
- }
5072
- return { type: "unknown" };
5073
- }
5074
- function decodeThinkingDuration(bytes) {
5075
- const reader = new Reader(bytes);
5076
- while (!reader.done) {
5077
- const { field, wireType } = reader.tag();
5078
- if (field === 1 && wireType === 0) return reader.varint();
5079
- reader.skip(wireType);
5080
- }
5081
- return 0;
5082
- }
5083
- function decodeTextDelta(bytes) {
5084
- const reader = new Reader(bytes);
5085
- while (!reader.done) {
5086
- const { field, wireType } = reader.tag();
5087
- if (field === 1 && wireType === 2) return reader.string();
5088
- reader.skip(wireType);
5089
- }
5090
- return "";
5091
- }
5092
- function decodeTokenDelta(bytes) {
5093
- const reader = new Reader(bytes);
5094
- while (!reader.done) {
5095
- const { field, wireType } = reader.tag();
5096
- if (field === 1 && wireType === 0) return reader.varint();
5097
- reader.skip(wireType);
5098
- }
5099
- return 0;
5100
- }
5101
- /** PartialToolCallUpdate { call_id=1, args_text_delta=3 } */
5102
- function decodePartialToolCall(bytes) {
5103
- const reader = new Reader(bytes);
5104
- let callId = "";
5105
- let argsTextDelta = "";
5106
- while (!reader.done) {
5107
- const { field, wireType } = reader.tag();
5108
- if (field === 1 && wireType === 2) callId = reader.string();
5109
- else if (field === 3 && wireType === 2) argsTextDelta = reader.string();
5110
- else reader.skip(wireType);
5111
- }
5112
- return {
5113
- callId,
5114
- argsTextDelta
5115
- };
5116
- }
5117
- /** KvServerMessage { id=1, get_blob_args=2 { blob_id=1 } | set_blob_args=3 { blob_id=1, blob_data=2 } } */
5118
- function decodeKvServerMessage(bytes) {
5119
- const reader = new Reader(bytes);
5120
- let id = 0;
5121
- let blobId;
5122
- while (!reader.done) {
5123
- const { field, wireType } = reader.tag();
5124
- if (field === 1 && wireType === 0) id = reader.varint();
5125
- else if (field === 2 && wireType === 2) blobId = decodeBlobId(reader.bytes());
5126
- else if (field === 3 && wireType === 2) return {
5127
- id,
5128
- case: "setBlobArgs",
5129
- ...decodeSetBlobArgs(reader.bytes())
5130
- };
5131
- else reader.skip(wireType);
5132
- }
5133
- return blobId === void 0 ? {
5134
- id,
5135
- case: "unknown"
5136
- } : {
5137
- id,
5138
- case: "getBlobArgs",
5139
- blobId
5140
- };
5141
- }
5142
- /** SetBlobArgs { blob_id=1, blob_data=2 } */
5143
- function decodeSetBlobArgs(bytes) {
5144
- const reader = new Reader(bytes);
5145
- let blobId;
5146
- let blobData;
5147
- while (!reader.done) {
5148
- const { field, wireType } = reader.tag();
5149
- if (field === 1 && wireType === 2) blobId = reader.bytes();
5150
- else if (field === 2 && wireType === 2) blobData = reader.bytes();
5151
- else reader.skip(wireType);
5152
- }
5153
- return {
5154
- blobId,
5155
- blobData
5156
- };
5157
- }
5158
- function decodeBlobId(bytes) {
5159
- const reader = new Reader(bytes);
5160
- while (!reader.done) {
5161
- const { field, wireType } = reader.tag();
5162
- if (field === 1 && wireType === 2) return reader.bytes();
5163
- reader.skip(wireType);
5164
- }
5165
- }
5166
- /**
5167
- * Field number of `ExecServerMessage.span_context`. It is a plain (non-oneof)
5168
- * field, so a message carrying only ids, a span context, and an exec variant
5169
- * this build does not know must not mistake it for the exec itself.
5170
- */
5171
- const EXEC_SPAN_CONTEXT_FIELD = 19;
5172
- /** ExecServerMessage { id=1, exec_id=15, request_context_args=10, mcp_args=11, ... } */
5173
- function decodeExecServerMessage(bytes) {
5174
- const reader = new Reader(bytes);
5175
- let id = 0;
5176
- let execId = "";
5177
- let unknownField;
5178
- const seen = [];
5179
- while (!reader.done) {
5180
- const { field, wireType } = reader.tag();
5181
- seen.push(field);
5182
- if (field === 1 && wireType === 0) id = reader.varint();
5183
- else if (field === 15 && wireType === 2) execId = reader.string();
5184
- else if (wireType === 2) {
5185
- const payload = reader.bytes();
5186
- if (field === 10) return {
5187
- id,
5188
- execId,
5189
- case: "requestContextArgs"
5190
- };
5191
- if (field === 11) return {
5192
- id,
5193
- execId,
5194
- case: "mcpArgs",
5195
- args: decodeMcpArgs(payload)
5196
- };
5197
- if (field === 2) return {
5198
- id,
5199
- execId,
5200
- case: "shellArgs",
5201
- path: decodeSinglePathArg(payload)
5202
- };
5203
- if (field === 3) return {
5204
- id,
5205
- execId,
5206
- case: "writeArgs",
5207
- path: decodeSinglePathArg(payload)
5208
- };
5209
- if (field === 4) return {
5210
- id,
5211
- execId,
5212
- case: "deleteArgs",
5213
- path: decodeSinglePathArg(payload)
5214
- };
5215
- if (field === 5) return {
5216
- id,
5217
- execId,
5218
- case: "grepArgs"
5219
- };
5220
- if (field === 7) return {
5221
- id,
5222
- execId,
5223
- case: "readArgs",
5224
- path: decodeReadArgsPath(payload)
5225
- };
5226
- if (field === 8) return {
5227
- id,
5228
- execId,
5229
- case: "lsArgs",
5230
- path: decodeSinglePathArg(payload)
5231
- };
5232
- if (field === 9) return {
5233
- id,
5234
- execId,
5235
- case: "diagnosticsArgs"
5236
- };
5237
- if (field === 14) return {
5238
- id,
5239
- execId,
5240
- case: "shellStreamArgs",
5241
- path: decodeSinglePathArg(payload)
5242
- };
5243
- if (field === 16) return {
5244
- id,
5245
- execId,
5246
- case: "backgroundShellSpawnArgs"
5247
- };
5248
- if (field === 17) return {
5249
- id,
5250
- execId,
5251
- case: "listMcpResourcesExecArgs"
5252
- };
5253
- if (field === 18) return {
5254
- id,
5255
- execId,
5256
- case: "readMcpResourceExecArgs"
5257
- };
5258
- if (field === 20) return {
5259
- id,
5260
- execId,
5261
- case: "fetchArgs",
5262
- url: decodeFetchUrl(payload)
5263
- };
5264
- if (field === 21) return {
5265
- id,
5266
- execId,
5267
- case: "recordScreenArgs"
5268
- };
5269
- if (field === 22) return {
5270
- id,
5271
- execId,
5272
- case: "computerUseArgs"
5273
- };
5274
- if (field === 23) return {
5275
- id,
5276
- execId,
5277
- case: "writeShellStdinArgs"
5278
- };
5279
- if (field !== EXEC_SPAN_CONTEXT_FIELD) unknownField ??= field;
5280
- } else reader.skip(wireType);
5281
- }
5282
- return {
5283
- id,
5284
- execId,
5285
- case: "unknown",
5286
- field: unknownField,
5287
- seen
5288
- };
5289
- }
5290
- function decodeSinglePathArg(bytes) {
5291
- const reader = new Reader(bytes);
5292
- while (!reader.done) {
5293
- const { field, wireType } = reader.tag();
5294
- if (field === 1 && wireType === 2) return reader.string();
5295
- reader.skip(wireType);
5296
- }
5297
- return "";
5298
- }
5299
- function decodeReadArgsPath(bytes) {
5300
- return decodeSinglePathArg(bytes);
5301
- }
5302
- function decodeFetchUrl(bytes) {
5303
- const reader = new Reader(bytes);
5304
- while (!reader.done) {
5305
- const { field, wireType } = reader.tag();
5306
- if (field === 1 && wireType === 2) return reader.string();
5307
- reader.skip(wireType);
5308
- }
5309
- return "";
5310
- }
5311
- /** McpArgs { name=1, args=2 (map<string,bytes>), tool_call_id=3, provider_identifier=4, tool_name=5 } */
5312
- function decodeMcpArgs(bytes) {
5313
- const reader = new Reader(bytes);
5314
- let name$1 = "";
5315
- let toolCallId = "";
5316
- let providerIdentifier = "";
5317
- let toolName = "";
5318
- const args = {};
5319
- while (!reader.done) {
5320
- const { field, wireType } = reader.tag();
5321
- if (field === 1 && wireType === 2) name$1 = reader.string();
5322
- else if (field === 2 && wireType === 2) {
5323
- const entry = new Reader(reader.bytes());
5324
- let key = "";
5325
- let value;
5326
- while (!entry.done) {
5327
- const tag = entry.tag();
5328
- if (tag.field === 1 && tag.wireType === 2) key = entry.string();
5329
- else if (tag.field === 2 && tag.wireType === 2) value = entry.bytes();
5330
- else entry.skip(tag.wireType);
5331
- }
5332
- if (key) args[key] = value;
5333
- } else if (field === 3 && wireType === 2) toolCallId = reader.string();
5334
- else if (field === 4 && wireType === 2) providerIdentifier = reader.string();
5335
- else if (field === 5 && wireType === 2) toolName = reader.string();
5336
- else reader.skip(wireType);
5337
- }
5338
- return {
5339
- name: name$1,
5340
- toolCallId,
5341
- providerIdentifier,
5342
- toolName,
5343
- args
5344
- };
5345
- }
5346
- /** GetUsableModelsResponse { models = 1: repeated ModelDetails } */
5347
- function decodeUsableModels(bytes) {
5348
- const reader = new Reader(bytes);
5349
- const models = [];
5350
- while (!reader.done) {
5351
- const { field, wireType } = reader.tag();
5352
- if (field === 1 && wireType === 2) {
5353
- const model = decodeModelDetails(reader.bytes());
5354
- if (model?.id) models.push(model);
5355
- } else reader.skip(wireType);
5356
- }
5357
- return models;
5358
- }
5359
- /** ModelDetails { model_id=1, display_model_id=3, display_name=4, display_name_short=5, aliases=6 } */
5360
- function decodeModelDetails(bytes) {
5361
- const reader = new Reader(bytes);
5362
- const model = {
5363
- id: "",
5364
- name: ""
5365
- };
5366
- while (!reader.done) {
5367
- const { field, wireType } = reader.tag();
5368
- if (wireType === 2) if (field === 1) model.id = reader.string();
5369
- else if (field === 3) model.displayModelId = reader.string();
5370
- else if (field === 4) model.name = reader.string();
5371
- else if (field === 5) model.displayNameShort = reader.string();
5372
- else if (field === 6) reader.bytes();
5373
- else reader.bytes();
5374
- else reader.skip(wireType);
5375
- }
5376
- return model;
5377
- }
5378
- /** Connect frame: [1-byte flags][4-byte big-endian length][payload]. */
5379
- function frameEncode(payload, flags = 0) {
5380
- const out = new Uint8Array(5 + payload.length);
5381
- const view = new DataView(out.buffer);
5382
- view.setUint8(0, flags);
5383
- view.setUint32(1, payload.length, false);
5384
- out.set(payload, 5);
5385
- return out;
5386
- }
5387
- const CONNECT_END_STREAM_FLAG = 2;
5388
- const CONNECT_COMPRESSED_FLAG = 1;
5389
- /** Upper bound for a decompressed Connect frame; guards against zip bombs. */
5390
- const MAX_CONNECT_FRAME_BYTES = 64 * 1024 * 1024;
5391
- /**
5392
- * Incremental Connect frame parser fed by response chunks.
5393
- * Yields { flags, payload } objects. Frames flagged compressed (bit 0) are
5394
- * gzip-decompressed here so consumers only ever see plain payloads.
5395
- */
5396
- var ConnectFrameReader = class {
5397
- constructor() {
5398
- this.buffer = new Uint8Array(0);
5399
- this.frames = [];
5400
- this.waiters = [];
5401
- this.ended = false;
5402
- this.paused = false;
5403
- this.error = void 0;
5404
- }
5405
- push(chunk) {
5406
- const combined = new Uint8Array(this.buffer.length + chunk.length);
5407
- combined.set(this.buffer);
5408
- combined.set(chunk, this.buffer.length);
5409
- this.buffer = combined;
5410
- while (this.buffer.length >= 5) {
5411
- const view = new DataView(this.buffer.buffer, this.buffer.byteOffset, 5);
5412
- const flags = view.getUint8(0);
5413
- const length = view.getUint32(1, false);
5414
- if (this.buffer.length < 5 + length) break;
5415
- const payload = this.buffer.slice(5, 5 + length);
5416
- this.buffer = this.buffer.slice(5 + length);
5417
- let data = payload;
5418
- if ((flags & CONNECT_COMPRESSED_FLAG) !== 0) try {
5419
- data = gunzipSync(Buffer.from(payload), { maxOutputLength: MAX_CONNECT_FRAME_BYTES });
5420
- } catch (error) {
5421
- this.fail(new Error("Cursor sent an unreadable compressed frame", { cause: error }));
5422
- return;
5423
- }
5424
- this.#enqueue({
5425
- flags: flags & ~CONNECT_COMPRESSED_FLAG,
5426
- payload: data
5427
- });
5428
- }
5429
- }
5430
- #enqueue(frame) {
5431
- if (this.waiters.length > 0) {
5432
- this.waiters.shift().resolve(frame);
5433
- return;
5434
- }
5435
- this.frames.push(frame);
5436
- }
5437
- finish() {
5438
- this.ended = true;
5439
- for (const waiter of this.waiters.splice(0)) waiter.resolve(void 0);
5440
- }
5441
- /**
5442
- * Stop blocking for new frames without ending the stream: buffered frames
5443
- * are still returned, and `resume()` restores normal waiting. A Cursor run
5444
- * stays alive across DSH steps, so a reader that is paused to end one step
5445
- * must accept frames again when the next step continues the same run.
5446
- */
5447
- pause() {
5448
- this.paused = true;
5449
- for (const waiter of this.waiters.splice(0)) waiter.resolve(void 0);
5450
- }
5451
- resume() {
5452
- this.paused = false;
5453
- }
5454
- fail(error) {
5455
- this.error = error;
5456
- this.ended = true;
5457
- for (const waiter of this.waiters.splice(0)) waiter.reject(error);
5458
- }
5459
- async next() {
5460
- if (this.frames.length > 0) return this.frames.shift();
5461
- if (this.paused) return void 0;
5462
- if (this.ended) {
5463
- if (this.error !== void 0) throw this.error;
5464
- return;
5465
- }
5466
- return new Promise((resolve$2, reject) => this.waiters.push({
5467
- resolve: resolve$2,
5468
- reject
5469
- }));
5470
- }
5471
- };
5472
- const AGENT_HEADERS = {
5473
- "content-type": "application/connect+proto",
5474
- "connect-protocol-version": "1",
5475
- "connect-accept-encoding": "gzip",
5476
- te: "trailers",
5477
- "x-ghost-mode": "true",
5478
- "x-cursor-client-version": CURSOR_CLIENT_VERSION,
5479
- "x-cursor-client-type": "cli"
5480
- };
5481
- /**
5482
- * One bidirectional agent run over HTTP/2. Writes Connect-framed
5483
- * `AgentClientMessage`s and surfaces `AgentServerMessage`s through the frame
5484
- * reader. Heartbeats keep the run alive.
5485
- */
5486
- var AgentRun = class {
5487
- constructor(accessToken, options = {}) {
5488
- this.accessToken = accessToken;
5489
- this.baseUrl = options.baseUrl ?? CURSOR_BASE_URL;
5490
- this.session = void 0;
5491
- this.frames = new ConnectFrameReader();
5492
- this.responseStatus = void 0;
5493
- this.responseContentType = void 0;
5494
- this.trailers = {};
5495
- this.finished = false;
5496
- }
5497
- async start() {
5498
- const url = new URL(CURSOR_RUN_PATH, this.baseUrl);
5499
- const headers = {
5500
- ":method": "POST",
5501
- ":path": url.pathname,
5502
- authorization: `Bearer ${this.accessToken}`,
5503
- ...AGENT_HEADERS
5504
- };
5505
- this.responsePromise = new Promise((resolve$2, reject) => {
5506
- this.resolveResponse = resolve$2;
5507
- this.rejectResponse = reject;
5508
- });
5509
- this.responsePromise.catch(() => {});
5510
- this.session = http2.connect(this.baseUrl);
5511
- this.stream = this.session.request(headers);
5512
- this.stream.on("response", (headers$1) => {
5513
- this.responseStatus = Number(headers$1[":status"]);
5514
- this.responseContentType = typeof headers$1["content-type"] === "string" ? headers$1["content-type"] : void 0;
5515
- this.#resolveResponse(this.responseStatus);
5516
- });
5517
- this.stream.on("data", (chunk) => this.frames.push(Buffer.from(chunk)));
5518
- this.stream.on("trailers", (trailers) => {
5519
- this.trailers = trailers;
5520
- });
5521
- this.stream.on("end", () => this.frames.finish());
5522
- this.stream.on("close", () => {
5523
- if (this.responseStatus === void 0) this.#rejectResponse(/* @__PURE__ */ new Error("Cursor HTTP stream closed before response"));
5524
- });
5525
- this.stream.on("error", (error) => {
5526
- this.#rejectResponse(error);
5527
- this.frames.fail(error);
5528
- });
5529
- this.session.on("error", (error) => {
5530
- this.#rejectResponse(error);
5531
- this.frames.fail(error);
5532
- });
5533
- }
5534
- #resolveResponse(status) {
5535
- const resolve$2 = this.resolveResponse;
5536
- this.resolveResponse = void 0;
5537
- this.rejectResponse = void 0;
5538
- resolve$2?.(status);
5539
- }
5540
- #rejectResponse(error) {
5541
- const reject = this.rejectResponse;
5542
- this.resolveResponse = void 0;
5543
- this.rejectResponse = void 0;
5544
- reject?.(error);
5545
- }
5546
- waitForResponse(timeoutMs) {
5547
- if (this.responsePromise === void 0) throw new Error("Cursor AgentRun has not started");
5548
- if (timeoutMs === void 0) return this.responsePromise;
5549
- return new Promise((resolve$2, reject) => {
5550
- const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Cursor HTTP response timeout")), timeoutMs);
5551
- timer.unref?.();
5552
- this.responsePromise.then(resolve$2, reject).finally(() => clearTimeout(timer));
5553
- });
5554
- }
5555
- write(payload) {
5556
- if (this.finished || this.stream === void 0 || this.stream.destroyed) return false;
5557
- try {
5558
- this.stream.write(Buffer.from(payload));
5559
- return true;
5560
- } catch {
5561
- return false;
5562
- }
5563
- }
5564
- writeMessage(bytes) {
5565
- return this.write(frameEncode(bytes));
5566
- }
5567
- startHeartbeat() {
5568
- this.heartbeat = setInterval(() => {
5569
- this.writeMessage(encodeHeartbeat());
5570
- }, HEARTBEAT_INTERVAL_MS);
5571
- this.heartbeat.unref?.();
5572
- }
5573
- /** Fail the run with a terminal error and tear down the connection. */
5574
- abort(error) {
5575
- if (this.finished) return;
5576
- this.#rejectResponse(error);
5577
- this.frames.fail(error);
5578
- this.close();
5579
- }
5580
- close() {
5581
- if (this.finished) return;
5582
- this.#rejectResponse(/* @__PURE__ */ new Error("Cursor AgentRun closed before response"));
5583
- this.finished = true;
5584
- if (this.heartbeat !== void 0) clearInterval(this.heartbeat);
5585
- if (!this.frames.ended) this.frames.finish();
5586
- try {
5587
- this.stream?.close();
5588
- } catch {}
5589
- try {
5590
- this.session.destroy();
5591
- } catch {}
5592
- }
5593
- };
5594
- function sha256(bytes) {
5595
- return createHash("sha256").update(bytes).digest();
5596
- }
5597
- function collectImageAttachments(options) {
5598
- const refs = /* @__PURE__ */ new Map();
5599
- const visit = (blocks) => {
5600
- for (const block of blocks ?? []) if (block.type === "image") refs.set(block.attachment.attachmentId, block.attachment);
5601
- else if (block.type === "tool-result") visit(block.content);
5602
- };
5603
- for (const message of options.messages ?? []) if (message.role === "user") visit(message.content);
5604
- return [...refs.values()];
5605
- }
5606
- async function prepareCursorImages(options, attachments) {
5607
- const refs = collectImageAttachments(options);
5608
- if (refs.length === 0) return [];
5609
- if (attachments === void 0) throw new LlmError("Cursor image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
5610
- return (await Promise.all(refs.map((ref) => attachments.readImage(ref, options.signal)))).map(({ ref, data }) => ({
5611
- data: Buffer.from(data).toString("base64"),
5612
- mimeType: ref.mediaType
5613
- }));
5614
- }
5615
- function flattenBlocks(content) {
5616
- let text = "";
5617
- for (const block of content ?? []) if (block.type === "text") text += block.text;
5618
- else if (block.type === "tool-result") {
5619
- const callId = block.toolCallId ? ` for ${block.toolCallId}` : "";
5620
- text += `\n[TOOL RESULT${callId}]\n${flattenBlocks(block.content)}`;
5621
- }
5622
- return text;
5623
- }
5624
- function sanitizeReplayedAssistantText(text) {
5625
- let omitted = false;
5626
- const sanitized = String(text ?? "").replace(/<tool_call\b[\s\S]*?<\/tool_call>/gi, () => {
5627
- omitted = true;
5628
- return "";
5629
- }).replace(/<\/?invoke>|<\|eos\|>/gi, "").trim();
5630
- if (!omitted) return sanitized;
5631
- return `${sanitized}${sanitized ? "\n" : ""}[Previous textual tool-call markup omitted from replay.]`;
5632
- }
5633
- /**
5634
- * How much of a replayed tool request to keep. Enough for the arguments that are
5635
- * state — a plan (`todo_write`) is its list, and a repeated `grep` is avoided
5636
- * only if the model can see what it already asked for — while a `write` of a
5637
- * whole file stays bounded.
5638
- */
5639
- const REPLAYED_CALL_CHARS = 600;
5640
- /** Replay assistant history without teaching models to imitate XML tool tags. */
5641
- function renderColdStartAssistant(content) {
5642
- const parts = [];
5643
- for (const block of content ?? []) if (block.type === "text") {
5644
- const text = sanitizeReplayedAssistantText(block.text);
5645
- if (text) parts.push(text);
5646
- } else if (block.type === "tool-call") {
5647
- const args = typeof block.arguments === "string" ? block.arguments : JSON.stringify(block.arguments ?? "");
5648
- const shown = args.length > REPLAYED_CALL_CHARS ? `${args.slice(0, REPLAYED_CALL_CHARS)}…` : args;
5649
- parts.push(`[Previous tool request: ${block.name}${shown.length > 0 ? ` ${shown}` : ""}]`);
5650
- }
5651
- return parts.join("\n").trim();
5652
- }
5653
- function actionBoundary(messages) {
5654
- for (let index = messages.length - 1; index >= 0; index--) if (messages[index].role === "assistant") return index + 1;
5655
- return 0;
5656
- }
5657
- /** Combine the current DSH turn's adjacent user/context messages into one action. */
5658
- function currentActionText(options) {
5659
- const messages = options.messages ?? [];
5660
- const boundary = actionBoundary(messages);
5661
- const parts = [];
5662
- for (let index = boundary; index < messages.length; index++) {
5663
- const message = messages[index];
5664
- if (message.role !== "user") continue;
5665
- const text = flattenBlocks(message.content).trim();
5666
- if (text.length > 0) parts.push(text);
5667
- }
5668
- if (parts.length > 0) return parts.join("\n\n");
5669
- for (let index = messages.length - 1; index >= 0; index--) {
5670
- if (messages[index].role !== "user") continue;
5671
- const text = flattenBlocks(messages[index].content).trim();
5672
- if (text.length > 0) return text;
5673
- }
5674
- return "";
5675
- }
5676
- /**
5677
- * Markers this harness leaves in a result when the sandbox refused the work.
5678
- *
5679
- * They call for different answers, and telling them apart matters: the
5680
- * `SetNamedSecurityInfoW` failure is the ACL grant the confinement itself needs
5681
- * and cannot get, so *no* narrower mode can start and only a mode without that
5682
- * grant can run the call. `[sandbox: ... denied under <mode> mode]` is a
5683
- * refusal from a sandbox that did start, where one step wider may well suffice.
5684
- * Naming the narrowest mode first for the ACL failure is what turned a single
5685
- * refusal into a retry loop: the model asked for `workspace-write` — the mode it
5686
- * was already in — and failed the same way 101 times in one session.
5687
- */
5688
- const SANDBOX_ACL_FAILURE = /SetNamedSecurityInfoW failed/i;
5689
- const SANDBOX_POLICY_DENIAL = /\[sandbox:[^\]]*denied/i;
5690
- /**
5691
- * DSH's escalation rule, restated where the refusal lands.
5692
- *
5693
- * Every provider gets this rule — retry the denied call once with
5694
- * `sandbox_permissions` and a justification, which raises an approval prompt —
5695
- * but only the `pwsh` description spells it out, and the refusal result carries
5696
- * nothing at all. A model that does not recall the rule from a tool description
5697
- * reads the refusal as "this tool does not work here" and stops using it, which
5698
- * is how a Cursor turn ends up saying local tools are unavailable instead of
5699
- * asking the user for permission.
5700
- */
5701
- const SANDBOX_ACL_HINT = "\n\n[cursor] The DSH sandbox cannot start in this workspace — its own ACL grant fails (`SetNamedSecurityInfoW`) — so no narrower mode can succeed. Retry this exact call once with sandbox_permissions=\"danger-full-access\" and a one-sentence justification; DSH then asks the user to approve it. Do not retry it in a narrower mode, and if the retry is refused, stop and report the refusal instead of trying again.";
5702
- const SANDBOX_POLICY_HINT = "\n\n[cursor] The DSH sandbox refused this call, and that is recoverable: retry this exact call once with sandbox_permissions set to the narrowest wider mode that suffices (workspace-write, or danger-full-access when that is not enough) plus a one-sentence justification. DSH then asks the user to approve it. If the retry is refused as well, stop and report it instead of trying another mode.";
5703
- /** Whether one result is the harness refusing, rather than the tool failing. */
5704
- function isSandboxRefusal(content) {
5705
- return SANDBOX_ACL_FAILURE.test(content) || SANDBOX_POLICY_DENIAL.test(content);
5706
- }
5707
- /**
5708
- * Result text as the model should read it: a sandbox refusal gains the rule that
5709
- * turns it back into a request the user can approve.
5710
- * @param content Result text on its way to the model.
5711
- * @returns The text, with the escalation rule a refusal calls for appended.
5712
- */
5713
- function withSandboxHint(content) {
5714
- if (content.includes("sandbox_permissions")) return content;
5715
- if (SANDBOX_ACL_FAILURE.test(content)) return `${content}${SANDBOX_ACL_HINT}`;
5716
- if (SANDBOX_POLICY_DENIAL.test(content)) return `${content}${SANDBOX_POLICY_HINT}`;
5717
- return content;
5718
- }
5719
- /** Extract DSH tool-result blocks for resuming a live Cursor exec bridge. */
5720
- function collectToolResults(options) {
5721
- const results = [];
5722
- for (const message of options.messages ?? []) for (const block of message.content ?? []) {
5723
- if (block.type !== "tool-result") continue;
5724
- const content = flattenBlocks(block.content).trim();
5725
- const sandboxDenied = isSandboxRefusal(content);
5726
- results.push({
5727
- toolCallId: block.toolCallId ?? message.source?.callId,
5728
- content: withSandboxHint(content),
5729
- isError: block.isError === true,
5730
- sandboxDenied
5731
- });
5732
- }
5733
- return results;
5734
- }
5735
- /**
5736
- * The compaction DSH applied to the history carried by one request, if any.
5737
- *
5738
- * DSH frees context by replacing a span of the conversation with a summary and
5739
- * marking that message `source.kind = "compact-checkpoint"` with the id of the
5740
- * compaction. The adapter compares that id with the one its persisted Cursor
5741
- * conversation was built from: equal means the checkpoint still describes the
5742
- * history DSH is sending, a new id means DSH has rewritten it.
5743
- *
5744
- * @param options The DSH request whose message history is being sent.
5745
- * @returns The newest compaction id in the history, or undefined when it has none.
5746
- */
5747
- function compactionIdOf(options) {
5748
- let id;
5749
- for (const message of options.messages ?? []) {
5750
- if (message.source?.kind !== "compact-checkpoint") continue;
5751
- id = message.source.compactionId ?? "compact-checkpoint";
5752
- }
5753
- return id;
5754
- }
5755
- function decodeXmlText(value) {
5756
- return String(value ?? "").replace(/&quot;/gi, "\"").replace(/&apos;/gi, "'").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&amp;/gi, "&");
5757
- }
5758
- function parseTagAttributes(source) {
5759
- const attributes = {};
5760
- const pattern = /([A-Za-z_][\w-]*)\s*=\s*"([\s\S]*?)"/g;
5761
- for (let match; (match = pattern.exec(source)) !== null;) attributes[match[1]] = decodeXmlText(match[2]);
5762
- return attributes;
5763
- }
5764
- function resolveTextTool(candidate, tools) {
5765
- const available = tools ?? [];
5766
- const normalized = String(candidate ?? "").toLowerCase();
5767
- let tool = available.find((entry) => {
5768
- const name$1 = String(entry.name).toLowerCase();
5769
- return normalized === name$1 || normalized.endsWith(`_${name$1}`);
5770
- });
5771
- if (tool !== void 0) return tool;
5772
- for (const name$1 of {
5773
- shell: ["pwsh", "bash"],
5774
- read: ["read"],
5775
- glob: ["glob"],
5776
- grep: ["grep"],
5777
- write: ["write"],
5778
- edit: ["edit"]
5779
- }[normalized] ?? []) {
5780
- tool = available.find((entry) => String(entry.name).toLowerCase() === name$1);
5781
- if (tool !== void 0) return tool;
5782
- }
5783
- }
5784
- function normalizeTextToolArguments(tool, raw) {
5785
- const args = { ...raw };
5786
- if (tool.name === "read" && args.file_path === void 0 && args.path !== void 0) {
5787
- args.file_path = args.path;
5788
- delete args.path;
5789
- }
5790
- if (tool.name === "glob") {
5791
- if (args.pattern === void 0 && args.glob_pattern !== void 0) args.pattern = args.glob_pattern;
5792
- if (args.path === void 0 && args.target_directory !== void 0) args.path = args.target_directory;
5793
- delete args.glob_pattern;
5794
- delete args.target_directory;
5795
- }
5796
- for (const [key, value] of Object.entries(args)) {
5797
- const type = tool.parameters?.properties?.[key]?.type;
5798
- if ((type === "number" || type === "integer") && typeof value === "string" && value.trim() !== "") {
5799
- const number$1 = Number(value);
5800
- if (Number.isFinite(number$1)) args[key] = number$1;
5801
- } else if (type === "boolean" && typeof value === "string") {
5802
- if (value.toLowerCase() === "true") args[key] = true;
5803
- else if (value.toLowerCase() === "false") args[key] = false;
5804
- } else if ((type === "object" || type === "array") && typeof value === "string") try {
5805
- args[key] = JSON.parse(value);
5806
- } catch {}
5807
- }
5808
- return args;
5809
- }
5810
- /**
5811
- * Recover Cursor models that print tool-call XML as text instead of emitting
5812
- * an execServerMessage.mcpArgs frame. This is a fallback for contaminated or
5813
- * compacted conversations; protocol-native MCP calls remain the primary path.
5814
- */
5815
- function parseTextToolCalls(text, tools) {
5816
- const calls = [];
5817
- const pattern = /<tool_call\b([^>]*)>([\s\S]*?)<\/tool_call>/gi;
5818
- for (let match; (match = pattern.exec(String(text ?? ""))) !== null;) {
5819
- const attributes = parseTagAttributes(match[1]);
5820
- const tool = resolveTextTool(attributes.name ?? attributes.id, tools);
5821
- if (tool === void 0) continue;
5822
- const rawArgs = {};
5823
- const parameterPattern = /<parameter\s+name="([^"]+)"\s*>([\s\S]*?)<\/parameter>/gi;
5824
- for (let parameter; (parameter = parameterPattern.exec(match[2])) !== null;) rawArgs[parameter[1]] = decodeXmlText(parameter[2].trim());
5825
- for (const [key, value] of Object.entries(attributes)) if (key !== "id" && key !== "name") rawArgs[key] = value;
5826
- if (Object.keys(rawArgs).length === 0) {
5827
- const body = decodeXmlText(match[2]).trim();
5828
- if (body.startsWith("{")) try {
5829
- Object.assign(rawArgs, JSON.parse(body));
5830
- } catch {}
5831
- }
5832
- calls.push({
5833
- id: `text-tool-${crypto.randomUUID()}`,
5834
- name: tool.name,
5835
- arguments: JSON.stringify(normalizeTextToolArguments(tool, rawArgs))
5836
- });
5837
- }
5838
- return calls;
5839
- }
5840
- function coldStartLabel(message) {
5841
- if (message.role === "assistant") return "ASSISTANT";
5842
- if ((message.content ?? []).some((block) => block.type === "tool-result")) return "TOOL RESULT";
5843
- if (message.source?.kind === "plugin") return "RUNTIME CONTEXT";
5844
- return "USER";
5845
- }
5846
- /**
5847
- * Rehydrate a DSH conversation after process restart without using Cursor's
5848
- * field-8 turns (the current server interprets those entries as blob ids).
5849
- */
5850
- function coldStartActionText(options) {
5851
- const entries = [];
5852
- for (const message of options.messages ?? []) {
5853
- if (message.role === "system") continue;
5854
- const text = message.role === "assistant" ? renderColdStartAssistant(message.content) : withSandboxHint(flattenBlocks(message.content).trim());
5855
- if (text.length === 0) continue;
5856
- entries.push({
5857
- label: coldStartLabel(message),
5858
- text
5859
- });
5860
- }
5861
- if (entries.length === 0) return "";
5862
- if (entries.length === 1 && entries[0].label === "USER") return entries[0].text;
5863
- return ["Continue the DSH conversation below. Treat entries according to their labels. Respond to the final USER request; RUNTIME CONTEXT and TOOL RESULT entries provide context only.", ...entries.map((entry) => `[${entry.label}]\n${entry.text}`)].join("\n\n");
5864
- }
5865
- /**
5866
- * Build a fresh state with root prompt blobs only. Current Cursor servers reject
5867
- * hand-encoded field-8 turns, so cold starts carry textual history in the action.
5868
- */
5869
- function buildInitialConversationState(options) {
5870
- const systemParts = [options.system ?? ""];
5871
- for (const message of options.messages ?? []) if (message.role === "system") systemParts.push(flattenBlocks(message.content));
5872
- const systemText = systemParts.filter((part) => part.length > 0).join("\n").trim();
5873
- const blobStore = /* @__PURE__ */ new Map();
5874
- const rootPromptBlobIds = [];
5875
- if (systemText.length > 0) {
5876
- const payload = textEncoder.encode(JSON.stringify({
5877
- role: "system",
5878
- content: systemText
5879
- }));
5880
- const id = sha256(payload);
5881
- blobStore.set(Buffer.from(id).toString("hex"), payload);
5882
- rootPromptBlobIds.push(id);
5883
- }
5884
- return {
5885
- conversationState: encodeConversationState({
5886
- rootPromptBlobIds,
5887
- turns: [],
5888
- systemText
5889
- }),
5890
- blobStore,
5891
- systemText
5892
- };
5893
- }
5894
- /**
5895
- * Build the full `AgentClientMessage` payload for one run.
5896
- *
5897
- * When a persisted checkpoint for this conversation exists (captured from a
5898
- * previous run's `conversation_checkpoint_update`), it becomes the
5899
- * conversation state and its referenced blobs are served from the persisted
5900
- * blob store. Otherwise the state is built from the DSH message history.
5901
- */
5902
- function buildRunPayload(options, modelId, persisted, images = []) {
5903
- let conversationState;
5904
- let blobStore;
5905
- let actionText;
5906
- if (persisted?.checkpoint !== void 0) {
5907
- conversationState = persisted.checkpoint;
5908
- blobStore = persisted.blobs ?? /* @__PURE__ */ new Map();
5909
- actionText = currentActionText(options);
5910
- } else {
5911
- const built = buildInitialConversationState(options);
5912
- conversationState = built.conversationState;
5913
- blobStore = built.blobStore;
5914
- actionText = coldStartActionText(options);
5915
- }
5916
- const action = encodeUserMessageAction(encodeUserMessage({
5917
- text: actionText,
5918
- messageId: randomUUID()
5919
- }), images);
5920
- const modelDetails = encodeModelDetails(modelId);
5921
- return {
5922
- payload: encodeRunMessage(encodeRunRequest({
5923
- conversationState,
5924
- action,
5925
- modelDetails,
5926
- conversationId: randomUUID()
5927
- })),
5928
- blobStore
5929
- };
5930
- }
5931
- /**
5932
- * Fetch usable models from Cursor's unary `GetUsableModels` endpoint.
5933
- * The request body is the raw (unframed) empty protobuf message.
5934
- */
5935
- async function fetchUsableModels(accessToken, options = {}) {
5936
- const response = await (options.fetch ?? nativeFetch)(`${CURSOR_BASE_URL}${CURSOR_MODELS_PATH}`, {
5937
- method: "POST",
5938
- redirect: "error",
5939
- headers: {
5940
- "content-type": "application/proto",
5941
- authorization: `Bearer ${accessToken}`,
5942
- "x-ghost-mode": "true",
5943
- "x-cursor-client-version": CURSOR_CLIENT_VERSION,
5944
- "x-cursor-client-type": "cli",
5945
- "user-agent": "dsh-cursor-subscription/0.1.0"
5946
- },
5947
- body: new Uint8Array(0),
5948
- signal: options.signal
5949
- });
5950
- if (!response.ok) throw new Error(`Cursor model discovery failed (HTTP ${response.status})`);
5951
- const bytes = new Uint8Array(await response.arrayBuffer());
5952
- if (bytes.length === 0) throw new Error("Cursor model discovery returned an empty response");
5953
- const models = decodeUsableModels(bytes);
5954
- if (models.length > 0) return models;
5955
- const frameReader = new ConnectFrameReader();
5956
- frameReader.push(bytes);
5957
- frameReader.finish();
5958
- const framed = [];
5959
- for (;;) {
5960
- const frame = await frameReader.next();
5961
- if (frame === void 0) break;
5962
- if ((frame.flags & CONNECT_END_STREAM_FLAG) !== 0) break;
5963
- framed.push(...decodeUsableModels(frame.payload));
5964
- }
5965
- return framed;
5966
- }
5967
- const record$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5968
- const TOOL_REJECT_REASON = "Tool not available in this environment. Use the MCP tools provided instead.";
5969
- /** Block indices are fixed per type so the assembler keeps blocks distinct. */
5970
- const TEXT_BLOCK_INDEX = 0;
5971
- const REASONING_BLOCK_INDEX = 1;
5972
- const TOOL_BLOCK_INDEX = 2;
5973
- /**
5974
- * Progress notes ride their own reasoning block, so the model's own thinking
5975
- * stays exactly what the model produced. Reasoning is the only channel an
5976
- * adapter has for showing work that is not answer text, and a Cursor step
5977
- * otherwise reduces to one sentence plus a tool card: the protocol round trips
5978
- * that cost the turn most of its wall clock — a declined native tool call, a
5979
- * tool-schema fetch — are invisible, and a turn of them reads as the same
5980
- * request being submitted again and again.
5981
- */
5982
- const PROGRESS_BLOCK_INDEX = 3;
5983
- /**
5984
- * One reader-visible progress line.
5985
- * @param text What just happened, in one clause.
5986
- * @returns A reasoning delta the conversation view renders as process.
5987
- */
5988
- function cursorProgress(text) {
5989
- return {
5990
- type: "reasoning-delta",
5991
- index: PROGRESS_BLOCK_INDEX,
5992
- text: `[cursor] ${text}\n`
5993
- };
5994
- }
5995
- /** Reader-facing name of one decoded exec. */
5996
- function nativeToolName(exec) {
5997
- if (exec.case === "unknown") return `tool (field ${exec.field ?? "?"})`;
5998
- return exec.case.replace(/ExecArgs$|Args$/, "");
5999
- }
6000
- /**
6001
- * DSH `LlmAdapter` for the Cursor subscription. Normal turns use Cursor
6002
- * checkpoints; MCP tool calls keep their HTTP/2 Run alive across DSH steps so
6003
- * the following tool-result can resume the exact pending exec with mcpResult.
6004
- */
6005
- var CursorAdapter = class extends LlmAdapter {
6006
- constructor(options) {
6007
- super();
6008
- this.auth = options.auth;
6009
- this.fetchModels = options.fetchModels ?? fetchUsableModels;
6010
- this.idleTimeoutMs = options.idleTimeoutMs ?? STREAM_IDLE_TIMEOUT_MS;
6011
- this.progressTimeoutMs = options.progressTimeoutMs ?? STREAM_PROGRESS_TIMEOUT_MS;
6012
- this.idleCheckIntervalMs = options.idleCheckIntervalMs ?? 15e3;
6013
- this.modelsCacheTtlMs = options.modelsCacheTtlMs ?? 300 * 1e3;
6014
- this.sessionStateTtlMs = options.sessionStateTtlMs ?? SESSION_STATE_TTL_MS;
6015
- const fallbackSettings = resolveCursorSettings({
6016
- maxToolRounds: options.maxToolRounds,
6017
- retryCount: options.retryCount,
6018
- retryIntervalMs: options.retryIntervalMs,
6019
- retryHttpStatusCodes: options.retryHttpStatusCodes
6020
- });
6021
- this.settings = options.settings ?? (() => fallbackSettings);
6022
- this.createAgentRun = options.createAgentRun ?? ((access) => new AgentRun(access));
6023
- this.resolveAttachments = options.resolveAttachments;
6024
- this.sleep = options.sleep ?? abortableDelay$1;
6025
- this.now = options.now ?? Date.now;
6026
- this.logger = options.logger;
6027
- this.hangTracePath = options.hangTracePath ?? join(homedir(), ".dsh", "cursor-hang-trace.log");
6028
- this.toolCallSettleMs = options.toolCallSettleMs ?? TOOL_CALL_SETTLE_MS;
6029
- this.#modelsCache = {
6030
- at: 0,
6031
- models: void 0
6032
- };
6033
- }
6034
- #modelsCache;
6035
- /** Persisted conversation checkpoints + referenced blobs per DSH session id. */
6036
- #sessions = /* @__PURE__ */ new Map();
6037
- #evictStaleSessions() {
6038
- const now = this.now();
6039
- for (const [key, state] of this.#sessions) {
6040
- if (now - (state.lastAccessMs ?? now) <= this.sessionStateTtlMs) continue;
6041
- state.bridge?.run.close();
6042
- this.#sessions.delete(key);
6043
- }
6044
- }
6045
- async #discoverModels(force = false) {
6046
- const now = this.now();
6047
- if (!force && this.#modelsCache.models !== void 0 && now - this.#modelsCache.at < this.modelsCacheTtlMs) return this.#modelsCache.models;
6048
- const access = await this.auth.accessToken();
6049
- const source = await this.fetchModels(access);
6050
- if (source.length === 0) throw new Error("Cursor returned no usable models");
6051
- const result = sortModelsByName(source.map((model) => ({
6052
- id: model.id,
6053
- name: model.name || model.id,
6054
- inputModalities: ["text", "image"]
6055
- })));
6056
- this.#modelsCache = {
6057
- at: now,
6058
- models: result
6059
- };
6060
- return result;
6061
- }
6062
- invalidateModels() {
6063
- this.#modelsCache = {
6064
- at: 0,
6065
- models: void 0
6066
- };
6067
- }
6068
- providerInfo(provider) {
6069
- return {
6070
- id: provider,
6071
- name: "Cursor subscription"
6072
- };
6073
- }
6074
- async listModels(provider) {
6075
- return (await this.#discoverModels()).map((model) => ({
6076
- provider,
6077
- ...model
6078
- }));
6079
- }
6080
- /** RPC-facing model listing; `force` bypasses the discovery cache. */
6081
- async listModelsForRpc({ force = false, signal } = {}) {
6082
- const models = await this.#discoverModels(force);
6083
- if (signal?.aborted) throw new LlmError("Cursor model listing aborted", "ABORTED");
6084
- return models;
6085
- }
6086
- async resolveModel(provider, model, _signal) {
6087
- let name$1 = model;
6088
- try {
6089
- const found = (await this.#discoverModels()).find((entry) => entry.id === model);
6090
- if (found !== void 0) name$1 = found.name;
6091
- } catch {}
6092
- return {
6093
- provider,
6094
- id: model,
6095
- name: name$1,
6096
- inputModalities: ["text", "image"],
6097
- context: { contextWindow: DEFAULT_CONTEXT_WINDOW },
6098
- defaultMaxTokens: DEFAULT_MAX_TOKENS
6099
- };
6100
- }
6101
- async *stream(options) {
6102
- const consumer = new AbortController();
6103
- const upstream = options.signal === void 0 ? consumer.signal : AbortSignal.any([options.signal, consumer.signal]);
6104
- let run;
6105
- let abortRun;
6106
- let bridgeStored = false;
6107
- const sessionKey = options.sessionId === void 0 ? void 0 : String(options.sessionId);
6108
- this.#evictStaleSessions();
6109
- const prior = sessionKey === void 0 ? void 0 : this.#sessions.get(sessionKey);
6110
- const persisted = {
6111
- checkpoint: prior?.checkpoint === void 0 ? void 0 : Uint8Array.from(prior.checkpoint),
6112
- blobs: new Map(prior?.blobs ?? [])
6113
- };
6114
- const compactionId = compactionIdOf(options);
6115
- const compacted = compactionId !== void 0 && compactionId !== prior?.compactionId;
6116
- if (compacted) {
6117
- persisted.checkpoint = void 0;
6118
- persisted.blobs.clear();
6119
- }
6120
- const replayHistoryEachStep = readConfigField(this.settings().replayHistoryEachStep) === true;
6121
- if (replayHistoryEachStep) {
6122
- persisted.checkpoint = void 0;
6123
- persisted.blobs.clear();
6124
- }
6125
- persisted.compactionId = compactionId ?? prior?.compactionId;
6126
- try {
6127
- if (options.stop !== void 0) throw new LlmError("cursor-subscription does not support GenerateOptions.stop", "UNSUPPORTED_OPTION");
6128
- const requestSettings = resolveCursorSettings(this.settings());
6129
- const mcpTools = (options.tools ?? []).map((tool) => encodeMcpToolDefinition({
6130
- name: tool.name,
6131
- description: tool.description,
6132
- inputSchema: encodeValue(tool.parameters ?? {})
6133
- }));
6134
- const toolResults = collectToolResults(options);
6135
- const liveBridge = prior?.bridge;
6136
- const canResume = !replayHistoryEachStep && !compacted && liveBridge !== void 0 && !liveBridge.run.finished && !liveBridge.run.stream?.destroyed && toolResults.length > 0;
6137
- let toolRoundCount = canResume ? liveBridge.toolRoundCount ?? 0 : 0;
6138
- let blobStore;
6139
- if (canResume) {
6140
- run = liveBridge.run;
6141
- run.frames?.resume?.();
6142
- blobStore = persisted.blobs;
6143
- abortRun = () => run.abort(/* @__PURE__ */ new Error("Cursor request aborted by caller"));
6144
- upstream.addEventListener("abort", abortRun, { once: true });
6145
- if (upstream.aborted) abortRun();
6146
- for (const pending of liveBridge.pendingExecs) {
6147
- const result = toolResults.find((entry) => entry.toolCallId === pending.toolCallId);
6148
- if (result?.sandboxDenied === true) yield cursorProgress("the sandbox refused a tool call; told the model how to ask for approval");
6149
- const payload = result === void 0 ? encodeMcpError("Tool result not provided") : encodeMcpResult(result);
6150
- if (!run.writeMessage(encodeExecClientMessageEnvelope(encodeExecClientMessage(pending.id, pending.execId, 11, payload)))) throw new Error("Cursor tool continuation bridge closed before accepting the result");
6151
- }
6152
- } else {
6153
- if (liveBridge !== void 0) {
6154
- liveBridge.run.close();
6155
- persisted.checkpoint = void 0;
6156
- persisted.blobs.clear();
6157
- }
6158
- const access = await this.auth.accessToken({ signal: upstream });
6159
- const images = await prepareCursorImages(options, this.resolveAttachments?.());
6160
- const built = buildRunPayload(options, options.model, persisted, images);
6161
- blobStore = built.blobStore;
6162
- let retriesUsed = 0;
6163
- for (;;) {
6164
- run = this.createAgentRun(access);
6165
- await run.start();
6166
- abortRun = () => run.abort(/* @__PURE__ */ new Error("Cursor request aborted by caller"));
6167
- upstream.addEventListener("abort", abortRun, { once: true });
6168
- if (upstream.aborted) abortRun();
6169
- if (!run.writeMessage(built.payload)) throw new Error("Cursor agent bridge closed before accepting the request");
6170
- const status = await run.waitForResponse(this.idleTimeoutMs);
6171
- if (isSuccessfulAgentResponse(status, run.responseContentType)) {
6172
- run.startHeartbeat();
6173
- break;
6174
- }
6175
- upstream.removeEventListener("abort", abortRun);
6176
- abortRun = void 0;
6177
- run.close();
6178
- if (status === 200) throw new LlmError(`Cursor agent returned an unexpected content type: ${run.responseContentType ?? "missing"}`, "CURSOR_PROTOCOL");
6179
- if (!shouldRetryHttpStatus(status, retriesUsed, requestSettings)) {
6180
- const message = `Cursor agent returned HTTP ${status}`;
6181
- throw new LlmError(message, classifyCursorError(message));
6182
- }
6183
- retriesUsed++;
6184
- await this.sleep(requestSettings.retryIntervalMs, upstream);
6185
- }
6186
- }
6187
- const started = this.now();
6188
- let lastActivity = started;
6189
- let lastProgress = started;
6190
- let emittedToolCall = false;
6191
- let toolCallBuffer = void 0;
6192
- let textOutput = "";
6193
- let outputTokens = 0;
6194
- let streamClosed = false;
6195
- let terminalErrorEmitted = false;
6196
- let toolCallPending = false;
6197
- let toolRoundCounted = false;
6198
- let unknownExecWarned = false;
6199
- const pendingExecs = [];
6200
- let toolCallSettle;
6201
- /**
6202
- * Stop blocking for more frames without ending the reader, so the same
6203
- * Run can be continued by the next DSH step.
6204
- */
6205
- const stopReading = () => {
6206
- if (typeof run.frames?.pause === "function") run.frames.pause();
6207
- else if (typeof run.frames?.finish === "function") run.frames.finish();
6208
- };
6209
- /**
6210
- * Cap how long the loop waits for the frame that closes a tool-call
6211
- * burst. Cursor usually follows mcpArgs with blob writes and a
6212
- * checkpoint, but it may instead send the checkpoint *before* the burst
6213
- * and then nothing at all; waiting for a second checkpoint in that case
6214
- * hung the step until the progress watchdog aborted it.
6215
- */
6216
- const armToolCallSettle = () => {
6217
- toolCallSettle ??= setTimeout(stopReading, this.toolCallSettleMs);
6218
- toolCallSettle.unref?.();
6219
- };
6220
- const frameTrace = [];
6221
- const traceFrame = (label) => {
6222
- const last = frameTrace.at(-1);
6223
- if (label === "heartbeat" && last?.label === "heartbeat") {
6224
- last.count++;
6225
- last.at = this.now();
6226
- return;
6227
- }
6228
- frameTrace.push({
6229
- label,
6230
- at: this.now(),
6231
- count: 1
6232
- });
6233
- if (frameTrace.length > 120) frameTrace.shift();
6234
- };
6235
- const stallReport = (message) => {
6236
- const lines = [
6237
- `=== ${(/* @__PURE__ */ new Date()).toISOString()} ${message} ===`,
6238
- `session=${sessionKey ?? "-"} model=${options.model ?? "-"} textChars=${textOutput.length} toolCallPending=${toolCallPending} toolRounds=${toolRoundCount} checkpoint=${persisted.checkpoint?.length ?? 0}B blobs=${blobStore?.size ?? 0}`,
6239
- `lastFrame=${Math.round((this.now() - lastActivity) / 1e3)}s ago lastContent=${Math.round((this.now() - lastProgress) / 1e3)}s ago`,
6240
- ...frameTrace.map((entry) => ` ${new Date(entry.at).toISOString()} ${entry.label}${entry.count > 1 ? ` x${entry.count}` : ""}`),
6241
- ""
6242
- ];
6243
- try {
6244
- appendFileSync(this.hangTracePath, lines.join("\n"));
6245
- } catch {}
6246
- this.logger?.warn?.(`cursor-subscription: ${message}; trace in ${this.hangTracePath}`);
6247
- };
6248
- const handleStall = (message) => {
6249
- stallReport(message);
6250
- if ((textOutput.length > 0 || toolCallPending) && typeof run.frames?.pause === "function") {
6251
- stopReading();
6252
- return;
6253
- }
6254
- run.abort(new Error(message));
6255
- };
6256
- const idleCheck = setInterval(() => {
6257
- const now = this.now();
6258
- if (now - lastActivity > this.idleTimeoutMs) handleStall("Cursor stream idle timeout");
6259
- else if (now - lastProgress > this.progressTimeoutMs) handleStall(`Cursor stream progress timeout: no content for ${this.progressTimeoutMs}ms`);
6260
- }, this.idleCheckIntervalMs);
6261
- idleCheck.unref?.();
6262
- try {
6263
- for (;;) {
6264
- const frame = await run.frames.next();
6265
- if (frame === void 0) break;
6266
- lastActivity = this.now();
6267
- traceFrame(describeServerFrame(frame));
6268
- if ((frame.flags & CONNECT_END_STREAM_FLAG) !== 0) {
6269
- const end = parseEndStream(frame.payload);
6270
- if (end !== void 0) {
6271
- terminalErrorEmitted = true;
6272
- yield {
6273
- type: "finish",
6274
- reason: {
6275
- kind: "error",
6276
- failure: {
6277
- message: end.message,
6278
- code: classifyCursorError(`${end.code} ${end.debugCode ?? ""} ${end.message}`)
6279
- }
6280
- }
6281
- };
6282
- streamClosed = true;
6283
- break;
6284
- }
6285
- break;
6286
- }
6287
- const message = decodeAgentServerMessage(frame.payload);
6288
- if (!(message.case === "interactionUpdate" && message.value?.type === "heartbeat")) lastProgress = this.now();
6289
- if (message.case === "conversationCheckpointUpdate") {
6290
- persisted.checkpoint = Uint8Array.from(message.value);
6291
- if (toolCallPending) {
6292
- streamClosed = true;
6293
- break;
6294
- }
6295
- } else if (message.case === "interactionUpdate") {
6296
- const update = message.value;
6297
- if (update.type === "textDelta") {
6298
- if (update.text.length > 0) {
6299
- textOutput += update.text;
6300
- yield {
6301
- type: "text-delta",
6302
- index: TEXT_BLOCK_INDEX,
6303
- text: update.text
6304
- };
6305
- }
6306
- } else if (update.type === "thinkingDelta") {
6307
- if (update.text.length > 0) yield {
6308
- type: "reasoning-delta",
6309
- index: REASONING_BLOCK_INDEX,
6310
- text: update.text
6311
- };
6312
- } else if (update.type === "thinkingCompleted") {
6313
- if (update.durationMs > 0) yield cursorProgress(`thinking finished after ${(update.durationMs / 1e3).toFixed(1)}s`);
6314
- } else if (update.type === "tokenDelta") {
6315
- if (Number.isFinite(update.tokens) && update.tokens > 0) outputTokens += update.tokens;
6316
- } else if (update.type === "partialToolCall") {
6317
- if (toolCallBuffer === void 0 || toolCallBuffer.id !== update.callId) toolCallBuffer = {
6318
- id: update.callId,
6319
- name: "",
6320
- arguments: ""
6321
- };
6322
- if (update.argsTextDelta.length > 0) toolCallBuffer.arguments += update.argsTextDelta;
6323
- } else if (update.type === "toolCallStarted" || update.type === "toolCallCompleted" || update.type === "toolCallDelta") emittedToolCall = true;
6324
- else if (update.type === "turnEnded") {
6325
- if (!toolCallPending) break;
6326
- }
6327
- } else if (message.case === "kvServerMessage") {
6328
- const kv = message.value;
6329
- if (kv.case === "getBlobArgs") {
6330
- const key = Buffer.from(kv.blobId).toString("hex");
6331
- const blob = blobStore.get(key);
6332
- run.writeMessage(encodeKvClientMessage(encodeGetBlobResult(kv.id, blob)));
6333
- } else if (kv.case === "setBlobArgs") {
6334
- if (kv.blobId !== void 0 && kv.blobData !== void 0) {
6335
- const key = Buffer.from(kv.blobId).toString("hex");
6336
- const data = Uint8Array.from(kv.blobData);
6337
- blobStore.set(key, data);
6338
- persisted.blobs.set(key, data);
6339
- }
6340
- run.writeMessage(encodeKvClientMessage(encodeSetBlobResult(kv.id)));
6341
- }
6342
- } else if (message.case === "execServerMessage") {
6343
- const exec = message.value;
6344
- if (exec.case === "requestContextArgs") {
6345
- run.writeMessage(encodeExecClientMessageEnvelope(encodeExecClientMessage(exec.id, exec.execId, 10, encodeRequestContextResult(mcpTools))));
6346
- yield cursorProgress(`loaded the DSH tool schemas (${mcpTools.length} tools)`);
6347
- } else if (exec.case === "mcpArgs") {
6348
- if (!toolRoundCounted) {
6349
- toolRoundCount++;
6350
- toolRoundCounted = true;
6351
- }
6352
- if (toolRoundCount > requestSettings.maxToolRounds) throw new LlmError(`Cursor tool-call safety limit reached after ${requestSettings.maxToolRounds} rounds. The run was stopped to prevent an infinite loop; send a new message to continue if needed.`, "TOOL_LIMIT");
6353
- const mcp = exec.args ?? {};
6354
- const args = {};
6355
- for (const [key, value] of Object.entries(mcp.args ?? {})) try {
6356
- args[key] = decodeValue(value);
6357
- } catch {
6358
- args[key] = null;
6359
- }
6360
- const candidateName = mcp.toolName || mcp.name;
6361
- const tool = resolveTextTool(candidateName, options.tools ?? []);
6362
- if (tool === void 0) {
6363
- run.writeMessage(encodeExecClientMessageEnvelope(encodeExecClientMessage(exec.id, exec.execId, 11, encodeMcpError(`Unknown or unsupported MCP tool: ${candidateName || "(empty)"}`))));
6364
- continue;
6365
- }
6366
- emittedToolCall = true;
6367
- const id = mcp.toolCallId || exec.execId || crypto.randomUUID();
6368
- const name$1 = tool.name;
6369
- const normalizedArgs = normalizeTextToolArguments(tool, args);
6370
- const argumentsJson = JSON.stringify(normalizedArgs);
6371
- const blockIndex = TOOL_BLOCK_INDEX + pendingExecs.length;
6372
- yield {
6373
- type: "block-start",
6374
- index: blockIndex,
6375
- blockType: "tool-call"
6376
- };
6377
- yield {
6378
- type: "tool-call-delta",
6379
- index: blockIndex,
6380
- id: ToolCallId(id),
6381
- name: name$1,
6382
- argumentsDelta: argumentsJson
6383
- };
6384
- yield {
6385
- type: "block-end",
6386
- index: blockIndex,
6387
- block: {
6388
- type: "tool-call",
6389
- id: ToolCallId(id),
6390
- name: name$1,
6391
- arguments: argumentsJson
6392
- }
6393
- };
6394
- pendingExecs.push({
6395
- id: exec.id,
6396
- execId: exec.execId,
6397
- toolCallId: id
6398
- });
6399
- toolCallPending = true;
6400
- yield cursorProgress(`calling the DSH tool ${name$1}`);
6401
- streamClosed = true;
6402
- armToolCallSettle();
6403
- } else {
6404
- const reply = rejectionFor(exec);
6405
- if (reply !== void 0) {
6406
- if (exec.case === "unknown" && !unknownExecWarned) {
6407
- unknownExecWarned = true;
6408
- this.logger?.warn?.(`cursor-subscription: Cursor requested an exec this build does not know (field ${exec.field}); rejected it so the run can continue with MCP tools`);
6409
- }
6410
- run.writeMessage(encodeExecClientMessageEnvelope(encodeExecClientMessage(exec.id, exec.execId, reply.field, reply.payload)));
6411
- yield cursorProgress(`native ${nativeToolName(exec)} call declined; using the DSH tools instead`);
6412
- } else if (exec.case === "unknown") this.logger?.warn?.("cursor-subscription: Cursor requested an exec with no addressable field; the run may stall");
6413
- }
6414
- }
6415
- }
6416
- } finally {
6417
- clearInterval(idleCheck);
6418
- clearTimeout(toolCallSettle);
6419
- }
6420
- if (!terminalErrorEmitted && !toolCallPending) {
6421
- const textToolCalls = parseTextToolCalls(textOutput, options.tools ?? []);
6422
- for (let index = 0; index < textToolCalls.length; index++) {
6423
- const call = textToolCalls[index];
6424
- const blockIndex = TOOL_BLOCK_INDEX + index;
6425
- yield {
6426
- type: "block-start",
6427
- index: blockIndex,
6428
- blockType: "tool-call"
6429
- };
6430
- yield {
6431
- type: "tool-call-delta",
6432
- index: blockIndex,
6433
- id: ToolCallId(call.id),
6434
- name: call.name,
6435
- argumentsDelta: call.arguments
6436
- };
6437
- yield {
6438
- type: "block-end",
6439
- index: blockIndex,
6440
- block: {
6441
- type: "tool-call",
6442
- id: ToolCallId(call.id),
6443
- name: call.name,
6444
- arguments: call.arguments
6445
- }
6446
- };
6447
- }
6448
- if (textToolCalls.length > 0) {
6449
- emittedToolCall = true;
6450
- streamClosed = true;
6451
- }
6452
- }
6453
- const liveToolBridge = toolCallPending && pendingExecs.length > 0 && !run.finished;
6454
- if (sessionKey !== void 0 && (persisted.checkpoint !== void 0 || liveToolBridge)) {
6455
- persisted.lastAccessMs = this.now();
6456
- if (liveToolBridge) {
6457
- persisted.bridge = {
6458
- run,
6459
- pendingExecs,
6460
- toolRoundCount
6461
- };
6462
- bridgeStored = true;
6463
- }
6464
- this.#sessions.delete(sessionKey);
6465
- this.#sessions.set(sessionKey, persisted);
6466
- while (this.#sessions.size > 100) {
6467
- const oldest = this.#sessions.keys().next().value;
6468
- if (oldest === void 0) break;
6469
- this.#sessions.get(oldest)?.bridge?.run.close();
6470
- this.#sessions.delete(oldest);
6471
- }
6472
- }
6473
- if (!terminalErrorEmitted) {
6474
- yield {
6475
- type: "usage",
6476
- usage: {
6477
- inputTokens: decodeCheckpointUsedTokens(persisted.checkpoint) ?? 0,
6478
- outputTokens
6479
- }
6480
- };
6481
- if (streamClosed && emittedToolCall) yield {
6482
- type: "finish",
6483
- reason: { kind: "tool-calls" }
6484
- };
6485
- else if (run.trailers && Number(run.trailers["grpc-status"]) > 0) {
6486
- const message = run.trailers["grpc-message"] ?? "Cursor agent run failed";
6487
- yield {
6488
- type: "finish",
6489
- reason: {
6490
- kind: "error",
6491
- failure: {
6492
- message,
6493
- code: classifyCursorError(message)
6494
- }
6495
- }
6496
- };
6497
- } else yield {
6498
- type: "finish",
6499
- reason: { kind: "stop" }
6500
- };
6501
- }
6502
- } catch (error) {
6503
- if (upstream.aborted) yield {
6504
- type: "finish",
6505
- reason: {
6506
- kind: "aborted",
6507
- failure: {
6508
- message: "Cursor request aborted by caller",
6509
- code: "ABORTED"
6510
- }
6511
- }
6512
- };
6513
- else if (error instanceof LlmError) yield {
6514
- type: "finish",
6515
- reason: {
6516
- kind: "error",
6517
- failure: {
6518
- message: error.message,
6519
- code: error.code
6520
- }
6521
- }
6522
- };
6523
- else {
6524
- const message = error instanceof Error ? error.message : String(error);
6525
- yield {
6526
- type: "finish",
6527
- reason: {
6528
- kind: "error",
6529
- failure: {
6530
- message,
6531
- code: classifyCursorError(message)
6532
- }
6533
- }
6534
- };
3869
+ case "ollamaSettings":
3870
+ if (!ollama) throw new BadRequest("Ollama settings are unavailable");
3871
+ return ok(await ollama.settings());
3872
+ case "ollamaSetSettings":
3873
+ if (!ollama) throw new BadRequest("Ollama settings are unavailable");
3874
+ if (payload === null || typeof payload !== "object") throw new BadRequest("payload must be an object");
3875
+ await ollama.setSettings(payload.settings);
3876
+ return ok({ ok: true });
3877
+ case "ollamaModels": {
3878
+ if (!ollama) throw new BadRequest("Ollama settings are unavailable");
3879
+ const apiKey = readOptionalString(payload, "apiKey");
3880
+ const rawBaseURL = readOptionalString(payload, "baseURL");
3881
+ let baseURL;
3882
+ if (rawBaseURL !== void 0) try {
3883
+ baseURL = validateOllamaBaseURL(rawBaseURL);
3884
+ } catch (error) {
3885
+ throw new BadRequest(error instanceof Error ? error.message : String(error));
6535
3886
  }
6536
- } finally {
6537
- if (abortRun !== void 0) upstream.removeEventListener("abort", abortRun);
6538
- consumer.abort("cursor stream consumer stopped");
6539
- if (!bridgeStored) run?.close();
3887
+ return ok(await ollama.models({
3888
+ ...apiKey === void 0 ? {} : { apiKey },
3889
+ ...baseURL === void 0 ? {} : { baseURL }
3890
+ }, signal));
6540
3891
  }
6541
- }
6542
- };
6543
- /**
6544
- * Parse a Connect end-stream frame into a structured error. Cursor's agent
6545
- * errors carry a `debug` payload with a stable code and human-readable
6546
- * `title`/`detail`; extracting those gives the user the real reason instead of
6547
- * a bare `resource_exhausted`.
6548
- * @returns {undefined | {code: string, debugCode?: string, message: string}}
6549
- */
6550
- function parseEndStream(payload) {
6551
- try {
6552
- const error = JSON.parse(new TextDecoder().decode(payload))?.error;
6553
- if (!error) return void 0;
6554
- const code = typeof error.code === "string" ? error.code : "unknown";
6555
- let debugCode;
6556
- let title;
6557
- let detail;
6558
- if (Array.isArray(error.details)) for (const entry of error.details) {
6559
- if (!record$1(entry) || !record$1(entry.debug)) continue;
6560
- if (typeof entry.debug.error === "string") debugCode = entry.debug.error;
6561
- const details = record$1(entry.debug.details) ? entry.debug.details : void 0;
6562
- if (details !== void 0) {
6563
- if (typeof details.title === "string") title = details.title;
6564
- if (typeof details.detail === "string") detail = details.detail;
3892
+ case "prepareReset":
3893
+ if (!controller.prepareReset) throw new BadRequest("Reset redemption unavailable");
3894
+ return ok(await controller.prepareReset(readString(payload, "account"), signal));
3895
+ case "consumeReset":
3896
+ if (!controller.consumeReset) throw new BadRequest("Reset redemption unavailable");
3897
+ await controller.consumeReset(readString(payload, "account"), readString(payload, "ticket"), signal);
3898
+ return ok({ ok: true });
3899
+ case "image": return ok(await controller.readImage(readImageRef(payload), signal));
3900
+ case "video": return ok(await controller.readVideo(readVideoName(payload), signal));
3901
+ case "speed": return ok(await speed.speed(readSessionId(payload), payload.model === void 0 ? void 0 : readString(payload, "model")));
3902
+ case "setSpeed":
3903
+ await speed.setSpeed(readSessionId(payload), readSpeedTier(payload));
3904
+ return ok({ ok: true });
3905
+ case "modelDefaults":
3906
+ if (modelDefaults === void 0) throw new BadRequest("model defaults are unavailable");
3907
+ return ok(await modelDefaults.catalog(readForce(payload)));
3908
+ case "setModelDefault":
3909
+ if (modelDefaults === void 0) throw new BadRequest("model defaults are unavailable");
3910
+ {
3911
+ const input = readModelDefaultInput(payload);
3912
+ await modelDefaults.set(input.provider, input.model, input.effort);
6565
3913
  }
6566
- }
6567
- const fallback = typeof error.message === "string" && error.message.length > 0 ? error.message : void 0;
6568
- const message = [title, detail].filter((part) => typeof part === "string" && part.length > 0).join(" ") || (debugCode !== void 0 ? `Cursor: ${debugCode}` : void 0) || (fallback !== void 0 ? `Cursor agent error ${code}: ${fallback}` : `Cursor agent error ${code}`);
6569
- return {
6570
- code,
6571
- debugCode,
6572
- message
6573
- };
6574
- } catch {
6575
- return;
6576
- }
6577
- }
6578
- function classifyCursorError(message) {
6579
- if (/\b(?:401|403)\b|unauth|invalid.*(?:key|token|credential)/i.test(message)) return "AUTH";
6580
- if (/rate.?limit|\b429\b|quota|resource_exhausted|spend.?limit|usage limit|exceeded/i.test(message)) return "RATE_LIMIT";
6581
- if (/\b400\b|invalid.?request/i.test(message)) return "INVALID_REQUEST";
6582
- if (/\b408\b|timeout|timed out/i.test(message)) return "TIMEOUT";
6583
- if (/\b5\d\d\b|internal/i.test(message)) return "SERVER";
6584
- if (/\b(?:network|connection|socket|fetch|ECONN|http2)/i.test(message)) return "TRANSPORT";
6585
- return "CURSOR_ERROR";
6586
- }
6587
- /**
6588
- * Build the exec-client reply for a native Cursor tool we reject.
6589
- *
6590
- * `ExecClientMessage` numbers its result variants exactly like
6591
- * `ExecServerMessage` numbers its exec variants, which is what lets an exec
6592
- * this build cannot name still be answered: reply on the exec's own field.
6593
- * Every unanswered exec leaves the server waiting for its result, and that
6594
- * wait is what the user sees as a turn that ends right after the model says
6595
- * what it is about to do — the progress watchdog aborts the step and only the
6596
- * preamble text survives.
6597
- */
6598
- function rejectionFor(exec) {
6599
- switch (exec.case) {
6600
- case "readArgs": return {
6601
- field: 7,
6602
- payload: encodeReadRejected(exec.path, TOOL_REJECT_REASON)
6603
- };
6604
- case "lsArgs": return {
6605
- field: 8,
6606
- payload: encodeLsRejected(exec.path, TOOL_REJECT_REASON)
6607
- };
6608
- case "grepArgs": return {
6609
- field: 5,
6610
- payload: encodeGrepError(TOOL_REJECT_REASON)
6611
- };
6612
- case "writeArgs": return {
6613
- field: 3,
6614
- payload: encodeWriteRejected(exec.path, TOOL_REJECT_REASON)
6615
- };
6616
- case "deleteArgs": return {
6617
- field: 4,
6618
- payload: encodeDeleteRejected(exec.path, TOOL_REJECT_REASON)
6619
- };
6620
- case "shellArgs": return {
6621
- field: 2,
6622
- payload: encodeShellRejectedResult(void 0, void 0, TOOL_REJECT_REASON)
6623
- };
6624
- case "shellStreamArgs": return {
6625
- field: 14,
6626
- payload: encodeShellStreamRejected(void 0, void 0, TOOL_REJECT_REASON)
6627
- };
6628
- case "backgroundShellSpawnArgs": return {
6629
- field: 16,
6630
- payload: encodeBackgroundShellRejectedResult(void 0, void 0, TOOL_REJECT_REASON)
6631
- };
6632
- case "fetchArgs": return {
6633
- field: 20,
6634
- payload: encodeFetchError(exec.url, TOOL_REJECT_REASON)
6635
- };
6636
- case "writeShellStdinArgs": return {
6637
- field: 23,
6638
- payload: encodeWriteShellStdinError(TOOL_REJECT_REASON)
6639
- };
6640
- case "diagnosticsArgs": return {
6641
- field: 9,
6642
- payload: encodeDiagnosticsResult()
6643
- };
6644
- case "recordScreenArgs": return {
6645
- field: 21,
6646
- payload: encodeMcpError("Screen recording is not available")
6647
- };
6648
- case "computerUseArgs": return {
6649
- field: 22,
6650
- payload: encodeMcpError("Computer use is not available")
6651
- };
6652
- case "listMcpResourcesExecArgs": return {
6653
- field: 17,
6654
- payload: encodeMcpError("MCP resources are not available")
6655
- };
6656
- case "readMcpResourceExecArgs": return {
6657
- field: 18,
6658
- payload: encodeMcpError("MCP resources are not available")
6659
- };
6660
- case "unknown":
6661
- if (typeof exec.field !== "number") return void 0;
6662
- return {
6663
- field: exec.field,
6664
- payload: encodeMcpError(TOOL_REJECT_REASON)
6665
- };
6666
- default: return;
3914
+ return ok({ ok: true });
3915
+ default: throw new BadRequest(`unknown /subscriptions-auth endpoint "${endpoint}"`);
6667
3916
  }
6668
3917
  }
6669
-
6670
- //#endregion
6671
- //#region src/providers/cursor-adapter.ts
6672
- /**
6673
- * The imported Cursor transport still reads tool results as user-role content
6674
- * blocks. DSH 0.1.7 carries them as first-class role=tool messages instead.
6675
- */
6676
- function projectCursorMessages(messages) {
6677
- return messages.map((message) => message.role === "tool" ? {
6678
- ...message,
6679
- role: "user",
6680
- content: [{
6681
- type: "tool-result",
6682
- toolCallId: message.toolCallId,
6683
- isError: message.isError,
6684
- content: message.content
6685
- }]
6686
- } : message);
6687
- }
6688
3918
  /**
6689
- * Vendor `LlmError` codes whose whole message is a local literal (no response
6690
- * header, trailer, body or end-stream text). Provider-derived failures can only
6691
- * carry a `classifyCursorError` code or `CURSOR_PROTOCOL`, never these, so the
6692
- * code is a sound discriminator for them.
3919
+ * Register the `/subscriptions-auth` RPC channel when a host connection exists.
3920
+ * @param ctx - the plugin context (headless profiles have no `connection`).
3921
+ * @param controller - the auth operations backing the endpoints.
3922
+ * @param speed - the per-session speed-tier state backing the Speed toggle.
3923
+ * @param modelDefaults - optional per-model default-effort state backing `modelDefaults`/`setModelDefault`.
3924
+ * @param providerSettings - optional OAuth provider/account preferences.
3925
+ * @param externalUsage - optional credential-backed quota readers.
3926
+ * @param ollama - optional Ollama Cloud catalog and connection settings.
6693
3927
  */
6694
- const LOCAL_VENDOR_CODES = new Set([
6695
- "UNSUPPORTED_OPTION",
6696
- "UNSUPPORTED_CONTENT",
6697
- "TOOL_LIMIT"
6698
- ]);
6699
- /**
6700
- * Plain-error messages the vendor throws as fixed literals (`vendor/cursor/
6701
- * index.js`: `waitForResponse`, the request write, the tool-continuation write,
6702
- * the stream `close` handler, the frame reader, the stall watchdog and the
6703
- * settings validation). None
6704
- * interpolates response text, so they are matched by exact message, the same way
6705
- * the auth messages are. A provider that echoed one of these sentences would
6706
- * only make the user read a local literal.
6707
- */
6708
- const LOCAL_VENDOR_MESSAGES = new Set([
6709
- "Cursor HTTP response timeout",
6710
- "Cursor HTTP stream closed before response",
6711
- "Cursor agent bridge closed before accepting the request",
6712
- "Cursor tool continuation bridge closed before accepting the result",
6713
- "Cursor sent an unreadable compressed frame",
6714
- "Cursor stream idle timeout",
6715
- "cursor-subscription: maxToolRounds must be an integer between 1 and 1000",
6716
- "cursor-subscription: retryCount must be an integer between 0 and 10",
6717
- "cursor-subscription: retryIntervalMs must be an integer between 0 and 300000",
6718
- "cursor-subscription: retryHttpStatusCodes must contain only HTTP status integers from 400 to 599",
6719
- "cursor-subscription: retryHttpStatusCodes must not contain duplicates",
6720
- "cursor-subscription: replayHistoryEachStep must be a boolean"
6721
- ]);
6722
- /**
6723
- * The same, for the two literals that embed a number the vendor computes: the
6724
- * response status (`Number(headers[':status'])`, so digits or `NaN`) and the
6725
- * configured progress timeout.
6726
- */
6727
- const LOCAL_VENDOR_PATTERNS = [/^Cursor agent returned HTTP \d{3}$/, /^Cursor stream progress timeout: no content for \d+ms$/];
6728
- /**
6729
- * Operating-system network error codes. An `Error` carrying one of these on
6730
- * itself, or on its `cause` (Node wraps a refused connection in
6731
- * `ERR_HTTP2_STREAM_CANCEL`), was produced by the local socket layer, so its
6732
- * message is local: `connect ECONNREFUSED 127.0.0.1:443`, `getaddrinfo ENOTFOUND
6733
- * host`. This is a property of the thrown object, which a provider cannot
6734
- * forge. It is deliberately not a test on the vendor's `TRANSPORT` code: the
6735
- * vendor derives that code from message text, so a provider's end-stream
6736
- * message or `grpc-message` containing "connection" or "socket" gets it too.
6737
- * Transport failures with no such code (TLS and certificate errors, HTTP/2
6738
- * protocol errors) are not recognised and stay replaced.
6739
- */
6740
- const LOCAL_SYSTEM_ERRNO = new Set([
6741
- "ECONNREFUSED",
6742
- "ECONNRESET",
6743
- "ECONNABORTED",
6744
- "ENOTFOUND",
6745
- "EAI_AGAIN",
6746
- "ETIMEDOUT",
6747
- "ENETUNREACH",
6748
- "ENETDOWN",
6749
- "EHOSTUNREACH",
6750
- "EHOSTDOWN",
6751
- "EPIPE"
6752
- ]);
6753
- function errorCode(value) {
6754
- return typeof value === "object" && value !== null ? value.code : void 0;
6755
- }
6756
- function isLocalSystemError(error) {
6757
- if (!(error instanceof Error)) return false;
6758
- const own = errorCode(error);
6759
- const cause = errorCode(error.cause);
6760
- return typeof own === "string" && LOCAL_SYSTEM_ERRNO.has(own) || typeof cause === "string" && LOCAL_SYSTEM_ERRNO.has(cause);
6761
- }
6762
- function messageOf(error) {
6763
- return error instanceof Error ? error.message : String(error);
6764
- }
6765
- /** Bound on remembered local failure messages; they are short and few. */
6766
- const MAX_LOCAL_MESSAGES = 64;
6767
- /**
6768
- * Remembers the messages of failures raised by this machine rather than read
6769
- * from the provider: whatever the auth service threw, whatever the host
6770
- * attachment store threw while reading an image, and a socket-layer network
6771
- * error seen on the run. The vendor stream turns each of these into
6772
- * `finish/error` carrying exactly `error.message`, so a failure whose message
6773
- * equals a remembered one originated locally and may keep its text. The match
6774
- * is by exact text: a provider that echoed a remembered message would only
6775
- * display a local literal.
6776
- */
6777
- var LocalFailureMessages = class {
6778
- messages = /* @__PURE__ */ new Set();
6779
- /** Wrap `auth` so its failures are remembered and rethrown unchanged. */
6780
- wrap(auth) {
6781
- return { accessToken: async (options) => {
3928
+ function registerAuthRpc(ctx, controller, speed, modelDefaults = void 0, providerSettings = void 0, externalUsage = void 0, ollama = void 0) {
3929
+ ctx.inject(["connection"], (ctx$1) => {
3930
+ const connection = ctx$1.get("connection");
3931
+ const handler = async (endpoint, payload, signal) => {
6782
3932
  try {
6783
- return await auth.accessToken(options);
3933
+ return await dispatch(controller, speed, modelDefaults, endpoint, payload, signal, providerSettings, externalUsage, ollama);
6784
3934
  } catch (error) {
6785
- this.remember(messageOf(error));
6786
- throw error;
3935
+ return failure(error);
6787
3936
  }
6788
- } };
6789
- }
6790
- /**
6791
- * Wrap the host attachment store the vendor reads images through, so a read
6792
- * failure keeps its own message. The store is a local service; its errors
6793
- * carry no provider text. Anything that is not a store passes through.
6794
- */
6795
- wrapAttachments(resolve$2) {
6796
- return () => {
6797
- const store = resolve$2();
6798
- const readImage = store?.readImage;
6799
- if (typeof readImage !== "function") return store;
6800
- return Object.create(store, { readImage: { value: async (...args) => {
6801
- try {
6802
- return await readImage.apply(store, args);
6803
- } catch (error) {
6804
- this.remember(messageOf(error));
6805
- throw error;
6806
- }
6807
- } } });
6808
- };
6809
- }
6810
- /**
6811
- * Observe a vendor run's two failure channels, the response promise and the
6812
- * frame reader, and remember the message of an error the socket layer raised.
6813
- * The run's behavior is unchanged: the same error is rethrown.
6814
- */
6815
- observeRun(run) {
6816
- if (typeof run !== "object" || run === null) return run;
6817
- const target = run;
6818
- const observe = (error) => {
6819
- if (isLocalSystemError(error)) this.remember(messageOf(error));
6820
- throw error;
6821
3937
  };
6822
- const wait = target.waitForResponse;
6823
- if (typeof wait === "function") target.waitForResponse = (...args) => Promise.resolve(wait.apply(target, args)).catch(observe);
6824
- const frames = target.frames;
6825
- const next = frames?.next;
6826
- if (frames !== void 0 && typeof next === "function") frames.next = (...args) => Promise.resolve(next.apply(frames, args)).catch(observe);
6827
- return run;
6828
- }
6829
- has(message) {
6830
- return this.messages.has(message);
6831
- }
6832
- remember(message) {
6833
- if (message === "") return;
6834
- this.messages.delete(message);
6835
- this.messages.add(message);
6836
- if (this.messages.size > MAX_LOCAL_MESSAGES) {
6837
- const oldest = this.messages.values().next().value;
6838
- if (oldest !== void 0) this.messages.delete(oldest);
6839
- }
6840
- }
6841
- };
6842
- function isVendorLocalMessage(message) {
6843
- return LOCAL_VENDOR_MESSAGES.has(message) || LOCAL_VENDOR_PATTERNS.some((pattern) => pattern.test(message));
3938
+ const register = connection.fetch.register;
3939
+ for (const endpoint of SUBSCRIPTIONS_AUTH_ENDPOINTS) ctx$1.effect(() => register(fetchRouteFor(endpoint, handler)), `dsh-subscriptions: /api/${SUBSCRIPTIONS_AUTH_PREFIX}${endpoint} route`);
3940
+ });
6844
3941
  }
6845
- /**
6846
- * Cursor adapter for the DSH host.
6847
- *
6848
- * Failure text contract: `finish/error` messages that originate from the
6849
- * vendor stream and can quote provider-controlled text (the response content
6850
- * type, trailers, end-stream errors, a frame-reader or other error whose message
6851
- * is not one of the literals below, and a transport-coded failure that is not a
6852
- * socket-layer error) are replaced by `<code> [provider response text omitted]`. Failures that are
6853
- * wholly local keep their own message and code:
6854
- *
6855
- * - anything the auth service threw (not signed in, sign-in expired, token
6856
- * refresh status), and a host attachment read failure;
6857
- * - the vendor's own request validation, by code (`UNSUPPORTED_OPTION`,
6858
- * `UNSUPPORTED_CONTENT`, `TOOL_LIMIT`);
6859
- * - the vendor's fixed-literal plain errors, by exact message: the HTTP status
6860
- * (`Cursor agent returned HTTP 429`), the response, idle and progress
6861
- * timeouts, a closed bridge or stream, an unreadable compressed frame, and
6862
- * an invalid Cursor setting;
6863
- * - a network error raised by the operating system (`ECONNREFUSED`,
6864
- * `ENOTFOUND`, ...), recognised from the thrown error's own code, not from
6865
- * the vendor's classification.
6866
- *
6867
- * Not a provider-origin class: a network error such as `ECONNREFUSED` is a
6868
- * local failure (no network, daemon down, DNS), and is kept. What is still
6869
- * replaced although it is probably local is a transport failure with no
6870
- * socket-layer errno: a TLS or certificate failure and an HTTP/2 protocol
6871
- * error. The vendor's `TRANSPORT` code cannot decide those, because
6872
- * `classifyCursorError` derives it from message text (`network`, `connection`,
6873
- * `socket`, `fetch`, `ECONN`, `http2`) and a provider's end-stream message or
6874
- * `grpc-message` can contain any of them; so the code is never trusted alone.
6875
- * Telling these apart would need a check on the thrown error object, as the
6876
- * errno check above does, extended to the TLS and HTTP/2 error codes. That is
6877
- * not done here and is not verified.
6878
- */
6879
- var CursorCompatAdapter = class extends CursorAdapter {
6880
- visibleModels;
6881
- localFailures;
6882
- constructor(options) {
6883
- const localFailures = new LocalFailureMessages();
6884
- super({
6885
- ...options,
6886
- auth: localFailures.wrap(options.auth),
6887
- ...options.resolveAttachments === void 0 ? {} : { resolveAttachments: localFailures.wrapAttachments(options.resolveAttachments) }
6888
- });
6889
- this.localFailures = localFailures;
6890
- this.visibleModels = options.visibleModels;
6891
- const vendor = this;
6892
- const createRun = vendor.createAgentRun;
6893
- vendor.createAgentRun = (access) => localFailures.observeRun(createRun(access));
6894
- }
6895
- async listModels(provider) {
6896
- const models = await super.listModels(provider);
6897
- const visible = this.visibleModels?.();
6898
- return visible === void 0 ? models : models.filter((model) => visible.includes(model.id));
6899
- }
6900
- async *stream(options) {
6901
- for await (const chunk of super.stream({
6902
- ...options,
6903
- model: options.model === "composer-2" ? "composer-2.5" : options.model,
6904
- messages: projectCursorMessages(options.messages)
6905
- })) if (chunk.type === "finish" && chunk.reason.kind === "error" && !LOCAL_VENDOR_CODES.has(chunk.reason.failure.code) && !isVendorLocalMessage(chunk.reason.failure.message) && !this.localFailures.has(chunk.reason.failure.message)) yield {
6906
- ...chunk,
6907
- reason: {
6908
- ...chunk.reason,
6909
- failure: {
6910
- ...chunk.reason.failure,
6911
- message: `${chunk.reason.failure.code} [provider response text omitted]`
6912
- }
6913
- }
6914
- };
6915
- else yield chunk;
6916
- }
6917
- };
6918
3942
 
6919
3943
  //#endregion
6920
3944
  //#region src/providers/ollama-web.ts
@@ -7032,13 +4056,13 @@ var OllamaWebSearchProvider = class {
7032
4056
  if (!Array.isArray(results)) throw new WebError("ollama web search returned no results array", "OLLAMA_WEB_BAD_REPLY");
7033
4057
  return {
7034
4058
  sources: results.flatMap((entry) => {
7035
- const record$4 = recordOf(entry);
7036
- const url = record$4?.["url"];
4059
+ const record$2 = recordOf(entry);
4060
+ const url = record$2?.["url"];
7037
4061
  if (typeof url !== "string" || url.length === 0) return [];
7038
4062
  return [{
7039
4063
  url,
7040
- ...typeof record$4?.["title"] === "string" ? { title: record$4?.["title"] } : {},
7041
- ...typeof record$4?.["content"] === "string" ? { snippet: record$4?.["content"] } : {}
4064
+ ...typeof record$2?.["title"] === "string" ? { title: record$2?.["title"] } : {},
4065
+ ...typeof record$2?.["content"] === "string" ? { snippet: record$2?.["content"] } : {}
7042
4066
  }];
7043
4067
  }),
7044
4068
  truncated: false
@@ -7076,51 +4100,6 @@ var OllamaWebFetchProvider = class {
7076
4100
  }
7077
4101
  };
7078
4102
 
7079
- //#endregion
7080
- //#region src/providers/cursor-model-settings.ts
7081
- function validateCursorModelSettings(input) {
7082
- if (input === null || typeof input !== "object" || Array.isArray(input)) throw new Error("Cursor model settings must be an object");
7083
- const models = input.visibleModels;
7084
- if (models === void 0) return {};
7085
- if (!Array.isArray(models) || models.some((id) => typeof id !== "string" || !id.trim())) throw new Error("visibleModels must be an array of model ids");
7086
- return { visibleModels: [...new Set(models)] };
7087
- }
7088
- /** Durable visibility preference; account tokens remain in DSH credentials. */
7089
- var CursorModelSettingsStore = class {
7090
- current = {};
7091
- writes = Promise.resolve();
7092
- constructor(path = dshHomePath("plugins", "subscriptions", "cursor-model-settings.json")) {
7093
- this.path = path;
7094
- try {
7095
- this.current = validateCursorModelSettings(JSON.parse(readFileSync(path, "utf8")));
7096
- } catch (error) {
7097
- if (error.code !== "ENOENT") throw new Error(`Cannot read Cursor model settings at ${path}`, { cause: error });
7098
- }
7099
- }
7100
- get() {
7101
- return structuredClone(this.current);
7102
- }
7103
- visibleModels() {
7104
- return this.current.visibleModels;
7105
- }
7106
- set(input) {
7107
- const validated = validateCursorModelSettings(input);
7108
- const run = this.writes.then(async () => {
7109
- await mkdir(dirname(this.path), { recursive: true });
7110
- const temporary = `${this.path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
7111
- try {
7112
- await writeFile(temporary, JSON.stringify(validated, null, 2), { mode: 384 });
7113
- await rename(temporary, this.path);
7114
- } finally {
7115
- await rm(temporary, { force: true });
7116
- }
7117
- this.current = validated;
7118
- });
7119
- this.writes = run.catch(() => void 0);
7120
- return run;
7121
- }
7122
- };
7123
-
7124
4103
  //#endregion
7125
4104
  //#region src/model-defaults.ts
7126
4105
  /** Absolute path of the defaults file. */
@@ -7167,11 +4146,11 @@ function sanitizeDefaults(value) {
7167
4146
  defaults: EMPTY,
7168
4147
  dropped: []
7169
4148
  };
7170
- const record$4 = value;
4149
+ const record$2 = value;
7171
4150
  const result = {};
7172
4151
  const dropped = [];
7173
4152
  for (const provider of PROVIDER_IDS) {
7174
- const section = sanitizeProvider(record$4[provider], dropped);
4153
+ const section = sanitizeProvider(record$2[provider], dropped);
7175
4154
  if (section !== void 0) result[provider] = section;
7176
4155
  }
7177
4156
  return {
@@ -7530,7 +4509,7 @@ function toAnthropicMessages(messages) {
7530
4509
  tool_use_id: id,
7531
4510
  content: toolResultContent({
7532
4511
  type: "tool-result",
7533
- toolCallId: ToolCallId$1(id),
4512
+ toolCallId: ToolCallId(id),
7534
4513
  content: message.content
7535
4514
  }),
7536
4515
  ...message.isError === true ? { is_error: true } : {}
@@ -7669,7 +4648,7 @@ function closeBlock$1(block) {
7669
4648
  };
7670
4649
  case "tool-call": return {
7671
4650
  type: "tool-call",
7672
- id: ToolCallId$1(block.callId),
4651
+ id: ToolCallId(block.callId),
7673
4652
  name: block.name ?? "",
7674
4653
  arguments: block.text
7675
4654
  };
@@ -7773,7 +4752,7 @@ var AnthropicStreamTranslator = class {
7773
4752
  chunks.push({
7774
4753
  type: "tool-call-delta",
7775
4754
  index: opened.index,
7776
- id: ToolCallId$1(opened.callId),
4755
+ id: ToolCallId(opened.callId),
7777
4756
  ...block.name === void 0 ? {} : { name: block.name },
7778
4757
  argumentsDelta: ""
7779
4758
  });
@@ -7810,7 +4789,7 @@ var AnthropicStreamTranslator = class {
7810
4789
  chunks.push({
7811
4790
  type: "tool-call-delta",
7812
4791
  index: block.index,
7813
- id: ToolCallId$1(block.callId),
4792
+ id: ToolCallId(block.callId),
7814
4793
  ...block.name === void 0 ? {} : { name: block.name },
7815
4794
  argumentsDelta: delta.partial_json ?? ""
7816
4795
  });
@@ -9159,7 +6138,7 @@ function closeBlock(block) {
9159
6138
  };
9160
6139
  case "tool-call": return {
9161
6140
  type: "tool-call",
9162
- id: ToolCallId$1(block.callId),
6141
+ id: ToolCallId(block.callId),
9163
6142
  name: block.name ?? "",
9164
6143
  arguments: block.text
9165
6144
  };
@@ -9249,7 +6228,7 @@ var ResponsesStreamTranslator = class {
9249
6228
  chunks.push({
9250
6229
  type: "tool-call-delta",
9251
6230
  index: block.index,
9252
- id: ToolCallId$1(callId),
6231
+ id: ToolCallId(callId),
9253
6232
  ...item.name === void 0 ? {} : { name: item.name },
9254
6233
  argumentsDelta: ""
9255
6234
  });
@@ -9291,7 +6270,7 @@ var ResponsesStreamTranslator = class {
9291
6270
  chunks.push({
9292
6271
  type: "tool-call-delta",
9293
6272
  index: block.index,
9294
- id: ToolCallId$1(block.callId),
6273
+ id: ToolCallId(block.callId),
9295
6274
  ...block.name === void 0 ? {} : { name: block.name },
9296
6275
  argumentsDelta: event.delta ?? ""
9297
6276
  });
@@ -10501,9 +7480,9 @@ var PoolHealthRegistry = class {
10501
7480
  }
10502
7481
  /** Whether one registry key is clear right now. */
10503
7482
  isAvailable(key, now = Date.now()) {
10504
- const record$4 = this.records.get(key);
10505
- if (record$4 === void 0) return true;
10506
- if (record$4.unavailableUntil <= now) {
7483
+ const record$2 = this.records.get(key);
7484
+ if (record$2 === void 0) return true;
7485
+ if (record$2.unavailableUntil <= now) {
10507
7486
  this.records.delete(key);
10508
7487
  return true;
10509
7488
  }
@@ -10529,20 +7508,20 @@ var PoolHealthRegistry = class {
10529
7508
  */
10530
7509
  earliestRecovery(keys, now = Date.now()) {
10531
7510
  let earliest;
10532
- for (const [key, record$4] of this.records) {
10533
- if (record$4.unavailableUntil <= now) {
7511
+ for (const [key, record$2] of this.records) {
7512
+ if (record$2.unavailableUntil <= now) {
10534
7513
  this.records.delete(key);
10535
7514
  continue;
10536
7515
  }
10537
7516
  if (!keys.has(key)) continue;
10538
- if (earliest === void 0 || record$4.unavailableUntil < earliest) earliest = record$4.unavailableUntil;
7517
+ if (earliest === void 0 || record$2.unavailableUntil < earliest) earliest = record$2.unavailableUntil;
10539
7518
  }
10540
7519
  return earliest;
10541
7520
  }
10542
7521
  /** Active failure codes for the given keys; reading them does not change cooldowns. */
10543
7522
  unavailableReasons(keys, now = Date.now()) {
10544
7523
  const reasons = [];
10545
- for (const [key, record$4] of this.records) if (keys.has(key) && record$4.unavailableUntil > now) reasons.push(record$4.reason);
7524
+ for (const [key, record$2] of this.records) if (keys.has(key) && record$2.unavailableUntil > now) reasons.push(record$2.reason);
10546
7525
  return reasons;
10547
7526
  }
10548
7527
  /** Drop records of one provider, or of a single account when given (auth changes). */
@@ -10840,22 +7819,29 @@ var PoolAdapter = class extends LlmAdapter {
10840
7819
  throw this.exhaustedWithWarn(options.model, members, failures);
10841
7820
  }
10842
7821
  /**
10843
- * Order the candidates for one request. Health filters both strategies;
10844
- * `quota_aware` then ranks by urgency (members without telemetry, e.g.
10845
- * copilot, score zero and sink to the bottom of their class), while
10846
- * quota-exhausted members stay as a last-resort tail in pool order. The
10847
- * sticky member keeps its lead unless a challenger out-scores it by
10848
- * `switchMargin`.
7822
+ * Order the candidates for one request. Health and known exhaustion filter
7823
+ * both strategies: an applicable window at 100% must never become a paid
7824
+ * fallback. `quota_aware` then ranks by urgency (members without telemetry,
7825
+ * e.g. copilot, score zero and sink to the bottom of their class), while
7826
+ * near-full members stay as a last-resort tail in urgency order. The sticky
7827
+ * member keeps its lead unless a challenger out-scores it by `switchMargin`.
10849
7828
  */
10850
7829
  async select(poolId, members, sessionId) {
10851
7830
  const usable = members.filter((member) => this.options.adapters[member.provider] !== void 0 && this.options.health.isMemberAvailable(member.provider, member.account, member.model));
10852
7831
  if (usable.length === 0) return [];
10853
- const stickyMember = sessionId === void 0 ? void 0 : usable.find((member) => memberKey(member.provider, member.account, member.model) === this.sticky.get(stickyKey(poolId, sessionId)));
10854
- if (this.options.strategy === "priority") return stickyMember === void 0 ? usable : [stickyMember, ...usable.filter((member) => member !== stickyMember)];
10855
7832
  const quotas = new Map(await Promise.all(usable.map(async (member) => [member, await this.options.usage.quotaFor(member)])));
10856
- const scored = usable.filter((member) => quotas.get(member)?.available === true);
10857
- const quotaFull = usable.filter((member) => quotas.get(member)?.available === false);
7833
+ const candidates = usable.filter((member) => quotas.get(member)?.exhausted !== true);
7834
+ if (candidates.length === 0) {
7835
+ const message = `${poolId}: all currently usable accounts have exhausted subscription quota; the request was stopped before spending paid credits. After an external reset, use Settings > Subscriptions > Refresh to re-check usage.`;
7836
+ this.warnOnce(message);
7837
+ throw new LlmError(message, QUOTA_EXCEEDED_CODE);
7838
+ }
7839
+ const stickyMember = sessionId === void 0 ? void 0 : candidates.find((member) => memberKey(member.provider, member.account, member.model) === this.sticky.get(stickyKey(poolId, sessionId)));
7840
+ if (this.options.strategy === "priority") return stickyMember === void 0 ? candidates : [stickyMember, ...candidates.filter((member) => member !== stickyMember)];
7841
+ const scored = candidates.filter((member) => quotas.get(member)?.available === true);
7842
+ const quotaFull = candidates.filter((member) => quotas.get(member)?.available === false);
10858
7843
  scored.sort((a, b) => (quotas.get(b)?.urgency ?? 0) - (quotas.get(a)?.urgency ?? 0));
7844
+ quotaFull.sort((a, b) => (quotas.get(b)?.urgency ?? 0) - (quotas.get(a)?.urgency ?? 0));
10859
7845
  if (stickyMember !== void 0 && scored.includes(stickyMember)) {
10860
7846
  const best = scored[0];
10861
7847
  const stickyUrgency = quotas.get(stickyMember)?.urgency ?? 0;
@@ -11275,21 +8261,19 @@ var PoolUsageTracker = class {
11275
8261
  * (member selection must never block on the network mid-conversation). A
11276
8262
  * failure still cooling down degrades immediately with no network call.
11277
8263
  *
11278
- * Deliberately does NOT fall back to `lastSnapshot` the way
11279
- * {@link snapshotFor} does: scoring routing decisions off data that is
11280
- * known to be stale-and-unrefreshable risks steering traffic by a urgency
11281
- * number the endpoint itself is no longer vouching for, whereas
11282
- * `snapshotFor`'s stale-display concern (the Settings page, the composer
11283
- * badge) has no such downside — showing an old percentage beats showing
11284
- * nothing.
8264
+ * A failed refresh does not reuse stale urgency for routing order, but it
8265
+ * does retain a stale snapshot's known exhaustion bit. That one-way safety
8266
+ * guard prevents a failed Settings refresh from re-enabling paid-credit
8267
+ * fallback; an account with no successful usage sample still fails open.
11285
8268
  * @param member - the pool member to score (account resolved).
11286
- * @returns availability plus the urgency score.
8269
+ * @returns availability, known exhaustion, and the urgency score.
11287
8270
  */
11288
8271
  async quotaFor(member) {
11289
8272
  const key = `${member.provider}/${member.account}`;
11290
8273
  const fetcher = this.fetcherFor(member.provider, member.account);
11291
8274
  if (fetcher === void 0) return {
11292
8275
  available: true,
8276
+ exhausted: false,
11293
8277
  urgency: 0,
11294
8278
  fetchedAt: 0
11295
8279
  };
@@ -11300,7 +8284,7 @@ var PoolUsageTracker = class {
11300
8284
  if (!fresh) this.refresh(key, fetcher).catch(() => void 0);
11301
8285
  return this.score(member, entry);
11302
8286
  }
11303
- if (fresh) return degradedQuota(entry.error);
8287
+ if (fresh) return this.degraded(member, entry.error, entry.lastSnapshot);
11304
8288
  }
11305
8289
  try {
11306
8290
  const snapshot = await this.refresh(key, fetcher);
@@ -11309,7 +8293,8 @@ var PoolUsageTracker = class {
11309
8293
  at: Date.now()
11310
8294
  });
11311
8295
  } catch (error) {
11312
- return degradedQuota(error);
8296
+ const failed = this.entries.get(key);
8297
+ return this.degraded(member, error, failed?.snapshot ?? failed?.lastSnapshot);
11313
8298
  }
11314
8299
  }
11315
8300
  /**
@@ -11415,17 +8400,27 @@ var PoolUsageTracker = class {
11415
8400
  }
11416
8401
  return pending;
11417
8402
  }
8403
+ /** Degrade ordering while retaining only a cached safety exhaustion bit. */
8404
+ degraded(member, error, snapshot) {
8405
+ return degradedQuota(error, (snapshot === void 0 ? void 0 : this.score(member, {
8406
+ snapshot,
8407
+ at: 0
8408
+ }))?.exhausted === true);
8409
+ }
11418
8410
  /** Score one member against a snapshot's windows. */
11419
8411
  score(member, entry) {
11420
8412
  const windows = (entry.snapshot.windows ?? []).filter((window) => windowApplies(window, member.model));
11421
8413
  let available = true;
8414
+ let exhausted = false;
11422
8415
  let urgency = 0;
11423
8416
  for (const window of windows) {
11424
8417
  if (window.usedPercent >= QUOTA_FULL_PERCENT) available = false;
8418
+ if (window.usedPercent >= 100) exhausted = true;
11425
8419
  urgency = Math.max(urgency, windowUrgency(window));
11426
8420
  }
11427
8421
  return {
11428
8422
  available,
8423
+ exhausted,
11429
8424
  urgency,
11430
8425
  fetchedAt: entry.at
11431
8426
  };
@@ -11433,17 +8428,14 @@ var PoolUsageTracker = class {
11433
8428
  };
11434
8429
  /**
11435
8430
  * The routing view of a fetch failure. Logged out: the member cannot serve
11436
- * at all. Any other failure (network, endpoint rate limit) must not block
11437
- * routing — the member stays available with a zero score, degrading the
11438
- * strategy to plain priority order for it.
8431
+ * at all. Any other failure (network, endpoint rate limit) has a zero urgency;
8432
+ * without a successful sample it stays available, while known exhaustion
8433
+ * remains unavailable so stale telemetry cannot re-enable paid fallback.
11439
8434
  */
11440
- function degradedQuota(error) {
11441
- return isMissingOrInvalidCredential(error) ? {
11442
- available: false,
11443
- urgency: 0,
11444
- fetchedAt: 0
11445
- } : {
11446
- available: true,
8435
+ function degradedQuota(error, exhausted = false) {
8436
+ return {
8437
+ available: !isMissingOrInvalidCredential(error) && !exhausted,
8438
+ exhausted,
11447
8439
  urgency: 0,
11448
8440
  fetchedAt: 0
11449
8441
  };
@@ -13074,7 +10066,7 @@ function toAntigravityContents(messages, model) {
13074
10066
  name: callNames.get(id) ?? "",
13075
10067
  response: toolResultValue({
13076
10068
  type: "tool-result",
13077
- toolCallId: ToolCallId$1(id),
10069
+ toolCallId: ToolCallId(id),
13078
10070
  content: message.content,
13079
10071
  ...message.isError === void 0 ? {} : { isError: message.isError }
13080
10072
  })
@@ -13242,7 +10234,7 @@ var AntigravityStreamTranslator = class {
13242
10234
  };
13243
10235
  else content = {
13244
10236
  type: "tool-call",
13245
- id: ToolCallId$1(block.id ?? `call_${randomUUID().replaceAll("-", "")}`),
10237
+ id: ToolCallId(block.id ?? `call_${randomUUID().replaceAll("-", "")}`),
13246
10238
  name: block.name ?? "",
13247
10239
  arguments: block.text
13248
10240
  };
@@ -13336,7 +10328,7 @@ var AntigravityStreamTranslator = class {
13336
10328
  chunks.push({
13337
10329
  type: "tool-call-delta",
13338
10330
  index: block.index,
13339
- id: ToolCallId$1(id),
10331
+ id: ToolCallId(id),
13340
10332
  name: call.name ?? "",
13341
10333
  argumentsDelta: args
13342
10334
  });
@@ -14115,11 +11107,11 @@ function parseImageGenerateResponse(payload) {
14115
11107
  const images = [];
14116
11108
  for (const entry of entries) {
14117
11109
  if (typeof entry !== "object" || entry === null) continue;
14118
- const record$4 = entry;
14119
- if (typeof record$4.b64_json !== "string" || record$4.b64_json.length === 0) continue;
11110
+ const record$2 = entry;
11111
+ if (typeof record$2.b64_json !== "string" || record$2.b64_json.length === 0) continue;
14120
11112
  images.push({
14121
- data: Buffer.from(record$4.b64_json, "base64"),
14122
- ...typeof record$4.revised_prompt === "string" && record$4.revised_prompt.length > 0 ? { revisedPrompt: record$4.revised_prompt } : {}
11113
+ data: Buffer.from(record$2.b64_json, "base64"),
11114
+ ...typeof record$2.revised_prompt === "string" && record$2.revised_prompt.length > 0 ? { revisedPrompt: record$2.revised_prompt } : {}
14123
11115
  });
14124
11116
  }
14125
11117
  if (images.length === 0) throw new Error("image_generate: the response carried no image data");
@@ -15369,7 +12361,6 @@ function apply(ctx, config) {
15369
12361
  restoreConnectAttemptTimeout(previousAttemptTimeout);
15370
12362
  }, "dsh-subscriptions: connect attempt timeout");
15371
12363
  const preferences = new ProviderSettingsStore();
15372
- const cursorModelSettings = new CursorModelSettingsStore();
15373
12364
  const ollamaSettings = new OllamaSettingsStore(void 0, (message) => {
15374
12365
  ctx.logger.warn(`${name}: ${message}`);
15375
12366
  });
@@ -15802,30 +12793,15 @@ function apply(ctx, config) {
15802
12793
  }
15803
12794
  };
15804
12795
  let resolveExternalCredential;
15805
- let cursorAuth;
15806
- let cursorAdapter;
15807
- let cursorHandle;
15808
- let cursorAuthenticated;
15809
- let cursorAttachments;
15810
12796
  let ollamaAdapter;
15811
12797
  let ollamaHandle;
15812
- ctx.inject(["attachments"], (attachmentsCtx) => {
15813
- cursorAttachments = attachmentsCtx.attachments;
15814
- });
15815
12798
  ctx.inject(["credentials"], (credentialsCtx) => {
15816
12799
  resolveExternalCredential = (name$1) => credentialsCtx.credentials.resolve(credentialRef(name$1));
15817
- cursorAuth = new CursorAuth(credentialsCtx.credentials, hostFetch);
15818
- cursorAdapter = new CursorCompatAdapter({
15819
- auth: cursorAuth,
15820
- resolveAttachments: () => cursorAttachments,
15821
- visibleModels: () => cursorModelSettings.visibleModels()
15822
- });
15823
- cursorHandle = credentialsCtx.llm.registerAdapter(["cursor-subscription"], cursorAdapter);
15824
12800
  ollamaAdapter = new OllamaAdapter({
15825
12801
  apiKey: async () => (await resolveExternalCredential?.(ollamaApiKeyRef()))?.value,
15826
12802
  baseURL: ollamaBaseURL,
15827
12803
  catalog: ollamaCatalog,
15828
- resolveAttachments: () => cursorAttachments,
12804
+ resolveAttachments,
15829
12805
  streamIdleTimeoutMs,
15830
12806
  onWarn
15831
12807
  });
@@ -15948,79 +12924,6 @@ function apply(ctx, config) {
15948
12924
  for (const [route, handle] of handles) handle.replace([route]);
15949
12925
  }
15950
12926
  }, externalUsage, {
15951
- status: async () => {
15952
- const status = cursorAuth === void 0 ? {
15953
- authenticated: false,
15954
- busy: false
15955
- } : await cursorAuth.status();
15956
- if (cursorAuthenticated !== status.authenticated) {
15957
- cursorAuthenticated = status.authenticated;
15958
- cursorAdapter?.invalidateModels();
15959
- cursorHandle?.replace(["cursor-subscription"]);
15960
- }
15961
- return status;
15962
- },
15963
- login: () => {
15964
- if (cursorAuth === void 0) throw new Error("DSH credentials are unavailable");
15965
- return cursorAuth.login();
15966
- },
15967
- cancel: async () => {
15968
- await cursorAuth?.cancel();
15969
- },
15970
- logout: async () => {
15971
- await cursorAuth?.logout();
15972
- cursorAuthenticated = false;
15973
- cursorAdapter?.invalidateModels();
15974
- cursorHandle?.replace(["cursor-subscription"]);
15975
- },
15976
- usage: async (signal) => {
15977
- if (cursorAuth === void 0) throw new Error("DSH credentials are unavailable");
15978
- return fetchCursorUsage(await cursorAuth.accessToken({ signal }), hostFetch, signal);
15979
- },
15980
- models: async (force, signal) => {
15981
- if (cursorAdapter === void 0) throw new Error("Cursor model provider is unavailable");
15982
- if (force) cursorAdapter.invalidateModels();
15983
- const models = await cursorAdapter.listModelsForRpc({
15984
- force,
15985
- signal
15986
- });
15987
- if (force) cursorHandle?.replace(["cursor-subscription"]);
15988
- return models;
15989
- },
15990
- settings: async (force, signal) => {
15991
- const status = cursorAuth === void 0 ? { authenticated: false } : await cursorAuth.status();
15992
- const models = status.authenticated && cursorAdapter !== void 0 ? await cursorAdapter.listModelsForRpc({
15993
- force,
15994
- signal
15995
- }) : [];
15996
- if (force && status.authenticated) cursorHandle?.replace(["cursor-subscription"]);
15997
- return {
15998
- provider: "cursor-subscription",
15999
- settings: cursorModelSettings.get(),
16000
- models: models.map(({ id, name: name$1 }) => ({
16001
- id,
16002
- name: name$1,
16003
- efforts: []
16004
- })),
16005
- tools: [],
16006
- accounts: status.authenticated ? [{
16007
- key: "cursor",
16008
- label: "Cursor",
16009
- models
16010
- }] : []
16011
- };
16012
- },
16013
- setSettings: async (input) => {
16014
- let validated;
16015
- try {
16016
- validated = validateCursorModelSettings(input);
16017
- } catch (error) {
16018
- throw new BadRequest(error instanceof Error ? error.message : String(error));
16019
- }
16020
- await cursorModelSettings.set(validated);
16021
- cursorHandle?.replace(["cursor-subscription"]);
16022
- }
16023
- }, {
16024
12927
  settings: async () => ({
16025
12928
  provider: "ollama-cloud",
16026
12929
  settings: ollamaSettings.get(),