codeer-cli 0.1.12__tar.gz → 0.1.13__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.
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/PKG-INFO +2 -2
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/pyproject.toml +1 -1
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/agents.py +6 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/commands/agent.py +15 -1
- codeer_cli-0.1.13/tests/test_agent_model_settings.py +230 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/uv.lock +1 -1
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/.gitignore +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/API_REFERENCE.md +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/README.md +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/__init__.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/_validate.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/chats.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/cli.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/client.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/commands/__init__.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/commands/_util.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/commands/check.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/commands/eval_cmd.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/commands/history.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/commands/kb.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/commands/model.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/commands/profile.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/constants.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/eval_.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/histories.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/kb.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/models.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/src/codeer_cli/parse.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_agent_handoff.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_chats_v2.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_client_transport.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_eval_labels.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_eval_pairs.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_history_read.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_history_send.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_kb_nodes.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_kb_ranges.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_models.py +0 -0
- {codeer_cli-0.1.12 → codeer_cli-0.1.13}/tests/test_util.py +0 -0
|
@@ -20,6 +20,7 @@ def create(
|
|
|
20
20
|
unified_tools: Optional[List[dict]] = None,
|
|
21
21
|
use_search: bool = False,
|
|
22
22
|
llm_model: Optional[str] = None,
|
|
23
|
+
llm_model_settings: Optional[dict[str, dict[str, Any]]] = None,
|
|
23
24
|
description: Optional[str] = None,
|
|
24
25
|
suggested_questions: Optional[List[str]] = None,
|
|
25
26
|
primary_object_ids: Optional[List[int]] = None,
|
|
@@ -41,6 +42,8 @@ def create(
|
|
|
41
42
|
body["description"] = description
|
|
42
43
|
if llm_model is not None:
|
|
43
44
|
body["llm_model"] = llm_model
|
|
45
|
+
if llm_model_settings is not None:
|
|
46
|
+
body["llm_model_settings"] = llm_model_settings
|
|
44
47
|
if validated_handoff is not None:
|
|
45
48
|
body["human_handoff"] = validated_handoff
|
|
46
49
|
return client.post("/external/agents", json=body)
|
|
@@ -57,6 +60,7 @@ def update(
|
|
|
57
60
|
version_note: str = "",
|
|
58
61
|
description: Optional[str] = None,
|
|
59
62
|
llm_model: Optional[str] = None,
|
|
63
|
+
llm_model_settings: Optional[dict[str, dict[str, Any]]] = None,
|
|
60
64
|
suggested_questions: Optional[List[str]] = None,
|
|
61
65
|
primary_object_ids: Optional[List[int]] = None,
|
|
62
66
|
attachment_ids: Optional[List[str]] = None,
|
|
@@ -79,6 +83,8 @@ def update(
|
|
|
79
83
|
body["description"] = description
|
|
80
84
|
if llm_model is not None:
|
|
81
85
|
body["llm_model"] = llm_model
|
|
86
|
+
if llm_model_settings is not None:
|
|
87
|
+
body["llm_model_settings"] = llm_model_settings
|
|
82
88
|
if validated_handoff is not None:
|
|
83
89
|
body["human_handoff"] = validated_handoff
|
|
84
90
|
return client.patch(f"/external/agents/{agent_id}", json=body)
|
|
@@ -89,7 +89,9 @@ def register(subparsers):
|
|
|
89
89
|
def _tool_summary(tools: list[dict] | None) -> list[dict]:
|
|
90
90
|
out = []
|
|
91
91
|
for t in tools or []:
|
|
92
|
-
form = t.get("custom_form_schema")
|
|
92
|
+
form = t.get("custom_form_schema")
|
|
93
|
+
if not isinstance(form, dict):
|
|
94
|
+
form = {}
|
|
93
95
|
out.append({
|
|
94
96
|
"id": t.get("id"),
|
|
95
97
|
"type": t.get("type"),
|
|
@@ -169,6 +171,8 @@ def run_apply(args, client) -> int:
|
|
|
169
171
|
log(f"error: invalid agent payload: {exc}")
|
|
170
172
|
return 2
|
|
171
173
|
|
|
174
|
+
llm_model_settings_provided = "llm_model_settings" in body
|
|
175
|
+
|
|
172
176
|
if args.dry_run:
|
|
173
177
|
operation = "update" if args.agent_id else "create"
|
|
174
178
|
result = {
|
|
@@ -181,6 +185,8 @@ def run_apply(args, client) -> int:
|
|
|
181
185
|
"tool_count": len(validated_tools),
|
|
182
186
|
"use_search": body.get("use_search", False),
|
|
183
187
|
"llm_model": body.get("llm_model"),
|
|
188
|
+
"llm_model_settings_provided": llm_model_settings_provided,
|
|
189
|
+
"llm_model_settings": body.get("llm_model_settings"),
|
|
184
190
|
"human_handoff": {
|
|
185
191
|
"enabled": bool((validated_handoff or {}).get("enabled")),
|
|
186
192
|
"idle_timeout_minutes": (validated_handoff or {}).get("idle_timeout_minutes"),
|
|
@@ -193,6 +199,12 @@ def run_apply(args, client) -> int:
|
|
|
193
199
|
print_json(result)
|
|
194
200
|
return 0
|
|
195
201
|
|
|
202
|
+
model_settings_kwargs = (
|
|
203
|
+
{"llm_model_settings": body["llm_model_settings"]}
|
|
204
|
+
if llm_model_settings_provided
|
|
205
|
+
else {}
|
|
206
|
+
)
|
|
207
|
+
|
|
196
208
|
if args.agent_id:
|
|
197
209
|
body.pop("workspace_id", None)
|
|
198
210
|
agents_mod.update(
|
|
@@ -208,6 +220,7 @@ def run_apply(args, client) -> int:
|
|
|
208
220
|
primary_object_ids=body.get("primary_object_ids") or [],
|
|
209
221
|
attachment_ids=body.get("attachment_ids") or [],
|
|
210
222
|
human_handoff=validated_handoff,
|
|
223
|
+
**model_settings_kwargs,
|
|
211
224
|
)
|
|
212
225
|
agent_id = args.agent_id
|
|
213
226
|
log(f"PUT /agents/{agent_id} ok")
|
|
@@ -227,6 +240,7 @@ def run_apply(args, client) -> int:
|
|
|
227
240
|
primary_object_ids=body.get("primary_object_ids") or [],
|
|
228
241
|
attachment_ids=body.get("attachment_ids") or [],
|
|
229
242
|
human_handoff=validated_handoff,
|
|
243
|
+
**model_settings_kwargs,
|
|
230
244
|
)
|
|
231
245
|
agent_id = agent["id"]
|
|
232
246
|
log(f"POST /agents ok, id={agent_id}")
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import io
|
|
5
|
+
import json
|
|
6
|
+
import tempfile
|
|
7
|
+
import unittest
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from types import SimpleNamespace
|
|
10
|
+
from unittest.mock import patch
|
|
11
|
+
|
|
12
|
+
from codeer_cli import agents
|
|
13
|
+
from codeer_cli.commands import agent as agent_cmd
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
MODEL_ID = "openai/gpt-5.6-luna"
|
|
17
|
+
MODEL_SETTINGS = {MODEL_ID: {"thinking": "xhigh"}}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FakeClient:
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
self.calls: list[tuple[str, str, dict]] = []
|
|
23
|
+
|
|
24
|
+
def post(self, path: str, **kwargs):
|
|
25
|
+
self.calls.append(("POST", path, kwargs))
|
|
26
|
+
return {"id": "agent-1"}
|
|
27
|
+
|
|
28
|
+
def patch(self, path: str, **kwargs):
|
|
29
|
+
self.calls.append(("PATCH", path, kwargs))
|
|
30
|
+
return {"id": "agent-1"}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AgentModelSettingsPayloadTests(unittest.TestCase):
|
|
34
|
+
def test_create_forwards_model_settings(self) -> None:
|
|
35
|
+
client = FakeClient()
|
|
36
|
+
|
|
37
|
+
agents.create(
|
|
38
|
+
client, # type: ignore[arg-type]
|
|
39
|
+
workspace_id="workspace-1",
|
|
40
|
+
name="Agent",
|
|
41
|
+
system_prompt="Help safely.",
|
|
42
|
+
llm_model=MODEL_ID,
|
|
43
|
+
llm_model_settings=MODEL_SETTINGS,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
self.assertEqual(client.calls[0][0:2], ("POST", "/external/agents"))
|
|
47
|
+
self.assertEqual(
|
|
48
|
+
client.calls[0][2]["json"]["llm_model_settings"],
|
|
49
|
+
MODEL_SETTINGS,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def test_update_forwards_model_settings(self) -> None:
|
|
53
|
+
client = FakeClient()
|
|
54
|
+
|
|
55
|
+
agents.update(
|
|
56
|
+
client, # type: ignore[arg-type]
|
|
57
|
+
"agent-1",
|
|
58
|
+
name="Agent",
|
|
59
|
+
system_prompt="Help safely.",
|
|
60
|
+
unified_tools=[],
|
|
61
|
+
use_search=False,
|
|
62
|
+
llm_model=MODEL_ID,
|
|
63
|
+
llm_model_settings=MODEL_SETTINGS,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
self.assertEqual(
|
|
67
|
+
client.calls[0][0:2],
|
|
68
|
+
("PATCH", "/external/agents/agent-1"),
|
|
69
|
+
)
|
|
70
|
+
self.assertEqual(
|
|
71
|
+
client.calls[0][2]["json"]["llm_model_settings"],
|
|
72
|
+
MODEL_SETTINGS,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def test_update_forwards_empty_map_to_clear_settings(self) -> None:
|
|
76
|
+
client = FakeClient()
|
|
77
|
+
|
|
78
|
+
agents.update(
|
|
79
|
+
client, # type: ignore[arg-type]
|
|
80
|
+
"agent-1",
|
|
81
|
+
name="Agent",
|
|
82
|
+
system_prompt="Help safely.",
|
|
83
|
+
unified_tools=[],
|
|
84
|
+
use_search=False,
|
|
85
|
+
llm_model_settings={},
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self.assertEqual(client.calls[0][2]["json"]["llm_model_settings"], {})
|
|
89
|
+
|
|
90
|
+
def test_update_omits_model_settings_when_unspecified(self) -> None:
|
|
91
|
+
client = FakeClient()
|
|
92
|
+
|
|
93
|
+
agents.update(
|
|
94
|
+
client, # type: ignore[arg-type]
|
|
95
|
+
"agent-1",
|
|
96
|
+
name="Agent",
|
|
97
|
+
system_prompt="Help safely.",
|
|
98
|
+
unified_tools=[],
|
|
99
|
+
use_search=False,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
self.assertNotIn("llm_model_settings", client.calls[0][2]["json"])
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AgentModelSettingsCommandTests(unittest.TestCase):
|
|
106
|
+
def _run_apply(
|
|
107
|
+
self,
|
|
108
|
+
payload: dict,
|
|
109
|
+
*,
|
|
110
|
+
agent_id: str | None = None,
|
|
111
|
+
dry_run: bool = False,
|
|
112
|
+
) -> tuple[int, dict]:
|
|
113
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
114
|
+
payload_path = Path(tmpdir) / "agent.json"
|
|
115
|
+
payload_path.write_text(json.dumps(payload))
|
|
116
|
+
args = SimpleNamespace(
|
|
117
|
+
payload=str(payload_path),
|
|
118
|
+
agent_id=agent_id,
|
|
119
|
+
dry_run=dry_run,
|
|
120
|
+
note="settings regression",
|
|
121
|
+
out=None,
|
|
122
|
+
)
|
|
123
|
+
stdout = io.StringIO()
|
|
124
|
+
with contextlib.redirect_stdout(stdout):
|
|
125
|
+
status = agent_cmd.run_apply(args, object())
|
|
126
|
+
return status, json.loads(stdout.getvalue()) if stdout.getvalue() else {}
|
|
127
|
+
|
|
128
|
+
def test_apply_create_forwards_model_settings(self) -> None:
|
|
129
|
+
payload = {
|
|
130
|
+
"workspace_id": "workspace-1",
|
|
131
|
+
"name": "Agent",
|
|
132
|
+
"system_prompt": "Help safely.",
|
|
133
|
+
"llm_model": MODEL_ID,
|
|
134
|
+
"llm_model_settings": MODEL_SETTINGS,
|
|
135
|
+
}
|
|
136
|
+
history = [{"id": "history-1", "version_number": 1, "status": "draft"}]
|
|
137
|
+
|
|
138
|
+
with (
|
|
139
|
+
patch.object(
|
|
140
|
+
agent_cmd.agents_mod,
|
|
141
|
+
"create",
|
|
142
|
+
return_value={"id": "agent-1"},
|
|
143
|
+
) as create,
|
|
144
|
+
patch.object(agent_cmd.agents_mod, "list_versions", return_value=history),
|
|
145
|
+
):
|
|
146
|
+
status, _ = self._run_apply(payload)
|
|
147
|
+
|
|
148
|
+
self.assertEqual(status, 0)
|
|
149
|
+
self.assertEqual(
|
|
150
|
+
create.call_args.kwargs["llm_model_settings"],
|
|
151
|
+
MODEL_SETTINGS,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def test_apply_update_preserves_present_and_empty_settings(self) -> None:
|
|
155
|
+
history = [{"id": "history-2", "version_number": 2, "status": "draft"}]
|
|
156
|
+
|
|
157
|
+
for model_settings in (MODEL_SETTINGS, {}):
|
|
158
|
+
with self.subTest(model_settings=model_settings):
|
|
159
|
+
payload = {
|
|
160
|
+
"name": "Agent",
|
|
161
|
+
"system_prompt": "Help safely.",
|
|
162
|
+
"llm_model_settings": model_settings,
|
|
163
|
+
}
|
|
164
|
+
with (
|
|
165
|
+
patch.object(agent_cmd.agents_mod, "update") as update,
|
|
166
|
+
patch.object(
|
|
167
|
+
agent_cmd.agents_mod,
|
|
168
|
+
"list_versions",
|
|
169
|
+
return_value=history,
|
|
170
|
+
),
|
|
171
|
+
):
|
|
172
|
+
status, _ = self._run_apply(payload, agent_id="agent-1")
|
|
173
|
+
|
|
174
|
+
self.assertEqual(status, 0)
|
|
175
|
+
self.assertEqual(
|
|
176
|
+
update.call_args.kwargs["llm_model_settings"],
|
|
177
|
+
model_settings,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def test_apply_update_does_not_supply_omitted_settings(self) -> None:
|
|
181
|
+
payload = {
|
|
182
|
+
"name": "Agent",
|
|
183
|
+
"system_prompt": "Help safely.",
|
|
184
|
+
}
|
|
185
|
+
history = [{"id": "history-2", "version_number": 2, "status": "draft"}]
|
|
186
|
+
|
|
187
|
+
with (
|
|
188
|
+
patch.object(agent_cmd.agents_mod, "update") as update,
|
|
189
|
+
patch.object(agent_cmd.agents_mod, "list_versions", return_value=history),
|
|
190
|
+
):
|
|
191
|
+
status, _ = self._run_apply(payload, agent_id="agent-1")
|
|
192
|
+
|
|
193
|
+
self.assertEqual(status, 0)
|
|
194
|
+
self.assertNotIn("llm_model_settings", update.call_args.kwargs)
|
|
195
|
+
|
|
196
|
+
def test_apply_dry_run_distinguishes_settings_states(self) -> None:
|
|
197
|
+
cases = (
|
|
198
|
+
(MODEL_SETTINGS, True, MODEL_SETTINGS),
|
|
199
|
+
({}, True, {}),
|
|
200
|
+
(None, False, None),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
for model_settings, expected_provided, expected_settings in cases:
|
|
204
|
+
with self.subTest(model_settings=model_settings):
|
|
205
|
+
payload: dict[str, object] = {
|
|
206
|
+
"name": "Agent",
|
|
207
|
+
"system_prompt": "Help safely.",
|
|
208
|
+
}
|
|
209
|
+
if expected_provided:
|
|
210
|
+
payload["llm_model_settings"] = model_settings
|
|
211
|
+
|
|
212
|
+
status, result = self._run_apply(
|
|
213
|
+
payload,
|
|
214
|
+
agent_id="agent-1",
|
|
215
|
+
dry_run=True,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
self.assertEqual(status, 0)
|
|
219
|
+
self.assertEqual(
|
|
220
|
+
result["llm_model_settings_provided"],
|
|
221
|
+
expected_provided,
|
|
222
|
+
)
|
|
223
|
+
self.assertEqual(
|
|
224
|
+
result["llm_model_settings"],
|
|
225
|
+
expected_settings,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
if __name__ == "__main__":
|
|
230
|
+
unittest.main()
|
|
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
|
|
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
|
|
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
|