@code-yeongyu/senpi-ai 2026.8.18 → 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/README.md +5 -3
- package/dist/api/anthropic-messages.d.ts +7 -1
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +21 -4
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/azure-openai-responses.d.ts +2 -0
- package/dist/api/azure-openai-responses.d.ts.map +1 -1
- package/dist/api/azure-openai-responses.js +7 -1
- package/dist/api/azure-openai-responses.js.map +1 -1
- package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/dist/api/bedrock-converse-stream.js +39 -2
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/cursor-agent/gen/agent_pb.d.ts +22 -1
- package/dist/api/cursor-agent/gen/agent_pb.d.ts.map +1 -1
- package/dist/api/cursor-agent/gen/agent_pb.js +1 -1
- package/dist/api/cursor-agent/gen/agent_pb.js.map +1 -1
- package/dist/api/cursor-agent/reasoning-params.d.ts +20 -0
- package/dist/api/cursor-agent/reasoning-params.d.ts.map +1 -0
- package/dist/api/cursor-agent/reasoning-params.js +26 -0
- package/dist/api/cursor-agent/reasoning-params.js.map +1 -0
- package/dist/api/cursor-agent/types.d.ts +7 -1
- package/dist/api/cursor-agent/types.d.ts.map +1 -1
- package/dist/api/cursor-agent/types.js.map +1 -1
- package/dist/api/cursor-agent.d.ts +7 -3
- package/dist/api/cursor-agent.d.ts.map +1 -1
- package/dist/api/cursor-agent.js +363 -218
- 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/api/google-generative-ai.d.ts +2 -2
- package/dist/api/google-generative-ai.d.ts.map +1 -1
- package/dist/api/google-generative-ai.js +14 -11
- package/dist/api/google-generative-ai.js.map +1 -1
- package/dist/api/google-shared.d.ts +5 -2
- package/dist/api/google-shared.d.ts.map +1 -1
- package/dist/api/google-shared.js +16 -0
- package/dist/api/google-shared.js.map +1 -1
- package/dist/api/google-vertex.d.ts +2 -2
- package/dist/api/google-vertex.d.ts.map +1 -1
- package/dist/api/google-vertex.js +13 -10
- package/dist/api/google-vertex.js.map +1 -1
- package/dist/api/mistral-conversations.d.ts.map +1 -1
- package/dist/api/mistral-conversations.js +4 -1
- package/dist/api/mistral-conversations.js.map +1 -1
- package/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/dist/api/openai-codex-responses.js +4 -1
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.d.ts +1 -1
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +45 -29
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +8 -1
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/pi-messages.js +1 -1
- package/dist/api/pi-messages.js.map +1 -1
- package/dist/api/simple-options.d.ts +4 -0
- package/dist/api/simple-options.d.ts.map +1 -1
- package/dist/api/simple-options.js +18 -9
- package/dist/api/simple-options.js.map +1 -1
- package/dist/auth/oauth/device-code.d.ts +1 -0
- package/dist/auth/oauth/device-code.d.ts.map +1 -1
- package/dist/auth/oauth/device-code.js +1 -1
- package/dist/auth/oauth/device-code.js.map +1 -1
- package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
- package/dist/auth/oauth/github-copilot.js +22 -9
- package/dist/auth/oauth/github-copilot.js.map +1 -1
- package/dist/cursor/catalog-grouping.d.ts +25 -0
- package/dist/cursor/catalog-grouping.d.ts.map +1 -0
- package/dist/cursor/catalog-grouping.js +134 -0
- package/dist/cursor/catalog-grouping.js.map +1 -0
- package/dist/cursor/cursor-variant-aliases.json +1893 -0
- package/dist/cursor/model-capabilities.d.ts +47 -0
- package/dist/cursor/model-capabilities.d.ts.map +1 -0
- package/dist/cursor/model-capabilities.js +208 -0
- package/dist/cursor/model-capabilities.js.map +1 -0
- package/dist/cursor/selection-descriptor.d.ts +21 -0
- package/dist/cursor/selection-descriptor.d.ts.map +1 -0
- package/dist/cursor/selection-descriptor.js +99 -0
- package/dist/cursor/selection-descriptor.js.map +1 -0
- package/dist/cursor/store-migration.d.ts +8 -0
- package/dist/cursor/store-migration.d.ts.map +1 -0
- package/dist/cursor/store-migration.js +76 -0
- package/dist/cursor/store-migration.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +13 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js.map +1 -1
- package/dist/models.d.ts +6 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +9 -1
- package/dist/models.js.map +1 -1
- package/dist/providers/cursor.d.ts.map +1 -1
- package/dist/providers/cursor.js +40 -11
- package/dist/providers/cursor.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/anthropic.json +1 -1
- package/dist/providers/data/baseten.json +1 -1
- package/dist/providers/data/github-copilot.json +1 -1
- package/dist/providers/data/huggingface.json +1 -1
- package/dist/providers/data/openai-codex.json +1 -1
- package/dist/providers/data/openai.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/qwen-token-plan-individual.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/providers/data/xai.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
- package/dist/providers/data/xiaomi.json +1 -1
- package/dist/providers/data/zai-coding-cn.json +1 -1
- package/dist/providers/data/zai.json +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.d.ts.map +1 -1
- package/dist/providers/xai.js +1 -5
- package/dist/providers/xai.js.map +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/tool-call-middleware/protocols/anthropic-xml/tool-resolver.d.ts.map +1 -1
- package/dist/tool-call-middleware/protocols/anthropic-xml/tool-resolver.js +21 -1
- package/dist/tool-call-middleware/protocols/anthropic-xml/tool-resolver.js.map +1 -1
- package/dist/tool-call-middleware/recovery-message-snapshot.d.ts.map +1 -1
- package/dist/tool-call-middleware/recovery-message-snapshot.js +2 -1
- package/dist/tool-call-middleware/recovery-message-snapshot.js.map +1 -1
- package/dist/types.d.ts +45 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.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 +48 -0
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/pi-user-agent.d.ts +2 -0
- package/dist/utils/pi-user-agent.d.ts.map +1 -1
- package/dist/utils/pi-user-agent.js +7 -0
- package/dist/utils/pi-user-agent.js.map +1 -1
- package/dist/utils/prompt-cache-ttl.d.ts +2 -1
- package/dist/utils/prompt-cache-ttl.d.ts.map +1 -1
- package/dist/utils/prompt-cache-ttl.js +2 -0
- package/dist/utils/prompt-cache-ttl.js.map +1 -1
- package/package.json +2 -3
package/dist/api/cursor-agent.js
CHANGED
|
@@ -28,8 +28,11 @@ import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
|
28
28
|
import { deterministicUuid } from "./cursor-agent/deterministic-id.js";
|
|
29
29
|
import { armExecHeartbeat } from "./cursor-agent/exec-lifecycle.js";
|
|
30
30
|
import { buildMcpStateResult, buildNeutralHookResult, buildPiBashError, buildPiBashResult, buildPiEditError, buildPiEditRejected, buildPiEditResult, buildPiFindError, buildPiFindResult, buildPiGrepError, buildPiGrepResult, buildPiLsError, buildPiLsResult, buildPiReadError, buildPiReadResult, buildPiWriteError, buildPiWriteRejected, buildPiWriteResult, } from "./cursor-agent/exec-modern.js";
|
|
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,
|
|
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
|
+
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";
|
|
33
36
|
export const CURSOR_API_URL = "https://api2.cursor.sh";
|
|
34
37
|
export const CURSOR_CLIENT_VERSION = "cli-2026.07.23-e383d2b";
|
|
35
38
|
const EXEC_HEARTBEAT_INTERVAL_MS = 3000;
|
|
@@ -90,7 +93,6 @@ export function sanitizeCursorCallerHeaders(headers) {
|
|
|
90
93
|
const NOT_IMPLEMENTED_SUFFIX = "not implemented by this client";
|
|
91
94
|
const NOT_IMPLEMENTED = "Not implemented by this client";
|
|
92
95
|
/** Bare gRPC `resource_exhausted` end-streams (also inside a Connect error message). */
|
|
93
|
-
const RESOURCE_EXHAUSTED_PATTERN = /resource.?exhausted/i;
|
|
94
96
|
const conversationStateCache = new Map();
|
|
95
97
|
const conversationBlobStores = new Map();
|
|
96
98
|
/**
|
|
@@ -100,7 +102,18 @@ const conversationBlobStores = new Map();
|
|
|
100
102
|
* and the cached state migrates, so the retry loop's next attempt starts a
|
|
101
103
|
* fresh conversation. Keyed by the base id so a failed rotation never repeats.
|
|
102
104
|
*/
|
|
103
|
-
|
|
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
|
+
}
|
|
104
117
|
const CONNECT_END_STREAM_FLAG = 0b00000010;
|
|
105
118
|
function log(type, subtype, data) {
|
|
106
119
|
if (!process.env.DEBUG_CURSOR)
|
|
@@ -216,10 +229,6 @@ export const stream = (model, context, options) => {
|
|
|
216
229
|
let openBlockState;
|
|
217
230
|
let resolveH2 = () => { };
|
|
218
231
|
let rejectH2 = () => { };
|
|
219
|
-
const h2Completion = new Promise((resolve, reject) => {
|
|
220
|
-
resolveH2 = resolve;
|
|
221
|
-
rejectH2 = reject;
|
|
222
|
-
});
|
|
223
232
|
const settleH2 = (error) => {
|
|
224
233
|
if (h2Settled)
|
|
225
234
|
return;
|
|
@@ -243,225 +252,271 @@ export const stream = (model, context, options) => {
|
|
|
243
252
|
let baseConversationId;
|
|
244
253
|
let conversationId;
|
|
245
254
|
let usageState;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
blobStore,
|
|
259
|
-
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;
|
|
260
267
|
});
|
|
261
|
-
|
|
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
|
-
|
|
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;
|
|
334
360
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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) });
|
|
355
383
|
}
|
|
356
384
|
}
|
|
357
|
-
|
|
358
|
-
|
|
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 || ""))}`);
|
|
359
401
|
}
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
const sendHeartbeat = () => {
|
|
363
|
-
if (!h2Request || h2Request.closed) {
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
const heartbeatMessage = create(AgentClientMessageSchema, {
|
|
367
|
-
message: { case: "clientHeartbeat", value: create(ClientHeartbeatSchema, {}) },
|
|
368
402
|
});
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (
|
|
376
|
-
|
|
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
|
+
});
|
|
377
414
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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,
|
|
389
431
|
});
|
|
432
|
+
stream.end();
|
|
390
433
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
message:
|
|
407
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
444
501
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
502
|
+
finally {
|
|
503
|
+
if (heartbeatTimer) {
|
|
504
|
+
clearInterval(heartbeatTimer);
|
|
505
|
+
heartbeatTimer = null;
|
|
506
|
+
}
|
|
507
|
+
h2Request?.close();
|
|
508
|
+
h2Client?.close();
|
|
509
|
+
h2Request = null;
|
|
510
|
+
h2Client = null;
|
|
454
511
|
}
|
|
455
|
-
|
|
456
|
-
h2Client?.close();
|
|
457
|
-
}
|
|
512
|
+
} while (retryPoisonedConversation);
|
|
458
513
|
})();
|
|
459
514
|
return stream;
|
|
460
515
|
};
|
|
461
516
|
/**
|
|
462
|
-
* `streamSimple` for Cursor:
|
|
463
|
-
*
|
|
464
|
-
*
|
|
517
|
+
* `streamSimple` for Cursor: an explicit thinking selection (`options.thinkingSelection`)
|
|
518
|
+
* is rendered into `RequestedModel.parameters`; reasoning output itself streams back as
|
|
519
|
+
* `ThinkingContent` regardless of the selection.
|
|
465
520
|
*/
|
|
466
521
|
export const streamSimple = (model, context, options) => {
|
|
467
522
|
return stream(model, context, options);
|
|
@@ -487,6 +542,7 @@ export async function handleServerMessage(msg, output, stream, state, blobStore,
|
|
|
487
542
|
await stream.trackLocalWork(handleExecServerMessage(msg.message.value, h2Request, execHandlers, onToolResult, requestContextTools, output, stream, state));
|
|
488
543
|
}
|
|
489
544
|
else if (msgCase === "conversationCheckpointUpdate") {
|
|
545
|
+
applyCheckpointTokenDetails(msg.message.value, output, usageState);
|
|
490
546
|
onConversationCheckpoint?.(msg.message.value);
|
|
491
547
|
}
|
|
492
548
|
}
|
|
@@ -2542,12 +2598,16 @@ export function processInteractionUpdate(update, output, stream, state, usageSta
|
|
|
2542
2598
|
const toolCall = update.message.value.toolCall;
|
|
2543
2599
|
if (settled[kStreamingBlockKind] === "mcp") {
|
|
2544
2600
|
// Authoritative full parse of the accumulated argument buffer.
|
|
2601
|
+
const previousArgs = settled.arguments;
|
|
2545
2602
|
const partial = settled[kStreamingPartialJson];
|
|
2546
2603
|
if (partial !== undefined) {
|
|
2547
2604
|
settled.arguments = parseStreamingJson(partial);
|
|
2548
2605
|
}
|
|
2549
2606
|
const decodedArgs = decodeMcpArgsMap(selectMcpCall(toolCall)?.args?.args);
|
|
2550
2607
|
settled.arguments = mergeCursorMcpToolCallArgs(settled.arguments, decodedArgs);
|
|
2608
|
+
if (settled.name === "task") {
|
|
2609
|
+
settled.arguments = keepUsableCursorTaskArgs(previousArgs, settled.arguments);
|
|
2610
|
+
}
|
|
2551
2611
|
}
|
|
2552
2612
|
else if (settled[kStreamingBlockKind] === "connect-scm") {
|
|
2553
2613
|
// The authoritative outcome arrives only here. Late args are merged
|
|
@@ -2587,14 +2647,79 @@ export function processInteractionUpdate(update, output, stream, state, usageSta
|
|
|
2587
2647
|
}
|
|
2588
2648
|
else if (updateCase === "turnEnded") {
|
|
2589
2649
|
output.stopReason = "stop";
|
|
2650
|
+
applyBilledTurnEndedUsage(update.message.value, output, usageState);
|
|
2590
2651
|
}
|
|
2591
2652
|
else if (updateCase === "tokenDelta") {
|
|
2592
2653
|
const tokenDelta = update.message.value;
|
|
2593
2654
|
usageState.sawTokenDelta = true;
|
|
2594
2655
|
output.usage.output += tokenDelta.tokens || 0;
|
|
2595
|
-
output.usage.totalTokens =
|
|
2656
|
+
output.usage.totalTokens =
|
|
2657
|
+
output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
2596
2658
|
}
|
|
2597
2659
|
}
|
|
2660
|
+
/**
|
|
2661
|
+
* Cursor's production schema (cursor-agent 2026.08.11) carries the billed
|
|
2662
|
+
* token split on `turnEnded`: 1 input, 2 output, 3 cache read, 4 cache write,
|
|
2663
|
+
* 5 reasoning (optional int64). Live probes against api2.cursor.sh show
|
|
2664
|
+
* input_tokens is cache-INCLUSIVE (turn 1: input 21357 ≈ cacheWrite 21354;
|
|
2665
|
+
* turn 2: input 17989 ≈ cacheRead 17575 + cacheWrite 411), so the uncached
|
|
2666
|
+
* remainder is backed out for senpi's exclusive `usage.input`. The billed
|
|
2667
|
+
* split is authoritative for context accounting; the tokenDelta-accumulated
|
|
2668
|
+
* output is kept only when the server omits the billed output field.
|
|
2669
|
+
* Reasoning tokens are deliberately not folded into output: no other field of
|
|
2670
|
+
* `Usage` represents them and double counting against the billed output must
|
|
2671
|
+
* be avoided.
|
|
2672
|
+
*/
|
|
2673
|
+
function applyBilledTurnEndedUsage(update, output, usageState) {
|
|
2674
|
+
const { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens } = update;
|
|
2675
|
+
if (inputTokens === undefined &&
|
|
2676
|
+
outputTokens === undefined &&
|
|
2677
|
+
cacheReadTokens === undefined &&
|
|
2678
|
+
cacheWriteTokens === undefined) {
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
usageState.sawTurnEndedUsage = true;
|
|
2682
|
+
const usage = output.usage;
|
|
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;
|
|
2701
|
+
usage.input = Math.max(0, Number(inputTokens ?? 0n) - usage.cacheRead - usage.cacheWrite);
|
|
2702
|
+
if (outputTokens !== undefined) {
|
|
2703
|
+
usage.output = Number(outputTokens);
|
|
2704
|
+
}
|
|
2705
|
+
usage.totalTokens = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
2706
|
+
}
|
|
2707
|
+
/**
|
|
2708
|
+
* A checkpoint's `tokenDetails.usedTokens` is the server's live conversation
|
|
2709
|
+
* size, sent mid-turn. It feeds context accounting while the turn streams,
|
|
2710
|
+
* but never overrides the billed turnEnded split once that arrived.
|
|
2711
|
+
*/
|
|
2712
|
+
function applyCheckpointTokenDetails(checkpoint, output, usageState) {
|
|
2713
|
+
if (usageState.sawTurnEndedUsage)
|
|
2714
|
+
return;
|
|
2715
|
+
const usedTokens = checkpoint.tokenDetails?.usedTokens ?? 0;
|
|
2716
|
+
if (usedTokens <= 0)
|
|
2717
|
+
return;
|
|
2718
|
+
usageState.liveUsedTokens = usedTokens;
|
|
2719
|
+
const usage = output.usage;
|
|
2720
|
+
usage.input = Math.max(0, usedTokens - usage.output - usage.cacheRead - usage.cacheWrite);
|
|
2721
|
+
usage.totalTokens = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
2722
|
+
}
|
|
2598
2723
|
function createBlobId(data) {
|
|
2599
2724
|
return new Uint8Array(createHash("sha256").update(data).digest());
|
|
2600
2725
|
}
|
|
@@ -2625,6 +2750,29 @@ function toolParametersToJsonSchema(tool) {
|
|
|
2625
2750
|
return { type: "object", properties: {}, required: [] };
|
|
2626
2751
|
}
|
|
2627
2752
|
}
|
|
2753
|
+
/**
|
|
2754
|
+
* JSON-Schema composition keywords Cursor's gateway cannot carry: an
|
|
2755
|
+
* advertised tool whose inputSchema contains `oneOf`, `anyOf`, or `allOf` is
|
|
2756
|
+
* rejected upstream with a wrapped provider 400 for the WHOLE request
|
|
2757
|
+
* (zero tokens, `resource_exhausted` end-stream). MCP tools imported from
|
|
2758
|
+
* external servers routinely ship such schemas (e.g. ast-grep's `scan`).
|
|
2759
|
+
* `not` is tolerated upstream and kept. Returns a new structure; the input is
|
|
2760
|
+
* never mutated.
|
|
2761
|
+
*/
|
|
2762
|
+
const CURSOR_UNSUPPORTED_SCHEMA_KEYS = new Set(["oneOf", "anyOf", "allOf"]);
|
|
2763
|
+
export function sanitizeCursorToolSchema(schema) {
|
|
2764
|
+
if (Array.isArray(schema))
|
|
2765
|
+
return schema.map(sanitizeCursorToolSchema);
|
|
2766
|
+
if (schema === null || typeof schema !== "object")
|
|
2767
|
+
return schema;
|
|
2768
|
+
const sanitized = {};
|
|
2769
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
2770
|
+
if (CURSOR_UNSUPPORTED_SCHEMA_KEYS.has(key))
|
|
2771
|
+
continue;
|
|
2772
|
+
sanitized[key] = sanitizeCursorToolSchema(value);
|
|
2773
|
+
}
|
|
2774
|
+
return sanitized;
|
|
2775
|
+
}
|
|
2628
2776
|
export function buildMcpToolDefinitions(tools) {
|
|
2629
2777
|
if (!tools || tools.length === 0) {
|
|
2630
2778
|
return [];
|
|
@@ -2634,7 +2782,7 @@ export function buildMcpToolDefinitions(tools) {
|
|
|
2634
2782
|
return [];
|
|
2635
2783
|
}
|
|
2636
2784
|
return advertisedTools.map((tool) => {
|
|
2637
|
-
const jsonSchema = toolParametersToJsonSchema(tool);
|
|
2785
|
+
const jsonSchema = sanitizeCursorToolSchema(toolParametersToJsonSchema(tool));
|
|
2638
2786
|
const schemaValue = jsonSchema && typeof jsonSchema === "object"
|
|
2639
2787
|
? jsonSchema
|
|
2640
2788
|
: { type: "object", properties: {}, required: [] };
|
|
@@ -3096,7 +3244,8 @@ async function buildGrpcRequest(model, context, options, state) {
|
|
|
3096
3244
|
rootPromptMessagesJson,
|
|
3097
3245
|
turns,
|
|
3098
3246
|
});
|
|
3099
|
-
const
|
|
3247
|
+
const requestedModel = buildRequestedModel(model, options?.thinkingSelection);
|
|
3248
|
+
const wireModelId = requestedModel.modelId;
|
|
3100
3249
|
const cursorMaxMode = model.compat?.cursorMaxMode === true;
|
|
3101
3250
|
const modelDetails = create(ModelDetailsSchema, {
|
|
3102
3251
|
modelId: wireModelId,
|
|
@@ -3104,10 +3253,6 @@ async function buildGrpcRequest(model, context, options, state) {
|
|
|
3104
3253
|
displayName: model.name,
|
|
3105
3254
|
...(cursorMaxMode ? { maxMode: true } : undefined),
|
|
3106
3255
|
});
|
|
3107
|
-
const requestedModel = create(RequestedModelSchema, {
|
|
3108
|
-
modelId: wireModelId,
|
|
3109
|
-
maxMode: cursorMaxMode,
|
|
3110
|
-
});
|
|
3111
3256
|
const runRequest = create(AgentRunRequestSchema, {
|
|
3112
3257
|
conversationState,
|
|
3113
3258
|
action,
|