@code-yeongyu/senpi-ai 2026.8.19 → 2026.8.20
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.
- package/dist/api/cursor-agent.d.ts +2 -0
- package/dist/api/cursor-agent.d.ts.map +1 -1
- package/dist/api/cursor-agent.js +284 -209
- package/dist/api/cursor-agent.js.map +1 -1
- package/dist/api/cursor-conversation-rotation.d.ts +37 -0
- package/dist/api/cursor-conversation-rotation.d.ts.map +1 -0
- package/dist/api/cursor-conversation-rotation.js +110 -0
- package/dist/api/cursor-conversation-rotation.js.map +1 -0
- package/dist/api/cursor-task-args.d.ts +3 -0
- package/dist/api/cursor-task-args.d.ts.map +1 -0
- package/dist/api/cursor-task-args.js +17 -0
- package/dist/api/cursor-task-args.js.map +1 -0
- package/dist/cursor/selection-descriptor.d.ts.map +1 -1
- package/dist/cursor/selection-descriptor.js +27 -11
- package/dist/cursor/selection-descriptor.js.map +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/providers/data/baseten.json +1 -1
- package/dist/providers/data/huggingface.json +1 -1
- package/dist/providers/data/opencode-go.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/tool-call-middleware/index.d.ts.map +1 -1
- package/dist/tool-call-middleware/index.js +2 -0
- package/dist/tool-call-middleware/index.js.map +1 -1
- package/dist/utils/overflow.d.ts +30 -0
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +33 -0
- package/dist/utils/overflow.js.map +1 -1
- package/package.json +2 -2
|
@@ -81,6 +81,8 @@ export interface BlockState {
|
|
|
81
81
|
export interface UsageState {
|
|
82
82
|
sawTokenDelta: boolean;
|
|
83
83
|
sawTurnEndedUsage: boolean;
|
|
84
|
+
/** Last checkpoint `usedTokens`; conversation window, not billed cache. */
|
|
85
|
+
liveUsedTokens?: number;
|
|
84
86
|
}
|
|
85
87
|
/** Exported for tests: drives one Cursor server message through the stream (exec waits mark the stream busy). */
|
|
86
88
|
export declare function handleServerMessage(msg: AgentServerMessage, output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, blobStore: Map<string, Uint8Array>, h2Request: http2.ClientHttp2Stream, execHandlers: CursorExecHandlers | undefined, onToolResult: CursorToolResultHandler | undefined, usageState: UsageState, requestContextTools: McpToolDefinition[], onConversationCheckpoint?: (checkpoint: ConversationStateStructure) => void): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor-agent.d.ts","sourceRoot":"","sources":["../../src/api/cursor-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAIpC,OAAO,KAAK,EAEX,gBAAgB,EAGhB,OAAO,EAEP,mBAAmB,EACnB,cAAc,EACd,WAAW,EACX,eAAe,EACf,IAAI,EACJ,QAAQ,EACR,iBAAiB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAGN,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AA0BvE,OAAO,EAIN,KAAK,kBAAkB,EAcvB,KAAK,0BAA0B,EA+D/B,KAAK,iBAAiB,EAoDtB,MAAM,gCAAgC,CAAC;AAUxC,OAAO,KAAK,EACX,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,EAGjB,uBAAuB,EACvB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"cursor-agent.d.ts","sourceRoot":"","sources":["../../src/api/cursor-agent.ts"],"names":[],"mappings":"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"}
|
package/dist/api/cursor-agent.js
CHANGED
|
@@ -31,6 +31,8 @@ import { buildMcpStateResult, buildNeutralHookResult, buildPiBashError, buildPiB
|
|
|
31
31
|
import { AgentClientMessageSchema, AgentConversationTurnStructureSchema, AgentRunRequestSchema, AgentServerMessageSchema, AgentStoreConflictErrorSchema, AgentStoreConflictResultSchema, AssistantMessageSchema, BackgroundShellSpawnResultSchema, CanvasDiagnosticsErrorSchema, CanvasDiagnosticsResultSchema, ClientHeartbeatSchema, ComputerUseErrorSchema, ComputerUseResultSchema, ConversationActionSchema, ConversationSearchErrorSchema, ConversationSearchResultSchema, ConversationStateStructureSchema, ConversationStepSchema, ConversationTurnStructureSchema, DeleteErrorSchema, DeleteRejectedSchema, DeleteResultSchema, DeleteSuccessSchema, DiagnosticsErrorSchema, DiagnosticsRejectedSchema, DiagnosticsResultSchema, DiagnosticsSuccessSchema, ExecClientControlMessageSchema, ExecClientHeartbeatSchema, ExecClientMessageSchema, ExecClientStreamCloseSchema, ExecClientThrowSchema, FetchErrorSchema, FetchResultSchema, ForceBackgroundShellResultSchema, ForceBackgroundShellStatus, ForceBackgroundSubagentResultSchema, ForceBackgroundSubagentStatus, GetBlobResultSchema, GetUsableModelsRequestSchema, GetUsableModelsResponseSchema, GrepContentMatchSchema, GrepContentResultSchema, GrepCountResultSchema, GrepErrorSchema, GrepFileCountSchema, GrepFileMatchSchema, GrepFilesResultSchema, GrepResultSchema, GrepSuccessSchema, GrepUnionResultSchema, KvClientMessageSchema, ListMcpResourcesExecResultSchema, ListMcpResourcesSuccessSchema, LsDirectoryTreeNode_FileSchema, LsDirectoryTreeNodeSchema, LsErrorSchema, LsRejectedSchema, LsResultSchema, LsSuccessSchema, McpAllowlistPrecheckResultSchema, McpApprovedSchema, McpArgsSchema, McpErrorSchema, McpImageContentSchema, McpRejectedSchema, McpResultSchema, McpSuccessSchema, McpTextContentSchema, McpToolCallSchema, McpToolDefinitionSchema, McpToolErrorSchema, McpToolNotFoundSchema, McpToolResultContentItemSchema, McpToolResultSchema, ModelDetailsSchema, ReadErrorSchema, ReadMcpResourceExecResultSchema, ReadMcpResourceNotFoundSchema, ReadRejectedSchema, ReadResultSchema, ReadSuccessSchema, RecordScreenFailureSchema, RecordScreenResultSchema, RequestContextResultSchema, RequestContextSchema, RequestContextSuccessSchema, ResumeActionSchema, SelectedContextSchema, SelectedImageSchema, SetBlobResultSchema, ShellAllowlistPrecheckResultSchema, ShellFailureSchema, ShellRejectedSchema, ShellResultSchema, ShellStreamExitSchema, ShellStreamSchema, ShellStreamStartSchema, ShellStreamStderrSchema, ShellStreamStdoutSchema, ShellSuccessSchema, SmartModeClassifierErrorSchema, SmartModeClassifierResultSchema, SubagentAwaitNotFoundSchema, SubagentAwaitResultSchema, SubagentErrorSchema, SubagentResultSchema, ToolCallSchema, UserMessageActionSchema, UserMessageSchema, WebFetchAllowlistPrecheckResultSchema, WriteErrorSchema, WriteRejectedSchema, WriteResultSchema, WriteShellStdinErrorSchema, WriteShellStdinResultSchema, WriteSuccessSchema, } from "./cursor-agent/gen/agent_pb.js";
|
|
32
32
|
import { composeShellCommand, omitUndefinedArgs, piLimit, piLsPath, piReadArgs, piTimeout, } from "./cursor-agent/pi-args.js";
|
|
33
33
|
import { buildRequestedModel } from "./cursor-agent/reasoning-params.js";
|
|
34
|
+
import { CURSOR_CONVERSATION_POISONED_MESSAGE, createConversationRotationStore, isZeroTokenResourceExhausted, resolveConversationRotationPersistPath, } from "./cursor-conversation-rotation.js";
|
|
35
|
+
import { keepUsableCursorTaskArgs } from "./cursor-task-args.js";
|
|
34
36
|
export const CURSOR_API_URL = "https://api2.cursor.sh";
|
|
35
37
|
export const CURSOR_CLIENT_VERSION = "cli-2026.07.23-e383d2b";
|
|
36
38
|
const EXEC_HEARTBEAT_INTERVAL_MS = 3000;
|
|
@@ -91,7 +93,6 @@ export function sanitizeCursorCallerHeaders(headers) {
|
|
|
91
93
|
const NOT_IMPLEMENTED_SUFFIX = "not implemented by this client";
|
|
92
94
|
const NOT_IMPLEMENTED = "Not implemented by this client";
|
|
93
95
|
/** Bare gRPC `resource_exhausted` end-streams (also inside a Connect error message). */
|
|
94
|
-
const RESOURCE_EXHAUSTED_PATTERN = /resource.?exhausted/i;
|
|
95
96
|
const conversationStateCache = new Map();
|
|
96
97
|
const conversationBlobStores = new Map();
|
|
97
98
|
/**
|
|
@@ -101,7 +102,18 @@ const conversationBlobStores = new Map();
|
|
|
101
102
|
* and the cached state migrates, so the retry loop's next attempt starts a
|
|
102
103
|
* fresh conversation. Keyed by the base id so a failed rotation never repeats.
|
|
103
104
|
*/
|
|
104
|
-
|
|
105
|
+
let conversationRotationStore = createConversationRotationStore({
|
|
106
|
+
persistPath: resolveConversationRotationPersistPath(),
|
|
107
|
+
});
|
|
108
|
+
let conversationRotationPersistPath = resolveConversationRotationPersistPath();
|
|
109
|
+
function rotationStore() {
|
|
110
|
+
const persistPath = resolveConversationRotationPersistPath();
|
|
111
|
+
if (persistPath !== conversationRotationPersistPath) {
|
|
112
|
+
conversationRotationPersistPath = persistPath;
|
|
113
|
+
conversationRotationStore = createConversationRotationStore({ persistPath });
|
|
114
|
+
}
|
|
115
|
+
return conversationRotationStore;
|
|
116
|
+
}
|
|
105
117
|
const CONNECT_END_STREAM_FLAG = 0b00000010;
|
|
106
118
|
function log(type, subtype, data) {
|
|
107
119
|
if (!process.env.DEBUG_CURSOR)
|
|
@@ -217,10 +229,6 @@ export const stream = (model, context, options) => {
|
|
|
217
229
|
let openBlockState;
|
|
218
230
|
let resolveH2 = () => { };
|
|
219
231
|
let rejectH2 = () => { };
|
|
220
|
-
const h2Completion = new Promise((resolve, reject) => {
|
|
221
|
-
resolveH2 = resolve;
|
|
222
|
-
rejectH2 = reject;
|
|
223
|
-
});
|
|
224
232
|
const settleH2 = (error) => {
|
|
225
233
|
if (h2Settled)
|
|
226
234
|
return;
|
|
@@ -244,218 +252,264 @@ export const stream = (model, context, options) => {
|
|
|
244
252
|
let baseConversationId;
|
|
245
253
|
let conversationId;
|
|
246
254
|
let usageState;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
blobStore,
|
|
260
|
-
conversationState: cachedState,
|
|
255
|
+
let retryPoisonedConversation = false;
|
|
256
|
+
let attempt = 0;
|
|
257
|
+
do {
|
|
258
|
+
retryPoisonedConversation = false;
|
|
259
|
+
attempt += 1;
|
|
260
|
+
h2Settled = false;
|
|
261
|
+
sawTurnEnded = false;
|
|
262
|
+
endStreamError = null;
|
|
263
|
+
openBlockState = undefined;
|
|
264
|
+
const h2Completion = new Promise((resolve, reject) => {
|
|
265
|
+
resolveH2 = resolve;
|
|
266
|
+
rejectH2 = reject;
|
|
261
267
|
});
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
"
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
268
|
+
try {
|
|
269
|
+
const apiKey = options?.apiKey;
|
|
270
|
+
if (!apiKey) {
|
|
271
|
+
throw new Error("Cursor access token is required; run /login cursor");
|
|
272
|
+
}
|
|
273
|
+
baseConversationId = options?.conversationId ?? options?.sessionId ?? randomUUID();
|
|
274
|
+
conversationId = rotationStore().getWireId(baseConversationId);
|
|
275
|
+
const blobStore = conversationBlobStores.get(conversationId) ?? new Map();
|
|
276
|
+
conversationBlobStores.set(conversationId, blobStore);
|
|
277
|
+
const cachedState = conversationStateCache.get(conversationId);
|
|
278
|
+
const { requestBytes, conversationState } = await buildGrpcRequest(model, context, options, {
|
|
279
|
+
conversationId,
|
|
280
|
+
blobStore,
|
|
281
|
+
conversationState: cachedState,
|
|
282
|
+
});
|
|
283
|
+
conversationStateCache.set(conversationId, conversationState);
|
|
284
|
+
const requestContextTools = buildMcpToolDefinitions(context.tools);
|
|
285
|
+
const baseUrl = model.baseUrl || CURSOR_API_URL;
|
|
286
|
+
const requestPath = "/agent.v1.AgentService/Run";
|
|
287
|
+
// Caller headers are additive, and are spread FIRST so the protocol
|
|
288
|
+
// framing, auth, and request id below always win.
|
|
289
|
+
const callerHeaders = sanitizeCursorCallerHeaders(providerHeadersToRecord(options?.headers));
|
|
290
|
+
const requestHeaders = {
|
|
291
|
+
...callerHeaders,
|
|
292
|
+
":method": "POST",
|
|
293
|
+
":path": requestPath,
|
|
294
|
+
"content-type": "application/connect+proto",
|
|
295
|
+
"connect-protocol-version": "1",
|
|
296
|
+
te: "trailers",
|
|
297
|
+
authorization: `Bearer ${apiKey}`,
|
|
298
|
+
"x-ghost-mode": "true",
|
|
299
|
+
"x-cursor-client-version": CURSOR_CLIENT_VERSION,
|
|
300
|
+
"x-cursor-client-type": "cli",
|
|
301
|
+
"x-request-id": randomUUID(),
|
|
302
|
+
};
|
|
303
|
+
h2Client = http2.connect(baseUrl);
|
|
304
|
+
h2Client.on("error", (error) => settleH2(mapH2TransportError(error, baseUrl)));
|
|
305
|
+
h2Request = h2Client.request(requestHeaders);
|
|
306
|
+
if (attempt === 1) {
|
|
307
|
+
stream.push({ type: "start", partial: output });
|
|
308
|
+
}
|
|
309
|
+
let pendingBuffer = Buffer.alloc(0);
|
|
310
|
+
let currentTextBlock = null;
|
|
311
|
+
let currentThinkingBlock = null;
|
|
312
|
+
let currentToolCall = null;
|
|
313
|
+
const resolvedMcpToolCallIds = new Set();
|
|
314
|
+
usageState = { sawTokenDelta: false, sawTurnEndedUsage: false };
|
|
315
|
+
const state = {
|
|
316
|
+
get currentTextBlock() {
|
|
317
|
+
return currentTextBlock;
|
|
318
|
+
},
|
|
319
|
+
get currentThinkingBlock() {
|
|
320
|
+
return currentThinkingBlock;
|
|
321
|
+
},
|
|
322
|
+
get currentToolCall() {
|
|
323
|
+
return currentToolCall;
|
|
324
|
+
},
|
|
325
|
+
openToolCalls: new Map(),
|
|
326
|
+
resolvedMcpToolCallIds,
|
|
327
|
+
setTextBlock: (b) => {
|
|
328
|
+
currentTextBlock = b;
|
|
329
|
+
},
|
|
330
|
+
setThinkingBlock: (b) => {
|
|
331
|
+
currentThinkingBlock = b;
|
|
332
|
+
},
|
|
333
|
+
setToolCall: (t) => {
|
|
334
|
+
currentToolCall = t;
|
|
335
|
+
},
|
|
336
|
+
onToolResult: options?.onToolResult ?? options?.execHandlers?.onToolResult,
|
|
337
|
+
};
|
|
338
|
+
openBlockState = state;
|
|
339
|
+
const onConversationCheckpoint = (checkpoint) => {
|
|
340
|
+
conversationStateCache.set(conversationId, checkpoint);
|
|
341
|
+
};
|
|
342
|
+
h2Request.on("data", (chunk) => {
|
|
343
|
+
// Steady state drains fully per chunk; alias the fresh h2 chunk
|
|
344
|
+
// instead of copying it through Buffer.concat.
|
|
345
|
+
pendingBuffer = pendingBuffer.length === 0 ? chunk : Buffer.concat([pendingBuffer, chunk]);
|
|
346
|
+
while (pendingBuffer.length >= 5) {
|
|
347
|
+
const flags = pendingBuffer[0];
|
|
348
|
+
const msgLen = pendingBuffer.readUInt32BE(1);
|
|
349
|
+
if (pendingBuffer.length < 5 + msgLen)
|
|
350
|
+
break;
|
|
351
|
+
const messageBytes = pendingBuffer.subarray(5, 5 + msgLen);
|
|
352
|
+
pendingBuffer = pendingBuffer.subarray(5 + msgLen);
|
|
353
|
+
if (flags & CONNECT_END_STREAM_FLAG) {
|
|
354
|
+
const endError = parseConnectEndStream(messageBytes);
|
|
355
|
+
if (endError) {
|
|
356
|
+
endStreamError = endError;
|
|
357
|
+
h2Request?.close();
|
|
358
|
+
}
|
|
359
|
+
continue;
|
|
335
360
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
361
|
+
try {
|
|
362
|
+
const serverMessage = fromBinary(AgentServerMessageSchema, messageBytes);
|
|
363
|
+
const isTurnEnded = serverMessage.message.case === "interactionUpdate" &&
|
|
364
|
+
serverMessage.message.value.message?.case === "turnEnded";
|
|
365
|
+
// Dispatch is fire-and-forget so the socket keeps draining
|
|
366
|
+
// while a handler runs, but the promise is tracked: `done`
|
|
367
|
+
// must not be pushed while an exec handler is still resolving,
|
|
368
|
+
// or the buffered tool result is delivered after the turn
|
|
369
|
+
// already finalized and the call is left unpaired.
|
|
370
|
+
const dispatch = handleServerMessage(serverMessage, output, stream, state, blobStore, h2Request, options?.execHandlers, state.onToolResult, usageState, requestContextTools, onConversationCheckpoint).catch((error) => {
|
|
371
|
+
log("error", "handleServerMessage", { error: String(error) });
|
|
372
|
+
});
|
|
373
|
+
inFlightDispatches.add(dispatch);
|
|
374
|
+
void dispatch.finally(() => inFlightDispatches.delete(dispatch));
|
|
375
|
+
// Application completion is not protocol success; wait for a
|
|
376
|
+
// clean HTTP/2 end.
|
|
377
|
+
if (isTurnEnded) {
|
|
378
|
+
sawTurnEnded = true;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch (e) {
|
|
382
|
+
log("error", "parseServerMessage", { error: String(e) });
|
|
356
383
|
}
|
|
357
384
|
}
|
|
358
|
-
|
|
359
|
-
|
|
385
|
+
});
|
|
386
|
+
const sendHeartbeat = () => {
|
|
387
|
+
if (!h2Request || h2Request.closed) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const heartbeatMessage = create(AgentClientMessageSchema, {
|
|
391
|
+
message: { case: "clientHeartbeat", value: create(ClientHeartbeatSchema, {}) },
|
|
392
|
+
});
|
|
393
|
+
const heartbeatBytes = toBinary(AgentClientMessageSchema, heartbeatMessage);
|
|
394
|
+
h2Request.write(frameConnectMessage(heartbeatBytes));
|
|
395
|
+
};
|
|
396
|
+
h2Request.on("trailers", (trailers) => {
|
|
397
|
+
const status = trailers["grpc-status"];
|
|
398
|
+
const msg = trailers["grpc-message"];
|
|
399
|
+
if (status && status !== "0" && !endStreamError) {
|
|
400
|
+
endStreamError = new Error(`gRPC error ${status}: ${decodeURIComponent(String(msg || ""))}`);
|
|
360
401
|
}
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
const sendHeartbeat = () => {
|
|
364
|
-
if (!h2Request || h2Request.closed) {
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
const heartbeatMessage = create(AgentClientMessageSchema, {
|
|
368
|
-
message: { case: "clientHeartbeat", value: create(ClientHeartbeatSchema, {}) },
|
|
369
402
|
});
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (
|
|
377
|
-
|
|
403
|
+
h2Request.on("end", () => {
|
|
404
|
+
settleH2();
|
|
405
|
+
});
|
|
406
|
+
h2Request.on("error", (error) => {
|
|
407
|
+
settleH2(mapH2TransportError(error, baseUrl));
|
|
408
|
+
});
|
|
409
|
+
if (options?.signal) {
|
|
410
|
+
options.signal.addEventListener("abort", () => {
|
|
411
|
+
h2Request?.close();
|
|
412
|
+
settleH2(new Error("Request was aborted"));
|
|
413
|
+
});
|
|
378
414
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
415
|
+
h2Request.write(frameConnectMessage(requestBytes));
|
|
416
|
+
heartbeatTimer = setInterval(sendHeartbeat, 5000);
|
|
417
|
+
await h2Completion;
|
|
418
|
+
// The transport is done, but a handler decoded from the last chunk
|
|
419
|
+
// may still be running. Pushing `done` now would let the host drain
|
|
420
|
+
// its buffered tool results before such a handler reserved its entry,
|
|
421
|
+
// leaving the call unpaired and stripped from rebuilt transcripts.
|
|
422
|
+
await drainInFlightDispatches();
|
|
423
|
+
endCurrentTextBlock(output, stream, state);
|
|
424
|
+
endCurrentThinkingBlock(output, stream, state);
|
|
425
|
+
flushOpenToolCalls(output, stream, state);
|
|
426
|
+
calculateCost(model, output.usage);
|
|
427
|
+
stream.push({
|
|
428
|
+
type: "done",
|
|
429
|
+
reason: output.stopReason,
|
|
430
|
+
message: output,
|
|
390
431
|
});
|
|
432
|
+
stream.end();
|
|
391
433
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
message:
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
434
|
+
catch (error) {
|
|
435
|
+
// Same reason as the success path: a handler still running would land
|
|
436
|
+
// its real result after the turn finalized and be discarded — even
|
|
437
|
+
// though the tool may already have run side effects. On abort the
|
|
438
|
+
// drain returns immediately.
|
|
439
|
+
await drainInFlightDispatches();
|
|
440
|
+
// A stream that dies mid-turn leaves blocks open. Closing them here
|
|
441
|
+
// settles their live cards and pairs the server-owned calls that
|
|
442
|
+
// nothing else answers — an unpaired call is stripped from every
|
|
443
|
+
// rebuilt transcript.
|
|
444
|
+
if (openBlockState) {
|
|
445
|
+
endCurrentTextBlock(output, stream, openBlockState);
|
|
446
|
+
endCurrentThinkingBlock(output, stream, openBlockState);
|
|
447
|
+
flushOpenToolCalls(output, stream, openBlockState);
|
|
448
|
+
}
|
|
449
|
+
let message = error instanceof Error ? error.message : JSON.stringify(error);
|
|
450
|
+
// A server-side per-conversation rejection surfaces as a bare
|
|
451
|
+
// resource_exhausted with zero tokens. That has two distinct causes:
|
|
452
|
+
// an oversized payload, and a genuinely poisoned conversationId.
|
|
453
|
+
// Only the second is fixed by rotating the wire id.
|
|
454
|
+
//
|
|
455
|
+
// The FIRST 0-token RE for a base conversation always surfaces without
|
|
456
|
+
// rotating, so the session layer gets first refusal: agent-session
|
|
457
|
+
// classifies a surfaced 0-token RE as overflow and compacts before
|
|
458
|
+
// retrying. Rotating here instead would swallow the error, make that
|
|
459
|
+
// compaction dead code, and burn the 3-rotation budget replaying the
|
|
460
|
+
// same oversized payload. Once that surface has happened (the flag is
|
|
461
|
+
// persisted with the wire id), compaction has had its turn and further
|
|
462
|
+
// 0-token REs rotate and retry in-call, up to the cap.
|
|
463
|
+
if (conversationId !== undefined &&
|
|
464
|
+
baseConversationId !== undefined &&
|
|
465
|
+
usageState !== undefined &&
|
|
466
|
+
isZeroTokenResourceExhausted(message, usageState.sawTokenDelta)) {
|
|
467
|
+
if (rotationStore().shouldSkip(baseConversationId)) {
|
|
468
|
+
// The base conversation burned its rotation cap; another wire id
|
|
469
|
+
// will not help, so surface the poisoned-conversation error and
|
|
470
|
+
// let the session move to a different provider.
|
|
471
|
+
message = CURSOR_CONVERSATION_POISONED_MESSAGE;
|
|
472
|
+
}
|
|
473
|
+
else if (rotationStore().shouldSurfaceBeforeRotating(baseConversationId)) {
|
|
474
|
+
// First 0-token RE for this conversation: surface it so the
|
|
475
|
+
// session layer can compact. If the payload really was oversized,
|
|
476
|
+
// the compacted retry succeeds and no rotation is ever spent.
|
|
477
|
+
rotationStore().markSurfaced(baseConversationId, conversationId);
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
const decision = rotationStore().recordZeroTokenPoison(baseConversationId, conversationId);
|
|
481
|
+
if (decision.kind === "rotated") {
|
|
482
|
+
const cached = conversationStateCache.get(conversationId);
|
|
483
|
+
if (cached)
|
|
484
|
+
conversationStateCache.set(decision.wireId, cached);
|
|
485
|
+
const blobs = conversationBlobStores.get(conversationId);
|
|
486
|
+
if (blobs)
|
|
487
|
+
conversationBlobStores.set(decision.wireId, blobs);
|
|
488
|
+
retryPoisonedConversation = true;
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
message = CURSOR_CONVERSATION_POISONED_MESSAGE;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (!retryPoisonedConversation) {
|
|
496
|
+
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
497
|
+
output.errorMessage = message;
|
|
498
|
+
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
499
|
+
stream.end();
|
|
500
|
+
}
|
|
445
501
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
502
|
+
finally {
|
|
503
|
+
if (heartbeatTimer) {
|
|
504
|
+
clearInterval(heartbeatTimer);
|
|
505
|
+
heartbeatTimer = null;
|
|
506
|
+
}
|
|
507
|
+
h2Request?.close();
|
|
508
|
+
h2Client?.close();
|
|
509
|
+
h2Request = null;
|
|
510
|
+
h2Client = null;
|
|
455
511
|
}
|
|
456
|
-
|
|
457
|
-
h2Client?.close();
|
|
458
|
-
}
|
|
512
|
+
} while (retryPoisonedConversation);
|
|
459
513
|
})();
|
|
460
514
|
return stream;
|
|
461
515
|
};
|
|
@@ -2544,12 +2598,16 @@ export function processInteractionUpdate(update, output, stream, state, usageSta
|
|
|
2544
2598
|
const toolCall = update.message.value.toolCall;
|
|
2545
2599
|
if (settled[kStreamingBlockKind] === "mcp") {
|
|
2546
2600
|
// Authoritative full parse of the accumulated argument buffer.
|
|
2601
|
+
const previousArgs = settled.arguments;
|
|
2547
2602
|
const partial = settled[kStreamingPartialJson];
|
|
2548
2603
|
if (partial !== undefined) {
|
|
2549
2604
|
settled.arguments = parseStreamingJson(partial);
|
|
2550
2605
|
}
|
|
2551
2606
|
const decodedArgs = decodeMcpArgsMap(selectMcpCall(toolCall)?.args?.args);
|
|
2552
2607
|
settled.arguments = mergeCursorMcpToolCallArgs(settled.arguments, decodedArgs);
|
|
2608
|
+
if (settled.name === "task") {
|
|
2609
|
+
settled.arguments = keepUsableCursorTaskArgs(previousArgs, settled.arguments);
|
|
2610
|
+
}
|
|
2553
2611
|
}
|
|
2554
2612
|
else if (settled[kStreamingBlockKind] === "connect-scm") {
|
|
2555
2613
|
// The authoritative outcome arrives only here. Late args are merged
|
|
@@ -2622,8 +2680,24 @@ function applyBilledTurnEndedUsage(update, output, usageState) {
|
|
|
2622
2680
|
}
|
|
2623
2681
|
usageState.sawTurnEndedUsage = true;
|
|
2624
2682
|
const usage = output.usage;
|
|
2625
|
-
|
|
2626
|
-
|
|
2683
|
+
const cacheRead = Number(cacheReadTokens ?? 0n);
|
|
2684
|
+
const cacheWrite = Number(cacheWriteTokens ?? 0n);
|
|
2685
|
+
const liveUsed = usageState.liveUsedTokens ?? 0;
|
|
2686
|
+
// Cursor sometimes reports dashboard-cumulative cache_read (millions) while
|
|
2687
|
+
// usedTokens stays at the real window (~150k). Folding that into totalTokens
|
|
2688
|
+
// forces a useless compact and then a 0-token resource_exhausted.
|
|
2689
|
+
if (liveUsed > 0 && cacheRead > liveUsed * 3) {
|
|
2690
|
+
if (outputTokens !== undefined) {
|
|
2691
|
+
usage.output = Number(outputTokens);
|
|
2692
|
+
}
|
|
2693
|
+
usage.cacheRead = 0;
|
|
2694
|
+
usage.cacheWrite = cacheWrite <= liveUsed ? cacheWrite : 0;
|
|
2695
|
+
usage.input = Math.max(0, liveUsed - usage.output - usage.cacheWrite);
|
|
2696
|
+
usage.totalTokens = liveUsed;
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
usage.cacheRead = cacheRead;
|
|
2700
|
+
usage.cacheWrite = cacheWrite;
|
|
2627
2701
|
usage.input = Math.max(0, Number(inputTokens ?? 0n) - usage.cacheRead - usage.cacheWrite);
|
|
2628
2702
|
if (outputTokens !== undefined) {
|
|
2629
2703
|
usage.output = Number(outputTokens);
|
|
@@ -2641,6 +2715,7 @@ function applyCheckpointTokenDetails(checkpoint, output, usageState) {
|
|
|
2641
2715
|
const usedTokens = checkpoint.tokenDetails?.usedTokens ?? 0;
|
|
2642
2716
|
if (usedTokens <= 0)
|
|
2643
2717
|
return;
|
|
2718
|
+
usageState.liveUsedTokens = usedTokens;
|
|
2644
2719
|
const usage = output.usage;
|
|
2645
2720
|
usage.input = Math.max(0, usedTokens - usage.output - usage.cacheRead - usage.cacheWrite);
|
|
2646
2721
|
usage.totalTokens = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|