code-puppy 0.0.341__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 +34 -252
- code_puppy/agents/event_stream_handler.py +350 -0
- 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/cli_runner.py +4 -3
- code_puppy/command_line/add_model_menu.py +8 -9
- code_puppy/command_line/core_commands.py +85 -0
- code_puppy/command_line/mcp/catalog_server_installer.py +5 -6
- code_puppy/command_line/mcp/custom_server_form.py +54 -19
- code_puppy/command_line/mcp/custom_server_installer.py +8 -9
- code_puppy/command_line/mcp/handler.py +0 -2
- code_puppy/command_line/mcp/help_command.py +1 -5
- code_puppy/command_line/mcp/start_command.py +36 -18
- code_puppy/command_line/onboarding_slides.py +0 -1
- code_puppy/command_line/prompt_toolkit_completion.py +16 -10
- code_puppy/command_line/utils.py +54 -0
- code_puppy/config.py +66 -62
- code_puppy/mcp_/async_lifecycle.py +35 -4
- code_puppy/mcp_/managed_server.py +49 -20
- code_puppy/mcp_/manager.py +81 -52
- code_puppy/messaging/__init__.py +15 -0
- code_puppy/messaging/message_queue.py +11 -23
- 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 +139 -36
- 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.341.dist-info → code_puppy-0.0.361.dist-info}/METADATA +13 -11
- {code_puppy-0.0.341.dist-info → code_puppy-0.0.361.dist-info}/RECORD +84 -53
- code_puppy/command_line/mcp/add_command.py +0 -170
- code_puppy/tools/browser/vqa_agent.py +0 -90
- {code_puppy-0.0.341.data → code_puppy-0.0.361.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.341.data → code_puppy-0.0.361.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.341.dist-info → code_puppy-0.0.361.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.341.dist-info → code_puppy-0.0.361.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.341.dist-info → code_puppy-0.0.361.dist-info}/licenses/LICENSE +0 -0
code_puppy/agents/base_agent.py
CHANGED
|
@@ -7,7 +7,6 @@ import signal
|
|
|
7
7
|
import threading
|
|
8
8
|
import uuid
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
|
-
from collections.abc import AsyncIterable
|
|
11
10
|
from typing import (
|
|
12
11
|
Any,
|
|
13
12
|
Callable,
|
|
@@ -30,7 +29,6 @@ from pydantic_ai import (
|
|
|
30
29
|
BinaryContent,
|
|
31
30
|
DocumentUrl,
|
|
32
31
|
ImageUrl,
|
|
33
|
-
PartEndEvent,
|
|
34
32
|
RunContext,
|
|
35
33
|
UsageLimitExceeded,
|
|
36
34
|
UsageLimits,
|
|
@@ -48,6 +46,8 @@ from pydantic_ai.messages import (
|
|
|
48
46
|
)
|
|
49
47
|
from rich.text import Text
|
|
50
48
|
|
|
49
|
+
from code_puppy.agents.event_stream_handler import event_stream_handler
|
|
50
|
+
|
|
51
51
|
# Consolidated relative imports
|
|
52
52
|
from code_puppy.config import (
|
|
53
53
|
get_agent_pinned_model,
|
|
@@ -100,9 +100,6 @@ class BaseAgent(ABC):
|
|
|
100
100
|
# Cache for MCP tool definitions (for token estimation)
|
|
101
101
|
# This is populated after the first successful run when MCP tools are retrieved
|
|
102
102
|
self._mcp_tool_definitions_cache: List[Dict[str, Any]] = []
|
|
103
|
-
# Shared console for streaming output - should be set by cli_runner
|
|
104
|
-
# to avoid conflicts between spinner's Live display and response streaming
|
|
105
|
-
self._console: Optional[Any] = None
|
|
106
103
|
|
|
107
104
|
@property
|
|
108
105
|
@abstractmethod
|
|
@@ -380,8 +377,10 @@ class BaseAgent(ABC):
|
|
|
380
377
|
# fixed instructions. For other models, count the full system prompt.
|
|
381
378
|
try:
|
|
382
379
|
from code_puppy.model_utils import (
|
|
380
|
+
get_antigravity_instructions,
|
|
383
381
|
get_chatgpt_codex_instructions,
|
|
384
382
|
get_claude_code_instructions,
|
|
383
|
+
is_antigravity_model,
|
|
385
384
|
is_chatgpt_codex_model,
|
|
386
385
|
is_claude_code_model,
|
|
387
386
|
)
|
|
@@ -399,6 +398,11 @@ class BaseAgent(ABC):
|
|
|
399
398
|
# The full system prompt is already in the message history
|
|
400
399
|
instructions = get_chatgpt_codex_instructions()
|
|
401
400
|
total_tokens += self.estimate_token_count(instructions)
|
|
401
|
+
elif is_antigravity_model(model_name):
|
|
402
|
+
# For Antigravity models, only count the short fixed instructions
|
|
403
|
+
# The full system prompt is already in the message history
|
|
404
|
+
instructions = get_antigravity_instructions()
|
|
405
|
+
total_tokens += self.estimate_token_count(instructions)
|
|
402
406
|
else:
|
|
403
407
|
# For other models, count the full system prompt
|
|
404
408
|
system_prompt = self.get_system_prompt()
|
|
@@ -1233,9 +1237,12 @@ class BaseAgent(ABC):
|
|
|
1233
1237
|
agent_tools = self.get_available_tools()
|
|
1234
1238
|
register_tools_for_agent(agent_without_mcp, agent_tools)
|
|
1235
1239
|
|
|
1236
|
-
# Wrap with DBOS
|
|
1240
|
+
# Wrap with DBOS - pass event_stream_handler at construction time
|
|
1241
|
+
# so DBOSModel gets the handler for streaming output
|
|
1237
1242
|
dbos_agent = DBOSAgent(
|
|
1238
|
-
agent_without_mcp,
|
|
1243
|
+
agent_without_mcp,
|
|
1244
|
+
name=f"{self.name}-{_reload_count}",
|
|
1245
|
+
event_stream_handler=event_stream_handler,
|
|
1239
1246
|
)
|
|
1240
1247
|
self.pydantic_agent = dbos_agent
|
|
1241
1248
|
self._code_generation_agent = dbos_agent
|
|
@@ -1314,8 +1321,11 @@ class BaseAgent(ABC):
|
|
|
1314
1321
|
)
|
|
1315
1322
|
agent_tools = self.get_available_tools()
|
|
1316
1323
|
register_tools_for_agent(temp_agent, agent_tools)
|
|
1324
|
+
# Pass event_stream_handler at construction time for streaming output
|
|
1317
1325
|
dbos_agent = DBOSAgent(
|
|
1318
|
-
temp_agent,
|
|
1326
|
+
temp_agent,
|
|
1327
|
+
name=f"{self.name}-structured-{_reload_count}",
|
|
1328
|
+
event_stream_handler=event_stream_handler,
|
|
1319
1329
|
)
|
|
1320
1330
|
return dbos_agent
|
|
1321
1331
|
else:
|
|
@@ -1357,241 +1367,6 @@ class BaseAgent(ABC):
|
|
|
1357
1367
|
self.set_message_history(result_messages_filtered_empty_thinking)
|
|
1358
1368
|
return self.get_message_history()
|
|
1359
1369
|
|
|
1360
|
-
async def _event_stream_handler(
|
|
1361
|
-
self, ctx: RunContext, events: AsyncIterable[Any]
|
|
1362
|
-
) -> None:
|
|
1363
|
-
"""Handle streaming events from the agent run.
|
|
1364
|
-
|
|
1365
|
-
This method processes streaming events and emits TextPart, ThinkingPart,
|
|
1366
|
-
and ToolCallPart content with styled banners/tokens as they stream in.
|
|
1367
|
-
|
|
1368
|
-
Args:
|
|
1369
|
-
ctx: The run context.
|
|
1370
|
-
events: Async iterable of streaming events (PartStartEvent, PartDeltaEvent, etc.).
|
|
1371
|
-
"""
|
|
1372
|
-
from pydantic_ai import PartDeltaEvent, PartStartEvent
|
|
1373
|
-
from pydantic_ai.messages import (
|
|
1374
|
-
TextPartDelta,
|
|
1375
|
-
ThinkingPartDelta,
|
|
1376
|
-
ToolCallPartDelta,
|
|
1377
|
-
)
|
|
1378
|
-
from rich.console import Console
|
|
1379
|
-
from rich.markup import escape
|
|
1380
|
-
|
|
1381
|
-
from code_puppy.messaging.spinner import pause_all_spinners
|
|
1382
|
-
|
|
1383
|
-
# IMPORTANT: Use the shared console (set by cli_runner) to avoid conflicts
|
|
1384
|
-
# with the spinner's Live display. Multiple Console instances with separate
|
|
1385
|
-
# Live displays cause cursor positioning chaos and line duplication.
|
|
1386
|
-
if self._console is not None:
|
|
1387
|
-
console = self._console
|
|
1388
|
-
else:
|
|
1389
|
-
# Fallback if console not set (shouldn't happen in normal use)
|
|
1390
|
-
console = Console()
|
|
1391
|
-
|
|
1392
|
-
# Track which part indices we're currently streaming (for Text/Thinking/Tool parts)
|
|
1393
|
-
streaming_parts: set[int] = set()
|
|
1394
|
-
thinking_parts: set[int] = (
|
|
1395
|
-
set()
|
|
1396
|
-
) # Track which parts are thinking (for dim style)
|
|
1397
|
-
text_parts: set[int] = set() # Track which parts are text
|
|
1398
|
-
tool_parts: set[int] = set() # Track which parts are tool calls
|
|
1399
|
-
banner_printed: set[int] = set() # Track if banner was already printed
|
|
1400
|
-
token_count: dict[int, int] = {} # Track token count per text/tool part
|
|
1401
|
-
did_stream_anything = False # Track if we streamed any content
|
|
1402
|
-
|
|
1403
|
-
# Termflow streaming state for text parts
|
|
1404
|
-
from termflow import Parser as TermflowParser
|
|
1405
|
-
from termflow import Renderer as TermflowRenderer
|
|
1406
|
-
|
|
1407
|
-
termflow_parsers: dict[int, TermflowParser] = {}
|
|
1408
|
-
termflow_renderers: dict[int, TermflowRenderer] = {}
|
|
1409
|
-
termflow_line_buffers: dict[int, str] = {} # Buffer incomplete lines
|
|
1410
|
-
|
|
1411
|
-
def _print_thinking_banner() -> None:
|
|
1412
|
-
"""Print the THINKING banner with spinner pause and line clear."""
|
|
1413
|
-
nonlocal did_stream_anything
|
|
1414
|
-
import time
|
|
1415
|
-
|
|
1416
|
-
from code_puppy.config import get_banner_color
|
|
1417
|
-
|
|
1418
|
-
pause_all_spinners()
|
|
1419
|
-
time.sleep(0.1) # Delay to let spinner fully clear
|
|
1420
|
-
# Clear line and print newline before banner
|
|
1421
|
-
console.print(" " * 50, end="\r")
|
|
1422
|
-
console.print() # Newline before banner
|
|
1423
|
-
# Bold banner with configurable color and lightning bolt
|
|
1424
|
-
thinking_color = get_banner_color("thinking")
|
|
1425
|
-
console.print(
|
|
1426
|
-
Text.from_markup(
|
|
1427
|
-
f"[bold white on {thinking_color}] THINKING [/bold white on {thinking_color}] [dim]⚡ "
|
|
1428
|
-
),
|
|
1429
|
-
end="",
|
|
1430
|
-
)
|
|
1431
|
-
did_stream_anything = True
|
|
1432
|
-
|
|
1433
|
-
def _print_response_banner() -> None:
|
|
1434
|
-
"""Print the AGENT RESPONSE banner with spinner pause and line clear."""
|
|
1435
|
-
nonlocal did_stream_anything
|
|
1436
|
-
import time
|
|
1437
|
-
|
|
1438
|
-
from code_puppy.config import get_banner_color
|
|
1439
|
-
|
|
1440
|
-
pause_all_spinners()
|
|
1441
|
-
time.sleep(0.1) # Delay to let spinner fully clear
|
|
1442
|
-
# Clear line and print newline before banner
|
|
1443
|
-
console.print(" " * 50, end="\r")
|
|
1444
|
-
console.print() # Newline before banner
|
|
1445
|
-
response_color = get_banner_color("agent_response")
|
|
1446
|
-
console.print(
|
|
1447
|
-
Text.from_markup(
|
|
1448
|
-
f"[bold white on {response_color}] AGENT RESPONSE [/bold white on {response_color}]"
|
|
1449
|
-
)
|
|
1450
|
-
)
|
|
1451
|
-
did_stream_anything = True
|
|
1452
|
-
|
|
1453
|
-
async for event in events:
|
|
1454
|
-
# PartStartEvent - register the part but defer banner until content arrives
|
|
1455
|
-
if isinstance(event, PartStartEvent):
|
|
1456
|
-
part = event.part
|
|
1457
|
-
if isinstance(part, ThinkingPart):
|
|
1458
|
-
streaming_parts.add(event.index)
|
|
1459
|
-
thinking_parts.add(event.index)
|
|
1460
|
-
# If there's initial content, print banner + content now
|
|
1461
|
-
if part.content and part.content.strip():
|
|
1462
|
-
_print_thinking_banner()
|
|
1463
|
-
escaped = escape(part.content)
|
|
1464
|
-
console.print(f"[dim]{escaped}[/dim]", end="")
|
|
1465
|
-
banner_printed.add(event.index)
|
|
1466
|
-
elif isinstance(part, TextPart):
|
|
1467
|
-
streaming_parts.add(event.index)
|
|
1468
|
-
text_parts.add(event.index)
|
|
1469
|
-
# Initialize termflow streaming for this text part
|
|
1470
|
-
termflow_parsers[event.index] = TermflowParser()
|
|
1471
|
-
termflow_renderers[event.index] = TermflowRenderer(
|
|
1472
|
-
output=console.file, width=console.width
|
|
1473
|
-
)
|
|
1474
|
-
termflow_line_buffers[event.index] = ""
|
|
1475
|
-
# Handle initial content if present
|
|
1476
|
-
if part.content and part.content.strip():
|
|
1477
|
-
_print_response_banner()
|
|
1478
|
-
banner_printed.add(event.index)
|
|
1479
|
-
termflow_line_buffers[event.index] = part.content
|
|
1480
|
-
elif isinstance(part, ToolCallPart):
|
|
1481
|
-
streaming_parts.add(event.index)
|
|
1482
|
-
tool_parts.add(event.index)
|
|
1483
|
-
token_count[event.index] = 0 # Initialize token counter
|
|
1484
|
-
# Track tool name for display
|
|
1485
|
-
banner_printed.add(
|
|
1486
|
-
event.index
|
|
1487
|
-
) # Use banner_printed to track if we've shown tool info
|
|
1488
|
-
|
|
1489
|
-
# PartDeltaEvent - stream the content as it arrives
|
|
1490
|
-
elif isinstance(event, PartDeltaEvent):
|
|
1491
|
-
if event.index in streaming_parts:
|
|
1492
|
-
delta = event.delta
|
|
1493
|
-
if isinstance(delta, (TextPartDelta, ThinkingPartDelta)):
|
|
1494
|
-
if delta.content_delta:
|
|
1495
|
-
# For text parts, stream markdown with termflow
|
|
1496
|
-
if event.index in text_parts:
|
|
1497
|
-
# Print banner on first content
|
|
1498
|
-
if event.index not in banner_printed:
|
|
1499
|
-
_print_response_banner()
|
|
1500
|
-
banner_printed.add(event.index)
|
|
1501
|
-
|
|
1502
|
-
# Add content to line buffer
|
|
1503
|
-
termflow_line_buffers[event.index] += (
|
|
1504
|
-
delta.content_delta
|
|
1505
|
-
)
|
|
1506
|
-
|
|
1507
|
-
# Process complete lines
|
|
1508
|
-
parser = termflow_parsers[event.index]
|
|
1509
|
-
renderer = termflow_renderers[event.index]
|
|
1510
|
-
buffer = termflow_line_buffers[event.index]
|
|
1511
|
-
|
|
1512
|
-
while "\n" in buffer:
|
|
1513
|
-
line, buffer = buffer.split("\n", 1)
|
|
1514
|
-
events_to_render = parser.parse_line(line)
|
|
1515
|
-
renderer.render_all(events_to_render)
|
|
1516
|
-
|
|
1517
|
-
termflow_line_buffers[event.index] = buffer
|
|
1518
|
-
else:
|
|
1519
|
-
# For thinking parts, stream immediately (dim)
|
|
1520
|
-
if event.index not in banner_printed:
|
|
1521
|
-
_print_thinking_banner()
|
|
1522
|
-
banner_printed.add(event.index)
|
|
1523
|
-
escaped = escape(delta.content_delta)
|
|
1524
|
-
console.print(f"[dim]{escaped}[/dim]", end="")
|
|
1525
|
-
elif isinstance(delta, ToolCallPartDelta):
|
|
1526
|
-
# For tool calls, count chunks received
|
|
1527
|
-
token_count[event.index] += 1
|
|
1528
|
-
# Get tool name if available
|
|
1529
|
-
tool_name = getattr(delta, "tool_name_delta", "")
|
|
1530
|
-
count = token_count[event.index]
|
|
1531
|
-
# Display with tool wrench icon and tool name
|
|
1532
|
-
if tool_name:
|
|
1533
|
-
console.print(
|
|
1534
|
-
f" 🔧 Calling {tool_name}... {count} chunks ",
|
|
1535
|
-
end="\r",
|
|
1536
|
-
)
|
|
1537
|
-
else:
|
|
1538
|
-
console.print(
|
|
1539
|
-
f" 🔧 Calling tool... {count} chunks ",
|
|
1540
|
-
end="\r",
|
|
1541
|
-
)
|
|
1542
|
-
|
|
1543
|
-
# PartEndEvent - finish the streaming with a newline
|
|
1544
|
-
elif isinstance(event, PartEndEvent):
|
|
1545
|
-
if event.index in streaming_parts:
|
|
1546
|
-
# For text parts, finalize termflow rendering
|
|
1547
|
-
if event.index in text_parts:
|
|
1548
|
-
# Render any remaining buffered content
|
|
1549
|
-
if event.index in termflow_parsers:
|
|
1550
|
-
parser = termflow_parsers[event.index]
|
|
1551
|
-
renderer = termflow_renderers[event.index]
|
|
1552
|
-
remaining = termflow_line_buffers.get(event.index, "")
|
|
1553
|
-
|
|
1554
|
-
# Parse and render any remaining partial line
|
|
1555
|
-
if remaining.strip():
|
|
1556
|
-
events_to_render = parser.parse_line(remaining)
|
|
1557
|
-
renderer.render_all(events_to_render)
|
|
1558
|
-
|
|
1559
|
-
# Finalize the parser to close any open blocks
|
|
1560
|
-
final_events = parser.finalize()
|
|
1561
|
-
renderer.render_all(final_events)
|
|
1562
|
-
|
|
1563
|
-
# Clean up termflow state
|
|
1564
|
-
del termflow_parsers[event.index]
|
|
1565
|
-
del termflow_renderers[event.index]
|
|
1566
|
-
del termflow_line_buffers[event.index]
|
|
1567
|
-
# For tool parts, clear the chunk counter line
|
|
1568
|
-
elif event.index in tool_parts:
|
|
1569
|
-
# Clear the chunk counter line by printing spaces and returning
|
|
1570
|
-
console.print(" " * 50, end="\r")
|
|
1571
|
-
# For thinking parts, just print newline
|
|
1572
|
-
elif event.index in banner_printed:
|
|
1573
|
-
console.print() # Final newline after streaming
|
|
1574
|
-
|
|
1575
|
-
# Clean up token count
|
|
1576
|
-
token_count.pop(event.index, None)
|
|
1577
|
-
# Clean up all tracking sets
|
|
1578
|
-
streaming_parts.discard(event.index)
|
|
1579
|
-
thinking_parts.discard(event.index)
|
|
1580
|
-
text_parts.discard(event.index)
|
|
1581
|
-
tool_parts.discard(event.index)
|
|
1582
|
-
banner_printed.discard(event.index)
|
|
1583
|
-
|
|
1584
|
-
# Resume spinner if next part is NOT text/thinking/tool (avoid race condition)
|
|
1585
|
-
# If next part is None or handled differently, it's safe to resume
|
|
1586
|
-
# Note: spinner itself handles blank line before appearing
|
|
1587
|
-
from code_puppy.messaging.spinner import resume_all_spinners
|
|
1588
|
-
|
|
1589
|
-
next_kind = getattr(event, "next_part_kind", None)
|
|
1590
|
-
if next_kind not in ("text", "thinking", "tool-call"):
|
|
1591
|
-
resume_all_spinners()
|
|
1592
|
-
|
|
1593
|
-
# Spinner is resumed in PartEndEvent when appropriate (based on next_part_kind)
|
|
1594
|
-
|
|
1595
1370
|
def _spawn_ctrl_x_key_listener(
|
|
1596
1371
|
self,
|
|
1597
1372
|
stop_event: threading.Event,
|
|
@@ -1790,11 +1565,17 @@ class BaseAgent(ABC):
|
|
|
1790
1565
|
if output_type is not None:
|
|
1791
1566
|
pydantic_agent = self._create_agent_with_output_type(output_type)
|
|
1792
1567
|
|
|
1793
|
-
# Handle claude-code
|
|
1794
|
-
from code_puppy.model_utils import
|
|
1568
|
+
# Handle claude-code, chatgpt-codex, and antigravity models: prepend system prompt to first user message
|
|
1569
|
+
from code_puppy.model_utils import (
|
|
1570
|
+
is_antigravity_model,
|
|
1571
|
+
is_chatgpt_codex_model,
|
|
1572
|
+
is_claude_code_model,
|
|
1573
|
+
)
|
|
1795
1574
|
|
|
1796
|
-
if
|
|
1797
|
-
self.get_model_name()
|
|
1575
|
+
if (
|
|
1576
|
+
is_claude_code_model(self.get_model_name())
|
|
1577
|
+
or is_chatgpt_codex_model(self.get_model_name())
|
|
1578
|
+
or is_antigravity_model(self.get_model_name())
|
|
1798
1579
|
):
|
|
1799
1580
|
if len(self.get_message_history()) == 0:
|
|
1800
1581
|
system_prompt = self.get_system_prompt()
|
|
@@ -1859,32 +1640,33 @@ class BaseAgent(ABC):
|
|
|
1859
1640
|
prompt_payload,
|
|
1860
1641
|
message_history=self.get_message_history(),
|
|
1861
1642
|
usage_limits=usage_limits,
|
|
1862
|
-
event_stream_handler=
|
|
1643
|
+
event_stream_handler=event_stream_handler,
|
|
1863
1644
|
**kwargs,
|
|
1864
1645
|
)
|
|
1646
|
+
return result_
|
|
1865
1647
|
finally:
|
|
1866
1648
|
# Always restore original toolsets
|
|
1867
1649
|
pydantic_agent._toolsets = original_toolsets
|
|
1868
1650
|
elif get_use_dbos():
|
|
1869
|
-
# DBOS without MCP servers
|
|
1870
1651
|
with SetWorkflowID(group_id):
|
|
1871
1652
|
result_ = await pydantic_agent.run(
|
|
1872
1653
|
prompt_payload,
|
|
1873
1654
|
message_history=self.get_message_history(),
|
|
1874
1655
|
usage_limits=usage_limits,
|
|
1875
|
-
event_stream_handler=
|
|
1656
|
+
event_stream_handler=event_stream_handler,
|
|
1876
1657
|
**kwargs,
|
|
1877
1658
|
)
|
|
1659
|
+
return result_
|
|
1878
1660
|
else:
|
|
1879
1661
|
# Non-DBOS path (MCP servers are already included)
|
|
1880
1662
|
result_ = await pydantic_agent.run(
|
|
1881
1663
|
prompt_payload,
|
|
1882
1664
|
message_history=self.get_message_history(),
|
|
1883
1665
|
usage_limits=usage_limits,
|
|
1884
|
-
event_stream_handler=
|
|
1666
|
+
event_stream_handler=event_stream_handler,
|
|
1885
1667
|
**kwargs,
|
|
1886
1668
|
)
|
|
1887
|
-
|
|
1669
|
+
return result_
|
|
1888
1670
|
except* UsageLimitExceeded as ule:
|
|
1889
1671
|
emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
|
|
1890
1672
|
emit_info(
|