gsd-pi 2.11.0 → 2.12.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 (113) hide show
  1. package/dist/onboarding.js +3 -0
  2. package/dist/resources/extensions/bg-shell/index.ts +51 -7
  3. package/dist/resources/extensions/gsd/auto.ts +159 -2
  4. package/dist/resources/extensions/gsd/commands.ts +9 -3
  5. package/dist/resources/extensions/gsd/doctor.ts +60 -3
  6. package/dist/resources/extensions/gsd/guided-flow.ts +81 -9
  7. package/dist/resources/extensions/gsd/post-unit-hooks.ts +449 -0
  8. package/dist/resources/extensions/gsd/preferences.ts +192 -0
  9. package/dist/resources/extensions/gsd/prompt-loader.ts +28 -1
  10. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  11. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -3
  12. package/dist/resources/extensions/gsd/prompts/discuss.md +10 -8
  13. package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
  14. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -1
  15. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -1
  16. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  17. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +3 -1
  18. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +4 -2
  19. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -1
  20. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +3 -1
  21. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +9 -12
  22. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -3
  23. package/dist/resources/extensions/gsd/prompts/queue.md +3 -1
  24. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  25. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  26. package/dist/resources/extensions/gsd/templates/context.md +1 -1
  27. package/dist/resources/extensions/gsd/templates/state.md +3 -3
  28. package/dist/resources/extensions/gsd/tests/doctor.test.ts +115 -1
  29. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
  30. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
  31. package/dist/resources/extensions/gsd/tests/regex-hardening.test.ts +12 -0
  32. package/dist/resources/extensions/gsd/types.ts +109 -0
  33. package/dist/resources/extensions/search-the-web/command-search-provider.ts +8 -4
  34. package/dist/resources/extensions/search-the-web/provider.ts +19 -2
  35. package/dist/resources/extensions/search-the-web/tool-fetch-page.ts +62 -0
  36. package/dist/resources/extensions/search-the-web/tool-llm-context.ts +62 -3
  37. package/dist/resources/extensions/search-the-web/tool-search.ts +62 -3
  38. package/dist/wizard.js +1 -0
  39. package/package.json +1 -1
  40. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  41. package/packages/pi-agent-core/dist/agent-loop.js +169 -55
  42. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  43. package/packages/pi-agent-core/dist/agent.d.ts +13 -1
  44. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  45. package/packages/pi-agent-core/dist/agent.js +16 -0
  46. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  47. package/packages/pi-agent-core/dist/types.d.ts +91 -1
  48. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  49. package/packages/pi-agent-core/dist/types.js.map +1 -1
  50. package/packages/pi-agent-core/src/agent-loop.ts +273 -63
  51. package/packages/pi-agent-core/src/agent.ts +24 -0
  52. package/packages/pi-agent-core/src/types.ts +98 -0
  53. package/packages/pi-ai/dist/env-api-keys.js +1 -0
  54. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  55. package/packages/pi-ai/dist/models.generated.d.ts +314 -0
  56. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  57. package/packages/pi-ai/dist/models.generated.js +236 -0
  58. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  59. package/packages/pi-ai/dist/types.d.ts +1 -1
  60. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  61. package/packages/pi-ai/dist/types.js.map +1 -1
  62. package/packages/pi-ai/src/env-api-keys.ts +1 -0
  63. package/packages/pi-ai/src/models.generated.ts +236 -0
  64. package/packages/pi-ai/src/types.ts +2 -1
  65. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  66. package/packages/pi-coding-agent/dist/cli/args.js +1 -0
  67. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  68. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +10 -0
  69. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/agent-session.js +69 -8
  71. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  73. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  74. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  75. package/packages/pi-coding-agent/src/cli/args.ts +1 -0
  76. package/packages/pi-coding-agent/src/core/agent-session.ts +76 -7
  77. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  78. package/src/resources/extensions/bg-shell/index.ts +51 -7
  79. package/src/resources/extensions/gsd/auto.ts +159 -2
  80. package/src/resources/extensions/gsd/commands.ts +9 -3
  81. package/src/resources/extensions/gsd/doctor.ts +60 -3
  82. package/src/resources/extensions/gsd/guided-flow.ts +81 -9
  83. package/src/resources/extensions/gsd/post-unit-hooks.ts +449 -0
  84. package/src/resources/extensions/gsd/preferences.ts +192 -0
  85. package/src/resources/extensions/gsd/prompt-loader.ts +28 -1
  86. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  87. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -3
  88. package/src/resources/extensions/gsd/prompts/discuss.md +10 -8
  89. package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
  90. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -1
  91. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -1
  92. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  93. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +3 -1
  94. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +4 -2
  95. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -1
  96. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +3 -1
  97. package/src/resources/extensions/gsd/prompts/plan-milestone.md +9 -12
  98. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -3
  99. package/src/resources/extensions/gsd/prompts/queue.md +3 -1
  100. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  101. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  102. package/src/resources/extensions/gsd/templates/context.md +1 -1
  103. package/src/resources/extensions/gsd/templates/state.md +3 -3
  104. package/src/resources/extensions/gsd/tests/doctor.test.ts +115 -1
  105. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
  106. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
  107. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +12 -0
  108. package/src/resources/extensions/gsd/types.ts +109 -0
  109. package/src/resources/extensions/search-the-web/command-search-provider.ts +8 -4
  110. package/src/resources/extensions/search-the-web/provider.ts +19 -2
  111. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +62 -0
  112. package/src/resources/extensions/search-the-web/tool-llm-context.ts +62 -3
  113. package/src/resources/extensions/search-the-web/tool-search.ts +62 -3
@@ -17,6 +17,7 @@ import type {
17
17
  AgentLoopConfig,
18
18
  AgentMessage,
19
19
  AgentTool,
20
+ AgentToolCall,
20
21
  AgentToolResult,
21
22
  StreamFn,
22
23
  } from "./types.js";
@@ -230,11 +231,11 @@ async function runLoop(
230
231
  const toolResults: ToolResultMessage[] = [];
231
232
  if (hasMoreToolCalls) {
232
233
  const toolExecution = await executeToolCalls(
233
- currentContext.tools,
234
+ currentContext,
234
235
  message,
236
+ config,
235
237
  signal,
236
238
  stream,
237
- config.getSteeringMessages,
238
239
  );
239
240
  toolResults.push(...toolExecution.toolResults);
240
241
  steeringAfterTools = toolExecution.steeringMessages ?? null;
@@ -367,20 +368,32 @@ async function streamAssistantResponse(
367
368
  * Execute tool calls from an assistant message.
368
369
  */
369
370
  async function executeToolCalls(
370
- tools: AgentTool<any>[] | undefined,
371
+ currentContext: AgentContext,
372
+ assistantMessage: AssistantMessage,
373
+ config: AgentLoopConfig,
374
+ signal: AbortSignal | undefined,
375
+ stream: EventStream<AgentEvent, AgentMessage[]>,
376
+ ): Promise<{ toolResults: ToolResultMessage[]; steeringMessages?: AgentMessage[] }> {
377
+ const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall") as AgentToolCall[];
378
+ if (config.toolExecution === "sequential") {
379
+ return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, signal, stream);
380
+ }
381
+ return executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, signal, stream);
382
+ }
383
+
384
+ async function executeToolCallsSequential(
385
+ currentContext: AgentContext,
371
386
  assistantMessage: AssistantMessage,
387
+ toolCalls: AgentToolCall[],
388
+ config: AgentLoopConfig,
372
389
  signal: AbortSignal | undefined,
373
390
  stream: EventStream<AgentEvent, AgentMessage[]>,
374
- getSteeringMessages?: AgentLoopConfig["getSteeringMessages"],
375
391
  ): Promise<{ toolResults: ToolResultMessage[]; steeringMessages?: AgentMessage[] }> {
376
- const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
377
392
  const results: ToolResultMessage[] = [];
378
393
  let steeringMessages: AgentMessage[] | undefined;
379
394
 
380
395
  for (let index = 0; index < toolCalls.length; index++) {
381
396
  const toolCall = toolCalls[index];
382
- const tool = tools?.find((t) => t.name === toolCall.name);
383
-
384
397
  stream.push({
385
398
  type: "tool_execution_start",
386
399
  toolCallId: toolCall.id,
@@ -388,91 +401,267 @@ async function executeToolCalls(
388
401
  args: toolCall.arguments,
389
402
  });
390
403
 
391
- let result: AgentToolResult<any>;
392
- let isError = false;
404
+ const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, signal);
405
+ if (preparation.kind === "immediate") {
406
+ results.push(emitToolCallOutcome(toolCall, preparation.result, preparation.isError, stream));
407
+ } else {
408
+ const executed = await executePreparedToolCall(preparation, signal, stream);
409
+ results.push(
410
+ await finalizeExecutedToolCall(
411
+ currentContext,
412
+ assistantMessage,
413
+ preparation,
414
+ executed,
415
+ config,
416
+ signal,
417
+ stream,
418
+ ),
419
+ );
420
+ }
393
421
 
394
- try {
395
- if (!tool) throw new Error(`Tool ${toolCall.name} not found`);
422
+ if (config.getSteeringMessages) {
423
+ const steering = await config.getSteeringMessages();
424
+ if (steering.length > 0) {
425
+ steeringMessages = steering;
426
+ const remainingCalls = toolCalls.slice(index + 1);
427
+ for (const skipped of remainingCalls) {
428
+ results.push(skipToolCall(skipped, stream));
429
+ }
430
+ break;
431
+ }
432
+ }
433
+ }
396
434
 
397
- const validatedArgs = validateToolArguments(tool, toolCall);
435
+ return { toolResults: results, steeringMessages };
436
+ }
398
437
 
399
- result = await tool.execute(toolCall.id, validatedArgs, signal, (partialResult) => {
400
- stream.push({
401
- type: "tool_execution_update",
402
- toolCallId: toolCall.id,
403
- toolName: toolCall.name,
404
- args: toolCall.arguments,
405
- partialResult,
406
- });
407
- });
408
- } catch (e) {
409
- result = {
410
- content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }],
411
- details: {},
412
- };
413
- isError = true;
414
- }
438
+ async function executeToolCallsParallel(
439
+ currentContext: AgentContext,
440
+ assistantMessage: AssistantMessage,
441
+ toolCalls: AgentToolCall[],
442
+ config: AgentLoopConfig,
443
+ signal: AbortSignal | undefined,
444
+ stream: EventStream<AgentEvent, AgentMessage[]>,
445
+ ): Promise<{ toolResults: ToolResultMessage[]; steeringMessages?: AgentMessage[] }> {
446
+ const results: ToolResultMessage[] = [];
447
+ const runnableCalls: PreparedToolCall[] = [];
448
+ let steeringMessages: AgentMessage[] | undefined;
415
449
 
450
+ for (let index = 0; index < toolCalls.length; index++) {
451
+ const toolCall = toolCalls[index];
416
452
  stream.push({
417
- type: "tool_execution_end",
453
+ type: "tool_execution_start",
418
454
  toolCallId: toolCall.id,
419
455
  toolName: toolCall.name,
420
- result,
421
- isError,
456
+ args: toolCall.arguments,
422
457
  });
423
458
 
424
- const toolResultMessage: ToolResultMessage = {
425
- role: "toolResult",
426
- toolCallId: toolCall.id,
427
- toolName: toolCall.name,
428
- content: result.content,
429
- details: result.details,
430
- isError,
431
- timestamp: Date.now(),
432
- };
433
-
434
- results.push(toolResultMessage);
435
- stream.push({ type: "message_start", message: toolResultMessage });
436
- stream.push({ type: "message_end", message: toolResultMessage });
459
+ const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, signal);
460
+ if (preparation.kind === "immediate") {
461
+ results.push(emitToolCallOutcome(toolCall, preparation.result, preparation.isError, stream));
462
+ } else {
463
+ runnableCalls.push(preparation);
464
+ }
437
465
 
438
- // Check for steering messages - skip remaining tools if user interrupted
439
- if (getSteeringMessages) {
440
- const steering = await getSteeringMessages();
466
+ if (config.getSteeringMessages) {
467
+ const steering = await config.getSteeringMessages();
441
468
  if (steering.length > 0) {
442
469
  steeringMessages = steering;
470
+ for (const runnable of runnableCalls) {
471
+ results.push(skipToolCall(runnable.toolCall, stream, { emitStart: false }));
472
+ }
443
473
  const remainingCalls = toolCalls.slice(index + 1);
444
474
  for (const skipped of remainingCalls) {
445
475
  results.push(skipToolCall(skipped, stream));
446
476
  }
447
- break;
477
+ return { toolResults: results, steeringMessages };
448
478
  }
449
479
  }
450
480
  }
451
481
 
482
+ const runningCalls = runnableCalls.map((prepared) => ({
483
+ prepared,
484
+ execution: executePreparedToolCall(prepared, signal, stream),
485
+ }));
486
+
487
+ for (const running of runningCalls) {
488
+ const executed = await running.execution;
489
+ results.push(
490
+ await finalizeExecutedToolCall(
491
+ currentContext,
492
+ assistantMessage,
493
+ running.prepared,
494
+ executed,
495
+ config,
496
+ signal,
497
+ stream,
498
+ ),
499
+ );
500
+ }
501
+
502
+ if (!steeringMessages && config.getSteeringMessages) {
503
+ const steering = await config.getSteeringMessages();
504
+ if (steering.length > 0) {
505
+ steeringMessages = steering;
506
+ }
507
+ }
508
+
452
509
  return { toolResults: results, steeringMessages };
453
510
  }
454
511
 
455
- function skipToolCall(
456
- toolCall: Extract<AssistantMessage["content"][number], { type: "toolCall" }>,
512
+ type PreparedToolCall = {
513
+ kind: "prepared";
514
+ toolCall: AgentToolCall;
515
+ tool: AgentTool<any>;
516
+ args: unknown;
517
+ };
518
+
519
+ type ImmediateToolCallOutcome = {
520
+ kind: "immediate";
521
+ result: AgentToolResult<any>;
522
+ isError: boolean;
523
+ };
524
+
525
+ type ExecutedToolCallOutcome = {
526
+ result: AgentToolResult<any>;
527
+ isError: boolean;
528
+ };
529
+
530
+ async function prepareToolCall(
531
+ currentContext: AgentContext,
532
+ assistantMessage: AssistantMessage,
533
+ toolCall: AgentToolCall,
534
+ config: AgentLoopConfig,
535
+ signal: AbortSignal | undefined,
536
+ ): Promise<PreparedToolCall | ImmediateToolCallOutcome> {
537
+ const tool = currentContext.tools?.find((t) => t.name === toolCall.name);
538
+ if (!tool) {
539
+ return {
540
+ kind: "immediate",
541
+ result: createErrorToolResult(`Tool ${toolCall.name} not found`),
542
+ isError: true,
543
+ };
544
+ }
545
+
546
+ try {
547
+ const validatedArgs = validateToolArguments(tool, toolCall);
548
+ if (config.beforeToolCall) {
549
+ const beforeResult = await config.beforeToolCall(
550
+ {
551
+ assistantMessage,
552
+ toolCall,
553
+ args: validatedArgs,
554
+ context: currentContext,
555
+ },
556
+ signal,
557
+ );
558
+ if (beforeResult?.block) {
559
+ return {
560
+ kind: "immediate",
561
+ result: createErrorToolResult(beforeResult.reason || "Tool execution was blocked"),
562
+ isError: true,
563
+ };
564
+ }
565
+ }
566
+ return {
567
+ kind: "prepared",
568
+ toolCall,
569
+ tool,
570
+ args: validatedArgs,
571
+ };
572
+ } catch (error) {
573
+ return {
574
+ kind: "immediate",
575
+ result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
576
+ isError: true,
577
+ };
578
+ }
579
+ }
580
+
581
+ async function executePreparedToolCall(
582
+ prepared: PreparedToolCall,
583
+ signal: AbortSignal | undefined,
457
584
  stream: EventStream<AgentEvent, AgentMessage[]>,
458
- ): ToolResultMessage {
459
- const result: AgentToolResult<any> = {
460
- content: [{ type: "text", text: "Skipped due to queued user message." }],
585
+ ): Promise<ExecutedToolCallOutcome> {
586
+ try {
587
+ const result = await prepared.tool.execute(
588
+ prepared.toolCall.id,
589
+ prepared.args as never,
590
+ signal,
591
+ (partialResult) => {
592
+ stream.push({
593
+ type: "tool_execution_update",
594
+ toolCallId: prepared.toolCall.id,
595
+ toolName: prepared.toolCall.name,
596
+ args: prepared.toolCall.arguments,
597
+ partialResult,
598
+ });
599
+ },
600
+ );
601
+ return { result, isError: false };
602
+ } catch (error) {
603
+ return {
604
+ result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
605
+ isError: true,
606
+ };
607
+ }
608
+ }
609
+
610
+ async function finalizeExecutedToolCall(
611
+ currentContext: AgentContext,
612
+ assistantMessage: AssistantMessage,
613
+ prepared: PreparedToolCall,
614
+ executed: ExecutedToolCallOutcome,
615
+ config: AgentLoopConfig,
616
+ signal: AbortSignal | undefined,
617
+ stream: EventStream<AgentEvent, AgentMessage[]>,
618
+ ): Promise<ToolResultMessage> {
619
+ let result = executed.result;
620
+ let isError = executed.isError;
621
+
622
+ if (config.afterToolCall) {
623
+ const afterResult = await config.afterToolCall(
624
+ {
625
+ assistantMessage,
626
+ toolCall: prepared.toolCall,
627
+ args: prepared.args,
628
+ result,
629
+ isError,
630
+ context: currentContext,
631
+ },
632
+ signal,
633
+ );
634
+ if (afterResult) {
635
+ result = {
636
+ content: afterResult.content !== undefined ? afterResult.content : result.content,
637
+ details: afterResult.details !== undefined ? afterResult.details : result.details,
638
+ };
639
+ isError = afterResult.isError !== undefined ? afterResult.isError : isError;
640
+ }
641
+ }
642
+
643
+ return emitToolCallOutcome(prepared.toolCall, result, isError, stream);
644
+ }
645
+
646
+ function createErrorToolResult(message: string): AgentToolResult<any> {
647
+ return {
648
+ content: [{ type: "text", text: message }],
461
649
  details: {},
462
650
  };
651
+ }
463
652
 
464
- stream.push({
465
- type: "tool_execution_start",
466
- toolCallId: toolCall.id,
467
- toolName: toolCall.name,
468
- args: toolCall.arguments,
469
- });
653
+ function emitToolCallOutcome(
654
+ toolCall: AgentToolCall,
655
+ result: AgentToolResult<any>,
656
+ isError: boolean,
657
+ stream: EventStream<AgentEvent, AgentMessage[]>,
658
+ ): ToolResultMessage {
470
659
  stream.push({
471
660
  type: "tool_execution_end",
472
661
  toolCallId: toolCall.id,
473
662
  toolName: toolCall.name,
474
663
  result,
475
- isError: true,
664
+ isError,
476
665
  });
477
666
 
478
667
  const toolResultMessage: ToolResultMessage = {
@@ -480,13 +669,34 @@ function skipToolCall(
480
669
  toolCallId: toolCall.id,
481
670
  toolName: toolCall.name,
482
671
  content: result.content,
483
- details: {},
484
- isError: true,
672
+ details: result.details,
673
+ isError,
485
674
  timestamp: Date.now(),
486
675
  };
487
676
 
488
677
  stream.push({ type: "message_start", message: toolResultMessage });
489
678
  stream.push({ type: "message_end", message: toolResultMessage });
490
-
491
679
  return toolResultMessage;
492
680
  }
681
+
682
+ function skipToolCall(
683
+ toolCall: AgentToolCall,
684
+ stream: EventStream<AgentEvent, AgentMessage[]>,
685
+ options?: { emitStart?: boolean },
686
+ ): ToolResultMessage {
687
+ const result: AgentToolResult<any> = {
688
+ content: [{ type: "text", text: "Skipped due to queued user message." }],
689
+ details: {},
690
+ };
691
+
692
+ if (options?.emitStart !== false) {
693
+ stream.push({
694
+ type: "tool_execution_start",
695
+ toolCallId: toolCall.id,
696
+ toolName: toolCall.name,
697
+ args: toolCall.arguments,
698
+ });
699
+ }
700
+
701
+ return emitToolCallOutcome(toolCall, result, true, stream);
702
+ }
@@ -22,6 +22,10 @@ import type {
22
22
  AgentMessage,
23
23
  AgentState,
24
24
  AgentTool,
25
+ BeforeToolCallContext,
26
+ BeforeToolCallResult,
27
+ AfterToolCallContext,
28
+ AfterToolCallResult,
25
29
  StreamFn,
26
30
  ThinkingLevel,
27
31
  } from "./types.js";
@@ -129,6 +133,8 @@ export class Agent {
129
133
  private _thinkingBudgets?: ThinkingBudgets;
130
134
  private _transport: Transport;
131
135
  private _maxRetryDelayMs?: number;
136
+ private _beforeToolCall?: AgentLoopConfig["beforeToolCall"];
137
+ private _afterToolCall?: AgentLoopConfig["afterToolCall"];
132
138
 
133
139
  constructor(opts: AgentOptions = {}) {
134
140
  this._state = { ...this._state, ...opts.initialState };
@@ -203,6 +209,22 @@ export class Agent {
203
209
  this._maxRetryDelayMs = value;
204
210
  }
205
211
 
212
+ /**
213
+ * Install a hook called before each tool executes, after argument validation.
214
+ * Return `{ block: true }` to prevent execution.
215
+ */
216
+ setBeforeToolCall(fn: AgentLoopConfig["beforeToolCall"]): void {
217
+ this._beforeToolCall = fn;
218
+ }
219
+
220
+ /**
221
+ * Install a hook called after each tool executes, before results are emitted.
222
+ * Return field overrides for content/details/isError.
223
+ */
224
+ setAfterToolCall(fn: AgentLoopConfig["afterToolCall"]): void {
225
+ this._afterToolCall = fn;
226
+ }
227
+
206
228
  get state(): AgentState {
207
229
  return this._state;
208
230
  }
@@ -452,6 +474,8 @@ export class Agent {
452
474
  return this.dequeueSteeringMessages();
453
475
  },
454
476
  getFollowUpMessages: async () => this.dequeueFollowUpMessages(),
477
+ beforeToolCall: this._beforeToolCall,
478
+ afterToolCall: this._afterToolCall,
455
479
  };
456
480
 
457
481
  let partial: AgentMessage | null = null;
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ AssistantMessage,
2
3
  AssistantMessageEvent,
3
4
  ImageContent,
4
5
  Message,
@@ -16,6 +17,73 @@ export type StreamFn = (
16
17
  ...args: Parameters<typeof streamSimple>
17
18
  ) => ReturnType<typeof streamSimple> | Promise<ReturnType<typeof streamSimple>>;
18
19
 
20
+ /**
21
+ * Configuration for how tool calls from a single assistant message are executed.
22
+ *
23
+ * - "sequential": each tool call is prepared, executed, and finalized before the next one starts.
24
+ * - "parallel": tool calls are prepared sequentially, then allowed tools execute concurrently.
25
+ * Final tool results are still emitted in assistant source order.
26
+ */
27
+ export type ToolExecutionMode = "sequential" | "parallel";
28
+
29
+ /** A single tool call content block emitted by an assistant message. */
30
+ export type AgentToolCall = Extract<AssistantMessage["content"][number], { type: "toolCall" }>;
31
+
32
+ /**
33
+ * Result returned from `beforeToolCall`.
34
+ *
35
+ * Returning `{ block: true }` prevents the tool from executing. The loop emits an error tool result instead.
36
+ * `reason` becomes the text shown in that error result. If omitted, a default blocked message is used.
37
+ */
38
+ export interface BeforeToolCallResult {
39
+ block?: boolean;
40
+ reason?: string;
41
+ }
42
+
43
+ /**
44
+ * Partial override returned from `afterToolCall`.
45
+ *
46
+ * Merge semantics are field-by-field:
47
+ * - `content`: if provided, replaces the tool result content array in full
48
+ * - `details`: if provided, replaces the tool result details value in full
49
+ * - `isError`: if provided, replaces the tool result error flag
50
+ *
51
+ * Omitted fields keep the original executed tool result values.
52
+ */
53
+ export interface AfterToolCallResult {
54
+ content?: (TextContent | ImageContent)[];
55
+ details?: unknown;
56
+ isError?: boolean;
57
+ }
58
+
59
+ /** Context passed to `beforeToolCall`. */
60
+ export interface BeforeToolCallContext {
61
+ /** The assistant message that requested the tool call. */
62
+ assistantMessage: AssistantMessage;
63
+ /** The raw tool call block from `assistantMessage.content`. */
64
+ toolCall: AgentToolCall;
65
+ /** Validated tool arguments for the target tool schema. */
66
+ args: unknown;
67
+ /** Current agent context at the time the tool call is prepared. */
68
+ context: AgentContext;
69
+ }
70
+
71
+ /** Context passed to `afterToolCall`. */
72
+ export interface AfterToolCallContext {
73
+ /** The assistant message that requested the tool call. */
74
+ assistantMessage: AssistantMessage;
75
+ /** The raw tool call block from `assistantMessage.content`. */
76
+ toolCall: AgentToolCall;
77
+ /** Validated tool arguments for the target tool schema. */
78
+ args: unknown;
79
+ /** The executed tool result before any `afterToolCall` overrides are applied. */
80
+ result: AgentToolResult<any>;
81
+ /** Whether the executed tool result is currently treated as an error. */
82
+ isError: boolean;
83
+ /** Current agent context at the time the tool call is finalized. */
84
+ context: AgentContext;
85
+ }
86
+
19
87
  /**
20
88
  * Configuration for the agent loop.
21
89
  */
@@ -95,6 +163,36 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
95
163
  * Use this for follow-up messages that should wait until the agent finishes.
96
164
  */
97
165
  getFollowUpMessages?: () => Promise<AgentMessage[]>;
166
+
167
+ /**
168
+ * Tool execution mode.
169
+ * - "sequential": execute tool calls one by one
170
+ * - "parallel": preflight tool calls sequentially, then execute allowed tools concurrently
171
+ *
172
+ * Default: "parallel"
173
+ */
174
+ toolExecution?: ToolExecutionMode;
175
+
176
+ /**
177
+ * Called before a tool is executed, after arguments have been validated.
178
+ *
179
+ * Return `{ block: true }` to prevent execution. The loop emits an error tool result instead.
180
+ * The hook receives the agent abort signal and is responsible for honoring it.
181
+ */
182
+ beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;
183
+
184
+ /**
185
+ * Called after a tool finishes executing, before final tool events are emitted.
186
+ *
187
+ * Return an `AfterToolCallResult` to override parts of the executed tool result:
188
+ * - `content` replaces the full content array
189
+ * - `details` replaces the full details payload
190
+ * - `isError` replaces the error flag
191
+ *
192
+ * Any omitted fields keep their original values. No deep merge is performed.
193
+ * The hook receives the agent abort signal and is responsible for honoring it.
194
+ */
195
+ afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;
98
196
  }
99
197
 
100
198
  /**
@@ -98,6 +98,7 @@ export function getEnvApiKey(provider) {
98
98
  "opencode-go": "OPENCODE_API_KEY",
99
99
  "kimi-coding": "KIMI_API_KEY",
100
100
  "alibaba-coding-plan": "ALIBABA_API_KEY",
101
+ "ollama-cloud": "OLLAMA_API_KEY",
101
102
  "custom-openai": "CUSTOM_OPENAI_API_KEY",
102
103
  };
103
104
  const envVar = envMap[provider];
@@ -1 +1 @@
1
- {"version":3,"file":"env-api-keys.js","sourceRoot":"","sources":["../src/env-api-keys.ts"],"names":[],"mappings":"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","sourcesContent":["// NEVER convert to top-level imports - breaks browser/Vite builds (web-ui)\nlet _existsSync: typeof import(\"node:fs\").existsSync | null = null;\nlet _homedir: typeof import(\"node:os\").homedir | null = null;\nlet _join: typeof import(\"node:path\").join | null = null;\n\ntype DynamicImport = (specifier: string) => Promise<unknown>;\n\nconst dynamicImport: DynamicImport = (specifier) => import(specifier);\nconst NODE_FS_SPECIFIER = \"node:\" + \"fs\";\nconst NODE_OS_SPECIFIER = \"node:\" + \"os\";\nconst NODE_PATH_SPECIFIER = \"node:\" + \"path\";\n\n// Eagerly load in Node.js/Bun environment only\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\tdynamicImport(NODE_FS_SPECIFIER).then((m) => {\n\t\t_existsSync = (m as typeof import(\"node:fs\")).existsSync;\n\t});\n\tdynamicImport(NODE_OS_SPECIFIER).then((m) => {\n\t\t_homedir = (m as typeof import(\"node:os\")).homedir;\n\t});\n\tdynamicImport(NODE_PATH_SPECIFIER).then((m) => {\n\t\t_join = (m as typeof import(\"node:path\")).join;\n\t});\n}\n\nimport type { KnownProvider } from \"./types.js\";\n\nlet cachedVertexAdcCredentialsExists: boolean | null = null;\n\nfunction hasVertexAdcCredentials(): boolean {\n\tif (cachedVertexAdcCredentialsExists === null) {\n\t\t// If node modules haven't loaded yet (async import race at startup),\n\t\t// return false WITHOUT caching so the next call retries once they're ready.\n\t\t// Only cache false permanently in a browser environment where fs is never available.\n\t\tif (!_existsSync || !_homedir || !_join) {\n\t\t\tconst isNode = typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun);\n\t\t\tif (!isNode) {\n\t\t\t\t// Definitively in a browser — safe to cache false permanently\n\t\t\t\tcachedVertexAdcCredentialsExists = false;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check GOOGLE_APPLICATION_CREDENTIALS env var first (standard way)\n\t\tconst gacPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;\n\t\tif (gacPath) {\n\t\t\tcachedVertexAdcCredentialsExists = _existsSync(gacPath);\n\t\t} else {\n\t\t\t// Fall back to default ADC path (lazy evaluation)\n\t\t\tcachedVertexAdcCredentialsExists = _existsSync(\n\t\t\t\t_join(_homedir(), \".config\", \"gcloud\", \"application_default_credentials.json\"),\n\t\t\t);\n\t\t}\n\t}\n\treturn cachedVertexAdcCredentialsExists;\n}\n\n/**\n * Get API key for provider from known environment variables, e.g. OPENAI_API_KEY.\n *\n * Will not return API keys for providers that require OAuth tokens.\n */\nexport function getEnvApiKey(provider: KnownProvider): string | undefined;\nexport function getEnvApiKey(provider: string): string | undefined;\nexport function getEnvApiKey(provider: any): string | undefined {\n\t// Fall back to environment variables\n\tif (provider === \"github-copilot\") {\n\t\treturn process.env.COPILOT_GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;\n\t}\n\n\t// ANTHROPIC_OAUTH_TOKEN takes precedence over ANTHROPIC_API_KEY\n\tif (provider === \"anthropic\") {\n\t\treturn process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;\n\t}\n\n\t// Vertex AI uses Application Default Credentials, not API keys.\n\t// Auth is configured via `gcloud auth application-default login`.\n\tif (provider === \"google-vertex\") {\n\t\tconst hasCredentials = hasVertexAdcCredentials();\n\t\tconst hasProject = !!(process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT);\n\t\tconst hasLocation = !!process.env.GOOGLE_CLOUD_LOCATION;\n\n\t\tif (hasCredentials && hasProject && hasLocation) {\n\t\t\treturn \"<authenticated>\";\n\t\t}\n\t}\n\n\tif (provider === \"amazon-bedrock\") {\n\t\t// Amazon Bedrock supports multiple credential sources:\n\t\t// 1. AWS_PROFILE - named profile from ~/.aws/credentials\n\t\t// 2. AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY - standard IAM keys\n\t\t// 3. AWS_BEARER_TOKEN_BEDROCK - Bedrock API keys (bearer token)\n\t\t// 4. AWS_CONTAINER_CREDENTIALS_RELATIVE_URI - ECS task roles\n\t\t// 5. AWS_CONTAINER_CREDENTIALS_FULL_URI - ECS task roles (full URI)\n\t\t// 6. AWS_WEB_IDENTITY_TOKEN_FILE - IRSA (IAM Roles for Service Accounts)\n\t\tif (\n\t\t\tprocess.env.AWS_PROFILE ||\n\t\t\t(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ||\n\t\t\tprocess.env.AWS_BEARER_TOKEN_BEDROCK ||\n\t\t\tprocess.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ||\n\t\t\tprocess.env.AWS_CONTAINER_CREDENTIALS_FULL_URI ||\n\t\t\tprocess.env.AWS_WEB_IDENTITY_TOKEN_FILE\n\t\t) {\n\t\t\treturn \"<authenticated>\";\n\t\t}\n\t}\n\n\tconst envMap: Record<string, string> = {\n\t\topenai: \"OPENAI_API_KEY\",\n\t\t\"azure-openai-responses\": \"AZURE_OPENAI_API_KEY\",\n\t\tgoogle: \"GEMINI_API_KEY\",\n\t\tgroq: \"GROQ_API_KEY\",\n\t\tcerebras: \"CEREBRAS_API_KEY\",\n\t\txai: \"XAI_API_KEY\",\n\t\topenrouter: \"OPENROUTER_API_KEY\",\n\t\t\"vercel-ai-gateway\": \"AI_GATEWAY_API_KEY\",\n\t\tzai: \"ZAI_API_KEY\",\n\t\tmistral: \"MISTRAL_API_KEY\",\n\t\tminimax: \"MINIMAX_API_KEY\",\n\t\t\"minimax-cn\": \"MINIMAX_CN_API_KEY\",\n\t\thuggingface: \"HF_TOKEN\",\n\t\topencode: \"OPENCODE_API_KEY\",\n\t\t\"opencode-go\": \"OPENCODE_API_KEY\",\n\t\t\"kimi-coding\": \"KIMI_API_KEY\",\n\t\t\"alibaba-coding-plan\": \"ALIBABA_API_KEY\",\n\t\t\"custom-openai\": \"CUSTOM_OPENAI_API_KEY\",\n\t};\n\n\tconst envVar = envMap[provider];\n\treturn envVar ? process.env[envVar] : undefined;\n}\n"]}
1
+ {"version":3,"file":"env-api-keys.js","sourceRoot":"","sources":["../src/env-api-keys.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,IAAI,WAAW,GAA+C,IAAI,CAAC;AACnE,IAAI,QAAQ,GAA4C,IAAI,CAAC;AAC7D,IAAI,KAAK,GAA2C,IAAI,CAAC;AAIzD,MAAM,aAAa,GAAkB,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AACtE,MAAM,iBAAiB,GAAG,OAAO,GAAG,IAAI,CAAC;AACzC,MAAM,iBAAiB,GAAG,OAAO,GAAG,IAAI,CAAC;AACzC,MAAM,mBAAmB,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,+CAA+C;AAC/C,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC;IACzF,aAAa,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3C,WAAW,GAAI,CAA8B,CAAC,UAAU,CAAC;IAC1D,CAAC,CAAC,CAAC;IACH,aAAa,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3C,QAAQ,GAAI,CAA8B,CAAC,OAAO,CAAC;IACpD,CAAC,CAAC,CAAC;IACH,aAAa,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QAC7C,KAAK,GAAI,CAAgC,CAAC,IAAI,CAAC;IAChD,CAAC,CAAC,CAAC;AACJ,CAAC;AAID,IAAI,gCAAgC,GAAmB,IAAI,CAAC;AAE5D,SAAS,uBAAuB;IAC/B,IAAI,gCAAgC,KAAK,IAAI,EAAE,CAAC;QAC/C,qEAAqE;QACrE,4EAA4E;QAC5E,qFAAqF;QACrF,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,OAAO,OAAO,KAAK,WAAW,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YACnG,IAAI,CAAC,MAAM,EAAE,CAAC;gBACb,8DAA8D;gBAC9D,gCAAgC,GAAG,KAAK,CAAC;YAC1C,CAAC;YACD,OAAO,KAAK,CAAC;QACd,CAAC;QAED,oEAAoE;QACpE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC;QAC3D,IAAI,OAAO,EAAE,CAAC;YACb,gCAAgC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QACzD,CAAC;aAAM,CAAC;YACP,kDAAkD;YAClD,gCAAgC,GAAG,WAAW,CAC7C,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,sCAAsC,CAAC,CAC9E,CAAC;QACH,CAAC;IACF,CAAC;IACD,OAAO,gCAAgC,CAAC;AACzC,CAAC;AASD,MAAM,UAAU,YAAY,CAAC,QAAa;IACzC,qCAAqC;IACrC,IAAI,QAAQ,KAAK,gBAAgB,EAAE,CAAC;QACnC,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAC7F,CAAC;IAED,gEAAgE;IAChE,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC3E,CAAC;IAED,gEAAgE;IAChE,kEAAkE;IAClE,IAAI,QAAQ,KAAK,eAAe,EAAE,CAAC;QAClC,MAAM,cAAc,GAAG,uBAAuB,EAAE,CAAC;QACjD,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACtF,MAAM,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;QAExD,IAAI,cAAc,IAAI,UAAU,IAAI,WAAW,EAAE,CAAC;YACjD,OAAO,iBAAiB,CAAC;QAC1B,CAAC;IACF,CAAC;IAED,IAAI,QAAQ,KAAK,gBAAgB,EAAE,CAAC;QACnC,uDAAuD;QACvD,yDAAyD;QACzD,mEAAmE;QACnE,gEAAgE;QAChE,6DAA6D;QAC7D,oEAAoE;QACpE,yEAAyE;QACzE,IACC,OAAO,CAAC,GAAG,CAAC,WAAW;YACvB,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;YACpE,OAAO,CAAC,GAAG,CAAC,wBAAwB;YACpC,OAAO,CAAC,GAAG,CAAC,sCAAsC;YAClD,OAAO,CAAC,GAAG,CAAC,kCAAkC;YAC9C,OAAO,CAAC,GAAG,CAAC,2BAA2B,EACtC,CAAC;YACF,OAAO,iBAAiB,CAAC;QAC1B,CAAC;IACF,CAAC;IAED,MAAM,MAAM,GAA2B;QACtC,MAAM,EAAE,gBAAgB;QACxB,wBAAwB,EAAE,sBAAsB;QAChD,MAAM,EAAE,gBAAgB;QACxB,IAAI,EAAE,cAAc;QACpB,QAAQ,EAAE,kBAAkB;QAC5B,GAAG,EAAE,aAAa;QAClB,UAAU,EAAE,oBAAoB;QAChC,mBAAmB,EAAE,oBAAoB;QACzC,GAAG,EAAE,aAAa;QAClB,OAAO,EAAE,iBAAiB;QAC1B,OAAO,EAAE,iBAAiB;QAC1B,YAAY,EAAE,oBAAoB;QAClC,WAAW,EAAE,UAAU;QACvB,QAAQ,EAAE,kBAAkB;QAC5B,aAAa,EAAE,kBAAkB;QACjC,aAAa,EAAE,cAAc;QAC7B,qBAAqB,EAAE,iBAAiB;QACxC,cAAc,EAAE,gBAAgB;QAChC,eAAe,EAAE,uBAAuB;KACxC,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChC,OAAO,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACjD,CAAC","sourcesContent":["// NEVER convert to top-level imports - breaks browser/Vite builds (web-ui)\nlet _existsSync: typeof import(\"node:fs\").existsSync | null = null;\nlet _homedir: typeof import(\"node:os\").homedir | null = null;\nlet _join: typeof import(\"node:path\").join | null = null;\n\ntype DynamicImport = (specifier: string) => Promise<unknown>;\n\nconst dynamicImport: DynamicImport = (specifier) => import(specifier);\nconst NODE_FS_SPECIFIER = \"node:\" + \"fs\";\nconst NODE_OS_SPECIFIER = \"node:\" + \"os\";\nconst NODE_PATH_SPECIFIER = \"node:\" + \"path\";\n\n// Eagerly load in Node.js/Bun environment only\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\tdynamicImport(NODE_FS_SPECIFIER).then((m) => {\n\t\t_existsSync = (m as typeof import(\"node:fs\")).existsSync;\n\t});\n\tdynamicImport(NODE_OS_SPECIFIER).then((m) => {\n\t\t_homedir = (m as typeof import(\"node:os\")).homedir;\n\t});\n\tdynamicImport(NODE_PATH_SPECIFIER).then((m) => {\n\t\t_join = (m as typeof import(\"node:path\")).join;\n\t});\n}\n\nimport type { KnownProvider } from \"./types.js\";\n\nlet cachedVertexAdcCredentialsExists: boolean | null = null;\n\nfunction hasVertexAdcCredentials(): boolean {\n\tif (cachedVertexAdcCredentialsExists === null) {\n\t\t// If node modules haven't loaded yet (async import race at startup),\n\t\t// return false WITHOUT caching so the next call retries once they're ready.\n\t\t// Only cache false permanently in a browser environment where fs is never available.\n\t\tif (!_existsSync || !_homedir || !_join) {\n\t\t\tconst isNode = typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun);\n\t\t\tif (!isNode) {\n\t\t\t\t// Definitively in a browser — safe to cache false permanently\n\t\t\t\tcachedVertexAdcCredentialsExists = false;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check GOOGLE_APPLICATION_CREDENTIALS env var first (standard way)\n\t\tconst gacPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;\n\t\tif (gacPath) {\n\t\t\tcachedVertexAdcCredentialsExists = _existsSync(gacPath);\n\t\t} else {\n\t\t\t// Fall back to default ADC path (lazy evaluation)\n\t\t\tcachedVertexAdcCredentialsExists = _existsSync(\n\t\t\t\t_join(_homedir(), \".config\", \"gcloud\", \"application_default_credentials.json\"),\n\t\t\t);\n\t\t}\n\t}\n\treturn cachedVertexAdcCredentialsExists;\n}\n\n/**\n * Get API key for provider from known environment variables, e.g. OPENAI_API_KEY.\n *\n * Will not return API keys for providers that require OAuth tokens.\n */\nexport function getEnvApiKey(provider: KnownProvider): string | undefined;\nexport function getEnvApiKey(provider: string): string | undefined;\nexport function getEnvApiKey(provider: any): string | undefined {\n\t// Fall back to environment variables\n\tif (provider === \"github-copilot\") {\n\t\treturn process.env.COPILOT_GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;\n\t}\n\n\t// ANTHROPIC_OAUTH_TOKEN takes precedence over ANTHROPIC_API_KEY\n\tif (provider === \"anthropic\") {\n\t\treturn process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;\n\t}\n\n\t// Vertex AI uses Application Default Credentials, not API keys.\n\t// Auth is configured via `gcloud auth application-default login`.\n\tif (provider === \"google-vertex\") {\n\t\tconst hasCredentials = hasVertexAdcCredentials();\n\t\tconst hasProject = !!(process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT);\n\t\tconst hasLocation = !!process.env.GOOGLE_CLOUD_LOCATION;\n\n\t\tif (hasCredentials && hasProject && hasLocation) {\n\t\t\treturn \"<authenticated>\";\n\t\t}\n\t}\n\n\tif (provider === \"amazon-bedrock\") {\n\t\t// Amazon Bedrock supports multiple credential sources:\n\t\t// 1. AWS_PROFILE - named profile from ~/.aws/credentials\n\t\t// 2. AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY - standard IAM keys\n\t\t// 3. AWS_BEARER_TOKEN_BEDROCK - Bedrock API keys (bearer token)\n\t\t// 4. AWS_CONTAINER_CREDENTIALS_RELATIVE_URI - ECS task roles\n\t\t// 5. AWS_CONTAINER_CREDENTIALS_FULL_URI - ECS task roles (full URI)\n\t\t// 6. AWS_WEB_IDENTITY_TOKEN_FILE - IRSA (IAM Roles for Service Accounts)\n\t\tif (\n\t\t\tprocess.env.AWS_PROFILE ||\n\t\t\t(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ||\n\t\t\tprocess.env.AWS_BEARER_TOKEN_BEDROCK ||\n\t\t\tprocess.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ||\n\t\t\tprocess.env.AWS_CONTAINER_CREDENTIALS_FULL_URI ||\n\t\t\tprocess.env.AWS_WEB_IDENTITY_TOKEN_FILE\n\t\t) {\n\t\t\treturn \"<authenticated>\";\n\t\t}\n\t}\n\n\tconst envMap: Record<string, string> = {\n\t\topenai: \"OPENAI_API_KEY\",\n\t\t\"azure-openai-responses\": \"AZURE_OPENAI_API_KEY\",\n\t\tgoogle: \"GEMINI_API_KEY\",\n\t\tgroq: \"GROQ_API_KEY\",\n\t\tcerebras: \"CEREBRAS_API_KEY\",\n\t\txai: \"XAI_API_KEY\",\n\t\topenrouter: \"OPENROUTER_API_KEY\",\n\t\t\"vercel-ai-gateway\": \"AI_GATEWAY_API_KEY\",\n\t\tzai: \"ZAI_API_KEY\",\n\t\tmistral: \"MISTRAL_API_KEY\",\n\t\tminimax: \"MINIMAX_API_KEY\",\n\t\t\"minimax-cn\": \"MINIMAX_CN_API_KEY\",\n\t\thuggingface: \"HF_TOKEN\",\n\t\topencode: \"OPENCODE_API_KEY\",\n\t\t\"opencode-go\": \"OPENCODE_API_KEY\",\n\t\t\"kimi-coding\": \"KIMI_API_KEY\",\n\t\t\"alibaba-coding-plan\": \"ALIBABA_API_KEY\",\n\t\t\"ollama-cloud\": \"OLLAMA_API_KEY\",\n\t\t\"custom-openai\": \"CUSTOM_OPENAI_API_KEY\",\n\t};\n\n\tconst envVar = envMap[provider];\n\treturn envVar ? process.env[envVar] : undefined;\n}\n"]}