agentcode-cli 1.2.2__tar.gz → 1.3.1__tar.gz
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.
- {agentcode_cli-1.2.2/agentcode_cli.egg-info → agentcode_cli-1.3.1}/PKG-INFO +1 -1
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/agent.py +17 -12
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1/agentcode_cli.egg-info}/PKG-INFO +1 -1
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/pyproject.toml +1 -1
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/router.py +1 -12
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/server.py +18 -12
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/xml_tool_parser.py +78 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/LICENSE +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/README.md +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/agentcode_cli.egg-info/SOURCES.txt +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/agentcode_cli.egg-info/dependency_links.txt +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/agentcode_cli.egg-info/entry_points.txt +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/agentcode_cli.egg-info/requires.txt +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/agentcode_cli.egg-info/top_level.txt +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/cli.py +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/mcp_client.py +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/settings.py +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/setup.cfg +0 -0
- {agentcode_cli-1.2.2 → agentcode_cli-1.3.1}/tools.py +0 -0
|
@@ -18,7 +18,10 @@ import litellm
|
|
|
18
18
|
from rich.console import Console
|
|
19
19
|
from rich.markdown import Markdown
|
|
20
20
|
|
|
21
|
-
from xml_tool_parser import
|
|
21
|
+
from xml_tool_parser import (
|
|
22
|
+
looks_like_xml_tool_call, parse_xml_tool_calls, strip_think,
|
|
23
|
+
looks_like_json_tool_call, parse_json_tool_calls,
|
|
24
|
+
)
|
|
22
25
|
|
|
23
26
|
from tools import TOOL_DEFINITIONS, execute_tool
|
|
24
27
|
from router import ModelRouter, display_routing_decision
|
|
@@ -348,17 +351,19 @@ def run_agent_loop(
|
|
|
348
351
|
cost,
|
|
349
352
|
)
|
|
350
353
|
|
|
351
|
-
# Open-weight fine-tunes
|
|
352
|
-
#
|
|
353
|
-
# treating this as a pure text turn.
|
|
354
|
-
if not tool_calls_accum
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
full_text =
|
|
354
|
+
# Open-weight fine-tunes emit tool calls in the text response: the 27B
|
|
355
|
+
# uses XML (<function=...>), the 3B uses bare JSON. Detect and convert
|
|
356
|
+
# before treating this as a pure text turn.
|
|
357
|
+
if not tool_calls_accum:
|
|
358
|
+
if looks_like_xml_tool_call(full_text):
|
|
359
|
+
cleaned, parsed = parse_xml_tool_calls(full_text)
|
|
360
|
+
elif looks_like_json_tool_call(full_text):
|
|
361
|
+
cleaned, parsed = parse_json_tool_calls(full_text)
|
|
362
|
+
else:
|
|
363
|
+
cleaned, parsed = strip_think(full_text), []
|
|
364
|
+
full_text = cleaned
|
|
365
|
+
for i, tc in enumerate(parsed):
|
|
366
|
+
tool_calls_accum[i] = tc
|
|
362
367
|
|
|
363
368
|
# Pure text response — done
|
|
364
369
|
if not tool_calls_accum:
|
|
@@ -47,20 +47,11 @@ GEMINI_TIERS = [
|
|
|
47
47
|
ModelTier("gemini/gemini-2.5-pro", "heavy", "Gemini 2.5 Pro", 1.25, 10.00),
|
|
48
48
|
]
|
|
49
49
|
|
|
50
|
-
# Azure deployment names are user-defined, so tiers use common defaults.
|
|
51
|
-
# Users can override via settings.json model.light/medium/heavy.
|
|
52
|
-
AZURE_TIERS = [
|
|
53
|
-
ModelTier("azure/gpt-4o-mini", "light", "Azure GPT-4o Mini", 0.15, 0.60),
|
|
54
|
-
ModelTier("azure/gpt-4o", "medium", "Azure GPT-4o", 2.50, 10.00),
|
|
55
|
-
ModelTier("azure/gpt-4o", "heavy", "Azure GPT-4o", 2.50, 10.00),
|
|
56
|
-
]
|
|
57
|
-
|
|
58
50
|
# Provider configs keyed by prefix
|
|
59
51
|
PROVIDER_TIERS = {
|
|
60
52
|
"anthropic": ANTHROPIC_TIERS,
|
|
61
53
|
"openai": OPENAI_TIERS,
|
|
62
54
|
"gemini": GEMINI_TIERS,
|
|
63
|
-
"azure": AZURE_TIERS,
|
|
64
55
|
}
|
|
65
56
|
|
|
66
57
|
|
|
@@ -212,9 +203,7 @@ class ModelRouter:
|
|
|
212
203
|
def detect_provider(self, model_string: str) -> str:
|
|
213
204
|
"""Detect provider from a model string."""
|
|
214
205
|
m = model_string.lower()
|
|
215
|
-
if m
|
|
216
|
-
return "azure"
|
|
217
|
-
elif "claude" in m or "anthropic" in m:
|
|
206
|
+
if "claude" in m or "anthropic" in m:
|
|
218
207
|
return "anthropic"
|
|
219
208
|
elif "gpt" in m or "openai" in m or "o1" in m or "o3" in m:
|
|
220
209
|
return "openai"
|
|
@@ -12,7 +12,10 @@ from agent import (
|
|
|
12
12
|
build_system_prompt, load_project_config, load_hooks,
|
|
13
13
|
_run_subagents, _run_hook, _is_denied, _get_permission,
|
|
14
14
|
)
|
|
15
|
-
from xml_tool_parser import
|
|
15
|
+
from xml_tool_parser import (
|
|
16
|
+
looks_like_xml_tool_call, parse_xml_tool_calls, strip_think,
|
|
17
|
+
looks_like_json_tool_call, parse_json_tool_calls,
|
|
18
|
+
)
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
# ── Wire protocol ─────────────────────────────────────────────────────────────
|
|
@@ -107,17 +110,20 @@ def _server_turn(
|
|
|
107
110
|
if tc.function.arguments:
|
|
108
111
|
tool_calls_accum[idx]["arguments"] += tc.function.arguments
|
|
109
112
|
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
113
|
+
# Open-weight fine-tunes emit tool calls in the text response instead of
|
|
114
|
+
# litellm's structured tool_calls field. The 27B uses XML
|
|
115
|
+
# (<function=...>), the 3B uses bare JSON ({"name":...,"arguments":...}).
|
|
116
|
+
# Detect and convert both.
|
|
117
|
+
if not tool_calls_accum:
|
|
118
|
+
if looks_like_xml_tool_call(full_text):
|
|
119
|
+
cleaned, parsed = parse_xml_tool_calls(full_text)
|
|
120
|
+
elif looks_like_json_tool_call(full_text):
|
|
121
|
+
cleaned, parsed = parse_json_tool_calls(full_text)
|
|
122
|
+
else:
|
|
123
|
+
cleaned, parsed = strip_think(full_text), []
|
|
124
|
+
full_text = cleaned
|
|
125
|
+
for i, tc in enumerate(parsed):
|
|
126
|
+
tool_calls_accum[i] = tc
|
|
121
127
|
|
|
122
128
|
if router and usage:
|
|
123
129
|
try:
|
|
@@ -85,3 +85,81 @@ def parse_xml_tool_calls(text: str) -> tuple[str, list[dict]]:
|
|
|
85
85
|
|
|
86
86
|
cleaned = _TOOL_CALL_RE.sub("", cleaned).strip()
|
|
87
87
|
return cleaned, tool_calls
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── JSON-style tool calls (e.g. the AgentCode 3B) ─────────────────────────────
|
|
91
|
+
# Smaller fine-tunes often emit a bare JSON object instead of the XML form:
|
|
92
|
+
# {"name": "git_status", "arguments": {}}
|
|
93
|
+
# optionally wrapped in <tool_call>...</tool_call>. litellm doesn't pick these
|
|
94
|
+
# up as structured tool_calls, so we detect and convert them too.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def looks_like_json_tool_call(text: str) -> bool:
|
|
98
|
+
"""Cheap check: a JSON object mentioning both name and arguments."""
|
|
99
|
+
return '"name"' in text and '"arguments"' in text
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _extract_json_objects(text: str) -> list[str]:
|
|
103
|
+
"""Return top-level {...} substrings via brace matching (ignores braces in strings)."""
|
|
104
|
+
objs: list[str] = []
|
|
105
|
+
depth = 0
|
|
106
|
+
start = None
|
|
107
|
+
in_str = False
|
|
108
|
+
escape = False
|
|
109
|
+
for i, ch in enumerate(text):
|
|
110
|
+
if in_str:
|
|
111
|
+
if escape:
|
|
112
|
+
escape = False
|
|
113
|
+
elif ch == "\\":
|
|
114
|
+
escape = True
|
|
115
|
+
elif ch == '"':
|
|
116
|
+
in_str = False
|
|
117
|
+
continue
|
|
118
|
+
if ch == '"':
|
|
119
|
+
in_str = True
|
|
120
|
+
elif ch == "{":
|
|
121
|
+
if depth == 0:
|
|
122
|
+
start = i
|
|
123
|
+
depth += 1
|
|
124
|
+
elif ch == "}":
|
|
125
|
+
if depth > 0:
|
|
126
|
+
depth -= 1
|
|
127
|
+
if depth == 0 and start is not None:
|
|
128
|
+
objs.append(text[start:i + 1])
|
|
129
|
+
start = None
|
|
130
|
+
return objs
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def parse_json_tool_calls(text: str) -> tuple[str, list[dict]]:
|
|
134
|
+
"""
|
|
135
|
+
Extract bare-JSON tool calls and return (cleaned_text, tool_calls).
|
|
136
|
+
|
|
137
|
+
Handles {"name": ..., "arguments": {...}} objects, with or without
|
|
138
|
+
surrounding <tool_call> tags. Returns the same accumulator shape as
|
|
139
|
+
parse_xml_tool_calls.
|
|
140
|
+
"""
|
|
141
|
+
cleaned = strip_think(text)
|
|
142
|
+
inner = cleaned.replace("<tool_call>", "").replace("</tool_call>", "")
|
|
143
|
+
|
|
144
|
+
tool_calls: list[dict] = []
|
|
145
|
+
matched_spans: list[str] = []
|
|
146
|
+
for cand in _extract_json_objects(inner):
|
|
147
|
+
try:
|
|
148
|
+
obj = json.loads(cand)
|
|
149
|
+
except (json.JSONDecodeError, ValueError):
|
|
150
|
+
continue
|
|
151
|
+
if isinstance(obj, dict) and "name" in obj and "arguments" in obj:
|
|
152
|
+
args = obj["arguments"]
|
|
153
|
+
args_str = args if isinstance(args, str) else json.dumps(args)
|
|
154
|
+
tool_calls.append({
|
|
155
|
+
"id": f"call_{uuid.uuid4().hex[:8]}",
|
|
156
|
+
"name": obj["name"],
|
|
157
|
+
"arguments": args_str,
|
|
158
|
+
})
|
|
159
|
+
matched_spans.append(cand)
|
|
160
|
+
|
|
161
|
+
if tool_calls:
|
|
162
|
+
for span in matched_spans:
|
|
163
|
+
cleaned = cleaned.replace(span, "")
|
|
164
|
+
cleaned = cleaned.replace("<tool_call>", "").replace("</tool_call>", "").strip()
|
|
165
|
+
return cleaned, tool_calls
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|