Skip to main content
Reference

SDK References

Functions

query()

The SDK's main entry function. Creates an async iterator that streams messages in arrival order.
async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: QoderAgentOptions | None = None,
) -> AsyncIterator[Message]:
    ...

Parameters

ParameterTypeDescription
promptstr | AsyncIterable[dict[str, Any]]Pass a string for single-turn; pass an async iterable for multi-turn
optionsQoderAgentOptionsOptional session configuration

Return Value

Returns AsyncIterator[Message], consumed via async for.

QoderSDKClient

Class-based multi-turn conversation API. Suitable for scenarios that need to keep session state across turns or dynamically switch the model or permission mode.
client = QoderSDKClient(options=options)
await client.connect("Initial prompt.")
MethodDescription
client.query(...)Send user input with optional priority, message UUID, and should_query
client.receive_response()Consume messages until ResultMessage
client.receive_messages()Background async iterator that receives the entire session message stream
client.connect(prompt) / client.disconnect()Manually manage the connection
client.interrupt()Interrupt the current generation or tool execution
client.cancel_async_message(uuid)Cancel an asynchronous user message before execution
client.set_model(model)Switch the model at runtime
client.set_permission_mode(mode)Switch the permission mode at runtime
client.set_proxy(proxy)Set or clear the runtime proxy
client.apply_flag_settings(settings)Inject flag-level settings
client.get_server_info()Read the current session's initialization snapshot
client.list_plugins()Read the current CLI plugin inventory
client.supported_agents()List the agents currently available
client.get_mcp_status()Get the status of all MCP servers
client.set_mcp_servers(servers)Replace the MCP server configuration
client.reconnect_mcp_server(name)Reconnect a specific MCP server
client.toggle_mcp_server(name, enabled)Enable or disable an MCP server
client.get_available_models()Get the list of available models
client.get_usage_info()Get account quota and usage information

client.query()

async def query(
    prompt: str | AsyncIterable[dict[str, Any]],
    session_id: str = "default",
    *,
    priority: Literal["now", "next", "later"] = "next",
    message_uuid: str | None = None,
    should_query: bool = True,
) -> None:
    ...
Sends a user message in the current long-lived session. priority="now" stops the current response and handles the message immediately, "next" uses the next suitable point, and "later" waits until the current response finishes. With should_query=False, the message is added to the conversation without starting a response by itself; processing time still follows priority. message_uuid is used by cancellation and replay deduplication; do not reuse it within a session. An async iterable cannot also use message_uuid; set uuid on each message dictionary instead. session_id is inbound wire metadata and does not route input to another session; input always belongs to the connected CLI session.

client.interrupt()

async def interrupt() -> None:
    ...
Stops the current generation or tool execution without disconnecting the client. This method does not clear queued messages. See Interrupting the current turn.

client.cancel_async_message()

async def cancel_async_message(message_uuid: str) -> bool:
    ...
Cancels a queued user message by UUID. Returns True when cancelled and False when the message is not found or can no longer be cancelled. See Cancel a queued message.

client.get_server_info()

async def get_server_info() -> SDKControlInitializeResponse | None:
    ...
Returns the initialization snapshot captured at connection time, including commands, agents, and skills discovered for the current session. skills is a discovery inventory, not every skill currently invokable in the main session. See SDKControlInitializeResponse for the return type and Skills for discovery versus invocation semantics.

client.list_plugins()

async def list_plugins() -> list[PluginDetails]:
    ...
Returns the current CLI plugin inventory and each plugin's resource summary. This is separate from the initialization-resource snapshot returned by get_server_info(); see the Plugins return-value reference for the fields.

External session functions

Python uses dedicated async functions to inspect and modify sessions in a SessionStore. See External Session Storage for setup and behavior.
async def list_sessions_from_store(store, directory=None, limit=None, offset=0) -> list[SDKSessionInfo]: ...
async def get_session_info_from_store(store, session_id, directory=None) -> SDKSessionInfo | None: ...
async def get_session_messages_from_store(store, session_id, directory=None, limit=None, offset=0) -> list[SessionMessage]: ...
async def list_subagents_from_store(store, session_id, directory=None) -> list[str]: ...
async def get_subagent_messages_from_store(store, session_id, agent_id, directory=None, limit=None, offset=0) -> list[SessionMessage]: ...
async def rename_session_via_store(store, session_id, title, directory=None) -> None: ...
async def tag_session_via_store(store, session_id, tag, directory=None) -> None: ...
async def delete_session_via_store(store, session_id, directory=None) -> None: ...
async def fork_session_via_store(store, session_id, directory=None, up_to_message_id=None, title=None) -> ForkSessionResult: ...
FunctionPurposeStore requirement
list_sessions_from_storeList sessions by most recent modificationSessionStore.list_sessions
get_session_info_from_storeRead metadata for one sessionload
get_session_messages_from_storeRead the active conversation chainload
rename_session_via_store / tag_session_via_storeUpdate the title or tagappend
fork_session_via_storeCreate an independent session from existing historyload and append
list_subagents_from_storeList subagent IDsSessionStore.list_subkeys
get_subagent_messages_from_storeRead one subagent's messagesload; uses SessionStore.list_subkeys when available
delete_session_via_storeDelete a session and its child dataSessionStore.delete; does nothing when absent
directory identifies the project and defaults to the current directory. List and message functions support limit and offset.

import_session_to_store()

Copies an existing local session into an external store.
async def import_session_to_store(
    session_id: str,
    store: SessionStore,
    *,
    directory: str | Path | None = None,
    include_subagents: bool = True,
    batch_size: int = 500,
) -> None:
    ...

Types

QoderAgentOptions

Configuration object for query() and QoderSDKClient. The Python SDK uses snake_case field names.
FieldTypeDefaultDescription
authInternalAuthOptions | dict | NoneNoneAuthentication configuration
on_auth_expiredCallable | NoneNoneCallback when authentication expires; triggered at most once per session
toolslist[str] | ToolsPreset | NoneNoneTool set. Pass a string array to restrict available tools; built-in tool names are listed in Built-in Tool List
allowed_toolslist[str][]Tool allowlist; listed tools are pre-authorized
disallowed_toolslist[str][]Tool blocklist; takes priority over allowed_tools and permission_mode
can_use_toolCanUseToolNoneCustom tool permission callback
permission_modePermissionModeNoneSession permission mode
allow_dangerously_skip_permissionsboolFalseAllow skipping permission checks; used together with permission_mode="bypassPermissions"
permission_prompt_tool_namestr | NoneNoneMCP tool name for permission prompts; mutually exclusive with can_use_tool
modelstr | NoneNoneModel to use
resolve_modelModelPolicyProviderNoneDynamic model-selection callback. Passing it switches into dynamic-callback mode; see Model Policy
resolve_model_timeout_msint500Callback timeout (milliseconds); only effective when resolve_model is passed
system_promptstr | SystemPromptPreset | SystemPromptFile | NoneNoneSystem prompt
cwdstr | Path | NoneNoneWorking directory
envdict[str, str | None]{}Environment variables passed to the CLI process; for QODER_CONFIG_DIR, see User data directory
proxystr | NoneNoneProxy URL for the CLI process; supports http://, https://, socks5://, and socks://
cli_pathstr | Path | NoneNonePath to the qodercli executable
session_idstr | NoneNoneSpecify the session UUID
resumestr | NoneNoneSession ID to resume
continue_conversationboolFalseContinue the most recent session
fork_sessionboolFalseWhen used with resume, fork into a new session ID
session_storeSessionStore | NoneNoneSave session history to external storage so another host can resume it. See External Session Storage
session_store_flushSessionStoreFlushMode"batched"External write timing; ignored when session_store is not set
load_timeout_msint60000Timeout for each external store read while restoring a session
max_turnsint | NoneNoneMaximum conversation turns
include_partial_messagesboolFalseInclude StreamEvent streaming fragments; see Streaming Output
mcp_serversdict[str, McpServerConfig] | str | Path{}MCP server configuration; see MCP
allowed_mcp_server_nameslist[str][]Restrict which MCP servers are available
strict_mcp_configboolFalseStrict MCP validation
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneLifecycle hooks; see Hooks
agentsdict[str, AgentDefinition] | NoneNoneProgrammatically defined subagents; see Agents Reference
agentstr | NoneNoneAgent name used by the main session; see Agents Reference
settingsstr | Path | Settings | NoneNoneInline Settings object or settings file path
setting_sourceslist[SettingSource] | NoneNoneWhich filesystem settings to load
add_dirslist[str | Path][]Additional directories accessible to the AI
extra_argsdict[str, str | None]{}Additional arguments passed to the CLI
pluginslist[SdkPluginConfig][]Load local plugins; see Plugins
skillslist[str] | Literal["all"] | NoneNoneMain-session skill policy: a list restricts model context and invocation, [] disables all, and "all" enables all; see Skills
enable_file_checkpointingbool | NoneNoneEnable file checkpointing
experimental_cloud_agentCloudAgentOptionsNoneSwitch to Qoder Cloud Agent runtime (experimental); see Cloud Agent

SessionStore

Async protocol implemented by external storage adapters. append and load are required. The remaining methods are optional capabilities and may raise NotImplementedError.
class SessionStore(Protocol):
    async def append(
        self, key: SessionKey, entries: list[SessionStoreEntry]
    ) -> None: ...

    async def load(
        self, key: SessionKey
    ) -> list[SessionStoreEntry] | None: ...

    async def list_sessions(
        self, project_key: str
    ) -> list[SessionStoreListEntry]: ...

    async def delete(self, key: SessionKey) -> None: ...

    async def list_subkeys(
        self, key: SessionListSubkeysKey
    ) -> list[str]: ...
SessionKey contains project_key, session_id, and an optional subpath. SessionStoreEntry is an opaque JSON-safe dictionary. See External Session Storage for behavioral requirements.

SessionStoreFlushMode

SessionStoreFlushMode = Literal["batched", "eager"]

InMemorySessionStore

Built-in store for development and tests. It implements all optional capabilities, plus get_entries(key), size, and clear(). Its data is lost when the process exits.

SDKSessionInfo

Metadata returned by external session listing and lookup functions.
@dataclass
class SDKSessionInfo:
    session_id: str
    summary: str
    last_modified: int
    file_size: int | None = None
    custom_title: str | None = None
    first_prompt: str | None = None
    git_branch: str | None = None
    cwd: str | None = None
    tag: str | None = None
    created_at: int | None = None
file_size is the size of the serialized session data when available. Time fields use Unix epoch milliseconds.

SessionMessage

Historical user or assistant message returned by session and subagent message functions.
@dataclass
class SessionMessage:
    type: Literal["user", "assistant"]
    uuid: str
    session_id: str
    message: Any
    parent_tool_use_id: str | None = None

Settings

QoderAgentOptions.settings accepts either a settings file path or a dict[str, Any]. The SDK passes object keys to the CLI unchanged, so settings keys remain camelCase. The skill-related fields are listed below; defaults and exact behavior depend on the paired CLI version.
settings = {
    "skillOverrides": {
        "plugin:review": "off",
    },
    "skillListingMaxDescChars": 1200,
    "skillListingBudgetFraction": 0.01,
}
FieldDescription
skillListingMaxDescCharsCharacter limit for each description in the model-visible skill listing
skillListingBudgetFractionFraction of the context window available to the skill listing

skillOverrides

Controls discovery and visibility by skill name. Use a plugin-qualified plugin:skill key for plugin skills and a bare name for other sources. Matching checks the full name first, then falls back to the bare name.
ValueBehavior
"on"Default visibility: name and description are model-visible; invocation remains subject to session tool and permission policies
"name-only"The model sees the name but not the description
"user-invocable-only"Hidden from the model; users can still trigger it with /name
"off"Hidden from get_server_info()["skills"] and model context, and rejected by the Skill tool
See Skills for the complete session-level configuration.

SDKControlInitializeResponse

Initialization snapshot returned by client.get_server_info().
class SDKControlInitializeResponse(TypedDict):
    commands: list[InitializeCommandInfo]
    agents: list[InitializeAgentInfo]
    skills: NotRequired[list[InitializeSkillInfo]]
    output_style: str
    available_output_styles: list[str]
    models: list[ModelInfo]
    account: dict[str, Any]
    fast_mode_state: NotRequired[dict[str, Any]]

class InitializeSkillInfo(TypedDict):
    name: str
    description: NotRequired[str]
    source: NotRequired[str]
Here, skills is a metadata-bearing discovery inventory. In the message stream, SystemMessage(subtype="init").data["skills"] is a list[str] of names; it is not the same return structure.

Authentication

from qoder_agent_sdk import access_token, access_token_from_env, qodercli_auth
Factory functionDescription
access_token(token)Pass the PAT directly
access_token_from_env()Read from the default environment variable QODER_PERSONAL_ACCESS_TOKEN
access_token_from_env("MY_PAT")Read from a specified environment variable
qodercli_auth()Reuse the local qodercli login session
For convenience usage, see SDK Authentication.

Agents Reference

This page summarizes the stable configuration items related to SDK Agents. For getting started and usage scenarios, see Subagent Usage Guide.

Sources of Available Agents

The agents available in the current session may come from multiple sources:
SourceDescription
SDK registrationRegistered for the current session via QoderAgentOptions.agents
CLI built-inBuilt-in Agents registered when qodercli starts
User / project configAgent definitions in the user or project configuration directory
PluginAgents provided by loaded plugins
In the interactive CLI, you can run /agents to view the currently discovered Agents; on the command line, run qodercli agents list. In the Python SDK, after QoderSDKClient connects, call client.supported_agents() to get a summary of agents available in the current session.

QoderAgentOptions.agents

Type: dict[str, AgentDefinition] | None Registers custom Agents available in the current session. The dict key is the Agent name and the value is that Agent's definition.
The Agent tool is required: Custom subagents require the main session to delegate through the built-in Agent tool, so allowed_tools must include the Agent tool.
from qoder_agent_sdk import AgentDefinition, QoderAgentOptions


options = QoderAgentOptions(
    allowed_tools=["Agent"],
    agents={
        "reviewer": AgentDefinition(
            description="Reviews code quality and reports actionable findings.",
            prompt="Review the requested code and report concrete issues.",
            tools=["Read", "Grep", "Glob"],
        ),
    },
)
After registration, the model can invoke these subagents through the built-in Agent tool. The main session must include Agent in its tool set to delegate work; allowed_tools=["Agent"] is the required pre-authorization form. If you use tools to narrow the main session's available tools, include Agent there as well.

QoderAgentOptions.agent

Type: str | None Specifies which Agent identity the main session runs as. The value can be a name registered in agents, or a built-in / plugin Agent name discovered by the current CLI.
options = QoderAgentOptions(
    agents={
        "planner": AgentDefinition(
            description="Plans work before implementation.",
            prompt="Break work into steps, risks, and validation checks.",
            tools=["Read", "Grep", "Glob"],
        ),
    },
    agent="planner",
)
When set, the main session uses that Agent's prompt, model, and tool restrictions. When omitted, the default main-session behavior is used.

AgentDefinition

Definition of a custom Agent. The Python SDK's AgentDefinition is a dataclass whose field names use the protocol-style camelCase. The fields below are the stable capabilities currently covered and verified by the SDK.
from dataclasses import dataclass
from typing import Any, Literal


@dataclass
class AgentDefinition:
    description: str
    prompt: str
    tools: list[str] | None = None
    disallowedTools: list[str] | None = None
    model: str | None = None
    skills: list[str] | None = None
    mcpServers: list[str | dict[str, Any]] | None = None
    initialPrompt: str | None = None
    maxTurns: int | None = None
    effort: Literal["low", "medium", "high", "max"] | None = None
    permissionMode: PermissionMode | None = None
FieldTypeRequiredDescription
descriptionstrYesAgent purpose description; the model uses it to decide when to invoke the Agent
promptstrYesAgent system prompt
toolslist[str] | NoneNoTool allowlist for this Agent
disallowedToolslist[str] | NoneNoTools excluded from this Agent's tool set
modelstr | NoneNoModel override; "inherit" means inherit the main session model
mcpServerslist[str | dict[str, Any]] | NoneNoMCP server specs available to this Agent
skillslist[str] | NoneNoSkill names preloaded into the Agent context
initialPromptstr | NoneNoFirst user input automatically submitted when this Agent is used as the main session Agent
maxTurnsint | NoneNoMaximum API turns for the Agent
effort"low" | "medium" | "high" | "max" | NoneNoReasoning effort level
permissionModePermissionMode | NoneNoPermission mode for tool execution inside this Agent
The Python SDK serializes AgentDefinition into the initialize request via dataclasses.asdict(); qodercli then parses it according to the current Agent schema.

description

Describes what tasks the Agent is suitable for. It affects whether the model chooses this Agent.
description="Runs project tests, analyzes failing output, and suggests fixes."
Prefer a clear triggering scenario. Avoid broad descriptions such as Helpful assistant.

prompt

The Agent's system prompt. Use it to define the role, constraints, and output format.
prompt="""You are a security reviewer.
Check for authentication bypass, authorization bugs, injection risks, and secret leaks.
Return findings sorted by severity."""

tools

Tool allowlist for the Agent. When set, the Agent can only use the listed tools.
tools=["Read", "Grep", "Glob"]
When tools is omitted, the subagent default tool set is used. A subagent's tool set does not inherit trimming from the main session's allowed_tools.

disallowedTools

Excludes specific tools from the Agent's tool set.
disallowedTools=["Bash", "Write"]
When disallowedTools is omitted, the subagent does not inherit trimming from the main session's disallowed_tools. Usually avoid setting both tools and disallowedTools unless you explicitly know the final tool set.

model

Specifies the model for the Agent; when omitted, the session default model is used. At the Python type level it is str | None; the SDK does not restrict specific strings locally. Common model tiers include:
ValueTierDescriptionSuitable forCredit cost
"auto"Smart routingIntelligently selects the best model, balancing capability and costMost daily development work; recommended default~1.0x
"ultimate"UltimateExpert-level deep reasoning and thinking capabilityComplex system design and difficult analysis~1.6x
"performance"PerformanceAdvanced reasoning and high-quality outputCore implementation, architecture design, refactoring~1.1x
"efficient"EfficientStandard reasoning with good cost efficiencyBasic code generation, unit tests, daily Q&A~0.3x
"lite"LiteBasic reasoning, free to useQuick validation, simple logic, quick questions0x
Agents also support two special forms:
ValueDescription
"inherit"Inherit the main session model; usually not echoed in supported_agents()
Full model IDDirectly specify a model ID supported by the current CLI / backend

mcpServers

mcpServers: list[str | dict[str, Any]] | None
Limits or adds MCP servers available to this Agent. Each entry can be a session-level server name, or an inline server configuration mapping. Reference a session-level MCP server:
options = QoderAgentOptions(
    mcp_servers={
        "orders": {
            "command": "python",
            "args": ["servers/orders.py"],
        },
    },
    allowed_tools=["Agent"],
    agents={
        "support": AgentDefinition(
            description="Answers support questions using order tools.",
            prompt="Use order tools when needed and return a concise answer.",
            mcpServers=["orders"],
            tools=["mcp__orders__lookup_order"],
        ),
    },
)
Configure a dedicated MCP server for a specific Agent:
AgentDefinition(
    description="Searches the internal knowledge base.",
    prompt="Search the knowledge base and cite relevant entries.",
    mcpServers=[
        {
            "kb": {
                "command": "python",
                "args": ["servers/kb.py"],
            },
        },
    ],
    tools=["mcp__kb__search"],
)
When you only want to expose a specific MCP tool, also configure tools=["mcp__server__tool"] to avoid exposing every tool of that server to the Agent.

skills

A list of skill names preloaded into the Agent context. Plain skill names and plugin-qualified names are both supported.
skills=["review", "sdk-test-plugin:sdk-echo"]
For session-level skill behavior, see Skills.

initialPrompt

Automatically submitted as the first user input when this Agent becomes the main session Agent through QoderAgentOptions.agent.
initialPrompt="Start by scanning authentication and session management code."
This field only takes effect for the main session Agent. It is ignored when the Agent is invoked as a subagent through the Agent tool.

maxTurns

Limits the Agent's maximum API turns. Use it to control cost, execution time, and loop risk.
maxTurns=6

effort

EffortLevel = Literal["low", "medium", "high", "max"]
Controls the Agent's reasoning effort level. Higher effort is usually suitable for complex reviews, architecture analysis, and high-risk changes, but increases latency and token usage.

permissionMode

Controls the permission mode for tool execution inside this Agent. It uses the same semantics as the session-level permission_mode, but its scope is limited to this Agent. For the session-level permission chain, the priority of allowed_tools / disallowed_tools / can_use_tool, and examples, see Permission Control.
PermissionMode = Literal[
    "default",
    "acceptEdits",
    "plan",
    "bypassPermissions",
    "yolo",
    "dontAsk",
    "auto",
]
ValueMeaningSuitable for
"default"Standard permission behavior. Tool calls still pass through tool sets, allow / deny rules, runtime approval, or CLI default policyMost interactive subagents
"acceptEdits"Automatically accepts file-edit operations; other sensitive operations still follow the permission flowA subagent that is approved to modify workspace files
"bypassPermissions"Skips permission checks. High-risk mode, usually only for trusted automation or test environmentsControlled CI, temporary validation, one-off automation
"yolo"Compatibility alias for "bypassPermissions"; also skips permission checksCompatibility with older configs; not recommended for new code
"plan"Plan mode. Suitable for producing a plan first; by default it does not perform real changesPlanning, design, review, or cases where the subagent should not modify files
"dontAsk"Does not ask interactively; operations that are not pre-authorized or allowed by rules are deniedNon-interactive environments, or workflows that should fail instead of prompting
"auto"Runtime capability decides allow or deny automaticallyReduce confirmation interruptions while retaining runtime judgment
For permission semantics, see Permission Control.

AgentInfo

Agent summary returned by QoderSDKClient.supported_agents().
class AgentInfo(TypedDict):
    name: str
    description: str
    model: NotRequired[str | None]
The Python SDK does not currently export a TypedDict named AgentInfo; the return type of supported_agents() is list[dict[str, Any]]. The structure above is the stable field convention of the actually returned dicts.
FieldTypeDescription
namestrAgent name
descriptionstrAgent purpose description
modelstr | NoneAgent model override; usually empty when unset or when model="inherit"
from qoder_agent_sdk import AgentDefinition, QoderAgentOptions, QoderSDKClient


options = QoderAgentOptions(
    agents={
        "reviewer": AgentDefinition(
            description="Reviews code quality.",
            prompt="Review code and report findings.",
        ),
    },
)

client = QoderSDKClient(options=options)
await client.connect("List agents.")
agents = client.supported_agents()
await client.disconnect()
The returned list may include Agents registered through agents, and may also include built-in, project, user, or plugin Agents discovered by the current CLI. The actual available entries depend on the qodercli version and current configuration.

Context and Invocation Boundaries

  • Subagents use independent context and do not receive the parent session's full history.
  • The main information passed from the parent session to a subagent is the task prompt supplied to the Agent tool.
  • A subagent's intermediate tool results do not directly enter the parent session; the parent session receives the subagent's final response.
  • Subagents cannot spawn their own subagents, so do not put Agent in a subagent's tools.
  • initialPrompt only takes effect for the main session Agent specified by agent.

Model Policy

Dynamic model-selection capability of query(). Two modes: fixed-model (no resolve_model, uses options.model or backend default) and dynamic-callback (pass resolve_model, the callback decides the model before every LLM call). For full concepts, triggers and error handling see Model Policy.

options.resolve_model

Type: ModelPolicyProvider Entry point for dynamic-callback mode. Once passed, dynamic-callback mode is enabled and the SDK calls this callback before every LLM request to fetch the model. The model returned by the callback is the final model for that request; there is no automatic fallback.

options.resolve_model_timeout_ms

Type: int, default 500 Callback timeout (milliseconds). On timeout ModelPolicyTimeoutError is thrown and the query fails (no fallback). Only effective when resolve_model is passed.

ModelPolicyProvider

Callback function signature. May be synchronous or asynchronous.
from typing import Awaitable, Callable

ModelPolicyProvider = Callable[
    ["ModelPolicyContext"],
    "ModelPolicyResult | Awaitable[ModelPolicyResult]",
]
Triggering scenarios are distinguished by QoderModelPurpose:
ScenariopurposeNotes
Main conversation'main'Re-invoked between turns / tools — a session may trigger many times
Subagent'subagent'Subagents share the same provider
WebFetch tool'web_fetch'After WebFetch retrieves content, a second LLM call summarizes it
ImageGen tool'image_gen'Used to pick the image-generation model
Context compaction'compact'Before compaction starts, the callback is queried for the compaction model
BYOKanySet model to a CustomModel object to route via a third-party LLM
Behavioral notes:
  • The callback may be triggered many times within a single session (re-invoked before every turn / tool / sub-task).
  • The model returned by the callback is the final model for that request; the SDK does not re-validate it.
  • Throwing an exception or returning an empty model fails the query directly. See Model Policy — Error handling.

ModelPolicyContext

The context passed to the callback on every invocation.
class ModelPolicyContext(TypedDict, total=False):
    purpose: str  # QoderModelPurpose type
    sessionId: str
    availableModels: list[ModelInfo]
FieldTypeRequiredDescription
purposeQoderModelPurposeYesPurpose of this LLM call
sessionIdstrYesCurrent session ID; the same value is passed across callback invocations within a session, so it can be used as a cache / telemetry key
availableModelsModelInfo[]YesThe models currently available to the account in real time (the CLI carries the list with every get_model_policy request)

QoderModelPurpose

from typing import Literal

QoderModelPurpose = Literal[
    "main",
    "plan",
    "task",
    "compact",
    "title",
    "suggestion",
    "generate",
    "hook_prompt",
    "subagent",
    "web_fetch",
    "image_gen",
    "compression",
    "utility",
]
ValueTriggering scenario
'main'Main-conversation LLM call
'subagent'Subagent call
'web_fetch'Secondary LLM call triggered by the WebFetch tool
'image_gen'Image-generation call triggered by the ImageGen tool
'compact'Context compaction / summarization

ModelPolicyResult

The callback's return value.
class ModelPolicyResult(TypedDict, total=False):
    model: str | CustomModel
    parameters: dict[str, Any]
FieldTypeRequiredDescription
modelstr | CustomModelYesString: model identifier; object: BYOK credentials + model identifier
parametersdict[str, Any]NoPer-request model-parameter overrides. SDK control keys use camelCase
Supported parameters keys:
KeyTypeDescription
contextWindowintContext-window size in tokens for this LLM request. Choose a value supported by the selected model, usually from ModelInfo.context_config
reasoningEffortstrThinking / reasoning depth for this LLM request. Choose a level supported by the selected model, usually from ModelInfo.thinking_config. Common levels include "none", "low", "medium", "high", "xhigh", and "max"
model forms:
  • String — any model ID supported by the backend (such as auto / performance / glm51); the exact set of valid values is returned in real time by client.get_available_models(). Must be non-empty, otherwise the query fails.
  • CustomModel object (BYOK) — the SDK extracts the object's model field as the model identifier for this call, and forwards the remaining fields as credentials to the CLI for routing to a third-party LLM.

CustomModel

BYOK credentials. In the resolve_model callback, set the model field directly to this object, and that LLM request will be routed to a third-party provider.
class CustomModel(TypedDict, total=False):
    provider: str
    model: str
    api_key: str
    url: str
    style: str  # Allowed values: "openai" | "anthropic"
FieldTypeRequiredDescription
providerstrYesProvider key — must match a BYOKProviderInfo.key
modelstrYesModel identifier — extracted by the SDK as the model ID for this call
api_keystrYesThe API Key supplied by the user
urlstrNoOverride the default base URL
stylestrNoUpstream protocol style, e.g. "openai" / "anthropic"; defaults to "openai"
Notes:
  • provider must match a key in the catalog, otherwise backend authentication fails.
  • A wrong api_key causes authentication to fail, which fails the query directly (dynamic-callback mode does not fall back).
  • BYOK calls report total_cost_usd as 0 on the platform; token usage is reported as-is and billed by the provider side.

BYOK Catalog Types

The provider/model catalog returned by client.list_byok_providers().
class SDKControlGetByokConfigResponse(TypedDict, total=False):
    providers: list[BYOKProviderInfo]


class BYOKProviderInfo(TypedDict, total=False):
    key: str
    display_name: str
    api_key_url: str
    url: str
    fields: list[BYOKFieldInfo]
    types: list[BYOKModelTypeInfo]


class BYOKFieldInfo(TypedDict, total=False):
    key: str
    display_name: str
    type: str  # For example, "free_input"
    mandatory: bool


class BYOKModelTypeInfo(TypedDict, total=False):
    key: str
    display_name: str
    models: list[BYOKModelInfo]


class BYOKModelInfo(TypedDict, total=False):
    key: str
    display_name: str
    is_vl: bool
    is_reasoning: bool
    format: str
    max_input_tokens: int

BYOKProviderInfo

FieldTypeDescription
keystrProvider key — fill into CustomModel.provider for BYOK
display_namestrDisplay name
api_key_urlstrURL pointing the user where to obtain an API Key
urlstrBase URL for inference requests
fieldslist[BYOKFieldInfo]List of fields the provider requires the user to fill in
typeslist[BYOKModelTypeInfo]Model groups under this provider

BYOKFieldInfo

FieldTypeDescription
keystrField key (e.g. api_key)
display_namestrDisplay name shown to the user
typestrField type (e.g. "free_input")
mandatoryboolWhether the field is required

BYOKModelTypeInfo

FieldTypeDescription
keystrGroup key, common values: cp / tp / pg (optional)
display_namestrGroup display name
modelslist[BYOKModelInfo]Models within the group

BYOKModelInfo

FieldTypeDescription
keystrModel ID — fill into CustomModel.model
display_namestrDisplay name
is_vlboolWhether vision / multi-modal input is supported
is_reasoningboolWhether this is a reasoning model
formatstrUpstream protocol format (e.g. openai)
max_input_tokensintMaximum input token count

ModelInfo

Summary of an available model returned by client.get_available_models(). Also used as the element type of ModelPolicyContext.availableModels.
class ModelInfo(TypedDict):
    value: str
    displayName: str
    description: str
    isEnabled: bool
    isNew: NotRequired[bool]
    isFree: NotRequired[bool]
    priceFactor: NotRequired[float]
    context_config: NotRequired[ModelContextConfig]
    thinking_config: NotRequired[ModelThinkingConfig]
    promotion: NotRequired[ModelPromotion]
    serverModel: NotRequired[ServerModelJson]
FieldTypeDescription
valuestrModel identifier; usable as ModelPolicyResult.model or as the argument of client.set_model()
displayNamestrDisplay name
descriptionstrModel description text
isEnabledboolWhether currently available
isNewbool | NoneWhether this is a newly launched model
isFreebool | NoneWhether this is a free model
priceFactorfloat | NonePrice factor
context_configModelContextConfig | NoneContext-window configuration (tier label → token count)
thinking_configModelThinkingConfig | NoneThinking / reasoning configuration
promotionModelPromotion | NonePromotion / discount info from the model list API; absent means no promotion
serverModelServerModelJson | NoneRaw model entry from the model list API, forwarded verbatim by the CLI

ModelContextConfig

Context-window configuration keyed by tier label, such as "200K" or "1M".
ModelContextConfig = dict[str, ModelContextWindowEntry]

class ModelContextWindowEntry(TypedDict):
    token_count: int
    is_default: NotRequired[bool]
FieldTypeDescription
token_countintToken count for this tier
is_defaultbool | NoneWhether this is the default tier

ModelThinkingConfig

Thinking / reasoning configuration for a model.
class ModelThinkingConfig(TypedDict, total=False):
    disabled: ModelThinkingDisabled
    enabled: ModelThinkingEnabled

class ModelThinkingDisabled(TypedDict, total=False):
    description: str

class ModelThinkingEnabled(TypedDict, total=False):
    description: str
    efforts: dict[str, ModelEffortEntry]
    is_default: bool

class ModelEffortEntry(TypedDict, total=False):
    description: str
    is_default: bool
FieldTypeDescription
disabledModelThinkingDisabled | NoneConfiguration shown when thinking is disabled
enabledModelThinkingEnabled | NoneConfiguration shown when thinking is enabled, including effort levels
enabled.effortsdict[str, ModelEffortEntry] | NonePer-effort metadata, such as "low" / "high" descriptions and default markers

ModelPromotion

Promotion / discount info forwarded from the model list API. Nested field names stay in the server's snake_case form.
LocalizedModelText = dict[str, str]

class ModelPromotion(TypedDict, total=False):
    active: bool
    badge: LocalizedModelText
    description: LocalizedModelText
    discount_factor: float
    before_promotion_price_factor: float
    timezone: str
    rule_id: str
    window_start: str
    window_end: str
FieldTypeDescription
activebool | NoneWhether the promotion is currently active for this model
badgeLocalizedModelText | NoneShort localized label
descriptionLocalizedModelText | NoneLonger localized description
discount_factorfloat | NoneDiscounted price factor while the promotion is active
before_promotion_price_factorfloat | NoneOriginal price factor before the promotion
timezonestr | NoneIANA timezone used to evaluate the promotion window
rule_idstr | NoneServer rule identifier
window_startstr | NoneDaily window start, in HH:mm format
window_endstr | NoneDaily window end, in HH:mm format

ServerModelJson

Raw JSON-compatible model entry from the model list API. Use this when a server field is needed before it has a first-class ModelInfo field.
ServerModelJson = dict[str, ServerModelJsonValue]

UsageInfo

Account quota and usage snapshot returned by client.get_usage_info().
class UsageQuotaBucket(TypedDict, total=False):
    total: float
    used: float
    remaining: float
    percentage: float
    unit: str

class UsageAddOnQuotaBucket(UsageQuotaBucket, total=False):
    detailUrl: str

class UsageOrgResourcePackage(TypedDict, total=False):
    used: float
    cap: float
    remaining: float
    percentage: float
    available: bool
    unit: str

class UsageInfo(TypedDict, total=False):
    userId: str
    userType: str
    totalUsagePercentage: float
    isHighestTier: bool
    expiresAt: float
    upgradeUrl: NotRequired[str]
    userQuota: UsageQuotaBucket
    addOnQuota: NotRequired[UsageAddOnQuotaBucket]
    isQuotaExceeded: bool
    isPlanQuotaProrated: bool
    orgResourcePackage: UsageOrgResourcePackage
FieldTypeDescription
userIdstr | NoneAccount identifier
userTypestr | NonePlan tier (e.g. free, pro, teams)
totalUsagePercentagefloat | NoneOverall usage across all buckets, 0100
isHighestTierbool | NoneWhether the account is already on the highest plan
expiresAtfloat | NoneCurrent plan/quota expiry, Unix epoch milliseconds
upgradeUrlstr | NoneUpgrade page URL, when an upgrade is available
userQuotaUsageQuotaBucket | NoneIncluded plan quota bucket
addOnQuotaUsageAddOnQuotaBucket | NonePurchased add-on quota bucket, when the account has add-on quota (detailUrl links to the usage page when provided)
isQuotaExceededbool | NoneWhether all available quota is exhausted
isPlanQuotaProratedbool | NoneWhether the plan quota is prorated for the current period
orgResourcePackageUsageOrgResourcePackage | NoneOrganization-shared resource package (available indicates it can be drawn from)
Each bucket reports used / remaining / percentage against its total (or cap for the org package) in unit (typically credits). Missing fields, or fields with an unexpected runtime type, are omitted from the returned dict.

ModelPolicyTimeoutError

class ModelPolicyTimeoutError(Exception): ...
Thrown by the SDK when the resolve_model callback exceeds options.resolve_model_timeout_ms without returning. The query fails directly, with no fallback.

client.set_model()

async def set_model(self, model: str | None = None) -> None: ...
Switches the model in fixed-model mode at runtime. Takes effect on the next LLM call. Effective only in fixed-model mode; in dynamic-callback mode, calling it does not override the callback's result. Valid model IDs: see ModelInfo.value.

client.set_proxy()

async def set_proxy(self, proxy: str | None = None) -> None: ...
Sets or clears the proxy for the running qodercli session. Supports http://, https://, socks5://, and socks://. Pass None or an empty string to clear the proxy.

client.get_available_models()

async def get_available_models(self) -> list[ModelInfo]: ...
Fetches the latest model list available to the current account in real time. Always returns the latest result, no caching; returns an empty array (does not throw) when the list cannot be fetched temporarily. In dynamic-callback mode, ModelPolicyContext.availableModels already carries the same up-to-date list, so calling this method explicitly is unnecessary.

client.list_byok_providers()

async def list_byok_providers(self) -> list[BYOKProviderInfo] | None: ...
Returns the BYOK provider/model catalog available to the current account as an array:
  • Returns None: the CLI does not support this API (graceful fallback, no exception).
  • Returns an array (may be empty): the list of providers available to the current account (an empty array means the account has not enabled BYOK).
For field semantics, see BYOK Catalog Types.

client.get_usage_info()

async def get_usage_info(self) -> UsageInfo | None: ...
Fetches the current account's quota and usage information from the running CLI in real time.
  • Returns None: the CLI is unauthenticated, an older CLI version does not support this API, or the CLI did not return a dict (graceful fallback, no exception).
  • Returns a UsageInfo dict: the known account quota and usage fields whose runtime types are valid. Missing or invalid fields are omitted.
async with QoderSDKClient() as client:
    usage = await client.get_usage_info()
    if usage is not None:
        print(f"Plan: {usage['userType']}, used {usage['totalUsagePercentage']}%")
        user_quota = usage.get("userQuota") or {}
        print(f"Plan quota: {user_quota.get('remaining')}/{user_quota.get('total')} {user_quota.get('unit')} left")
For the return type, see UsageInfo.

CanUseTool

Host-defined custom tool permission approval callback.
from collections.abc import Awaitable, Callable
from typing import Any


CanUseTool = Callable[
    [str, dict[str, Any], ToolPermissionContext],
    Awaitable[PermissionResult],
]

ToolPermissionContext

import asyncio
from dataclasses import dataclass, field
from typing import Any


@dataclass
class ToolPermissionContext:
    signal: asyncio.Event | None = None
    suggestions: list[Any] = field(default_factory=list)
    blocked_path: str | None = None
    decision_reason: str | None = None
    decision_reason_type: str | None = None
    classifier_approvable: bool | None = None
    title: str | None = None
    display_name: str | None = None
    description: str | None = None
    tool_use_id: str | None = None
    agent_id: str | None = None
    exit_plan_mode: ExitPlanModeApprovalDetails | None = None
FieldTypeDescription
signalasyncio.Event | NoneSet when cancelled
suggestionslist[Any]Permission update suggestions from the CLI
blocked_pathstr | NoneFile path triggering authorization (file-related scenarios only)
decision_reasonstr | NoneHuman-readable authorization reason from the CLI
decision_reason_typestr | NonePermission reason classification
classifier_approvablebool | NoneWhether the current call can be auto-approved by the runtime classifier
title / display_name / descriptionstr | NoneHuman-readable authorization text generated at runtime
tool_use_idstr | NoneThis tool invocation's ID
agent_idstr | NoneSubagent ID initiating the call
exit_plan_modeExitPlanModeApprovalDetails | NoneApproval details when exiting plan mode
For full usage and examples, see Permission Control.

PermissionMode

PermissionMode = Literal[
    "default",
    "acceptEdits",
    "bypassPermissions",
    "yolo",
    "plan",
    "dontAsk",
    "auto",
]
ValueMeaningSuitable for
"default"Standard permission behavior. Tool calls are handled by tools, allow / deny rules, dynamic approval, or runtime policyMost interactive sessions
"acceptEdits"Automatically accepts file-edit operations; other sensitive operations still follow the permission flowSessions that are approved to modify the workspace
"bypassPermissions"Skips permission checks; must also set allow_dangerously_skip_permissions=TrueTrusted automation or test environments
"yolo"Compatibility alias for "bypassPermissions"; must also set allow_dangerously_skip_permissions=TrueCompatibility with older configs; not recommended for new code
"plan"Plan mode. Suitable for producing a plan first; by default it does not perform real changesPlanning, design, review
"dontAsk"Does not ask interactively; operations that are not pre-authorized or allowed by rules are deniedNon-interactive environments, or workflows that should fail instead of prompting
"auto"Runtime capability decides allow or deny automatically; safe workspace file edits may be auto-allowedReduce confirmation interruptions while retaining runtime judgment
For more details on the permission chain, see Permission Control.

PermissionResult

Return value of CanUseTool.
from dataclasses import dataclass
from typing import Any, Literal


@dataclass
class PermissionResultAllow:
    behavior: Literal["allow"] = "allow"
    updated_input: dict[str, Any] | None = None
    updated_permissions: list[PermissionUpdate | dict[str, Any]] | None = None
    decision_classification: PermissionDecisionClassification | None = None


@dataclass
class PermissionResultDeny:
    behavior: Literal["deny"] = "deny"
    message: str = ""
    interrupt: bool = False
    decision_classification: PermissionDecisionClassification | None = None


PermissionResult = PermissionResultAllow | PermissionResultDeny
allow.updated_input, when modified, replaces the actual parameters the tool receives. deny.interrupt=True denies and also interrupts the Agent. The tool_name received by can_use_tool is the full tool name, for example "Bash", "Read", "mcp__orders__lookup_order".

McpServerConfig

MCP server configuration, passed to QoderAgentOptions.mcp_servers.
McpServerConfig = (
    McpStdioServerConfig
    | McpSSEServerConfig
    | McpHttpServerConfig
    | McpSdkServerConfig
)

McpStdioServerConfig

class McpStdioServerConfig(TypedDict):
    type: NotRequired[Literal["stdio"]]
    command: str
    args: NotRequired[list[str]]
    env: NotRequired[dict[str, str]]
    tools: NotRequired[list[McpServerToolPolicy]]

McpSSEServerConfig

class McpSSEServerConfig(TypedDict):
    type: Literal["sse"]
    url: str
    headers: NotRequired[dict[str, str]]
    tools: NotRequired[list[McpServerToolPolicy]]

McpHttpServerConfig

class McpHttpServerConfig(TypedDict):
    type: Literal["http"]
    url: str
    headers: NotRequired[dict[str, str]]
    tools: NotRequired[list[McpServerToolPolicy]]

McpSdkServerConfig

class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: McpServer
    tools: NotRequired[list[McpServerToolPolicy]]
Returned by the create_sdk_mcp_server() factory; see MCP - In-Process Server.

McpServerToolPolicy

class McpServerToolPolicy(TypedDict):
    name: str
    permission_policy: Literal["always_allow", "always_ask", "always_deny"]

SdkPluginConfig

Load local plugins.
class SdkPluginConfig(TypedDict):
    type: Literal["local"]
    path: str
FieldTypeDescription
type"local"Currently only local is supported
pathstrAbsolute or relative path to the plugin directory

SettingSource

Controls which filesystem settings are loaded.
SettingSource = Literal["user", "project", "local"]
ValueMeaningLocation
"user"User-level global settings~/.qoder/settings.json
"project"Project shared settings (version controlled).qoder/settings.json
"local"Project local settings (gitignored).qoder/settings.local.json
When omitted, all sources are loaded per CLI defaults; pass [] to skip entirely.

Cloud Agent Reference

This section covers the experimental Cloud Agent runtime configuration types. For usage guide and examples, see Cloud Agent.

CloudAgentOptions

Type for QoderAgentOptions.experimental_cloud_agent. Cloud runtime agent / session reference configuration; full usage in Cloud Agent.
class CloudAgentOptions(TypedDict, total=False):
    agent: CloudAgentReference
    session: CloudSessionReference
    stream: CloudAgentStreamOptions
FieldTypeDescription
agent["id"]strReuse an existing Cloud Agent; mutually exclusive with agent["create"]
agent["create"]AgentCreateParamsCreate a new Cloud Agent; mutually exclusive with agent["id"]
session["id"]strReuse an existing Cloud session; cannot pass agent alongside
session["create"]CloudSessionCreateParamsCreate a new Cloud session; environment_id is required
stream["after_id"]strSSE replay starting event ID (also accepts afterId)
stream["delta_flush_interval_ms"]intDelta content merge/flush interval in ms (also accepts deltaFlushIntervalMs)
Mutual exclusion constraints:
  • agent["id"] and agent["create"] are mutually exclusive.
  • session["id"] and session["create"] are mutually exclusive.
  • When passing session["id"], agent must not be provided (the session already binds its agent).

AgentCreateParams (Cloud)

Request body for creating a Cloud Agent, corresponding to the Qoder Cloud OpenAPI agent create fields.
class AgentCreateParams(TypedDict, total=False):
    model: str
    name: str
    description: str | None
    system: str | None
    tools: list[BetaManagedAgentsAgentToolset20260401Params]
    mcp_servers: list[BetaManagedAgentsURLMCPServerParams]
    skills: list[BetaManagedAgentsSkillParams]
    metadata: dict[str, str]
FieldTypeDescription
modelstrModel identifier, e.g. "auto" / "ultimate" / "performance" / "efficient" / "lite"
namestrHuman-readable agent name
descriptionstr | NoneDescription
systemstr | NoneSystem prompt
toolssee belowBuilt-in toolset; enabled_tools controls the allowlist
mcp_serverssee belowURL-based MCP server connections
skillssee belowCustom user skills
metadatadict[str, str]Arbitrary key-value metadata

BetaManagedAgentsAgentToolset20260401Params

class BetaManagedAgentsAgentToolset20260401Params(TypedDict, total=False):
    type: Literal["agent_toolset_20260401"]
    enabled_tools: list[BetaManagedAgentsAgentToolName]


BetaManagedAgentsAgentToolName = Literal[
    "bash", "write", "glob", "web_fetch", "read", "edit", "grep", "web_search"
]

BetaManagedAgentsURLMCPServerParams

class BetaManagedAgentsURLMCPServerParams(TypedDict):
    name: str
    type: Literal["url"]
    url: str

BetaManagedAgentsSkillParams

class BetaManagedAgentsCustomSkillParams(TypedDict):
    skill_id: str
    type: Literal["custom"]

CloudSessionCreateParams

Request body for creating a Cloud session.
class CloudSessionCreateParams(TypedDict, total=False):
    environment_id: str
    resources: list[CloudSessionResource]
    title: str | None
    vault_ids: list[str]
    memory_store_ids: list[str]
FieldTypeDescription
environment_idstrContainer environment ID, required
resourcessee belowResources to mount into the session container (currently supports file)
titlestr | NoneSession title
vault_idslist[str]Credential vault IDs
memory_store_idslist[str]Memory store IDs

CloudSessionResource

class BetaManagedAgentsFileResourceParams(TypedDict, total=False):
    file_id: str
    type: Literal["file"]
    path: str

CloudSessionResource = BetaManagedAgentsFileResourceParams

CloudAgentStreamOptions

SSE replay and delta options. The Python SDK accepts both snake_case and camelCase.
class CloudAgentStreamOptions(TypedDict, total=False):
    after_id: str
    delta_flush_interval_ms: int
    # Compatibility aliases
    afterId: str
    deltaFlushIntervalMs: int

CloudAgentEventMessage

Under the Cloud runtime (options.experimental_cloud_agent), events forwarded from the Qoder Cloud session SSE stream. Full usage in Cloud Agent.
@dataclass
class CloudAgentEventMessage:
    event: str
    data: Any
    uuid: str
    session_id: str
    id: str | None = None
FieldTypeDescription
eventstrCloud event name, e.g. user.message, agent.message, session.status_idle
idstr | NoneSSE event ID, usable as stream["after_id"] replay anchor
dataAnyCloud event payload (includes turn_id and other fields)
uuidstrSDK-generated unique ID for deduplication
session_idstrCloud session ID this event belongs to

Cloud Agent Error Types

Exception classDescription
CloudAgentUnsupportedAuthErrorNon-PAT auth was used (e.g. qodercli_auth() / job_token())
CloudAgentApiErrorCloud OpenAPI returned non-2xx or SSE channel failed; attributes: status: int, message: str
from qoder_agent_sdk import CloudAgentApiError, CloudAgentUnsupportedAuthError

Tools Reference

This page summarizes the stable tool-related APIs, the built-in tool list, and type definitions. For usage paths and scenarios, see Tools Usage Guide.
Note: The Python SDK currently does not export the built-in tool input / output type collections from the TypeScript SDK, such as BashInput, FileReadInput, or ToolInputSchemas. The implementation status is explicitly noted in the relevant sections.

ToolConfig

The TypeScript SDK provides options.toolConfig to configure the behavior of certain built-in tools:
type ToolConfig = {
  askUserQuestion?: {
    previewFormat?: "markdown" | "html";
  };
};
The Python SDK does not currently export an equivalent QoderAgentOptions.tool_config field; AskUserQuestion can still be used as a runtime tool name in tools, allowed_tools, disallowed_tools, can_use_tool, and hook matchers.

Built-in Tool List

In tools, allowed_tools, disallowed_tools, can_use_tool, hook matchers, and Agent tool allowlists, built-in tools use the runtime tool names in the table below.
CategoryTool nameDescriptionPython SDK status
Command executionBashExecute shell commandsUsable in permission / tool-scope configuration
File operationsReadRead file contentsUsable in permission / tool-scope configuration
File operationsEditEdit files by string matchingUsable in permission / tool-scope configuration
File operationsWriteCreate or overwrite filesUsable in permission / tool-scope configuration
SearchGlobSearch by filename patternUsable in permission / tool-scope configuration
SearchGrepSearch by content regexUsable in permission / tool-scope configuration
NetworkWebFetchFetch and process URL contentUsable in permission / tool-scope configuration
NetworkWebSearchWeb searchUsable in permission / tool-scope configuration
AgentAgentInvoke a subagentUsable in permission / tool-scope configuration
InteractionAskUserQuestionAsk the user a questionUsable in permission / tool-scope configuration
NotebookNotebookEditEdit notebook cellsUsable in permission / tool-scope configuration
Background tasksTaskOutputSend output to a background taskUsable in permission / tool-scope configuration
Background tasksTaskStopStop a background taskUsable in permission / tool-scope configuration
Plan / worktreeExitPlanModeExit plan modeUsable in permission / tool-scope configuration
Plan / worktreeEnterWorktreeEnter a git worktreeUsable in permission / tool-scope configuration
Plan / worktreeExitWorktreeExit a worktreeUsable in permission / tool-scope configuration
ConfigConfigRead or write configurationUsable in permission / tool-scope configuration
TodoTodoWriteManage todo itemsUsable in permission / tool-scope configuration
MCP resourcesListMcpResourcesList MCP resourcesUsable in permission / tool-scope configuration
MCP resourcesReadMcpResourceRead an MCP resourceUsable in permission / tool-scope configuration
MCP invocationMcpGeneric MCP tool callUsable in permission / tool-scope configuration
Custom MCP tool names use this format:
mcp__{serverName}__{toolName}

tool()

Creates an SDK MCP tool definition. The Python version is decorator-style; the handler is wrapped via @tool(...).
from collections.abc import Awaitable, Callable
from typing import Any

from mcp.types import ToolAnnotations


def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any],
    annotations: ToolAnnotations | None = None,
) -> Callable[[Callable[..., Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]:
    ...
ParameterTypeRequiredSemanticsCurrent Qoder behavior
namestrYesUnique identifier of the tool within the current MCP serverForms the model-visible full tool name mcp__{serverName}__{name}; required to be non-empty at registration
descriptionstrYesTool description shown to the model — when to use it, what it does, what it returnsPassed through to the tool list and directly affects whether the model invokes it correctly; required to be non-empty at registration
input_schematype | dict[str, Any]YesDefines the tool's input parametersThe SDK generates an MCP input schema; supports a simple dict, a TypedDict, or a complete JSON Schema dict
annotationsToolAnnotations | NoneNoAdditional tool metadataThe SDK registers annotations on the MCP server; does not replace permission configuration
tool() itself only defines the tool; the decorated async handler is the function executed when the tool is called. Registration constraints such as name, description, and duplicate tool names are validated by create_sdk_mcp_server() when the tool is registered.

input_schema

The Python SDK does not have the TypeScript SDK's AnyZodRawShape / InferShape. The Python version of input_schema supports the following forms:
# Plain dictionary: all fields are required
{"query": str, "limit": int}

# Use Annotated to add field descriptions
{"query": Annotated[str, "Search keywords"]}

# TypedDict: supports NotRequired
class SearchInput(TypedDict):
    query: str
    limit: NotRequired[int]

# Complete JSON Schema dictionary
{
    "type": "object",
    "properties": {
        "query": {"type": "string"},
        "source": {"type": "string", "enum": ["docs", "wiki"]},
    },
    "required": ["query"],
}
Common Python type conversions:
Python formJSON Schema semantics
str{"type": "string"}
int{"type": "integer"}
float{"type": "number"}
bool{"type": "boolean"}
list[T]array, with items
dictobject
Annotated[T, "..."]adds description to T's schema
TypedDictobject; generates required based on required / NotRequired

TypeScript-only schema helper types

TypeScript reference typePython SDK statusPython equivalent capability
AnyZodRawShapeNot implemented / not exportedUse dict[str, type], TypedDict, or a complete JSON Schema dict
InferShapeNot implemented / not exportedThe handler receives an args dict; if static typing is needed, declare your own TypedDict in business code
ToolExtrasNot implemented / not exportedIn Python, annotations is passed directly as the 4th argument to @tool()

SdkMcpTool

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any, Generic, TypeVar

from mcp.types import ToolAnnotations


T = TypeVar("T")


@dataclass
class SdkMcpTool(Generic[T]):
    name: str
    description: str
    input_schema: type[T] | dict[str, Any]
    handler: Callable[..., Awaitable[dict[str, Any]]]
    annotations: ToolAnnotations | None = None
The @tool() decorator returns an SdkMcpTool. You typically do not need to construct one manually.

ToolInvocationContext

import asyncio
from dataclasses import dataclass, field


@dataclass
class ToolInvocationContext:
    signal: asyncio.Event = field(default_factory=asyncio.Event)
The handler may take one or two parameters:
@tool("watch", "Watch a counter.", {"max": int})
async def watch(args, extra: ToolInvocationContext):
    ...
When the handler accepts a second positional parameter, the SDK passes a ToolInvocationContext. extra.signal is set when the CLI cancels an in-flight tool call.

ToolAnnotations

The Python version directly uses mcp.types.ToolAnnotations.
from mcp.types import ToolAnnotations


ToolAnnotations(
    title="Search docs",
    readOnlyHint=True,
    destructiveHint=False,
    idempotentHint=True,
    openWorldHint=False,
    maxResultSizeChars=500_000,
)
FieldTypeOptionalSemanticsCurrent Qoder behavior
titlestrYesHuman-readable title of the toolMCP metadata; not currently a verified Qoder behavior capability
readOnlyHintboolYesMarks the tool as not modifying stateThe observable effect today is that read-only tools are eligible for concurrent execution within the same batch of tool calls; not a permission switch
destructiveHintboolYesMarks the tool as potentially performing destructive updatesRisk metadata; does not currently auto-block authorized tool execution
openWorldHintboolYesMarks whether the tool interacts with an open external worldExternal-interaction metadata; does not currently auto-block authorized tool execution
maxResultSizeCharsintYesMarks the tool's result-size capThe Python SDK writes it into _meta["anthropic/maxResultSizeChars"] for the CLI to read; this is the MCP type extension field currently used in Python
These fields are metadata and scheduling hints, not permission switches. Whether execution is allowed is still determined by tools, allowed_tools, disallowed_tools, permission_mode, can_use_tool, and hooks.

create_sdk_mcp_server()

Creates an MCP server that runs in the same process as the SDK.
from typing import Any


def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None,
) -> McpSdkServerConfig:
    ...
ParameterDefaultDescription
nameRequiredMCP server name; used in mcp__{serverName}__{toolName}
version"1.0.0"Server version info
toolsNoneList of tools registered to this server

CreateSdkMcpServerOptions

The TypeScript SDK uses a CreateSdkMcpServerOptions object parameter; the Python SDK does not export this type and does not use an options object. The Python equivalent is the three function parameters of create_sdk_mcp_server(name, version="1.0.0", tools=None).

Return Value

Returns an McpSdkServerConfig that can be used directly as a value in QoderAgentOptions.mcp_servers.
from typing import Literal, TypedDict

from typing_extensions import NotRequired


class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: McpServer
    tools: NotRequired[list[McpServerToolPolicy]]

McpServerToolPolicy

class McpServerToolPolicy(TypedDict):
    name: str
    permission_policy: Literal["always_allow", "always_ask", "always_deny"]
The tools policy field exists in the Python types. It is primarily used for tool permission policy at the MCP server configuration layer; common in-process SDK server integrations are still controlled through allowed_tools, disallowed_tools, permission_mode, can_use_tool, and hooks.

CallToolResult

The Python SDK does not export its own CallToolResult type. Handlers return a dict, and the SDK converts it into the MCP CallToolResult.
from typing import Literal, TypedDict

from typing_extensions import NotRequired


class TextToolContent(TypedDict):
    type: Literal["text"]
    text: str


class ImageToolContent(TypedDict):
    type: Literal["image"]
    data: str
    mimeType: str


class ResourceLinkToolContent(TypedDict):
    type: Literal["resource_link"]
    uri: str
    name: NotRequired[str]
    description: NotRequired[str]
    mimeType: NotRequired[str]


class EmbeddedResourceValue(TypedDict, total=False):
    uri: str
    mimeType: str
    text: str
    blob: str


class EmbeddedResourceToolContent(TypedDict):
    type: Literal["resource"]
    resource: EmbeddedResourceValue


ToolContent = (
    TextToolContent
    | ImageToolContent
    | ResourceLinkToolContent
    | EmbeddedResourceToolContent
)


class ToolHandlerResult(TypedDict):
    content: list[ToolContent]
    is_error: NotRequired[bool]

McpToolResultContent

The Python SDK currently recognizes the following content blocks:
TypePython handler dictCurrent Qoder behavior
Text{"type": "text", "text": "..."}Converted to TextContent
Image{"type": "image", "data": "...", "mimeType": "image/png"}Converted to ImageContent
Resource link{"type": "resource_link", "uri": "...", "name": "...", "description": "..."}Downgraded to TextContent, concatenating name / uri / description
Embedded text resource{"type": "resource", "resource": {"text": "..."}}Converted to TextContent
Embedded binary resource{"type": "resource", "resource": {"blob": "..."}}Currently skipped, with a warning logged
Differences from the TS reference:
  • The Python handler uses is_error, not the MCP / TypeScript isError field name; the SDK maps it to MCP isError internally.
  • Top-level _meta on the Python handler is not currently passed through to CallToolResult.
  • The Python call_tool conversion logic does not currently handle the audio content block; even though MCP types import AudioContent, a handler returning {"type": "audio"} will fall into the unsupported-content warning branch.

can_use_tool

The tool permission approval callback is defined in the common types and is repeated here for ease of reference.
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from typing import Any, Literal


@dataclass
class ToolPermissionContext:
    signal: asyncio.Event | None = None
    suggestions: list[Any] = field(default_factory=list)
    blocked_path: str | None = None
    decision_reason: str | None = None
    decision_reason_type: str | None = None
    classifier_approvable: bool | None = None
    title: str | None = None
    display_name: str | None = None
    description: str | None = None
    tool_use_id: str | None = None
    agent_id: str | None = None
    exit_plan_mode: ExitPlanModeApprovalDetails | None = None


CanUseTool = Callable[
    [str, dict[str, Any], ToolPermissionContext],
    Awaitable[PermissionResult],
]

PermissionResult

@dataclass
class PermissionResultAllow:
    behavior: Literal["allow"] = "allow"
    updated_input: dict[str, Any] | None = None
    updated_permissions: list[PermissionUpdate | dict[str, Any]] | None = None
    decision_classification: PermissionDecisionClassification | None = None


@dataclass
class PermissionResultDeny:
    behavior: Literal["deny"] = "deny"
    message: str = ""
    interrupt: bool = False
    decision_classification: PermissionDecisionClassification | None = None


PermissionResult = PermissionResultAllow | PermissionResultDeny
The tool_name received by can_use_tool is the full tool name, for example Bash, Read, mcp__orders__lookup_order.

MCP Status Tool Information

The Python SDK exports McpToolInfo and McpToolAnnotations to describe the per-server tool information returned by QoderSDKClient.get_mcp_status().
from typing import TypedDict

from typing_extensions import NotRequired


class McpToolAnnotations(TypedDict, total=False):
    readOnly: bool
    destructive: bool
    openWorld: bool


class McpToolInfo(TypedDict):
    name: str
    description: NotRequired[str]
    annotations: NotRequired[McpToolAnnotations]
Note: the annotation field names in status are the CLI-projected readOnly, destructive, openWorld, not the readOnlyHint, destructiveHint, openWorldHint from the ToolAnnotations input. idempotentHint is not currently echoed in the status tool list.

Built-in Tool Input/Output Types

The TypeScript SDK provides input / output structures for built-in tools at the type level. The Python SDK currently does not export these TypedDicts, nor does it export ToolInputSchemas / ToolOutputSchemas union types. Note: the type names in the table below are the TypeScript reference type names; Python permission configuration and tool allowlists still use the runtime tool names from Built-in Tool List.
TypeScript typePython SDK status
AgentInput / AgentOutputNot exported
BashInput / BashOutputNot exported
FileReadInput / FileReadOutputNot exported; runtime tool name remains Read
FileEditInput / FileEditOutputNot exported; runtime tool name remains Edit
FileWriteInput / FileWriteOutputNot exported; runtime tool name remains Write
GlobInput / GlobOutputNot exported
GrepInput / GrepOutputNot exported
WebFetchInput / WebFetchOutputNot exported
WebSearchInput / WebSearchOutputNot exported
AskUserQuestionInput / AskUserQuestionOutputNot exported
NotebookEditInput / NotebookEditOutputNot exported
TaskOutputInputNot exported
TaskStopInput / TaskStopOutputNot exported
ExitPlanModeInput / ExitPlanModeOutputNot exported
ConfigInput / ConfigOutputNot exported
EnterWorktreeInput / EnterWorktreeOutputNot exported
ExitWorktreeInput / ExitWorktreeOutputNot exported
TodoWriteInput / TodoWriteOutputNot exported
ListMcpResourcesInput / ListMcpResourcesOutputNot exported
ReadMcpResourceInputNot exported
McpInput / McpOutputNot exported
ToolInputSchemasNot exported
ToolOutputSchemasNot exported
What the Python side exposes as stable and configurable is the runtime tool names in Built-in Tool List. If you need strongly-typed built-in tool parameters in a Python application, define your own TypedDict or dataclass on the business side.

Hooks Reference

For usage guides and examples, see Hooks.

Event Overview

EventTriggerControllable behavior
PreToolUseBefore a tool callBlock / allow / modify input
PostToolUseAfter a tool succeedsAudit / inject context / override output
PostToolUseFailureAfter a tool failsError handling / logging
UserPromptSubmitBefore a user prompt is sentInject context / block
SessionStartWhen a session startsInitialize / inject context
SessionEndWhen a session endsCleanup / logging
StopWhen AI stops generatingBlock stop and force continuation
SubagentStartWhen a subagent startsObserve / log
SubagentStopWhen a subagent stopsObserve / log
PreCompactBefore context compactionObserve / log
PostCompactAfter context compactionObserve / log
CwdChangedWhen the working directory changesObserve / log
InstructionsLoadedWhen an instructions file is loadedObserve / log
FileChangedWhen a file is created/modified/deletedObserve / log
PermissionRequestWhen a permission is requestedAuto-approve / deny permission requests

HookEvent

Union type of registrable hook events.
HookEvent = Literal[
    "PreToolUse",
    "PostToolUse",
    "PostToolUseFailure",
    "UserPromptSubmit",
    "SessionStart",
    "SessionEnd",
    "Stop",
    "SubagentStart",
    "SubagentStop",
    "PreCompact",
    "PostCompact",
    "CwdChanged",
    "InstructionsLoaded",
    "FileChanged",
    "PermissionRequest",
]

HookCallback

HookCallback = Callable[
    [HookInput, str | None, HookCallbackOptions],
    Awaitable[HookJSONOutput],
]

HookMatcher

@dataclass
class HookMatcher:
    hooks: list[HookCallback]
    matcher: str | None = None
    timeout: int | None = None  # In seconds; defaults to 60
FieldTypeDescription
hookslist[HookCallback]List of callbacks executed when matched
matcherstr | NoneOptional regex; filters by tool_name
timeoutint | NoneOptional timeout (seconds); default 60

BaseHookInput

Common input fields for all hook events.
@dataclass
class BaseHookInput:
    hook_event_name: str
    session_id: str
    transcript_path: str
    cwd: str
FieldTypeDescription
hook_event_namestrEvent-type identifier (e.g. "PreToolUse")
session_idstrUnique identifier of the current session
transcript_pathstrPath to the session transcript file (JSONL format)
cwdstrCurrent working directory of the session

HookJSONOutput

Return type of a hook callback.
@dataclass
class HookJSONOutput:
    continue_: bool | None = None       # JSON key: "continue"
    stop_reason: str | None = None      # JSON key: "stopReason"
    decision: str | None = None
    reason: str | None = None
    hook_specific_output: dict | None = None  # JSON key: "hookSpecificOutput"
FieldTypeDefaultDescription
continue_bool | NoneNone (equivalent to True)Set to False to terminate the session. Effective only for PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, SubagentStop
stop_reasonstr | NoneNoneHuman-readable reason for stopping the session (used with continue_=False)
decisionstr | NoneNone"approve" or "block". "block" blocks tool execution; for the Stop event, "block" blocks stopping and forces continuation
reasonstr | NoneNoneReason for the decision (shown to the model; for the "block" decision on a Stop event, injected as a continuation prompt into the context)
hook_specific_outputdict | NoneNoneEvent-specific output (see each event type)
In the Python SDK, continue_ corresponds to the JSON key "continue" (to avoid the keyword conflict). When multiple hooks return conflicting decision values, "deny" / "block" takes priority (the strictest rule wins).

PreToolUseHookInput

@dataclass
class PreToolUseHookInput(BaseHookInput):
    hook_event_name: Literal["PreToolUse"]
    permission_mode: str | None
    tool_name: str
    tool_input: Any
FieldTypeDescription
permission_modestr | NoneCurrent permission mode of the session
tool_namestrName of the tool being invoked
tool_inputAnyParameters passed to the tool
hook_specific_output (hookSpecificOutput):
FieldTypeDescription
hookEventName"PreToolUse"Must be set
permissionDecisionstr"allow" / "deny" / "ask" / "defer"
permissionDecisionReasonstrReason for the permission decision
updatedInputdictModified tool input that replaces the original tool_input
additionalContextstrExtra context injected into the model's next turn

PostToolUseHookInput

@dataclass
class PostToolUseHookInput(BaseHookInput):
    hook_event_name: Literal["PostToolUse"]
    tool_name: str
    tool_input: Any
    tool_response: Any
FieldTypeDescription
tool_namestrName of the tool being invoked
tool_inputAnyParameters passed to the tool
tool_responseAnyResult of tool execution
Output behavior:
FieldLocationBehavior
hookSpecificOutput.updatedToolOutputEvent-specific outputOverrides tool_response; the model only sees the overridden value
hookSpecificOutput.additionalContextEvent-specific outputAppends supplemental context without modifying the original result
decision: "block" + reasonTop-level outputPrevents the agent from further processing the tool result
hook_specific_output (hookSpecificOutput):
FieldTypeDescription
hookEventName"PostToolUse"Must be set
updatedToolOutputstrOverride the tool response content
additionalContextstrExtra context appended alongside the tool result
When multiple hooks set updatedToolOutput, the last non-empty value wins. For chained transformations, perform them sequentially within a single callback.

PostToolUseFailureHookInput

@dataclass
class PostToolUseFailureHookInput(BaseHookInput):
    hook_event_name: Literal["PostToolUseFailure"]
    tool_name: str
    tool_input: Any
    error: str
    is_interrupt: bool | None
FieldTypeDescription
tool_namestrName of the failed tool
tool_inputAnyParameters passed to the tool
errorstrError message
is_interruptbool | NoneWhether caused by an interrupt/abort

UserPromptSubmitHookInput

@dataclass
class UserPromptSubmitHookInput(BaseHookInput):
    hook_event_name: Literal["UserPromptSubmit"]
    prompt: str
FieldTypeDescription
promptstrText entered by the user
hook_specific_output (hookSpecificOutput):
FieldTypeDescription
hookEventName"UserPromptSubmit"Must be set
additionalContextstrExtra context appended to the user prompt

SessionStartHookInput

@dataclass
class SessionStartHookInput(BaseHookInput):
    hook_event_name: Literal["SessionStart"]
    source: str
FieldTypeDescription
sourcestrReason for starting the session: "startup" / "resume" / "clear" / "compact"
hook_specific_output (hookSpecificOutput):
FieldTypeDescription
hookEventName"SessionStart"Must be set
additionalContextstrContext injected at the start of the session

SessionEndHookInput

@dataclass
class SessionEndHookInput(BaseHookInput):
    hook_event_name: Literal["SessionEnd"]
    reason: str
FieldTypeDescription
reasonstrReason the session ended: "clear" / "resume" / "logout" / "prompt_input_exit" / "other" / "bypass_permissions_disabled"

StopHookInput

@dataclass
class StopHookInput(BaseHookInput):
    hook_event_name: Literal["Stop"]
    stop_hook_active: bool
FieldTypeDescription
stop_hook_activeboolWhether a Stop hook is currently blocking stopping
Returning {"decision": "block", "reason": "..."} blocks the AI from stopping and forces continuation. reason is injected into the model context as a continuation prompt.

SubagentStartHookInput

@dataclass
class SubagentStartHookInput(BaseHookInput):
    hook_event_name: Literal["SubagentStart"]
    agent_id: str
    agent_type: str
FieldTypeDescription
agent_idstrUnique identifier of the subagent instance
agent_typestrType / role of the subagent

SubagentStopHookInput

@dataclass
class SubagentStopHookInput(BaseHookInput):
    hook_event_name: Literal["SubagentStop"]
    stop_hook_active: bool
FieldTypeDescription
stop_hook_activeboolWhether a Stop hook is currently blocking stopping

PreCompactHookInput

@dataclass
class PreCompactHookInput(BaseHookInput):
    hook_event_name: Literal["PreCompact"]
    trigger: str
    custom_instructions: str | None
FieldTypeDescription
triggerstrTrigger reason: "manual" / "auto"
custom_instructionsstr | NoneCustom instructions for the compaction summary

PostCompactHookInput

@dataclass
class PostCompactHookInput(BaseHookInput):
    hook_event_name: Literal["PostCompact"]
    trigger: str
    compact_summary: str
FieldTypeDescription
triggerstrTrigger reason: "manual" / "auto"
compact_summarystrSummary generated after context compaction

CwdChangedHookInput

@dataclass
class CwdChangedHookInput(BaseHookInput):
    hook_event_name: Literal["CwdChanged"]
    old_cwd: str
    new_cwd: str
FieldTypeDescription
old_cwdstrWorking directory before the change
new_cwdstrWorking directory after the change

InstructionsLoadedHookInput

@dataclass
class InstructionsLoadedHookInput(BaseHookInput):
    hook_event_name: Literal["InstructionsLoaded"]
    load_reason: str
FieldTypeDescription
load_reasonstrLoad reason: "nested_traversal" / "path_glob_match"

FileChangedHookInput

@dataclass
class FileChangedHookInput(BaseHookInput):
    hook_event_name: Literal["FileChanged"]
    file_path: str
    event: str
FieldTypeDescription
file_pathstrPath of the changed file
eventstrFilesystem event: "change" / "add" / "unlink"

PermissionRequestHookInput

@dataclass
class PermissionRequestHookInput(BaseHookInput):
    hook_event_name: Literal["PermissionRequest"]
    tool_name: str
    tool_input: Any
    permission_suggestions: list | None
FieldTypeDescription
tool_namestrTool requesting permission
tool_inputAnyTool input parameters
permission_suggestionslist | NoneSuggested permission rules
hook_specific_output (hookSpecificOutput):
FieldTypeDescription
hookEventName"PermissionRequest"Must be set
decisiondictPermission decision (see below)
decision is one of:
  • Approve: {"behavior": "allow", "updatedInput": {...}, "updatedPermissions": [...]}
  • Deny: {"behavior": "deny", "message": "..."}

Message Types

AssistantMessage

The complete AI reply, delivered once per turn. content is a list of TextBlock and ToolUseBlock.
@dataclass
class AssistantMessage:
    content: list[TextBlock | ToolUseBlock]
    parent_tool_use_id: str | None = None
    session_id: str | None = None
    uuid: str | None = None

UserMessage

A user message in the CLI output stream. priority and should_query preserve the scheduling metadata used when the message entered the session.
@dataclass
class UserMessage:
    content: str | list[ContentBlock]
    uuid: str | None = None
    parent_tool_use_id: str | None = None
    tool_use_result: dict[str, Any] | None = None
    priority: Literal["now", "next", "later"] | None = None
    should_query: bool | None = None

ResultMessage

The final message at the end of the entire session.
@dataclass
class ResultMessage:
    subtype: str  # Allowed values: "success" | "error_max_turns" | "error_during_execution" | ...
    duration_ms: int
    num_turns: int
    session_id: str
    total_cost_usd: float | None = None
    result: str | None = None  # Present only when subtype == "success"

SystemMessage

Session system message. When subtype == "init", data carries initialization information (session_id, model, tools, etc.). data["skills"] is a list of skill names; read client.get_server_info() for descriptions and sources.
@dataclass
class SystemMessage:
    subtype: str  # Allowed values: "init" | "compact_boundary" | "status" | "mcp_status_change" | ...
    data: dict[str, Any]

SDKMirrorErrorMessage

Non-fatal message emitted when the SDK cannot write a batch to the external session store after its final attempt. The query continues.
@dataclass
class SDKMirrorErrorMessage(SystemMessage):
    error: str
    key: SessionKey
    uuid: str
    session_id: str

StreamEvent

Requires include_partial_messages=True; streamed token-by-token.
@dataclass
class StreamEvent:
    uuid: str
    session_id: str
    event: dict[str, Any]  # Raw stream event compatible with the upstream format
    parent_tool_use_id: str | None = None
event["type"] values:
event["type"]Description
message_startMessage begins
content_block_startBlock starts (text / tool_use / thinking)
content_block_deltaIncrement: text_delta / input_json_delta / thinking_delta
content_block_stopBlock ends
message_deltaMessage-level state change (e.g. stop_reason)
message_stopComplete turn ends
For full usage, see Streaming Output.

CloudAgentEventMessage

SSE event message under Cloud runtime. Only present in experimental_cloud_agent mode. Full field reference in Cloud Agent Reference.
from qoder_agent_sdk import CloudAgentEventMessage

async for msg in result:
    if isinstance(msg, CloudAgentEventMessage):
        print(msg.event, msg.data)

Content Blocks

Elements in AssistantMessage.content:
@dataclass
class TextBlock:
    text: str


@dataclass
class ToolUseBlock:
    id: str
    name: str
    input: dict[str, Any]
Using Qoder CLI
Configuration & Security
Reference
SDK