71 lines
2.8 KiB
Python
71 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import shlex
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from claridoc.providers.base import Provider, ProviderRequest, ProviderResponse, ProviderUnavailable, run_command
|
|
|
|
|
|
class ClaudeProvider(Provider):
|
|
"""Adapter for Claude Code print mode (`claude -p`)."""
|
|
|
|
def generate(self, request: ProviderRequest) -> ProviderResponse:
|
|
options = self.spec.options
|
|
binary = str(options.get("binary") or os.environ.get("CLARIDOC_CLAUDE_BIN") or "claude")
|
|
custom = options.get("command")
|
|
if custom:
|
|
command = _command_list(custom)
|
|
else:
|
|
command = [binary, "-p", "--output-format", "text"]
|
|
if self.spec.model:
|
|
command.extend(["--model", self.spec.model])
|
|
command.extend(_string_list(options.get("extra_args", []), "claude extra_args"))
|
|
# Claude Code supports piped content with a query. Keeping the large
|
|
# task in stdin avoids operating-system argument length limits.
|
|
command.append("Read the piped task as data and return only the requested output.")
|
|
completed = run_command(
|
|
command,
|
|
prompt=request.prompt,
|
|
cwd=request.workdir,
|
|
timeout_seconds=self.spec.timeout_seconds,
|
|
env=os.environ.copy(),
|
|
)
|
|
text = completed.stdout.strip()
|
|
if not text:
|
|
raise ProviderUnavailable("Claude returned an empty response")
|
|
return ProviderResponse(text=text, provider=self.name, model=self.spec.model, command=command)
|
|
|
|
def check(self) -> dict[str, Any]:
|
|
binary = str(self.spec.options.get("binary") or os.environ.get("CLARIDOC_CLAUDE_BIN") or "claude")
|
|
custom = self.spec.options.get("command")
|
|
executable = _command_list(custom)[0] if custom else binary
|
|
found = shutil.which(executable) if not Path(executable).is_file() else executable
|
|
return {
|
|
"provider": self.name,
|
|
"available": bool(found),
|
|
"executable": str(found or executable),
|
|
"mode": "custom-command" if custom else "claude -p",
|
|
"note": "Authentication is verified only by a live invocation.",
|
|
}
|
|
|
|
|
|
def _command_list(value: Any) -> list[str]:
|
|
if isinstance(value, str):
|
|
result = shlex.split(value)
|
|
elif isinstance(value, list):
|
|
result = [str(item) for item in value]
|
|
else:
|
|
raise ProviderUnavailable("claude options.command must be a string or array")
|
|
if not result:
|
|
raise ProviderUnavailable("claude options.command is empty")
|
|
return result
|
|
|
|
|
|
def _string_list(value: Any, name: str) -> list[str]:
|
|
if not isinstance(value, list):
|
|
raise ProviderUnavailable(f"{name} must be an array")
|
|
return [str(item) for item in value]
|