SDK Permission Control
Version Requirement: This document is for CodeBuddy Agent SDK v0.1.0 and above.
This document explains how to implement permission control in the SDK, including permission modes, canUseTool callbacks, and tool whitelists/blacklists.
Table of Contents
- Overview
- Permission Modes
- canUseTool Callback
- Handling AskUserQuestion
- Tool Whitelists/Blacklists
- Best Practices
- Related Documentation
Overview
CodeBuddy Agent SDK provides multiple permission control mechanisms:
| Mechanism | Description | Use Case |
|---|---|---|
| Permission Modes | Global permission behavior control | Quick setup of overall policy |
| canUseTool Callback | Runtime dynamic approval | Interactive permission confirmation |
| Tool Whitelists/Blacklists | Declarative tool filtering | Static policy configuration |
Permission Modes
Set global permission behavior through permissionMode (TypeScript) or permission_mode (Python).
Available Modes
| Mode | Description |
|---|---|
default | Default mode, all tool operations require confirmation |
acceptEdits | Auto-approve file edits, other operations still require confirmation |
plan | Planning mode, only read-only tools allowed |
bypassPermissions | Skip all permission checks (use with caution) |
Initial Configuration
typescript
import { query } from '@tencent-ai/agent-sdk';
const q = query({
prompt: 'Help me refactor this code',
options: {
model: 'deepseek-v3.1',
permissionMode: 'acceptEdits' // Auto-approve edits
}
});
for await (const message of q) {
console.log(message);
}python
import asyncio
from codebuddy_agent_sdk import query, CodeBuddyAgentOptions
async def main():
options = CodeBuddyAgentOptions(
model="deepseek-v3.1",
permission_mode="acceptEdits" # Auto-approve edits
)
async for message in query(prompt="Help me refactor this code", options=options):
print(message)
asyncio.run(main())Dynamically Modifying Permission Mode
Using Session/Client API allows you to dynamically modify permission mode at runtime:
typescript
import { unstable_v2_createSession } from '@tencent-ai/agent-sdk';
const session = unstable_v2_createSession({
model: 'deepseek-v3.1'
});
// Send first message
await session.send('Analyze this project');
for await (const msg of session.stream()) {
console.log(msg);
}
// Dynamically switch to acceptEdits mode for faster development
// Note: This is an unstable APIpython
from codebuddy_agent_sdk import CodeBuddySDKClient, CodeBuddyAgentOptions
async def main():
options = CodeBuddyAgentOptions(model="deepseek-v3.1")
async with CodeBuddySDKClient(options=options) as client:
await client.query("Analyze this project")
async for msg in client.receive_response():
print(msg)
# Dynamically switch permission mode
await client.set_permission_mode("acceptEdits")
await client.query("Now help me modify the code")
async for msg in client.receive_response():
print(msg)canUseTool Callback
The canUseTool callback is triggered when a tool requires permission confirmation, allowing you to implement custom permission logic.
Callback Signature
typescript
type CanUseTool = (
toolName: string,
input: Record<string, unknown>,
options: CanUseToolOptions
) => Promise<PermissionResult>;
type CanUseToolOptions = {
signal: AbortSignal;
toolUseID: string;
agentID?: string;
suggestions?: PermissionUpdate[];
blockedPath?: string;
decisionReason?: string;
};
type PermissionResult =
| { behavior: 'allow'; updatedInput: Record<string, unknown> }
| { behavior: 'deny'; message: string; interrupt?: boolean };python
CanUseTool = Callable[
[str, dict[str, Any], CanUseToolOptions],
Awaitable[PermissionResult],
]
@dataclass
class CanUseToolOptions:
tool_use_id: str
signal: Any | None = None
agent_id: str | None = None
suggestions: list[dict[str, Any]] | None = None
blocked_path: str | None = None
decision_reason: str | None = None
PermissionResult = PermissionResultAllow | PermissionResultDenyComplete Example: Interactive Approval
typescript
import { query } from '@tencent-ai/agent-sdk';
const q = query({
prompt: 'Help me analyze this codebase',
options: {
model: 'deepseek-v3.1',
canUseTool: async (toolName, input, options) => {
console.log(`\n🔧 Tool request: ${toolName}`);
console.log(` Parameters:`, JSON.stringify(input, null, 2));
// Auto-allow read-only tools
const readOnlyTools = ['Read', 'Glob', 'Grep'];
if (readOnlyTools.includes(toolName)) {
return { behavior: 'allow', updatedInput: input };
}
// Deny dangerous commands
if (toolName === 'Bash') {
const command = input.command as string;
if (command.includes('rm -rf') || command.includes('sudo')) {
return {
behavior: 'deny',
message: 'Dangerous command rejected',
interrupt: true // Interrupt the entire session
};
}
}
// Other cases: simulate user confirmation
const approved = await promptUser(`Allow ${toolName} execution?`);
if (approved) {
return { behavior: 'allow', updatedInput: input };
} else {
return { behavior: 'deny', message: 'User denied' };
}
}
}
});
for await (const message of q) {
console.log(message);
}python
from codebuddy_agent_sdk import (
query, CodeBuddyAgentOptions,
CanUseToolOptions, PermissionResultAllow, PermissionResultDeny
)
async def can_use_tool(
tool_name: str,
input_data: dict,
options: CanUseToolOptions
):
print(f"\n🔧 Tool request: {tool_name}")
print(f" Parameters: {input_data}")
# Auto-allow read-only tools
read_only_tools = ["Read", "Glob", "Grep"]
if tool_name in read_only_tools:
return PermissionResultAllow(updated_input=input_data)
# Deny dangerous commands
if tool_name == "Bash":
command = input_data.get("command", "")
if "rm -rf" in command or "sudo" in command:
return PermissionResultDeny(
message="Dangerous command rejected",
interrupt=True # Interrupt the entire session
)
# Other cases: simulate user confirmation
answer = input(f"Allow {tool_name} execution? (y/n): ")
if answer.lower() == 'y':
return PermissionResultAllow(updated_input=input_data)
else:
return PermissionResultDeny(message="User denied")
async def main():
options = CodeBuddyAgentOptions(
model="deepseek-v3.1",
can_use_tool=can_use_tool
)
async for message in query(prompt="Help me analyze this codebase", options=options):
print(message)Modifying Tool Input
You can modify tool input parameters in canUseTool:
typescript
canUseTool: async (toolName, input) => {
if (toolName === 'Bash') {
// Add safety check before command
return {
behavior: 'allow',
updatedInput: {
...input,
command: `set -e; ${input.command}`
}
};
}
return { behavior: 'allow', updatedInput: input };
}python
async def can_use_tool(tool_name, input_data, options):
if tool_name == "Bash":
# Add safety check before command
return PermissionResultAllow(
updated_input={
**input_data,
"command": f"set -e; {input_data.get('command', '')}"
}
)
return PermissionResultAllow(updated_input=input_data)Handling AskUserQuestion
When AI needs to ask the user a question, it calls the AskUserQuestion tool. You need to handle this tool in canUseTool.
Input Structure
typescript
{
questions: [
{
question: "Which database to use?",
header: "Database",
options: [
{ label: "PostgreSQL", description: "Relational database" },
{ label: "MongoDB", description: "Document database" }
],
multiSelect: false
}
]
}Returning Answers
typescript
canUseTool: async (toolName, input) => {
if (toolName === 'AskUserQuestion') {
const questions = input.questions as any[];
const answers: Record<string, string> = {};
for (const q of questions) {
console.log(`Question: ${q.question}`);
for (let i = 0; i < q.options.length; i++) {
console.log(` ${i + 1}. ${q.options[i].label}`);
}
// Get user input
const choice = await getUserChoice();
answers[q.question] = q.options[choice].label;
}
return {
behavior: 'allow',
updatedInput: { ...input, answers }
};
}
return { behavior: 'allow', updatedInput: input };
}python
async def can_use_tool(tool_name, input_data, options):
if tool_name == "AskUserQuestion":
questions = input_data.get("questions", [])
answers = {}
for q in questions:
print(f"Question: {q['question']}")
for i, opt in enumerate(q["options"]):
print(f" {i + 1}. {opt['label']}")
# Get user input
choice = int(input("Select (1/2/...): ")) - 1
answers[q["question"]] = q["options"][choice]["label"]
return PermissionResultAllow(
updated_input={**input_data, "answers": answers}
)
return PermissionResultAllow(updated_input=input_data)Tool Whitelists/Blacklists
Use allowedTools and disallowedTools to declaratively control tool access.
Configuration Example
typescript
const q = query({
prompt: 'Analyze project structure',
options: {
model: 'deepseek-v3.1',
// Only allow these tools
allowedTools: ['Read', 'Glob', 'Grep'],
// Or disallow these tools
disallowedTools: ['Bash', 'Write', 'Edit']
}
});python
options = CodeBuddyAgentOptions(
model="deepseek-v3.1",
# Only allow these tools
allowed_tools=["Read", "Glob", "Grep"],
# Or disallow these tools
disallowed_tools=["Bash", "Write", "Edit"]
)Common Tool Names
| Tool Name | Functionality |
|---|---|
Read | Read files |
Write | Write files |
Edit | Edit files |
Glob | File pattern matching |
Grep | Content search |
Bash | Execute shell commands |
Task | Sub-agent tasks |
WebFetch | Fetch web content |
WebSearch | Web search |
Best Practices
Use
defaultmode by default: Provides the most complete permission controlUse
planmode for read-only tasks:typescriptpermissionMode: 'plan' // Only allow Read, Glob, GrepCombine with whitelist for precise control:
typescriptallowedTools: ['Read', 'Glob', 'Grep'], permissionMode: 'bypassPermissions' // Allowed tools execute automaticallyUse
interruptfor dangerous commands:typescriptreturn { behavior: 'deny', message: 'Dangerous operation', interrupt: true // Immediately interrupt, don't let AI retry };Avoid
bypassPermissionsin production: This mode skips all permission checks
Related Documentation
- SDK Overview - Quick start and usage examples
- SDK Hook System - More granular tool control
- TypeScript SDK Reference - Complete API reference
- Python SDK Reference - Complete API reference