Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/claude_agent_sdk/_internal/transport/subprocess_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ def _build_command(self) -> list[str]:
cmd.extend(["--system-prompt", ""])
elif isinstance(self._options.system_prompt, str):
cmd.extend(["--system-prompt", self._options.system_prompt])
elif isinstance(self._options.system_prompt, list):
cmd.extend(["--system-prompt", json.dumps(self._options.system_prompt)])
else:
sp = self._options.system_prompt
if sp.get("type") == "file":
Expand Down
25 changes: 23 additions & 2 deletions src/claude_agent_sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,13 @@ class AssistantMessage:
stop_reason: str | None = None
session_id: str | None = None
uuid: str | None = None
raw: dict[str, Any] = field(default_factory=dict)
"""Full raw wire dict from the CLI, including any fields not yet modeled above.

Use this to access newer CLI wire fields (e.g. ``ttft_ms``,
``terminal_reason``) that are present in the raw JSON but have not yet
been promoted to typed attributes on this dataclass.
"""


@dataclass
Expand Down Expand Up @@ -1220,6 +1227,13 @@ class ResultMessage:
# Emitted by the CLI since v2.1.110. Safe to log (no message content).
api_error_status: int | None = None
uuid: str | None = None
raw: dict[str, Any] = field(default_factory=dict)
"""Full raw wire dict from the CLI, including any fields not yet modeled above.

Use this to access newer CLI wire fields (e.g. ``ttft_ms``,
``terminal_reason``, ``stop_details``) that are present in the raw JSON
but have not yet been promoted to typed attributes on this dataclass.
"""


@dataclass
Expand Down Expand Up @@ -1657,10 +1671,17 @@ class ClaudeAgentOptions:
``Skill`` tool).
"""

system_prompt: str | SystemPromptPreset | SystemPromptFile | None = None
system_prompt: (
str | list[dict[str, Any]] | SystemPromptPreset | SystemPromptFile | None
) = None
"""System prompt configuration.

- ``str`` — Use a custom system prompt.
- ``str`` — Use a custom system prompt string.
- ``list[dict]`` — Multi-block system prompt (supports ``cache_control`` and
other Anthropic API content block fields). Each dict should follow the
Anthropic API ``system`` content block schema, e.g.
``[{"type": "text", "text": "You are helpful.", "cache_control": {"type": "ephemeral"}}]``.
Serialized to JSON when passed to the CLI subprocess.
- ``{"type": "preset", "preset": "claude_code"}`` — Use Claude Code's default
system prompt.
- ``{"type": "preset", "preset": "claude_code", "append": "..."}`` — Default
Expand Down
62 changes: 62 additions & 0 deletions tests/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,68 @@ def test_build_command_with_system_prompt_file(self):
assert "--system-prompt-file" in cmd
assert "/path/to/prompt.md" in cmd

def test_build_command_with_system_prompt_list(self):
"""List-form system_prompt is serialized to JSON for --system-prompt."""
import json

blocks = [
{"type": "text", "text": "You are helpful."},
{
"type": "text",
"text": "Be concise.",
"cache_control": {"type": "ephemeral"},
},
]
transport = SubprocessCLITransport(
prompt="test",
options=make_options(system_prompt=blocks),
)

cmd = transport._build_command()
assert "--system-prompt" in cmd
sp_value = cmd[cmd.index("--system-prompt") + 1]
# Must be valid JSON
parsed = json.loads(sp_value)
assert parsed == blocks

def test_build_command_with_system_prompt_list_single_block(self):
"""Single-element list system_prompt also serializes to JSON."""
import json

blocks = [{"type": "text", "text": "You are a helpful assistant."}]
transport = SubprocessCLITransport(
prompt="test",
options=make_options(system_prompt=blocks),
)

cmd = transport._build_command()
assert "--system-prompt" in cmd
sp_value = cmd[cmd.index("--system-prompt") + 1]
parsed = json.loads(sp_value)
assert parsed == blocks

def test_build_command_system_prompt_none_sends_empty_string(self):
"""None system_prompt still emits --system-prompt with empty string."""
transport = SubprocessCLITransport(
prompt="test",
options=make_options(),
)

cmd = transport._build_command()
assert "--system-prompt" in cmd
assert cmd[cmd.index("--system-prompt") + 1] == ""

def test_build_command_system_prompt_string_unchanged(self):
"""String system_prompt is passed through as-is (regression guard)."""
transport = SubprocessCLITransport(
prompt="test",
options=make_options(system_prompt="You are a pirate."),
)

cmd = transport._build_command()
assert "--system-prompt" in cmd
assert cmd[cmd.index("--system-prompt") + 1] == "You are a pirate."

def test_build_command_with_options(self):
"""Test building CLI command with options."""
transport = SubprocessCLITransport(
Expand Down