| 4147 | * This function converts the permissionPromptTool into a CanUseToolFn that can be used in ask.tsx |
| 4148 | */ |
| 4149 | export function createCanUseToolWithPermissionPrompt( |
| 4150 | permissionPromptTool: PermissionPromptTool, |
| 4151 | ): CanUseToolFn { |
| 4152 | const canUseTool: CanUseToolFn = async ( |
| 4153 | tool, |
| 4154 | input, |
| 4155 | toolUseContext, |
| 4156 | assistantMessage, |
| 4157 | toolUseId, |
| 4158 | forceDecision, |
| 4159 | ) => { |
| 4160 | const mainPermissionResult = |
| 4161 | forceDecision ?? |
| 4162 | (await hasPermissionsToUseTool( |
| 4163 | tool, |
| 4164 | input, |
| 4165 | toolUseContext, |
| 4166 | assistantMessage, |
| 4167 | toolUseId, |
| 4168 | )) |
| 4169 | |
| 4170 | // If the tool is allowed or denied, return the result |
| 4171 | if ( |
| 4172 | mainPermissionResult.behavior === 'allow' || |
| 4173 | mainPermissionResult.behavior === 'deny' |
| 4174 | ) { |
| 4175 | return mainPermissionResult |
| 4176 | } |
| 4177 | |
| 4178 | // Race the permission prompt tool against the abort signal. |
| 4179 | // |
| 4180 | // Why we need this: The permission prompt tool may block indefinitely waiting |
| 4181 | // for user input (e.g., via stdin or a UI dialog). If the user triggers an |
| 4182 | // interrupt (Ctrl+C), we need to detect it even while the tool is blocked. |
| 4183 | // Without this race, the abort check would only run AFTER the tool completes, |
| 4184 | // which may never happen if the tool is waiting for input that will never come. |
| 4185 | // |
| 4186 | // The second check (combinedSignal.aborted) handles a race condition where |
| 4187 | // abort fires after Promise.race resolves but before we reach this check. |
| 4188 | const { signal: combinedSignal, cleanup: cleanupAbortListener } = |
| 4189 | createCombinedAbortSignal(toolUseContext.abortController.signal) |
| 4190 | |
| 4191 | // Check if already aborted before starting the race |
| 4192 | if (combinedSignal.aborted) { |
| 4193 | cleanupAbortListener() |
| 4194 | return { |
| 4195 | behavior: 'deny', |
| 4196 | message: 'Permission prompt was aborted.', |
| 4197 | decisionReason: { |
| 4198 | type: 'permissionPromptTool' as const, |
| 4199 | permissionPromptToolName: tool.name, |
| 4200 | toolResult: undefined, |
| 4201 | }, |
| 4202 | } |
| 4203 | } |
| 4204 | |
| 4205 | const abortPromise = new Promise<'aborted'>(resolve => { |
| 4206 | combinedSignal.addEventListener('abort', () => resolve('aborted'), { |