fix: intercept non-cli errors and allow agent to continue

This commit is contained in:
Fahad
2025-10-05 11:38:59 +04:00
parent 15ae3f24ba
commit a150e1c312
4 changed files with 190 additions and 8 deletions

View File

@@ -136,6 +136,18 @@ class BaseCLIAgent:
if output_file_content and not stdout_text.strip():
stdout_text = output_file_content
if return_code != 0:
recovered = self._recover_from_error(
returncode=return_code,
stdout=stdout_text,
stderr=stderr_text,
sanitized_command=sanitized_command,
duration_seconds=duration,
output_file_content=output_file_content,
)
if recovered is not None:
return recovered
if return_code != 0:
raise CLIAgentError(
f"CLI '{self.client.name}' exited with status {return_code}",
@@ -177,3 +189,25 @@ class BaseCLIAgent:
env = os.environ.copy()
env.update(self.client.env)
return env
# ------------------------------------------------------------------
# Error recovery hooks
# ------------------------------------------------------------------
def _recover_from_error(
self,
*,
returncode: int,
stdout: str,
stderr: str,
sanitized_command: list[str],
duration_seconds: float,
output_file_content: str | None,
) -> AgentOutput | None:
"""Hook for subclasses to convert CLI errors into successful outputs.
Return an AgentOutput to treat the failure as success, or None to signal
that normal error handling should proceed.
"""
return None

View File

@@ -2,13 +2,85 @@
from __future__ import annotations
from clink.models import ResolvedCLIClient
import json
from typing import Any
from .base import BaseCLIAgent
from clink.models import ResolvedCLIClient
from clink.parsers.base import ParsedCLIResponse
from .base import AgentOutput, BaseCLIAgent
class GeminiAgent(BaseCLIAgent):
"""Placeholder for Gemini-specific behaviour."""
"""Gemini-specific behaviour."""
def __init__(self, client: ResolvedCLIClient):
super().__init__(client)
def _recover_from_error(
self,
*,
returncode: int,
stdout: str,
stderr: str,
sanitized_command: list[str],
duration_seconds: float,
output_file_content: str | None,
) -> AgentOutput | None:
combined = "\n".join(part for part in (stderr, stdout) if part)
if not combined:
return None
brace_index = combined.find("{")
if brace_index == -1:
return None
json_candidate = combined[brace_index:]
try:
payload: dict[str, Any] = json.loads(json_candidate)
except json.JSONDecodeError:
return None
error_block = payload.get("error")
if not isinstance(error_block, dict):
return None
code = error_block.get("code")
err_type = error_block.get("type")
detail_message = error_block.get("message")
prologue = combined[:brace_index].strip()
lines: list[str] = []
if prologue and (not detail_message or prologue not in detail_message):
lines.append(prologue)
if detail_message:
lines.append(detail_message)
header = "Gemini CLI reported a tool failure"
if code:
header = f"{header} ({code})"
elif err_type:
header = f"{header} ({err_type})"
content_lines = [header.rstrip(".") + "."]
content_lines.extend(lines)
message = "\n".join(content_lines).strip()
metadata = {
"cli_error_recovered": True,
"cli_error_code": code,
"cli_error_type": err_type,
"cli_error_payload": payload,
}
parsed = ParsedCLIResponse(content=message or header, metadata=metadata)
return AgentOutput(
parsed=parsed,
sanitized_command=sanitized_command,
returncode=returncode,
stdout=stdout,
stderr=stderr,
duration_seconds=duration_seconds,
parser_name=self._parser.name,
output_file_content=output_file_content,
)