opencode-cursor-proxy 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/README.zh-CN.md +136 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/api/agent-service.d.ts +136 -0
  8. package/dist/lib/api/agent-service.js +938 -0
  9. package/dist/lib/api/agent-service.js.map +1 -0
  10. package/dist/lib/api/ai-service.d.ts +26 -0
  11. package/dist/lib/api/ai-service.js +38 -0
  12. package/dist/lib/api/ai-service.js.map +1 -0
  13. package/dist/lib/api/cursor-client.d.ts +119 -0
  14. package/dist/lib/api/cursor-client.js +511 -0
  15. package/dist/lib/api/cursor-client.js.map +1 -0
  16. package/dist/lib/api/cursor-models.d.ts +13 -0
  17. package/dist/lib/api/cursor-models.js +34 -0
  18. package/dist/lib/api/cursor-models.js.map +1 -0
  19. package/dist/lib/api/openai-compat.d.ts +10 -0
  20. package/dist/lib/api/openai-compat.js +262 -0
  21. package/dist/lib/api/openai-compat.js.map +1 -0
  22. package/dist/lib/api/proto/agent-messages.d.ts +25 -0
  23. package/dist/lib/api/proto/agent-messages.js +132 -0
  24. package/dist/lib/api/proto/agent-messages.js.map +1 -0
  25. package/dist/lib/api/proto/bidi.d.ts +17 -0
  26. package/dist/lib/api/proto/bidi.js +24 -0
  27. package/dist/lib/api/proto/bidi.js.map +1 -0
  28. package/dist/lib/api/proto/decoding.d.ts +19 -0
  29. package/dist/lib/api/proto/decoding.js +118 -0
  30. package/dist/lib/api/proto/decoding.js.map +1 -0
  31. package/dist/lib/api/proto/encoding.d.ts +64 -0
  32. package/dist/lib/api/proto/encoding.js +180 -0
  33. package/dist/lib/api/proto/encoding.js.map +1 -0
  34. package/dist/lib/api/proto/exec.d.ts +12 -0
  35. package/dist/lib/api/proto/exec.js +383 -0
  36. package/dist/lib/api/proto/exec.js.map +1 -0
  37. package/dist/lib/api/proto/index.d.ts +13 -0
  38. package/dist/lib/api/proto/index.js +10 -0
  39. package/dist/lib/api/proto/index.js.map +1 -0
  40. package/dist/lib/api/proto/interaction.d.ts +15 -0
  41. package/dist/lib/api/proto/interaction.js +99 -0
  42. package/dist/lib/api/proto/interaction.js.map +1 -0
  43. package/dist/lib/api/proto/kv.d.ts +52 -0
  44. package/dist/lib/api/proto/kv.js +156 -0
  45. package/dist/lib/api/proto/kv.js.map +1 -0
  46. package/dist/lib/api/proto/tool-calls.d.ts +9 -0
  47. package/dist/lib/api/proto/tool-calls.js +144 -0
  48. package/dist/lib/api/proto/tool-calls.js.map +1 -0
  49. package/dist/lib/api/proto/types.d.ts +201 -0
  50. package/dist/lib/api/proto/types.js +10 -0
  51. package/dist/lib/api/proto/types.js.map +1 -0
  52. package/dist/lib/auth/helpers.d.ts +40 -0
  53. package/dist/lib/auth/helpers.js +103 -0
  54. package/dist/lib/auth/helpers.js.map +1 -0
  55. package/dist/lib/auth/index.d.ts +7 -0
  56. package/dist/lib/auth/index.js +10 -0
  57. package/dist/lib/auth/index.js.map +1 -0
  58. package/dist/lib/auth/login.d.ts +55 -0
  59. package/dist/lib/auth/login.js +184 -0
  60. package/dist/lib/auth/login.js.map +1 -0
  61. package/dist/lib/config.d.ts +153 -0
  62. package/dist/lib/config.js +182 -0
  63. package/dist/lib/config.js.map +1 -0
  64. package/dist/lib/openai-compat/handler.d.ts +40 -0
  65. package/dist/lib/openai-compat/handler.js +808 -0
  66. package/dist/lib/openai-compat/handler.js.map +1 -0
  67. package/dist/lib/openai-compat/index.d.ts +9 -0
  68. package/dist/lib/openai-compat/index.js +13 -0
  69. package/dist/lib/openai-compat/index.js.map +1 -0
  70. package/dist/lib/openai-compat/types.d.ts +127 -0
  71. package/dist/lib/openai-compat/types.js +6 -0
  72. package/dist/lib/openai-compat/types.js.map +1 -0
  73. package/dist/lib/openai-compat/utils.d.ts +143 -0
  74. package/dist/lib/openai-compat/utils.js +348 -0
  75. package/dist/lib/openai-compat/utils.js.map +1 -0
  76. package/dist/lib/session-reuse.d.ts +88 -0
  77. package/dist/lib/session-reuse.js +198 -0
  78. package/dist/lib/session-reuse.js.map +1 -0
  79. package/dist/lib/storage.d.ts +55 -0
  80. package/dist/lib/storage.js +159 -0
  81. package/dist/lib/storage.js.map +1 -0
  82. package/dist/lib/utils/cache.d.ts +131 -0
  83. package/dist/lib/utils/cache.js +297 -0
  84. package/dist/lib/utils/cache.js.map +1 -0
  85. package/dist/lib/utils/fetch.d.ts +84 -0
  86. package/dist/lib/utils/fetch.js +261 -0
  87. package/dist/lib/utils/fetch.js.map +1 -0
  88. package/dist/lib/utils/index.d.ts +13 -0
  89. package/dist/lib/utils/index.js +22 -0
  90. package/dist/lib/utils/index.js.map +1 -0
  91. package/dist/lib/utils/jwt.d.ts +40 -0
  92. package/dist/lib/utils/jwt.js +102 -0
  93. package/dist/lib/utils/jwt.js.map +1 -0
  94. package/dist/lib/utils/logger.d.ts +107 -0
  95. package/dist/lib/utils/logger.js +227 -0
  96. package/dist/lib/utils/logger.js.map +1 -0
  97. package/dist/lib/utils/model-resolver.d.ts +49 -0
  98. package/dist/lib/utils/model-resolver.js +503 -0
  99. package/dist/lib/utils/model-resolver.js.map +1 -0
  100. package/dist/lib/utils/request-pool.d.ts +38 -0
  101. package/dist/lib/utils/request-pool.js +105 -0
  102. package/dist/lib/utils/request-pool.js.map +1 -0
  103. package/dist/lib/utils/request-transformer.d.ts +87 -0
  104. package/dist/lib/utils/request-transformer.js +154 -0
  105. package/dist/lib/utils/request-transformer.js.map +1 -0
  106. package/dist/lib/utils/tokenizer.d.ts +14 -0
  107. package/dist/lib/utils/tokenizer.js +76 -0
  108. package/dist/lib/utils/tokenizer.js.map +1 -0
  109. package/dist/plugin/index.d.ts +8 -0
  110. package/dist/plugin/index.js +9 -0
  111. package/dist/plugin/index.js.map +1 -0
  112. package/dist/plugin/plugin.d.ts +21 -0
  113. package/dist/plugin/plugin.js +309 -0
  114. package/dist/plugin/plugin.js.map +1 -0
  115. package/dist/plugin/types.d.ts +120 -0
  116. package/dist/plugin/types.js +7 -0
  117. package/dist/plugin/types.js.map +1 -0
  118. package/dist/server.d.ts +15 -0
  119. package/dist/server.js +95 -0
  120. package/dist/server.js.map +1 -0
  121. package/package.json +79 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * OpenAI-Compatible API Utilities
3
+ *
4
+ * Shared utility functions for OpenAI API compatibility layer.
5
+ */
6
+ import { randomUUID } from "node:crypto";
7
+ /**
8
+ * Generate a unique completion ID
9
+ */
10
+ export function generateCompletionId() {
11
+ return `chatcmpl-${randomUUID().replace(/-/g, "").slice(0, 24)}`;
12
+ }
13
+ /**
14
+ * Check if content contains multimodal (image) parts
15
+ */
16
+ export function hasMultimodalContent(content) {
17
+ if (content === null || typeof content === "string") {
18
+ return false;
19
+ }
20
+ return content.some((part) => part.type === "image_url");
21
+ }
22
+ /**
23
+ * Process multimodal content into separate text and image parts
24
+ *
25
+ * Handles:
26
+ * - Plain string content
27
+ * - Array of text/image parts
28
+ * - Base64 data URLs
29
+ * - Regular image URLs
30
+ */
31
+ export function processMultimodalContent(content) {
32
+ if (content === null) {
33
+ return { text: "", images: [], hasImages: false };
34
+ }
35
+ if (typeof content === "string") {
36
+ return { text: content, images: [], hasImages: false };
37
+ }
38
+ const texts = [];
39
+ const images = [];
40
+ for (const part of content) {
41
+ if (part.type === "text") {
42
+ texts.push(part.text);
43
+ }
44
+ else if (part.type === "image_url") {
45
+ const imagePart = part;
46
+ const url = imagePart.image_url.url;
47
+ const isBase64 = url.startsWith("data:");
48
+ let mimeType;
49
+ if (isBase64) {
50
+ // Extract MIME type from data URL: data:image/png;base64,...
51
+ const match = url.match(/^data:([^;]+);/);
52
+ mimeType = match?.[1];
53
+ }
54
+ images.push({
55
+ url,
56
+ detail: imagePart.image_url.detail,
57
+ isBase64,
58
+ mimeType,
59
+ });
60
+ }
61
+ }
62
+ return {
63
+ text: texts.join("\n"),
64
+ images,
65
+ hasImages: images.length > 0,
66
+ };
67
+ }
68
+ /**
69
+ * Extract only text content from potentially multimodal content
70
+ */
71
+ function extractTextContent(content) {
72
+ if (content === null)
73
+ return "";
74
+ if (typeof content === "string")
75
+ return content;
76
+ return content
77
+ .filter((part) => part.type === "text")
78
+ .map(part => part.text)
79
+ .join("\n");
80
+ }
81
+ /**
82
+ * Format image references for text-based prompts
83
+ * When images can't be directly passed, include a description
84
+ */
85
+ export function formatImageReferences(images) {
86
+ if (images.length === 0)
87
+ return "";
88
+ const refs = images.map((img, i) => {
89
+ const source = img.isBase64
90
+ ? `[embedded ${img.mimeType || "image"}]`
91
+ : `[image: ${img.url}]`;
92
+ const detail = img.detail ? ` (detail: ${img.detail})` : "";
93
+ return `Image ${i + 1}: ${source}${detail}`;
94
+ });
95
+ return refs.join("\n");
96
+ }
97
+ /**
98
+ * Convert OpenAI messages array to a prompt string for Cursor.
99
+ * Handles the full message history including:
100
+ * - system messages (prepended)
101
+ * - user messages (with optional multimodal content)
102
+ * - assistant messages (including those with tool_calls)
103
+ * - tool result messages (role: "tool")
104
+ *
105
+ * For multi-turn conversations with tool calls, this formats the conversation
106
+ * so the model can see what tools were called and their results.
107
+ *
108
+ * For multimodal content:
109
+ * - Extracts text content for the prompt
110
+ * - Collects images separately for models that support vision
111
+ * - Optionally adds image references for non-vision models
112
+ */
113
+ export function messagesToPrompt(messages, options = {}) {
114
+ const result = messagesToPromptWithImages(messages, options);
115
+ return result.prompt;
116
+ }
117
+ /**
118
+ * Convert OpenAI messages to prompt with image extraction
119
+ * Returns both the prompt and any extracted images for vision models
120
+ */
121
+ export function messagesToPromptWithImages(messages, options = {}) {
122
+ const { supportsVision = false, includeImageReferences = true } = options;
123
+ const parts = [];
124
+ const allImages = [];
125
+ // Extract system messages to prepend
126
+ const systemMessages = messages.filter(m => m.role === "system");
127
+ if (systemMessages.length > 0) {
128
+ parts.push(systemMessages.map(m => extractTextContent(m.content)).join("\n"));
129
+ }
130
+ // Process non-system messages in order
131
+ const conversationMessages = messages.filter(m => m.role !== "system");
132
+ // Check if this is a continuation with tool results
133
+ const hasToolResults = conversationMessages.some(m => m.role === "tool");
134
+ // Format the full conversation history
135
+ for (const msg of conversationMessages) {
136
+ if (msg.role === "user") {
137
+ // Process potentially multimodal user messages
138
+ const multimodal = processMultimodalContent(msg.content);
139
+ let userContent = multimodal.text;
140
+ if (multimodal.hasImages) {
141
+ // Collect images for vision-capable models
142
+ allImages.push(...multimodal.images);
143
+ // Add image references for non-vision models or as context
144
+ if (!supportsVision && includeImageReferences) {
145
+ const imageRefs = formatImageReferences(multimodal.images);
146
+ if (imageRefs) {
147
+ userContent = `${userContent}\n${imageRefs}`;
148
+ }
149
+ }
150
+ }
151
+ parts.push(`User: ${userContent}`);
152
+ }
153
+ else if (msg.role === "assistant") {
154
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
155
+ // Assistant made tool calls - show what was called
156
+ const toolCallsDesc = msg.tool_calls.map(tc => `[Called tool: ${tc.function.name}(${tc.function.arguments})]`).join("\n");
157
+ const textContent = extractTextContent(msg.content);
158
+ if (textContent) {
159
+ parts.push(`Assistant: ${textContent}\n${toolCallsDesc}`);
160
+ }
161
+ else {
162
+ parts.push(`Assistant: ${toolCallsDesc}`);
163
+ }
164
+ }
165
+ else {
166
+ const textContent = extractTextContent(msg.content);
167
+ if (textContent) {
168
+ parts.push(`Assistant: ${textContent}`);
169
+ }
170
+ }
171
+ }
172
+ else if (msg.role === "tool") {
173
+ // Tool result - show the result with the tool call ID for context
174
+ parts.push(`[Tool result for ${msg.tool_call_id}]: ${extractTextContent(msg.content)}`);
175
+ }
176
+ }
177
+ // Add instruction for the model to continue if there are tool results
178
+ if (hasToolResults) {
179
+ parts.push("\nBased on the tool results above, please continue your response:");
180
+ }
181
+ return {
182
+ prompt: parts.join("\n\n"),
183
+ images: allImages,
184
+ hasImages: allImages.length > 0,
185
+ };
186
+ }
187
+ /**
188
+ * Map exec request to OpenAI tool call format
189
+ *
190
+ * Mapping rules:
191
+ * - shell → bash: Execute shell commands
192
+ * - read → read: Read file contents
193
+ * - ls → list: List directory contents
194
+ * - grep (with pattern) → grep: Search file contents
195
+ * - grep (with glob) → glob: Search files by pattern
196
+ * - write → write: Write/create files (supports text and binary)
197
+ * - mcp → original tool name: MCP tool passthrough
198
+ * - request_context → null: Internal use only, not exposed
199
+ */
200
+ export function mapExecRequestToTool(execReq) {
201
+ switch (execReq.type) {
202
+ case "shell": {
203
+ const toolArgs = { command: execReq.command };
204
+ if (execReq.cwd)
205
+ toolArgs.cwd = execReq.cwd;
206
+ return { toolName: "bash", toolArgs };
207
+ }
208
+ case "read":
209
+ return { toolName: "read", toolArgs: { filePath: execReq.path } };
210
+ case "ls":
211
+ return { toolName: "list", toolArgs: { path: execReq.path } };
212
+ case "grep": {
213
+ // Prefer glob pattern over regex pattern when both exist
214
+ // glob is used for file path matching (e.g., **/*.ts)
215
+ // pattern is used for content searching (e.g., "function foo")
216
+ if (execReq.glob) {
217
+ return { toolName: "glob", toolArgs: { pattern: execReq.glob, path: execReq.path } };
218
+ }
219
+ return { toolName: "grep", toolArgs: { pattern: execReq.pattern, path: execReq.path } };
220
+ }
221
+ case "write": {
222
+ // Handle both text and binary content
223
+ // Binary content (fileBytes) takes precedence if present
224
+ const content = execReq.fileBytes && execReq.fileBytes.length > 0
225
+ ? Buffer.from(execReq.fileBytes).toString("base64")
226
+ : execReq.fileText;
227
+ const toolArgs = { filePath: execReq.path, content };
228
+ // Flag to indicate base64 encoding for binary files
229
+ if (execReq.fileBytes && execReq.fileBytes.length > 0) {
230
+ toolArgs.encoding = "base64";
231
+ }
232
+ return { toolName: "write", toolArgs };
233
+ }
234
+ case "mcp":
235
+ return {
236
+ toolName: execReq.toolName,
237
+ toolArgs: (execReq.args ?? {})
238
+ };
239
+ case "request_context":
240
+ // Internal tool, not exposed to OpenCode
241
+ return { toolName: null, toolArgs: null };
242
+ default:
243
+ // Unknown type - return null to skip
244
+ return { toolName: null, toolArgs: null };
245
+ }
246
+ }
247
+ /**
248
+ * Create an error response in OpenAI format
249
+ */
250
+ export function createErrorResponse(message, type = "invalid_request_error", status = 400) {
251
+ return new Response(JSON.stringify({
252
+ error: {
253
+ message,
254
+ type,
255
+ param: null,
256
+ code: null,
257
+ },
258
+ }), {
259
+ status,
260
+ headers: {
261
+ "Content-Type": "application/json",
262
+ "Access-Control-Allow-Origin": "*",
263
+ },
264
+ });
265
+ }
266
+ /**
267
+ * Create an SSE chunk string from data
268
+ */
269
+ export function createSSEChunk(data) {
270
+ return `data: ${JSON.stringify(data)}\n\n`;
271
+ }
272
+ /**
273
+ * Create an SSE done signal
274
+ */
275
+ export function createSSEDone() {
276
+ return "data: [DONE]\n\n";
277
+ }
278
+ /**
279
+ * Create a streaming SSE response
280
+ */
281
+ export function makeStreamResponse(readable) {
282
+ return new Response(readable, {
283
+ headers: {
284
+ "Content-Type": "text/event-stream",
285
+ "Cache-Control": "no-cache",
286
+ "Connection": "keep-alive",
287
+ "Access-Control-Allow-Origin": "*",
288
+ },
289
+ });
290
+ }
291
+ /**
292
+ * Create CORS preflight response
293
+ */
294
+ export function handleCORS() {
295
+ return new Response(null, {
296
+ status: 204,
297
+ headers: {
298
+ "Access-Control-Allow-Origin": "*",
299
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
300
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
301
+ "Access-Control-Max-Age": "86400",
302
+ },
303
+ });
304
+ }
305
+ /**
306
+ * Determine model owner based on model name
307
+ */
308
+ export function getModelOwner(displayName) {
309
+ const lowerName = displayName.toLowerCase();
310
+ if (lowerName.includes("claude") || lowerName.includes("opus") || lowerName.includes("sonnet")) {
311
+ return "anthropic";
312
+ }
313
+ if (lowerName.includes("gpt")) {
314
+ return "openai";
315
+ }
316
+ if (lowerName.includes("gemini")) {
317
+ return "google";
318
+ }
319
+ if (lowerName.includes("grok")) {
320
+ return "xai";
321
+ }
322
+ return "cursor";
323
+ }
324
+ /**
325
+ * Create an OpenAI stream chunk
326
+ */
327
+ export function createStreamChunk(completionId, model, created, delta, finishReason = null) {
328
+ return {
329
+ id: completionId,
330
+ object: "chat.completion.chunk",
331
+ created,
332
+ model,
333
+ choices: [{
334
+ index: 0,
335
+ delta,
336
+ finish_reason: finishReason,
337
+ }],
338
+ };
339
+ }
340
+ /**
341
+ * Generate a tool call ID from completion ID and index
342
+ */
343
+ export function generateToolCallId(completionId, index) {
344
+ // completionId format is "chatcmpl-{uuid}", so skip the "chatcmpl-" prefix (9 chars)
345
+ // This ensures unique IDs across different requests
346
+ return `call_${completionId.slice(9, 17)}_${index}`;
347
+ }
348
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/lib/openai-compat/utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Session Reuse Utilities
3
+ *
4
+ * ARCHITECTURAL NOTE:
5
+ * True session reuse (keeping a single bidirectional stream open across multiple
6
+ * OpenAI API requests) is not possible due to a fundamental mismatch:
7
+ *
8
+ * - OpenAI API: Request/response model. Must close HTTP response to return tool_calls
9
+ * to the client, then receive a new HTTP request with tool results.
10
+ *
11
+ * - Cursor's bidirectional streaming: Keeps a single stream open. Tool execution
12
+ * happens locally while the stream stays open. Results are sent via bidiAppend,
13
+ * and the server continues generating automatically.
14
+ *
15
+ * Our workaround: When tool results come back in a new request, we close any
16
+ * existing session and start completely fresh. The messagesToPrompt() function
17
+ * formats the full conversation history including prior tool calls and results,
18
+ * so the server has full context even though we're starting a new stream.
19
+ *
20
+ * The session infrastructure (SessionLike, pendingExecs, etc.) is retained for:
21
+ * 1. Potential future improvements if we find a way to bridge the gap
22
+ * 2. Internal read handling during edit flows (single-request scope)
23
+ */
24
+ import type { ExecRequest, McpExecRequest } from "./api/agent-service";
25
+ export interface OpenAIToolCallLite {
26
+ id?: string;
27
+ function?: {
28
+ name?: string;
29
+ arguments?: string;
30
+ };
31
+ }
32
+ export interface OpenAIMessageLite {
33
+ role: "system" | "user" | "assistant" | "tool";
34
+ content: unknown;
35
+ tool_calls?: OpenAIToolCallLite[];
36
+ tool_call_id?: string;
37
+ }
38
+ export interface SessionClient {
39
+ sendToolResult: (execRequest: McpExecRequest & {
40
+ type: "mcp";
41
+ }, result: {
42
+ success?: {
43
+ content: string;
44
+ isError?: boolean;
45
+ };
46
+ error?: string;
47
+ }) => Promise<void>;
48
+ sendShellResult: (id: number, execId: string | undefined, command: string, cwd: string, stdout: string, stderr: string, exitCode: number, executionTimeMs?: number) => Promise<void>;
49
+ sendReadResult: (id: number, execId: string | undefined, content: string, path: string, totalLines?: number, fileSize?: bigint, truncated?: boolean) => Promise<void>;
50
+ sendLsResult: (id: number, execId: string | undefined, filesString: string) => Promise<void>;
51
+ sendGrepResult: (id: number, execId: string | undefined, pattern: string, path: string, files: string[]) => Promise<void>;
52
+ sendWriteResult: (id: number, execId: string | undefined, result: {
53
+ success?: {
54
+ path: string;
55
+ linesCreated: number;
56
+ fileSize: number;
57
+ fileContentAfterWrite?: string;
58
+ };
59
+ error?: {
60
+ path: string;
61
+ error: string;
62
+ };
63
+ }) => Promise<void>;
64
+ sendResumeAction?: () => Promise<void>;
65
+ }
66
+ export interface SessionLike {
67
+ id: string;
68
+ iterator: AsyncIterator<unknown>;
69
+ pendingExecs: Map<string, ExecRequest>;
70
+ createdAt: number;
71
+ lastActivity: number;
72
+ state: "running" | "waiting_tool";
73
+ client: SessionClient;
74
+ }
75
+ export declare function createSessionId(): string;
76
+ export declare function makeToolCallId(sessionId: string, callBase: string): string;
77
+ export declare function parseSessionIdFromToolCallId(toolCallId: string | null | undefined): string | null;
78
+ export declare function findSessionIdInMessages(messages: OpenAIMessageLite[]): string | null;
79
+ export declare function collectToolMessages(messages: OpenAIMessageLite[]): OpenAIMessageLite[];
80
+ export declare function extractMessageContent(message: OpenAIMessageLite): string;
81
+ export declare function selectCallBase(execReq: ExecRequest): string;
82
+ export { mapExecRequestToTool } from "./openai-compat/utils";
83
+ export declare function safeParseJson(input: string): Record<string, unknown> | null;
84
+ export declare function sendToolResultsToCursor(session: SessionLike, toolMessages: OpenAIMessageLite[]): Promise<boolean>;
85
+ export declare function cleanupExpiredSessions(sessionMap: Map<string, {
86
+ iterator?: AsyncIterator<unknown>;
87
+ lastActivity: number;
88
+ }>, timeoutMs: number, now?: number): Promise<void>;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Session Reuse Utilities
3
+ *
4
+ * ARCHITECTURAL NOTE:
5
+ * True session reuse (keeping a single bidirectional stream open across multiple
6
+ * OpenAI API requests) is not possible due to a fundamental mismatch:
7
+ *
8
+ * - OpenAI API: Request/response model. Must close HTTP response to return tool_calls
9
+ * to the client, then receive a new HTTP request with tool results.
10
+ *
11
+ * - Cursor's bidirectional streaming: Keeps a single stream open. Tool execution
12
+ * happens locally while the stream stays open. Results are sent via bidiAppend,
13
+ * and the server continues generating automatically.
14
+ *
15
+ * Our workaround: When tool results come back in a new request, we close any
16
+ * existing session and start completely fresh. The messagesToPrompt() function
17
+ * formats the full conversation history including prior tool calls and results,
18
+ * so the server has full context even though we're starting a new stream.
19
+ *
20
+ * The session infrastructure (SessionLike, pendingExecs, etc.) is retained for:
21
+ * 1. Potential future improvements if we find a way to bridge the gap
22
+ * 2. Internal read handling during edit flows (single-request scope)
23
+ */
24
+ import crypto from "node:crypto";
25
+ export function createSessionId() {
26
+ return crypto.randomUUID().replace(/-/g, "").slice(0, 12);
27
+ }
28
+ export function makeToolCallId(sessionId, callBase) {
29
+ return `sess_${sessionId}__call_${callBase}`;
30
+ }
31
+ export function parseSessionIdFromToolCallId(toolCallId) {
32
+ if (!toolCallId)
33
+ return null;
34
+ const match = toolCallId.match(/^sess_([a-zA-Z0-9]+)__call_/);
35
+ if (!match)
36
+ return null;
37
+ return match[1] ?? null;
38
+ }
39
+ export function findSessionIdInMessages(messages) {
40
+ for (let i = messages.length - 1; i >= 0; i--) {
41
+ const msg = messages[i];
42
+ if (!msg)
43
+ continue;
44
+ if (msg.role === "tool" && msg.tool_call_id) {
45
+ const sessionId = parseSessionIdFromToolCallId(msg.tool_call_id);
46
+ if (sessionId)
47
+ return sessionId;
48
+ }
49
+ if (msg.role === "assistant" && Array.isArray(msg.tool_calls)) {
50
+ for (const tc of msg.tool_calls) {
51
+ const sessionId = parseSessionIdFromToolCallId(tc?.id);
52
+ if (sessionId)
53
+ return sessionId;
54
+ }
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ export function collectToolMessages(messages) {
60
+ return messages.filter((m) => m?.role === "tool" && !!m.tool_call_id);
61
+ }
62
+ export function extractMessageContent(message) {
63
+ if (typeof message.content === "string")
64
+ return message.content;
65
+ if (Array.isArray(message.content)) {
66
+ return message.content
67
+ .map((part) => {
68
+ if (typeof part === "string")
69
+ return part;
70
+ if (part && typeof part === "object" && "text" in part) {
71
+ const text = part.text;
72
+ return typeof text === "string" ? text : "";
73
+ }
74
+ return "";
75
+ })
76
+ .filter(Boolean)
77
+ .join("");
78
+ }
79
+ return "";
80
+ }
81
+ export function selectCallBase(execReq) {
82
+ const raw = execReq.type === "mcp"
83
+ ? execReq.toolCallId
84
+ : execReq.execId ?? String(execReq.id ?? crypto.randomUUID());
85
+ const cleaned = raw.replace(/[^a-zA-Z0-9]/g, "");
86
+ return cleaned.slice(0, 32) || crypto.randomUUID().replace(/-/g, "").slice(0, 12);
87
+ }
88
+ // Re-export from utils.ts to maintain backward compatibility
89
+ // The canonical implementation is in openai-compat/utils.ts
90
+ export { mapExecRequestToTool } from "./openai-compat/utils";
91
+ export function safeParseJson(input) {
92
+ try {
93
+ return JSON.parse(input);
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ export async function sendToolResultsToCursor(session, toolMessages) {
100
+ let processedAny = false;
101
+ for (const message of toolMessages) {
102
+ if (!message.tool_call_id)
103
+ continue;
104
+ console.log(`[Session ${session.id}] Looking up tool_call_id=${message.tool_call_id}, available keys=[${Array.from(session.pendingExecs.keys()).join(", ")}]`);
105
+ const execReq = session.pendingExecs.get(message.tool_call_id);
106
+ if (!execReq) {
107
+ console.warn(`[Session ${session.id}] Tool result for unknown tool_call_id ${message.tool_call_id}; ignoring`);
108
+ continue;
109
+ }
110
+ const content = extractMessageContent(message);
111
+ try {
112
+ if (execReq.type === "mcp") {
113
+ await session.client.sendToolResult(execReq, {
114
+ success: { content, isError: false },
115
+ });
116
+ }
117
+ else if (execReq.type === "shell") {
118
+ const parsed = safeParseJson(content);
119
+ const stdout = parsed && typeof parsed.stdout === "string" ? parsed.stdout : content;
120
+ const stderr = parsed && typeof parsed.stderr === "string" ? parsed.stderr : "";
121
+ const exitCode = parsed && typeof parsed.exitCode === "number" ? parsed.exitCode : 0;
122
+ const executionTimeMs = parsed && typeof parsed.executionTimeMs === "number" ? parsed.executionTimeMs : undefined;
123
+ await session.client.sendShellResult(execReq.id, execReq.execId, execReq.command, execReq.cwd || process.cwd(), stdout, stderr, exitCode, executionTimeMs);
124
+ }
125
+ else if (execReq.type === "read") {
126
+ await session.client.sendReadResult(execReq.id, execReq.execId, content, execReq.path, content.split("\n").length, BigInt(content.length), false);
127
+ }
128
+ else if (execReq.type === "ls") {
129
+ await session.client.sendLsResult(execReq.id, execReq.execId, content);
130
+ }
131
+ else if (execReq.type === "grep") {
132
+ const files = content.trim().split("\n").filter(Boolean);
133
+ const pattern = execReq.glob ?? execReq.pattern ?? "";
134
+ const path = execReq.path ?? process.cwd();
135
+ await session.client.sendGrepResult(execReq.id, execReq.execId, pattern, path, files);
136
+ }
137
+ else if (execReq.type === "write") {
138
+ const parsed = safeParseJson(content);
139
+ const parsedError = parsed?.error;
140
+ if (typeof parsedError === "string" && parsedError.length > 0) {
141
+ await session.client.sendWriteResult(execReq.id, execReq.execId, {
142
+ error: { path: execReq.path, error: parsedError },
143
+ });
144
+ }
145
+ else {
146
+ const linesCreatedValue = parsed?.linesCreated;
147
+ const fileSizeValue = parsed?.fileSize;
148
+ const fileContentAfterWriteValue = parsed?.fileContentAfterWrite;
149
+ const linesCreated = typeof linesCreatedValue === "number" ? linesCreatedValue : content.split("\n").length;
150
+ const fileSize = typeof fileSizeValue === "number" ? fileSizeValue : content.length;
151
+ const fileContentAfterWrite = typeof fileContentAfterWriteValue === "string" ? fileContentAfterWriteValue : undefined;
152
+ await session.client.sendWriteResult(execReq.id, execReq.execId, {
153
+ success: {
154
+ path: execReq.path,
155
+ linesCreated,
156
+ fileSize,
157
+ fileContentAfterWrite,
158
+ },
159
+ });
160
+ }
161
+ }
162
+ else {
163
+ return false;
164
+ }
165
+ }
166
+ catch (err) {
167
+ console.error(`[Session ${session.id}] Failed to send tool result for ${message.tool_call_id}:`, err);
168
+ return false;
169
+ }
170
+ session.pendingExecs.delete(message.tool_call_id);
171
+ processedAny = true;
172
+ }
173
+ if (processedAny) {
174
+ session.state = "running";
175
+ session.lastActivity = Date.now();
176
+ console.log(`[Session ${session.id}] processedAny=true, tool results sent. Waiting for server to continue...`);
177
+ }
178
+ else {
179
+ console.log(`[Session ${session.id}] processedAny=false, no matching tool results found`);
180
+ }
181
+ return processedAny;
182
+ }
183
+ export async function cleanupExpiredSessions(sessionMap, timeoutMs, now = Date.now()) {
184
+ for (const [sessionId, session] of sessionMap) {
185
+ if (now - session.lastActivity > timeoutMs) {
186
+ try {
187
+ await session.iterator?.return?.();
188
+ }
189
+ catch (err) {
190
+ console.warn(`[Session ${sessionId}] Failed to close expired iterator:`, err);
191
+ }
192
+ finally {
193
+ sessionMap.delete(sessionId);
194
+ }
195
+ }
196
+ }
197
+ }
198
+ //# sourceMappingURL=session-reuse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-reuse.js","sourceRoot":"","sources":["../../src/lib/session-reuse.ts"],"names":[],"mappings":"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"}