tencent cloud

Permission Control

Download
Focus Mode
Font Size
Last updated: 2026-09-30 18:41:49
AI-Translated
Version Requirements: This document applies to CodeBuddy Agent SDK v0.1.0 and later versions.
This document describes how to implement permission control in the SDK, including permission modes, the canUseTool callback, and tool allowlists/blocklists.

Overview

CodeBuddy Agent SDK provides multiple permission control mechanisms:
Mechanism
Description
Scenario
Permission mode
Globally control permission behavior
Quickly configure global policies
canUseTool callback
Dynamic approval at runtime
Interactive Permission Confirmation
Tool allowlist/blocklist
Declarative tool filtering
Static policy configuration

Permission Mode

Set the global permission behavior through permissionMode (TypeScript) or permission_mode (Python).

Available Modes

Mode
Description
default
Default mode. All tool operations require confirmation.
acceptEdits
Automatically approves file edits. Other operations still require confirmation.
plan
Planning mode. Only read-only tools are allowed.
bypassPermissions
Skip all permission checks. Use with caution.

Initial Configuration

TypeScript
Python
import { query } from '@tencent-ai/agent-sdk';

const q = query({
prompt: 'Help me refactor this code',
options: {
model: 'deepseek-v3.1',
permissionMode: 'acceptEdits' // Automatically approve edits
}
});

for await (const message of q) {
console.log(message);
}
import asyncio
from codebuddy_agent_sdk import query, CodeBuddyAgentOptions

async def main():
options = CodeBuddyAgentOptions(
model="deepseek-v3.1",
permission_mode="acceptEdits" # Automatically approve edits
)

async for message in query(prompt="Help me refactor this code", options=options):
print(message)

asyncio.run(main())

Dynamically Modifying Permission Mode

You can use the Session/Client API to dynamically modify the permission mode at runtime:
TypeScript
Python
import { unstable_v2_createSession } from '@tencent-ai/agent-sdk';

const session = unstable_v2_createSession({
model: 'deepseek-v3.1'
});

// Send the first message.
await session.send('Analyze this project');
for await (const msg of session.stream()) {
console.log(msg);
}

// Dynamically switch to acceptEdits mode to accelerate development.
// Note: This is an unstable API.
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 the 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
Python
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 };
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 | PermissionResultDeny

Complete Example: Interactive Approval

TypeScript
Python
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));

// Read-only tools are automatically allowed.
const readOnlyTools = ['Read', 'Glob', 'Grep'];
if (readOnlyTools.includes(toolName)) {
return { behavior: 'allow', updatedInput: input };
}

// Reject 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 execution of ${toolName}?`);

if (approved) {
return { behavior: 'allow', updatedInput: input };
} else {
return { behavior: 'deny', message: 'User rejected' };
}
}
}
});

for await (const message of q) {
console.log(message);
}
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}")

# Read-only tools are automatically allowed.
read_only_tools = ["Read", "Glob", "Grep"]
if tool_name in read_only_tools:
return PermissionResultAllow(updated_input=input_data)

# Reject 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 execution of {tool_name}? (y/n): ")

if answer.lower() == 'y':
return PermissionResultAllow(updated_input=input_data)
else:
return PermissionResultDeny(message="User rejected")

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 the input parameters of the tool in canUseTool:
TypeScript
Python
canUseTool: async (toolName, input) => {
if (toolName === 'Bash') {
// Add a security check before the command
return {
behavior: 'allow',
updatedInput: {
...input,
command: `set -e; ${input.command}`
}
};
}
return { behavior: 'allow', updatedInput: input };
}
async def can_use_tool(tool_name, input_data, options):
if tool_name == "Bash":
# Add a security check before the command
return PermissionResultAllow(
updated_input={
**input_data,
"command": f"set -e; {input_data.get('command', '')}"
}
)
return PermissionResultAllow(updated_input=input_data)

Handling AskUserQuestion

When the AI needs to ask the user a question, it calls the AskUserQuestion tool. You need to handle this tool in canUseTool.

Input Structure

{
questions: [
{
question: "Which database should be used?",
header: "Database",
options: [
{ label: "PostgreSQL", description: "Relational database" },
{ label: "MongoDB", description: "Document database" }
],
multiSelect: false
}
]
}

Returning Answers

TypeScript
Python
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 };
}
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 Allowlist/Blocklist

The SDK provides multiple tool filtering mechanisms:
Option
Description
Priority
tools
Built-in tool allowlist that fundamentally limits the available tool set
Highest
allowedTools
Tools allowed (pattern matching supported)
Medium
disallowedTools
Tools prohibited (pattern matching supported)
Medium

tools: Built-in Tool Allowlist

Use the tools option to fundamentally limit the built-in toolset available to CodeBuddy:
TypeScript
Python
const q = query({
prompt: 'Analyze the project structure',
options: {
model: 'deepseek-v3.1',
// Only these built-in tools are allowed.
tools: ['Read', 'Glob', 'Grep']
}
});

// Disable all built-in tools (use only MCP tools).
const q2 = query({
prompt: 'Use MCP tools to complete the task',
options: {
tools: [] // An empty array disables all built-in tools.
}
});
options = CodeBuddyAgentOptions(
model="deepseek-v3.1",
# Only these built-in tools are allowed.
tools=["Read", "Glob", "Grep"]
)

# Disable all built-in tools (use only MCP tools).
options2 = CodeBuddyAgentOptions(
model="deepseek-v3.1",
tools=[] # An empty array disables all built-in tools.
)

allowedTools/disallowedTools: Tool Filtering

Use allowedTools and disallowedTools for more fine-grained tool filtering with pattern matching support:
TypeScript
Python
const q = query({
prompt: 'Analyze the project structure',
options: {
model: 'deepseek-v3.1',
// Only these tools are allowed.
allowedTools: ['Read', 'Glob', 'Grep'],
// Or disallow these tools.
disallowedTools: ['Bash', 'Write', 'Edit']
}
});
options = CodeBuddyAgentOptions(
model="deepseek-v3.1",
# Only these tools are allowed.
allowed_tools=["Read", "Glob", "Grep"],
# Or disallow these tools.
disallowed_tools=["Bash", "Write", "Edit"]
)

Common Tool Names

Tool Name
Function
Read
Reads files
Write
Writes files
Edit
Edits files
Glob
File pattern matching
Grep
Content search
Bash
Executes Shell commands
Task
Sub-Agent tasks
WebFetch
Fetches web content
WebSearch
Web search
ToolSearch
Searches for lazily loaded tools

Best Practices

1. By default, the default mode is used: it provides the most complete permission control.
2. Read-only tasks use plan mode:
permissionMode: 'plan' // Only Read, Glob, and Grep are allowed.
3. Precise control with an allowlist:
allowedTools: ['Read', 'Glob', 'Grep'],
permissionMode: 'bypassPermissions' // Allowed tools run automatically.
4. Use interrupt for dangerous commands:
return {
behavior: 'deny',
message: 'Dangerous operation',
interrupt: true // Interrupt immediately to prevent the AI from continuing to try.
};
5. Avoid bypassPermissions in production environments: this mode skips all permission checks.

References

SDK Overview - Quick Start and Usage Examples
SDK Hook System - Finer-Grained Tool Control
TypeScript SDK Reference - Complete API Reference
Python SDK Reference - Complete API Reference


Help and Support

Was this page helpful?

Help us improve! Rate your documentation experience in 5 mins.

Feedback