ata-coder 2.4.2__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.
- ata_coder/__init__.py +1 -0
- ata_coder/agent.py +874 -0
- ata_coder/agent_compact.py +190 -0
- ata_coder/agent_controller.py +218 -0
- ata_coder/agent_extension.py +69 -0
- ata_coder/agent_routing.py +105 -0
- ata_coder/agent_subsystems.py +72 -0
- ata_coder/agent_tools.py +318 -0
- ata_coder/agent_undo.py +63 -0
- ata_coder/anthropic_client.py +465 -0
- ata_coder/change_tracker.py +368 -0
- ata_coder/clawd_integration.py +574 -0
- ata_coder/commands/__init__.py +128 -0
- ata_coder/commands/_core.py +184 -0
- ata_coder/commands/_safety.py +95 -0
- ata_coder/commands/_settings.py +241 -0
- ata_coder/commands/_workflow.py +451 -0
- ata_coder/commands.py +974 -0
- ata_coder/config.py +257 -0
- ata_coder/core/__init__.py +35 -0
- ata_coder/core/events.py +73 -0
- ata_coder/core/queue.py +85 -0
- ata_coder/core/state.py +17 -0
- ata_coder/event_queue.py +5 -0
- ata_coder/extension.py +654 -0
- ata_coder/extensions/__init__.py +1 -0
- ata_coder/extensions/hello_skill.py +47 -0
- ata_coder/fool_proof.py +295 -0
- ata_coder/git_workflow.py +371 -0
- ata_coder/gui.py +511 -0
- ata_coder/llm_client.py +543 -0
- ata_coder/main.py +814 -0
- ata_coder/mcp_client.py +1095 -0
- ata_coder/memory.py +539 -0
- ata_coder/model_registry.py +134 -0
- ata_coder/model_router.py +105 -0
- ata_coder/permissions.py +274 -0
- ata_coder/privilege.py +464 -0
- ata_coder/project.py +273 -0
- ata_coder/prompt_template.py +423 -0
- ata_coder/prompts/auto-mode.md +7 -0
- ata_coder/prompts/coding-rules.md +40 -0
- ata_coder/prompts/execution-guardrails.md +14 -0
- ata_coder/prompts/memory-system.md +24 -0
- ata_coder/prompts/output-style.md +23 -0
- ata_coder/prompts/safety.md +17 -0
- ata_coder/prompts/slash-commands.md +24 -0
- ata_coder/prompts/sub-agents.md +38 -0
- ata_coder/prompts/system-reminders.md +17 -0
- ata_coder/prompts/system.md +105 -0
- ata_coder/prompts/tool-policy.md +46 -0
- ata_coder/repl_theme.py +99 -0
- ata_coder/repl_tracker.py +89 -0
- ata_coder/repl_ui.py +1214 -0
- ata_coder/safety_guard.py +434 -0
- ata_coder/self_correct.py +346 -0
- ata_coder/server.py +882 -0
- ata_coder/server_session.py +159 -0
- ata_coder/server_shell.py +129 -0
- ata_coder/session.py +431 -0
- ata_coder/settings.py +439 -0
- ata_coder/setup_wizard.py +136 -0
- ata_coder/skill_extension.py +92 -0
- ata_coder/skills/architect/SKILL.md +42 -0
- ata_coder/skills/code-reviewer/SKILL.md +37 -0
- ata_coder/skills/codecraft/SKILL.md +452 -0
- ata_coder/skills/debugger/SKILL.md +45 -0
- ata_coder/skills/doc-writer/SKILL.md +36 -0
- ata_coder/skills/general-coder/SKILL.md +76 -0
- ata_coder/skills/math-calculator/README.md +40 -0
- ata_coder/skills/math-calculator/SKILL.md +59 -0
- ata_coder/skills/math-calculator/handler.py +103 -0
- ata_coder/skills/math-calculator/prompts/system.md +8 -0
- ata_coder/skills/math-calculator/requirements.txt +2 -0
- ata_coder/skills/math-calculator/resources/constants.json +8 -0
- ata_coder/skills/math-calculator/tests/test_handler.py +53 -0
- ata_coder/skills/security-auditor/SKILL.md +40 -0
- ata_coder/skills/test-writer/SKILL.md +36 -0
- ata_coder/skills/weather-skill/README.md +45 -0
- ata_coder/skills/weather-skill/handler.py +76 -0
- ata_coder/skills/weather-skill/manifest.json +48 -0
- ata_coder/skills/weather-skill/prompts/system_prompt.txt +9 -0
- ata_coder/skills/weather-skill/prompts/user_prompt_template.txt +3 -0
- ata_coder/skills/weather-skill/requirements.txt +1 -0
- ata_coder/skills/weather-skill/resources/city_list.json +17 -0
- ata_coder/skills/weather-skill/resources/error_messages.json +7 -0
- ata_coder/skills/weather-skill/tests/test_handler.py +28 -0
- ata_coder/skills/weather-skill/weather_utils.py +50 -0
- ata_coder/skills.py +1014 -0
- ata_coder/sub_agent.py +273 -0
- ata_coder/sub_agent_manager.py +203 -0
- ata_coder/system_prompt_builder.py +146 -0
- ata_coder/task_planner.py +391 -0
- ata_coder/terminal.py +318 -0
- ata_coder/test_runner.py +219 -0
- ata_coder/thread_supervisor.py +195 -0
- ata_coder/tool_defs.py +335 -0
- ata_coder/tools/__init__.py +11 -0
- ata_coder/tools/definitions.py +335 -0
- ata_coder/tools/executor.py +1036 -0
- ata_coder/tools/result.py +26 -0
- ata_coder/tools/subagent.py +332 -0
- ata_coder/tools/web.py +361 -0
- ata_coder/tools.py +1576 -0
- ata_coder/types.py +92 -0
- ata_coder/utils.py +113 -0
- ata_coder/web/css/style.css +180 -0
- ata_coder/web/index.html +84 -0
- ata_coder/web/js/app.js +489 -0
- ata_coder/web/package-lock.json +25 -0
- ata_coder/web/package.json +10 -0
- ata_coder/web/tsconfig.json +13 -0
- ata_coder-2.4.2.dist-info/METADATA +799 -0
- ata_coder-2.4.2.dist-info/RECORD +118 -0
- ata_coder-2.4.2.dist-info/WHEEL +5 -0
- ata_coder-2.4.2.dist-info/entry_points.txt +2 -0
- ata_coder-2.4.2.dist-info/licenses/LICENSE +21 -0
- ata_coder-2.4.2.dist-info/top_level.txt +1 -0
ata_coder/config.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for ATA Coder.
|
|
3
|
+
Loads from environment variables with sensible defaults.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import threading
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
from dotenv import load_dotenv
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _find_project_root() -> Path:
|
|
18
|
+
"""Find the project root directory."""
|
|
19
|
+
return Path(__file__).parent.resolve()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _load_env():
|
|
23
|
+
"""Load .env files: CWD first (lowest priority), then project root overrides."""
|
|
24
|
+
cwd_env = Path.cwd() / ".env"
|
|
25
|
+
project_env = _find_project_root() / ".env"
|
|
26
|
+
# Load CWD .env as baseline
|
|
27
|
+
if cwd_env.exists():
|
|
28
|
+
load_dotenv(cwd_env)
|
|
29
|
+
# Project-root .env takes precedence (overrides CWD values)
|
|
30
|
+
if project_env.exists() and project_env != cwd_env:
|
|
31
|
+
load_dotenv(project_env, override=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Defer env loading to first config access, not import time
|
|
35
|
+
_env_loaded = False
|
|
36
|
+
_env_lock = threading.Lock()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _ensure_env():
|
|
40
|
+
"""Thread-safe lazy env loader."""
|
|
41
|
+
global _env_loaded
|
|
42
|
+
if _env_loaded:
|
|
43
|
+
return
|
|
44
|
+
with _env_lock:
|
|
45
|
+
if not _env_loaded:
|
|
46
|
+
_load_env()
|
|
47
|
+
_env_loaded = True
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class LLMConfig:
|
|
52
|
+
"""Configuration for the OpenAI-compatible LLM client."""
|
|
53
|
+
|
|
54
|
+
api_key: str = field(
|
|
55
|
+
default_factory=lambda: _settings_api_key()
|
|
56
|
+
)
|
|
57
|
+
base_url: str = field(
|
|
58
|
+
default_factory=lambda: _settings_base_url()
|
|
59
|
+
)
|
|
60
|
+
model: str = field(
|
|
61
|
+
default_factory=lambda: _settings_default_model()
|
|
62
|
+
)
|
|
63
|
+
temperature: float = field(
|
|
64
|
+
default_factory=lambda: _safe_temperature()
|
|
65
|
+
)
|
|
66
|
+
max_tokens: int = field(
|
|
67
|
+
default_factory=lambda: _settings_max_output_tokens()
|
|
68
|
+
)
|
|
69
|
+
thinking_strength: str = field(
|
|
70
|
+
default_factory=lambda: _settings_thinking_strength()
|
|
71
|
+
) # "" = default, "off" = explicitly disabled, "low"|"medium"|"high"|"xhigh"|"max"
|
|
72
|
+
use_anthropic: bool = field(
|
|
73
|
+
default_factory=lambda: _from_settings("use_anthropic", False)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def __post_init__(self):
|
|
77
|
+
"""Normalize model name: auto-strip [1m] suffix (DeepSeek Anthropic marker)."""
|
|
78
|
+
if self.model and self.model.endswith("[1m]"):
|
|
79
|
+
self.model = self.model[:-4].strip()
|
|
80
|
+
logger.info(
|
|
81
|
+
"Auto-stripped [1m] suffix from model: %s", self.model
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def validate(self) -> list[str]:
|
|
85
|
+
"""Validate configuration, returns list of errors."""
|
|
86
|
+
errors = []
|
|
87
|
+
if not self.api_key:
|
|
88
|
+
errors.append(
|
|
89
|
+
"OPENAI_API_KEY is not set. "
|
|
90
|
+
"Set it in .env file or as environment variable."
|
|
91
|
+
)
|
|
92
|
+
if not self.model:
|
|
93
|
+
errors.append("OPENAI_MODEL is not set.")
|
|
94
|
+
return errors
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class AgentConfig:
|
|
99
|
+
"""Configuration for the agent behavior."""
|
|
100
|
+
|
|
101
|
+
max_tool_calls: int = field(
|
|
102
|
+
default_factory=lambda: int(_from_settings("max_tool_calls", 999))
|
|
103
|
+
)
|
|
104
|
+
max_context_tokens: int = field(
|
|
105
|
+
default_factory=lambda: int(_from_settings("max_context_tokens", 1000000))
|
|
106
|
+
)
|
|
107
|
+
effective_context_tokens: int = field(
|
|
108
|
+
default_factory=lambda: int(_from_settings("effective_context_tokens", 200000))
|
|
109
|
+
)
|
|
110
|
+
max_message_output_chars: int = field(
|
|
111
|
+
default_factory=lambda: int(_from_settings("max_message_output_chars", 8000))
|
|
112
|
+
)
|
|
113
|
+
workspace_dir: str = field(
|
|
114
|
+
default_factory=lambda: _from_settings("workspace_dir", str(Path.cwd()))
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Extension & sub-agent settings
|
|
118
|
+
extension_dirs: list[str] = field(
|
|
119
|
+
default_factory=lambda: _from_settings("extension_dirs", [])
|
|
120
|
+
)
|
|
121
|
+
max_sub_agents: int = field(
|
|
122
|
+
default_factory=lambda: int(_from_settings("max_sub_agents", 5))
|
|
123
|
+
)
|
|
124
|
+
sub_agent_timeout: float = field(
|
|
125
|
+
default_factory=lambda: float(_from_settings("sub_agent_timeout", 300.0))
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Safety settings
|
|
129
|
+
allowed_commands: list[str] = field(
|
|
130
|
+
default_factory=lambda: [
|
|
131
|
+
"ls", "dir", "cat", "head", "tail", "wc", "find",
|
|
132
|
+
"git", "python", "python3", "node", "npm", "npx",
|
|
133
|
+
"pip", "poetry", "cargo", "go", "rustc", "javac", "java",
|
|
134
|
+
"make", "cmake", "gcc", "g++", "clang", "clang++",
|
|
135
|
+
"tsc", "eslint", "prettier", "pytest", "jest",
|
|
136
|
+
"mypy", "ruff", "black", "isort",
|
|
137
|
+
"echo", "mkdir", "touch", "cp", "mv", "rm",
|
|
138
|
+
"cd", # harmless — cwd is already workspace; cd in compound commands just navigates within one shell invocation
|
|
139
|
+
"grep", "rg", "fd", "tree",
|
|
140
|
+
"docker", "docker-compose",
|
|
141
|
+
"cowsay", "fortune", "date", "whoami", "pwd",
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Blocked commands for safety
|
|
146
|
+
blocked_commands: list[str] = field(
|
|
147
|
+
default_factory=lambda: [
|
|
148
|
+
"rm -rf /",
|
|
149
|
+
"mkfs.",
|
|
150
|
+
"dd if=",
|
|
151
|
+
":(){ :|:& };:", # fork bomb
|
|
152
|
+
"shutdown",
|
|
153
|
+
"reboot",
|
|
154
|
+
"chmod 777 /",
|
|
155
|
+
"> /dev/sda",
|
|
156
|
+
]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass
|
|
161
|
+
class AppConfig:
|
|
162
|
+
"""Top-level application configuration."""
|
|
163
|
+
|
|
164
|
+
llm: LLMConfig = field(default_factory=LLMConfig)
|
|
165
|
+
agent: AgentConfig = field(default_factory=AgentConfig)
|
|
166
|
+
effort: str = field(default_factory=lambda: _from_settings("effort_level", "medium"))
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def load(cls) -> "AppConfig":
|
|
170
|
+
"""Load and validate all configuration (errors logged by caller)."""
|
|
171
|
+
_ensure_env()
|
|
172
|
+
config = cls()
|
|
173
|
+
return config
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Module-level config — initialised lazily via get_config().
|
|
177
|
+
# Using a lazy pattern because AppConfig.load() references _from_settings()
|
|
178
|
+
# which is defined after the dataclass body in this module.
|
|
179
|
+
_config: AppConfig | None = None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def get_config() -> AppConfig:
|
|
183
|
+
"""Return the module-level config singleton (lazy init on first call).
|
|
184
|
+
|
|
185
|
+
After the first call the config is cached. The codebase is
|
|
186
|
+
single-threaded by design so no lock is needed.
|
|
187
|
+
"""
|
|
188
|
+
global _config
|
|
189
|
+
if _config is None:
|
|
190
|
+
_config = AppConfig.load()
|
|
191
|
+
return _config
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ── Single config resolution helper ────────────────────────────────────────
|
|
195
|
+
# All config values come from settings.json. CLI overrides (--model, --api-key)
|
|
196
|
+
# are applied later via _apply_config_overrides() in main.py.
|
|
197
|
+
# Environment variables are intentionally NOT read — settings.json is the
|
|
198
|
+
# single source of truth for predictable, reproducible configuration.
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _from_settings(attr: str, default: Any = "") -> Any:
|
|
202
|
+
"""Read a config value from the Settings singleton.
|
|
203
|
+
|
|
204
|
+
Lazy import to avoid circular dependency with settings.py.
|
|
205
|
+
Always returns *default* when settings are unavailable (fail-safe),
|
|
206
|
+
but logs a warning for non-trivial failures so they don't go unnoticed.
|
|
207
|
+
"""
|
|
208
|
+
try:
|
|
209
|
+
from .settings import get_settings
|
|
210
|
+
except ImportError:
|
|
211
|
+
return default
|
|
212
|
+
try:
|
|
213
|
+
settings = get_settings()
|
|
214
|
+
return getattr(settings, attr, default)
|
|
215
|
+
except AttributeError:
|
|
216
|
+
# settings object exists but attribute is missing — use default
|
|
217
|
+
logger = logging.getLogger(__name__)
|
|
218
|
+
logger.debug("Settings property %r not found, using default %r", attr, default)
|
|
219
|
+
return default
|
|
220
|
+
except Exception:
|
|
221
|
+
logger = logging.getLogger(__name__)
|
|
222
|
+
logger.warning(
|
|
223
|
+
"Failed to read settings.%s — using default %r. "
|
|
224
|
+
"Check your settings.json for corruption.", attr, default, exc_info=True
|
|
225
|
+
)
|
|
226
|
+
return default
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _safe_temperature() -> float:
|
|
230
|
+
"""Read temperature from settings, ensuring it's a numeric type."""
|
|
231
|
+
val = _from_settings("temperature", 0.1)
|
|
232
|
+
return val if isinstance(val, (int, float)) else 0.1
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _settings_api_key() -> str:
|
|
236
|
+
return _from_settings("api_key", "")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _settings_base_url() -> str:
|
|
240
|
+
return _from_settings("api_base_url", "https://api.deepseek.com")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _settings_default_model() -> str:
|
|
244
|
+
return _from_settings("default_model", "deepseek-chat")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _settings_max_output_tokens() -> int:
|
|
248
|
+
return _from_settings("max_output_tokens", 16384)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _settings_thinking_strength() -> str:
|
|
252
|
+
return _from_settings("effort_level", "")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# Use get_config() to obtain the global AppConfig instance.
|
|
256
|
+
# Lazy-loading is handled inside get_config() itself.
|
|
257
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Core agent modules — split from agent.py for maintainability.
|
|
4
|
+
|
|
5
|
+
- events: AgentEvent dataclasses
|
|
6
|
+
- state: AgentState dataclass
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .events import (
|
|
10
|
+
AgentEvent,
|
|
11
|
+
TextDeltaEvent,
|
|
12
|
+
ToolCallEvent,
|
|
13
|
+
ToolResultEvent,
|
|
14
|
+
ToolStreamEvent,
|
|
15
|
+
ThinkingEvent,
|
|
16
|
+
ReasoningEvent,
|
|
17
|
+
SkillChangedEvent,
|
|
18
|
+
ErrorEvent,
|
|
19
|
+
CompleteEvent,
|
|
20
|
+
)
|
|
21
|
+
from .state import AgentState
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"AgentEvent",
|
|
25
|
+
"TextDeltaEvent",
|
|
26
|
+
"ToolCallEvent",
|
|
27
|
+
"ToolResultEvent",
|
|
28
|
+
"ToolStreamEvent",
|
|
29
|
+
"ThinkingEvent",
|
|
30
|
+
"ReasoningEvent",
|
|
31
|
+
"SkillChangedEvent",
|
|
32
|
+
"ErrorEvent",
|
|
33
|
+
"CompleteEvent",
|
|
34
|
+
"AgentState",
|
|
35
|
+
]
|
ata_coder/core/events.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Agent event types — produced during CoderAgent.run() and consumed by UI.
|
|
4
|
+
Extracted from agent.py to keep modules under 500 lines.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from ..tools import ToolResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ── Event types ──────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class AgentEvent:
|
|
17
|
+
"""Base event."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class TextDeltaEvent(AgentEvent):
|
|
23
|
+
text: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ToolCallEvent(AgentEvent):
|
|
28
|
+
tool_name: str
|
|
29
|
+
arguments: dict[str, Any]
|
|
30
|
+
source: str = "builtin" # "builtin" or "mcp"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ToolResultEvent(AgentEvent):
|
|
35
|
+
tool_name: str
|
|
36
|
+
result: ToolResult
|
|
37
|
+
source: str = "builtin"
|
|
38
|
+
arguments: dict[str, Any] = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ThinkingEvent(AgentEvent):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ReasoningEvent(AgentEvent):
|
|
48
|
+
"""The model is thinking/reasoning (DeepSeek R1/v4 thinking mode)."""
|
|
49
|
+
text: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class SkillChangedEvent(AgentEvent):
|
|
54
|
+
skill_name: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class ErrorEvent(AgentEvent):
|
|
59
|
+
error: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class ToolStreamEvent(AgentEvent):
|
|
64
|
+
"""Real-time streaming output chunk from a running tool (e.g. run_shell)."""
|
|
65
|
+
tool_name: str
|
|
66
|
+
chunk: str # incremental output text
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class CompleteEvent(AgentEvent):
|
|
71
|
+
total_tool_calls: int
|
|
72
|
+
total_time: float
|
|
73
|
+
estimated_tokens: int = 0 # current conversation window size
|
ata_coder/core/queue.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Async event queue for agent→UI communication — part of the core infrastructure.
|
|
3
|
+
|
|
4
|
+
Both agent and UI run as asyncio tasks on the same event loop.
|
|
5
|
+
asyncio.Queue provides coroutine-safe FIFO semantics without locks.
|
|
6
|
+
"""
|
|
7
|
+
import asyncio
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
logger = __import__("logging").getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
__all__ = ["EventQueue"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EventQueue:
|
|
16
|
+
"""Async event queue wrapping asyncio.Queue.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
eq = EventQueue()
|
|
20
|
+
# Agent task:
|
|
21
|
+
await eq.put(TextDeltaEvent("hello"))
|
|
22
|
+
# UI task:
|
|
23
|
+
async for event in eq.drain():
|
|
24
|
+
ui.on_event(event)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, maxsize: int = 0):
|
|
28
|
+
self._queue: asyncio.Queue = asyncio.Queue(maxsize=maxsize)
|
|
29
|
+
self._total_put = 0
|
|
30
|
+
self._total_get = 0
|
|
31
|
+
|
|
32
|
+
async def put(self, event: Any) -> None:
|
|
33
|
+
"""Enqueue an event (coroutine-safe, called from agent task)."""
|
|
34
|
+
await self._queue.put(event)
|
|
35
|
+
self._total_put += 1
|
|
36
|
+
|
|
37
|
+
def put_nowait(self, event: Any) -> None:
|
|
38
|
+
"""Enqueue an event without blocking."""
|
|
39
|
+
self._queue.put_nowait(event)
|
|
40
|
+
self._total_put += 1
|
|
41
|
+
|
|
42
|
+
async def get(self, timeout: Optional[float] = None) -> Optional[Any]:
|
|
43
|
+
"""Get one event, blocking with optional timeout."""
|
|
44
|
+
try:
|
|
45
|
+
if timeout:
|
|
46
|
+
event = await asyncio.wait_for(self._queue.get(), timeout=timeout)
|
|
47
|
+
else:
|
|
48
|
+
event = await self._queue.get()
|
|
49
|
+
self._total_get += 1
|
|
50
|
+
return event
|
|
51
|
+
except asyncio.TimeoutError:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
async def drain(self) -> list[Any]:
|
|
55
|
+
"""Get ALL pending events without blocking."""
|
|
56
|
+
events: list[Any] = []
|
|
57
|
+
while not self._queue.empty():
|
|
58
|
+
try:
|
|
59
|
+
events.append(self._queue.get_nowait())
|
|
60
|
+
except asyncio.QueueEmpty:
|
|
61
|
+
break
|
|
62
|
+
count = len(events)
|
|
63
|
+
self._total_get += count
|
|
64
|
+
return events
|
|
65
|
+
|
|
66
|
+
def count(self) -> int:
|
|
67
|
+
"""Return number of pending events (exact — asyncio.Queue.qsize is precise)."""
|
|
68
|
+
return self._queue.qsize()
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def total_put(self) -> int:
|
|
72
|
+
return self._total_put
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def total_get(self) -> int:
|
|
76
|
+
return self._total_get
|
|
77
|
+
|
|
78
|
+
async def clear(self) -> None:
|
|
79
|
+
"""Discard all pending events."""
|
|
80
|
+
while not self._queue.empty():
|
|
81
|
+
try:
|
|
82
|
+
self._queue.get_nowait()
|
|
83
|
+
except asyncio.QueueEmpty:
|
|
84
|
+
break
|
|
85
|
+
self._total_get = self._total_put
|
ata_coder/core/state.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Agent state — holds conversation messages and metrics during a run.
|
|
4
|
+
Extracted from agent.py.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
|
|
9
|
+
from ..types import Message
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class AgentState:
|
|
14
|
+
"""Mutable state tracked across a single agent run."""
|
|
15
|
+
messages: list[Message] = field(default_factory=list)
|
|
16
|
+
tool_call_count: int = 0
|
|
17
|
+
start_time: float = 0.0
|