Skip to content

Commit 05bd9ff

Browse files
author
Your Name
committed
fix: expose cursor thinking as reasoning_content in plugin proxy
1 parent b02fa97 commit 05bd9ff

2 files changed

Lines changed: 51 additions & 13 deletions

File tree

src/plugin.ts

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { startCursorOAuth } from "./auth";
88
import { LineBuffer } from "./streaming/line-buffer.js";
99
import { StreamToSseConverter, formatSseDone } from "./streaming/openai-sse.js";
1010
import { parseStreamJsonLine } from "./streaming/parser.js";
11-
import { extractText, isAssistantText } from "./streaming/types.js";
11+
import { extractText, extractThinking, isAssistantText, isThinking } from "./streaming/types.js";
1212
import { createLogger } from "./utils/logger";
1313
import { parseAgentError, formatErrorForUser, stripAnsi } from "./utils/errors";
1414
import { OpenCodeToolDiscovery } from "./tools/discovery.js";
@@ -38,7 +38,16 @@ function getGlobalKey(): string {
3838
return "__opencode_cursor_proxy_server__";
3939
}
4040

41-
function createChatCompletionResponse(model: string, content: string) {
41+
function createChatCompletionResponse(model: string, content: string, reasoningContent?: string) {
42+
const message: { role: "assistant"; content: string; reasoning_content?: string } = {
43+
role: "assistant",
44+
content,
45+
};
46+
47+
if (reasoningContent && reasoningContent.length > 0) {
48+
message.reasoning_content = reasoningContent;
49+
}
50+
4251
return {
4352
id: `cursor-acp-${Date.now()}`,
4453
object: "chat.completion",
@@ -47,7 +56,7 @@ function createChatCompletionResponse(model: string, content: string) {
4756
choices: [
4857
{
4958
index: 0,
50-
message: { role: "assistant", content },
59+
message,
5160
finish_reason: "stop",
5261
},
5362
],
@@ -70,16 +79,30 @@ function createChatCompletionChunk(id: string, created: number, model: string, d
7079
};
7180
}
7281

73-
function extractAssistantTextFromStream(output: string): string {
82+
function extractCompletionFromStream(output: string): { assistantText: string; reasoningText: string } {
7483
const lines = output.split("\n");
75-
let content = "";
84+
let assistantText = "";
85+
let reasoningText = "";
86+
7687
for (const line of lines) {
7788
const event = parseStreamJsonLine(line);
78-
if (event && isAssistantText(event)) {
79-
content = extractText(event);
89+
if (!event) {
90+
continue;
91+
}
92+
93+
if (isAssistantText(event)) {
94+
assistantText = extractText(event);
95+
}
96+
97+
if (isThinking(event)) {
98+
const thinking = extractThinking(event);
99+
if (thinking) {
100+
reasoningText += thinking;
101+
}
80102
}
81103
}
82-
return content;
104+
105+
return { assistantText, reasoningText };
83106
}
84107

85108
function formatToolUpdateEvent(update: ToolUpdate): string {
@@ -198,6 +221,7 @@ async function ensureCursorProxyServer(workspaceDirectory: string, toolRouter?:
198221
"--print",
199222
"--output-format",
200223
"stream-json",
224+
"--stream-partial-output",
201225
"--workspace",
202226
workspaceDirectory,
203227
"--model",
@@ -239,8 +263,12 @@ async function ensureCursorProxyServer(workspaceDirectory: string, toolRouter?:
239263
});
240264
}
241265

242-
const assistantText = extractAssistantTextFromStream(stdout);
243-
const payload = createChatCompletionResponse(model, assistantText || stdout || stderr);
266+
const completion = extractCompletionFromStream(stdout);
267+
const payload = createChatCompletionResponse(
268+
model,
269+
completion.assistantText || stdout || stderr,
270+
completion.reasoningText || undefined,
271+
);
244272
return new Response(JSON.stringify(payload), {
245273
status: 200,
246274
headers: { "Content-Type": "application/json" },
@@ -452,6 +480,7 @@ async function ensureCursorProxyServer(workspaceDirectory: string, toolRouter?:
452480
"--print",
453481
"--output-format",
454482
"stream-json",
483+
"--stream-partial-output",
455484
"--workspace",
456485
workspaceDirectory,
457486
"--model",
@@ -474,7 +503,7 @@ async function ensureCursorProxyServer(workspaceDirectory: string, toolRouter?:
474503
child.on("close", async (code) => {
475504
const stdout = Buffer.concat(stdoutChunks).toString().trim();
476505
const stderr = Buffer.concat(stderrChunks).toString().trim();
477-
const assistantText = extractAssistantTextFromStream(stdout);
506+
const completion = extractCompletionFromStream(stdout);
478507

479508
if (code !== 0 && stderr.length > 0) {
480509
const parsed = parseAgentError(stderr);
@@ -507,7 +536,15 @@ async function ensureCursorProxyServer(workspaceDirectory: string, toolRouter?:
507536
choices: [
508537
{
509538
index: 0,
510-
message: { role: "assistant", content: assistantText || stdout || stderr },
539+
message: {
540+
role: "assistant",
541+
content: completion.assistantText || stdout || stderr,
542+
...(completion.reasoningText
543+
? {
544+
reasoning_content: completion.reasoningText,
545+
}
546+
: {}),
547+
},
511548
finish_reason: "stop",
512549
},
513550
],

src/streaming/openai-sse.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type OpenAiToolCall = {
2222

2323
type OpenAiDelta = {
2424
content?: string;
25+
reasoning_content?: string;
2526
tool_calls?: OpenAiToolCall[];
2627
};
2728

@@ -75,7 +76,7 @@ export class StreamToSseConverter {
7576

7677
if (isThinking(event)) {
7778
const delta = this.tracker.nextThinking(extractThinking(event));
78-
return delta ? [this.chunkWith({ content: delta })] : [];
79+
return delta ? [this.chunkWith({ reasoning_content: delta })] : [];
7980
}
8081

8182
if (isToolCall(event)) {

0 commit comments

Comments
 (0)