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.
- package/dist/onboarding.js +3 -0
- package/dist/resources/extensions/bg-shell/index.ts +51 -7
- package/dist/resources/extensions/gsd/auto.ts +159 -2
- package/dist/resources/extensions/gsd/commands.ts +9 -3
- package/dist/resources/extensions/gsd/doctor.ts +60 -3
- package/dist/resources/extensions/gsd/guided-flow.ts +81 -9
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +449 -0
- package/dist/resources/extensions/gsd/preferences.ts +192 -0
- package/dist/resources/extensions/gsd/prompt-loader.ts +28 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -3
- package/dist/resources/extensions/gsd/prompts/discuss.md +10 -8
- package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +3 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +4 -2
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +9 -12
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -3
- package/dist/resources/extensions/gsd/prompts/queue.md +3 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/templates/context.md +1 -1
- package/dist/resources/extensions/gsd/templates/state.md +3 -3
- package/dist/resources/extensions/gsd/tests/doctor.test.ts +115 -1
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
- package/dist/resources/extensions/gsd/tests/regex-hardening.test.ts +12 -0
- package/dist/resources/extensions/gsd/types.ts +109 -0
- package/dist/resources/extensions/search-the-web/command-search-provider.ts +8 -4
- package/dist/resources/extensions/search-the-web/provider.ts +19 -2
- package/dist/resources/extensions/search-the-web/tool-fetch-page.ts +62 -0
- package/dist/resources/extensions/search-the-web/tool-llm-context.ts +62 -3
- package/dist/resources/extensions/search-the-web/tool-search.ts +62 -3
- package/dist/wizard.js +1 -0
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +169 -55
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +13 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +16 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +91 -1
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +273 -63
- package/packages/pi-agent-core/src/agent.ts +24 -0
- package/packages/pi-agent-core/src/types.ts +98 -0
- package/packages/pi-ai/dist/env-api-keys.js +1 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +314 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +236 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +1 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/env-api-keys.ts +1 -0
- package/packages/pi-ai/src/models.generated.ts +236 -0
- package/packages/pi-ai/src/types.ts +2 -1
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +1 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +69 -8
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +1 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +76 -7
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/src/resources/extensions/bg-shell/index.ts +51 -7
- package/src/resources/extensions/gsd/auto.ts +159 -2
- package/src/resources/extensions/gsd/commands.ts +9 -3
- package/src/resources/extensions/gsd/doctor.ts +60 -3
- package/src/resources/extensions/gsd/guided-flow.ts +81 -9
- package/src/resources/extensions/gsd/post-unit-hooks.ts +449 -0
- package/src/resources/extensions/gsd/preferences.ts +192 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +28 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -3
- package/src/resources/extensions/gsd/prompts/discuss.md +10 -8
- package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +9 -12
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -3
- package/src/resources/extensions/gsd/prompts/queue.md +3 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/templates/context.md +1 -1
- package/src/resources/extensions/gsd/templates/state.md +3 -3
- package/src/resources/extensions/gsd/tests/doctor.test.ts +115 -1
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +12 -0
- package/src/resources/extensions/gsd/types.ts +109 -0
- package/src/resources/extensions/search-the-web/command-search-provider.ts +8 -4
- package/src/resources/extensions/search-the-web/provider.ts +19 -2
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +62 -0
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +62 -3
- 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
|
|
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
|
-
|
|
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
|
-
|
|
392
|
-
|
|
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
|
-
|
|
395
|
-
|
|
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
|
-
|
|
435
|
+
return { toolResults: results, steeringMessages };
|
|
436
|
+
}
|
|
398
437
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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: "
|
|
453
|
+
type: "tool_execution_start",
|
|
418
454
|
toolCallId: toolCall.id,
|
|
419
455
|
toolName: toolCall.name,
|
|
420
|
-
|
|
421
|
-
isError,
|
|
456
|
+
args: toolCall.arguments,
|
|
422
457
|
});
|
|
423
458
|
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
439
|
-
|
|
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
|
-
|
|
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
|
-
|
|
456
|
-
|
|
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
|
-
):
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
|
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
|
|
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":"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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\"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"]}
|