# 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](#overview)
- [Permission Modes](#permission-modes)
- [canUseTool Callback](#canusetool-callback)
- [Handling AskUserQuestion](#handling-askuserquestion)
- [Tool Whitelists/Blacklists](#tool-whitelistsblacklists)
- [Best Practices](#best-practices)
- [Related Documentation](#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

::: code-group

```typescript [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 [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:

::: code-group

```typescript [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 API
```

```python [Python]
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

::: code-group

```typescript [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 [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 | PermissionResultDeny
```

:::

### Complete Example: Interactive Approval

::: code-group

```typescript [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 [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`:

::: code-group

```typescript [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 [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

::: code-group

```typescript [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 [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

::: code-group

```typescript [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 [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

1. **Use `default` mode by default**: Provides the most complete permission control

2. **Use `plan` mode for read-only tasks**:
   ```typescript
   permissionMode: 'plan'  // Only allow Read, Glob, Grep
   ```

3. **Combine with whitelist for precise control**:
   ```typescript
   allowedTools: ['Read', 'Glob', 'Grep'],
   permissionMode: 'bypassPermissions'  // Allowed tools execute automatically
   ```

4. **Use `interrupt` for dangerous commands**:
   ```typescript
   return {
     behavior: 'deny',
     message: 'Dangerous operation',
     interrupt: true  // Immediately interrupt, don't let AI retry
   };
   ```

5. **Avoid `bypassPermissions` in production**: This mode skips all permission checks

## Related Documentation

- [SDK Overview](sdk.md) - Quick start and usage examples
- [SDK Hook System](sdk-hooks.md) - More granular tool control
- [TypeScript SDK Reference](sdk-typescript.md) - Complete API reference
- [Python SDK Reference](sdk-python.md) - Complete API reference
