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