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