Internal client implementation.
| 26 | |
| 27 | |
| 28 | class InternalClient: |
| 29 | """Internal client implementation.""" |
| 30 | |
| 31 | def __init__(self) -> None: |
| 32 | """Initialize the internal client.""" |
| 33 | |
| 34 | def _convert_hooks_to_internal_format( |
| 35 | self, hooks: dict[HookEvent, list[HookMatcher]] |
| 36 | ) -> dict[str, list[dict[str, Any]]]: |
| 37 | """Convert HookMatcher format to internal Query format.""" |
| 38 | internal_hooks: dict[str, list[dict[str, Any]]] = {} |
| 39 | for event, matchers in hooks.items(): |
| 40 | internal_hooks[event] = [] |
| 41 | for matcher in matchers: |
| 42 | # Convert HookMatcher to internal dict format |
| 43 | internal_matcher: dict[str, Any] = { |
| 44 | "matcher": matcher.matcher if hasattr(matcher, "matcher") else None, |
| 45 | "hooks": matcher.hooks if hasattr(matcher, "hooks") else [], |
| 46 | } |
| 47 | if hasattr(matcher, "timeout") and matcher.timeout is not None: |
| 48 | internal_matcher["timeout"] = matcher.timeout |
| 49 | internal_hooks[event].append(internal_matcher) |
| 50 | return internal_hooks |
| 51 | |
| 52 | async def process_query( |
| 53 | self, |
| 54 | prompt: str | AsyncIterable[dict[str, Any]], |
| 55 | options: ClaudeAgentOptions, |
| 56 | transport: Transport | None = None, |
| 57 | ) -> AsyncIterator[Message]: |
| 58 | """Process a query through transport and Query.""" |
| 59 | |
| 60 | # Fail fast on invalid session_store option combinations before |
| 61 | # spawning the subprocess. |
| 62 | validate_session_store_options(options) |
| 63 | |
| 64 | # resume/continue + session_store: load the session from the store |
| 65 | # into a temp CLAUDE_CONFIG_DIR for the subprocess to resume from. |
| 66 | # Skipped when a custom transport was supplied — the materialized |
| 67 | # options never reach a pre-constructed transport, so loading the |
| 68 | # store and writing .credentials.json to a temp dir would be wasted. |
| 69 | materialized = ( |
| 70 | await materialize_resume_session(options) if transport is None else None |
| 71 | ) |
| 72 | inner = self._process_query_inner(prompt, options, transport, materialized) |
| 73 | try: |
| 74 | async for msg in inner: |
| 75 | yield msg |
| 76 | finally: |
| 77 | # ``async for`` does NOT close its iterator when the loop body |
| 78 | # raises (PEP 533 was deferred). Explicitly aclose the inner |
| 79 | # generator first so its ``finally: await query.close()`` runs — |
| 80 | # i.e. the subprocess is terminated — *before* we remove the temp |
| 81 | # CLAUDE_CONFIG_DIR it is reading/writing. |
| 82 | try: |
| 83 | await inner.aclose() |
| 84 | finally: |
| 85 | # The temp dir holds a .credentials.json copy — remove it on |