@gajae-code/ai 0.18.5 → 0.18.7

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 (36) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/types/auth-storage.d.ts +8 -1
  3. package/dist/types/providers/cursor.d.ts +6 -3
  4. package/dist/types/providers/kiro-api-key.d.ts +1 -0
  5. package/dist/types/providers/openai-codex-responses.d.ts +3 -0
  6. package/dist/types/types.d.ts +16 -1
  7. package/dist/types/utils/fallback-transport.d.ts +6 -0
  8. package/dist/types/utils/idle-iterator.d.ts +2 -0
  9. package/dist/types/utils/oauth/glm-zcode.d.ts +5 -0
  10. package/dist/types/utils/oauth/index.d.ts +4 -0
  11. package/package.json +3 -3
  12. package/src/auth-storage.ts +115 -39
  13. package/src/model-thinking.ts +8 -0
  14. package/src/providers/aws-eventstream.ts +2 -1
  15. package/src/providers/cursor.d.ts +6 -3
  16. package/src/providers/cursor.ts +73 -64
  17. package/src/providers/google-gemini-cli.ts +49 -3
  18. package/src/providers/kiro-api-key.d.ts +1 -0
  19. package/src/providers/kiro-api-key.ts +1 -1
  20. package/src/providers/kiro-codewhisperer.ts +164 -68
  21. package/src/providers/openai-codex-responses.d.ts +3 -0
  22. package/src/providers/openai-codex-responses.ts +589 -41
  23. package/src/providers/openai-opencodex-responses.ts +6 -1
  24. package/src/providers/openai-responses.ts +1 -1
  25. package/src/providers/register-builtins.ts +53 -5
  26. package/src/stream.ts +20 -0
  27. package/src/types.d.ts +16 -1
  28. package/src/types.ts +19 -0
  29. package/src/utils/fallback-transport.d.ts +6 -0
  30. package/src/utils/fallback-transport.ts +6 -0
  31. package/src/utils/idle-iterator.d.ts +2 -0
  32. package/src/utils/idle-iterator.ts +13 -3
  33. package/src/utils/oauth/glm-zcode.d.ts +5 -0
  34. package/src/utils/oauth/glm-zcode.ts +8 -1
  35. package/src/utils/oauth/index.ts +11 -1
  36. package/src/utils/validation.ts +5 -0
@@ -2,8 +2,10 @@ import http2 from "node:http2";
2
2
  import { type JsonValue } from "@bufbuild/protobuf";
3
3
  import type { AssistantMessage, CursorExecHandlerResult, CursorExecHandlers, CursorToolResultHandler, Message, Model, StreamFunction, StreamOptions, Tool, ToolCall, ToolResultMessage, Usage } from "../types";
4
4
  import { kProviderResolvedToolCall } from "../utils/block-symbols";
5
+ import { AssistantMessageEventStream } from "../utils/event-stream";
5
6
  import { CURSOR_CLIENT_VERSION } from "./cursor/client-version";
6
7
  import type { CursorRule, RequestedModel_ModelParameterbytes } from "./cursor/gen/agent_pb";
8
+ import { type AgentServerMessage } from "./cursor/gen/agent_pb";
7
9
  export declare const CURSOR_API_URL = "https://api2.cursor.sh";
8
10
  export { CURSOR_CLIENT_VERSION };
9
11
  /** Drop all cached state + blob bytes for a conversation (F15 bound + session-teardown hook). */
@@ -46,9 +48,8 @@ export declare function mapH2TransportError(error: unknown, baseUrl: string): un
46
48
  export declare const streamCursor: StreamFunction<"cursor-agent">;
47
49
  type ToolCallState = ToolCall & {
48
50
  index: number;
49
- partialJson?: string;
50
- kind: "mcp" | "todo_write" | "native" | "cursor-exec";
51
- [kProviderResolvedToolCall]?: true;
51
+ kind: "todo_write" | "native" | "cursor-exec";
52
+ [kProviderResolvedToolCall]: true;
52
53
  };
53
54
  interface UsageState {
54
55
  sawTokenDelta: boolean;
@@ -56,6 +57,8 @@ interface UsageState {
56
57
  checkpointOutputTokens: number;
57
58
  hasConversationCheckpoint: boolean;
58
59
  }
60
+ /** Offline test seam for the same server-message boundary used by streamCursor. */
61
+ export declare function createCursorServerMessageHandlerForTest(output: AssistantMessage, stream: AssistantMessageEventStream, write: (frame: Uint8Array, done: () => void) => boolean, options?: CursorOptions): (message: AgentServerMessage) => Promise<void>;
59
62
  export declare function storeCursorBlobForTest(blobStore: Map<string, Uint8Array>, blobId: Uint8Array, blobData: Uint8Array, limits: {
60
63
  maxBytes: number;
61
64
  }): boolean;
@@ -32,7 +32,6 @@ import { kProviderResolvedToolCall } from "../utils/block-symbols";
32
32
  import { AssistantMessageEventStream } from "../utils/event-stream";
33
33
  import { transportFailureFacts } from "../utils/fallback-transport";
34
34
  import { FirstEventTimeoutError, getStreamFirstEventTimeoutMs, getStreamIdleTimeoutMs } from "../utils/idle-iterator";
35
- import { captureUnicodeEscapeEvidence, parseStreamingJson } from "../utils/json-parse";
36
35
  import { connectProxiedSocket, getProxyForUrl } from "../utils/proxy";
37
36
  import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
38
37
  import { flattenToolRootCombinators, toolWireSchema } from "../utils/schema";
@@ -2288,10 +2287,8 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
2288
2287
  }
2289
2288
  if (state.currentToolCall) {
2290
2289
  const idx = output.content.indexOf(state.currentToolCall);
2291
- state.currentToolCall.arguments = parseStreamingJson(state.currentToolCall.partialJson);
2292
- captureUnicodeEscapeEvidence(state.currentToolCall, state.currentToolCall.partialJson ?? "");
2293
- delete (state.currentToolCall as any).partialJson;
2294
- delete (state.currentToolCall as any).index;
2290
+ delete (state.currentToolCall as Partial<ToolCallState>).index;
2291
+ delete (state.currentToolCall as Partial<ToolCallState>).kind;
2295
2292
  stream.push({
2296
2293
  type: "toolcall_end",
2297
2294
  contentIndex: idx,
@@ -2428,9 +2425,8 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
2428
2425
 
2429
2426
  type ToolCallState = ToolCall & {
2430
2427
  index: number;
2431
- partialJson?: string;
2432
- kind: "mcp" | "todo_write" | "native" | "cursor-exec";
2433
- [kProviderResolvedToolCall]?: true;
2428
+ kind: "todo_write" | "native" | "cursor-exec";
2429
+ [kProviderResolvedToolCall]: true;
2434
2430
  };
2435
2431
 
2436
2432
  interface BlockState {
@@ -2493,6 +2489,57 @@ async function handleServerMessage(
2493
2489
  }
2494
2490
  }
2495
2491
 
2492
+ /** Offline test seam for the same server-message boundary used by streamCursor. */
2493
+ export function createCursorServerMessageHandlerForTest(
2494
+ output: AssistantMessage,
2495
+ stream: AssistantMessageEventStream,
2496
+ write: (frame: Uint8Array, done: () => void) => boolean,
2497
+ options: CursorOptions = {},
2498
+ ): (message: AgentServerMessage) => Promise<void> {
2499
+ const state: BlockState = {
2500
+ currentTextBlock: null,
2501
+ currentThinkingBlock: null,
2502
+ currentToolCall: null,
2503
+ firstTokenTime: undefined,
2504
+ setTextBlock(block) {
2505
+ this.currentTextBlock = block;
2506
+ },
2507
+ setThinkingBlock(block) {
2508
+ this.currentThinkingBlock = block;
2509
+ },
2510
+ setToolCall(block) {
2511
+ this.currentToolCall = block;
2512
+ },
2513
+ setFirstTokenTime() {
2514
+ this.firstTokenTime ??= Date.now();
2515
+ },
2516
+ };
2517
+ const usageState: UsageState = {
2518
+ sawTokenDelta: false,
2519
+ conversationUsedTokens: 0,
2520
+ checkpointOutputTokens: 0,
2521
+ hasConversationCheckpoint: false,
2522
+ };
2523
+ const writer = { write, isActive: () => true } as unknown as CursorRequestWriter;
2524
+ const blobs = new Map<string, Uint8Array>();
2525
+ return message =>
2526
+ handleServerMessage(
2527
+ message,
2528
+ output,
2529
+ stream,
2530
+ state,
2531
+ blobs,
2532
+ writer,
2533
+ options.execHandlers,
2534
+ options.onToolResult,
2535
+ usageState,
2536
+ [],
2537
+ undefined,
2538
+ [],
2539
+ options.signal,
2540
+ );
2541
+ }
2542
+
2496
2543
  function handleKvServerMessage(
2497
2544
  kvMsg: KvServerMessage,
2498
2545
  blobStore: Map<string, Uint8Array>,
@@ -3164,6 +3211,7 @@ async function handleExecServerMessage(
3164
3211
  case "mcpArgs": {
3165
3212
  const args = execMsg.message.value;
3166
3213
  const mcpCall = decodeMcpCall(args);
3214
+ synthesizeCursorExecToolCall(output, stream, mcpCall.toolCallId, mcpCall.toolName, mcpCall.args);
3167
3215
  const { execResult } = await resolveExecHandler(
3168
3216
  mcpCall,
3169
3217
  execHandlers?.mcp?.bind(execHandlers),
@@ -4063,17 +4111,6 @@ function decodeMcpArgValue(value: Uint8Array): unknown {
4063
4111
  return parseToolArgsJson(text);
4064
4112
  }
4065
4113
 
4066
- function decodeMcpArgsMap(args?: Record<string, Uint8Array>): Record<string, unknown> | undefined {
4067
- if (!args) {
4068
- return undefined;
4069
- }
4070
- const decoded: Record<string, unknown> = {};
4071
- for (const [key, value] of Object.entries(args)) {
4072
- decoded[key] = decodeMcpArgValue(value);
4073
- }
4074
- return decoded;
4075
- }
4076
-
4077
4114
  function decodeMcpCall(args: {
4078
4115
  name: string;
4079
4116
  args: Record<string, Uint8Array>;
@@ -4089,7 +4126,7 @@ function decodeMcpCall(args: {
4089
4126
  name: args.name,
4090
4127
  providerIdentifier: args.providerIdentifier,
4091
4128
  toolName: args.toolName || args.name,
4092
- toolCallId: args.toolCallId,
4129
+ toolCallId: args.toolCallId || crypto.randomUUID(),
4093
4130
  args: decodedArgs,
4094
4131
  rawArgs: args.args ?? {},
4095
4132
  };
@@ -4336,7 +4373,7 @@ const CURSOR_EXEC_OWNED_TOOL_CASES = new Set([
4336
4373
  ]);
4337
4374
 
4338
4375
  function isExecOwnedToolCall(toolCall: any): boolean {
4339
- return CURSOR_EXEC_OWNED_TOOL_CASES.has(toolCall?.tool?.case);
4376
+ return !!selectMcpToolCall(toolCall) || CURSOR_EXEC_OWNED_TOOL_CASES.has(toolCall?.tool?.case);
4340
4377
  }
4341
4378
 
4342
4379
  export function buildNativeToolCallBlock(
@@ -4358,6 +4395,7 @@ export function buildNativeToolCallBlock(
4358
4395
  : { raw: convertedArgs },
4359
4396
  index,
4360
4397
  kind: "native",
4398
+ [kProviderResolvedToolCall]: true,
4361
4399
  };
4362
4400
  }
4363
4401
  for (const [key, payload] of Object.entries(toolCall)) {
@@ -4380,6 +4418,7 @@ export function buildNativeToolCallBlock(
4380
4418
  arguments: safeArguments,
4381
4419
  index,
4382
4420
  kind: "native",
4421
+ [kProviderResolvedToolCall]: true,
4383
4422
  };
4384
4423
  }
4385
4424
  return null;
@@ -4445,6 +4484,7 @@ function synthesizeCursorExecToolCall(
4445
4484
  name: string,
4446
4485
  args: Record<string, unknown>,
4447
4486
  ): void {
4487
+ if (output.content.some(block => block.type === "toolCall" && block.id === toolCallId)) return;
4448
4488
  const block: ToolCallState = {
4449
4489
  type: "toolCall",
4450
4490
  id: toolCallId,
@@ -4518,33 +4558,19 @@ function processInteractionUpdate(
4518
4558
  state.setThinkingBlock(null);
4519
4559
  }
4520
4560
  } else if (updateCase === "toolCallStarted" && isExecOwnedToolCall(update.message.value.toolCall)) {
4521
- // Pi stream call IDs and exec IDs are distinct namespaces; without a shared
4522
- // correlation field, suppress the streamed variant and synthesize from exec.
4561
+ // Exec owns MCP and Pi tools. Suppress interaction announcements and
4562
+ // synthesize canonical, provider-resolved blocks before executing them.
4523
4563
  log("exec", "streamedToolCallOwnedByExec", { case: update.message.value.toolCall?.tool?.case });
4524
4564
  } else if (updateCase === "toolCallStarted") {
4525
4565
  const toolCall = update.message.value.toolCall;
4526
4566
  if (toolCall) {
4527
- const mcpCall = selectMcpToolCall(toolCall);
4528
- if (mcpCall) {
4529
- const args = mcpCall.args || {};
4530
- const block: ToolCallState = {
4531
- type: "toolCall",
4532
- id: args.toolCallId || crypto.randomUUID(),
4533
- name: args.name || args.toolName || "",
4534
- arguments: {},
4535
- index: output.content.length,
4536
- partialJson: "",
4537
- kind: "mcp",
4538
- };
4539
- output.content.push(block);
4540
- state.setToolCall(block);
4541
- stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
4542
- return;
4543
- }
4567
+ const callId = update.message.value.callId || crypto.randomUUID();
4568
+ // Repeated announcements describe the same provider-owned execution.
4569
+ if (output.content.some(block => block.type === "toolCall" && block.id === callId)) return;
4544
4570
 
4545
4571
  const todoArgs = buildTodoWriteArgs(toolCall);
4546
4572
  if (todoArgs) {
4547
- const callId = update.message.value.callId || crypto.randomUUID();
4573
+ // Cursor owns its todo state; this snapshot is for display, not local { ops } dispatch.
4548
4574
  const block: ToolCallState = {
4549
4575
  type: "toolCall",
4550
4576
  id: callId,
@@ -4552,6 +4578,7 @@ function processInteractionUpdate(
4552
4578
  arguments: todoArgs,
4553
4579
  index: output.content.length,
4554
4580
  kind: "todo_write",
4581
+ [kProviderResolvedToolCall]: true,
4555
4582
  };
4556
4583
  output.content.push(block);
4557
4584
  state.setToolCall(block);
@@ -4562,44 +4589,26 @@ function processInteractionUpdate(
4562
4589
  // Fallback: cursor-native tool variants (shell/glob/grep/…) we don't model
4563
4590
  // explicitly. Render them so the call and its result are visible instead of
4564
4591
  // vanishing.
4565
- const nativeBlock = buildNativeToolCallBlock(
4566
- toolCall,
4567
- update.message.value.callId || crypto.randomUUID(),
4568
- output.content.length,
4569
- );
4592
+ const nativeBlock = buildNativeToolCallBlock(toolCall, callId, output.content.length);
4570
4593
  if (nativeBlock) {
4571
4594
  output.content.push(nativeBlock);
4572
4595
  state.setToolCall(nativeBlock);
4573
4596
  stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
4574
4597
  }
4575
4598
  }
4576
- } else if (updateCase === "toolCallDelta" || updateCase === "partialToolCall") {
4577
- if (state.currentToolCall?.kind === "mcp") {
4578
- const delta = update.message.value.argsTextDelta || "";
4579
- state.currentToolCall.partialJson = `${state.currentToolCall.partialJson ?? ""}${delta}`;
4580
- state.currentToolCall.arguments = parseStreamingJson(state.currentToolCall.partialJson ?? "");
4581
- const idx = output.content.indexOf(state.currentToolCall);
4582
- stream.push({ type: "toolcall_delta", contentIndex: idx, delta, partial: output });
4583
- }
4584
4599
  } else if (updateCase === "toolCallCompleted") {
4600
+ if (isExecOwnedToolCall(update.message.value.toolCall)) return;
4585
4601
  if (state.currentToolCall) {
4586
4602
  const toolCall = update.message.value.toolCall;
4587
- if (state.currentToolCall.kind === "mcp") {
4588
- captureUnicodeEscapeEvidence(state.currentToolCall, state.currentToolCall.partialJson ?? "");
4589
- const decodedArgs = decodeMcpArgsMap(selectMcpToolCall(toolCall)?.args?.args);
4590
- if (decodedArgs) {
4591
- state.currentToolCall.arguments = decodedArgs;
4592
- }
4593
- } else if (state.currentToolCall.kind === "todo_write" && toolCall) {
4603
+ if (state.currentToolCall.kind === "todo_write" && toolCall) {
4594
4604
  const todoArgs = buildTodoWriteArgs(toolCall);
4595
4605
  if (todoArgs) {
4596
4606
  state.currentToolCall.arguments = todoArgs;
4597
4607
  }
4598
4608
  }
4599
4609
  const idx = output.content.indexOf(state.currentToolCall);
4600
- delete (state.currentToolCall as any).partialJson;
4601
- delete (state.currentToolCall as any).index;
4602
- delete (state.currentToolCall as any).kind;
4610
+ delete (state.currentToolCall as Partial<ToolCallState>).index;
4611
+ delete (state.currentToolCall as Partial<ToolCallState>).kind;
4603
4612
  stream.push({ type: "toolcall_end", contentIndex: idx, toolCall: state.currentToolCall, partial: output });
4604
4613
  state.setToolCall(null);
4605
4614
  }
@@ -16,6 +16,7 @@ import type {
16
16
  Api,
17
17
  AssistantMessage,
18
18
  Context,
19
+ GoogleGeminiCliDiagnostics,
19
20
  Model,
20
21
  StreamFunction,
21
22
  StreamOptions,
@@ -27,6 +28,7 @@ import { normalizeSystemPrompts } from "../utils";
27
28
  import { AssistantMessageEventStream } from "../utils/event-stream";
28
29
  import { transportFailureFacts } from "../utils/fallback-transport";
29
30
  import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
31
+ import { FirstEventTimeoutError } from "../utils/idle-iterator";
30
32
  import { resolveRetryBudget } from "../utils/retry-budget";
31
33
  // Refresh is the sole responsibility of AuthStorage (broker-aware, single-flighted);
32
34
  // the stream provider trusts the access token threaded through `options.apiKey`.
@@ -325,6 +327,29 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
325
327
  timestamp: Date.now(),
326
328
  };
327
329
  let rawRequestDump: RawHttpRequestDump | undefined;
330
+ const googleGeminiCliDiagnostics: GoogleGeminiCliDiagnostics = {
331
+ chunkCounts: { content: 0, thinking: 0, functionCall: 0, usageOnly: 0, other: 0 },
332
+ };
333
+ (
334
+ stream as AssistantMessageEventStream & { googleGeminiCliDiagnostics?: GoogleGeminiCliDiagnostics }
335
+ ).googleGeminiCliDiagnostics = googleGeminiCliDiagnostics;
336
+ const recordTimeoutDiagnostics = (): void => {
337
+ const reason = options?.signal?.reason;
338
+ if (!(reason instanceof FirstEventTimeoutError)) return;
339
+ googleGeminiCliDiagnostics.firstEventTimeoutMs = reason.firstEventTimeoutMs;
340
+ googleGeminiCliDiagnostics.firstEventTimeoutSource = reason.firstEventTimeoutSource;
341
+ (
342
+ reason as FirstEventTimeoutError & {
343
+ googleGeminiCliDiagnostics?: GoogleGeminiCliDiagnostics;
344
+ }
345
+ ).googleGeminiCliDiagnostics = googleGeminiCliDiagnostics;
346
+ };
347
+ options?.signal?.addEventListener("abort", recordTimeoutDiagnostics, { once: true });
348
+ const recordResponse = (): void => {
349
+ if (googleGeminiCliDiagnostics.responseAtMs === undefined) {
350
+ googleGeminiCliDiagnostics.responseAtMs = Date.now() - startTime;
351
+ }
352
+ };
328
353
 
329
354
  try {
330
355
  const apiKeyRaw = options?.apiKey;
@@ -405,6 +430,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
405
430
  fetch: options?.fetch,
406
431
  },
407
432
  );
433
+ recordResponse();
408
434
  if (!response.ok && sentForcedToolChoice) {
409
435
  const errorText = await response.text();
410
436
  const error = createGeminiCliHttpError(response, errorText);
@@ -447,6 +473,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
447
473
  fetch: options?.fetch,
448
474
  },
449
475
  );
476
+ recordResponse();
450
477
  } else {
451
478
  throw error;
452
479
  }
@@ -496,20 +523,29 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
496
523
  for await (const chunk of readSseJson<CloudCodeAssistResponseChunk>(
497
524
  activeResponse.body!,
498
525
  options?.signal,
499
- event =>
526
+ event => {
527
+ if (googleGeminiCliDiagnostics.firstRawSseAtMs === undefined) {
528
+ googleGeminiCliDiagnostics.firstRawSseAtMs = Date.now() - startTime;
529
+ }
500
530
  options?.onSseEvent?.(
501
531
  { event: event.event, data: event.data, raw: [...event.raw] },
502
532
  model,
503
533
  options?.attemptScope,
504
- ),
534
+ );
535
+ },
505
536
  )) {
506
537
  const responseData = chunk.response;
507
- if (!responseData) continue;
538
+ if (!responseData) {
539
+ googleGeminiCliDiagnostics.chunkCounts.other += 1;
540
+ continue;
541
+ }
508
542
 
509
543
  const candidate = responseData.candidates?.[0];
544
+ let chunkKind: keyof GoogleGeminiCliDiagnostics["chunkCounts"] = "other";
510
545
  if (candidate?.content?.parts) {
511
546
  for (const part of candidate.content.parts) {
512
547
  if (part.text !== undefined) {
548
+ chunkKind = isThinkingPart(part) ? "thinking" : "content";
513
549
  hasContent = true;
514
550
  const isThinking = isThinkingPart(part);
515
551
  if (
@@ -550,6 +586,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
550
586
  }
551
587
 
552
588
  if (part.functionCall) {
589
+ chunkKind = "functionCall";
553
590
  hasContent = true;
554
591
  if (currentBlock) {
555
592
  pushBlockEndEvent(currentBlock, blockIndex(), output, stream);
@@ -618,6 +655,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
618
655
  }
619
656
 
620
657
  if (responseData.usageMetadata) {
658
+ if (chunkKind === "other") chunkKind = "usageOnly";
621
659
  // promptTokenCount includes cachedContentTokenCount, so subtract to get fresh input
622
660
  const promptTokens = responseData.usageMetadata.promptTokenCount || 0;
623
661
  const cacheReadTokens = responseData.usageMetadata.cachedContentTokenCount || 0;
@@ -639,6 +677,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
639
677
  };
640
678
  calculateCost(model, output.usage);
641
679
  }
680
+ googleGeminiCliDiagnostics.chunkCounts[chunkKind] += 1;
642
681
  }
643
682
 
644
683
  if (currentBlock) {
@@ -676,6 +715,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
676
715
  body: requestBodyJson,
677
716
  signal: options?.signal,
678
717
  });
718
+ recordResponse();
679
719
 
680
720
  if (!currentResponse.ok) {
681
721
  const retryErrorText = await currentResponse.text();
@@ -711,6 +751,11 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
711
751
  stream.push({ type: "done", reason: output.stopReason, message: output });
712
752
  stream.end();
713
753
  } catch (error) {
754
+ const timeoutError = options?.signal?.reason;
755
+ if (timeoutError instanceof FirstEventTimeoutError) {
756
+ googleGeminiCliDiagnostics.firstEventTimeoutMs = timeoutError.firstEventTimeoutMs;
757
+ googleGeminiCliDiagnostics.firstEventTimeoutSource = timeoutError.firstEventTimeoutSource;
758
+ }
714
759
  for (const block of output.content) {
715
760
  if ("index" in block) {
716
761
  delete (block as { index?: number }).index;
@@ -719,6 +764,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
719
764
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
720
765
  output.errorStatus = extractHttpStatusFromError(error);
721
766
  output.transportFailure = transportFailureFacts(error);
767
+ output.googleGeminiCliDiagnostics = googleGeminiCliDiagnostics;
722
768
  output.errorMessage = await appendRawHttpRequestDumpFor400(
723
769
  error instanceof Error ? error.message : JSON.stringify(error),
724
770
  error,
@@ -5,6 +5,7 @@ export declare function kiroApiRegion(options?: {
5
5
  }): string;
6
6
  export declare function kiroApiBaseUrl(region: string): string;
7
7
  export declare function toKiroModelId(modelId: string): string;
8
+ export declare function sanitizeKiroError(value: unknown, secret?: string): string;
8
9
  export declare function kiroApiStaticModels(): Model<"kiro-codewhisperer-stream">[];
9
10
  /** Discover models this API key can use. Returns null when the key is missing. */
10
11
  export declare function fetchKiroApiModels(apiKey: string, region?: string): Promise<Model<"kiro-codewhisperer-stream">[]>;
@@ -118,7 +118,7 @@ function kiroApiHeaders(apiKey: string, target: string): Record<string, string>
118
118
  };
119
119
  }
120
120
 
121
- function sanitizeKiroError(value: unknown, secret?: string): string {
121
+ export function sanitizeKiroError(value: unknown, secret?: string): string {
122
122
  let message = value instanceof Error ? value.message : String(value);
123
123
  if (secret) message = message.split(secret).join("[redacted]");
124
124
  message = message.replace(/bearer\s+[^\s,;]+/gi, "Bearer [redacted]");