chore: release api v1.0.0
Build and Push API / release (push) Successful in 4s
Build and Push API / build (push) Successful in 2m26s

- Event-based streaming for task agent
- Retry logic when LLM responds without calling tools
- Hardened prompts to enforce tool use
- Working directory context in all agent prompts
- Project paused: local LLMs not capable enough for agentic use

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-15 07:54:33 +01:00
co-authored by Claude Opus 4.5
parent 6ea520519c
commit d385f47395
13 changed files with 1061 additions and 110 deletions
+60 -8
View File
@@ -26,6 +26,41 @@ class PermissionMode(str, Enum):
auto_accept = "auto_accept"
class StreamEventType(str, Enum):
"""Event types for structured agent streaming."""
tool_start = "tool_start"
tool_done = "tool_done"
thinking = "thinking"
response = "response"
error = "error"
done = "done"
chunk = "chunk" # Legacy text chunk
@dataclass
class StreamEvent:
"""
Structured streaming event from agent execution.
Different event types carry different data:
- tool_start: tool, args
- tool_done: tool, result_summary
- thinking: message
- response: text
- error: error_message
- done: mode
- chunk: text (legacy)
"""
event: StreamEventType
tool: str | None = None
args: dict | None = None
result_summary: str | None = None
message: str | None = None
text: str | None = None
error_message: str | None = None
mode: str | None = None
@dataclass
class AgentResponse:
"""Response from agent execution."""
@@ -202,7 +237,7 @@ class WebberClient:
prompt: str,
working_dir: str = ".",
mode: PermissionMode = PermissionMode.default,
) -> AsyncIterator[str]:
) -> AsyncIterator[StreamEvent]:
"""
Run an agent with streaming response.
@@ -213,7 +248,7 @@ class WebberClient:
mode: Permission mode controlling tool access
Yields:
Text chunks as they arrive
StreamEvent objects as they arrive
"""
# Use a fresh client for streaming with longer timeout
async with httpx.AsyncClient(
@@ -235,13 +270,30 @@ class WebberClient:
if line.startswith("data: "):
try:
data = json.loads(line[6:])
event = data.get("event")
if event == "chunk":
yield data.get("data", "")
elif event == "error":
raise Exception(data.get("data", "Unknown error"))
elif event == "done":
event_type = data.get("event")
# Parse event type
try:
evt_type = StreamEventType(event_type)
except ValueError:
continue # Unknown event type
# Build StreamEvent from response data
yield StreamEvent(
event=evt_type,
tool=data.get("tool"),
args=data.get("args"),
result_summary=data.get("result_summary"),
message=data.get("message"),
text=data.get("text") or data.get("data"), # 'data' for legacy chunk
error_message=data.get("error_message") or data.get("data"),
mode=data.get("mode"),
)
# Stop on done or error
if evt_type in (StreamEventType.done, StreamEventType.error):
break
except json.JSONDecodeError:
continue