agentcode-cli 1.2.0__py3-none-any.whl → 1.2.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.
- agent.py +14 -0
- {agentcode_cli-1.2.0.dist-info → agentcode_cli-1.2.2.dist-info}/METADATA +1 -1
- agentcode_cli-1.2.2.dist-info/RECORD +14 -0
- {agentcode_cli-1.2.0.dist-info → agentcode_cli-1.2.2.dist-info}/top_level.txt +1 -0
- cli.py +2 -0
- server.py +13 -0
- xml_tool_parser.py +87 -0
- agentcode_cli-1.2.0.dist-info/RECORD +0 -13
- {agentcode_cli-1.2.0.dist-info → agentcode_cli-1.2.2.dist-info}/WHEEL +0 -0
- {agentcode_cli-1.2.0.dist-info → agentcode_cli-1.2.2.dist-info}/entry_points.txt +0 -0
- {agentcode_cli-1.2.0.dist-info → agentcode_cli-1.2.2.dist-info}/licenses/LICENSE +0 -0
agent.py
CHANGED
|
@@ -18,6 +18,8 @@ import litellm
|
|
|
18
18
|
from rich.console import Console
|
|
19
19
|
from rich.markdown import Markdown
|
|
20
20
|
|
|
21
|
+
from xml_tool_parser import looks_like_xml_tool_call, parse_xml_tool_calls, strip_think
|
|
22
|
+
|
|
21
23
|
from tools import TOOL_DEFINITIONS, execute_tool
|
|
22
24
|
from router import ModelRouter, display_routing_decision
|
|
23
25
|
from mcp_client import MCPManager
|
|
@@ -346,6 +348,18 @@ def run_agent_loop(
|
|
|
346
348
|
cost,
|
|
347
349
|
)
|
|
348
350
|
|
|
351
|
+
# Open-weight fine-tunes (e.g. Vigp17/agentcode-27b) may emit tool
|
|
352
|
+
# calls as inline XML in the text response. Detect and convert before
|
|
353
|
+
# treating this as a pure text turn.
|
|
354
|
+
if not tool_calls_accum and looks_like_xml_tool_call(full_text):
|
|
355
|
+
cleaned, xml_calls = parse_xml_tool_calls(full_text)
|
|
356
|
+
if xml_calls:
|
|
357
|
+
full_text = cleaned
|
|
358
|
+
for i, tc in enumerate(xml_calls):
|
|
359
|
+
tool_calls_accum[i] = tc
|
|
360
|
+
elif not tool_calls_accum:
|
|
361
|
+
full_text = strip_think(full_text)
|
|
362
|
+
|
|
349
363
|
# Pure text response — done
|
|
350
364
|
if not tool_calls_accum:
|
|
351
365
|
conversation.messages.append({"role": "assistant", "content": full_text})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
agent.py,sha256=lBZ8vsduMGt30E7VsPKbByA96Hum8B5pEIE_B7-QlrY,17784
|
|
2
|
+
cli.py,sha256=FhaY-UCKQdmAcKeLw6Cvd2kDowh6yRlvmaKGfGECk80,25074
|
|
3
|
+
mcp_client.py,sha256=2PviTqJtXM4UC_fsYLbAOAfWJvayWy7Q8VOQIIsDiqQ,6710
|
|
4
|
+
router.py,sha256=IjomIOaLVmGHQG9rNgc3xNpGhr82rCnn5h8X-QvZhRU,11573
|
|
5
|
+
server.py,sha256=KZhq4rYsKlRBth1g2fkqYHdCelJR4DD4-ZugC4jAgtw,10334
|
|
6
|
+
settings.py,sha256=Qjc3tiVbT1cqIrnQW6m2UG8Xsvqsxl9qXPMTueqwn50,6903
|
|
7
|
+
tools.py,sha256=MBYy0OeSIZjqyGOezaMvZ6AMW3WRDESU-2u48sifd0Q,24741
|
|
8
|
+
xml_tool_parser.py,sha256=T62DigRj8VCH30dq5AnxV8hsJXf0f2vyubg5F4nzDDk,3138
|
|
9
|
+
agentcode_cli-1.2.2.dist-info/licenses/LICENSE,sha256=BqTzyKKaSaVQoumXzhYCj1UgOSPCgvn-sxV6BIuT558,1068
|
|
10
|
+
agentcode_cli-1.2.2.dist-info/METADATA,sha256=oRCW6r2goUhjL-nrryIi0i0Su-2A45r_128KbameN3Y,12709
|
|
11
|
+
agentcode_cli-1.2.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
+
agentcode_cli-1.2.2.dist-info/entry_points.txt,sha256=xP_zeySufuVhL5v10_EqCooKRSxGYB8QuRVNUj4_m1E,39
|
|
13
|
+
agentcode_cli-1.2.2.dist-info/top_level.txt,sha256=684sAEdroiDabB0I7Zud0vYWXcDbCl2WK0LjTQzxiUk,66
|
|
14
|
+
agentcode_cli-1.2.2.dist-info/RECORD,,
|
cli.py
CHANGED
|
@@ -151,6 +151,8 @@ def handle_slash_command(
|
|
|
151
151
|
config.model = arg
|
|
152
152
|
if config.router:
|
|
153
153
|
config.router.enabled = False
|
|
154
|
+
config.router.default_model = arg
|
|
155
|
+
config.router.provider = config.router.detect_provider(arg)
|
|
154
156
|
console.print(f"[success]✓ Switched to model: {arg} (routing disabled)[/success]")
|
|
155
157
|
else:
|
|
156
158
|
console.print(f"[info]Current model: {config.model}[/info]")
|
server.py
CHANGED
|
@@ -12,6 +12,7 @@ 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 looks_like_xml_tool_call, parse_xml_tool_calls, strip_think
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
# ── Wire protocol ─────────────────────────────────────────────────────────────
|
|
@@ -106,6 +107,18 @@ def _server_turn(
|
|
|
106
107
|
if tc.function.arguments:
|
|
107
108
|
tool_calls_accum[idx]["arguments"] += tc.function.arguments
|
|
108
109
|
|
|
110
|
+
# Some open-weight fine-tunes (e.g. Vigp17/agentcode-27b) emit tool
|
|
111
|
+
# calls as inline XML in the text response instead of using litellm's
|
|
112
|
+
# structured tool_calls field. Detect and convert.
|
|
113
|
+
if not tool_calls_accum and looks_like_xml_tool_call(full_text):
|
|
114
|
+
cleaned, xml_calls = parse_xml_tool_calls(full_text)
|
|
115
|
+
if xml_calls:
|
|
116
|
+
full_text = cleaned
|
|
117
|
+
for i, tc in enumerate(xml_calls):
|
|
118
|
+
tool_calls_accum[i] = tc
|
|
119
|
+
elif not tool_calls_accum:
|
|
120
|
+
full_text = strip_think(full_text)
|
|
121
|
+
|
|
109
122
|
if router and usage:
|
|
110
123
|
try:
|
|
111
124
|
cost = litellm.completion_cost(
|
xml_tool_parser.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parse Hermes/Qwen-3-style XML tool calls into OpenAI-compatible tool_calls.
|
|
3
|
+
|
|
4
|
+
Some fine-tuned open-weight models (e.g. Vigp17/agentcode-27b) emit tool calls
|
|
5
|
+
as inline XML in the assistant's text response instead of using the structured
|
|
6
|
+
`tool_calls` field that litellm/OpenAI clients expect. This module detects that
|
|
7
|
+
shape and converts it.
|
|
8
|
+
|
|
9
|
+
Input shape we handle:
|
|
10
|
+
|
|
11
|
+
<think>...optional reasoning...</think>
|
|
12
|
+
|
|
13
|
+
<tool_call>
|
|
14
|
+
<function=tool_name>
|
|
15
|
+
<parameter=key1>value1</parameter>
|
|
16
|
+
<parameter=key2>value2</parameter>
|
|
17
|
+
</function>
|
|
18
|
+
</tool_call>
|
|
19
|
+
|
|
20
|
+
Output: the same payload as litellm's structured tool_calls — a list of
|
|
21
|
+
{"id", "name", "arguments"} dicts plus the cleaned text (think blocks stripped,
|
|
22
|
+
tool_call blocks removed).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import re
|
|
27
|
+
import uuid
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_THINK_RE = re.compile(r"<think>.*?</think>\s*", re.DOTALL)
|
|
31
|
+
# Qwen 3 / similar templates often consume the opening <think> tag during
|
|
32
|
+
# prompt construction but leave the </think>. In that case everything from
|
|
33
|
+
# the start of the response up to and including the first </think> is
|
|
34
|
+
# reasoning that should be stripped.
|
|
35
|
+
_OPEN_THINK_MISSING_RE = re.compile(r"\A.*?</think>\s*", re.DOTALL)
|
|
36
|
+
_TOOL_CALL_RE = re.compile(r"<tool_call>(.*?)</tool_call>", re.DOTALL)
|
|
37
|
+
_FUNCTION_RE = re.compile(r"<function=([^>]+)>(.*?)</function>", re.DOTALL)
|
|
38
|
+
_PARAMETER_RE = re.compile(r"<parameter=([^>]+)>(.*?)</parameter>", re.DOTALL)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def looks_like_xml_tool_call(text: str) -> bool:
|
|
42
|
+
"""Cheap check before doing full regex parsing."""
|
|
43
|
+
return "<tool_call>" in text and "<function=" in text
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def strip_think(text: str) -> str:
|
|
47
|
+
"""Remove <think>...</think> blocks so users don't see chain-of-thought.
|
|
48
|
+
|
|
49
|
+
Also handles the chat-template-ate-the-opening-tag case: if </think>
|
|
50
|
+
appears but no matching <think>, strip everything up to and including it.
|
|
51
|
+
"""
|
|
52
|
+
text = _THINK_RE.sub("", text)
|
|
53
|
+
if "</think>" in text and "<think>" not in text:
|
|
54
|
+
text = _OPEN_THINK_MISSING_RE.sub("", text)
|
|
55
|
+
return text
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def parse_xml_tool_calls(text: str) -> tuple[str, list[dict]]:
|
|
59
|
+
"""
|
|
60
|
+
Extract XML tool calls from text and return (cleaned_text, tool_calls).
|
|
61
|
+
|
|
62
|
+
cleaned_text has the <think> and <tool_call> blocks removed so it can be
|
|
63
|
+
streamed to the user as a normal assistant reply.
|
|
64
|
+
|
|
65
|
+
tool_calls is a list of dicts shaped like litellm's accumulator:
|
|
66
|
+
{"id": "call_xxx", "name": "tool_name", "arguments": '{"key": "value"}'}
|
|
67
|
+
"""
|
|
68
|
+
cleaned = strip_think(text)
|
|
69
|
+
tool_calls: list[dict] = []
|
|
70
|
+
|
|
71
|
+
for tc_match in _TOOL_CALL_RE.finditer(cleaned):
|
|
72
|
+
body = tc_match.group(1)
|
|
73
|
+
for fn_match in _FUNCTION_RE.finditer(body):
|
|
74
|
+
name = fn_match.group(1).strip()
|
|
75
|
+
params_body = fn_match.group(2)
|
|
76
|
+
args = {
|
|
77
|
+
p.group(1).strip(): p.group(2).strip()
|
|
78
|
+
for p in _PARAMETER_RE.finditer(params_body)
|
|
79
|
+
}
|
|
80
|
+
tool_calls.append({
|
|
81
|
+
"id": f"call_{uuid.uuid4().hex[:8]}",
|
|
82
|
+
"name": name,
|
|
83
|
+
"arguments": json.dumps(args),
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
cleaned = _TOOL_CALL_RE.sub("", cleaned).strip()
|
|
87
|
+
return cleaned, tool_calls
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
agent.py,sha256=zcYfTAarYhj1hdLc9DH5sWs6Qqqp1VpmrHGJfHSJZIQ,17118
|
|
2
|
-
cli.py,sha256=lzZzJkfAJ7nzmTZqIkRnVfJg1SsfuhMwge9J7iX9xM4,24948
|
|
3
|
-
mcp_client.py,sha256=2PviTqJtXM4UC_fsYLbAOAfWJvayWy7Q8VOQIIsDiqQ,6710
|
|
4
|
-
router.py,sha256=IjomIOaLVmGHQG9rNgc3xNpGhr82rCnn5h8X-QvZhRU,11573
|
|
5
|
-
server.py,sha256=w7EXPCsXHiSqfIXep3VIcS66t_ZY02ulF2Yn6e7N5TY,9654
|
|
6
|
-
settings.py,sha256=Qjc3tiVbT1cqIrnQW6m2UG8Xsvqsxl9qXPMTueqwn50,6903
|
|
7
|
-
tools.py,sha256=MBYy0OeSIZjqyGOezaMvZ6AMW3WRDESU-2u48sifd0Q,24741
|
|
8
|
-
agentcode_cli-1.2.0.dist-info/licenses/LICENSE,sha256=BqTzyKKaSaVQoumXzhYCj1UgOSPCgvn-sxV6BIuT558,1068
|
|
9
|
-
agentcode_cli-1.2.0.dist-info/METADATA,sha256=DQDncSy8pCDk9jy-Ac6b2HbVV9skzbSe5gfQvPjmOBA,12709
|
|
10
|
-
agentcode_cli-1.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
11
|
-
agentcode_cli-1.2.0.dist-info/entry_points.txt,sha256=xP_zeySufuVhL5v10_EqCooKRSxGYB8QuRVNUj4_m1E,39
|
|
12
|
-
agentcode_cli-1.2.0.dist-info/top_level.txt,sha256=PQseaNK25xxImV2WLHRHRIkipStqhLdVWYmrccM5ln0,50
|
|
13
|
-
agentcode_cli-1.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|