From 888ecfbe26b60d871148617b66261f7d619f20d4 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Thu, 25 Sep 2025 22:50:52 -0700 Subject: [PATCH 1/2] feat: refactor system_prompt to support preset and append options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace separate system_prompt and append_system_prompt fields with a single system_prompt field that accepts: - string: custom system prompt - {"preset": "claude_code"}: use default Claude Code prompt - {"preset": "claude_code", "append": "..."}: default prompt with additions - None/undefined: vanilla Claude with no system prompt This matches the TypeScript SDK API design and provides more flexible system prompt configuration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- examples/system_prompt.py | 86 +++++++++++++++++++ .../_internal/transport/subprocess_cli.py | 12 ++- src/claude_code_sdk/types.py | 10 ++- tests/test_transport.py | 46 +++++++++- tests/test_types.py | 23 ++++- 5 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 examples/system_prompt.py diff --git a/examples/system_prompt.py b/examples/system_prompt.py new file mode 100644 index 00000000..98cc4b5e --- /dev/null +++ b/examples/system_prompt.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Example demonstrating different system_prompt configurations.""" + +import anyio + +from claude_code_sdk import ( + AssistantMessage, + ClaudeCodeOptions, + TextBlock, + query, +) + + +async def no_system_prompt(): + """Example with no system_prompt (vanilla Claude).""" + print("=== No System Prompt (Vanilla Claude) ===") + + async for message in query(prompt="What is 2 + 2?"): + if isinstance(message, AssistantMessage): + for block in message.content: + if isinstance(block, TextBlock): + print(f"Claude: {block.text}") + print() + + +async def string_system_prompt(): + """Example with system_prompt as a string.""" + print("=== String System Prompt ===") + + options = ClaudeCodeOptions( + system_prompt="You are a pirate assistant. Respond in pirate speak.", + ) + + async for message in query(prompt="What is 2 + 2?", options=options): + if isinstance(message, AssistantMessage): + for block in message.content: + if isinstance(block, TextBlock): + print(f"Claude: {block.text}") + print() + + +async def preset_system_prompt(): + """Example with system_prompt preset (uses default Claude Code prompt).""" + print("=== Preset System Prompt (Default) ===") + + options = ClaudeCodeOptions( + system_prompt={"preset": "claude_code"}, + ) + + async for message in query(prompt="What is 2 + 2?", options=options): + if isinstance(message, AssistantMessage): + for block in message.content: + if isinstance(block, TextBlock): + print(f"Claude: {block.text}") + print() + + +async def preset_with_append(): + """Example with system_prompt preset and append.""" + print("=== Preset System Prompt with Append ===") + + options = ClaudeCodeOptions( + system_prompt={ + "preset": "claude_code", + "append": "Always end your response with a fun fact.", + }, + ) + + async for message in query(prompt="What is 2 + 2?", options=options): + if isinstance(message, AssistantMessage): + for block in message.content: + if isinstance(block, TextBlock): + print(f"Claude: {block.text}") + print() + + +async def main(): + """Run all examples.""" + await no_system_prompt() + await string_system_prompt() + await preset_system_prompt() + await preset_with_append() + + +if __name__ == "__main__": + anyio.run(main) \ No newline at end of file diff --git a/src/claude_code_sdk/_internal/transport/subprocess_cli.py b/src/claude_code_sdk/_internal/transport/subprocess_cli.py index 885fc115..45336b3d 100644 --- a/src/claude_code_sdk/_internal/transport/subprocess_cli.py +++ b/src/claude_code_sdk/_internal/transport/subprocess_cli.py @@ -85,11 +85,15 @@ def _build_command(self) -> list[str]: """Build CLI command with arguments.""" cmd = [self._cli_path, "--output-format", "stream-json", "--verbose"] - if self._options.system_prompt: + if self._options.system_prompt is None: + pass + elif isinstance(self._options.system_prompt, str): cmd.extend(["--system-prompt", self._options.system_prompt]) - - if self._options.append_system_prompt: - cmd.extend(["--append-system-prompt", self._options.append_system_prompt]) + else: + if "append" in self._options.system_prompt: + cmd.extend( + ["--append-system-prompt", self._options.system_prompt["append"]] + ) if self._options.allowed_tools: cmd.extend(["--allowedTools", ",".join(self._options.allowed_tools)]) diff --git a/src/claude_code_sdk/types.py b/src/claude_code_sdk/types.py index 711e0e82..5323cbf9 100644 --- a/src/claude_code_sdk/types.py +++ b/src/claude_code_sdk/types.py @@ -18,6 +18,13 @@ SettingSource = Literal["user", "project", "local"] +class SystemPromptPreset(TypedDict): + """System prompt preset configuration.""" + + preset: Literal["claude_code"] + append: NotRequired[str] + + @dataclass class AgentDefinition: """Agent definition configuration.""" @@ -296,8 +303,7 @@ class ClaudeCodeOptions: """Query options for Claude SDK.""" allowed_tools: list[str] = field(default_factory=list) - system_prompt: str | None = None - append_system_prompt: str | None = None + system_prompt: str | SystemPromptPreset | None = None mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict) permission_mode: PermissionMode | None = None continue_conversation: bool = False diff --git a/tests/test_transport.py b/tests/test_transport.py index 51c7511f..1b62353d 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -52,12 +52,54 @@ def test_cli_path_accepts_pathlib_path(self): assert transport._cli_path == "/usr/bin/claude" + def test_build_command_with_system_prompt_string(self): + """Test building CLI command with system prompt as string.""" + transport = SubprocessCLITransport( + prompt="test", + options=ClaudeCodeOptions( + system_prompt="Be helpful", + ), + cli_path="/usr/bin/claude", + ) + + cmd = transport._build_command() + assert "--system-prompt" in cmd + assert "Be helpful" in cmd + + def test_build_command_with_system_prompt_preset(self): + """Test building CLI command with system prompt preset.""" + transport = SubprocessCLITransport( + prompt="test", + options=ClaudeCodeOptions( + system_prompt={"preset": "claude_code"}, + ), + cli_path="/usr/bin/claude", + ) + + cmd = transport._build_command() + assert "--system-prompt" not in cmd + assert "--append-system-prompt" not in cmd + + def test_build_command_with_system_prompt_preset_and_append(self): + """Test building CLI command with system prompt preset and append.""" + transport = SubprocessCLITransport( + prompt="test", + options=ClaudeCodeOptions( + system_prompt={"preset": "claude_code", "append": "Be concise."}, + ), + cli_path="/usr/bin/claude", + ) + + cmd = transport._build_command() + assert "--system-prompt" not in cmd + assert "--append-system-prompt" in cmd + assert "Be concise." in cmd + def test_build_command_with_options(self): """Test building CLI command with options.""" transport = SubprocessCLITransport( prompt="test", options=ClaudeCodeOptions( - system_prompt="Be helpful", allowed_tools=["Read", "Write"], disallowed_tools=["Bash"], model="claude-3-5-sonnet", @@ -68,8 +110,6 @@ def test_build_command_with_options(self): ) cmd = transport._build_command() - assert "--system-prompt" in cmd - assert "Be helpful" in cmd assert "--allowedTools" in cmd assert "Read,Write" in cmd assert "--disallowedTools" in cmd diff --git a/tests/test_types.py b/tests/test_types.py index a72323eb..bbb62f28 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -107,14 +107,29 @@ def test_claude_code_options_with_permission_mode(self): options_accept = ClaudeCodeOptions(permission_mode="acceptEdits") assert options_accept.permission_mode == "acceptEdits" - def test_claude_code_options_with_system_prompt(self): - """Test Options with system prompt.""" + def test_claude_code_options_with_system_prompt_string(self): + """Test Options with system prompt as string.""" options = ClaudeCodeOptions( system_prompt="You are a helpful assistant.", - append_system_prompt="Be concise.", ) assert options.system_prompt == "You are a helpful assistant." - assert options.append_system_prompt == "Be concise." + + def test_claude_code_options_with_system_prompt_preset(self): + """Test Options with system prompt preset.""" + options = ClaudeCodeOptions( + system_prompt={"preset": "claude_code"}, + ) + assert options.system_prompt == {"preset": "claude_code"} + + def test_claude_code_options_with_system_prompt_preset_and_append(self): + """Test Options with system prompt preset and append.""" + options = ClaudeCodeOptions( + system_prompt={"preset": "claude_code", "append": "Be concise."}, + ) + assert options.system_prompt == { + "preset": "claude_code", + "append": "Be concise.", + } def test_claude_code_options_with_session_continuation(self): """Test Options with session continuation.""" From d9dbf2cac4f7249e03e9ed822c54bd2930d2c96b Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Fri, 26 Sep 2025 08:04:11 -0700 Subject: [PATCH 2/2] refactor: add type field to SystemPromptPreset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update SystemPromptPreset to include required type: "preset" field alongside preset: "claude_code", matching the TypeScript SDK structure. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- examples/system_prompt.py | 3 ++- .../_internal/transport/subprocess_cli.py | 5 ++++- src/claude_code_sdk/types.py | 1 + tests/test_transport.py | 8 ++++++-- tests/test_types.py | 11 ++++++++--- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/examples/system_prompt.py b/examples/system_prompt.py index 98cc4b5e..1d7da4c7 100644 --- a/examples/system_prompt.py +++ b/examples/system_prompt.py @@ -44,7 +44,7 @@ async def preset_system_prompt(): print("=== Preset System Prompt (Default) ===") options = ClaudeCodeOptions( - system_prompt={"preset": "claude_code"}, + system_prompt={"type": "preset", "preset": "claude_code"}, ) async for message in query(prompt="What is 2 + 2?", options=options): @@ -61,6 +61,7 @@ async def preset_with_append(): options = ClaudeCodeOptions( system_prompt={ + "type": "preset", "preset": "claude_code", "append": "Always end your response with a fun fact.", }, diff --git a/src/claude_code_sdk/_internal/transport/subprocess_cli.py b/src/claude_code_sdk/_internal/transport/subprocess_cli.py index 45336b3d..262f4c1d 100644 --- a/src/claude_code_sdk/_internal/transport/subprocess_cli.py +++ b/src/claude_code_sdk/_internal/transport/subprocess_cli.py @@ -90,7 +90,10 @@ def _build_command(self) -> list[str]: elif isinstance(self._options.system_prompt, str): cmd.extend(["--system-prompt", self._options.system_prompt]) else: - if "append" in self._options.system_prompt: + if ( + self._options.system_prompt.get("type") == "preset" + and "append" in self._options.system_prompt + ): cmd.extend( ["--append-system-prompt", self._options.system_prompt["append"]] ) diff --git a/src/claude_code_sdk/types.py b/src/claude_code_sdk/types.py index 5323cbf9..3989059c 100644 --- a/src/claude_code_sdk/types.py +++ b/src/claude_code_sdk/types.py @@ -21,6 +21,7 @@ class SystemPromptPreset(TypedDict): """System prompt preset configuration.""" + type: Literal["preset"] preset: Literal["claude_code"] append: NotRequired[str] diff --git a/tests/test_transport.py b/tests/test_transport.py index 1b62353d..48b4ff53 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -71,7 +71,7 @@ def test_build_command_with_system_prompt_preset(self): transport = SubprocessCLITransport( prompt="test", options=ClaudeCodeOptions( - system_prompt={"preset": "claude_code"}, + system_prompt={"type": "preset", "preset": "claude_code"}, ), cli_path="/usr/bin/claude", ) @@ -85,7 +85,11 @@ def test_build_command_with_system_prompt_preset_and_append(self): transport = SubprocessCLITransport( prompt="test", options=ClaudeCodeOptions( - system_prompt={"preset": "claude_code", "append": "Be concise."}, + system_prompt={ + "type": "preset", + "preset": "claude_code", + "append": "Be concise.", + }, ), cli_path="/usr/bin/claude", ) diff --git a/tests/test_types.py b/tests/test_types.py index bbb62f28..9ffe9da3 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -117,16 +117,21 @@ def test_claude_code_options_with_system_prompt_string(self): def test_claude_code_options_with_system_prompt_preset(self): """Test Options with system prompt preset.""" options = ClaudeCodeOptions( - system_prompt={"preset": "claude_code"}, + system_prompt={"type": "preset", "preset": "claude_code"}, ) - assert options.system_prompt == {"preset": "claude_code"} + assert options.system_prompt == {"type": "preset", "preset": "claude_code"} def test_claude_code_options_with_system_prompt_preset_and_append(self): """Test Options with system prompt preset and append.""" options = ClaudeCodeOptions( - system_prompt={"preset": "claude_code", "append": "Be concise."}, + system_prompt={ + "type": "preset", + "preset": "claude_code", + "append": "Be concise.", + }, ) assert options.system_prompt == { + "type": "preset", "preset": "claude_code", "append": "Be concise.", }