code-puppy 0.0.348__py3-none-any.whl → 0.0.361__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_puppy/agents/__init__.py +2 -0
- code_puppy/agents/agent_manager.py +49 -0
- code_puppy/agents/agent_pack_leader.py +383 -0
- code_puppy/agents/agent_qa_kitten.py +12 -7
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/base_agent.py +17 -4
- code_puppy/agents/event_stream_handler.py +101 -8
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +321 -0
- code_puppy/agents/pack/retriever.py +393 -0
- code_puppy/agents/pack/shepherd.py +348 -0
- code_puppy/agents/pack/terrier.py +287 -0
- code_puppy/agents/pack/watchdog.py +367 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +446 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +74 -0
- code_puppy/api/routers/sessions.py +232 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +73 -0
- code_puppy/claude_cache_client.py +249 -34
- code_puppy/command_line/core_commands.py +85 -0
- code_puppy/config.py +66 -62
- code_puppy/messaging/__init__.py +15 -0
- code_puppy/messaging/messages.py +27 -0
- code_puppy/messaging/queue_console.py +1 -1
- code_puppy/messaging/rich_renderer.py +36 -1
- code_puppy/messaging/spinner/__init__.py +20 -2
- code_puppy/messaging/subagent_console.py +461 -0
- code_puppy/model_utils.py +54 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +90 -19
- code_puppy/plugins/antigravity_oauth/transport.py +1 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/status_display.py +6 -2
- code_puppy/tools/__init__.py +37 -1
- code_puppy/tools/agent_tools.py +83 -33
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +6 -6
- code_puppy/tools/browser/browser_interactions.py +21 -20
- code_puppy/tools/browser/browser_locators.py +9 -9
- code_puppy/tools/browser/browser_navigation.py +7 -7
- code_puppy/tools/browser/browser_screenshot.py +78 -140
- code_puppy/tools/browser/browser_scripts.py +15 -13
- code_puppy/tools/browser/camoufox_manager.py +226 -64
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +521 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +292 -101
- code_puppy/tools/common.py +176 -1
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/subagent_context.py +158 -0
- {code_puppy-0.0.348.dist-info → code_puppy-0.0.361.dist-info}/METADATA +13 -11
- {code_puppy-0.0.348.dist-info → code_puppy-0.0.361.dist-info}/RECORD +69 -38
- code_puppy/tools/browser/vqa_agent.py +0 -90
- {code_puppy-0.0.348.data → code_puppy-0.0.361.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.348.data → code_puppy-0.0.361.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.348.dist-info → code_puppy-0.0.361.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.348.dist-info → code_puppy-0.0.361.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.348.dist-info → code_puppy-0.0.361.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
"""Terminal connection tools for managing terminal browser connections.
|
|
2
|
+
|
|
3
|
+
This module provides tools for:
|
|
4
|
+
- Checking if the Code Puppy API server is running
|
|
5
|
+
- Opening the terminal browser interface
|
|
6
|
+
- Closing the terminal browser
|
|
7
|
+
|
|
8
|
+
These tools use the ChromiumTerminalManager to manage the browser instance
|
|
9
|
+
and connect to the Code Puppy API server's terminal endpoint.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import contextvars
|
|
13
|
+
import logging
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
from pydantic_ai import RunContext
|
|
18
|
+
from rich.text import Text
|
|
19
|
+
|
|
20
|
+
from code_puppy.messaging import emit_error, emit_info, emit_success
|
|
21
|
+
from code_puppy.tools.browser import format_terminal_banner
|
|
22
|
+
from code_puppy.tools.common import generate_group_id
|
|
23
|
+
|
|
24
|
+
from .chromium_terminal_manager import get_chromium_terminal_manager
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
# Context variable for terminal session - properly inherits through async tasks
|
|
29
|
+
_terminal_session_var: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
|
|
30
|
+
"terminal_session", default=None
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def set_terminal_session(session_id: Optional[str]) -> contextvars.Token:
|
|
35
|
+
"""Set the terminal session ID for the current context.
|
|
36
|
+
|
|
37
|
+
This must be called BEFORE any tool calls that use the terminal.
|
|
38
|
+
The context will properly propagate to all subsequent async calls.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
session_id: The session ID to use for terminal operations.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
A token that can be used to reset the context.
|
|
45
|
+
"""
|
|
46
|
+
return _terminal_session_var.set(session_id)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_terminal_session() -> Optional[str]:
|
|
50
|
+
"""Get the terminal session ID for the current context.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
The current session ID, or None if not set.
|
|
54
|
+
"""
|
|
55
|
+
return _terminal_session_var.get()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_session_manager():
|
|
59
|
+
"""Get the ChromiumTerminalManager for the current context's session."""
|
|
60
|
+
session_id = get_terminal_session()
|
|
61
|
+
return get_chromium_terminal_manager(session_id)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _get_session_from_context(context: RunContext) -> str:
|
|
65
|
+
"""Get the session ID for the current context.
|
|
66
|
+
|
|
67
|
+
If no session is set in the context var, generates one based on
|
|
68
|
+
the page URL that was opened (stored in the manager).
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
context: The pydantic-ai RunContext from a tool call.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
A session ID string for the terminal browser.
|
|
75
|
+
"""
|
|
76
|
+
# First check if we have a session in the context var
|
|
77
|
+
session = get_terminal_session()
|
|
78
|
+
if session:
|
|
79
|
+
return session
|
|
80
|
+
|
|
81
|
+
# Fallback: return default (all tools share one browser)
|
|
82
|
+
return "default"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Default timeout for health check requests (seconds)
|
|
86
|
+
HEALTH_CHECK_TIMEOUT = 5.0
|
|
87
|
+
|
|
88
|
+
# How long to wait for xterm.js to load in the terminal page (ms)
|
|
89
|
+
TERMINAL_LOAD_TIMEOUT = 10000
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
async def check_terminal_server(
|
|
93
|
+
host: str = "localhost", port: int = 8765
|
|
94
|
+
) -> Dict[str, Any]:
|
|
95
|
+
"""Check if the Code Puppy API server is running.
|
|
96
|
+
|
|
97
|
+
Attempts to connect to the /health endpoint of the API server to verify
|
|
98
|
+
it is running and responsive.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
host: The hostname where the server is running. Defaults to "localhost".
|
|
102
|
+
port: The port number for the server. Defaults to 8765.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
A dictionary containing:
|
|
106
|
+
- success (bool): True if server is healthy, False otherwise.
|
|
107
|
+
- server_url (str): The full URL of the server (if successful).
|
|
108
|
+
- status (str): "healthy" if server is running (if successful).
|
|
109
|
+
- error (str): Error message describing the failure (if unsuccessful).
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
>>> result = await check_terminal_server()
|
|
113
|
+
>>> if result["success"]:
|
|
114
|
+
... print(f"Server running at {result['server_url']}")
|
|
115
|
+
... else:
|
|
116
|
+
... print(f"Error: {result['error']}")
|
|
117
|
+
"""
|
|
118
|
+
group_id = generate_group_id("terminal_check_server", f"{host}:{port}")
|
|
119
|
+
banner = format_terminal_banner("TERMINAL CHECK SERVER 🔍")
|
|
120
|
+
emit_info(
|
|
121
|
+
Text.from_markup(f"{banner} [bold cyan]{host}:{port}[/bold cyan]"),
|
|
122
|
+
message_group=group_id,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
server_url = f"http://{host}:{port}"
|
|
126
|
+
health_url = f"{server_url}/health"
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
async with httpx.AsyncClient(timeout=HEALTH_CHECK_TIMEOUT) as client:
|
|
130
|
+
response = await client.get(health_url)
|
|
131
|
+
response.raise_for_status()
|
|
132
|
+
|
|
133
|
+
# Parse the response to verify it's the expected health check
|
|
134
|
+
health_data = response.json()
|
|
135
|
+
|
|
136
|
+
if health_data.get("status") == "healthy":
|
|
137
|
+
emit_success(
|
|
138
|
+
f"Server is healthy at {server_url}",
|
|
139
|
+
message_group=group_id,
|
|
140
|
+
)
|
|
141
|
+
return {
|
|
142
|
+
"success": True,
|
|
143
|
+
"server_url": server_url,
|
|
144
|
+
"status": "healthy",
|
|
145
|
+
}
|
|
146
|
+
else:
|
|
147
|
+
# Server responded but not with expected health status
|
|
148
|
+
emit_error(
|
|
149
|
+
f"Server responded but health check failed: {health_data}",
|
|
150
|
+
message_group=group_id,
|
|
151
|
+
)
|
|
152
|
+
return {
|
|
153
|
+
"success": False,
|
|
154
|
+
"error": f"Unexpected health response: {health_data}",
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
except httpx.ConnectError:
|
|
158
|
+
error_msg = (
|
|
159
|
+
f"Server not running at {server_url}. "
|
|
160
|
+
"Please start the Code Puppy API server first."
|
|
161
|
+
)
|
|
162
|
+
emit_error(error_msg, message_group=group_id)
|
|
163
|
+
return {"success": False, "error": error_msg}
|
|
164
|
+
|
|
165
|
+
except httpx.TimeoutException:
|
|
166
|
+
error_msg = f"Connection to {server_url} timed out."
|
|
167
|
+
emit_error(error_msg, message_group=group_id)
|
|
168
|
+
return {"success": False, "error": error_msg}
|
|
169
|
+
|
|
170
|
+
except httpx.HTTPStatusError as e:
|
|
171
|
+
error_msg = f"Server returned error status {e.response.status_code}."
|
|
172
|
+
emit_error(error_msg, message_group=group_id)
|
|
173
|
+
return {"success": False, "error": error_msg}
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
error_msg = f"Failed to check server health: {str(e)}"
|
|
177
|
+
emit_error(error_msg, message_group=group_id)
|
|
178
|
+
logger.exception("Unexpected error checking terminal server")
|
|
179
|
+
return {"success": False, "error": error_msg}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def open_terminal(host: str = "localhost", port: int = 8765) -> Dict[str, Any]:
|
|
183
|
+
"""Open the terminal browser interface.
|
|
184
|
+
|
|
185
|
+
First checks if the API server is running, then opens a Chromium browser
|
|
186
|
+
and navigates to the terminal endpoint. Waits for the terminal (xterm.js)
|
|
187
|
+
to be fully loaded before returning.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
host: The hostname where the server is running. Defaults to "localhost".
|
|
191
|
+
port: The port number for the server. Defaults to 8765.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
A dictionary containing:
|
|
195
|
+
- success (bool): True if terminal was opened successfully.
|
|
196
|
+
- url (str): The URL of the terminal page (if successful).
|
|
197
|
+
- page_title (str): The title of the terminal page (if successful).
|
|
198
|
+
- error (str): Error message describing the failure (if unsuccessful).
|
|
199
|
+
|
|
200
|
+
Example:
|
|
201
|
+
>>> result = await open_terminal()
|
|
202
|
+
>>> if result["success"]:
|
|
203
|
+
... print(f"Terminal opened at {result['url']}")
|
|
204
|
+
... else:
|
|
205
|
+
... print(f"Error: {result['error']}")
|
|
206
|
+
"""
|
|
207
|
+
group_id = generate_group_id("terminal_open", f"{host}:{port}")
|
|
208
|
+
banner = format_terminal_banner("TERMINAL OPEN 🖥️")
|
|
209
|
+
emit_info(
|
|
210
|
+
Text.from_markup(f"{banner} [bold cyan]{host}:{port}[/bold cyan]"),
|
|
211
|
+
message_group=group_id,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# First, check if the server is running
|
|
215
|
+
server_check = await check_terminal_server(host, port)
|
|
216
|
+
if not server_check["success"]:
|
|
217
|
+
return {
|
|
218
|
+
"success": False,
|
|
219
|
+
"error": (
|
|
220
|
+
f"Cannot open terminal: {server_check['error']} "
|
|
221
|
+
"Please start the API server with 'code-puppy api' first."
|
|
222
|
+
),
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
terminal_url = f"http://{host}:{port}/terminal"
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
# Get the ChromiumTerminalManager for this session and initialize browser
|
|
229
|
+
manager = get_session_manager()
|
|
230
|
+
await manager.async_initialize()
|
|
231
|
+
|
|
232
|
+
# Get the existing page (don't create a new one!) and navigate to terminal
|
|
233
|
+
# This avoids leaving an about:blank tab that causes focus issues
|
|
234
|
+
page = await manager.get_current_page()
|
|
235
|
+
if not page:
|
|
236
|
+
return {"success": False, "error": "Failed to get browser page"}
|
|
237
|
+
|
|
238
|
+
await page.goto(terminal_url)
|
|
239
|
+
|
|
240
|
+
# Wait for xterm.js to be loaded and ready
|
|
241
|
+
# The terminal container should have the xterm class when ready
|
|
242
|
+
try:
|
|
243
|
+
await page.wait_for_selector(
|
|
244
|
+
".xterm",
|
|
245
|
+
timeout=TERMINAL_LOAD_TIMEOUT,
|
|
246
|
+
)
|
|
247
|
+
emit_info("Terminal xterm.js loaded", message_group=group_id)
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.warning(f"Timeout waiting for xterm.js: {e}")
|
|
250
|
+
# Continue anyway - the page might still be usable
|
|
251
|
+
|
|
252
|
+
# Get page information
|
|
253
|
+
final_url = page.url
|
|
254
|
+
page_title = await page.title()
|
|
255
|
+
|
|
256
|
+
emit_success(
|
|
257
|
+
f"Terminal opened: {final_url}",
|
|
258
|
+
message_group=group_id,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
"success": True,
|
|
263
|
+
"url": final_url,
|
|
264
|
+
"page_title": page_title,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
except Exception as e:
|
|
268
|
+
error_msg = f"Failed to open terminal: {str(e)}"
|
|
269
|
+
emit_error(error_msg, message_group=group_id)
|
|
270
|
+
logger.exception("Error opening terminal browser")
|
|
271
|
+
return {"success": False, "error": error_msg}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
async def close_terminal() -> Dict[str, Any]:
|
|
275
|
+
"""Close the terminal browser and clean up resources.
|
|
276
|
+
|
|
277
|
+
Closes the Chromium browser instance managed by ChromiumTerminalManager,
|
|
278
|
+
saving any browser state and releasing resources.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
A dictionary containing:
|
|
282
|
+
- success (bool): True if terminal was closed successfully.
|
|
283
|
+
- message (str): A message describing the result.
|
|
284
|
+
- error (str): Error message if closing failed (only if unsuccessful).
|
|
285
|
+
|
|
286
|
+
Example:
|
|
287
|
+
>>> result = await close_terminal()
|
|
288
|
+
>>> print(result["message"])
|
|
289
|
+
"Terminal closed"
|
|
290
|
+
"""
|
|
291
|
+
group_id = generate_group_id("terminal_close")
|
|
292
|
+
banner = format_terminal_banner("TERMINAL CLOSE 🔒")
|
|
293
|
+
emit_info(
|
|
294
|
+
Text.from_markup(f"{banner}"),
|
|
295
|
+
message_group=group_id,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
manager = get_session_manager()
|
|
300
|
+
await manager.close()
|
|
301
|
+
|
|
302
|
+
emit_success("Terminal browser closed", message_group=group_id)
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
"success": True,
|
|
306
|
+
"message": "Terminal closed",
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
except Exception as e:
|
|
310
|
+
error_msg = f"Failed to close terminal: {str(e)}"
|
|
311
|
+
emit_error(error_msg, message_group=group_id)
|
|
312
|
+
logger.exception("Error closing terminal browser")
|
|
313
|
+
return {"success": False, "error": error_msg}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
async def start_api_server(port: int = 8765) -> Dict[str, Any]:
|
|
317
|
+
"""Start the Code Puppy API server in the background.
|
|
318
|
+
|
|
319
|
+
This starts the API server that provides the terminal endpoint for
|
|
320
|
+
browser-based terminal testing. The server runs in the background
|
|
321
|
+
and persists until stopped with /api stop or the process is killed.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
port: The port to run the server on (default: 8765).
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
A dictionary containing:
|
|
328
|
+
- success (bool): True if server was started successfully.
|
|
329
|
+
- pid (int): Process ID of the server (if successful).
|
|
330
|
+
- url (str): URL where the server is running (if successful).
|
|
331
|
+
- already_running (bool): True if server was already running.
|
|
332
|
+
- error (str): Error message if start failed (only if unsuccessful).
|
|
333
|
+
|
|
334
|
+
Example:
|
|
335
|
+
>>> result = await start_api_server()
|
|
336
|
+
>>> if result["success"]:
|
|
337
|
+
... print(f"Server running at {result['url']}")
|
|
338
|
+
"""
|
|
339
|
+
import os
|
|
340
|
+
import subprocess
|
|
341
|
+
import sys
|
|
342
|
+
from pathlib import Path
|
|
343
|
+
|
|
344
|
+
from code_puppy.config import STATE_DIR
|
|
345
|
+
|
|
346
|
+
group_id = generate_group_id("start_api_server", str(port))
|
|
347
|
+
emit_info(
|
|
348
|
+
Text.from_markup(format_terminal_banner(f"START API SERVER 🚀 port:{port}")),
|
|
349
|
+
message_group=group_id,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
pid_file = Path(STATE_DIR) / "api_server.pid"
|
|
353
|
+
server_url = f"http://127.0.0.1:{port}"
|
|
354
|
+
|
|
355
|
+
# Check if already running
|
|
356
|
+
if pid_file.exists():
|
|
357
|
+
try:
|
|
358
|
+
pid = int(pid_file.read_text().strip())
|
|
359
|
+
os.kill(pid, 0) # Check if process exists
|
|
360
|
+
emit_success(
|
|
361
|
+
f"API server already running (PID {pid})", message_group=group_id
|
|
362
|
+
)
|
|
363
|
+
return {
|
|
364
|
+
"success": True,
|
|
365
|
+
"pid": pid,
|
|
366
|
+
"url": server_url,
|
|
367
|
+
"already_running": True,
|
|
368
|
+
}
|
|
369
|
+
except (OSError, ValueError):
|
|
370
|
+
pid_file.unlink(missing_ok=True) # Stale PID file
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
# Start the server in background
|
|
374
|
+
proc = subprocess.Popen(
|
|
375
|
+
[sys.executable, "-m", "code_puppy.api.main"],
|
|
376
|
+
stdout=subprocess.DEVNULL,
|
|
377
|
+
stderr=subprocess.DEVNULL,
|
|
378
|
+
start_new_session=True,
|
|
379
|
+
)
|
|
380
|
+
pid_file.parent.mkdir(parents=True, exist_ok=True)
|
|
381
|
+
pid_file.write_text(str(proc.pid))
|
|
382
|
+
|
|
383
|
+
emit_success(f"API server started (PID {proc.pid})", message_group=group_id)
|
|
384
|
+
emit_info(f"Server URL: {server_url}", message_group=group_id)
|
|
385
|
+
emit_info(f"Docs: {server_url}/docs", message_group=group_id)
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
"success": True,
|
|
389
|
+
"pid": proc.pid,
|
|
390
|
+
"url": server_url,
|
|
391
|
+
"already_running": False,
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
except Exception as e:
|
|
395
|
+
error_msg = f"Failed to start API server: {str(e)}"
|
|
396
|
+
emit_error(error_msg, message_group=group_id)
|
|
397
|
+
logger.exception("Error starting API server")
|
|
398
|
+
return {"success": False, "error": error_msg}
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# =============================================================================
|
|
402
|
+
# Tool Registration Functions
|
|
403
|
+
# =============================================================================
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def register_check_terminal_server(agent):
|
|
407
|
+
"""Register the terminal server health check tool with an agent.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
agent: The pydantic-ai agent to register the tool with.
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
@agent.tool
|
|
414
|
+
async def terminal_check_server(
|
|
415
|
+
context: RunContext,
|
|
416
|
+
host: str = "localhost",
|
|
417
|
+
port: int = 8765,
|
|
418
|
+
) -> Dict[str, Any]:
|
|
419
|
+
"""
|
|
420
|
+
Check if the Code Puppy API server is running and healthy.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
host: The hostname where the server is running (default: localhost)
|
|
424
|
+
port: The port number for the server (default: 8765)
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Dict with:
|
|
428
|
+
- success: True if server is healthy
|
|
429
|
+
- server_url: Full URL of the server (if successful)
|
|
430
|
+
- status: "healthy" if running (if successful)
|
|
431
|
+
- error: Error message (if unsuccessful)
|
|
432
|
+
"""
|
|
433
|
+
return await check_terminal_server(host, port)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def register_open_terminal(agent):
|
|
437
|
+
"""Register the terminal open tool with an agent.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
agent: The pydantic-ai agent to register the tool with.
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
@agent.tool
|
|
444
|
+
async def terminal_open(
|
|
445
|
+
context: RunContext,
|
|
446
|
+
host: str = "localhost",
|
|
447
|
+
port: int = 8765,
|
|
448
|
+
) -> Dict[str, Any]:
|
|
449
|
+
"""
|
|
450
|
+
Open the terminal browser interface.
|
|
451
|
+
|
|
452
|
+
First checks if the API server is running, then opens a browser
|
|
453
|
+
to the terminal endpoint. Waits for xterm.js to load.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
host: The hostname where the server is running (default: localhost)
|
|
457
|
+
port: The port number for the server (default: 8765)
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Dict with:
|
|
461
|
+
- success: True if terminal opened successfully
|
|
462
|
+
- url: URL of the terminal page (if successful)
|
|
463
|
+
- page_title: Title of the page (if successful)
|
|
464
|
+
- error: Error message (if unsuccessful)
|
|
465
|
+
"""
|
|
466
|
+
# Session is set by invoke_agent via contextvar - just use it
|
|
467
|
+
return await open_terminal(host, port)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def register_close_terminal(agent):
|
|
471
|
+
"""Register the terminal close tool with an agent.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
agent: The pydantic-ai agent to register the tool with.
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
@agent.tool
|
|
478
|
+
async def terminal_close(
|
|
479
|
+
context: RunContext,
|
|
480
|
+
) -> Dict[str, Any]:
|
|
481
|
+
"""
|
|
482
|
+
Close the terminal browser and clean up resources.
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
Dict with:
|
|
486
|
+
- success: True if terminal closed successfully
|
|
487
|
+
- message: Status message (if successful)
|
|
488
|
+
- error: Error message (if unsuccessful)
|
|
489
|
+
"""
|
|
490
|
+
# Session is set by invoke_agent via contextvar - just use it
|
|
491
|
+
return await close_terminal()
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def register_start_api_server(agent):
|
|
495
|
+
"""Register the API server start tool with an agent.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
agent: The pydantic-ai agent to register the tool with.
|
|
499
|
+
"""
|
|
500
|
+
|
|
501
|
+
@agent.tool
|
|
502
|
+
async def start_api_server(
|
|
503
|
+
context: RunContext,
|
|
504
|
+
port: int = 8765,
|
|
505
|
+
) -> Dict[str, Any]:
|
|
506
|
+
"""
|
|
507
|
+
Start the Code Puppy API server in the background.
|
|
508
|
+
|
|
509
|
+
This starts the API server that provides the terminal endpoint.
|
|
510
|
+
Use this if terminal_check_server reports the server isn't running.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
port: The port to run the server on (default: 8765)
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
Dict with:
|
|
517
|
+
- success: True if server started successfully
|
|
518
|
+
- pid: Process ID of the server (if successful)
|
|
519
|
+
- url: URL where the server is running (if successful)
|
|
520
|
+
- already_running: True if server was already running
|
|
521
|
+
- error: Error message (if unsuccessful)
|
|
522
|
+
"""
|
|
523
|
+
from . import terminal_tools
|
|
524
|
+
|
|
525
|
+
return await terminal_tools.start_api_server(port)
|