Skip to content

Commit 2f781ed

Browse files
anthhubclaude
andcommitted
fix: sync notebook types and add permission demo to main.ts
- Update notebook Tool interface with ToolCategory field - Update notebook PermissionRule with PermissionRuleSource field - Add category: "builtin" to FileReadTool and BashTool in notebook - Add permission system demo to main.ts (rules, context, checkPermission) - main.ts now validates all three type modules: messages, tools, permissions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2d96386 commit 2f781ed

2 files changed

Lines changed: 57 additions & 121 deletions

File tree

demo/main.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import type {
2727
ContentBlock,
2828
Tool,
2929
AppConfig,
30+
PermissionRule,
31+
PermissionContext,
32+
PermissionDecision,
3033
} from "./types/index.js";
3134

3235
// ─── 验证类型系统 ────────────────────────────────────────────────────────────
@@ -87,6 +90,48 @@ const apiTool = toolToAPIFormat(readTool);
8790
// 构建消息历史
8891
const messages: Message[] = [userMsg, assistantMsg];
8992

93+
// ─── 验证权限系统 ──────────────────────────────────────────────────────────
94+
95+
// 定义权限规则
96+
const permissionRules: PermissionRule[] = [
97+
{ toolName: "Read", behavior: "allow", source: "default", reason: "只读操作无风险" },
98+
{ toolName: "Bash", pattern: "rm -rf", behavior: "deny", source: "default", reason: "危险的删除操作" },
99+
{ toolName: "Bash", behavior: "ask", source: "default", reason: "Shell 命令可能有副作用" },
100+
];
101+
102+
// 构建权限上下文
103+
const permissionCtx: PermissionContext = {
104+
mode: "default",
105+
cwd: process.cwd(),
106+
rules: permissionRules,
107+
};
108+
109+
// 简单的权限检查函数(模拟真实 Claude Code 的 canUseTool)
110+
function checkPermission(
111+
toolName: string,
112+
input: Record<string, unknown>,
113+
rules: PermissionRule[]
114+
): PermissionDecision {
115+
for (const rule of rules) {
116+
if (rule.toolName !== "*" && rule.toolName !== toolName) continue;
117+
if (rule.pattern) {
118+
const command = String(input.command ?? "");
119+
if (!command.includes(rule.pattern)) continue;
120+
}
121+
if (rule.behavior === "allow") return { behavior: "allow" };
122+
if (rule.behavior === "deny") return { behavior: "deny", message: rule.reason ?? "Denied" };
123+
return { behavior: "ask", message: rule.reason ?? "需要确认" };
124+
}
125+
return { behavior: "ask", message: "默认需要确认" };
126+
}
127+
128+
// 测试权限检查
129+
const permTests = [
130+
{ tool: "Read", input: { file_path: "main.ts" } },
131+
{ tool: "Bash", input: { command: "ls -la" } },
132+
{ tool: "Bash", input: { command: "rm -rf /" } },
133+
];
134+
90135
// ─── 输出验证结果 ─────────────────────────────────────────────────────────
91136

92137
console.log("mini-claude - 类型系统验证");
@@ -110,5 +155,13 @@ console.log(` 模型: ${DEFAULT_CONFIG.model}`);
110155
console.log(` 最大 Token: ${DEFAULT_CONFIG.maxTokens}`);
111156
console.log(` 权限模式: ${DEFAULT_CONFIG.permissionMode}`);
112157
console.log();
158+
console.log(`权限系统 (模式: ${permissionCtx.mode}):`);
159+
permTests.forEach((tc) => {
160+
const decision = checkPermission(tc.tool, tc.input, permissionCtx.rules);
161+
const icon = decision.behavior === "allow" ? "✅" : decision.behavior === "deny" ? "🚫" : "❓";
162+
const cmd = (tc.input.command ?? tc.input.file_path) as string;
163+
console.log(` ${icon} ${tc.tool}("${cmd}") → ${decision.behavior}`);
164+
});
165+
console.log();
113166
console.log("类型系统验证通过!");
114167
console.log("下一步: 第 2 章 - 实现 Tool 接口和工具注册表");

notebooks/01-overview.ipynb

Lines changed: 4 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -295,36 +295,7 @@
295295
"execution_count": null,
296296
"metadata": {},
297297
"outputs": [],
298-
"source": [
299-
"// --- Tool 接口定义 ---\n",
300-
"\n",
301-
"interface ToolResult {\n",
302-
" content: string;\n",
303-
" isError?: boolean;\n",
304-
"}\n",
305-
"\n",
306-
"interface JSONSchemaProperty {\n",
307-
" type: \"string\" | \"number\" | \"boolean\" | \"array\" | \"object\";\n",
308-
" description?: string;\n",
309-
" enum?: string[];\n",
310-
"}\n",
311-
"\n",
312-
"interface JSONSchema {\n",
313-
" type: \"object\";\n",
314-
" properties: Record<string, JSONSchemaProperty>;\n",
315-
" required?: string[];\n",
316-
"}\n",
317-
"\n",
318-
"interface Tool {\n",
319-
" name: string;\n",
320-
" description: string;\n",
321-
" inputSchema: JSONSchema;\n",
322-
" call(input: Record<string, unknown>): Promise<ToolResult>;\n",
323-
" isReadOnly?: boolean;\n",
324-
"}\n",
325-
"\n",
326-
"console.log(\"✅ Tool 接口定义完成\");"
327-
]
298+
"source": "// --- Tool 接口定义 ---\n\ninterface ToolResult {\n content: string;\n isError?: boolean;\n}\n\ninterface JSONSchemaProperty {\n type: \"string\" | \"number\" | \"boolean\" | \"array\" | \"object\";\n description?: string;\n enum?: string[];\n}\n\ninterface JSONSchema {\n type: \"object\";\n properties: Record<string, JSONSchemaProperty>;\n required?: string[];\n}\n\n// 工具分类\ntype ToolCategory = \"builtin\" | \"mcp\" | \"skill\";\n\ninterface Tool {\n name: string;\n description: string;\n inputSchema: JSONSchema;\n call(input: Record<string, unknown>): Promise<ToolResult>;\n category?: ToolCategory;\n isReadOnly?: boolean;\n}\n\nconsole.log(\"✅ Tool 接口定义完成\");"
328299
},
329300
{
330301
"cell_type": "markdown",
@@ -340,66 +311,7 @@
340311
"execution_count": null,
341312
"metadata": {},
342313
"outputs": [],
343-
"source": [
344-
"// 实现一个真正的 FileRead 工具\n",
345-
"const FileReadTool: Tool = {\n",
346-
" name: \"Read\",\n",
347-
" description: \"读取指定文件的内容\",\n",
348-
" inputSchema: {\n",
349-
" type: \"object\",\n",
350-
" properties: {\n",
351-
" file_path: { type: \"string\", description: \"文件的路径\" },\n",
352-
" },\n",
353-
" required: [\"file_path\"],\n",
354-
" },\n",
355-
" isReadOnly: true,\n",
356-
" async call(input) {\n",
357-
" try {\n",
358-
" const path = input.file_path as string;\n",
359-
" const content = await Deno.readTextFile(path);\n",
360-
" return { content };\n",
361-
" } catch (e) {\n",
362-
" return { content: `Error: ${e}`, isError: true };\n",
363-
" }\n",
364-
" },\n",
365-
"};\n",
366-
"\n",
367-
"// 实现一个 Bash 工具\n",
368-
"const BashTool: Tool = {\n",
369-
" name: \"Bash\",\n",
370-
" description: \"执行 shell 命令\",\n",
371-
" inputSchema: {\n",
372-
" type: \"object\",\n",
373-
" properties: {\n",
374-
" command: { type: \"string\", description: \"要执行的命令\" },\n",
375-
" },\n",
376-
" required: [\"command\"],\n",
377-
" },\n",
378-
" isReadOnly: false, // shell 命令可能有副作用\n",
379-
" async call(input) {\n",
380-
" try {\n",
381-
" const cmd = new Deno.Command(\"sh\", {\n",
382-
" args: [\"-c\", input.command as string],\n",
383-
" stdout: \"piped\",\n",
384-
" stderr: \"piped\",\n",
385-
" });\n",
386-
" const output = await cmd.output();\n",
387-
" const stdout = new TextDecoder().decode(output.stdout);\n",
388-
" const stderr = new TextDecoder().decode(output.stderr);\n",
389-
" return {\n",
390-
" content: stdout + (stderr ? `\\nSTDERR: ${stderr}` : \"\"),\n",
391-
" isError: !output.success,\n",
392-
" };\n",
393-
" } catch (e) {\n",
394-
" return { content: `Error: ${e}`, isError: true };\n",
395-
" }\n",
396-
" },\n",
397-
"};\n",
398-
"\n",
399-
"console.log(\"✅ FileReadTool 和 BashTool 创建完成\");\n",
400-
"console.log(` Read: ${FileReadTool.description} (只读: ${FileReadTool.isReadOnly})`);\n",
401-
"console.log(` Bash: ${BashTool.description} (只读: ${BashTool.isReadOnly})`);"
402-
]
314+
"source": "// 实现一个真正的 FileRead 工具\nconst FileReadTool: Tool = {\n name: \"Read\",\n description: \"读取指定文件的内容\",\n inputSchema: {\n type: \"object\",\n properties: {\n file_path: { type: \"string\", description: \"文件的路径\" },\n },\n required: [\"file_path\"],\n },\n category: \"builtin\" as const,\n isReadOnly: true,\n async call(input) {\n try {\n const path = input.file_path as string;\n const content = await Deno.readTextFile(path);\n return { content };\n } catch (e) {\n return { content: `Error: ${e}`, isError: true };\n }\n },\n};\n\n// 实现一个 Bash 工具\nconst BashTool: Tool = {\n name: \"Bash\",\n description: \"执行 shell 命令\",\n inputSchema: {\n type: \"object\",\n properties: {\n command: { type: \"string\", description: \"要执行的命令\" },\n },\n required: [\"command\"],\n },\n category: \"builtin\" as const,\n isReadOnly: false, // shell 命令可能有副作用\n async call(input) {\n try {\n const cmd = new Deno.Command(\"sh\", {\n args: [\"-c\", input.command as string],\n stdout: \"piped\",\n stderr: \"piped\",\n });\n const output = await cmd.output();\n const stdout = new TextDecoder().decode(output.stdout);\n const stderr = new TextDecoder().decode(output.stderr);\n return {\n content: stdout + (stderr ? `\\nSTDERR: ${stderr}` : \"\"),\n isError: !output.success,\n };\n } catch (e) {\n return { content: `Error: ${e}`, isError: true };\n }\n },\n};\n\nconsole.log(\"✅ FileReadTool 和 BashTool 创建完成\");\nconsole.log(` Read: ${FileReadTool.description} (分类: ${FileReadTool.category}, 只读: ${FileReadTool.isReadOnly})`);\nconsole.log(` Bash: ${BashTool.description} (分类: ${BashTool.category}, 只读: ${BashTool.isReadOnly})`);"
403315
},
404316
{
405317
"cell_type": "code",
@@ -533,36 +445,7 @@
533445
"execution_count": null,
534446
"metadata": {},
535447
"outputs": [],
536-
"source": [
537-
"// --- 权限类型定义 ---\n",
538-
"\n",
539-
"type PermissionMode = \"default\" | \"auto\" | \"bypassPermissions\";\n",
540-
"type PermissionBehavior = \"allow\" | \"deny\" | \"ask\";\n",
541-
"\n",
542-
"interface PermissionRule {\n",
543-
" toolName: string; // \"*\" 匹配所有工具\n",
544-
" pattern?: string; // 可选的命令模式匹配\n",
545-
" behavior: PermissionBehavior;\n",
546-
" reason?: string;\n",
547-
"}\n",
548-
"\n",
549-
"// 定义一组权限规则\n",
550-
"const permissionRules: PermissionRule[] = [\n",
551-
" // 读操作:始终允许\n",
552-
" { toolName: \"Read\", behavior: \"allow\", reason: \"只读操作无风险\" },\n",
553-
" // 危险命令:始终拒绝\n",
554-
" { toolName: \"Bash\", pattern: \"rm -rf\", behavior: \"deny\", reason: \"危险的删除操作\" },\n",
555-
" { toolName: \"Bash\", pattern: \":(){ :|:& };:\", behavior: \"deny\", reason: \"Fork 炸弹\" },\n",
556-
" // 其他 Bash 命令:需要询问用户\n",
557-
" { toolName: \"Bash\", behavior: \"ask\", reason: \"Shell 命令可能有副作用\" },\n",
558-
"];\n",
559-
"\n",
560-
"console.log(\"✅ 权限规则定义完成:\");\n",
561-
"for (const rule of permissionRules) {\n",
562-
" const target = rule.pattern ? `${rule.toolName}(${rule.pattern})` : rule.toolName;\n",
563-
" console.log(` ${rule.behavior.toUpperCase().padEnd(5)} ${target} — ${rule.reason}`);\n",
564-
"}"
565-
]
448+
"source": "// --- 权限类型定义 ---\n\ntype PermissionMode = \"default\" | \"auto\" | \"bypassPermissions\";\ntype PermissionBehavior = \"allow\" | \"deny\" | \"ask\";\n\n// 权限规则来源\ntype PermissionRuleSource = \"userSettings\" | \"projectSettings\" | \"session\" | \"default\";\n\ninterface PermissionRule {\n toolName: string; // \"*\" 匹配所有工具\n pattern?: string; // 可选的命令模式匹配\n behavior: PermissionBehavior;\n reason?: string;\n source?: PermissionRuleSource;\n}\n\n// 定义一组权限规则\nconst permissionRules: PermissionRule[] = [\n // 读操作:始终允许\n { toolName: \"Read\", behavior: \"allow\", reason: \"只读操作无风险\", source: \"default\" },\n // 危险命令:始终拒绝\n { toolName: \"Bash\", pattern: \"rm -rf\", behavior: \"deny\", reason: \"危险的删除操作\", source: \"default\" },\n { toolName: \"Bash\", pattern: \":(){ :|:& };:\", behavior: \"deny\", reason: \"Fork 炸弹\", source: \"default\" },\n // 其他 Bash 命令:需要询问用户\n { toolName: \"Bash\", behavior: \"ask\", reason: \"Shell 命令可能有副作用\", source: \"default\" },\n];\n\nconsole.log(\"✅ 权限规则定义完成:\");\nfor (const rule of permissionRules) {\n const target = rule.pattern ? `${rule.toolName}(${rule.pattern})` : rule.toolName;\n console.log(` ${rule.behavior.toUpperCase().padEnd(5)} ${target} — ${rule.reason} [${rule.source}]`);\n}"
566449
},
567450
{
568451
"cell_type": "code",
@@ -791,4 +674,4 @@
791674
},
792675
"nbformat": 4,
793676
"nbformat_minor": 4
794-
}
677+
}

0 commit comments

Comments
 (0)