@bitkyc08/opencodex 2.6.17 → 2.6.18

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 (84) hide show
  1. package/README.md +9 -0
  2. package/bin/ocx.mjs +70 -5
  3. package/gui/dist/assets/index-DDcEW0Cm.css +1 -0
  4. package/gui/dist/assets/index-DbTEyo46.js +9 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +3 -1
  7. package/src/adapters/anthropic.ts +9 -2
  8. package/src/adapters/base.ts +6 -0
  9. package/src/adapters/cursor/arg-codec.ts +38 -0
  10. package/src/adapters/cursor/arg-normalize.ts +88 -0
  11. package/src/adapters/cursor/cursor-errors.ts +85 -0
  12. package/src/adapters/cursor/discovery.ts +144 -0
  13. package/src/adapters/cursor/effort-map.ts +74 -0
  14. package/src/adapters/cursor/exec-policy.ts +44 -0
  15. package/src/adapters/cursor/framing.ts +136 -0
  16. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  17. package/src/adapters/cursor/kv-store.ts +25 -0
  18. package/src/adapters/cursor/live-models.ts +93 -0
  19. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  20. package/src/adapters/cursor/live-transport.ts +758 -0
  21. package/src/adapters/cursor/mcp-config.ts +42 -0
  22. package/src/adapters/cursor/mcp-manager.ts +236 -0
  23. package/src/adapters/cursor/message-mapper.ts +46 -0
  24. package/src/adapters/cursor/native-exec-common.ts +55 -0
  25. package/src/adapters/cursor/native-exec-desktop.ts +177 -0
  26. package/src/adapters/cursor/native-exec-fs.ts +284 -0
  27. package/src/adapters/cursor/native-exec-mcp.ts +151 -0
  28. package/src/adapters/cursor/native-exec-network.ts +32 -0
  29. package/src/adapters/cursor/native-exec-shell.ts +191 -0
  30. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  31. package/src/adapters/cursor/native-exec.ts +177 -0
  32. package/src/adapters/cursor/protobuf-events.ts +309 -0
  33. package/src/adapters/cursor/protobuf-request.ts +347 -0
  34. package/src/adapters/cursor/request-builder.ts +98 -0
  35. package/src/adapters/cursor/tool-definitions.ts +301 -0
  36. package/src/adapters/cursor/transport-retry.ts +116 -0
  37. package/src/adapters/cursor/transport.ts +47 -0
  38. package/src/adapters/cursor/types.ts +36 -0
  39. package/src/adapters/cursor.ts +99 -0
  40. package/src/adapters/google.ts +7 -1
  41. package/src/adapters/kiro.ts +15 -0
  42. package/src/adapters/openai-chat.ts +7 -2
  43. package/src/adapters/run-turn-queue.ts +58 -0
  44. package/src/adapters/tool-catalog-nudge.ts +71 -0
  45. package/src/bridge.ts +7 -1
  46. package/src/cli-help.ts +9 -2
  47. package/src/cli-status.ts +7 -5
  48. package/src/cli.ts +122 -79
  49. package/src/codex-catalog.ts +213 -71
  50. package/src/codex-history-provider.ts +31 -14
  51. package/src/codex-inject.ts +17 -9
  52. package/src/codex-paths.ts +2 -1
  53. package/src/codex-shim.ts +30 -7
  54. package/src/codex-sync.ts +70 -0
  55. package/src/config.ts +58 -2
  56. package/src/doctor.ts +4 -2
  57. package/src/index.ts +1 -0
  58. package/src/model-cache.ts +22 -2
  59. package/src/oauth/callback-server.ts +44 -16
  60. package/src/oauth/cursor.ts +188 -0
  61. package/src/oauth/index.ts +29 -3
  62. package/src/oauth/key-providers.ts +20 -33
  63. package/src/oauth/login-cli.ts +7 -4
  64. package/src/open-url.ts +5 -1
  65. package/src/ports.ts +13 -0
  66. package/src/process-control.ts +76 -0
  67. package/src/provider-label.ts +10 -5
  68. package/src/providers/derive.ts +30 -3
  69. package/src/providers/registry.ts +39 -1
  70. package/src/proxy-liveness.ts +122 -0
  71. package/src/responses/parser.ts +1 -0
  72. package/src/responses/state.ts +83 -0
  73. package/src/router.ts +38 -23
  74. package/src/server/adapter-resolve.ts +3 -0
  75. package/src/server.ts +130 -18
  76. package/src/service.ts +94 -32
  77. package/src/types.ts +24 -1
  78. package/src/update-job.ts +360 -0
  79. package/src/update.ts +73 -11
  80. package/src/usage-log.ts +3 -3
  81. package/src/usage-summary.ts +3 -2
  82. package/src/win-paths.ts +68 -0
  83. package/gui/dist/assets/index-DIBiVVC0.css +0 -1
  84. package/gui/dist/assets/index-DcnD944i.js +0 -9
@@ -0,0 +1,347 @@
1
+ import { create, toBinary } from "@bufbuild/protobuf";
2
+ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
3
+ import { ValueSchema } from "@bufbuild/protobuf/wkt";
4
+ import type { OcxAssistantContentPart, OcxMessage, OcxToolResultMessage } from "../../types";
5
+ import { namespacedToolName } from "../../types";
6
+ import type { CursorRunRequest } from "./types";
7
+ import { storeCursorBlob } from "./native-exec";
8
+ import {
9
+ AgentClientMessageSchema,
10
+ AgentConversationTurnStructureSchema,
11
+ AssistantMessageSchema,
12
+ AgentRunRequestSchema,
13
+ ConversationActionSchema,
14
+ ConversationStepSchema,
15
+ ConversationStateStructureSchema,
16
+ ConversationTurnStructureSchema,
17
+ McpArgsSchema,
18
+ McpSuccessSchema,
19
+ McpTextContentSchema,
20
+ McpToolCallSchema,
21
+ McpToolResultContentItemSchema,
22
+ McpToolResultSchema,
23
+ ModelDetailsSchema,
24
+ ResumeActionSchema,
25
+ ThinkingMessageSchema,
26
+ ToolCallSchema,
27
+ UserMessageActionSchema,
28
+ UserMessageSchema,
29
+ } from "./gen/agent_pb";
30
+ import {
31
+ appendCursorGenericToolUseHint,
32
+ appendCursorShellAliasHint,
33
+ cursorToolsForActivePrompt,
34
+ buildCursorToolGuidanceSystemNote,
35
+ cursorRequestHasShellAlias,
36
+ CURSOR_SHELL_ALIAS_SYSTEM_NOTE,
37
+ OCX_RESPONSES_TOOL_PROVIDER,
38
+ } from "./tool-definitions";
39
+
40
+ const encoder = new TextEncoder();
41
+
42
+ function jsonBlob(value: unknown): Uint8Array {
43
+ return encoder.encode(JSON.stringify(value));
44
+ }
45
+
46
+ function systemPromptBlobs(request: CursorRunRequest): Uint8Array[] {
47
+ const prompts = request.system.length > 0 ? [...request.system] : ["You are a helpful assistant."];
48
+ if (cursorRequestHasShellAlias(request.tools)) prompts.push(CURSOR_SHELL_ALIAS_SYSTEM_NOTE);
49
+ const cursorToolGuidance = buildCursorToolGuidanceSystemNote(
50
+ cursorToolsForActivePrompt(request.tools, activePromptText(request), request.toolChoice),
51
+ request.toolChoice,
52
+ );
53
+ if (cursorToolGuidance) prompts.push(cursorToolGuidance);
54
+ return prompts.map(content => storeCursorBlob(jsonBlob({ role: "system", content })));
55
+ }
56
+
57
+ function assistantRootText(message: Extract<OcxMessage, { role: "assistant" }>): string {
58
+ if (typeof message.content === "string") return message.content;
59
+ return message.content
60
+ .map(part => (part.type === "text" ? part.text : part.type === "thinking" ? part.thinking : undefined))
61
+ .filter((value): value is string => typeof value === "string" && value.length > 0)
62
+ .join("\n");
63
+ }
64
+
65
+ // Cursor builds the actual model prompt from rootPromptMessagesJson (turns[] is UI/display metadata),
66
+ // so prior history — including assistant tool calls and tool results — must be replayed here or a
67
+ // ResumeAction has nothing model-visible to continue from. The active user message is excluded
68
+ // because it travels in the action. Tool results are rendered as user-role text with a marker, and
69
+ // each entry is a SHA-256 blob ID (Cursor fetches the bytes back via getBlobArgs). Mirrors the
70
+ // danger-pi reference buildRootPromptMessagesJson.
71
+ function rootPromptMessages(request: CursorRunRequest): Uint8Array[] {
72
+ const entries = systemPromptBlobs(request);
73
+ const messages = request.rawMessages;
74
+ if (!messages?.length) return entries;
75
+
76
+ const lastRawIsToolResult = messages.at(-1)?.role === "toolResult";
77
+ const activeUserIndex = lastRawIsToolResult ? -1 : lastActionIndex(messages);
78
+
79
+ for (let i = 0; i < messages.length; i++) {
80
+ if (i === activeUserIndex) break;
81
+ const message = messages[i];
82
+ if (!message) continue;
83
+ if (message.role === "user" || message.role === "developer") {
84
+ const text = contentText(message).trim();
85
+ if (text.length > 0) entries.push(storeCursorBlob(jsonBlob({ role: "user", content: text })));
86
+ } else if (message.role === "assistant") {
87
+ const text = assistantRootText(message).trim();
88
+ if (text.length > 0) entries.push(storeCursorBlob(jsonBlob({ role: "assistant", content: [{ type: "text", text }] })));
89
+ for (const part of message.content) {
90
+ if (typeof part === "string" || part.type !== "toolCall") continue;
91
+ const toolName = namespacedToolName(part.namespace, part.name);
92
+ const callText = `[Tool Call]\ncall_id: ${part.id}\nname: ${toolName}\narguments:\n${JSON.stringify(part.arguments ?? {})}`;
93
+ entries.push(storeCursorBlob(jsonBlob({ role: "assistant", content: [{ type: "text", text: callText }] })));
94
+ }
95
+ } else if (message.role === "toolResult") {
96
+ const prefix = message.isError ? "[Tool Error]" : "[Tool Result]";
97
+ const text = `${prefix}\n${toolResultToText(message)}`;
98
+ entries.push(storeCursorBlob(jsonBlob({ role: "user", content: [{ type: "text", text }] })));
99
+ }
100
+ }
101
+ return entries;
102
+ }
103
+
104
+ function contentText(message: OcxMessage): string {
105
+ if (message.role === "toolResult") return toolResultToText(message);
106
+ if (typeof message.content === "string") return message.content;
107
+ return message.content
108
+ .map(part => {
109
+ if (part.type === "text") return part.text;
110
+ if (part.type === "thinking") return part.thinking;
111
+ if (part.type === "image") return `[image input unsupported by Cursor adapter phase 3: ${part.detail ?? "auto"}]`;
112
+ return undefined;
113
+ })
114
+ .filter((value): value is string => typeof value === "string" && value.length > 0)
115
+ .join("\n");
116
+ }
117
+
118
+ function contentToText(content: OcxToolResultMessage["content"]): string {
119
+ if (typeof content === "string") return content;
120
+ return content
121
+ .map(part => part.type === "text" ? part.text : `[image input unsupported by Cursor adapter phase 3: ${part.detail ?? "auto"}]`)
122
+ .join("\n");
123
+ }
124
+
125
+ function toolResultToText(message: OcxToolResultMessage): string {
126
+ return [
127
+ "[tool_result]",
128
+ `call_id: ${message.toolCallId}`,
129
+ `name: ${namespacedToolName(message.toolNamespace, message.toolName)}`,
130
+ `is_error: ${message.isError}`,
131
+ "output:",
132
+ contentToText(message.content),
133
+ ].join("\n");
134
+ }
135
+
136
+ function argBytes(value: unknown): Uint8Array {
137
+ try {
138
+ return toBinary(ValueSchema, fromJson(ValueSchema, value as JsonValue));
139
+ } catch {
140
+ return encoder.encode(JSON.stringify(value));
141
+ }
142
+ }
143
+
144
+ function toolCallStep(part: Extract<OcxAssistantContentPart, { type: "toolCall" }>, result?: OcxToolResultMessage): Uint8Array {
145
+ const args: Record<string, Uint8Array> = {};
146
+ for (const [key, value] of Object.entries(part.arguments ?? {})) args[key] = argBytes(value);
147
+ const toolName = namespacedToolName(part.namespace, part.name);
148
+ return storeCursorBlob(toBinary(ConversationStepSchema, create(ConversationStepSchema, {
149
+ message: {
150
+ case: "toolCall",
151
+ value: create(ToolCallSchema, {
152
+ tool: {
153
+ case: "mcpToolCall",
154
+ value: create(McpToolCallSchema, {
155
+ args: create(McpArgsSchema, {
156
+ name: toolName,
157
+ toolName,
158
+ toolCallId: part.id,
159
+ providerIdentifier: OCX_RESPONSES_TOOL_PROVIDER,
160
+ args,
161
+ }),
162
+ ...(result ? { result: toolResultPart(result) } : {}),
163
+ }),
164
+ },
165
+ }),
166
+ },
167
+ })));
168
+ }
169
+
170
+ function toolResultPart(message: OcxToolResultMessage) {
171
+ return create(McpToolResultSchema, {
172
+ result: {
173
+ case: "success",
174
+ value: create(McpSuccessSchema, {
175
+ isError: message.isError,
176
+ content: [create(McpToolResultContentItemSchema, {
177
+ content: { case: "text", value: create(McpTextContentSchema, { text: contentToText(message.content) }) },
178
+ })],
179
+ }),
180
+ },
181
+ });
182
+ }
183
+
184
+ function assistantStep(part: OcxAssistantContentPart): Uint8Array | undefined {
185
+ if (part.type === "toolCall") return toolCallStep(part);
186
+ if (part.type === "thinking") {
187
+ return storeCursorBlob(toBinary(ConversationStepSchema, create(ConversationStepSchema, {
188
+ message: {
189
+ case: "thinkingMessage",
190
+ value: create(ThinkingMessageSchema, { text: part.thinking }),
191
+ },
192
+ })));
193
+ }
194
+ if (part.text.length === 0) return undefined;
195
+ return storeCursorBlob(toBinary(ConversationStepSchema, create(ConversationStepSchema, {
196
+ message: {
197
+ case: "assistantMessage",
198
+ value: create(AssistantMessageSchema, { text: part.text }),
199
+ },
200
+ })));
201
+ }
202
+
203
+ function lastActionIndex(messages: readonly OcxMessage[] | undefined): number {
204
+ if (!messages) return -1;
205
+ for (let i = messages.length - 1; i >= 0; i--) {
206
+ const role = messages[i]?.role;
207
+ if (role === "user" || role === "developer") return i;
208
+ if (role === "toolResult") continue;
209
+ }
210
+ return -1;
211
+ }
212
+
213
+ function conversationTurns(request: CursorRunRequest): Uint8Array[] {
214
+ const messages = request.rawMessages;
215
+ if (!messages?.length) return [];
216
+ const end = lastActionIndex(messages);
217
+ const historyEnd = messages.at(-1)?.role === "toolResult" ? messages.length : Math.max(0, end);
218
+ const turns: Uint8Array[] = [];
219
+ let current: { userMessage: Uint8Array; steps: Uint8Array[] } | undefined;
220
+ const pendingToolCalls = new Map<string, Extract<OcxAssistantContentPart, { type: "toolCall" }>>();
221
+ const flush = () => {
222
+ if (!current) return;
223
+ for (const part of pendingToolCalls.values()) current.steps.push(toolCallStep(part));
224
+ turns.push(storeCursorBlob(toBinary(ConversationTurnStructureSchema, create(ConversationTurnStructureSchema, {
225
+ turn: {
226
+ case: "agentConversationTurn",
227
+ value: create(AgentConversationTurnStructureSchema, current),
228
+ },
229
+ }))));
230
+ current = undefined;
231
+ pendingToolCalls.clear();
232
+ };
233
+
234
+ for (const message of messages.slice(0, historyEnd)) {
235
+ if (message.role === "assistant") {
236
+ if (!current) continue;
237
+ for (const part of message.content) {
238
+ if (part.type === "toolCall") {
239
+ pendingToolCalls.set(part.id, part);
240
+ continue;
241
+ }
242
+ const step = assistantStep(part);
243
+ if (step) current.steps.push(step);
244
+ }
245
+ continue;
246
+ }
247
+ if (message.role === "toolResult") {
248
+ if (!current) continue;
249
+ const priorCall = pendingToolCalls.get(message.toolCallId);
250
+ if (priorCall) {
251
+ current.steps.push(toolCallStep(priorCall, message));
252
+ pendingToolCalls.delete(message.toolCallId);
253
+ } else {
254
+ current.steps.push(storeCursorBlob(toBinary(ConversationStepSchema, create(ConversationStepSchema, {
255
+ message: {
256
+ case: "assistantMessage",
257
+ value: create(AssistantMessageSchema, { text: toolResultToText(message) }),
258
+ },
259
+ }))));
260
+ }
261
+ continue;
262
+ }
263
+ flush();
264
+ current = {
265
+ userMessage: storeCursorBlob(toBinary(UserMessageSchema, create(UserMessageSchema, {
266
+ text: contentText(message),
267
+ messageId: crypto.randomUUID(),
268
+ }))),
269
+ steps: [],
270
+ };
271
+ }
272
+ flush();
273
+ return turns;
274
+ }
275
+
276
+ export function activePromptText(request: CursorRunRequest): string {
277
+ const last = request.messages.at(-1);
278
+ if (last?.role === "user" || last?.role === "developer") return last.content;
279
+ for (let i = (request.rawMessages?.length ?? 0) - 1; i >= 0; i--) {
280
+ const message = request.rawMessages?.[i];
281
+ if (message?.role === "user" || message?.role === "developer") {
282
+ const text = contentText(message);
283
+ if (text.trim().length > 0) return text;
284
+ }
285
+ }
286
+ return last?.role === "tool" ? last.content : "";
287
+ }
288
+
289
+ export function encodeCursorRunRequest(request: CursorRunRequest): Uint8Array {
290
+ const rawText = activePromptText(request);
291
+ const lastRole = request.messages.at(-1)?.role;
292
+ const text = lastRole === "user" || lastRole === "developer"
293
+ ? appendCursorShellAliasHint(request.tools, appendCursorGenericToolUseHint(request.tools, rawText))
294
+ : rawText;
295
+ // A tool-result-only turn (the last raw message is a toolResult) continues the SAME Cursor
296
+ // conversation with the tool result carried as structured conversation history (mcpToolCall.result
297
+ // in conversationTurns). It must NOT inject the tool result text as a new UserMessageAction — that
298
+ // would pollute the model input and double-deliver the result. Use ResumeAction so Cursor picks up
299
+ // from the history we provided.
300
+ const lastRawIsToolResult = request.rawMessages?.at(-1)?.role === "toolResult";
301
+ const action = create(ConversationActionSchema, {
302
+ action: !lastRawIsToolResult && text.trim().length > 0
303
+ ? {
304
+ case: "userMessageAction",
305
+ value: create(UserMessageActionSchema, {
306
+ userMessage: create(UserMessageSchema, {
307
+ text,
308
+ messageId: crypto.randomUUID(),
309
+ }),
310
+ }),
311
+ }
312
+ : {
313
+ case: "resumeAction",
314
+ value: create(ResumeActionSchema, {}),
315
+ },
316
+ });
317
+
318
+ const runRequest = create(AgentRunRequestSchema, {
319
+ conversationId: request.conversationId,
320
+ conversationState: create(ConversationStateStructureSchema, {
321
+ rootPromptMessagesJson: rootPromptMessages(request),
322
+ turns: conversationTurns(request),
323
+ todos: [],
324
+ pendingToolCalls: [],
325
+ previousWorkspaceUris: [],
326
+ fileStates: {},
327
+ fileStatesV2: {},
328
+ summaryArchives: [],
329
+ turnTimings: [],
330
+ subagentStates: {},
331
+ readPaths: [],
332
+ }),
333
+ action,
334
+ modelDetails: create(ModelDetailsSchema, {
335
+ modelId: request.modelId,
336
+ displayModelId: request.modelId,
337
+ displayName: request.modelId,
338
+ displayNameShort: request.modelId,
339
+ aliases: [],
340
+ }),
341
+ });
342
+
343
+ const message = create(AgentClientMessageSchema, {
344
+ message: { case: "runRequest", value: runRequest },
345
+ });
346
+ return toBinary(AgentClientMessageSchema, message);
347
+ }
@@ -0,0 +1,98 @@
1
+ import type {
2
+ OcxAssistantContentPart,
3
+ OcxContentPart,
4
+ OcxMessage,
5
+ OcxParsedRequest,
6
+ OcxToolCall,
7
+ OcxToolResultMessage,
8
+ } from "../../types";
9
+ import { namespacedToolName } from "../../types";
10
+ import type { CursorRequestMessage, CursorRunRequest } from "./types";
11
+ import { cursorEffortSuffix } from "./effort-map";
12
+
13
+ /**
14
+ * Resolve a `cursor/<model>` selection + Codex reasoning effort to the actual Cursor model id. Cursor
15
+ * encodes the effort as a per-model suffix (`claude-4.6-opus-high`); `cursorEffortSuffix` picks the
16
+ * right tier for that specific model (top effort → the model's top tier, e.g. `-max`/`-xhigh`) or
17
+ * `undefined` for non-reasoning models like `composer-2.5`. A fully-qualified id (one that isn't a
18
+ * known effort base) passes through unchanged.
19
+ */
20
+ function normalizeCursorModelId(modelId: string, reasoning?: string): string {
21
+ const id = modelId.startsWith("cursor/") ? modelId.slice("cursor/".length) : modelId;
22
+ const suffix = cursorEffortSuffix(id, reasoning);
23
+ return suffix ? `${id}-${suffix}` : id;
24
+ }
25
+
26
+ function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): string | undefined {
27
+ switch (part.type) {
28
+ case "text":
29
+ return part.text;
30
+ case "thinking":
31
+ return part.thinking;
32
+ case "image":
33
+ return `[image input unsupported by Cursor adapter phase 3: ${part.detail ?? "auto"}]`;
34
+ case "toolCall":
35
+ // Cursor does not accept OpenAI Responses assistant tool-call parts as native history here.
36
+ // Rendering them as visible "[tool_call]" text leaks synthetic protocol markers back into
37
+ // model output and can halt multi-tool continuations. The paired tool result carries the
38
+ // call id/name/output Cursor needs for the next action.
39
+ return undefined;
40
+ }
41
+ }
42
+
43
+ function toolResultToText(message: OcxToolResultMessage): string {
44
+ return [
45
+ "[tool_result]",
46
+ `call_id: ${message.toolCallId}`,
47
+ `name: ${namespacedToolName(message.toolNamespace, message.toolName)}`,
48
+ `is_error: ${message.isError}`,
49
+ "output:",
50
+ contentToText(message.content),
51
+ ].join("\n");
52
+ }
53
+
54
+ function contentToText(content: string | readonly (OcxContentPart | OcxAssistantContentPart)[]): string {
55
+ if (typeof content === "string") return content;
56
+ return content
57
+ .map(contentPartToText)
58
+ .filter((value): value is string => typeof value === "string" && value.length > 0)
59
+ .join("\n");
60
+ }
61
+
62
+ function requestMessage(message: OcxMessage): CursorRequestMessage | undefined {
63
+ switch (message.role) {
64
+ case "user":
65
+ case "developer":
66
+ return { role: message.role, content: contentToText(message.content) };
67
+ case "assistant":
68
+ return { role: "assistant", content: contentToText(message.content) };
69
+ case "toolResult":
70
+ return {
71
+ role: "tool",
72
+ content: toolResultToText(message),
73
+ };
74
+ }
75
+ }
76
+
77
+ export function generatedCursorConversationId(): string {
78
+ return `cursor_${crypto.randomUUID().replace(/-/g, "")}`;
79
+ }
80
+
81
+ export function createCursorRequest(parsed: OcxParsedRequest): CursorRunRequest {
82
+ return {
83
+ modelId: normalizeCursorModelId(parsed.modelId, parsed.options.reasoning),
84
+ // The Cursor conversation id comes ONLY from remembered state (_cursorConversationId). Do NOT fall
85
+ // back to the OpenAI Responses previous_response_id (resp_*): that is a Responses-chain id in a
86
+ // different namespace and would start an unrelated Cursor conversation, breaking tool-result
87
+ // continuation. If we have no remembered Cursor conversation, start a fresh one.
88
+ conversationId: parsed._cursorConversationId ?? generatedCursorConversationId(),
89
+ system: [...(parsed.context.systemPrompt ?? [])],
90
+ messages: parsed.context.messages
91
+ .map(requestMessage)
92
+ .filter((message): message is CursorRequestMessage => !!message && message.content.length > 0),
93
+ rawMessages: parsed.context.messages,
94
+ ...(parsed.context.tools?.length ? { tools: parsed.context.tools } : {}),
95
+ ...(parsed.options.toolChoice ? { toolChoice: parsed.options.toolChoice } : {}),
96
+ ...(parsed.options.parallelToolCalls !== undefined ? { parallelToolCalls: parsed.options.parallelToolCalls } : {}),
97
+ };
98
+ }