Dependency | Version Requirement |
Python | >= 3.10 |
CodeBuddy CLI | Installed |
asyncio, and all APIs are asynchronous.uv add codebuddy-agent-sdk
pip install codebuddy-agent-sdk
Variable Name | Description | Required |
CODEBUDDY_CODE_PATH | CodeBuddy CLI executable file path | Optional |
CODEBUDDY_CODE_PATHasync def query(*,prompt: str | AsyncIterable[dict[str, Any]],options: CodeBuddyAgentOptions | None = None,transport: Transport | None = None,) -> AsyncIterator[Message]:
Parameter | Type | Description |
prompt | str | AsyncIterable[dict] | Query prompt or user message stream |
options | CodeBuddyAgentOptions | Configuration options (optional) |
transport | Transport | Custom transport layer (optional) |
AsyncIterator[Message] - an asynchronous message iteratorfrom codebuddy_agent_sdk import query, AssistantMessage, TextBlockasync for message in query(prompt="What is 2+2?"):if isinstance(message, AssistantMessage):for block in message.content:if isinstance(block, TextBlock):print(block.text)
class CodeBuddySDKClient:def __init__(self,options: CodeBuddyAgentOptions | None = None,transport: Transport | None = None,): ...
async def connect(self,prompt: str | AsyncIterable[dict[str, Any]] | None = None) -> None:
async def query(self,prompt: str | AsyncIterable[dict[str, Any]],session_id: str = "default",) -> None:
async def receive_response(self) -> AsyncIterator[Message]:
async def receive_messages(self) -> AsyncIterator[Message]:
async def disconnect(self) -> None:
async with CodeBuddySDKClient() as client:await client.query("Hello!")async for msg in client.receive_response():print(msg)
async def mcp_server_status(self) -> list[McpServerStatus]:
AuthFlow object.async def authenticate(*,method_id: str = "external",environment: str | None = None,endpoint: str | None = None,codebuddy_code_path: str | None = None,env: dict[str, str] | None = None,timeout: float = 300.0,) -> AuthFlow:
Parameter | Type | Description |
method_id | str | Authentication method identifier (default: "external") |
environment | str | None | Predefined environment name |
endpoint | str | None | Custom endpoint URL (mutually exclusive with environment) |
codebuddy_code_path | str | None | CLI executable file path |
env | dict[str, str] | None | Additional environment variables |
timeout | float | Timeout for users to complete login (in seconds, default: 300) |
AuthFlow — an awaitable object that carries the login URLfrom codebuddy_agent_sdk import authenticate# Two-phase: obtain the URL → present it to the user → wait for completionauth = await authenticate()if auth.auth_url:print(f"Please visit: {auth.auth_url}")result = await authprint(f"Welcome, {result.userinfo.user_name}")# When already logged in, auth.auth_url is empty and await returns immediately.auth = await authenticate()result = await auth # Returns immediately if already logged in.# Custom timeoutauth = await authenticate()result = await auth.wait(timeout=60)
authenticate(). It implements the __await__ protocol and can be directly awaited with await.Attribute | Type | Description |
auth_url | str | Login URL (empty string when the user is logged in) |
method_id | str | None | Authentication method identifier |
async def wait(self, timeout: float | None = None) -> AuthenticateResponse:
async def cancel(self) -> None:
async def logout(*,environment: str | None = None,endpoint: str | None = None,codebuddy_code_path: str | None = None,env: dict[str, str] | None = None,) -> None:
from codebuddy_agent_sdk import logoutawait logout()
async def interrupt(self) -> None:
async def set_permission_mode(self, mode: str) -> None:
async def set_model(self, model: str | None = None) -> None:
@dataclassclass CodeBuddyAgentOptions:allowed_tools: list[str] = field(default_factory=list)disallowed_tools: list[str] = field(default_factory=list)system_prompt: str | AppendSystemPrompt | None = Nonemcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)permission_mode: PermissionMode | None = Nonecontinue_conversation: bool = Falseresume: str | None = Nonemax_turns: int | None = Nonemodel: str | None = Nonefallback_model: str | None = Nonecwd: str | Path | None = Nonecodebuddy_code_path: str | Path | None = Noneenv: dict[str, str] = field(default_factory=dict)extra_args: dict[str, str | None] = field(default_factory=dict)stderr: Callable[[str], None] | None = Nonehooks: dict[HookEvent, list[HookMatcher]] | None = Noneinclude_partial_messages: bool = Falsefork_session: bool = Falsepersist_session: bool = Trueagents: dict[str, AgentDefinition] | None = Nonesetting_sources: list[SettingSource] | None = Nonecan_use_tool: CanUseTool | None = None
Field | Type | Description |
allowed_tools | list[str] | Allowlist of Automatically Allowed Tools |
disallowed_tools | list[str] | Blocklist of Disallowed Tools |
system_prompt | str | AppendSystemPrompt | System Prompt Configuration |
mcp_servers | dict[str, McpServerConfig] | MCP server configuration |
permission_mode | PermissionMode | Permission Mode |
continue_conversation | bool | Continue the most recent session |
resume | str | Session ID to resume |
max_turns | int | Maximum number of conversation turns |
model | str | Specify the model |
fallback_model | str | Fallback model |
cwd | str | Path | Working Directory |
codebuddy_code_path | str | Path | CLI executable file path |
env | dict[str, str] | Environment Variable |
extra_args | dict[str, str | None] | Additional CLI arguments |
stderr | Callable[[str], None] | stderr callback |
hooks | dict[HookEvent, list[HookMatcher]] | Hook configuration |
include_partial_messages | bool | Include partial messages |
fork_session | bool | Fork session |
persist_session | bool | Whether to persist session records. Defaults to True. When set to False, sessions are kept only in memory and are not written to local transcripts, and file checkpoints are also skipped. Existing sessions can still be resumed, but they are no longer written. Requires CLI >= 2.125.1. |
agents | dict[str, AgentDefinition] | Custom Agent |
setting_sources | list[SettingSource] | Setting sources |
can_use_tool | CanUseTool | Permission callback function |
max_thinking_tokens | int | Maximum thinking tokens (deprecated, use thinking instead). |
thinking | ThinkingConfig | Thinking mode configuration: {"type": "adaptive"}, {"type": "enabled", "budget_tokens": N}, or {"type": "disabled"} |
effort | 'low' | 'medium' | 'high' | 'xhigh' | Model reasoning effort level |
PermissionMode = Literal["default", "acceptEdits", "plan", "bypassPermissions"]
Value | Description |
"default" | Default mode. All operations require confirmation. |
"acceptEdits" | Automatically approve file edits |
"plan" | Planning mode. Only read operations are allowed. |
"bypassPermissions" | Skip all permission checks |
PermissionResult = PermissionResultAllow | PermissionResultDeny@dataclassclass PermissionResultAllow:updated_input: dict[str, Any]behavior: Literal["allow"] = "allow"updated_permissions: list[dict[str, Any]] | None = None@dataclassclass PermissionResultDeny:message: strbehavior: Literal["deny"] = "deny"interrupt: bool = False
CanUseTool = Callable[[str, dict[str, Any], CanUseToolOptions],Awaitable[PermissionResult],]@dataclassclass CanUseToolOptions:tool_use_id: strsignal: Any | None = Noneagent_id: str | None = Nonesuggestions: list[dict[str, Any]] | None = Noneblocked_path: str | None = Nonedecision_reason: str | None = None
@dataclassclass AgentDefinition:description: str # Agent descriptionprompt: str # System prompttools: list[str] | None = None # Allowed toolsdisallowed_tools: list[str] | None = None # Disallowed toolsmodel: str | None = None # Model used
class McpStdioServerConfig(TypedDict):type: NotRequired[Literal["stdio"]]command: strargs: NotRequired[list[str]]env: NotRequired[dict[str, str]]McpServerConfig = McpStdioServerConfig
HookEvent = (Literal["PreToolUse"]| Literal["PostToolUse"]| Literal["UserPromptSubmit"]| Literal["Stop"]| Literal["SubagentStop"]| Literal["PreCompact"]| Literal["WorktreeCreate"]| Literal["WorktreeRemove"])
@dataclassclass HookMatcher:matcher: str | None = None # Matching pattern (regex supported)hooks: list[HookCallback] = field(default_factory=list)timeout: float | None = None # Timeout in seconds
HookCallback = Callable[[Any, str | None, HookContext],Awaitable[HookJSONOutput],]class HookContext(TypedDict):signal: Any | Noneclass SyncHookJSONOutput(TypedDict):continue_: NotRequired[bool]suppressOutput: NotRequired[bool]stopReason: NotRequired[str]decision: NotRequired[Literal["block"]]reason: NotRequired[str]
SettingSource = Literal["user", "project", "local"]
Value | Description | Position |
"user" | Global user settings | ~/.codebuddy/settings.json |
"project" | Project-shared settings | .codebuddy/settings.json |
"local" | Project-local settings | .codebuddy/settings.local.json |
setting_sources is not specified, the SDK does not load any file system configuration. This provides a completely clean runtime environment.# Default: No configuration is loaded (clean environment)async for msg in query(prompt="..."):pass# Load project configurationoptions = CodeBuddyAgentOptions(setting_sources=["project"])# Load all configurations (similar to CLI behavior)options = CodeBuddyAgentOptions(setting_sources=["user", "project", "local"])
@dataclassclass AppendSystemPrompt:append: str # Content to append to the default system prompt
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage | StreamEvent
TaskStartedMessage / TaskNotificationMessage are subclasses of SystemMessage. They are already covered by the union (isinstance / case SystemMessage() continues to match) and are exported separately for precise type checking at call sites.@dataclassclass SystemMessage:subtype: strdata: dict[str, Any]
run_in_background: true) enters the running state. It is a subclass of SystemMessage, so isinstance(msg, SystemMessage) still holds true.@dataclassclass TaskStartedMessage(SystemMessage):task_id: strdescription: struuid: strsession_id: strtool_use_id: str | None = Nonetask_type: str | None = None # "Bash" / "PowerShell" / "Workflow" / "Agent"
task_progress / task_notification (aligned with Claude Code's TaskUsage). This field has a value for sub-agent (task_type == "Agent") background tasks, while it is typically omitted for background shell tasks.class TaskUsage(TypedDict):total_tokens: inttool_uses: intduration_ms: int
usage and the most recent tool name last_tool_name. Background shell tasks do not emit progress.@dataclassclass TaskProgressMessage(SystemMessage):task_id: strdescription: strusage: TaskUsageuuid: strsession_id: strtool_use_id: str | None = Nonelast_tool_name: str | None = None
patch carries the fields changed in this update (at least status, and end_time is added for terminal states).task_updated (patch.status is terminal) without a corresponding task_notification. Consumers tracking "active tasks" should treat the terminal status of both equally—use the TERMINAL_TASK_STATUSES frozenset for the check ({"completed", "failed", "stopped", "killed"}).@dataclassclass TaskUpdatedMessage(SystemMessage):task_id: strpatch: dict[str, Any]status: TaskUpdatedStatus | None = None # pending/running/paused/completed/failed/killedsession_id: str | None = Noneuuid: str | None = None
stream-json long-connection mode, if a task completes after the result of the turn that triggered it, this message is actively pushed back to the same output stream. Consumers must continuously read with receive_messages() to receive it (query() and receive_response() stop at the first ResultMessage and will miss background completion events). usage is carried on sub-agent tasks and omitted for shell tasks.query() automatically disables background tasks: because query() stops at the first ResultMessage and closes the child process, it cannot receive cross-turn push-back events. The SDK automatically injects CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS=1 in the query() path (the run_in_background option for Bash / PowerShell / Agent is hidden or downgraded to foreground). If you need to keep background tasks under query(), explicitly set this variable in options.env or the process environment (any value, including "0") to override it. The CodeBuddySDKClient + receive_messages() path is not affected.@dataclassclass TaskNotificationMessage(SystemMessage):task_id: strstatus: Literal["completed", "failed", "stopped"]summary: struuid: strsession_id: strtool_use_id: str | None = Noneoutput_file: str | None = None # Path for writing background task stdout to disk (file mode)output_stderr_file: str | None = Noneusage: TaskUsage | None = None
task_id, and tool_use_id links back to the tool_use that initiated the task. Example (keep reading to receive the notification after overall completion):from codebuddy_agent_sdk import (CodeBuddySDKClient,CodeBuddyAgentOptions,TaskStartedMessage,TaskNotificationMessage,)async def run_background_tasks():options = CodeBuddyAgentOptions(permission_mode="bypassPermissions")async with CodeBuddySDKClient(options=options) as client:await client.query("Run two background commands in parallel and tell me the results when they finish")# Keep reading with receive_messages—do not use receive_response (it stops at the first result)async for msg in client.receive_messages():if isinstance(msg, TaskStartedMessage):print(f"[started] {msg.task_id} ({msg.task_type}): {msg.description}")elif isinstance(msg, TaskNotificationMessage):print(f"[done] {msg.task_id} status={msg.status} output={msg.output_file}")# break on your own after collecting all the tasks you care about
@dataclassclass UserMessage:content: str | list[ContentBlock]uuid: str | None = Noneparent_tool_use_id: str | None = None
@dataclassclass AssistantMessage:content: list[ContentBlock]model: strparent_tool_use_id: str | None = Noneerror: str | None = None
@dataclassclass ResultMessage:subtype: strduration_ms: intduration_api_ms: intis_error: boolnum_turns: intsession_id: strtotal_cost_usd: float | None = Noneusage: dict[str, Any] | None = Noneresult: str | None = Noneerrors: list[str] | None = None# Structured error info aligned with `errors` by index.# - Length matches `errors` when present; entry is None if no structured dimension available.# - Field is absent entirely when every entry would be None (backward compatible).# - Each entry may carry: status (HTTP), code (SDK/business), category (network/quota/auth/model_service/...), details (message).errors_info: list[dict[str, Any] | None] | None = None
@dataclassclass StreamEvent:uuid: strsession_id: strevent: dict[str, Any]parent_tool_use_id: str | None = None
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock@dataclassclass TextBlock:text: str@dataclassclass ThinkingBlock:thinking: strsignature: str@dataclassclass ToolUseBlock:id: strname: strinput: dict[str, Any]@dataclassclass ToolResultBlock:tool_use_id: strcontent: str | list[dict[str, Any]] | None = Noneis_error: bool | None = None
@dataclassclass AskUserQuestionInput:questions: list[AskUserQuestionQuestion]answers: dict[str, str] | None = None
@dataclassclass AskUserQuestionQuestion:question: str # Complete question text (should end with ?)header: str # Short label (up to 12 characters)options: list[AskUserQuestionOption]multi_select: bool # Whether to allow multiple selections
@dataclassclass AskUserQuestionOption:label: str # Display text (1-5 words)description: str # Option description
CodeBuddySDKError.class CodeBuddySDKError(Exception):"""Base exception for CodeBuddy SDK errors."""pass
class CLIConnectionError(CodeBuddySDKError):pass
class CLINotFoundError(CodeBuddySDKError):def __init__(self,message: str,platform: str | None = None,arch: str | None = None,): ...
Attribute | Type | Description |
platform | str | None | Current platform |
arch | str | None | Current architecture |
class CLIJSONDecodeError(CodeBuddySDKError):pass
class ProcessError(CodeBuddySDKError):pass
class CLIStartupError(CodeBuddySDKError):def __init__(self,message: str,stderr: str = "",exit_code: int | None = None,): ...
Attribute | Type | Description |
stderr | str | stderr output of the CLI process |
exit_code | int | None | Process exit code |
class ExecutionError(CodeBuddySDKError):def __init__(self, errors: list[str], subtype: str): ...
Attribute | Type | Description |
errors | list[str] | List of error messages |
subtype | str | Error subtype |
class AuthenticationError(CodeBuddySDKError):def __init__(self, error_type: str, message: str): ...
Attribute | Type | Description |
error_type | str | Error type (such as "timeout", "auth_failed") |
@dataclass(slots=True)class AuthenticateResponse:userinfo: UserInfo
@dataclass(slots=True)class UserInfo:user_id: struser_name: str = ""user_nickname: str = ""token: str = ""enterprise_id: str | None = Noneenterprise: str | None = None
@dataclass(slots=True)class McpServerStatus:name: strstatus: Literal["connected", "failed", "needs-auth", "pending"]server_info: dict[str, Any] | None = None
Was this page helpful?
You can also Contact sales or Submit a Ticket for help.
Help us improve! Rate your documentation experience in 5 mins.
Feedback