codeer-cli 0.1.7__tar.gz → 0.1.9__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.7 → codeer_cli-0.1.9}/API_REFERENCE.md +23 -12
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/PKG-INFO +12 -1
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/README.md +11 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/pyproject.toml +1 -1
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/__init__.py +2 -2
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/chats.py +2 -2
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/cli.py +1 -1
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/client.py +96 -24
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/commands/_util.py +3 -1
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/commands/eval_cmd.py +149 -46
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/commands/history.py +125 -13
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/eval_.py +24 -7
- codeer_cli-0.1.9/tests/test_client_transport.py +84 -0
- codeer_cli-0.1.9/tests/test_eval_labels.py +238 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/tests/test_eval_pairs.py +94 -1
- codeer_cli-0.1.9/tests/test_history_send.py +159 -0
- codeer_cli-0.1.9/tests/test_util.py +22 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/uv.lock +1 -1
- codeer_cli-0.1.7/tests/test_eval_labels.py +0 -101
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/.gitignore +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/_validate.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/agents.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/commands/__init__.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/commands/agent.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/commands/check.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/commands/kb.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/commands/profile.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/constants.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/histories.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/kb.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/src/codeer_cli/parse.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/tests/test_kb_nodes.py +0 -0
- {codeer_cli-0.1.7 → codeer_cli-0.1.9}/tests/test_kb_ranges.py +0 -0
|
@@ -149,10 +149,10 @@ for the apply → test → publish workflow. Pass the draft `AgentHistory.id` fr
|
|
|
149
149
|
|
|
150
150
|
| Method & path | Purpose |
|
|
151
151
|
| --- | --- |
|
|
152
|
-
| `GET /eval/
|
|
153
|
-
| `POST /eval/
|
|
154
|
-
| `PUT /eval/case-labels/{label_id}` | Update eval case label
|
|
155
|
-
| `DELETE /eval/case-labels/{label_id}` | Delete eval case label and clear associations |
|
|
152
|
+
| `GET /external/eval/case-labels` | List reusable eval case labels in the API key workspace |
|
|
153
|
+
| `POST /external/eval/case-labels` | Create reusable eval case label (`name`, `color?`) in the API key workspace |
|
|
154
|
+
| `PUT /external/eval/case-labels/{label_id}` | Update an eval case label in the API key workspace |
|
|
155
|
+
| `DELETE /external/eval/case-labels/{label_id}` | Delete an eval case label and clear associations |
|
|
156
156
|
| `POST /eval/cases` | Create case (`input`, `expected_output?`, `rubric?`, `label_ids?`); rubric = user-docs "Standard" |
|
|
157
157
|
| `GET /eval/agents/{agent_id}/cases` | List cases for an agent |
|
|
158
158
|
| `GET /eval/cases/{case_id}` | Read one |
|
|
@@ -171,8 +171,11 @@ for the apply → test → publish workflow. Pass the draft `AgentHistory.id` fr
|
|
|
171
171
|
| `POST /eval/rubric` | Set/override the rubric for one (case, evaluator); also creates assignment |
|
|
172
172
|
| `POST /eval/rubrics/batch` | **Read** rubrics for a batch of (case, evaluator) pairs |
|
|
173
173
|
|
|
174
|
-
Eval case labels are workspace-scoped reusable objects. The
|
|
175
|
-
|
|
174
|
+
Eval case labels are workspace-scoped reusable objects. The external label
|
|
175
|
+
endpoints infer the workspace from the API key and do not accept a workspace
|
|
176
|
+
override; switch CLI profiles to operate on another workspace. The case
|
|
177
|
+
create/update payload uses `label_ids` (stringified label IDs), not freeform
|
|
178
|
+
label names:
|
|
176
179
|
|
|
177
180
|
```json
|
|
178
181
|
{
|
|
@@ -222,17 +225,18 @@ normalized `tool_calls`, `tool_calls_summary`, and `tool_total_duration_ms`;
|
|
|
222
225
|
in `eval_table_full.json`. Per-tool time is computed from `start_at/end_at`.
|
|
223
226
|
|
|
224
227
|
**`evaluator_id` is singular — one call returns results for one evaluator
|
|
225
|
-
only.**
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
prefer `eval_mod.get_case_evaluator_infos(case_ids=[...])` as the source of
|
|
230
|
-
truth for which pairs should run.
|
|
228
|
+
only.** To see the full picture for a case, call results once per evaluator
|
|
229
|
+
you care about. `codeer eval run` and `codeer eval rubrics` handle this
|
|
230
|
+
automatically; when no evaluator is supplied, the public CLI uses external
|
|
231
|
+
rubric batches to find case/evaluator pairs with configured rubrics.
|
|
231
232
|
|
|
232
233
|
Regression workflow (apply prompt change → re-run all assigned pairs → spot
|
|
233
234
|
side effects): `codeer eval run --agent <agent_id>` runs the latest
|
|
234
235
|
AgentHistory by default. For the common "many cases, one tester" flow, use
|
|
235
236
|
`codeer eval run --agent <agent_id> --cases <ids> --evaluator <evaluator_id>`.
|
|
237
|
+
When triggering runs, call `POST /external/eval/runs` once per evaluator with
|
|
238
|
+
that evaluator's case IDs. Do not call legacy internal trigger endpoints from
|
|
239
|
+
the public CLI.
|
|
236
240
|
|
|
237
241
|
## Stage 7 — Publish
|
|
238
242
|
|
|
@@ -246,12 +250,19 @@ AgentHistory by default. For the common "many cases, one tester" flow, use
|
|
|
246
250
|
|
|
247
251
|
| Method & path | Purpose |
|
|
248
252
|
| --- | --- |
|
|
253
|
+
| `POST /chats` | Create a persisted history using an agent's current published version |
|
|
254
|
+
| `POST /chats/{id}/messages` | Append a turn to an existing persisted history using the current published version |
|
|
249
255
|
| `GET /histories?agent_id=X&feedback_filter=improve_feedback&external_user_id=…` | List conversations with filters |
|
|
250
256
|
| `GET /histories/{id}` | Read one history's metadata |
|
|
251
257
|
| `GET /histories/{id}/conversations` | Full conversation turns incl. tool calls |
|
|
252
258
|
| `POST /histories/{hid}/conversations/{cid}/feedbacks` | Leave freeform improvement feedback |
|
|
253
259
|
| `POST /histories/{hid}/conversations/{cid}/score` | Numeric score |
|
|
254
260
|
|
|
261
|
+
The CLI exposes the first two operations as `codeer history create` and
|
|
262
|
+
`codeer history send`. Non-streaming message requests default to a 120-second
|
|
263
|
+
per-message timeout. A timeout has an uncertain write outcome, so read the
|
|
264
|
+
history before retrying to avoid duplicate turns.
|
|
265
|
+
|
|
255
266
|
`feedback_filter` accepts the `FeedbackFilterType` enum values:
|
|
256
267
|
`no_feedback`, `with_feedback`, `helpful_feedback`, `improve_feedback`.
|
|
257
268
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeer-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Command line tools for managing Codeer agents over the Codeer API.
|
|
5
5
|
Project-URL: Homepage, https://www.codeer.ai
|
|
6
6
|
Author: Codeer.AI
|
|
@@ -143,9 +143,20 @@ Use this pattern during agent lifecycle work:
|
|
|
143
143
|
```bash
|
|
144
144
|
codeer agent list
|
|
145
145
|
codeer history list --agent <agent-id> --limit 50
|
|
146
|
+
codeer history create --agent <agent-id> --message "Review this plan" --timeout 120
|
|
147
|
+
codeer history send <history-id> --message "Use the recommended options" --timeout 120
|
|
146
148
|
codeer eval run --agent <agent-id> --cases <case-ids> --evaluator <evaluator-id> --out .codeer/eval_run.json
|
|
147
149
|
```
|
|
148
150
|
|
|
151
|
+
`history create` and `history send` use the agent's current published version.
|
|
152
|
+
Their per-message timeout defaults to 120 seconds. If a write request times
|
|
153
|
+
out, inspect the history before retrying: the server may have completed the
|
|
154
|
+
turn after the client stopped waiting.
|
|
155
|
+
|
|
156
|
+
Eval case label commands always operate on the active API-key workspace. They
|
|
157
|
+
do not accept a workspace override; switch CLI profiles to target another
|
|
158
|
+
workspace.
|
|
159
|
+
|
|
149
160
|
Flags:
|
|
150
161
|
|
|
151
162
|
- `--full` prints bounded extra detail for human inspection. It is still
|
|
@@ -125,9 +125,20 @@ Use this pattern during agent lifecycle work:
|
|
|
125
125
|
```bash
|
|
126
126
|
codeer agent list
|
|
127
127
|
codeer history list --agent <agent-id> --limit 50
|
|
128
|
+
codeer history create --agent <agent-id> --message "Review this plan" --timeout 120
|
|
129
|
+
codeer history send <history-id> --message "Use the recommended options" --timeout 120
|
|
128
130
|
codeer eval run --agent <agent-id> --cases <case-ids> --evaluator <evaluator-id> --out .codeer/eval_run.json
|
|
129
131
|
```
|
|
130
132
|
|
|
133
|
+
`history create` and `history send` use the agent's current published version.
|
|
134
|
+
Their per-message timeout defaults to 120 seconds. If a write request times
|
|
135
|
+
out, inspect the history before retrying: the server may have completed the
|
|
136
|
+
turn after the client stopped waiting.
|
|
137
|
+
|
|
138
|
+
Eval case label commands always operate on the active API-key workspace. They
|
|
139
|
+
do not accept a workspace override; switch CLI profiles to target another
|
|
140
|
+
workspace.
|
|
141
|
+
|
|
131
142
|
Flags:
|
|
132
143
|
|
|
133
144
|
- `--full` prints bounded extra detail for human inspection. It is still
|
|
@@ -19,7 +19,7 @@ workspace-local dotenv files or credential files.
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
from ._validate import ToolValidationError
|
|
22
|
-
from .client import AuthError, CodeerClient, CodeerError
|
|
22
|
+
from .client import AuthError, CodeerClient, CodeerError, TransportError
|
|
23
23
|
from .parse import (
|
|
24
24
|
AgentSummary,
|
|
25
25
|
ConversationTurn,
|
|
@@ -43,7 +43,7 @@ from .parse import (
|
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
__all__ = [
|
|
46
|
-
"CodeerClient", "CodeerError", "AuthError", "ToolValidationError",
|
|
46
|
+
"CodeerClient", "CodeerError", "TransportError", "AuthError", "ToolValidationError",
|
|
47
47
|
# parsers
|
|
48
48
|
"AgentSummary", "ConversationTurn", "EvalResultSummary", "HistorySummary",
|
|
49
49
|
"KBNode", "ToolCall", "EvalToolCall",
|
|
@@ -36,6 +36,7 @@ def send_published_agent_message(
|
|
|
36
36
|
external_user_id: Optional[str] = None,
|
|
37
37
|
attachment_ids: Optional[List[str]] = None,
|
|
38
38
|
stream: bool = False,
|
|
39
|
+
timeout: Optional[float] = None,
|
|
39
40
|
) -> Iterator[dict] | dict:
|
|
40
41
|
"""Send a user message through the API-key external chat flow.
|
|
41
42
|
|
|
@@ -51,7 +52,7 @@ def send_published_agent_message(
|
|
|
51
52
|
path = f"/chats/{chat_id}/messages"
|
|
52
53
|
if stream:
|
|
53
54
|
return client.stream_sse("POST", path, json=body)
|
|
54
|
-
return client.post(path, json=body)
|
|
55
|
+
return client.post(path, json=body, timeout=timeout)
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
def send_message(
|
|
@@ -84,4 +85,3 @@ def list_messages(client: CodeerClient, chat_id: int) -> list[dict]:
|
|
|
84
85
|
|
|
85
86
|
def list_chats(client: CodeerClient) -> list[dict]:
|
|
86
87
|
return client.get("/chats")
|
|
87
|
-
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
codeer agent list|get|apply|diff|versions
|
|
5
5
|
codeer kb list|files|upload|node-rename|node-delete|faq-list|faq-get|faq-create|faq-update|faq-delete
|
|
6
6
|
codeer eval list|label-list|label-create|label-update|label-delete|case-update|case-delete|evaluators|evaluator-create|evaluator-update|run|export|reconcile|cases-apply|rubrics|rubrics-apply
|
|
7
|
-
codeer history list|get|conversations|negative-feedback
|
|
7
|
+
codeer history list|get|conversations|negative-feedback|create|send
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
@@ -37,6 +37,16 @@ class ScopeResolutionError(CodeerError):
|
|
|
37
37
|
"""Raised when workspace or organization scope cannot be inferred."""
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
class TransportError(CodeerError):
|
|
41
|
+
"""Raised when an HTTP request fails before a response is available."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, message: str, body: Any = None):
|
|
44
|
+
RuntimeError.__init__(self, message)
|
|
45
|
+
self.status = 0
|
|
46
|
+
self.message = message
|
|
47
|
+
self.body = body
|
|
48
|
+
|
|
49
|
+
|
|
40
50
|
@dataclass
|
|
41
51
|
class CodeerClient:
|
|
42
52
|
"""Thin wrapper around httpx.Client with Codeer API-key auth.
|
|
@@ -148,9 +158,32 @@ class CodeerClient:
|
|
|
148
158
|
json: Any = None,
|
|
149
159
|
files: Any = None,
|
|
150
160
|
data: Any = None,
|
|
161
|
+
timeout: Optional[float] = None,
|
|
151
162
|
) -> Any:
|
|
152
163
|
url = path if path.startswith("http") else f"/api/v1{path if path.startswith('/') else '/' + path}"
|
|
153
|
-
|
|
164
|
+
request_kwargs: dict[str, Any] = {}
|
|
165
|
+
if timeout is not None:
|
|
166
|
+
request_kwargs["timeout"] = timeout
|
|
167
|
+
method_upper = method.upper()
|
|
168
|
+
try:
|
|
169
|
+
r = self._client.request(
|
|
170
|
+
method_upper,
|
|
171
|
+
url,
|
|
172
|
+
params=params,
|
|
173
|
+
json=json,
|
|
174
|
+
files=files,
|
|
175
|
+
data=data,
|
|
176
|
+
**request_kwargs,
|
|
177
|
+
)
|
|
178
|
+
except httpx.TimeoutException as exc:
|
|
179
|
+
raise self._transport_error(
|
|
180
|
+
method_upper,
|
|
181
|
+
path,
|
|
182
|
+
exc,
|
|
183
|
+
timeout_seconds=timeout if timeout is not None else self.timeout,
|
|
184
|
+
) from exc
|
|
185
|
+
except httpx.RequestError as exc:
|
|
186
|
+
raise self._transport_error(method_upper, path, exc) from exc
|
|
154
187
|
return self._parse(r)
|
|
155
188
|
|
|
156
189
|
def get(self, path: str, **kwargs: Any) -> Any:
|
|
@@ -181,29 +214,68 @@ class CodeerClient:
|
|
|
181
214
|
Each event is a dict like ``{"event": "message", "data": <parsed-json-or-str>}``.
|
|
182
215
|
"""
|
|
183
216
|
url = path if path.startswith("http") else f"/api/v1{path if path.startswith('/') else '/' + path}"
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
217
|
+
method_upper = method.upper()
|
|
218
|
+
try:
|
|
219
|
+
with self._client.stream(method_upper, url, params=params, json=json) as r:
|
|
220
|
+
if r.status_code >= 400:
|
|
221
|
+
body = r.read().decode("utf-8", "replace")
|
|
222
|
+
self._raise_for_error(r.status_code, body)
|
|
223
|
+
event = "message"
|
|
224
|
+
buf: list[str] = []
|
|
225
|
+
for line in r.iter_lines():
|
|
226
|
+
if line == "":
|
|
227
|
+
if buf:
|
|
228
|
+
raw = "\n".join(buf)
|
|
229
|
+
yield {"event": event, "data": _maybe_json(raw)}
|
|
230
|
+
buf = []
|
|
231
|
+
event = "message"
|
|
232
|
+
continue
|
|
233
|
+
if line.startswith(":"):
|
|
234
|
+
continue
|
|
235
|
+
if line.startswith("event:"):
|
|
236
|
+
event = line[len("event:"):].strip()
|
|
237
|
+
continue
|
|
238
|
+
if line.startswith("data:"):
|
|
239
|
+
buf.append(line[len("data:"):].lstrip())
|
|
240
|
+
if buf:
|
|
241
|
+
yield {"event": event, "data": _maybe_json("\n".join(buf))}
|
|
242
|
+
except httpx.TimeoutException as exc:
|
|
243
|
+
raise self._transport_error(
|
|
244
|
+
method_upper,
|
|
245
|
+
path,
|
|
246
|
+
exc,
|
|
247
|
+
timeout_seconds=self.timeout,
|
|
248
|
+
) from exc
|
|
249
|
+
except httpx.RequestError as exc:
|
|
250
|
+
raise self._transport_error(method_upper, path, exc) from exc
|
|
251
|
+
|
|
252
|
+
def _transport_error(
|
|
253
|
+
self,
|
|
254
|
+
method: str,
|
|
255
|
+
path: str,
|
|
256
|
+
exc: httpx.RequestError,
|
|
257
|
+
*,
|
|
258
|
+
timeout_seconds: float | None = None,
|
|
259
|
+
) -> TransportError:
|
|
260
|
+
outcome_uncertain = (
|
|
261
|
+
method not in {"GET", "HEAD", "OPTIONS"}
|
|
262
|
+
and not isinstance(exc, httpx.ConnectError)
|
|
263
|
+
)
|
|
264
|
+
if isinstance(exc, httpx.TimeoutException):
|
|
265
|
+
timeout_value = timeout_seconds if timeout_seconds is not None else self.timeout
|
|
266
|
+
message = f"Request timed out after {timeout_value:g}s: {method} {path}."
|
|
267
|
+
if outcome_uncertain:
|
|
268
|
+
message += " The server may have completed the request; inspect current state before retrying."
|
|
269
|
+
else:
|
|
270
|
+
message = f"Request failed: {method} {path}: {exc}"
|
|
271
|
+
return TransportError(
|
|
272
|
+
message,
|
|
273
|
+
{
|
|
274
|
+
"method": method,
|
|
275
|
+
"path": path,
|
|
276
|
+
"outcome_uncertain": outcome_uncertain,
|
|
277
|
+
},
|
|
278
|
+
)
|
|
207
279
|
|
|
208
280
|
def _parse(self, r: httpx.Response) -> Any:
|
|
209
281
|
text = r.text
|
|
@@ -62,5 +62,7 @@ def print_json(value: Any) -> None:
|
|
|
62
62
|
def write_json(path: str | None, value: Any) -> None:
|
|
63
63
|
if not path:
|
|
64
64
|
return
|
|
65
|
-
Path(path)
|
|
65
|
+
out = Path(path)
|
|
66
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
out.write_text(json.dumps(value, ensure_ascii=False, indent=2, default=str) + "\n")
|
|
66
68
|
log(f"wrote full detail to {path}")
|