diff --git a/packages/sdk-python/src/qwen_code_sdk/query.py b/packages/sdk-python/src/qwen_code_sdk/query.py index 9bef8578145..f07b0b26962 100644 --- a/packages/sdk-python/src/qwen_code_sdk/query.py +++ b/packages/sdk-python/src/qwen_code_sdk/query.py @@ -110,6 +110,8 @@ async def _ensure_started(self) -> None: async def _initialize(self) -> None: try: payload: dict[str, Any] = {"hooks": None} + if self._options.agents: + payload["agents"] = self._options.agents await self._send_control_request("initialize", payload) except Exception as exc: await self._finish_with_error(exc) diff --git a/packages/sdk-python/src/qwen_code_sdk/transport.py b/packages/sdk-python/src/qwen_code_sdk/transport.py index 8542364943f..8d2250d8c2b 100644 --- a/packages/sdk-python/src/qwen_code_sdk/transport.py +++ b/packages/sdk-python/src/qwen_code_sdk/transport.py @@ -218,6 +218,9 @@ def build_cli_arguments(options: QueryOptions) -> list[str]: if options.max_session_turns is not None: args.extend(["--max-session-turns", str(options.max_session_turns)]) + if options.max_subagent_depth is not None: + args.extend(["--max-subagent-depth", str(options.max_subagent_depth)]) + if options.core_tools: args.extend(["--core-tools", ",".join(options.core_tools)]) diff --git a/packages/sdk-python/src/qwen_code_sdk/types.py b/packages/sdk-python/src/qwen_code_sdk/types.py index 3d8ec72038e..49d9a7a60ba 100644 --- a/packages/sdk-python/src/qwen_code_sdk/types.py +++ b/packages/sdk-python/src/qwen_code_sdk/types.py @@ -25,6 +25,29 @@ ] +class RunConfig(TypedDict, total=False): + """Run configuration for a sub-agent.""" + + max_time_minutes: int + max_turns: int + + +class SubagentConfig(TypedDict, total=False): + """Configuration for a sub-agent. + + Required fields: name, description, systemPrompt. + Field names match the CLI wire format (camelCase). + """ + + name: str + description: str + systemPrompt: str + tools: list[str] + model: str + runConfig: RunConfig + color: str + + class PermissionSuggestion(TypedDict): type: Literal["allow", "deny", "modify"] label: str @@ -114,6 +137,8 @@ class QueryOptionsDict(TypedDict, total=False): timeout: TimeoutOptionsDict mcp_servers: dict[str, dict[str, Any]] stderr: Callable[[str], None] + agents: list[dict[str, Any]] + max_subagent_depth: int @dataclass @@ -139,6 +164,8 @@ class QueryOptions: timeout: TimeoutOptions = TimeoutOptions() mcp_servers: dict[str, dict[str, Any]] | None = None stderr: Callable[[str], None] | None = None + agents: list[dict[str, Any]] | None = None + max_subagent_depth: int | None = None @classmethod def from_mapping(cls, value: Mapping[str, Any] | None) -> QueryOptions: @@ -183,6 +210,8 @@ def from_mapping(cls, value: Mapping[str, Any] | None) -> QueryOptions: Callable[[str], None] | None, _as_optional_callable(data, "stderr"), ), + agents=_as_optional_list_of_dicts(data, "agents"), + max_subagent_depth=_as_optional_int(data, "max_subagent_depth"), ) @@ -321,3 +350,19 @@ def _as_optional_nested_dict( raise TypeError(f"{key} must be a mapping of string to mapping") parsed[k] = dict(v) return parsed + + +def _as_optional_list_of_dicts( + data: Mapping[str, Any], key: str +) -> list[dict[str, Any]] | None: + raw = data.get(key) + if raw is None: + return None + if not isinstance(raw, list): + raise TypeError(f"{key} must be a list of mappings") + parsed: list[dict[str, Any]] = [] + for item in raw: + if not isinstance(item, Mapping): + raise TypeError(f"{key} must be a list of mappings") + parsed.append(dict(item)) + return parsed diff --git a/packages/sdk-python/src/qwen_code_sdk/validation.py b/packages/sdk-python/src/qwen_code_sdk/validation.py index f19fe4cfba6..70410de334f 100644 --- a/packages/sdk-python/src/qwen_code_sdk/validation.py +++ b/packages/sdk-python/src/qwen_code_sdk/validation.py @@ -70,6 +70,28 @@ def validate_query_options(options: QueryOptions) -> None: "Remove the mcp_servers option or use the TypeScript SDK." ) + if options.max_subagent_depth is not None: + if not isinstance(options.max_subagent_depth, int) or isinstance( + options.max_subagent_depth, bool + ): + raise ValidationError("max_subagent_depth must be an integer") + if options.max_subagent_depth < 1 or options.max_subagent_depth > 100: + raise ValidationError( + "max_subagent_depth must be between 1 and 100" + ) + + if options.agents: + for i, agent in enumerate(options.agents): + if not isinstance(agent, dict): + raise ValidationError( + f"agents[{i}] must be a mapping" + ) + for required_field in ("name", "description", "systemPrompt"): + if not agent.get(required_field): + raise ValidationError( + f"agents[{i}] must have a non-empty '{required_field}'" + ) + def _validate_optional_callable( value: object, diff --git a/packages/sdk-typescript/src/query/createQuery.ts b/packages/sdk-typescript/src/query/createQuery.ts index 42d332b15f1..542e44a4210 100644 --- a/packages/sdk-typescript/src/query/createQuery.ts +++ b/packages/sdk-typescript/src/query/createQuery.ts @@ -63,6 +63,7 @@ export function query({ stderr: options.stderr, logLevel: options.logLevel, maxSessionTurns: options.maxSessionTurns, + maxSubagentDepth: options.maxSubagentDepth, coreTools: options.coreTools, excludeTools: options.excludeTools, allowedTools: options.allowedTools, diff --git a/packages/sdk-typescript/src/transport/ProcessTransport.ts b/packages/sdk-typescript/src/transport/ProcessTransport.ts index 1a63e96d550..15cd6c345d3 100644 --- a/packages/sdk-typescript/src/transport/ProcessTransport.ts +++ b/packages/sdk-typescript/src/transport/ProcessTransport.ts @@ -305,6 +305,10 @@ export class ProcessTransport implements Transport { args.push('--max-session-turns', String(this.options.maxSessionTurns)); } + if (this.options.maxSubagentDepth !== undefined) { + args.push('--max-subagent-depth', String(this.options.maxSubagentDepth)); + } + if (this.options.coreTools && this.options.coreTools.length > 0) { args.push('--core-tools', this.options.coreTools.join(',')); } diff --git a/packages/sdk-typescript/src/types/queryOptionsSchema.ts b/packages/sdk-typescript/src/types/queryOptionsSchema.ts index 702ea1c632d..5b81cbc1e4e 100644 --- a/packages/sdk-typescript/src/types/queryOptionsSchema.ts +++ b/packages/sdk-typescript/src/types/queryOptionsSchema.ts @@ -182,5 +182,6 @@ export const QueryOptionsSchema = z resume: z.string().optional(), sessionId: z.string().optional(), timeout: TimeoutConfigSchema.optional(), + maxSubagentDepth: z.number().int().min(1).max(100).optional(), }) .strict(); diff --git a/packages/sdk-typescript/src/types/types.ts b/packages/sdk-typescript/src/types/types.ts index 3a7e26ee0aa..f1b7c15ff7b 100644 --- a/packages/sdk-typescript/src/types/types.ts +++ b/packages/sdk-typescript/src/types/types.ts @@ -46,6 +46,12 @@ export type TransportOptions = { * When resume is provided, this should match the resume ID. */ sessionId?: string; + /** + * Maximum sub-agent nesting depth (1-based). + * 1 keeps sub-agents available but disables nesting; capped at 100. + * @default 5 (CLI default) + */ + maxSubagentDepth?: number; }; export interface QuerySystemPromptPreset { @@ -503,4 +509,12 @@ export interface QueryOptions { */ streamClose?: number; }; + + /** + * Maximum sub-agent nesting depth (1-based). + * 1 keeps sub-agents available but disables nesting; capped at 100. + * Equivalent to CLI's `--max-subagent-depth` flag. + * @default 5 (CLI default) + */ + maxSubagentDepth?: number; }