agentpool 2.1.9__py3-none-any.whl → 2.5.0__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.
- acp/__init__.py +13 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/bridge/README.md +15 -2
- acp/bridge/__init__.py +3 -2
- acp/bridge/__main__.py +60 -19
- acp/bridge/ws_server.py +173 -0
- acp/bridge/ws_server_cli.py +89 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +20 -50
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/stdio.py +39 -9
- acp/task/supervisor.py +2 -2
- acp/transports.py +362 -2
- acp/utils.py +17 -4
- agentpool/__init__.py +6 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +407 -277
- agentpool/agents/acp_agent/acp_converters.py +196 -38
- agentpool/agents/acp_agent/client_handler.py +191 -26
- agentpool/agents/acp_agent/session_state.py +17 -6
- agentpool/agents/agent.py +607 -572
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +176 -110
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +632 -17
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
- agentpool/agents/claude_code_agent/converters.py +74 -143
- agentpool/agents/claude_code_agent/history.py +474 -0
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/context.py +40 -0
- agentpool/agents/events/__init__.py +24 -0
- agentpool/agents/events/builtin_handlers.py +67 -1
- agentpool/agents/events/event_emitter.py +32 -2
- agentpool/agents/events/events.py +104 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +67 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +56 -21
- agentpool/config_resources/__init__.py +38 -1
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +10 -6
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +136 -289
- agentpool/delegation/team.py +58 -57
- agentpool/delegation/teamrun.py +51 -55
- agentpool/diagnostics/__init__.py +53 -0
- agentpool/diagnostics/lsp_manager.py +1593 -0
- agentpool/diagnostics/lsp_proxy.py +41 -0
- agentpool/diagnostics/lsp_proxy_script.py +229 -0
- agentpool/diagnostics/models.py +398 -0
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +76 -32
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +34 -54
- agentpool/mcp_server/registries/official_registry_client.py +35 -1
- agentpool/mcp_server/tool_bridge.py +186 -139
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +99 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +54 -35
- agentpool/messaging/processing.py +12 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -24
- agentpool/models/acp_agents/mcp_capable.py +126 -157
- agentpool/models/acp_agents/non_mcp.py +129 -95
- agentpool/models/agents.py +98 -76
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +144 -19
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +113 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +11 -1
- agentpool/resource_providers/aggregating.py +56 -5
- agentpool/resource_providers/base.py +70 -4
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +89 -12
- agentpool/resource_providers/plan_provider.py +228 -46
- agentpool/resource_providers/pool.py +7 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +4 -2
- agentpool/sessions/__init__.py +4 -1
- agentpool/sessions/manager.py +33 -5
- agentpool/sessions/models.py +59 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +572 -49
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +538 -20
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/file_watcher.py +269 -0
- agentpool/utils/identifiers.py +121 -0
- agentpool/utils/pydantic_ai_helpers.py +46 -0
- agentpool/utils/streams.py +616 -2
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
- agentpool-2.5.0.dist-info/RECORD +579 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +24 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +100 -21
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +42 -5
- agentpool_commands/agents.py +75 -2
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/tools.py +57 -0
- agentpool_commands/utils.py +80 -30
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +132 -6
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +82 -38
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -22
- agentpool_config/toolsets.py +109 -233
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +234 -181
- agentpool_server/acp_server/commands/acp_commands.py +151 -156
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +24 -90
- agentpool_server/acp_server/session.py +173 -331
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +401 -0
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +19 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +975 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +421 -0
- agentpool_server/opencode_server/models/__init__.py +250 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +72 -0
- agentpool_server/opencode_server/models/base.py +26 -0
- agentpool_server/opencode_server/models/common.py +23 -0
- agentpool_server/opencode_server/models/config.py +37 -0
- agentpool_server/opencode_server/models/events.py +821 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +44 -0
- agentpool_server/opencode_server/models/message.py +179 -0
- agentpool_server/opencode_server/models/parts.py +323 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +111 -0
- agentpool_server/opencode_server/routes/__init__.py +29 -0
- agentpool_server/opencode_server/routes/agent_routes.py +473 -0
- agentpool_server/opencode_server/routes/app_routes.py +202 -0
- agentpool_server/opencode_server/routes/config_routes.py +302 -0
- agentpool_server/opencode_server/routes/file_routes.py +571 -0
- agentpool_server/opencode_server/routes/global_routes.py +94 -0
- agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
- agentpool_server/opencode_server/routes/message_routes.py +761 -0
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +300 -0
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +1276 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +475 -0
- agentpool_server/opencode_server/state.py +151 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +12 -0
- agentpool_storage/base.py +184 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/claude_provider/provider.py +1089 -0
- agentpool_storage/file_provider.py +278 -15
- agentpool_storage/memory_provider.py +193 -12
- agentpool_storage/models.py +3 -0
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/project_store.py +325 -0
- agentpool_storage/session_store.py +26 -6
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +269 -3
- agentpool_storage/sql_provider/utils.py +12 -13
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -12
- agentpool_toolsets/builtin/code.py +96 -57
- agentpool_toolsets/builtin/debug.py +118 -48
- agentpool_toolsets/builtin/execution_environment.py +115 -230
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +9 -4
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +99 -7
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +511 -0
- agentpool_toolsets/mcp_run_toolset.py +87 -12
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool-2.1.9.dist-info/RECORD +0 -474
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/text_log_provider.py +0 -275
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Claude Code usage limits helper.
|
|
2
|
+
|
|
3
|
+
Fetches usage information from Anthropic's OAuth API using stored credentials.
|
|
4
|
+
Works on both Linux and macOS.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
import anyenv
|
|
15
|
+
import httpx
|
|
16
|
+
from pydantic import BaseModel
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UsageLimit(BaseModel):
|
|
20
|
+
"""A single usage limit with utilization percentage and reset time."""
|
|
21
|
+
|
|
22
|
+
utilization: float
|
|
23
|
+
"""Utilization percentage (0-100)."""
|
|
24
|
+
|
|
25
|
+
resets_at: datetime | None = None
|
|
26
|
+
"""When this limit resets, or None if not set."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ExtraUsage(BaseModel):
|
|
30
|
+
"""Extra usage information for paid plans."""
|
|
31
|
+
|
|
32
|
+
is_enabled: bool = False
|
|
33
|
+
monthly_limit: float | None = None
|
|
34
|
+
used_credits: float | None = None
|
|
35
|
+
utilization: float | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ClaudeCodeUsage(BaseModel):
|
|
39
|
+
"""Claude Code usage limits."""
|
|
40
|
+
|
|
41
|
+
five_hour: UsageLimit | None = None
|
|
42
|
+
"""5-hour rolling usage limit."""
|
|
43
|
+
|
|
44
|
+
seven_day: UsageLimit | None = None
|
|
45
|
+
"""7-day rolling usage limit."""
|
|
46
|
+
|
|
47
|
+
seven_day_opus: UsageLimit | None = None
|
|
48
|
+
"""7-day Opus-specific limit."""
|
|
49
|
+
|
|
50
|
+
seven_day_sonnet: UsageLimit | None = None
|
|
51
|
+
"""7-day Sonnet-specific limit."""
|
|
52
|
+
|
|
53
|
+
seven_day_oauth_apps: UsageLimit | None = None
|
|
54
|
+
"""7-day OAuth apps limit."""
|
|
55
|
+
|
|
56
|
+
extra_usage: ExtraUsage | None = None
|
|
57
|
+
"""Extra usage info for paid plans."""
|
|
58
|
+
|
|
59
|
+
def format_table(self) -> str:
|
|
60
|
+
"""Format usage as a readable table."""
|
|
61
|
+
lines = ["Claude Code Usage Limits", "=" * 50]
|
|
62
|
+
|
|
63
|
+
def format_limit(name: str, limit: UsageLimit | None) -> str | None:
|
|
64
|
+
if limit is None:
|
|
65
|
+
return None
|
|
66
|
+
reset_str = ""
|
|
67
|
+
if limit.resets_at:
|
|
68
|
+
reset_str = f" (resets {limit.resets_at.strftime('%Y-%m-%d %H:%M UTC')})"
|
|
69
|
+
return f"{name}: {limit.utilization:.0f}%{reset_str}"
|
|
70
|
+
|
|
71
|
+
for name, limit in [
|
|
72
|
+
("5-hour", self.five_hour),
|
|
73
|
+
("7-day", self.seven_day),
|
|
74
|
+
("7-day Opus", self.seven_day_opus),
|
|
75
|
+
("7-day Sonnet", self.seven_day_sonnet),
|
|
76
|
+
("7-day OAuth Apps", self.seven_day_oauth_apps),
|
|
77
|
+
]:
|
|
78
|
+
formatted = format_limit(name, limit)
|
|
79
|
+
if formatted:
|
|
80
|
+
lines.append(formatted)
|
|
81
|
+
|
|
82
|
+
if self.extra_usage and self.extra_usage.is_enabled:
|
|
83
|
+
lines.append("")
|
|
84
|
+
lines.append("Extra Usage (paid):")
|
|
85
|
+
if self.extra_usage.utilization is not None:
|
|
86
|
+
lines.append(f" Utilization: {self.extra_usage.utilization:.0f}%")
|
|
87
|
+
if self.extra_usage.used_credits is not None:
|
|
88
|
+
lines.append(f" Used credits: {self.extra_usage.used_credits}")
|
|
89
|
+
if self.extra_usage.monthly_limit is not None:
|
|
90
|
+
lines.append(f" Monthly limit: {self.extra_usage.monthly_limit}")
|
|
91
|
+
|
|
92
|
+
return "\n".join(lines)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _get_credentials_path() -> Path | None:
|
|
96
|
+
"""Get the path to Claude Code credentials file.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Path to credentials file, or None if not found.
|
|
100
|
+
"""
|
|
101
|
+
# Linux: ~/.claude/.credentials.json
|
|
102
|
+
linux_path = Path.home() / ".claude" / ".credentials.json"
|
|
103
|
+
if linux_path.exists():
|
|
104
|
+
return linux_path
|
|
105
|
+
|
|
106
|
+
# macOS: Also check ~/.claude first (newer versions)
|
|
107
|
+
if linux_path.exists():
|
|
108
|
+
return linux_path
|
|
109
|
+
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _get_access_token_from_file(path: Path) -> str | None:
|
|
114
|
+
"""Read access token from credentials file."""
|
|
115
|
+
try:
|
|
116
|
+
data = anyenv.load_json(path.read_text(), return_type=dict)
|
|
117
|
+
val = data.get("claudeAiOauth", {}).get("accessToken")
|
|
118
|
+
except (anyenv.JsonLoadError, OSError):
|
|
119
|
+
return None
|
|
120
|
+
else:
|
|
121
|
+
assert isinstance(val, str)
|
|
122
|
+
return val
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _get_access_token_from_keychain() -> str | None:
|
|
126
|
+
"""Read access token from macOS Keychain."""
|
|
127
|
+
if sys.platform != "darwin":
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
result = subprocess.run(
|
|
132
|
+
["security", "find-generic-password", "-s", "Claude Code-credentials", "-w"],
|
|
133
|
+
capture_output=True,
|
|
134
|
+
text=True,
|
|
135
|
+
check=True,
|
|
136
|
+
)
|
|
137
|
+
data = anyenv.load_json(result.stdout.strip(), return_type=dict)
|
|
138
|
+
val = data.get("claudeAiOauth", {}).get("accessToken")
|
|
139
|
+
except (subprocess.CalledProcessError, anyenv.JsonLoadError, FileNotFoundError):
|
|
140
|
+
return None
|
|
141
|
+
else:
|
|
142
|
+
assert isinstance(val, str)
|
|
143
|
+
return val
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_access_token() -> str | None:
|
|
147
|
+
"""Get Claude Code OAuth access token.
|
|
148
|
+
|
|
149
|
+
Checks both file-based storage (Linux/newer macOS) and Keychain (macOS).
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Access token string, or None if not found.
|
|
153
|
+
"""
|
|
154
|
+
# Try file-based first (works on Linux and newer macOS)
|
|
155
|
+
creds_path = _get_credentials_path()
|
|
156
|
+
if creds_path:
|
|
157
|
+
token = _get_access_token_from_file(creds_path)
|
|
158
|
+
if token:
|
|
159
|
+
return token
|
|
160
|
+
# Fall back to macOS Keychain
|
|
161
|
+
return _get_access_token_from_keychain()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
async def get_usage_async(token: str | None = None) -> ClaudeCodeUsage:
|
|
165
|
+
"""Fetch Claude Code usage limits asynchronously.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
token: OAuth access token. If not provided, will attempt to read from
|
|
169
|
+
stored credentials.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
ClaudeCodeUsage with current limits.
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
ValueError: If no token provided and credentials not found.
|
|
176
|
+
httpx.HTTPStatusError: If API request fails.
|
|
177
|
+
"""
|
|
178
|
+
if token is None:
|
|
179
|
+
token = get_access_token()
|
|
180
|
+
if token is None:
|
|
181
|
+
msg = "No Claude Code credentials found. Please authenticate with Claude Code first."
|
|
182
|
+
raise ValueError(msg)
|
|
183
|
+
|
|
184
|
+
async with httpx.AsyncClient() as client:
|
|
185
|
+
response = await client.get(
|
|
186
|
+
"https://api.anthropic.com/api/oauth/usage",
|
|
187
|
+
headers={
|
|
188
|
+
"Accept": "application/json",
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
"User-Agent": "claude-code/2.0.32",
|
|
191
|
+
"Authorization": f"Bearer {token}",
|
|
192
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
193
|
+
},
|
|
194
|
+
)
|
|
195
|
+
response.raise_for_status()
|
|
196
|
+
return ClaudeCodeUsage.model_validate(response.json())
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_usage(token: str | None = None) -> ClaudeCodeUsage:
|
|
200
|
+
"""Fetch Claude Code usage limits synchronously.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
token: OAuth access token. If not provided, will attempt to read from
|
|
204
|
+
stored credentials.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
ClaudeCodeUsage with current limits.
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
ValueError: If no token provided and credentials not found.
|
|
211
|
+
httpx.HTTPStatusError: If API request fails.
|
|
212
|
+
"""
|
|
213
|
+
if token is None:
|
|
214
|
+
token = get_access_token()
|
|
215
|
+
if token is None:
|
|
216
|
+
msg = "No Claude Code credentials found. Please authenticate with Claude Code first."
|
|
217
|
+
raise ValueError(msg)
|
|
218
|
+
|
|
219
|
+
with httpx.Client() as client:
|
|
220
|
+
response = client.get(
|
|
221
|
+
"https://api.anthropic.com/api/oauth/usage",
|
|
222
|
+
headers={
|
|
223
|
+
"Accept": "application/json",
|
|
224
|
+
"Content-Type": "application/json",
|
|
225
|
+
"User-Agent": "claude-code/2.0.32",
|
|
226
|
+
"Authorization": f"Bearer {token}",
|
|
227
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
228
|
+
},
|
|
229
|
+
)
|
|
230
|
+
response.raise_for_status()
|
|
231
|
+
return ClaudeCodeUsage.model_validate(response.json())
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if __name__ == "__main__":
|
|
235
|
+
# Quick test
|
|
236
|
+
try:
|
|
237
|
+
usage = get_usage()
|
|
238
|
+
print(usage.format_table())
|
|
239
|
+
except ValueError as e:
|
|
240
|
+
print(f"Error: {e}")
|
|
241
|
+
except httpx.HTTPStatusError as e:
|
|
242
|
+
print(f"API Error: {e.response.status_code} - {e.response.text}")
|
agentpool/agents/context.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from contextvars import ContextVar
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
from typing import TYPE_CHECKING, Any, Literal
|
|
7
8
|
|
|
@@ -9,6 +10,45 @@ from agentpool.log import get_logger
|
|
|
9
10
|
from agentpool.messaging.context import NodeContext
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from contextvars import Token
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ContextVar for passing deps through async call boundaries (e.g., MCP tool bridge)
|
|
18
|
+
# This allows run_stream() to set deps that are accessible in tool invocations
|
|
19
|
+
_current_deps: ContextVar[Any] = ContextVar("current_deps", default=None)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def set_current_deps(deps: Any) -> Token[Any]:
|
|
23
|
+
"""Set the current deps for the running context.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
deps: Dependencies to set
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Token to reset the deps when done
|
|
30
|
+
"""
|
|
31
|
+
return _current_deps.set(deps)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_current_deps() -> Any:
|
|
35
|
+
"""Get the current deps from the running context.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Current deps or None if not set
|
|
39
|
+
"""
|
|
40
|
+
return _current_deps.get()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def reset_current_deps(token: Token[Any]) -> None:
|
|
44
|
+
"""Reset deps to previous value.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
token: Token from set_current_deps
|
|
48
|
+
"""
|
|
49
|
+
_current_deps.reset(token)
|
|
50
|
+
|
|
51
|
+
|
|
12
52
|
if TYPE_CHECKING:
|
|
13
53
|
from mcp import types
|
|
14
54
|
|
|
@@ -3,22 +3,27 @@
|
|
|
3
3
|
from .events import (
|
|
4
4
|
CommandCompleteEvent,
|
|
5
5
|
CommandOutputEvent,
|
|
6
|
+
CompactionEvent,
|
|
6
7
|
CustomEvent,
|
|
7
8
|
DiffContentItem,
|
|
8
9
|
FileContentItem,
|
|
9
10
|
LocationContentItem,
|
|
10
11
|
PlanUpdateEvent,
|
|
12
|
+
PartStartEvent,
|
|
13
|
+
PartDeltaEvent,
|
|
11
14
|
RichAgentStreamEvent,
|
|
12
15
|
RunErrorEvent,
|
|
13
16
|
RunStartedEvent,
|
|
14
17
|
SlashedAgentStreamEvent,
|
|
15
18
|
StreamCompleteEvent,
|
|
19
|
+
SubAgentEvent,
|
|
16
20
|
TerminalContentItem,
|
|
17
21
|
TextContentItem,
|
|
18
22
|
ToolCallCompleteEvent,
|
|
19
23
|
ToolCallContentItem,
|
|
20
24
|
ToolCallProgressEvent,
|
|
21
25
|
ToolCallStartEvent,
|
|
26
|
+
ToolResultMetadataEvent,
|
|
22
27
|
)
|
|
23
28
|
from .event_emitter import StreamEventEmitter
|
|
24
29
|
from .builtin_handlers import (
|
|
@@ -31,17 +36,30 @@ from .tts_handlers import (
|
|
|
31
36
|
EdgeTTSEventHandler,
|
|
32
37
|
OpenAITTSEventHandler,
|
|
33
38
|
)
|
|
39
|
+
from .processors import (
|
|
40
|
+
FileTracker,
|
|
41
|
+
FileTrackingProcessor,
|
|
42
|
+
StreamPipeline,
|
|
43
|
+
StreamProcessor,
|
|
44
|
+
event_handler_processor,
|
|
45
|
+
extract_file_path_from_tool_call,
|
|
46
|
+
)
|
|
34
47
|
|
|
35
48
|
__all__ = [
|
|
36
49
|
"BaseTTSEventHandler",
|
|
37
50
|
"CommandCompleteEvent",
|
|
38
51
|
"CommandOutputEvent",
|
|
52
|
+
"CompactionEvent",
|
|
39
53
|
"CustomEvent",
|
|
40
54
|
"DiffContentItem",
|
|
41
55
|
"EdgeTTSEventHandler",
|
|
42
56
|
"FileContentItem",
|
|
57
|
+
"FileTracker",
|
|
58
|
+
"FileTrackingProcessor",
|
|
43
59
|
"LocationContentItem",
|
|
44
60
|
"OpenAITTSEventHandler",
|
|
61
|
+
"PartDeltaEvent",
|
|
62
|
+
"PartStartEvent",
|
|
45
63
|
"PlanUpdateEvent",
|
|
46
64
|
"RichAgentStreamEvent",
|
|
47
65
|
"RunErrorEvent",
|
|
@@ -49,13 +67,19 @@ __all__ = [
|
|
|
49
67
|
"SlashedAgentStreamEvent",
|
|
50
68
|
"StreamCompleteEvent",
|
|
51
69
|
"StreamEventEmitter",
|
|
70
|
+
"StreamPipeline",
|
|
71
|
+
"StreamProcessor",
|
|
72
|
+
"SubAgentEvent",
|
|
52
73
|
"TerminalContentItem",
|
|
53
74
|
"TextContentItem",
|
|
54
75
|
"ToolCallCompleteEvent",
|
|
55
76
|
"ToolCallContentItem",
|
|
56
77
|
"ToolCallProgressEvent",
|
|
57
78
|
"ToolCallStartEvent",
|
|
79
|
+
"ToolResultMetadataEvent",
|
|
58
80
|
"detailed_print_handler",
|
|
81
|
+
"event_handler_processor",
|
|
82
|
+
"extract_file_path_from_tool_call",
|
|
59
83
|
"resolve_event_handlers",
|
|
60
84
|
"simple_print_handler",
|
|
61
85
|
]
|
|
@@ -24,6 +24,7 @@ from agentpool.agents.events import (
|
|
|
24
24
|
ToolCallProgressEvent,
|
|
25
25
|
ToolCallStartEvent,
|
|
26
26
|
)
|
|
27
|
+
from agentpool.utils.pydantic_ai_helpers import safe_args_as_dict
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
if TYPE_CHECKING:
|
|
@@ -52,7 +53,7 @@ async def simple_print_handler(ctx: RunContext, event: RichAgentStreamEvent[Any]
|
|
|
52
53
|
print(delta, end="", flush=True, file=sys.stderr)
|
|
53
54
|
|
|
54
55
|
case FunctionToolCallEvent(part=ToolCallPart() as part):
|
|
55
|
-
kwargs_str = ", ".join(f"{k}={v!r}" for k, v in part
|
|
56
|
+
kwargs_str = ", ".join(f"{k}={v!r}" for k, v in safe_args_as_dict(part).items())
|
|
56
57
|
print(f"\n🔧 {part.tool_name}({kwargs_str})", flush=True, file=sys.stderr)
|
|
57
58
|
|
|
58
59
|
case FunctionToolResultEvent(result=ToolReturnPart() as return_part):
|
|
@@ -119,6 +120,71 @@ async def detailed_print_handler(ctx: RunContext, event: RichAgentStreamEvent[An
|
|
|
119
120
|
print(file=sys.stderr) # Final newline
|
|
120
121
|
|
|
121
122
|
|
|
123
|
+
def create_file_stream_handler(
|
|
124
|
+
path: str,
|
|
125
|
+
mode: str = "a",
|
|
126
|
+
include_tools: bool = False,
|
|
127
|
+
include_thinking: bool = False,
|
|
128
|
+
) -> IndividualEventHandler:
|
|
129
|
+
"""Create an event handler that streams text output to a file.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
path: Path to the output file
|
|
133
|
+
mode: File open mode ('w' for overwrite, 'a' for append)
|
|
134
|
+
include_tools: Whether to include tool call/result information
|
|
135
|
+
include_thinking: Whether to include thinking content
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Event handler function that writes to the specified file
|
|
139
|
+
"""
|
|
140
|
+
from pathlib import Path
|
|
141
|
+
|
|
142
|
+
file_path = Path(path).expanduser()
|
|
143
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
|
|
145
|
+
# Open file handle that persists across calls
|
|
146
|
+
file_handle = file_path.open(mode, encoding="utf-8")
|
|
147
|
+
|
|
148
|
+
async def file_stream_handler(ctx: RunContext, event: RichAgentStreamEvent[Any]) -> None:
|
|
149
|
+
"""Stream agent output to file."""
|
|
150
|
+
match event:
|
|
151
|
+
case (
|
|
152
|
+
PartStartEvent(part=TextPart(content=delta))
|
|
153
|
+
| PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
|
|
154
|
+
):
|
|
155
|
+
file_handle.write(delta)
|
|
156
|
+
file_handle.flush()
|
|
157
|
+
|
|
158
|
+
case (
|
|
159
|
+
PartStartEvent(part=ThinkingPart(content=delta))
|
|
160
|
+
| PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
|
|
161
|
+
):
|
|
162
|
+
if include_thinking and delta:
|
|
163
|
+
file_handle.write(f"\n[thinking] {delta}")
|
|
164
|
+
file_handle.flush()
|
|
165
|
+
|
|
166
|
+
case FunctionToolCallEvent(part=ToolCallPart() as part):
|
|
167
|
+
if include_tools:
|
|
168
|
+
kwargs_str = ", ".join(f"{k}={v!r}" for k, v in safe_args_as_dict(part).items())
|
|
169
|
+
file_handle.write(f"\n[tool] {part.tool_name}({kwargs_str})\n")
|
|
170
|
+
file_handle.flush()
|
|
171
|
+
|
|
172
|
+
case FunctionToolResultEvent(result=ToolReturnPart() as return_part):
|
|
173
|
+
if include_tools:
|
|
174
|
+
file_handle.write(f"[result] {return_part.content}\n")
|
|
175
|
+
file_handle.flush()
|
|
176
|
+
|
|
177
|
+
case RunErrorEvent(message=message):
|
|
178
|
+
file_handle.write(f"\n[error] {message}\n")
|
|
179
|
+
file_handle.flush()
|
|
180
|
+
|
|
181
|
+
case StreamCompleteEvent():
|
|
182
|
+
file_handle.write("\n")
|
|
183
|
+
file_handle.flush()
|
|
184
|
+
|
|
185
|
+
return file_stream_handler
|
|
186
|
+
|
|
187
|
+
|
|
122
188
|
def resolve_event_handlers(
|
|
123
189
|
event_handlers: Sequence[IndividualEventHandler | BuiltinEventHandlerType] | None,
|
|
124
190
|
) -> list[IndividualEventHandler]:
|
|
@@ -8,12 +8,15 @@ from agentpool.agents.events import (
|
|
|
8
8
|
CustomEvent,
|
|
9
9
|
LocationContentItem,
|
|
10
10
|
PlanUpdateEvent,
|
|
11
|
+
TextContentItem,
|
|
11
12
|
ToolCallProgressEvent,
|
|
12
13
|
ToolCallStartEvent,
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Sequence
|
|
19
|
+
|
|
17
20
|
from agentpool.agents.context import AgentContext
|
|
18
21
|
from agentpool.agents.events import RichAgentStreamEvent, ToolCallContentItem
|
|
19
22
|
from agentpool.resource_providers.plan_provider import PlanEntry
|
|
@@ -78,7 +81,7 @@ class StreamEventEmitter:
|
|
|
78
81
|
title: str,
|
|
79
82
|
*,
|
|
80
83
|
status: Literal["pending", "in_progress", "completed", "failed"] = "in_progress",
|
|
81
|
-
items:
|
|
84
|
+
items: Sequence[ToolCallContentItem | str] | None = None,
|
|
82
85
|
replace_content: bool = False,
|
|
83
86
|
) -> None:
|
|
84
87
|
"""Emit a progress event.
|
|
@@ -89,12 +92,26 @@ class StreamEventEmitter:
|
|
|
89
92
|
items: Rich content items (terminals, diffs, locations, text)
|
|
90
93
|
replace_content: If True, items replace existing content instead of appending
|
|
91
94
|
"""
|
|
95
|
+
# Record file changes from DiffContentItem for diff/revert support
|
|
96
|
+
if self._context.pool and self._context.pool.file_ops and items:
|
|
97
|
+
from agentpool.agents.events import DiffContentItem
|
|
98
|
+
|
|
99
|
+
for item in items:
|
|
100
|
+
if isinstance(item, DiffContentItem):
|
|
101
|
+
self._context.pool.file_ops.record_change(
|
|
102
|
+
path=item.path,
|
|
103
|
+
old_content=item.old_text,
|
|
104
|
+
new_content=item.new_text,
|
|
105
|
+
operation="create" if item.old_text is None else "write",
|
|
106
|
+
agent_name=self._context.node_name,
|
|
107
|
+
)
|
|
108
|
+
|
|
92
109
|
event = ToolCallProgressEvent(
|
|
93
110
|
tool_call_id=self._context.tool_call_id or "",
|
|
94
111
|
tool_name=self._context.tool_name,
|
|
95
112
|
status=status,
|
|
96
113
|
title=title,
|
|
97
|
-
items=items or [],
|
|
114
|
+
items=[TextContentItem(text=i) if isinstance(i, str) else i for i in items or []],
|
|
98
115
|
replace_content=replace_content,
|
|
99
116
|
)
|
|
100
117
|
await self._emit(event)
|
|
@@ -129,6 +146,7 @@ class StreamEventEmitter:
|
|
|
129
146
|
path: str,
|
|
130
147
|
success: bool,
|
|
131
148
|
error: str | None = None,
|
|
149
|
+
line: int = 0,
|
|
132
150
|
) -> None:
|
|
133
151
|
"""Emit file operation event.
|
|
134
152
|
|
|
@@ -137,6 +155,7 @@ class StreamEventEmitter:
|
|
|
137
155
|
path: The file/directory path that was operated on
|
|
138
156
|
success: Whether the operation completed successfully
|
|
139
157
|
error: Error message if operation failed
|
|
158
|
+
line: Line number for navigation (0 = beginning)
|
|
140
159
|
"""
|
|
141
160
|
event = ToolCallProgressEvent.file_operation(
|
|
142
161
|
tool_call_id=self._context.tool_call_id or "",
|
|
@@ -145,6 +164,7 @@ class StreamEventEmitter:
|
|
|
145
164
|
path=path,
|
|
146
165
|
success=success,
|
|
147
166
|
error=error,
|
|
167
|
+
line=line,
|
|
148
168
|
)
|
|
149
169
|
await self._emit(event)
|
|
150
170
|
|
|
@@ -163,6 +183,16 @@ class StreamEventEmitter:
|
|
|
163
183
|
new_text: New file content
|
|
164
184
|
status: Current status of the edit operation
|
|
165
185
|
"""
|
|
186
|
+
# Record file change for diff/revert support
|
|
187
|
+
if self._context.pool and self._context.pool.file_ops:
|
|
188
|
+
self._context.pool.file_ops.record_change(
|
|
189
|
+
path=path,
|
|
190
|
+
old_content=old_text,
|
|
191
|
+
new_content=new_text,
|
|
192
|
+
operation="edit",
|
|
193
|
+
agent_name=self._context.node_name,
|
|
194
|
+
)
|
|
195
|
+
|
|
166
196
|
event = ToolCallProgressEvent.file_edit(
|
|
167
197
|
tool_call_id=self._context.tool_call_id or "",
|
|
168
198
|
tool_name=self._context.tool_name,
|