Skip to content

[FEATURE] : Add set_effort and set_thinking to ClaudeSDKClient #981

@ritu456286

Description

@ritu456286

Problem

ClaudeSDKClient exposes set_model and set_permission_mode as public, mid-conversation setters backed by control-protocol subtypes. effort and thinking are first-class fields on ClaudeAgentOptions , but the Python SDK exposes no equivalent setter - both are launch-only, mapped to --effort / --thinking on the CLI command line.

The TypeScript SDK exposes applyFlagSettings on its Query interface as a partial workaround. The Python SDK does not surface it at all.

There is no public mid-conversation knob in the Python SDK for either effort or thinking.

Reproduction

  • SDK: claude-agent-sdk 0.1.60
  • Bundled CLI: cli.js 2.1.111
  • Python 3.11.13, Linux
import asyncio
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient

async def main():
    options = ClaudeAgentOptions(model="claude-opus-4-7", effort="low")
    async with ClaudeSDKClient(options=options) as client:
        await client.query("Quick exploratory question.")
        async for _ in client.receive_response():
            pass

        # Want to bump effort before the next turn — no public method exists.
        # client.set_effort("max")     # AttributeError
        # client.set_thinking({"type": "adaptive"})   # AttributeError

        await client.query("Now give a deep planning answer.")
        # ...still running at effort=low.

asyncio.run(main())

The only mid-conversation setters on ClaudeSDKClient are:

await client.set_model(...)
await client.set_permission_mode(...)

Workaround

Send the raw control request through the private Query._send_control_request:

await client._query._send_control_request({
    "subtype": "apply_flag_settings",
    "settings": {"effortLevel": "max"},
})

This is not a clean substitute for dedicated setters:

  • _send_control_request is private. apply_flag_settings has no TypedDict in SDKControlRequest.
  • { effortLevel: "max" } updates the in-memory state.effortValue but is stripped before persistenceflagSettings source serialises as {}.

CLI versions inspected for a dedicated subtype: cli.js 2.1.19 → 2.1.111 and native binaries 2.1.114 → 2.1.143. No set_effort or set_thinking subtype exists in any of them.

What we'd like

  1. Confirm whether there's a public API we missed for changing effort or thinking mid-conversation. We checked the methods on ClaudeSDKClient (set_model, set_permission_mode, set_effort?, set_thinking?) and the public exports, and didn't find one.

  2. If not, please add public client.set_effort() and client.set_thinking() methods, symmetric with set_model:

    class ClaudeSDKClient:
        async def set_effort(self, effort: EffortLevel | None) -> None: ...
        async def set_thinking(self, thinking: ThinkingConfig | None) -> None: ...

    EffortLevel = Literal["low", "medium", "high", "xhigh", "max"] and ThinkingConfig are already in types.py. Semantics matching set_model: streaming-mode only; effective on next turn; None resets to the value supplied at launch.

  3. Backed by new control-protocol subtypes on @anthropic-ai/claude-code:

    { "type": "control_request", "request_id": "...",
      "request": {
        "subtype": "set_effort",
        "effort": "low" | "medium" | "high" | "xhigh" | "max" | null
      }
    }
    
    { "type": "control_request", "request_id": "...",
      "request": {
        "subtype": "set_thinking",
        "thinking":
            { "type": "adaptive",  "display": "summarized" | "omitted" } |
            { "type": "enabled",   "budget_tokens": number, "display": "summarized" | "omitted" } |
            { "type": "disabled" } |
            null
      }
    }

    Add corresponding TypedDict variants (SDKControlSetEffortRequest, SDKControlSetThinkingRequest) to the SDKControlRequest union in types.py. Also add the missing SDKControlSetModelRequest for parity — set_model is currently dispatched as an untyped dict.

  4. Once the CLI subtypes land, the Python SDK PR is a forwarder identical in shape to set_model:

    async def set_effort(self, effort: EffortLevel | None) -> None:
        await self._send_control_request({"subtype": "set_effort", "effort": effort})
    
    async def set_thinking(self, thinking: ThinkingConfig | None) -> None:
        await self._send_control_request({"subtype": "set_thinking", "thinking": thinking})

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions