source dump of claude code
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 127 lines 4.1 kB view raw
1import type { Tool, ToolUseContext } from 'src/Tool.js' 2import z from 'zod/v4' 3import { logForDebugging } from '../debug.js' 4import { lazySchema } from '../lazySchema.js' 5import type { 6 PermissionDecision, 7 PermissionDecisionReason, 8} from './PermissionResult.js' 9import { 10 applyPermissionUpdates, 11 persistPermissionUpdates, 12} from './PermissionUpdate.js' 13import { permissionUpdateSchema } from './PermissionUpdateSchema.js' 14 15export const inputSchema = lazySchema(() => 16 z.object({ 17 tool_name: z 18 .string() 19 .describe('The name of the tool requesting permission'), 20 input: z.record(z.string(), z.unknown()).describe('The input for the tool'), 21 tool_use_id: z 22 .string() 23 .optional() 24 .describe('The unique tool use request ID'), 25 }), 26) 27 28export type Input = z.infer<ReturnType<typeof inputSchema>> 29 30// Zod schema for permission results 31// This schema is used to validate the MCP permission prompt tool 32// so we maintain it as a subset of the real PermissionDecision type 33 34// Matches PermissionDecisionClassificationSchema in entrypoints/sdk/coreSchemas.ts. 35// Malformed values fall through to undefined (same pattern as updatedPermissions 36// below) so a bad string from the SDK host doesn't reject the whole decision. 37const decisionClassificationField = lazySchema(() => 38 z 39 .enum(['user_temporary', 'user_permanent', 'user_reject']) 40 .optional() 41 .catch(undefined), 42) 43 44const PermissionAllowResultSchema = lazySchema(() => 45 z.object({ 46 behavior: z.literal('allow'), 47 updatedInput: z.record(z.string(), z.unknown()), 48 // SDK hosts may send malformed entries; fall back to undefined rather 49 // than rejecting the entire allow decision (anthropics/claude-code#29440) 50 updatedPermissions: z 51 .array(permissionUpdateSchema()) 52 .optional() 53 .catch(ctx => { 54 logForDebugging( 55 `Malformed updatedPermissions from SDK host ignored: ${ctx.error.issues[0]?.message ?? 'unknown'}`, 56 { level: 'warn' }, 57 ) 58 return undefined 59 }), 60 toolUseID: z.string().optional(), 61 decisionClassification: decisionClassificationField(), 62 }), 63) 64 65const PermissionDenyResultSchema = lazySchema(() => 66 z.object({ 67 behavior: z.literal('deny'), 68 message: z.string(), 69 interrupt: z.boolean().optional(), 70 toolUseID: z.string().optional(), 71 decisionClassification: decisionClassificationField(), 72 }), 73) 74 75export const outputSchema = lazySchema(() => 76 z.union([PermissionAllowResultSchema(), PermissionDenyResultSchema()]), 77) 78 79export type Output = z.infer<ReturnType<typeof outputSchema>> 80 81/** 82 * Normalizes the result of a permission prompt tool to a PermissionDecision. 83 */ 84export function permissionPromptToolResultToPermissionDecision( 85 result: Output, 86 tool: Tool, 87 input: { [key: string]: unknown }, 88 toolUseContext: ToolUseContext, 89): PermissionDecision { 90 const decisionReason: PermissionDecisionReason = { 91 type: 'permissionPromptTool', 92 permissionPromptToolName: tool.name, 93 toolResult: result, 94 } 95 if (result.behavior === 'allow') { 96 const updatedPermissions = result.updatedPermissions 97 if (updatedPermissions) { 98 toolUseContext.setAppState(prev => ({ 99 ...prev, 100 toolPermissionContext: applyPermissionUpdates( 101 prev.toolPermissionContext, 102 updatedPermissions, 103 ), 104 })) 105 persistPermissionUpdates(updatedPermissions) 106 } 107 // Mobile clients responding from a push notification don't have the 108 // original tool input, so they send `{}` to satisfy the schema. Treat an 109 // empty object as "use original" so the tool doesn't run with no args. 110 const updatedInput = 111 Object.keys(result.updatedInput).length > 0 ? result.updatedInput : input 112 return { 113 ...result, 114 updatedInput, 115 decisionReason, 116 } 117 } else if (result.behavior === 'deny' && result.interrupt) { 118 logForDebugging( 119 `SDK permission prompt deny+interrupt: tool=${tool.name} message=${result.message}`, 120 ) 121 toolUseContext.abortController.abort() 122 } 123 return { 124 ...result, 125 decisionReason, 126 } 127}