@xiaoso/dsh-tool-plus 0.1.16 → 0.1.17-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/lib/{abort-DZ54CaKU.mjs → abort-BTblp0Rt.mjs} +2 -2
  2. package/lib/{alibaba-coding-plan-DODpkcqi.mjs → alibaba-coding-plan-t3R4YKCB.mjs} +3 -3
  3. package/lib/{alibaba-token-plan-C6lpKCda.mjs → alibaba-token-plan-CO4djG1y.mjs} +4 -4
  4. package/lib/{alibaba-token-plan-CHGyWOfX.mjs → alibaba-token-plan-CUjYJX0-.mjs} +1 -1
  5. package/lib/{anthropic-Dv_GGX6g.mjs → anthropic-nf7w6so1.mjs} +48 -21
  6. package/lib/{api-key-validation-MnMf_ihX.mjs → api-key-validation-C_tZ7Y9a.mjs} +2 -2
  7. package/lib/behavior-BwvTUtag.mjs +111 -0
  8. package/lib/{browser-render-BZXt548F.mjs → browser-render-BQaEVK_n.mjs} +4 -4
  9. package/lib/{reference-CtvM4zLU.mjs → bundled-references-CrQ4dQcT.mjs} +57 -4
  10. package/lib/cascade-BWjRTD1q.mjs +30265 -0
  11. package/lib/{cline-pass-BT9CBEOj.mjs → cline-pass-Bjm-O8GG.mjs} +2 -2
  12. package/lib/cloudflare-CKXRJ0l9.mjs +37 -0
  13. package/lib/{cloudflare-ai-gateway-DReN6ohJ.mjs → cloudflare-ai-gateway-BpnBSjwV.mjs} +8 -4
  14. package/lib/cloudflare-workers-ai-odj9XswL.mjs +32 -0
  15. package/lib/collapse-_nkb2dn7.mjs +1945 -0
  16. package/lib/{coreweave-C82HrLpb.mjs → coreweave-CGDZOD8-.mjs} +1 -1
  17. package/lib/{coreweave-C68UgRQO.mjs → coreweave-DwffcgW5.mjs} +4 -4
  18. package/lib/cursor-BgOuV7Qf.mjs +794 -0
  19. package/lib/{cursor-_gHHq-Dq.mjs → cursor-CF1iHHbv.mjs} +68 -19
  20. package/lib/{cursor-proto-Cr_WEMuv.mjs → cursor-proto-BuNTjX3L.mjs} +823 -23
  21. package/lib/{devin-QpfTSl9H.mjs → devin-7OEeEb80.mjs} +112 -31
  22. package/lib/{devin-proto-DYhp4Ty6.mjs → devin-proto-C-QreeAv.mjs} +71 -5
  23. package/lib/{dom-DG3sPESc.mjs → dom-BmbZv4v9.mjs} +598 -182
  24. package/lib/{error-O63pC8zg.mjs → error-8LVo7tug.mjs} +725 -104
  25. package/lib/factory-droid-ChhmsGWb.mjs +71 -0
  26. package/lib/{fs-promises-shim-B2YFWkcd.mjs → fs-promises-shim-CANAxFWh.mjs} +10 -1
  27. package/lib/{github-copilot-BMEcw8N1.mjs → github-copilot-QwevdT30.mjs} +4 -4
  28. package/lib/{github-copilot-headers-DnjGEbko.mjs → github-copilot-headers-CAUZ0rUD.mjs} +138 -26
  29. package/lib/{gitlab-duo-DnHqJjFY.mjs → gitlab-duo-DigV7k_o.mjs} +2 -2
  30. package/lib/{google-antigravity-B1mSckrK.mjs → google-antigravity-D-oe0r8A.mjs} +6 -7
  31. package/lib/{google-gemini-cli-DJwThCCn.mjs → google-gemini-cli-CQBHgGJT.mjs} +5 -6
  32. package/lib/{google-oauth-shared-BctLpVUN.mjs → google-oauth-shared-CvgVIUwJ.mjs} +2 -2
  33. package/lib/google-validation-CdvRj4ec.mjs +126 -0
  34. package/lib/id-EJwVLDHE.mjs +2548 -0
  35. package/lib/index.mjs +248 -258
  36. package/lib/{kilo-BEgEwMJQ.mjs → kilo-5r6OffCa.mjs} +2 -2
  37. package/lib/{kimi-T9NldEIJ.mjs → kimi-DOOtbXmm.mjs} +9 -10
  38. package/lib/{launch-BIWNyrBB.mjs → launch-DkAjib7h.mjs} +1 -1
  39. package/lib/{markit-Dvp2JPdT.mjs → markit-CCcyeAds.mjs} +359 -140
  40. package/lib/{models-DnXMYGcJ.mjs → models-DCpDT_im.mjs} +199203 -112275
  41. package/lib/{muse-code-CNvqqAp6.mjs → muse-code-CYf8zj_B.mjs} +5 -5
  42. package/lib/{openai-codex-D5-CyTxa.mjs → openai-codex-Cj-TvSFh.mjs} +13 -10
  43. package/lib/{perplexity-A2PEoGaC.mjs → perplexity-Dh_Qgsmx.mjs} +68 -25
  44. package/lib/{pkce-BYjmugmw.mjs → pkce-DO-jBJLV.mjs} +1 -1
  45. package/lib/pricing-DusBEhwm.mjs +593 -0
  46. package/lib/{readability-uGhqb4GC.mjs → readability-XHK0o3bv.mjs} +287 -27
  47. package/lib/{scrapers-C1ujjV8k.mjs → scrapers-B6PADOVm.mjs} +13 -13
  48. package/lib/slugify-ynYWBIA5.mjs +11 -0
  49. package/lib/snowflake-B_jap6lz.mjs +666 -0
  50. package/lib/snowflake-Cu1ZyOp6.mjs +151 -0
  51. package/lib/{src-AELUUb5U.mjs → src-BhinCSpi.mjs} +1172 -458
  52. package/lib/{stream-AVOoe5Hj.mjs → stream-D2ivg3GH.mjs} +3740 -5311
  53. package/lib/{turndown-D6fof1OX.mjs → turndown-Vya8SavL.mjs} +82 -30
  54. package/lib/types/tools/hashline/native/index.d.ts +2 -2
  55. package/lib/types/tools/hashline/native/policy.d.ts +19 -21
  56. package/lib/types/tools/omp/session/agent-storage.d.ts +3 -3
  57. package/lib/types/tools/shared/bun-named-shim.d.ts +5 -0
  58. package/lib/{types-BLrLohwK.mjs → types-tH1v9uRm.mjs} +2 -2
  59. package/lib/{utils-cZX3f_Ls.mjs → utils-C_Ncya1r.mjs} +1737 -509
  60. package/lib/utils-D2tK38xq.mjs +34006 -0
  61. package/lib/{utils-I4I8azHN.mjs → utils-DrDwmNtu.mjs} +8 -3
  62. package/lib/{xai-oauth-CbeldUSV.mjs → xai-oauth-DLLzX6my.mjs} +4 -4
  63. package/lib/{xiaomi-Dl3VSs04.mjs → xiaomi-CqK-KivK.mjs} +2 -2
  64. package/lib/{zai-FyjGSypr.mjs → zai-DoJ2xPIH.mjs} +2 -2
  65. package/lib/{zip-DRrycScC.mjs → zip-DG1mUV6X.mjs} +1 -1
  66. package/package.json +8 -8
  67. package/lib/amazon-bedrock-Dx0rr9dY.mjs +0 -996
  68. package/lib/anthropic-ClCw0PV_.mjs +0 -3624
  69. package/lib/anthropic-wire-lbGAFzYb.mjs +0 -927
  70. package/lib/azure-openai-responses-WE_bgFWp.mjs +0 -285
  71. package/lib/bundled-references-DY3EJxZV.mjs +0 -56
  72. package/lib/cloudflare-ai-gateway-CrMa97Ap.mjs +0 -44
  73. package/lib/collapse-CkMNRNr7.mjs +0 -2304
  74. package/lib/cursor-C6jaIJDg.mjs +0 -283
  75. package/lib/cursor-D-XZYDf_.mjs +0 -4544
  76. package/lib/deterministic-id-CUEIMSqR.mjs +0 -17
  77. package/lib/devin-ExGIghmx.mjs +0 -69
  78. package/lib/devin-u0ljw5P6.mjs +0 -628
  79. package/lib/effort-BdtQMN-_.mjs +0 -23
  80. package/lib/event-stream-BKEtb45Y.mjs +0 -167
  81. package/lib/gemini-headers-Co9cVvex.mjs +0 -54
  82. package/lib/google-auth-qAN-6DMW.mjs +0 -232
  83. package/lib/google-gemini-cli-B2Aw1S5w.mjs +0 -844
  84. package/lib/google-lTxuq28n.mjs +0 -38
  85. package/lib/google-shared-Y01j9xOr.mjs +0 -856
  86. package/lib/google-validation-BEkNs-Bx.mjs +0 -18
  87. package/lib/google-vertex-DcM-FRC5.mjs +0 -99
  88. package/lib/hosts-00rjRzUD.mjs +0 -203
  89. package/lib/id-AL5obQzR.mjs +0 -71
  90. package/lib/idle-iterator-D0ZXEcUq.mjs +0 -366
  91. package/lib/ollama-6R0oXZks.mjs +0 -574
  92. package/lib/openai-codex-responses-C8bnBWOe.mjs +0 -3232
  93. package/lib/openai-completions-DNr3mUsn.mjs +0 -1430
  94. package/lib/openai-configuration-update-xMYUjaK_.mjs +0 -150
  95. package/lib/openai-reasoning-fallback-Ju-5pSR8.mjs +0 -380
  96. package/lib/openai-responses-CjpafAEr.mjs +0 -861
  97. package/lib/protobuf-BMgDYu_U.mjs +0 -717
  98. package/lib/provider-response-SqKSkkUc.mjs +0 -22
  99. package/lib/proxy-DxvCQOCA.mjs +0 -230
  100. package/lib/request-debug-BwPOqN_j.mjs +0 -183
  101. package/lib/sanitize-text-BCf5KmCf.mjs +0 -33
  102. package/lib/sse-debug-B-tkLCPW.mjs +0 -11
  103. package/lib/stream-markup-healing-CN3RsqG9.mjs +0 -267
  104. package/lib/strict-tool-validation-CBTz1dAA.mjs +0 -105
  105. package/lib/taxonomy-DSIM99rS.mjs +0 -15392
  106. package/lib/thinking-ngMwf_1Y.mjs +0 -336
  107. package/lib/type-guards-CttAANEx.mjs +0 -11
  108. package/lib/utils-CE3DSebh.mjs +0 -11763
  109. package/lib/vision-guard-BCpXWUj3.mjs +0 -77
@@ -1,4544 +0,0 @@
1
- import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
- __installBunShim();
3
- import { n as fs_promises_shim_exports } from "./fs-promises-shim-B2YFWkcd.mjs";
4
- import { C as warn, M as truncate, N as $env, _ as parseStreamingJson, b as debug, g as parseJsonWithRepair, v as parseStreamingJsonThrottled } from "./src-AELUUb5U.mjs";
5
- import { _ as AbortError, c as finalize, h as MissingApiKeyError, r as ValidationError, s as ProviderResponseError } from "./error-O63pC8zg.mjs";
6
- import { A as toolWireSchema, C as kStreamingLastParseLen, I as sanitizeSchemaForCursor, S as kStreamingEnvelopeId, b as kStreamingBlockIndex, g as clearStreamingPartialJson, o as normalizeSystemPrompts, s as normalizeToolCallId, v as kCursorExecResolved, w as kStreamingPartialJson, x as kStreamingBlockKind } from "./utils-cZX3f_Ls.mjs";
7
- import { t as sanitizeText } from "./sanitize-text-BCf5KmCf.mjs";
8
- import { n as isRecord$1 } from "./type-guards-CttAANEx.mjs";
9
- import { n as classifyModel, r as collapseVariantId } from "./taxonomy-DSIM99rS.mjs";
10
- import { n as getProxyForUrl, t as connectProxiedSocket } from "./proxy-DxvCQOCA.mjs";
11
- import { n as THINKING_EFFORTS } from "./effort-BdtQMN-_.mjs";
12
- import { t as calculateCost } from "./models-DnXMYGcJ.mjs";
13
- import { t as AssistantMessageEventStream } from "./event-stream-BKEtb45Y.mjs";
14
- import { n as isRequestDebugEnabled, t as createRequestDebugSession } from "./request-debug-BwPOqN_j.mjs";
15
- import { i as fromBinary, n as decodeJsonValue, o as toBinary, r as encodeJsonValue, t as create } from "./protobuf-BMgDYu_U.mjs";
16
- import { $ as ForceBackgroundSubagentResultSchema, $n as SubagentResultSchema, $t as PiFindExecSuccessSchema, A as CursorRuleTypeGlobalSchema, An as RequestContextSuccessSchema, At as McpRejectedSchema, B as ExaFetchRequestResponseSchema, Bn as ShellResultSchema, Bt as McpToolNotFoundSchema, C as ConversationStateStructureSchema, Cn as ReadRejectedSchema, Ct as LsResultSchema, D as CreatePlanRequestResponseSchema, Dn as RecordScreenResultSchema, Dt as McpArgsSchema, E as CreatePlanErrorSchema, En as RecordScreenFailureSchema, Et as McpApprovedSchema, F as DeleteSuccessSchema, Fn as SelectedImageSchema, Ft as McpSuccessSchema, G as ExecClientMessageSchema, Gn as ShellStreamStdoutSchema, Gt as PiBashExecResultSchema, H as ExaSearchRequestResponseSchema, Hn as ShellStreamSchema, Ht as McpToolResultSchema, I as DiagnosticsErrorSchema, In as SetBlobResultSchema, It as McpTextContentSchema, J as ExecuteHookResponseSchema, Jn as SmartModeClassifierResultSchema, Jt as PiEditExecRejectedSchema, K as ExecClientStreamCloseSchema, Kn as ShellSuccessSchema, Kt as PiBashExecSuccessSchema, L as DiagnosticsRejectedSchema, Ln as ShellAllowlistPrecheckResultSchema, Lt as McpToolCallSchema, M as DeleteErrorSchema, Mn as RequestedModel_ModelParameterbytesSchema, Mt as McpStateExecResultSchema, N as DeleteRejectedSchema, Nn as ResumeActionSchema, Nt as McpStateServerSchema, O as CreatePlanResultSchema, On as RequestContextResultSchema, Ot as McpErrorSchema, P as DeleteResultSchema, Pn as SelectedContextSchema, Pt as McpStateSuccessSchema, Q as ForceBackgroundShellResultSchema, Qn as SubagentErrorSchema, Qt as PiFindExecResultSchema, R as DiagnosticsResultSchema, Rn as ShellFailureSchema, Rt as McpToolDefinitionSchema, S as ConversationSearchResultSchema, Sn as ReadMcpResourceSuccessSchema, St as LsRejectedSchema, T as ConversationTurnStructureSchema, Tn as ReadSuccessSchema, Tt as McpAllowlistPrecheckResultSchema, U as ExaSearchRequestResponse_ApprovedSchema, Un as ShellStreamStartSchema, Ut as ModelDetailsSchema, V as ExaFetchRequestResponse_ApprovedSchema, Vn as ShellStreamExitSchema, Vt as McpToolResultContentItemSchema, W as ExecClientControlMessageSchema, Wn as ShellStreamStderrSchema, Wt as PiBashExecErrorSchema, X as FetchErrorSchema, Xn as SubagentAwaitNotFoundSchema, Xt as PiEditExecSuccessSchema, Y as ExecuteHookResultSchema, Yn as StopRequestResponseSchema, Yt as PiEditExecResultSchema, Z as FetchResultSchema, Zn as SubagentAwaitResultSchema, Zt as PiFindExecErrorSchema, _ as ClientHeartbeatSchema, _n as PreToolUseRequestResponseSchema, _r as WriteShellStdinResultSchema, _t as ListMcpResourcesExecResult_McpResourceSchema, a as AgentRunRequestSchema, an as PiLsExecSuccessSchema, ar as ToolCallSchema, at as GrepCountResultSchema, b as ConversationActionSchema, bn as ReadMcpResourceExecResultSchema, bt as LsDirectoryTreeNode_FileSchema, c as AgentStoreConflictResultSchema, cn as PiReadExecSuccessSchema, cr as WebFetchAllowlistPrecheckResultSchema, ct as GrepFileMatchSchema, d as AskQuestionResultSchema, dn as PiWriteExecRejectedSchema, dr as WebSearchRequestResponseSchema, dt as GrepSuccessSchema, en as PiGrepExecErrorSchema, er as SubagentStartRequestResponseSchema, et as GetBlobResultSchema, f as AssistantMessageSchema, fn as PiWriteExecResultSchema, fr as WebSearchRequestResponse_ApprovedSchema, ft as GrepUnionResultSchema, g as CanvasDiagnosticsResultSchema, gn as PreCompactRequestResponseSchema, gr as WriteShellStdinErrorSchema, gt as ListMcpResourcesExecResultSchema, h as CanvasDiagnosticsErrorSchema, hn as PostToolUseRequestResponseSchema, hr as WriteResultSchema, ht as ListMcpResourcesErrorSchema, i as AgentConversationTurnStructureSchema, in as PiLsExecResultSchema, ir as ThinkingMessageSchema, it as GrepContentResultSchema, j as CursorRuleTypeSchema, jn as RequestedModelSchema, jt as McpResultSchema, k as CursorRuleSchema, kn as RequestContextSchema, kt as McpImageContentSchema, l as AskQuestionInteractionResponseSchema, ln as PiTruncationSchema, lr as WebFetchRequestResponseSchema, lt as GrepFilesResultSchema, m as BeforeSubmitPromptRequestResponseSchema, mn as PostToolUseFailureRequestResponseSchema, mr as WriteRejectedSchema, mt as KvClientMessageSchema, n as AfterAgentThoughtRequestResponseSchema, nn as PiGrepExecSuccessSchema, nr as SwitchModeRequestResponseSchema, o as AgentServerMessageSchema, on as PiReadExecErrorSchema, or as UserMessageActionSchema, ot as GrepErrorSchema, p as BackgroundShellSpawnResultSchema, pn as PiWriteExecSuccessSchema, pr as WriteErrorSchema, pt as InteractionResponseSchema, q as ExecClientThrowSchema, qn as SmartModeClassifierErrorSchema, qt as PiEditExecErrorSchema, r as AgentClientMessageSchema, rn as PiLsExecErrorSchema, rr as SwitchModeRequestResponse_RejectedSchema, rt as GrepContentMatchSchema, s as AgentStoreConflictErrorSchema, sn as PiReadExecResultSchema, sr as UserMessageSchema, st as GrepFileCountSchema, t as AfterAgentResponseRequestResponseSchema, tn as PiGrepExecResultSchema, tr as SubagentStopRequestResponseSchema, u as AskQuestionRejectedSchema, un as PiWriteExecErrorSchema, ur as WebFetchRequestResponse_ApprovedSchema, ut as GrepResultSchema, v as ComputerUseErrorSchema, vn as ReadErrorSchema, vr as WriteSuccessSchema, vt as ListMcpResourcesSuccessSchema, w as ConversationStepSchema, wn as ReadResultSchema, wt as LsSuccessSchema, x as ConversationSearchErrorSchema, xn as ReadMcpResourceNotFoundSchema, xt as LsErrorSchema, y as ComputerUseResultSchema, yn as ReadMcpResourceErrorSchema, yt as LsDirectoryTreeNodeSchema, z as DiagnosticsSuccessSchema, zn as ShellRejectedSchema, zt as McpToolErrorSchema } from "./cursor-proto-Cr_WEMuv.mjs";
17
- import { t as deterministicUuid } from "./deterministic-id-CUEIMSqR.mjs";
18
- import * as path$1 from "node:path";
19
- import { createHash } from "node:crypto";
20
- import http2 from "node:http2";
21
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/providers/connect-error-detail.ts
22
- /**
23
- * Connect-protocol end-stream error formatting.
24
- *
25
- * A Connect end-of-stream trailer carries an error object of the shape
26
- * code / message / details. Some backends collapse the useful part into a
27
- * generic message (for example just "Error"), which previously surfaced
28
- * verbatim as "Connect error invalid_argument: Error" — an unactionable
29
- * string that hid both the failing argument and which transport produced it
30
- * (Refs #4813). This module keeps the existing "Connect error code: message"
31
- * prefix byte-for-byte and appends what the trailer actually contained:
32
- * typed detail entries are summarized, and when the message is generic the
33
- * remaining trailer fields are appended (truncated) so the server's real
34
- * rejection is visible in logs and bug reports.
35
- */
36
- /** Messages that carry no diagnostic content on their own. */
37
- const GENERIC_CONNECT_ERROR_MESSAGES = /* @__PURE__ */ new Set([
38
- "",
39
- "error",
40
- "unknown",
41
- "unknown error",
42
- "internal",
43
- "internal error"
44
- ]);
45
- /** Upper bound for appended trailer context so errors stay log-line sized. */
46
- const MAX_EXTRA_DETAIL_CHARS = 400;
47
- function isRecord(value) {
48
- return typeof value === "object" && value !== null && !Array.isArray(value);
49
- }
50
- function safeJson(value) {
51
- try {
52
- return (typeof value === "string" ? value : JSON.stringify(value)) || void 0;
53
- } catch {
54
- return;
55
- }
56
- }
57
- /**
58
- * Summarizes Connect error detail entries (objects with type / value / debug
59
- * fields per the Connect JSON error model). Returns undefined when nothing
60
- * usable is present.
61
- */
62
- function summarizeConnectErrorDetails(details) {
63
- if (!Array.isArray(details) || details.length === 0) return void 0;
64
- const parts = [];
65
- for (const entry of details) {
66
- if (!isRecord(entry)) continue;
67
- const type = typeof entry.type === "string" && entry.type ? entry.type : void 0;
68
- const debug = entry.debug !== void 0 ? safeJson(entry.debug) : void 0;
69
- const value = entry.value !== void 0 ? safeJson(entry.value) : void 0;
70
- const diagnostic = debug ?? value;
71
- if (type && diagnostic) parts.push(`${type}: ${diagnostic}`);
72
- else if (type) parts.push(type);
73
- else if (diagnostic) parts.push(diagnostic);
74
- }
75
- if (parts.length === 0) return void 0;
76
- return truncate(parts.join("; "), MAX_EXTRA_DETAIL_CHARS);
77
- }
78
- /**
79
- * Formats a Connect end-stream error object into a diagnosable message.
80
- * The "Connect error code: message" prefix is preserved exactly; detail
81
- * entries are appended when present, and when the message itself is generic
82
- * the remaining trailer fields are inlined so the error names what the
83
- * server actually sent instead of a bare "Error".
84
- */
85
- function formatConnectEndStreamError(error) {
86
- const record = isRecord(error) ? error : {};
87
- const code = typeof record.code === "string" && record.code ? record.code : "unknown";
88
- const message = typeof record.message === "string" ? record.message : "";
89
- const detail = summarizeConnectErrorDetails(record.details);
90
- const parts = [`Connect error ${code}: ${message || "Unknown error"}`];
91
- if (detail) parts.push(`[details: ${detail}]`);
92
- else if (GENERIC_CONNECT_ERROR_MESSAGES.has(message.trim().toLowerCase())) {
93
- const extras = {};
94
- for (const [key, value] of Object.entries(record)) {
95
- if (key === "code" || key === "message") continue;
96
- extras[key] = value;
97
- }
98
- const raw = Object.keys(extras).length > 0 ? safeJson(extras) : void 0;
99
- if (raw && raw !== "{}") parts.push(`[trailer: ${truncate(raw, MAX_EXTRA_DETAIL_CHARS)}]`);
100
- }
101
- return parts.join(" ");
102
- }
103
- //#endregion
104
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/providers/cursor-external-tool-handoff.md
105
- var cursor_external_tool_handoff_default = "Tool call received and handed off to the external client for execution. Do not retry or call it again; end the turn. The result will be provided in the next request.\n";
106
- //#endregion
107
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/providers/cursor-pi-args.ts
108
- /**
109
- * Translate a Pi frame's args into the local tool kwargs that run it.
110
- *
111
- * Shared deliberately by three consumers: the provider synthesizes a display
112
- * block from these, the coding-agent bridge executes with them, and the legacy
113
- * pi shim performs the identical translation for the old wire. Separate
114
- * hand-rolled copies drift, and the drift is invisible — the transcript shows
115
- * one operation while a different one runs.
116
- *
117
- * Kept apart from `cursor/exec-modern.ts` on purpose: these are pure
118
- * string/path functions with no protobuf coupling, while that module pulls in
119
- * the generated cursor protobuf graph. The legacy shim is
120
- * compiled into the bundled virtual module registry, so importing it from a
121
- * nested path would drag the whole exec implementation in with it — and
122
- * `./providers/*` is a single-segment wildcard export that cannot serve a
123
- * nested specifier under bunfs (issue #3442).
124
- *
125
- * Every `optional int32` here is presence-sensitive: `0` is a supplied value,
126
- * not "unset", so it must never be folded into a default.
127
- */
128
- /**
129
- * A `pi_read` range composed onto the path as `read`'s inline `:raw:N+K`
130
- * selector.
131
- *
132
- * `read` exposes no range kwargs, so an uncomposed range reads the whole file.
133
- * `offset` is a 1-indexed start clamped like the reference's
134
- * `Math.max(0, offset - 1)` over 0-indexed lines; `limit` is a line count.
135
- * `null` marks a present `limit: 0` — zero lines, which no selector expresses
136
- * and which must not degrade into a whole-file read.
137
- *
138
- * The range is `raw` because a plain `:N+K` deliberately pads with one leading
139
- * and three trailing context lines: helpful for a human reading a snippet,
140
- * wrong for a caller that asked for exactly `limit` lines from `offset`. The
141
- * wire result is an opaque `output` string, so the hashline and line-number
142
- * gutter that `raw` also drops carry nothing the frame's contract needs.
143
- * A range-free read keeps the ordinary form — whole-file reads want them.
144
- */
145
- function piReadPath(readPath, offset, limit) {
146
- if (limit !== void 0 && Math.floor(limit) <= 0) return null;
147
- const start = offset !== void 0 ? Math.max(1, Math.floor(offset)) : void 0;
148
- const count = limit !== void 0 ? Math.floor(limit) : void 0;
149
- if (start === void 0 && count === void 0) return readPath;
150
- const base = readPath.split(":").some((chunk) => chunk.toLowerCase() === "raw") ? readPath : `${readPath}:raw`;
151
- if (start === void 0) return `${base}:1+${count}`;
152
- return count === void 0 ? `${base}:${start}-` : `${base}:${start}+${count}`;
153
- }
154
- const READ_RANGE_CHUNK_RE = /^L?(\d+)(?:(\.\.|[-+])L?(\d+)?)?$/i;
155
- function isReadRangeList(value) {
156
- return value.split(",").every((chunk) => {
157
- const match = READ_RANGE_CHUNK_RE.exec(chunk);
158
- if (!match) return false;
159
- const start = Number.parseInt(match[1], 10);
160
- if (start < 1) return false;
161
- const separator = match[2];
162
- if (!separator) return true;
163
- const end = match[3] ? Number.parseInt(match[3], 10) : void 0;
164
- if (separator === "+") return end !== void 0 && end >= 1;
165
- return end === void 0 || end >= start;
166
- });
167
- }
168
- /**
169
- * Whether a read path ends in an OMP line selector, including compound `raw`
170
- * forms. Cursor uses this only to describe the operation already executed by
171
- * the coding-agent read tool; the selector remains embedded in the path.
172
- */
173
- function piReadPathHasRange(readPath) {
174
- const chunks = readPath.split(":");
175
- const last = chunks.at(-1);
176
- if (last && isReadRangeList(last)) return true;
177
- if (last?.toLowerCase() !== "raw") return false;
178
- const preceding = chunks.at(-2);
179
- return preceding !== void 0 && isReadRangeList(preceding);
180
- }
181
- /**
182
- * Force a Cursor exec read onto `read`'s verbatim `:raw` selector.
183
- *
184
- * Native `editToolCall` (StrReplace) materializes via `readArgs` then
185
- * `writeArgs`. The server treats the read result as file bytes and writes
186
- * them back. A hashline-formatted native read (`[path#TAG]` + `LINE:`
187
- * prefixes) poisons that cycle: the write would persist the markup.
188
- * `:raw` is the existing selector that drops both. A path that already
189
- * carries `raw` is left alone; a range-only selector gets `raw` inserted
190
- * so the range still applies without the gutter.
191
- */
192
- function cursorRawReadPath(readPath) {
193
- const chunks = readPath.split(":");
194
- if (chunks.some((chunk) => chunk.toLowerCase() === "raw")) return readPath;
195
- if (piReadPathHasRange(readPath)) {
196
- const last = chunks.pop();
197
- return `${chunks.join(":")}:raw:${last}`;
198
- }
199
- return `${readPath}:raw`;
200
- }
201
- /**
202
- * Path the edit-owned materialization read should execute.
203
- *
204
- * Range is composed first (`piReadPath` already uses `:raw` for a range),
205
- * then a whole-file path is forced onto `:raw`. The caller must drop
206
- * `offset`/`limit` after this so the bridge's `piReadPath` cannot append a
207
- * second `:raw` onto the already-composed selector.
208
- */
209
- function cursorEditOwnedReadPath(readPath, offset, limit) {
210
- const ranged = piReadPath(readPath, offset, limit);
211
- if (ranged === null) return null;
212
- return cursorRawReadPath(ranged);
213
- }
214
- /**
215
- * The same range as {@link piReadPath}, rendered for a transcript block rather
216
- * than for execution.
217
- *
218
- * Differs only at `limit: 0`, where `piReadPath` returns `null` because no
219
- * selector reads zero lines and the frame is answered with empty output
220
- * directly. The block still has to say so: falling back to the bare path there
221
- * would record a whole-file read whose result is empty, which is the widest
222
- * possible gap between what a rebuilt transcript shows and what happened.
223
- * `+0` is never executed — it exists to be read.
224
- */
225
- function piReadDisplayPath(readPath, offset, limit) {
226
- const composed = piReadPath(readPath, offset, limit);
227
- if (composed !== null) return composed;
228
- return `${readPath}:raw:${offset !== void 0 ? Math.max(1, Math.floor(offset)) : 1}+0`;
229
- }
230
- /**
231
- * A legacy `grep` frame's pagination `offset` as the local tool's file `skip`.
232
- *
233
- * `grep` paginates by file and reports "use skip=N for the next page" in that
234
- * same unit, so the offset maps across directly. A present `0` means "start at
235
- * the beginning", which is the unskipped search rather than a skip of zero.
236
- *
237
- * Shared because both the executing bridge and the provider's transcript
238
- * synthesis need it: a block showing an unskipped search beside a result from
239
- * a later file window misreports what was searched.
240
- */
241
- function piGrepSkip(offset) {
242
- return offset !== void 0 && offset > 0 ? Math.floor(offset) : void 0;
243
- }
244
- /**
245
- * Join a Pi frame's optional `path` with the `glob`/`pattern` it scopes.
246
- *
247
- * The local `grep`/`glob` tools take one combined path spec. An absolute
248
- * pattern ignores the path, and an absent or `.` path leaves the pattern
249
- * standing alone rather than building a `./`- or `//`-prefixed spec.
250
- *
251
- * Uses `node:path` rather than string surgery so Windows absolutes (`C:\…`,
252
- * UNC) are recognised and separators stay normalized.
253
- */
254
- function piJoinPath(basePath, pattern) {
255
- if (path$1.isAbsolute(pattern)) return pattern;
256
- if (!basePath || basePath === ".") return pattern;
257
- return path$1.join(basePath, pattern);
258
- }
259
- /**
260
- * The path a `pi_ls` frame lists.
261
- *
262
- * The frame's `limit` is deliberately NOT mapped. It caps directory *entries*
263
- * (the reference does a flat `readdir` and slices the entry array), while the
264
- * local `read` tool renders a depth-2 tree with per-directory caps and elision
265
- * summaries and applies a selector as a *rendered line* slice. Nested rows,
266
- * headers and "N more" lines all count toward that slice, so `:1+K` would cap
267
- * a different unit while looking honored — worse than leaving it unset, which
268
- * at least reports the local listing's own truncation faithfully.
269
- */
270
- function piLsPath(basePath) {
271
- return basePath || ".";
272
- }
273
- /** Escape a literal string so the regex-only local `grep` tool matches it verbatim. */
274
- function piEscapeRegexLiteral(value) {
275
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
276
- }
277
- /** Clamp a present `optional int32` result cap the way the reference does; `undefined` stays unset. */
278
- function piLimit(limit) {
279
- return limit === void 0 ? void 0 : Math.max(1, Math.floor(limit));
280
- }
281
- /**
282
- * A `pi_bash` frame's timeout as the local `bash` tool's kwarg.
283
- *
284
- * Presence-sensitive like every other `optional int32` here, and unusually
285
- * load-bearing: `bash` documents `timeout: 0` as "disables the command
286
- * deadline", so folding a supplied `0` into `undefined` applies the 300s
287
- * default and kills exactly the long-running command that asked not to be.
288
- * Negative values have no local meaning and fall back to the default.
289
- */
290
- function piTimeout(timeout) {
291
- return timeout !== void 0 && timeout >= 0 ? timeout : void 0;
292
- }
293
- /**
294
- * Drop keys whose value is `undefined` so optional local-tool kwargs stay
295
- * absent rather than present-as-undefined.
296
- *
297
- * The Cursor exec bridge historically wrote forms like
298
- * `cwd: workingDirectory || undefined` and
299
- * `case: caseInsensitive === true ? false : undefined`. ArkType rejects a
300
- * present `undefined` on an optional field (`was undefined`) even though
301
- * omitting the key is valid — which flooded Cursor sessions with bash/grep
302
- * validation errors for otherwise fine frames.
303
- */
304
- function omitUndefinedArgs(args) {
305
- const out = {};
306
- for (const key of Object.keys(args)) {
307
- const value = args[key];
308
- if (value !== void 0) out[key] = value;
309
- }
310
- return out;
311
- }
312
- //#endregion
313
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/providers/cursor/exec-modern.ts
314
- /**
315
- * Proto builders for the modern Cursor CLI exec frames (`ExecServerMessage`
316
- * 27-31, 36-38, 40-55).
317
- *
318
- * Split out of `cursor.ts` because these are pure `create(...)` shapes with no
319
- * transport, stream, or block-state coupling: the dispatcher decides *which*
320
- * answer a frame gets, this module knows *what* that answer looks like on the
321
- * wire. Every builder returns a result whose oneof case is set — an
322
- * `ExecClientMessage` carrying a result with an unset oneof is a fake success
323
- * the server reads as "the tool ran and produced nothing".
324
- */
325
- /** Flatten a tool result's content into the single `output` string the Pi frames carry. */
326
- function piOutputText(toolResult) {
327
- return toolResult.content.map((item) => item.type === "text" ? item.text : `[${item.mimeType} image]`).join("\n");
328
- }
329
- /**
330
- * Read one field off a tool result's `details` bag, or off a nested object
331
- * inside it.
332
- *
333
- * `details` is `unknown` by design — every tool ships its own shape — so each
334
- * read is narrowed rather than asserted, and the caller decides what a value of
335
- * the wrong type means.
336
- */
337
- function bagValue(bag, key) {
338
- if (!bag || typeof bag !== "object" || !(key in bag)) return void 0;
339
- return Reflect.get(bag, key);
340
- }
341
- /**
342
- * A positive integer count from `details`, or `undefined`.
343
- *
344
- * The Pi frames model their limit counters as `optional uint32`: zero means
345
- * "the limit was reached at zero results", so a missing, non-numeric, or
346
- * non-positive value must stay unset rather than be sent as 0.
347
- */
348
- function detailCount(toolResult, key) {
349
- return positiveCount(bagValue(toolResult.details, key));
350
- }
351
- function positiveCount(value) {
352
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
353
- }
354
- /**
355
- * The entry cap a listing hit, from either shape a local tool records it in.
356
- *
357
- * `glob` sets a flat `details.resultLimitReached` alongside the structured
358
- * meta; `read` — which serves `pi_ls` — records the cap only through
359
- * `OutputMeta` at `details.meta.limits.resultLimit.reached`. Reading just the
360
- * flat field dropped `entry_limit_reached` for every real listing, so Cursor
361
- * received clipped output with no incompleteness signal.
362
- */
363
- function resultLimitReached(toolResult) {
364
- const flat = detailCount(toolResult, "resultLimitReached");
365
- if (flat !== void 0) return flat;
366
- return positiveCount(bagValue(bagValue(bagValue(bagValue(toolResult.details, "meta"), "limits"), "resultLimit"), "reached"));
367
- }
368
- /**
369
- * Translate a local tool's truncation summary into `PiTruncation`.
370
- *
371
- * Two shapes reach here. `read`/`grep` set `details.truncation`
372
- * (`TruncationResult`), which carries an explicit `truncated` boolean. `bash`
373
- * sets `details.meta.truncation` (`TruncationMeta`), which has **no** such
374
- * flag — its presence *is* the signal, and requiring the boolean silently
375
- * dropped every Bash truncation, handing Cursor clipped output with no notice
376
- * that it was clipped.
377
- *
378
- * Returns `undefined` when nothing was truncated: the field is `optional` on
379
- * every Pi success message, and emitting a zeroed `PiTruncation` would tell the
380
- * server the output was trimmed to nothing.
381
- */
382
- function piTruncation(toolResult) {
383
- const direct = bagValue(toolResult.details, "truncation");
384
- const truncation = direct !== void 0 ? direct : bagValue(bagValue(toolResult.details, "meta"), "truncation");
385
- if (truncation === void 0 || truncation === null) return void 0;
386
- const flag = bagValue(truncation, "truncated");
387
- if (flag !== void 0 && flag !== true) return void 0;
388
- const truncatedBy = bagValue(truncation, "truncatedBy");
389
- const totalLines = bagValue(truncation, "totalLines");
390
- const outputLines = bagValue(truncation, "outputLines");
391
- const outputBytes = bagValue(truncation, "outputBytes");
392
- return create(PiTruncationSchema, {
393
- truncated: true,
394
- truncatedBy: typeof truncatedBy === "string" ? truncatedBy : "",
395
- totalLines: typeof totalLines === "number" ? totalLines : 0,
396
- outputLines: typeof outputLines === "number" ? outputLines : 0,
397
- outputBytes: typeof outputBytes === "number" ? outputBytes : 0,
398
- firstLineExceedsLimit: bagValue(truncation, "firstLineExceedsLimit") === true,
399
- lastLinePartial: bagValue(truncation, "lastLinePartial") === true
400
- });
401
- }
402
- function buildPiReadResult(toolResult) {
403
- const text = piOutputText(toolResult);
404
- if (toolResult.isError) return buildPiReadError(text || "Read failed");
405
- return create(PiReadExecResultSchema, { result: {
406
- case: "success",
407
- value: create(PiReadExecSuccessSchema, {
408
- output: text,
409
- truncation: piTruncation(toolResult)
410
- })
411
- } });
412
- }
413
- function buildPiReadError(error) {
414
- return create(PiReadExecResultSchema, { result: {
415
- case: "error",
416
- value: create(PiReadExecErrorSchema, { error })
417
- } });
418
- }
419
- function buildPiBashResult(toolResult) {
420
- const text = piOutputText(toolResult);
421
- const truncation = piTruncation(toolResult);
422
- if (toolResult.isError) return create(PiBashExecResultSchema, { result: {
423
- case: "error",
424
- value: create(PiBashExecErrorSchema, {
425
- error: text || "Command failed",
426
- truncation
427
- })
428
- } });
429
- return create(PiBashExecResultSchema, { result: {
430
- case: "success",
431
- value: create(PiBashExecSuccessSchema, {
432
- output: text,
433
- truncation
434
- })
435
- } });
436
- }
437
- function buildPiBashError(error) {
438
- return create(PiBashExecResultSchema, { result: {
439
- case: "error",
440
- value: create(PiBashExecErrorSchema, { error })
441
- } });
442
- }
443
- /**
444
- * `PiEditExecSuccess` requires `diff` and `patch` alongside `output`. The local
445
- * `edit` tool reports them under `details`; when it does not, the strings stay
446
- * empty rather than being faked from the output text.
447
- */
448
- function buildPiEditResult(toolResult) {
449
- const text = piOutputText(toolResult);
450
- if (toolResult.isError) return buildPiEditError(text || "Edit failed");
451
- const diff = bagValue(toolResult.details, "diff");
452
- const patch = bagValue(toolResult.details, "patch");
453
- return create(PiEditExecResultSchema, { result: {
454
- case: "success",
455
- value: create(PiEditExecSuccessSchema, {
456
- output: text,
457
- diff: typeof diff === "string" ? diff : "",
458
- patch: typeof patch === "string" ? patch : "",
459
- firstChangedLine: detailCount(toolResult, "firstChangedLine")
460
- })
461
- } });
462
- }
463
- function buildPiEditError(error) {
464
- return create(PiEditExecResultSchema, { result: {
465
- case: "error",
466
- value: create(PiEditExecErrorSchema, { error })
467
- } });
468
- }
469
- /**
470
- * A refusal is not an execution failure: `PiEditExecResult` models them as
471
- * separate variants, and answering a denied call with `error` reads as "the
472
- * edit ran and broke", which invites a retry of an operation that was never
473
- * permitted.
474
- */
475
- function buildPiEditRejected(reason) {
476
- return create(PiEditExecResultSchema, { result: {
477
- case: "rejected",
478
- value: create(PiEditExecRejectedSchema, { reason })
479
- } });
480
- }
481
- function buildPiWriteResult(toolResult) {
482
- const text = piOutputText(toolResult);
483
- if (toolResult.isError) return buildPiWriteError(text || "Write failed");
484
- return create(PiWriteExecResultSchema, { result: {
485
- case: "success",
486
- value: create(PiWriteExecSuccessSchema, { output: text })
487
- } });
488
- }
489
- function buildPiWriteError(error) {
490
- return create(PiWriteExecResultSchema, { result: {
491
- case: "error",
492
- value: create(PiWriteExecErrorSchema, { error })
493
- } });
494
- }
495
- /** Same variant split as {@link buildPiEditRejected}. */
496
- function buildPiWriteRejected(reason) {
497
- return create(PiWriteExecResultSchema, { result: {
498
- case: "rejected",
499
- value: create(PiWriteExecRejectedSchema, { reason })
500
- } });
501
- }
502
- function buildPiGrepResult(toolResult) {
503
- const text = piOutputText(toolResult);
504
- if (toolResult.isError) return buildPiGrepError(text || "Grep failed");
505
- const matchLimitReached = detailCount(toolResult, "perFileLimitReached");
506
- return create(PiGrepExecResultSchema, { result: {
507
- case: "success",
508
- value: create(PiGrepExecSuccessSchema, {
509
- output: text,
510
- truncation: piTruncation(toolResult) ?? grepInternalCapTruncation(toolResult, text, matchLimitReached),
511
- matchLimitReached,
512
- linesTruncated: bagValue(toolResult.details, "linesTruncated") === true
513
- })
514
- } });
515
- }
516
- /**
517
- * The one grep cap that reaches Cursor through no other field.
518
- *
519
- * `GrepTool` folds every cap into the flat `details.truncated`, but only
520
- * populates `details.truncation` when the output text itself was trimmed and
521
- * `perFileLimitReached` when a per-file or explicit total cap fired. Its native
522
- * backend's own ceiling (`INTERNAL_TOTAL_CAP`) sets neither, so a search that
523
- * hit it answered as an unqualified success over incomplete results — the one
524
- * failure mode a caller cannot detect and cannot retry around.
525
- *
526
- * Only consulted once the specific signals came back empty, so a cap that did
527
- * report itself is never restated. `totalLines` stays 0: the flat flag says
528
- * that results were dropped, not how many there were.
529
- */
530
- function grepInternalCapTruncation(toolResult, text, matchLimitReached) {
531
- if (matchLimitReached !== void 0) return void 0;
532
- if (bagValue(toolResult.details, "truncated") !== true) return void 0;
533
- return create(PiTruncationSchema, {
534
- truncated: true,
535
- truncatedBy: "matches",
536
- totalLines: 0,
537
- outputLines: text ? text.split("\n").length : 0,
538
- outputBytes: Buffer.byteLength(text, "utf-8"),
539
- firstLineExceedsLimit: false,
540
- lastLinePartial: false
541
- });
542
- }
543
- function buildPiGrepError(error) {
544
- return create(PiGrepExecResultSchema, { result: {
545
- case: "error",
546
- value: create(PiGrepExecErrorSchema, { error })
547
- } });
548
- }
549
- function buildPiFindResult(toolResult) {
550
- const text = piOutputText(toolResult);
551
- if (toolResult.isError) return buildPiFindError(text || "Find failed");
552
- return create(PiFindExecResultSchema, { result: {
553
- case: "success",
554
- value: create(PiFindExecSuccessSchema, {
555
- output: text,
556
- truncation: piTruncation(toolResult),
557
- resultLimitReached: resultLimitReached(toolResult)
558
- })
559
- } });
560
- }
561
- function buildPiFindError(error) {
562
- return create(PiFindExecResultSchema, { result: {
563
- case: "error",
564
- value: create(PiFindExecErrorSchema, { error })
565
- } });
566
- }
567
- function buildPiLsResult(toolResult) {
568
- const text = piOutputText(toolResult);
569
- if (toolResult.isError) return buildPiLsError(text || "Ls failed");
570
- return create(PiLsExecResultSchema, { result: {
571
- case: "success",
572
- value: create(PiLsExecSuccessSchema, {
573
- output: text,
574
- truncation: piTruncation(toolResult),
575
- entryLimitReached: resultLimitReached(toolResult)
576
- })
577
- } });
578
- }
579
- function buildPiLsError(error) {
580
- return create(PiLsExecResultSchema, { result: {
581
- case: "error",
582
- value: create(PiLsExecErrorSchema, { error })
583
- } });
584
- }
585
- /**
586
- * Answer `mcpStateExecArgs` (frame 36) from the catalog already advertised in
587
- * `RequestContext.tools`.
588
- *
589
- * This client hosts no MCP servers of its own: every forwarded tool is a local
590
- * pi-agent tool published under a synthetic `providerIdentifier`. Regrouping
591
- * the same list keeps the server's view of "which servers exist and what do
592
- * they expose" consistent with what it was told at context time, instead of
593
- * claiming zero servers while tool calls for them keep arriving.
594
- *
595
- * `serverIdentifiers` filters the answer when the server asks about specific
596
- * servers. `kickOnly` is a restart request — there is nothing to restart, so it
597
- * is answered with the same state rather than an error.
598
- */
599
- function buildMcpStateResult(tools, serverIdentifiers) {
600
- const byProvider = /* @__PURE__ */ new Map();
601
- for (const tool of tools) {
602
- const identifier = tool.providerIdentifier;
603
- const existing = byProvider.get(identifier);
604
- if (existing) existing.push(tool);
605
- else byProvider.set(identifier, [tool]);
606
- }
607
- const wanted = serverIdentifiers.length > 0 ? new Set(serverIdentifiers) : void 0;
608
- const servers = [];
609
- for (const [identifier, serverTools] of byProvider) {
610
- if (wanted && !wanted.has(identifier)) continue;
611
- servers.push(create(McpStateServerSchema, {
612
- serverName: identifier,
613
- serverIdentifier: identifier,
614
- tools: serverTools,
615
- status: "connected"
616
- }));
617
- }
618
- return create(McpStateExecResultSchema, { result: {
619
- case: "success",
620
- value: create(McpStateSuccessSchema, { servers })
621
- } });
622
- }
623
- /**
624
- * Build the neutral response for a hook query: the matching response case with
625
- * every field unset.
626
- *
627
- * This client runs no Cursor hooks, and every field of every response variant
628
- * is `optional` — so an empty response of the right case means "no hook had
629
- * anything to say", which is exactly true. It is NOT the unset-oneof fake
630
- * success: the case itself is set, only the payload is empty.
631
- *
632
- * `ExecuteHookRequest` and `ExecuteHookResponse` are parallel oneofs whose case
633
- * names line up, but the two unions are unrelated to the compiler: a `switch`
634
- * is what makes each pairing individually type-checked, and it forces a
635
- * deliberate branch when a future regen adds a request case.
636
- *
637
- * Returns `null` for a request case this build does not model, which the
638
- * dispatcher answers with `ExecClientThrow` rather than guessing a case.
639
- */
640
- function buildNeutralHookResult(request) {
641
- let response;
642
- switch (request?.request.case) {
643
- case "preCompact":
644
- response = create(ExecuteHookResponseSchema, { response: {
645
- case: "preCompact",
646
- value: create(PreCompactRequestResponseSchema, {})
647
- } });
648
- break;
649
- case "subagentStart":
650
- response = create(ExecuteHookResponseSchema, { response: {
651
- case: "subagentStart",
652
- value: create(SubagentStartRequestResponseSchema, {})
653
- } });
654
- break;
655
- case "subagentStop":
656
- response = create(ExecuteHookResponseSchema, { response: {
657
- case: "subagentStop",
658
- value: create(SubagentStopRequestResponseSchema, {})
659
- } });
660
- break;
661
- case "preToolUse":
662
- response = create(ExecuteHookResponseSchema, { response: {
663
- case: "preToolUse",
664
- value: create(PreToolUseRequestResponseSchema, {})
665
- } });
666
- break;
667
- case "postToolUse":
668
- response = create(ExecuteHookResponseSchema, { response: {
669
- case: "postToolUse",
670
- value: create(PostToolUseRequestResponseSchema, {})
671
- } });
672
- break;
673
- case "postToolUseFailure":
674
- response = create(ExecuteHookResponseSchema, { response: {
675
- case: "postToolUseFailure",
676
- value: create(PostToolUseFailureRequestResponseSchema, {})
677
- } });
678
- break;
679
- case "beforeSubmitPrompt":
680
- response = create(ExecuteHookResponseSchema, { response: {
681
- case: "beforeSubmitPrompt",
682
- value: create(BeforeSubmitPromptRequestResponseSchema, {})
683
- } });
684
- break;
685
- case "afterAgentResponse":
686
- response = create(ExecuteHookResponseSchema, { response: {
687
- case: "afterAgentResponse",
688
- value: create(AfterAgentResponseRequestResponseSchema, {})
689
- } });
690
- break;
691
- case "afterAgentThought":
692
- response = create(ExecuteHookResponseSchema, { response: {
693
- case: "afterAgentThought",
694
- value: create(AfterAgentThoughtRequestResponseSchema, {})
695
- } });
696
- break;
697
- case "stop":
698
- response = create(ExecuteHookResponseSchema, { response: {
699
- case: "stop",
700
- value: create(StopRequestResponseSchema, {})
701
- } });
702
- break;
703
- default: return null;
704
- }
705
- return create(ExecuteHookResultSchema, { response });
706
- }
707
- //#endregion
708
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/providers/cursor/interaction-query.ts
709
- const NOT_IMPLEMENTED_SUFFIX$1 = "not implemented by this client";
710
- function frameConnectMessage$1(data, flags = 0) {
711
- const frame = Buffer.alloc(5 + data.length);
712
- frame[0] = flags;
713
- frame.writeUInt32BE(data.length, 1);
714
- frame.set(data, 5);
715
- return frame;
716
- }
717
- function isProtoUnknownField(value) {
718
- if (!value || typeof value !== "object") return false;
719
- if (!("no" in value) || !("wireType" in value) || !("data" in value)) return false;
720
- return typeof value.no === "number" && typeof value.wireType === "number" && value.data instanceof Uint8Array;
721
- }
722
- function protoUnknownFields$1(message) {
723
- if (!("$unknown" in message) || !Array.isArray(message.$unknown)) return [];
724
- return message.$unknown.filter(isProtoUnknownField);
725
- }
726
- function attachUnknownApprovedField(response, fieldNo) {
727
- const bag = response;
728
- const field = {
729
- no: fieldNo,
730
- wireType: 2,
731
- data: new Uint8Array([
732
- 2,
733
- 10,
734
- 0
735
- ])
736
- };
737
- const existing = bag.$unknown;
738
- if (Array.isArray(existing)) {
739
- existing.push(field);
740
- return;
741
- }
742
- bag.$unknown = [field];
743
- }
744
- function log$1(type, subtype, data) {
745
- if (!$env.DEBUG_CURSOR) return;
746
- const dataStr = ($env.DEBUG_CURSOR === "2" || $env.DEBUG_CURSOR === "verbose") && data ? ` ${JSON.stringify(data)?.slice(0, 500)}` : "";
747
- console.error(`[CURSOR] ${type}${subtype ? `: ${subtype}` : ""}${dataStr}`);
748
- }
749
- function sendInteractionResponse(h2Request, queryId, result) {
750
- const response = create(InteractionResponseSchema, {
751
- id: queryId,
752
- result
753
- });
754
- const clientMessage = create(AgentClientMessageSchema, { message: {
755
- case: "interactionResponse",
756
- value: response
757
- } });
758
- h2Request.write(frameConnectMessage$1(toBinary(AgentClientMessageSchema, clientMessage)));
759
- log$1("interactionResponse", result.case, { id: queryId });
760
- }
761
- function sendUnknownApprovedInteractionResponse(h2Request, queryId, fieldNo) {
762
- const response = create(InteractionResponseSchema, { id: queryId });
763
- attachUnknownApprovedField(response, fieldNo);
764
- const clientMessage = create(AgentClientMessageSchema, { message: {
765
- case: "interactionResponse",
766
- value: response
767
- } });
768
- h2Request.write(frameConnectMessage$1(toBinary(AgentClientMessageSchema, clientMessage)));
769
- log$1("interactionResponse", "unknownApproved", {
770
- id: queryId,
771
- field: fieldNo
772
- });
773
- }
774
- /**
775
- * Answer a Cursor `interaction_query` so the Run RPC can continue.
776
- *
777
- * Hosted web search / Exa / unnamed permission gates (field 9 = WebFetch on
778
- * current Cursor builds) block the turn until the client writes an
779
- * `interaction_response`. Dropping the frame leaves the HTTP/2 stream alive
780
- * on heartbeats that are not semantic progress; the lazy idle watchdog then
781
- * aborts with "Provider stream stalled while waiting for the next event".
782
- *
783
- * Unsupported interactive queries are rejected so the server is not stranded.
784
- * VM setup is left unanswered rather than reporting a fake success.
785
- */
786
- function handleInteractionQuery(query, h2Request) {
787
- const queryCase = query.query.case;
788
- log$1("interactionQuery", queryCase, query.query.value);
789
- if (!queryCase) {
790
- const unknown = protoUnknownFields$1(query).find((field) => field.wireType === 2 && field.no >= 2);
791
- if (unknown) {
792
- log$1("warn", "unknownInteractionQueryApproved", {
793
- id: query.id,
794
- field: unknown.no
795
- });
796
- sendUnknownApprovedInteractionResponse(h2Request, query.id, unknown.no);
797
- return;
798
- }
799
- log$1("warn", "unknownInteractionQuery", { id: query.id });
800
- return;
801
- }
802
- switch (queryCase) {
803
- case "webSearchRequestQuery":
804
- sendInteractionResponse(h2Request, query.id, {
805
- case: "webSearchRequestResponse",
806
- value: create(WebSearchRequestResponseSchema, { result: {
807
- case: "approved",
808
- value: create(WebSearchRequestResponse_ApprovedSchema, {})
809
- } })
810
- });
811
- return;
812
- case "exaSearchRequestQuery":
813
- sendInteractionResponse(h2Request, query.id, {
814
- case: "exaSearchRequestResponse",
815
- value: create(ExaSearchRequestResponseSchema, { result: {
816
- case: "approved",
817
- value: create(ExaSearchRequestResponse_ApprovedSchema, {})
818
- } })
819
- });
820
- return;
821
- case "exaFetchRequestQuery":
822
- sendInteractionResponse(h2Request, query.id, {
823
- case: "exaFetchRequestResponse",
824
- value: create(ExaFetchRequestResponseSchema, { result: {
825
- case: "approved",
826
- value: create(ExaFetchRequestResponse_ApprovedSchema, {})
827
- } })
828
- });
829
- return;
830
- case "webFetchRequestQuery":
831
- sendInteractionResponse(h2Request, query.id, {
832
- case: "webFetchRequestResponse",
833
- value: create(WebFetchRequestResponseSchema, { result: {
834
- case: "approved",
835
- value: create(WebFetchRequestResponse_ApprovedSchema, {})
836
- } })
837
- });
838
- return;
839
- case "askQuestionInteractionQuery":
840
- sendInteractionResponse(h2Request, query.id, {
841
- case: "askQuestionInteractionResponse",
842
- value: create(AskQuestionInteractionResponseSchema, { result: create(AskQuestionResultSchema, { result: {
843
- case: "rejected",
844
- value: create(AskQuestionRejectedSchema, { reason: `Interactive questions are ${NOT_IMPLEMENTED_SUFFIX$1}` })
845
- } }) })
846
- });
847
- return;
848
- case "switchModeRequestQuery":
849
- sendInteractionResponse(h2Request, query.id, {
850
- case: "switchModeRequestResponse",
851
- value: create(SwitchModeRequestResponseSchema, { result: {
852
- case: "rejected",
853
- value: create(SwitchModeRequestResponse_RejectedSchema, { reason: `Mode switches are ${NOT_IMPLEMENTED_SUFFIX$1}` })
854
- } })
855
- });
856
- return;
857
- case "createPlanRequestQuery":
858
- sendInteractionResponse(h2Request, query.id, {
859
- case: "createPlanRequestResponse",
860
- value: create(CreatePlanRequestResponseSchema, { result: create(CreatePlanResultSchema, { result: {
861
- case: "error",
862
- value: create(CreatePlanErrorSchema, { error: `Plan files are ${NOT_IMPLEMENTED_SUFFIX$1}` })
863
- } }) })
864
- });
865
- return;
866
- case "setupVmEnvironmentArgs":
867
- log$1("warn", "unhandledInteractionQuery", {
868
- queryCase,
869
- id: query.id
870
- });
871
- return;
872
- default: log$1("warn", "unhandledInteractionQuery", {
873
- queryCase,
874
- id: query.id
875
- });
876
- }
877
- }
878
- const CURSOR_CLIENT_VERSION = "cli-2026.07.23-e383d2b";
879
- /**
880
- * HTTP/1 connection-specific headers that HTTP/2 forbids. Node's `http2.request()`
881
- * throws `ERR_HTTP2_INVALID_CONNECTION_HEADERS` on these rather than dropping
882
- * them, so a caller sending one would kill the request outright.
883
- */
884
- const HTTP2_FORBIDDEN_HEADERS = /* @__PURE__ */ new Set([
885
- "connection",
886
- "keep-alive",
887
- "proxy-connection",
888
- "transfer-encoding",
889
- "upgrade",
890
- "http2-settings"
891
- ]);
892
- /**
893
- * Header names the Cursor request sets for itself. A caller copy in ANY casing
894
- * has to go: the spread below adds the fixed lower-case name regardless, and two
895
- * spellings of one field are a duplicate rather than an override.
896
- */
897
- const CURSOR_RESERVED_HEADERS = /* @__PURE__ */ new Set([
898
- "content-type",
899
- "connect-protocol-version",
900
- "te",
901
- "authorization",
902
- "x-ghost-mode",
903
- "x-cursor-client-version",
904
- "x-cursor-client-type",
905
- "x-request-id",
906
- "host",
907
- "content-length"
908
- ]);
909
- /**
910
- * Reduce caller-supplied headers to what this HTTP/2 request can legally carry.
911
- *
912
- * Everything is lower-cased, because HTTP/2 field names are lower-case and node
913
- * compares them that way. A caller `Authorization` next to the fixed
914
- * `authorization` does not lose to it, it DUPLICATES it, and node throws
915
- * `ERR_HTTP2_HEADER_SINGLE_VALUE` before the request goes out. Same for a `TE`
916
- * that is not `trailers`. Node throws on all three classes here rather than
917
- * ignoring them, so a miss turns a harmless header into a dead request.
918
- */
919
- function sanitizeCursorCallerHeaders(headers) {
920
- const sanitized = {};
921
- for (const [name, value] of Object.entries(headers ?? {})) {
922
- const field = name.toLowerCase();
923
- if (field.startsWith(":")) continue;
924
- if (HTTP2_FORBIDDEN_HEADERS.has(field)) continue;
925
- if (CURSOR_RESERVED_HEADERS.has(field)) continue;
926
- sanitized[field] = value;
927
- }
928
- return sanitized;
929
- }
930
- const CURSOR_PROXY_TUNNEL_TIMEOUT_MS = 3e4;
931
- /**
932
- * Text for a recognised frame this client answers with its own typed error
933
- * variant. Phrased as a client capability statement, not a tool failure: the
934
- * model reads it and should route around the capability, not retry the call.
935
- */
936
- const NOT_IMPLEMENTED_SUFFIX = "not implemented by this client";
937
- /** Bare gRPC `resource_exhausted` end-streams (also inside a Connect error message). */
938
- const RESOURCE_EXHAUSTED_PATTERN = /resource.?exhausted/i;
939
- /** Model-resolution failures that can safely retry the exact discovery id before any server output. */
940
- const CURSOR_MODEL_NOT_FOUND_PATTERN = /^(?:Connect error not_found:|gRPC error 5:)/i;
941
- const NOT_IMPLEMENTED = `Not implemented by this client`;
942
- const conversationStateCache = /* @__PURE__ */ new Map();
943
- const conversationBlobStores = /* @__PURE__ */ new Map();
944
- const warnedCursorKimiK3ReplayMessages = /* @__PURE__ */ new Set();
945
- /**
946
- * Base conversation id → rotated wire id (#8345). Cursor's backend can pin a
947
- * per-conversation rejection (bare `resource_exhausted`, zero tokens) to one
948
- * conversationId forever. On such a failure the id is rotated and the next
949
- * attempt rebuilds a fresh conversation from `context` (no cached-state
950
- * migration). A failed rotation is not repeated, so real account exhaustion
951
- * is not hidden. After the rotated id completes a turn, a later poison of
952
- * that id is allowed to rotate again.
953
- */
954
- const rotatedConversationIds = /* @__PURE__ */ new Map();
955
- const successfulRotatedConversationIds = /* @__PURE__ */ new Set();
956
- const freshRotatedConversationIds = /* @__PURE__ */ new Set();
957
- const CONNECT_END_STREAM_FLAG = 2;
958
- async function appendCursorDebugLog(entry) {
959
- const logPath = $env.DEBUG_CURSOR_LOG;
960
- if (!logPath) return;
961
- try {
962
- await fs_promises_shim_exports.appendFile(logPath, `${JSON.stringify(entry, debugReplacer)}\n`);
963
- } catch {}
964
- }
965
- function log(type, subtype, data) {
966
- if (!$env.DEBUG_CURSOR) return;
967
- const normalizedData = data ? decodeLogData(data) : data;
968
- const entry = {
969
- ts: Date.now(),
970
- type,
971
- subtype,
972
- data: normalizedData
973
- };
974
- const dataStr = ($env.DEBUG_CURSOR === "2" || $env.DEBUG_CURSOR === "verbose") && normalizedData ? ` ${JSON.stringify(normalizedData, debugReplacer)?.slice(0, 500)}` : "";
975
- console.error(`[CURSOR] ${type}${subtype ? `: ${subtype}` : ""}${dataStr}`);
976
- appendCursorDebugLog(entry);
977
- }
978
- function frameConnectMessage(data, flags = 0) {
979
- const frame = Buffer.alloc(5 + data.length);
980
- frame[0] = flags;
981
- frame.writeUInt32BE(data.length, 1);
982
- frame.set(data, 5);
983
- return frame;
984
- }
985
- var ConnectEndStreamError = class extends ProviderResponseError {
986
- diagnosticMessage;
987
- constructor(classificationMessage, diagnosticMessage) {
988
- super(classificationMessage, { kind: "envelope" });
989
- this.diagnosticMessage = diagnosticMessage;
990
- }
991
- };
992
- function parseConnectEndStream(data) {
993
- try {
994
- const error = JSON.parse(new TextDecoder().decode(data))?.error;
995
- if (error) return new ConnectEndStreamError(`Connect error ${typeof error.code === "string" ? error.code : "unknown"}: ${typeof error.message === "string" ? error.message : "Unknown error"}`, formatConnectEndStreamError(error));
996
- return null;
997
- } catch {
998
- return new ProviderResponseError("Failed to parse Connect end stream", { kind: "envelope" });
999
- }
1000
- }
1001
- /**
1002
- * Maps an opaque HTTP/2 negotiation failure into an actionable error.
1003
- *
1004
- * bun only opens an HTTP/2 session when TLS-ALPN negotiates `h2`. Behind a
1005
- * TLS-intercepting proxy that strips ALPN (e.g. Zscaler), the handshake yields
1006
- * no `h2` protocol and bun throws `ERR_HTTP2_ERROR: h2 is not supported`. The
1007
- * Cursor run RPC is HTTP/2-only (the ALB rejects HTTP/1.1 with 464), so there
1008
- * is no h1 fallback the way model discovery has one — the run simply cannot
1009
- * proceed. Replace the opaque message with one that names the cause and points
1010
- * at the `providers.cursor.baseUrl` workaround.
1011
- *
1012
- * Non-ALPN errors pass through untouched.
1013
- */
1014
- function mapH2TransportError(error, baseUrl) {
1015
- const code = error?.code;
1016
- const message = error instanceof Error ? error.message : String(error);
1017
- if (code === "ERR_HTTP2_ERROR" && /h2 is not supported/i.test(message)) return new ProviderResponseError(`Cursor run transport could not negotiate HTTP/2 with ${baseUrl}: "h2 is not supported". This host serves the run RPC over HTTP/2 only, and the TLS handshake did not negotiate h2 via ALPN — typically an ALPN-stripping TLS-intercepting proxy (e.g. Zscaler). Front the provider with a local HTTP/2 bridge and set providers.cursor.baseUrl to it.`, {
1018
- provider: "cursor",
1019
- kind: "runtime",
1020
- cause: error
1021
- });
1022
- return error;
1023
- }
1024
- function debugBytes(bytes, asHex) {
1025
- if (asHex) return Buffer.from(bytes).toString("hex");
1026
- try {
1027
- const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1028
- if (/^[\x20-\x7E\s]*$/.test(text)) return text;
1029
- } catch {}
1030
- return Buffer.from(bytes).toString("hex");
1031
- }
1032
- function debugReplacer(key, value) {
1033
- if (value instanceof Uint8Array || value && typeof value === "object" && "type" in value && value.type === "Buffer") return debugBytes(value instanceof Uint8Array ? value : new Uint8Array(value.data), key === "blobId" || key === "blob_id" || key.endsWith("Id") || key.endsWith("_id"));
1034
- if (typeof value === "bigint") return value.toString();
1035
- return value;
1036
- }
1037
- function extractLogBytes(value) {
1038
- if (value instanceof Uint8Array) return value;
1039
- if (value && typeof value === "object" && "type" in value && value.type === "Buffer") {
1040
- const data = value.data;
1041
- if (Array.isArray(data)) return new Uint8Array(data);
1042
- }
1043
- return null;
1044
- }
1045
- function decodeMcpArgsForLog(args) {
1046
- if (!args) return;
1047
- let mutated = false;
1048
- const decoded = {};
1049
- for (const [key, value] of Object.entries(args)) {
1050
- const bytes = extractLogBytes(value);
1051
- if (bytes) {
1052
- decoded[key] = decodeMcpArgValue(bytes);
1053
- mutated = true;
1054
- continue;
1055
- }
1056
- const normalizedValue = decodeLogData(value);
1057
- decoded[key] = normalizedValue;
1058
- if (normalizedValue !== value) mutated = true;
1059
- }
1060
- return mutated ? decoded : args;
1061
- }
1062
- function decodeLogData(value) {
1063
- if (!value || typeof value !== "object") return value;
1064
- if (Array.isArray(value)) return value.map((entry) => decodeLogData(entry));
1065
- const record = value;
1066
- const typeName = record.$typeName;
1067
- const stripTypeName = typeof typeName === "string" && typeName.startsWith("agent.v1.");
1068
- if (typeName === "agent.v1.McpArgs") {
1069
- const decodedArgs = decodeMcpArgsForLog(record.args);
1070
- const base = stripTypeName ? omitTypeName(record) : record;
1071
- return decodedArgs ? {
1072
- ...base,
1073
- args: decodedArgs
1074
- } : base;
1075
- }
1076
- if (typeName === "agent.v1.McpToolCall") {
1077
- const argsRecord = record.args;
1078
- const decodedArgs = decodeMcpArgsForLog(argsRecord?.args);
1079
- const base = stripTypeName ? omitTypeName(record) : record;
1080
- if (decodedArgs && argsRecord) return {
1081
- ...base,
1082
- args: {
1083
- ...argsRecord,
1084
- args: decodedArgs
1085
- }
1086
- };
1087
- return base;
1088
- }
1089
- let mutated = stripTypeName;
1090
- const decoded = {};
1091
- for (const [key, entry] of Object.entries(record)) {
1092
- if (stripTypeName && key === "$typeName") continue;
1093
- const normalizedEntry = decodeLogData(entry);
1094
- decoded[key] = normalizedEntry;
1095
- if (normalizedEntry !== entry) mutated = true;
1096
- }
1097
- return mutated ? decoded : record;
1098
- }
1099
- function omitTypeName(record) {
1100
- const { $typeName: _, ...rest } = record;
1101
- return rest;
1102
- }
1103
- function streamCursorWithWireMode(model, context, options, wireMode, timing) {
1104
- const stream = new AssistantMessageEventStream();
1105
- (async () => {
1106
- const startTime = timing?.startTime ?? performance.now();
1107
- let firstTokenTime;
1108
- const output = {
1109
- role: "assistant",
1110
- content: [],
1111
- api: "cursor-agent",
1112
- provider: model.provider,
1113
- model: model.id,
1114
- usage: {
1115
- input: 0,
1116
- output: 0,
1117
- cacheRead: 0,
1118
- cacheWrite: 0,
1119
- totalTokens: 0,
1120
- cost: {
1121
- input: 0,
1122
- output: 0,
1123
- cacheRead: 0,
1124
- cacheWrite: 0,
1125
- total: 0
1126
- }
1127
- },
1128
- stopReason: "stop",
1129
- timestamp: timing?.timestamp ?? Date.now()
1130
- };
1131
- const inFlightDispatches = /* @__PURE__ */ new Set();
1132
- let abortSettled;
1133
- const drainInFlightDispatches = async () => {
1134
- const signal = options?.signal;
1135
- while (inFlightDispatches.size > 0) {
1136
- if (signal?.aborted) return;
1137
- const settled = Promise.all(inFlightDispatches);
1138
- if (!signal) {
1139
- await settled;
1140
- continue;
1141
- }
1142
- abortSettled ??= new Promise((resolve) => signal.addEventListener("abort", () => resolve(), { once: true }));
1143
- await Promise.race([settled, abortSettled]);
1144
- }
1145
- };
1146
- let h2Client = null;
1147
- let h2Request = null;
1148
- let heartbeatTimer = null;
1149
- let debugResponseLogPromise;
1150
- const h2Completion = Promise.withResolvers();
1151
- let h2Settled = false;
1152
- let sawTurnEnded = false;
1153
- let endStreamError = null;
1154
- let sawProgressOrSideEffect = false;
1155
- let openBlockState;
1156
- const settleH2 = (error) => {
1157
- if (h2Settled) return;
1158
- h2Settled = true;
1159
- if (error !== void 0) {
1160
- h2Completion.reject(error);
1161
- return;
1162
- }
1163
- if (endStreamError) {
1164
- h2Completion.reject(endStreamError);
1165
- return;
1166
- }
1167
- if (!sawTurnEnded) {
1168
- h2Completion.reject(new ProviderResponseError("Cursor stream ended before turnEnded", { kind: "incomplete-stream" }));
1169
- return;
1170
- }
1171
- h2Completion.resolve();
1172
- };
1173
- let baseConversationId;
1174
- let conversationId;
1175
- let usageState;
1176
- let serializedFallbackWireModelId;
1177
- try {
1178
- const apiKey = options?.apiKey;
1179
- if (!apiKey) throw new MissingApiKeyError(void 0, "Cursor API key (access token) is required");
1180
- baseConversationId = options?.conversationId ?? options?.sessionId ?? crypto.randomUUID();
1181
- conversationId = rotatedConversationIds.get(baseConversationId) ?? baseConversationId;
1182
- const rotatedFresh = freshRotatedConversationIds.has(conversationId);
1183
- const blobStore = conversationBlobStores.get(conversationId) ?? /* @__PURE__ */ new Map();
1184
- conversationBlobStores.set(conversationId, blobStore);
1185
- const cachedState = rotatedFresh ? void 0 : conversationStateCache.get(conversationId);
1186
- const builtRequest = await buildGrpcRequestForWireMode(model, context, options, {
1187
- conversationId,
1188
- blobStore,
1189
- conversationState: cachedState,
1190
- rotatedFresh
1191
- }, wireMode);
1192
- const { requestBytes, conversationState } = builtRequest;
1193
- serializedFallbackWireModelId = builtRequest.fallbackWireModelId;
1194
- conversationStateCache.set(conversationId, conversationState);
1195
- const requestContextTools = buildMcpToolDefinitions(context.tools, model.requiresCursorToolSchemaProjection === true);
1196
- const requestContextRules = buildCursorRequestContextRules(context.systemPrompt);
1197
- const baseUrl = model.baseUrl || "https://api2.cursor.sh";
1198
- const requestPath = "/agent.v1.AgentService/Run";
1199
- const requestHeaders = {
1200
- ...sanitizeCursorCallerHeaders(options?.headers),
1201
- ":method": "POST",
1202
- ":path": requestPath,
1203
- "content-type": "application/connect+proto",
1204
- "connect-protocol-version": "1",
1205
- te: "trailers",
1206
- authorization: `Bearer ${apiKey}`,
1207
- "x-ghost-mode": "true",
1208
- "x-cursor-client-version": CURSOR_CLIENT_VERSION,
1209
- "x-cursor-client-type": "cli",
1210
- "x-request-id": crypto.randomUUID()
1211
- };
1212
- const debugSession = isRequestDebugEnabled() ? await createRequestDebugSession({
1213
- protocol: "http2",
1214
- method: "POST",
1215
- url: new URL(requestPath, baseUrl).toString(),
1216
- headers: requestHeaders,
1217
- bodyBase64: Buffer.from(requestBytes).toString("base64")
1218
- }) : void 0;
1219
- const proxyUrl = getProxyForUrl(model.provider, new URL(baseUrl));
1220
- if (proxyUrl) {
1221
- const tlsSocket = await connectProxiedSocket(proxyUrl, baseUrl, {
1222
- signal: options?.signal,
1223
- timeoutMs: CURSOR_PROXY_TUNNEL_TIMEOUT_MS
1224
- });
1225
- h2Client = http2.connect(baseUrl, { createConnection: () => tlsSocket });
1226
- } else h2Client = http2.connect(baseUrl);
1227
- h2Client.on("error", (error) => settleH2(mapH2TransportError(error, baseUrl)));
1228
- h2Request = h2Client.request(requestHeaders);
1229
- stream.push({
1230
- type: "start",
1231
- partial: output
1232
- });
1233
- let pendingBuffer = Buffer.alloc(0);
1234
- let currentTextBlock = null;
1235
- let currentThinkingBlock = null;
1236
- let currentToolCall = null;
1237
- const resolvedMcpToolCallIds = /* @__PURE__ */ new Set();
1238
- usageState = { sawTokenDelta: false };
1239
- const state = {
1240
- get currentTextBlock() {
1241
- return currentTextBlock;
1242
- },
1243
- get currentThinkingBlock() {
1244
- return currentThinkingBlock;
1245
- },
1246
- get currentToolCall() {
1247
- return currentToolCall;
1248
- },
1249
- openToolCalls: /* @__PURE__ */ new Map(),
1250
- resolvedMcpToolCallIds,
1251
- get firstTokenTime() {
1252
- return firstTokenTime;
1253
- },
1254
- setTextBlock: (b) => {
1255
- currentTextBlock = b;
1256
- },
1257
- setThinkingBlock: (b) => {
1258
- currentThinkingBlock = b;
1259
- },
1260
- setToolCall: (t) => {
1261
- currentToolCall = t;
1262
- },
1263
- setFirstTokenTime: () => {
1264
- if (!firstTokenTime) firstTokenTime = performance.now();
1265
- },
1266
- onTodoSnapshot: options?.execHandlers?.todoSync?.bind(options.execHandlers),
1267
- onToolResult: options?.onToolResult
1268
- };
1269
- openBlockState = state;
1270
- const onConversationCheckpoint = (checkpoint) => {
1271
- conversationStateCache.set(conversationId, checkpoint);
1272
- };
1273
- h2Request.on("response", (headers) => {
1274
- debugResponseLogPromise = debugSession?.openResponseLog(`HTTP/2 ${headers[":status"] ?? ""}`.trim(), headers);
1275
- });
1276
- h2Request.on("data", (chunk) => {
1277
- if (debugResponseLogPromise) debugResponseLogPromise.then((log) => {
1278
- log?.write(chunk);
1279
- });
1280
- pendingBuffer = pendingBuffer.length === 0 ? chunk : Buffer.concat([pendingBuffer, chunk]);
1281
- while (pendingBuffer.length >= 5) {
1282
- const flags = pendingBuffer[0];
1283
- const msgLen = pendingBuffer.readUInt32BE(1);
1284
- if (pendingBuffer.length < 5 + msgLen) break;
1285
- const messageBytes = pendingBuffer.subarray(5, 5 + msgLen);
1286
- pendingBuffer = pendingBuffer.subarray(5 + msgLen);
1287
- if (flags & CONNECT_END_STREAM_FLAG) {
1288
- const endError = parseConnectEndStream(messageBytes);
1289
- if (endError) {
1290
- endStreamError = endError;
1291
- h2Request?.close();
1292
- }
1293
- continue;
1294
- }
1295
- try {
1296
- const serverMessage = fromBinary(AgentServerMessageSchema, messageBytes);
1297
- const interactionCase = (serverMessage.message.case === "interactionUpdate" ? serverMessage.message.value : void 0)?.message?.case;
1298
- if (interactionCase !== "heartbeat") sawProgressOrSideEffect = true;
1299
- const isTurnEnded = interactionCase === "turnEnded";
1300
- const dispatch = handleServerMessage(serverMessage, output, stream, state, blobStore, h2Request, options?.execHandlers, options?.onToolResult, usageState, requestContextTools, requestContextRules, onConversationCheckpoint, options?.externalToolExecutor).catch((error) => {
1301
- log("error", "handleServerMessage", { error: String(error) });
1302
- });
1303
- inFlightDispatches.add(dispatch);
1304
- dispatch.finally(() => inFlightDispatches.delete(dispatch));
1305
- if (isTurnEnded) sawTurnEnded = true;
1306
- } catch (e) {
1307
- log("error", "parseServerMessage", { error: String(e) });
1308
- }
1309
- }
1310
- });
1311
- const sendHeartbeat = () => {
1312
- if (!h2Request || h2Request.closed) return;
1313
- const heartbeatMessage = create(AgentClientMessageSchema, { message: {
1314
- case: "clientHeartbeat",
1315
- value: create(ClientHeartbeatSchema, {})
1316
- } });
1317
- const heartbeatBytes = toBinary(AgentClientMessageSchema, heartbeatMessage);
1318
- h2Request.write(frameConnectMessage(heartbeatBytes));
1319
- };
1320
- const closeDebugLog = async () => {
1321
- await (await debugResponseLogPromise)?.close();
1322
- };
1323
- h2Request.on("trailers", (trailers) => {
1324
- const status = trailers["grpc-status"];
1325
- const msg = trailers["grpc-message"];
1326
- if (status && status !== "0" && !endStreamError) endStreamError = new ProviderResponseError(`gRPC error ${status}: ${decodeURIComponent(String(msg || ""))}`, { kind: "envelope" });
1327
- });
1328
- h2Request.on("end", () => {
1329
- closeDebugLog().then(() => settleH2()).catch((error) => settleH2(error));
1330
- });
1331
- h2Request.on("error", (error) => {
1332
- const mapped = mapH2TransportError(error, baseUrl);
1333
- closeDebugLog().finally(() => settleH2(mapped));
1334
- });
1335
- if (options?.signal) options.signal.addEventListener("abort", () => {
1336
- h2Request?.close();
1337
- closeDebugLog().finally(() => {
1338
- settleH2(new AbortError());
1339
- });
1340
- });
1341
- h2Request.write(frameConnectMessage(requestBytes));
1342
- heartbeatTimer = setInterval(sendHeartbeat, 5e3);
1343
- await h2Completion.promise;
1344
- if (conversationId && baseConversationId && conversationId !== baseConversationId) {
1345
- successfulRotatedConversationIds.add(conversationId);
1346
- freshRotatedConversationIds.delete(conversationId);
1347
- }
1348
- await drainInFlightDispatches();
1349
- endCurrentTextBlock(output, stream, state);
1350
- endCurrentThinkingBlock(output, stream, state);
1351
- flushOpenToolCalls(output, stream, state);
1352
- calculateCost(model, output.usage, output.timestamp);
1353
- output.duration = performance.now() - startTime;
1354
- if (firstTokenTime) output.ttft = firstTokenTime - startTime;
1355
- stream.push({
1356
- type: "done",
1357
- reason: output.stopReason,
1358
- message: output
1359
- });
1360
- stream.end();
1361
- } catch (error) {
1362
- if ((wireMode === "normalized" && !sawProgressOrSideEffect && !options?.signal?.aborted && error instanceof Error && CURSOR_MODEL_NOT_FOUND_PATTERN.test(error.message) ? serializedFallbackWireModelId : void 0) !== void 0) {
1363
- if (heartbeatTimer) {
1364
- clearInterval(heartbeatTimer);
1365
- heartbeatTimer = null;
1366
- }
1367
- h2Request?.close();
1368
- h2Client?.close();
1369
- const fallbackStream = streamCursorWithWireMode(model, context, options, "discovered", {
1370
- startTime,
1371
- timestamp: output.timestamp
1372
- });
1373
- stream.forwardLocalWorkFrom(fallbackStream);
1374
- try {
1375
- for await (const event of fallbackStream) {
1376
- if (event.type === "start") continue;
1377
- stream.push(event);
1378
- }
1379
- const fallbackResult = await fallbackStream.result();
1380
- if (!stream.resultSettled) stream.end(fallbackResult);
1381
- } finally {
1382
- stream.forwardLocalWorkFrom(void 0);
1383
- }
1384
- return;
1385
- }
1386
- await drainInFlightDispatches();
1387
- if (openBlockState) {
1388
- endCurrentTextBlock(output, stream, openBlockState);
1389
- endCurrentThinkingBlock(output, stream, openBlockState);
1390
- flushOpenToolCalls(output, stream, openBlockState);
1391
- }
1392
- const result = await finalize(error, {
1393
- api: model.api,
1394
- signal: options?.signal
1395
- });
1396
- const currentRotated = baseConversationId === void 0 ? void 0 : rotatedConversationIds.get(baseConversationId);
1397
- const canRotate = currentRotated === void 0 || successfulRotatedConversationIds.has(currentRotated);
1398
- if (conversationId !== void 0 && baseConversationId !== void 0 && usageState !== void 0 && !usageState.sawTokenDelta && RESOURCE_EXHAUSTED_PATTERN.test(result.message) && canRotate) {
1399
- const rotated = crypto.randomUUID();
1400
- if (currentRotated) successfulRotatedConversationIds.delete(currentRotated);
1401
- rotatedConversationIds.set(baseConversationId, rotated);
1402
- freshRotatedConversationIds.add(rotated);
1403
- debug("cursor conversation rotated", {
1404
- base: baseConversationId,
1405
- from: conversationId,
1406
- to: rotated
1407
- });
1408
- }
1409
- output.stopReason = result.stopReason;
1410
- output.errorStatus = result.status;
1411
- output.errorId = result.id;
1412
- if (error instanceof ConnectEndStreamError) {
1413
- output.errorClassificationMessage = result.message;
1414
- output.errorMessage = error.diagnosticMessage;
1415
- } else output.errorMessage = result.message;
1416
- output.duration = performance.now() - startTime;
1417
- if (firstTokenTime) output.ttft = firstTokenTime - startTime;
1418
- stream.push({
1419
- type: "error",
1420
- reason: output.stopReason,
1421
- error: output
1422
- });
1423
- stream.end();
1424
- } finally {
1425
- await (await debugResponseLogPromise)?.close();
1426
- if (heartbeatTimer) {
1427
- clearInterval(heartbeatTimer);
1428
- heartbeatTimer = null;
1429
- }
1430
- h2Request?.close();
1431
- h2Client?.close();
1432
- }
1433
- })();
1434
- return stream;
1435
- }
1436
- /** Streams a Cursor Agent turn, retrying a discovered effort id when its normalized wire id is unavailable. */
1437
- const streamCursor = (model, context, options) => streamCursorWithWireMode(model, context, options, "normalized");
1438
- function markCursorExecResolved(block) {
1439
- block[kCursorExecResolved] = true;
1440
- }
1441
- /** Exported for tests: drives one Cursor server message through the stream (exec waits mark the stream busy). */
1442
- async function handleServerMessage(msg, output, stream, state, blobStore, h2Request, execHandlers, onToolResult, usageState, requestContextTools, requestContextRules = [], onConversationCheckpoint, externalToolExecutor = false) {
1443
- const msgCase = msg.message.case;
1444
- log("serverMessage", msgCase, msg.message.value);
1445
- if (msgCase === "interactionUpdate") processInteractionUpdate(msg.message.value, output, stream, state, usageState);
1446
- else if (msgCase === "kvServerMessage") handleKvServerMessage(msg.message.value, blobStore, h2Request);
1447
- else if (msgCase === "execServerMessage") await stream.trackLocalWork(handleExecServerMessage(msg.message.value, h2Request, execHandlers, onToolResult, requestContextTools, requestContextRules, output, stream, state, externalToolExecutor));
1448
- else if (msgCase === "interactionQuery") handleInteractionQuery(msg.message.value, h2Request);
1449
- else if (msgCase === "conversationCheckpointUpdate") handleConversationCheckpointUpdate(msg.message.value, output, usageState, onConversationCheckpoint);
1450
- }
1451
- function selectHostedFetchCall(toolCall) {
1452
- const oneof = toolCall?.tool;
1453
- if (oneof?.case === "fetchToolCall" || oneof?.case === "webFetchToolCall") return oneof.value;
1454
- }
1455
- function hostedFetchUnknown(toolCall) {
1456
- if (!toolCall) return false;
1457
- return protoUnknownFields(toolCall).some((field) => field.no === 37) || protoUnknownFields(toolCall.tool ?? {}).some((field) => field.no === 37);
1458
- }
1459
- function extractHttpUrlFromUnknown(message) {
1460
- for (const field of protoUnknownFields(message)) {
1461
- const match = new TextDecoder().decode(field.data).match(/https?:\/\/[^\x00-\x1f]+/);
1462
- if (match) return match[0];
1463
- }
1464
- const nested = message.tool;
1465
- return nested ? extractHttpUrlFromUnknown(nested) : void 0;
1466
- }
1467
- function describeHostedFetchResult(call) {
1468
- const result = call?.result?.result;
1469
- if (result?.case === "success") return {
1470
- text: result.value?.content || result.value?.url || "Fetched",
1471
- isError: false
1472
- };
1473
- if (result?.case === "error") return {
1474
- text: result.value?.error || "Fetch failed",
1475
- isError: true
1476
- };
1477
- return {
1478
- text: "Fetch completed",
1479
- isError: false
1480
- };
1481
- }
1482
- function protoUnknownFields(message) {
1483
- const raw = message.$unknown;
1484
- return Array.isArray(raw) ? raw : [];
1485
- }
1486
- function handleKvServerMessage(kvMsg, blobStore, h2Request) {
1487
- const kvCase = kvMsg.message.case;
1488
- if (kvCase === "getBlobArgs") {
1489
- const blobId = kvMsg.message.value.blobId;
1490
- const blobIdKey = Buffer.from(blobId).toString("hex");
1491
- const blobData = blobStore.get(blobIdKey);
1492
- const response = create(KvClientMessageSchema, {
1493
- id: kvMsg.id,
1494
- message: {
1495
- case: "getBlobResult",
1496
- value: create(GetBlobResultSchema, blobData ? { blobData } : {})
1497
- }
1498
- });
1499
- const kvClientMessage = create(AgentClientMessageSchema, { message: {
1500
- case: "kvClientMessage",
1501
- value: response
1502
- } });
1503
- const responseBytes = toBinary(AgentClientMessageSchema, kvClientMessage);
1504
- h2Request.write(frameConnectMessage(responseBytes));
1505
- log("kvClient", "getBlobResult", { blobId: blobIdKey.slice(0, 40) });
1506
- } else if (kvCase === "setBlobArgs") {
1507
- const { blobId, blobData } = kvMsg.message.value;
1508
- const blobIdKey = Buffer.from(blobId).toString("hex");
1509
- blobStore.set(blobIdKey, blobData);
1510
- const response = create(KvClientMessageSchema, {
1511
- id: kvMsg.id,
1512
- message: {
1513
- case: "setBlobResult",
1514
- value: create(SetBlobResultSchema, {})
1515
- }
1516
- });
1517
- const kvClientMessage = create(AgentClientMessageSchema, { message: {
1518
- case: "kvClientMessage",
1519
- value: response
1520
- } });
1521
- const responseBytes = toBinary(AgentClientMessageSchema, kvClientMessage);
1522
- h2Request.write(frameConnectMessage(responseBytes));
1523
- log("kvClient", "setBlobResult", { blobId: blobIdKey.slice(0, 40) });
1524
- }
1525
- }
1526
- function sendShellStreamEvent(h2Request, execMsg, event) {
1527
- sendExecClientMessage(h2Request, execMsg, "shellStream", create(ShellStreamSchema, { event }));
1528
- }
1529
- function sanitizeShellExecResult(execResult) {
1530
- const result = execResult.result;
1531
- if (!result) return execResult;
1532
- switch (result.case) {
1533
- case "success":
1534
- case "failure": {
1535
- const value = result.value;
1536
- return {
1537
- ...execResult,
1538
- result: {
1539
- case: result.case,
1540
- value: {
1541
- ...value,
1542
- stdout: value.stdout ? sanitizeText(value.stdout) : value.stdout,
1543
- stderr: value.stderr ? sanitizeText(value.stderr) : value.stderr
1544
- }
1545
- }
1546
- };
1547
- }
1548
- default: return execResult;
1549
- }
1550
- }
1551
- async function handleShellStreamArgs(args, execMsg, h2Request, execHandlers, onToolResult) {
1552
- const normalizedWorkingDirectory = args.workingDirectory || process.cwd();
1553
- const normalizedArgs = {
1554
- ...args,
1555
- workingDirectory: normalizedWorkingDirectory
1556
- };
1557
- const startTs = performance.now();
1558
- log("shellStream", "start", {
1559
- command: args.command,
1560
- workingDirectory: normalizedWorkingDirectory,
1561
- execId: execMsg.execId,
1562
- hasExecHandlers: !!execHandlers,
1563
- hasShell: !!execHandlers?.shell,
1564
- hasShellStream: !!execHandlers?.shellStream
1565
- });
1566
- sendShellStreamEvent(h2Request, execMsg, {
1567
- case: "start",
1568
- value: create(ShellStreamStartSchema, {})
1569
- });
1570
- let stdoutBuffer = "";
1571
- let stderrBuffer = "";
1572
- const incompleteEscapeRegex = /\x1b(|\[|\[\d*|\[\?|\[\?\d*|\]\d*;?)$/;
1573
- const flushStdout = () => {
1574
- if (stdoutBuffer) {
1575
- let safeEnd = stdoutBuffer.length;
1576
- const match = stdoutBuffer.match(incompleteEscapeRegex);
1577
- if (match && match[0].length > 0) safeEnd = stdoutBuffer.length - match[0].length;
1578
- const toSend = stdoutBuffer.slice(0, safeEnd);
1579
- const remaining = stdoutBuffer.slice(safeEnd);
1580
- if (toSend) sendShellStreamEvent(h2Request, execMsg, {
1581
- case: "stdout",
1582
- value: create(ShellStreamStdoutSchema, { data: sanitizeText(toSend) })
1583
- });
1584
- stdoutBuffer = remaining;
1585
- }
1586
- };
1587
- const flushStderr = () => {
1588
- if (stderrBuffer) {
1589
- let safeEnd = stderrBuffer.length;
1590
- const match = stderrBuffer.match(incompleteEscapeRegex);
1591
- if (match && match[0].length > 0) safeEnd = stderrBuffer.length - match[0].length;
1592
- const toSend = stderrBuffer.slice(0, safeEnd);
1593
- const remaining = stderrBuffer.slice(safeEnd);
1594
- if (toSend) sendShellStreamEvent(h2Request, execMsg, {
1595
- case: "stderr",
1596
- value: create(ShellStreamStderrSchema, { data: sanitizeText(toSend) })
1597
- });
1598
- stderrBuffer = remaining;
1599
- }
1600
- };
1601
- let stdoutFlushTimer = null;
1602
- let stderrFlushTimer = null;
1603
- const scheduleStdoutFlush = () => {
1604
- if (!stdoutFlushTimer) stdoutFlushTimer = setTimeout(() => {
1605
- stdoutFlushTimer = null;
1606
- flushStdout();
1607
- }, 100);
1608
- };
1609
- const scheduleStderrFlush = () => {
1610
- if (!stderrFlushTimer) stderrFlushTimer = setTimeout(() => {
1611
- stderrFlushTimer = null;
1612
- flushStderr();
1613
- }, 100);
1614
- };
1615
- const streamCallbacks = {
1616
- onStdout(data) {
1617
- stdoutBuffer += data;
1618
- if (stdoutBuffer.includes("\n") || stdoutBuffer.length > 4096) {
1619
- if (stdoutFlushTimer) {
1620
- clearTimeout(stdoutFlushTimer);
1621
- stdoutFlushTimer = null;
1622
- }
1623
- flushStdout();
1624
- } else scheduleStdoutFlush();
1625
- },
1626
- onStderr(data) {
1627
- stderrBuffer += data;
1628
- if (stderrBuffer.includes("\n") || stderrBuffer.length > 4096) {
1629
- if (stderrFlushTimer) {
1630
- clearTimeout(stderrFlushTimer);
1631
- stderrFlushTimer = null;
1632
- }
1633
- flushStderr();
1634
- } else scheduleStderrFlush();
1635
- }
1636
- };
1637
- const streamHandler = execHandlers?.shellStream?.bind(execHandlers);
1638
- const batchHandler = execHandlers?.shell?.bind(execHandlers);
1639
- const { execResult } = await resolveExecHandler(args, streamHandler ? (shellArgs) => streamHandler(shellArgs, streamCallbacks) : batchHandler, onToolResult, (toolResult) => buildShellResultFromToolResult(normalizedArgs, toolResult), (reason) => buildShellRejectedResult(normalizedArgs.command, normalizedArgs.workingDirectory, reason), (error) => buildShellFailureResult(normalizedArgs.command, normalizedArgs.workingDirectory, error), {
1640
- toolCallId: args.toolCallId,
1641
- toolName: "bash"
1642
- });
1643
- const sendBufferedOutput = !streamHandler;
1644
- const sanitizedExecResult = sanitizeShellExecResult(execResult);
1645
- if (stdoutFlushTimer) clearTimeout(stdoutFlushTimer);
1646
- if (stderrFlushTimer) clearTimeout(stderrFlushTimer);
1647
- flushStdout();
1648
- flushStderr();
1649
- sendShellStreamExitFromResult(h2Request, execMsg, sanitizedExecResult, sendBufferedOutput);
1650
- sendExecClientMessage(h2Request, execMsg, "shellResult", sanitizedExecResult);
1651
- sendExecClientStreamClose(h2Request, execMsg);
1652
- log("shellStream", "done", { elapsed: performance.now() - startTs });
1653
- }
1654
- function sendShellStreamExitFromResult(h2Request, execMsg, execResult, sendBufferedOutput) {
1655
- const result = execResult.result;
1656
- switch (result.case) {
1657
- case "success": {
1658
- const value = result.value;
1659
- if (sendBufferedOutput) {
1660
- if (value.stdout) sendShellStreamEvent(h2Request, execMsg, {
1661
- case: "stdout",
1662
- value: create(ShellStreamStdoutSchema, { data: sanitizeText(value.stdout) })
1663
- });
1664
- if (value.stderr) sendShellStreamEvent(h2Request, execMsg, {
1665
- case: "stderr",
1666
- value: create(ShellStreamStderrSchema, { data: sanitizeText(value.stderr) })
1667
- });
1668
- }
1669
- sendShellStreamEvent(h2Request, execMsg, {
1670
- case: "exit",
1671
- value: create(ShellStreamExitSchema, {
1672
- code: value.exitCode,
1673
- cwd: value.workingDirectory,
1674
- aborted: false
1675
- })
1676
- });
1677
- return;
1678
- }
1679
- case "failure": {
1680
- const value = result.value;
1681
- if (sendBufferedOutput) {
1682
- if (value.stdout) sendShellStreamEvent(h2Request, execMsg, {
1683
- case: "stdout",
1684
- value: create(ShellStreamStdoutSchema, { data: sanitizeText(value.stdout) })
1685
- });
1686
- if (value.stderr) sendShellStreamEvent(h2Request, execMsg, {
1687
- case: "stderr",
1688
- value: create(ShellStreamStderrSchema, { data: sanitizeText(value.stderr) })
1689
- });
1690
- }
1691
- sendShellStreamEvent(h2Request, execMsg, {
1692
- case: "exit",
1693
- value: create(ShellStreamExitSchema, {
1694
- code: value.exitCode,
1695
- cwd: value.workingDirectory,
1696
- aborted: value.aborted,
1697
- abortReason: value.abortReason
1698
- })
1699
- });
1700
- return;
1701
- }
1702
- case "rejected":
1703
- sendShellStreamEvent(h2Request, execMsg, {
1704
- case: "rejected",
1705
- value: result.value
1706
- });
1707
- sendShellStreamEvent(h2Request, execMsg, {
1708
- case: "exit",
1709
- value: create(ShellStreamExitSchema, {
1710
- code: 1,
1711
- cwd: result.value.workingDirectory,
1712
- aborted: false
1713
- })
1714
- });
1715
- return;
1716
- case "timeout": {
1717
- const value = result.value;
1718
- sendShellStreamEvent(h2Request, execMsg, {
1719
- case: "stderr",
1720
- value: create(ShellStreamStderrSchema, { data: `Command timed out after ${value.timeoutMs}ms` })
1721
- });
1722
- sendShellStreamEvent(h2Request, execMsg, {
1723
- case: "exit",
1724
- value: create(ShellStreamExitSchema, {
1725
- code: 1,
1726
- cwd: value.workingDirectory,
1727
- aborted: true
1728
- })
1729
- });
1730
- return;
1731
- }
1732
- case "permissionDenied":
1733
- sendShellStreamEvent(h2Request, execMsg, {
1734
- case: "permissionDenied",
1735
- value: result.value
1736
- });
1737
- sendShellStreamEvent(h2Request, execMsg, {
1738
- case: "exit",
1739
- value: create(ShellStreamExitSchema, {
1740
- code: 1,
1741
- cwd: result.value.workingDirectory,
1742
- aborted: false
1743
- })
1744
- });
1745
- return;
1746
- default: return;
1747
- }
1748
- }
1749
- async function handleExecServerMessage(execMsg, h2Request, execHandlers, onToolResult, requestContextTools, requestContextRules, output, stream, state, externalToolExecutor) {
1750
- const execCase = execMsg.message.case;
1751
- log("exec", "dispatch", {
1752
- execCase,
1753
- execId: execMsg.execId,
1754
- hasHandlers: !!execHandlers
1755
- });
1756
- if (execCase === "requestContextArgs") {
1757
- const requestContext = create(RequestContextSchema, {
1758
- rules: requestContextRules,
1759
- repositoryInfo: [],
1760
- tools: requestContextTools,
1761
- gitRepos: [],
1762
- projectLayouts: [],
1763
- mcpInstructions: [],
1764
- fileContents: {},
1765
- customSubagents: []
1766
- });
1767
- sendExecClientMessage(h2Request, execMsg, "requestContextResult", create(RequestContextResultSchema, { result: {
1768
- case: "success",
1769
- value: create(RequestContextSuccessSchema, { requestContext })
1770
- } }));
1771
- log("execClient", "requestContextResult");
1772
- return;
1773
- }
1774
- if (!execCase) {
1775
- log("warn", "unknownExecVariant", {
1776
- id: execMsg.id,
1777
- execId: execMsg.execId
1778
- });
1779
- sendExecClientThrow(h2Request, execMsg, "Unknown exec message variant", "unknown_exec_variant");
1780
- return;
1781
- }
1782
- switch (execCase) {
1783
- case "readArgs": {
1784
- const args = execMsg.message.value;
1785
- if (!args.toolCallId) args.toolCallId = crypto.randomUUID();
1786
- const editOwned = isEditOwnedToolCallId(state, output, args.toolCallId);
1787
- const composed = editOwned ? cursorEditOwnedReadPath(args.path, args.offset, args.limit) : args.path;
1788
- const handlerArgs = editOwned ? composed === null ? args : {
1789
- ...args,
1790
- path: composed,
1791
- offset: void 0,
1792
- limit: void 0
1793
- } : args;
1794
- if (!editOwned) synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "read", { path: piReadDisplayPath(args.path, args.offset, args.limit) });
1795
- const { execResult } = await resolveExecHandler(handlerArgs, execHandlers?.read?.bind(execHandlers), editOwned ? void 0 : onToolResult, (toolResult) => buildReadResultFromToolResult(args.path, toolResult, args.offset !== void 0 || args.limit !== void 0 || piReadPathHasRange(args.path)), (reason) => buildReadRejectedResult(args.path, reason), (error) => buildReadErrorResult(args.path, error), editOwned ? null : {
1796
- toolCallId: args.toolCallId,
1797
- toolName: "read"
1798
- });
1799
- sendExecClientMessage(h2Request, execMsg, "readResult", execResult);
1800
- return;
1801
- }
1802
- case "lsArgs": {
1803
- const args = execMsg.message.value;
1804
- if (!args.toolCallId) args.toolCallId = crypto.randomUUID();
1805
- synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "read", { path: args.path });
1806
- const { execResult } = await resolveExecHandler(args, execHandlers?.ls?.bind(execHandlers), onToolResult, (toolResult) => buildLsResultFromToolResult(args.path, toolResult), (reason) => buildLsRejectedResult(args.path, reason), (error) => buildLsErrorResult(args.path, error), {
1807
- toolCallId: args.toolCallId,
1808
- toolName: "read"
1809
- });
1810
- sendExecClientMessage(h2Request, execMsg, "lsResult", execResult);
1811
- return;
1812
- }
1813
- case "grepArgs": {
1814
- const args = execMsg.message.value;
1815
- if (!args.toolCallId) args.toolCallId = crypto.randomUUID();
1816
- const emptyPatternError = emptyGrepPatternRejection(args.pattern, args.glob);
1817
- if (emptyPatternError !== null) {
1818
- sendExecClientMessage(h2Request, execMsg, "grepResult", buildGrepErrorResult(emptyPatternError));
1819
- return;
1820
- }
1821
- const searchPath = args.glob ? `${args.path || "."}/${args.glob}` : args.path || ".";
1822
- synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "grep", {
1823
- pattern: args.pattern,
1824
- path: searchPath,
1825
- case: args.caseInsensitive === true ? false : void 0,
1826
- skip: piGrepSkip(args.offset)
1827
- });
1828
- const { execResult } = await resolveExecHandler(args, execHandlers?.grep?.bind(execHandlers), onToolResult, (toolResult) => buildGrepResultFromToolResult(args, toolResult), (reason) => buildGrepErrorResult(reason), (error) => buildGrepErrorResult(error), {
1829
- toolCallId: args.toolCallId,
1830
- toolName: "grep"
1831
- });
1832
- sendExecClientMessage(h2Request, execMsg, "grepResult", execResult);
1833
- return;
1834
- }
1835
- case "writeArgs": {
1836
- const args = execMsg.message.value;
1837
- if (!args.toolCallId) args.toolCallId = crypto.randomUUID();
1838
- const editOwned = isEditOwnedToolCallId(state, output, args.toolCallId);
1839
- const content = args.fileText ?? new TextDecoder().decode(args.fileBytes ?? /* @__PURE__ */ new Uint8Array());
1840
- if (!editOwned) synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "write", {
1841
- path: args.path,
1842
- content
1843
- });
1844
- const write = execHandlers?.write?.bind(execHandlers);
1845
- const { execResult } = await resolveExecHandler(args, write ? async (writeArgs) => {
1846
- const result = await write(writeArgs);
1847
- return editOwned ? remapExecHandlerToolName(result, "edit") : result;
1848
- } : void 0, onToolResult, (toolResult) => buildWriteResultFromToolResult({
1849
- path: args.path,
1850
- fileText: args.fileText,
1851
- fileBytes: args.fileBytes,
1852
- returnFileContentAfterWrite: args.returnFileContentAfterWrite
1853
- }, toolResult), (reason) => buildWriteRejectedResult(args.path, reason), (error) => buildWriteErrorResult(args.path, error), {
1854
- toolCallId: args.toolCallId,
1855
- toolName: editOwned ? "edit" : "write"
1856
- });
1857
- if (editOwned) markEditToolCallPaired(state, args.toolCallId);
1858
- sendExecClientMessage(h2Request, execMsg, "writeResult", execResult);
1859
- return;
1860
- }
1861
- case "deleteArgs": {
1862
- const args = execMsg.message.value;
1863
- if (!args.toolCallId) args.toolCallId = crypto.randomUUID();
1864
- synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "delete", { path: args.path });
1865
- const { execResult } = await resolveExecHandler(args, execHandlers?.delete?.bind(execHandlers), onToolResult, (toolResult) => buildDeleteResultFromToolResult(args.path, toolResult), (reason) => buildDeleteRejectedResult(args.path, reason), (error) => buildDeleteErrorResult(args.path, error), {
1866
- toolCallId: args.toolCallId,
1867
- toolName: "delete"
1868
- });
1869
- sendExecClientMessage(h2Request, execMsg, "deleteResult", execResult);
1870
- return;
1871
- }
1872
- case "shellArgs": {
1873
- const args = execMsg.message.value;
1874
- if (!args.toolCallId) args.toolCallId = crypto.randomUUID();
1875
- const normalizedArgs = {
1876
- ...args,
1877
- workingDirectory: args.workingDirectory || process.cwd()
1878
- };
1879
- const shellTimeout = args.timeout && args.timeout > 0 ? args.timeout : void 0;
1880
- synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "bash", {
1881
- command: args.command,
1882
- cwd: args.workingDirectory || void 0,
1883
- timeout: shellTimeout
1884
- });
1885
- const { execResult } = await resolveExecHandler(args, execHandlers?.shell?.bind(execHandlers), onToolResult, (toolResult) => buildShellResultFromToolResult(normalizedArgs, toolResult), (reason) => buildShellRejectedResult(normalizedArgs.command, normalizedArgs.workingDirectory, reason), (error) => buildShellFailureResult(normalizedArgs.command, normalizedArgs.workingDirectory, error), {
1886
- toolCallId: args.toolCallId,
1887
- toolName: "bash"
1888
- });
1889
- sendExecClientMessage(h2Request, execMsg, "shellResult", sanitizeShellExecResult(execResult));
1890
- return;
1891
- }
1892
- case "shellStreamArgs": {
1893
- const args = execMsg.message.value;
1894
- if (!args.toolCallId) args.toolCallId = crypto.randomUUID();
1895
- const shellStreamTimeout = args.timeout && args.timeout > 0 ? args.timeout : void 0;
1896
- synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "bash", {
1897
- command: args.command,
1898
- cwd: args.workingDirectory || void 0,
1899
- timeout: shellStreamTimeout
1900
- });
1901
- await handleShellStreamArgs(args, execMsg, h2Request, execHandlers, onToolResult);
1902
- return;
1903
- }
1904
- case "backgroundShellSpawnArgs": {
1905
- const args = execMsg.message.value;
1906
- sendExecClientMessage(h2Request, execMsg, "backgroundShellSpawnResult", create(BackgroundShellSpawnResultSchema, { result: {
1907
- case: "rejected",
1908
- value: create(ShellRejectedSchema, {
1909
- command: args.command,
1910
- workingDirectory: args.workingDirectory,
1911
- reason: "Not implemented",
1912
- isReadonly: false
1913
- })
1914
- } }));
1915
- return;
1916
- }
1917
- case "writeShellStdinArgs":
1918
- sendExecClientMessage(h2Request, execMsg, "writeShellStdinResult", create(WriteShellStdinResultSchema, { result: {
1919
- case: "error",
1920
- value: create(WriteShellStdinErrorSchema, { error: "Not implemented" })
1921
- } }));
1922
- return;
1923
- case "fetchArgs": {
1924
- const args = execMsg.message.value;
1925
- sendExecClientMessage(h2Request, execMsg, "fetchResult", create(FetchResultSchema, { result: {
1926
- case: "error",
1927
- value: create(FetchErrorSchema, {
1928
- url: args.url,
1929
- error: "Not implemented"
1930
- })
1931
- } }));
1932
- return;
1933
- }
1934
- case "diagnosticsArgs": {
1935
- const args = execMsg.message.value;
1936
- if (!args.toolCallId) args.toolCallId = crypto.randomUUID();
1937
- synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "lsp", {
1938
- action: "diagnostics",
1939
- file: args.path
1940
- });
1941
- const { execResult } = await resolveExecHandler(args, execHandlers?.diagnostics?.bind(execHandlers), onToolResult, (toolResult) => buildDiagnosticsResultFromToolResult(args.path, toolResult), (reason) => buildDiagnosticsRejectedResult(args.path, reason), (error) => buildDiagnosticsErrorResult(args.path, error), {
1942
- toolCallId: args.toolCallId,
1943
- toolName: "lsp"
1944
- });
1945
- sendExecClientMessage(h2Request, execMsg, "diagnosticsResult", execResult);
1946
- return;
1947
- }
1948
- case "mcpArgs": {
1949
- const args = execMsg.message.value;
1950
- const mcpCall = decodeMcpCall(args);
1951
- if (mcpCall.approvalOnly) {
1952
- const approved = await execHandlers?.mcpApprovalPreflight?.(mcpCall) === true;
1953
- sendExecClientMessage(h2Request, execMsg, "mcpResult", create(McpResultSchema, { result: approved ? {
1954
- case: "approved",
1955
- value: create(McpApprovedSchema, {})
1956
- } : {
1957
- case: "rejected",
1958
- value: create(McpRejectedSchema, { reason: `Tool "${mcpCall.toolName || mcpCall.name}" is not approved to run without asking.` })
1959
- } }));
1960
- return;
1961
- }
1962
- if (execHandlers?.mcp) {
1963
- const existingBlock = output.content.find((block) => block.type === "toolCall" && block.id === mcpCall.toolCallId);
1964
- if (existingBlock) markCursorExecResolved(existingBlock);
1965
- else {
1966
- synthesizeCursorExecToolCall(output, stream, state, mcpCall.toolCallId, mcpCall.toolName || mcpCall.name, mcpCall.args);
1967
- state.resolvedMcpToolCallIds.add(mcpCall.toolCallId);
1968
- }
1969
- }
1970
- const { execResult } = await resolveExecHandler(mcpCall, execHandlers?.mcp?.bind(execHandlers), onToolResult, (toolResult) => buildMcpResultFromToolResult(mcpCall, toolResult), (_reason) => externalToolExecutor && !execHandlers?.mcp ? buildMcpExternalHandoffResult() : buildMcpToolNotFoundResult(mcpCall), (error) => buildMcpErrorResult(error), execHandlers?.mcp ? {
1971
- toolCallId: mcpCall.toolCallId,
1972
- toolName: mcpCall.toolName
1973
- } : null);
1974
- sendExecClientMessage(h2Request, execMsg, "mcpResult", execResult);
1975
- return;
1976
- }
1977
- case "listMcpResourcesExecArgs": {
1978
- const args = execMsg.message.value;
1979
- let execResult;
1980
- const toolCallId = execHandlers?.listMcpResources ? crypto.randomUUID() : void 0;
1981
- if (toolCallId) synthesizeCursorExecToolCall(output, stream, state, toolCallId, "list_mcp_resources", { server: args.server });
1982
- try {
1983
- const resources = await execHandlers?.listMcpResources?.({ server: args.server }) ?? [];
1984
- execResult = create(ListMcpResourcesExecResultSchema, { result: {
1985
- case: "success",
1986
- value: create(ListMcpResourcesSuccessSchema, { resources: resources.map((resource) => create(ListMcpResourcesExecResult_McpResourceSchema, {
1987
- uri: resource.uri,
1988
- name: resource.name,
1989
- description: resource.description,
1990
- mimeType: resource.mimeType,
1991
- server: resource.server
1992
- })) })
1993
- } });
1994
- } catch (error) {
1995
- execResult = create(ListMcpResourcesExecResultSchema, { result: {
1996
- case: "error",
1997
- value: create(ListMcpResourcesErrorSchema, { error: error instanceof Error ? error.message : String(error) })
1998
- } });
1999
- }
2000
- if (toolCallId) {
2001
- const settled = execResult.result;
2002
- await pairSynthesizedExecResult(state, onToolResult, toolCallId, "list_mcp_resources", settled.case === "success" ? formatListedMcpResources(settled.value.resources) : settled.case === "error" ? settled.value.error || "Failed to list MCP resources" : settled.value?.reason ?? "Failed to list MCP resources", settled.case !== "success");
2003
- }
2004
- sendExecClientMessage(h2Request, execMsg, "listMcpResourcesExecResult", execResult);
2005
- return;
2006
- }
2007
- case "readMcpResourceExecArgs": {
2008
- const args = execMsg.message.value;
2009
- let execResult;
2010
- const toolCallId = execHandlers?.readMcpResource ? crypto.randomUUID() : void 0;
2011
- if (toolCallId) synthesizeCursorExecToolCall(output, stream, state, toolCallId, "read_mcp_resource", {
2012
- server: args.server,
2013
- uri: args.uri,
2014
- download_path: args.downloadPath
2015
- });
2016
- try {
2017
- const content = await execHandlers?.readMcpResource?.({
2018
- server: args.server,
2019
- uri: args.uri,
2020
- downloadPath: args.downloadPath
2021
- });
2022
- execResult = content ? create(ReadMcpResourceExecResultSchema, { result: {
2023
- case: "success",
2024
- value: create(ReadMcpResourceSuccessSchema, {
2025
- uri: content.uri,
2026
- name: content.name,
2027
- description: content.description,
2028
- mimeType: content.mimeType,
2029
- downloadPath: content.downloadPath,
2030
- content: content.downloadPath !== void 0 ? { case: void 0 } : content.text !== void 0 ? {
2031
- case: "text",
2032
- value: content.text
2033
- } : content.blob !== void 0 ? {
2034
- case: "blob",
2035
- value: content.blob
2036
- } : { case: void 0 }
2037
- })
2038
- } }) : create(ReadMcpResourceExecResultSchema, { result: {
2039
- case: "notFound",
2040
- value: create(ReadMcpResourceNotFoundSchema, { uri: args.uri })
2041
- } });
2042
- } catch (error) {
2043
- execResult = create(ReadMcpResourceExecResultSchema, { result: {
2044
- case: "error",
2045
- value: create(ReadMcpResourceErrorSchema, {
2046
- uri: args.uri,
2047
- error: error instanceof Error ? error.message : String(error)
2048
- })
2049
- } });
2050
- }
2051
- if (toolCallId) {
2052
- const settled = execResult.result;
2053
- let text;
2054
- switch (settled.case) {
2055
- case "success":
2056
- text = settled.value.downloadPath ? `Downloaded ${args.uri} to ${settled.value.downloadPath}` : `Read ${args.uri}`;
2057
- break;
2058
- case "notFound":
2059
- text = `No such resource: ${args.uri}`;
2060
- break;
2061
- case "rejected":
2062
- text = `Refused: ${settled.value.reason}`;
2063
- break;
2064
- default: text = settled.value?.error ?? `Failed to read ${args.uri}`;
2065
- }
2066
- await pairSynthesizedExecResult(state, onToolResult, toolCallId, "read_mcp_resource", text, settled.case !== "success");
2067
- }
2068
- sendExecClientMessage(h2Request, execMsg, "readMcpResourceExecResult", execResult);
2069
- return;
2070
- }
2071
- case "recordScreenArgs":
2072
- sendExecClientMessage(h2Request, execMsg, "recordScreenResult", create(RecordScreenResultSchema, { result: {
2073
- case: "failure",
2074
- value: create(RecordScreenFailureSchema, { error: NOT_IMPLEMENTED })
2075
- } }));
2076
- return;
2077
- case "computerUseArgs":
2078
- sendExecClientMessage(h2Request, execMsg, "computerUseResult", create(ComputerUseResultSchema, { result: {
2079
- case: "error",
2080
- value: create(ComputerUseErrorSchema, { error: NOT_IMPLEMENTED })
2081
- } }));
2082
- return;
2083
- case "piReadArgs": {
2084
- const args = execMsg.message.value;
2085
- const toolCallId = crypto.randomUUID();
2086
- synthesizeCursorExecToolCall(output, stream, state, toolCallId, "read", { path: piReadDisplayPath(args.path, args.offset, args.limit) });
2087
- const { execResult } = await resolveExecHandler({
2088
- args,
2089
- toolCallId
2090
- }, execHandlers?.piRead?.bind(execHandlers), onToolResult, buildPiReadResult, buildPiReadError, buildPiReadError, {
2091
- toolCallId,
2092
- toolName: "read"
2093
- });
2094
- sendExecClientMessage(h2Request, execMsg, "piReadResult", execResult);
2095
- return;
2096
- }
2097
- case "piBashArgs": {
2098
- const args = execMsg.message.value;
2099
- const toolCallId = crypto.randomUUID();
2100
- synthesizeCursorExecToolCall(output, stream, state, toolCallId, "bash", {
2101
- command: args.command,
2102
- timeout: piTimeout(args.timeout)
2103
- });
2104
- const { execResult } = await resolveExecHandler({
2105
- args,
2106
- toolCallId
2107
- }, execHandlers?.piBash?.bind(execHandlers), onToolResult, buildPiBashResult, buildPiBashError, buildPiBashError, {
2108
- toolCallId,
2109
- toolName: "bash"
2110
- });
2111
- sendExecClientMessage(h2Request, execMsg, "piBashResult", execResult);
2112
- return;
2113
- }
2114
- case "piEditArgs": {
2115
- const args = execMsg.message.value;
2116
- const toolCallId = crypto.randomUUID();
2117
- const firstEdit = args.edits[0];
2118
- synthesizeCursorExecToolCall(output, stream, state, toolCallId, "edit", {
2119
- path: args.path,
2120
- old_string: firstEdit?.oldText ?? "",
2121
- new_string: firstEdit?.newText ?? ""
2122
- });
2123
- const { execResult } = await resolveExecHandler({
2124
- args,
2125
- toolCallId
2126
- }, execHandlers?.piEdit?.bind(execHandlers), onToolResult, buildPiEditResult, buildPiEditRejected, buildPiEditError, {
2127
- toolCallId,
2128
- toolName: "edit"
2129
- });
2130
- sendExecClientMessage(h2Request, execMsg, "piEditResult", execResult);
2131
- return;
2132
- }
2133
- case "piWriteArgs": {
2134
- const args = execMsg.message.value;
2135
- const toolCallId = crypto.randomUUID();
2136
- synthesizeCursorExecToolCall(output, stream, state, toolCallId, "write", {
2137
- path: args.path,
2138
- content: args.content
2139
- });
2140
- const { execResult } = await resolveExecHandler({
2141
- args,
2142
- toolCallId
2143
- }, execHandlers?.piWrite?.bind(execHandlers), onToolResult, buildPiWriteResult, buildPiWriteRejected, buildPiWriteError, {
2144
- toolCallId,
2145
- toolName: "write"
2146
- });
2147
- sendExecClientMessage(h2Request, execMsg, "piWriteResult", execResult);
2148
- return;
2149
- }
2150
- case "piGrepArgs": {
2151
- const args = execMsg.message.value;
2152
- const toolCallId = crypto.randomUUID();
2153
- synthesizeCursorExecToolCall(output, stream, state, toolCallId, "grep", {
2154
- pattern: args.literal === true ? piEscapeRegexLiteral(args.pattern) : args.pattern,
2155
- path: args.glob ? piJoinPath(args.path, args.glob) : args.path || ".",
2156
- case: args.ignoreCase === true ? false : void 0,
2157
- context: args.context,
2158
- limit: piLimit(args.limit)
2159
- });
2160
- const { execResult } = await resolveExecHandler({
2161
- args,
2162
- toolCallId
2163
- }, execHandlers?.piGrep?.bind(execHandlers), onToolResult, buildPiGrepResult, buildPiGrepError, buildPiGrepError, {
2164
- toolCallId,
2165
- toolName: "grep"
2166
- });
2167
- sendExecClientMessage(h2Request, execMsg, "piGrepResult", execResult);
2168
- return;
2169
- }
2170
- case "piFindArgs": {
2171
- const args = execMsg.message.value;
2172
- const toolCallId = crypto.randomUUID();
2173
- synthesizeCursorExecToolCall(output, stream, state, toolCallId, "glob", {
2174
- path: piJoinPath(args.path, args.pattern),
2175
- limit: piLimit(args.limit)
2176
- });
2177
- const { execResult } = await resolveExecHandler({
2178
- args,
2179
- toolCallId
2180
- }, execHandlers?.piFind?.bind(execHandlers), onToolResult, buildPiFindResult, buildPiFindError, buildPiFindError, {
2181
- toolCallId,
2182
- toolName: "glob"
2183
- });
2184
- sendExecClientMessage(h2Request, execMsg, "piFindResult", execResult);
2185
- return;
2186
- }
2187
- case "piLsArgs": {
2188
- const args = execMsg.message.value;
2189
- const toolCallId = crypto.randomUUID();
2190
- synthesizeCursorExecToolCall(output, stream, state, toolCallId, "read", { path: piLsPath(args.path) });
2191
- const { execResult } = await resolveExecHandler({
2192
- args,
2193
- toolCallId
2194
- }, execHandlers?.piLs?.bind(execHandlers), onToolResult, buildPiLsResult, buildPiLsError, buildPiLsError, {
2195
- toolCallId,
2196
- toolName: "read"
2197
- });
2198
- sendExecClientMessage(h2Request, execMsg, "piLsResult", execResult);
2199
- return;
2200
- }
2201
- case "miniSweAgentBashArgs": {
2202
- const args = execMsg.message.value;
2203
- if (!args.toolCallId) args.toolCallId = crypto.randomUUID();
2204
- const normalizedArgs = {
2205
- ...args,
2206
- workingDirectory: args.workingDirectory || process.cwd()
2207
- };
2208
- synthesizeCursorExecToolCall(output, stream, state, args.toolCallId, "bash", {
2209
- command: args.command,
2210
- cwd: args.workingDirectory || void 0,
2211
- timeout: args.timeout && args.timeout > 0 ? args.timeout : void 0
2212
- });
2213
- const { execResult } = await resolveExecHandler(normalizedArgs, execHandlers?.shell?.bind(execHandlers), onToolResult, (toolResult) => buildShellResultFromToolResult(normalizedArgs, toolResult), (reason) => buildShellRejectedResult(normalizedArgs.command, normalizedArgs.workingDirectory, reason), (error) => buildShellFailureResult(normalizedArgs.command, normalizedArgs.workingDirectory, error), {
2214
- toolCallId: args.toolCallId,
2215
- toolName: "bash"
2216
- });
2217
- sendExecClientMessage(h2Request, execMsg, "miniSweAgentBashResult", sanitizeShellExecResult(execResult));
2218
- return;
2219
- }
2220
- case "redactedReadArgs": {
2221
- const args = execMsg.message.value;
2222
- sendExecClientMessage(h2Request, execMsg, "redactedReadResult", buildReadErrorResult(args.path, "Secret redaction is not implemented by this client"));
2223
- return;
2224
- }
2225
- case "mcpStateExecArgs": {
2226
- const args = execMsg.message.value;
2227
- sendExecClientMessage(h2Request, execMsg, "mcpStateExecResult", buildMcpStateResult(requestContextTools, args.serverIdentifiers));
2228
- return;
2229
- }
2230
- case "executeHookArgs": {
2231
- const args = execMsg.message.value;
2232
- const execResult = buildNeutralHookResult(args.request);
2233
- if (!execResult) {
2234
- sendExecClientThrow(h2Request, execMsg, `Unsupported hook request: ${args.request?.request.case ?? "unset"}`, "unknown_hook_request");
2235
- return;
2236
- }
2237
- sendExecClientMessage(h2Request, execMsg, "executeHookResult", execResult);
2238
- return;
2239
- }
2240
- case "subagentArgs": {
2241
- const args = execMsg.message.value;
2242
- const execResult = create(SubagentResultSchema, { result: {
2243
- case: "error",
2244
- value: create(SubagentErrorSchema, { error: `Subagents are ${NOT_IMPLEMENTED_SUFFIX}` })
2245
- } });
2246
- log("exec", "subagentRejected", { subagentType: args.subagentType });
2247
- sendExecClientMessage(h2Request, execMsg, "subagentResult", execResult);
2248
- return;
2249
- }
2250
- case "subagentAwaitArgs": {
2251
- const args = execMsg.message.value;
2252
- sendExecClientMessage(h2Request, execMsg, "subagentAwaitResult", create(SubagentAwaitResultSchema, { result: {
2253
- case: "notFound",
2254
- value: create(SubagentAwaitNotFoundSchema, { agentId: args.agentId })
2255
- } }));
2256
- return;
2257
- }
2258
- case "forceBackgroundShellArgs":
2259
- sendExecClientMessage(h2Request, execMsg, "forceBackgroundShellResult", create(ForceBackgroundShellResultSchema, { status: 2 }));
2260
- return;
2261
- case "forceBackgroundSubagentArgs":
2262
- sendExecClientMessage(h2Request, execMsg, "forceBackgroundSubagentResult", create(ForceBackgroundSubagentResultSchema, { status: 2 }));
2263
- return;
2264
- case "smartModeClassifierArgs":
2265
- sendExecClientMessage(h2Request, execMsg, "smartModeClassifierResult", create(SmartModeClassifierResultSchema, { result: {
2266
- case: "error",
2267
- value: create(SmartModeClassifierErrorSchema, { error: `Smart-mode classification is ${NOT_IMPLEMENTED_SUFFIX}` })
2268
- } }));
2269
- return;
2270
- case "canvasDiagnosticsArgs": {
2271
- const args = execMsg.message.value;
2272
- sendExecClientMessage(h2Request, execMsg, "canvasDiagnosticsResult", create(CanvasDiagnosticsResultSchema, { result: {
2273
- case: "error",
2274
- value: create(CanvasDiagnosticsErrorSchema, {
2275
- path: args.path,
2276
- error: `Canvas diagnostics are ${NOT_IMPLEMENTED_SUFFIX}`
2277
- })
2278
- } }));
2279
- return;
2280
- }
2281
- case "shellAllowlistPrecheckArgs":
2282
- sendExecClientMessage(h2Request, execMsg, "shellAllowlistPrecheckResult", create(ShellAllowlistPrecheckResultSchema, { allowlisted: false }));
2283
- return;
2284
- case "mcpAllowlistPrecheckArgs":
2285
- sendExecClientMessage(h2Request, execMsg, "mcpAllowlistPrecheckResult", create(McpAllowlistPrecheckResultSchema, { allowlisted: false }));
2286
- return;
2287
- case "webFetchAllowlistPrecheckArgs":
2288
- sendExecClientMessage(h2Request, execMsg, "webFetchAllowlistPrecheckResult", create(WebFetchAllowlistPrecheckResultSchema, { allowlisted: false }));
2289
- return;
2290
- case "conversationSearchArgs": {
2291
- const args = execMsg.message.value;
2292
- const toolCallId = args.toolCallId || crypto.randomUUID();
2293
- const error = `Conversation search is ${NOT_IMPLEMENTED_SUFFIX}`;
2294
- synthesizeCursorExecToolCall(output, stream, state, toolCallId, "search_conversations", {
2295
- query: args.query,
2296
- limit: args.limit
2297
- });
2298
- await pairSynthesizedExecResult(state, onToolResult, toolCallId, "search_conversations", error);
2299
- sendExecClientMessage(h2Request, execMsg, "conversationSearchResult", create(ConversationSearchResultSchema, { result: {
2300
- case: "error",
2301
- value: create(ConversationSearchErrorSchema, { error })
2302
- } }));
2303
- return;
2304
- }
2305
- case "agentStoreConflictArgs":
2306
- sendExecClientMessage(h2Request, execMsg, "agentStoreConflictResult", create(AgentStoreConflictResultSchema, { result: {
2307
- case: "error",
2308
- value: create(AgentStoreConflictErrorSchema, { error: `Agent store conflicts are ${NOT_IMPLEMENTED_SUFFIX}` })
2309
- } }));
2310
- return;
2311
- case "gitDiffRequest":
2312
- sendExecClientThrow(h2Request, execMsg, `Git diff is ${NOT_IMPLEMENTED_SUFFIX}`, "exec_variant_unsupported");
2313
- return;
2314
- default:
2315
- log("warn", "unhandledExecMessage", { execCase });
2316
- sendExecClientThrow(h2Request, execMsg, `No handler for exec message of type ${execCase}`, "exec_variant_unsupported");
2317
- }
2318
- }
2319
- /**
2320
- * Send one typed answer on the exec channel.
2321
- *
2322
- * `ExecClientMessage["message"]` is a discriminated union pairing each case
2323
- * with its own result type, so the generic is keyed on the case: passing a
2324
- * `ReadResult` under `"shellResult"` is a compile error rather than a wire
2325
- * message the server rejects at runtime.
2326
- */
2327
- function sendExecClientMessage(h2Request, execMsg, messageCase, value) {
2328
- const execClientMessage = create(ExecClientMessageSchema, {
2329
- id: execMsg.id,
2330
- execId: execMsg.execId,
2331
- message: {
2332
- case: messageCase,
2333
- value
2334
- }
2335
- });
2336
- const clientMessage = create(AgentClientMessageSchema, { message: {
2337
- case: "execClientMessage",
2338
- value: execClientMessage
2339
- } });
2340
- const responseBytes = toBinary(AgentClientMessageSchema, clientMessage);
2341
- h2Request.write(frameConnectMessage(responseBytes));
2342
- log("execClientMessage", messageCase, value);
2343
- }
2344
- /**
2345
- * Fail one exec frame in band.
2346
- *
2347
- * `ExecClientThrow` is the protocol's failure channel for a frame that cannot
2348
- * be answered at all — as opposed to a frame answered with its own typed error
2349
- * variant, which means "the tool ran and failed". Cursor's own executor sends
2350
- * exactly this for a frame no handler claims
2351
- * (`agent-exec/dist/index.js`: `No handler found for server message of type …`
2352
- * → `case: 'throw'` then `streamClose`), so the server already knows how to
2353
- * recover from it: it surfaces the error to the model instead of blocking on a
2354
- * reply that never comes.
2355
- *
2356
- * The alternative this replaces — writing an `ExecClientMessage` whose `message`
2357
- * oneof is unset — is not a valid answer: the server sees a reply carrying no
2358
- * result and cannot tell it apart from a malformed frame.
2359
- */
2360
- function sendExecClientThrow(h2Request, execMsg, error, errorCode) {
2361
- const controlMessage = create(ExecClientControlMessageSchema, { message: {
2362
- case: "throw",
2363
- value: create(ExecClientThrowSchema, {
2364
- id: execMsg.id,
2365
- error,
2366
- errorCode
2367
- })
2368
- } });
2369
- const clientMessage = create(AgentClientMessageSchema, { message: {
2370
- case: "execClientControlMessage",
2371
- value: controlMessage
2372
- } });
2373
- h2Request.write(frameConnectMessage(toBinary(AgentClientMessageSchema, clientMessage)));
2374
- log("execClientControl", "throw", {
2375
- id: execMsg.id,
2376
- execId: execMsg.execId,
2377
- error,
2378
- errorCode
2379
- });
2380
- sendExecClientStreamClose(h2Request, execMsg);
2381
- }
2382
- function sendExecClientStreamClose(h2Request, execMsg) {
2383
- const closeMessage = create(ExecClientControlMessageSchema, { message: {
2384
- case: "streamClose",
2385
- value: create(ExecClientStreamCloseSchema, { id: execMsg.id })
2386
- } });
2387
- const clientMessage = create(AgentClientMessageSchema, { message: {
2388
- case: "execClientControlMessage",
2389
- value: closeMessage
2390
- } });
2391
- const responseBytes = toBinary(AgentClientMessageSchema, clientMessage);
2392
- h2Request.write(frameConnectMessage(responseBytes));
2393
- log("execClientControl", "streamClose", {
2394
- id: execMsg.id,
2395
- execId: execMsg.execId
2396
- });
2397
- }
2398
- /**
2399
- * Exported for tests: verifies handler is invoked with correct `this` when passed as bound.
2400
- *
2401
- * Every exit pairs a `toolResult`. The synthesized block was already marked
2402
- * `kCursorExecResolved` before this runs (`synthesizeCursorExecToolCall`), so
2403
- * `agent-loop.ts` emits no placeholder for it: a path that returns without a
2404
- * result leaves the call unpaired and `buildSessionContext` strips the whole
2405
- * interaction on replay. The three result-less paths — no handler installed, a
2406
- * handler that produced nothing, and a thrown handler — therefore synthesize
2407
- * one from the same text the server sees in `execResult`.
2408
- *
2409
- * `pairing` is required so a new callsite cannot silently recreate the orphan,
2410
- * and nullable for the one caller whose block is NOT pre-resolved: MCP without
2411
- * an `mcp` handler, which `agent-loop.ts` runs locally and pairs itself.
2412
- */
2413
- async function resolveExecHandler(args, handler, onToolResult, buildFromToolResult, buildRejected, buildError, pairing) {
2414
- const pair = async (text, isError) => {
2415
- if (!pairing) return void 0;
2416
- return await applyToolResultHandler({
2417
- role: "toolResult",
2418
- toolCallId: pairing.toolCallId,
2419
- toolName: pairing.toolName,
2420
- content: [{
2421
- type: "text",
2422
- text
2423
- }],
2424
- isError,
2425
- timestamp: Date.now()
2426
- }, onToolResult);
2427
- };
2428
- if (!handler) {
2429
- const reason = "Tool not available";
2430
- return {
2431
- execResult: buildRejected(reason),
2432
- toolResult: await pair(reason, true)
2433
- };
2434
- }
2435
- try {
2436
- const { execResult, toolResult } = splitExecHandlerResult(await handler(args));
2437
- const finalToolResult = await applyToolResultHandler(toolResult, onToolResult);
2438
- if (execResult) return {
2439
- execResult,
2440
- toolResult: finalToolResult ?? await pair(...describeExecResult(execResult))
2441
- };
2442
- if (finalToolResult) return {
2443
- execResult: buildFromToolResult(finalToolResult),
2444
- toolResult: finalToolResult
2445
- };
2446
- const reason = "Tool returned no result";
2447
- return {
2448
- execResult: buildRejected(reason),
2449
- toolResult: await pair(reason, true)
2450
- };
2451
- } catch (error) {
2452
- const message = error instanceof Error ? error.message : String(error);
2453
- return {
2454
- execResult: buildError(message),
2455
- toolResult: await pair(message, true)
2456
- };
2457
- }
2458
- }
2459
- /**
2460
- * Derive the transcript state of an exec result the SDK handler returned in the
2461
- * R-only form, which carries no `toolResult` to copy it from.
2462
- *
2463
- * Every exec result in `agent.proto` is a `oneof result` whose success variant
2464
- * is named `success` — the rest (`error`, `rejected`, `file_not_found`,
2465
- * `permission_denied`, `invalid_file`, ...) are failures. Recording those as a
2466
- * successful call would show the user a green entry for a call Cursor was told
2467
- * failed. The variant's own `error`/`reason` text is the same string the server
2468
- * receives, so it is reused verbatim as the transcript body.
2469
- *
2470
- * MCP is the one shape where `success` is not enough: `McpSuccess.is_error`
2471
- * carries an application-level tool failure inside the success variant
2472
- * (`agent.proto:2058`), mirroring the MCP spec's own `isError`. The transport
2473
- * succeeded, the tool did not — so the entry must be a failure, and its text
2474
- * comes from the payload's own content rather than a placeholder.
2475
- */
2476
- function describeExecResult(execResult) {
2477
- const result = execResult?.result;
2478
- const variant = result?.case;
2479
- if (variant === "success") {
2480
- const success = result?.value;
2481
- if (!success?.isError) return ["Tool produced no transcript result", false];
2482
- return [mcpContentToText(success.content) || "MCP tool reported an error", true];
2483
- }
2484
- if (!variant) return ["Tool produced no transcript result", false];
2485
- const value = result?.value;
2486
- return [value?.error || value?.reason || `Tool call ${variant}`, true];
2487
- }
2488
- /**
2489
- * Flatten `McpSuccess.content` into transcript text. Image items carry no text
2490
- * to surface, so only the text variant contributes; an all-image failure falls
2491
- * back to the caller's generic message.
2492
- */
2493
- function mcpContentToText(content) {
2494
- if (!Array.isArray(content)) return "";
2495
- const parts = [];
2496
- for (const item of content) {
2497
- const inner = item?.content;
2498
- if (inner?.case === "text" && inner.value?.text) parts.push(inner.value.text);
2499
- }
2500
- return parts.join("\n");
2501
- }
2502
- function splitExecHandlerResult(result) {
2503
- if (isToolResultMessage(result)) return { toolResult: result };
2504
- if (result && typeof result === "object") {
2505
- const record = result;
2506
- if ("execResult" in record) {
2507
- const { execResult, toolResult } = record;
2508
- return {
2509
- execResult,
2510
- toolResult
2511
- };
2512
- }
2513
- if ("toolResult" in record && !isToolResultMessage(record)) {
2514
- const { result: execResult, toolResult } = record;
2515
- return {
2516
- execResult,
2517
- toolResult
2518
- };
2519
- }
2520
- if ("result" in record && !("$typeName" in record)) {
2521
- const { result: execResult, toolResult } = record;
2522
- return {
2523
- execResult,
2524
- toolResult
2525
- };
2526
- }
2527
- }
2528
- return { execResult: result };
2529
- }
2530
- function isToolResultMessage(value) {
2531
- return !!value && typeof value === "object" && value.role === "toolResult";
2532
- }
2533
- async function applyToolResultHandler(toolResult, onToolResult) {
2534
- if (!toolResult || !onToolResult) return toolResult;
2535
- return await onToolResult(toolResult) ?? toolResult;
2536
- }
2537
- function toolResultToText(toolResult) {
2538
- return toolResult.content.map((item) => item.type === "text" ? item.text : `[${item.mimeType} image]`).join("\n");
2539
- }
2540
- /**
2541
- * The catalog as the paired transcript result records it.
2542
- *
2543
- * Cursor receives every resource's identity on the wire, but rebuilt history is
2544
- * serialized from this local result — so recording only a count leaves the
2545
- * model, one reload later, aware that it once saw N resources and unable to
2546
- * name any of them. The URI is what a follow-up `read_mcp_resource` needs, so
2547
- * it leads; name and mime type follow only when the server supplied them.
2548
- */
2549
- function formatListedMcpResources(resources) {
2550
- if (resources.length === 0) return "No MCP resources available";
2551
- const lines = resources.map((resource) => {
2552
- const qualifiers = [resource.name, resource.mimeType].filter((part) => !!part).join(", ");
2553
- const server = resource.server ? `[${resource.server}] ` : "";
2554
- return qualifiers ? `- ${server}${resource.uri} (${qualifiers})` : `- ${server}${resource.uri}`;
2555
- });
2556
- return [`Listed ${resources.length} MCP resource(s):`, ...lines].join("\n");
2557
- }
2558
- function toolResultWasTruncated(toolResult) {
2559
- if (!toolResult.details || typeof toolResult.details !== "object") return false;
2560
- return !!toolResult.details.truncation?.truncated;
2561
- }
2562
- function toolResultDetailBoolean(toolResult, key) {
2563
- if (!toolResult.details || typeof toolResult.details !== "object") return false;
2564
- const value = toolResult.details[key];
2565
- return typeof value === "boolean" ? value : false;
2566
- }
2567
- /**
2568
- * The file's own line count, when the tool recorded one.
2569
- *
2570
- * Read results expose the source-wide count directly when known. Older tool
2571
- * results carry it at `details.meta.truncation.totalLines`; the flat
2572
- * `details.truncation.totalLines` counts from a window's start and is
2573
- * deliberately not consulted here.
2574
- */
2575
- function readTotalLinesFromDetails(toolResult) {
2576
- const details = toolResult.details;
2577
- if (!details || typeof details !== "object") return void 0;
2578
- const direct = "totalLines" in details ? details.totalLines : void 0;
2579
- if (typeof direct === "number" && Number.isFinite(direct)) return direct;
2580
- const meta = "meta" in details ? details.meta : void 0;
2581
- if (!meta || typeof meta !== "object") return void 0;
2582
- const truncation = "truncation" in meta ? meta.truncation : void 0;
2583
- if (!truncation || typeof truncation !== "object") return void 0;
2584
- const totalLines = "totalLines" in truncation ? truncation.totalLines : void 0;
2585
- return typeof totalLines === "number" && Number.isFinite(totalLines) ? totalLines : void 0;
2586
- }
2587
- function readFileSizeFromDetails(toolResult) {
2588
- const details = toolResult.details;
2589
- if (!details || typeof details !== "object" || !("fileSize" in details)) return void 0;
2590
- const { fileSize } = details;
2591
- return typeof fileSize === "number" && Number.isSafeInteger(fileSize) && fileSize >= 0 ? fileSize : void 0;
2592
- }
2593
- function buildReadResultFromToolResult(path, toolResult, rangeApplied = false) {
2594
- const text = toolResultToText(toolResult);
2595
- if (toolResult.isError) return buildReadErrorResult(path, text || "Read failed");
2596
- const totalLines = readTotalLinesFromDetails(toolResult) ?? (rangeApplied ? 0 : text ? text.split("\n").length : 0);
2597
- return create(ReadResultSchema, { result: {
2598
- case: "success",
2599
- value: create(ReadSuccessSchema, {
2600
- path,
2601
- totalLines,
2602
- fileSize: BigInt(readFileSizeFromDetails(toolResult) ?? Buffer.byteLength(text, "utf-8")),
2603
- truncated: toolResultWasTruncated(toolResult),
2604
- output: {
2605
- case: "content",
2606
- value: text
2607
- },
2608
- rangeApplied
2609
- })
2610
- } });
2611
- }
2612
- function buildReadErrorResult(path, error) {
2613
- return create(ReadResultSchema, { result: {
2614
- case: "error",
2615
- value: create(ReadErrorSchema, {
2616
- path,
2617
- error
2618
- })
2619
- } });
2620
- }
2621
- function buildReadRejectedResult(path, reason) {
2622
- return create(ReadResultSchema, { result: {
2623
- case: "rejected",
2624
- value: create(ReadRejectedSchema, {
2625
- path,
2626
- reason
2627
- })
2628
- } });
2629
- }
2630
- function buildWriteResultFromToolResult(args, toolResult) {
2631
- const text = toolResultToText(toolResult);
2632
- if (toolResult.isError) return buildWriteErrorResult(args.path, text || "Write failed");
2633
- const fileText = args.fileText ?? "";
2634
- const fileSize = args.fileBytes?.length ?? Buffer.byteLength(fileText, "utf-8");
2635
- const linesCreated = fileText ? fileText.split("\n").length : 0;
2636
- return create(WriteResultSchema, { result: {
2637
- case: "success",
2638
- value: create(WriteSuccessSchema, {
2639
- path: args.path,
2640
- linesCreated,
2641
- fileSize,
2642
- fileContentAfterWrite: args.returnFileContentAfterWrite ? fileText : void 0
2643
- })
2644
- } });
2645
- }
2646
- function buildWriteErrorResult(path, error) {
2647
- return create(WriteResultSchema, { result: {
2648
- case: "error",
2649
- value: create(WriteErrorSchema, {
2650
- path,
2651
- error
2652
- })
2653
- } });
2654
- }
2655
- function buildWriteRejectedResult(path, reason) {
2656
- return create(WriteResultSchema, { result: {
2657
- case: "rejected",
2658
- value: create(WriteRejectedSchema, {
2659
- path,
2660
- reason
2661
- })
2662
- } });
2663
- }
2664
- function buildDeleteResultFromToolResult(path, toolResult) {
2665
- const text = toolResultToText(toolResult);
2666
- if (toolResult.isError) return buildDeleteErrorResult(path, text || "Delete failed");
2667
- return create(DeleteResultSchema, { result: {
2668
- case: "success",
2669
- value: create(DeleteSuccessSchema, {
2670
- path,
2671
- deletedFile: path,
2672
- fileSize: BigInt(0),
2673
- prevContent: ""
2674
- })
2675
- } });
2676
- }
2677
- function buildDeleteErrorResult(path, error) {
2678
- return create(DeleteResultSchema, { result: {
2679
- case: "error",
2680
- value: create(DeleteErrorSchema, {
2681
- path,
2682
- error
2683
- })
2684
- } });
2685
- }
2686
- function buildDeleteRejectedResult(path, reason) {
2687
- return create(DeleteResultSchema, { result: {
2688
- case: "rejected",
2689
- value: create(DeleteRejectedSchema, {
2690
- path,
2691
- reason
2692
- })
2693
- } });
2694
- }
2695
- function buildShellResultFromToolResult(args, toolResult) {
2696
- const output = toolResultToText(toolResult);
2697
- if (toolResult.isError) return buildShellFailureResult(args.command, args.workingDirectory, output || "Shell failed");
2698
- return create(ShellResultSchema, { result: {
2699
- case: "success",
2700
- value: create(ShellSuccessSchema, {
2701
- command: args.command,
2702
- workingDirectory: args.workingDirectory,
2703
- exitCode: 0,
2704
- signal: "",
2705
- stdout: output,
2706
- stderr: "",
2707
- executionTime: 0
2708
- })
2709
- } });
2710
- }
2711
- function buildShellFailureResult(command, workingDirectory, error) {
2712
- return create(ShellResultSchema, { result: {
2713
- case: "failure",
2714
- value: create(ShellFailureSchema, {
2715
- command,
2716
- workingDirectory,
2717
- exitCode: 1,
2718
- signal: "",
2719
- stdout: "",
2720
- stderr: error,
2721
- executionTime: 0,
2722
- aborted: false
2723
- })
2724
- } });
2725
- }
2726
- function buildShellRejectedResult(command, workingDirectory, reason) {
2727
- return create(ShellResultSchema, { result: {
2728
- case: "rejected",
2729
- value: create(ShellRejectedSchema, {
2730
- command,
2731
- workingDirectory,
2732
- reason,
2733
- isReadonly: false
2734
- })
2735
- } });
2736
- }
2737
- function buildLsResultFromToolResult(path, toolResult) {
2738
- const text = toolResultToText(toolResult);
2739
- if (toolResult.isError) return buildLsErrorResult(path, text || "Ls failed");
2740
- const rootPath = path || ".";
2741
- const entries = text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("["));
2742
- const childrenDirs = [];
2743
- const childrenFiles = [];
2744
- for (const entry of entries) {
2745
- const name = entry.split(" (")[0];
2746
- if (name.endsWith("/")) {
2747
- const dirName = name.slice(0, -1);
2748
- childrenDirs.push(create(LsDirectoryTreeNodeSchema, {
2749
- absPath: `${rootPath.replace(/\/$/, "")}/${dirName}`,
2750
- childrenDirs: [],
2751
- childrenFiles: [],
2752
- childrenWereProcessed: false,
2753
- fullSubtreeExtensionCounts: {},
2754
- numFiles: 0
2755
- }));
2756
- } else childrenFiles.push(create(LsDirectoryTreeNode_FileSchema, { name }));
2757
- }
2758
- const root = create(LsDirectoryTreeNodeSchema, {
2759
- absPath: rootPath,
2760
- childrenDirs,
2761
- childrenFiles,
2762
- childrenWereProcessed: true,
2763
- fullSubtreeExtensionCounts: {},
2764
- numFiles: childrenFiles.length
2765
- });
2766
- return create(LsResultSchema, { result: {
2767
- case: "success",
2768
- value: create(LsSuccessSchema, { directoryTreeRoot: root })
2769
- } });
2770
- }
2771
- function buildLsErrorResult(path, error) {
2772
- return create(LsResultSchema, { result: {
2773
- case: "error",
2774
- value: create(LsErrorSchema, {
2775
- path,
2776
- error
2777
- })
2778
- } });
2779
- }
2780
- function buildLsRejectedResult(path, reason) {
2781
- return create(LsResultSchema, { result: {
2782
- case: "rejected",
2783
- value: create(LsRejectedSchema, {
2784
- path,
2785
- reason
2786
- })
2787
- } });
2788
- }
2789
- function buildGrepResultFromToolResult(args, toolResult) {
2790
- const text = toolResultToText(toolResult);
2791
- if (toolResult.isError) return buildGrepErrorResult(text || "Grep failed");
2792
- const outputMode = args.outputMode || "content";
2793
- const clientTruncated = toolResultDetailBoolean(toolResult, "truncated");
2794
- const lines = text.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0 && !line.startsWith("[") && !line.toLowerCase().startsWith("no matches"));
2795
- const workspaceKey = args.path || ".";
2796
- let unionResult;
2797
- if (outputMode === "files_with_matches") {
2798
- const files = lines;
2799
- unionResult = create(GrepUnionResultSchema, { result: {
2800
- case: "files",
2801
- value: create(GrepFilesResultSchema, {
2802
- files,
2803
- totalFiles: files.length,
2804
- clientTruncated,
2805
- ripgrepTruncated: false,
2806
- offsetApplied: args.offset
2807
- })
2808
- } });
2809
- } else if (outputMode === "count") {
2810
- const counts = lines.map((line) => {
2811
- const separatorIndex = line.lastIndexOf(":");
2812
- if (separatorIndex === -1) return null;
2813
- const file = line.slice(0, separatorIndex);
2814
- const count = Number.parseInt(line.slice(separatorIndex + 1), 10);
2815
- if (!file || Number.isNaN(count)) return null;
2816
- return create(GrepFileCountSchema, {
2817
- file,
2818
- count
2819
- });
2820
- }).filter((entry) => entry !== null);
2821
- const totalMatches = counts.reduce((sum, entry) => sum + entry.count, 0);
2822
- unionResult = create(GrepUnionResultSchema, { result: {
2823
- case: "count",
2824
- value: create(GrepCountResultSchema, {
2825
- counts,
2826
- totalFiles: counts.length,
2827
- totalMatches,
2828
- clientTruncated,
2829
- ripgrepTruncated: false,
2830
- offsetApplied: args.offset
2831
- })
2832
- } });
2833
- } else {
2834
- const matchMap = /* @__PURE__ */ new Map();
2835
- let totalMatchedLines = 0;
2836
- for (const line of lines) {
2837
- const matchLine = line.match(/^(.+?):(\d+):\s?(.*)$/);
2838
- const contextLine = line.match(/^(.+?)-(\d+)-\s?(.*)$/);
2839
- const match = matchLine ?? contextLine;
2840
- if (!match) continue;
2841
- const [, file, lineNumber, content] = match;
2842
- const isContextLine = Boolean(contextLine);
2843
- const list = matchMap.get(file) ?? [];
2844
- list.push({
2845
- line: Number(lineNumber),
2846
- content,
2847
- isContextLine
2848
- });
2849
- matchMap.set(file, list);
2850
- if (!isContextLine) totalMatchedLines += 1;
2851
- }
2852
- const matches = Array.from(matchMap.entries()).map(([file, matches]) => create(GrepFileMatchSchema, {
2853
- file,
2854
- matches: matches.map((entry) => create(GrepContentMatchSchema, {
2855
- lineNumber: entry.line,
2856
- content: entry.content,
2857
- contentTruncated: false,
2858
- isContextLine: entry.isContextLine
2859
- }))
2860
- }));
2861
- const totalLines = matches.reduce((sum, entry) => sum + entry.matches.length, 0);
2862
- unionResult = create(GrepUnionResultSchema, { result: {
2863
- case: "content",
2864
- value: create(GrepContentResultSchema, {
2865
- matches,
2866
- totalLines,
2867
- totalMatchedLines,
2868
- clientTruncated,
2869
- ripgrepTruncated: false,
2870
- offsetApplied: args.offset
2871
- })
2872
- } });
2873
- }
2874
- return create(GrepResultSchema, { result: {
2875
- case: "success",
2876
- value: create(GrepSuccessSchema, {
2877
- pattern: args.pattern,
2878
- path: args.path || "",
2879
- outputMode,
2880
- workspaceResults: { [workspaceKey]: unionResult }
2881
- })
2882
- } });
2883
- }
2884
- function buildGrepErrorResult(error) {
2885
- return create(GrepResultSchema, { result: {
2886
- case: "error",
2887
- value: create(GrepErrorSchema, { error })
2888
- } });
2889
- }
2890
- /**
2891
- * Reject a Cursor exec-channel `grepArgs` frame whose `pattern` is empty or
2892
- * whitespace-only. Returns an actionable error message when the pattern is
2893
- * unusable (with a `glob`-aware hint when the model likely meant to list
2894
- * files), or `null` when the pattern is valid and grep should run.
2895
- *
2896
- * Exported for tests. Cursor's model sometimes sends `pattern=""` together
2897
- * with a non-empty `glob`, expecting grep to enumerate matching files; the
2898
- * downstream coding-agent `grep` tool rejects that with a bare "Pattern must
2899
- * not be empty", which the TUI renders as `?` in the tool preview (issue
2900
- * #4574). Handling it at the Cursor exec dispatch keeps the synthesized
2901
- * `toolCall` block off the persisted assistant message and gives the model a
2902
- * specific recovery hint.
2903
- */
2904
- function emptyGrepPatternRejection(pattern, glob) {
2905
- if (pattern && pattern.trim().length > 0) return null;
2906
- if (glob && glob.length > 0) return `grep pattern is required (received an empty pattern). To list files matching "${glob}", pass a non-empty regex (e.g. ".") and set path to that glob, or use the ls/read tool instead.`;
2907
- return "grep pattern is required (received an empty pattern).";
2908
- }
2909
- function buildDiagnosticsResultFromToolResult(path, toolResult) {
2910
- const text = toolResultToText(toolResult);
2911
- if (toolResult.isError) return buildDiagnosticsErrorResult(path, text || "Diagnostics failed");
2912
- return create(DiagnosticsResultSchema, { result: {
2913
- case: "success",
2914
- value: create(DiagnosticsSuccessSchema, {
2915
- path,
2916
- diagnostics: [],
2917
- totalDiagnostics: 0
2918
- })
2919
- } });
2920
- }
2921
- function buildDiagnosticsErrorResult(_path, error) {
2922
- return create(DiagnosticsResultSchema, { result: {
2923
- case: "error",
2924
- value: create(DiagnosticsErrorSchema, { error })
2925
- } });
2926
- }
2927
- function buildDiagnosticsRejectedResult(path, reason) {
2928
- return create(DiagnosticsResultSchema, { result: {
2929
- case: "rejected",
2930
- value: create(DiagnosticsRejectedSchema, {
2931
- path,
2932
- reason
2933
- })
2934
- } });
2935
- }
2936
- function parseToolArgsJson(text) {
2937
- const trimmed = text.trim();
2938
- if (!trimmed) return text;
2939
- try {
2940
- return parseJsonWithRepair(trimmed);
2941
- } catch {
2942
- return text;
2943
- }
2944
- }
2945
- function decodeMcpArgValue(value) {
2946
- try {
2947
- const jsonValue = decodeJsonValue(value);
2948
- if (typeof jsonValue === "string") {
2949
- const first = jsonValue.trimStart()[0];
2950
- return first === "{" || first === "[" || first === "\"" ? parseToolArgsJson(jsonValue) : jsonValue;
2951
- }
2952
- return jsonValue;
2953
- } catch {}
2954
- return parseToolArgsJson(new TextDecoder().decode(value));
2955
- }
2956
- function decodeMcpArgsMap(args) {
2957
- if (!args) return;
2958
- const decoded = {};
2959
- for (const [key, value] of Object.entries(args)) decoded[key] = decodeMcpArgValue(value);
2960
- return decoded;
2961
- }
2962
- function decodeMcpCall(args) {
2963
- const decodedArgs = {};
2964
- for (const [key, value] of Object.entries(args.args ?? {})) decodedArgs[key] = decodeMcpArgValue(value);
2965
- return {
2966
- name: args.name,
2967
- providerIdentifier: args.providerIdentifier,
2968
- toolName: args.toolName || args.name,
2969
- toolCallId: args.toolCallId,
2970
- args: decodedArgs,
2971
- rawArgs: args.args ?? {},
2972
- approvalOnly: args.smartModeApprovalOnly === true
2973
- };
2974
- }
2975
- /**
2976
- * Map Cursor's `TodoStatus` enum (agent.proto) onto the local todo statuses.
2977
- *
2978
- * `TODO_STATUS_CANCELLED` (4) maps to `abandoned` rather than collapsing to
2979
- * `pending`, which would resurrect a task the model explicitly cancelled.
2980
- */
2981
- function mapTodoStatusValue(status) {
2982
- switch (status) {
2983
- case 2: return "in_progress";
2984
- case 3: return "completed";
2985
- case 4: return "abandoned";
2986
- default: return "pending";
2987
- }
2988
- }
2989
- function selectTodoCalls(toolCall) {
2990
- const oneof = toolCall.tool;
2991
- if (oneof?.case === "updateTodosToolCall") return { update: oneof.value };
2992
- if (oneof?.case === "readTodosToolCall") return { read: oneof.value };
2993
- return {
2994
- update: toolCall.updateTodosToolCall,
2995
- read: toolCall.readTodosToolCall
2996
- };
2997
- }
2998
- function mapTodoSnapshot(todos) {
2999
- return todos.map((todo) => ({
3000
- content: typeof todo.content === "string" ? todo.content : "",
3001
- status: mapTodoStatusValue(typeof todo.status === "number" ? todo.status : void 0)
3002
- }));
3003
- }
3004
- /**
3005
- * `ToolCall.tool` is a protobuf oneof: a wire-decoded message exposes the
3006
- * variant as `{ case, value }` and NEVER as a flattened `mcpToolCall`
3007
- * property. Reading the flat property alone is what made native todo calls
3008
- * invisible on the wire while hand-shaped test fixtures kept passing, so MCP
3009
- * goes through the same selector. The flat fallback is kept for those fixtures.
3010
- */
3011
- function selectMcpCall(toolCall) {
3012
- const oneof = toolCall?.tool;
3013
- if (oneof?.case === "mcpToolCall") return oneof.value;
3014
- return toolCall?.mcpToolCall;
3015
- }
3016
- /**
3017
- * Same oneof-first selector as MCP: a wire-decoded `ToolCall` exposes
3018
- * `editToolCall` only as `{ case: "editToolCall", value }`.
3019
- */
3020
- function selectEditCall(toolCall) {
3021
- const oneof = toolCall?.tool;
3022
- if (oneof?.case === "editToolCall") return oneof.value;
3023
- return toolCall?.editToolCall;
3024
- }
3025
- function selectEditStreamDelta(update) {
3026
- const oneof = update.toolCallDelta?.delta;
3027
- if (oneof?.case === "editToolCallDelta") return oneof.value?.streamContentDelta;
3028
- return update.toolCallDelta?.editToolCallDelta?.streamContentDelta;
3029
- }
3030
- function rememberEditOwnedToolCall(state, toolCall, envelopeId) {
3031
- if (!state.editOwnedToolCallIds) state.editOwnedToolCallIds = /* @__PURE__ */ new Set();
3032
- const ids = state.editOwnedToolCallIds;
3033
- if (envelopeId) ids.add(envelopeId);
3034
- if (toolCall?.toolCallId) ids.add(toolCall.toolCallId);
3035
- }
3036
- function isEditOwnedToolCallId(state, output, toolCallId) {
3037
- if (state.editOwnedToolCallIds?.has(toolCallId)) return true;
3038
- return output.content.some((block) => block.type === "toolCall" && block.id === toolCallId && block.name === "edit");
3039
- }
3040
- function stringToolArg(args, key) {
3041
- const value = args?.[key];
3042
- return typeof value === "string" ? value : void 0;
3043
- }
3044
- function markEditToolCallPaired(state, toolCallId) {
3045
- if (!state.pairedEditToolCallIds) state.pairedEditToolCallIds = /* @__PURE__ */ new Set();
3046
- state.pairedEditToolCallIds.add(toolCallId);
3047
- }
3048
- /**
3049
- * `EditToolCall.result` is an `EditResult` message whose own oneof is also
3050
- * named `result`. The discriminator is `result.result.case`, same nesting as
3051
- * `describeConnectScmResult` / the todo extractors.
3052
- */
3053
- function describeEditResult(toolCall) {
3054
- const oneof = selectEditCall(toolCall)?.result?.result;
3055
- const variant = oneof?.case;
3056
- const value = oneof?.value;
3057
- if (variant === "success") return {
3058
- text: value?.message || value?.path || "Edited",
3059
- isError: false
3060
- };
3061
- if (variant === "error") return {
3062
- text: value?.modelVisibleError || value?.error || "Edit failed",
3063
- isError: true
3064
- };
3065
- if (variant === "rejected") return {
3066
- text: value?.reason || "Edit rejected",
3067
- isError: true
3068
- };
3069
- if (variant === "fileNotFound") return {
3070
- text: value?.path ? `File not found: ${value.path}` : "File not found",
3071
- isError: true
3072
- };
3073
- if (variant === "readPermissionDenied") return {
3074
- text: value?.path ? `Read permission denied: ${value.path}` : "Read permission denied",
3075
- isError: true
3076
- };
3077
- if (variant === "writePermissionDenied") return {
3078
- text: value?.error || (value?.path ? `Write permission denied: ${value.path}` : "Write permission denied"),
3079
- isError: true
3080
- };
3081
- return {
3082
- text: "Edit reported no result",
3083
- isError: true
3084
- };
3085
- }
3086
- function remapExecHandlerToolName(result, toolName) {
3087
- if (isToolResultMessage(result)) return {
3088
- ...result,
3089
- toolName
3090
- };
3091
- if (result && typeof result === "object" && "toolResult" in result) {
3092
- const record = result;
3093
- if (record.toolResult && record.result !== void 0) return {
3094
- result: record.result,
3095
- toolResult: {
3096
- ...record.toolResult,
3097
- toolName
3098
- }
3099
- };
3100
- if (record.toolResult) return {
3101
- ...record.toolResult,
3102
- toolName
3103
- };
3104
- }
3105
- return result;
3106
- }
3107
- /**
3108
- * Open (or refresh) the single `edit` transcript block for a native StrReplace
3109
- * `editToolCall`. Materialization reads/writes reuse this id and must not
3110
- * synthesize their own blocks.
3111
- */
3112
- function openOrUpdateEditBlock(output, stream, state, toolCall, envelopeId) {
3113
- const edit = selectEditCall(toolCall);
3114
- if (!edit && toolCall?.tool?.case !== "editToolCall") return void 0;
3115
- rememberEditOwnedToolCall(state, toolCall, envelopeId);
3116
- const id = toolCall?.toolCallId || envelopeId;
3117
- if (!id) return void 0;
3118
- const nextArgs = omitUndefinedArgs({
3119
- path: edit?.args?.path,
3120
- stream_content: edit?.args?.streamContent
3121
- });
3122
- const existing = output.content.find((block) => block.type === "toolCall" && block.id === id);
3123
- if (existing) {
3124
- existing.arguments = {
3125
- ...existing.arguments,
3126
- ...nextArgs
3127
- };
3128
- return existing;
3129
- }
3130
- endCurrentTextBlock(output, stream, state);
3131
- endCurrentThinkingBlock(output, stream, state);
3132
- const block = {
3133
- type: "toolCall",
3134
- id,
3135
- name: "edit",
3136
- arguments: nextArgs,
3137
- [kStreamingBlockIndex]: output.content.length,
3138
- [kStreamingBlockKind]: "cursor-edit",
3139
- [kStreamingEnvelopeId]: envelopeId || void 0,
3140
- [kCursorExecResolved]: true
3141
- };
3142
- output.content.push(block);
3143
- retainStreamedCall(state, block, envelopeId);
3144
- stream.push({
3145
- type: "toolcall_start",
3146
- contentIndex: output.content.length - 1,
3147
- partial: output
3148
- });
3149
- return block;
3150
- }
3151
- /**
3152
- * The streamed `ToolCall` variants whose block the exec channel owns.
3153
- *
3154
- * Each of these is announced on the interaction stream AND dispatched as its
3155
- * own `ExecServerMessage` frame — the Pi family (45-51), plus the two MCP
3156
- * resource frames — so the block is synthesized once, by the exec handler,
3157
- * which is the side that has the result.
3158
- *
3159
- * `connect_scm` is deliberately NOT here: `ExecServerMessage` has no
3160
- * connect-SCM case at all (field 44 is `git_diff_request`), so nothing on the
3161
- * exec channel ever answers it and the streamed announcement is the only
3162
- * signal. `search_conversations` is not here either: frame 53 answers it, but
3163
- * it carries its own `tool_call_id` on the streamed envelope and pairs there,
3164
- * so the exec branch does not synthesize a block for it.
3165
- */
3166
- const EXEC_OWNED_TOOL_CALL_CASES = /* @__PURE__ */ new Set([
3167
- "piReadToolCall",
3168
- "piBashToolCall",
3169
- "piEditToolCall",
3170
- "piWriteToolCall",
3171
- "piGrepToolCall",
3172
- "piFindToolCall",
3173
- "piLsToolCall",
3174
- "listMcpResourcesToolCall",
3175
- "readMcpResourceToolCall"
3176
- ]);
3177
- function isExecOwnedToolCall(toolCall) {
3178
- const variant = toolCall?.tool?.case;
3179
- return variant !== void 0 && EXEC_OWNED_TOOL_CALL_CASES.has(variant);
3180
- }
3181
- /**
3182
- * Retain a freshly opened streamed tool-call block.
3183
- *
3184
- * Keyed by the interaction envelope's `call_id`, which is the only key every
3185
- * `ToolCall*Update` for that call shares. The block's own `id` is deliberately
3186
- * not the key: MCP, Pi and connect-SCM blocks are filed under the id carried
3187
- * inside the call's `args`, because that is what the exec channel pairs its
3188
- * result under and what the transcript files the visible block under.
3189
- *
3190
- * `currentToolCall` is still set, as the fallback for frames that carry no
3191
- * `call_id` (proto3-optional, and unset on what older builds send).
3192
- */
3193
- /**
3194
- * Close every tool-call block still open when the stream ends.
3195
- *
3196
- * Not just the last one started: with interleaved calls several can be open at
3197
- * once, and an unclosed block leaves its live card animating and its call
3198
- * unpaired.
3199
- *
3200
- * Only blocks fed by a streamed argument buffer get reparsed. Todo,
3201
- * connect-SCM and MCP-settled frames arrive with complete `arguments` and
3202
- * never set the partial buffer; `parseStreamingJson(undefined)` returns `{}`,
3203
- * so reparsing unconditionally would erase the arguments of every such block
3204
- * caught open by a truncated stream.
3205
- *
3206
- * Server-owned blocks are also paired here. `connect-scm` and `todo` are
3207
- * stamped {@link kCursorExecResolved} the moment they open, so `agent-loop.ts`
3208
- * synthesizes no placeholder for them and only their `toolCallCompleted` frame
3209
- * pairs a result. A transport that closes before that frame would leave the
3210
- * call unpaired, and `buildSessionContext` strips a dangling call from every
3211
- * rebuilt transcript — the interaction disappears. An interrupted result is
3212
- * emitted instead.
3213
- *
3214
- * MCP blocks are excluded even when resolved: the exec dispatch that marked
3215
- * them owns their result, and `drainInFlightDispatches` awaits it before this
3216
- * runs, so pairing here would duplicate one against the same `toolCallId`.
3217
- */
3218
- function flushOpenToolCalls(output, stream, state) {
3219
- const openBlocks = new Set(state.openToolCalls.values());
3220
- if (state.currentToolCall) openBlocks.add(state.currentToolCall);
3221
- for (const block of openBlocks) {
3222
- const idx = output.content.indexOf(block);
3223
- const partialJson = block[kStreamingPartialJson];
3224
- if (partialJson !== void 0) {
3225
- block.arguments = parseStreamingJson(partialJson);
3226
- clearStreamingPartialJson(block);
3227
- }
3228
- const kind = block[kStreamingBlockKind];
3229
- if (kind === "connect-scm" || kind === "todo" || kind === "cursor-edit") {
3230
- if (!(kind === "cursor-edit" && state.pairedEditToolCallIds?.has(block.id))) state.onToolResult?.({
3231
- role: "toolResult",
3232
- toolCallId: block.id,
3233
- toolName: block.name,
3234
- content: [{
3235
- type: "text",
3236
- text: "The connection to Cursor closed before this call completed."
3237
- }],
3238
- isError: true,
3239
- timestamp: Date.now()
3240
- });
3241
- }
3242
- stream.push({
3243
- type: "toolcall_end",
3244
- contentIndex: idx,
3245
- toolCall: block,
3246
- partial: output
3247
- });
3248
- }
3249
- state.openToolCalls.clear();
3250
- state.setToolCall(null);
3251
- }
3252
- function retainStreamedCall(state, block, envelopeId) {
3253
- if (envelopeId) state.openToolCalls.set(envelopeId, block);
3254
- state.setToolCall(block);
3255
- }
3256
- /**
3257
- * The open block a streamed update addresses, or `null` to ignore the update.
3258
- *
3259
- * Cursor interleaves calls: `start A, start B, complete A` is legal, so the
3260
- * update must reach block A even though B opened last. An id naming no open
3261
- * block is ignored rather than misapplied — settling the wrong block would pair
3262
- * it with another call's result.
3263
- *
3264
- * A missing id falls back to the current block: the correlation key is
3265
- * optional, and dropping those updates would strand a block stamped
3266
- * {@link kCursorExecResolved}, which nothing else settles and whose whole
3267
- * interaction is then stripped from every rebuilt transcript.
3268
- */
3269
- function resolveStreamedCall(state, envelopeId) {
3270
- if (!envelopeId) return state.currentToolCall;
3271
- const keyed = state.openToolCalls.get(envelopeId);
3272
- if (keyed) return keyed;
3273
- const current = state.currentToolCall;
3274
- return current && current[kStreamingEnvelopeId] === void 0 ? current : null;
3275
- }
3276
- /** Release a settled block from both the keyed map and the current slot. */
3277
- function releaseStreamedCall(state, block) {
3278
- const envelopeId = block[kStreamingEnvelopeId];
3279
- if (envelopeId) state.openToolCalls.delete(envelopeId);
3280
- if (state.currentToolCall === block) state.setToolCall(null);
3281
- }
3282
- /**
3283
- * The streamed `connect_scm_tool_call` variant, if this update carries one.
3284
- *
3285
- * Same oneof-vs-flattened handling as {@link selectMcpCall}: a wire-decoded
3286
- * `ToolCall` exposes its variant as `{ case, value }`, while hand-shaped test
3287
- * fixtures use the flat property.
3288
- */
3289
- function selectConnectScmCall(toolCall) {
3290
- const oneof = toolCall?.tool;
3291
- if (oneof?.case === "connectScmToolCall") return oneof.value;
3292
- return toolCall?.connectScmToolCall;
3293
- }
3294
- /** The repository a connect-SCM call targets, across the `target` oneof. */
3295
- function selectConnectScmRepository(call) {
3296
- const target = call?.args?.target;
3297
- if (target?.case === "github") return target.value?.repository;
3298
- return call?.args?.github?.repository;
3299
- }
3300
- /**
3301
- * Render a settled `ConnectScmResult` as the text of its paired tool result.
3302
- *
3303
- * Returns `isError` because the three outcomes are not interchangeable: only
3304
- * `success` means the repository was connected, and reporting a rejection as
3305
- * success would tell the model to proceed against a repo it cannot reach.
3306
- */
3307
- function describeConnectScmResult(call) {
3308
- const result = call?.result?.result;
3309
- switch (result?.case) {
3310
- case "success": return {
3311
- text: "SCM connected",
3312
- isError: false
3313
- };
3314
- case "error": return {
3315
- text: result.value?.error || "SCM connection failed",
3316
- isError: true
3317
- };
3318
- case "rejected": return {
3319
- text: result.value?.reason || "SCM connection rejected",
3320
- isError: true
3321
- };
3322
- default: return {
3323
- text: "SCM connection reported no result",
3324
- isError: true
3325
- };
3326
- }
3327
- }
3328
- /**
3329
- * Extract the authoritative full todo list from a completed native todo call.
3330
- *
3331
- * Cursor owns this list server-side: `update_todos` / `read_todos` are resolved
3332
- * remotely and the settled state rides on the tool call's `result`, never on
3333
- * the exec channel (`ExecServerMessage` has no todo case). Only
3334
- * `result.success.todos` is authoritative — the request `args` may differ from
3335
- * what the server actually stored after a merge or normalization, and on
3336
- * `UpdateTodosError` nothing was stored at all.
3337
- *
3338
- * A `read_todos` call carrying `status_filter` / `id_filter` (agent.proto
3339
- * `ReadTodosArgs`) returns a SUBSET, not the list, and its `total_count`
3340
- * reports the full size. Mirroring a partial response would delete every task
3341
- * it omitted, so filtered and short reads are refused here. An empty read is
3342
- * refused too: proto3 defaults unset `total_count` to 0, so `todos=[]` cannot
3343
- * be told from a missing count.
3344
- *
3345
- * A snapshot whose rows are not unique by content is refused for a different
3346
- * reason: Cursor keys todos by `id`, the local list is keyed by content, and
3347
- * the collision is unrepresentable rather than merely partial.
3348
- *
3349
- * Returns `null` when no usable full snapshot is available, which the caller
3350
- * MUST treat as "leave local state untouched".
3351
- */
3352
- function extractTodoSnapshot(toolCall) {
3353
- const { update, read } = selectTodoCalls(toolCall);
3354
- if (read && ((read.args?.statusFilter?.length ?? 0) > 0 || (read.args?.idFilter?.length ?? 0) > 0)) return null;
3355
- const call = update ?? read;
3356
- if (!call) return null;
3357
- const result = call.result?.result;
3358
- if (result?.case !== "success") return null;
3359
- const todos = result.value?.todos;
3360
- if (!todos) return null;
3361
- const totalCount = result.value?.totalCount;
3362
- if (typeof totalCount === "number" && totalCount !== todos.length) return null;
3363
- if (read && todos.length === 0) return null;
3364
- const mapped = mapTodoSnapshot(todos);
3365
- if (mapped.some((todo) => todo.content.length === 0)) return null;
3366
- const seen = /* @__PURE__ */ new Set();
3367
- for (const todo of mapped) {
3368
- if (seen.has(todo.content)) return null;
3369
- seen.add(todo.content);
3370
- }
3371
- const finished = /* @__PURE__ */ new Set();
3372
- for (const todo of todos) {
3373
- const status = mapTodoStatusValue(typeof todo.status === "number" ? todo.status : void 0);
3374
- if (todo.id && (status === "completed" || status === "abandoned")) finished.add(todo.id);
3375
- }
3376
- for (const todo of todos) for (const dependency of todo.dependencies ?? []) if (!finished.has(dependency)) return null;
3377
- return {
3378
- todos: mapped,
3379
- merged: result.value?.wasMerge === true
3380
- };
3381
- }
3382
- /**
3383
- * Error text when the server itself rejected the call.
3384
- *
3385
- * Distinct from {@link extractTodoSnapshot} returning `null`: a filtered read, a
3386
- * truncated or empty one (proto3 cannot tell unset `total_count` from zero), or
3387
- * a snapshot the local model cannot represent are all benign refusals (the call
3388
- * succeeded, we just decline to mirror it), whereas an `UpdateTodosError` /
3389
- * `ReadTodosError` is a real failure that must not replay as a successful no-op.
3390
- */
3391
- function extractTodoError(toolCall) {
3392
- const { update, read } = selectTodoCalls(toolCall);
3393
- const result = (update ?? read)?.result?.result;
3394
- if (result?.case !== "error") return null;
3395
- const error = result.value?.error;
3396
- return typeof error === "string" && error.length > 0 ? error : "Todo operation failed";
3397
- }
3398
- /** Args echoed onto the synthesized display block, for rendering only. */
3399
- function buildTodoDisplayArgs(toolCall) {
3400
- const args = selectTodoCalls(toolCall).update?.args;
3401
- return {
3402
- todos: args?.todos ? mapTodoSnapshot(args.todos) : [],
3403
- merge: args?.merge === true ? true : void 0
3404
- };
3405
- }
3406
- /**
3407
- * Paired result for a server-resolved native todo call.
3408
- *
3409
- * The bridge never runs a local `todo` tool for these, so nothing else would
3410
- * produce a `toolResult` for the block — and `buildSessionContext` strips any
3411
- * `toolCall` left unpaired, taking the interaction out of every rebuilt
3412
- * transcript.
3413
- *
3414
- * Three outcomes, kept distinct: a server error replays as a failure, a benign
3415
- * refusal (a filtered, truncated, or empty read, or a snapshot the local model
3416
- * cannot represent) replays as `"Todo snapshot not mirrored"`, and a settled
3417
- * snapshot replays as its summary. Collapsing the first into the second would
3418
- * hide the failure and let downstream lifecycle logic treat it as success. The
3419
- * refusal text must not say `"No todo changes"`: an `update_todos` the server
3420
- * accepted may still be declined locally, and that is not "no changes".
3421
- */
3422
- function buildTodoToolResult(toolCallId, snapshot, error) {
3423
- return {
3424
- role: "toolResult",
3425
- toolCallId,
3426
- toolName: "todo",
3427
- content: [{
3428
- type: "text",
3429
- text: error ?? (snapshot ? formatTodoSnapshotSummary(snapshot.todos) : "Todo snapshot not mirrored")
3430
- }],
3431
- isError: error !== null,
3432
- timestamp: Date.now()
3433
- };
3434
- }
3435
- function formatTodoSnapshotSummary(todos) {
3436
- if (todos.length === 0) return "No todos";
3437
- return `${todos.filter((todo) => todo.status === "completed").length}/${todos.length} tasks completed`;
3438
- }
3439
- function buildMcpResultFromToolResult(_mcpCall, toolResult) {
3440
- if (toolResult.isError) return buildMcpErrorResult(toolResultToText(toolResult) || "MCP tool failed");
3441
- const content = toolResult.content.map((item) => {
3442
- if (item.type === "image") return create(McpToolResultContentItemSchema, { content: {
3443
- case: "image",
3444
- value: create(McpImageContentSchema, {
3445
- data: Uint8Array.from(Buffer.from(item.data, "base64")),
3446
- mimeType: item.mimeType
3447
- })
3448
- } });
3449
- return create(McpToolResultContentItemSchema, { content: {
3450
- case: "text",
3451
- value: create(McpTextContentSchema, { text: item.text })
3452
- } });
3453
- });
3454
- return create(McpResultSchema, { result: {
3455
- case: "success",
3456
- value: create(McpSuccessSchema, {
3457
- content,
3458
- isError: false
3459
- })
3460
- } });
3461
- }
3462
- const MCP_EXTERNAL_HANDOFF_MESSAGE = cursor_external_tool_handoff_default.trim();
3463
- function buildMcpExternalHandoffResult() {
3464
- return create(McpResultSchema, { result: {
3465
- case: "success",
3466
- value: create(McpSuccessSchema, {
3467
- content: [create(McpToolResultContentItemSchema, { content: {
3468
- case: "text",
3469
- value: create(McpTextContentSchema, { text: MCP_EXTERNAL_HANDOFF_MESSAGE })
3470
- } })],
3471
- isError: false
3472
- })
3473
- } });
3474
- }
3475
- function buildMcpToolNotFoundResult(mcpCall) {
3476
- return create(McpResultSchema, { result: {
3477
- case: "toolNotFound",
3478
- value: create(McpToolNotFoundSchema, {
3479
- name: mcpCall.toolName,
3480
- availableTools: []
3481
- })
3482
- } });
3483
- }
3484
- function buildMcpErrorResult(error) {
3485
- return create(McpResultSchema, { result: {
3486
- case: "error",
3487
- value: create(McpErrorSchema, { error })
3488
- } });
3489
- }
3490
- /**
3491
- * Merge the decoded completion-frame `McpArgs` map into the args assembled
3492
- * from streamed `args_text_delta` snapshots.
3493
- *
3494
- * The completion frame is authoritative for the scalars it carries — but it
3495
- * can omit oversized parameters entirely and can downgrade a structured value
3496
- * to its raw string fallback when `decodeMcpArgValue` cannot parse it as
3497
- * JSON. Overwriting the streamed args wholesale therefore loses data (e.g.
3498
- * the task tool's `tasks` array on multi-subagent dispatches, issue #2615).
3499
- *
3500
- * Rules per key:
3501
- * - completion key absent → keep the streamed value.
3502
- * - completion is a string while the streamed value is structured (object or
3503
- * array) → keep the streamed value (the completion frame downgraded it).
3504
- * - otherwise → completion wins.
3505
- */
3506
- function mergeCursorMcpToolCallArgs(streamed, completion) {
3507
- const merged = { ...streamed };
3508
- if (!completion) return merged;
3509
- for (const [key, completionValue] of Object.entries(completion)) {
3510
- const streamedValue = merged[key];
3511
- if (typeof completionValue === "string" && streamedValue !== null && typeof streamedValue === "object") continue;
3512
- merged[key] = completionValue;
3513
- }
3514
- return merged;
3515
- }
3516
- function endCurrentTextBlock(output, stream, state) {
3517
- const block = state.currentTextBlock;
3518
- if (!block) return;
3519
- const idx = output.content.indexOf(block);
3520
- stream.push({
3521
- type: "text_end",
3522
- contentIndex: idx,
3523
- content: block.text,
3524
- partial: output
3525
- });
3526
- state.setTextBlock(null);
3527
- }
3528
- function endCurrentThinkingBlock(output, stream, state) {
3529
- const block = state.currentThinkingBlock;
3530
- if (!block) return;
3531
- const idx = output.content.indexOf(block);
3532
- stream.push({
3533
- type: "thinking_end",
3534
- contentIndex: idx,
3535
- content: block.thinking,
3536
- partial: output
3537
- });
3538
- state.setThinkingBlock(null);
3539
- }
3540
- /**
3541
- * Synthesize a completed `toolCall` content block for a Cursor exec-channel
3542
- * native tool (`shell`, `read`, `write`, `grep`, `ls`, `delete`, `diagnostics`)
3543
- * or for an MCP exec frame whose corresponding interaction block is absent.
3544
- *
3545
- * Args arrive complete on the exec message, so the block opens and closes in
3546
- * one step — no partial-JSON streaming path. Without this the persisted
3547
- * assistant message carries only text/thinking blocks, and on replay the
3548
- * following `toolResult` messages have no matching `toolCall.id` in
3549
- * `renderSessionContext`, so they render beneath the final answer or disappear.
3550
- *
3551
- * The block is stamped with {@link kCursorExecResolved} so the shared
3552
- * `agent-loop.ts` execution pass skips it — Cursor's server-driven exec
3553
- * channel already ran the tool via the bridge and buffered the result, so
3554
- * treating this block as runnable would re-execute the same side-effecting
3555
- * tool a second time.
3556
- *
3557
- * Exported for tests to exercise ordering with adjacent text/thinking blocks.
3558
- */
3559
- function synthesizeCursorExecToolCall(output, stream, state, toolCallId, toolName, args) {
3560
- endCurrentTextBlock(output, stream, state);
3561
- endCurrentThinkingBlock(output, stream, state);
3562
- const block = {
3563
- type: "toolCall",
3564
- id: toolCallId,
3565
- name: toolName,
3566
- arguments: omitUndefinedArgs(args),
3567
- [kStreamingBlockIndex]: output.content.length,
3568
- [kStreamingBlockKind]: "cursor-exec",
3569
- [kCursorExecResolved]: true
3570
- };
3571
- output.content.push(block);
3572
- const idx = output.content.length - 1;
3573
- stream.push({
3574
- type: "toolcall_start",
3575
- contentIndex: idx,
3576
- partial: output
3577
- });
3578
- stream.push({
3579
- type: "toolcall_end",
3580
- contentIndex: idx,
3581
- toolCall: block,
3582
- partial: output
3583
- });
3584
- }
3585
- /**
3586
- * Pair a `toolResult` for a synthesized block the client answered itself,
3587
- * without ever consulting a handler.
3588
- *
3589
- * {@link resolveExecHandler} does this for every frame backed by a local tool.
3590
- * Frames answered from a fixed verdict — no handler, no local execution — still
3591
- * need the pair for the same reason: the block was stamped
3592
- * {@link kCursorExecResolved}, so `agent-loop.ts` emits no placeholder for it
3593
- * and `buildSessionContext` strips an unpaired call, taking the whole
3594
- * interaction out of every rebuilt transcript.
3595
- *
3596
- * `isError` defaults true because most such verdicts are refusals; the MCP
3597
- * resource frames run locally and can genuinely succeed, and a success filed
3598
- * as an error would render as a failed call in every rebuilt transcript.
3599
- */
3600
- async function pairSynthesizedExecResult(state, onToolResult, toolCallId, toolName, text, isError = true) {
3601
- const synthesized = {
3602
- role: "toolResult",
3603
- toolCallId,
3604
- toolName,
3605
- content: [{
3606
- type: "text",
3607
- text
3608
- }],
3609
- isError,
3610
- timestamp: Date.now()
3611
- };
3612
- const sink = onToolResult ?? state.onToolResult;
3613
- if (!sink) return;
3614
- await sink(synthesized);
3615
- }
3616
- /** Exported for tests: drives one Cursor interaction update through the streaming state machine. */
3617
- function processInteractionUpdate(update, output, stream, state, usageState) {
3618
- const updateCase = update.message?.case;
3619
- log("interactionUpdate", updateCase, update.message?.value);
3620
- if (updateCase === "textDelta") {
3621
- state.setFirstTokenTime();
3622
- const delta = update.message.value.text || "";
3623
- if (!state.currentTextBlock) {
3624
- const block = {
3625
- type: "text",
3626
- text: "",
3627
- [kStreamingBlockIndex]: output.content.length
3628
- };
3629
- output.content.push(block);
3630
- state.setTextBlock(block);
3631
- stream.push({
3632
- type: "text_start",
3633
- contentIndex: output.content.length - 1,
3634
- partial: output
3635
- });
3636
- }
3637
- state.currentTextBlock.text += delta;
3638
- const idx = output.content.indexOf(state.currentTextBlock);
3639
- stream.push({
3640
- type: "text_delta",
3641
- contentIndex: idx,
3642
- delta,
3643
- partial: output
3644
- });
3645
- } else if (updateCase === "thinkingDelta") {
3646
- state.setFirstTokenTime();
3647
- const delta = update.message.value.text || "";
3648
- if (!state.currentThinkingBlock) {
3649
- const block = {
3650
- type: "thinking",
3651
- thinking: "",
3652
- [kStreamingBlockIndex]: output.content.length
3653
- };
3654
- output.content.push(block);
3655
- state.setThinkingBlock(block);
3656
- stream.push({
3657
- type: "thinking_start",
3658
- contentIndex: output.content.length - 1,
3659
- partial: output
3660
- });
3661
- }
3662
- state.currentThinkingBlock.thinking += delta;
3663
- const idx = output.content.indexOf(state.currentThinkingBlock);
3664
- stream.push({
3665
- type: "thinking_delta",
3666
- contentIndex: idx,
3667
- delta,
3668
- partial: output
3669
- });
3670
- } else if (updateCase === "thinkingCompleted") endCurrentThinkingBlock(output, stream, state);
3671
- else if (updateCase === "toolCallStarted" && selectConnectScmCall(update.message.value.toolCall)) {
3672
- endCurrentTextBlock(output, stream, state);
3673
- endCurrentThinkingBlock(output, stream, state);
3674
- const scmCall = selectConnectScmCall(update.message.value.toolCall);
3675
- const repository = selectConnectScmRepository(scmCall);
3676
- const block = {
3677
- type: "toolCall",
3678
- id: scmCall?.args?.toolCallId || update.message.value.callId || crypto.randomUUID(),
3679
- name: "connect_scm",
3680
- arguments: repository ? {
3681
- owner: repository.owner,
3682
- repo: repository.repo
3683
- } : {},
3684
- [kStreamingBlockIndex]: output.content.length,
3685
- [kStreamingBlockKind]: "connect-scm",
3686
- [kStreamingEnvelopeId]: update.message.value.callId || void 0,
3687
- [kCursorExecResolved]: true
3688
- };
3689
- output.content.push(block);
3690
- retainStreamedCall(state, block, update.message.value.callId);
3691
- stream.push({
3692
- type: "toolcall_start",
3693
- contentIndex: output.content.length - 1,
3694
- partial: output
3695
- });
3696
- } else if (updateCase === "toolCallStarted" && isExecOwnedToolCall(update.message.value.toolCall)) {
3697
- endCurrentTextBlock(output, stream, state);
3698
- endCurrentThinkingBlock(output, stream, state);
3699
- log("exec", "streamedToolCallOwnedByExec", { case: update.message.value.toolCall?.tool?.case });
3700
- } else if (updateCase === "toolCallStarted") {
3701
- endCurrentTextBlock(output, stream, state);
3702
- endCurrentThinkingBlock(output, stream, state);
3703
- const toolCall = update.message.value.toolCall;
3704
- if (toolCall) {
3705
- const mcpCall = selectMcpCall(toolCall);
3706
- if (mcpCall) {
3707
- const args = mcpCall.args || {};
3708
- const id = args.toolCallId || crypto.randomUUID();
3709
- const resolvedByExec = state.resolvedMcpToolCallIds.delete(id);
3710
- if (resolvedByExec && output.content.some((block) => block.type === "toolCall" && block.id === id)) return;
3711
- const block = {
3712
- type: "toolCall",
3713
- id,
3714
- name: args.toolName || args.name || "",
3715
- arguments: decodeMcpArgsMap(args.args) ?? {},
3716
- [kStreamingBlockIndex]: output.content.length,
3717
- [kStreamingBlockKind]: "mcp",
3718
- [kStreamingEnvelopeId]: update.message.value.callId || void 0
3719
- };
3720
- if (resolvedByExec) markCursorExecResolved(block);
3721
- output.content.push(block);
3722
- retainStreamedCall(state, block, update.message.value.callId);
3723
- stream.push({
3724
- type: "toolcall_start",
3725
- contentIndex: output.content.length - 1,
3726
- partial: output
3727
- });
3728
- return;
3729
- }
3730
- const todoCalls = selectTodoCalls(toolCall);
3731
- if (todoCalls.update || todoCalls.read) {
3732
- const block = {
3733
- type: "toolCall",
3734
- id: update.message.value.callId || crypto.randomUUID(),
3735
- name: "todo",
3736
- arguments: buildTodoDisplayArgs(toolCall),
3737
- [kStreamingBlockIndex]: output.content.length,
3738
- [kStreamingBlockKind]: "todo",
3739
- [kStreamingEnvelopeId]: update.message.value.callId || void 0,
3740
- [kCursorExecResolved]: true
3741
- };
3742
- output.content.push(block);
3743
- retainStreamedCall(state, block, update.message.value.callId);
3744
- stream.push({
3745
- type: "toolcall_start",
3746
- contentIndex: output.content.length - 1,
3747
- partial: output
3748
- });
3749
- return;
3750
- }
3751
- const fetchCall = selectHostedFetchCall(toolCall);
3752
- if (fetchCall || hostedFetchUnknown(toolCall)) {
3753
- const url = fetchCall?.args?.url || extractHttpUrlFromUnknown(toolCall);
3754
- const block = {
3755
- type: "toolCall",
3756
- id: fetchCall?.args?.toolCallId || update.message.value.callId || crypto.randomUUID(),
3757
- name: "web_fetch",
3758
- arguments: url ? { url } : {},
3759
- [kStreamingBlockIndex]: output.content.length,
3760
- [kStreamingBlockKind]: "web-fetch",
3761
- [kStreamingEnvelopeId]: update.message.value.callId || void 0,
3762
- [kCursorExecResolved]: true
3763
- };
3764
- output.content.push(block);
3765
- retainStreamedCall(state, block, update.message.value.callId);
3766
- stream.push({
3767
- type: "toolcall_start",
3768
- contentIndex: output.content.length - 1,
3769
- partial: output
3770
- });
3771
- return;
3772
- }
3773
- openOrUpdateEditBlock(output, stream, state, toolCall, update.message.value.callId);
3774
- }
3775
- } else if (updateCase === "toolCallDelta" || updateCase === "partialToolCall") {
3776
- const value = update.message.value;
3777
- if (updateCase === "partialToolCall" && value.toolCall) openOrUpdateEditBlock(output, stream, state, value.toolCall, value.callId);
3778
- const editDelta = selectEditStreamDelta(value);
3779
- if (editDelta) {
3780
- const target = resolveStreamedCall(state, value.callId);
3781
- if (target?.[kStreamingBlockKind] === "cursor-edit") {
3782
- const current = stringToolArg(target.arguments, "stream_content") ?? "";
3783
- target.arguments = {
3784
- ...target.arguments,
3785
- stream_content: current + editDelta
3786
- };
3787
- const idx = output.content.indexOf(target);
3788
- stream.push({
3789
- type: "toolcall_delta",
3790
- contentIndex: idx,
3791
- delta: editDelta,
3792
- partial: output
3793
- });
3794
- return;
3795
- }
3796
- }
3797
- const target = resolveStreamedCall(state, value.callId);
3798
- if (target?.[kStreamingBlockKind] === "mcp") {
3799
- const snapshot = update.message.value.argsTextDelta || "";
3800
- const current = target[kStreamingPartialJson] ?? "";
3801
- const chunk = snapshot.startsWith(current) ? snapshot.slice(current.length) : snapshot;
3802
- if (chunk.length === 0) return;
3803
- const nextBuffer = current + chunk;
3804
- target[kStreamingPartialJson] = nextBuffer;
3805
- const throttled = parseStreamingJsonThrottled(nextBuffer, target[kStreamingLastParseLen] ?? 0);
3806
- if (throttled) {
3807
- target.arguments = throttled.value;
3808
- target[kStreamingLastParseLen] = throttled.parsedLen;
3809
- }
3810
- const idx = output.content.indexOf(target);
3811
- stream.push({
3812
- type: "toolcall_delta",
3813
- contentIndex: idx,
3814
- delta: chunk,
3815
- partial: output
3816
- });
3817
- }
3818
- } else if (updateCase === "toolCallCompleted") {
3819
- const settled = resolveStreamedCall(state, update.message.value.callId);
3820
- if (settled) {
3821
- const toolCall = update.message.value.toolCall;
3822
- if (settled[kStreamingBlockKind] === "mcp") {
3823
- const partial = settled[kStreamingPartialJson];
3824
- if (partial) settled.arguments = parseStreamingJson(partial);
3825
- const decodedArgs = decodeMcpArgsMap(selectMcpCall(toolCall)?.args?.args);
3826
- settled.arguments = mergeCursorMcpToolCallArgs(settled.arguments, decodedArgs);
3827
- } else if (settled[kStreamingBlockKind] === "connect-scm") {
3828
- const scmCall = selectConnectScmCall(toolCall);
3829
- const repository = selectConnectScmRepository(scmCall);
3830
- if (repository) settled.arguments = {
3831
- owner: repository.owner,
3832
- repo: repository.repo
3833
- };
3834
- const { text, isError } = describeConnectScmResult(scmCall);
3835
- state.onToolResult?.({
3836
- role: "toolResult",
3837
- toolCallId: settled.id,
3838
- toolName: "connect_scm",
3839
- content: [{
3840
- type: "text",
3841
- text
3842
- }],
3843
- isError,
3844
- timestamp: Date.now()
3845
- });
3846
- } else if (settled[kStreamingBlockKind] === "web-fetch") {
3847
- const fetchCall = selectHostedFetchCall(toolCall);
3848
- const url = fetchCall?.args?.url || extractHttpUrlFromUnknown(toolCall ?? {});
3849
- if (url) settled.arguments = { url };
3850
- const { text, isError } = describeHostedFetchResult(fetchCall);
3851
- state.onToolResult?.({
3852
- role: "toolResult",
3853
- toolCallId: settled.id,
3854
- toolName: "web_fetch",
3855
- content: [{
3856
- type: "text",
3857
- text
3858
- }],
3859
- isError,
3860
- timestamp: Date.now()
3861
- });
3862
- } else if (settled[kStreamingBlockKind] === "todo") {
3863
- const snapshot = toolCall ? extractTodoSnapshot(toolCall) : null;
3864
- const error = toolCall ? extractTodoError(toolCall) : null;
3865
- if (snapshot) settled.arguments = {
3866
- todos: snapshot.todos,
3867
- merged: snapshot.merged
3868
- };
3869
- let persisted;
3870
- let hostError = null;
3871
- try {
3872
- persisted = state.onTodoSnapshot?.(snapshot, settled.id, error) ?? void 0;
3873
- } catch (callbackError) {
3874
- hostError = callbackError instanceof Error ? callbackError.message : String(callbackError);
3875
- log("error", "onTodoSnapshot", { error: hostError });
3876
- }
3877
- state.onToolResult?.(persisted ?? buildTodoToolResult(settled.id, snapshot, hostError ?? error));
3878
- } else if (settled[kStreamingBlockKind] === "cursor-edit") {
3879
- const edit = selectEditCall(toolCall);
3880
- if (edit?.args) settled.arguments = omitUndefinedArgs({
3881
- ...settled.arguments,
3882
- path: edit.args.path ?? stringToolArg(settled.arguments, "path"),
3883
- stream_content: edit.args.streamContent ?? stringToolArg(settled.arguments, "stream_content")
3884
- });
3885
- if (!state.pairedEditToolCallIds?.has(settled.id)) {
3886
- const { text, isError } = describeEditResult(toolCall);
3887
- state.onToolResult?.({
3888
- role: "toolResult",
3889
- toolCallId: settled.id,
3890
- toolName: "edit",
3891
- content: [{
3892
- type: "text",
3893
- text
3894
- }],
3895
- isError,
3896
- timestamp: Date.now()
3897
- });
3898
- }
3899
- }
3900
- const idx = output.content.indexOf(settled);
3901
- clearStreamingPartialJson(settled);
3902
- stream.push({
3903
- type: "toolcall_end",
3904
- contentIndex: idx,
3905
- toolCall: settled,
3906
- partial: output
3907
- });
3908
- releaseStreamedCall(state, settled);
3909
- }
3910
- } else if (updateCase === "turnEnded") {
3911
- output.stopReason = "stop";
3912
- if (classifyModel("cursor", output.model).family === "k3" && !output.content.some((item) => item.type === "thinking" && item.thinking.length > 0)) warn("Cursor kimi-k3 turn completed without thinking blocks; persisted history will replay this turn without reasoning", {
3913
- model: output.model,
3914
- messageTimestamp: output.timestamp
3915
- });
3916
- } else if (updateCase === "tokenDelta") {
3917
- const tokenDelta = update.message.value;
3918
- usageState.sawTokenDelta = true;
3919
- output.usage.output += tokenDelta.tokens || 0;
3920
- output.usage.totalTokens = output.usage.input + output.usage.output;
3921
- }
3922
- }
3923
- function handleConversationCheckpointUpdate(checkpoint, output, usageState, onConversationCheckpoint) {
3924
- onConversationCheckpoint?.(checkpoint);
3925
- if (usageState.sawTokenDelta) return;
3926
- const usedTokens = checkpoint.tokenDetails?.usedTokens ?? 0;
3927
- if (usedTokens <= 0) return;
3928
- if (output.usage.contextTokens !== usedTokens) output.usage.contextTokens = usedTokens;
3929
- }
3930
- function createBlobId(data) {
3931
- return new Uint8Array(createHash("sha256").update(data).digest());
3932
- }
3933
- function storeCursorBlob(blobStore, data) {
3934
- const blobId = createBlobId(data);
3935
- blobStore.set(Buffer.from(blobId).toString("hex"), data);
3936
- return blobId;
3937
- }
3938
- /**
3939
- * Cursor AgentService reconstructs the model prompt from `requestContext.rules`,
3940
- * not from the client-supplied `rootPromptMessagesJson` system blobs. Map each
3941
- * OMP system-prompt entry to a global CursorRule so always-apply rules survive
3942
- * that reconstruction.
3943
- */
3944
- function buildCursorRequestContextRules(systemPrompt) {
3945
- return normalizeSystemPrompts(systemPrompt).map((content, index) => create(CursorRuleSchema, {
3946
- fullPath: `/omp/system-prompt/${index}.mdc`,
3947
- content,
3948
- source: 2,
3949
- type: create(CursorRuleTypeSchema, { type: {
3950
- case: "global",
3951
- value: create(CursorRuleTypeGlobalSchema, {})
3952
- } })
3953
- }));
3954
- }
3955
- /**
3956
- * Local tools Cursor already drives natively over the exec channel, so
3957
- * advertising them again as MCP tools would give the model two ways to call the
3958
- * same thing.
3959
- *
3960
- * `lsp` is deliberately NOT here. The native `diagnosticsArgs` frame covers
3961
- * exactly one of the tool's actions (`action: "diagnostics"`); the rest —
3962
- * `definition`, `references`, `rename`, `code_actions`, `hover`,
3963
- * `implementation`, `type_definition`, `symbols`, ... — have no native frame at
3964
- * all, so filtering the whole tool out hid every one of them from the model.
3965
- */
3966
- const CURSOR_NATIVE_TOOL_NAMES = /* @__PURE__ */ new Set([
3967
- "bash",
3968
- "read",
3969
- "write",
3970
- "delete",
3971
- "ls",
3972
- "grep",
3973
- "todo"
3974
- ]);
3975
- function isJsonValue(value) {
3976
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
3977
- if (typeof value === "number") return Number.isFinite(value);
3978
- if (Array.isArray(value)) return value.every(isJsonValue);
3979
- if (!isRecord$1(value)) return false;
3980
- for (const key in value) if (!isJsonValue(value[key])) return false;
3981
- return true;
3982
- }
3983
- function buildMcpToolDefinitions(tools, requiresCursorToolSchemaProjection = false) {
3984
- if (!tools || tools.length === 0) return [];
3985
- const advertisedTools = tools.filter((tool) => !CURSOR_NATIVE_TOOL_NAMES.has(tool.name));
3986
- if (advertisedTools.length === 0) return [];
3987
- const writeTool = tools.find((tool) => tool.name === "write");
3988
- return (writeTool ? [...advertisedTools, writeTool] : advertisedTools).map((tool) => {
3989
- const wireSchema = toolWireSchema(tool);
3990
- const jsonSchema = requiresCursorToolSchemaProjection ? sanitizeSchemaForCursor(wireSchema) : wireSchema;
3991
- const schemaValue = jsonSchema !== null && !Array.isArray(jsonSchema) && isJsonValue(jsonSchema) ? jsonSchema : {
3992
- type: "object",
3993
- properties: {},
3994
- required: []
3995
- };
3996
- const inputSchema = encodeJsonValue(schemaValue);
3997
- return create(McpToolDefinitionSchema, {
3998
- name: tool.name,
3999
- description: tool.description || "",
4000
- providerIdentifier: "pi-agent",
4001
- toolName: tool.name,
4002
- inputSchema
4003
- });
4004
- });
4005
- }
4006
- /**
4007
- * Extract text content from a user or developer message.
4008
- */
4009
- function extractUserMessageText(msg) {
4010
- if (msg.role !== "user" && msg.role !== "developer") return "";
4011
- const content = msg.content;
4012
- if (typeof content === "string") return content.trim();
4013
- return content.filter((c) => c.type === "text").map((c) => c.text).join("\n").trim();
4014
- }
4015
- function hasUserMessageImages(msg) {
4016
- return (msg.role === "user" || msg.role === "developer") && Array.isArray(msg.content) && msg.content.some((item) => item.type === "image");
4017
- }
4018
- function buildCursorRootPromptContent(content) {
4019
- if (typeof content === "string") {
4020
- const text = content.trim();
4021
- return text ? [{
4022
- type: "text",
4023
- text
4024
- }] : [];
4025
- }
4026
- const parts = [];
4027
- for (const item of content) if (item.type === "text") {
4028
- const text = item.text.trim();
4029
- if (text) parts.push({
4030
- type: "text",
4031
- text
4032
- });
4033
- } else parts.push({
4034
- type: "image",
4035
- image: `data:${item.mimeType};base64,${item.data}`,
4036
- mediaType: item.mimeType
4037
- });
4038
- return parts;
4039
- }
4040
- function cursorUserContentKey(content) {
4041
- if (typeof content === "string") return content.trim();
4042
- const hash = createHash("sha256");
4043
- for (const item of content) {
4044
- hash.update(item.type);
4045
- if (item.type === "text") hash.update(item.text);
4046
- else {
4047
- hash.update(item.mimeType);
4048
- hash.update(item.data);
4049
- }
4050
- }
4051
- return hash.digest("hex");
4052
- }
4053
- function canReplayCursorThinking(msg, targetModelId) {
4054
- return targetModelId !== void 0 && classifyModel("cursor", targetModelId).family === "k3" && msg.api === "cursor-agent" && msg.provider === "cursor" && msg.model === targetModelId;
4055
- }
4056
- function buildCursorAssistantContent(msg, targetModelId) {
4057
- const content = [];
4058
- const replayThinking = canReplayCursorThinking(msg, targetModelId);
4059
- for (const item of msg.content) if (item.type === "text") {
4060
- if (item.text) content.push({
4061
- type: "text",
4062
- text: item.text
4063
- });
4064
- } else if (item.type === "thinking") {
4065
- if (replayThinking && item.thinking) content.push({
4066
- type: "reasoning",
4067
- text: item.thinking,
4068
- providerOptions: { cursor: { modelName: msg.model } },
4069
- ...item.thinkingSignature ? { signature: item.thinkingSignature } : {}
4070
- });
4071
- } else if (item.type === "toolCall") content.push({
4072
- type: "tool-call",
4073
- toolCallId: normalizeToolCallId(item.id),
4074
- toolName: item.name,
4075
- args: normalizeCursorMcpArguments(item.arguments)
4076
- });
4077
- return content;
4078
- }
4079
- function assertCursorKimiK3HistoryReplayable(messages, activeUserMessageIndex, targetModelId) {
4080
- if (!targetModelId || classifyModel("cursor", targetModelId).family !== "k3") return;
4081
- const historyEnd = activeUserMessageIndex >= 0 ? activeUserMessageIndex : messages.length;
4082
- const missingThinkingTurns = [];
4083
- const newlyWarnedKeys = [];
4084
- let assistantTurn = 0;
4085
- for (let i = 0; i < historyEnd; i++) {
4086
- const msg = messages[i];
4087
- if (msg.role !== "assistant") continue;
4088
- assistantTurn++;
4089
- if (!(msg.api === "cursor-agent" && msg.provider === "cursor" && msg.model === targetModelId)) throw new ValidationError(`Cursor ${targetModelId} cannot continue history from a different model (${msg.provider}/${msg.model}); start a new session.`);
4090
- if (msg.content.some((item) => item.type === "thinking" && item.thinking.length > 0)) continue;
4091
- const warningKey = `${msg.api}\0${msg.provider}\0${msg.model}\0${msg.timestamp}`;
4092
- if (warnedCursorKimiK3ReplayMessages.has(warningKey)) continue;
4093
- missingThinkingTurns.push(assistantTurn);
4094
- newlyWarnedKeys.push(warningKey);
4095
- }
4096
- if (missingThinkingTurns.length === 0) return;
4097
- for (const key of newlyWarnedKeys) warnedCursorKimiK3ReplayMessages.add(key);
4098
- warn(`Cursor kimi-k3 history contains same-model assistant turn(s) ${missingThinkingTurns.join(", ")} without thinking blocks; replaying those spans without reasoning may make generation less stable`, {
4099
- model: targetModelId,
4100
- assistantTurns: missingThinkingTurns
4101
- });
4102
- }
4103
- /**
4104
- * Index of the last user/developer message in `messages`, or -1 if none.
4105
- * Used to exclude the current user turn from history builders — it goes in
4106
- * `ConversationActionSchema.userMessageAction`, not in history structures.
4107
- */
4108
- function findLastUserMessageIndex(messages) {
4109
- for (let i = messages.length - 1; i >= 0; i--) {
4110
- const role = messages[i].role;
4111
- if (role === "user" || role === "developer") return i;
4112
- }
4113
- return -1;
4114
- }
4115
- /**
4116
- * Build `ConversationStateStructure.rootPromptMessagesJson` blob IDs for the
4117
- * system prompt plus prior conversation history, as JSON blobs matching
4118
- * Cursor's internal Vercel-AI-SDK-shaped message format.
4119
- *
4120
- * Cursor's server uses `rootPromptMessagesJson` (not `turns[]`) to build the
4121
- * actual model prompt. `turns[]` is UI/display metadata. Without populating
4122
- * this field, multi-turn conversations lose prior context — the model sees
4123
- * only an empty placeholder where historical user turns should be.
4124
- * The active user message is excluded because it is sent in the action.
4125
- */
4126
- /**
4127
- * Build one Cursor system-message JSON blob per ordered system prompt. Emitting separate blobs
4128
- * (rather than a single `\n\n`-joined string) lets Cursor's blob cache hit independently per
4129
- * entry: changing only the last prompt does not invalidate earlier blob ids, so the prefix
4130
- * up to the changed prompt remains cached on the server side.
4131
- *
4132
- * When no system prompts are provided, returns a single default greeting so we never emit
4133
- * an empty `rootPromptMessagesJson` head.
4134
- */
4135
- function buildCursorSystemPromptJsons(systemPrompt) {
4136
- const systemPrompts = normalizeSystemPrompts(systemPrompt);
4137
- if (systemPrompts.length === 0) return [JSON.stringify({
4138
- role: "system",
4139
- content: "You are a helpful assistant."
4140
- })];
4141
- return systemPrompts.map((content) => JSON.stringify({
4142
- role: "system",
4143
- content
4144
- }));
4145
- }
4146
- function collectCursorToolHistory(messages, historyEnd) {
4147
- const toolResults = /* @__PURE__ */ new Map();
4148
- const pairedToolCallIds = /* @__PURE__ */ new Set();
4149
- for (let index = 0; index < historyEnd; index++) {
4150
- const message = messages[index];
4151
- if (message.role === "toolResult") toolResults.set(message.toolCallId, message);
4152
- else if (message.role === "assistant") {
4153
- for (const item of message.content) if (item.type === "toolCall") pairedToolCallIds.add(item.id);
4154
- }
4155
- }
4156
- return {
4157
- toolResults,
4158
- pairedToolCallIds
4159
- };
4160
- }
4161
- function cursorOrphanToolResultText(result) {
4162
- return `${result.isError ? "[Tool Error]" : "[Tool Result]"}\n${toolResultToText(result) || "(empty result)"}`;
4163
- }
4164
- function buildRootPromptMessagesJson(messages, systemPromptIds, blobStore, activeUserMessageIndex = findLastUserMessageIndex(messages), targetModelId) {
4165
- assertCursorKimiK3HistoryReplayable(messages, activeUserMessageIndex, targetModelId);
4166
- const { pairedToolCallIds } = collectCursorToolHistory(messages, activeUserMessageIndex >= 0 ? activeUserMessageIndex : messages.length);
4167
- const entries = [...systemPromptIds];
4168
- const pushJson = (obj) => {
4169
- const bytes = new TextEncoder().encode(JSON.stringify(obj));
4170
- entries.push(storeCursorBlob(blobStore, bytes));
4171
- };
4172
- for (let i = 0; i < messages.length; i++) {
4173
- if (i === activeUserMessageIndex) break;
4174
- const msg = messages[i];
4175
- if (msg.role === "user" || msg.role === "developer") {
4176
- const content = buildCursorRootPromptContent(msg.content);
4177
- if (content.length === 0) continue;
4178
- pushJson({
4179
- role: "user",
4180
- content
4181
- });
4182
- } else if (msg.role === "assistant") {
4183
- const content = buildCursorAssistantContent(msg, targetModelId);
4184
- if (content.length === 0) continue;
4185
- pushJson({
4186
- role: "assistant",
4187
- content
4188
- });
4189
- } else if (msg.role === "toolResult") {
4190
- if (!pairedToolCallIds.has(msg.toolCallId)) {
4191
- pushJson({
4192
- role: "assistant",
4193
- content: [{
4194
- type: "text",
4195
- text: cursorOrphanToolResultText(msg)
4196
- }]
4197
- });
4198
- continue;
4199
- }
4200
- const toolCallId = normalizeToolCallId(msg.toolCallId);
4201
- pushJson({
4202
- role: "tool",
4203
- id: toolCallId,
4204
- content: [{
4205
- type: "tool-result",
4206
- toolName: msg.toolName,
4207
- toolCallId,
4208
- result: toolResultToText(msg),
4209
- ...msg.isError ? { isError: true } : {}
4210
- }]
4211
- });
4212
- }
4213
- }
4214
- return entries;
4215
- }
4216
- function normalizeCursorMcpArgument(value, seen) {
4217
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
4218
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
4219
- if (typeof value !== "object") return void 0;
4220
- if (seen.has(value)) return void 0;
4221
- seen.add(value);
4222
- try {
4223
- if (Array.isArray(value)) return value.map((item) => normalizeCursorMcpArgument(item, seen) ?? null);
4224
- if (!isRecord$1(value)) return void 0;
4225
- const normalized = Object.create(null);
4226
- for (const key in value) {
4227
- if (!Object.hasOwn(value, key)) continue;
4228
- const normalizedItem = normalizeCursorMcpArgument(value[key], seen);
4229
- if (normalizedItem !== void 0) normalized[key] = normalizedItem;
4230
- }
4231
- return normalized;
4232
- } finally {
4233
- seen.delete(value);
4234
- }
4235
- }
4236
- function normalizeCursorMcpArguments(args) {
4237
- const normalized = Object.create(null);
4238
- const seen = /* @__PURE__ */ new Set();
4239
- for (const name in args) {
4240
- if (!Object.hasOwn(args, name)) continue;
4241
- const normalizedValue = normalizeCursorMcpArgument(args[name], seen);
4242
- if (normalizedValue !== void 0) normalized[name] = normalizedValue;
4243
- }
4244
- return normalized;
4245
- }
4246
- function encodeCursorMcpArguments(toolCall) {
4247
- const encoded = Object.create(null);
4248
- const normalized = normalizeCursorMcpArguments(toolCall.arguments);
4249
- for (const name in normalized) encoded[name] = encodeJsonValue(normalized[name]);
4250
- return encoded;
4251
- }
4252
- function createCursorMcpResult(result) {
4253
- if (result.isError) return create(McpToolResultSchema, { result: {
4254
- case: "error",
4255
- value: create(McpToolErrorSchema, { error: toolResultToText(result) })
4256
- } });
4257
- return create(McpToolResultSchema, { result: {
4258
- case: "success",
4259
- value: create(McpSuccessSchema, { content: result.content.map((item) => item.type === "text" ? create(McpToolResultContentItemSchema, { content: {
4260
- case: "text",
4261
- value: create(McpTextContentSchema, { text: item.text })
4262
- } }) : create(McpToolResultContentItemSchema, { content: {
4263
- case: "image",
4264
- value: create(McpImageContentSchema, {
4265
- data: Uint8Array.from(Buffer.from(item.data, "base64")),
4266
- mimeType: item.mimeType
4267
- })
4268
- } })) })
4269
- } });
4270
- }
4271
- function createCursorToolCallStep(toolCall, result) {
4272
- const toolCallId = normalizeToolCallId(toolCall.id);
4273
- const mcpCall = create(McpToolCallSchema, {
4274
- args: create(McpArgsSchema, {
4275
- name: toolCall.name,
4276
- args: encodeCursorMcpArguments(toolCall),
4277
- toolCallId,
4278
- providerIdentifier: "pi-agent",
4279
- toolName: toolCall.name
4280
- }),
4281
- ...result ? { result: createCursorMcpResult(result) } : {}
4282
- });
4283
- return create(ConversationStepSchema, { message: {
4284
- case: "toolCall",
4285
- value: create(ToolCallSchema, {
4286
- tool: {
4287
- case: "mcpToolCall",
4288
- value: mcpCall
4289
- },
4290
- toolCallId
4291
- })
4292
- } });
4293
- }
4294
- /**
4295
- * Convert context.messages to Cursor's ConversationTurnStructure blob IDs.
4296
- * Groups messages into turns: each turn is a user message followed by the assistant's response.
4297
- * Excludes the active user message (which goes in the action).
4298
- *
4299
- * Each `AgentConversationTurnStructure.user_message`, `steps[]`, and the outer
4300
- * `ConversationStateStructure.turns[]` entry is a blob ID into `blobStore`.
4301
- */
4302
- function buildConversationTurns(messages, blobStore, activeUserMessageIndex = findLastUserMessageIndex(messages), targetModelId) {
4303
- const turns = [];
4304
- const { toolResults, pairedToolCallIds } = collectCursorToolHistory(messages, activeUserMessageIndex >= 0 ? activeUserMessageIndex : messages.length);
4305
- let i = 0;
4306
- while (i < messages.length) {
4307
- const msg = messages[i];
4308
- if (msg.role !== "user" && msg.role !== "developer") {
4309
- i++;
4310
- continue;
4311
- }
4312
- if (i === activeUserMessageIndex) break;
4313
- const userText = extractUserMessageText(msg);
4314
- if (userText.length === 0 && !hasUserMessageImages(msg)) {
4315
- i++;
4316
- continue;
4317
- }
4318
- const userMessage = createCursorUserMessage(msg.content, userText, deterministicUuid(`u:${turns.length}:${cursorUserContentKey(msg.content)}`));
4319
- const userMessageBlobId = storeCursorBlob(blobStore, toBinary(UserMessageSchema, userMessage));
4320
- const stepBlobIds = [];
4321
- i++;
4322
- while (i < messages.length && messages[i].role !== "user" && messages[i].role !== "developer") {
4323
- const stepMsg = messages[i];
4324
- if (stepMsg.role === "assistant") for (const item of stepMsg.content) {
4325
- let step;
4326
- if (item.type === "text") {
4327
- if (!item.text) continue;
4328
- step = create(ConversationStepSchema, { message: {
4329
- case: "assistantMessage",
4330
- value: create(AssistantMessageSchema, { text: item.text })
4331
- } });
4332
- } else if (item.type === "thinking") {
4333
- if (!item.thinking || !canReplayCursorThinking(stepMsg, targetModelId)) continue;
4334
- step = create(ConversationStepSchema, { message: {
4335
- case: "thinkingMessage",
4336
- value: create(ThinkingMessageSchema, { text: item.thinking })
4337
- } });
4338
- } else if (item.type === "toolCall") step = createCursorToolCallStep(item, toolResults.get(item.id));
4339
- else continue;
4340
- stepBlobIds.push(storeCursorBlob(blobStore, toBinary(ConversationStepSchema, step)));
4341
- }
4342
- else if (stepMsg.role === "toolResult" && !pairedToolCallIds.has(stepMsg.toolCallId)) {
4343
- const step = create(ConversationStepSchema, { message: {
4344
- case: "assistantMessage",
4345
- value: create(AssistantMessageSchema, { text: cursorOrphanToolResultText(stepMsg) })
4346
- } });
4347
- stepBlobIds.push(storeCursorBlob(blobStore, toBinary(ConversationStepSchema, step)));
4348
- }
4349
- i++;
4350
- }
4351
- const agentTurn = create(AgentConversationTurnStructureSchema, {
4352
- userMessage: userMessageBlobId,
4353
- steps: stepBlobIds
4354
- });
4355
- const turn = create(ConversationTurnStructureSchema, { turn: {
4356
- case: "agentConversationTurn",
4357
- value: agentTurn
4358
- } });
4359
- turns.push(storeCursorBlob(blobStore, toBinary(ConversationTurnStructureSchema, turn)));
4360
- }
4361
- return turns;
4362
- }
4363
- function createCursorUserMessage(content, text, messageId = crypto.randomUUID()) {
4364
- const images = typeof content === "string" ? [] : extractImages(content);
4365
- return create(UserMessageSchema, {
4366
- text,
4367
- messageId,
4368
- ...images.length > 0 ? { selectedContext: create(SelectedContextSchema, { selectedImages: images }) } : {}
4369
- });
4370
- }
4371
- function extractImages(content) {
4372
- return content.filter((item) => item.type === "image").map((image) => create(SelectedImageSchema, {
4373
- uuid: crypto.randomUUID(),
4374
- mimeType: image.mimeType,
4375
- dataOrBlobId: {
4376
- case: "data",
4377
- value: Uint8Array.from(Buffer.from(image.data, "base64"))
4378
- }
4379
- }));
4380
- }
4381
- /**
4382
- * Resolve the Cursor Run wire model id and its parameter list.
4383
- *
4384
- * Cursor's `GetUsableModels` lists reasoning models as per-effort sibling
4385
- * slugs (`gpt-5.4-mini-low`, `gpt-5.6-sol-high`), and OMP copies those ids 1:1.
4386
- * The Run endpoint rejects a sibling slug as the wire `model_id` with
4387
- * `resource_exhausted` (errorId 528384); the official `cursor-agent` splits the
4388
- * slug into its base model id plus a `reasoning` effort parameter. Mirror that
4389
- * for OpenAI-family ids: split the tier with the compiled catalog policy
4390
- * (`collapseVariantId`, KDL suffix/lane rules) and emit
4391
- * `{ id: "reasoning", value: <effort> }`. The off tier (`-none`) is a sibling
4392
- * slug too, so it normalizes to the bare (lane-preserving) base with no
4393
- * reasoning parameter instead of going out raw.
4394
- *
4395
- * Non-OpenAI ids pass through unchanged — Cursor-native ids (`composer-*`,
4396
- * `cursor-grok-*`, `default`) carry no effort suffix, and Claude/other siblings
4397
- * need additional parameters (`thinking`, `context`) whose per-model values are
4398
- * not exposed by the decoded `GetUsableModels` schema, so guessing them would
4399
- * re-trigger 528384.
4400
- */
4401
- function resolveCursorWireModel(model, requestModelId, wireMode = "normalized") {
4402
- const wireModelId = requestModelId ?? model.requestModelId ?? model.id;
4403
- if (wireMode === "discovered") return {
4404
- modelId: wireModelId,
4405
- parameters: []
4406
- };
4407
- const collapsed = collapseVariantId("cursor", wireModelId);
4408
- const effort = collapsed.effort;
4409
- const base = effort !== void 0 ? collapsed.logicalId : void 0;
4410
- if (effort !== void 0 && base && classifyModel("cursor", base).class === "openai") {
4411
- if (effort === "off") return {
4412
- modelId: base,
4413
- parameters: []
4414
- };
4415
- if (THINKING_EFFORTS.includes(effort)) return {
4416
- modelId: base,
4417
- parameters: [create(RequestedModel_ModelParameterbytesSchema, {
4418
- id: "reasoning",
4419
- value: collapsed.effort
4420
- })]
4421
- };
4422
- }
4423
- if (wireModelId === "composer-2.5") return {
4424
- modelId: wireModelId,
4425
- parameters: [create(RequestedModel_ModelParameterbytesSchema, {
4426
- id: "fast",
4427
- value: "false"
4428
- })]
4429
- };
4430
- return {
4431
- modelId: wireModelId,
4432
- parameters: []
4433
- };
4434
- }
4435
- async function buildGrpcRequestForWireMode(model, context, options, state, wireMode) {
4436
- const blobStore = state.blobStore;
4437
- const systemPromptIds = buildCursorSystemPromptJsons(context.systemPrompt).map((json) => storeCursorBlob(blobStore, new TextEncoder().encode(json)));
4438
- const lastUserMessageIndex = findLastUserMessageIndex(context.messages);
4439
- let activeUserMessageIndex = context.messages.length - 1;
4440
- const activeMessage = context.messages[activeUserMessageIndex];
4441
- let activeUserMessage = activeMessage?.role === "user" || activeMessage?.role === "developer" ? activeMessage : void 0;
4442
- if (state.rotatedFresh && !activeUserMessage && lastUserMessageIndex >= 0) {
4443
- activeUserMessageIndex = lastUserMessageIndex;
4444
- const lastUser = context.messages[lastUserMessageIndex];
4445
- if (lastUser.role === "user" || lastUser.role === "developer") activeUserMessage = lastUser;
4446
- }
4447
- let userContent;
4448
- let userText = "";
4449
- let hasUserImages = false;
4450
- if (activeUserMessage?.role === "user" || activeUserMessage?.role === "developer") {
4451
- userContent = activeUserMessage.content;
4452
- if (typeof userContent === "string") userText = userContent.trim();
4453
- else {
4454
- userText = extractText(userContent);
4455
- hasUserImages = hasImages(userContent);
4456
- }
4457
- }
4458
- const action = create(ConversationActionSchema, { action: userContent && (userText.trim().length > 0 || hasUserImages) ? {
4459
- case: "userMessageAction",
4460
- value: create(UserMessageActionSchema, { userMessage: createCursorUserMessage(userContent, userText) })
4461
- } : {
4462
- case: "resumeAction",
4463
- value: create(ResumeActionSchema, {})
4464
- } });
4465
- const turns = buildConversationTurns(context.messages, blobStore, activeUserMessage ? activeUserMessageIndex : -1, model.id);
4466
- const rootPromptMessagesJson = buildRootPromptMessagesJson(context.messages, systemPromptIds, blobStore, activeUserMessage ? activeUserMessageIndex : -1, model.id);
4467
- const cachedPromptHead = state.conversationState?.rootPromptMessagesJson?.slice(0, systemPromptIds.length) ?? [];
4468
- const hasMatchingPrompt = cachedPromptHead.length === systemPromptIds.length && systemPromptIds.every((id, idx) => Buffer.from(cachedPromptHead[idx]).equals(id));
4469
- const baseState = state.conversationState && hasMatchingPrompt ? state.conversationState : create(ConversationStateStructureSchema, {
4470
- rootPromptMessagesJson: systemPromptIds,
4471
- turns: [],
4472
- todos: [],
4473
- pendingToolCalls: [],
4474
- previousWorkspaceUris: [],
4475
- fileStates: {},
4476
- fileStatesV2: {},
4477
- summaryArchives: [],
4478
- turnTimings: [],
4479
- subagentStates: {},
4480
- selfSummaryCount: 0,
4481
- readPaths: []
4482
- });
4483
- const conversationState = create(ConversationStateStructureSchema, {
4484
- ...baseState,
4485
- rootPromptMessagesJson,
4486
- turns
4487
- });
4488
- const { modelId: wireModelId, parameters: wireParameters } = resolveCursorWireModel(model, options?.wireModelId, wireMode);
4489
- const cursorMaxMode = model.cursorMaxMode === true;
4490
- const modelDetails = create(ModelDetailsSchema, {
4491
- modelId: wireModelId,
4492
- displayModelId: model.id,
4493
- displayName: model.name,
4494
- ...cursorMaxMode ? { maxMode: true } : void 0
4495
- });
4496
- const requestedModel = create(RequestedModelSchema, {
4497
- modelId: wireModelId,
4498
- maxMode: cursorMaxMode,
4499
- parameters: wireParameters
4500
- });
4501
- let runRequest = create(AgentRunRequestSchema, {
4502
- conversationState,
4503
- action,
4504
- modelDetails,
4505
- requestedModel,
4506
- conversationId: state.conversationId
4507
- });
4508
- if (options?.customSystemPrompt) runRequest.customSystemPrompt = options.customSystemPrompt;
4509
- const replacementRequest = await options?.onPayload?.(runRequest, model);
4510
- if (replacementRequest !== void 0) runRequest = replacementRequest;
4511
- const discoveredWireModelId = options?.wireModelId ?? model.requestModelId ?? model.id;
4512
- const serializedParameters = runRequest.requestedModel?.parameters;
4513
- const fallbackWireModelId = wireMode === "normalized" && wireParameters.length > 0 && wireModelId !== discoveredWireModelId && runRequest.requestedModel?.modelId === wireModelId && runRequest.modelDetails?.modelId === wireModelId && serializedParameters?.length === wireParameters.length && serializedParameters.every((parameter, index) => {
4514
- const expected = wireParameters[index];
4515
- return expected !== void 0 && parameter.id === expected.id && parameter.value === expected.value;
4516
- }) ? discoveredWireModelId : void 0;
4517
- const clientMessage = create(AgentClientMessageSchema, { message: {
4518
- case: "runRequest",
4519
- value: runRequest
4520
- } });
4521
- const requestBytes = toBinary(AgentClientMessageSchema, clientMessage);
4522
- const toolNames = context.tools?.map((tool) => tool.name) ?? [];
4523
- const detail = $env.DEBUG_CURSOR === "2" ? ` ${JSON.stringify(clientMessage.message.value, debugReplacer, 2)?.slice(0, 2e3)}` : "";
4524
- log("info", "builtRunRequest", {
4525
- bytes: requestBytes.length,
4526
- tools: toolNames.length,
4527
- toolNames: toolNames.slice(0, 20),
4528
- detail: detail || void 0
4529
- });
4530
- return {
4531
- requestBytes,
4532
- blobStore,
4533
- conversationState,
4534
- fallbackWireModelId
4535
- };
4536
- }
4537
- function hasImages(content) {
4538
- return content.some((item) => item.type === "image");
4539
- }
4540
- function extractText(content) {
4541
- return content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
4542
- }
4543
- //#endregion
4544
- export { streamCursor };