regcode 0.2.0__py3-none-any.whl → 0.4.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.
- regcode/__init__.py +1 -1
- regcode/cli.py +6 -1
- regcode/config.py +49 -17
- regcode/main.py +45 -0
- regcode/tools/__init__.py +1 -1
- regcode/tools/builtins.py +460 -38
- regcode/tools/web_crawl.py +177 -0
- regcode/tracing.py +107 -0
- regcode/tui.py +607 -302
- regcode-0.4.0.dist-info/METADATA +237 -0
- regcode-0.4.0.dist-info/RECORD +20 -0
- regcode-0.2.0.dist-info/METADATA +0 -164
- regcode-0.2.0.dist-info/RECORD +0 -18
- {regcode-0.2.0.dist-info → regcode-0.4.0.dist-info}/WHEEL +0 -0
- {regcode-0.2.0.dist-info → regcode-0.4.0.dist-info}/entry_points.txt +0 -0
regcode/__init__.py
CHANGED
regcode/cli.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
import click
|
|
4
5
|
import yaml
|
|
@@ -42,7 +43,10 @@ def cli(ctx):
|
|
|
42
43
|
)
|
|
43
44
|
def chat(model, config_path, stream, full, no_color):
|
|
44
45
|
"""Chat with the agent using a Textual TUI with sticky bottom bar."""
|
|
45
|
-
|
|
46
|
+
# Pass None if the file doesn't exist so Config.load falls back
|
|
47
|
+
# to ~/.regcode/config.yaml (matching the TUI's expected behaviour).
|
|
48
|
+
_effective_path = config_path if Path(config_path).exists() else None
|
|
49
|
+
config = regcode.Config.load(_effective_path)
|
|
46
50
|
if model:
|
|
47
51
|
config.provider.model = model
|
|
48
52
|
if full:
|
|
@@ -57,6 +61,7 @@ def chat(model, config_path, stream, full, no_color):
|
|
|
57
61
|
status_callback=ui.handle_status,
|
|
58
62
|
result_callback=ui.handle_result,
|
|
59
63
|
context_callback=ui.set_context_progress,
|
|
64
|
+
cost_callback=ui.handle_cost,
|
|
60
65
|
)
|
|
61
66
|
|
|
62
67
|
# Attach agent to the Textual app
|
regcode/config.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from textwrap import dedent
|
|
@@ -40,9 +41,18 @@ default_system_prompt = dedent(
|
|
|
40
41
|
"""
|
|
41
42
|
)
|
|
42
43
|
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
43
45
|
|
|
44
46
|
class AgentConfig(BaseModel):
|
|
45
|
-
|
|
47
|
+
"""Configuration specific to the agent's behavior (not provider params).
|
|
48
|
+
|
|
49
|
+
Model, temperature, max_tokens belong in ProviderConfig where
|
|
50
|
+
litellm.completion expects them. AgentConfig only holds compaction
|
|
51
|
+
and context-window tuning — passing model params through this object
|
|
52
|
+
caused conflicts with different providers.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
model_config = ConfigDict(extra="forbid")
|
|
46
56
|
context_window: int = 128000
|
|
47
57
|
compaction_threshold: float = 0.8 # Compact when context > 80% of window
|
|
48
58
|
enable_compaction: bool = True
|
|
@@ -79,11 +89,30 @@ class SandboxConfig(BaseModel):
|
|
|
79
89
|
type_check: bool = True
|
|
80
90
|
allowed_imports: list[str] = []
|
|
81
91
|
|
|
92
|
+
class TracingConfig(BaseModel):
|
|
93
|
+
"""Configuration for tracing and logging."""
|
|
94
|
+
|
|
95
|
+
model_config = ConfigDict(extra="allow")
|
|
96
|
+
enable_tracing: bool = False
|
|
97
|
+
mlflow_url: str | None = None
|
|
98
|
+
mlflow_experiment_name: str | None = None
|
|
99
|
+
mlflow_async: bool = True
|
|
100
|
+
mlflow_requires_auth: bool = True
|
|
101
|
+
mlflow_username: str | None = None
|
|
102
|
+
mlflow_password: str | None = None
|
|
103
|
+
|
|
104
|
+
def tracing_enabled(self) -> bool:
|
|
105
|
+
"""Check if tracing is fully configured and enabled."""
|
|
106
|
+
return (
|
|
107
|
+
self.enable_tracing
|
|
108
|
+
and self.mlflow_url is not None
|
|
109
|
+
)
|
|
82
110
|
|
|
83
111
|
class Config(BaseModel):
|
|
84
112
|
model_config = ConfigDict(extra="allow")
|
|
85
113
|
|
|
86
114
|
agent: AgentConfig = AgentConfig()
|
|
115
|
+
tracing: TracingConfig = TracingConfig()
|
|
87
116
|
provider: ProviderConfig = ProviderConfig()
|
|
88
117
|
system_prompt: str = default_system_prompt
|
|
89
118
|
tools: dict = {}
|
|
@@ -94,15 +123,29 @@ class Config(BaseModel):
|
|
|
94
123
|
@classmethod
|
|
95
124
|
def load(cls, path: str | None = None) -> "Config":
|
|
96
125
|
# get default home dir for user first
|
|
126
|
+
_path = None
|
|
97
127
|
if path is None:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
128
|
+
possible_paths = [
|
|
129
|
+
Path("./config.yaml"), Path.home() / ".regcode" / "config.yaml"
|
|
130
|
+
]
|
|
131
|
+
for p in possible_paths:
|
|
132
|
+
if p.exists():
|
|
133
|
+
logger.info(f"Loading from config at {p}")
|
|
134
|
+
_path = p
|
|
135
|
+
break
|
|
136
|
+
if _path is None:
|
|
137
|
+
logger.warning(
|
|
138
|
+
"No config file found, using default config, "
|
|
139
|
+
"Use `regcode configure` to configure."
|
|
140
|
+
)
|
|
141
|
+
return cls()
|
|
103
142
|
else:
|
|
104
143
|
_path = Path(path)
|
|
105
144
|
if not _path.exists():
|
|
145
|
+
logger.warning(
|
|
146
|
+
f"Config file {path} not found, using default config, "
|
|
147
|
+
"Use `regcode configure` to configure."
|
|
148
|
+
)
|
|
106
149
|
return cls()
|
|
107
150
|
with open(_path) as f:
|
|
108
151
|
data = yaml.safe_load(f)
|
|
@@ -129,17 +172,6 @@ class Config(BaseModel):
|
|
|
129
172
|
result = self.provider.model_dump()
|
|
130
173
|
if extra_kwargs:
|
|
131
174
|
result.update(extra_kwargs)
|
|
132
|
-
# Agent-level config overrides provider defaults for known keys
|
|
133
|
-
if self.agent.context_window is not None:
|
|
134
|
-
result["context_window"] = self.agent.context_window
|
|
135
|
-
if hasattr(self.agent, "compaction_threshold"):
|
|
136
|
-
result["compaction_threshold"] = self.agent.compaction_threshold
|
|
137
|
-
if hasattr(self.agent, "enable_compaction"):
|
|
138
|
-
result["enable_compaction"] = self.agent.enable_compaction
|
|
139
|
-
# Apply agent-level extra fields (e.g. model, max_tokens, temperature
|
|
140
|
-
# from YAML agent section) as overrides over provider defaults
|
|
141
|
-
if hasattr(self.agent, "model_extra") and self.agent.model_extra:
|
|
142
|
-
result.update(self.agent.model_extra)
|
|
143
175
|
return result
|
|
144
176
|
|
|
145
177
|
|
regcode/main.py
CHANGED
|
@@ -19,7 +19,9 @@ from regcode.tools.base import ToolResult
|
|
|
19
19
|
from regcode.tools.builtins import create_default_tools
|
|
20
20
|
from regcode.tools.registry import ToolRegistry
|
|
21
21
|
from regcode.tools.review_notes import AddReviewNoteTool, ReadReviewNotesTool
|
|
22
|
+
from regcode.tracing import setup_mlflow_tracing
|
|
22
23
|
|
|
24
|
+
setup_mlflow_tracing()
|
|
23
25
|
|
|
24
26
|
class _SafeEncoder(json.JSONEncoder):
|
|
25
27
|
def default(self, o):
|
|
@@ -33,6 +35,7 @@ class AgentStatus(Enum):
|
|
|
33
35
|
|
|
34
36
|
TOOL_CALL = "tool_call"
|
|
35
37
|
TOOL_RESULT = "tool_result"
|
|
38
|
+
COMPACTION_START = "compaction_start"
|
|
36
39
|
IDLE = "idle"
|
|
37
40
|
BUDGET_EXHAUSTED = "budget_exhausted"
|
|
38
41
|
|
|
@@ -63,6 +66,7 @@ class Agent:
|
|
|
63
66
|
)
|
|
64
67
|
| None = None,
|
|
65
68
|
context_callback: Callable[[int, int], None] | None = None,
|
|
69
|
+
cost_callback: Callable[[float, int | None], None] | None = None,
|
|
66
70
|
) -> None:
|
|
67
71
|
self.config = config or Config.load()
|
|
68
72
|
self.system_prompt = self.config.system_prompt
|
|
@@ -71,10 +75,13 @@ class Agent:
|
|
|
71
75
|
self._status_callback = status_callback
|
|
72
76
|
self._result_callback = result_callback
|
|
73
77
|
self._context_callback = context_callback
|
|
78
|
+
self._cost_callback = cost_callback
|
|
74
79
|
self._allowed_dir = directory
|
|
75
80
|
self._tool_budget = self.config.tool_budget
|
|
76
81
|
self._tool_budget_remaining = self._tool_budget
|
|
77
82
|
self._tool_budget_exhausted = False
|
|
83
|
+
self._cumulative_cost: float = 0.0
|
|
84
|
+
self._total_tokens: int = 0
|
|
78
85
|
|
|
79
86
|
# Context window manager
|
|
80
87
|
self.context_manager = ConversationManager()
|
|
@@ -212,6 +219,8 @@ class Agent:
|
|
|
212
219
|
self.config.agent.enable_compaction
|
|
213
220
|
and self.context_manager.should_compact()
|
|
214
221
|
):
|
|
222
|
+
if self._status_callback is not None:
|
|
223
|
+
self._status_callback(AgentStatus.COMPACTION_START, None)
|
|
215
224
|
self.context_manager.compact()
|
|
216
225
|
messages = self.context_manager.get_messages()
|
|
217
226
|
|
|
@@ -249,6 +258,9 @@ class Agent:
|
|
|
249
258
|
|
|
250
259
|
response = litellm.completion(**completion_kwargs)
|
|
251
260
|
|
|
261
|
+
# Track cost and tokens from this response
|
|
262
|
+
self._track_response_cost(response)
|
|
263
|
+
|
|
252
264
|
choice = response.choices[0] # type: ignore
|
|
253
265
|
message_obj = choice.message
|
|
254
266
|
response_message = {
|
|
@@ -510,6 +522,9 @@ class Agent:
|
|
|
510
522
|
|
|
511
523
|
response = litellm.completion(**completion_kwargs)
|
|
512
524
|
|
|
525
|
+
# Track cost and tokens from this response
|
|
526
|
+
self._track_response_cost(response)
|
|
527
|
+
|
|
513
528
|
# Stream the response — accumulate text and tool_calls
|
|
514
529
|
# across ALL chunks, not just the last one.
|
|
515
530
|
full_text = ""
|
|
@@ -750,6 +765,36 @@ class Agent:
|
|
|
750
765
|
except Exception:
|
|
751
766
|
pass
|
|
752
767
|
|
|
768
|
+
def _track_response_cost(self, response) -> None:
|
|
769
|
+
"""Track cost and token usage from a litellm completion response."""
|
|
770
|
+
if self._cost_callback is None:
|
|
771
|
+
return
|
|
772
|
+
try:
|
|
773
|
+
# Try to get cost from _hidden_params first
|
|
774
|
+
cost = 0.0
|
|
775
|
+
if hasattr(response, "_hidden_params") and response._hidden_params:
|
|
776
|
+
cost = response._hidden_params.get("response_cost", 0.0) or 0.0
|
|
777
|
+
# Fallback: compute from usage if available
|
|
778
|
+
if cost == 0.0 and hasattr(response, "usage") and response.usage:
|
|
779
|
+
usage = response.usage
|
|
780
|
+
prompt_tokens = getattr(usage, "prompt_tokens", 0) or 0
|
|
781
|
+
completion_tokens = getattr(usage, "completion_tokens", 0) or 0
|
|
782
|
+
total_tokens = getattr(usage, "total_tokens", 0) or 0
|
|
783
|
+
self._total_tokens += total_tokens
|
|
784
|
+
# Use litellm's cost calculator for the current model
|
|
785
|
+
try:
|
|
786
|
+
cost = litellm.completion_cost(
|
|
787
|
+
model=self.config.provider.model,
|
|
788
|
+
prompt_tokens=prompt_tokens,
|
|
789
|
+
completion_tokens=completion_tokens,
|
|
790
|
+
)
|
|
791
|
+
except (KeyError, Exception):
|
|
792
|
+
cost = 0.0
|
|
793
|
+
self._cumulative_cost += cost
|
|
794
|
+
self._cost_callback(self._cumulative_cost, self._total_tokens)
|
|
795
|
+
except Exception:
|
|
796
|
+
pass
|
|
797
|
+
|
|
753
798
|
def reset(self) -> None:
|
|
754
799
|
"""Clear conversation history."""
|
|
755
800
|
self._history = []
|
regcode/tools/__init__.py
CHANGED