@playwo/opencode-cursor-oauth 0.0.0-dev.762b07a81479 → 0.0.0-dev.825419c3fcde

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.
@@ -1,6 +1,6 @@
1
1
  import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
2
2
  import { AgentClientMessageSchema, AgentServerMessageSchema, ExecClientMessageSchema, McpErrorSchema, McpResultSchema, McpSuccessSchema, McpTextContentSchema, McpToolResultContentItemSchema, } from "../proto/agent_pb";
3
- import { errorDetails, logPluginError, logPluginWarn } from "../logger";
3
+ import { errorDetails, logPluginError, logPluginInfo, logPluginWarn, } from "../logger";
4
4
  import { formatToolCallSummary, formatToolResultSummary, } from "../openai/messages";
5
5
  import { activeBridges, updateStoredConversationAfterCompletion, } from "./conversation-state";
6
6
  import { startBridge } from "./bridge-session";
@@ -8,7 +8,7 @@ import { updateConversationCheckpoint, syncStoredBlobStore, } from "./state-sync
8
8
  import { SSE_HEADERS } from "./sse";
9
9
  import { computeUsage, createConnectFrameParser, createThinkingTagFilter, parseConnectEndStream, processServerMessage, scheduleBridgeEnd, } from "./stream-dispatch";
10
10
  const SSE_KEEPALIVE_INTERVAL_MS = 15_000;
11
- function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools, modelId, bridgeKey, convKey, metadata) {
11
+ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, cloudRule, mcpTools, modelId, bridgeKey, convKey, metadata) {
12
12
  const completionId = `chatcmpl-${crypto.randomUUID().replace(/-/g, "").slice(0, 28)}`;
13
13
  const created = Math.floor(Date.now() / 1000);
14
14
  let keepaliveTimer;
@@ -27,8 +27,6 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
27
27
  pendingExecs: [],
28
28
  outputTokens: 0,
29
29
  totalTokens: 0,
30
- interactionToolArgsText: new Map(),
31
- emittedToolCallIds: new Set(),
32
30
  };
33
31
  const tagFilter = createThinkingTagFilter();
34
32
  let assistantText = metadata.assistantSeedText ?? "";
@@ -49,6 +47,19 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
49
47
  return;
50
48
  controller.enqueue(encoder.encode("data: [DONE]\n\n"));
51
49
  };
50
+ const failStream = (message, code) => {
51
+ if (closed)
52
+ return;
53
+ sendSSE({
54
+ error: {
55
+ message,
56
+ type: "server_error",
57
+ ...(code ? { code } : {}),
58
+ },
59
+ });
60
+ sendDone();
61
+ closeController();
62
+ };
52
63
  const closeController = () => {
53
64
  if (closed)
54
65
  return;
@@ -77,7 +88,7 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
77
88
  const processChunk = createConnectFrameParser((messageBytes) => {
78
89
  try {
79
90
  const serverMessage = fromBinary(AgentServerMessageSchema, messageBytes);
80
- processServerMessage(serverMessage, blobStore, mcpTools, (data) => bridge.write(data), state, (text, isThinking) => {
91
+ processServerMessage(serverMessage, blobStore, cloudRule, mcpTools, (data) => bridge.write(data), state, (text, isThinking) => {
81
92
  if (isThinking) {
82
93
  sendSSE(makeChunk({ reasoning_content: text }));
83
94
  return;
@@ -90,7 +101,13 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
90
101
  sendSSE(makeChunk({ content }));
91
102
  }
92
103
  }, (exec) => {
93
- state.pendingExecs.push(exec);
104
+ const existingIndex = state.pendingExecs.findIndex((candidate) => candidate.toolCallId === exec.toolCallId);
105
+ if (existingIndex >= 0) {
106
+ state.pendingExecs[existingIndex] = exec;
107
+ }
108
+ else {
109
+ state.pendingExecs.push(exec);
110
+ }
94
111
  mcpExecReceived = true;
95
112
  const flushed = tagFilter.flush();
96
113
  if (flushed.reasoning)
@@ -129,6 +146,7 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
129
146
  bridge,
130
147
  heartbeatTimer,
131
148
  blobStore,
149
+ cloudRule,
132
150
  mcpTools,
133
151
  pendingExecs: state.pendingExecs,
134
152
  modelId,
@@ -193,17 +211,29 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
193
211
  stopKeepalive();
194
212
  }
195
213
  }, SSE_KEEPALIVE_INTERVAL_MS);
214
+ logPluginInfo("Opened Cursor streaming bridge", {
215
+ modelId,
216
+ bridgeKey,
217
+ convKey,
218
+ mcpToolCount: mcpTools.length,
219
+ hasCloudRule: Boolean(cloudRule),
220
+ });
196
221
  bridge.onData(processChunk);
197
222
  bridge.onClose((code) => {
223
+ logPluginInfo("Cursor streaming bridge closed", {
224
+ modelId,
225
+ bridgeKey,
226
+ convKey,
227
+ code,
228
+ mcpExecReceived,
229
+ hadEndStreamError: Boolean(endStreamError),
230
+ });
198
231
  clearInterval(heartbeatTimer);
199
232
  stopKeepalive();
200
233
  syncStoredBlobStore(convKey, blobStore);
201
234
  if (endStreamError) {
202
235
  activeBridges.delete(bridgeKey);
203
- if (!closed) {
204
- closed = true;
205
- controller.error(endStreamError);
206
- }
236
+ failStream(endStreamError.message, "cursor_bridge_closed");
207
237
  return;
208
238
  }
209
239
  if (!mcpExecReceived) {
@@ -223,11 +253,7 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
223
253
  }
224
254
  activeBridges.delete(bridgeKey);
225
255
  if (code !== 0 && !closed) {
226
- sendSSE(makeChunk({ content: "\n[Error: bridge connection lost]" }));
227
- sendSSE(makeChunk({}, "stop"));
228
- sendSSE(makeUsageChunk());
229
- sendDone();
230
- closeController();
256
+ failStream("Cursor bridge connection lost", "cursor_bridge_closed");
231
257
  }
232
258
  });
233
259
  },
@@ -251,11 +277,36 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
251
277
  return new Response(stream, { headers: SSE_HEADERS });
252
278
  }
253
279
  export async function handleStreamingResponse(payload, accessToken, modelId, bridgeKey, convKey, metadata) {
280
+ logPluginInfo("Starting Cursor streaming response", {
281
+ modelId,
282
+ bridgeKey,
283
+ convKey,
284
+ mcpToolCount: payload.mcpTools.length,
285
+ });
254
286
  const { bridge, heartbeatTimer } = await startBridge(accessToken, payload.requestBytes);
255
- return createBridgeStreamResponse(bridge, heartbeatTimer, payload.blobStore, payload.mcpTools, modelId, bridgeKey, convKey, metadata);
287
+ return createBridgeStreamResponse(bridge, heartbeatTimer, payload.blobStore, payload.cloudRule, payload.mcpTools, modelId, bridgeKey, convKey, metadata);
288
+ }
289
+ async function waitForResolvablePendingExecs(active, toolResults, timeoutMs = 2_000) {
290
+ const pendingToolCallIds = new Set(toolResults.map((result) => result.toolCallId));
291
+ const deadline = Date.now() + timeoutMs;
292
+ while (Date.now() < deadline) {
293
+ const unresolved = active.pendingExecs.filter((exec) => pendingToolCallIds.has(exec.toolCallId) && exec.execMsgId === 0);
294
+ if (unresolved.length === 0) {
295
+ return unresolved;
296
+ }
297
+ await new Promise((resolve) => setTimeout(resolve, 25));
298
+ }
299
+ const unresolved = active.pendingExecs.filter((exec) => pendingToolCallIds.has(exec.toolCallId) && exec.execMsgId === 0);
300
+ if (unresolved.length > 0) {
301
+ logPluginWarn("Cursor exec metadata did not arrive before tool-result resume", {
302
+ bridgeToolCallIds: unresolved.map((exec) => exec.toolCallId),
303
+ modelId: active.modelId,
304
+ });
305
+ }
306
+ return unresolved;
256
307
  }
257
- export function handleToolResultResume(active, toolResults, bridgeKey, convKey) {
258
- const { bridge, heartbeatTimer, blobStore, mcpTools, pendingExecs, modelId, metadata, } = active;
308
+ export async function handleToolResultResume(active, toolResults, bridgeKey, convKey) {
309
+ const { bridge, heartbeatTimer, blobStore, cloudRule, mcpTools, pendingExecs, modelId, metadata, } = active;
259
310
  const resumeMetadata = {
260
311
  ...metadata,
261
312
  assistantSeedText: [
@@ -265,6 +316,33 @@ export function handleToolResultResume(active, toolResults, bridgeKey, convKey)
265
316
  .filter(Boolean)
266
317
  .join("\n\n"),
267
318
  };
319
+ logPluginInfo("Preparing Cursor tool-result resume", {
320
+ bridgeKey,
321
+ convKey,
322
+ modelId,
323
+ toolResults,
324
+ pendingExecs,
325
+ });
326
+ const unresolved = await waitForResolvablePendingExecs(active, toolResults);
327
+ logPluginInfo("Resolved pending exec state before Cursor tool-result resume", {
328
+ bridgeKey,
329
+ convKey,
330
+ modelId,
331
+ toolResults,
332
+ pendingExecs,
333
+ unresolvedPendingExecs: unresolved,
334
+ });
335
+ if (unresolved.length > 0) {
336
+ clearInterval(heartbeatTimer);
337
+ bridge.end();
338
+ return new Response(JSON.stringify({
339
+ error: {
340
+ message: "Cursor requested a tool call but never provided resumable exec metadata. Aborting instead of retrying with synthetic ids.",
341
+ type: "invalid_request_error",
342
+ code: "cursor_missing_exec_metadata",
343
+ },
344
+ }), { status: 400, headers: { "Content-Type": "application/json" } });
345
+ }
268
346
  for (const exec of pendingExecs) {
269
347
  const result = toolResults.find((toolResult) => toolResult.toolCallId === exec.toolCallId);
270
348
  const mcpResult = result
@@ -305,7 +383,20 @@ export function handleToolResultResume(active, toolResults, bridgeKey, convKey)
305
383
  const clientMessage = create(AgentClientMessageSchema, {
306
384
  message: { case: "execClientMessage", value: execClientMessage },
307
385
  });
386
+ logPluginInfo("Sending Cursor tool-result resume message", {
387
+ bridgeKey,
388
+ convKey,
389
+ modelId,
390
+ toolCallId: exec.toolCallId,
391
+ toolName: exec.toolName,
392
+ source: exec.source,
393
+ execId: exec.execId,
394
+ execMsgId: exec.execMsgId,
395
+ cursorCallId: exec.cursorCallId,
396
+ modelCallId: exec.modelCallId,
397
+ matchedToolResult: result,
398
+ });
308
399
  bridge.write(toBinary(AgentClientMessageSchema, clientMessage));
309
400
  }
310
- return createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools, modelId, bridgeKey, convKey, resumeMetadata);
401
+ return createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, cloudRule, mcpTools, modelId, bridgeKey, convKey, resumeMetadata);
311
402
  }
@@ -1,4 +1,4 @@
1
- import { logPluginWarn } from "../logger";
1
+ import { logPluginInfo, logPluginWarn } from "../logger";
2
2
  import { buildInitialHandoffPrompt, buildTitleSourceText, buildToolResumePrompt, detectTitleRequest, parseMessages, } from "../openai/messages";
3
3
  import { buildMcpToolDefinitions, selectToolsForChoice } from "../openai/tools";
4
4
  import { activeBridges, conversationStates, createStoredConversation, deriveBridgeKey, deriveConversationKey, evictStaleConversations, hashString, normalizeAgentKey, resetStoredConversation, } from "./conversation-state";
@@ -7,9 +7,22 @@ import { handleNonStreamingResponse, handleStreamingResponse, handleToolResultRe
7
7
  import { handleTitleGenerationRequest } from "./title";
8
8
  export function handleChatCompletion(body, accessToken, context = {}) {
9
9
  const parsed = parseMessages(body.messages);
10
- const { systemPrompt, userText, turns, toolResults, pendingAssistantSummary, completedTurnsFingerprint, assistantContinuation, } = parsed;
10
+ const { systemPrompt, userText, turns, toolResults, pendingAssistantSummary, completedTurnsFingerprint, } = parsed;
11
11
  const modelId = body.model;
12
12
  const normalizedAgentKey = normalizeAgentKey(context.agentKey);
13
+ logPluginInfo("Handling Cursor chat completion request", {
14
+ modelId,
15
+ stream: body.stream !== false,
16
+ messageCount: body.messages.length,
17
+ toolCount: body.tools?.length ?? 0,
18
+ toolChoice: body.tool_choice,
19
+ sessionId: context.sessionId,
20
+ agentKey: normalizedAgentKey,
21
+ parsedUserText: userText,
22
+ parsedToolResults: toolResults,
23
+ hasPendingAssistantSummary: pendingAssistantSummary.trim().length > 0,
24
+ turnCount: turns.length,
25
+ });
13
26
  const titleDetection = detectTitleRequest(body);
14
27
  const isTitleAgent = titleDetection.matched;
15
28
  if (isTitleAgent) {
@@ -38,7 +51,23 @@ export function handleChatCompletion(body, accessToken, context = {}) {
38
51
  const bridgeKey = deriveBridgeKey(modelId, body.messages, context.sessionId, context.agentKey);
39
52
  const convKey = deriveConversationKey(body.messages, context.sessionId, context.agentKey);
40
53
  const activeBridge = activeBridges.get(bridgeKey);
54
+ logPluginInfo("Resolved Cursor conversation keys", {
55
+ modelId,
56
+ bridgeKey,
57
+ convKey,
58
+ hasActiveBridge: Boolean(activeBridge),
59
+ sessionId: context.sessionId,
60
+ agentKey: normalizedAgentKey,
61
+ });
41
62
  if (activeBridge && toolResults.length > 0) {
63
+ logPluginInfo("Matched OpenAI tool results to active Cursor bridge", {
64
+ bridgeKey,
65
+ convKey,
66
+ requestedModelId: modelId,
67
+ activeBridgeModelId: activeBridge.modelId,
68
+ toolResults,
69
+ pendingExecs: activeBridge.pendingExecs,
70
+ });
42
71
  activeBridges.delete(bridgeKey);
43
72
  if (activeBridge.bridge.alive) {
44
73
  if (activeBridge.modelId !== modelId) {
@@ -79,27 +108,30 @@ export function handleChatCompletion(body, accessToken, context = {}) {
79
108
  stored.completedTurnsFingerprint = completedTurnsFingerprint;
80
109
  stored.lastAccessMs = Date.now();
81
110
  evictStaleConversations();
82
- if (assistantContinuation) {
83
- return new Response(JSON.stringify({
84
- error: {
85
- message: "Assistant-last continuation is not supported by the Cursor provider",
86
- type: "invalid_request_error",
87
- },
88
- }), { status: 400, headers: { "Content-Type": "application/json" } });
89
- }
90
111
  // Build the request. When tool results are present but the bridge died,
91
112
  // we must still include the last user text so Cursor has context.
92
113
  const mcpTools = buildMcpToolDefinitions(tools);
114
+ const hasPendingAssistantSummary = pendingAssistantSummary.trim().length > 0;
93
115
  const needsInitialHandoff = !stored.checkpoint &&
94
- (turns.length > 0 || pendingAssistantSummary || toolResults.length > 0);
116
+ (turns.length > 0 || hasPendingAssistantSummary || toolResults.length > 0);
95
117
  const replayTurns = needsInitialHandoff ? [] : turns;
96
118
  let effectiveUserText = needsInitialHandoff
97
119
  ? buildInitialHandoffPrompt(userText, turns, pendingAssistantSummary, toolResults)
98
- : toolResults.length > 0
120
+ : toolResults.length > 0 || hasPendingAssistantSummary
99
121
  ? buildToolResumePrompt(userText, pendingAssistantSummary, toolResults)
100
122
  : userText;
101
123
  const payload = buildCursorRequest(modelId, systemPrompt, effectiveUserText, replayTurns, stored.conversationId, stored.checkpoint, stored.blobStore);
102
124
  payload.mcpTools = mcpTools;
125
+ logPluginInfo("Built Cursor run request payload", {
126
+ modelId,
127
+ bridgeKey,
128
+ convKey,
129
+ mcpToolCount: mcpTools.length,
130
+ conversationId: stored.conversationId,
131
+ hasCheckpoint: Boolean(stored.checkpoint),
132
+ replayTurnCount: replayTurns.length,
133
+ effectiveUserText,
134
+ });
103
135
  if (body.stream === false) {
104
136
  return handleNonStreamingResponse(payload, accessToken, modelId, convKey, {
105
137
  systemPrompt,
@@ -1,13 +1,8 @@
1
1
  import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
2
- import { createHash } from "node:crypto";
3
- import { AgentClientMessageSchema, AgentRunRequestSchema, ConversationActionSchema, ConversationStateStructureSchema, ConversationStepSchema, AgentConversationTurnStructureSchema, ConversationTurnStructureSchema, AssistantMessageSchema, ModelDetailsSchema, ResumeActionSchema, UserMessageActionSchema, UserMessageSchema, } from "../proto/agent_pb";
2
+ import { AgentClientMessageSchema, AgentRunRequestSchema, AgentConversationTurnStructureSchema, AssistantMessageSchema, ConversationActionSchema, ConversationStateStructureSchema, ConversationTurnStructureSchema, ConversationStepSchema, ModelDetailsSchema, ResumeActionSchema, UserMessageActionSchema, UserMessageSchema, } from "../proto/agent_pb";
4
3
  export function buildCursorRequest(modelId, systemPrompt, userText, turns, conversationId, checkpoint, existingBlobStore) {
5
4
  const blobStore = new Map(existingBlobStore ?? []);
6
- // System prompt → blob store (Cursor requests it back via KV handshake)
7
- const systemJson = JSON.stringify({ role: "system", content: systemPrompt });
8
- const systemBytes = new TextEncoder().encode(systemJson);
9
- const systemBlobId = new Uint8Array(createHash("sha256").update(systemBytes).digest());
10
- blobStore.set(Buffer.from(systemBlobId).toString("hex"), systemBytes);
5
+ const cloudRule = buildCloudRule(systemPrompt);
11
6
  let conversationState;
12
7
  if (checkpoint) {
13
8
  conversationState = fromBinary(ConversationStateStructureSchema, checkpoint);
@@ -40,7 +35,7 @@ export function buildCursorRequest(modelId, systemPrompt, userText, turns, conve
40
35
  turnBytes.push(toBinary(ConversationTurnStructureSchema, turnStructure));
41
36
  }
42
37
  conversationState = create(ConversationStateStructureSchema, {
43
- rootPromptMessagesJson: [systemBlobId],
38
+ rootPromptMessagesJson: [],
44
39
  turns: turnBytes,
45
40
  todos: [],
46
41
  pendingToolCalls: [],
@@ -64,14 +59,11 @@ export function buildCursorRequest(modelId, systemPrompt, userText, turns, conve
64
59
  value: create(UserMessageActionSchema, { userMessage }),
65
60
  },
66
61
  });
67
- return buildRunRequest(modelId, conversationId, conversationState, action, blobStore);
62
+ return buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule);
68
63
  }
69
64
  export function buildCursorResumeRequest(modelId, systemPrompt, conversationId, checkpoint, existingBlobStore) {
70
65
  const blobStore = new Map(existingBlobStore ?? []);
71
- const systemJson = JSON.stringify({ role: "system", content: systemPrompt });
72
- const systemBytes = new TextEncoder().encode(systemJson);
73
- const systemBlobId = new Uint8Array(createHash("sha256").update(systemBytes).digest());
74
- blobStore.set(Buffer.from(systemBlobId).toString("hex"), systemBytes);
66
+ const cloudRule = buildCloudRule(systemPrompt);
75
67
  const conversationState = fromBinary(ConversationStateStructureSchema, checkpoint);
76
68
  const action = create(ConversationActionSchema, {
77
69
  action: {
@@ -79,9 +71,9 @@ export function buildCursorResumeRequest(modelId, systemPrompt, conversationId,
79
71
  value: create(ResumeActionSchema, {}),
80
72
  },
81
73
  });
82
- return buildRunRequest(modelId, conversationId, conversationState, action, blobStore);
74
+ return buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule);
83
75
  }
84
- function buildRunRequest(modelId, conversationId, conversationState, action, blobStore) {
76
+ function buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule) {
85
77
  const modelDetails = create(ModelDetailsSchema, {
86
78
  modelId,
87
79
  displayModelId: modelId,
@@ -99,6 +91,11 @@ function buildRunRequest(modelId, conversationId, conversationState, action, blo
99
91
  return {
100
92
  requestBytes: toBinary(AgentClientMessageSchema, clientMessage),
101
93
  blobStore,
94
+ cloudRule,
102
95
  mcpTools: [],
103
96
  };
104
97
  }
98
+ function buildCloudRule(systemPrompt) {
99
+ const content = systemPrompt.trim();
100
+ return content || undefined;
101
+ }
@@ -1,4 +1,4 @@
1
- import { errorDetails, logPluginError } from "../logger";
1
+ import { errorDetails, logPluginError, logPluginWarn } from "../logger";
2
2
  import { handleChatCompletion } from "./chat-completion";
3
3
  import { activeBridges, conversationStates } from "./conversation-state";
4
4
  let proxyServer;
@@ -42,14 +42,32 @@ export async function startProxy(getAccessToken, models = []) {
42
42
  throw new Error("Cursor proxy access token provider not configured");
43
43
  }
44
44
  const accessToken = await proxyAccessTokenProvider();
45
- const sessionId = req.headers.get("x-opencode-session-id") ??
46
- req.headers.get("x-session-id") ??
47
- undefined;
45
+ const sessionId = req.headers.get("x-session-id") ?? undefined;
48
46
  const agentKey = req.headers.get("x-opencode-agent") ?? undefined;
49
- return handleChatCompletion(body, accessToken, {
47
+ const response = await handleChatCompletion(body, accessToken, {
50
48
  sessionId,
51
49
  agentKey,
52
50
  });
51
+ if (response.status >= 400) {
52
+ let responseBody = "";
53
+ try {
54
+ responseBody = await response.clone().text();
55
+ }
56
+ catch (error) {
57
+ responseBody = `Failed to read rejected response body: ${error instanceof Error ? error.message : String(error)}`;
58
+ }
59
+ logPluginWarn("Rejected Cursor chat completion", {
60
+ path: url.pathname,
61
+ method: req.method,
62
+ sessionId,
63
+ agentKey,
64
+ status: response.status,
65
+ requestBody: body,
66
+ requestBodyText: JSON.stringify(body),
67
+ responseBody,
68
+ });
69
+ }
70
+ return response;
53
71
  }
54
72
  catch (err) {
55
73
  const message = err instanceof Error ? err.message : String(err);
@@ -39,4 +39,4 @@ export declare function computeUsage(state: StreamState): {
39
39
  completion_tokens: number;
40
40
  total_tokens: number;
41
41
  };
42
- export declare function processServerMessage(msg: AgentServerMessage, blobStore: Map<string, Uint8Array>, mcpTools: McpToolDefinition[], sendFrame: (data: Uint8Array) => void, state: StreamState, onText: (text: string, isThinking?: boolean) => void, onMcpExec: (exec: PendingExec) => void, onCheckpoint?: (checkpointBytes: Uint8Array) => void, onTurnEnded?: () => void, onUnsupportedMessage?: (info: UnsupportedServerMessageInfo) => void, onUnhandledExec?: (info: UnhandledExecInfo) => void): void;
42
+ export declare function processServerMessage(msg: AgentServerMessage, blobStore: Map<string, Uint8Array>, cloudRule: string | undefined, mcpTools: McpToolDefinition[], sendFrame: (data: Uint8Array) => void, state: StreamState, onText: (text: string, isThinking?: boolean) => void, onMcpExec: (exec: PendingExec) => void, onCheckpoint?: (checkpointBytes: Uint8Array) => void, onTurnEnded?: () => void, onUnsupportedMessage?: (info: UnsupportedServerMessageInfo) => void, onUnhandledExec?: (info: UnhandledExecInfo) => void): void;