Coverage for src / infra / sdk_adapter.py: 50%
12 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-01-04 04:43 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-01-04 04:43 +0000
1"""SDK adapter for Claude Agent SDK.
3This module provides a factory for creating Claude SDK clients, isolating
4SDK imports to the infra layer. The pipeline layer uses SDKClientProtocol
5from core.protocols instead of importing SDK types directly.
7Design principles:
8- All SDK imports are local (inside methods, not at module level)
9- Factory pattern enables dependency injection and testing
10- TYPE_CHECKING imports for SDK types avoid runtime dependency
11"""
13from __future__ import annotations
15from typing import TYPE_CHECKING, cast
17if TYPE_CHECKING:
18 from src.core.protocols import SDKClientProtocol
21class SDKClientFactory:
22 """Factory for creating Claude SDK clients.
24 This factory encapsulates SDK imports and client creation, allowing
25 the pipeline layer to use SDK clients without importing SDK directly.
27 Usage:
28 factory = SDKClientFactory()
29 client = factory.create(options)
30 async with client:
31 await client.query(prompt)
32 async for msg in client.receive_response():
33 ...
34 """
36 def create(self, options: object) -> SDKClientProtocol:
37 """Create a new SDK client with the given options.
39 Args:
40 options: ClaudeAgentOptions (or compatible) for the client.
41 The type is `object` to avoid requiring SDK import in
42 the caller's type annotations.
44 Returns:
45 SDKClientProtocol wrapping a ClaudeSDKClient.
46 """
47 from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient # noqa: TC002
49 return cast(
50 "SDKClientProtocol",
51 ClaudeSDKClient(options=cast("ClaudeAgentOptions", options)),
52 )
54 def create_options(
55 self,
56 *,
57 cwd: str,
58 permission_mode: str = "bypassPermissions",
59 model: str = "opus",
60 system_prompt: dict[str, str] | None = None,
61 setting_sources: list[str] | None = None,
62 mcp_servers: object | None = None,
63 disallowed_tools: list[str] | None = None,
64 env: dict[str, str] | None = None,
65 hooks: dict[str, list[object]] | None = None,
66 ) -> object:
67 """Create SDK options without requiring SDK import in caller.
69 This method encapsulates the ClaudeAgentOptions construction,
70 allowing callers to build options without SDK imports.
72 Args:
73 cwd: Working directory for the agent.
74 permission_mode: Permission mode (default "bypassPermissions").
75 model: Model to use (default "opus").
76 system_prompt: System prompt configuration.
77 setting_sources: List of setting sources.
78 mcp_servers: List of MCP server configurations.
79 disallowed_tools: List of tools to disallow.
80 env: Environment variables for the agent.
81 hooks: Hook configurations keyed by event type.
83 Returns:
84 ClaudeAgentOptions instance.
85 """
86 from claude_agent_sdk import ClaudeAgentOptions
88 return ClaudeAgentOptions(
89 cwd=cwd,
90 permission_mode=permission_mode, # type: ignore[arg-type]
91 model=model,
92 system_prompt=system_prompt or {"type": "preset", "preset": "claude_code"}, # type: ignore[arg-type]
93 setting_sources=setting_sources or ["project", "user"], # type: ignore[arg-type]
94 mcp_servers=mcp_servers, # type: ignore[arg-type]
95 disallowed_tools=disallowed_tools, # type: ignore[arg-type]
96 env=env, # type: ignore[arg-type]
97 hooks=hooks, # type: ignore[arg-type]
98 )
100 def create_hook_matcher(
101 self,
102 matcher: object | None,
103 hooks: list[object],
104 ) -> object:
105 """Create a HookMatcher for SDK hook registration.
107 Args:
108 matcher: Optional matcher configuration.
109 hooks: List of hook callables.
111 Returns:
112 HookMatcher instance.
113 """
114 from claude_agent_sdk.types import HookMatcher
116 return HookMatcher(matcher=matcher, hooks=hooks) # type: ignore[arg-type]