gsd-pi 2.46.1 → 2.47.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 (93) hide show
  1. package/README.md +46 -29
  2. package/dist/resources/extensions/claude-code-cli/index.js +25 -0
  3. package/dist/resources/extensions/claude-code-cli/models.js +40 -0
  4. package/dist/resources/extensions/claude-code-cli/package.json +11 -0
  5. package/dist/resources/extensions/claude-code-cli/partial-builder.js +223 -0
  6. package/dist/resources/extensions/claude-code-cli/readiness.js +26 -0
  7. package/dist/resources/extensions/claude-code-cli/sdk-types.js +8 -0
  8. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +309 -0
  9. package/dist/resources/extensions/gsd/auto-start.js +9 -8
  10. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  11. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +2 -2
  12. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  13. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  14. package/dist/resources/extensions/gsd/prompts/research-milestone.md +2 -2
  15. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -2
  16. package/dist/resources/extensions/gsd/repo-identity.js +5 -2
  17. package/dist/resources/extensions/gsd/state.js +29 -2
  18. package/dist/resources/extensions/gsd/workflow-events.js +1 -1
  19. package/dist/web/standalone/.next/BUILD_ID +1 -1
  20. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  21. package/dist/web/standalone/.next/build-manifest.json +2 -2
  22. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  23. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  24. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.html +1 -1
  40. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  47. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  48. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  49. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  50. package/package.json +3 -1
  51. package/packages/pi-agent-core/dist/agent-loop.js +26 -1
  52. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  53. package/packages/pi-agent-core/dist/agent.d.ts +7 -0
  54. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  55. package/packages/pi-agent-core/dist/agent.js +2 -0
  56. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  57. package/packages/pi-agent-core/dist/types.d.ts +9 -0
  58. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  59. package/packages/pi-agent-core/dist/types.js.map +1 -1
  60. package/packages/pi-agent-core/src/agent-loop.ts +25 -1
  61. package/packages/pi-agent-core/src/agent.ts +10 -0
  62. package/packages/pi-agent-core/src/types.ts +10 -0
  63. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +27 -2
  64. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  65. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/sdk.js +1 -0
  67. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  68. package/packages/pi-coding-agent/package.json +1 -1
  69. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +27 -2
  70. package/packages/pi-coding-agent/src/core/sdk.ts +1 -0
  71. package/pkg/package.json +1 -1
  72. package/src/resources/extensions/claude-code-cli/index.ts +28 -0
  73. package/src/resources/extensions/claude-code-cli/models.ts +42 -0
  74. package/src/resources/extensions/claude-code-cli/package.json +11 -0
  75. package/src/resources/extensions/claude-code-cli/partial-builder.ts +258 -0
  76. package/src/resources/extensions/claude-code-cli/readiness.ts +30 -0
  77. package/src/resources/extensions/claude-code-cli/sdk-types.ts +149 -0
  78. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +370 -0
  79. package/src/resources/extensions/gsd/auto-start.ts +8 -7
  80. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  81. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +2 -2
  82. package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  83. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  84. package/src/resources/extensions/gsd/prompts/research-milestone.md +2 -2
  85. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -2
  86. package/src/resources/extensions/gsd/repo-identity.ts +5 -2
  87. package/src/resources/extensions/gsd/state.ts +33 -1
  88. package/src/resources/extensions/gsd/tests/inherited-repo-home-dir.test.ts +70 -0
  89. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +40 -0
  90. package/src/resources/extensions/gsd/tests/run-uat.test.ts +25 -0
  91. package/src/resources/extensions/gsd/workflow-events.ts +1 -1
  92. /package/dist/web/standalone/.next/static/{P4nF4UcdATjrbNMBH_Ulh → VPcLnRF4BL8VoJEilBwlB}/_buildManifest.js +0 -0
  93. /package/dist/web/standalone/.next/static/{P4nF4UcdATjrbNMBH_Ulh → VPcLnRF4BL8VoJEilBwlB}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n\tAssistantMessage,\n\tAssistantMessageEvent,\n\tImageContent,\n\tMessage,\n\tModel,\n\tSimpleStreamOptions,\n\tstreamSimple,\n\tTextContent,\n\tTool,\n\tToolResultMessage,\n} from \"@gsd/pi-ai\";\nimport type { Static, TSchema } from \"@sinclair/typebox\";\n\n/** Stream function - can return sync or Promise for async config lookup */\nexport type StreamFn = (\n\t...args: Parameters<typeof streamSimple>\n) => ReturnType<typeof streamSimple> | Promise<ReturnType<typeof streamSimple>>;\n\n/**\n * Configuration for how tool calls from a single assistant message are executed.\n *\n * - \"sequential\": each tool call is prepared, executed, and finalized before the next one starts.\n * - \"parallel\": tool calls are prepared sequentially, then allowed tools execute concurrently.\n * Final tool results are still emitted in assistant source order.\n */\nexport type ToolExecutionMode = \"sequential\" | \"parallel\";\n\n/** A single tool call content block emitted by an assistant message. */\nexport type AgentToolCall = Extract<AssistantMessage[\"content\"][number], { type: \"toolCall\" }>;\n\n/**\n * Result returned from `beforeToolCall`.\n *\n * Returning `{ block: true }` prevents the tool from executing. The loop emits an error tool result instead.\n * `reason` becomes the text shown in that error result. If omitted, a default blocked message is used.\n */\nexport interface BeforeToolCallResult {\n\tblock?: boolean;\n\treason?: string;\n}\n\n/**\n * Partial override returned from `afterToolCall`.\n *\n * Merge semantics are field-by-field:\n * - `content`: if provided, replaces the tool result content array in full\n * - `details`: if provided, replaces the tool result details value in full\n * - `isError`: if provided, replaces the tool result error flag\n *\n * Omitted fields keep the original executed tool result values.\n */\nexport interface AfterToolCallResult {\n\tcontent?: (TextContent | ImageContent)[];\n\tdetails?: unknown;\n\tisError?: boolean;\n}\n\n/** Context passed to `beforeToolCall`. */\nexport interface BeforeToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** Current agent context at the time the tool call is prepared. */\n\tcontext: AgentContext;\n}\n\n/** Context passed to `afterToolCall`. */\nexport interface AfterToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** The executed tool result before any `afterToolCall` overrides are applied. */\n\tresult: AgentToolResult<any>;\n\t/** Whether the executed tool result is currently treated as an error. */\n\tisError: boolean;\n\t/** Current agent context at the time the tool call is finalized. */\n\tcontext: AgentContext;\n}\n\n/**\n * Configuration for the agent loop.\n */\nexport interface AgentLoopConfig extends SimpleStreamOptions {\n\tmodel: Model<any>;\n\n\t/**\n\t * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.\n\t *\n\t * Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage\n\t * that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,\n\t * status messages) should be filtered out.\n\t *\n\t * @example\n\t * ```typescript\n\t * convertToLlm: (messages) => messages.flatMap(m => {\n\t * if (m.role === \"custom\") {\n\t * // Convert custom message to user message\n\t * return [{ role: \"user\", content: m.content, timestamp: m.timestamp }];\n\t * }\n\t * if (m.role === \"notification\") {\n\t * // Filter out UI-only messages\n\t * return [];\n\t * }\n\t * // Pass through standard LLM messages\n\t * return [m];\n\t * })\n\t * ```\n\t */\n\tconvertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;\n\n\t/**\n\t * Optional transform applied to the context before `convertToLlm`.\n\t *\n\t * Use this for operations that work at the AgentMessage level:\n\t * - Context window management (pruning old messages)\n\t * - Injecting context from external sources\n\t *\n\t * @example\n\t * ```typescript\n\t * transformContext: async (messages) => {\n\t * if (estimateTokens(messages) > MAX_TOKENS) {\n\t * return pruneOldMessages(messages);\n\t * }\n\t * return messages;\n\t * }\n\t * ```\n\t */\n\ttransformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;\n\n\t/**\n\t * Resolves an API key dynamically for each LLM call.\n\t *\n\t * Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire\n\t * during long-running tool execution phases.\n\t */\n\tgetApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;\n\n\t/**\n\t * Returns steering messages to inject into the conversation mid-run.\n\t *\n\t * Called after each tool execution to check for user interruptions.\n\t * If messages are returned, remaining tool calls are skipped and\n\t * these messages are added to the context before the next LLM call.\n\t *\n\t * Use this for \"steering\" the agent while it's working.\n\t */\n\tgetSteeringMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Returns follow-up messages to process after the agent would otherwise stop.\n\t *\n\t * Called when the agent has no more tool calls and no steering messages.\n\t * If messages are returned, they're added to the context and the agent\n\t * continues with another turn.\n\t *\n\t * Use this for follow-up messages that should wait until the agent finishes.\n\t */\n\tgetFollowUpMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Tool execution mode.\n\t * - \"sequential\": execute tool calls one by one\n\t * - \"parallel\": preflight tool calls sequentially, then execute allowed tools concurrently\n\t *\n\t * Default: \"parallel\"\n\t */\n\ttoolExecution?: ToolExecutionMode;\n\n\t/**\n\t * Called before a tool is executed, after arguments have been validated.\n\t *\n\t * Return `{ block: true }` to prevent execution. The loop emits an error tool result instead.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tbeforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;\n\n\t/**\n\t * Called after a tool finishes executing, before final tool events are emitted.\n\t *\n\t * Return an `AfterToolCallResult` to override parts of the executed tool result:\n\t * - `content` replaces the full content array\n\t * - `details` replaces the full details payload\n\t * - `isError` replaces the error flag\n\t *\n\t * Any omitted fields keep their original values. No deep merge is performed.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tafterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;\n}\n\n/**\n * Thinking/reasoning level for models that support it.\n * Note: \"xhigh\" is only supported by OpenAI gpt-5.1-codex-max, gpt-5.2, gpt-5.2-codex, gpt-5.3, and gpt-5.3-codex models.\n */\nexport type ThinkingLevel = \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\n/**\n * Extensible interface for custom app messages.\n * Apps can extend via declaration merging:\n *\n * @example\n * ```typescript\n * declare module \"@mariozechner/agent\" {\n * interface CustomAgentMessages {\n * artifact: ArtifactMessage;\n * notification: NotificationMessage;\n * }\n * }\n * ```\n */\nexport interface CustomAgentMessages {\n\t// Empty by default - apps extend via declaration merging\n}\n\n/**\n * AgentMessage: Union of LLM messages + custom messages.\n * This abstraction allows apps to add custom message types while maintaining\n * type safety and compatibility with the base LLM messages.\n */\nexport type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];\n\n/**\n * Agent state containing all configuration and conversation data.\n */\nexport interface AgentState {\n\tsystemPrompt: string;\n\tmodel: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\ttools: AgentTool<any>[];\n\tmessages: AgentMessage[]; // Can include attachments + custom message types\n\tisStreaming: boolean;\n\tstreamMessage: AgentMessage | null;\n\tpendingToolCalls: Set<string>;\n\terror?: string;\n\t/**\n\t * The model currently being used for inference. Set at _runLoop() start,\n\t * cleared when the loop ends. When present, UI should display this instead\n\t * of `model` to avoid showing a stale value after a mid-turn model switch.\n\t */\n\tactiveInferenceModel?: Model<any>;\n}\n\nexport interface AgentToolResult<T> {\n\t// Content blocks supporting text and images\n\tcontent: (TextContent | ImageContent)[];\n\t// Details to be displayed in a UI or logged\n\tdetails: T;\n}\n\n// Callback for streaming tool execution updates\nexport type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<T>) => void;\n\n// AgentTool extends Tool but adds the execute function\nexport interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {\n\t// A human-readable label for the tool to be displayed in UI\n\tlabel: string;\n\texecute: (\n\t\ttoolCallId: string,\n\t\tparams: Static<TParameters>,\n\t\tsignal?: AbortSignal,\n\t\tonUpdate?: AgentToolUpdateCallback<TDetails>,\n\t) => Promise<AgentToolResult<TDetails>>;\n}\n\n// AgentContext is like Context but uses AgentTool\nexport interface AgentContext {\n\tsystemPrompt: string;\n\tmessages: AgentMessage[];\n\ttools?: AgentTool<any>[];\n}\n\n/**\n * Events emitted by the Agent for UI updates.\n * These events provide fine-grained lifecycle information for messages, turns, and tool executions.\n */\nexport type AgentEvent =\n\t// Agent lifecycle\n\t| { type: \"agent_start\" }\n\t| { type: \"agent_end\"; messages: AgentMessage[] }\n\t// Turn lifecycle - a turn is one assistant response + any tool calls/results\n\t| { type: \"turn_start\" }\n\t| { type: \"turn_end\"; message: AgentMessage; toolResults: ToolResultMessage[] }\n\t// Message lifecycle - emitted for user, assistant, and toolResult messages\n\t| { type: \"message_start\"; message: AgentMessage }\n\t// Only emitted for assistant messages during streaming\n\t| { type: \"message_update\"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }\n\t| { type: \"message_end\"; message: AgentMessage }\n\t// Tool execution lifecycle\n\t| { type: \"tool_execution_start\"; toolCallId: string; toolName: string; args: any }\n\t| { type: \"tool_execution_update\"; toolCallId: string; toolName: string; args: any; partialResult: any }\n\t| { type: \"tool_execution_end\"; toolCallId: string; toolName: string; result: any; isError: boolean };\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n\tAssistantMessage,\n\tAssistantMessageEvent,\n\tImageContent,\n\tMessage,\n\tModel,\n\tSimpleStreamOptions,\n\tstreamSimple,\n\tTextContent,\n\tTool,\n\tToolResultMessage,\n} from \"@gsd/pi-ai\";\nimport type { Static, TSchema } from \"@sinclair/typebox\";\n\n/** Stream function - can return sync or Promise for async config lookup */\nexport type StreamFn = (\n\t...args: Parameters<typeof streamSimple>\n) => ReturnType<typeof streamSimple> | Promise<ReturnType<typeof streamSimple>>;\n\n/**\n * Configuration for how tool calls from a single assistant message are executed.\n *\n * - \"sequential\": each tool call is prepared, executed, and finalized before the next one starts.\n * - \"parallel\": tool calls are prepared sequentially, then allowed tools execute concurrently.\n * Final tool results are still emitted in assistant source order.\n */\nexport type ToolExecutionMode = \"sequential\" | \"parallel\";\n\n/** A single tool call content block emitted by an assistant message. */\nexport type AgentToolCall = Extract<AssistantMessage[\"content\"][number], { type: \"toolCall\" }>;\n\n/**\n * Result returned from `beforeToolCall`.\n *\n * Returning `{ block: true }` prevents the tool from executing. The loop emits an error tool result instead.\n * `reason` becomes the text shown in that error result. If omitted, a default blocked message is used.\n */\nexport interface BeforeToolCallResult {\n\tblock?: boolean;\n\treason?: string;\n}\n\n/**\n * Partial override returned from `afterToolCall`.\n *\n * Merge semantics are field-by-field:\n * - `content`: if provided, replaces the tool result content array in full\n * - `details`: if provided, replaces the tool result details value in full\n * - `isError`: if provided, replaces the tool result error flag\n *\n * Omitted fields keep the original executed tool result values.\n */\nexport interface AfterToolCallResult {\n\tcontent?: (TextContent | ImageContent)[];\n\tdetails?: unknown;\n\tisError?: boolean;\n}\n\n/** Context passed to `beforeToolCall`. */\nexport interface BeforeToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** Current agent context at the time the tool call is prepared. */\n\tcontext: AgentContext;\n}\n\n/** Context passed to `afterToolCall`. */\nexport interface AfterToolCallContext {\n\t/** The assistant message that requested the tool call. */\n\tassistantMessage: AssistantMessage;\n\t/** The raw tool call block from `assistantMessage.content`. */\n\ttoolCall: AgentToolCall;\n\t/** Validated tool arguments for the target tool schema. */\n\targs: unknown;\n\t/** The executed tool result before any `afterToolCall` overrides are applied. */\n\tresult: AgentToolResult<any>;\n\t/** Whether the executed tool result is currently treated as an error. */\n\tisError: boolean;\n\t/** Current agent context at the time the tool call is finalized. */\n\tcontext: AgentContext;\n}\n\n/**\n * Configuration for the agent loop.\n */\nexport interface AgentLoopConfig extends SimpleStreamOptions {\n\tmodel: Model<any>;\n\n\t/**\n\t * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.\n\t *\n\t * Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage\n\t * that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,\n\t * status messages) should be filtered out.\n\t *\n\t * @example\n\t * ```typescript\n\t * convertToLlm: (messages) => messages.flatMap(m => {\n\t * if (m.role === \"custom\") {\n\t * // Convert custom message to user message\n\t * return [{ role: \"user\", content: m.content, timestamp: m.timestamp }];\n\t * }\n\t * if (m.role === \"notification\") {\n\t * // Filter out UI-only messages\n\t * return [];\n\t * }\n\t * // Pass through standard LLM messages\n\t * return [m];\n\t * })\n\t * ```\n\t */\n\tconvertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;\n\n\t/**\n\t * Optional transform applied to the context before `convertToLlm`.\n\t *\n\t * Use this for operations that work at the AgentMessage level:\n\t * - Context window management (pruning old messages)\n\t * - Injecting context from external sources\n\t *\n\t * @example\n\t * ```typescript\n\t * transformContext: async (messages) => {\n\t * if (estimateTokens(messages) > MAX_TOKENS) {\n\t * return pruneOldMessages(messages);\n\t * }\n\t * return messages;\n\t * }\n\t * ```\n\t */\n\ttransformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;\n\n\t/**\n\t * Resolves an API key dynamically for each LLM call.\n\t *\n\t * Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire\n\t * during long-running tool execution phases.\n\t */\n\tgetApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;\n\n\t/**\n\t * Returns steering messages to inject into the conversation mid-run.\n\t *\n\t * Called after each tool execution to check for user interruptions.\n\t * If messages are returned, remaining tool calls are skipped and\n\t * these messages are added to the context before the next LLM call.\n\t *\n\t * Use this for \"steering\" the agent while it's working.\n\t */\n\tgetSteeringMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Returns follow-up messages to process after the agent would otherwise stop.\n\t *\n\t * Called when the agent has no more tool calls and no steering messages.\n\t * If messages are returned, they're added to the context and the agent\n\t * continues with another turn.\n\t *\n\t * Use this for follow-up messages that should wait until the agent finishes.\n\t */\n\tgetFollowUpMessages?: () => Promise<AgentMessage[]>;\n\n\t/**\n\t * Tool execution mode.\n\t * - \"sequential\": execute tool calls one by one\n\t * - \"parallel\": preflight tool calls sequentially, then execute allowed tools concurrently\n\t *\n\t * Default: \"parallel\"\n\t */\n\ttoolExecution?: ToolExecutionMode;\n\n\t/**\n\t * Called before a tool is executed, after arguments have been validated.\n\t *\n\t * Return `{ block: true }` to prevent execution. The loop emits an error tool result instead.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tbeforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;\n\n\t/**\n\t * Called after a tool finishes executing, before final tool events are emitted.\n\t *\n\t * Return an `AfterToolCallResult` to override parts of the executed tool result:\n\t * - `content` replaces the full content array\n\t * - `details` replaces the full details payload\n\t * - `isError` replaces the error flag\n\t *\n\t * Any omitted fields keep their original values. No deep merge is performed.\n\t * The hook receives the agent abort signal and is responsible for honoring it.\n\t */\n\tafterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;\n\n\t/**\n\t * When true, tool calls in assistant messages are rendered in the TUI\n\t * but NOT executed locally. Used for providers that handle tool execution\n\t * internally (e.g., Claude Code CLI via Agent SDK).\n\t *\n\t * The agent loop emits tool_execution_start/end events for TUI rendering\n\t * but skips tool.execute() and does not add tool results to context.\n\t */\n\texternalToolExecution?: boolean;\n}\n\n/**\n * Thinking/reasoning level for models that support it.\n * Note: \"xhigh\" is only supported by OpenAI gpt-5.1-codex-max, gpt-5.2, gpt-5.2-codex, gpt-5.3, and gpt-5.3-codex models.\n */\nexport type ThinkingLevel = \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\n/**\n * Extensible interface for custom app messages.\n * Apps can extend via declaration merging:\n *\n * @example\n * ```typescript\n * declare module \"@mariozechner/agent\" {\n * interface CustomAgentMessages {\n * artifact: ArtifactMessage;\n * notification: NotificationMessage;\n * }\n * }\n * ```\n */\nexport interface CustomAgentMessages {\n\t// Empty by default - apps extend via declaration merging\n}\n\n/**\n * AgentMessage: Union of LLM messages + custom messages.\n * This abstraction allows apps to add custom message types while maintaining\n * type safety and compatibility with the base LLM messages.\n */\nexport type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];\n\n/**\n * Agent state containing all configuration and conversation data.\n */\nexport interface AgentState {\n\tsystemPrompt: string;\n\tmodel: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\ttools: AgentTool<any>[];\n\tmessages: AgentMessage[]; // Can include attachments + custom message types\n\tisStreaming: boolean;\n\tstreamMessage: AgentMessage | null;\n\tpendingToolCalls: Set<string>;\n\terror?: string;\n\t/**\n\t * The model currently being used for inference. Set at _runLoop() start,\n\t * cleared when the loop ends. When present, UI should display this instead\n\t * of `model` to avoid showing a stale value after a mid-turn model switch.\n\t */\n\tactiveInferenceModel?: Model<any>;\n}\n\nexport interface AgentToolResult<T> {\n\t// Content blocks supporting text and images\n\tcontent: (TextContent | ImageContent)[];\n\t// Details to be displayed in a UI or logged\n\tdetails: T;\n}\n\n// Callback for streaming tool execution updates\nexport type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<T>) => void;\n\n// AgentTool extends Tool but adds the execute function\nexport interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {\n\t// A human-readable label for the tool to be displayed in UI\n\tlabel: string;\n\texecute: (\n\t\ttoolCallId: string,\n\t\tparams: Static<TParameters>,\n\t\tsignal?: AbortSignal,\n\t\tonUpdate?: AgentToolUpdateCallback<TDetails>,\n\t) => Promise<AgentToolResult<TDetails>>;\n}\n\n// AgentContext is like Context but uses AgentTool\nexport interface AgentContext {\n\tsystemPrompt: string;\n\tmessages: AgentMessage[];\n\ttools?: AgentTool<any>[];\n}\n\n/**\n * Events emitted by the Agent for UI updates.\n * These events provide fine-grained lifecycle information for messages, turns, and tool executions.\n */\nexport type AgentEvent =\n\t// Agent lifecycle\n\t| { type: \"agent_start\" }\n\t| { type: \"agent_end\"; messages: AgentMessage[] }\n\t// Turn lifecycle - a turn is one assistant response + any tool calls/results\n\t| { type: \"turn_start\" }\n\t| { type: \"turn_end\"; message: AgentMessage; toolResults: ToolResultMessage[] }\n\t// Message lifecycle - emitted for user, assistant, and toolResult messages\n\t| { type: \"message_start\"; message: AgentMessage }\n\t// Only emitted for assistant messages during streaming\n\t| { type: \"message_update\"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }\n\t| { type: \"message_end\"; message: AgentMessage }\n\t// Tool execution lifecycle\n\t| { type: \"tool_execution_start\"; toolCallId: string; toolName: string; args: any }\n\t| { type: \"tool_execution_update\"; toolCallId: string; toolName: string; args: any; partialResult: any }\n\t| { type: \"tool_execution_end\"; toolCallId: string; toolName: string; result: any; isError: boolean };\n"]}
@@ -233,7 +233,31 @@ async function runLoop(
233
233
  hasMoreToolCalls = toolCalls.length > 0;
234
234
 
235
235
  const toolResults: ToolResultMessage[] = [];
236
- if (hasMoreToolCalls) {
236
+ if (hasMoreToolCalls && config.externalToolExecution) {
237
+ // External execution mode: tools were handled by the provider (e.g., Claude Code SDK).
238
+ // Emit synthetic tool events for TUI rendering but skip local dispatch.
239
+ for (const tc of toolCalls as AgentToolCall[]) {
240
+ stream.push({
241
+ type: "tool_execution_start",
242
+ toolCallId: tc.id,
243
+ toolName: tc.name,
244
+ args: tc.arguments,
245
+ });
246
+ stream.push({
247
+ type: "tool_execution_end",
248
+ toolCallId: tc.id,
249
+ toolName: tc.name,
250
+ result: {
251
+ content: [{ type: "text", text: "(executed by Claude Code)" }],
252
+ details: {},
253
+ },
254
+ isError: false,
255
+ });
256
+ }
257
+ // Don't add tool results to context or loop back — the streamSimple
258
+ // call already ran the full multi-turn agentic loop.
259
+ hasMoreToolCalls = false;
260
+ } else if (hasMoreToolCalls) {
237
261
  const toolExecution = await executeToolCalls(
238
262
  currentContext,
239
263
  message,
@@ -101,6 +101,13 @@ export interface AgentOptions {
101
101
  * Default: 60000 (60 seconds). Set to 0 to disable the cap.
102
102
  */
103
103
  maxRetryDelayMs?: number;
104
+
105
+ /**
106
+ * Determines whether a model uses external tool execution (tools handled
107
+ * by the provider, not dispatched locally). Evaluated per-loop so model
108
+ * switches mid-session are handled correctly.
109
+ */
110
+ externalToolExecution?: (model: Model<any>) => boolean;
104
111
  }
105
112
 
106
113
  /**
@@ -144,6 +151,7 @@ export class Agent {
144
151
  private _maxRetryDelayMs?: number;
145
152
  private _beforeToolCall?: AgentLoopConfig["beforeToolCall"];
146
153
  private _afterToolCall?: AgentLoopConfig["afterToolCall"];
154
+ private _externalToolExecution?: (model: Model<any>) => boolean;
147
155
 
148
156
  constructor(opts: AgentOptions = {}) {
149
157
  this._state = { ...this._state, ...opts.initialState };
@@ -158,6 +166,7 @@ export class Agent {
158
166
  this._thinkingBudgets = opts.thinkingBudgets;
159
167
  this._transport = opts.transport ?? "sse";
160
168
  this._maxRetryDelayMs = opts.maxRetryDelayMs;
169
+ this._externalToolExecution = opts.externalToolExecution;
161
170
  }
162
171
 
163
172
  /**
@@ -499,6 +508,7 @@ export class Agent {
499
508
  getFollowUpMessages: async () => this.dequeueFollowUpMessages(),
500
509
  beforeToolCall: this._beforeToolCall,
501
510
  afterToolCall: this._afterToolCall,
511
+ externalToolExecution: this._externalToolExecution?.(model) ?? false,
502
512
  };
503
513
 
504
514
  let partial: AgentMessage | null = null;
@@ -193,6 +193,16 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
193
193
  * The hook receives the agent abort signal and is responsible for honoring it.
194
194
  */
195
195
  afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;
196
+
197
+ /**
198
+ * When true, tool calls in assistant messages are rendered in the TUI
199
+ * but NOT executed locally. Used for providers that handle tool execution
200
+ * internally (e.g., Claude Code CLI via Agent SDK).
201
+ *
202
+ * The agent loop emits tool_execution_start/end events for TUI rendering
203
+ * but skips tool.execute() and does not add tool results to context.
204
+ */
205
+ externalToolExecution?: boolean;
196
206
  }
197
207
 
198
208
  /**
@@ -215,7 +215,7 @@ describe("AuthStorage — areAllCredentialsBackedOff", () => {
215
215
  });
216
216
  // ─── mismatched oauth credential for non-OAuth provider (#2083) ───────────────
217
217
  describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () => {
218
- it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async () => {
218
+ it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async (t) => {
219
219
  // Simulates the bug: OpenRouter credential stored as type:"oauth"
220
220
  // but OpenRouter is not a registered OAuth provider.
221
221
  const storage = inMemory({
@@ -226,12 +226,25 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
226
226
  expires: Date.now() + 3_600_000,
227
227
  },
228
228
  });
229
+ // Isolate from any real OPENROUTER_API_KEY in the environment so the
230
+ // fall-through to env / fallback finds nothing and returns undefined.
231
+ const origEnv = process.env.OPENROUTER_API_KEY;
232
+ delete process.env.OPENROUTER_API_KEY;
233
+ t.after(() => {
234
+ if (origEnv === undefined) {
235
+ delete process.env.OPENROUTER_API_KEY;
236
+ }
237
+ else {
238
+ process.env.OPENROUTER_API_KEY = origEnv;
239
+ }
240
+ });
229
241
  // Before the fix, getApiKey returns undefined because
230
242
  // resolveCredentialApiKey calls getOAuthProvider("openrouter") → null → undefined.
231
243
  // The key in the oauth credential is never extracted.
232
244
  const key = await storage.getApiKey("openrouter");
233
245
  // After the fix, the oauth credential with an unrecognised provider
234
246
  // should be skipped, and getApiKey should fall through to env / fallback.
247
+ // With no env var and no fallback resolver configured, the result is undefined.
235
248
  assert.equal(key, undefined);
236
249
  });
237
250
  it("falls through to env var when openrouter has type:oauth credential", async (t) => {
@@ -257,7 +270,7 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
257
270
  const key = await storage.getApiKey("openrouter");
258
271
  assert.equal(key, "sk-or-v1-env-key");
259
272
  });
260
- it("falls through to fallback resolver when openrouter has type:oauth credential", async () => {
273
+ it("falls through to fallback resolver when openrouter has type:oauth credential", async (t) => {
261
274
  const storage = inMemory({
262
275
  openrouter: {
263
276
  type: "oauth",
@@ -266,6 +279,18 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
266
279
  expires: Date.now() + 3_600_000,
267
280
  },
268
281
  });
282
+ // Isolate from any real OPENROUTER_API_KEY so env fallback is skipped
283
+ // and the fallback resolver is reached.
284
+ const origEnv = process.env.OPENROUTER_API_KEY;
285
+ delete process.env.OPENROUTER_API_KEY;
286
+ t.after(() => {
287
+ if (origEnv === undefined) {
288
+ delete process.env.OPENROUTER_API_KEY;
289
+ }
290
+ else {
291
+ process.env.OPENROUTER_API_KEY = origEnv;
292
+ }
293
+ });
269
294
  storage.setFallbackResolver((provider) => provider === "openrouter" ? "sk-or-v1-fallback" : undefined);
270
295
  const key = await storage.getApiKey("openrouter");
271
296
  assert.equal(key, "sk-or-v1-fallback");
@@ -1 +1 @@
1
- {"version":3,"file":"auth-storage.test.js","sourceRoot":"","sources":["../../src/core/auth-storage.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { AuthStorage } from \"./auth-storage.js\";\n\n// ─── helpers ──────────────────────────────────────────────────────────────────\n\nfunction makeKey(key: string) {\n\treturn { type: \"api_key\" as const, key };\n}\n\nfunction inMemory(data: Record<string, unknown> = {}) {\n\treturn AuthStorage.inMemory(data as any);\n}\n\n// ─── single credential (backward compat) ─────────────────────────────────────\n\ndescribe(\"AuthStorage — single credential (backward compat)\", () => {\n\tit(\"returns the api key for a provider with one key\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-abc\") });\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-abc\");\n\t});\n\n\tit(\"returns undefined for unknown provider\", async () => {\n\t\tconst storage = inMemory({});\n\t\tconst key = await storage.getApiKey(\"unknown\");\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"runtime override takes precedence over stored key\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-stored\") });\n\t\tstorage.setRuntimeApiKey(\"anthropic\", \"sk-runtime\");\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-runtime\");\n\t});\n});\n\n// ─── multiple credentials ─────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — multiple credentials\", () => {\n\tit(\"round-robins across multiple api keys without sessionId\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst keys = new Set<string>();\n\t\tfor (let i = 0; i < 6; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\");\n\t\t\tassert.ok(k, `call ${i} should return a key`);\n\t\t\tkeys.add(k);\n\t\t}\n\t\t// All three keys should have been selected across 6 calls\n\t\tassert.deepEqual(keys, new Set([\"sk-1\", \"sk-2\", \"sk-3\"]));\n\t});\n\n\tit(\"session-sticky: same sessionId always picks the same key\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst sessionId = \"sess-abc\";\n\t\tconst first = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tfor (let i = 0; i < 5; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\", sessionId);\n\t\t\tassert.equal(k, first, `call ${i} should be sticky to first selection`);\n\t\t}\n\t});\n\n\tit(\"different sessionIds may select different keys\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst results = new Set<string>();\n\t\tfor (let i = 0; i < 20; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\", `sess-${i}`);\n\t\t\tif (k) results.add(k);\n\t\t}\n\t\t// With 20 different sessions and 3 keys, we should see more than one key\n\t\tassert.ok(results.size > 1, \"multiple sessions should hash to different keys\");\n\t});\n});\n\n// ─── login accumulation ───────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — login accumulation\", () => {\n\tit(\"accumulates api keys on repeated set()\", () => {\n\t\tconst storage = inMemory({});\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-2\"));\n\t\tconst creds = storage.getCredentialsForProvider(\"anthropic\");\n\t\tassert.equal(creds.length, 2);\n\t\tassert.deepEqual(\n\t\t\tcreds.map((c) => (c.type === \"api_key\" ? c.key : null)),\n\t\t\t[\"sk-1\", \"sk-2\"],\n\t\t);\n\t});\n\n\tit(\"deduplicates identical api keys\", () => {\n\t\tconst storage = inMemory({});\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tconst creds = storage.getCredentialsForProvider(\"anthropic\");\n\t\tassert.equal(creds.length, 1);\n\t});\n});\n\n// ─── backoff / markUsageLimitReached ─────────────────────────────────────────\n\ndescribe(\"AuthStorage — rate-limit backoff\", () => {\n\tit(\"returns true when a backed-off credential has an alternate\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// Use sk-1 via round-robin (first call, index 0)\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// Mark it as rate-limited; sk-2 should still be available\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(hasAlternate, true);\n\t});\n\n\tit(\"returns false when all credentials are backed off\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// Back off both keys\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 1\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\"); // backs off index 1\n\t\tassert.equal(hasAlternate, false);\n\t});\n\n\tit(\"backed-off credential is skipped; next available key is returned\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// First call → sk-1 (round-robin index 0)\n\t\tconst first = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(first, \"sk-1\");\n\n\t\t// Back off sk-1\n\t\tstorage.markUsageLimitReached(\"anthropic\");\n\n\t\t// Next call should skip backed-off sk-1 and return sk-2\n\t\tconst second = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(second, \"sk-2\");\n\t});\n\n\tit(\"single credential: markUsageLimitReached returns false\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(hasAlternate, false);\n\t});\n\n\tit(\"single credential: unknown error type skips backoff entirely\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// Mark with unknown error type (transport failure)\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"unknown\",\n\t\t});\n\t\tassert.equal(hasAlternate, false);\n\n\t\t// Key should still be available — backoff was not applied\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-only\");\n\t});\n\n\tit(\"multiple credentials: unknown error type still backs off the used credential\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\t\tawait storage.getApiKey(\"anthropic\"); // uses sk-1\n\n\t\t// Mark with unknown error type — should still back off when alternates exist\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"unknown\",\n\t\t});\n\t\tassert.equal(hasAlternate, true);\n\n\t\t// Next call should return sk-2\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-2\");\n\t});\n\n\tit(\"single credential: rate_limit error type still backs off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// rate_limit should still back off even single credentials\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"rate_limit\",\n\t\t});\n\t\tassert.equal(hasAlternate, false);\n\n\t\t// Key should be backed off\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"session-sticky: marks the correct credential as backed off\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\tconst sessionId = \"sess-xyz\";\n\t\tconst chosen = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tassert.ok(chosen);\n\n\t\t// Back off the chosen credential for this session\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", sessionId);\n\t\tassert.equal(hasAlternate, true);\n\n\t\t// Next call with same session should return the other key\n\t\tconst next = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tassert.ok(next);\n\t\tassert.notEqual(next, chosen);\n\t});\n});\n\n// ─── areAllCredentialsBackedOff ───────────────────────────────────────────────\n\ndescribe(\"AuthStorage — areAllCredentialsBackedOff\", () => {\n\tit(\"returns false when no credentials are configured\", () => {\n\t\tconst storage = inMemory({});\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns false when credentials exist and none are backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-abc\") });\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns true when the single credential is backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\t\tstorage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), true);\n\t});\n\n\tit(\"returns false when at least one credential is still available\", async () => {\n\t\tconst storage = inMemory({ anthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")] });\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\t// index 1 is still available\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns true when all credentials are backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")] });\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 1\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 1\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), true);\n\t});\n});\n\n// ─── mismatched oauth credential for non-OAuth provider (#2083) ───────────────\n\ndescribe(\"AuthStorage — oauth credential for non-OAuth provider (#2083)\", () => {\n\tit(\"returns undefined when openrouter has type:oauth (no registered OAuth provider)\", async () => {\n\t\t// Simulates the bug: OpenRouter credential stored as type:\"oauth\"\n\t\t// but OpenRouter is not a registered OAuth provider.\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\t// Before the fix, getApiKey returns undefined because\n\t\t// resolveCredentialApiKey calls getOAuthProvider(\"openrouter\") → null → undefined.\n\t\t// The key in the oauth credential is never extracted.\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\t// After the fix, the oauth credential with an unrecognised provider\n\t\t// should be skipped, and getApiKey should fall through to env / fallback.\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"falls through to env var when openrouter has type:oauth credential\", async (t) => {\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\t// Simulate OPENROUTER_API_KEY being set via env\n\t\tconst origEnv = process.env.OPENROUTER_API_KEY;\n\t\tt.after(() => {\n\t\t\tif (origEnv === undefined) {\n\t\t\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\t\t} else {\n\t\t\t\tprocess.env.OPENROUTER_API_KEY = origEnv;\n\t\t\t}\n\t\t});\n\n\t\tprocess.env.OPENROUTER_API_KEY = \"sk-or-v1-env-key\";\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\tassert.equal(key, \"sk-or-v1-env-key\");\n\t});\n\n\tit(\"falls through to fallback resolver when openrouter has type:oauth credential\", async () => {\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\tstorage.setFallbackResolver((provider) =>\n\t\t\tprovider === \"openrouter\" ? \"sk-or-v1-fallback\" : undefined,\n\t\t);\n\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\tassert.equal(key, \"sk-or-v1-fallback\");\n\t});\n});\n\n// ─── getAll truncation ────────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — getAll()\", () => {\n\tit(\"returns first credential only for providers with multiple keys\", () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t\topenai: makeKey(\"sk-openai\"),\n\t\t});\n\t\tconst all = storage.getAll();\n\t\tassert.ok(all[\"anthropic\"]?.type === \"api_key\");\n\t\tassert.equal((all[\"anthropic\"] as any).key, \"sk-1\");\n\t\tassert.equal((all[\"openai\"] as any).key, \"sk-openai\");\n\t});\n});\n"]}
1
+ {"version":3,"file":"auth-storage.test.js","sourceRoot":"","sources":["../../src/core/auth-storage.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { AuthStorage } from \"./auth-storage.js\";\n\n// ─── helpers ──────────────────────────────────────────────────────────────────\n\nfunction makeKey(key: string) {\n\treturn { type: \"api_key\" as const, key };\n}\n\nfunction inMemory(data: Record<string, unknown> = {}) {\n\treturn AuthStorage.inMemory(data as any);\n}\n\n// ─── single credential (backward compat) ─────────────────────────────────────\n\ndescribe(\"AuthStorage — single credential (backward compat)\", () => {\n\tit(\"returns the api key for a provider with one key\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-abc\") });\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-abc\");\n\t});\n\n\tit(\"returns undefined for unknown provider\", async () => {\n\t\tconst storage = inMemory({});\n\t\tconst key = await storage.getApiKey(\"unknown\");\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"runtime override takes precedence over stored key\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-stored\") });\n\t\tstorage.setRuntimeApiKey(\"anthropic\", \"sk-runtime\");\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-runtime\");\n\t});\n});\n\n// ─── multiple credentials ─────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — multiple credentials\", () => {\n\tit(\"round-robins across multiple api keys without sessionId\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst keys = new Set<string>();\n\t\tfor (let i = 0; i < 6; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\");\n\t\t\tassert.ok(k, `call ${i} should return a key`);\n\t\t\tkeys.add(k);\n\t\t}\n\t\t// All three keys should have been selected across 6 calls\n\t\tassert.deepEqual(keys, new Set([\"sk-1\", \"sk-2\", \"sk-3\"]));\n\t});\n\n\tit(\"session-sticky: same sessionId always picks the same key\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst sessionId = \"sess-abc\";\n\t\tconst first = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tfor (let i = 0; i < 5; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\", sessionId);\n\t\t\tassert.equal(k, first, `call ${i} should be sticky to first selection`);\n\t\t}\n\t});\n\n\tit(\"different sessionIds may select different keys\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst results = new Set<string>();\n\t\tfor (let i = 0; i < 20; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\", `sess-${i}`);\n\t\t\tif (k) results.add(k);\n\t\t}\n\t\t// With 20 different sessions and 3 keys, we should see more than one key\n\t\tassert.ok(results.size > 1, \"multiple sessions should hash to different keys\");\n\t});\n});\n\n// ─── login accumulation ───────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — login accumulation\", () => {\n\tit(\"accumulates api keys on repeated set()\", () => {\n\t\tconst storage = inMemory({});\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-2\"));\n\t\tconst creds = storage.getCredentialsForProvider(\"anthropic\");\n\t\tassert.equal(creds.length, 2);\n\t\tassert.deepEqual(\n\t\t\tcreds.map((c) => (c.type === \"api_key\" ? c.key : null)),\n\t\t\t[\"sk-1\", \"sk-2\"],\n\t\t);\n\t});\n\n\tit(\"deduplicates identical api keys\", () => {\n\t\tconst storage = inMemory({});\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tconst creds = storage.getCredentialsForProvider(\"anthropic\");\n\t\tassert.equal(creds.length, 1);\n\t});\n});\n\n// ─── backoff / markUsageLimitReached ─────────────────────────────────────────\n\ndescribe(\"AuthStorage — rate-limit backoff\", () => {\n\tit(\"returns true when a backed-off credential has an alternate\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// Use sk-1 via round-robin (first call, index 0)\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// Mark it as rate-limited; sk-2 should still be available\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(hasAlternate, true);\n\t});\n\n\tit(\"returns false when all credentials are backed off\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// Back off both keys\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 1\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\"); // backs off index 1\n\t\tassert.equal(hasAlternate, false);\n\t});\n\n\tit(\"backed-off credential is skipped; next available key is returned\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// First call → sk-1 (round-robin index 0)\n\t\tconst first = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(first, \"sk-1\");\n\n\t\t// Back off sk-1\n\t\tstorage.markUsageLimitReached(\"anthropic\");\n\n\t\t// Next call should skip backed-off sk-1 and return sk-2\n\t\tconst second = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(second, \"sk-2\");\n\t});\n\n\tit(\"single credential: markUsageLimitReached returns false\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(hasAlternate, false);\n\t});\n\n\tit(\"single credential: unknown error type skips backoff entirely\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// Mark with unknown error type (transport failure)\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"unknown\",\n\t\t});\n\t\tassert.equal(hasAlternate, false);\n\n\t\t// Key should still be available — backoff was not applied\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-only\");\n\t});\n\n\tit(\"multiple credentials: unknown error type still backs off the used credential\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\t\tawait storage.getApiKey(\"anthropic\"); // uses sk-1\n\n\t\t// Mark with unknown error type — should still back off when alternates exist\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"unknown\",\n\t\t});\n\t\tassert.equal(hasAlternate, true);\n\n\t\t// Next call should return sk-2\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-2\");\n\t});\n\n\tit(\"single credential: rate_limit error type still backs off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// rate_limit should still back off even single credentials\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"rate_limit\",\n\t\t});\n\t\tassert.equal(hasAlternate, false);\n\n\t\t// Key should be backed off\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"session-sticky: marks the correct credential as backed off\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\tconst sessionId = \"sess-xyz\";\n\t\tconst chosen = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tassert.ok(chosen);\n\n\t\t// Back off the chosen credential for this session\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", sessionId);\n\t\tassert.equal(hasAlternate, true);\n\n\t\t// Next call with same session should return the other key\n\t\tconst next = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tassert.ok(next);\n\t\tassert.notEqual(next, chosen);\n\t});\n});\n\n// ─── areAllCredentialsBackedOff ───────────────────────────────────────────────\n\ndescribe(\"AuthStorage — areAllCredentialsBackedOff\", () => {\n\tit(\"returns false when no credentials are configured\", () => {\n\t\tconst storage = inMemory({});\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns false when credentials exist and none are backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-abc\") });\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns true when the single credential is backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\t\tstorage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), true);\n\t});\n\n\tit(\"returns false when at least one credential is still available\", async () => {\n\t\tconst storage = inMemory({ anthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")] });\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\t// index 1 is still available\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns true when all credentials are backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")] });\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 1\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 1\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), true);\n\t});\n});\n\n// ─── mismatched oauth credential for non-OAuth provider (#2083) ───────────────\n\ndescribe(\"AuthStorage — oauth credential for non-OAuth provider (#2083)\", () => {\n\tit(\"returns undefined when openrouter has type:oauth (no registered OAuth provider)\", async (t) => {\n\t\t// Simulates the bug: OpenRouter credential stored as type:\"oauth\"\n\t\t// but OpenRouter is not a registered OAuth provider.\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\t// Isolate from any real OPENROUTER_API_KEY in the environment so the\n\t\t// fall-through to env / fallback finds nothing and returns undefined.\n\t\tconst origEnv = process.env.OPENROUTER_API_KEY;\n\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\tt.after(() => {\n\t\t\tif (origEnv === undefined) {\n\t\t\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\t\t} else {\n\t\t\t\tprocess.env.OPENROUTER_API_KEY = origEnv;\n\t\t\t}\n\t\t});\n\n\t\t// Before the fix, getApiKey returns undefined because\n\t\t// resolveCredentialApiKey calls getOAuthProvider(\"openrouter\") → null → undefined.\n\t\t// The key in the oauth credential is never extracted.\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\t// After the fix, the oauth credential with an unrecognised provider\n\t\t// should be skipped, and getApiKey should fall through to env / fallback.\n\t\t// With no env var and no fallback resolver configured, the result is undefined.\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"falls through to env var when openrouter has type:oauth credential\", async (t) => {\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\t// Simulate OPENROUTER_API_KEY being set via env\n\t\tconst origEnv = process.env.OPENROUTER_API_KEY;\n\t\tt.after(() => {\n\t\t\tif (origEnv === undefined) {\n\t\t\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\t\t} else {\n\t\t\t\tprocess.env.OPENROUTER_API_KEY = origEnv;\n\t\t\t}\n\t\t});\n\n\t\tprocess.env.OPENROUTER_API_KEY = \"sk-or-v1-env-key\";\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\tassert.equal(key, \"sk-or-v1-env-key\");\n\t});\n\n\tit(\"falls through to fallback resolver when openrouter has type:oauth credential\", async (t) => {\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\t// Isolate from any real OPENROUTER_API_KEY so env fallback is skipped\n\t\t// and the fallback resolver is reached.\n\t\tconst origEnv = process.env.OPENROUTER_API_KEY;\n\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\tt.after(() => {\n\t\t\tif (origEnv === undefined) {\n\t\t\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\t\t} else {\n\t\t\t\tprocess.env.OPENROUTER_API_KEY = origEnv;\n\t\t\t}\n\t\t});\n\n\t\tstorage.setFallbackResolver((provider) =>\n\t\t\tprovider === \"openrouter\" ? \"sk-or-v1-fallback\" : undefined,\n\t\t);\n\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\tassert.equal(key, \"sk-or-v1-fallback\");\n\t});\n});\n\n// ─── getAll truncation ────────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — getAll()\", () => {\n\tit(\"returns first credential only for providers with multiple keys\", () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t\topenai: makeKey(\"sk-openai\"),\n\t\t});\n\t\tconst all = storage.getAll();\n\t\tassert.ok(all[\"anthropic\"]?.type === \"api_key\");\n\t\tassert.equal((all[\"anthropic\"] as any).key, \"sk-1\");\n\t\tassert.equal((all[\"openai\"] as any).key, \"sk-openai\");\n\t});\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"AACA,OAAO,EAA4B,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAW,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAmB,oBAAoB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEnG,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EACN,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,yBAAyB,EACzB,sBAAsB,EACtB,sBAAsB,EACtB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,KAAK,IAAI,EAET,SAAS,EACT,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,yBAAyB;IACzC,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,oFAAoF;IACpF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,oFAAoF;IACpF,aAAa,CAAC,EAAE,aAAa,CAAC;IAE9B,iEAAiE;IACjE,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,gEAAgE;IAChE,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAAC,aAAa,CAAC,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;IAE3E,4EAA4E;IAC5E,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,gEAAgE;IAChE,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAE/B,oEAAoE;IACpE,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC,2DAA2D;IAC3D,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC,uEAAuE;IACvE,eAAe,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,qCAAqC;AACrC,MAAM,WAAW,wBAAwB;IACxC,0BAA0B;IAC1B,OAAO,EAAE,YAAY,CAAC;IACtB,mEAAmE;IACnE,gBAAgB,EAAE,oBAAoB,CAAC;IACvC,wEAAwE;IACxE,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAID,YAAY,EACX,YAAY,EACZ,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,GACd,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAEN,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,WAAW,EACX,aAAa,EACb,QAAQ,IAAI,eAAe,EAE3B,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,cAAc,EACd,cAAc,EACd,YAAY,EAEZ,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,yBAAyB,EACzB,sBAAsB,EACtB,sBAAsB,GACtB,CAAC;AAQF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,OAAO,CAAC,wBAAwB,CAAC,CA+PnH"}
@@ -189,6 +189,7 @@ export async function createAgentSession(options = {}) {
189
189
  transport: settingsManager.getTransport(),
190
190
  thinkingBudgets: settingsManager.getThinkingBudgets(),
191
191
  maxRetryDelayMs: settingsManager.getRetrySettings().maxDelayMs,
192
+ externalToolExecution: (m) => modelRegistry.getProviderAuthMode(m.provider) === "externalCli",
192
193
  getApiKey: async (provider) => {
193
194
  // Use the provider argument from the in-flight request;
194
195
  // agent.state.model may already be switched mid-turn.
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.js","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport { Agent, type AgentMessage, type ThinkingLevel } from \"@gsd/pi-agent-core\";\nimport type { Message, Model } from \"@gsd/pi-ai\";\nimport { getAgentDir, getDocsPath } from \"../config.js\";\nimport { AgentSession } from \"./agent-session.js\";\nimport { AuthStorage } from \"./auth-storage.js\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.js\";\nimport type { ExtensionRunner, LoadExtensionsResult, ToolDefinition } from \"./extensions/index.js\";\nimport { convertToLlm } from \"./messages.js\";\nimport { ModelRegistry } from \"./model-registry.js\";\nimport { findInitialModel } from \"./model-resolver.js\";\nimport type { ResourceLoader } from \"./resource-loader.js\";\nimport { DefaultResourceLoader } from \"./resource-loader.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { SettingsManager } from \"./settings-manager.js\";\nimport { time } from \"./timings.js\";\nimport {\n\tallTools,\n\tbashTool,\n\tcodingTools,\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\teditTool,\n\tfindTool,\n\tgrepTool,\n\thashlineCodingTools,\n\thashlineEditTool,\n\thashlineReadTool,\n\tcreateHashlineCodingTools,\n\tcreateHashlineEditTool,\n\tcreateHashlineReadTool,\n\tlsTool,\n\treadOnlyTools,\n\treadTool,\n\ttype Tool,\n\ttype ToolName,\n\twriteTool,\n} from \"./tools/index.js\";\n\nexport interface CreateAgentSessionOptions {\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.pi/agent */\n\tagentDir?: string;\n\n\t/** Auth storage for credentials. Default: AuthStorage.create(agentDir/auth.json) */\n\tauthStorage?: AuthStorage;\n\t/** Model registry. Default: new ModelRegistry(authStorage, agentDir/models.json) */\n\tmodelRegistry?: ModelRegistry;\n\n\t/** Model to use. Default: from settings, else first available */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/** Built-in tools to use. Default: codingTools [read, bash, edit, write] */\n\ttools?: Tool[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tSlashCommandInfo,\n\tSlashCommandLocation,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.js\";\nexport type { PromptTemplate } from \"./prompt-templates.js\";\nexport type { Skill } from \"./skills.js\";\nexport type { Tool } from \"./tools/index.js\";\n\nexport {\n\t// Pre-built tools (use process.cwd())\n\treadTool,\n\tbashTool,\n\teditTool,\n\twriteTool,\n\tgrepTool,\n\tfindTool,\n\tlsTool,\n\tcodingTools,\n\treadOnlyTools,\n\tallTools as allBuiltInTools,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n\t// Hashline edit mode\n\thashlineCodingTools,\n\thashlineEditTool,\n\thashlineReadTool,\n\tcreateHashlineCodingTools,\n\tcreateHashlineEditTool,\n\tcreateHashlineReadTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@gsd/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [readTool, bashTool],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst agentDir = options.agentDir ?? getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\t// Use provided or create AuthStorage and ModelRegistry\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst authStorage = options.authStorage ?? AuthStorage.create(authPath);\n\tconst modelRegistry = options.modelRegistry ?? new ModelRegistry(authStorage, modelsPath);\n\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd);\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRegistry.find(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel && (await modelRegistry.getApiKey(restoredModel))) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use findInitialModel (checks settings default, then provider defaults)\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelRegistry,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `No models available. Use /login or set an API key environment variable. See ${join(getDocsPath(), \"providers.md\")}. Then use /model to select a model.`;\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to settings default\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model || !model.reasoning) {\n\t\tthinkingLevel = \"off\";\n\t}\n\n\tconst editMode = settingsManager.getEditMode();\n\tconst defaultActiveToolNames: ToolName[] = editMode === \"hashline\"\n\t\t? [\"hashline_read\", \"bash\", \"hashline_edit\", \"write\", \"lsp\"]\n\t\t: [\"read\", \"bash\", \"edit\", \"write\", \"lsp\"];\n\tconst initialActiveToolNames: ToolName[] = options.tools\n\t\t? options.tools.map((t) => t.name).filter((n): n is ToolName => n in allTools)\n\t\t: defaultActiveToolNames;\n\n\tlet agent: Agent;\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tonPayload: async (payload, currentModel) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload, currentModel);\n\t\t},\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getRetrySettings().maxDelayMs,\n\t\tgetApiKey: async (provider) => {\n\t\t\t// Use the provider argument from the in-flight request;\n\t\t\t// agent.state.model may already be switched mid-turn.\n\t\t\tconst resolvedProvider = provider || agent.state.model?.provider;\n\t\t\tif (!resolvedProvider) {\n\t\t\t\tthrow new Error(\"No model selected\");\n\t\t\t}\n\t\t\tconst authMode = modelRegistry.getProviderAuthMode(resolvedProvider);\n\t\t\tif (authMode === \"externalCli\" || authMode === \"none\") {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Retry key resolution with backoff to handle transient network failures\n\t\t\t// (e.g., OAuth token refresh failing due to brief connectivity loss).\n\t\t\tconst maxAttempts = 3;\n\t\t\tconst baseDelayMs = 2000;\n\t\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\t\tconst key = await modelRegistry.getApiKeyForProvider(resolvedProvider);\n\t\t\t\tif (key) return key;\n\n\t\t\t\t// On the last attempt, fall through to error handling below\n\t\t\t\tif (attempt >= maxAttempts) break;\n\n\t\t\t\t// Only retry if credentials exist (network issue) — no point retrying\n\t\t\t\t// when there are genuinely no credentials configured.\n\t\t\t\tconst hasAuth = modelRegistry.authStorage.hasAuth(resolvedProvider);\n\t\t\t\tconst model = agent.state.model;\n\t\t\t\tconst isOAuth = model && modelRegistry.isUsingOAuth(model);\n\t\t\t\tif (!hasAuth && !isOAuth) break;\n\n\t\t\t\t// Wait with exponential backoff before retrying\n\t\t\t\tawait new Promise(resolve => setTimeout(resolve, baseDelayMs * attempt));\n\t\t\t}\n\n\t\t\t// All retries exhausted — throw descriptive error\n\t\t\t// Check if credentials exist but are temporarily backed off\n\t\t\t// (e.g., after a 429 quota exhaustion). Provide a specific error\n\t\t\t// so the retry handler knows this is transient, not a permanent\n\t\t\t// auth failure.\n\t\t\tconst hasAuth = modelRegistry.authStorage.hasAuth(resolvedProvider);\n\t\t\tif (hasAuth) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`All credentials for \"${resolvedProvider}\" are temporarily backed off due to rate limiting. ` +\n\t\t\t\t\t\t`The request will be retried automatically when backoff expires.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst model = agent.state.model;\n\t\t\tconst isOAuth = model && modelRegistry.isUsingOAuth(model);\n\t\t\tif (isOAuth) {\n\t\t\t\t// If credentials exist but are all in a backoff window (quota / rate-limit),\n\t\t\t\t// surface a specific message instead of the misleading \"Authentication failed\".\n\t\t\t\tif (modelRegistry.authStorage.areAllCredentialsBackedOff(resolvedProvider)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Rate limit in effect for \"${resolvedProvider}\". ` +\n\t\t\t\t\t\t\t`Please wait before retrying or switch to a different model.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Authentication failed for \"${resolvedProvider}\". ` +\n\t\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t\t`Run '/login ${resolvedProvider}' to re-authenticate.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t`No API key found for \"${resolvedProvider}\". ` +\n\t\t\t\t\t`Set an API key environment variable or run '/login ${resolvedProvider}'.`,\n\t\t\t);\n\t\t},\n\t});\n\n\t// Restore messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.replaceMessages(existingSession.messages);\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools: options.customTools,\n\t\tmodelRegistry,\n\t\tinitialActiveToolNames,\n\t\textensionRunnerRef,\n\t});\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
1
+ {"version":3,"file":"sdk.js","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport { Agent, type AgentMessage, type ThinkingLevel } from \"@gsd/pi-agent-core\";\nimport type { Message, Model } from \"@gsd/pi-ai\";\nimport { getAgentDir, getDocsPath } from \"../config.js\";\nimport { AgentSession } from \"./agent-session.js\";\nimport { AuthStorage } from \"./auth-storage.js\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.js\";\nimport type { ExtensionRunner, LoadExtensionsResult, ToolDefinition } from \"./extensions/index.js\";\nimport { convertToLlm } from \"./messages.js\";\nimport { ModelRegistry } from \"./model-registry.js\";\nimport { findInitialModel } from \"./model-resolver.js\";\nimport type { ResourceLoader } from \"./resource-loader.js\";\nimport { DefaultResourceLoader } from \"./resource-loader.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { SettingsManager } from \"./settings-manager.js\";\nimport { time } from \"./timings.js\";\nimport {\n\tallTools,\n\tbashTool,\n\tcodingTools,\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\teditTool,\n\tfindTool,\n\tgrepTool,\n\thashlineCodingTools,\n\thashlineEditTool,\n\thashlineReadTool,\n\tcreateHashlineCodingTools,\n\tcreateHashlineEditTool,\n\tcreateHashlineReadTool,\n\tlsTool,\n\treadOnlyTools,\n\treadTool,\n\ttype Tool,\n\ttype ToolName,\n\twriteTool,\n} from \"./tools/index.js\";\n\nexport interface CreateAgentSessionOptions {\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.pi/agent */\n\tagentDir?: string;\n\n\t/** Auth storage for credentials. Default: AuthStorage.create(agentDir/auth.json) */\n\tauthStorage?: AuthStorage;\n\t/** Model registry. Default: new ModelRegistry(authStorage, agentDir/models.json) */\n\tmodelRegistry?: ModelRegistry;\n\n\t/** Model to use. Default: from settings, else first available */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/** Built-in tools to use. Default: codingTools [read, bash, edit, write] */\n\ttools?: Tool[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tSlashCommandInfo,\n\tSlashCommandLocation,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.js\";\nexport type { PromptTemplate } from \"./prompt-templates.js\";\nexport type { Skill } from \"./skills.js\";\nexport type { Tool } from \"./tools/index.js\";\n\nexport {\n\t// Pre-built tools (use process.cwd())\n\treadTool,\n\tbashTool,\n\teditTool,\n\twriteTool,\n\tgrepTool,\n\tfindTool,\n\tlsTool,\n\tcodingTools,\n\treadOnlyTools,\n\tallTools as allBuiltInTools,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n\t// Hashline edit mode\n\thashlineCodingTools,\n\thashlineEditTool,\n\thashlineReadTool,\n\tcreateHashlineCodingTools,\n\tcreateHashlineEditTool,\n\tcreateHashlineReadTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@gsd/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [readTool, bashTool],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst agentDir = options.agentDir ?? getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\t// Use provided or create AuthStorage and ModelRegistry\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst authStorage = options.authStorage ?? AuthStorage.create(authPath);\n\tconst modelRegistry = options.modelRegistry ?? new ModelRegistry(authStorage, modelsPath);\n\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd);\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRegistry.find(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel && (await modelRegistry.getApiKey(restoredModel))) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use findInitialModel (checks settings default, then provider defaults)\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelRegistry,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `No models available. Use /login or set an API key environment variable. See ${join(getDocsPath(), \"providers.md\")}. Then use /model to select a model.`;\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to settings default\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model || !model.reasoning) {\n\t\tthinkingLevel = \"off\";\n\t}\n\n\tconst editMode = settingsManager.getEditMode();\n\tconst defaultActiveToolNames: ToolName[] = editMode === \"hashline\"\n\t\t? [\"hashline_read\", \"bash\", \"hashline_edit\", \"write\", \"lsp\"]\n\t\t: [\"read\", \"bash\", \"edit\", \"write\", \"lsp\"];\n\tconst initialActiveToolNames: ToolName[] = options.tools\n\t\t? options.tools.map((t) => t.name).filter((n): n is ToolName => n in allTools)\n\t\t: defaultActiveToolNames;\n\n\tlet agent: Agent;\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tonPayload: async (payload, currentModel) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload, currentModel);\n\t\t},\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getRetrySettings().maxDelayMs,\n\t\texternalToolExecution: (m) => modelRegistry.getProviderAuthMode(m.provider) === \"externalCli\",\n\t\tgetApiKey: async (provider) => {\n\t\t\t// Use the provider argument from the in-flight request;\n\t\t\t// agent.state.model may already be switched mid-turn.\n\t\t\tconst resolvedProvider = provider || agent.state.model?.provider;\n\t\t\tif (!resolvedProvider) {\n\t\t\t\tthrow new Error(\"No model selected\");\n\t\t\t}\n\t\t\tconst authMode = modelRegistry.getProviderAuthMode(resolvedProvider);\n\t\t\tif (authMode === \"externalCli\" || authMode === \"none\") {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Retry key resolution with backoff to handle transient network failures\n\t\t\t// (e.g., OAuth token refresh failing due to brief connectivity loss).\n\t\t\tconst maxAttempts = 3;\n\t\t\tconst baseDelayMs = 2000;\n\t\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\t\tconst key = await modelRegistry.getApiKeyForProvider(resolvedProvider);\n\t\t\t\tif (key) return key;\n\n\t\t\t\t// On the last attempt, fall through to error handling below\n\t\t\t\tif (attempt >= maxAttempts) break;\n\n\t\t\t\t// Only retry if credentials exist (network issue) — no point retrying\n\t\t\t\t// when there are genuinely no credentials configured.\n\t\t\t\tconst hasAuth = modelRegistry.authStorage.hasAuth(resolvedProvider);\n\t\t\t\tconst model = agent.state.model;\n\t\t\t\tconst isOAuth = model && modelRegistry.isUsingOAuth(model);\n\t\t\t\tif (!hasAuth && !isOAuth) break;\n\n\t\t\t\t// Wait with exponential backoff before retrying\n\t\t\t\tawait new Promise(resolve => setTimeout(resolve, baseDelayMs * attempt));\n\t\t\t}\n\n\t\t\t// All retries exhausted — throw descriptive error\n\t\t\t// Check if credentials exist but are temporarily backed off\n\t\t\t// (e.g., after a 429 quota exhaustion). Provide a specific error\n\t\t\t// so the retry handler knows this is transient, not a permanent\n\t\t\t// auth failure.\n\t\t\tconst hasAuth = modelRegistry.authStorage.hasAuth(resolvedProvider);\n\t\t\tif (hasAuth) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`All credentials for \"${resolvedProvider}\" are temporarily backed off due to rate limiting. ` +\n\t\t\t\t\t\t`The request will be retried automatically when backoff expires.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst model = agent.state.model;\n\t\t\tconst isOAuth = model && modelRegistry.isUsingOAuth(model);\n\t\t\tif (isOAuth) {\n\t\t\t\t// If credentials exist but are all in a backoff window (quota / rate-limit),\n\t\t\t\t// surface a specific message instead of the misleading \"Authentication failed\".\n\t\t\t\tif (modelRegistry.authStorage.areAllCredentialsBackedOff(resolvedProvider)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Rate limit in effect for \"${resolvedProvider}\". ` +\n\t\t\t\t\t\t\t`Please wait before retrying or switch to a different model.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Authentication failed for \"${resolvedProvider}\". ` +\n\t\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t\t`Run '/login ${resolvedProvider}' to re-authenticate.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t`No API key found for \"${resolvedProvider}\". ` +\n\t\t\t\t\t`Set an API key environment variable or run '/login ${resolvedProvider}'.`,\n\t\t\t);\n\t\t},\n\t});\n\n\t// Restore messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.replaceMessages(existingSession.messages);\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools: options.customTools,\n\t\tmodelRegistry,\n\t\tinitialActiveToolNames,\n\t\textensionRunnerRef,\n\t});\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-coding-agent",
3
- "version": "2.46.1",
3
+ "version": "2.47.0",
4
4
  "description": "Coding agent CLI (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -266,7 +266,7 @@ describe("AuthStorage — areAllCredentialsBackedOff", () => {
266
266
  // ─── mismatched oauth credential for non-OAuth provider (#2083) ───────────────
267
267
 
268
268
  describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () => {
269
- it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async () => {
269
+ it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async (t) => {
270
270
  // Simulates the bug: OpenRouter credential stored as type:"oauth"
271
271
  // but OpenRouter is not a registered OAuth provider.
272
272
  const storage = inMemory({
@@ -278,12 +278,25 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
278
278
  },
279
279
  });
280
280
 
281
+ // Isolate from any real OPENROUTER_API_KEY in the environment so the
282
+ // fall-through to env / fallback finds nothing and returns undefined.
283
+ const origEnv = process.env.OPENROUTER_API_KEY;
284
+ delete process.env.OPENROUTER_API_KEY;
285
+ t.after(() => {
286
+ if (origEnv === undefined) {
287
+ delete process.env.OPENROUTER_API_KEY;
288
+ } else {
289
+ process.env.OPENROUTER_API_KEY = origEnv;
290
+ }
291
+ });
292
+
281
293
  // Before the fix, getApiKey returns undefined because
282
294
  // resolveCredentialApiKey calls getOAuthProvider("openrouter") → null → undefined.
283
295
  // The key in the oauth credential is never extracted.
284
296
  const key = await storage.getApiKey("openrouter");
285
297
  // After the fix, the oauth credential with an unrecognised provider
286
298
  // should be skipped, and getApiKey should fall through to env / fallback.
299
+ // With no env var and no fallback resolver configured, the result is undefined.
287
300
  assert.equal(key, undefined);
288
301
  });
289
302
 
@@ -312,7 +325,7 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
312
325
  assert.equal(key, "sk-or-v1-env-key");
313
326
  });
314
327
 
315
- it("falls through to fallback resolver when openrouter has type:oauth credential", async () => {
328
+ it("falls through to fallback resolver when openrouter has type:oauth credential", async (t) => {
316
329
  const storage = inMemory({
317
330
  openrouter: {
318
331
  type: "oauth",
@@ -322,6 +335,18 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
322
335
  },
323
336
  });
324
337
 
338
+ // Isolate from any real OPENROUTER_API_KEY so env fallback is skipped
339
+ // and the fallback resolver is reached.
340
+ const origEnv = process.env.OPENROUTER_API_KEY;
341
+ delete process.env.OPENROUTER_API_KEY;
342
+ t.after(() => {
343
+ if (origEnv === undefined) {
344
+ delete process.env.OPENROUTER_API_KEY;
345
+ } else {
346
+ process.env.OPENROUTER_API_KEY = origEnv;
347
+ }
348
+ });
349
+
325
350
  storage.setFallbackResolver((provider) =>
326
351
  provider === "openrouter" ? "sk-or-v1-fallback" : undefined,
327
352
  );
@@ -326,6 +326,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
326
326
  transport: settingsManager.getTransport(),
327
327
  thinkingBudgets: settingsManager.getThinkingBudgets(),
328
328
  maxRetryDelayMs: settingsManager.getRetrySettings().maxDelayMs,
329
+ externalToolExecution: (m) => modelRegistry.getProviderAuthMode(m.provider) === "externalCli",
329
330
  getApiKey: async (provider) => {
330
331
  // Use the provider argument from the in-flight request;
331
332
  // agent.state.model may already be switched mid-turn.
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glittercowboy/gsd",
3
- "version": "2.46.1",
3
+ "version": "2.47.0",
4
4
  "piConfig": {
5
5
  "name": "gsd",
6
6
  "configDir": ".gsd"