@gajae-code/ai 0.15.4 → 0.15.6
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 +27 -1
- package/dist/types/auth-broker/client.d.ts +6 -2
- package/dist/types/auth-broker/remote-store.d.ts +14 -2
- package/dist/types/auth-broker/types.d.ts +6 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +19 -0
- package/dist/types/auth-gateway/server.d.ts +39 -5
- package/dist/types/auth-gateway/types.d.ts +16 -2
- package/dist/types/auth-storage.d.ts +116 -34
- package/dist/types/provider-models/openai-compat.d.ts +1 -0
- package/dist/types/provider-models/special.d.ts +2 -1
- package/dist/types/providers/kiro-api-key.d.ts +50 -0
- package/dist/types/providers/kiro-codewhisperer.d.ts +3 -0
- package/dist/types/providers/register-builtins.d.ts +12 -12
- package/dist/types/stream.d.ts +2 -1
- package/dist/types/types.d.ts +35 -24
- package/dist/types/utils/fallback-transport.d.ts +7 -0
- package/dist/types/utils/json-parse.d.ts +5 -3
- package/dist/types/utils/oauth/api-key-login.d.ts +4 -1
- package/dist/types/utils/oauth/api-key-validation.d.ts +12 -6
- package/dist/types/utils/oauth/commandcode.d.ts +1 -0
- package/dist/types/utils/oauth/types.d.ts +1 -1
- package/dist/types/utils/retry.d.ts +2 -0
- package/dist/types/utils/tool-call-healing.d.ts +4 -4
- package/package.json +3 -3
- package/src/auth-broker/client.ts +41 -13
- package/src/auth-broker/redact.ts +25 -1
- package/src/auth-broker/remote-store.ts +374 -115
- package/src/auth-broker/server.ts +131 -91
- package/src/auth-broker/types.ts +6 -0
- package/src/auth-broker/wire-schemas.ts +6 -0
- package/src/auth-gateway/server.ts +447 -79
- package/src/auth-gateway/types.ts +28 -2
- package/src/auth-storage.ts +742 -157
- package/src/cli.ts +1 -0
- package/src/model-thinking.ts +16 -0
- package/src/models.json +1054 -0
- package/src/models.ts +9 -1
- package/src/provider-models/descriptors.ts +3 -1
- package/src/provider-models/openai-compat.ts +41 -1
- package/src/provider-models/special.ts +15 -3
- package/src/providers/anthropic.ts +7 -1
- package/src/providers/azure-openai-responses.ts +4 -1
- package/src/providers/cursor.ts +256 -101
- package/src/providers/gitlab-duo.ts +18 -1
- package/src/providers/google-gemini-cli.ts +3 -0
- package/src/providers/google-shared.ts +3 -0
- package/src/providers/kiro-api-key.d.ts +50 -0
- package/src/providers/kiro-api-key.ts +786 -0
- package/src/providers/kiro-codewhisperer.d.ts +3 -0
- package/src/providers/kiro-codewhisperer.ts +34 -9
- package/src/providers/ollama.ts +3 -0
- package/src/providers/openai-codex-responses.ts +24 -6
- package/src/providers/openai-completions.ts +11 -1
- package/src/providers/openai-responses-shared.ts +23 -2
- package/src/providers/openai-responses.ts +10 -1
- package/src/providers/pi-native-client.ts +1 -0
- package/src/providers/pi-native-server.ts +24 -0
- package/src/providers/register-builtins.d.ts +12 -12
- package/src/providers/register-builtins.ts +16 -3
- package/src/stream.d.ts +2 -1
- package/src/stream.ts +180 -70
- package/src/types.d.ts +35 -24
- package/src/types.ts +40 -23
- package/src/utils/fallback-transport.d.ts +7 -0
- package/src/utils/fallback-transport.ts +21 -4
- package/src/utils/json-parse.d.ts +5 -3
- package/src/utils/json-parse.ts +6 -6
- package/src/utils/oauth/api-key-login.ts +13 -2
- package/src/utils/oauth/api-key-validation.ts +242 -41
- package/src/utils/oauth/commandcode.ts +17 -0
- package/src/utils/oauth/index.ts +20 -5
- package/src/utils/oauth/types.d.ts +1 -1
- package/src/utils/oauth/types.ts +1 -0
- package/src/utils/retry.d.ts +2 -0
- package/src/utils/retry.ts +15 -2
- package/src/utils/tool-call-healing.d.ts +4 -4
- package/src/utils/tool-call-healing.ts +4 -4
package/src/providers/cursor.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
import { normalizeSystemPrompts } from "../utils";
|
|
29
29
|
import { kCursorExecResolved } from "../utils/block-symbols";
|
|
30
30
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
31
|
+
import { getStreamIdleTimeoutMs } from "../utils/idle-iterator";
|
|
31
32
|
import { captureUnicodeEscapeEvidence, parseStreamingJson } from "../utils/json-parse";
|
|
32
33
|
import { connectProxiedSocket, getProxyForUrl } from "../utils/proxy";
|
|
33
34
|
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
@@ -266,6 +267,179 @@ function parseConnectEndStream(data: Uint8Array): Error | null {
|
|
|
266
267
|
}
|
|
267
268
|
}
|
|
268
269
|
|
|
270
|
+
interface CursorRequestWriter {
|
|
271
|
+
enqueue(frame: Uint8Array): void;
|
|
272
|
+
isActive(): boolean;
|
|
273
|
+
registerShellGate(close: () => void): () => void;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
class CursorRequestCoordinator implements CursorRequestWriter {
|
|
277
|
+
#state: "open" | "draining" | "failed" | "succeeded" = "open";
|
|
278
|
+
#tasks = new Set<Promise<void>>();
|
|
279
|
+
#taskChain = Promise.resolve();
|
|
280
|
+
#hasAdmittedTask = false;
|
|
281
|
+
#frames: Uint8Array[] = [];
|
|
282
|
+
#writing = false;
|
|
283
|
+
#drainWaiters: Array<() => void> = [];
|
|
284
|
+
#failure: Error | null = null;
|
|
285
|
+
#shellGates = new Set<() => void>();
|
|
286
|
+
#request: http2.ClientHttp2Stream;
|
|
287
|
+
#stopHeartbeat: () => void;
|
|
288
|
+
#onSuccess: () => void;
|
|
289
|
+
#onFailure: (error: Error) => void;
|
|
290
|
+
#drainTimer: NodeJS.Timeout | null = null;
|
|
291
|
+
#drainTimeoutMs: number | undefined;
|
|
292
|
+
|
|
293
|
+
constructor(
|
|
294
|
+
request: http2.ClientHttp2Stream,
|
|
295
|
+
stopHeartbeat: () => void,
|
|
296
|
+
onSuccess: () => void,
|
|
297
|
+
onFailure: (error: Error) => void,
|
|
298
|
+
drainTimeoutMs: number | undefined,
|
|
299
|
+
) {
|
|
300
|
+
this.#request = request;
|
|
301
|
+
this.#stopHeartbeat = stopHeartbeat;
|
|
302
|
+
this.#onSuccess = onSuccess;
|
|
303
|
+
this.#onFailure = onFailure;
|
|
304
|
+
this.#drainTimeoutMs = drainTimeoutMs;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
isActive(): boolean {
|
|
308
|
+
return this.#state === "open" || this.#state === "draining";
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
canAdmitTask(): boolean {
|
|
312
|
+
return this.#state === "open";
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
hasTurnEnded(): boolean {
|
|
316
|
+
return this.#state === "draining" || this.#state === "succeeded";
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
failureError(): Error | null {
|
|
320
|
+
return this.#failure;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
enqueue(frame: Uint8Array): void {
|
|
324
|
+
if (!this.isActive()) return;
|
|
325
|
+
this.#frames.push(Buffer.from(frame));
|
|
326
|
+
this.#writeNext();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
registerShellGate(close: () => void): () => void {
|
|
330
|
+
if (!this.isActive()) {
|
|
331
|
+
close();
|
|
332
|
+
return () => {};
|
|
333
|
+
}
|
|
334
|
+
this.#shellGates.add(close);
|
|
335
|
+
return () => this.#shellGates.delete(close);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
admit(taskFactory: () => Promise<void>): void {
|
|
339
|
+
if (!this.canAdmitTask()) return;
|
|
340
|
+
const orderedTask = this.#hasAdmittedTask
|
|
341
|
+
? this.#taskChain.then(() => {
|
|
342
|
+
if (this.#state === "failed" || this.#state === "succeeded") return;
|
|
343
|
+
return taskFactory();
|
|
344
|
+
})
|
|
345
|
+
: taskFactory();
|
|
346
|
+
this.#hasAdmittedTask = true;
|
|
347
|
+
this.#taskChain = orderedTask.then(
|
|
348
|
+
() => {},
|
|
349
|
+
error => {
|
|
350
|
+
this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
this.#tasks.add(orderedTask);
|
|
354
|
+
void orderedTask.then(
|
|
355
|
+
() => this.#tasks.delete(orderedTask),
|
|
356
|
+
() => this.#tasks.delete(orderedTask),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
turnEnded(): void {
|
|
361
|
+
if (this.#state !== "open") return;
|
|
362
|
+
this.#state = "draining";
|
|
363
|
+
this.#stopHeartbeat();
|
|
364
|
+
if (this.#drainTimeoutMs !== undefined && this.#drainTimeoutMs > 0) {
|
|
365
|
+
this.#drainTimer = setTimeout(() => {
|
|
366
|
+
this.fail(new Error(`Cursor admitted work drain timed out after ${this.#drainTimeoutMs}ms`));
|
|
367
|
+
}, this.#drainTimeoutMs);
|
|
368
|
+
}
|
|
369
|
+
void Promise.all([...this.#tasks]).then(
|
|
370
|
+
() => {
|
|
371
|
+
if (this.#state !== "draining") return;
|
|
372
|
+
this.#drain(() => {
|
|
373
|
+
if (this.#state !== "draining") return;
|
|
374
|
+
if (this.#drainTimer) {
|
|
375
|
+
clearTimeout(this.#drainTimer);
|
|
376
|
+
this.#drainTimer = null;
|
|
377
|
+
}
|
|
378
|
+
this.#state = "succeeded";
|
|
379
|
+
this.#onSuccess();
|
|
380
|
+
});
|
|
381
|
+
},
|
|
382
|
+
error => this.fail(error instanceof Error ? error : new Error(String(error))),
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
fail(error: Error): void {
|
|
387
|
+
if (this.#state === "failed" || this.#state === "succeeded") return;
|
|
388
|
+
this.#state = "failed";
|
|
389
|
+
this.#failure = error;
|
|
390
|
+
if (this.#drainTimer) {
|
|
391
|
+
clearTimeout(this.#drainTimer);
|
|
392
|
+
this.#drainTimer = null;
|
|
393
|
+
}
|
|
394
|
+
this.#stopHeartbeat();
|
|
395
|
+
for (const close of this.#shellGates) close();
|
|
396
|
+
this.#shellGates.clear();
|
|
397
|
+
this.#frames = [];
|
|
398
|
+
this.#writing = false;
|
|
399
|
+
this.#releaseDrains();
|
|
400
|
+
this.#request.close();
|
|
401
|
+
this.#onFailure(error);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
#writeNext(): void {
|
|
405
|
+
if (this.#writing || !this.isActive()) return;
|
|
406
|
+
const frame = this.#frames.shift();
|
|
407
|
+
if (!frame) {
|
|
408
|
+
this.#releaseDrains();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
this.#writing = true;
|
|
412
|
+
try {
|
|
413
|
+
this.#request.write(frame, error => {
|
|
414
|
+
this.#writing = false;
|
|
415
|
+
if (error) {
|
|
416
|
+
this.fail(error);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
this.#writeNext();
|
|
420
|
+
});
|
|
421
|
+
} catch (error) {
|
|
422
|
+
this.#writing = false;
|
|
423
|
+
this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#drain(resolve: () => void): void {
|
|
428
|
+
if (!this.#writing && this.#frames.length === 0) {
|
|
429
|
+
resolve();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
this.#drainWaiters.push(resolve);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
#releaseDrains(): void {
|
|
436
|
+
if (this.#writing || this.#frames.length > 0) return;
|
|
437
|
+
const waiters = this.#drainWaiters;
|
|
438
|
+
this.#drainWaiters = [];
|
|
439
|
+
for (const resolve of waiters) resolve();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
269
443
|
function debugBytes(bytes: Uint8Array, asHex: boolean): string {
|
|
270
444
|
if (asHex) {
|
|
271
445
|
return Buffer.from(bytes).toString("hex");
|
|
@@ -474,40 +648,9 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
474
648
|
let h2Request: http2.ClientHttp2Stream | null = null;
|
|
475
649
|
let proxiedSocket: Awaited<ReturnType<typeof connectProxiedSocket>> | null = null;
|
|
476
650
|
let heartbeatTimer: NodeJS.Timeout | null = null;
|
|
477
|
-
let h2ClientErrorHandler: ((error: Error) => void) | undefined;
|
|
478
|
-
let h2RequestErrorHandler: ((error: Error) => void) | undefined;
|
|
479
651
|
let onAbort: (() => void) | undefined;
|
|
652
|
+
let coordinator: CursorRequestCoordinator = undefined!;
|
|
480
653
|
const baseUrl = model.baseUrl || CURSOR_API_URL;
|
|
481
|
-
const h2Completion = Promise.withResolvers<void>();
|
|
482
|
-
h2Completion.promise.catch(() => {});
|
|
483
|
-
let h2Settled = false;
|
|
484
|
-
let h2Failure: unknown;
|
|
485
|
-
let sawTurnEnded = false;
|
|
486
|
-
let responseEnded = false;
|
|
487
|
-
let queueDrained = false;
|
|
488
|
-
let endStreamError: Error | null = null;
|
|
489
|
-
const settleH2 = (error?: unknown): void => {
|
|
490
|
-
if (h2Settled) return;
|
|
491
|
-
h2Settled = true;
|
|
492
|
-
if (error !== undefined) {
|
|
493
|
-
h2Failure = mapH2TransportError(error, baseUrl);
|
|
494
|
-
h2Completion.reject(h2Failure);
|
|
495
|
-
} else {
|
|
496
|
-
h2Completion.resolve();
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
const settleH2WhenReady = (): void => {
|
|
500
|
-
if (!queueDrained) return;
|
|
501
|
-
if (endStreamError) {
|
|
502
|
-
settleH2(endStreamError);
|
|
503
|
-
} else if (sawTurnEnded) {
|
|
504
|
-
// A drained turnEnded is the successful terminal condition; Cursor
|
|
505
|
-
// may leave the HTTP/2 response open after sending it.
|
|
506
|
-
settleH2();
|
|
507
|
-
} else if (responseEnded) {
|
|
508
|
-
settleH2(new Error("Cursor HTTP/2 stream ended before turnEnded"));
|
|
509
|
-
}
|
|
510
|
-
};
|
|
511
654
|
|
|
512
655
|
try {
|
|
513
656
|
const apiKey = options?.apiKey;
|
|
@@ -544,9 +687,8 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
544
687
|
} else {
|
|
545
688
|
h2Client = http2.connect(baseUrl);
|
|
546
689
|
}
|
|
547
|
-
h2ClientErrorHandler = error => settleH2(error);
|
|
548
|
-
h2Client.on("error", h2ClientErrorHandler);
|
|
549
690
|
|
|
691
|
+
options?.onStreamCreated?.();
|
|
550
692
|
h2Request = h2Client.request({
|
|
551
693
|
":method": "POST",
|
|
552
694
|
":path": "/agent.v1.AgentService/Run",
|
|
@@ -559,8 +701,31 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
559
701
|
"x-cursor-client-type": "cli",
|
|
560
702
|
"x-request-id": crypto.randomUUID(),
|
|
561
703
|
});
|
|
562
|
-
|
|
563
|
-
|
|
704
|
+
const stopHeartbeat = () => {
|
|
705
|
+
if (heartbeatTimer) {
|
|
706
|
+
clearInterval(heartbeatTimer);
|
|
707
|
+
heartbeatTimer = null;
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
let resolveH2: (() => void) | undefined;
|
|
711
|
+
let rejectH2: ((error: Error) => void) | undefined;
|
|
712
|
+
coordinator = new CursorRequestCoordinator(
|
|
713
|
+
h2Request,
|
|
714
|
+
stopHeartbeat,
|
|
715
|
+
() => {
|
|
716
|
+
const resolve = resolveH2;
|
|
717
|
+
resolveH2 = undefined;
|
|
718
|
+
resolve?.();
|
|
719
|
+
},
|
|
720
|
+
error => {
|
|
721
|
+
const reject = rejectH2;
|
|
722
|
+
rejectH2 = undefined;
|
|
723
|
+
reject?.(error);
|
|
724
|
+
},
|
|
725
|
+
options?.streamIdleTimeoutMs ?? getStreamIdleTimeoutMs(),
|
|
726
|
+
);
|
|
727
|
+
h2Client.on("error", error => coordinator.fail(error));
|
|
728
|
+
h2Request.on("error", error => coordinator.fail(error));
|
|
564
729
|
|
|
565
730
|
stream.push({ type: "start", partial: output });
|
|
566
731
|
|
|
@@ -602,38 +767,20 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
602
767
|
touchCursorConversation(conversationId);
|
|
603
768
|
};
|
|
604
769
|
|
|
605
|
-
const messageQueue = createCursorMessageQueueForTest(error => {
|
|
606
|
-
log("error", "handleServerMessage", { error: String(error) });
|
|
607
|
-
});
|
|
608
|
-
const drainMessageQueue = (): void => {
|
|
609
|
-
void messageQueue.drain().then(
|
|
610
|
-
() => {
|
|
611
|
-
queueDrained = true;
|
|
612
|
-
settleH2WhenReady();
|
|
613
|
-
},
|
|
614
|
-
error => {
|
|
615
|
-
queueDrained = true;
|
|
616
|
-
settleH2(error);
|
|
617
|
-
},
|
|
618
|
-
);
|
|
619
|
-
};
|
|
620
|
-
|
|
621
770
|
h2Request.on("trailers", trailers => {
|
|
622
771
|
const status = trailers["grpc-status"];
|
|
623
772
|
const msg = trailers["grpc-message"];
|
|
624
773
|
if (status && status !== "0") {
|
|
625
|
-
|
|
774
|
+
coordinator.fail(new Error(`gRPC error ${status}: ${decodeURIComponent(String(msg || ""))}`));
|
|
626
775
|
}
|
|
627
776
|
});
|
|
628
777
|
h2Request.on("end", () => {
|
|
629
|
-
|
|
630
|
-
|
|
778
|
+
if (!coordinator.hasTurnEnded()) {
|
|
779
|
+
coordinator.fail(new Error("Cursor stream ended before turnEnded"));
|
|
780
|
+
}
|
|
631
781
|
});
|
|
632
782
|
onAbort = () => {
|
|
633
|
-
|
|
634
|
-
h2Client?.close();
|
|
635
|
-
proxiedSocket?.destroy();
|
|
636
|
-
settleH2(new Error("Request was aborted"));
|
|
783
|
+
coordinator.fail(new Error("Request was aborted"));
|
|
637
784
|
};
|
|
638
785
|
if (options?.signal) {
|
|
639
786
|
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -652,14 +799,11 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
652
799
|
pendingBuffer = pendingBuffer.subarray(5 + msgLen);
|
|
653
800
|
|
|
654
801
|
if (flags & CONNECT_END_STREAM_FLAG) {
|
|
655
|
-
responseEnded = true;
|
|
656
802
|
const endError = parseConnectEndStream(messageBytes);
|
|
657
803
|
if (endError) {
|
|
658
|
-
|
|
659
|
-
settleH2(endError);
|
|
660
|
-
h2Request?.close();
|
|
804
|
+
coordinator.fail(endError);
|
|
661
805
|
} else {
|
|
662
|
-
|
|
806
|
+
coordinator.turnEnded();
|
|
663
807
|
}
|
|
664
808
|
continue;
|
|
665
809
|
}
|
|
@@ -671,14 +815,15 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
671
815
|
serverMessage.message.value.message?.case === "turnEnded";
|
|
672
816
|
// Serialize handlers: exec messages can be asynchronous, and resolving the
|
|
673
817
|
// request on turnEnded before prior handlers finish loses their responses.
|
|
674
|
-
|
|
818
|
+
if (!coordinator.canAdmitTask()) continue;
|
|
819
|
+
coordinator.admit(() =>
|
|
675
820
|
handleServerMessage(
|
|
676
821
|
serverMessage,
|
|
677
822
|
output,
|
|
678
823
|
stream,
|
|
679
824
|
state,
|
|
680
825
|
blobStore,
|
|
681
|
-
|
|
826
|
+
coordinator,
|
|
682
827
|
options?.execHandlers,
|
|
683
828
|
options?.onToolResult,
|
|
684
829
|
usageState,
|
|
@@ -689,8 +834,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
689
834
|
);
|
|
690
835
|
|
|
691
836
|
if (isTurnEnded) {
|
|
692
|
-
|
|
693
|
-
drainMessageQueue();
|
|
837
|
+
coordinator.turnEnded();
|
|
694
838
|
}
|
|
695
839
|
} catch (e) {
|
|
696
840
|
log("error", "parseServerMessage", { error: String(e) });
|
|
@@ -698,24 +842,30 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
698
842
|
}
|
|
699
843
|
});
|
|
700
844
|
|
|
701
|
-
|
|
702
|
-
await h2Completion.promise;
|
|
703
|
-
}
|
|
704
|
-
h2Request.write(frameConnectMessage(requestBytes));
|
|
845
|
+
coordinator.enqueue(frameConnectMessage(requestBytes));
|
|
705
846
|
|
|
706
847
|
const sendHeartbeat = () => {
|
|
707
|
-
if (!
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
848
|
+
if (!coordinator.isActive()) return;
|
|
710
849
|
const heartbeatMessage = create(AgentClientMessageSchema, {
|
|
711
850
|
message: { case: "clientHeartbeat", value: create(ClientHeartbeatSchema, {}) },
|
|
712
851
|
});
|
|
713
852
|
const heartbeatBytes = toBinary(AgentClientMessageSchema, heartbeatMessage);
|
|
714
|
-
|
|
853
|
+
coordinator.enqueue(frameConnectMessage(heartbeatBytes));
|
|
715
854
|
};
|
|
716
855
|
|
|
717
856
|
heartbeatTimer = setInterval(sendHeartbeat, 5000);
|
|
718
|
-
await
|
|
857
|
+
await new Promise<void>((resolve, reject) => {
|
|
858
|
+
resolveH2 = resolve;
|
|
859
|
+
rejectH2 = reject;
|
|
860
|
+
const initialFailure = coordinator.failureError();
|
|
861
|
+
if (initialFailure) {
|
|
862
|
+
rejectH2 = undefined;
|
|
863
|
+
reject(initialFailure);
|
|
864
|
+
} else if (coordinator.hasTurnEnded() && !coordinator.isActive()) {
|
|
865
|
+
resolveH2 = undefined;
|
|
866
|
+
resolve();
|
|
867
|
+
}
|
|
868
|
+
});
|
|
719
869
|
|
|
720
870
|
if (state.currentTextBlock) {
|
|
721
871
|
const idx = output.content.indexOf(state.currentTextBlock);
|
|
@@ -762,8 +912,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
762
912
|
} catch (error) {
|
|
763
913
|
// Keep the completion promise terminal even for synchronous setup/write
|
|
764
914
|
// failures that may not emit a separate HTTP/2 error event.
|
|
765
|
-
|
|
766
|
-
const mappedError = h2Failure ?? error;
|
|
915
|
+
const mappedError = mapH2TransportError(coordinator?.failureError() ?? error, baseUrl);
|
|
767
916
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
768
917
|
output.errorStatus = extractHttpStatusFromError(mappedError);
|
|
769
918
|
output.errorMessage = formatErrorMessageWithRetryAfter(mappedError);
|
|
@@ -779,13 +928,9 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
779
928
|
if (options?.signal && onAbort) {
|
|
780
929
|
options.signal.removeEventListener("abort", onAbort);
|
|
781
930
|
}
|
|
782
|
-
if (h2Request &&
|
|
783
|
-
h2Request.
|
|
931
|
+
if (h2Request && !h2Request.closed && !h2Request.destroyed) {
|
|
932
|
+
h2Request.end();
|
|
784
933
|
}
|
|
785
|
-
if (h2Client && h2ClientErrorHandler) {
|
|
786
|
-
h2Client.removeListener("error", h2ClientErrorHandler);
|
|
787
|
-
}
|
|
788
|
-
h2Request?.close();
|
|
789
934
|
h2Client?.close();
|
|
790
935
|
proxiedSocket?.destroy();
|
|
791
936
|
}
|
|
@@ -822,7 +967,7 @@ async function handleServerMessage(
|
|
|
822
967
|
stream: AssistantMessageEventStream,
|
|
823
968
|
state: BlockState,
|
|
824
969
|
blobStore: Map<string, Uint8Array>,
|
|
825
|
-
|
|
970
|
+
writer: CursorRequestWriter,
|
|
826
971
|
execHandlers: CursorExecHandlers | undefined,
|
|
827
972
|
onToolResult: CursorToolResultHandler | undefined,
|
|
828
973
|
usageState: UsageState,
|
|
@@ -837,11 +982,11 @@ async function handleServerMessage(
|
|
|
837
982
|
if (msgCase === "interactionUpdate") {
|
|
838
983
|
processInteractionUpdate(msg.message.value, output, stream, state, usageState);
|
|
839
984
|
} else if (msgCase === "kvServerMessage") {
|
|
840
|
-
handleKvServerMessage(msg.message.value as KvServerMessage, blobStore,
|
|
985
|
+
handleKvServerMessage(msg.message.value as KvServerMessage, blobStore, writer);
|
|
841
986
|
} else if (msgCase === "execServerMessage") {
|
|
842
987
|
await handleExecServerMessage(
|
|
843
988
|
msg.message.value as ExecServerMessage,
|
|
844
|
-
|
|
989
|
+
writer,
|
|
845
990
|
execHandlers,
|
|
846
991
|
onToolResult,
|
|
847
992
|
requestContextTools,
|
|
@@ -857,7 +1002,7 @@ async function handleServerMessage(
|
|
|
857
1002
|
function handleKvServerMessage(
|
|
858
1003
|
kvMsg: KvServerMessage,
|
|
859
1004
|
blobStore: Map<string, Uint8Array>,
|
|
860
|
-
|
|
1005
|
+
writer: CursorRequestWriter,
|
|
861
1006
|
): void {
|
|
862
1007
|
const kvCase = kvMsg.message.case;
|
|
863
1008
|
|
|
@@ -880,7 +1025,7 @@ function handleKvServerMessage(
|
|
|
880
1025
|
});
|
|
881
1026
|
|
|
882
1027
|
const responseBytes = toBinary(AgentClientMessageSchema, kvClientMessage);
|
|
883
|
-
|
|
1028
|
+
writer.enqueue(frameConnectMessage(responseBytes));
|
|
884
1029
|
|
|
885
1030
|
log("kvClient", "getBlobResult", { blobId: blobIdKey.slice(0, 40) });
|
|
886
1031
|
} else if (kvCase === "setBlobArgs") {
|
|
@@ -901,14 +1046,14 @@ function handleKvServerMessage(
|
|
|
901
1046
|
});
|
|
902
1047
|
|
|
903
1048
|
const responseBytes = toBinary(AgentClientMessageSchema, kvClientMessage);
|
|
904
|
-
|
|
1049
|
+
writer.enqueue(frameConnectMessage(responseBytes));
|
|
905
1050
|
|
|
906
1051
|
log("kvClient", "setBlobResult", { blobId: blobIdKey.slice(0, 40) });
|
|
907
1052
|
}
|
|
908
1053
|
}
|
|
909
1054
|
|
|
910
1055
|
function sendShellStreamEvent(
|
|
911
|
-
h2Request:
|
|
1056
|
+
h2Request: CursorRequestWriter,
|
|
912
1057
|
execMsg: ExecServerMessage,
|
|
913
1058
|
event: ShellStream["event"],
|
|
914
1059
|
): void {
|
|
@@ -943,7 +1088,7 @@ function sanitizeShellExecResult(execResult: ShellResult): ShellResult {
|
|
|
943
1088
|
async function handleShellStreamArgs(
|
|
944
1089
|
args: ShellArgs,
|
|
945
1090
|
execMsg: ExecServerMessage,
|
|
946
|
-
h2Request:
|
|
1091
|
+
h2Request: CursorRequestWriter,
|
|
947
1092
|
execHandlers: CursorExecHandlers | undefined,
|
|
948
1093
|
onToolResult: CursorToolResultHandler | undefined,
|
|
949
1094
|
): Promise<void> {
|
|
@@ -964,6 +1109,12 @@ async function handleShellStreamArgs(
|
|
|
964
1109
|
// Buffer for incomplete ANSI sequences across chunks
|
|
965
1110
|
let stdoutBuffer = "";
|
|
966
1111
|
let stderrBuffer = "";
|
|
1112
|
+
let callbacksOpen = true;
|
|
1113
|
+
const unregisterShellGate = h2Request.registerShellGate(() => {
|
|
1114
|
+
callbacksOpen = false;
|
|
1115
|
+
if (stdoutFlushTimer) clearTimeout(stdoutFlushTimer);
|
|
1116
|
+
if (stderrFlushTimer) clearTimeout(stderrFlushTimer);
|
|
1117
|
+
});
|
|
967
1118
|
|
|
968
1119
|
const incompleteEscapeRegex = /\x1b(|\[|\[\d*|\[\?|\[\?\d*|\]\d*;?)$/;
|
|
969
1120
|
|
|
@@ -1028,6 +1179,7 @@ async function handleShellStreamArgs(
|
|
|
1028
1179
|
|
|
1029
1180
|
const streamCallbacks: CursorShellStreamCallbacks = {
|
|
1030
1181
|
onStdout(data: string) {
|
|
1182
|
+
if (!callbacksOpen || !h2Request.isActive()) return;
|
|
1031
1183
|
stdoutBuffer += data;
|
|
1032
1184
|
if (stdoutBuffer.includes("\n") || stdoutBuffer.length > 4096) {
|
|
1033
1185
|
if (stdoutFlushTimer) {
|
|
@@ -1040,6 +1192,7 @@ async function handleShellStreamArgs(
|
|
|
1040
1192
|
}
|
|
1041
1193
|
},
|
|
1042
1194
|
onStderr(data: string) {
|
|
1195
|
+
if (!callbacksOpen || !h2Request.isActive()) return;
|
|
1043
1196
|
stderrBuffer += data;
|
|
1044
1197
|
if (stderrBuffer.includes("\n") || stderrBuffer.length > 4096) {
|
|
1045
1198
|
if (stderrFlushTimer) {
|
|
@@ -1086,12 +1239,14 @@ async function handleShellStreamArgs(
|
|
|
1086
1239
|
// Send the final structured shellResult as completion acknowledgement.
|
|
1087
1240
|
sendExecClientMessage(h2Request, execMsg, "shellResult", sanitizedExecResult);
|
|
1088
1241
|
sendExecClientStreamClose(h2Request, execMsg);
|
|
1242
|
+
callbacksOpen = false;
|
|
1243
|
+
unregisterShellGate();
|
|
1089
1244
|
|
|
1090
1245
|
log("shellStream", "done", { elapsed: Date.now() - startTs });
|
|
1091
1246
|
}
|
|
1092
1247
|
|
|
1093
1248
|
function sendShellStreamExitFromResult(
|
|
1094
|
-
h2Request:
|
|
1249
|
+
h2Request: CursorRequestWriter,
|
|
1095
1250
|
execMsg: ExecServerMessage,
|
|
1096
1251
|
execResult: ShellResult,
|
|
1097
1252
|
sendBufferedOutput: boolean,
|
|
@@ -1200,7 +1355,7 @@ function sendShellStreamExitFromResult(
|
|
|
1200
1355
|
|
|
1201
1356
|
async function handleExecServerMessage(
|
|
1202
1357
|
execMsg: ExecServerMessage,
|
|
1203
|
-
h2Request:
|
|
1358
|
+
h2Request: CursorRequestWriter,
|
|
1204
1359
|
execHandlers: CursorExecHandlers | undefined,
|
|
1205
1360
|
onToolResult: CursorToolResultHandler | undefined,
|
|
1206
1361
|
requestContextTools: McpToolDefinition[],
|
|
@@ -1619,7 +1774,7 @@ async function handleExecServerMessage(
|
|
|
1619
1774
|
}
|
|
1620
1775
|
|
|
1621
1776
|
function sendExecClientMessage<TCase extends NonNullable<ExecClientMessage["message"]["case"]>>(
|
|
1622
|
-
h2Request:
|
|
1777
|
+
h2Request: CursorRequestWriter,
|
|
1623
1778
|
execMsg: ExecServerMessage,
|
|
1624
1779
|
messageCase: TCase,
|
|
1625
1780
|
value: Extract<ExecClientMessage["message"], { case: TCase }>["value"],
|
|
@@ -1635,13 +1790,13 @@ function sendExecClientMessage<TCase extends NonNullable<ExecClientMessage["mess
|
|
|
1635
1790
|
});
|
|
1636
1791
|
|
|
1637
1792
|
const responseBytes = toBinary(AgentClientMessageSchema, clientMessage);
|
|
1638
|
-
h2Request.
|
|
1793
|
+
h2Request.enqueue(frameConnectMessage(responseBytes));
|
|
1639
1794
|
|
|
1640
1795
|
log("execClientMessage", messageCase, value);
|
|
1641
1796
|
}
|
|
1642
1797
|
|
|
1643
1798
|
function sendExecClientThrow(
|
|
1644
|
-
h2Request:
|
|
1799
|
+
h2Request: CursorRequestWriter,
|
|
1645
1800
|
execMsg: ExecServerMessage,
|
|
1646
1801
|
error: string,
|
|
1647
1802
|
errorCode: string,
|
|
@@ -1655,11 +1810,11 @@ function sendExecClientThrow(
|
|
|
1655
1810
|
const clientMessage = create(AgentClientMessageSchema, {
|
|
1656
1811
|
message: { case: "execClientControlMessage", value: controlMessage },
|
|
1657
1812
|
});
|
|
1658
|
-
h2Request.
|
|
1813
|
+
h2Request.enqueue(frameConnectMessage(toBinary(AgentClientMessageSchema, clientMessage)));
|
|
1659
1814
|
sendExecClientStreamClose(h2Request, execMsg);
|
|
1660
1815
|
}
|
|
1661
1816
|
|
|
1662
|
-
function sendExecClientStreamClose(h2Request:
|
|
1817
|
+
function sendExecClientStreamClose(h2Request: CursorRequestWriter, execMsg: ExecServerMessage): void {
|
|
1663
1818
|
const closeMessage = create(ExecClientControlMessageSchema, {
|
|
1664
1819
|
message: {
|
|
1665
1820
|
case: "streamClose",
|
|
@@ -1672,7 +1827,7 @@ function sendExecClientStreamClose(h2Request: http2.ClientHttp2Stream, execMsg:
|
|
|
1672
1827
|
message: { case: "execClientControlMessage", value: closeMessage },
|
|
1673
1828
|
});
|
|
1674
1829
|
const responseBytes = toBinary(AgentClientMessageSchema, clientMessage);
|
|
1675
|
-
h2Request.
|
|
1830
|
+
h2Request.enqueue(frameConnectMessage(responseBytes));
|
|
1676
1831
|
log("execClientControl", "streamClose", { id: execMsg.id, execId: execMsg.execId });
|
|
1677
1832
|
}
|
|
1678
1833
|
|
|
@@ -176,12 +176,14 @@ const directAccessCache = new Map<string, DirectAccessToken>();
|
|
|
176
176
|
async function getDirectAccessToken(
|
|
177
177
|
gitlabAccessToken: string,
|
|
178
178
|
fetchImpl: FetchImpl = fetch,
|
|
179
|
+
onStreamCreated?: () => void,
|
|
179
180
|
): Promise<DirectAccessToken> {
|
|
180
181
|
const cached = directAccessCache.get(gitlabAccessToken);
|
|
181
182
|
if (cached && cached.expiresAt > Date.now()) {
|
|
182
183
|
return cached;
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
onStreamCreated?.();
|
|
185
187
|
const response = await fetchImpl(`${GITLAB_COM_URL}/api/v4/ai/third_party_agents/direct_access`, {
|
|
186
188
|
method: "POST",
|
|
187
189
|
headers: {
|
|
@@ -244,7 +246,7 @@ export function streamGitLabDuo(
|
|
|
244
246
|
throw new Error(`Unsupported GitLab Duo model: ${model.id}`);
|
|
245
247
|
}
|
|
246
248
|
|
|
247
|
-
const directAccess = await getDirectAccessToken(options.apiKey, options.fetch);
|
|
249
|
+
const directAccess = await getDirectAccessToken(options.apiKey, options.fetch, options.onStreamCreated);
|
|
248
250
|
const headers = {
|
|
249
251
|
...directAccess.headers,
|
|
250
252
|
...options.headers,
|
|
@@ -276,10 +278,15 @@ export function streamGitLabDuo(
|
|
|
276
278
|
cacheRetention: options.cacheRetention,
|
|
277
279
|
headers,
|
|
278
280
|
maxRetryDelayMs: options.maxRetryDelayMs,
|
|
281
|
+
requestMaxRetries: options.requestMaxRetries,
|
|
282
|
+
streamMaxRetries: options.streamMaxRetries,
|
|
283
|
+
fallbackManaged: options.fallbackManaged,
|
|
284
|
+
disableProviderRetries: options.disableProviderRetries,
|
|
279
285
|
metadata: options.metadata,
|
|
280
286
|
sessionId: options.sessionId,
|
|
281
287
|
providerSessionState: options.providerSessionState,
|
|
282
288
|
onPayload: options.onPayload,
|
|
289
|
+
onStreamCreated: options.onStreamCreated,
|
|
283
290
|
attemptScope: options?.attemptScope,
|
|
284
291
|
onResponse: options.onResponse,
|
|
285
292
|
onSseEvent: options.onSseEvent,
|
|
@@ -314,10 +321,15 @@ export function streamGitLabDuo(
|
|
|
314
321
|
cacheRetention: options.cacheRetention,
|
|
315
322
|
headers,
|
|
316
323
|
maxRetryDelayMs: options.maxRetryDelayMs,
|
|
324
|
+
requestMaxRetries: options.requestMaxRetries,
|
|
325
|
+
streamMaxRetries: options.streamMaxRetries,
|
|
326
|
+
fallbackManaged: options.fallbackManaged,
|
|
327
|
+
disableProviderRetries: options.disableProviderRetries,
|
|
317
328
|
metadata: options.metadata,
|
|
318
329
|
sessionId: options.sessionId,
|
|
319
330
|
providerSessionState: options.providerSessionState,
|
|
320
331
|
onPayload: options.onPayload,
|
|
332
|
+
onStreamCreated: options.onStreamCreated,
|
|
321
333
|
attemptScope: options?.attemptScope,
|
|
322
334
|
onResponse: options.onResponse,
|
|
323
335
|
onSseEvent: options.onSseEvent,
|
|
@@ -347,10 +359,15 @@ export function streamGitLabDuo(
|
|
|
347
359
|
cacheRetention: options.cacheRetention,
|
|
348
360
|
headers,
|
|
349
361
|
maxRetryDelayMs: options.maxRetryDelayMs,
|
|
362
|
+
requestMaxRetries: options.requestMaxRetries,
|
|
363
|
+
streamMaxRetries: options.streamMaxRetries,
|
|
364
|
+
fallbackManaged: options.fallbackManaged,
|
|
365
|
+
disableProviderRetries: options.disableProviderRetries,
|
|
350
366
|
metadata: options.metadata,
|
|
351
367
|
sessionId: options.sessionId,
|
|
352
368
|
providerSessionState: options.providerSessionState,
|
|
353
369
|
onPayload: options.onPayload,
|
|
370
|
+
onStreamCreated: options.onStreamCreated,
|
|
354
371
|
attemptScope: options?.attemptScope,
|
|
355
372
|
onResponse: options.onResponse,
|
|
356
373
|
onSseEvent: options.onSseEvent,
|
|
@@ -386,6 +386,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
386
386
|
options?.toolChoice !== undefined &&
|
|
387
387
|
options.toolChoice !== "auto" &&
|
|
388
388
|
options.toolChoice !== "none";
|
|
389
|
+
options.onStreamCreated?.();
|
|
389
390
|
let response = await fetchWithRetry(
|
|
390
391
|
attempt => `${endpoints[Math.min(attempt, endpoints.length - 1)]}/v1internal:streamGenerateContent?alt=sse`,
|
|
391
392
|
{
|
|
@@ -404,6 +405,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
404
405
|
const error = createGeminiCliHttpError(response, errorText);
|
|
405
406
|
if (
|
|
406
407
|
!options?.fallbackManaged &&
|
|
408
|
+
!options?.disableProviderRetries &&
|
|
407
409
|
firstTokenTime === undefined &&
|
|
408
410
|
isForcedToolChoiceUnsupportedError(error, true)
|
|
409
411
|
) {
|
|
@@ -425,6 +427,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
425
427
|
};
|
|
426
428
|
requestBodyJson = JSON.stringify(requestBody);
|
|
427
429
|
rawRequestDump = { ...rawRequestDump, body: requestBody };
|
|
430
|
+
options.onStreamCreated?.();
|
|
428
431
|
response = await fetchWithRetry(
|
|
429
432
|
attempt =>
|
|
430
433
|
`${endpoints[Math.min(attempt, endpoints.length - 1)]}/v1internal:streamGenerateContent?alt=sse`,
|