dsh-subscriptions 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +28 -32
  2. package/lib/auth/rpc.d.ts +10 -24
  3. package/lib/auth/rpc.js +18 -51
  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 +61 -43
  8. package/lib/client/SpeedSelect.js +13 -12
  9. package/lib/client/SubscriptionUsageBadge.d.ts +1 -1
  10. package/lib/client/SubscriptionUsageBadge.js +1 -13
  11. package/lib/client/SubscriptionsSection.d.ts +2 -0
  12. package/lib/client/SubscriptionsSection.js +10 -4
  13. package/lib/client/locales.d.ts +12 -32
  14. package/lib/client/locales.js +14 -34
  15. package/lib/client/model-filter.d.ts +37 -0
  16. package/lib/client/model-filter.js +45 -0
  17. package/lib/client/subscription-card-styles.d.ts +1 -1
  18. package/lib/client/subscription-card-styles.js +1 -1
  19. package/lib/client/usage-window-filter.d.ts +7 -0
  20. package/lib/client/usage-window-filter.js +12 -0
  21. package/lib/client.js +374 -584
  22. package/lib/client.js.map +1 -1
  23. package/lib/index.js +249 -3322
  24. package/lib/provider-settings.d.ts +5 -0
  25. package/lib/provider-settings.js +21 -0
  26. package/lib/providers/ollama-settings.d.ts +1 -1
  27. package/lib/providers/ollama-settings.js +1 -1
  28. package/lib/translate/antigravity.js +10 -2
  29. package/package.json +2 -7
  30. package/lib/client/CursorCard.d.ts +0 -9
  31. package/lib/client/CursorCard.js +0 -151
  32. package/lib/providers/cursor-adapter.d.ts +0 -50
  33. package/lib/providers/cursor-adapter.js +0 -304
  34. package/lib/providers/cursor-auth.d.ts +0 -43
  35. package/lib/providers/cursor-auth.js +0 -221
  36. package/lib/providers/cursor-model-settings.d.ts +0 -15
  37. package/lib/providers/cursor-model-settings.js +0 -60
  38. package/lib/providers/cursor-usage.d.ts +0 -9
  39. package/lib/providers/cursor-usage.js +0 -131
  40. package/vendor/cursor/LICENSE +0 -21
  41. package/vendor/cursor/index.d.ts +0 -18
  42. package/vendor/cursor/index.js +0 -3574
  43. 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");
@@ -3834,3081 +3823,122 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
3834
3823
  accounts: [],
3835
3824
  detail: error instanceof Error ? error.message : String(error)
3836
3825
  }))]));
3837
- return ok({ providers: Object.fromEntries(entries) });
3838
- }
3839
- case "login": {
3840
- const provider = readProvider(payload);
3841
- return ok(await controller.login(provider, readLoginMethod(payload, provider)));
3842
- }
3843
- case "manual": {
3844
- const provider = readProvider(payload);
3845
- await controller.manual(provider, readString(payload, "input"));
3846
- return ok({ ok: true });
3847
- }
3848
- case "cancel":
3849
- await controller.cancel(readProvider(payload));
3850
- return ok({ ok: true });
3851
- case "logout": {
3852
- const provider = readProvider(payload);
3853
- await controller.logout(provider, readString(payload, "account"));
3854
- return ok({ ok: true });
3855
- }
3856
- case "setDefault": {
3857
- const provider = readProvider(payload);
3858
- await controller.setDefault(provider, readString(payload, "account"));
3859
- return ok({ ok: true });
3860
- }
3861
- case "usage": {
3862
- const provider = readProvider(payload);
3863
- return ok(await controller.usage(provider, readString(payload, "account"), signal, readForce(payload)));
3864
- }
3865
- case "externalStatus":
3866
- if (!externalUsage) throw new BadRequest("external usage is unavailable");
3867
- return ok(await externalUsage.status());
3868
- case "externalUsage": {
3869
- if (!externalUsage) throw new BadRequest("external usage is unavailable");
3870
- const source = readString(payload, "source");
3871
- if (!EXTERNAL_USAGE_SOURCES.includes(source)) throw new BadRequest(`payload.source must be one of ${EXTERNAL_USAGE_SOURCES.join(", ")}`);
3872
- return ok(await externalUsage.usage(source, signal));
3873
- }
3874
- case "cursorStatus":
3875
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3876
- return ok(await cursor.status());
3877
- case "cursorLogin":
3878
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3879
- return ok(await cursor.login());
3880
- case "cursorCancel":
3881
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3882
- await cursor.cancel();
3883
- return ok({ ok: true });
3884
- case "cursorLogout":
3885
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3886
- await cursor.logout();
3887
- return ok({ ok: true });
3888
- case "cursorUsage":
3889
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3890
- return ok(await cursor.usage(signal));
3891
- case "cursorModels":
3892
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3893
- return ok(await cursor.models(readForce(payload), signal));
3894
- case "cursorSettings":
3895
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3896
- return ok(await cursor.settings(readForce(payload), signal));
3897
- case "cursorSetSettings":
3898
- if (!cursor) throw new BadRequest("Cursor account is unavailable");
3899
- if (payload === null || typeof payload !== "object") throw new BadRequest("payload must be an object");
3900
- await cursor.setSettings(payload.settings);
3901
- return ok({ ok: true });
3902
- case "ollamaSettings":
3903
- if (!ollama) throw new BadRequest("Ollama settings are unavailable");
3904
- return ok(await ollama.settings());
3905
- case "ollamaSetSettings":
3906
- if (!ollama) throw new BadRequest("Ollama settings are unavailable");
3907
- if (payload === null || typeof payload !== "object") throw new BadRequest("payload must be an object");
3908
- await ollama.setSettings(payload.settings);
3909
- return ok({ ok: true });
3910
- case "ollamaModels": {
3911
- if (!ollama) throw new BadRequest("Ollama settings are unavailable");
3912
- const apiKey = readOptionalString(payload, "apiKey");
3913
- const rawBaseURL = readOptionalString(payload, "baseURL");
3914
- let baseURL;
3915
- if (rawBaseURL !== void 0) try {
3916
- baseURL = validateOllamaBaseURL(rawBaseURL);
3917
- } catch (error) {
3918
- throw new BadRequest(error instanceof Error ? error.message : String(error));
3919
- }
3920
- return ok(await ollama.models({
3921
- ...apiKey === void 0 ? {} : { apiKey },
3922
- ...baseURL === void 0 ? {} : { baseURL }
3923
- }, signal));
3924
- }
3925
- case "prepareReset":
3926
- if (!controller.prepareReset) throw new BadRequest("Reset redemption unavailable");
3927
- return ok(await controller.prepareReset(readString(payload, "account"), signal));
3928
- case "consumeReset":
3929
- if (!controller.consumeReset) throw new BadRequest("Reset redemption unavailable");
3930
- await controller.consumeReset(readString(payload, "account"), readString(payload, "ticket"), signal);
3931
- return ok({ ok: true });
3932
- case "image": return ok(await controller.readImage(readImageRef(payload), signal));
3933
- case "video": return ok(await controller.readVideo(readVideoName(payload), signal));
3934
- case "speed": return ok(await speed.speed(readSessionId(payload)));
3935
- case "setSpeed":
3936
- await speed.setSpeed(readSessionId(payload), readSpeedTier(payload));
3937
- return ok({ ok: true });
3938
- case "modelDefaults":
3939
- if (modelDefaults === void 0) throw new BadRequest("model defaults are unavailable");
3940
- return ok(await modelDefaults.catalog(readForce(payload)));
3941
- case "setModelDefault":
3942
- if (modelDefaults === void 0) throw new BadRequest("model defaults are unavailable");
3943
- {
3944
- const input = readModelDefaultInput(payload);
3945
- await modelDefaults.set(input.provider, input.model, input.effort);
3946
- }
3947
- return ok({ ok: true });
3948
- default: throw new BadRequest(`unknown /subscriptions-auth endpoint "${endpoint}"`);
3949
- }
3950
- }
3951
- /**
3952
- * Register the `/subscriptions-auth` RPC channel when a host connection exists.
3953
- * @param ctx - the plugin context (headless profiles have no `connection`).
3954
- * @param controller - the auth operations backing the endpoints.
3955
- * @param speed - the per-session speed-tier state backing the Speed toggle.
3956
- * @param modelDefaults - optional per-model default-effort state backing `modelDefaults`/`setModelDefault`.
3957
- * @param providerSettings - optional OAuth provider/account preferences.
3958
- * @param externalUsage - optional credential-backed quota readers.
3959
- * @param cursor - optional Cursor account and model-visibility actions.
3960
- * @param ollama - optional Ollama Cloud catalog and connection settings.
3961
- */
3962
- function registerAuthRpc(ctx, controller, speed, modelDefaults = void 0, providerSettings = void 0, externalUsage = void 0, cursor = void 0, ollama = void 0) {
3963
- ctx.inject(["connection"], (ctx$1) => {
3964
- const connection = ctx$1.get("connection");
3965
- const handler = async (endpoint, payload, signal) => {
3966
- try {
3967
- return await dispatch(controller, speed, modelDefaults, endpoint, payload, signal, providerSettings, externalUsage, cursor, ollama);
3968
- } catch (error) {
3969
- return failure(error);
3970
- }
3971
- };
3972
- const register = connection.fetch.register;
3973
- for (const endpoint of SUBSCRIPTIONS_AUTH_ENDPOINTS) ctx$1.effect(() => register(fetchRouteFor(endpoint, handler)), `dsh-subscriptions: /api/${SUBSCRIPTIONS_AUTH_PREFIX}${endpoint} route`);
3974
- });
3975
- }
3976
-
3977
- //#endregion
3978
- //#region src/providers/cursor-auth.ts
3979
- const CURSOR_CREDENTIAL_REF = credentialRef("CURSOR_SUBSCRIPTION_OAUTH");
3980
- const API_ORIGIN = "https://api2.cursor.sh";
3981
- const REFRESH_AHEAD_MS$1 = 5 * 6e4;
3982
- async function parseCredential(value) {
3983
- if (value === void 0 || value.length === 0) return void 0;
3984
- const parsed = await parseProviderJson(value, "Cursor stored credential");
3985
- if (parsed === null || typeof parsed !== "object") throw new Error("Cursor credential is malformed");
3986
- const item = parsed;
3987
- 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");
3988
- return {
3989
- type: "oauth",
3990
- access: item.access,
3991
- refresh: item.refresh,
3992
- expires: item.expires
3993
- };
3994
- }
3995
- function tokenExpiry(token, now) {
3996
- try {
3997
- const payload = token.split(".")[1];
3998
- if (payload === void 0) return now + 1440 * 6e4;
3999
- const exp = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"))?.exp;
4000
- if (typeof exp === "number" && Number.isFinite(exp)) return exp * 1e3;
4001
- } catch {}
4002
- return now + 1440 * 6e4;
4003
- }
4004
- function abortableDelay$2(ms, signal) {
4005
- if (signal.aborted) return Promise.reject(signal.reason);
4006
- return new Promise((resolve$2, reject) => {
4007
- const timer = setTimeout(() => {
4008
- signal.removeEventListener("abort", aborted);
4009
- resolve$2();
4010
- }, ms);
4011
- function aborted() {
4012
- clearTimeout(timer);
4013
- reject(signal.reason);
4014
- }
4015
- signal.addEventListener("abort", aborted, { once: true });
4016
- });
4017
- }
4018
- var CursorAuth = class {
4019
- loginTask;
4020
- error;
4021
- refreshTask;
4022
- constructor(credentials, http$1 = fetch, now = Date.now) {
4023
- this.credentials = credentials;
4024
- this.http = http$1;
4025
- this.now = now;
4026
- }
4027
- async read() {
4028
- return parseCredential((await this.credentials.resolve(CURSOR_CREDENTIAL_REF))?.value);
4029
- }
4030
- async status() {
4031
- const current$1 = await this.read();
4032
- return {
4033
- authenticated: current$1 !== void 0,
4034
- busy: this.loginTask !== void 0,
4035
- ...current$1 === void 0 ? {} : { expiresAt: current$1.expires },
4036
- ...this.error === void 0 ? {} : { error: this.error }
4037
- };
4038
- }
4039
- async accessToken({ signal } = {}) {
4040
- signal?.throwIfAborted();
4041
- const current$1 = await this.read();
4042
- if (current$1 === void 0) throw new Error("Cursor is not signed in");
4043
- if (current$1.expires - this.now() > REFRESH_AHEAD_MS$1) return current$1.access;
4044
- if (current$1.refresh.length === 0) throw new Error("Cursor sign-in needs to be renewed");
4045
- if (this.refreshTask === void 0) {
4046
- const pending = this.refresh(current$1, signal);
4047
- this.refreshTask = pending;
4048
- pending.finally(() => {
4049
- if (this.refreshTask === pending) this.refreshTask = void 0;
4050
- }).catch(() => void 0);
4051
- }
4052
- return (await this.refreshTask).access;
4053
- }
4054
- async refresh(current$1, signal) {
4055
- const response = await this.http(`${API_ORIGIN}/auth/exchange_user_api_key`, {
4056
- method: "POST",
4057
- redirect: "error",
4058
- headers: {
4059
- authorization: `Bearer ${current$1.refresh}`,
4060
- accept: "application/json",
4061
- "content-type": "application/json"
4062
- },
4063
- body: "{}",
4064
- ...signal === void 0 ? {} : { signal }
4065
- });
4066
- if (!response.ok) throw new Error(`Cursor token refresh failed (HTTP ${response.status})`);
4067
- const body = await parseProviderJson(response, "Cursor token refresh");
4068
- if (typeof body.accessToken !== "string" || body.accessToken.length === 0) throw new Error("Cursor token refresh returned no access token");
4069
- const next = {
4070
- type: "oauth",
4071
- access: body.accessToken,
4072
- refresh: typeof body.refreshToken === "string" && body.refreshToken.length > 0 ? body.refreshToken : current$1.refresh,
4073
- expires: tokenExpiry(body.accessToken, this.now())
4074
- };
4075
- const latest = await this.read();
4076
- if (latest === void 0) throw new Error("Cursor signed out during token refresh");
4077
- if (latest.refresh !== current$1.refresh) return latest;
4078
- await this.credentials.set(CURSOR_CREDENTIAL_REF, JSON.stringify(next));
4079
- return next;
4080
- }
4081
- /** Return a Cursor browser URL immediately; polling continues on the host. */
4082
- async login() {
4083
- await this.cancel();
4084
- this.error = void 0;
4085
- const verifier = randomBytes(96).toString("base64url");
4086
- const challenge = createHash("sha256").update(verifier).digest("base64url");
4087
- const uuid = randomUUID();
4088
- const url = new URL("https://cursor.com/loginDeepControl");
4089
- url.search = new URLSearchParams({
4090
- challenge,
4091
- uuid,
4092
- mode: "login",
4093
- redirectTarget: "cli"
4094
- }).toString();
4095
- const abort = new AbortController();
4096
- this.loginTask = {
4097
- abort,
4098
- promise: this.pollLogin(uuid, verifier, abort.signal).catch((error) => {
4099
- if (!abort.signal.aborted) this.error = error instanceof Error ? error.message : String(error);
4100
- }).finally(() => {
4101
- if (this.loginTask?.abort === abort) this.loginTask = void 0;
4102
- })
4103
- };
4104
- return { authorizeUrl: url.href };
4105
- }
4106
- async pollLogin(uuid, verifier, signal) {
4107
- let delay = 1e3;
4108
- for (let attempt = 0; attempt < 150; attempt++) {
4109
- await abortableDelay$2(delay, signal);
4110
- const url = `${API_ORIGIN}/auth/poll?uuid=${encodeURIComponent(uuid)}&verifier=${encodeURIComponent(verifier)}`;
4111
- const response = await this.http(url, {
4112
- headers: { accept: "application/json" },
4113
- redirect: "error",
4114
- signal
4115
- });
4116
- if (response.status === 404) {
4117
- delay = Math.min(Math.ceil(delay * 1.2), 1e4);
4118
- continue;
4119
- }
4120
- if (!response.ok) throw new Error(`Cursor login failed (HTTP ${response.status})`);
4121
- const body = await parseProviderJson(response, "Cursor login poll");
4122
- signal.throwIfAborted();
4123
- if (typeof body.accessToken !== "string" || body.accessToken.length === 0) throw new Error("Cursor login returned no access token");
4124
- if (typeof body.refreshToken !== "string" || body.refreshToken.length === 0) throw new Error("Cursor login returned no refresh token");
4125
- const value = {
4126
- type: "oauth",
4127
- access: body.accessToken,
4128
- refresh: body.refreshToken,
4129
- expires: tokenExpiry(body.accessToken, this.now())
4130
- };
4131
- await this.credentials.set(CURSOR_CREDENTIAL_REF, JSON.stringify(value));
4132
- return;
4133
- }
4134
- throw new Error("Cursor login timed out");
4135
- }
4136
- async cancel() {
4137
- const task = this.loginTask;
4138
- task?.abort.abort(/* @__PURE__ */ new Error("Cursor login cancelled"));
4139
- await task?.promise;
4140
- if (this.loginTask === task) this.loginTask = void 0;
4141
- this.error = void 0;
4142
- }
4143
- async logout() {
4144
- await this.cancel();
4145
- await this.credentials.unset(CURSOR_CREDENTIAL_REF);
4146
- }
4147
- };
4148
-
4149
- //#endregion
4150
- //#region src/providers/cursor-usage.ts
4151
- const ORIGIN = "https://cursor.com";
4152
- function record$2(value) {
4153
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4154
- }
4155
- function number(value) {
4156
- if (typeof value === "number" && Number.isFinite(value)) return value;
4157
- if (typeof value === "string" && value.trim() !== "") {
4158
- const parsed = Number(value);
4159
- if (Number.isFinite(parsed)) return parsed;
4160
- }
4161
- }
4162
- /** Cursor's WorkOS user id from the access JWT, without the identity prefix. */
4163
- function cursorUserId(token) {
4164
- try {
4165
- const parts = token.split(".");
4166
- if (parts.length !== 3 || !parts[1]) return void 0;
4167
- const raw = record$2(JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8")))?.sub;
4168
- if (typeof raw !== "string" || raw.length === 0) return void 0;
4169
- const id = raw.includes("|") ? raw.split("|").at(-1) : raw;
4170
- return id !== void 0 && /^[A-Za-z0-9_-]+$/.test(id) ? id : void 0;
4171
- } catch {
4172
- return;
4173
- }
4174
- }
4175
- async function dashboardJson(url, cookie, http$1, signal) {
4176
- const response = await http$1(url, {
4177
- headers: {
4178
- accept: "application/json",
4179
- cookie,
4180
- origin: ORIGIN,
4181
- referer: `${ORIGIN}/dashboard`
4182
- },
4183
- ...signal === void 0 ? {} : { signal }
4184
- });
4185
- if (!response.ok) throw new Error(`Cursor usage endpoint returned HTTP ${response.status}`);
4186
- return parseProviderJson(response, "Cursor usage");
4187
- }
4188
- function legacyRequestWindow(body, resetsAt) {
4189
- const usage = record$2(body);
4190
- if (usage === void 0) return void 0;
4191
- const gpt4 = record$2(usage["gpt-4"]);
4192
- 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];
4193
- const used = number(candidate?.numRequests);
4194
- const limit = number(candidate?.maxRequestUsage);
4195
- if (used === void 0 || limit === void 0 || limit <= 0) return void 0;
4196
- return {
4197
- kind: "other",
4198
- scope: "Included requests",
4199
- usedPercent: Math.max(0, Math.min(100, used / limit * 100)),
4200
- ...resetsAt === void 0 ? {} : { resetsAt }
4201
- };
4202
- }
4203
- /**
4204
- * Read Cursor's individual dashboard quota using an OAuth access token.
4205
- * The dashboard cookie is request-local; no token enters ProviderUsage.
4206
- */
4207
- async function fetchCursorUsage(accessToken, http$1 = fetch, signal) {
4208
- const userId = cursorUserId(accessToken);
4209
- if (userId === void 0) throw new Error("Cursor session token has no usable user identity");
4210
- const cookie = `WorkosCursorSessionToken=${userId}::${accessToken}`;
4211
- 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)]);
4212
- if (summaryResult.status === "rejected" && legacyResult.status === "rejected") throw summaryResult.reason instanceof Error ? summaryResult.reason : /* @__PURE__ */ new Error("Cursor usage is unavailable");
4213
- const summary = summaryResult.status === "fulfilled" ? record$2(summaryResult.value) : void 0;
4214
- const plan = record$2(record$2(summary?.individualUsage)?.plan) ?? record$2(record$2(summary?.teamUsage)?.plan);
4215
- const resetValue = summary?.billingCycleEnd;
4216
- const parsedReset = typeof resetValue === "string" ? Date.parse(resetValue) : NaN;
4217
- const resetsAt = Number.isFinite(parsedReset) ? parsedReset : void 0;
4218
- const startValue = summary?.billingCycleStart;
4219
- const parsedStart = typeof startValue === "string" ? Date.parse(startValue) : NaN;
4220
- const timing = {
4221
- ...resetsAt === void 0 ? {} : { resetsAt },
4222
- ...Number.isFinite(parsedStart) ? { startsAt: parsedStart } : {}
4223
- };
4224
- const windows = [];
4225
- for (const [field, scope] of [
4226
- ["totalPercentUsed", "Included"],
4227
- ["autoPercentUsed", "Cursor Models"],
4228
- ["apiPercentUsed", "Other Models"]
4229
- ]) {
4230
- const usedPercent = number(plan?.[field]);
4231
- if (usedPercent === void 0 || usedPercent < 0 || usedPercent > 100) continue;
4232
- windows.push({
4233
- kind: "other",
4234
- scope,
4235
- usedPercent,
4236
- ...timing
4237
- });
4238
- }
4239
- const legacy = legacyRequestWindow(legacyResult.status === "fulfilled" ? legacyResult.value : void 0, resetsAt);
4240
- if (legacy !== void 0) windows.push({
4241
- ...legacy,
4242
- ...timing
4243
- });
4244
- if (windows.length === 0 && summary?.isUnlimited !== true) throw new Error("Cursor usage response has no quota windows");
4245
- const membershipType = summary?.membershipType;
4246
- return {
4247
- supported: true,
4248
- windows,
4249
- ...typeof membershipType === "string" && membershipType.length > 0 ? { plan: membershipType } : {}
4250
- };
4251
- }
4252
-
4253
- //#endregion
4254
- //#region vendor/cursor/proto.js
4255
- /**
4256
- * Minimal protobuf wire-format helpers for the Cursor Agent protocol.
4257
- *
4258
- * Only the subset of protobuf needed by `dsh-cursor-subscription` is
4259
- * implemented: varints, length-delimited fields, fixed64/double, and nested
4260
- * messages. Map fields are encoded as repeated entry messages, exactly like
4261
- * the official protobuf runtime.
4262
- *
4263
- * @module dsh-cursor-subscription/proto
4264
- */
4265
- /** Encode one unsigned varint into a Uint8Array. */
4266
- function varintEncode(value) {
4267
- const out = [];
4268
- let n = Math.trunc(value);
4269
- while (n > 127) {
4270
- out.push(n & 127 | 128);
4271
- n = Math.floor(n / 128);
4272
- }
4273
- out.push(n);
4274
- return Uint8Array.from(out);
4275
- }
4276
- /** Combine byte arrays. */
4277
- function concatBytes(parts) {
4278
- let total = 0;
4279
- for (const part of parts) total += part.length;
4280
- const out = new Uint8Array(total);
4281
- let offset = 0;
4282
- for (const part of parts) {
4283
- out.set(part, offset);
4284
- offset += part.length;
4285
- }
4286
- return out;
4287
- }
4288
- /** A streaming protobuf message writer. */
4289
- var Writer = class {
4290
- constructor() {
4291
- this.parts = [];
4292
- }
4293
- /** Append a raw field tag. */
4294
- tag(field, wireType) {
4295
- this.parts.push(varintEncode(field << 3 | wireType));
4296
- return this;
4297
- }
4298
- /** Append a length-delimited payload. */
4299
- bytes(field, data) {
4300
- this.tag(field, 2);
4301
- this.parts.push(varintEncode(data.length));
4302
- this.parts.push(data);
4303
- return this;
4304
- }
4305
- /** Append a UTF-8 string field. */
4306
- string(field, value) {
4307
- return this.bytes(field, new TextEncoder().encode(value));
4308
- }
4309
- /** Append a nested message field. */
4310
- message(field, inner) {
4311
- return this.bytes(field, inner);
4312
- }
4313
- /** Append a varint field (uint32/int32/bool/enum). */
4314
- varint(field, value) {
4315
- this.tag(field, 0);
4316
- this.parts.push(varintEncode(Math.trunc(value)));
4317
- return this;
4318
- }
4319
- /** Append a double (fixed64, little-endian) field. */
4320
- double(field, value) {
4321
- this.tag(field, 1);
4322
- const buffer = /* @__PURE__ */ new ArrayBuffer(8);
4323
- new DataView(buffer).setFloat64(0, value, true);
4324
- this.parts.push(new Uint8Array(buffer));
4325
- return this;
4326
- }
4327
- finish() {
4328
- return concatBytes(this.parts);
4329
- }
4330
- };
4331
- /** A streaming protobuf message reader. */
4332
- var Reader = class {
4333
- constructor(bytes) {
4334
- this.data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
4335
- this.pos = 0;
4336
- }
4337
- get done() {
4338
- return this.pos >= this.data.length;
4339
- }
4340
- varint() {
4341
- let result = 0;
4342
- let shift = 0;
4343
- while (true) {
4344
- if (this.pos >= this.data.length) throw new Error("protobuf: truncated varint");
4345
- const byte = this.data[this.pos++];
4346
- result += (byte & 127) * 2 ** shift;
4347
- if ((byte & 128) === 0) break;
4348
- shift += 7;
4349
- if (shift > 63) throw new Error("protobuf: varint too long");
4350
- }
4351
- return result;
4352
- }
4353
- /** Read the tag and return { field, wireType }. */
4354
- tag() {
4355
- const raw = this.varint();
4356
- return {
4357
- field: Math.floor(raw / 8),
4358
- wireType: raw % 8
4359
- };
4360
- }
4361
- bytes() {
4362
- const length = this.varint();
4363
- if (this.pos + length > this.data.length) throw new Error("protobuf: truncated bytes");
4364
- const out = this.data.subarray(this.pos, this.pos + length);
4365
- this.pos += length;
4366
- return out;
4367
- }
4368
- string() {
4369
- return new TextDecoder().decode(this.bytes());
4370
- }
4371
- double() {
4372
- if (this.pos + 8 > this.data.length) throw new Error("protobuf: truncated double");
4373
- const view = new DataView(this.data.buffer, this.data.byteOffset + this.pos, 8);
4374
- this.pos += 8;
4375
- return view.getFloat64(0, true);
4376
- }
4377
- /** Skip a field of the given wire type (length-delimited or varint or fixed64). */
4378
- skip(wireType) {
4379
- if (wireType === 0) this.varint();
4380
- else if (wireType === 1) {
4381
- if (this.pos + 8 > this.bytes.length) throw new Error("protobuf: truncated fixed64");
4382
- this.pos += 8;
4383
- } else if (wireType === 2) this.bytes();
4384
- else if (wireType === 5) {
4385
- if (this.pos + 4 > this.bytes.length) throw new Error("protobuf: truncated fixed32");
4386
- this.pos += 4;
4387
- } else throw new Error(`protobuf: unsupported wire type ${wireType}`);
4388
- }
4389
- };
4390
- /**
4391
- * Encode a JSON value as `google.protobuf.Value` (the wire format Cursor uses
4392
- * for MCP tool input schemas and MCP argument values).
4393
- */
4394
- function encodeValue(value) {
4395
- const writer = new Writer();
4396
- if (value === null || value === void 0) writer.varint(1, 0);
4397
- else if (typeof value === "boolean") writer.varint(4, value ? 1 : 0);
4398
- else if (typeof value === "number") writer.double(2, value);
4399
- else if (typeof value === "string") writer.string(3, value);
4400
- else if (Array.isArray(value)) {
4401
- const list = new Writer();
4402
- for (const item of value) list.message(1, encodeValue(item));
4403
- writer.message(6, list.finish());
4404
- } else if (typeof value === "object") {
4405
- const struct = new Writer();
4406
- for (const [key, item] of Object.entries(value)) {
4407
- const entry = new Writer();
4408
- entry.string(1, key);
4409
- entry.message(2, encodeValue(item));
4410
- struct.message(1, entry.finish());
4411
- }
4412
- writer.message(5, struct.finish());
4413
- } else throw new Error(`cannot encode ${typeof value} as google.protobuf.Value`);
4414
- return writer.finish();
4415
- }
4416
- /**
4417
- * Decode `google.protobuf.Value` bytes back into a JSON value.
4418
- * @param {Uint8Array} bytes - serialized google.protobuf.Value message.
4419
- * @returns {unknown} the decoded JSON value.
4420
- */
4421
- function decodeValue(bytes) {
4422
- const reader = new Reader(bytes);
4423
- while (!reader.done) {
4424
- const { field, wireType } = reader.tag();
4425
- if (field === 1 && wireType === 0) {
4426
- reader.varint();
4427
- return null;
4428
- }
4429
- if (field === 2 && wireType === 1) return reader.double();
4430
- if (field === 3 && wireType === 2) return reader.string();
4431
- if (field === 4 && wireType === 0) return reader.varint() !== 0;
4432
- if (field === 5 && wireType === 2) return decodeStruct(reader.bytes());
4433
- if (field === 6 && wireType === 2) return decodeList(reader.bytes());
4434
- reader.skip(wireType);
4435
- }
4436
- return null;
4437
- }
4438
- function decodeStruct(bytes) {
4439
- const reader = new Reader(bytes);
4440
- const result = {};
4441
- while (!reader.done) {
4442
- const { field, wireType } = reader.tag();
4443
- if (field === 1 && wireType === 2) {
4444
- const entry = new Reader(reader.bytes());
4445
- let key = "";
4446
- let value = null;
4447
- while (!entry.done) {
4448
- const tag = entry.tag();
4449
- if (tag.field === 1 && tag.wireType === 2) key = entry.string();
4450
- else if (tag.field === 2 && tag.wireType === 2) value = decodeValue(entry.bytes());
4451
- else entry.skip(tag.wireType);
4452
- }
4453
- result[key] = value;
4454
- } else reader.skip(wireType);
4455
- }
4456
- return result;
4457
- }
4458
- function decodeList(bytes) {
4459
- const reader = new Reader(bytes);
4460
- const result = [];
4461
- while (!reader.done) {
4462
- const { field, wireType } = reader.tag();
4463
- if (field === 1 && wireType === 2) result.push(decodeValue(reader.bytes()));
4464
- else reader.skip(wireType);
4465
- }
4466
- return result;
4467
- }
4468
-
4469
- //#endregion
4470
- //#region vendor/cursor/index.js
4471
- /**
4472
- * Read this package's own version from the manifest beside `lib/`.
4473
- * @returns the manifest version, or undefined when it cannot be read.
4474
- */
4475
- function readVersion() {
4476
- try {
4477
- return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
4478
- } catch {
4479
- return;
4480
- }
4481
- }
4482
- /** Build version the settings panel prints; undefined when the manifest is unreadable. */
4483
- const VERSION = readVersion();
4484
- /**
4485
- * DSH web replaces the ambient `fetch` with an MCP `createFetchWithInit`
4486
- * wrapper whose `baseFetch` is undefined in the host process, so any consumer
4487
- * of the global `fetch` throws `baseFetch is not a function`. Cursor usage /
4488
- * refresh / model discovery go through HTTP, so this module uses Node's own
4489
- * `http`/`https` to talk to the upstream directly instead of trusting the
4490
- * host-injected `fetch`. It exposes the small Fetch subset these callers use.
4491
- */
4492
- async function nativeFetch(url, init = {}) {
4493
- const target = new URL(url);
4494
- const client = target.protocol === "https:" ? https : http;
4495
- const method = (init.method ?? "GET").toUpperCase();
4496
- const body = init.body === void 0 ? void 0 : isBytes(init.body) ? Buffer.from(init.body) : String(init.body);
4497
- const request = (u$1, redirect) => new Promise((resolve$2, reject) => {
4498
- const req = client.request(u$1, {
4499
- method,
4500
- headers: sanitizeHeaders(init.headers),
4501
- signal: init.signal
4502
- }, (res) => {
4503
- const chunks = [];
4504
- res.on("data", (chunk) => chunks.push(chunk));
4505
- res.on("end", () => {
4506
- const data = Buffer.concat(chunks);
4507
- const status = res.statusCode ?? 0;
4508
- if ((redirect === "error" || redirect === "manual") && status >= 300 && status < 400) {
4509
- reject(/* @__PURE__ */ new Error(`HTTP ${status} from ${u$1.origin}${u$1.pathname}`));
4510
- return;
4511
- }
4512
- const location = res.headers.location;
4513
- if ((redirect ?? "follow") === "follow" && location && status >= 300 && status < 400) {
4514
- resolve$2({ redirect: new URL(location, u$1) });
4515
- return;
4516
- }
4517
- resolve$2({ response: makeResponse(status, res, data) });
4518
- });
4519
- });
4520
- req.on("error", reject);
4521
- if (body !== void 0) req.write(body);
4522
- req.end();
4523
- });
4524
- let u = target;
4525
- for (let hops = 0;; hops++) {
4526
- const result = await request(u, init.redirect);
4527
- if (result.redirect !== void 0) {
4528
- if (hops >= 6) throw new Error(`too many redirects from ${target.href}`);
4529
- u = result.redirect;
4530
- continue;
4531
- }
4532
- return result.response;
4533
- }
4534
- }
4535
- function isBytes(value) {
4536
- return value instanceof Uint8Array || typeof value !== "string" && ArrayBuffer.isView(value);
4537
- }
4538
- function sanitizeHeaders(headers) {
4539
- if (headers === void 0) return void 0;
4540
- const out = {};
4541
- for (const [key, value] of Object.entries(headers)) {
4542
- if (value === void 0) continue;
4543
- out[key] = value;
4544
- }
4545
- return out;
4546
- }
4547
- function makeResponse(status, res, data) {
4548
- const text = async () => data.toString("utf8");
4549
- return {
4550
- ok: status >= 200 && status < 300,
4551
- status,
4552
- statusText: res.statusMessage ?? "",
4553
- headers: { get: (name$1) => res.headers[String(name$1).toLowerCase()] ?? null },
4554
- text: async () => text(),
4555
- json: async () => JSON.parse(await text()),
4556
- arrayBuffer: async () => data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)
4557
- };
4558
- }
4559
- const CREDENTIAL_REF = credentialRef("CURSOR_SUBSCRIPTION_OAUTH");
4560
- /** Connection registry options: accept the account channel from loopback Hosts only. */
4561
- const LOOPBACK_RPC = Object.freeze({ authority: "loopback" });
4562
- const CURSOR_BASE_URL = "https://api2.cursor.sh";
4563
- const CURSOR_RUN_PATH = "/agent.v1.AgentService/Run";
4564
- const CURSOR_MODELS_PATH = "/agent.v1.AgentService/GetUsableModels";
4565
- /** Cursor dashboard usage endpoints (authenticated with the session cookie). */
4566
- const USAGE_API_ORIGIN = "https://cursor.com";
4567
- const USAGE_URL = `${USAGE_API_ORIGIN}/api/usage`;
4568
- const USAGE_SUMMARY_URL = `${USAGE_API_ORIGIN}/api/usage-summary`;
4569
- const USAGE_TEAMS_URL = `${USAGE_API_ORIGIN}/api/dashboard/teams`;
4570
- const USAGE_AGGREGATED_URL = `${USAGE_API_ORIGIN}/api/dashboard/get-aggregated-usage-events`;
4571
- const USAGE_TTL_MS$1 = 60 * 1e3;
4572
- /** Client version reported to the Agent service; bump when Cursor requires it. */
4573
- const CURSOR_CLIENT_VERSION = "cli-2026.02.13-41ac335";
4574
- /** Keep-alive heartbeats while an agent run is streaming. */
4575
- const HEARTBEAT_INTERVAL_MS = 5e3;
4576
- /** How long an agent run may stay completely silent before we abort. */
4577
- const STREAM_IDLE_TIMEOUT_MS = 120 * 1e3;
4578
- /** How long a run may keep sending heartbeats without any real content. */
4579
- const STREAM_PROGRESS_TIMEOUT_MS = 60 * 1e3;
4580
- /** Retain checkpoints/live tool bridges for inactive DSH sessions. */
4581
- const SESSION_STATE_TTL_MS = 1800 * 1e3;
4582
- /** Stop one Cursor Run before an unconstrained agent can loop forever. */
4583
- const MAX_TOOL_ROUNDS = 200;
4584
- /**
4585
- * How long a tool-call step keeps reading after `mcpArgs` for the frames that
4586
- * close the burst (sibling calls, blob writes, checkpoint). Sibling calls of
4587
- * one burst arrive within ~100 ms of each other.
4588
- */
4589
- const TOOL_CALL_SETTLE_MS = 500;
4590
- const DEFAULT_RETRY_COUNT = 0;
4591
- const DEFAULT_RETRY_INTERVAL_MS = 1e3;
4592
- const DEFAULT_RETRY_HTTP_STATUS_CODES = Object.freeze([
4593
- 408,
4594
- 425,
4595
- 429,
4596
- 500,
4597
- 502,
4598
- 503,
4599
- 504
4600
- ]);
4601
- /**
4602
- * Rebuild the Cursor conversation from the DSH history on every step instead of
4603
- * resuming the conversation Cursor keeps server-side.
4604
- *
4605
- * A plugin whose transport re-sends its own message history — as the standard
4606
- * function-calling ones do — hands the model the same explicit history at every
4607
- * step. Delegating that history to Cursor's server instead is what a Cursor turn
4608
- * was observed to re-derive from: the step re-stated its goal, re-read its goal
4609
- * and rewrote its plan while its tool calls kept succeeding. Off by default: a
4610
- * rebuild re-sends the whole transcript each step, and Cursor has no
4611
- * provider-side compaction to bound it.
4612
- */
4613
- const DEFAULT_REPLAY_HISTORY_EACH_STEP = false;
4614
- /**
4615
- * Editable runtime settings. Every field is `.volatile()`: the Loader parses a
4616
- * volatile field into a live reference that is updated in place when the
4617
- * settings page saves, which is what carries a saved value into a Cursor run
4618
- * that is already in flight. On DSH 0.1.7 a Config without a volatile field
4619
- * produces no settings section at all, so none of these may lose the marker.
4620
- */
4621
- const Config$1 = z.object({
4622
- maxToolRounds: z.number().step(1).min(1).max(1e3).default(MAX_TOOL_ROUNDS).volatile(),
4623
- retryCount: z.number().step(1).min(0).max(10).default(DEFAULT_RETRY_COUNT).volatile(),
4624
- retryIntervalMs: z.number().step(1).min(0).max(3e5).default(DEFAULT_RETRY_INTERVAL_MS).volatile(),
4625
- retryHttpStatusCodes: z.array(z.number().step(1).min(400).max(599)).default([...DEFAULT_RETRY_HTTP_STATUS_CODES]).volatile(),
4626
- replayHistoryEachStep: z.boolean().default(DEFAULT_REPLAY_HISTORY_EACH_STEP).volatile()
4627
- });
4628
- /** Refresh the access token this early before its JWT expiry. */
4629
- const REFRESH_AHEAD_MS = 300 * 1e3;
4630
- /** Default access-token lifetime when the JWT has no usable exp claim. */
4631
- const DEFAULT_TOKEN_LIFETIME_MS = 1440 * 60 * 1e3;
4632
- const DEFAULT_CONTEXT_WINDOW = 2e5;
4633
- const DEFAULT_MAX_TOKENS = 64e3;
4634
- /**
4635
- * Read one Config field. DSH 0.1.7 delivers a `.volatile()` field to the plugin
4636
- * as a reference whose `get()` returns the value the settings page last saved —
4637
- * the Loader keeps that one object and writes into it, so reading it per call
4638
- * is what makes a saved edit visible. Tests and the frozen fallback pass
4639
- * ordinary values, so both shapes are accepted here.
4640
- * @param value Config field as delivered by the Loader, or a plain value.
4641
- * @returns The current plain value.
4642
- */
4643
- function readConfigField(value) {
4644
- return typeof value?.get === "function" ? value.get() : value;
4645
- }
4646
- function resolveCursorSettings(input = {}) {
4647
- const maxToolRounds = readConfigField(input.maxToolRounds) ?? MAX_TOOL_ROUNDS;
4648
- const retryCount = readConfigField(input.retryCount) ?? DEFAULT_RETRY_COUNT;
4649
- const retryIntervalMs = readConfigField(input.retryIntervalMs) ?? DEFAULT_RETRY_INTERVAL_MS;
4650
- const retryHttpStatusCodes = readConfigField(input.retryHttpStatusCodes) ?? DEFAULT_RETRY_HTTP_STATUS_CODES;
4651
- const replayHistoryEachStep = readConfigField(input.replayHistoryEachStep) ?? DEFAULT_REPLAY_HISTORY_EACH_STEP;
4652
- if (!Number.isSafeInteger(maxToolRounds) || maxToolRounds < 1 || maxToolRounds > 1e3) throw new Error("cursor-subscription: maxToolRounds must be an integer between 1 and 1000");
4653
- if (!Number.isSafeInteger(retryCount) || retryCount < 0 || retryCount > 10) throw new Error("cursor-subscription: retryCount must be an integer between 0 and 10");
4654
- if (!Number.isSafeInteger(retryIntervalMs) || retryIntervalMs < 0 || retryIntervalMs > 3e5) throw new Error("cursor-subscription: retryIntervalMs must be an integer between 0 and 300000");
4655
- 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");
4656
- if (new Set(retryHttpStatusCodes).size !== retryHttpStatusCodes.length) throw new Error("cursor-subscription: retryHttpStatusCodes must not contain duplicates");
4657
- if (typeof replayHistoryEachStep !== "boolean") throw new Error("cursor-subscription: replayHistoryEachStep must be a boolean");
4658
- return Object.freeze({
4659
- maxToolRounds,
4660
- retryCount,
4661
- retryIntervalMs,
4662
- retryHttpStatusCodes: Object.freeze([...retryHttpStatusCodes]),
4663
- replayHistoryEachStep
4664
- });
4665
- }
4666
- function shouldRetryHttpStatus(status, retriesUsed, settings) {
4667
- return retriesUsed < settings.retryCount && settings.retryHttpStatusCodes.includes(status);
4668
- }
4669
- function isSuccessfulAgentResponse(status, contentType) {
4670
- return status === 200 && /^application\/connect\+proto(?:\s*;|\s*$)/i.test(contentType ?? "");
4671
- }
4672
- function abortableDelay$1(ms, signal) {
4673
- if (signal.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Cursor retry aborted"));
4674
- if (ms === 0) return Promise.resolve();
4675
- return new Promise((resolve$2, reject) => {
4676
- const timer = setTimeout(done, ms);
4677
- timer.unref?.();
4678
- function done() {
4679
- signal.removeEventListener("abort", aborted);
4680
- resolve$2();
4681
- }
4682
- function aborted() {
4683
- clearTimeout(timer);
4684
- signal.removeEventListener("abort", aborted);
4685
- reject(signal.reason ?? /* @__PURE__ */ new Error("Cursor retry aborted"));
4686
- }
4687
- signal.addEventListener("abort", aborted, { once: true });
4688
- });
4689
- }
4690
- /**
4691
- * Cursor's `GetUsableModels` answers in its own order (newest family first),
4692
- * and that order moves between releases, so the picker would reshuffle on its
4693
- * own. Sort by display name instead, with the id as a tiebreak.
4694
- *
4695
- * The collation is pinned to `"en"` so one build lists the same order on every
4696
- * host locale, and `numeric` so families keep their natural sequence
4697
- * (`GPT-5` before `GPT-10`, `Composer 2` after `Composer 1.5`).
4698
- */
4699
- const MODEL_NAME_COLLATOR = new Intl.Collator("en", {
4700
- numeric: true,
4701
- sensitivity: "base"
4702
- });
4703
- /** Order model entries by name then id, without mutating the input. */
4704
- function sortModelsByName(models) {
4705
- return [...models].sort((a, b) => {
4706
- const byName = MODEL_NAME_COLLATOR.compare(a?.name ?? "", b?.name ?? "");
4707
- if (byName !== 0) return byName;
4708
- return MODEL_NAME_COLLATOR.compare(a?.id ?? "", b?.id ?? "");
4709
- });
4710
- }
4711
- const textEncoder = new TextEncoder();
4712
- function bytesOf(value) {
4713
- return value instanceof Uint8Array ? value : new Uint8Array(value);
4714
- }
4715
- function encodeUserMessage({ text, messageId }) {
4716
- const writer = new Writer();
4717
- if (typeof text === "string" && text.length > 0) writer.string(1, text);
4718
- if (typeof messageId === "string" && messageId.length > 0) writer.string(2, messageId);
4719
- return writer.finish();
4720
- }
4721
- /** ModelDetails { model_id = 1, display_model_id = 3, display_name = 4 } */
4722
- function encodeModelDetails(modelId) {
4723
- const writer = new Writer();
4724
- writer.string(1, modelId);
4725
- writer.string(3, modelId);
4726
- writer.string(4, modelId);
4727
- return writer.finish();
4728
- }
4729
- /**
4730
- * ConversationHistoryImageContent { data = 1, mime_type = 2 }
4731
- * wrapped as ConversationHistoryMessage.user.content[].image
4732
- */
4733
- function encodeConversationHistoryImage({ data, mimeType }) {
4734
- const image = new Writer().string(1, data);
4735
- if (mimeType) image.string(2, mimeType);
4736
- const content = new Writer().message(2, image.finish()).finish();
4737
- const user = new Writer().message(1, content).finish();
4738
- return new Writer().message(1, user).finish();
4739
- }
4740
- /** ConversationHistory { messages = 1 } with one history message per image. */
4741
- function encodeConversationHistory(images) {
4742
- const writer = new Writer();
4743
- for (const image of images) writer.message(1, encodeConversationHistoryImage(image));
4744
- return writer.finish();
4745
- }
4746
- /** ConversationAction { user_message_action = 1 } → UserMessageAction { user_message = 1, conversation_history = 7 } */
4747
- function encodeUserMessageAction(userBytes, images = []) {
4748
- const inner = new Writer().message(1, userBytes);
4749
- if (images.length > 0) inner.message(7, encodeConversationHistory(images));
4750
- return new Writer().message(1, inner.finish()).finish();
4751
- }
4752
- /** ConversationStateStructure — the durable conversation payload. */
4753
- function encodeConversationState({ rootPromptBlobIds, turns }) {
4754
- const writer = new Writer();
4755
- for (const id of rootPromptBlobIds) writer.bytes(1, id);
4756
- for (const turn of turns) writer.bytes(8, turn);
4757
- return writer.finish();
4758
- }
4759
- /** AgentRunRequest { conversation_state=1, action=2, model_details=3, conversation_id=5 } */
4760
- function encodeRunRequest({ conversationState, action, modelDetails, conversationId }) {
4761
- const writer = new Writer();
4762
- writer.message(1, conversationState);
4763
- writer.message(2, action);
4764
- writer.message(3, modelDetails);
4765
- if (conversationId) writer.string(5, conversationId);
4766
- return writer.finish();
4767
- }
4768
- /** AgentClientMessage { run_request = 1 } */
4769
- function encodeRunMessage(runRequestBytes) {
4770
- return new Writer().message(1, runRequestBytes).finish();
4771
- }
4772
- /** AgentClientMessage { client_heartbeat = 7 } */
4773
- function encodeHeartbeat() {
4774
- return new Writer().message(7, new Uint8Array(0)).finish();
4775
- }
4776
- /** KvClientMessage { id=1, get_blob_result=2 { blob_data=1 } } */
4777
- function encodeGetBlobResult(id, blobData) {
4778
- const inner = blobData === void 0 ? new Uint8Array(0) : blobData;
4779
- const result = new Writer().bytes(1, inner).finish();
4780
- const writer = new Writer();
4781
- writer.varint(1, id);
4782
- writer.message(2, result);
4783
- return writer.finish();
4784
- }
4785
- /** AgentClientMessage { kv_client_message = 3 } */
4786
- function encodeKvClientMessage(kvBytes) {
4787
- return new Writer().message(3, kvBytes).finish();
4788
- }
4789
- /** KvClientMessage { id=1, set_blob_result=3 } — acknowledge a server blob write. */
4790
- function encodeSetBlobResult(id) {
4791
- const writer = new Writer();
4792
- writer.varint(1, id);
4793
- writer.message(3, new Uint8Array(0));
4794
- return writer.finish();
4795
- }
4796
- /** McpResult.success with one text content item. */
4797
- function encodeMcpResult({ content, isError = false }) {
4798
- return encodeMcpResultSuccess(String(content ?? ""), isError);
4799
- }
4800
- /** ExecClientMessage { id=1, exec_id=15, message=... } */
4801
- function encodeExecClientMessage(id, execId, messageField, messageBytes) {
4802
- const writer = new Writer();
4803
- writer.varint(1, id);
4804
- if (typeof execId === "string" && execId.length > 0) writer.string(15, execId);
4805
- if (messageBytes !== void 0) writer.message(messageField, messageBytes);
4806
- return writer.finish();
4807
- }
4808
- /** AgentClientMessage { exec_client_message = 2 } */
4809
- function encodeExecClientMessageEnvelope(execBytes) {
4810
- return new Writer().message(2, execBytes).finish();
4811
- }
4812
- /** RequestContextResult { success=1 { request_context=1 } } with tools. */
4813
- function encodeRequestContextResult(tools) {
4814
- const context = new Writer();
4815
- for (const tool of tools) context.message(7, tool);
4816
- const success = new Writer().message(1, context.finish()).finish();
4817
- return new Writer().message(1, success).finish();
4818
- }
4819
- /** McpToolDefinition { name=1, description=2, input_schema=3, provider_identifier=4, tool_name=5 } */
4820
- function encodeMcpToolDefinition({ name: name$1, description, inputSchema, providerIdentifier, toolName }) {
4821
- const writer = new Writer();
4822
- writer.string(1, name$1);
4823
- writer.string(2, description ?? "");
4824
- writer.bytes(3, bytesOf(inputSchema));
4825
- writer.string(4, providerIdentifier ?? "dsh-cursor-subscription");
4826
- writer.string(5, toolName ?? name$1);
4827
- return writer.finish();
4828
- }
4829
- /** McpTextContent { text = 1 } */
4830
- function encodeMcpTextContent(text) {
4831
- return new Writer().string(1, text).finish();
4832
- }
4833
- /** McpToolResultContentItem { text = 1 } */
4834
- function encodeMcpToolResultContentItem(text) {
4835
- return new Writer().message(1, encodeMcpTextContent(text)).finish();
4836
- }
4837
- /** McpSuccess { content=1, is_error=2 } */
4838
- function encodeMcpSuccess(text, isError) {
4839
- const writer = new Writer();
4840
- writer.message(1, encodeMcpToolResultContentItem(text));
4841
- writer.varint(2, isError ? 1 : 0);
4842
- return writer.finish();
4843
- }
4844
- /** McpResult { success=1 | error=2 } */
4845
- function encodeMcpResultSuccess(text, isError) {
4846
- return new Writer().message(1, encodeMcpSuccess(text, isError)).finish();
4847
- }
4848
- function encodeMcpError(error) {
4849
- return new Writer().message(2, new Writer().string(1, error).finish()).finish();
4850
- }
4851
- /** ReadResult { rejected=3 { path=1, reason=2 } } */
4852
- function encodeReadRejected(path, reason) {
4853
- const rejected = new Writer().string(1, path ?? "").string(2, reason).finish();
4854
- return new Writer().message(3, rejected).finish();
4855
- }
4856
- /** LsResult { rejected=3 { path=1, reason=2 } } */
4857
- function encodeLsRejected(path, reason) {
4858
- const rejected = new Writer().string(1, path ?? "").string(2, reason).finish();
4859
- return new Writer().message(3, rejected).finish();
4860
- }
4861
- /** GrepResult { error=2 { error=1 } } */
4862
- function encodeGrepError(error) {
4863
- return new Writer().message(2, new Writer().string(1, error).finish()).finish();
4864
- }
4865
- /** WriteResult { rejected = 6 } (oneof: success=1, permission_denied=3, no_space=4, error=5, rejected=6) */
4866
- function encodeWriteRejected(path, reason) {
4867
- const rejected = new Writer().string(1, path ?? "").string(2, reason).finish();
4868
- return new Writer().message(6, rejected).finish();
4869
- }
4870
- /** DeleteResult { rejected = 6 } (oneof: success=1, file_not_found=2, not_file=3, permission_denied=4, file_busy=5, rejected=6) */
4871
- function encodeDeleteRejected(path, reason) {
4872
- const rejected = new Writer().string(1, path ?? "").string(2, reason).finish();
4873
- return new Writer().message(6, rejected).finish();
4874
- }
4875
- /** ShellRejected { command=1, working_directory=2, reason=3, is_readonly=4 } */
4876
- function encodeShellRejected(command, workingDirectory, reason) {
4877
- const writer = new Writer();
4878
- writer.string(1, command ?? "");
4879
- writer.string(2, workingDirectory ?? "");
4880
- writer.string(3, reason);
4881
- writer.varint(4, 0);
4882
- return writer.finish();
4883
- }
4884
- /** ShellResult { rejected = 4 } (oneof: success=1, failure=2, timeout=3, rejected=4) */
4885
- function encodeShellRejectedResult(command, workingDirectory, reason) {
4886
- return new Writer().message(4, encodeShellRejected(command, workingDirectory, reason)).finish();
4887
- }
4888
- /**
4889
- * ShellStream { rejected = 5 } — the reply type for `shellStreamArgs` execs
4890
- * (streaming shell), carried by ExecClientMessage.shell_stream (field 14).
4891
- */
4892
- function encodeShellStreamRejected(command, workingDirectory, reason) {
4893
- return new Writer().message(5, encodeShellRejected(command, workingDirectory, reason)).finish();
4894
- }
4895
- /** BackgroundShellSpawnResult { rejected = 3 } (oneof: success=1, error=2, rejected=3, permission_denied=4) */
4896
- function encodeBackgroundShellRejectedResult(command, workingDirectory, reason) {
4897
- return new Writer().message(3, encodeShellRejected(command, workingDirectory, reason)).finish();
4898
- }
4899
- /** FetchResult { error=2 { url=1, error=2 } } */
4900
- function encodeFetchError(url, error) {
4901
- const inner = new Writer().string(1, url ?? "").string(2, error).finish();
4902
- return new Writer().message(2, inner).finish();
4903
- }
4904
- /** WriteShellStdinResult { error=2 { error=1 } } */
4905
- function encodeWriteShellStdinError(error) {
4906
- return new Writer().message(2, new Writer().string(1, error).finish()).finish();
4907
- }
4908
- /** DiagnosticsResult { success = 1 } — empty success keeps the exec well-formed. */
4909
- function encodeDiagnosticsResult() {
4910
- return new Writer().message(1, new Uint8Array(0)).finish();
4911
- }
4912
- new TextDecoder();
4913
- /** AgentServerMessage { interaction_update=1, exec_server_message=2, kv_server_message=4, ... } */
4914
- function decodeAgentServerMessage(bytes) {
4915
- const reader = new Reader(bytes);
4916
- while (!reader.done) {
4917
- const { field, wireType } = reader.tag();
4918
- if (wireType !== 2) {
4919
- reader.skip(wireType);
4920
- continue;
4921
- }
4922
- const payload = reader.bytes();
4923
- if (field === 1) return {
4924
- case: "interactionUpdate",
4925
- value: decodeInteractionUpdate(payload)
4926
- };
4927
- if (field === 2) return {
4928
- case: "execServerMessage",
4929
- value: decodeExecServerMessage(payload)
4930
- };
4931
- if (field === 3) return {
4932
- case: "conversationCheckpointUpdate",
4933
- value: payload
4934
- };
4935
- if (field === 4) return {
4936
- case: "kvServerMessage",
4937
- value: decodeKvServerMessage(payload)
4938
- };
4939
- }
4940
- return {
4941
- case: "unknown",
4942
- value: void 0
4943
- };
4944
- }
4945
- /** Field numbers inside one protobuf message; used to describe frames we do not decode. */
4946
- function messageFields(bytes) {
4947
- const reader = new Reader(bytes);
4948
- const fields = [];
4949
- while (!reader.done) {
4950
- const { field, wireType } = reader.tag();
4951
- fields.push(field);
4952
- reader.skip(wireType);
4953
- }
4954
- return fields.join(",");
4955
- }
4956
- /** Field numbers of a nested message (`wanted` inside `bytes`). */
4957
- function nestedMessageFields(bytes, wanted) {
4958
- const reader = new Reader(bytes);
4959
- while (!reader.done) {
4960
- const { field, wireType } = reader.tag();
4961
- if (wireType !== 2) {
4962
- reader.skip(wireType);
4963
- continue;
4964
- }
4965
- const payload = reader.bytes();
4966
- if (field === wanted) return messageFields(payload);
4967
- }
4968
- return "";
4969
- }
4970
- /**
4971
- * One-line description of a server frame for the stall trace. Undecoded
4972
- * frames report their protobuf field numbers so a server message the adapter
4973
- * ignores is still visible when a run goes silent.
4974
- */
4975
- function describeServerFrame(frame) {
4976
- if ((frame.flags & CONNECT_END_STREAM_FLAG) !== 0) return `endstream(${Buffer.from(frame.payload).toString("utf8").replace(/\s+/g, " ").slice(0, 60)})`;
4977
- try {
4978
- const message = decodeAgentServerMessage(frame.payload);
4979
- if (message.case === "interactionUpdate") {
4980
- const update = message.value;
4981
- if (update.type === "textDelta") return `text(${update.text.length})`;
4982
- if (update.type === "thinkingDelta") return `thinking(${update.text.length})`;
4983
- if (update.type === "tokenDelta") return `tokens(${update.tokens})`;
4984
- if (update.type === "partialToolCall") return "partialToolCall";
4985
- if (update.type === "unknown") return `interaction#${nestedMessageFields(frame.payload, 1)}`;
4986
- return update.type;
4987
- }
4988
- if (message.case === "conversationCheckpointUpdate") return `checkpoint(${message.value.length}B)`;
4989
- if (message.case === "kvServerMessage") return `kv:${message.value.case}`;
4990
- if (message.case === "execServerMessage") {
4991
- const exec = message.value;
4992
- return exec.case === "unknown" ? `exec:unknown#${exec.field ?? "?"}[${(exec.seen ?? []).join(",")}]` : `exec:${exec.case}`;
4993
- }
4994
- return `message#${messageFields(frame.payload)}`;
4995
- } catch (error) {
4996
- return `undecodable(${(error instanceof Error ? error.message : String(error)).slice(0, 40)})`;
4997
- }
4998
- }
4999
- /**
5000
- * Prompt tokens Cursor reports for the conversation it is holding.
5001
- *
5002
- * `ConversationStateStructure.token_details.used_tokens` rides in every
5003
- * `conversation_checkpoint_update`, so the run that resumes that checkpoint can
5004
- * report the real prompt size. Without it the adapter reported a placeholder
5005
- * zero, and the context gauge showed nothing however large the model's
5006
- * conversation had grown.
5007
- *
5008
- * @param checkpoint Raw checkpoint bytes from the last update, if any.
5009
- * @returns Used tokens, or undefined when the field is absent or unreadable.
5010
- */
5011
- function decodeCheckpointUsedTokens(checkpoint) {
5012
- if (checkpoint === void 0) return void 0;
5013
- try {
5014
- const reader = new Reader(checkpoint);
5015
- while (!reader.done) {
5016
- const { field, wireType } = reader.tag();
5017
- if (field === 5 && wireType === 2) {
5018
- const details = new Reader(reader.bytes());
5019
- while (!details.done) {
5020
- const tag = details.tag();
5021
- if (tag.field === 1 && tag.wireType === 0) return details.varint();
5022
- details.skip(tag.wireType);
5023
- }
5024
- return;
5025
- }
5026
- reader.skip(wireType);
5027
- }
5028
- } catch {}
5029
- }
5030
- /** InteractionUpdate { text_delta=1, thinking_delta=4, token_delta=8, turn_ended=14, ... } */
5031
- function decodeInteractionUpdate(bytes) {
5032
- const reader = new Reader(bytes);
5033
- while (!reader.done) {
5034
- const { field, wireType } = reader.tag();
5035
- if (wireType !== 2) {
5036
- reader.skip(wireType);
5037
- continue;
5038
- }
5039
- const payload = reader.bytes();
5040
- if (field === 1) return {
5041
- type: "textDelta",
5042
- text: decodeTextDelta(payload)
5043
- };
5044
- if (field === 4) return {
5045
- type: "thinkingDelta",
5046
- text: decodeTextDelta(payload)
5047
- };
5048
- if (field === 5) return {
5049
- type: "thinkingCompleted",
5050
- durationMs: decodeThinkingDuration(payload)
5051
- };
5052
- if (field === 8) return {
5053
- type: "tokenDelta",
5054
- tokens: decodeTokenDelta(payload)
5055
- };
5056
- if (field === 14) return { type: "turnEnded" };
5057
- if (field === 2) return { type: "toolCallStarted" };
5058
- if (field === 3) return { type: "toolCallCompleted" };
5059
- if (field === 7) return {
5060
- type: "partialToolCall",
5061
- ...decodePartialToolCall(payload)
5062
- };
5063
- if (field === 15) return { type: "toolCallDelta" };
5064
- if (field === 13) return { type: "heartbeat" };
5065
- }
5066
- return { type: "unknown" };
5067
- }
5068
- function decodeThinkingDuration(bytes) {
5069
- const reader = new Reader(bytes);
5070
- while (!reader.done) {
5071
- const { field, wireType } = reader.tag();
5072
- if (field === 1 && wireType === 0) return reader.varint();
5073
- reader.skip(wireType);
5074
- }
5075
- return 0;
5076
- }
5077
- function decodeTextDelta(bytes) {
5078
- const reader = new Reader(bytes);
5079
- while (!reader.done) {
5080
- const { field, wireType } = reader.tag();
5081
- if (field === 1 && wireType === 2) return reader.string();
5082
- reader.skip(wireType);
5083
- }
5084
- return "";
5085
- }
5086
- function decodeTokenDelta(bytes) {
5087
- const reader = new Reader(bytes);
5088
- while (!reader.done) {
5089
- const { field, wireType } = reader.tag();
5090
- if (field === 1 && wireType === 0) return reader.varint();
5091
- reader.skip(wireType);
5092
- }
5093
- return 0;
5094
- }
5095
- /** PartialToolCallUpdate { call_id=1, args_text_delta=3 } */
5096
- function decodePartialToolCall(bytes) {
5097
- const reader = new Reader(bytes);
5098
- let callId = "";
5099
- let argsTextDelta = "";
5100
- while (!reader.done) {
5101
- const { field, wireType } = reader.tag();
5102
- if (field === 1 && wireType === 2) callId = reader.string();
5103
- else if (field === 3 && wireType === 2) argsTextDelta = reader.string();
5104
- else reader.skip(wireType);
5105
- }
5106
- return {
5107
- callId,
5108
- argsTextDelta
5109
- };
5110
- }
5111
- /** KvServerMessage { id=1, get_blob_args=2 { blob_id=1 } | set_blob_args=3 { blob_id=1, blob_data=2 } } */
5112
- function decodeKvServerMessage(bytes) {
5113
- const reader = new Reader(bytes);
5114
- let id = 0;
5115
- let blobId;
5116
- while (!reader.done) {
5117
- const { field, wireType } = reader.tag();
5118
- if (field === 1 && wireType === 0) id = reader.varint();
5119
- else if (field === 2 && wireType === 2) blobId = decodeBlobId(reader.bytes());
5120
- else if (field === 3 && wireType === 2) return {
5121
- id,
5122
- case: "setBlobArgs",
5123
- ...decodeSetBlobArgs(reader.bytes())
5124
- };
5125
- else reader.skip(wireType);
5126
- }
5127
- return blobId === void 0 ? {
5128
- id,
5129
- case: "unknown"
5130
- } : {
5131
- id,
5132
- case: "getBlobArgs",
5133
- blobId
5134
- };
5135
- }
5136
- /** SetBlobArgs { blob_id=1, blob_data=2 } */
5137
- function decodeSetBlobArgs(bytes) {
5138
- const reader = new Reader(bytes);
5139
- let blobId;
5140
- let blobData;
5141
- while (!reader.done) {
5142
- const { field, wireType } = reader.tag();
5143
- if (field === 1 && wireType === 2) blobId = reader.bytes();
5144
- else if (field === 2 && wireType === 2) blobData = reader.bytes();
5145
- else reader.skip(wireType);
5146
- }
5147
- return {
5148
- blobId,
5149
- blobData
5150
- };
5151
- }
5152
- function decodeBlobId(bytes) {
5153
- const reader = new Reader(bytes);
5154
- while (!reader.done) {
5155
- const { field, wireType } = reader.tag();
5156
- if (field === 1 && wireType === 2) return reader.bytes();
5157
- reader.skip(wireType);
5158
- }
5159
- }
5160
- /**
5161
- * Field number of `ExecServerMessage.span_context`. It is a plain (non-oneof)
5162
- * field, so a message carrying only ids, a span context, and an exec variant
5163
- * this build does not know must not mistake it for the exec itself.
5164
- */
5165
- const EXEC_SPAN_CONTEXT_FIELD = 19;
5166
- /** ExecServerMessage { id=1, exec_id=15, request_context_args=10, mcp_args=11, ... } */
5167
- function decodeExecServerMessage(bytes) {
5168
- const reader = new Reader(bytes);
5169
- let id = 0;
5170
- let execId = "";
5171
- let unknownField;
5172
- const seen = [];
5173
- while (!reader.done) {
5174
- const { field, wireType } = reader.tag();
5175
- seen.push(field);
5176
- if (field === 1 && wireType === 0) id = reader.varint();
5177
- else if (field === 15 && wireType === 2) execId = reader.string();
5178
- else if (wireType === 2) {
5179
- const payload = reader.bytes();
5180
- if (field === 10) return {
5181
- id,
5182
- execId,
5183
- case: "requestContextArgs"
5184
- };
5185
- if (field === 11) return {
5186
- id,
5187
- execId,
5188
- case: "mcpArgs",
5189
- args: decodeMcpArgs(payload)
5190
- };
5191
- if (field === 2) return {
5192
- id,
5193
- execId,
5194
- case: "shellArgs",
5195
- path: decodeSinglePathArg(payload)
5196
- };
5197
- if (field === 3) return {
5198
- id,
5199
- execId,
5200
- case: "writeArgs",
5201
- path: decodeSinglePathArg(payload)
5202
- };
5203
- if (field === 4) return {
5204
- id,
5205
- execId,
5206
- case: "deleteArgs",
5207
- path: decodeSinglePathArg(payload)
5208
- };
5209
- if (field === 5) return {
5210
- id,
5211
- execId,
5212
- case: "grepArgs"
5213
- };
5214
- if (field === 7) return {
5215
- id,
5216
- execId,
5217
- case: "readArgs",
5218
- path: decodeReadArgsPath(payload)
5219
- };
5220
- if (field === 8) return {
5221
- id,
5222
- execId,
5223
- case: "lsArgs",
5224
- path: decodeSinglePathArg(payload)
5225
- };
5226
- if (field === 9) return {
5227
- id,
5228
- execId,
5229
- case: "diagnosticsArgs"
5230
- };
5231
- if (field === 14) return {
5232
- id,
5233
- execId,
5234
- case: "shellStreamArgs",
5235
- path: decodeSinglePathArg(payload)
5236
- };
5237
- if (field === 16) return {
5238
- id,
5239
- execId,
5240
- case: "backgroundShellSpawnArgs"
5241
- };
5242
- if (field === 17) return {
5243
- id,
5244
- execId,
5245
- case: "listMcpResourcesExecArgs"
5246
- };
5247
- if (field === 18) return {
5248
- id,
5249
- execId,
5250
- case: "readMcpResourceExecArgs"
5251
- };
5252
- if (field === 20) return {
5253
- id,
5254
- execId,
5255
- case: "fetchArgs",
5256
- url: decodeFetchUrl(payload)
5257
- };
5258
- if (field === 21) return {
5259
- id,
5260
- execId,
5261
- case: "recordScreenArgs"
5262
- };
5263
- if (field === 22) return {
5264
- id,
5265
- execId,
5266
- case: "computerUseArgs"
5267
- };
5268
- if (field === 23) return {
5269
- id,
5270
- execId,
5271
- case: "writeShellStdinArgs"
5272
- };
5273
- if (field !== EXEC_SPAN_CONTEXT_FIELD) unknownField ??= field;
5274
- } else reader.skip(wireType);
5275
- }
5276
- return {
5277
- id,
5278
- execId,
5279
- case: "unknown",
5280
- field: unknownField,
5281
- seen
5282
- };
5283
- }
5284
- function decodeSinglePathArg(bytes) {
5285
- const reader = new Reader(bytes);
5286
- while (!reader.done) {
5287
- const { field, wireType } = reader.tag();
5288
- if (field === 1 && wireType === 2) return reader.string();
5289
- reader.skip(wireType);
5290
- }
5291
- return "";
5292
- }
5293
- function decodeReadArgsPath(bytes) {
5294
- return decodeSinglePathArg(bytes);
5295
- }
5296
- function decodeFetchUrl(bytes) {
5297
- const reader = new Reader(bytes);
5298
- while (!reader.done) {
5299
- const { field, wireType } = reader.tag();
5300
- if (field === 1 && wireType === 2) return reader.string();
5301
- reader.skip(wireType);
5302
- }
5303
- return "";
5304
- }
5305
- /** McpArgs { name=1, args=2 (map<string,bytes>), tool_call_id=3, provider_identifier=4, tool_name=5 } */
5306
- function decodeMcpArgs(bytes) {
5307
- const reader = new Reader(bytes);
5308
- let name$1 = "";
5309
- let toolCallId = "";
5310
- let providerIdentifier = "";
5311
- let toolName = "";
5312
- const args = {};
5313
- while (!reader.done) {
5314
- const { field, wireType } = reader.tag();
5315
- if (field === 1 && wireType === 2) name$1 = reader.string();
5316
- else if (field === 2 && wireType === 2) {
5317
- const entry = new Reader(reader.bytes());
5318
- let key = "";
5319
- let value;
5320
- while (!entry.done) {
5321
- const tag = entry.tag();
5322
- if (tag.field === 1 && tag.wireType === 2) key = entry.string();
5323
- else if (tag.field === 2 && tag.wireType === 2) value = entry.bytes();
5324
- else entry.skip(tag.wireType);
5325
- }
5326
- if (key) args[key] = value;
5327
- } else if (field === 3 && wireType === 2) toolCallId = reader.string();
5328
- else if (field === 4 && wireType === 2) providerIdentifier = reader.string();
5329
- else if (field === 5 && wireType === 2) toolName = reader.string();
5330
- else reader.skip(wireType);
5331
- }
5332
- return {
5333
- name: name$1,
5334
- toolCallId,
5335
- providerIdentifier,
5336
- toolName,
5337
- args
5338
- };
5339
- }
5340
- /** GetUsableModelsResponse { models = 1: repeated ModelDetails } */
5341
- function decodeUsableModels(bytes) {
5342
- const reader = new Reader(bytes);
5343
- const models = [];
5344
- while (!reader.done) {
5345
- const { field, wireType } = reader.tag();
5346
- if (field === 1 && wireType === 2) {
5347
- const model = decodeModelDetails(reader.bytes());
5348
- if (model?.id) models.push(model);
5349
- } else reader.skip(wireType);
5350
- }
5351
- return models;
5352
- }
5353
- /** ModelDetails { model_id=1, display_model_id=3, display_name=4, display_name_short=5, aliases=6 } */
5354
- function decodeModelDetails(bytes) {
5355
- const reader = new Reader(bytes);
5356
- const model = {
5357
- id: "",
5358
- name: ""
5359
- };
5360
- while (!reader.done) {
5361
- const { field, wireType } = reader.tag();
5362
- if (wireType === 2) if (field === 1) model.id = reader.string();
5363
- else if (field === 3) model.displayModelId = reader.string();
5364
- else if (field === 4) model.name = reader.string();
5365
- else if (field === 5) model.displayNameShort = reader.string();
5366
- else if (field === 6) reader.bytes();
5367
- else reader.bytes();
5368
- else reader.skip(wireType);
5369
- }
5370
- return model;
5371
- }
5372
- /** Connect frame: [1-byte flags][4-byte big-endian length][payload]. */
5373
- function frameEncode(payload, flags = 0) {
5374
- const out = new Uint8Array(5 + payload.length);
5375
- const view = new DataView(out.buffer);
5376
- view.setUint8(0, flags);
5377
- view.setUint32(1, payload.length, false);
5378
- out.set(payload, 5);
5379
- return out;
5380
- }
5381
- const CONNECT_END_STREAM_FLAG = 2;
5382
- const CONNECT_COMPRESSED_FLAG = 1;
5383
- /** Upper bound for a decompressed Connect frame; guards against zip bombs. */
5384
- const MAX_CONNECT_FRAME_BYTES = 64 * 1024 * 1024;
5385
- /**
5386
- * Incremental Connect frame parser fed by response chunks.
5387
- * Yields { flags, payload } objects. Frames flagged compressed (bit 0) are
5388
- * gzip-decompressed here so consumers only ever see plain payloads.
5389
- */
5390
- var ConnectFrameReader = class {
5391
- constructor() {
5392
- this.buffer = new Uint8Array(0);
5393
- this.frames = [];
5394
- this.waiters = [];
5395
- this.ended = false;
5396
- this.paused = false;
5397
- this.error = void 0;
5398
- }
5399
- push(chunk) {
5400
- const combined = new Uint8Array(this.buffer.length + chunk.length);
5401
- combined.set(this.buffer);
5402
- combined.set(chunk, this.buffer.length);
5403
- this.buffer = combined;
5404
- while (this.buffer.length >= 5) {
5405
- const view = new DataView(this.buffer.buffer, this.buffer.byteOffset, 5);
5406
- const flags = view.getUint8(0);
5407
- const length = view.getUint32(1, false);
5408
- if (this.buffer.length < 5 + length) break;
5409
- const payload = this.buffer.slice(5, 5 + length);
5410
- this.buffer = this.buffer.slice(5 + length);
5411
- let data = payload;
5412
- if ((flags & CONNECT_COMPRESSED_FLAG) !== 0) try {
5413
- data = gunzipSync(Buffer.from(payload), { maxOutputLength: MAX_CONNECT_FRAME_BYTES });
5414
- } catch (error) {
5415
- this.fail(new Error("Cursor sent an unreadable compressed frame", { cause: error }));
5416
- return;
5417
- }
5418
- this.#enqueue({
5419
- flags: flags & ~CONNECT_COMPRESSED_FLAG,
5420
- payload: data
5421
- });
5422
- }
5423
- }
5424
- #enqueue(frame) {
5425
- if (this.waiters.length > 0) {
5426
- this.waiters.shift().resolve(frame);
5427
- return;
5428
- }
5429
- this.frames.push(frame);
5430
- }
5431
- finish() {
5432
- this.ended = true;
5433
- for (const waiter of this.waiters.splice(0)) waiter.resolve(void 0);
5434
- }
5435
- /**
5436
- * Stop blocking for new frames without ending the stream: buffered frames
5437
- * are still returned, and `resume()` restores normal waiting. A Cursor run
5438
- * stays alive across DSH steps, so a reader that is paused to end one step
5439
- * must accept frames again when the next step continues the same run.
5440
- */
5441
- pause() {
5442
- this.paused = true;
5443
- for (const waiter of this.waiters.splice(0)) waiter.resolve(void 0);
5444
- }
5445
- resume() {
5446
- this.paused = false;
5447
- }
5448
- fail(error) {
5449
- this.error = error;
5450
- this.ended = true;
5451
- for (const waiter of this.waiters.splice(0)) waiter.reject(error);
5452
- }
5453
- async next() {
5454
- if (this.frames.length > 0) return this.frames.shift();
5455
- if (this.paused) return void 0;
5456
- if (this.ended) {
5457
- if (this.error !== void 0) throw this.error;
5458
- return;
5459
- }
5460
- return new Promise((resolve$2, reject) => this.waiters.push({
5461
- resolve: resolve$2,
5462
- reject
5463
- }));
5464
- }
5465
- };
5466
- const AGENT_HEADERS = {
5467
- "content-type": "application/connect+proto",
5468
- "connect-protocol-version": "1",
5469
- "connect-accept-encoding": "gzip",
5470
- te: "trailers",
5471
- "x-ghost-mode": "true",
5472
- "x-cursor-client-version": CURSOR_CLIENT_VERSION,
5473
- "x-cursor-client-type": "cli"
5474
- };
5475
- /**
5476
- * One bidirectional agent run over HTTP/2. Writes Connect-framed
5477
- * `AgentClientMessage`s and surfaces `AgentServerMessage`s through the frame
5478
- * reader. Heartbeats keep the run alive.
5479
- */
5480
- var AgentRun = class {
5481
- constructor(accessToken, options = {}) {
5482
- this.accessToken = accessToken;
5483
- this.baseUrl = options.baseUrl ?? CURSOR_BASE_URL;
5484
- this.session = void 0;
5485
- this.frames = new ConnectFrameReader();
5486
- this.responseStatus = void 0;
5487
- this.responseContentType = void 0;
5488
- this.trailers = {};
5489
- this.finished = false;
5490
- }
5491
- async start() {
5492
- const url = new URL(CURSOR_RUN_PATH, this.baseUrl);
5493
- const headers = {
5494
- ":method": "POST",
5495
- ":path": url.pathname,
5496
- authorization: `Bearer ${this.accessToken}`,
5497
- ...AGENT_HEADERS
5498
- };
5499
- this.responsePromise = new Promise((resolve$2, reject) => {
5500
- this.resolveResponse = resolve$2;
5501
- this.rejectResponse = reject;
5502
- });
5503
- this.responsePromise.catch(() => {});
5504
- this.session = http2.connect(this.baseUrl);
5505
- this.stream = this.session.request(headers);
5506
- this.stream.on("response", (headers$1) => {
5507
- this.responseStatus = Number(headers$1[":status"]);
5508
- this.responseContentType = typeof headers$1["content-type"] === "string" ? headers$1["content-type"] : void 0;
5509
- this.#resolveResponse(this.responseStatus);
5510
- });
5511
- this.stream.on("data", (chunk) => this.frames.push(Buffer.from(chunk)));
5512
- this.stream.on("trailers", (trailers) => {
5513
- this.trailers = trailers;
5514
- });
5515
- this.stream.on("end", () => this.frames.finish());
5516
- this.stream.on("close", () => {
5517
- if (this.responseStatus === void 0) this.#rejectResponse(/* @__PURE__ */ new Error("Cursor HTTP stream closed before response"));
5518
- });
5519
- this.stream.on("error", (error) => {
5520
- this.#rejectResponse(error);
5521
- this.frames.fail(error);
5522
- });
5523
- this.session.on("error", (error) => {
5524
- this.#rejectResponse(error);
5525
- this.frames.fail(error);
5526
- });
5527
- }
5528
- #resolveResponse(status) {
5529
- const resolve$2 = this.resolveResponse;
5530
- this.resolveResponse = void 0;
5531
- this.rejectResponse = void 0;
5532
- resolve$2?.(status);
5533
- }
5534
- #rejectResponse(error) {
5535
- const reject = this.rejectResponse;
5536
- this.resolveResponse = void 0;
5537
- this.rejectResponse = void 0;
5538
- reject?.(error);
5539
- }
5540
- waitForResponse(timeoutMs) {
5541
- if (this.responsePromise === void 0) throw new Error("Cursor AgentRun has not started");
5542
- if (timeoutMs === void 0) return this.responsePromise;
5543
- return new Promise((resolve$2, reject) => {
5544
- const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Cursor HTTP response timeout")), timeoutMs);
5545
- timer.unref?.();
5546
- this.responsePromise.then(resolve$2, reject).finally(() => clearTimeout(timer));
5547
- });
5548
- }
5549
- write(payload) {
5550
- if (this.finished || this.stream === void 0 || this.stream.destroyed) return false;
5551
- try {
5552
- this.stream.write(Buffer.from(payload));
5553
- return true;
5554
- } catch {
5555
- return false;
5556
- }
5557
- }
5558
- writeMessage(bytes) {
5559
- return this.write(frameEncode(bytes));
5560
- }
5561
- startHeartbeat() {
5562
- this.heartbeat = setInterval(() => {
5563
- this.writeMessage(encodeHeartbeat());
5564
- }, HEARTBEAT_INTERVAL_MS);
5565
- this.heartbeat.unref?.();
5566
- }
5567
- /** Fail the run with a terminal error and tear down the connection. */
5568
- abort(error) {
5569
- if (this.finished) return;
5570
- this.#rejectResponse(error);
5571
- this.frames.fail(error);
5572
- this.close();
5573
- }
5574
- close() {
5575
- if (this.finished) return;
5576
- this.#rejectResponse(/* @__PURE__ */ new Error("Cursor AgentRun closed before response"));
5577
- this.finished = true;
5578
- if (this.heartbeat !== void 0) clearInterval(this.heartbeat);
5579
- if (!this.frames.ended) this.frames.finish();
5580
- try {
5581
- this.stream?.close();
5582
- } catch {}
5583
- try {
5584
- this.session.destroy();
5585
- } catch {}
5586
- }
5587
- };
5588
- function sha256(bytes) {
5589
- return createHash("sha256").update(bytes).digest();
5590
- }
5591
- function collectImageAttachments(options) {
5592
- const refs = /* @__PURE__ */ new Map();
5593
- const visit = (blocks) => {
5594
- for (const block of blocks ?? []) if (block.type === "image") refs.set(block.attachment.attachmentId, block.attachment);
5595
- else if (block.type === "tool-result") visit(block.content);
5596
- };
5597
- for (const message of options.messages ?? []) if (message.role === "user") visit(message.content);
5598
- return [...refs.values()];
5599
- }
5600
- async function prepareCursorImages(options, attachments) {
5601
- const refs = collectImageAttachments(options);
5602
- if (refs.length === 0) return [];
5603
- if (attachments === void 0) throw new LlmError("Cursor image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
5604
- return (await Promise.all(refs.map((ref) => attachments.readImage(ref, options.signal)))).map(({ ref, data }) => ({
5605
- data: Buffer.from(data).toString("base64"),
5606
- mimeType: ref.mediaType
5607
- }));
5608
- }
5609
- function flattenBlocks(content) {
5610
- let text = "";
5611
- for (const block of content ?? []) if (block.type === "text") text += block.text;
5612
- else if (block.type === "tool-result") {
5613
- const callId = block.toolCallId ? ` for ${block.toolCallId}` : "";
5614
- text += `\n[TOOL RESULT${callId}]\n${flattenBlocks(block.content)}`;
5615
- }
5616
- return text;
5617
- }
5618
- function sanitizeReplayedAssistantText(text) {
5619
- let omitted = false;
5620
- const sanitized = String(text ?? "").replace(/<tool_call\b[\s\S]*?<\/tool_call>/gi, () => {
5621
- omitted = true;
5622
- return "";
5623
- }).replace(/<\/?invoke>|<\|eos\|>/gi, "").trim();
5624
- if (!omitted) return sanitized;
5625
- return `${sanitized}${sanitized ? "\n" : ""}[Previous textual tool-call markup omitted from replay.]`;
5626
- }
5627
- /**
5628
- * How much of a replayed tool request to keep. Enough for the arguments that are
5629
- * state — a plan (`todo_write`) is its list, and a repeated `grep` is avoided
5630
- * only if the model can see what it already asked for — while a `write` of a
5631
- * whole file stays bounded.
5632
- */
5633
- const REPLAYED_CALL_CHARS = 600;
5634
- /** Replay assistant history without teaching models to imitate XML tool tags. */
5635
- function renderColdStartAssistant(content) {
5636
- const parts = [];
5637
- for (const block of content ?? []) if (block.type === "text") {
5638
- const text = sanitizeReplayedAssistantText(block.text);
5639
- if (text) parts.push(text);
5640
- } else if (block.type === "tool-call") {
5641
- const args = typeof block.arguments === "string" ? block.arguments : JSON.stringify(block.arguments ?? "");
5642
- const shown = args.length > REPLAYED_CALL_CHARS ? `${args.slice(0, REPLAYED_CALL_CHARS)}…` : args;
5643
- parts.push(`[Previous tool request: ${block.name}${shown.length > 0 ? ` ${shown}` : ""}]`);
5644
- }
5645
- return parts.join("\n").trim();
5646
- }
5647
- function actionBoundary(messages) {
5648
- for (let index = messages.length - 1; index >= 0; index--) if (messages[index].role === "assistant") return index + 1;
5649
- return 0;
5650
- }
5651
- /** Combine the current DSH turn's adjacent user/context messages into one action. */
5652
- function currentActionText(options) {
5653
- const messages = options.messages ?? [];
5654
- const boundary = actionBoundary(messages);
5655
- const parts = [];
5656
- for (let index = boundary; index < messages.length; index++) {
5657
- const message = messages[index];
5658
- if (message.role !== "user") continue;
5659
- const text = flattenBlocks(message.content).trim();
5660
- if (text.length > 0) parts.push(text);
5661
- }
5662
- if (parts.length > 0) return parts.join("\n\n");
5663
- for (let index = messages.length - 1; index >= 0; index--) {
5664
- if (messages[index].role !== "user") continue;
5665
- const text = flattenBlocks(messages[index].content).trim();
5666
- if (text.length > 0) return text;
5667
- }
5668
- return "";
5669
- }
5670
- /**
5671
- * Markers this harness leaves in a result when the sandbox refused the work.
5672
- *
5673
- * They call for different answers, and telling them apart matters: the
5674
- * `SetNamedSecurityInfoW` failure is the ACL grant the confinement itself needs
5675
- * and cannot get, so *no* narrower mode can start and only a mode without that
5676
- * grant can run the call. `[sandbox: ... denied under <mode> mode]` is a
5677
- * refusal from a sandbox that did start, where one step wider may well suffice.
5678
- * Naming the narrowest mode first for the ACL failure is what turned a single
5679
- * refusal into a retry loop: the model asked for `workspace-write` — the mode it
5680
- * was already in — and failed the same way 101 times in one session.
5681
- */
5682
- const SANDBOX_ACL_FAILURE = /SetNamedSecurityInfoW failed/i;
5683
- const SANDBOX_POLICY_DENIAL = /\[sandbox:[^\]]*denied/i;
5684
- /**
5685
- * DSH's escalation rule, restated where the refusal lands.
5686
- *
5687
- * Every provider gets this rule — retry the denied call once with
5688
- * `sandbox_permissions` and a justification, which raises an approval prompt —
5689
- * but only the `pwsh` description spells it out, and the refusal result carries
5690
- * nothing at all. A model that does not recall the rule from a tool description
5691
- * reads the refusal as "this tool does not work here" and stops using it, which
5692
- * is how a Cursor turn ends up saying local tools are unavailable instead of
5693
- * asking the user for permission.
5694
- */
5695
- 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.";
5696
- 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.";
5697
- /** Whether one result is the harness refusing, rather than the tool failing. */
5698
- function isSandboxRefusal(content) {
5699
- return SANDBOX_ACL_FAILURE.test(content) || SANDBOX_POLICY_DENIAL.test(content);
5700
- }
5701
- /**
5702
- * Result text as the model should read it: a sandbox refusal gains the rule that
5703
- * turns it back into a request the user can approve.
5704
- * @param content Result text on its way to the model.
5705
- * @returns The text, with the escalation rule a refusal calls for appended.
5706
- */
5707
- function withSandboxHint(content) {
5708
- if (content.includes("sandbox_permissions")) return content;
5709
- if (SANDBOX_ACL_FAILURE.test(content)) return `${content}${SANDBOX_ACL_HINT}`;
5710
- if (SANDBOX_POLICY_DENIAL.test(content)) return `${content}${SANDBOX_POLICY_HINT}`;
5711
- return content;
5712
- }
5713
- /** Extract DSH tool-result blocks for resuming a live Cursor exec bridge. */
5714
- function collectToolResults(options) {
5715
- const results = [];
5716
- for (const message of options.messages ?? []) for (const block of message.content ?? []) {
5717
- if (block.type !== "tool-result") continue;
5718
- const content = flattenBlocks(block.content).trim();
5719
- const sandboxDenied = isSandboxRefusal(content);
5720
- results.push({
5721
- toolCallId: block.toolCallId ?? message.source?.callId,
5722
- content: withSandboxHint(content),
5723
- isError: block.isError === true,
5724
- sandboxDenied
5725
- });
5726
- }
5727
- return results;
5728
- }
5729
- /**
5730
- * The compaction DSH applied to the history carried by one request, if any.
5731
- *
5732
- * DSH frees context by replacing a span of the conversation with a summary and
5733
- * marking that message `source.kind = "compact-checkpoint"` with the id of the
5734
- * compaction. The adapter compares that id with the one its persisted Cursor
5735
- * conversation was built from: equal means the checkpoint still describes the
5736
- * history DSH is sending, a new id means DSH has rewritten it.
5737
- *
5738
- * @param options The DSH request whose message history is being sent.
5739
- * @returns The newest compaction id in the history, or undefined when it has none.
5740
- */
5741
- function compactionIdOf(options) {
5742
- let id;
5743
- for (const message of options.messages ?? []) {
5744
- if (message.source?.kind !== "compact-checkpoint") continue;
5745
- id = message.source.compactionId ?? "compact-checkpoint";
5746
- }
5747
- return id;
5748
- }
5749
- function decodeXmlText(value) {
5750
- return String(value ?? "").replace(/&quot;/gi, "\"").replace(/&apos;/gi, "'").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&amp;/gi, "&");
5751
- }
5752
- function parseTagAttributes(source) {
5753
- const attributes = {};
5754
- const pattern = /([A-Za-z_][\w-]*)\s*=\s*"([\s\S]*?)"/g;
5755
- for (let match; (match = pattern.exec(source)) !== null;) attributes[match[1]] = decodeXmlText(match[2]);
5756
- return attributes;
5757
- }
5758
- function resolveTextTool(candidate, tools) {
5759
- const available = tools ?? [];
5760
- const normalized = String(candidate ?? "").toLowerCase();
5761
- let tool = available.find((entry) => {
5762
- const name$1 = String(entry.name).toLowerCase();
5763
- return normalized === name$1 || normalized.endsWith(`_${name$1}`);
5764
- });
5765
- if (tool !== void 0) return tool;
5766
- for (const name$1 of {
5767
- shell: ["pwsh", "bash"],
5768
- read: ["read"],
5769
- glob: ["glob"],
5770
- grep: ["grep"],
5771
- write: ["write"],
5772
- edit: ["edit"]
5773
- }[normalized] ?? []) {
5774
- tool = available.find((entry) => String(entry.name).toLowerCase() === name$1);
5775
- if (tool !== void 0) return tool;
5776
- }
5777
- }
5778
- function normalizeTextToolArguments(tool, raw) {
5779
- const args = { ...raw };
5780
- if (tool.name === "read" && args.file_path === void 0 && args.path !== void 0) {
5781
- args.file_path = args.path;
5782
- delete args.path;
5783
- }
5784
- if (tool.name === "glob") {
5785
- if (args.pattern === void 0 && args.glob_pattern !== void 0) args.pattern = args.glob_pattern;
5786
- if (args.path === void 0 && args.target_directory !== void 0) args.path = args.target_directory;
5787
- delete args.glob_pattern;
5788
- delete args.target_directory;
5789
- }
5790
- for (const [key, value] of Object.entries(args)) {
5791
- const type = tool.parameters?.properties?.[key]?.type;
5792
- if ((type === "number" || type === "integer") && typeof value === "string" && value.trim() !== "") {
5793
- const number$1 = Number(value);
5794
- if (Number.isFinite(number$1)) args[key] = number$1;
5795
- } else if (type === "boolean" && typeof value === "string") {
5796
- if (value.toLowerCase() === "true") args[key] = true;
5797
- else if (value.toLowerCase() === "false") args[key] = false;
5798
- } else if ((type === "object" || type === "array") && typeof value === "string") try {
5799
- args[key] = JSON.parse(value);
5800
- } catch {}
5801
- }
5802
- return args;
5803
- }
5804
- /**
5805
- * Recover Cursor models that print tool-call XML as text instead of emitting
5806
- * an execServerMessage.mcpArgs frame. This is a fallback for contaminated or
5807
- * compacted conversations; protocol-native MCP calls remain the primary path.
5808
- */
5809
- function parseTextToolCalls(text, tools) {
5810
- const calls = [];
5811
- const pattern = /<tool_call\b([^>]*)>([\s\S]*?)<\/tool_call>/gi;
5812
- for (let match; (match = pattern.exec(String(text ?? ""))) !== null;) {
5813
- const attributes = parseTagAttributes(match[1]);
5814
- const tool = resolveTextTool(attributes.name ?? attributes.id, tools);
5815
- if (tool === void 0) continue;
5816
- const rawArgs = {};
5817
- const parameterPattern = /<parameter\s+name="([^"]+)"\s*>([\s\S]*?)<\/parameter>/gi;
5818
- for (let parameter; (parameter = parameterPattern.exec(match[2])) !== null;) rawArgs[parameter[1]] = decodeXmlText(parameter[2].trim());
5819
- for (const [key, value] of Object.entries(attributes)) if (key !== "id" && key !== "name") rawArgs[key] = value;
5820
- if (Object.keys(rawArgs).length === 0) {
5821
- const body = decodeXmlText(match[2]).trim();
5822
- if (body.startsWith("{")) try {
5823
- Object.assign(rawArgs, JSON.parse(body));
5824
- } catch {}
5825
- }
5826
- calls.push({
5827
- id: `text-tool-${crypto.randomUUID()}`,
5828
- name: tool.name,
5829
- arguments: JSON.stringify(normalizeTextToolArguments(tool, rawArgs))
5830
- });
5831
- }
5832
- return calls;
5833
- }
5834
- function coldStartLabel(message) {
5835
- if (message.role === "assistant") return "ASSISTANT";
5836
- if ((message.content ?? []).some((block) => block.type === "tool-result")) return "TOOL RESULT";
5837
- if (message.source?.kind === "plugin") return "RUNTIME CONTEXT";
5838
- return "USER";
5839
- }
5840
- /**
5841
- * Rehydrate a DSH conversation after process restart without using Cursor's
5842
- * field-8 turns (the current server interprets those entries as blob ids).
5843
- */
5844
- function coldStartActionText(options) {
5845
- const entries = [];
5846
- for (const message of options.messages ?? []) {
5847
- if (message.role === "system") continue;
5848
- const text = message.role === "assistant" ? renderColdStartAssistant(message.content) : withSandboxHint(flattenBlocks(message.content).trim());
5849
- if (text.length === 0) continue;
5850
- entries.push({
5851
- label: coldStartLabel(message),
5852
- text
5853
- });
5854
- }
5855
- if (entries.length === 0) return "";
5856
- if (entries.length === 1 && entries[0].label === "USER") return entries[0].text;
5857
- 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");
5858
- }
5859
- /**
5860
- * Build a fresh state with root prompt blobs only. Current Cursor servers reject
5861
- * hand-encoded field-8 turns, so cold starts carry textual history in the action.
5862
- */
5863
- function buildInitialConversationState(options) {
5864
- const systemParts = [options.system ?? ""];
5865
- for (const message of options.messages ?? []) if (message.role === "system") systemParts.push(flattenBlocks(message.content));
5866
- const systemText = systemParts.filter((part) => part.length > 0).join("\n").trim();
5867
- const blobStore = /* @__PURE__ */ new Map();
5868
- const rootPromptBlobIds = [];
5869
- if (systemText.length > 0) {
5870
- const payload = textEncoder.encode(JSON.stringify({
5871
- role: "system",
5872
- content: systemText
5873
- }));
5874
- const id = sha256(payload);
5875
- blobStore.set(Buffer.from(id).toString("hex"), payload);
5876
- rootPromptBlobIds.push(id);
5877
- }
5878
- return {
5879
- conversationState: encodeConversationState({
5880
- rootPromptBlobIds,
5881
- turns: [],
5882
- systemText
5883
- }),
5884
- blobStore,
5885
- systemText
5886
- };
5887
- }
5888
- /**
5889
- * Build the full `AgentClientMessage` payload for one run.
5890
- *
5891
- * When a persisted checkpoint for this conversation exists (captured from a
5892
- * previous run's `conversation_checkpoint_update`), it becomes the
5893
- * conversation state and its referenced blobs are served from the persisted
5894
- * blob store. Otherwise the state is built from the DSH message history.
5895
- */
5896
- function buildRunPayload(options, modelId, persisted, images = []) {
5897
- let conversationState;
5898
- let blobStore;
5899
- let actionText;
5900
- if (persisted?.checkpoint !== void 0) {
5901
- conversationState = persisted.checkpoint;
5902
- blobStore = persisted.blobs ?? /* @__PURE__ */ new Map();
5903
- actionText = currentActionText(options);
5904
- } else {
5905
- const built = buildInitialConversationState(options);
5906
- conversationState = built.conversationState;
5907
- blobStore = built.blobStore;
5908
- actionText = coldStartActionText(options);
5909
- }
5910
- const action = encodeUserMessageAction(encodeUserMessage({
5911
- text: actionText,
5912
- messageId: randomUUID()
5913
- }), images);
5914
- const modelDetails = encodeModelDetails(modelId);
5915
- return {
5916
- payload: encodeRunMessage(encodeRunRequest({
5917
- conversationState,
5918
- action,
5919
- modelDetails,
5920
- conversationId: randomUUID()
5921
- })),
5922
- blobStore
5923
- };
5924
- }
5925
- /**
5926
- * Fetch usable models from Cursor's unary `GetUsableModels` endpoint.
5927
- * The request body is the raw (unframed) empty protobuf message.
5928
- */
5929
- async function fetchUsableModels(accessToken, options = {}) {
5930
- const response = await (options.fetch ?? nativeFetch)(`${CURSOR_BASE_URL}${CURSOR_MODELS_PATH}`, {
5931
- method: "POST",
5932
- redirect: "error",
5933
- headers: {
5934
- "content-type": "application/proto",
5935
- authorization: `Bearer ${accessToken}`,
5936
- "x-ghost-mode": "true",
5937
- "x-cursor-client-version": CURSOR_CLIENT_VERSION,
5938
- "x-cursor-client-type": "cli",
5939
- "user-agent": "dsh-cursor-subscription/0.1.0"
5940
- },
5941
- body: new Uint8Array(0),
5942
- signal: options.signal
5943
- });
5944
- if (!response.ok) throw new Error(`Cursor model discovery failed (HTTP ${response.status})`);
5945
- const bytes = new Uint8Array(await response.arrayBuffer());
5946
- if (bytes.length === 0) throw new Error("Cursor model discovery returned an empty response");
5947
- const models = decodeUsableModels(bytes);
5948
- if (models.length > 0) return models;
5949
- const frameReader = new ConnectFrameReader();
5950
- frameReader.push(bytes);
5951
- frameReader.finish();
5952
- const framed = [];
5953
- for (;;) {
5954
- const frame = await frameReader.next();
5955
- if (frame === void 0) break;
5956
- if ((frame.flags & CONNECT_END_STREAM_FLAG) !== 0) break;
5957
- framed.push(...decodeUsableModels(frame.payload));
5958
- }
5959
- return framed;
5960
- }
5961
- const record$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5962
- const TOOL_REJECT_REASON = "Tool not available in this environment. Use the MCP tools provided instead.";
5963
- /** Block indices are fixed per type so the assembler keeps blocks distinct. */
5964
- const TEXT_BLOCK_INDEX = 0;
5965
- const REASONING_BLOCK_INDEX = 1;
5966
- const TOOL_BLOCK_INDEX = 2;
5967
- /**
5968
- * Progress notes ride their own reasoning block, so the model's own thinking
5969
- * stays exactly what the model produced. Reasoning is the only channel an
5970
- * adapter has for showing work that is not answer text, and a Cursor step
5971
- * otherwise reduces to one sentence plus a tool card: the protocol round trips
5972
- * that cost the turn most of its wall clock — a declined native tool call, a
5973
- * tool-schema fetch — are invisible, and a turn of them reads as the same
5974
- * request being submitted again and again.
5975
- */
5976
- const PROGRESS_BLOCK_INDEX = 3;
5977
- /**
5978
- * One reader-visible progress line.
5979
- * @param text What just happened, in one clause.
5980
- * @returns A reasoning delta the conversation view renders as process.
5981
- */
5982
- function cursorProgress(text) {
5983
- return {
5984
- type: "reasoning-delta",
5985
- index: PROGRESS_BLOCK_INDEX,
5986
- text: `[cursor] ${text}\n`
5987
- };
5988
- }
5989
- /** Reader-facing name of one decoded exec. */
5990
- function nativeToolName(exec) {
5991
- if (exec.case === "unknown") return `tool (field ${exec.field ?? "?"})`;
5992
- return exec.case.replace(/ExecArgs$|Args$/, "");
5993
- }
5994
- /**
5995
- * DSH `LlmAdapter` for the Cursor subscription. Normal turns use Cursor
5996
- * checkpoints; MCP tool calls keep their HTTP/2 Run alive across DSH steps so
5997
- * the following tool-result can resume the exact pending exec with mcpResult.
5998
- */
5999
- var CursorAdapter = class extends LlmAdapter {
6000
- constructor(options) {
6001
- super();
6002
- this.auth = options.auth;
6003
- this.fetchModels = options.fetchModels ?? fetchUsableModels;
6004
- this.idleTimeoutMs = options.idleTimeoutMs ?? STREAM_IDLE_TIMEOUT_MS;
6005
- this.progressTimeoutMs = options.progressTimeoutMs ?? STREAM_PROGRESS_TIMEOUT_MS;
6006
- this.idleCheckIntervalMs = options.idleCheckIntervalMs ?? 15e3;
6007
- this.modelsCacheTtlMs = options.modelsCacheTtlMs ?? 300 * 1e3;
6008
- this.sessionStateTtlMs = options.sessionStateTtlMs ?? SESSION_STATE_TTL_MS;
6009
- const fallbackSettings = resolveCursorSettings({
6010
- maxToolRounds: options.maxToolRounds,
6011
- retryCount: options.retryCount,
6012
- retryIntervalMs: options.retryIntervalMs,
6013
- retryHttpStatusCodes: options.retryHttpStatusCodes
6014
- });
6015
- this.settings = options.settings ?? (() => fallbackSettings);
6016
- this.createAgentRun = options.createAgentRun ?? ((access) => new AgentRun(access));
6017
- this.resolveAttachments = options.resolveAttachments;
6018
- this.sleep = options.sleep ?? abortableDelay$1;
6019
- this.now = options.now ?? Date.now;
6020
- this.logger = options.logger;
6021
- this.hangTracePath = options.hangTracePath ?? join(homedir(), ".dsh", "cursor-hang-trace.log");
6022
- this.toolCallSettleMs = options.toolCallSettleMs ?? TOOL_CALL_SETTLE_MS;
6023
- this.#modelsCache = {
6024
- at: 0,
6025
- models: void 0
6026
- };
6027
- }
6028
- #modelsCache;
6029
- /** Persisted conversation checkpoints + referenced blobs per DSH session id. */
6030
- #sessions = /* @__PURE__ */ new Map();
6031
- #evictStaleSessions() {
6032
- const now = this.now();
6033
- for (const [key, state] of this.#sessions) {
6034
- if (now - (state.lastAccessMs ?? now) <= this.sessionStateTtlMs) continue;
6035
- state.bridge?.run.close();
6036
- this.#sessions.delete(key);
6037
- }
6038
- }
6039
- async #discoverModels(force = false) {
6040
- const now = this.now();
6041
- if (!force && this.#modelsCache.models !== void 0 && now - this.#modelsCache.at < this.modelsCacheTtlMs) return this.#modelsCache.models;
6042
- const access = await this.auth.accessToken();
6043
- const source = await this.fetchModels(access);
6044
- if (source.length === 0) throw new Error("Cursor returned no usable models");
6045
- const result = sortModelsByName(source.map((model) => ({
6046
- id: model.id,
6047
- name: model.name || model.id,
6048
- inputModalities: ["text", "image"]
6049
- })));
6050
- this.#modelsCache = {
6051
- at: now,
6052
- models: result
6053
- };
6054
- return result;
6055
- }
6056
- invalidateModels() {
6057
- this.#modelsCache = {
6058
- at: 0,
6059
- models: void 0
6060
- };
6061
- }
6062
- providerInfo(provider) {
6063
- return {
6064
- id: provider,
6065
- name: "Cursor subscription"
6066
- };
6067
- }
6068
- async listModels(provider) {
6069
- return (await this.#discoverModels()).map((model) => ({
6070
- provider,
6071
- ...model
6072
- }));
6073
- }
6074
- /** RPC-facing model listing; `force` bypasses the discovery cache. */
6075
- async listModelsForRpc({ force = false, signal } = {}) {
6076
- const models = await this.#discoverModels(force);
6077
- if (signal?.aborted) throw new LlmError("Cursor model listing aborted", "ABORTED");
6078
- return models;
6079
- }
6080
- async resolveModel(provider, model, _signal) {
6081
- let name$1 = model;
6082
- try {
6083
- const found = (await this.#discoverModels()).find((entry) => entry.id === model);
6084
- if (found !== void 0) name$1 = found.name;
6085
- } catch {}
6086
- return {
6087
- provider,
6088
- id: model,
6089
- name: name$1,
6090
- inputModalities: ["text", "image"],
6091
- context: { contextWindow: DEFAULT_CONTEXT_WINDOW },
6092
- defaultMaxTokens: DEFAULT_MAX_TOKENS
6093
- };
6094
- }
6095
- async *stream(options) {
6096
- const consumer = new AbortController();
6097
- const upstream = options.signal === void 0 ? consumer.signal : AbortSignal.any([options.signal, consumer.signal]);
6098
- let run;
6099
- let abortRun;
6100
- let bridgeStored = false;
6101
- const sessionKey = options.sessionId === void 0 ? void 0 : String(options.sessionId);
6102
- this.#evictStaleSessions();
6103
- const prior = sessionKey === void 0 ? void 0 : this.#sessions.get(sessionKey);
6104
- const persisted = {
6105
- checkpoint: prior?.checkpoint === void 0 ? void 0 : Uint8Array.from(prior.checkpoint),
6106
- blobs: new Map(prior?.blobs ?? [])
6107
- };
6108
- const compactionId = compactionIdOf(options);
6109
- const compacted = compactionId !== void 0 && compactionId !== prior?.compactionId;
6110
- if (compacted) {
6111
- persisted.checkpoint = void 0;
6112
- persisted.blobs.clear();
6113
- }
6114
- const replayHistoryEachStep = readConfigField(this.settings().replayHistoryEachStep) === true;
6115
- if (replayHistoryEachStep) {
6116
- persisted.checkpoint = void 0;
6117
- persisted.blobs.clear();
6118
- }
6119
- persisted.compactionId = compactionId ?? prior?.compactionId;
6120
- try {
6121
- if (options.stop !== void 0) throw new LlmError("cursor-subscription does not support GenerateOptions.stop", "UNSUPPORTED_OPTION");
6122
- const requestSettings = resolveCursorSettings(this.settings());
6123
- const mcpTools = (options.tools ?? []).map((tool) => encodeMcpToolDefinition({
6124
- name: tool.name,
6125
- description: tool.description,
6126
- inputSchema: encodeValue(tool.parameters ?? {})
6127
- }));
6128
- const toolResults = collectToolResults(options);
6129
- const liveBridge = prior?.bridge;
6130
- const canResume = !replayHistoryEachStep && !compacted && liveBridge !== void 0 && !liveBridge.run.finished && !liveBridge.run.stream?.destroyed && toolResults.length > 0;
6131
- let toolRoundCount = canResume ? liveBridge.toolRoundCount ?? 0 : 0;
6132
- let blobStore;
6133
- if (canResume) {
6134
- run = liveBridge.run;
6135
- run.frames?.resume?.();
6136
- blobStore = persisted.blobs;
6137
- abortRun = () => run.abort(/* @__PURE__ */ new Error("Cursor request aborted by caller"));
6138
- upstream.addEventListener("abort", abortRun, { once: true });
6139
- if (upstream.aborted) abortRun();
6140
- for (const pending of liveBridge.pendingExecs) {
6141
- const result = toolResults.find((entry) => entry.toolCallId === pending.toolCallId);
6142
- if (result?.sandboxDenied === true) yield cursorProgress("the sandbox refused a tool call; told the model how to ask for approval");
6143
- const payload = result === void 0 ? encodeMcpError("Tool result not provided") : encodeMcpResult(result);
6144
- if (!run.writeMessage(encodeExecClientMessageEnvelope(encodeExecClientMessage(pending.id, pending.execId, 11, payload)))) throw new Error("Cursor tool continuation bridge closed before accepting the result");
6145
- }
6146
- } else {
6147
- if (liveBridge !== void 0) {
6148
- liveBridge.run.close();
6149
- persisted.checkpoint = void 0;
6150
- persisted.blobs.clear();
6151
- }
6152
- const access = await this.auth.accessToken({ signal: upstream });
6153
- const images = await prepareCursorImages(options, this.resolveAttachments?.());
6154
- const built = buildRunPayload(options, options.model, persisted, images);
6155
- blobStore = built.blobStore;
6156
- let retriesUsed = 0;
6157
- for (;;) {
6158
- run = this.createAgentRun(access);
6159
- await run.start();
6160
- abortRun = () => run.abort(/* @__PURE__ */ new Error("Cursor request aborted by caller"));
6161
- upstream.addEventListener("abort", abortRun, { once: true });
6162
- if (upstream.aborted) abortRun();
6163
- if (!run.writeMessage(built.payload)) throw new Error("Cursor agent bridge closed before accepting the request");
6164
- const status = await run.waitForResponse(this.idleTimeoutMs);
6165
- if (isSuccessfulAgentResponse(status, run.responseContentType)) {
6166
- run.startHeartbeat();
6167
- break;
6168
- }
6169
- upstream.removeEventListener("abort", abortRun);
6170
- abortRun = void 0;
6171
- run.close();
6172
- if (status === 200) throw new LlmError(`Cursor agent returned an unexpected content type: ${run.responseContentType ?? "missing"}`, "CURSOR_PROTOCOL");
6173
- if (!shouldRetryHttpStatus(status, retriesUsed, requestSettings)) {
6174
- const message = `Cursor agent returned HTTP ${status}`;
6175
- throw new LlmError(message, classifyCursorError(message));
6176
- }
6177
- retriesUsed++;
6178
- await this.sleep(requestSettings.retryIntervalMs, upstream);
6179
- }
6180
- }
6181
- const started = this.now();
6182
- let lastActivity = started;
6183
- let lastProgress = started;
6184
- let emittedToolCall = false;
6185
- let toolCallBuffer = void 0;
6186
- let textOutput = "";
6187
- let outputTokens = 0;
6188
- let streamClosed = false;
6189
- let terminalErrorEmitted = false;
6190
- let toolCallPending = false;
6191
- let toolRoundCounted = false;
6192
- let unknownExecWarned = false;
6193
- const pendingExecs = [];
6194
- let toolCallSettle;
6195
- /**
6196
- * Stop blocking for more frames without ending the reader, so the same
6197
- * Run can be continued by the next DSH step.
6198
- */
6199
- const stopReading = () => {
6200
- if (typeof run.frames?.pause === "function") run.frames.pause();
6201
- else if (typeof run.frames?.finish === "function") run.frames.finish();
6202
- };
6203
- /**
6204
- * Cap how long the loop waits for the frame that closes a tool-call
6205
- * burst. Cursor usually follows mcpArgs with blob writes and a
6206
- * checkpoint, but it may instead send the checkpoint *before* the burst
6207
- * and then nothing at all; waiting for a second checkpoint in that case
6208
- * hung the step until the progress watchdog aborted it.
6209
- */
6210
- const armToolCallSettle = () => {
6211
- toolCallSettle ??= setTimeout(stopReading, this.toolCallSettleMs);
6212
- toolCallSettle.unref?.();
6213
- };
6214
- const frameTrace = [];
6215
- const traceFrame = (label) => {
6216
- const last = frameTrace.at(-1);
6217
- if (label === "heartbeat" && last?.label === "heartbeat") {
6218
- last.count++;
6219
- last.at = this.now();
6220
- return;
6221
- }
6222
- frameTrace.push({
6223
- label,
6224
- at: this.now(),
6225
- count: 1
6226
- });
6227
- if (frameTrace.length > 120) frameTrace.shift();
6228
- };
6229
- const stallReport = (message) => {
6230
- const lines = [
6231
- `=== ${(/* @__PURE__ */ new Date()).toISOString()} ${message} ===`,
6232
- `session=${sessionKey ?? "-"} model=${options.model ?? "-"} textChars=${textOutput.length} toolCallPending=${toolCallPending} toolRounds=${toolRoundCount} checkpoint=${persisted.checkpoint?.length ?? 0}B blobs=${blobStore?.size ?? 0}`,
6233
- `lastFrame=${Math.round((this.now() - lastActivity) / 1e3)}s ago lastContent=${Math.round((this.now() - lastProgress) / 1e3)}s ago`,
6234
- ...frameTrace.map((entry) => ` ${new Date(entry.at).toISOString()} ${entry.label}${entry.count > 1 ? ` x${entry.count}` : ""}`),
6235
- ""
6236
- ];
6237
- try {
6238
- appendFileSync(this.hangTracePath, lines.join("\n"));
6239
- } catch {}
6240
- this.logger?.warn?.(`cursor-subscription: ${message}; trace in ${this.hangTracePath}`);
6241
- };
6242
- const handleStall = (message) => {
6243
- stallReport(message);
6244
- if ((textOutput.length > 0 || toolCallPending) && typeof run.frames?.pause === "function") {
6245
- stopReading();
6246
- return;
6247
- }
6248
- run.abort(new Error(message));
6249
- };
6250
- const idleCheck = setInterval(() => {
6251
- const now = this.now();
6252
- if (now - lastActivity > this.idleTimeoutMs) handleStall("Cursor stream idle timeout");
6253
- else if (now - lastProgress > this.progressTimeoutMs) handleStall(`Cursor stream progress timeout: no content for ${this.progressTimeoutMs}ms`);
6254
- }, this.idleCheckIntervalMs);
6255
- idleCheck.unref?.();
6256
- try {
6257
- for (;;) {
6258
- const frame = await run.frames.next();
6259
- if (frame === void 0) break;
6260
- lastActivity = this.now();
6261
- traceFrame(describeServerFrame(frame));
6262
- if ((frame.flags & CONNECT_END_STREAM_FLAG) !== 0) {
6263
- const end = parseEndStream(frame.payload);
6264
- if (end !== void 0) {
6265
- terminalErrorEmitted = true;
6266
- yield {
6267
- type: "finish",
6268
- reason: {
6269
- kind: "error",
6270
- failure: {
6271
- message: end.message,
6272
- code: classifyCursorError(`${end.code} ${end.debugCode ?? ""} ${end.message}`)
6273
- }
6274
- }
6275
- };
6276
- streamClosed = true;
6277
- break;
6278
- }
6279
- break;
6280
- }
6281
- const message = decodeAgentServerMessage(frame.payload);
6282
- if (!(message.case === "interactionUpdate" && message.value?.type === "heartbeat")) lastProgress = this.now();
6283
- if (message.case === "conversationCheckpointUpdate") {
6284
- persisted.checkpoint = Uint8Array.from(message.value);
6285
- if (toolCallPending) {
6286
- streamClosed = true;
6287
- break;
6288
- }
6289
- } else if (message.case === "interactionUpdate") {
6290
- const update = message.value;
6291
- if (update.type === "textDelta") {
6292
- if (update.text.length > 0) {
6293
- textOutput += update.text;
6294
- yield {
6295
- type: "text-delta",
6296
- index: TEXT_BLOCK_INDEX,
6297
- text: update.text
6298
- };
6299
- }
6300
- } else if (update.type === "thinkingDelta") {
6301
- if (update.text.length > 0) yield {
6302
- type: "reasoning-delta",
6303
- index: REASONING_BLOCK_INDEX,
6304
- text: update.text
6305
- };
6306
- } else if (update.type === "thinkingCompleted") {
6307
- if (update.durationMs > 0) yield cursorProgress(`thinking finished after ${(update.durationMs / 1e3).toFixed(1)}s`);
6308
- } else if (update.type === "tokenDelta") {
6309
- if (Number.isFinite(update.tokens) && update.tokens > 0) outputTokens += update.tokens;
6310
- } else if (update.type === "partialToolCall") {
6311
- if (toolCallBuffer === void 0 || toolCallBuffer.id !== update.callId) toolCallBuffer = {
6312
- id: update.callId,
6313
- name: "",
6314
- arguments: ""
6315
- };
6316
- if (update.argsTextDelta.length > 0) toolCallBuffer.arguments += update.argsTextDelta;
6317
- } else if (update.type === "toolCallStarted" || update.type === "toolCallCompleted" || update.type === "toolCallDelta") emittedToolCall = true;
6318
- else if (update.type === "turnEnded") {
6319
- if (!toolCallPending) break;
6320
- }
6321
- } else if (message.case === "kvServerMessage") {
6322
- const kv = message.value;
6323
- if (kv.case === "getBlobArgs") {
6324
- const key = Buffer.from(kv.blobId).toString("hex");
6325
- const blob = blobStore.get(key);
6326
- run.writeMessage(encodeKvClientMessage(encodeGetBlobResult(kv.id, blob)));
6327
- } else if (kv.case === "setBlobArgs") {
6328
- if (kv.blobId !== void 0 && kv.blobData !== void 0) {
6329
- const key = Buffer.from(kv.blobId).toString("hex");
6330
- const data = Uint8Array.from(kv.blobData);
6331
- blobStore.set(key, data);
6332
- persisted.blobs.set(key, data);
6333
- }
6334
- run.writeMessage(encodeKvClientMessage(encodeSetBlobResult(kv.id)));
6335
- }
6336
- } else if (message.case === "execServerMessage") {
6337
- const exec = message.value;
6338
- if (exec.case === "requestContextArgs") {
6339
- run.writeMessage(encodeExecClientMessageEnvelope(encodeExecClientMessage(exec.id, exec.execId, 10, encodeRequestContextResult(mcpTools))));
6340
- yield cursorProgress(`loaded the DSH tool schemas (${mcpTools.length} tools)`);
6341
- } else if (exec.case === "mcpArgs") {
6342
- if (!toolRoundCounted) {
6343
- toolRoundCount++;
6344
- toolRoundCounted = true;
6345
- }
6346
- 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");
6347
- const mcp = exec.args ?? {};
6348
- const args = {};
6349
- for (const [key, value] of Object.entries(mcp.args ?? {})) try {
6350
- args[key] = decodeValue(value);
6351
- } catch {
6352
- args[key] = null;
6353
- }
6354
- const candidateName = mcp.toolName || mcp.name;
6355
- const tool = resolveTextTool(candidateName, options.tools ?? []);
6356
- if (tool === void 0) {
6357
- run.writeMessage(encodeExecClientMessageEnvelope(encodeExecClientMessage(exec.id, exec.execId, 11, encodeMcpError(`Unknown or unsupported MCP tool: ${candidateName || "(empty)"}`))));
6358
- continue;
6359
- }
6360
- emittedToolCall = true;
6361
- const id = mcp.toolCallId || exec.execId || crypto.randomUUID();
6362
- const name$1 = tool.name;
6363
- const normalizedArgs = normalizeTextToolArguments(tool, args);
6364
- const argumentsJson = JSON.stringify(normalizedArgs);
6365
- const blockIndex = TOOL_BLOCK_INDEX + pendingExecs.length;
6366
- yield {
6367
- type: "block-start",
6368
- index: blockIndex,
6369
- blockType: "tool-call"
6370
- };
6371
- yield {
6372
- type: "tool-call-delta",
6373
- index: blockIndex,
6374
- id: ToolCallId(id),
6375
- name: name$1,
6376
- argumentsDelta: argumentsJson
6377
- };
6378
- yield {
6379
- type: "block-end",
6380
- index: blockIndex,
6381
- block: {
6382
- type: "tool-call",
6383
- id: ToolCallId(id),
6384
- name: name$1,
6385
- arguments: argumentsJson
6386
- }
6387
- };
6388
- pendingExecs.push({
6389
- id: exec.id,
6390
- execId: exec.execId,
6391
- toolCallId: id
6392
- });
6393
- toolCallPending = true;
6394
- yield cursorProgress(`calling the DSH tool ${name$1}`);
6395
- streamClosed = true;
6396
- armToolCallSettle();
6397
- } else {
6398
- const reply = rejectionFor(exec);
6399
- if (reply !== void 0) {
6400
- if (exec.case === "unknown" && !unknownExecWarned) {
6401
- unknownExecWarned = true;
6402
- 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`);
6403
- }
6404
- run.writeMessage(encodeExecClientMessageEnvelope(encodeExecClientMessage(exec.id, exec.execId, reply.field, reply.payload)));
6405
- yield cursorProgress(`native ${nativeToolName(exec)} call declined; using the DSH tools instead`);
6406
- } else if (exec.case === "unknown") this.logger?.warn?.("cursor-subscription: Cursor requested an exec with no addressable field; the run may stall");
6407
- }
6408
- }
6409
- }
6410
- } finally {
6411
- clearInterval(idleCheck);
6412
- clearTimeout(toolCallSettle);
6413
- }
6414
- if (!terminalErrorEmitted && !toolCallPending) {
6415
- const textToolCalls = parseTextToolCalls(textOutput, options.tools ?? []);
6416
- for (let index = 0; index < textToolCalls.length; index++) {
6417
- const call = textToolCalls[index];
6418
- const blockIndex = TOOL_BLOCK_INDEX + index;
6419
- yield {
6420
- type: "block-start",
6421
- index: blockIndex,
6422
- blockType: "tool-call"
6423
- };
6424
- yield {
6425
- type: "tool-call-delta",
6426
- index: blockIndex,
6427
- id: ToolCallId(call.id),
6428
- name: call.name,
6429
- argumentsDelta: call.arguments
6430
- };
6431
- yield {
6432
- type: "block-end",
6433
- index: blockIndex,
6434
- block: {
6435
- type: "tool-call",
6436
- id: ToolCallId(call.id),
6437
- name: call.name,
6438
- arguments: call.arguments
6439
- }
6440
- };
6441
- }
6442
- if (textToolCalls.length > 0) {
6443
- emittedToolCall = true;
6444
- streamClosed = true;
6445
- }
6446
- }
6447
- const liveToolBridge = toolCallPending && pendingExecs.length > 0 && !run.finished;
6448
- if (sessionKey !== void 0 && (persisted.checkpoint !== void 0 || liveToolBridge)) {
6449
- persisted.lastAccessMs = this.now();
6450
- if (liveToolBridge) {
6451
- persisted.bridge = {
6452
- run,
6453
- pendingExecs,
6454
- toolRoundCount
6455
- };
6456
- bridgeStored = true;
6457
- }
6458
- this.#sessions.delete(sessionKey);
6459
- this.#sessions.set(sessionKey, persisted);
6460
- while (this.#sessions.size > 100) {
6461
- const oldest = this.#sessions.keys().next().value;
6462
- if (oldest === void 0) break;
6463
- this.#sessions.get(oldest)?.bridge?.run.close();
6464
- this.#sessions.delete(oldest);
6465
- }
6466
- }
6467
- if (!terminalErrorEmitted) {
6468
- yield {
6469
- type: "usage",
6470
- usage: {
6471
- inputTokens: decodeCheckpointUsedTokens(persisted.checkpoint) ?? 0,
6472
- outputTokens
6473
- }
6474
- };
6475
- if (streamClosed && emittedToolCall) yield {
6476
- type: "finish",
6477
- reason: { kind: "tool-calls" }
6478
- };
6479
- else if (run.trailers && Number(run.trailers["grpc-status"]) > 0) {
6480
- const message = run.trailers["grpc-message"] ?? "Cursor agent run failed";
6481
- yield {
6482
- type: "finish",
6483
- reason: {
6484
- kind: "error",
6485
- failure: {
6486
- message,
6487
- code: classifyCursorError(message)
6488
- }
6489
- }
6490
- };
6491
- } else yield {
6492
- type: "finish",
6493
- reason: { kind: "stop" }
6494
- };
6495
- }
6496
- } catch (error) {
6497
- if (upstream.aborted) yield {
6498
- type: "finish",
6499
- reason: {
6500
- kind: "aborted",
6501
- failure: {
6502
- message: "Cursor request aborted by caller",
6503
- code: "ABORTED"
6504
- }
6505
- }
6506
- };
6507
- else if (error instanceof LlmError) yield {
6508
- type: "finish",
6509
- reason: {
6510
- kind: "error",
6511
- failure: {
6512
- message: error.message,
6513
- code: error.code
6514
- }
6515
- }
6516
- };
6517
- else {
6518
- const message = error instanceof Error ? error.message : String(error);
6519
- yield {
6520
- type: "finish",
6521
- reason: {
6522
- kind: "error",
6523
- failure: {
6524
- message,
6525
- code: classifyCursorError(message)
6526
- }
6527
- }
6528
- };
6529
- }
6530
- } finally {
6531
- if (abortRun !== void 0) upstream.removeEventListener("abort", abortRun);
6532
- consumer.abort("cursor stream consumer stopped");
6533
- if (!bridgeStored) run?.close();
3826
+ return ok({ providers: Object.fromEntries(entries.map(([provider, status]) => {
3827
+ const visibleModels = providerSettings?.visibleModels?.(provider);
3828
+ return [provider, {
3829
+ ...status,
3830
+ ...visibleModels === void 0 ? {} : { visibleModels }
3831
+ }];
3832
+ })) });
6534
3833
  }
6535
- }
6536
- };
6537
- /**
6538
- * Parse a Connect end-stream frame into a structured error. Cursor's agent
6539
- * errors carry a `debug` payload with a stable code and human-readable
6540
- * `title`/`detail`; extracting those gives the user the real reason instead of
6541
- * a bare `resource_exhausted`.
6542
- * @returns {undefined | {code: string, debugCode?: string, message: string}}
6543
- */
6544
- function parseEndStream(payload) {
6545
- try {
6546
- const error = JSON.parse(new TextDecoder().decode(payload))?.error;
6547
- if (!error) return void 0;
6548
- const code = typeof error.code === "string" ? error.code : "unknown";
6549
- let debugCode;
6550
- let title;
6551
- let detail;
6552
- if (Array.isArray(error.details)) for (const entry of error.details) {
6553
- if (!record$1(entry) || !record$1(entry.debug)) continue;
6554
- if (typeof entry.debug.error === "string") debugCode = entry.debug.error;
6555
- const details = record$1(entry.debug.details) ? entry.debug.details : void 0;
6556
- if (details !== void 0) {
6557
- if (typeof details.title === "string") title = details.title;
6558
- if (typeof details.detail === "string") detail = details.detail;
3834
+ case "login": {
3835
+ const provider = readProvider(payload);
3836
+ return ok(await controller.login(provider, readLoginMethod(payload, provider)));
3837
+ }
3838
+ case "manual": {
3839
+ const provider = readProvider(payload);
3840
+ await controller.manual(provider, readString(payload, "input"));
3841
+ return ok({ ok: true });
3842
+ }
3843
+ case "cancel":
3844
+ await controller.cancel(readProvider(payload));
3845
+ return ok({ ok: true });
3846
+ case "logout": {
3847
+ const provider = readProvider(payload);
3848
+ await controller.logout(provider, readString(payload, "account"));
3849
+ return ok({ ok: true });
3850
+ }
3851
+ case "setDefault": {
3852
+ const provider = readProvider(payload);
3853
+ await controller.setDefault(provider, readString(payload, "account"));
3854
+ return ok({ ok: true });
3855
+ }
3856
+ case "usage": {
3857
+ const provider = readProvider(payload);
3858
+ return ok(await controller.usage(provider, readString(payload, "account"), signal, readForce(payload)));
3859
+ }
3860
+ case "externalStatus":
3861
+ if (!externalUsage) throw new BadRequest("external usage is unavailable");
3862
+ return ok(await externalUsage.status());
3863
+ case "externalUsage": {
3864
+ if (!externalUsage) throw new BadRequest("external usage is unavailable");
3865
+ const source = readString(payload, "source");
3866
+ if (!EXTERNAL_USAGE_SOURCES.includes(source)) throw new BadRequest(`payload.source must be one of ${EXTERNAL_USAGE_SOURCES.join(", ")}`);
3867
+ return ok(await externalUsage.usage(source, signal));
3868
+ }
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));
6559
3886
  }
3887
+ return ok(await ollama.models({
3888
+ ...apiKey === void 0 ? {} : { apiKey },
3889
+ ...baseURL === void 0 ? {} : { baseURL }
3890
+ }, signal));
6560
3891
  }
6561
- const fallback = typeof error.message === "string" && error.message.length > 0 ? error.message : void 0;
6562
- 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}`);
6563
- return {
6564
- code,
6565
- debugCode,
6566
- message
6567
- };
6568
- } catch {
6569
- return;
6570
- }
6571
- }
6572
- function classifyCursorError(message) {
6573
- if (/\b(?:401|403)\b|unauth|invalid.*(?:key|token|credential)/i.test(message)) return "AUTH";
6574
- if (/rate.?limit|\b429\b|quota|resource_exhausted|spend.?limit|usage limit|exceeded/i.test(message)) return "RATE_LIMIT";
6575
- if (/\b400\b|invalid.?request/i.test(message)) return "INVALID_REQUEST";
6576
- if (/\b408\b|timeout|timed out/i.test(message)) return "TIMEOUT";
6577
- if (/\b5\d\d\b|internal/i.test(message)) return "SERVER";
6578
- if (/\b(?:network|connection|socket|fetch|ECONN|http2)/i.test(message)) return "TRANSPORT";
6579
- return "CURSOR_ERROR";
6580
- }
6581
- /**
6582
- * Build the exec-client reply for a native Cursor tool we reject.
6583
- *
6584
- * `ExecClientMessage` numbers its result variants exactly like
6585
- * `ExecServerMessage` numbers its exec variants, which is what lets an exec
6586
- * this build cannot name still be answered: reply on the exec's own field.
6587
- * Every unanswered exec leaves the server waiting for its result, and that
6588
- * wait is what the user sees as a turn that ends right after the model says
6589
- * what it is about to do — the progress watchdog aborts the step and only the
6590
- * preamble text survives.
6591
- */
6592
- function rejectionFor(exec) {
6593
- switch (exec.case) {
6594
- case "readArgs": return {
6595
- field: 7,
6596
- payload: encodeReadRejected(exec.path, TOOL_REJECT_REASON)
6597
- };
6598
- case "lsArgs": return {
6599
- field: 8,
6600
- payload: encodeLsRejected(exec.path, TOOL_REJECT_REASON)
6601
- };
6602
- case "grepArgs": return {
6603
- field: 5,
6604
- payload: encodeGrepError(TOOL_REJECT_REASON)
6605
- };
6606
- case "writeArgs": return {
6607
- field: 3,
6608
- payload: encodeWriteRejected(exec.path, TOOL_REJECT_REASON)
6609
- };
6610
- case "deleteArgs": return {
6611
- field: 4,
6612
- payload: encodeDeleteRejected(exec.path, TOOL_REJECT_REASON)
6613
- };
6614
- case "shellArgs": return {
6615
- field: 2,
6616
- payload: encodeShellRejectedResult(void 0, void 0, TOOL_REJECT_REASON)
6617
- };
6618
- case "shellStreamArgs": return {
6619
- field: 14,
6620
- payload: encodeShellStreamRejected(void 0, void 0, TOOL_REJECT_REASON)
6621
- };
6622
- case "backgroundShellSpawnArgs": return {
6623
- field: 16,
6624
- payload: encodeBackgroundShellRejectedResult(void 0, void 0, TOOL_REJECT_REASON)
6625
- };
6626
- case "fetchArgs": return {
6627
- field: 20,
6628
- payload: encodeFetchError(exec.url, TOOL_REJECT_REASON)
6629
- };
6630
- case "writeShellStdinArgs": return {
6631
- field: 23,
6632
- payload: encodeWriteShellStdinError(TOOL_REJECT_REASON)
6633
- };
6634
- case "diagnosticsArgs": return {
6635
- field: 9,
6636
- payload: encodeDiagnosticsResult()
6637
- };
6638
- case "recordScreenArgs": return {
6639
- field: 21,
6640
- payload: encodeMcpError("Screen recording is not available")
6641
- };
6642
- case "computerUseArgs": return {
6643
- field: 22,
6644
- payload: encodeMcpError("Computer use is not available")
6645
- };
6646
- case "listMcpResourcesExecArgs": return {
6647
- field: 17,
6648
- payload: encodeMcpError("MCP resources are not available")
6649
- };
6650
- case "readMcpResourceExecArgs": return {
6651
- field: 18,
6652
- payload: encodeMcpError("MCP resources are not available")
6653
- };
6654
- case "unknown":
6655
- if (typeof exec.field !== "number") return void 0;
6656
- return {
6657
- field: exec.field,
6658
- payload: encodeMcpError(TOOL_REJECT_REASON)
6659
- };
6660
- default: return;
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);
3913
+ }
3914
+ return ok({ ok: true });
3915
+ default: throw new BadRequest(`unknown /subscriptions-auth endpoint "${endpoint}"`);
6661
3916
  }
6662
3917
  }
6663
-
6664
- //#endregion
6665
- //#region src/providers/cursor-adapter.ts
6666
- /**
6667
- * The imported Cursor transport still reads tool results as user-role content
6668
- * blocks. DSH 0.1.7 carries them as first-class role=tool messages instead.
6669
- */
6670
- function projectCursorMessages(messages) {
6671
- return messages.map((message) => message.role === "tool" ? {
6672
- ...message,
6673
- role: "user",
6674
- content: [{
6675
- type: "tool-result",
6676
- toolCallId: message.toolCallId,
6677
- isError: message.isError,
6678
- content: message.content
6679
- }]
6680
- } : message);
6681
- }
6682
3918
  /**
6683
- * Vendor `LlmError` codes whose whole message is a local literal (no response
6684
- * header, trailer, body or end-stream text). Provider-derived failures can only
6685
- * carry a `classifyCursorError` code or `CURSOR_PROTOCOL`, never these, so the
6686
- * 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.
6687
3927
  */
6688
- const LOCAL_VENDOR_CODES = new Set([
6689
- "UNSUPPORTED_OPTION",
6690
- "UNSUPPORTED_CONTENT",
6691
- "TOOL_LIMIT"
6692
- ]);
6693
- /**
6694
- * Plain-error messages the vendor throws as fixed literals (`vendor/cursor/
6695
- * index.js`: `waitForResponse`, the request write, the tool-continuation write,
6696
- * the stream `close` handler, the frame reader, the stall watchdog and the
6697
- * settings validation). None
6698
- * interpolates response text, so they are matched by exact message, the same way
6699
- * the auth messages are. A provider that echoed one of these sentences would
6700
- * only make the user read a local literal.
6701
- */
6702
- const LOCAL_VENDOR_MESSAGES = new Set([
6703
- "Cursor HTTP response timeout",
6704
- "Cursor HTTP stream closed before response",
6705
- "Cursor agent bridge closed before accepting the request",
6706
- "Cursor tool continuation bridge closed before accepting the result",
6707
- "Cursor sent an unreadable compressed frame",
6708
- "Cursor stream idle timeout",
6709
- "cursor-subscription: maxToolRounds must be an integer between 1 and 1000",
6710
- "cursor-subscription: retryCount must be an integer between 0 and 10",
6711
- "cursor-subscription: retryIntervalMs must be an integer between 0 and 300000",
6712
- "cursor-subscription: retryHttpStatusCodes must contain only HTTP status integers from 400 to 599",
6713
- "cursor-subscription: retryHttpStatusCodes must not contain duplicates",
6714
- "cursor-subscription: replayHistoryEachStep must be a boolean"
6715
- ]);
6716
- /**
6717
- * The same, for the two literals that embed a number the vendor computes: the
6718
- * response status (`Number(headers[':status'])`, so digits or `NaN`) and the
6719
- * configured progress timeout.
6720
- */
6721
- const LOCAL_VENDOR_PATTERNS = [/^Cursor agent returned HTTP \d{3}$/, /^Cursor stream progress timeout: no content for \d+ms$/];
6722
- /**
6723
- * Operating-system network error codes. An `Error` carrying one of these on
6724
- * itself, or on its `cause` (Node wraps a refused connection in
6725
- * `ERR_HTTP2_STREAM_CANCEL`), was produced by the local socket layer, so its
6726
- * message is local: `connect ECONNREFUSED 127.0.0.1:443`, `getaddrinfo ENOTFOUND
6727
- * host`. This is a property of the thrown object, which a provider cannot
6728
- * forge. It is deliberately not a test on the vendor's `TRANSPORT` code: the
6729
- * vendor derives that code from message text, so a provider's end-stream
6730
- * message or `grpc-message` containing "connection" or "socket" gets it too.
6731
- * Transport failures with no such code (TLS and certificate errors, HTTP/2
6732
- * protocol errors) are not recognised and stay replaced.
6733
- */
6734
- const LOCAL_SYSTEM_ERRNO = new Set([
6735
- "ECONNREFUSED",
6736
- "ECONNRESET",
6737
- "ECONNABORTED",
6738
- "ENOTFOUND",
6739
- "EAI_AGAIN",
6740
- "ETIMEDOUT",
6741
- "ENETUNREACH",
6742
- "ENETDOWN",
6743
- "EHOSTUNREACH",
6744
- "EHOSTDOWN",
6745
- "EPIPE"
6746
- ]);
6747
- function errorCode(value) {
6748
- return typeof value === "object" && value !== null ? value.code : void 0;
6749
- }
6750
- function isLocalSystemError(error) {
6751
- if (!(error instanceof Error)) return false;
6752
- const own = errorCode(error);
6753
- const cause = errorCode(error.cause);
6754
- return typeof own === "string" && LOCAL_SYSTEM_ERRNO.has(own) || typeof cause === "string" && LOCAL_SYSTEM_ERRNO.has(cause);
6755
- }
6756
- function messageOf(error) {
6757
- return error instanceof Error ? error.message : String(error);
6758
- }
6759
- /** Bound on remembered local failure messages; they are short and few. */
6760
- const MAX_LOCAL_MESSAGES = 64;
6761
- /**
6762
- * Remembers the messages of failures raised by this machine rather than read
6763
- * from the provider: whatever the auth service threw, whatever the host
6764
- * attachment store threw while reading an image, and a socket-layer network
6765
- * error seen on the run. The vendor stream turns each of these into
6766
- * `finish/error` carrying exactly `error.message`, so a failure whose message
6767
- * equals a remembered one originated locally and may keep its text. The match
6768
- * is by exact text: a provider that echoed a remembered message would only
6769
- * display a local literal.
6770
- */
6771
- var LocalFailureMessages = class {
6772
- messages = /* @__PURE__ */ new Set();
6773
- /** Wrap `auth` so its failures are remembered and rethrown unchanged. */
6774
- wrap(auth) {
6775
- 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) => {
6776
3932
  try {
6777
- return await auth.accessToken(options);
3933
+ return await dispatch(controller, speed, modelDefaults, endpoint, payload, signal, providerSettings, externalUsage, ollama);
6778
3934
  } catch (error) {
6779
- this.remember(messageOf(error));
6780
- throw error;
3935
+ return failure(error);
6781
3936
  }
6782
- } };
6783
- }
6784
- /**
6785
- * Wrap the host attachment store the vendor reads images through, so a read
6786
- * failure keeps its own message. The store is a local service; its errors
6787
- * carry no provider text. Anything that is not a store passes through.
6788
- */
6789
- wrapAttachments(resolve$2) {
6790
- return () => {
6791
- const store = resolve$2();
6792
- const readImage = store?.readImage;
6793
- if (typeof readImage !== "function") return store;
6794
- return Object.create(store, { readImage: { value: async (...args) => {
6795
- try {
6796
- return await readImage.apply(store, args);
6797
- } catch (error) {
6798
- this.remember(messageOf(error));
6799
- throw error;
6800
- }
6801
- } } });
6802
- };
6803
- }
6804
- /**
6805
- * Observe a vendor run's two failure channels, the response promise and the
6806
- * frame reader, and remember the message of an error the socket layer raised.
6807
- * The run's behavior is unchanged: the same error is rethrown.
6808
- */
6809
- observeRun(run) {
6810
- if (typeof run !== "object" || run === null) return run;
6811
- const target = run;
6812
- const observe = (error) => {
6813
- if (isLocalSystemError(error)) this.remember(messageOf(error));
6814
- throw error;
6815
3937
  };
6816
- const wait = target.waitForResponse;
6817
- if (typeof wait === "function") target.waitForResponse = (...args) => Promise.resolve(wait.apply(target, args)).catch(observe);
6818
- const frames = target.frames;
6819
- const next = frames?.next;
6820
- if (frames !== void 0 && typeof next === "function") frames.next = (...args) => Promise.resolve(next.apply(frames, args)).catch(observe);
6821
- return run;
6822
- }
6823
- has(message) {
6824
- return this.messages.has(message);
6825
- }
6826
- remember(message) {
6827
- if (message === "") return;
6828
- this.messages.delete(message);
6829
- this.messages.add(message);
6830
- if (this.messages.size > MAX_LOCAL_MESSAGES) {
6831
- const oldest = this.messages.values().next().value;
6832
- if (oldest !== void 0) this.messages.delete(oldest);
6833
- }
6834
- }
6835
- };
6836
- function isVendorLocalMessage(message) {
6837
- 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
+ });
6838
3941
  }
6839
- /**
6840
- * Cursor adapter for the DSH host.
6841
- *
6842
- * Failure text contract: `finish/error` messages that originate from the
6843
- * vendor stream and can quote provider-controlled text (the response content
6844
- * type, trailers, end-stream errors, a frame-reader or other error whose message
6845
- * is not one of the literals below, and a transport-coded failure that is not a
6846
- * socket-layer error) are replaced by `<code> [provider response text omitted]`. Failures that are
6847
- * wholly local keep their own message and code:
6848
- *
6849
- * - anything the auth service threw (not signed in, sign-in expired, token
6850
- * refresh status), and a host attachment read failure;
6851
- * - the vendor's own request validation, by code (`UNSUPPORTED_OPTION`,
6852
- * `UNSUPPORTED_CONTENT`, `TOOL_LIMIT`);
6853
- * - the vendor's fixed-literal plain errors, by exact message: the HTTP status
6854
- * (`Cursor agent returned HTTP 429`), the response, idle and progress
6855
- * timeouts, a closed bridge or stream, an unreadable compressed frame, and
6856
- * an invalid Cursor setting;
6857
- * - a network error raised by the operating system (`ECONNREFUSED`,
6858
- * `ENOTFOUND`, ...), recognised from the thrown error's own code, not from
6859
- * the vendor's classification.
6860
- *
6861
- * Not a provider-origin class: a network error such as `ECONNREFUSED` is a
6862
- * local failure (no network, daemon down, DNS), and is kept. What is still
6863
- * replaced although it is probably local is a transport failure with no
6864
- * socket-layer errno: a TLS or certificate failure and an HTTP/2 protocol
6865
- * error. The vendor's `TRANSPORT` code cannot decide those, because
6866
- * `classifyCursorError` derives it from message text (`network`, `connection`,
6867
- * `socket`, `fetch`, `ECONN`, `http2`) and a provider's end-stream message or
6868
- * `grpc-message` can contain any of them; so the code is never trusted alone.
6869
- * Telling these apart would need a check on the thrown error object, as the
6870
- * errno check above does, extended to the TLS and HTTP/2 error codes. That is
6871
- * not done here and is not verified.
6872
- */
6873
- var CursorCompatAdapter = class extends CursorAdapter {
6874
- visibleModels;
6875
- localFailures;
6876
- constructor(options) {
6877
- const localFailures = new LocalFailureMessages();
6878
- super({
6879
- ...options,
6880
- auth: localFailures.wrap(options.auth),
6881
- ...options.resolveAttachments === void 0 ? {} : { resolveAttachments: localFailures.wrapAttachments(options.resolveAttachments) }
6882
- });
6883
- this.localFailures = localFailures;
6884
- this.visibleModels = options.visibleModels;
6885
- const vendor = this;
6886
- const createRun = vendor.createAgentRun;
6887
- vendor.createAgentRun = (access) => localFailures.observeRun(createRun(access));
6888
- }
6889
- async listModels(provider) {
6890
- const models = await super.listModels(provider);
6891
- const visible = this.visibleModels?.();
6892
- return visible === void 0 ? models : models.filter((model) => visible.includes(model.id));
6893
- }
6894
- async *stream(options) {
6895
- for await (const chunk of super.stream({
6896
- ...options,
6897
- model: options.model === "composer-2" ? "composer-2.5" : options.model,
6898
- messages: projectCursorMessages(options.messages)
6899
- })) 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 {
6900
- ...chunk,
6901
- reason: {
6902
- ...chunk.reason,
6903
- failure: {
6904
- ...chunk.reason.failure,
6905
- message: `${chunk.reason.failure.code} [provider response text omitted]`
6906
- }
6907
- }
6908
- };
6909
- else yield chunk;
6910
- }
6911
- };
6912
3942
 
6913
3943
  //#endregion
6914
3944
  //#region src/providers/ollama-web.ts
@@ -7026,13 +4056,13 @@ var OllamaWebSearchProvider = class {
7026
4056
  if (!Array.isArray(results)) throw new WebError("ollama web search returned no results array", "OLLAMA_WEB_BAD_REPLY");
7027
4057
  return {
7028
4058
  sources: results.flatMap((entry) => {
7029
- const record$4 = recordOf(entry);
7030
- const url = record$4?.["url"];
4059
+ const record$2 = recordOf(entry);
4060
+ const url = record$2?.["url"];
7031
4061
  if (typeof url !== "string" || url.length === 0) return [];
7032
4062
  return [{
7033
4063
  url,
7034
- ...typeof record$4?.["title"] === "string" ? { title: record$4?.["title"] } : {},
7035
- ...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"] } : {}
7036
4066
  }];
7037
4067
  }),
7038
4068
  truncated: false
@@ -7070,51 +4100,6 @@ var OllamaWebFetchProvider = class {
7070
4100
  }
7071
4101
  };
7072
4102
 
7073
- //#endregion
7074
- //#region src/providers/cursor-model-settings.ts
7075
- function validateCursorModelSettings(input) {
7076
- if (input === null || typeof input !== "object" || Array.isArray(input)) throw new Error("Cursor model settings must be an object");
7077
- const models = input.visibleModels;
7078
- if (models === void 0) return {};
7079
- if (!Array.isArray(models) || models.some((id) => typeof id !== "string" || !id.trim())) throw new Error("visibleModels must be an array of model ids");
7080
- return { visibleModels: [...new Set(models)] };
7081
- }
7082
- /** Durable visibility preference; account tokens remain in DSH credentials. */
7083
- var CursorModelSettingsStore = class {
7084
- current = {};
7085
- writes = Promise.resolve();
7086
- constructor(path = dshHomePath("plugins", "subscriptions", "cursor-model-settings.json")) {
7087
- this.path = path;
7088
- try {
7089
- this.current = validateCursorModelSettings(JSON.parse(readFileSync(path, "utf8")));
7090
- } catch (error) {
7091
- if (error.code !== "ENOENT") throw new Error(`Cannot read Cursor model settings at ${path}`, { cause: error });
7092
- }
7093
- }
7094
- get() {
7095
- return structuredClone(this.current);
7096
- }
7097
- visibleModels() {
7098
- return this.current.visibleModels;
7099
- }
7100
- set(input) {
7101
- const validated = validateCursorModelSettings(input);
7102
- const run = this.writes.then(async () => {
7103
- await mkdir(dirname(this.path), { recursive: true });
7104
- const temporary = `${this.path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
7105
- try {
7106
- await writeFile(temporary, JSON.stringify(validated, null, 2), { mode: 384 });
7107
- await rename(temporary, this.path);
7108
- } finally {
7109
- await rm(temporary, { force: true });
7110
- }
7111
- this.current = validated;
7112
- });
7113
- this.writes = run.catch(() => void 0);
7114
- return run;
7115
- }
7116
- };
7117
-
7118
4103
  //#endregion
7119
4104
  //#region src/model-defaults.ts
7120
4105
  /** Absolute path of the defaults file. */
@@ -7161,11 +4146,11 @@ function sanitizeDefaults(value) {
7161
4146
  defaults: EMPTY,
7162
4147
  dropped: []
7163
4148
  };
7164
- const record$4 = value;
4149
+ const record$2 = value;
7165
4150
  const result = {};
7166
4151
  const dropped = [];
7167
4152
  for (const provider of PROVIDER_IDS) {
7168
- const section = sanitizeProvider(record$4[provider], dropped);
4153
+ const section = sanitizeProvider(record$2[provider], dropped);
7169
4154
  if (section !== void 0) result[provider] = section;
7170
4155
  }
7171
4156
  return {
@@ -7524,7 +4509,7 @@ function toAnthropicMessages(messages) {
7524
4509
  tool_use_id: id,
7525
4510
  content: toolResultContent({
7526
4511
  type: "tool-result",
7527
- toolCallId: ToolCallId$1(id),
4512
+ toolCallId: ToolCallId(id),
7528
4513
  content: message.content
7529
4514
  }),
7530
4515
  ...message.isError === true ? { is_error: true } : {}
@@ -7663,7 +4648,7 @@ function closeBlock$1(block) {
7663
4648
  };
7664
4649
  case "tool-call": return {
7665
4650
  type: "tool-call",
7666
- id: ToolCallId$1(block.callId),
4651
+ id: ToolCallId(block.callId),
7667
4652
  name: block.name ?? "",
7668
4653
  arguments: block.text
7669
4654
  };
@@ -7767,7 +4752,7 @@ var AnthropicStreamTranslator = class {
7767
4752
  chunks.push({
7768
4753
  type: "tool-call-delta",
7769
4754
  index: opened.index,
7770
- id: ToolCallId$1(opened.callId),
4755
+ id: ToolCallId(opened.callId),
7771
4756
  ...block.name === void 0 ? {} : { name: block.name },
7772
4757
  argumentsDelta: ""
7773
4758
  });
@@ -7804,7 +4789,7 @@ var AnthropicStreamTranslator = class {
7804
4789
  chunks.push({
7805
4790
  type: "tool-call-delta",
7806
4791
  index: block.index,
7807
- id: ToolCallId$1(block.callId),
4792
+ id: ToolCallId(block.callId),
7808
4793
  ...block.name === void 0 ? {} : { name: block.name },
7809
4794
  argumentsDelta: delta.partial_json ?? ""
7810
4795
  });
@@ -9153,7 +6138,7 @@ function closeBlock(block) {
9153
6138
  };
9154
6139
  case "tool-call": return {
9155
6140
  type: "tool-call",
9156
- id: ToolCallId$1(block.callId),
6141
+ id: ToolCallId(block.callId),
9157
6142
  name: block.name ?? "",
9158
6143
  arguments: block.text
9159
6144
  };
@@ -9243,7 +6228,7 @@ var ResponsesStreamTranslator = class {
9243
6228
  chunks.push({
9244
6229
  type: "tool-call-delta",
9245
6230
  index: block.index,
9246
- id: ToolCallId$1(callId),
6231
+ id: ToolCallId(callId),
9247
6232
  ...item.name === void 0 ? {} : { name: item.name },
9248
6233
  argumentsDelta: ""
9249
6234
  });
@@ -9285,7 +6270,7 @@ var ResponsesStreamTranslator = class {
9285
6270
  chunks.push({
9286
6271
  type: "tool-call-delta",
9287
6272
  index: block.index,
9288
- id: ToolCallId$1(block.callId),
6273
+ id: ToolCallId(block.callId),
9289
6274
  ...block.name === void 0 ? {} : { name: block.name },
9290
6275
  argumentsDelta: event.delta ?? ""
9291
6276
  });
@@ -10495,9 +7480,9 @@ var PoolHealthRegistry = class {
10495
7480
  }
10496
7481
  /** Whether one registry key is clear right now. */
10497
7482
  isAvailable(key, now = Date.now()) {
10498
- const record$4 = this.records.get(key);
10499
- if (record$4 === void 0) return true;
10500
- 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) {
10501
7486
  this.records.delete(key);
10502
7487
  return true;
10503
7488
  }
@@ -10523,20 +7508,20 @@ var PoolHealthRegistry = class {
10523
7508
  */
10524
7509
  earliestRecovery(keys, now = Date.now()) {
10525
7510
  let earliest;
10526
- for (const [key, record$4] of this.records) {
10527
- if (record$4.unavailableUntil <= now) {
7511
+ for (const [key, record$2] of this.records) {
7512
+ if (record$2.unavailableUntil <= now) {
10528
7513
  this.records.delete(key);
10529
7514
  continue;
10530
7515
  }
10531
7516
  if (!keys.has(key)) continue;
10532
- 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;
10533
7518
  }
10534
7519
  return earliest;
10535
7520
  }
10536
7521
  /** Active failure codes for the given keys; reading them does not change cooldowns. */
10537
7522
  unavailableReasons(keys, now = Date.now()) {
10538
7523
  const reasons = [];
10539
- 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);
10540
7525
  return reasons;
10541
7526
  }
10542
7527
  /** Drop records of one provider, or of a single account when given (auth changes). */
@@ -13038,9 +10023,17 @@ function toAntigravityTools(tools, model = "gemini") {
13038
10023
  })) }];
13039
10024
  }
13040
10025
  const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator";
13041
- /** Gemini 3 validates the first function call in every model tool-call step. */
10026
+ /**
10027
+ * Whether this model validates thought signatures on replayed function calls.
10028
+ *
10029
+ * Gemini 3 and 4 require a signature on the first function call of each step;
10030
+ * foreign history uses the documented placeholder when no signature can be reused.
10031
+ * https://ai.google.dev/gemini-api/docs/generate-content/thought-signatures
10032
+ * Keep this scoped to these model families; future versions and unversioned aliases
10033
+ * need verification before enabling the same fallback.
10034
+ */
13042
10035
  function requiresThoughtSignatures(model) {
13043
- return model !== void 0 && /^gemini-3(?:[.-]|$)/i.test(model);
10036
+ return model !== void 0 && /^gemini-[34](?:[.-]|$)/i.test(model.trim());
13044
10037
  }
13045
10038
  /**
13046
10039
  * Convert resolved harness messages into Gemini contents. Function response
@@ -13060,7 +10053,7 @@ function toAntigravityContents(messages, model) {
13060
10053
  name: callNames.get(id) ?? "",
13061
10054
  response: toolResultValue({
13062
10055
  type: "tool-result",
13063
- toolCallId: ToolCallId$1(id),
10056
+ toolCallId: ToolCallId(id),
13064
10057
  content: message.content,
13065
10058
  ...message.isError === void 0 ? {} : { isError: message.isError }
13066
10059
  })
@@ -13228,7 +10221,7 @@ var AntigravityStreamTranslator = class {
13228
10221
  };
13229
10222
  else content = {
13230
10223
  type: "tool-call",
13231
- id: ToolCallId$1(block.id ?? `call_${randomUUID().replaceAll("-", "")}`),
10224
+ id: ToolCallId(block.id ?? `call_${randomUUID().replaceAll("-", "")}`),
13232
10225
  name: block.name ?? "",
13233
10226
  arguments: block.text
13234
10227
  };
@@ -13322,7 +10315,7 @@ var AntigravityStreamTranslator = class {
13322
10315
  chunks.push({
13323
10316
  type: "tool-call-delta",
13324
10317
  index: block.index,
13325
- id: ToolCallId$1(id),
10318
+ id: ToolCallId(id),
13326
10319
  name: call.name ?? "",
13327
10320
  argumentsDelta: args
13328
10321
  });
@@ -14101,11 +11094,11 @@ function parseImageGenerateResponse(payload) {
14101
11094
  const images = [];
14102
11095
  for (const entry of entries) {
14103
11096
  if (typeof entry !== "object" || entry === null) continue;
14104
- const record$4 = entry;
14105
- if (typeof record$4.b64_json !== "string" || record$4.b64_json.length === 0) continue;
11097
+ const record$2 = entry;
11098
+ if (typeof record$2.b64_json !== "string" || record$2.b64_json.length === 0) continue;
14106
11099
  images.push({
14107
- data: Buffer.from(record$4.b64_json, "base64"),
14108
- ...typeof record$4.revised_prompt === "string" && record$4.revised_prompt.length > 0 ? { revisedPrompt: record$4.revised_prompt } : {}
11100
+ data: Buffer.from(record$2.b64_json, "base64"),
11101
+ ...typeof record$2.revised_prompt === "string" && record$2.revised_prompt.length > 0 ? { revisedPrompt: record$2.revised_prompt } : {}
14109
11102
  });
14110
11103
  }
14111
11104
  if (images.length === 0) throw new Error("image_generate: the response carried no image data");
@@ -14749,6 +11742,15 @@ function validatePreferences(provider, input) {
14749
11742
  if (raw.promptCacheTtl !== "5m" && raw.promptCacheTtl !== "1h") throw new Error("promptCacheTtl must be \"5m\" or \"1h\"");
14750
11743
  result.promptCacheTtl = raw.promptCacheTtl;
14751
11744
  }
11745
+ if (raw.subagentSpeeds !== void 0) {
11746
+ if (provider !== "codex") throw new Error("subagent speeds are currently supported only for Codex");
11747
+ if (!raw.subagentSpeeds || typeof raw.subagentSpeeds !== "object" || Array.isArray(raw.subagentSpeeds)) throw new Error("subagent speeds must be a model-to-speed map");
11748
+ result.subagentSpeeds = Object.create(null);
11749
+ for (const [model, tier] of Object.entries(raw.subagentSpeeds)) {
11750
+ if (!model.trim() || tier !== "standard" && tier !== "fast") throw new Error("subagent speeds require a model id and either \"standard\" or \"fast\"");
11751
+ result.subagentSpeeds[model] = tier;
11752
+ }
11753
+ }
14752
11754
  if (raw.tools !== void 0) {
14753
11755
  if (!raw.tools || typeof raw.tools !== "object" || Array.isArray(raw.tools)) throw new Error("tools must be an object");
14754
11756
  result.tools = {};
@@ -14797,6 +11799,11 @@ var ProviderSettingsStore = class {
14797
11799
  const windows = this.current.providers[provider]?.contextWindows;
14798
11800
  return windows && Object.hasOwn(windows, model) ? windows[model] : void 0;
14799
11801
  }
11802
+ /** Read on every request so saved changes apply to the next subagent request. */
11803
+ subagentSpeed(model) {
11804
+ const speeds = this.current.providers.codex?.subagentSpeeds;
11805
+ return speeds && Object.hasOwn(speeds, model) ? speeds[model] : "standard";
11806
+ }
14800
11807
  /**
14801
11808
  * The Claude route's prompt-cache TTL override, read live so a saved change
14802
11809
  * takes effect from the next request without a restart. Absent means follow
@@ -15341,7 +12348,6 @@ function apply(ctx, config) {
15341
12348
  restoreConnectAttemptTimeout(previousAttemptTimeout);
15342
12349
  }, "dsh-subscriptions: connect attempt timeout");
15343
12350
  const preferences = new ProviderSettingsStore();
15344
- const cursorModelSettings = new CursorModelSettingsStore();
15345
12351
  const ollamaSettings = new OllamaSettingsStore(void 0, (message) => {
15346
12352
  ctx.logger.warn(`${name}: ${message}`);
15347
12353
  });
@@ -15401,6 +12407,13 @@ function apply(ctx, config) {
15401
12407
  let grokTokens;
15402
12408
  const usageFetchers = {};
15403
12409
  const speedBySession = /* @__PURE__ */ new Map();
12410
+ const selectedSpeed = (sessionId, model) => {
12411
+ const explicit = speedBySession.get(sessionId);
12412
+ if (explicit !== void 0) return explicit;
12413
+ const agent = ctx.get("agents")?.get(sessionId);
12414
+ if (model !== void 0 && agent?.session.header.origin === "subagent") return preferences.subagentSpeed(parseAccountModelId(model)?.model ?? model);
12415
+ return "standard";
12416
+ };
15404
12417
  let codexAdapter;
15405
12418
  let claudeAdapter;
15406
12419
  let copilotAdapter;
@@ -15466,7 +12479,7 @@ function apply(ctx, config) {
15466
12479
  defaultEffortOf: (model) => defaultEffortOf("codex", model),
15467
12480
  contextWindowOf: (model) => preferences.contextWindow(model),
15468
12481
  pool: () => poolAdapter,
15469
- speedFor: (sessionId, model) => sessionId !== void 0 && speedBySession.get(sessionId) === "fast" && adapter.supportsFastTier(model)
12482
+ speedFor: (sessionId, model) => sessionId !== void 0 && selectedSpeed(sessionId, model) === "fast" && adapter.supportsFastTier(model)
15470
12483
  });
15471
12484
  codexAdapter = adapter;
15472
12485
  adapters.set("codex", adapter);
@@ -15682,24 +12695,23 @@ function apply(ctx, config) {
15682
12695
  adapter.listModels = async (route) => (await list(route)).filter((model) => preferences.visible(provider, model.id));
15683
12696
  }
15684
12697
  const speed = {
15685
- async speed(sessionId) {
12698
+ async speed(sessionId, model) {
15686
12699
  const fastModels = await codexAdapter?.fastCapableModels() ?? [];
15687
12700
  const accountPreferences = preferences.get("codex").accounts;
15688
12701
  for (const { key } of await codexTokens?.list() ?? []) {
15689
12702
  if (!accountPreferences || !Object.hasOwn(accountPreferences, key) || accountPreferences[key].independentEntry !== true) continue;
15690
- for (const model of [...fastModels]) {
15691
- if (parseAccountModelId(model)) continue;
15692
- if (await codexAdapter?.supportsFastTier(model, key)) fastModels.push(accountModelId(key, model));
12703
+ for (const model$1 of [...fastModels]) {
12704
+ if (parseAccountModelId(model$1)) continue;
12705
+ if (await codexAdapter?.supportsFastTier(model$1, key)) fastModels.push(accountModelId(key, model$1));
15693
12706
  }
15694
12707
  }
15695
12708
  return {
15696
- tier: speedBySession.get(sessionId) ?? "standard",
12709
+ tier: selectedSpeed(sessionId, model),
15697
12710
  fastModels
15698
12711
  };
15699
12712
  },
15700
12713
  async setSpeed(sessionId, tier) {
15701
- if (tier === "standard") speedBySession.delete(sessionId);
15702
- else speedBySession.set(sessionId, tier);
12714
+ speedBySession.set(sessionId, tier);
15703
12715
  }
15704
12716
  };
15705
12717
  const modelDefaults = {
@@ -15768,30 +12780,15 @@ function apply(ctx, config) {
15768
12780
  }
15769
12781
  };
15770
12782
  let resolveExternalCredential;
15771
- let cursorAuth;
15772
- let cursorAdapter;
15773
- let cursorHandle;
15774
- let cursorAuthenticated;
15775
- let cursorAttachments;
15776
12783
  let ollamaAdapter;
15777
12784
  let ollamaHandle;
15778
- ctx.inject(["attachments"], (attachmentsCtx) => {
15779
- cursorAttachments = attachmentsCtx.attachments;
15780
- });
15781
12785
  ctx.inject(["credentials"], (credentialsCtx) => {
15782
12786
  resolveExternalCredential = (name$1) => credentialsCtx.credentials.resolve(credentialRef(name$1));
15783
- cursorAuth = new CursorAuth(credentialsCtx.credentials, hostFetch);
15784
- cursorAdapter = new CursorCompatAdapter({
15785
- auth: cursorAuth,
15786
- resolveAttachments: () => cursorAttachments,
15787
- visibleModels: () => cursorModelSettings.visibleModels()
15788
- });
15789
- cursorHandle = credentialsCtx.llm.registerAdapter(["cursor-subscription"], cursorAdapter);
15790
12787
  ollamaAdapter = new OllamaAdapter({
15791
12788
  apiKey: async () => (await resolveExternalCredential?.(ollamaApiKeyRef()))?.value,
15792
12789
  baseURL: ollamaBaseURL,
15793
12790
  catalog: ollamaCatalog,
15794
- resolveAttachments: () => cursorAttachments,
12791
+ resolveAttachments,
15795
12792
  streamIdleTimeoutMs,
15796
12793
  onWarn
15797
12794
  });
@@ -15831,6 +12828,7 @@ function apply(ctx, config) {
15831
12828
  }) } : {},
15832
12829
  ...providers.includes("claude") ? { claude: presentedVersion(claudeVersion) } : {}
15833
12830
  }, resetRedemption, (provider, account) => poolHealth?.clear(provider, account)), speed, modelDefaults, {
12831
+ visibleModels: (provider) => preferences.get(provider).visibleModels,
15834
12832
  async get(provider, force) {
15835
12833
  await loadModelDefaults();
15836
12834
  const adapter = adapters.get(provider);
@@ -15849,6 +12847,7 @@ function apply(ctx, config) {
15849
12847
  models: await withTimeout((signal) => adapter.listOwnModels(provider, account.key, signal), DISCOVERY_TIMEOUT_MS).catch(() => void 0)
15850
12848
  })));
15851
12849
  const tierIds = new Set((await poolAdapter?.modelsForProvider(provider).catch(() => []) ?? []).map((model) => model.id));
12850
+ const fastModels = new Set(provider === "codex" ? await codexAdapter?.fastCapableModels() ?? [] : []);
15852
12851
  const rows = await Promise.all(models.map(async (model) => {
15853
12852
  const contexts = [];
15854
12853
  if (provider === "codex" && codexAdapter) {
@@ -15873,6 +12872,7 @@ function apply(ctx, config) {
15873
12872
  name: name$1
15874
12873
  })) ?? [],
15875
12874
  configured: defaultEffortOf(provider, model.id),
12875
+ ...provider === "codex" ? { fastTier: !tierIds.has(model.id) && fastModels.has(model.id) } : {},
15876
12876
  ...contexts.length ? {
15877
12877
  defaultContextWindow: Math.min(...contexts.map((entry) => entry.default)),
15878
12878
  maxContextWindow: Math.min(...contexts.map((entry) => entry.max))
@@ -15911,79 +12911,6 @@ function apply(ctx, config) {
15911
12911
  for (const [route, handle] of handles) handle.replace([route]);
15912
12912
  }
15913
12913
  }, externalUsage, {
15914
- status: async () => {
15915
- const status = cursorAuth === void 0 ? {
15916
- authenticated: false,
15917
- busy: false
15918
- } : await cursorAuth.status();
15919
- if (cursorAuthenticated !== status.authenticated) {
15920
- cursorAuthenticated = status.authenticated;
15921
- cursorAdapter?.invalidateModels();
15922
- cursorHandle?.replace(["cursor-subscription"]);
15923
- }
15924
- return status;
15925
- },
15926
- login: () => {
15927
- if (cursorAuth === void 0) throw new Error("DSH credentials are unavailable");
15928
- return cursorAuth.login();
15929
- },
15930
- cancel: async () => {
15931
- await cursorAuth?.cancel();
15932
- },
15933
- logout: async () => {
15934
- await cursorAuth?.logout();
15935
- cursorAuthenticated = false;
15936
- cursorAdapter?.invalidateModels();
15937
- cursorHandle?.replace(["cursor-subscription"]);
15938
- },
15939
- usage: async (signal) => {
15940
- if (cursorAuth === void 0) throw new Error("DSH credentials are unavailable");
15941
- return fetchCursorUsage(await cursorAuth.accessToken({ signal }), hostFetch, signal);
15942
- },
15943
- models: async (force, signal) => {
15944
- if (cursorAdapter === void 0) throw new Error("Cursor model provider is unavailable");
15945
- if (force) cursorAdapter.invalidateModels();
15946
- const models = await cursorAdapter.listModelsForRpc({
15947
- force,
15948
- signal
15949
- });
15950
- if (force) cursorHandle?.replace(["cursor-subscription"]);
15951
- return models;
15952
- },
15953
- settings: async (force, signal) => {
15954
- const status = cursorAuth === void 0 ? { authenticated: false } : await cursorAuth.status();
15955
- const models = status.authenticated && cursorAdapter !== void 0 ? await cursorAdapter.listModelsForRpc({
15956
- force,
15957
- signal
15958
- }) : [];
15959
- if (force && status.authenticated) cursorHandle?.replace(["cursor-subscription"]);
15960
- return {
15961
- provider: "cursor-subscription",
15962
- settings: cursorModelSettings.get(),
15963
- models: models.map(({ id, name: name$1 }) => ({
15964
- id,
15965
- name: name$1,
15966
- efforts: []
15967
- })),
15968
- tools: [],
15969
- accounts: status.authenticated ? [{
15970
- key: "cursor",
15971
- label: "Cursor",
15972
- models
15973
- }] : []
15974
- };
15975
- },
15976
- setSettings: async (input) => {
15977
- let validated;
15978
- try {
15979
- validated = validateCursorModelSettings(input);
15980
- } catch (error) {
15981
- throw new BadRequest(error instanceof Error ? error.message : String(error));
15982
- }
15983
- await cursorModelSettings.set(validated);
15984
- cursorHandle?.replace(["cursor-subscription"]);
15985
- }
15986
- }, {
15987
12914
  settings: async () => ({
15988
12915
  provider: "ollama-cloud",
15989
12916
  settings: ollamaSettings.get(),