(
self,
module_name: str,
components: Dict[str, Node],
core_component_ids: List[str],
module_path: List[str],
working_dir: str,
start_depth: int = 1,
module_tree: Dict[str, Any] | None = None,
)
| 204 | _patch_claude_allowed_tools() |
| 205 | # --- end stopgap -------------------------------------------------------------- |
| 206 | |
| 207 | |
| 208 | class CawBackend(LLMBackend): |
| 209 | """Routes LLM operations through the claude / codex CLI subscription.""" |
| 210 | |
| 211 | def __init__(self, config: Config) -> None: |
| 212 | self._config = config |
| 213 | self._caw_provider = _resolve_caw_provider(config.provider) |
| 214 | # main_model is passed straight through; empty string → caw default. |
| 215 | self._model: str | None = config.main_model or None |
| 216 | # Resolve once, before any agent-run chdir can move the cwd — |
| 217 | # os.path.abspath is cwd-relative and _run_module_agent_sync recurses |
| 218 | # (via generate_sub_module_documentation) while the cwd is pinned. |
| 219 | self._repo_root = str(os.path.abspath(config.repo_path)) |
| 220 | |
| 221 | # Fail loudly here rather than producing a confusing caw error mid-run. |
| 222 | cli = _CLI_BINARY[config.provider] |
| 223 | if shutil.which(cli) is None: |
| 224 | raise RuntimeError( |
| 225 | f"Subscription mode requires the '{cli}' CLI on PATH. " |
| 226 | f"Install it and run '{cli} login', then try again." |
| 227 | ) |
| 228 | |
| 229 | if self._caw_provider == "claude_code": |
| 230 | # Prevent claude-code CLI from cancelling long sub-module recursion; |
| 231 | # setdefault preserves a user-supplied value (e.g. shell override). |
| 232 | os.environ.setdefault("MCP_TOOL_TIMEOUT", "86400000") |
| 233 | os.environ.setdefault("MCP_TIMEOUT", "60000") |
| 234 | logger.info( |
| 235 | "claude-code MCP timeouts: MCP_TOOL_TIMEOUT=%s MCP_TIMEOUT=%s", |
| 236 | os.environ["MCP_TOOL_TIMEOUT"], |
| 237 | os.environ["MCP_TIMEOUT"], |
| 238 | ) |
| 239 | |
| 240 | # ------------------------------------------------------------------ |
| 241 | # Single-shot completion (clustering, parent / repo overviews) |
| 242 | # ------------------------------------------------------------------ |
| 243 | |
| 244 | def complete( |
| 245 | self, |
| 246 | prompt: str, |
| 247 | *, |
| 248 | model: str | None = None, |
| 249 | ) -> str: |
| 250 | # Blocks the calling thread for the lifetime of the claude/codex |
| 251 | # subprocess. Callers running this from an async context (e.g. the |
| 252 | # documentation_generator) accept this — there is no concurrent work |
| 253 | # to do while clustering is in flight anyway. |
| 254 | effective_model = model or self._model |
| 255 | agent = CawAgent( |
| 256 | provider=self._caw_provider, |
| 257 | model=effective_model, |
| 258 | tools=ToolGroup.READER, |
| 259 | ) |
| 260 | traj = agent.completion(prompt) |
| 261 | self.last_usage = usage_to_dict(getattr(traj, "total_usage", None)) |
| 262 | return traj.result |
| 263 |
no test coverage detected