@gajae-code/ai 0.14.2 → 0.15.0
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/CHANGELOG.md +26 -0
- package/dist/types/auth-storage.d.ts +2 -2
- package/dist/types/model-cache.d.ts +2 -0
- package/dist/types/provider-models/special.d.ts +3 -1
- package/dist/types/providers/anthropic.d.ts +1 -0
- package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
- package/dist/types/providers/cursor/gen/agent_pb.d.ts +3854 -107
- package/dist/types/providers/cursor-pi-args.d.ts +119 -0
- package/dist/types/providers/cursor.d.ts +8 -1
- package/dist/types/providers/openai-codex-responses.d.ts +2 -0
- package/dist/types/providers/openai-responses-shared.d.ts +1 -1
- package/dist/types/types.d.ts +41 -1
- package/dist/types/utils/block-symbols.d.ts +6 -0
- package/dist/types/utils/discovery/openai-compatible.d.ts +2 -0
- package/dist/types/utils/idle-iterator.d.ts +5 -2
- package/dist/types/utils/oauth/kimi.d.ts +3 -9
- package/dist/types/utils/oauth/openrouter.d.ts +1 -0
- package/dist/types/utils/oauth/types.d.ts +1 -1
- package/package.json +4 -4
- package/src/auth-broker/remote-store.ts +13 -2
- package/src/auth-storage.ts +10 -11
- package/src/model-cache.ts +78 -0
- package/src/model-manager.ts +194 -25
- package/src/provider-models/special.ts +67 -4
- package/src/providers/anthropic.ts +115 -40
- package/src/providers/aws-credential-config.ts +2 -3
- package/src/providers/aws-credentials.ts +2 -3
- package/src/providers/azure-openai-responses.ts +18 -2
- package/src/providers/cursor/exec-modern.ts +497 -0
- package/src/providers/cursor/gen/agent_pb.ts +4687 -181
- package/src/providers/cursor/proto/agent.proto +1007 -0
- package/src/providers/cursor-pi-args.ts +187 -0
- package/src/providers/cursor.ts +382 -47
- package/src/providers/google-auth.ts +2 -3
- package/src/providers/openai-codex-responses.ts +358 -73
- package/src/providers/openai-completions.ts +2 -2
- package/src/providers/openai-responses-shared.ts +55 -6
- package/src/providers/openai-responses.ts +27 -4
- package/src/stream.ts +8 -3
- package/src/types.ts +55 -0
- package/src/utils/block-symbols.ts +11 -0
- package/src/utils/discovery/openai-compatible.ts +21 -6
- package/src/utils/idle-iterator.ts +22 -4
- package/src/utils/oauth/index.ts +6 -0
- package/src/utils/oauth/kimi.ts +14 -8
- package/src/utils/oauth/kiro.ts +2 -2
- package/src/utils/oauth/openrouter.ts +16 -0
- package/src/utils/oauth/types.ts +1 -0
package/src/providers/cursor.ts
CHANGED
|
@@ -26,12 +26,39 @@ import type {
|
|
|
26
26
|
ToolResultMessage,
|
|
27
27
|
} from "../types";
|
|
28
28
|
import { normalizeSystemPrompts } from "../utils";
|
|
29
|
+
import { kCursorExecResolved } from "../utils/block-symbols";
|
|
29
30
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
30
31
|
import { findUnnecessaryUnicodeEscape, parseStreamingJson } from "../utils/json-parse";
|
|
31
32
|
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
32
33
|
import { flattenToolRootCombinators, toolWireSchema } from "../utils/schema";
|
|
33
34
|
import { CURSOR_COMPOSER_EDIT_DISCIPLINE_PROMPT, isComposerHarnessModel } from "./composer-discipline";
|
|
34
35
|
import { CURSOR_CLIENT_VERSION } from "./cursor/client-version";
|
|
36
|
+
import {
|
|
37
|
+
buildMcpStateResult,
|
|
38
|
+
buildNeutralHookResult,
|
|
39
|
+
buildPiBashError,
|
|
40
|
+
buildPiBashResult,
|
|
41
|
+
buildPiEditError,
|
|
42
|
+
buildPiEditRejected,
|
|
43
|
+
buildPiEditResult,
|
|
44
|
+
buildPiFindError,
|
|
45
|
+
buildPiFindResult,
|
|
46
|
+
buildPiGrepError,
|
|
47
|
+
buildPiGrepResult,
|
|
48
|
+
buildPiLsError,
|
|
49
|
+
buildPiLsResult,
|
|
50
|
+
buildPiReadError,
|
|
51
|
+
buildPiReadResult,
|
|
52
|
+
buildPiWriteError,
|
|
53
|
+
buildPiWriteRejected,
|
|
54
|
+
buildPiWriteResult,
|
|
55
|
+
piEscapeRegexLiteral,
|
|
56
|
+
piJoinPath,
|
|
57
|
+
piLimit,
|
|
58
|
+
piLsPath,
|
|
59
|
+
piReadDisplayPath,
|
|
60
|
+
piTimeout,
|
|
61
|
+
} from "./cursor/exec-modern";
|
|
35
62
|
import type { McpToolDefinition } from "./cursor/gen/agent_pb";
|
|
36
63
|
import {
|
|
37
64
|
AgentClientMessageSchema,
|
|
@@ -60,6 +87,7 @@ import {
|
|
|
60
87
|
type ExecClientMessage,
|
|
61
88
|
ExecClientMessageSchema,
|
|
62
89
|
ExecClientStreamCloseSchema,
|
|
90
|
+
ExecClientThrowSchema,
|
|
63
91
|
type ExecServerMessage,
|
|
64
92
|
FetchErrorSchema,
|
|
65
93
|
FetchResultSchema,
|
|
@@ -87,6 +115,7 @@ import {
|
|
|
87
115
|
LsRejectedSchema,
|
|
88
116
|
LsResultSchema,
|
|
89
117
|
LsSuccessSchema,
|
|
118
|
+
McpAllowlistPrecheckResultSchema,
|
|
90
119
|
McpErrorSchema,
|
|
91
120
|
McpImageContentSchema,
|
|
92
121
|
McpResultSchema,
|
|
@@ -109,6 +138,7 @@ import {
|
|
|
109
138
|
SelectedContextSchema,
|
|
110
139
|
SelectedImageSchema,
|
|
111
140
|
SetBlobResultSchema,
|
|
141
|
+
ShellAllowlistPrecheckResultSchema,
|
|
112
142
|
type ShellArgs,
|
|
113
143
|
ShellFailureSchema,
|
|
114
144
|
ShellRejectedSchema,
|
|
@@ -123,6 +153,7 @@ import {
|
|
|
123
153
|
ShellSuccessSchema,
|
|
124
154
|
UserMessageActionSchema,
|
|
125
155
|
UserMessageSchema,
|
|
156
|
+
WebFetchAllowlistPrecheckResultSchema,
|
|
126
157
|
WriteErrorSchema,
|
|
127
158
|
WriteRejectedSchema,
|
|
128
159
|
WriteResultSchema,
|
|
@@ -443,6 +474,10 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
443
474
|
};
|
|
444
475
|
|
|
445
476
|
let resolveH2: (() => void) | undefined;
|
|
477
|
+
let rejectH2: ((error: unknown) => void) | undefined;
|
|
478
|
+
const messageQueue = createCursorMessageQueueForTest(error => {
|
|
479
|
+
log("error", "handleServerMessage", { error: String(error) });
|
|
480
|
+
});
|
|
446
481
|
|
|
447
482
|
h2Request.on("data", (chunk: Buffer) => {
|
|
448
483
|
pendingBuffer = Buffer.concat([pendingBuffer, chunk]);
|
|
@@ -469,28 +504,35 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
469
504
|
const isTurnEnded =
|
|
470
505
|
serverMessage.message.case === "interactionUpdate" &&
|
|
471
506
|
serverMessage.message.value.message?.case === "turnEnded";
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
507
|
+
// Serialize handlers: exec messages can be asynchronous, and resolving the
|
|
508
|
+
// request on turnEnded before prior handlers finish loses their responses.
|
|
509
|
+
messageQueue.enqueue(() =>
|
|
510
|
+
handleServerMessage(
|
|
511
|
+
serverMessage,
|
|
512
|
+
output,
|
|
513
|
+
stream,
|
|
514
|
+
state,
|
|
515
|
+
blobStore,
|
|
516
|
+
h2Request!,
|
|
517
|
+
options?.execHandlers,
|
|
518
|
+
options?.onToolResult,
|
|
519
|
+
usageState,
|
|
520
|
+
requestContextTools,
|
|
521
|
+
onConversationCheckpoint,
|
|
522
|
+
),
|
|
523
|
+
);
|
|
487
524
|
|
|
488
525
|
// Resolve only on explicit turnEnded. stopReason defaults to "stop"
|
|
489
526
|
// and is not a reliable signal for stream completion.
|
|
490
|
-
if (isTurnEnded
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
527
|
+
if (isTurnEnded) {
|
|
528
|
+
messageQueue.drain().then(() => {
|
|
529
|
+
if (resolveH2) {
|
|
530
|
+
const r = resolveH2;
|
|
531
|
+
resolveH2 = undefined;
|
|
532
|
+
rejectH2 = undefined;
|
|
533
|
+
r();
|
|
534
|
+
}
|
|
535
|
+
});
|
|
494
536
|
}
|
|
495
537
|
} catch (e) {
|
|
496
538
|
log("error", "parseServerMessage", { error: String(e) });
|
|
@@ -515,6 +557,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
515
557
|
|
|
516
558
|
await new Promise<void>((resolve, reject) => {
|
|
517
559
|
resolveH2 = resolve;
|
|
560
|
+
rejectH2 = reject;
|
|
518
561
|
|
|
519
562
|
h2Request!.on("trailers", trailers => {
|
|
520
563
|
const status = trailers["grpc-status"];
|
|
@@ -525,15 +568,17 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
525
568
|
});
|
|
526
569
|
|
|
527
570
|
h2Request!.on("end", () => {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
571
|
+
messageQueue.drain().then(() => {
|
|
572
|
+
resolveH2 = undefined;
|
|
573
|
+
rejectH2 = undefined;
|
|
574
|
+
if (endStreamError) reject(endStreamError);
|
|
575
|
+
else resolve();
|
|
576
|
+
});
|
|
534
577
|
});
|
|
535
578
|
|
|
536
|
-
h2Request!.on("error",
|
|
579
|
+
h2Request!.on("error", error => {
|
|
580
|
+
if (rejectH2) rejectH2(error);
|
|
581
|
+
});
|
|
537
582
|
|
|
538
583
|
if (options?.signal) {
|
|
539
584
|
options.signal.addEventListener("abort", () => {
|
|
@@ -608,7 +653,12 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
608
653
|
return stream;
|
|
609
654
|
};
|
|
610
655
|
|
|
611
|
-
type ToolCallState = ToolCall & {
|
|
656
|
+
type ToolCallState = ToolCall & {
|
|
657
|
+
index: number;
|
|
658
|
+
partialJson?: string;
|
|
659
|
+
kind: "mcp" | "todo_write" | "native" | "cursor-exec";
|
|
660
|
+
[kCursorExecResolved]?: true;
|
|
661
|
+
};
|
|
612
662
|
|
|
613
663
|
interface BlockState {
|
|
614
664
|
currentTextBlock: (TextContent & { index: number }) | null;
|
|
@@ -653,6 +703,8 @@ async function handleServerMessage(
|
|
|
653
703
|
execHandlers,
|
|
654
704
|
onToolResult,
|
|
655
705
|
requestContextTools,
|
|
706
|
+
output,
|
|
707
|
+
stream,
|
|
656
708
|
);
|
|
657
709
|
} else if (msgCase === "conversationCheckpointUpdate") {
|
|
658
710
|
handleConversationCheckpointUpdate(msg.message.value, output, usageState, onConversationCheckpoint);
|
|
@@ -1009,6 +1061,8 @@ async function handleExecServerMessage(
|
|
|
1009
1061
|
execHandlers: CursorExecHandlers | undefined,
|
|
1010
1062
|
onToolResult: CursorToolResultHandler | undefined,
|
|
1011
1063
|
requestContextTools: McpToolDefinition[],
|
|
1064
|
+
output: AssistantMessage,
|
|
1065
|
+
stream: AssistantMessageEventStream,
|
|
1012
1066
|
): Promise<void> {
|
|
1013
1067
|
const execCase = execMsg.message.case;
|
|
1014
1068
|
log("exec", "dispatch", { execCase, execId: execMsg.execId, hasHandlers: !!execHandlers });
|
|
@@ -1224,35 +1278,212 @@ async function handleExecServerMessage(
|
|
|
1224
1278
|
sendExecClientMessage(h2Request, execMsg, "computerUseResult", execResult);
|
|
1225
1279
|
return;
|
|
1226
1280
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1281
|
+
case "piReadArgs": {
|
|
1282
|
+
const args = execMsg.message.value;
|
|
1283
|
+
const toolCallId = crypto.randomUUID();
|
|
1284
|
+
synthesizeCursorExecToolCall(output, stream, toolCallId, "read", {
|
|
1285
|
+
path: piReadDisplayPath(args.path, args.offset, args.limit),
|
|
1286
|
+
});
|
|
1287
|
+
const call = { args, toolCallId };
|
|
1288
|
+
const { execResult } = await resolveExecHandler(
|
|
1289
|
+
call,
|
|
1290
|
+
execHandlers?.piRead?.bind(execHandlers),
|
|
1291
|
+
onToolResult,
|
|
1292
|
+
buildPiReadResult,
|
|
1293
|
+
buildPiReadError,
|
|
1294
|
+
buildPiReadError,
|
|
1295
|
+
);
|
|
1296
|
+
sendExecClientMessage(h2Request, execMsg, "piReadResult", execResult);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
case "piBashArgs": {
|
|
1300
|
+
const args = execMsg.message.value;
|
|
1301
|
+
const toolCallId = crypto.randomUUID();
|
|
1302
|
+
synthesizeCursorExecToolCall(output, stream, toolCallId, "bash", {
|
|
1303
|
+
command: args.command,
|
|
1304
|
+
timeout: piTimeout(args.timeout),
|
|
1234
1305
|
});
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1306
|
+
const call = { args, toolCallId };
|
|
1307
|
+
const { execResult } = await resolveExecHandler(
|
|
1308
|
+
call,
|
|
1309
|
+
execHandlers?.piBash?.bind(execHandlers),
|
|
1310
|
+
onToolResult,
|
|
1311
|
+
buildPiBashResult,
|
|
1312
|
+
buildPiBashError,
|
|
1313
|
+
buildPiBashError,
|
|
1314
|
+
);
|
|
1315
|
+
sendExecClientMessage(h2Request, execMsg, "piBashResult", execResult);
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
case "piEditArgs": {
|
|
1319
|
+
const args = execMsg.message.value;
|
|
1320
|
+
const toolCallId = crypto.randomUUID();
|
|
1321
|
+
synthesizeCursorExecToolCall(output, stream, toolCallId, "edit", {
|
|
1322
|
+
path: args.path,
|
|
1323
|
+
edits: args.edits.map(edit => ({ old_text: edit.oldText, new_text: edit.newText })),
|
|
1237
1324
|
});
|
|
1238
|
-
|
|
1325
|
+
const call = { args, toolCallId };
|
|
1326
|
+
const { execResult } = await resolveExecHandler(
|
|
1327
|
+
call,
|
|
1328
|
+
execHandlers?.piEdit?.bind(execHandlers),
|
|
1329
|
+
onToolResult,
|
|
1330
|
+
buildPiEditResult,
|
|
1331
|
+
buildPiEditRejected,
|
|
1332
|
+
buildPiEditError,
|
|
1333
|
+
);
|
|
1334
|
+
sendExecClientMessage(h2Request, execMsg, "piEditResult", execResult);
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
case "piWriteArgs": {
|
|
1338
|
+
const args = execMsg.message.value;
|
|
1339
|
+
const toolCallId = crypto.randomUUID();
|
|
1340
|
+
synthesizeCursorExecToolCall(output, stream, toolCallId, "write", {
|
|
1341
|
+
path: args.path,
|
|
1342
|
+
content: args.content,
|
|
1343
|
+
});
|
|
1344
|
+
const call = { args, toolCallId };
|
|
1345
|
+
const { execResult } = await resolveExecHandler(
|
|
1346
|
+
call,
|
|
1347
|
+
execHandlers?.piWrite?.bind(execHandlers),
|
|
1348
|
+
onToolResult,
|
|
1349
|
+
buildPiWriteResult,
|
|
1350
|
+
buildPiWriteRejected,
|
|
1351
|
+
buildPiWriteError,
|
|
1352
|
+
);
|
|
1353
|
+
sendExecClientMessage(h2Request, execMsg, "piWriteResult", execResult);
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
case "piGrepArgs": {
|
|
1357
|
+
const args = execMsg.message.value;
|
|
1358
|
+
const toolCallId = crypto.randomUUID();
|
|
1359
|
+
synthesizeCursorExecToolCall(output, stream, toolCallId, "search", {
|
|
1360
|
+
pattern: args.literal === true ? piEscapeRegexLiteral(args.pattern) : args.pattern,
|
|
1361
|
+
paths: [args.glob ? piJoinPath(args.path, args.glob) : args.path || "."],
|
|
1362
|
+
// The model-facing search schema uses `i: true` for
|
|
1363
|
+
// case-insensitive matching. Keep the field absent otherwise.
|
|
1364
|
+
...(args.ignoreCase === true ? { i: true } : {}),
|
|
1365
|
+
context: args.context,
|
|
1366
|
+
limit: piLimit(args.limit),
|
|
1367
|
+
});
|
|
1368
|
+
const call = { args, toolCallId };
|
|
1369
|
+
const { execResult } = await resolveExecHandler(
|
|
1370
|
+
call,
|
|
1371
|
+
execHandlers?.piGrep?.bind(execHandlers),
|
|
1372
|
+
onToolResult,
|
|
1373
|
+
buildPiGrepResult,
|
|
1374
|
+
buildPiGrepError,
|
|
1375
|
+
buildPiGrepError,
|
|
1376
|
+
);
|
|
1377
|
+
sendExecClientMessage(h2Request, execMsg, "piGrepResult", execResult);
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
case "piFindArgs": {
|
|
1381
|
+
const args = execMsg.message.value;
|
|
1382
|
+
const toolCallId = crypto.randomUUID();
|
|
1383
|
+
synthesizeCursorExecToolCall(output, stream, toolCallId, "find", {
|
|
1384
|
+
paths: [piJoinPath(args.path, args.pattern)],
|
|
1385
|
+
limit: piLimit(args.limit),
|
|
1386
|
+
});
|
|
1387
|
+
const call = { args, toolCallId };
|
|
1388
|
+
const { execResult } = await resolveExecHandler(
|
|
1389
|
+
call,
|
|
1390
|
+
execHandlers?.piFind?.bind(execHandlers),
|
|
1391
|
+
onToolResult,
|
|
1392
|
+
buildPiFindResult,
|
|
1393
|
+
buildPiFindError,
|
|
1394
|
+
buildPiFindError,
|
|
1395
|
+
);
|
|
1396
|
+
sendExecClientMessage(h2Request, execMsg, "piFindResult", execResult);
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
case "piLsArgs": {
|
|
1400
|
+
const args = execMsg.message.value;
|
|
1401
|
+
const toolCallId = crypto.randomUUID();
|
|
1402
|
+
synthesizeCursorExecToolCall(output, stream, toolCallId, "read", { path: piLsPath(args.path) });
|
|
1403
|
+
const call = { args, toolCallId };
|
|
1404
|
+
const { execResult } = await resolveExecHandler(
|
|
1405
|
+
call,
|
|
1406
|
+
execHandlers?.piLs?.bind(execHandlers),
|
|
1407
|
+
onToolResult,
|
|
1408
|
+
buildPiLsResult,
|
|
1409
|
+
buildPiLsError,
|
|
1410
|
+
buildPiLsError,
|
|
1411
|
+
);
|
|
1412
|
+
sendExecClientMessage(h2Request, execMsg, "piLsResult", execResult);
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
case "mcpStateExecArgs": {
|
|
1416
|
+
sendExecClientMessage(
|
|
1417
|
+
h2Request,
|
|
1418
|
+
execMsg,
|
|
1419
|
+
"mcpStateExecResult",
|
|
1420
|
+
buildMcpStateResult(requestContextTools, execMsg.message.value.serverIdentifiers),
|
|
1421
|
+
);
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
case "executeHookArgs": {
|
|
1425
|
+
const execResult = buildNeutralHookResult(execMsg.message.value.request);
|
|
1426
|
+
if (!execResult) {
|
|
1427
|
+
sendExecClientThrow(
|
|
1428
|
+
h2Request,
|
|
1429
|
+
execMsg,
|
|
1430
|
+
`Unsupported hook request: ${execMsg.message.value.request?.request.case ?? "unset"}`,
|
|
1431
|
+
"unknown_hook_request",
|
|
1432
|
+
);
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
sendExecClientMessage(h2Request, execMsg, "executeHookResult", execResult);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
case "shellAllowlistPrecheckArgs": {
|
|
1439
|
+
sendExecClientMessage(
|
|
1440
|
+
h2Request,
|
|
1441
|
+
execMsg,
|
|
1442
|
+
"shellAllowlistPrecheckResult",
|
|
1443
|
+
create(ShellAllowlistPrecheckResultSchema, { allowlisted: false }),
|
|
1444
|
+
);
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
case "mcpAllowlistPrecheckArgs": {
|
|
1448
|
+
sendExecClientMessage(
|
|
1449
|
+
h2Request,
|
|
1450
|
+
execMsg,
|
|
1451
|
+
"mcpAllowlistPrecheckResult",
|
|
1452
|
+
create(McpAllowlistPrecheckResultSchema, { allowlisted: false }),
|
|
1453
|
+
);
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
case "webFetchAllowlistPrecheckArgs": {
|
|
1457
|
+
sendExecClientMessage(
|
|
1458
|
+
h2Request,
|
|
1459
|
+
execMsg,
|
|
1460
|
+
"webFetchAllowlistPrecheckResult",
|
|
1461
|
+
create(WebFetchAllowlistPrecheckResultSchema, { allowlisted: false }),
|
|
1462
|
+
);
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
default: {
|
|
1466
|
+
log("warn", "unhandledExecMessage", { execCase });
|
|
1467
|
+
sendExecClientThrow(
|
|
1468
|
+
h2Request,
|
|
1469
|
+
execMsg,
|
|
1470
|
+
`No handler for exec message of type ${execCase}`,
|
|
1471
|
+
"exec_variant_unsupported",
|
|
1472
|
+
);
|
|
1239
1473
|
}
|
|
1240
1474
|
}
|
|
1241
1475
|
}
|
|
1242
1476
|
|
|
1243
|
-
function sendExecClientMessage<
|
|
1477
|
+
function sendExecClientMessage<TCase extends NonNullable<ExecClientMessage["message"]["case"]>>(
|
|
1244
1478
|
h2Request: http2.ClientHttp2Stream,
|
|
1245
1479
|
execMsg: ExecServerMessage,
|
|
1246
|
-
messageCase:
|
|
1247
|
-
value:
|
|
1480
|
+
messageCase: TCase,
|
|
1481
|
+
value: Extract<ExecClientMessage["message"], { case: TCase }>["value"],
|
|
1248
1482
|
): void {
|
|
1249
1483
|
const execClientMessage = create(ExecClientMessageSchema, {
|
|
1250
1484
|
id: execMsg.id,
|
|
1251
1485
|
execId: execMsg.execId,
|
|
1252
|
-
message: {
|
|
1253
|
-
case: messageCase,
|
|
1254
|
-
value: value as any,
|
|
1255
|
-
},
|
|
1486
|
+
message: { case: messageCase, value } as ExecClientMessage["message"],
|
|
1256
1487
|
});
|
|
1257
1488
|
|
|
1258
1489
|
const clientMessage = create(AgentClientMessageSchema, {
|
|
@@ -1265,6 +1496,25 @@ function sendExecClientMessage<T>(
|
|
|
1265
1496
|
log("execClientMessage", messageCase, value);
|
|
1266
1497
|
}
|
|
1267
1498
|
|
|
1499
|
+
function sendExecClientThrow(
|
|
1500
|
+
h2Request: http2.ClientHttp2Stream,
|
|
1501
|
+
execMsg: ExecServerMessage,
|
|
1502
|
+
error: string,
|
|
1503
|
+
errorCode: string,
|
|
1504
|
+
): void {
|
|
1505
|
+
const controlMessage = create(ExecClientControlMessageSchema, {
|
|
1506
|
+
message: {
|
|
1507
|
+
case: "throw",
|
|
1508
|
+
value: create(ExecClientThrowSchema, { id: execMsg.id, error, errorCode }),
|
|
1509
|
+
},
|
|
1510
|
+
});
|
|
1511
|
+
const clientMessage = create(AgentClientMessageSchema, {
|
|
1512
|
+
message: { case: "execClientControlMessage", value: controlMessage },
|
|
1513
|
+
});
|
|
1514
|
+
h2Request.write(frameConnectMessage(toBinary(AgentClientMessageSchema, clientMessage)));
|
|
1515
|
+
sendExecClientStreamClose(h2Request, execMsg);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1268
1518
|
function sendExecClientStreamClose(h2Request: http2.ClientHttp2Stream, execMsg: ExecServerMessage): void {
|
|
1269
1519
|
const closeMessage = create(ExecClientControlMessageSchema, {
|
|
1270
1520
|
message: {
|
|
@@ -1313,6 +1563,26 @@ export async function resolveExecHandler<TArgs, TResult>(
|
|
|
1313
1563
|
}
|
|
1314
1564
|
}
|
|
1315
1565
|
|
|
1566
|
+
/** Exported for deterministic coverage of ordered server-message handling. */
|
|
1567
|
+
export function createCursorMessageQueueForTest(onError?: (error: unknown) => void): {
|
|
1568
|
+
enqueue(handler: () => void | Promise<void>): Promise<void>;
|
|
1569
|
+
drain(): Promise<void>;
|
|
1570
|
+
} {
|
|
1571
|
+
let chain = Promise.resolve();
|
|
1572
|
+
return {
|
|
1573
|
+
enqueue(handler) {
|
|
1574
|
+
const result = chain.then(handler);
|
|
1575
|
+
chain = result.catch(error => {
|
|
1576
|
+
onError?.(error);
|
|
1577
|
+
});
|
|
1578
|
+
return result;
|
|
1579
|
+
},
|
|
1580
|
+
drain() {
|
|
1581
|
+
return chain;
|
|
1582
|
+
},
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1316
1586
|
function splitExecHandlerResult<TResult>(result: CursorExecHandlerResult<TResult>): {
|
|
1317
1587
|
execResult?: TResult;
|
|
1318
1588
|
toolResult?: ToolResultMessage;
|
|
@@ -1866,12 +2136,15 @@ interface CursorTodoItem {
|
|
|
1866
2136
|
|
|
1867
2137
|
interface CursorUpdateTodosToolCall {
|
|
1868
2138
|
updateTodosToolCall?: { args?: { todos?: CursorTodoItem[] } };
|
|
2139
|
+
tool?: { case?: string; value?: { args?: { todos?: CursorTodoItem[] } } };
|
|
1869
2140
|
}
|
|
1870
2141
|
|
|
1871
2142
|
function buildTodoWriteArgs(toolCall: CursorUpdateTodosToolCall): {
|
|
1872
2143
|
todos: Array<{ id?: string; content: string; activeForm: string; status: "pending" | "in_progress" | "completed" }>;
|
|
1873
2144
|
} | null {
|
|
1874
|
-
const
|
|
2145
|
+
const updateCall =
|
|
2146
|
+
toolCall.tool?.case === "updateTodosToolCall" ? toolCall.tool.value : toolCall.updateTodosToolCall;
|
|
2147
|
+
const todos = updateCall?.args?.todos;
|
|
1875
2148
|
if (!todos) return null;
|
|
1876
2149
|
return {
|
|
1877
2150
|
todos: todos.map(todo => ({
|
|
@@ -1984,11 +2257,45 @@ export function cursorJsonSafeValueForTest(value: unknown): unknown {
|
|
|
1984
2257
|
return cursorJsonSafeValue(value);
|
|
1985
2258
|
}
|
|
1986
2259
|
|
|
2260
|
+
function selectMcpToolCall(toolCall: any): any {
|
|
2261
|
+
return toolCall?.tool?.case === "mcpToolCall" ? toolCall.tool.value : toolCall?.mcpToolCall;
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
const CURSOR_EXEC_OWNED_TOOL_CASES = new Set([
|
|
2265
|
+
"piReadToolCall",
|
|
2266
|
+
"piBashToolCall",
|
|
2267
|
+
"piEditToolCall",
|
|
2268
|
+
"piWriteToolCall",
|
|
2269
|
+
"piGrepToolCall",
|
|
2270
|
+
"piFindToolCall",
|
|
2271
|
+
"piLsToolCall",
|
|
2272
|
+
]);
|
|
2273
|
+
|
|
2274
|
+
function isExecOwnedToolCall(toolCall: any): boolean {
|
|
2275
|
+
return CURSOR_EXEC_OWNED_TOOL_CASES.has(toolCall?.tool?.case);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
1987
2278
|
export function buildNativeToolCallBlock(
|
|
1988
2279
|
toolCall: Record<string, unknown>,
|
|
1989
2280
|
callId: string,
|
|
1990
2281
|
index: number,
|
|
1991
2282
|
): ToolCallState | null {
|
|
2283
|
+
const oneof = toolCall.tool as { case?: string; value?: unknown } | undefined;
|
|
2284
|
+
if (oneof?.case && oneof.value && typeof oneof.value === "object") {
|
|
2285
|
+
const args = (oneof.value as { args?: unknown }).args;
|
|
2286
|
+
const convertedArgs = cursorJsonSafeValue(args ?? oneof.value);
|
|
2287
|
+
return {
|
|
2288
|
+
type: "toolCall",
|
|
2289
|
+
id: callId,
|
|
2290
|
+
name: cursorNativeToolName(oneof.case),
|
|
2291
|
+
arguments:
|
|
2292
|
+
convertedArgs && typeof convertedArgs === "object" && !Array.isArray(convertedArgs)
|
|
2293
|
+
? (convertedArgs as Record<string, unknown>)
|
|
2294
|
+
: { raw: convertedArgs },
|
|
2295
|
+
index,
|
|
2296
|
+
kind: "native",
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
1992
2299
|
for (const [key, payload] of Object.entries(toolCall)) {
|
|
1993
2300
|
if (!/ToolCall$/.test(key) || !payload || typeof payload !== "object") continue;
|
|
1994
2301
|
if (key === "mcpToolCall" || key === "updateTodosToolCall") continue;
|
|
@@ -2067,6 +2374,30 @@ function buildMcpErrorResult(error: string) {
|
|
|
2067
2374
|
});
|
|
2068
2375
|
}
|
|
2069
2376
|
|
|
2377
|
+
function synthesizeCursorExecToolCall(
|
|
2378
|
+
output: AssistantMessage,
|
|
2379
|
+
stream: AssistantMessageEventStream,
|
|
2380
|
+
toolCallId: string,
|
|
2381
|
+
name: string,
|
|
2382
|
+
args: Record<string, unknown>,
|
|
2383
|
+
): void {
|
|
2384
|
+
const block: ToolCallState = {
|
|
2385
|
+
type: "toolCall",
|
|
2386
|
+
id: toolCallId,
|
|
2387
|
+
name,
|
|
2388
|
+
arguments: cursorJsonSafeValue(args) as Record<string, unknown>,
|
|
2389
|
+
index: output.content.length,
|
|
2390
|
+
kind: "cursor-exec",
|
|
2391
|
+
[kCursorExecResolved]: true,
|
|
2392
|
+
};
|
|
2393
|
+
output.content.push(block);
|
|
2394
|
+
const contentIndex = output.content.length - 1;
|
|
2395
|
+
stream.push({ type: "toolcall_start", contentIndex, partial: output });
|
|
2396
|
+
delete (block as Partial<ToolCallState>).index;
|
|
2397
|
+
delete (block as Partial<ToolCallState>).kind;
|
|
2398
|
+
stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial: output });
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2070
2401
|
function processInteractionUpdate(
|
|
2071
2402
|
update: any,
|
|
2072
2403
|
output: AssistantMessage,
|
|
@@ -2122,10 +2453,14 @@ function processInteractionUpdate(
|
|
|
2122
2453
|
});
|
|
2123
2454
|
state.setThinkingBlock(null);
|
|
2124
2455
|
}
|
|
2456
|
+
} else if (updateCase === "toolCallStarted" && isExecOwnedToolCall(update.message.value.toolCall)) {
|
|
2457
|
+
// Pi stream call IDs and exec IDs are distinct namespaces; without a shared
|
|
2458
|
+
// correlation field, suppress the streamed variant and synthesize from exec.
|
|
2459
|
+
log("exec", "streamedToolCallOwnedByExec", { case: update.message.value.toolCall?.tool?.case });
|
|
2125
2460
|
} else if (updateCase === "toolCallStarted") {
|
|
2126
2461
|
const toolCall = update.message.value.toolCall;
|
|
2127
2462
|
if (toolCall) {
|
|
2128
|
-
const mcpCall = toolCall
|
|
2463
|
+
const mcpCall = selectMcpToolCall(toolCall);
|
|
2129
2464
|
if (mcpCall) {
|
|
2130
2465
|
const args = mcpCall.args || {};
|
|
2131
2466
|
const block: ToolCallState = {
|
|
@@ -2186,7 +2521,7 @@ function processInteractionUpdate(
|
|
|
2186
2521
|
if (state.currentToolCall) {
|
|
2187
2522
|
const toolCall = update.message.value.toolCall;
|
|
2188
2523
|
if (state.currentToolCall.kind === "mcp") {
|
|
2189
|
-
const decodedArgs = decodeMcpArgsMap(toolCall?.
|
|
2524
|
+
const decodedArgs = decodeMcpArgsMap(selectMcpToolCall(toolCall)?.args?.args);
|
|
2190
2525
|
if (decodedArgs) {
|
|
2191
2526
|
state.currentToolCall.arguments = decodedArgs;
|
|
2192
2527
|
}
|
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { Buffer } from "node:buffer";
|
|
16
|
-
import * as os from "node:os";
|
|
17
16
|
import * as path from "node:path";
|
|
18
|
-
import { $credentialEnv, $envpos, isEnoent, logger } from "@gajae-code/utils";
|
|
17
|
+
import { $credentialEnv, $envpos, getTrustedHomeDir, isEnoent, logger } from "@gajae-code/utils";
|
|
19
18
|
import type { FetchImpl } from "../types";
|
|
20
19
|
|
|
21
20
|
const OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
@@ -58,7 +57,7 @@ function getRefreshSkewMs(): number {
|
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
function userAdcPath(): string {
|
|
61
|
-
return path.join(
|
|
60
|
+
return path.join(getTrustedHomeDir(), ".config", "gcloud", "application_default_credentials.json");
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
async function readJsonFile<T>(filePath: string): Promise<T | undefined> {
|