reeval 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/THIRD_PARTY_NOTICES.md +2160 -0
- package/assets/package.json +9 -0
- package/assets/pnpm-lock.yaml +1336 -0
- package/dist/agent-DVHkx8aT.js +532 -0
- package/dist/agent-DVHkx8aT.js.map +1 -0
- package/dist/analysis-CxSCt1YY.js +16 -0
- package/dist/analysis-CxSCt1YY.js.map +1 -0
- package/dist/assemble-B-nmz8oH.js +695 -0
- package/dist/assemble-B-nmz8oH.js.map +1 -0
- package/dist/assemble-CmRVDZ2Q.js +2 -0
- package/dist/attempt-BdRQ9jKX.js +176 -0
- package/dist/attempt-BdRQ9jKX.js.map +1 -0
- package/dist/bridge/index.mjs +1911 -0
- package/dist/bridge/package.json +13 -0
- package/dist/bridge/pnpm-lock.yaml +1041 -0
- package/dist/bridge/pnpm-workspace.yaml +2 -0
- package/dist/budget-DBUrH4jX.js +8 -0
- package/dist/budget-DBUrH4jX.js.map +1 -0
- package/dist/cli--CQ3Iy9q.js +1865 -0
- package/dist/cli--CQ3Iy9q.js.map +1 -0
- package/dist/cli-spec-DKAu09Gi.js +478 -0
- package/dist/cli-spec-DKAu09Gi.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors-QV9dFVhN.js +34 -0
- package/dist/colors-QV9dFVhN.js.map +1 -0
- package/dist/command-BSJzHMtP.js +280 -0
- package/dist/command-BSJzHMtP.js.map +1 -0
- package/dist/command-CEm8PZ1N.js +1043 -0
- package/dist/command-CEm8PZ1N.js.map +1 -0
- package/dist/command-D-jnQWVG.js +673 -0
- package/dist/command-D-jnQWVG.js.map +1 -0
- package/dist/command-DQAvbG7d.js +432 -0
- package/dist/command-DQAvbG7d.js.map +1 -0
- package/dist/commands-A4udfyKZ.js +2 -0
- package/dist/commands-CF8mOfiu.js +207 -0
- package/dist/commands-CF8mOfiu.js.map +1 -0
- package/dist/config-Cuj0kVYj.js +415 -0
- package/dist/config-Cuj0kVYj.js.map +1 -0
- package/dist/context-loi9OCZv.js +106 -0
- package/dist/context-loi9OCZv.js.map +1 -0
- package/dist/core-tLN-z1Be.js +96 -0
- package/dist/core-tLN-z1Be.js.map +1 -0
- package/dist/db-Cjj2ZsfQ.js +2 -0
- package/dist/db-DxDzMhtB.js +827 -0
- package/dist/db-DxDzMhtB.js.map +1 -0
- package/dist/debug-D24NOQH1.js +89 -0
- package/dist/debug-D24NOQH1.js.map +1 -0
- package/dist/debug-SEmW4w8f.js +2 -0
- package/dist/dist-BZXYHN2X.js +1673 -0
- package/dist/dist-BZXYHN2X.js.map +1 -0
- package/dist/docker-DQDZWXF9.js +20 -0
- package/dist/docker-DQDZWXF9.js.map +1 -0
- package/dist/docker-DnWAVdEm.js +2 -0
- package/dist/docker-oSbCExHL.js +153 -0
- package/dist/docker-oSbCExHL.js.map +1 -0
- package/dist/doctor-CxlH1pDM.js +525 -0
- package/dist/doctor-CxlH1pDM.js.map +1 -0
- package/dist/egress-73SQfKdx.js +619 -0
- package/dist/egress-73SQfKdx.js.map +1 -0
- package/dist/env-BQqj93gu.js +21 -0
- package/dist/env-BQqj93gu.js.map +1 -0
- package/dist/environment-Mz7wvrtD.js +552 -0
- package/dist/environment-Mz7wvrtD.js.map +1 -0
- package/dist/errors-C9w_ud7g.js +160 -0
- package/dist/errors-C9w_ud7g.js.map +1 -0
- package/dist/errors-CSxfJ4qu.js +11 -0
- package/dist/errors-CSxfJ4qu.js.map +1 -0
- package/dist/files--6CEUXm7.js +2 -0
- package/dist/files-BLlrhU4-.js +62 -0
- package/dist/files-BLlrhU4-.js.map +1 -0
- package/dist/files-Bd25OAQH.js +2 -0
- package/dist/files-BvHCFWgD.js +2 -0
- package/dist/files-D2-qn_Dl.js +275 -0
- package/dist/files-D2-qn_Dl.js.map +1 -0
- package/dist/files-bF2fPuT9.js +99 -0
- package/dist/files-bF2fPuT9.js.map +1 -0
- package/dist/git-D6hMeieS.js +44 -0
- package/dist/git-D6hMeieS.js.map +1 -0
- package/dist/harness-ls5aX1UO.js +131 -0
- package/dist/harness-ls5aX1UO.js.map +1 -0
- package/dist/harness.d.ts +74 -0
- package/dist/harness.js +2 -0
- package/dist/header-DzFDoMCI.js +78 -0
- package/dist/header-DzFDoMCI.js.map +1 -0
- package/dist/ids-Bp6ut7ZD.js +49 -0
- package/dist/ids-Bp6ut7ZD.js.map +1 -0
- package/dist/images-Cc1-coFc.js +286 -0
- package/dist/images-Cc1-coFc.js.map +1 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +1959 -0
- package/dist/index.js.map +1 -0
- package/dist/init-CFljS8BJ.js +344 -0
- package/dist/init-CFljS8BJ.js.map +1 -0
- package/dist/init-z2ZPn9Tj.js +2 -0
- package/dist/invocations-rXktgSAn.js +157 -0
- package/dist/invocations-rXktgSAn.js.map +1 -0
- package/dist/jiti-BOLXpKyG.js +46 -0
- package/dist/jiti-BOLXpKyG.js.map +1 -0
- package/dist/judge-D8Uf4dA9.js +146 -0
- package/dist/judge-D8Uf4dA9.js.map +1 -0
- package/dist/judge.d.ts +21 -0
- package/dist/judge.js +19 -0
- package/dist/judge.js.map +1 -0
- package/dist/live-Bs3LReMV.js +98 -0
- package/dist/live-Bs3LReMV.js.map +1 -0
- package/dist/matchers-DyLvm7EG.js +374 -0
- package/dist/matchers-DyLvm7EG.js.map +1 -0
- package/dist/model-BdO79gWz.js +44 -0
- package/dist/model-BdO79gWz.js.map +1 -0
- package/dist/model-CDL98xue.js +725 -0
- package/dist/model-CDL98xue.js.map +1 -0
- package/dist/model-v-0_Ik7S.js +2 -0
- package/dist/node-kG64XxdE.js +506 -0
- package/dist/node-kG64XxdE.js.map +1 -0
- package/dist/opencode-providers-DZuD_oYt.js +3149 -0
- package/dist/opencode-providers-DZuD_oYt.js.map +1 -0
- package/dist/options-CAQhu--C.js +452 -0
- package/dist/options-CAQhu--C.js.map +1 -0
- package/dist/patch-BkKje4cp.js +69 -0
- package/dist/patch-BkKje4cp.js.map +1 -0
- package/dist/paths-CBvm6pap.js +72 -0
- package/dist/paths-CBvm6pap.js.map +1 -0
- package/dist/plan-gwk6dJ8c.js +237 -0
- package/dist/plan-gwk6dJ8c.js.map +1 -0
- package/dist/preflight-BtubXKQ8.js +2 -0
- package/dist/preflight-BypZMNBG.js +185 -0
- package/dist/preflight-BypZMNBG.js.map +1 -0
- package/dist/prepare-Cwv0bHs_.js +3 -0
- package/dist/prepare-D6Uzcubb.js +213 -0
- package/dist/prepare-D6Uzcubb.js.map +1 -0
- package/dist/provenance-Dfb8YY5Y.js +129 -0
- package/dist/provenance-Dfb8YY5Y.js.map +1 -0
- package/dist/provider-BDbNOQhQ.js +2 -0
- package/dist/provider-CmAYTQQs.js +14 -0
- package/dist/provider-CmAYTQQs.js.map +1 -0
- package/dist/providers-2p3ylMHI.js +107 -0
- package/dist/providers-2p3ylMHI.js.map +1 -0
- package/dist/providers-BUOxitcT.js +2 -0
- package/dist/query-CFATHZmV.js +91 -0
- package/dist/query-CFATHZmV.js.map +1 -0
- package/dist/query-DR4m6CXf.js +2 -0
- package/dist/record-DQ8wzyco.js +99 -0
- package/dist/record-DQ8wzyco.js.map +1 -0
- package/dist/redact-C4fL_bZP.js +167 -0
- package/dist/redact-C4fL_bZP.js.map +1 -0
- package/dist/results-CeXb8Wde.js +37 -0
- package/dist/results-CeXb8Wde.js.map +1 -0
- package/dist/root-CFDjpWuc.js +110 -0
- package/dist/root-CFDjpWuc.js.map +1 -0
- package/dist/sandbox-By2gkdkC.js +133 -0
- package/dist/sandbox-By2gkdkC.js.map +1 -0
- package/dist/sandbox.d.ts +41 -0
- package/dist/sandbox.js +2 -0
- package/dist/server-Bi61uiY0.js +350 -0
- package/dist/server-Bi61uiY0.js.map +1 -0
- package/dist/server-CsSBL8NC.js +2 -0
- package/dist/source-BXzrmof5.js +2 -0
- package/dist/source-Bsfa3LZH.js +40 -0
- package/dist/source-Bsfa3LZH.js.map +1 -0
- package/dist/src-55j1zNHH.js +3 -0
- package/dist/src-AEwxd1UP.js +602 -0
- package/dist/src-AEwxd1UP.js.map +1 -0
- package/dist/src-BDwSuyIo.js +3 -0
- package/dist/src-BQyMiWri.js +490 -0
- package/dist/src-BQyMiWri.js.map +1 -0
- package/dist/src-C-LHyTSV.js +3 -0
- package/dist/src-Cmp-7YrD.js +1136 -0
- package/dist/src-Cmp-7YrD.js.map +1 -0
- package/dist/src-D0RdqpNb.js +252 -0
- package/dist/src-D0RdqpNb.js.map +1 -0
- package/dist/src-D8KYzxBX.js +793 -0
- package/dist/src-D8KYzxBX.js.map +1 -0
- package/dist/src-DEgff9Z7.js +4 -0
- package/dist/src-DXx_29pq.js +567 -0
- package/dist/src-DXx_29pq.js.map +1 -0
- package/dist/src-DwwiVLqv.js +2937 -0
- package/dist/src-DwwiVLqv.js.map +1 -0
- package/dist/src-N7E_mfcZ.js +2 -0
- package/dist/src-W9g-y5X5.js +3 -0
- package/dist/src-daS0wKH2.js +3 -0
- package/dist/state-Cyw3QTsN.js +2 -0
- package/dist/state-DZ7eqb1F.js +167 -0
- package/dist/state-DZ7eqb1F.js.map +1 -0
- package/dist/stats-BNAHlNMD.js +114 -0
- package/dist/stats-BNAHlNMD.js.map +1 -0
- package/dist/studio-app/assets/index-D6XyfFSH.js +58 -0
- package/dist/studio-app/assets/index-xwzWdcE_.css +2 -0
- package/dist/studio-app/favicon.svg +1 -0
- package/dist/studio-app/index.html +15 -0
- package/dist/summary-D7Ttky0z.js +166 -0
- package/dist/summary-D7Ttky0z.js.map +1 -0
- package/dist/text-gEcKjAyp.js +29 -0
- package/dist/text-gEcKjAyp.js.map +1 -0
- package/dist/types-C5vI3g1C.js +9 -0
- package/dist/types-C5vI3g1C.js.map +1 -0
- package/dist/types-DLec-6Cx.d.ts +1535 -0
- package/dist/usage-DsYUqIUM.js +24 -0
- package/dist/usage-DsYUqIUM.js.map +1 -0
- package/dist/view/index.d.ts +645 -0
- package/dist/view/index.js +8 -0
- package/dist/views-BrHOv6wg.js +220 -0
- package/dist/views-BrHOv6wg.js.map +1 -0
- package/dist/views-CwCWLWqo.js +3 -0
- package/dist/worker-entry-DnYkANPG.js +506 -0
- package/dist/worker-entry-DnYkANPG.js.map +1 -0
- package/dist/workspace-C2S0Z6gl.js +327 -0
- package/dist/workspace-C2S0Z6gl.js.map +1 -0
- package/dist/workspace-CyoXG6CY.js +2 -0
- package/docker/Dockerfile +19 -0
- package/package.json +79 -0
|
@@ -0,0 +1,1136 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import { HarnessCapabilityUnsupportedError, commonTool, harnessSessionDataDirectoryPath, harnessStateDirectoryPath } from "@ai-sdk/harness";
|
|
4
|
+
import { generateSandboxCredentialPlaceholder, isHarnessAuthenticationEnvironment, resolveSandboxHomeDir, shellQuote, warnCredentialBrokeringUnavailable, writeSkills } from "@ai-sdk/harness/utils";
|
|
5
|
+
import { tool } from "@ai-sdk/provider-utils";
|
|
6
|
+
import { createInterface } from "node:readline";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
//#region ../harness-cursor-sdk/src/cursor-sdk-auth.ts
|
|
9
|
+
/** The only credential the adapter uses: a Cursor user or service-account API key. */
|
|
10
|
+
const CURSOR_SDK_CREDENTIAL_ENV = "CURSOR_API_KEY";
|
|
11
|
+
/** Cursor's public API: the model catalog and `GET /v1/agents/{id}/usage`. Called from the host. */
|
|
12
|
+
const CURSOR_API_URL = "https://api.cursor.com";
|
|
13
|
+
/**
|
|
14
|
+
* The SDK looks the model up in this catalog instead of calling `GET api.cursor.com/v1/models`
|
|
15
|
+
* with the API key. The adapter fetches it on the host, so the sandbox never needs that host.
|
|
16
|
+
*/
|
|
17
|
+
const CURSOR_SDK_MODEL_CATALOG_ENV = "CURSOR_SDK_LOCAL_MODEL_CATALOG_JSON";
|
|
18
|
+
function resolveCursorSdkCredentials(auth = "auto", hostEnv = process.env) {
|
|
19
|
+
if (auth !== "auto" && !isHarnessAuthenticationEnvironment(auth)) throw new Error(`Cursor SDK auth must be 'auto' or an environment object such as { ${CURSOR_SDK_CREDENTIAL_ENV}: '…' } (got ${JSON.stringify(auth)})`);
|
|
20
|
+
const apiKey = (auth === "auto" ? hostEnv : auth)[CURSOR_SDK_CREDENTIAL_ENV];
|
|
21
|
+
if (!apiKey) throw new Error(`${CURSOR_SDK_CREDENTIAL_ENV} isn't set, so the Cursor SDK can't authenticate. It needs a Cursor user or service-account API key.`);
|
|
22
|
+
return { apiKey };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The one request the SDK sends the API key on (checked with @cursor/sdk 1.0.32): it trades
|
|
26
|
+
* the key for session tokens, which it sends everywhere else. The same endpoint the Cursor CLI
|
|
27
|
+
* uses, so a gateway that brokers the CLI brokers the SDK unchanged.
|
|
28
|
+
*/
|
|
29
|
+
function cursorSdkEndpoints() {
|
|
30
|
+
return [{
|
|
31
|
+
host: "api2.cursor.sh",
|
|
32
|
+
path: { exact: "/auth/exchange_user_api_key" },
|
|
33
|
+
method: ["POST"]
|
|
34
|
+
}];
|
|
35
|
+
}
|
|
36
|
+
/** Swaps the sandbox's placeholder for the real key on the key exchange, and nowhere else. */
|
|
37
|
+
function createCursorSdkRequestTransformations({ credentials, placeholder, headers }) {
|
|
38
|
+
const [exchange] = cursorSdkEndpoints();
|
|
39
|
+
return [{
|
|
40
|
+
match: {
|
|
41
|
+
host: exchange.host,
|
|
42
|
+
path: exchange.path,
|
|
43
|
+
method: [...exchange.method],
|
|
44
|
+
headers: [{
|
|
45
|
+
key: { exact: "Authorization" },
|
|
46
|
+
value: { exact: `Bearer ${placeholder}` }
|
|
47
|
+
}]
|
|
48
|
+
},
|
|
49
|
+
transform: { headers: { Authorization: `Bearer ${credentials.apiKey}` } }
|
|
50
|
+
}, ...headers && Object.keys(headers).length > 0 ? [{
|
|
51
|
+
match: { host: exchange.host },
|
|
52
|
+
transform: { headers: { ...headers } }
|
|
53
|
+
}] : []];
|
|
54
|
+
}
|
|
55
|
+
const catalogs = /* @__PURE__ */ new Map();
|
|
56
|
+
/** `GET /v1/models` with the real key, on the host, once per key and process. */
|
|
57
|
+
function fetchCursorModelCatalog(credentials, options = {}) {
|
|
58
|
+
const baseUrl = (options.baseUrl ?? "https://api.cursor.com").replace(/\/+$/, "");
|
|
59
|
+
const cacheKey = `${baseUrl} ${credentials.apiKey}`;
|
|
60
|
+
let pending = catalogs.get(cacheKey);
|
|
61
|
+
if (!pending) {
|
|
62
|
+
pending = (async () => {
|
|
63
|
+
const res = await (options.fetch ?? fetch)(`${baseUrl}/v1/models`, {
|
|
64
|
+
headers: { Authorization: `Bearer ${credentials.apiKey}` },
|
|
65
|
+
...options.signal ? { signal: options.signal } : {}
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok) throw new Error(`Cursor's model catalog request failed: HTTP ${res.status}`);
|
|
68
|
+
const body = await res.json();
|
|
69
|
+
const items = Array.isArray(body) ? body : body.items;
|
|
70
|
+
if (!Array.isArray(items)) throw new Error("Cursor's model catalog has no items");
|
|
71
|
+
return items;
|
|
72
|
+
})();
|
|
73
|
+
pending.catch(() => catalogs.delete(cacheKey));
|
|
74
|
+
catalogs.set(cacheKey, pending);
|
|
75
|
+
}
|
|
76
|
+
return pending;
|
|
77
|
+
}
|
|
78
|
+
/** The catalog entries for `model` (by id or alias), or the whole catalog if none match. */
|
|
79
|
+
function catalogFor(catalog, model) {
|
|
80
|
+
if (!model) return catalog;
|
|
81
|
+
const match = catalog.filter((m) => m.id === model || m.aliases?.includes(model));
|
|
82
|
+
return match.length > 0 ? match : catalog;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region ../harness-cursor-sdk/src/cursor-sdk-bootstrap.ts
|
|
86
|
+
/** The SDK build the bootstrap installs. Bump it, then check the stream and endpoints still hold. */
|
|
87
|
+
const CURSOR_SDK_VERSION = "1.0.32";
|
|
88
|
+
/**
|
|
89
|
+
* Built-in tools whose calls never reach the stream at {@link CURSOR_SDK_VERSION}: Cursor's
|
|
90
|
+
* backend sends them, but the SDK's interaction-update converter maps `webSearchToolCall` and
|
|
91
|
+
* `webFetchToolCall` to null, so neither `run.stream()` nor `onDelta` / `onStep` sees them. The
|
|
92
|
+
* agent's stored conversation (`Agent.messages.list`) keeps them, with args, results and times,
|
|
93
|
+
* so the runner reports them from there after each turn, as `tool_call` events marked
|
|
94
|
+
* `recovered`. When it can't, the turn's `harnessMetadata.cursorSdk.unreportedTools` lists them.
|
|
95
|
+
*/
|
|
96
|
+
const CURSOR_SDK_RECOVERED_TOOLS = {
|
|
97
|
+
webSearchToolCall: "webSearch",
|
|
98
|
+
webFetchToolCall: "webFetch"
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The web calls in `Agent.messages.list()`'s messages (as JSON), wherever they're nested: each is
|
|
102
|
+
* a `ToolCall` whose oneof key is in `recovered`. The runner embeds this function's source.
|
|
103
|
+
*/
|
|
104
|
+
function storedWebCalls(value, recovered = CURSOR_SDK_RECOVERED_TOOLS, found = []) {
|
|
105
|
+
if (Array.isArray(value)) for (const v of value) storedWebCalls(v, recovered, found);
|
|
106
|
+
else if (value && typeof value === "object") {
|
|
107
|
+
const o = value;
|
|
108
|
+
for (const [key, name] of Object.entries(recovered)) if (o[key] && typeof o.toolCallId === "string") found.push({
|
|
109
|
+
name,
|
|
110
|
+
id: o.toolCallId,
|
|
111
|
+
at: o,
|
|
112
|
+
call: o[key]
|
|
113
|
+
});
|
|
114
|
+
for (const v of Object.values(o)) storedWebCalls(v, recovered, found);
|
|
115
|
+
}
|
|
116
|
+
return found;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* What's wrong with the stored conversation's web calls, by the fields the runner reads: each
|
|
120
|
+
* call's `toolCallId`, `startedAtMs` and `completedAtMs`, its `args` (`url` for a fetch,
|
|
121
|
+
* `searchTerm` for a search) and a `result` with `success` or `error`; and any web tool key the
|
|
122
|
+
* runner doesn't know (a renamed `webFetchToolCall` would otherwise just find nothing). Empty when
|
|
123
|
+
* the shape is as the runner expects. The runner embeds this function's source, and reports what
|
|
124
|
+
* it finds as the turn's unreported tools, with this message.
|
|
125
|
+
*/
|
|
126
|
+
function storedShapeProblems(value, calls, recovered = CURSOR_SDK_RECOVERED_TOOLS) {
|
|
127
|
+
const problems = [];
|
|
128
|
+
const unknown = /* @__PURE__ */ new Set();
|
|
129
|
+
const walk = (v) => {
|
|
130
|
+
if (Array.isArray(v)) for (const x of v) walk(x);
|
|
131
|
+
else if (v && typeof v === "object") for (const [k, x] of Object.entries(v)) {
|
|
132
|
+
if (/^web\w*ToolCall$/.test(k) && !(k in recovered)) unknown.add(k);
|
|
133
|
+
walk(x);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
walk(value);
|
|
137
|
+
for (const k of unknown) problems.push(`an unknown web tool call key ${k}`);
|
|
138
|
+
const target = {
|
|
139
|
+
webFetch: "url",
|
|
140
|
+
webSearch: "searchTerm"
|
|
141
|
+
};
|
|
142
|
+
for (const c of calls) {
|
|
143
|
+
const at = `${c.name} ${c.id}`;
|
|
144
|
+
const epoch = (x) => x !== void 0 && x !== null && !Number.isNaN(Number(x));
|
|
145
|
+
if (!epoch(c.at.startedAtMs)) problems.push(`${at}: no startedAtMs`);
|
|
146
|
+
if (!epoch(c.at.completedAtMs)) problems.push(`${at}: no completedAtMs`);
|
|
147
|
+
const args = c.call?.args;
|
|
148
|
+
const field = target[c.name];
|
|
149
|
+
if (!args || typeof args !== "object") problems.push(`${at}: no args`);
|
|
150
|
+
else if (field && typeof args[field] !== "string") problems.push(`${at}: args has no ${field}`);
|
|
151
|
+
const result = c.call?.result;
|
|
152
|
+
if (!result || typeof result !== "object" || !("success" in result || "error" in result)) problems.push(`${at}: result has neither success nor error`);
|
|
153
|
+
}
|
|
154
|
+
return problems;
|
|
155
|
+
}
|
|
156
|
+
/** Relative to the harness state directory, `$HOME/.ai-sdk-harness`. */
|
|
157
|
+
const CURSOR_SDK_BOOTSTRAP_DIR = ".harness-bootstrap/cursor-sdk";
|
|
158
|
+
/** The runner's path relative to the bootstrap directory. */
|
|
159
|
+
const CURSOR_SDK_RUNNER_PATH = "runner.mjs";
|
|
160
|
+
/**
|
|
161
|
+
* The SDK needs Node 22.13; routing its requests through `HTTPS_PROXY` (`NODE_USE_ENV_PROXY`)
|
|
162
|
+
* needs 22.21, and a sandbox that brokers the key has no other way out.
|
|
163
|
+
*/
|
|
164
|
+
const CURSOR_SDK_MIN_NODE = "22.21.0";
|
|
165
|
+
/** The file the adapter writes once a turn's request is complete, so the runner never reads half. */
|
|
166
|
+
const cursorSdkTurnReady = (turn) => `turn-${turn}.ready`;
|
|
167
|
+
/**
|
|
168
|
+
* Runs a session's turns in the sandbox, one process for as long as it lives: it waits for each
|
|
169
|
+
* turn's request (`turn-<n>.json`, then `turn-<n>.ready`) in the session directory, creates the
|
|
170
|
+
* agent on the first (or resumes it by id from its JSONL store), sends the prompt and prints one
|
|
171
|
+
* JSON line per event on stdout, then a `result` line. The agent stays loaded between turns, so a
|
|
172
|
+
* follow-up turn skips Node's start, the SDK's import and the resume. The SDK logs to stdout, so
|
|
173
|
+
* everything but those lines goes to stderr.
|
|
174
|
+
*/
|
|
175
|
+
const CURSOR_SDK_RUNNER = `// A local Cursor SDK agent's turns, as NDJSON on stdout.
|
|
176
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
177
|
+
import { join } from 'node:path'
|
|
178
|
+
import { setTimeout as sleep } from 'node:timers/promises'
|
|
179
|
+
|
|
180
|
+
const write = process.stdout.write.bind(process.stdout)
|
|
181
|
+
process.stdout.write = process.stderr.write.bind(process.stderr)
|
|
182
|
+
const out = (line) => write(\`\${JSON.stringify(line)}\\n\`)
|
|
183
|
+
const dir = process.argv[2]
|
|
184
|
+
let n = Number(process.argv[3] ?? 1)
|
|
185
|
+
|
|
186
|
+
let sdk
|
|
187
|
+
try {
|
|
188
|
+
sdk = await import('@cursor/sdk')
|
|
189
|
+
} catch (error) {
|
|
190
|
+
out({ type: 'error', turn: n, message: \`The Cursor SDK didn't load: \${error?.message ?? error}\` })
|
|
191
|
+
process.exit(1)
|
|
192
|
+
}
|
|
193
|
+
const { Agent, Cursor, JsonlLocalAgentStore } = sdk
|
|
194
|
+
|
|
195
|
+
// Over HTTP/2 the agent's stream connects directly, ignoring HTTPS_PROXY; over HTTP/1.1 it
|
|
196
|
+
// goes through the proxy like every other request.
|
|
197
|
+
Cursor.configure({ local: { useHttp1ForAgent: true } })
|
|
198
|
+
|
|
199
|
+
const RECOVERED = ${JSON.stringify(CURSOR_SDK_RECOVERED_TOOLS)}
|
|
200
|
+
const storedWebCalls = ${storedWebCalls.toString()}
|
|
201
|
+
const storedShapeProblems = ${storedShapeProblems.toString()}
|
|
202
|
+
const epochMs = (v) => (v === undefined || v === null || Number.isNaN(Number(v)) ? undefined : Number(v))
|
|
203
|
+
// As JSON: in memory they're protobuf messages, whose oneofs are { case, value }.
|
|
204
|
+
const listMessages = async (turn) =>
|
|
205
|
+
JSON.parse(JSON.stringify(await Agent.messages.list(agent.agentId, { cwd: turn.cwd, store })))
|
|
206
|
+
|
|
207
|
+
let agent
|
|
208
|
+
let run
|
|
209
|
+
let store
|
|
210
|
+
let stopping = false
|
|
211
|
+
const stop = () => {
|
|
212
|
+
stopping = true
|
|
213
|
+
if (run) void run.cancel().catch(() => {})
|
|
214
|
+
else process.exit(130)
|
|
215
|
+
}
|
|
216
|
+
process.on('SIGTERM', stop)
|
|
217
|
+
process.on('SIGINT', stop)
|
|
218
|
+
|
|
219
|
+
for (; !stopping; n++) {
|
|
220
|
+
while (!existsSync(join(dir, \`turn-\${n}.ready\`))) await sleep(15)
|
|
221
|
+
const turn = JSON.parse(readFileSync(join(dir, \`turn-\${n}.json\`), 'utf8'))
|
|
222
|
+
const prompt = readFileSync(turn.promptFile, 'utf8')
|
|
223
|
+
const started = Date.now()
|
|
224
|
+
try {
|
|
225
|
+
if (!agent) {
|
|
226
|
+
store = new JsonlLocalAgentStore(turn.storeDir)
|
|
227
|
+
const options = {
|
|
228
|
+
apiKey: process.env.CURSOR_API_KEY,
|
|
229
|
+
...(turn.model ? { model: turn.model } : {}),
|
|
230
|
+
local: {
|
|
231
|
+
cwd: turn.cwd,
|
|
232
|
+
store,
|
|
233
|
+
settingSources: turn.settingSources,
|
|
234
|
+
...(turn.sandbox ? { sandboxOptions: { enabled: true } } : {}),
|
|
235
|
+
},
|
|
236
|
+
...(turn.mcpServers ? { mcpServers: turn.mcpServers } : {}),
|
|
237
|
+
}
|
|
238
|
+
agent = turn.agentId ? await Agent.resume(turn.agentId, options) : await Agent.create(options)
|
|
239
|
+
}
|
|
240
|
+
out({ type: 'init', turn: n, agentId: agent.agentId, model: agent.model, sdkVersion: turn.sdkVersion })
|
|
241
|
+
// Stored messages are updated in place, so earlier turns' calls are told apart by id.
|
|
242
|
+
const before = await listMessages(turn).then(
|
|
243
|
+
(m) => new Set(storedWebCalls(m, RECOVERED).map((c) => c.id)),
|
|
244
|
+
() => undefined,
|
|
245
|
+
)
|
|
246
|
+
run = await agent.send(prompt)
|
|
247
|
+
const streamed = new Set()
|
|
248
|
+
for await (const event of run.stream()) {
|
|
249
|
+
if (event.type === 'tool_call') streamed.add(event.call_id)
|
|
250
|
+
out({ type: 'event', event })
|
|
251
|
+
}
|
|
252
|
+
const result = await run.wait()
|
|
253
|
+
try {
|
|
254
|
+
if (before === undefined) throw new Error('the stored conversation could not be read before the turn')
|
|
255
|
+
const messages = await listMessages(turn)
|
|
256
|
+
const calls = storedWebCalls(messages, RECOVERED)
|
|
257
|
+
const problems = storedShapeProblems(messages, calls.filter((c) => !before.has(c.id)), RECOVERED)
|
|
258
|
+
if (problems.length) throw new Error(\`the stored conversation's shape changed (@cursor/sdk \${turn.sdkVersion ?? '?'}): \${problems.join('; ')}\`)
|
|
259
|
+
for (const c of calls.filter((c) => !before.has(c.id) && !streamed.has(c.id))) {
|
|
260
|
+
const { toolCallId, ...args } = c.call.args ?? {}
|
|
261
|
+
const base = { type: 'tool_call', agent_id: agent.agentId, run_id: result.id, call_id: c.id, name: c.name, args }
|
|
262
|
+
out({ type: 'event', recovered: 'Agent.messages.list', event: { ...base, status: 'running', started_at_ms: epochMs(c.at.startedAtMs) } })
|
|
263
|
+
const r = c.call.result
|
|
264
|
+
const failed = !r?.success
|
|
265
|
+
out({
|
|
266
|
+
type: 'event',
|
|
267
|
+
recovered: 'Agent.messages.list',
|
|
268
|
+
event: {
|
|
269
|
+
...base,
|
|
270
|
+
status: failed ? 'error' : 'completed',
|
|
271
|
+
result: failed ? { status: 'error', error: r?.error ?? r ?? 'no result' } : { status: 'success', value: r.success },
|
|
272
|
+
started_at_ms: epochMs(c.at.startedAtMs),
|
|
273
|
+
completed_at_ms: epochMs(c.at.completedAtMs),
|
|
274
|
+
},
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
out({ type: 'unreported', turn: n, tools: Object.values(RECOVERED), message: String(error?.message ?? error) })
|
|
279
|
+
}
|
|
280
|
+
out({
|
|
281
|
+
type: 'result',
|
|
282
|
+
turn: n,
|
|
283
|
+
agentId: agent.agentId,
|
|
284
|
+
runId: result.id,
|
|
285
|
+
requestId: result.requestId,
|
|
286
|
+
status: result.status,
|
|
287
|
+
result: result.result,
|
|
288
|
+
error: result.error,
|
|
289
|
+
model: result.model,
|
|
290
|
+
durationMs: result.durationMs ?? Date.now() - started,
|
|
291
|
+
usage: result.usage,
|
|
292
|
+
})
|
|
293
|
+
} catch (error) {
|
|
294
|
+
out({
|
|
295
|
+
type: 'error',
|
|
296
|
+
turn: n,
|
|
297
|
+
message: String(error?.message ?? error),
|
|
298
|
+
name: error?.name,
|
|
299
|
+
code: error?.code,
|
|
300
|
+
status: error?.status,
|
|
301
|
+
})
|
|
302
|
+
// The next turn starts from the store, not from an agent in an unknown state.
|
|
303
|
+
await agent?.[Symbol.asyncDispose]?.().catch(() => {})
|
|
304
|
+
agent = undefined
|
|
305
|
+
} finally {
|
|
306
|
+
run = undefined
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
await agent?.[Symbol.asyncDispose]?.().catch(() => {})
|
|
310
|
+
process.exit(130)
|
|
311
|
+
`;
|
|
312
|
+
/** Checks Node, then installs the pinned SDK (and its platform package) next to the runner. */
|
|
313
|
+
function cursorSdkInstallScript(version = CURSOR_SDK_VERSION) {
|
|
314
|
+
return `set -eu
|
|
315
|
+
node -e 'const [a,b]=process.versions.node.split(".").map(Number);const [x,y]="${CURSOR_SDK_MIN_NODE}".split(".").map(Number);if(a<x||(a===x&&b<y)){console.error("The Cursor SDK harness needs Node ${CURSOR_SDK_MIN_NODE} or later (found "+process.versions.node+")");process.exit(1)}'
|
|
316
|
+
cat > package.json <<'JSON'
|
|
317
|
+
{ "private": true, "type": "module", "dependencies": { "@cursor/sdk": "${version}" } }
|
|
318
|
+
JSON
|
|
319
|
+
npm install --omit=dev --no-audit --no-fund --loglevel=error
|
|
320
|
+
node -e 'import("@cursor/sdk").then(() => console.log("@cursor/sdk ${version}"))'
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
async function getCursorSdkBootstrap() {
|
|
324
|
+
return {
|
|
325
|
+
harnessId: "cursor-sdk",
|
|
326
|
+
bootstrapDir: CURSOR_SDK_BOOTSTRAP_DIR,
|
|
327
|
+
files: [{
|
|
328
|
+
path: `${CURSOR_SDK_BOOTSTRAP_DIR}/install.sh`,
|
|
329
|
+
content: cursorSdkInstallScript()
|
|
330
|
+
}, {
|
|
331
|
+
path: `${CURSOR_SDK_BOOTSTRAP_DIR}/${CURSOR_SDK_RUNNER_PATH}`,
|
|
332
|
+
content: CURSOR_SDK_RUNNER
|
|
333
|
+
}],
|
|
334
|
+
commands: [{ command: "sh install.sh" }]
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region ../harness-cursor-sdk/src/cursor-sdk-tools.ts
|
|
339
|
+
const native = (nativeName, title, toolUseKind) => ({
|
|
340
|
+
...tool({
|
|
341
|
+
title,
|
|
342
|
+
inputSchema: z.looseObject({})
|
|
343
|
+
}),
|
|
344
|
+
nativeName,
|
|
345
|
+
...toolUseKind ? { toolUseKind } : {}
|
|
346
|
+
});
|
|
347
|
+
const CURSOR_SDK_BUILTIN_TOOLS = {
|
|
348
|
+
bash: {
|
|
349
|
+
...commonTool("bash", {
|
|
350
|
+
nativeName: "shell",
|
|
351
|
+
toolUseKind: "bash",
|
|
352
|
+
inputSchema: z.looseObject({ command: z.string() })
|
|
353
|
+
}),
|
|
354
|
+
title: "Terminal"
|
|
355
|
+
},
|
|
356
|
+
read: {
|
|
357
|
+
...tool({
|
|
358
|
+
title: "Read",
|
|
359
|
+
inputSchema: z.looseObject({ path: z.string() })
|
|
360
|
+
}),
|
|
361
|
+
nativeName: "read",
|
|
362
|
+
toolUseKind: "readonly"
|
|
363
|
+
},
|
|
364
|
+
edit: {
|
|
365
|
+
...tool({
|
|
366
|
+
title: "Edit",
|
|
367
|
+
inputSchema: z.looseObject({ path: z.string() })
|
|
368
|
+
}),
|
|
369
|
+
nativeName: "edit",
|
|
370
|
+
toolUseKind: "edit"
|
|
371
|
+
},
|
|
372
|
+
write: {
|
|
373
|
+
...tool({
|
|
374
|
+
title: "Write",
|
|
375
|
+
inputSchema: z.looseObject({ path: z.string() })
|
|
376
|
+
}),
|
|
377
|
+
nativeName: "write",
|
|
378
|
+
toolUseKind: "edit"
|
|
379
|
+
},
|
|
380
|
+
delete: {
|
|
381
|
+
...tool({
|
|
382
|
+
title: "Delete",
|
|
383
|
+
inputSchema: z.looseObject({ path: z.string() })
|
|
384
|
+
}),
|
|
385
|
+
nativeName: "delete",
|
|
386
|
+
toolUseKind: "edit"
|
|
387
|
+
},
|
|
388
|
+
glob: {
|
|
389
|
+
...commonTool("glob", {
|
|
390
|
+
nativeName: "glob",
|
|
391
|
+
toolUseKind: "readonly",
|
|
392
|
+
inputSchema: z.looseObject({ pattern: z.string() })
|
|
393
|
+
}),
|
|
394
|
+
title: "Find"
|
|
395
|
+
},
|
|
396
|
+
grep: {
|
|
397
|
+
...commonTool("grep", {
|
|
398
|
+
nativeName: "grep",
|
|
399
|
+
toolUseKind: "readonly",
|
|
400
|
+
inputSchema: z.looseObject({
|
|
401
|
+
pattern: z.string(),
|
|
402
|
+
path: z.string().optional()
|
|
403
|
+
})
|
|
404
|
+
}),
|
|
405
|
+
title: "grep"
|
|
406
|
+
},
|
|
407
|
+
ls: native("ls", "List", "readonly"),
|
|
408
|
+
webSearch: {
|
|
409
|
+
...commonTool("webSearch", {
|
|
410
|
+
nativeName: "webSearch",
|
|
411
|
+
toolUseKind: "readonly",
|
|
412
|
+
inputSchema: z.looseObject({ query: z.string() })
|
|
413
|
+
}),
|
|
414
|
+
title: "Web Search"
|
|
415
|
+
},
|
|
416
|
+
webFetch: {
|
|
417
|
+
...tool({
|
|
418
|
+
title: "Fetch",
|
|
419
|
+
inputSchema: z.looseObject({ url: z.string() })
|
|
420
|
+
}),
|
|
421
|
+
nativeName: "webFetch",
|
|
422
|
+
toolUseKind: "readonly"
|
|
423
|
+
},
|
|
424
|
+
mcp: native("mcp", "MCP"),
|
|
425
|
+
task: native("task", "Task"),
|
|
426
|
+
semSearch: native("semSearch", "Semantic Search", "readonly"),
|
|
427
|
+
updateTodos: native("updateTodos", "Update TODOs"),
|
|
428
|
+
readTodos: native("readTodos", "Read TODOs", "readonly"),
|
|
429
|
+
readLints: native("readLints", "Read Lints", "readonly")
|
|
430
|
+
};
|
|
431
|
+
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
432
|
+
const str = (value) => typeof value === "string" ? value : void 0;
|
|
433
|
+
const BY_NATIVE_NAME = new Map(Object.entries(CURSOR_SDK_BUILTIN_TOOLS).map(([key, t]) => [t.nativeName ?? key, key]));
|
|
434
|
+
/** Maps an SDK `tool_call` to its catalog name, adding the common-schema fields it implies. */
|
|
435
|
+
function projectToolCall(nativeName, args) {
|
|
436
|
+
const a = record(args);
|
|
437
|
+
const common = (toolName, extra = {}) => ({
|
|
438
|
+
toolName,
|
|
439
|
+
nativeName,
|
|
440
|
+
input: {
|
|
441
|
+
...a,
|
|
442
|
+
...extra
|
|
443
|
+
},
|
|
444
|
+
dynamic: false
|
|
445
|
+
});
|
|
446
|
+
switch (nativeName) {
|
|
447
|
+
case "shell": return common("bash", { command: str(a.command) ?? "" });
|
|
448
|
+
case "glob": return common("glob", { pattern: str(a.globPattern) ?? str(a.pattern) ?? "" });
|
|
449
|
+
case "grep": return common("grep", { pattern: str(a.pattern) ?? "" });
|
|
450
|
+
case "webSearch": return common("webSearch", { query: str(a.searchTerm) ?? str(a.query) ?? "" });
|
|
451
|
+
}
|
|
452
|
+
const key = BY_NATIVE_NAME.get(nativeName);
|
|
453
|
+
return key ? common(key) : {
|
|
454
|
+
toolName: nativeName,
|
|
455
|
+
nativeName,
|
|
456
|
+
input: { ...a },
|
|
457
|
+
dynamic: true
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* An SDK tool result is `{ status: 'success', value }` or `{ status: 'error', error }`. The
|
|
462
|
+
* value is passed on as is, except shell output, which becomes its text (as the ACP adapter
|
|
463
|
+
* reports it), and MCP content, which is flattened to text parts.
|
|
464
|
+
*/
|
|
465
|
+
function projectToolResult(toolName, status, result) {
|
|
466
|
+
const r = record(result);
|
|
467
|
+
if (status === "error" || r.status !== void 0 && r.status !== "success") {
|
|
468
|
+
const error = str(r.error) ?? str(record(r.error).message) ?? str(record(r.value).error) ?? (result === void 0 ? `${toolName} failed` : JSON.stringify(result));
|
|
469
|
+
return {
|
|
470
|
+
output: error,
|
|
471
|
+
error
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
const value = r.status === "success" ? r.value : result;
|
|
475
|
+
if (toolName === "bash") {
|
|
476
|
+
const v = record(value);
|
|
477
|
+
const stdout = str(v.stdout) ?? "";
|
|
478
|
+
const stderr = str(v.stderr) ?? "";
|
|
479
|
+
return { output: stderr ? `${stdout}${stdout.endsWith("\n") || !stdout ? "" : "\n"}${stderr}` : stdout };
|
|
480
|
+
}
|
|
481
|
+
if (toolName === "mcp") {
|
|
482
|
+
const content = record(value).content;
|
|
483
|
+
if (Array.isArray(content)) return { output: content.map((c) => {
|
|
484
|
+
const text = record(record(c).text).text ?? record(c).text;
|
|
485
|
+
return typeof text === "string" ? {
|
|
486
|
+
type: "text",
|
|
487
|
+
text
|
|
488
|
+
} : c;
|
|
489
|
+
}) };
|
|
490
|
+
}
|
|
491
|
+
return { output: value ?? "" };
|
|
492
|
+
}
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region ../harness-cursor-sdk/src/cursor-sdk-stream.ts
|
|
495
|
+
/** Parts that are a step's content, as opposed to the stream's framing. */
|
|
496
|
+
const isContent = (part) => ![
|
|
497
|
+
"raw",
|
|
498
|
+
"stream-start",
|
|
499
|
+
"finish",
|
|
500
|
+
"finish-step",
|
|
501
|
+
"error"
|
|
502
|
+
].includes(part.type);
|
|
503
|
+
/** Maps the SDK's counts onto the AI SDK's usage shape, keeping the original under `raw`. */
|
|
504
|
+
function toUsage(usage) {
|
|
505
|
+
if (!usage) return {
|
|
506
|
+
inputTokens: {
|
|
507
|
+
total: void 0,
|
|
508
|
+
noCache: void 0,
|
|
509
|
+
cacheRead: void 0,
|
|
510
|
+
cacheWrite: void 0
|
|
511
|
+
},
|
|
512
|
+
outputTokens: {
|
|
513
|
+
total: void 0,
|
|
514
|
+
text: void 0,
|
|
515
|
+
reasoning: void 0
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
const noCache = usage.inputTokens;
|
|
519
|
+
const cacheRead = usage.cacheReadTokens;
|
|
520
|
+
const cacheWrite = usage.cacheWriteTokens;
|
|
521
|
+
const output = usage.outputTokens;
|
|
522
|
+
const reasoning = usage.reasoningTokens;
|
|
523
|
+
return {
|
|
524
|
+
inputTokens: {
|
|
525
|
+
total: noCache === void 0 ? void 0 : noCache + (cacheRead ?? 0) + (cacheWrite ?? 0),
|
|
526
|
+
noCache,
|
|
527
|
+
cacheRead,
|
|
528
|
+
cacheWrite
|
|
529
|
+
},
|
|
530
|
+
outputTokens: {
|
|
531
|
+
total: output,
|
|
532
|
+
text: output !== void 0 && reasoning !== void 0 ? output - reasoning : void 0,
|
|
533
|
+
reasoning
|
|
534
|
+
},
|
|
535
|
+
raw: { ...usage }
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/** The model as one id, with its parameters: `composer-2.5[fast=false]`. */
|
|
539
|
+
function modelLabel(model) {
|
|
540
|
+
if (!model?.id) return void 0;
|
|
541
|
+
const params = model.params ?? [];
|
|
542
|
+
return params.length ? `${model.id}[${params.map((p) => `${p.id}=${p.value}`).join(",")}]` : model.id;
|
|
543
|
+
}
|
|
544
|
+
const FINISH = {
|
|
545
|
+
finished: "stop",
|
|
546
|
+
error: "error",
|
|
547
|
+
cancelled: "other"
|
|
548
|
+
};
|
|
549
|
+
/** `webFetch <url>` or `webSearch <query>` for a web tool call, else undefined. */
|
|
550
|
+
function webKey(e) {
|
|
551
|
+
const p = projectToolCall(e.name ?? "", e.args);
|
|
552
|
+
if (p.toolName !== "webFetch" && p.toolName !== "webSearch") return void 0;
|
|
553
|
+
const target = p.input.url ?? p.input.query;
|
|
554
|
+
return typeof target === "string" ? `${p.toolName} ${target}` : void 0;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Turns the runner's NDJSON for one turn into harness stream parts, in order. Text and
|
|
558
|
+
* thinking arrive as deltas; a thinking event with `thinking_duration_ms` ends its block.
|
|
559
|
+
* Each tool call is reported `running` (maybe more than once) and then `completed` or `error`.
|
|
560
|
+
*/
|
|
561
|
+
var CursorSdkStreamTranslator = class {
|
|
562
|
+
agentId;
|
|
563
|
+
/** The last `result` line, for callers that want the SDK's own record of the run. */
|
|
564
|
+
result;
|
|
565
|
+
started = false;
|
|
566
|
+
/** True once the turn's `result` or `error` line (or {@link end}) closed it. */
|
|
567
|
+
finished = false;
|
|
568
|
+
sawText = false;
|
|
569
|
+
block = 0;
|
|
570
|
+
openText;
|
|
571
|
+
openReasoning;
|
|
572
|
+
openTools = /* @__PURE__ */ new Map();
|
|
573
|
+
/** Calls already reported, so a late duplicate `completed` doesn't open a second one. */
|
|
574
|
+
seen = /* @__PURE__ */ new Set();
|
|
575
|
+
/** Web calls the stream itself reported, by id and by tool and target. */
|
|
576
|
+
streamedWeb = /* @__PURE__ */ new Set();
|
|
577
|
+
/**
|
|
578
|
+
* The turn's `usage` event, held until its result line: the runner reports the calls it
|
|
579
|
+
* recovers after it, and HarnessAgent refuses content after a step's `finish-step`.
|
|
580
|
+
*/
|
|
581
|
+
stepUsage;
|
|
582
|
+
/** Whether the open step has content, which HarnessAgent needs closed before `finish`. */
|
|
583
|
+
stepHasContent = false;
|
|
584
|
+
/** Built-in tools this turn may have used with no `tool_call` reported. */
|
|
585
|
+
unreportedTools = [];
|
|
586
|
+
workDir;
|
|
587
|
+
/**
|
|
588
|
+
* `workDir`: the agent's workspace. Tool paths under it are reported relative to it, as the
|
|
589
|
+
* ACP adapter reports them; the SDK reports them absolute.
|
|
590
|
+
*/
|
|
591
|
+
constructor(options = {}) {
|
|
592
|
+
this.agentId = options.agentId;
|
|
593
|
+
this.workDir = options.workDir?.replace(/\/+$/, "");
|
|
594
|
+
}
|
|
595
|
+
relative(input) {
|
|
596
|
+
const prefix = this.workDir ? `${this.workDir}/` : void 0;
|
|
597
|
+
if (!prefix || typeof input.path !== "string" || !input.path.startsWith(prefix)) return input;
|
|
598
|
+
return {
|
|
599
|
+
...input,
|
|
600
|
+
path: input.path.slice(prefix.length)
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
/** Parts for one stdout line. Lines that aren't runner lines are passed through as raw. */
|
|
604
|
+
push(line) {
|
|
605
|
+
const trimmed = line.trim();
|
|
606
|
+
if (!trimmed) return [];
|
|
607
|
+
let parsed;
|
|
608
|
+
try {
|
|
609
|
+
parsed = JSON.parse(trimmed);
|
|
610
|
+
} catch {
|
|
611
|
+
return [{
|
|
612
|
+
type: "raw",
|
|
613
|
+
rawValue: { unparsed: trimmed }
|
|
614
|
+
}];
|
|
615
|
+
}
|
|
616
|
+
if (this.finished) return [{
|
|
617
|
+
type: "raw",
|
|
618
|
+
rawValue: parsed
|
|
619
|
+
}];
|
|
620
|
+
const parts = [];
|
|
621
|
+
switch (parsed.type) {
|
|
622
|
+
case "init":
|
|
623
|
+
this.agentId = parsed.agentId ?? this.agentId;
|
|
624
|
+
this.start(parts, modelLabel(parsed.model));
|
|
625
|
+
break;
|
|
626
|
+
case "event":
|
|
627
|
+
this.start(parts);
|
|
628
|
+
this.event(parts, parsed.event ?? { type: "unknown" }, parsed.recovered !== void 0);
|
|
629
|
+
break;
|
|
630
|
+
case "result":
|
|
631
|
+
this.start(parts);
|
|
632
|
+
this.finish(parts, parsed);
|
|
633
|
+
break;
|
|
634
|
+
case "error":
|
|
635
|
+
this.start(parts);
|
|
636
|
+
this.fail(parts, parsed.message, parsed.code ?? parsed.name ?? "error");
|
|
637
|
+
break;
|
|
638
|
+
case "unreported": this.unreportedTools = [.../* @__PURE__ */ new Set([...this.unreportedTools, ...parsed.tools ?? []])];
|
|
639
|
+
}
|
|
640
|
+
parts.push({
|
|
641
|
+
type: "raw",
|
|
642
|
+
rawValue: parsed
|
|
643
|
+
});
|
|
644
|
+
if (parts.some(isContent)) this.stepHasContent = true;
|
|
645
|
+
return parts;
|
|
646
|
+
}
|
|
647
|
+
/** Closes the turn when the runner ends without a result (killed, crashed, aborted). */
|
|
648
|
+
end(options) {
|
|
649
|
+
if (this.finished) return [];
|
|
650
|
+
const parts = [];
|
|
651
|
+
this.start(parts);
|
|
652
|
+
this.closeTools(parts, options.aborted ? "aborted" : "interrupted");
|
|
653
|
+
this.closeBlocks(parts);
|
|
654
|
+
this.closeStep(parts);
|
|
655
|
+
if (!options.aborted) {
|
|
656
|
+
const detail = options.stderr?.trim();
|
|
657
|
+
parts.push({
|
|
658
|
+
type: "error",
|
|
659
|
+
error: /* @__PURE__ */ new Error(`The Cursor SDK runner exited with code ${options.exitCode} before reporting a result${detail ? `: ${detail}` : ""}`)
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
this.finished = true;
|
|
663
|
+
parts.push({
|
|
664
|
+
type: "finish",
|
|
665
|
+
finishReason: options.aborted ? {
|
|
666
|
+
unified: "other",
|
|
667
|
+
raw: "aborted"
|
|
668
|
+
} : {
|
|
669
|
+
unified: "error",
|
|
670
|
+
raw: `exit ${options.exitCode}`
|
|
671
|
+
},
|
|
672
|
+
totalUsage: toUsage(void 0),
|
|
673
|
+
harnessMetadata: this.unrecovered()
|
|
674
|
+
});
|
|
675
|
+
return parts;
|
|
676
|
+
}
|
|
677
|
+
start(parts, modelId) {
|
|
678
|
+
if (this.started) return;
|
|
679
|
+
this.started = true;
|
|
680
|
+
parts.push({
|
|
681
|
+
type: "stream-start",
|
|
682
|
+
...modelId ? { modelId } : {}
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
closeText(parts) {
|
|
686
|
+
if (this.openText) parts.push({
|
|
687
|
+
type: "text-end",
|
|
688
|
+
id: this.openText
|
|
689
|
+
});
|
|
690
|
+
this.openText = void 0;
|
|
691
|
+
}
|
|
692
|
+
closeReasoning(parts) {
|
|
693
|
+
if (this.openReasoning) parts.push({
|
|
694
|
+
type: "reasoning-end",
|
|
695
|
+
id: this.openReasoning
|
|
696
|
+
});
|
|
697
|
+
this.openReasoning = void 0;
|
|
698
|
+
}
|
|
699
|
+
closeBlocks(parts) {
|
|
700
|
+
this.closeText(parts);
|
|
701
|
+
this.closeReasoning(parts);
|
|
702
|
+
}
|
|
703
|
+
closeTools(parts, why) {
|
|
704
|
+
for (const [toolCallId, call] of this.openTools) parts.push({
|
|
705
|
+
type: "tool-result",
|
|
706
|
+
toolCallId,
|
|
707
|
+
toolName: call.toolName,
|
|
708
|
+
result: `${call.toolName} ${why}`,
|
|
709
|
+
isError: true,
|
|
710
|
+
...call.dynamic ? { dynamic: true } : {}
|
|
711
|
+
});
|
|
712
|
+
this.openTools.clear();
|
|
713
|
+
}
|
|
714
|
+
event(parts, e, recovered = false) {
|
|
715
|
+
this.agentId ??= e.agent_id;
|
|
716
|
+
switch (e.type) {
|
|
717
|
+
case "thinking":
|
|
718
|
+
if (e.text) {
|
|
719
|
+
this.closeText(parts);
|
|
720
|
+
if (!this.openReasoning) {
|
|
721
|
+
this.openReasoning = `reasoning-${++this.block}`;
|
|
722
|
+
parts.push({
|
|
723
|
+
type: "reasoning-start",
|
|
724
|
+
id: this.openReasoning
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
parts.push({
|
|
728
|
+
type: "reasoning-delta",
|
|
729
|
+
id: this.openReasoning,
|
|
730
|
+
delta: e.text
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (e.thinking_duration_ms !== void 0) this.closeReasoning(parts);
|
|
734
|
+
return;
|
|
735
|
+
case "assistant": {
|
|
736
|
+
const text = (e.message?.content ?? []).filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("");
|
|
737
|
+
if (!text) return;
|
|
738
|
+
this.closeReasoning(parts);
|
|
739
|
+
if (!this.openText) {
|
|
740
|
+
this.openText = `text-${++this.block}`;
|
|
741
|
+
parts.push({
|
|
742
|
+
type: "text-start",
|
|
743
|
+
id: this.openText
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
this.sawText = true;
|
|
747
|
+
parts.push({
|
|
748
|
+
type: "text-delta",
|
|
749
|
+
id: this.openText,
|
|
750
|
+
delta: text
|
|
751
|
+
});
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
case "tool_call": {
|
|
755
|
+
const id = e.call_id;
|
|
756
|
+
if (!id) return;
|
|
757
|
+
const web = webKey(e);
|
|
758
|
+
if (recovered) {
|
|
759
|
+
if (this.streamedWeb.has(id) || web && this.streamedWeb.has(web)) return;
|
|
760
|
+
} else if (web) {
|
|
761
|
+
this.streamedWeb.add(id);
|
|
762
|
+
this.streamedWeb.add(web);
|
|
763
|
+
}
|
|
764
|
+
const status = e.status ?? "running";
|
|
765
|
+
let call = this.openTools.get(id);
|
|
766
|
+
if (!call) {
|
|
767
|
+
if (status !== "running" && this.seen.has(id)) return;
|
|
768
|
+
this.closeBlocks(parts);
|
|
769
|
+
const projected = projectToolCall(e.name ?? "unknown", e.args);
|
|
770
|
+
call = {
|
|
771
|
+
toolName: projected.toolName,
|
|
772
|
+
dynamic: projected.dynamic
|
|
773
|
+
};
|
|
774
|
+
this.openTools.set(id, call);
|
|
775
|
+
this.seen.add(id);
|
|
776
|
+
parts.push({
|
|
777
|
+
type: "tool-call",
|
|
778
|
+
toolCallId: id,
|
|
779
|
+
toolName: projected.toolName,
|
|
780
|
+
input: JSON.stringify(this.relative(projected.input)),
|
|
781
|
+
providerExecuted: true,
|
|
782
|
+
...projected.dynamic ? { dynamic: true } : {},
|
|
783
|
+
...projected.toolName !== projected.nativeName ? { nativeName: projected.nativeName } : {}
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
if (status === "running") return;
|
|
787
|
+
this.openTools.delete(id);
|
|
788
|
+
const { output, error } = projectToolResult(call.toolName, status, e.result);
|
|
789
|
+
parts.push({
|
|
790
|
+
type: "tool-result",
|
|
791
|
+
toolCallId: id,
|
|
792
|
+
toolName: call.toolName,
|
|
793
|
+
result: output,
|
|
794
|
+
...error !== void 0 ? { isError: true } : {},
|
|
795
|
+
...call.dynamic ? { dynamic: true } : {}
|
|
796
|
+
});
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
case "usage":
|
|
800
|
+
this.closeBlocks(parts);
|
|
801
|
+
this.stepUsage = e.usage ?? {};
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
finish(parts, r) {
|
|
806
|
+
this.result = r;
|
|
807
|
+
this.agentId = r.agentId ?? this.agentId;
|
|
808
|
+
this.closeTools(parts, "unfinished");
|
|
809
|
+
if (!this.sawText && r.result) {
|
|
810
|
+
parts.push({
|
|
811
|
+
type: "text-start",
|
|
812
|
+
id: "text-result"
|
|
813
|
+
});
|
|
814
|
+
parts.push({
|
|
815
|
+
type: "text-delta",
|
|
816
|
+
id: "text-result",
|
|
817
|
+
delta: r.result
|
|
818
|
+
});
|
|
819
|
+
parts.push({
|
|
820
|
+
type: "text-end",
|
|
821
|
+
id: "text-result"
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
this.closeBlocks(parts);
|
|
825
|
+
this.closeStep(parts, r.usage);
|
|
826
|
+
const status = r.status ?? "unknown";
|
|
827
|
+
if (status !== "finished" && status !== "cancelled") parts.push({
|
|
828
|
+
type: "error",
|
|
829
|
+
error: new Error(r.error?.message || `The Cursor SDK run ended with status ${status}`)
|
|
830
|
+
});
|
|
831
|
+
this.finished = true;
|
|
832
|
+
parts.push({
|
|
833
|
+
type: "finish",
|
|
834
|
+
finishReason: {
|
|
835
|
+
unified: FINISH[status] ?? "other",
|
|
836
|
+
raw: status
|
|
837
|
+
},
|
|
838
|
+
totalUsage: toUsage(r.usage),
|
|
839
|
+
harnessMetadata: { cursorSdk: {
|
|
840
|
+
...r.agentId ? { agentId: r.agentId } : {},
|
|
841
|
+
...r.runId ? { runId: r.runId } : {},
|
|
842
|
+
...r.requestId ? { requestId: r.requestId } : {},
|
|
843
|
+
...r.model ? { model: r.model } : {},
|
|
844
|
+
...r.durationMs !== void 0 ? { durationMs: r.durationMs } : {},
|
|
845
|
+
...r.usage ? { usage: { ...r.usage } } : {},
|
|
846
|
+
...this.unreportedTools.length ? { unreportedTools: [...this.unreportedTools] } : {}
|
|
847
|
+
} }
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
fail(parts, message, raw) {
|
|
851
|
+
this.closeTools(parts, "interrupted");
|
|
852
|
+
this.closeBlocks(parts);
|
|
853
|
+
this.closeStep(parts);
|
|
854
|
+
parts.push({
|
|
855
|
+
type: "error",
|
|
856
|
+
error: new Error(message)
|
|
857
|
+
});
|
|
858
|
+
this.finished = true;
|
|
859
|
+
parts.push({
|
|
860
|
+
type: "finish",
|
|
861
|
+
finishReason: {
|
|
862
|
+
unified: "error",
|
|
863
|
+
raw
|
|
864
|
+
},
|
|
865
|
+
totalUsage: toUsage(void 0),
|
|
866
|
+
harnessMetadata: this.unrecovered()
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
/** The turn's one step, with the `usage` event's counts, else the result's when it has them. */
|
|
870
|
+
closeStep(parts, resultUsage) {
|
|
871
|
+
const usage = this.stepUsage ?? resultUsage;
|
|
872
|
+
if (!usage && !this.stepHasContent && !parts.some(isContent)) return;
|
|
873
|
+
parts.push({
|
|
874
|
+
type: "finish-step",
|
|
875
|
+
finishReason: usage ? {
|
|
876
|
+
unified: "stop",
|
|
877
|
+
raw: this.stepUsage ? "turn-ended" : "result"
|
|
878
|
+
} : {
|
|
879
|
+
unified: "other",
|
|
880
|
+
raw: "interrupted"
|
|
881
|
+
},
|
|
882
|
+
usage: toUsage(usage)
|
|
883
|
+
});
|
|
884
|
+
this.stepUsage = void 0;
|
|
885
|
+
this.stepHasContent = false;
|
|
886
|
+
}
|
|
887
|
+
/** A turn that ended before its result never had its web calls recovered. */
|
|
888
|
+
unrecovered() {
|
|
889
|
+
return { cursorSdk: { unreportedTools: Object.values(CURSOR_SDK_RECOVERED_TOOLS) } };
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
//#endregion
|
|
893
|
+
//#region ../harness-cursor-sdk/src/cursor-sdk-harness.ts
|
|
894
|
+
const CURSOR_SDK_HARNESS_ID = "cursor-sdk";
|
|
895
|
+
/** Where harness-placed skills go, under `~/` (the SDK's `user` layer reads `~/.agents/skills`). */
|
|
896
|
+
const CURSOR_SDK_HOME_SKILLS_DIR = ".agents/skills";
|
|
897
|
+
const lifecycleData = z.object({ agentId: z.string().nullable() });
|
|
898
|
+
function promptText(prompt, instructions) {
|
|
899
|
+
const content = typeof prompt === "string" ? prompt : prompt.content;
|
|
900
|
+
const text = typeof content === "string" ? content : content.map((part) => {
|
|
901
|
+
if (part.type !== "text") throw new HarnessCapabilityUnsupportedError({
|
|
902
|
+
message: `The Cursor SDK adapter takes text prompts only (got a ${part.type} part)`,
|
|
903
|
+
harnessId: CURSOR_SDK_HARNESS_ID
|
|
904
|
+
});
|
|
905
|
+
return part.text;
|
|
906
|
+
}).join("\n");
|
|
907
|
+
if (!instructions) return text;
|
|
908
|
+
return [
|
|
909
|
+
"<session-guidance>",
|
|
910
|
+
"This block is operating guidance from the harness, not user-authored content.",
|
|
911
|
+
"<instructions>",
|
|
912
|
+
instructions,
|
|
913
|
+
"</instructions>",
|
|
914
|
+
"</session-guidance>",
|
|
915
|
+
text
|
|
916
|
+
].join("\n");
|
|
917
|
+
}
|
|
918
|
+
const unsupported = (what) => new HarnessCapabilityUnsupportedError({
|
|
919
|
+
message: `The Cursor SDK adapter has no ${what}`,
|
|
920
|
+
harnessId: CURSOR_SDK_HARNESS_ID
|
|
921
|
+
});
|
|
922
|
+
const lines = (stream) => createInterface({
|
|
923
|
+
input: Readable.fromWeb(stream),
|
|
924
|
+
crlfDelay: Infinity
|
|
925
|
+
});
|
|
926
|
+
function startRunner(proc) {
|
|
927
|
+
let stderr = "";
|
|
928
|
+
const drained = (async () => {
|
|
929
|
+
for await (const line of lines(proc.stderr)) stderr = `${stderr}${line}\n`.slice(-4e3);
|
|
930
|
+
})().catch(() => {});
|
|
931
|
+
const runner = {
|
|
932
|
+
proc,
|
|
933
|
+
stdout: lines(proc.stdout)[Symbol.asyncIterator](),
|
|
934
|
+
stderr: async () => {
|
|
935
|
+
await Promise.race([drained, new Promise((r) => setTimeout(r, 1e3).unref())]);
|
|
936
|
+
return stderr;
|
|
937
|
+
},
|
|
938
|
+
exited: false
|
|
939
|
+
};
|
|
940
|
+
Promise.resolve(proc.wait()).then(() => runner.exited = true, () => runner.exited = true);
|
|
941
|
+
return runner;
|
|
942
|
+
}
|
|
943
|
+
const canBroker = (sandbox) => "addRequestTransformations" in sandbox && typeof sandbox.addRequestTransformations === "function";
|
|
944
|
+
/**
|
|
945
|
+
* Cursor's agent through the Cursor TypeScript SDK's local runtime, as an AI SDK harness. The
|
|
946
|
+
* agent loop runs in a Node process in the sandbox that lives across the session's turns (a new
|
|
947
|
+
* one resumes the agent by id from a JSONL store), so its tools act on the sandbox's files;
|
|
948
|
+
* inference is Cursor's hosted models. The SDK reports token usage for every turn.
|
|
949
|
+
*/
|
|
950
|
+
function createCursorSdk(settings = {}) {
|
|
951
|
+
return {
|
|
952
|
+
specificationVersion: "harness-v1",
|
|
953
|
+
harnessId: CURSOR_SDK_HARNESS_ID,
|
|
954
|
+
builtinTools: CURSOR_SDK_BUILTIN_TOOLS,
|
|
955
|
+
lifecycleStateSchema: lifecycleData,
|
|
956
|
+
getBootstrap: getCursorSdkBootstrap,
|
|
957
|
+
doStart: (options) => startSession(settings, options)
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
async function startSession(settings, options) {
|
|
961
|
+
const { sandboxSession: sandbox, sessionWorkDir, sessionId, abortSignal } = options;
|
|
962
|
+
const credentials = resolveCursorSdkCredentials(settings.auth);
|
|
963
|
+
let sandboxKey = credentials.apiKey;
|
|
964
|
+
if (canBroker(sandbox)) {
|
|
965
|
+
sandboxKey = generateSandboxCredentialPlaceholder();
|
|
966
|
+
await sandbox.addRequestTransformations(createCursorSdkRequestTransformations({
|
|
967
|
+
credentials,
|
|
968
|
+
placeholder: sandboxKey,
|
|
969
|
+
...options.headers ? { headers: options.headers } : {}
|
|
970
|
+
}));
|
|
971
|
+
} else if (settings.requireCredentialBrokering) throw new Error(`The sandbox can't broker ${CURSOR_SDK_CREDENTIAL_ENV} (it has no request transformations), and requireCredentialBrokering is set, so the key stays out of it.`);
|
|
972
|
+
else warnCredentialBrokeringUnavailable({
|
|
973
|
+
environment: { [CURSOR_SDK_CREDENTIAL_ENV]: credentials.apiKey },
|
|
974
|
+
forwardedEnvironment: { [CURSOR_SDK_CREDENTIAL_ENV]: sandboxKey },
|
|
975
|
+
credentialEnvironmentVariables: [CURSOR_SDK_CREDENTIAL_ENV]
|
|
976
|
+
});
|
|
977
|
+
const catalogSetting = settings.modelCatalog ?? "fetch";
|
|
978
|
+
const catalog = catalogSetting === "fetch" ? await fetchCursorModelCatalog(credentials, abortSignal ? { signal: abortSignal } : {}) : catalogSetting === false ? void 0 : [...catalogSetting];
|
|
979
|
+
const home = await resolveSandboxHomeDir({
|
|
980
|
+
sandbox,
|
|
981
|
+
abortSignal
|
|
982
|
+
});
|
|
983
|
+
const stateDir = harnessStateDirectoryPath({ sandboxHomeDir: home });
|
|
984
|
+
const runnerPath = posix.join(stateDir, CURSOR_SDK_BOOTSTRAP_DIR, CURSOR_SDK_RUNNER_PATH);
|
|
985
|
+
const sessionDir = harnessSessionDataDirectoryPath({
|
|
986
|
+
stateDirectory: stateDir,
|
|
987
|
+
sessionId
|
|
988
|
+
});
|
|
989
|
+
const storeDir = posix.join(sessionDir, "agent-store");
|
|
990
|
+
const resumed = lifecycleData.safeParse(options.resumeFrom?.data);
|
|
991
|
+
let agentId = resumed.success ? resumed.data.agentId : null;
|
|
992
|
+
let turns = 0;
|
|
993
|
+
let runner;
|
|
994
|
+
let ended = false;
|
|
995
|
+
const state = () => ({
|
|
996
|
+
type: "resume-session",
|
|
997
|
+
harnessId: CURSOR_SDK_HARNESS_ID,
|
|
998
|
+
specificationVersion: "harness-v1",
|
|
999
|
+
data: { agentId }
|
|
1000
|
+
});
|
|
1001
|
+
const stopRunner = async () => {
|
|
1002
|
+
const r = runner;
|
|
1003
|
+
runner = void 0;
|
|
1004
|
+
await r?.proc.kill();
|
|
1005
|
+
};
|
|
1006
|
+
const stop = async () => {
|
|
1007
|
+
ended = true;
|
|
1008
|
+
await stopRunner();
|
|
1009
|
+
};
|
|
1010
|
+
return {
|
|
1011
|
+
sessionId,
|
|
1012
|
+
isResume: options.resumeFrom !== void 0,
|
|
1013
|
+
async doPromptTurn(turn) {
|
|
1014
|
+
if (ended) throw new Error("This Cursor SDK session has ended");
|
|
1015
|
+
if (turn.tools.length > 0) throw unsupported("host-executed tools");
|
|
1016
|
+
if (turn.responseFormat?.type === "json") throw unsupported("structured output");
|
|
1017
|
+
const n = ++turns;
|
|
1018
|
+
await writeSkills({
|
|
1019
|
+
sandbox,
|
|
1020
|
+
homePath: home,
|
|
1021
|
+
skillsDir: CURSOR_SDK_HOME_SKILLS_DIR,
|
|
1022
|
+
skills: turn.skills,
|
|
1023
|
+
...turn.abortSignal ? { abortSignal: turn.abortSignal } : {}
|
|
1024
|
+
});
|
|
1025
|
+
const promptFile = posix.join(sessionDir, `prompt-${n}.txt`);
|
|
1026
|
+
await sandbox.writeTextFile({
|
|
1027
|
+
path: promptFile,
|
|
1028
|
+
content: promptText(turn.prompt, turn.instructions),
|
|
1029
|
+
abortSignal: turn.abortSignal
|
|
1030
|
+
});
|
|
1031
|
+
const model = turn.model ? withParams(turn.model, settings.modelParams, catalog) : void 0;
|
|
1032
|
+
const turnFile = posix.join(sessionDir, `turn-${n}.json`);
|
|
1033
|
+
await sandbox.writeTextFile({
|
|
1034
|
+
path: turnFile,
|
|
1035
|
+
content: JSON.stringify({
|
|
1036
|
+
cwd: sessionWorkDir,
|
|
1037
|
+
storeDir,
|
|
1038
|
+
promptFile,
|
|
1039
|
+
agentId: agentId ?? void 0,
|
|
1040
|
+
model,
|
|
1041
|
+
settingSources: settings.settingSources ?? ["project", "user"],
|
|
1042
|
+
sandbox: settings.sdkSandbox === true,
|
|
1043
|
+
sdkVersion: CURSOR_SDK_VERSION,
|
|
1044
|
+
...settings.mcpServers && Object.keys(settings.mcpServers).length > 0 ? { mcpServers: settings.mcpServers } : {}
|
|
1045
|
+
}),
|
|
1046
|
+
abortSignal: turn.abortSignal
|
|
1047
|
+
});
|
|
1048
|
+
await sandbox.writeTextFile({
|
|
1049
|
+
path: posix.join(sessionDir, cursorSdkTurnReady(n)),
|
|
1050
|
+
content: "",
|
|
1051
|
+
abortSignal: turn.abortSignal
|
|
1052
|
+
});
|
|
1053
|
+
if (runner?.exited) runner = void 0;
|
|
1054
|
+
if (!runner) {
|
|
1055
|
+
const env = {
|
|
1056
|
+
...settings.env,
|
|
1057
|
+
[CURSOR_SDK_CREDENTIAL_ENV]: sandboxKey,
|
|
1058
|
+
...catalog ? { [CURSOR_SDK_MODEL_CATALOG_ENV]: JSON.stringify(catalogFor(catalog, turn.model)) } : {}
|
|
1059
|
+
};
|
|
1060
|
+
runner = startRunner(await sandbox.spawn({
|
|
1061
|
+
command: `exec node ${shellQuote(runnerPath)} ${shellQuote(sessionDir)} ${n}`,
|
|
1062
|
+
workingDirectory: sessionWorkDir,
|
|
1063
|
+
env
|
|
1064
|
+
}));
|
|
1065
|
+
}
|
|
1066
|
+
const current = runner;
|
|
1067
|
+
const translator = new CursorSdkStreamTranslator({
|
|
1068
|
+
agentId: agentId ?? void 0,
|
|
1069
|
+
workDir: sessionWorkDir
|
|
1070
|
+
});
|
|
1071
|
+
const emit = (parts) => {
|
|
1072
|
+
for (const part of parts) turn.emit(part);
|
|
1073
|
+
};
|
|
1074
|
+
const onAbort = () => void stopRunner();
|
|
1075
|
+
if (turn.abortSignal?.aborted) onAbort();
|
|
1076
|
+
turn.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1077
|
+
return {
|
|
1078
|
+
submitToolResult: () => Promise.reject(unsupported("host-executed tools")),
|
|
1079
|
+
done: (async () => {
|
|
1080
|
+
try {
|
|
1081
|
+
while (!translator.finished) {
|
|
1082
|
+
const next = await current.stdout.next();
|
|
1083
|
+
if (next.done) break;
|
|
1084
|
+
emit(translator.push(next.value));
|
|
1085
|
+
}
|
|
1086
|
+
if (!translator.finished) {
|
|
1087
|
+
if (runner === current) runner = void 0;
|
|
1088
|
+
const { exitCode } = await current.proc.wait();
|
|
1089
|
+
emit(translator.end({
|
|
1090
|
+
exitCode,
|
|
1091
|
+
stderr: await current.stderr(),
|
|
1092
|
+
aborted: turn.abortSignal?.aborted === true
|
|
1093
|
+
}));
|
|
1094
|
+
}
|
|
1095
|
+
} finally {
|
|
1096
|
+
turn.abortSignal?.removeEventListener("abort", onAbort);
|
|
1097
|
+
agentId = translator.agentId ?? agentId;
|
|
1098
|
+
}
|
|
1099
|
+
})()
|
|
1100
|
+
};
|
|
1101
|
+
},
|
|
1102
|
+
doCompact: () => Promise.reject(unsupported("manual compaction")),
|
|
1103
|
+
doContinueTurn: () => Promise.reject(unsupported("turn continuation")),
|
|
1104
|
+
doSuspendTurn: () => Promise.reject(unsupported("turn suspension")),
|
|
1105
|
+
async doDetach() {
|
|
1106
|
+
await stop();
|
|
1107
|
+
return state();
|
|
1108
|
+
},
|
|
1109
|
+
async doStop() {
|
|
1110
|
+
await stop();
|
|
1111
|
+
return state();
|
|
1112
|
+
},
|
|
1113
|
+
doDestroy: stop
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* The model with every parameter set: the ones given, then the rest from the catalog's default
|
|
1118
|
+
* variant (the SDK's own choice for an unset parameter), so nothing is left for the SDK to pick.
|
|
1119
|
+
*/
|
|
1120
|
+
function withParams(id, given, catalog) {
|
|
1121
|
+
const entry = catalog?.find((m) => m.id === id || m.aliases?.includes(id));
|
|
1122
|
+
const variants = Array.isArray(entry?.variants) ? entry.variants : [];
|
|
1123
|
+
const fallback = (variants.find((v) => v.isDefault) ?? variants[0])?.params ?? [];
|
|
1124
|
+
const params = [...Object.entries(given ?? {}).map(([pid, value]) => ({
|
|
1125
|
+
id: pid,
|
|
1126
|
+
value
|
|
1127
|
+
})), ...fallback.filter((p) => !given || !(p.id in given))];
|
|
1128
|
+
return params.length ? {
|
|
1129
|
+
id,
|
|
1130
|
+
params
|
|
1131
|
+
} : { id };
|
|
1132
|
+
}
|
|
1133
|
+
//#endregion
|
|
1134
|
+
export { createCursorSdkRequestTransformations as C, resolveCursorSdkCredentials as E, catalogFor as S, fetchCursorModelCatalog as T, storedShapeProblems as _, toUsage as a, CURSOR_SDK_CREDENTIAL_ENV as b, projectToolResult as c, CURSOR_SDK_RECOVERED_TOOLS as d, CURSOR_SDK_RUNNER as f, getCursorSdkBootstrap as g, cursorSdkInstallScript as h, CursorSdkStreamTranslator as i, CURSOR_SDK_BOOTSTRAP_DIR as l, CURSOR_SDK_VERSION as m, CURSOR_SDK_HOME_SKILLS_DIR as n, CURSOR_SDK_BUILTIN_TOOLS as o, CURSOR_SDK_RUNNER_PATH as p, createCursorSdk as r, projectToolCall as s, CURSOR_SDK_HARNESS_ID as t, CURSOR_SDK_MIN_NODE as u, storedWebCalls as v, cursorSdkEndpoints as w, CURSOR_SDK_MODEL_CATALOG_ENV as x, CURSOR_API_URL as y };
|
|
1135
|
+
|
|
1136
|
+
//# sourceMappingURL=src-Cmp-7YrD.js.map
|