Skip to main content
Reference

SDK References - TypeScript

Functions

query()

The SDK's main entry function. Creates an async generator that streams SDKMessage in message arrival order.
function query(params: {
  prompt: string | AsyncIterable<SDKUserMessage>;
  options?: Options;
}): Query

Parameters

ParameterTypeDescription
promptstring | AsyncIterable<SDKUserMessage>Pass a string for single-turn; pass an async iterable for multi-turn
optionsOptionsOptional session configuration

Return Value

Returns Query — an AsyncGenerator<SDKMessage, void>, consumed via for await.

startup()

Starts the qodercli subprocess and waits for the initialization handshake without sending a user message or starting a model request. Use it to remove CLI startup latency from the first query.
function startup(params?: StartupParams): Promise<WarmQuery>;
initializeTimeoutMs defaults to 60000. Initialization errors and timeouts close the subprocess before the promise rejects. The experimental Cloud Agent runtime is not supported. Each WarmQuery accepts exactly one query() call. Always call close() when the warm session is no longer needed.
import { qodercliAuth, startup } from '@qodercn-ai/qodercn-agent-sdk';

const warm = await startup({
  options: {
    auth: qodercliAuth(),
    cwd: process.cwd(),
  },
  initializeTimeoutMs: 30_000,
});

try {
  const q = warm.query('Summarize this repository.');
  for await (const message of q) {
    console.log(message);
  }
} finally {
  warm.close();
}
See StartupParams and WarmQuery for the complete types.

resolveSettings()

Reads and merges Qoder settings without starting qodercli. Sources are returned from low to high precedence: user, project, then local.
function resolveSettings(
  options?: ResolveSettingsOptions,
): Promise<ResolvedSettings>;
Omit settingSources to read all three filesystem sources, or pass [] to skip them all. Missing files are skipped; unreadable files, invalid JSON/JSONC, and non-object roots reject the promise. effective is the raw settings cascade, not a complete snapshot of the final runtime configuration. It does not include runtime defaults, query options, environment-placeholder expansion, or workspace-trust decisions.
import { resolveSettings } from '@qodercn-ai/qodercn-agent-sdk';

const resolved = await resolveSettings({
  cwd: process.cwd(),
  settingSources: ['user', 'project'],
});

console.log(resolved.effective);
console.table(resolved.sources.map(({ source, path }) => ({ source, path })));
See ResolveSettingsOptions and ResolvedSettings for the complete types.

q.interrupt()

interrupt(): Promise<SDKControlInterruptResponse | undefined>;
Stops the current generation or tool execution without closing the session. The optional response lists messages that remain queued. See Interrupting the current turn.
type SDKControlInterruptResponse = {
  still_queued: string[];
  cancelled?: string[];
};

q.askSideQuestion()

Asks a question against the current main-session context without interrupting the active generation or tool execution.
askSideQuestion(
  question: string,
  options?: AskSideQuestionOptions,
): Promise<SideQuestionResult | null>;

type SideQuestionHistoryEntry = {
  question: string;
  response: string;
  fallback_notice?: string;
};

type AskSideQuestionOptions = {
  signal?: AbortSignal;
  history?: readonly SideQuestionHistoryEntry[];
};

type SideQuestionResult = {
  response: string;
  synthetic: boolean;
  refusalFallback?: {
    originalModel: string;
    fallbackModel: string;
    content: string;
  };
};
history supplies earlier side-question exchanges as local context. These entries are not added to the main conversation. Aborting signal cancels only this side question; the main Agent turn continues. The method returns null when no answer is produced.
const answer = await q.askSideQuestion(
  'Which configuration file is the current task using?',
  {
    history: [
      {
        question: 'What are we investigating?',
        response: 'A production login failure.',
      },
    ],
  },
);

console.log(answer?.response);
While the answer is pending, the normal query stream may emit system/control_request_progress messages.

q.cancelAsyncMessage()

cancelAsyncMessage(messageUuid: string): Promise<boolean>;
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.

q.backgroundTasks()

backgroundTasks(toolUseId?: string): Promise<boolean>;
Moves an eligible in-flight foreground Agent or Shell execution into the background. Pass a tool_use ID to target one execution; omit it to background all eligible executions. When an ID is provided, the method returns true only when that execution transitions. The CLI must advertise background_tasks_v1.

q.stopTask()

stopTask(taskId: string): Promise<void>;
Stops one background task without interrupting the main session turn. Use the ID from task_started.task_id or background_tasks_changed.tasks[].task_id. Agent and Shell tasks share this method and event channel; inspect the open-string task_type field (local_agent or local_bash) when the host needs to distinguish them. The CLI must advertise background_tasks_v1.

q.initializationResult()

initializationResult(): Promise<SDKControlInitializeResponse>;
Waits for the CLI to finish initializing the current session and returns its initialization snapshot. The result includes commands, agents, skills, and other resources discovered for the session. Its skills field is a discovery inventory, not the complete list currently callable by the main session. See SDKControlInitializeResponse for the return type and Skills for discovery and invocation semantics.

q.accountInfo()

accountInfo(): Promise<AccountInfo>;
Returns information about the authenticated Qoder account. The SDK first requests a fresh account snapshot from the current CLI. If an older CLI rejects account_info, or the live response is empty, it falls back to the initialization snapshot. All fields are optional and the result may be empty when account metadata is unavailable. The method does not return access tokens, service-account keys, or other credentials. The experimental Cloud Agent runtime does not support this method. See AccountInfo for the field definitions.

Session management functions

These functions read or modify local sessions by default. Pass sessionStore in their options to operate on an external store. See External Session Storage for setup and behavior.
function listSessions(options?: ListSessionsOptions): Promise<SDKSessionInfo[]>;
function getSessionInfo(sessionId: string, options?: GetSessionInfoOptions): Promise<SDKSessionInfo | undefined>;
function getSessionMessages(sessionId: string, options?: GetSessionMessagesOptions): Promise<SessionMessage[]>;
function renameSession(sessionId: string, title: string, options?: SessionMutationOptions): Promise<void>;
function tagSession(sessionId: string, tag: string | null, options?: SessionMutationOptions): Promise<void>;
function forkSession(sessionId: string, options?: ForkSessionOptions): Promise<{ sessionId: string }>;
function listSubagents(sessionId: string, options?: ListSubagentsOptions): Promise<string[]>;
function getSubagentMessages(sessionId: string, agentId: string, options?: GetSubagentMessagesOptions): Promise<SessionMessage[]>;
function deleteSession(sessionId: string, options?: SessionMutationOptions): Promise<void>;
FunctionPurposeExternal store requirement
listSessionsList sessions by most recent modificationSessionStore.listSessions
getSessionInfoRead metadata for one sessionload
getSessionMessagesRead the active conversation chainload
renameSession / tagSessionUpdate the title or tagappend
forkSessionCreate an independent session from existing historyload and append
listSubagentsList subagent IDsSessionStore.listSubkeys
getSubagentMessagesRead one subagent's messagesload; uses SessionStore.listSubkeys when available
deleteSessionDelete a session and its child dataSessionStore.delete; does nothing externally when absent
The option types share dir and sessionStore. List and message functions also support pagination through limit and offset. listSessions supports includeWorktrees for local sessions. forkSession additionally supports upToMessageId and title; getSessionMessages supports includeSystemMessages.

importSessionToStore()

Copies an existing local session into an external store.
function importSessionToStore(
  sessionId: string,
  store: SessionStore,
  options?: {
    dir?: string;
    includeSubagents?: boolean;
    batchSize?: number;
  },
): Promise<void>;
includeSubagents defaults to true. batchSize defaults to 500.

Types

Options

Configuration object for query().
FieldTypeDefaultDescription
abortControllerAbortControllerundefinedController for defining when the session ends; calling abort() closes the entire session. See Managing the session lifecycle
additionalDirectoriesstring[][]Additional directories accessible to the AI
agentstringundefinedAgent name used by the main session; see Agents Reference
agentsRecord<string, AgentDefinition>undefinedProgrammatically defined subagents; see Agents Reference
allowDangerouslySkipPermissionsbooleanfalseAllow skipping permission checks; used with permissionMode: 'bypassPermissions'
allowedToolsstring[][]Tool allowlist; listed tools are pre-authorized. Built-in tool names are listed in Tools Reference
authAuthOptionsundefinedAuthentication configuration, required for query()
canUseToolCanUseToolundefinedCustom tool permission callback
continuebooleanfalseContinue the most recent session
customContextRecord<string, string>undefinedDefault custom context for local qodercli inference requests; message-level custom_context takes precedence. See limits below
cwdstringprocess.cwd()Working directory
disallowedToolsstring[][]Tool blocklist; priority is higher than allowedTools and permissionMode. Built-in tool names are listed in Tools Reference
enableFileCheckpointingbooleanfalseEnable file checkpointing for use with rewindFiles(); see Checkpoint
envRecord<string, string | undefined>process.envEnvironment variables passed to the CLI process; for QODERCN_CONFIG_DIR, see User data directory
proxystringundefinedExplicit proxy URL for qodercli's own outbound traffic; supports http://, https://, socks5://, and socks://. When omitted, SDK-integrated qodercli connects directly instead of reading inherited proxy environment variables; inherited variables remain available to tools and subprocesses
vpcEndpointstringundefinedEndpoint for a CN VPC private deployment; see Options.vpcEndpoint
executable'bun' | 'deno' | 'node'Auto-detectedJavaScript runtime
executableArgsstring[][]Arguments passed to the runtime
extraArgsRecord<string, string | null>{}Additional arguments passed to the CLI
fallbackModelstringundefinedFallback model when the main model fails
forkSessionbooleanfalseFork into a new session ID when used with resume
hooksPartial<Record<HookEvent, HookCallbackMatcher[]>>{}Lifecycle hooks; see Hooks
includeHookEventsbooleanfalseInclude hook lifecycle events in the message stream
includePartialMessagesbooleanfalseInclude stream_event streaming fragments; see Streaming Output
maxTurnsnumberundefinedMaximum conversation turns (tool call round-trips)
mcpServersRecord<string, McpServerConfig>{}MCP server configuration; see MCP
modelstringCLI defaultModel to use; options: 'auto' / 'ultimate' / 'performance' / 'efficient' / 'lite'
pathToQoderCLIExecutablestringAuto-resolved bundled binaryPath to qoderclicn executable
permissionModePermissionMode'default'Session permission mode
permissionPromptToolNamestringundefinedMCP tool name for permission prompts; mutually exclusive with canUseTool
pluginsSdkPluginConfig[][]Load local plugins; see Plugins
promptSuggestionsbooleanfalseEmit prompt_suggestion messages after each turn's result
resolveModelModelPolicyProviderundefinedDynamic model-selection callback. Passing it switches the query into dynamic-callback mode; see Model Policy
resolveModelTimeoutMsnumber500Callback timeout in milliseconds; only effective when resolveModel is passed
resumestringundefinedSession ID to resume
resumeSessionAtstringundefinedResume from a specified message UUID
resumeDropsTurnstringundefinedWith resumeSessionAt, require the discarded range to belong to this prompt UUID
persistSessionbooleantruePersist the local session so it can be resumed later. Cannot be false when sessionStore is set
sessionIdstringAuto-generatedSpecify session UUID
sessionStoreSessionStoreundefinedSave session history to external storage so another host can resume it. See External Session Storage
sessionStoreFlushSessionStoreFlush'batched'External write timing. Ignored when sessionStore is not set
loadTimeoutMsnumber60000Timeout for each external store read while restoring a session
settingsstring | SettingsundefinedInline settings object or settings file path
settingSourcesSettingSource[]CLI defaultWhich filesystem settings to load; pass [] to skip user/project/local
skillsstring[] | 'all'undefinedMain-session skill policy; an array restricts model context and invocation, [] disables all, and 'all' enables all; see Skills
spawnQoderCLIProcess(options: SpawnOptions) => SpawnedProcessundefinedCustom process spawn function
strictMcpConfigbooleanfalseStrict MCP validation
systemPromptstring | { type: 'preset'; preset: 'qodercli'; append?: string }undefinedSystem prompt. String overrides; preset form appends after qoderclicn preset
toolConfigToolConfigundefinedBuilt-in tool behavior configuration; see Tools
toolsstring[] | { type: 'preset'; preset: 'qodercli' }undefinedTool set. When omitted, the SDK default surface includes the persistent Task tools. Pass a string array to restrict available tools; pass an empty array to disable all tools. Built-in tool names are listed in Tools Reference

Options.vpcEndpoint

When the CN SDK connects to a VPC private deployment, set the endpoint through vpcEndpoint:
import { qodercliAuth, query } from '@qodercn-ai/qodercn-agent-sdk';

const response = query({
  prompt: 'Analyze the current project',
  options: {
    auth: qodercliAuth(),
    vpcEndpoint: 'acme',
  },
});

for await (const message of response) {
  console.log(message);
}
The value can be a VPC instance name such as acme, or a full VPC domain such as acme.vpc.qoder.com.cn. Do not include https://, a port, a path, or query parameters.

customContext

customContext is optional. When omitted, the SDK does not send a custom-context field. The TypeScript public options API uses the camelCase name customContext; only the SDKUserMessage wire message uses the snake_case name custom_context. A message's own custom_context overrides the options default; omit the message field to inherit options.customContext. The resolved value is used for every inference round in the main session and explicitly invoked child Agents; internal utility-model calls do not inherit it. Messages carrying custom_context remain separate and are not coalesced with adjacent messages. This capability is available only with the local qodercli runtime and is not supported by the Cloud Agent runtime. Backend limits are 1–8 string entries, keys up to 32 Unicode code points, values up to 128 Unicode code points, and at most 1024 UTF-8 bytes for the serialized JSON object. Do not include tokens, Service Account keys, other credentials, email addresses, phone numbers, or personally identifiable information (PII). The SDK does not validate the content locally. The backend accepts the field only with Service Account authentication and a Qoder VPC deployment. It ignores the entire field without failing inference when the object is invalid or the authentication/deployment requirements are not met. The paired CLI should advertise custom_context_v1; an older CLI may ignore the field and emit a compatibility diagnostic.

SessionStore

Interface implemented by external storage adapters. append and load are required; the other methods enable listing, deletion, and complete subagent restoration.
type SessionKey = {
  projectKey: string;
  sessionId: string;
  subpath?: string;
};

type SessionStoreEntry = {
  type: string;
  uuid?: string;
  timestamp?: string;
  [key: string]: unknown;
};

interface SessionStore {
  append(key: SessionKey, entries: SessionStoreEntry[]): Promise<void>;
  load(key: SessionKey): Promise<SessionStoreEntry[] | null>;
  listSessions?(projectKey: string): Promise<Array<{ sessionId: string; mtime: number }>>;
  delete?(key: SessionKey): Promise<void>;
  listSubkeys?(key: Omit<SessionKey, 'subpath'>): Promise<string[]>;
}
Treat keys and entries as opaque SDK data. See External Session Storage for behavioral requirements and implementation guidance.

SessionStoreFlush

type SessionStoreFlush = 'batched' | 'eager';
batched writes at result boundaries and is the default. eager starts writes more frequently.

InMemorySessionStore

Built-in SessionStore implementation for development and tests. It supports all optional methods, plus getEntries(key), size, and clear(). Its data is process-local and is lost when the process exits.

SDKSessionInfo

Metadata returned by listSessions() and getSessionInfo().
type SDKSessionInfo = {
  sessionId: string;
  summary: string;
  lastModified: number;
  fileSize?: number;
  customTitle?: string;
  firstPrompt?: string;
  gitBranch?: string;
  cwd?: string;
  tag?: string;
  createdAt?: number;
};
fileSize is available only for local storage. Time fields use Unix epoch milliseconds.

SessionMessage

Historical message returned by session and subagent message functions.
type SessionMessage = {
  type: 'user' | 'assistant' | 'system';
  uuid: string;
  session_id: string;
  message: unknown;
  parent_tool_use_id: string | null;
  parent_agent_id: string | null;
  timestamp?: string;
  subtype?: string;
  compact_metadata?: object;
};

Settings

Options.settings accepts either a settings file path or an inline Settings object. The SDK passes these fields to the CLI. The fields below are the skill-related subset; defaults and runtime behavior depend on the paired CLI version.
interface Settings {
  skillOverrides?: Record<
    string,
    'on' | 'name-only' | 'user-invocable-only' | 'off'
  >;
  skillListingMaxDescChars?: number;
  skillListingBudgetFraction?: number;
  // Other settings fields
}
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 the plugin-qualified name plugin:skill 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 visible to the model, with no additional invocation block; session tool and permission policies still apply
'name-only'The model sees the name but not the description
'user-invocable-only'Hidden from the model; the user can still trigger it through /name
'off'Hidden from initializationResult().skills and model context; Skill tool invocation is rejected
See Skills for complete session-level skill configuration.

StartupParams and WarmQuery

interface StartupParams {
  options?: Options;
  initializeTimeoutMs?: number;
}

interface WarmQuery extends AsyncDisposable {
  query(prompt: string | AsyncIterable<SDKUserMessage>): Query;
  close(): void;
}
initializeTimeoutMs must be a positive finite number and defaults to 60000. WarmQuery.query() can be called once; subsequent calls throw. close() is idempotent. WarmQuery also implements Symbol.asyncDispose for runtimes that support explicit resource management.

ResolveSettingsOptions and ResolvedSettings

type ResolveSettingsOptions = {
  cwd?: string;
  settingSources?: Array<'user' | 'project' | 'local'>;
};

type ResolvedSettingSource = 'user' | 'project' | 'local';

type ResolvedSettingsSource = {
  source: ResolvedSettingSource;
  settings: Settings;
  path?: string;
};

type ResolvedSettings = {
  effective: Settings;
  provenance: Record<
    string,
    { source: ResolvedSettingSource; path?: string }
  >;
  sources: ResolvedSettingsSource[];
};
provenance records the winning source for each top-level key. Use sources when nested settings require per-layer inspection.

AccountInfo

type AccountInfo = {
  userId?: string;
  name?: string;
  email?: string;
  organization?: string;
  organizationName?: string;
  subscriptionType?: string;
  tokenSource?: string;
  apiKeySource?: string;
  apiProvider?: 'firstParty' | 'bedrock' | 'vertex' | 'foundry';
};
For Qoder-authenticated accounts, organization is the organization identifier and organizationName is its display name. Authentication-source and provider fields are present only when the paired CLI can determine them.

SDKControlInitializeResponse

Initialization snapshot returned by q.initializationResult().
type SDKControlInitializeResponse = {
  commands: SlashCommand[];
  agents: AgentInfo[];
  skills?: Array<{
    name: string;
    description?: string;
    source?: string;
  }>;
  output_style: string;
  available_output_styles: string[];
  models: ModelInfo[];
  account: AccountInfo;
  fast_mode_state?: FastModeState;
};
This skills field is a discovery inventory with metadata. SDKSystemMessage.skills in the message stream is a string[] of names carried by the init message; the two fields do not have the same return structure.

AuthOptions

type AuthOptions =
  | { type: 'accessToken'; accessToken: string | { envVar: string } }
  | { type: 'qodercli' }
  | {
      type: 'serviceAccount';
      serviceAccountKey: string;
    }
  | {
      type: 'serviceAccount';
      fetchServiceAccountToken: FetchServiceAccountToken;
    };

type ServiceAccountTokenResult = {
  token: string;
  expiresAt?: number;
};

type FetchServiceAccountToken = () => Promise<ServiceAccountTokenResult | null>;
fetchServiceAccountToken takes no arguments. The host supplies the scope when it obtains a SAT and returns that SAT through token. expiresAt is optional. When provided, it must be the SAT's absolute expiration time as a Unix timestamp in milliseconds. For example, Date.now() + 3_600_000 represents one hour from the current time.
FormDescription
{ type: 'accessToken'; accessToken: string }Pass PAT directly
{ type: 'accessToken'; accessToken: { envVar } }Read PAT from specified environment variable; defaults to QODERCN_PERSONAL_ACCESS_TOKEN
{ type: 'qodercli' }Reuse local qoderclicn login session
{ type: 'serviceAccount'; serviceAccountKey: string }Use a Service Account key and automatically obtain and refresh short-lived Service Account Tokens (SATs)
{ type: 'serviceAccount'; fetchServiceAccountToken: FetchServiceAccountToken }Provide and refresh short-lived Service Account Tokens (SATs) through a host callback
Convenience constructors: accessToken(token) / accessTokenFromEnv(envVar?) / qodercliAuth() / serviceAccount({ serviceAccountKey }) / serviceAccount({ fetchServiceAccountToken }); see SDK Authentication.

options.agents

Type: Record<string, AgentDefinition> Registers custom Agents available to the current query() session. The object 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. The Agent tool must be included in allowedTools because Qoder invokes subagents through the Agent tool.
const q = query({
  prompt: 'Use the reviewer agent to inspect recent changes.',
  options: {
    allowedTools: ['Agent'],
    agents: {
      reviewer: {
        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; allowedTools: ['Agent'] is the required pre-authorization form. If you use options.tools to narrow the main session's available tools, include Agent there as well.

options.agent

Type: string Specifies which Agent identity the main session should run as. The value can be a name registered in options.agents, or a built-in / plugin Agent name discovered by the current CLI.
const q = query({
  prompt: 'Plan the implementation.',
  options: {
    agents: {
      planner: {
        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 session uses the default main-session behavior.

AgentDefinition

Definition of a custom Agent. The fields below are the stable capabilities currently covered and verified by the SDK.
type AgentDefinition = {
  description: string;
  prompt: string;
  tools?: string[];
  disallowedTools?: string[];
  model?: string;
  mcpServers?: AgentMcpServerSpec[];
  skills?: string[];
  initialPrompt?: string;
  maxTurns?: number;
  effort?: EffortLevel;
  permissionMode?: PermissionMode;
};
FieldTypeRequiredDescription
descriptionstringYesAgent purpose description; the model uses it to decide when to invoke the Agent
promptstringYesAgent system prompt
toolsstring[]NoTool allowlist for this Agent
disallowedToolsstring[]NoTools excluded from this Agent's tool set
modelstringNoModel override; 'inherit' means inherit the main session model
mcpServersAgentMcpServerSpec[]NoMCP server specs available to this Agent
skillsstring[]NoSkill names preloaded into the Agent context
initialPromptstringNoFirst user input automatically submitted when this Agent is used as the main session Agent
maxTurnsnumberNoMaximum API turns for the Agent
effortEffortLevelNoReasoning effort level
permissionModePermissionModeNoPermission mode for tool execution inside this Agent

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 allowedTools.

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 disallowedTools. Usually avoid setting both tools and disallowedTools unless you know the final tool set explicitly.

model

Specifies the model for the Agent. When omitted, the session default model is used. Supported model tiers include:
ValueTierDescriptionSuitable forCredit cost
autoSmart routingIntelligently selects the best model, balancing capability and costMost daily development work; recommended default~1.0x
ultimateUltimateExpert-level deep reasoning and thinking capabilityComplex system design and difficult analysis~1.6x
performancePerformanceAdvanced reasoning and high-quality outputCore implementation, architecture design, refactoring~1.1x
efficientEfficientStandard reasoning with good cost efficiencyBasic code generation, unit tests, daily Q&A~0.3x
liteLiteBasic reasoning, free to useQuick validation, simple logic, quick questions0x
Agents also support two special forms:
ValueDescription
inheritInherit the main session model
Full model IDDirectly specify a model ID supported by the current CLI / backend

mcpServers

Limits or adds MCP servers available to this Agent.
type AgentMcpServerSpec =
  | string
  | Record<string, McpServerConfig>;
The string form references an MCP server already configured in the session. The object form configures a dedicated MCP server for this Agent. For the MCP server configuration shape, see SDK References - McpServerConfig.

skills

List of skill names to preload 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 options.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

type EffortLevel = '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 permissionMode, but its scope is limited to this Agent. For the session-level permission chain, allowedTools / disallowedTools / canUseTool priority, and examples, see Permission Control.
type PermissionMode =
  | 'default'
  | 'acceptEdits'
  | 'bypassPermissions'
  | 'yolo'
  | 'plan'
  | '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 automatically; safe workspace file edits may be auto-allowedReduce confirmation interruptions while retaining runtime judgment
For permission semantics, see Permission Control.

AgentInfo

Agent summary returned by q.supportedAgents().
type AgentInfo = {
  name: string;
  description: string;
  model?: string;
};
FieldTypeDescription
namestringAgent name
descriptionstringAgent purpose description
modelstring | undefinedAgent model override; usually empty when unset or when model: 'inherit'
const q = query({
  prompt: 'List agents.',
  options: {
    agents: {
      reviewer: {
        description: 'Reviews code quality.',
        prompt: 'Review code and report findings.',
      },
    },
  },
});

const agents = await q.supportedAgents();
The returned list may include Agents registered through options.agents, and may also include built-in, project, user, or plugin Agents discovered by the current CLI. The actual available entries depend on the qoderclicn 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 options.agent.

Model Policy

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

options.resolveModel

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.resolveModelTimeoutMs

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

ModelPolicyProvider

Callback function signature. May be synchronous or asynchronous.
type ModelPolicyProvider = (
  context: ModelPolicyContext,
) => ModelPolicyResult | Promise<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 summarises 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
Behavioural 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. See Model Policy — Error handling.

ModelPolicyContext

The context passed to the callback on every invocation.
interface ModelPolicyContext {
  purpose: QoderModelPurpose;
  sessionId: string;
  availableModels: ModelInfo[];
}
FieldTypeRequiredDescription
purposeQoderModelPurposeyesPurpose of this LLM call
sessionIdstringyesCurrent 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, supplied by the CLI on every get_model_policy request

QoderModelPurpose

type QoderModelPurpose =
  | 'main'
  | 'subagent'
  | 'web_fetch'
  | 'image_gen'
  | 'compact';
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 / summarisation

ModelPolicyResult

The callback's return value.
interface ModelPolicyResult {
  model: string | (CustomModel & { model: string });
  parameters?: Record<string, unknown>;
}
FieldTypeRequiredDescription
modelstring | (CustomModel & { model: string })yesString: model identifier; object: BYOK credentials + model identifier
parametersRecord<string, unknown>noPer-request model-parameter overrides. SDK control keys use camelCase
Supported parameters keys:
KeyTypeDescription
contextWindownumberContext-window size in tokens for this LLM request. Choose a value supported by the selected model, usually from ModelInfo.context_config
reasoningEffortstringThinking / 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 q.getAvailableModels(). 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 resolveModel callback, set the model field to this object directly, and that LLM request will be routed to a third-party provider.
interface CustomModel {
  provider: string;
  model: string;
  api_key: string;
  style?: string;
}
FieldTypeRequiredDescription
providerstringyesProvider key — must match a BYOKProviderInfo.key
modelstringyesModel identifier — extracted by the SDK as the model ID for this call
api_keystringyesThe API Key supplied by the user
stylestringnoUpstream 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.

BYOK catalog types

The provider/model catalog returned by q.listByokProviders().
interface SDKControlGetByokConfigResponse {
  providers: BYOKProviderInfo[];
}

interface BYOKProviderInfo {
  key: string;
  display_name: string;
  api_key_url: string;
  types: BYOKModelTypeInfo[];
}

interface BYOKModelTypeInfo {
  key?: string;
  display_name: string;
  models: BYOKModelInfo[];
}

interface BYOKModelInfo {
  key: string;
  display_name: string;
  is_vl: boolean;
  is_reasoning: boolean;
  format: string;
  max_input_tokens: number;
}

BYOKProviderInfo

FieldTypeDescription
keystringProvider key — fill into CustomModel.provider for BYOK
display_namestringDisplay name
api_key_urlstringURL pointing the user where to obtain an API Key
typesBYOKModelTypeInfo[]Model groups under this provider

BYOKModelTypeInfo

FieldTypeDescription
keystring | undefinedGroup key, common values: cp / tp / pg
display_namestringGroup display name
modelsBYOKModelInfo[]Models within the group

BYOKModelInfo

FieldTypeDescription
keystringModel ID — fill into CustomModel.model
display_namestringDisplay name
is_vlbooleanWhether vision / multi-modal input is supported
is_reasoningbooleanWhether this is a reasoning model
formatstringUpstream protocol format (e.g. openai)
max_input_tokensnumberMaximum input token count

ModelInfo

Summary of an available model returned by q.getAvailableModels(). Also used as the element type of ModelPolicyContext.availableModels.
interface ModelInfo {
  value: string;
  displayName: string;
  description: string;
  isEnabled: boolean;
  isNew?: boolean;
  isFree?: boolean;
  priceFactor?: number;
  serverScene?: string;
  context_config?: ModelContextConfig;
  thinking_config?: ModelThinkingConfig;
  promotion?: ModelPromotion;
  serverModel?: ServerModelJson;
}
FieldTypeDescription
valuestringModel identifier — usable as ModelPolicyResult.model or q.setModel() argument
displayNamestringDisplay name
descriptionstringModel description text
isEnabledbooleanWhether currently available
isNewboolean | undefinedWhether this is a newly launched model
isFreeboolean | undefinedWhether this is a free model
priceFactornumber | undefinedPrice factor
serverScenestring | undefinedOriginal top-level key in the server model-list response before client-side merging, such as assistant or byok_enterprise
context_configModelContextConfig | undefinedContext-window configuration (tier label -> token count)
thinking_configModelThinkingConfig | undefinedThinking / reasoning configuration
promotionModelPromotion | undefinedPromotion / discount info from the model list API; absent means no promotion
serverModelServerModelJson | undefinedRaw 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".
type ModelContextConfig = Record<string, ModelContextWindowEntry>;

interface ModelContextWindowEntry {
  token_count: number;
  is_default?: boolean;
}
FieldTypeDescription
token_countnumberToken count for this tier
is_defaultboolean | undefinedWhether this is the default tier

ModelThinkingConfig

Thinking / reasoning configuration for a model.
interface ModelThinkingConfig {
  disabled?: ModelThinkingDisabled;
  enabled?: ModelThinkingEnabled;
}

interface ModelThinkingDisabled {
  description?: string;
}

interface ModelThinkingEnabled {
  description?: string;
  efforts?: Record<string, ModelEffortEntry>;
  is_default?: boolean;
}

interface ModelEffortEntry {
  description?: string;
  is_default?: boolean;
}
FieldTypeDescription
disabledModelThinkingDisabled | undefinedConfiguration shown when thinking is disabled
enabledModelThinkingEnabled | undefinedConfiguration shown when thinking is enabled, including effort levels
enabled.effortsRecord<string, ModelEffortEntry> | undefinedPer-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.
type LocalizedModelText = {
  en?: string;
  zh?: string;
} & Record<string, string | undefined>;

interface ModelPromotion {
  active: boolean;
  badge?: LocalizedModelText;
  description?: LocalizedModelText;
  discount_factor?: number;
  before_promotion_price_factor?: number;
  timezone?: string;
  rule_id?: string;
  window_start?: string;
  window_end?: string;
}
FieldTypeDescription
activebooleanWhether the promotion is currently active for this model
badgeLocalizedModelText | undefinedShort localized label
descriptionLocalizedModelText | undefinedLonger localized description
discount_factornumber | undefinedDiscounted price factor while the promotion is active
before_promotion_price_factornumber | undefinedOriginal price factor before the promotion
timezonestring | undefinedIANA timezone used to evaluate the promotion window
rule_idstring | undefinedServer rule identifier
window_startstring | undefinedDaily window start, in HH:mm format
window_endstring | undefinedDaily 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.
type ServerModelJson = Record<string, ServerModelJsonValue>;

SDKControlGetContextUsageResponse

Current context occupancy and the same locally estimated category data shown by the CLI's /context view. Returned by q.getContextUsage().
type SDKControlGetContextUsageResponse = {
  model: string;
  contextWindow: {
    usedPercentage: number;
  };
  categories: Array<{
    type:
      | 'system_prompt'
      | 'system_tools'
      | 'skills'
      | 'messages'
      | 'other'
      | 'free_space'
      | 'auto_compact';
    percentage: number;
  }>;
  autoCompact: {
    enabled: boolean;
    thresholdPercentage: number;
  };
  skills: {
    count: number;
    percentageOfContext: number;
    items: Array<{
      name: string;
      source: 'project' | 'user' | 'built-in' | 'plugin';
      percentageOfContext: number;
    }>;
  };
  duplicateFileReads: Array<{
    path: string;
    count: number;
  }>;
  session: {
    messageCount: number;
    promptCount: number;
    toolCalls: { total: number; succeeded: number; failed: number };
    linesChanged: { added: number; removed: number };
  };
};
All percentages use a 0100 scale. skills.percentageOfContext and every skill item's percentageOfContext use the full context window as the denominator, not the aggregate size of all skills. The SDK requests this snapshot from the running CLI and does not recalculate it client-side. Category values are local estimates and are not expected to add up exactly to contextWindow.usedPercentage. Use contextWindow.usedPercentage for overall occupancy and the category and skill percentage fields for the corresponding breakdowns.

UsageInfo

Account quota and usage snapshot returned by q.getUsageInfo().
interface UsageInfo {
  userId?: string;
  userType?: string;
  totalUsagePercentage?: number;
  isHighestTier?: boolean;
  expiresAt?: number;
  upgradeUrl?: string;
  userQuota?: UsageQuotaBucket;
  addOnQuota?: UsageAddOnQuotaBucket;
  isQuotaExceeded?: boolean;
  isPlanQuotaProrated?: boolean;
  orgResourcePackage?: UsageOrgResourcePackage;
}

interface UsageQuotaBucket {
  total?: number;
  used?: number;
  remaining?: number;
  percentage?: number;
  unit?: string;
}

interface UsageAddOnQuotaBucket extends UsageQuotaBucket {
  detailUrl?: string;
}

interface UsageOrgResourcePackage {
  used?: number;
  cap?: number;
  remaining?: number;
  percentage?: number;
  available?: boolean;
  unit?: string;
}
FieldTypeDescription
userIdstring | undefinedAccount identifier
userTypestring | undefinedPlan tier (e.g. free, pro, teams)
totalUsagePercentagenumber | undefinedOverall usage across all buckets, 0100
isHighestTierboolean | undefinedWhether the account is already on the highest plan
expiresAtnumber | undefinedCurrent plan/quota expiry, Unix epoch milliseconds
upgradeUrlstring | undefinedUpgrade page URL, when an upgrade is available
userQuotaUsageQuotaBucket | undefinedIncluded plan quota bucket
addOnQuotaUsageAddOnQuotaBucket | undefinedPurchased add-on quota bucket, when the account has add-on quota (detailUrl links to the usage page)
isQuotaExceededboolean | undefinedWhether all available quota is exhausted
isPlanQuotaProratedboolean | undefinedWhether the plan quota is prorated for the current period
orgResourcePackageUsageOrgResourcePackage | undefinedOrganization-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 object.

ModelPolicyTimeoutError

class ModelPolicyTimeoutError extends Error {}
Thrown by the SDK when the resolveModel callback exceeds options.resolveModelTimeoutMs without returning. The query fails directly, with no fallback.

q.setModel()

setModel(model?: string): Promise<void>;
Switches the model for 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.

q.getAvailableModels()

getAvailableModels(): Promise<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.

q.listByokProviders()

listByokProviders(): Promise<BYOKProviderInfo[] | null>;
Returns the BYOK provider/model catalog available to the current account as an array:
  • Returns null: 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).
Field semantics: see BYOK catalog types.

q.getContextUsage()

getContextUsage(): Promise<SDKControlGetContextUsageResponse>;
Fetches a live context snapshot from the running CLI. This does not start an Agent turn, so it can be used to update a context meter while an interactive session remains open.
const usage = await q.getContextUsage();

console.log(`Context used: ${usage.contextWindow.usedPercentage}%`);
for (const skill of usage.skills.items) {
  console.log(`${skill.name}: ${skill.percentageOfContext}% of context`);
}
Return type: see SDKControlGetContextUsageResponse.

q.getUsageInfo()

getUsageInfo(): Promise<UsageInfo | null>;
Fetches the current account's quota and usage information from the running CLI in real time.
  • Returns null: the CLI is unauthenticated, an older CLI version does not support this API, or the CLI did not return an object (graceful fallback, no exception).
  • Returns a UsageInfo object: the known account quota and usage fields whose runtime types are valid. Missing or invalid fields are omitted.
import { query, qodercliAuth } from '@qodercn-ai/qodercn-agent-sdk';

const q = query({
  prompt: userMessages(),
  options: { auth: qodercliAuth() },
});

const usage = await q.getUsageInfo();
if (usage) {
  console.log(`Plan: ${usage.userType}, used ${usage.totalUsagePercentage}%`);
  console.log(`Plan quota: ${usage.userQuota?.remaining}/${usage.userQuota?.total} ${usage.userQuota?.unit} left`);
}
Return type: see UsageInfo.

CanUseTool

Host-defined custom tool permission approval callback.
type CanUseTool = (
  toolName: string,
  input: Record<string, unknown>,
  options: CanUseToolOptions,
) => Promise<PermissionResult>;

CanUseToolOptions

type CanUseToolOptions = {
  signal: AbortSignal;
  suggestions?: PermissionUpdate[];
  blockedPath?: string;
  decisionReason?: string;
  decisionReasonType?: PermissionDecisionReasonType;
  classifierApprovable?: boolean;
  title?: string;
  displayName?: string;
  description?: string;
  toolUseID: string;
  agentID?: string;
  exitPlanMode?: ExitPlanModeApprovalDetails;
};
options FieldTypeDescription
signalAbortSignalAborted when cancelled
suggestionsPermissionUpdate[]Permission update suggestions from CLI
blockedPathstringFile path triggering authorization (file-related scenarios only)
decisionReasonstringHuman-readable authorization reason from CLI
decisionReasonTypePermissionDecisionReasonTypePermission reason classification
classifierApprovablebooleanWhether the current call can be auto-approved by the runtime classifier
title / displayName / descriptionstringHuman-readable authorization text generated at runtime
toolUseIDstringThis tool invocation's ID
agentIDstringSub-Agent ID initiating the call
exitPlanModeExitPlanModeApprovalDetailsApproval details for exiting plan mode
For full usage and examples, see Permission Control.

PermissionMode

type PermissionMode =
  | '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 workspace files
'bypassPermissions'Skips permission checks; must also set allowDangerouslySkipPermissions: trueTrusted automation or test environments
'yolo'Compatibility alias for 'bypassPermissions'; must also set allowDangerouslySkipPermissions: 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, see Permission Control.

PermissionResult

Return value of CanUseTool.
type PermissionResult =
  | {
      behavior: 'allow';
      updatedInput?: Record<string, unknown>;
      updatedPermissions?: PermissionUpdate[];
      toolUseID?: string;
      decisionClassification?: PermissionDecisionClassification;
    }
  | {
      behavior: 'deny';
      message: string;
      interrupt?: boolean;
      toolUseID?: string;
      decisionClassification?: PermissionDecisionClassification;
    };
allow.updatedInput replaces the actual parameters the tool receives when modified. deny.interrupt: true denies and also interrupts the Agent.

McpServerConfig

MCP server configuration, passed to Options.mcpServers.
type McpServerConfig =
  | McpStdioServerConfig
  | McpSSEServerConfig
  | McpHttpServerConfig
  | McpSdkServerConfigWithInstance;

McpStdioServerConfig

type McpStdioServerConfig = {
  type?: 'stdio';
  command: string;
  args?: string[];
  env?: Record<string, string>;
};

McpSSEServerConfig

type McpSSEServerConfig = {
  type: 'sse';
  url: string;
  headers?: Record<string, string>;
};

McpHttpServerConfig

type McpHttpServerConfig = {
  type: 'http';
  url: string;
  headers?: Record<string, string>;
};

McpSdkServerConfigWithInstance

type McpSdkServerConfigWithInstance = {
  type: 'sdk';
  name: string;
  instance: McpServer;
};
Returned by the createSdkMcpServer() factory; see MCP - In-Process Server.

SdkPluginConfig

Load local plugins.
type SdkPluginConfig = {
  type: 'local';
  path: string;
};
FieldTypeDescription
type'local'Currently only local is supported
pathstringAbsolute or relative path to the plugin directory

SettingSource

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

ToolConfig

Built-in tool behavior configuration.
type ToolConfig = {
  askUserQuestion?: {
    previewFormat?: 'markdown' | 'html';
  };
};

Built-in Tool List

In tools, allowedTools, disallowedTools, canUseTool, hook matchers, and Agent tool allowlists, built-in tools use the runtime tool names in the table below.
CategoryTool nameDescription
Command executionBashExecute shell commands
File operationsReadRead file contents
File operationsEditEdit files by string matching
File operationsWriteCreate or overwrite files
SearchGlobSearch by filename pattern
SearchGrepSearch by content regex
NetworkWebFetchFetch and process URL content
NetworkWebSearchWeb search
AgentAgentInvoke a subagent
InteractionAskUserQuestionAsk the user a question
NotebookNotebookEditEdit notebook cells
Persistent tasksTaskCreateCreate a task
Persistent tasksTaskGetRetrieve task details
Persistent tasksTaskUpdateUpdate task fields, status, and dependencies
Persistent tasksTaskListList tasks
Background tasksTaskOutputSend output to a background task
Background tasksTaskStopStop a background task
Plan / worktreeExitPlanModeExit plan mode
Plan / worktreeEnterWorktreeEnter a git worktree
Plan / worktreeExitWorktreeExit a worktree
ConfigConfigRead or write configuration
Todo fallbackTodoWriteReplaces the persistent Task tools when QODER_FEATURE_TASKS=false
MCP resourcesListMcpResourcesList MCP resources
MCP resourcesReadMcpResourceRead an MCP resource
MCP invocationMcpGeneric MCP tool call
Custom MCP tool names use this format:
mcp__{serverName}__{toolName}

tool()

Creates a type-safe SDK MCP tool definition.
function tool<Schema extends AnyZodRawShape>(
  name: string,
  description: string,
  inputSchema: Schema,
  handler: (
    args: InferShape<Schema>,
    extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
  ) => Promise<CallToolResult>,
  extras?: ToolExtras,
): SdkMcpToolDefinition<Schema>;
ParameterTypeRequiredMeaningCurrent Qoder behavior
namestringYesUnique tool identifier within the current MCP serverForms the model-visible full tool name mcp__{serverName}__{name}; registration requires it to be non-empty
descriptionstringYesTool description shown to the model; describe when to use it, what it does, and what it returnsForwarded into the tool list and directly affects whether the model calls the tool correctly; registration requires it to be non-empty
inputSchemaSchema extends AnyZodRawShapeYesZod raw shape defining tool input parametersThe SDK uses it to generate the MCP input schema and infer handler args as InferShape<Schema>
handler(args, extra) => Promise<CallToolResult>YesAsync function executed when the tool is calledExecuted by the SDK when the model calls the tool; the return value is sent back to the model as a tool result
extrasToolExtrasNoExtra tool metadata, currently used for annotationsThe SDK registers supported annotations on the MCP server; this does not replace permission configuration
tool() itself is a factory for defining tools. Registration constraints such as non-empty name, non-empty description, and duplicate tool names are validated by createSdkMcpServer() when tools are registered.

AnyZodRawShape

type AnyZodRawShape = ZodRawShapeCompat;
AnyZodRawShape is compatible with Zod 3 / Zod 4. It represents a field object, not z.object(...).

InferShape

type InferShape<T extends AnyZodRawShape> = ShapeOutput<T>;
InferShape infers the handler args type from the Zod raw shape.

SdkMcpToolDefinition

type SdkMcpToolDefinition<
  Schema extends AnyZodRawShape = AnyZodRawShape,
> = {
  name: string;
  description: string;
  inputSchema: Schema;
  annotations?: ToolAnnotations;
  handler: (
    args: InferShape<Schema>,
    extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
  ) => Promise<CallToolResult>;
};

ToolExtras

type ToolExtras = {
  annotations?: ToolAnnotations;
};

ToolAnnotations

type ToolAnnotations = {
  title?: string;
  readOnlyHint?: boolean;
  destructiveHint?: boolean;
  openWorldHint?: boolean;
};
FieldTypeOptionalMeaningCurrent Qoder behavior
titlestringYesHuman-readable title for the toolMCP metadata; currently not documented as a verified Qoder behavioral capability
readOnlyHintbooleanYesMarks that the tool does not modify stateCurrent observable effect: read-only tools can be eligible for concurrent execution within the same batch of tool calls; this is not a permission switch
destructiveHintbooleanYesMarks that the tool may perform destructive updatesRisk metadata; currently does not automatically block an authorized tool execution
openWorldHintbooleanYesMarks whether the tool interacts with the external open worldExternal-interaction metadata; currently does not automatically block an authorized tool execution
These fields are metadata and scheduling hints, not permission switches. Whether execution is allowed is still determined by tools, allowedTools, disallowedTools, permissionMode, canUseTool, and hooks. In the feature documentation, the verified behavior capabilities are readOnlyHint, destructiveHint, and openWorldHint; title is retained here only as MCP metadata in the type reference.

createSdkMcpServer()

Creates an MCP server that runs in the same process as the SDK.
function createSdkMcpServer(
  options: CreateSdkMcpServerOptions,
): McpSdkServerConfigWithInstance;

CreateSdkMcpServerOptions

type CreateSdkMcpServerOptions = {
  name: string;
  version?: string;
  tools?: Array<SdkMcpToolDefinition<any>>;
};
FieldDefaultDescription
nameRequiredMCP server name; it becomes part of mcp__{serverName}__{toolName}
version'1.0.0'Server version information
toolsundefinedTools registered to this server

Return Value

Returns McpSdkServerConfigWithInstance, which can be passed directly as a value in options.mcpServers. For the full MCP server configuration, see McpServerConfig.
type McpSdkServerConfigWithInstance = {
  type: 'sdk';
  name: string;
  instance: McpServer;
};

CallToolResult

A tool handler returns the MCP protocol CallToolResult.
type CallToolResult = {
  content: McpToolResultContent[];
  isError?: boolean;
  _meta?: Record<string, unknown>;
};

McpToolResultContent

type McpToolResultContent =
  | { type: 'text'; text: string }
  | { type: 'image'; data: string; mimeType: string }
  | { type: 'audio'; data: string; mimeType: string }
  | {
      type: 'resource_link';
      uri: string;
      name?: string;
      description?: string;
      mimeType?: string;
    }
  | {
      type: 'resource';
      resource: {
        uri: string;
        mimeType?: string;
        text?: string;
        blob?: string;
      };
    };
FieldDescription
contentArray of content blocks returned to the model
isErrorWhen true, indicates the tool failed semantically
_metaTool-result metadata, forwarded with the MCP response

Built-in Tool Input and Output Types

The SDK provides input / output structures for built-in tools at the type level. Note: these are TypeScript type names; permission configuration still uses the runtime tool names above.

AgentInput / AgentOutput

type AgentInput = {
  prompt: string;
  agent?: string;
  timeout_ms?: number;
};

type AgentOutput = {
  result: string;
  agent?: string;
  error?: string;
};

BashInput / BashOutput

type BashInput = {
  command: string;
  timeout?: number;
  description?: string;
  run_in_background?: boolean;
};

type BashOutput = {
  stdout: string;
  stderr: string;
  exitCode: number;
  interrupted?: boolean;
};

type BashToolBackgroundLaunchResult = {
  kind: 'backgrounded';
  pid: number;
  taskId: string;
  outputFile?: string;
  command: string;
  stdout?: string;
  initialOutput: string;
  backgroundReason?: 'user' | 'model' | 'timeout' | 'auto';
  telemetryExecutionId?: string;
  totalLines?: number;
  totalBytes?: number;
};

function isBashToolBackgroundLaunchResult(
  value: unknown,
): value is BashToolBackgroundLaunchResult;
BashOutput remains the foreground compatibility output. When Bash moves to the background, its structured launch data arrives in SDKUserMessage.tool_use_result; narrow the unknown value with isBashToolBackgroundLaunchResult(). When outputFile is present, it is an ordinary raw byte stream containing combined stdout and stderr, not JSONL. A host can either read it once after the matching terminal CLI event, or tail it while the task runs by retaining a byte offset and decoding new bytes with a streaming TextDecoder. After the terminal event, perform one final drain and flush the decoder. A temporary EOF does not mean the task is complete, and the SDK does not read, poll, parse, or cache this file. The CLI event stream is authoritative for lifecycle state. A naturally completed background Shell emits task_notification; an explicitly stopped Shell emits terminal task_updated state and is removed from background_tasks_changed.
import { readFile } from 'node:fs/promises';
import {
  isBashToolBackgroundLaunchResult,
  type BashToolBackgroundLaunchResult,
} from '@qodercn-ai/qodercn-agent-sdk';

let launch: BashToolBackgroundLaunchResult | undefined;

for await (const message of q) {
  if (
    message.type === 'user' &&
    isBashToolBackgroundLaunchResult(message.tool_use_result)
  ) {
    launch = message.tool_use_result;
  }

  if (
    launch?.outputFile &&
    message.type === 'system' &&
    message.subtype === 'task_notification' &&
    message.task_id === launch.taskId
  ) {
    const rawOutput = await readFile(launch.outputFile, 'utf8');
    console.log(rawOutput);
  }
}

FileReadInput / FileReadOutput

The runtime tool name is Read; the type names remain FileReadInput / FileReadOutput.
type FileReadInput = {
  file_path: string;
  offset?: number;
  limit?: number;
  pages?: string;
};

type FileReadOutput =
  | {
      type: 'text';
      text: string;
      file_path: string;
      totalLines?: number;
    }
  | {
      type: 'image';
      source: {
        type: 'base64';
        media_type: string;
        data: string;
      };
      file_path: string;
    }
  | {
      type: 'notebook';
      cells: Array<{
        cell_number: number;
        cell_type: 'code' | 'markdown' | 'raw';
        source: string;
        outputs?: string[];
      }>;
      file_path: string;
    }
  | {
      type: 'pdf';
      pages: Array<{
        page_number: number;
        content: string;
      }>;
      file_path: string;
      totalPages: number;
    }
  | {
      type: 'parts';
      parts: Array<
        | { type: 'text'; text: string }
        | { type: 'image'; source: { type: 'base64'; media_type: string; data: string } }
      >;
      file_path: string;
    }
  | {
      type: 'file_unchanged';
      file_path: string;
      message: string;
    };

FileEditInput / FileEditOutput

The runtime tool name is Edit.
type FileEditInput = {
  file_path: string;
  old_string: string;
  new_string: string;
  replace_all?: boolean;
};

type FileEditOutput = {
  success: boolean;
  file_path: string;
  diff?: string;
  error?: string;
};

FileWriteInput / FileWriteOutput

The runtime tool name is Write.
type FileWriteInput = {
  file_path: string;
  content: string;
};

type FileWriteOutput = {
  success: boolean;
  file_path: string;
  bytesWritten?: number;
  error?: string;
};

GlobInput / GlobOutput

type GlobInput = {
  pattern: string;
  path?: string;
};

type GlobOutput = {
  files: string[];
  totalMatches: number;
  truncated?: boolean;
};

GrepInput / GrepOutput

type GrepInput = {
  pattern: string;
  path?: string;
  glob?: string;
  type?: string;
  output_mode?: 'content' | 'files_with_matches' | 'count';
  head_limit?: number;
  offset?: number;
  context?: number;
  '-A'?: number;
  '-B'?: number;
  '-C'?: number;
  '-i'?: boolean;
  '-n'?: boolean;
  multiline?: boolean;
};

type GrepOutput = {
  results: string;
  matchCount: number;
  truncated?: boolean;
};

WebFetchInput / WebFetchOutput

type WebFetchInput = {
  url: string;
  prompt: string;
};

type WebFetchOutput = {
  content: string;
  url: string;
  statusCode?: number;
  error?: string;
  redirectUrl?: string;
};

WebSearchInput / WebSearchOutput

type WebSearchInput = {
  query: string;
  allowed_domains?: string[];
  blocked_domains?: string[];
};

type WebSearchOutput = {
  results: Array<{
    title: string;
    url: string;
    snippet: string;
  }>;
  query: string;
};

AskUserQuestionInput / AskUserQuestionOutput

type AskUserQuestionInput = {
  question: string;
  options?: string[];
  default?: string;
};

type AskUserQuestionOutput = {
  answer: string;
};

NotebookEditInput / NotebookEditOutput

type NotebookEditInput = {
  notebook_path: string;
  cell_id?: string;
  cell_type?: 'code' | 'markdown';
  new_source: string;
  edit_mode?: 'replace' | 'insert' | 'delete';
};

type NotebookEditOutput = {
  success: boolean;
  notebook_path: string;
  error?: string;
};

TaskOutputInput

type TaskOutputInput = {
  task_id: string;
  output: string;
};

TaskStopInput / TaskStopOutput

type TaskStopInput = {
  task_id: string;
  reason?: string;
};

type TaskStopOutput = {
  success: boolean;
  task_id: string;
  error?: string;
};

ExitPlanModeInput / ExitPlanModeOutput

type ExitPlanModeInput = {
  confirm?: boolean;
};

type ExitPlanModeOutput = {
  success: boolean;
  error?: string;
};

ConfigInput / ConfigOutput

type ConfigInput = {
  action: 'get' | 'set' | 'list';
  key?: string;
  value?: unknown;
  scope?: 'user' | 'project' | 'local';
};

type ConfigOutput = {
  success: boolean;
  value?: unknown;
  values?: Record<string, unknown>;
  error?: string;
};

EnterWorktreeInput / EnterWorktreeOutput

type EnterWorktreeInput = {
  name?: string;
  path?: string;
};

type EnterWorktreeOutput = {
  worktreePath: string;
  worktreeBranch?: string;
  message: string;
};
Compatibility: structured Worktree results require qodercli 1.1.27 or newer. SDK 1.0.25 and earlier incorrectly declared worktree_path, branch_name, success, and error; qodercli does not return those legacy fields. Migrate to worktreePath and worktreeBranch, and detect failures through the common tool_result.is_error/content fields.

ExitWorktreeInput / ExitWorktreeOutput

type ExitWorktreeInput = {
  action: 'keep' | 'remove';
  discard_changes?: boolean;
};

type ExitWorktreeOutput = {
  action: 'keep' | 'remove';
  originalCwd: string;
  worktreePath: string;
  worktreeBranch?: string;
  tmuxSessionName?: string;
  discardedFiles?: number;
  discardedCommits?: number;
  message: string;
};
Compatibility: SDK 1.0.25 and earlier incorrectly declared success, error, uncommitted_files, and unmerged_commits; qodercli does not return those legacy fields. The current Qoder CLI returns action, originalCwd, worktreePath, and message, and may also return worktreeBranch. tmuxSessionName, discardedFiles, and discardedCommits are optional fields reserved for forward compatibility and are not currently populated. Do not rely on them to determine the exit result.

Persistent Task input / output types

type TaskCreateInput = {
  subject: string;
  description: string;
  activeForm?: string;
  metadata?: Record<string, unknown>;
};

type TaskCreateOutput = {
  task: { id: string; subject: string };
};

type TaskGetInput = { taskId: string };

type TaskGetOutput = {
  task: {
    id: string;
    subject: string;
    description: string;
    status: 'pending' | 'in_progress' | 'completed';
    blocks: string[];
    blockedBy: string[];
  } | null;
};

type TaskUpdateInput = {
  taskId: string;
  subject?: string;
  description?: string;
  activeForm?: string;
  status?: 'pending' | 'in_progress' | 'completed' | 'deleted';
  addBlocks?: string[];
  addBlockedBy?: string[];
  owner?: string;
  metadata?: Record<string, unknown>;
};

type TaskUpdateOutput = {
  success: boolean;
  taskId: string;
  updatedFields: string[];
  error?: string;
  statusChange?: { from: string; to: string };
};

type TaskListInput = Record<string, never>;

type TaskListOutput = {
  tasks: Array<{
    id: string;
    subject: string;
    status: 'pending' | 'in_progress' | 'completed';
    owner?: string;
    blockedBy: string[];
  }>;
};

TodoWriteInput / TodoWriteOutput

TodoWrite is used when persistent tasks are disabled with QODER_FEATURE_TASKS=false. Default SDK sessions expose the persistent Task tools above instead. When options.tools is omitted, the SDK follows this feature flag automatically.
type TodoWriteInput = {
  todos: Array<{
    id?: string;
    content: string;
    status: 'pending' | 'in_progress' | 'completed';
    priority?: 'low' | 'medium' | 'high';
  }>;
};

type TodoWriteOutput = {
  success: boolean;
  todos: Array<{
    id: string;
    content: string;
    status: 'pending' | 'in_progress' | 'completed';
    priority?: 'low' | 'medium' | 'high';
  }>;
  error?: string;
};

ListMcpResourcesInput / ListMcpResourcesOutput

type ListMcpResourcesInput = {
  server_name: string;
};

type ListMcpResourcesOutput = {
  resources: Array<{
    uri: string;
    name: string;
    description?: string;
    mimeType?: string;
  }>;
  server_name: string;
};

ReadMcpResourceInput

type ReadMcpResourceInput = {
  server_name: string;
  uri: string;
};

McpInput / McpOutput

type McpInput = {
  server_name: string;
  tool_name: string;
  arguments?: Record<string, unknown>;
};

type McpOutput = {
  content: unknown;
  isError?: boolean;
};

ToolInputSchemas

type ToolInputSchemas =
  | AgentInput
  | BashInput
  | FileReadInput
  | FileEditInput
  | FileWriteInput
  | GlobInput
  | GrepInput
  | WebFetchInput
  | WebSearchInput
  | AskUserQuestionInput
  | NotebookEditInput
  | TaskOutputInput
  | TaskStopInput
  | TaskCreateInput
  | TaskGetInput
  | TaskUpdateInput
  | TaskListInput
  | ExitPlanModeInput
  | ConfigInput
  | EnterWorktreeInput
  | ExitWorktreeInput
  | TodoWriteInput
  | ListMcpResourcesInput
  | ReadMcpResourceInput
  | McpInput;

ToolOutputSchemas

type ToolOutputSchemas =
  | AgentOutput
  | BashToolBackgroundLaunchResult
  | BashOutput
  | FileReadOutput
  | FileEditOutput
  | FileWriteOutput
  | GlobOutput
  | GrepOutput
  | WebFetchOutput
  | WebSearchOutput
  | AskUserQuestionOutput
  | NotebookEditOutput
  | TaskStopOutput
  | TaskCreateOutput
  | TaskGetOutput
  | TaskUpdateOutput
  | TaskListOutput
  | ExitPlanModeOutput
  | ConfigOutput
  | EnterWorktreeOutput
  | ExitWorktreeOutput
  | TodoWriteOutput
  | ListMcpResourcesOutput
  | McpOutput;

Hooks Reference

For usage guide and examples, see Hooks.

Event Overview

EventTriggerControllable Behavior
PreToolUseBefore tool invocationIntercept / allow / modify input
PostToolUseAfter tool succeedsAudit / inject context / override output
PostToolUseFailureAfter tool failsError handling / logging
UserPromptSubmitBefore user prompt is sentInject context / intercept
SessionStartSession beginsInitialize / inject context
SessionEndSession endsCleanup / logging
StopAI stops generatingPrevent stop, force continuation
SubagentStartSubagent startsObserve / log
SubagentStopSubagent stopsObserve / log
PreCompactBefore context compactionObserve / log
PostCompactAfter context compactionObserve / log
CwdChangedWorking directory changesObserve / log
InstructionsLoadedInstruction file loadedObserve / log
FileChangedFile created/modified/deletedObserve / log
PermissionRequestPermission requestedAuto-approve / deny permission requests
PermissionDeniedPermission request deniedObserve / audit the denial
WorktreeCreateManaged worktree requestedReplace the built-in physical creation
WorktreeRemoveHook-created worktree is removedReplace the built-in physical deletion

HookEvent

Union type of registrable hook events.
type HookEvent =
  | 'PreToolUse'
  | 'PostToolUse'
  | 'PostToolUseFailure'
  | 'UserPromptSubmit'
  | 'SessionStart'
  | 'SessionEnd'
  | 'Stop'
  | 'SubagentStart'
  | 'SubagentStop'
  | 'PreCompact'
  | 'PostCompact'
  | 'CwdChanged'
  | 'InstructionsLoaded'
  | 'FileChanged'
  | 'PermissionRequest'
  | 'PermissionDenied'
  | 'WorktreeCreate'
  | 'WorktreeRemove';

HookCallback

type HookCallback = (
  input: HookInput,
  toolUseID: string | undefined,
  options: { signal: AbortSignal }
) => Promise<HookJSONOutput>;

HookCallbackMatcher

interface HookCallbackMatcher {
  matcher?: string;
  hooks: HookCallback[];
  timeout?: number;
}
FieldTypeDescription
matcherstringOptional regex; filters by tool_name
hooksHookCallback[]Callback list executed on match
timeoutnumberOptional timeout in seconds (default: 60)

BaseHookInput

Common input fields shared by all hook events.
interface BaseHookInput {
  hook_event_name: string;
  session_id: string;
  transcript_path: string;
  cwd: string;
}
FieldTypeDescription
hook_event_namestringEvent type identifier (e.g. "PreToolUse")
session_idstringUnique identifier for the current session
transcript_pathstringPath to session transcript file (JSONL format)
cwdstringCurrent working directory of the session

HookJSONOutput

Return type for hook callbacks.
interface HookJSONOutput {
  continue?: boolean;
  stopReason?: string;
  decision?: string;
  reason?: string;
  hookSpecificOutput?: object;
}
FieldTypeDefaultDescription
continuebooleantrueSet to false to terminate the session. Only effective for PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, SubagentStop
stopReasonstringHuman-readable reason for stopping (used with continue: false)
decisionstring"approve" or "block". "block" prevents tool execution; for Stop events, "block" prevents stopping and forces continuation
reasonstringReason for the decision (shown to the model; for Stop event "block", injected as a continuation prompt)
hookSpecificOutputobjectEvent-specific output (see each event type)
When multiple hooks return conflicting decision values, "deny" / "block" takes precedence (strictest rule wins).

PreToolUseHookInput

interface PreToolUseHookInput extends BaseHookInput {
  hook_event_name: 'PreToolUse';
  permission_mode: string | undefined;
  tool_name: string;
  tool_input: unknown;
}
FieldTypeDescription
permission_modestring | undefinedCurrent session permission mode
tool_namestringName of the tool being called
tool_inputunknownArguments passed to the tool
hookSpecificOutput:
FieldTypeDescription
hookEventName"PreToolUse"Must be set
permissionDecisionstring"allow" / "deny" / "ask" / "defer"
permissionDecisionReasonstringReason for the permission decision
updatedInputRecord<string, unknown>Modified tool input, replaces original tool_input
additionalContextstringExtra context injected into the model's next turn

PostToolUseHookInput

interface PostToolUseHookInput extends BaseHookInput {
  hook_event_name: 'PostToolUse';
  tool_name: string;
  tool_input: unknown;
  tool_response: unknown;
}
FieldTypeDescription
tool_namestringName of the tool that was called
tool_inputunknownArguments passed to the tool
tool_responseunknownTool execution result
Output behavior:
FieldLocationBehavior
hookSpecificOutput.updatedToolOutputEvent-specific outputOverrides tool_response; model only sees the overridden value
hookSpecificOutput.additionalContextEvent-specific outputAppends supplementary context without modifying original result
decision: "block" + reasonTop-level outputPrevents agent from further processing this tool result
hookSpecificOutput:
FieldTypeDescription
hookEventName"PostToolUse"Must be set
updatedToolOutputstringOverrides tool response content
additionalContextstringExtra context appended alongside tool result
When multiple hooks set updatedToolOutput, the last non-empty value wins. For chained transforms, execute them sequentially within a single callback.

PostToolUseFailureHookInput

interface PostToolUseFailureHookInput extends BaseHookInput {
  hook_event_name: 'PostToolUseFailure';
  tool_name: string;
  tool_input: unknown;
  error: string;
  is_interrupt: boolean | undefined;
}
FieldTypeDescription
tool_namestringName of the failed tool
tool_inputunknownArguments passed to the tool
errorstringError message
is_interruptboolean | undefinedWhether caused by an interrupt/abort

UserPromptSubmitHookInput

interface UserPromptSubmitHookInput extends BaseHookInput {
  hook_event_name: 'UserPromptSubmit';
  prompt: string;
}
FieldTypeDescription
promptstringUser input text
hookSpecificOutput:
FieldTypeDescription
hookEventName"UserPromptSubmit"Must be set
additionalContextstringExtra context appended to the user prompt

SessionStartHookInput

interface SessionStartHookInput extends BaseHookInput {
  hook_event_name: 'SessionStart';
  source: string;
}
FieldTypeDescription
sourcestringSession start reason: "startup" / "resume" / "clear" / "compact"
hookSpecificOutput:
FieldTypeDescription
hookEventName"SessionStart"Must be set
additionalContextstringContext injected at session start

SessionEndHookInput

interface SessionEndHookInput extends BaseHookInput {
  hook_event_name: 'SessionEnd';
  reason: string;
}
FieldTypeDescription
reasonstringSession end reason: "clear" / "resume" / "logout" / "prompt_input_exit" / "other" / "bypass_permissions_disabled"

StopHookInput

interface StopHookInput extends BaseHookInput {
  hook_event_name: 'Stop';
  stop_hook_active: boolean;
}
FieldTypeDescription
stop_hook_activebooleanWhether a Stop hook is currently preventing stop
Return { decision: 'block', reason: '...' } to prevent the AI from stopping and force continuation. reason is injected as a continuation prompt into the model context.

SubagentStartHookInput

interface SubagentStartHookInput extends BaseHookInput {
  hook_event_name: 'SubagentStart';
  agent_id: string;
  agent_type: string;
}
FieldTypeDescription
agent_idstringUnique identifier of the subagent instance
agent_typestringType/role of the subagent

SubagentStopHookInput

interface SubagentStopHookInput extends BaseHookInput {
  hook_event_name: 'SubagentStop';
  stop_hook_active: boolean;
}
FieldTypeDescription
stop_hook_activebooleanWhether a Stop hook is currently preventing stop

PreCompactHookInput

interface PreCompactHookInput extends BaseHookInput {
  hook_event_name: 'PreCompact';
  trigger: string;
  custom_instructions: string | null;
}
FieldTypeDescription
triggerstringTrigger reason: "manual" / "auto"
custom_instructionsstring | nullCustom instructions for compaction summary

PostCompactHookInput

interface PostCompactHookInput extends BaseHookInput {
  hook_event_name: 'PostCompact';
  trigger: string;
  compact_summary: string;
}
FieldTypeDescription
triggerstringTrigger reason: "manual" / "auto"
compact_summarystringSummary generated after context compaction

CwdChangedHookInput

interface CwdChangedHookInput extends BaseHookInput {
  hook_event_name: 'CwdChanged';
  old_cwd: string;
  new_cwd: string;
}
FieldTypeDescription
old_cwdstringWorking directory before the change
new_cwdstringWorking directory after the change

InstructionsLoadedHookInput

interface InstructionsLoadedHookInput extends BaseHookInput {
  hook_event_name: 'InstructionsLoaded';
  load_reason: string;
}
FieldTypeDescription
load_reasonstringLoad reason: "nested_traversal" / "path_glob_match"

FileChangedHookInput

interface FileChangedHookInput extends BaseHookInput {
  hook_event_name: 'FileChanged';
  file_path: string;
  event: string;
}
FieldTypeDescription
file_pathstringPath of the changed file
eventstringFilesystem event: "change" / "add" / "unlink"

PermissionRequestHookInput

interface PermissionRequestHookInput extends BaseHookInput {
  hook_event_name: 'PermissionRequest';
  tool_name: string;
  tool_input: unknown;
  permission_suggestions: PermissionUpdate[] | undefined;
}
FieldTypeDescription
tool_namestringTool requesting permission
tool_inputunknownTool input arguments
permission_suggestionsPermissionUpdate[] | undefinedSuggested permission rules
hookSpecificOutput:
FieldTypeDescription
hookEventName"PermissionRequest"Must be set
decisionobjectPermission decision (see below)
decision is one of:
  • Approve: { behavior: "allow", updatedInput?: Record<string, unknown>, updatedPermissions?: PermissionUpdate[] }
  • Deny: { behavior: "deny", message?: string }

PermissionDeniedHookInput

interface PermissionDeniedHookInput extends BaseHookInput {
  hook_event_name: 'PermissionDenied';
  tool_name: string;
  tool_input: unknown;
  tool_use_id: string;
  denial_reason?: string;
}
This event observes a permission denial after it occurs. It does not replace PermissionRequest decisions.

WorktreeCreateHookInput / WorktreeRemoveHookInput

interface WorktreeCreateHookInput extends BaseHookInput {
  hook_event_name: 'WorktreeCreate';
  name: string;
}

interface WorktreeRemoveHookInput extends BaseHookInput {
  hook_event_name: 'WorktreeRemove';
  worktree_path: string;
}

type WorktreeCreateHookSpecificOutput = {
  hookEventName: 'WorktreeCreate';
  worktreePath: string;
};
These are replacement hooks, not read-only notifications. A registered WorktreeCreate callback must physically create the resource and return hookSpecificOutput.worktreePath; the paired WorktreeRemove callback must physically delete the hook-created resource. Qoder CLI continues to manage session cwd, transcript, Resume, and Exit state.

Message Types

SDKMessage

Discriminated union of all messages flowing from Query.
type SDKMessage =
  | SDKAssistantMessage
  | SDKUserMessage
  | SDKUserMessageReplay
  | SDKResultMessage
  | SDKSystemMessage
  | SDKPartialAssistantMessage
  | SDKCompactBoundaryMessage
  | SDKStatusMessage
  | SDKMcpStatusChangeMessage
  | SDKAPIRetryMessage
  | SDKModelQueueStatusMessage
  | SDKHookStartedMessage
  | SDKHookProgressMessage
  | SDKHookResponseMessage
  | SDKTaskStartedMessage
  | SDKTaskProgressMessage
  | SDKTaskNotificationMessage
  | SDKSessionStateChangedMessage
  | SDKSessionTitleChangedMessage
  | SDKFilesPersistedEvent
  | SDKElicitationCompleteMessage
  | SDKPermissionDeniedMessage
  | SDKMirrorErrorMessage
  | SDKPromptSuggestionMessage;
Callers should first branch on message.type, then further dispatch on subtype (only system / result types have subtypes).

SDKAssistantMessage

AI's complete reply, delivered once per turn.
type SDKAssistantMessage = {
  type: 'assistant';
  uuid: string;
  session_id: string;
  parent_tool_use_id: string | null;
  request_id?: string;
  message: {
    role: 'assistant';
    content: Array<
      | { type: 'text'; text: string }
      | { type: 'tool_use'; id: string; name: string; input: unknown }
      | { type: 'thinking'; thinking: string }
    >;
  };
};
The top-level request_id is absent by default. To expose it through options.env, set QODERCN_EXPOSE_REQUEST_ID='true'. Existing message.usage.request_id data is unchanged.

SDKUserMessage

User message or tool result feedback.
type SDKUserMessage = {
  type: 'user';
  uuid?: string;
  session_id?: string;
  priority?: 'now' | 'next' | 'later';
  shouldQuery?: boolean;
  timestamp?: string;
  parent_tool_use_id: string | null;
  custom_context?: Record<string, string>;
  message: {
    role: 'user';
    content: Array<
      | { type: 'text'; text: string }
      | { type: 'image'; source: { type: 'base64'; media_type: string; data: string } }
      | { type: 'tool_result'; tool_use_id: string; content: string | unknown[]; is_error?: boolean }
    >;
  };
  isSynthetic?: boolean;
  tool_use_result?: unknown;
};
FieldDescription
custom_contextCustom context for this message. Overrides options.customContext; omit it to inherit the options default
priorityProcessing time. now stops the current response and handles the message immediately; next (default) uses the next suitable point; later waits until the current response finishes
shouldQueryWhen false, the message is added to the conversation without starting a response by itself. Processing time still follows priority
uuidOptional session-unique message ID used for cancellation and replay deduplication. Do not reuse UUIDs within a session
session_idOutput metadata. Inbound messages always belong to the current query() CLI session. To create a session with a chosen ID, use options.sessionId
timestampOptional message timestamp

SDKUserMessageReplay

Historical user messages replayed during session resume.
type SDKUserMessageReplay = SDKUserMessage & {
  uuid: string;
  session_id: string;
  isReplay: true;
};

SDKResultMessage

Final message when the entire session ends.
type SDKResultMessage =
  | {
      type: 'result';
      subtype: 'success';
      uuid: string;
      session_id: string;
      duration_ms: number;
      duration_api_ms: number;
      is_error: boolean;
      num_turns: number;
      result: string;
      permission_denials: SDKPermissionDenial[];
    }
  | {
      type: 'result';
      subtype:
        | 'error_max_turns'
        | 'error_during_execution';
      // Common fields shared with the success result
      errors: string[];
    };

SDKSystemMessage

Session initialization message (subtype: 'init'). Other system events are delivered via separate message types; see the various SDK*Message types below.
type SDKSystemMessage = {
  type: 'system';
  subtype: 'init';
  uuid: string;
  session_id: string;
  qodercli_version: string;
  protocol_version?: string;
  apiKeySource: 'user' | 'project' | 'org' | 'temporary';
  cwd: string;
  model: string;
  permissionMode: PermissionMode;
  tools: string[];
  slash_commands: string[];
  output_style: string;
  agents?: string[];
  skills: string[];
  plugins: { name: string; path: string; source?: string }[];
  mcp_servers: { name: string; status: string }[];
  capabilities?: string[];
  fast_mode_state?: 'off' | 'cooldown' | 'on';
};
capabilities is an open set of runtime features. Ignore values your application does not recognize.

SDKMirrorErrorMessage

Non-fatal message emitted when the SDK cannot write a batch to the external session store after its final attempt. The query continues.
type SDKMirrorErrorMessage = {
  type: 'system';
  subtype: 'mirror_error';
  error: string;
  key: SessionKey;
  uuid: string;
  session_id: string;
};

SDKPartialAssistantMessage

Requires includePartialMessages: true; streams out incrementally per token. For full usage, see Streaming Output.
type SDKPartialAssistantMessage = {
  type: 'stream_event';
  uuid: string;
  session_id: string;
  parent_tool_use_id: string | null;
  event: {
    type: string;
    index?: number;
    delta?: {
      type?: string;
      text?: string;
      partial_json?: string;
      thinking?: string;
    };
    content_block?: {
      type: string;
      id?: string;
      name?: string;
      text?: string;
    };
  };
};

SDKCompactBoundaryMessage

Boundary marker for context compaction completion.
type SDKCompactBoundaryMessage = {
  type: 'system';
  subtype: 'compact_boundary';
  uuid: string;
  session_id: string;
  compact_metadata: {
    trigger: 'manual' | 'auto';
    pre_tokens: number;
    preserved_segment?: {
      head_uuid: string;
      anchor_uuid: string;
      tail_uuid: string;
    };
  };
};

SDKStatusMessage

Session running state changes (e.g., compacting).
type SDKStatusMessage = {
  type: 'system';
  subtype: 'status';
  status: 'compacting' | null;
  permissionMode?: PermissionMode;
  uuid: string;
  session_id: string;
};

SDKMcpStatusChangeMessage

MCP connection pool state change.
type SDKMcpStatusChangeMessage = {
  type: 'system';
  subtype: 'mcp_status_change';
  servers: McpServerStatus[];
  uuid: string;
  session_id: string;
};

SDKAPIRetryMessage

Automatic retry on network/service errors.
type SDKAPIRetryMessage = {
  type: 'system';
  subtype: 'api_retry';
  attempt: number;
  max_retries: number;
  retry_delay_ms: number;
  error_status: number | null;
  error: SDKAssistantMessageError;
  uuid: string;
  session_id: string;
};

SDKModelQueueStatusMessage

Emitted when the selected model does not have immediate capacity. Applications can use this message to show a waiting state instead of making a long-running query look stalled. No option is required: keep consuming the same Query while qodercli polls for capacity and retries the model request automatically.
model request -> queued -> queued (optional updates) -> ready -> assistant/result
type SDKModelQueueStatusMessage = {
  type: 'system';
  subtype: 'model_queue_status';
  status: 'queued' | 'ready';
  request_id: string;
  request_set_id: string;
  model_key: string;
  queue_type?: string;
  queue_count?: number;
  wait_time_ms?: number;
  queue_wait_elapsed_ms?: number;
  queue_max_wait_ms?: number;
  service_available?: boolean;
  uuid: string;
  session_id: string;
};
FieldDescription
statusqueued means qodercli is still waiting. ready means capacity is available and qodercli is continuing the model request
request_idModel request that entered the queue
request_set_idStable identifier for grouping updates from the same queue recovery cycle
model_keyServer model identifier
queue_typeServer-defined queue category; treat unknown values as opaque
queue_countServer-reported queue count, when available
wait_time_msServer-reported wait time, in milliseconds
queue_wait_elapsed_msTime already spent waiting, in milliseconds
queue_max_wait_msMaximum time qodercli will wait, in milliseconds
service_availableWhether the service reported that the model was available during this status check
for await (const message of q) {
  if (
    message.type === 'system' &&
    message.subtype === 'model_queue_status'
  ) {
    if (message.status === 'queued') {
      showModelWaiting({
        model: message.model_key,
        queueCount: message.queue_count,
        elapsedMs: message.queue_wait_elapsed_ms,
      });
    } else {
      hideModelWaiting(message.request_set_id);
    }
  }

  if (message.type === 'result') {
    hideModelWaiting();
  }
}
More than one queued update can be emitted for a request. Group updates by request_set_id, and treat ready as a transition back to model execution—not as the final query result. Continue consuming messages until an SDKResultMessage arrives.

SDKHookStartedMessage

Hook begins execution.
type SDKHookStartedMessage = {
  type: 'system';
  subtype: 'hook_started';
  hook_id: string;
  hook_name: string;
  hook_event: string;
  uuid: string;
  session_id: string;
};

SDKHookProgressMessage

Hook execution output in progress.
type SDKHookProgressMessage = {
  type: 'system';
  subtype: 'hook_progress';
  hook_id: string;
  hook_name: string;
  hook_event: string;
  stdout: string;
  stderr: string;
  output: string;
  uuid: string;
  session_id: string;
};

SDKHookResponseMessage

Hook finishes.
type SDKHookResponseMessage = {
  type: 'system';
  subtype: 'hook_response';
  hook_id: string;
  hook_name: string;
  hook_event: string;
  output: string;
  stdout: string;
  stderr: string;
  exit_code?: number;
  outcome: 'success' | 'error' | 'cancelled';
  uuid: string;
  session_id: string;
};

SDKTaskStartedMessage

Sub-Agent task starts.
type SDKTaskStartedMessage = {
  type: 'system';
  subtype: 'task_started';
  task_id: string;
  tool_use_id?: string;
  description: string;
  task_type?: string;
  workflow_name?: string;
  prompt?: string;
  uuid: string;
  session_id: string;
};

SDKTaskProgressMessage

Sub-Agent task progress.
type SDKTaskProgressMessage = {
  type: 'system';
  subtype: 'task_progress';
  task_id: string;
  tool_use_id?: string;
  description: string;
  usage: {
    total_tokens: number;
    tool_uses: number;
    duration_ms: number;
  };
  last_tool_name?: string;
  summary?: string;
  uuid: string;
  session_id: string;
};

SDKTaskNotificationMessage

Sub-Agent task finishes.
type SDKTaskNotificationMessage = {
  type: 'system';
  subtype: 'task_notification';
  task_id: string;
  tool_use_id?: string;
  status: 'completed' | 'failed' | 'stopped';
  output_file: string;
  summary: string;
  usage?: {
    total_tokens: number;
    tool_uses: number;
    duration_ms: number;
  };
  uuid: string;
  session_id: string;
};

SDKSessionStateChangedMessage

Main session running state change.
type SDKSessionStateChangedMessage = {
  type: 'system';
  subtype: 'session_state_changed';
  state: 'idle' | 'running' | 'requires_action';
  uuid: string;
  session_id: string;
};

SDKSessionTitleChangedMessage

Session title change.
type SDKSessionTitleChangedMessage = {
  type: 'system';
  subtype: 'session_title_changed';
  title: string;
  source: 'ai' | 'custom';
  revision: number;
  uuid: string;
  session_id: string;
};

SDKFilesPersistedEvent

File checkpoint persistence result.
type SDKFilesPersistedEvent = {
  type: 'system';
  subtype: 'files_persisted';
  files: { filename: string; file_id: string }[];
  failed: { filename: string; error: string }[];
  processed_at: string;
  uuid: string;
  session_id: string;
};

SDKElicitationCompleteMessage

MCP elicitation complete.
type SDKElicitationCompleteMessage = {
  type: 'system';
  subtype: 'elicitation_complete';
  mcp_server_name: string;
  elicitation_id: string;
  uuid: string;
  session_id: string;
};

SDKPermissionDeniedMessage

Tool call short-circuited by permission policy (dontAsk / auto / deny rule, etc.).
type SDKPermissionDeniedMessage = {
  type: 'system';
  subtype: 'permission_denied';
  tool_name: string;
  tool_use_id: string;
  agent_id?: string;
  decision_reason_type?: string;
  decision_reason?: string;
  message: string;
  uuid: string;
  session_id: string;
};

SDKPromptSuggestionMessage

When promptSuggestions: true is enabled, next-step suggestions that may be received after each turn's result.
type SDKPromptSuggestionMessage = {
  type: 'prompt_suggestion';
  suggestion: string;
  uuid: string;
  session_id: string;
};

SDKPermissionDenial

Element in the SDKResultMessage.permission_denials array.
type SDKPermissionDenial = {
  tool_name: string;
  tool_use_id: string;
  tool_input: Record<string, unknown>;
};