@gajae-code/ai 0.15.5 → 0.16.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/types/adapter-internals/aws-region.d.ts +7 -0
  3. package/dist/types/auth-broker/client.d.ts +6 -2
  4. package/dist/types/auth-broker/remote-store.d.ts +14 -2
  5. package/dist/types/auth-broker/types.d.ts +6 -0
  6. package/dist/types/auth-broker/wire-schemas.d.ts +19 -0
  7. package/dist/types/auth-gateway/server.d.ts +39 -5
  8. package/dist/types/auth-gateway/types.d.ts +16 -2
  9. package/dist/types/auth-storage.d.ts +92 -24
  10. package/dist/types/core.d.ts +1 -0
  11. package/dist/types/index.d.ts +1 -0
  12. package/dist/types/provider-models/openai-compat.d.ts +1 -0
  13. package/dist/types/providers/anthropic.d.ts +1 -1
  14. package/dist/types/providers/google-gemini-headers.d.ts +1 -1
  15. package/dist/types/providers/openai-codex-responses.d.ts +6 -0
  16. package/dist/types/providers/register-builtins.d.ts +12 -12
  17. package/dist/types/stream.d.ts +2 -1
  18. package/dist/types/types.d.ts +22 -2
  19. package/dist/types/utils/oauth/api-key-login.d.ts +4 -1
  20. package/dist/types/utils/oauth/api-key-validation.d.ts +12 -6
  21. package/dist/types/utils/oauth/commandcode.d.ts +1 -0
  22. package/dist/types/utils/oauth/types.d.ts +1 -1
  23. package/dist/types/utils/retry.d.ts +2 -0
  24. package/dist/types/utils/schema/normalize.d.ts +0 -5
  25. package/dist/types/utils/sqlite-errors.d.ts +4 -0
  26. package/package.json +3 -3
  27. package/src/adapter-internals/aws-region.d.ts +7 -0
  28. package/src/adapter-internals/aws-region.ts +14 -0
  29. package/src/auth-broker/client.ts +41 -13
  30. package/src/auth-broker/redact.ts +25 -1
  31. package/src/auth-broker/remote-store.ts +374 -115
  32. package/src/auth-broker/server.ts +131 -91
  33. package/src/auth-broker/types.ts +6 -0
  34. package/src/auth-broker/wire-schemas.ts +6 -0
  35. package/src/auth-gateway/server.ts +447 -79
  36. package/src/auth-gateway/types.ts +28 -2
  37. package/src/auth-storage.ts +672 -168
  38. package/src/cli.ts +1 -0
  39. package/src/core.ts +1 -0
  40. package/src/index.ts +1 -0
  41. package/src/model-thinking.ts +8 -0
  42. package/src/models.json +1224 -3
  43. package/src/provider-models/descriptors.ts +3 -1
  44. package/src/provider-models/openai-compat.ts +102 -1
  45. package/src/providers/amazon-bedrock.ts +5 -1
  46. package/src/providers/anthropic.d.ts +1 -1
  47. package/src/providers/anthropic.ts +8 -2
  48. package/src/providers/aws-credentials.ts +6 -0
  49. package/src/providers/azure-openai-responses.ts +4 -1
  50. package/src/providers/cursor.ts +256 -101
  51. package/src/providers/gitlab-duo.ts +18 -1
  52. package/src/providers/google-gemini-cli.ts +3 -0
  53. package/src/providers/google-gemini-headers.d.ts +1 -1
  54. package/src/providers/google-gemini-headers.ts +1 -1
  55. package/src/providers/google-shared.ts +3 -0
  56. package/src/providers/kiro-api-key.ts +33 -8
  57. package/src/providers/kiro-codewhisperer.ts +28 -9
  58. package/src/providers/ollama.ts +3 -0
  59. package/src/providers/openai-codex-responses.d.ts +6 -0
  60. package/src/providers/openai-codex-responses.ts +37 -8
  61. package/src/providers/openai-completions.ts +11 -1
  62. package/src/providers/openai-responses.ts +10 -1
  63. package/src/providers/pi-native-client.ts +25 -1
  64. package/src/providers/pi-native-server.ts +24 -0
  65. package/src/providers/register-builtins.d.ts +12 -12
  66. package/src/providers/register-builtins.ts +16 -3
  67. package/src/stream.d.ts +2 -1
  68. package/src/stream.ts +175 -67
  69. package/src/types.d.ts +22 -2
  70. package/src/types.ts +27 -1
  71. package/src/utils/oauth/api-key-login.ts +13 -2
  72. package/src/utils/oauth/api-key-validation.ts +242 -41
  73. package/src/utils/oauth/commandcode.ts +17 -0
  74. package/src/utils/oauth/index.ts +20 -5
  75. package/src/utils/oauth/kiro.ts +91 -22
  76. package/src/utils/oauth/types.d.ts +1 -1
  77. package/src/utils/oauth/types.ts +1 -0
  78. package/src/utils/retry.d.ts +2 -0
  79. package/src/utils/retry.ts +15 -2
  80. package/src/utils/schema/dereference.ts +169 -49
  81. package/src/utils/schema/draft.ts +46 -23
  82. package/src/utils/schema/normalize.d.ts +0 -5
  83. package/src/utils/schema/normalize.ts +396 -119
  84. package/src/utils/schema/types.ts +3 -1
  85. package/src/utils/schema/zod-decontaminate.ts +83 -29
  86. package/src/utils/sqlite-errors.d.ts +4 -0
  87. package/src/utils/sqlite-errors.ts +13 -0
  88. package/src/utils/tool-choice-capability.ts +2 -3
@@ -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
- h2RequestErrorHandler = error => settleH2(error);
563
- h2Request.on("error", h2RequestErrorHandler);
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
- settleH2(new Error(`gRPC error ${status}: ${decodeURIComponent(String(msg || ""))}`));
774
+ coordinator.fail(new Error(`gRPC error ${status}: ${decodeURIComponent(String(msg || ""))}`));
626
775
  }
627
776
  });
628
777
  h2Request.on("end", () => {
629
- responseEnded = true;
630
- drainMessageQueue();
778
+ if (!coordinator.hasTurnEnded()) {
779
+ coordinator.fail(new Error("Cursor stream ended before turnEnded"));
780
+ }
631
781
  });
632
782
  onAbort = () => {
633
- h2Request?.close();
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
- endStreamError = endError;
659
- settleH2(endError);
660
- h2Request?.close();
804
+ coordinator.fail(endError);
661
805
  } else {
662
- drainMessageQueue();
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
- messageQueue.enqueue(() =>
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
- h2Request!,
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
- sawTurnEnded = true;
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
- if (h2Settled) {
702
- await h2Completion.promise;
703
- }
704
- h2Request.write(frameConnectMessage(requestBytes));
845
+ coordinator.enqueue(frameConnectMessage(requestBytes));
705
846
 
706
847
  const sendHeartbeat = () => {
707
- if (!h2Request || h2Request.closed) {
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
- h2Request.write(frameConnectMessage(heartbeatBytes));
853
+ coordinator.enqueue(frameConnectMessage(heartbeatBytes));
715
854
  };
716
855
 
717
856
  heartbeatTimer = setInterval(sendHeartbeat, 5000);
718
- await h2Completion.promise;
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
- if (!h2Settled) settleH2(error);
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 && h2RequestErrorHandler) {
783
- h2Request.removeListener("error", h2RequestErrorHandler);
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
- h2Request: http2.ClientHttp2Stream,
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, h2Request);
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
- h2Request,
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
- h2Request: http2.ClientHttp2Stream,
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
- h2Request.write(frameConnectMessage(responseBytes));
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
- h2Request.write(frameConnectMessage(responseBytes));
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: http2.ClientHttp2Stream,
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: http2.ClientHttp2Stream,
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: http2.ClientHttp2Stream,
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: http2.ClientHttp2Stream,
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: http2.ClientHttp2Stream,
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.write(frameConnectMessage(responseBytes));
1793
+ h2Request.enqueue(frameConnectMessage(responseBytes));
1639
1794
 
1640
1795
  log("execClientMessage", messageCase, value);
1641
1796
  }
1642
1797
 
1643
1798
  function sendExecClientThrow(
1644
- h2Request: http2.ClientHttp2Stream,
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.write(frameConnectMessage(toBinary(AgentClientMessageSchema, clientMessage)));
1813
+ h2Request.enqueue(frameConnectMessage(toBinary(AgentClientMessageSchema, clientMessage)));
1659
1814
  sendExecClientStreamClose(h2Request, execMsg);
1660
1815
  }
1661
1816
 
1662
- function sendExecClientStreamClose(h2Request: http2.ClientHttp2Stream, execMsg: ExecServerMessage): void {
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.write(frameConnectMessage(responseBytes));
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`,
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export declare const GEMINI_CLI_VERSION_ENV = "GJC_AI_GEMINI_CLI_VERSION";
7
7
  export declare const LEGACY_GEMINI_CLI_VERSION_ENV = "PI_AI_GEMINI_CLI_VERSION";
8
- export declare const DEFAULT_GEMINI_CLI_VERSION = "0.52.0";
8
+ export declare const DEFAULT_GEMINI_CLI_VERSION = "0.58.0";
9
9
  export declare function getGeminiCliUserAgent(modelId?: string): string;
10
10
  export declare const getGeminiCliHeaders: (modelId?: string) => {
11
11
  "User-Agent": string;