codeer-cli 0.1.9__tar.gz → 0.1.11__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.9 → codeer_cli-0.1.11}/API_REFERENCE.md +52 -26
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/PKG-INFO +37 -6
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/README.md +36 -5
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/pyproject.toml +1 -1
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/__init__.py +2 -1
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/_validate.py +42 -1
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/agents.py +9 -1
- codeer_cli-0.1.11/src/codeer_cli/chats.py +201 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/cli.py +8 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/client.py +30 -5
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/commands/agent.py +26 -3
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/commands/history.py +26 -12
- codeer_cli-0.1.11/src/codeer_cli/commands/model.py +43 -0
- codeer_cli-0.1.11/src/codeer_cli/models.py +16 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/parse.py +7 -8
- codeer_cli-0.1.11/tests/test_agent_handoff.py +153 -0
- codeer_cli-0.1.11/tests/test_chats_v2.py +135 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/tests/test_client_transport.py +48 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/tests/test_history_send.py +52 -12
- codeer_cli-0.1.11/tests/test_models.py +68 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/uv.lock +1 -1
- codeer_cli-0.1.9/src/codeer_cli/chats.py +0 -87
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/.gitignore +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/commands/__init__.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/commands/_util.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/commands/check.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/commands/eval_cmd.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/commands/kb.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/commands/profile.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/constants.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/eval_.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/histories.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/src/codeer_cli/kb.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/tests/test_eval_labels.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/tests/test_eval_pairs.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/tests/test_kb_nodes.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/tests/test_kb_ranges.py +0 -0
- {codeer_cli-0.1.9 → codeer_cli-0.1.11}/tests/test_util.py +0 -0
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
> This file is the request-shape reference for maintaining `codeer-cli`.
|
|
4
4
|
|
|
5
5
|
The 9 stages below mirror the user-docs lifecycle (`agent-creation` →
|
|
6
|
-
`optimization-loop` → `publish`).
|
|
7
|
-
|
|
6
|
+
`optimization-loop` → `publish`). Agent, KB, eval, history, and model paths are
|
|
7
|
+
under `/api/v1/`; Chat create/message/read paths use `/api/v2/chats`. All
|
|
8
|
+
endpoints authenticate via `x-api-key` from `CODEER_API_KEY`.
|
|
8
9
|
|
|
9
10
|
Envelope: successful responses look like
|
|
10
11
|
`{"error_code": 0, "message": "", "pagination": null, "data": <payload>}`.
|
|
@@ -49,6 +50,23 @@ like `knowledge_node_ids`, `domain`, `agent_id`, `http_request` config.
|
|
|
49
50
|
|
|
50
51
|
Limits: 10 tools per agent, ≤5 `call_agent`, ≤1 `memory`.
|
|
51
52
|
|
|
53
|
+
Agent create/update payloads may also include:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"human_handoff": {
|
|
58
|
+
"enabled": true,
|
|
59
|
+
"idle_timeout_minutes": null,
|
|
60
|
+
"handoff_instructions": "Hand off when the user asks for a person."
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`idle_timeout_minutes` must be greater than zero when provided. The runtime
|
|
66
|
+
only exposes the handoff tool to an external conversation with a non-empty
|
|
67
|
+
`external_user_id`, or to an evaluation run. Internal editor Live Test does not
|
|
68
|
+
activate human mode.
|
|
69
|
+
|
|
52
70
|
## Stage 2 — Knowledge bases
|
|
53
71
|
|
|
54
72
|
Base path: `/organizations/{org_id}/workspaces/{ws_id}/knowledge_bases`
|
|
@@ -123,19 +141,21 @@ codeer kb files --kb-id <kb-id>
|
|
|
123
141
|
codeer kb faq-create --context-object-id <snapshot-object-id> --question "..." --range 12:0-12:42 --dry-run
|
|
124
142
|
```
|
|
125
143
|
|
|
126
|
-
## Stage 3 — Live Test on a specific version
|
|
144
|
+
## Stage 3 — Live Test on a specific version (legacy V1 compatibility)
|
|
127
145
|
|
|
128
146
|
| Method & path | Purpose |
|
|
129
147
|
| --- | --- |
|
|
130
|
-
| `POST /chats` | Create a
|
|
131
|
-
| `POST /chats/{chat_id}/messages` | Send a message
|
|
132
|
-
| `GET /chats/{chat_id}/messages` | Read
|
|
133
|
-
| `POST /chats/{chat_id}/regenerate` | Re-run the last turn |
|
|
134
|
-
| `POST /chats/{chat_id}/messages/{msg_id}/feedbacks` | Thumbs up/down on a reply |
|
|
135
|
-
|
|
136
|
-
`POST /chats/.../messages`
|
|
137
|
-
for the apply → test → publish workflow. Pass the draft
|
|
138
|
-
`PUT /agents/{id}` to test an unpublished version.
|
|
148
|
+
| `POST /api/v1/chats` | Create a legacy chat session bound to an agent |
|
|
149
|
+
| `POST /api/v1/chats/{chat_id}/messages` | Send a version-pinned message through legacy SSE |
|
|
150
|
+
| `GET /api/v1/chats/{chat_id}/messages` | Read legacy messages for a chat |
|
|
151
|
+
| `POST /api/v1/chats/{chat_id}/regenerate` | Re-run the last turn |
|
|
152
|
+
| `POST /api/v1/chats/{chat_id}/messages/{msg_id}/feedbacks` | Thumbs up/down on a reply |
|
|
153
|
+
|
|
154
|
+
Legacy V1 `POST /chats/.../messages` accepts `agent_history_id` — this is the
|
|
155
|
+
compatibility hook for the apply → test → publish workflow. Pass the draft
|
|
156
|
+
`AgentHistory.id` from `PUT /agents/{id}` to test an unpublished version.
|
|
157
|
+
The API-key external flow in Chat V2 accepts `agent_id` and runs its published
|
|
158
|
+
version; it does not currently replace this draft-pinning path.
|
|
139
159
|
|
|
140
160
|
## Stage 4 — Version management
|
|
141
161
|
|
|
@@ -250,8 +270,9 @@ the public CLI.
|
|
|
250
270
|
|
|
251
271
|
| Method & path | Purpose |
|
|
252
272
|
| --- | --- |
|
|
253
|
-
| `POST /chats` | Create a persisted history using an agent's current published version |
|
|
254
|
-
| `POST /chats/{id}/messages` | Append a turn
|
|
273
|
+
| `POST /api/v2/chats` | Create a persisted history using an agent's current published version |
|
|
274
|
+
| `POST /api/v2/chats/{id}/messages` | Append a turn through structured SSE using the current published version |
|
|
275
|
+
| `GET /api/v2/chats/{id}/messages` | Read persisted Chat V2 conversation parts |
|
|
255
276
|
| `GET /histories?agent_id=X&feedback_filter=improve_feedback&external_user_id=…` | List conversations with filters |
|
|
256
277
|
| `GET /histories/{id}` | Read one history's metadata |
|
|
257
278
|
| `GET /histories/{id}/conversations` | Full conversation turns incl. tool calls |
|
|
@@ -259,9 +280,11 @@ the public CLI.
|
|
|
259
280
|
| `POST /histories/{hid}/conversations/{cid}/score` | Numeric score |
|
|
260
281
|
|
|
261
282
|
The CLI exposes the first two operations as `codeer history create` and
|
|
262
|
-
`codeer history send`.
|
|
263
|
-
|
|
264
|
-
|
|
283
|
+
`codeer history send`. Messages explicitly set `stream: true`, consume Chat V2
|
|
284
|
+
SSE, and require `response.completed` before reporting success. Their
|
|
285
|
+
per-message SSE read timeout defaults to 240 seconds. A timeout,
|
|
286
|
+
`response.failed`, or disconnect before completion has an uncertain write
|
|
287
|
+
outcome, so read the history before retrying to avoid duplicate turns.
|
|
265
288
|
|
|
266
289
|
`feedback_filter` accepts the `FeedbackFilterType` enum values:
|
|
267
290
|
`no_feedback`, `with_feedback`, `helpful_feedback`, `improve_feedback`.
|
|
@@ -280,7 +303,7 @@ Non-destructive: older versions stay in `GET /agents/{id}/histories`.
|
|
|
280
303
|
| --- | --- |
|
|
281
304
|
| `GET /accounts/me` | Sanity-check session, read workspace_organization_map |
|
|
282
305
|
| `GET /organizations` | List orgs visible to the user |
|
|
283
|
-
| `GET /llm/models` | List available LLM model IDs to use as `llm_model` |
|
|
306
|
+
| `GET /llm/models` | List available LLM model IDs to use as `llm_model` (`codeer model list`) |
|
|
284
307
|
| `GET /retrieval/...` | Shared retrieval helpers (file upload for attachments, markdown conversion) |
|
|
285
308
|
|
|
286
309
|
---
|
|
@@ -340,7 +363,7 @@ said (scope, factuality, tool-use rules).
|
|
|
340
363
|
|
|
341
364
|
### 4. Agent version pinning works everywhere — use it
|
|
342
365
|
|
|
343
|
-
|
|
366
|
+
Legacy V1 `POST /chats/{id}/messages` (`agent_history_id` required) and
|
|
344
367
|
`POST /eval/trigger` (`agent_history_id` optional, null = live state) accept
|
|
345
368
|
the draft history id. The apply-→-test-→-publish loop:
|
|
346
369
|
|
|
@@ -446,9 +469,10 @@ pass. Workspace scope is inferred from the API-key virtual user profile.
|
|
|
446
469
|
|
|
447
470
|
### 11. Tool args + outputs are NOT persisted in history reads
|
|
448
471
|
|
|
449
|
-
|
|
450
|
-
— there is no `tool` role row.
|
|
451
|
-
|
|
472
|
+
Legacy V1 `History` conversation rows have only three roles
|
|
473
|
+
(`OpenAIChatRole = system | user | assistant`) — there is no `tool` role row.
|
|
474
|
+
When you read those rows through `/histories/{id}/conversations`, here's what
|
|
475
|
+
you can and can't recover from each assistant turn:
|
|
452
476
|
|
|
453
477
|
| Recoverable | Where |
|
|
454
478
|
| --- | --- |
|
|
@@ -464,10 +488,12 @@ and can't recover from each assistant turn:
|
|
|
464
488
|
| Tool **outputs** (raw JSON returned by the tool) | same — stored only as derived `primary_sources` for retrieval tools |
|
|
465
489
|
| Reasoning steps mid-turn | `meta.reasoning_steps` is currently always `null` |
|
|
466
490
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
491
|
+
Chat V2 improves this contract: structured SSE emits tool calls and returns as
|
|
492
|
+
`response.part.created` / `response.part.completed`, and
|
|
493
|
+
`GET /api/v2/chats/{id}/messages` reads the persisted conversation parts.
|
|
494
|
+
Capture the SSE artifact with `--out` when exact event order matters; use the
|
|
495
|
+
V2 message read for persisted after-the-fact tool I/O. The legacy v1 history
|
|
496
|
+
read remains useful for compact turn-level analysis.
|
|
471
497
|
|
|
472
498
|
### 10. A KB has exactly ONE level of folders — no nesting
|
|
473
499
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeer-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
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
|
|
@@ -117,6 +117,35 @@ Validate setup before API work:
|
|
|
117
117
|
codeer check
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
+
List the active cloud models without opening the Codeer web app:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
codeer model list --type text
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Agent human handoff
|
|
127
|
+
|
|
128
|
+
`codeer agent apply` accepts the same `human_handoff` object as the Agent API.
|
|
129
|
+
The dry-run validates it and shows whether handoff is enabled before any server
|
|
130
|
+
write:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"name": "Support Agent",
|
|
135
|
+
"system_prompt": "Help the user safely.",
|
|
136
|
+
"human_handoff": {
|
|
137
|
+
"enabled": true,
|
|
138
|
+
"idle_timeout_minutes": null,
|
|
139
|
+
"handoff_instructions": "Hand off when the user asks for a person."
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`idle_timeout_minutes` must be a positive integer or `null`. Human handoff only
|
|
145
|
+
becomes available in live published-agent conversations with a non-empty
|
|
146
|
+
`external_user_id`; editor Live Test conversations are internal and cannot
|
|
147
|
+
activate human mode.
|
|
148
|
+
|
|
120
149
|
## Upgrade and uninstall
|
|
121
150
|
|
|
122
151
|
Upgrade the CLI:
|
|
@@ -143,15 +172,17 @@ Use this pattern during agent lifecycle work:
|
|
|
143
172
|
```bash
|
|
144
173
|
codeer agent list
|
|
145
174
|
codeer history list --agent <agent-id> --limit 50
|
|
146
|
-
codeer history create --agent <agent-id> --message "Review this plan" --timeout
|
|
147
|
-
codeer history send <history-id> --message "Use the recommended options" --timeout
|
|
175
|
+
codeer history create --agent <agent-id> --message "Review this plan" --timeout 240
|
|
176
|
+
codeer history send <history-id> --message "Use the recommended options" --timeout 240
|
|
148
177
|
codeer eval run --agent <agent-id> --cases <case-ids> --evaluator <evaluator-id> --out .codeer/eval_run.json
|
|
149
178
|
```
|
|
150
179
|
|
|
151
180
|
`history create` and `history send` use the agent's current published version.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
181
|
+
They use Chat V2 structured SSE with `stream: true`; their per-message read
|
|
182
|
+
timeout defaults to 240 seconds. Success requires a `response.completed`
|
|
183
|
+
event. If the stream times out, reports `response.failed`, or disconnects
|
|
184
|
+
early, inspect the history before retrying: the server may already have
|
|
185
|
+
persisted the turn.
|
|
155
186
|
|
|
156
187
|
Eval case label commands always operate on the active API-key workspace. They
|
|
157
188
|
do not accept a workspace override; switch CLI profiles to target another
|
|
@@ -99,6 +99,35 @@ Validate setup before API work:
|
|
|
99
99
|
codeer check
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
List the active cloud models without opening the Codeer web app:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
codeer model list --type text
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Agent human handoff
|
|
109
|
+
|
|
110
|
+
`codeer agent apply` accepts the same `human_handoff` object as the Agent API.
|
|
111
|
+
The dry-run validates it and shows whether handoff is enabled before any server
|
|
112
|
+
write:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"name": "Support Agent",
|
|
117
|
+
"system_prompt": "Help the user safely.",
|
|
118
|
+
"human_handoff": {
|
|
119
|
+
"enabled": true,
|
|
120
|
+
"idle_timeout_minutes": null,
|
|
121
|
+
"handoff_instructions": "Hand off when the user asks for a person."
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`idle_timeout_minutes` must be a positive integer or `null`. Human handoff only
|
|
127
|
+
becomes available in live published-agent conversations with a non-empty
|
|
128
|
+
`external_user_id`; editor Live Test conversations are internal and cannot
|
|
129
|
+
activate human mode.
|
|
130
|
+
|
|
102
131
|
## Upgrade and uninstall
|
|
103
132
|
|
|
104
133
|
Upgrade the CLI:
|
|
@@ -125,15 +154,17 @@ Use this pattern during agent lifecycle work:
|
|
|
125
154
|
```bash
|
|
126
155
|
codeer agent list
|
|
127
156
|
codeer history list --agent <agent-id> --limit 50
|
|
128
|
-
codeer history create --agent <agent-id> --message "Review this plan" --timeout
|
|
129
|
-
codeer history send <history-id> --message "Use the recommended options" --timeout
|
|
157
|
+
codeer history create --agent <agent-id> --message "Review this plan" --timeout 240
|
|
158
|
+
codeer history send <history-id> --message "Use the recommended options" --timeout 240
|
|
130
159
|
codeer eval run --agent <agent-id> --cases <case-ids> --evaluator <evaluator-id> --out .codeer/eval_run.json
|
|
131
160
|
```
|
|
132
161
|
|
|
133
162
|
`history create` and `history send` use the agent's current published version.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
163
|
+
They use Chat V2 structured SSE with `stream: true`; their per-message read
|
|
164
|
+
timeout defaults to 240 seconds. Success requires a `response.completed`
|
|
165
|
+
event. If the stream times out, reports `response.failed`, or disconnects
|
|
166
|
+
early, inspect the history before retrying: the server may already have
|
|
167
|
+
persisted the turn.
|
|
137
168
|
|
|
138
169
|
Eval case label commands always operate on the active API-key workspace. They
|
|
139
170
|
do not accept a workspace override; switch CLI profiles to target another
|
|
@@ -18,7 +18,7 @@ production and can be overridden from process env. The CLI does not read
|
|
|
18
18
|
workspace-local dotenv files or credential files.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
from ._validate import ToolValidationError
|
|
21
|
+
from ._validate import HumanHandoffValidationError, ToolValidationError
|
|
22
22
|
from .client import AuthError, CodeerClient, CodeerError, TransportError
|
|
23
23
|
from .parse import (
|
|
24
24
|
AgentSummary,
|
|
@@ -44,6 +44,7 @@ from .parse import (
|
|
|
44
44
|
|
|
45
45
|
__all__ = [
|
|
46
46
|
"CodeerClient", "CodeerError", "TransportError", "AuthError", "ToolValidationError",
|
|
47
|
+
"HumanHandoffValidationError",
|
|
47
48
|
# parsers
|
|
48
49
|
"AgentSummary", "ConversationTurn", "EvalResultSummary", "HistorySummary",
|
|
49
50
|
"KBNode", "ToolCall", "EvalToolCall",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Client-side validation for
|
|
1
|
+
"""Client-side validation for agent payloads.
|
|
2
2
|
|
|
3
3
|
These checks exist because the backend's form-schema validator is lenient
|
|
4
4
|
(``extra="allow"``) and silently accepts unknown ``type`` strings, which then
|
|
@@ -24,6 +24,47 @@ class ToolValidationError(ValueError):
|
|
|
24
24
|
"""Raised when a unified_tools payload is definitely wrong."""
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class HumanHandoffValidationError(ValueError):
|
|
28
|
+
"""Raised when a human_handoff payload is definitely wrong."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def validate_human_handoff(config: Any) -> dict[str, Any] | None:
|
|
32
|
+
"""Validate and normalize an optional human-handoff configuration."""
|
|
33
|
+
if config is None:
|
|
34
|
+
return None
|
|
35
|
+
if not isinstance(config, dict):
|
|
36
|
+
raise HumanHandoffValidationError("human_handoff must be an object.")
|
|
37
|
+
|
|
38
|
+
allowed_keys = {"enabled", "idle_timeout_minutes", "handoff_instructions"}
|
|
39
|
+
unknown_keys = sorted(set(config) - allowed_keys)
|
|
40
|
+
if unknown_keys:
|
|
41
|
+
raise HumanHandoffValidationError(
|
|
42
|
+
f"human_handoff contains unsupported field(s): {', '.join(unknown_keys)}."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
enabled = config.get("enabled", False)
|
|
46
|
+
if not isinstance(enabled, bool):
|
|
47
|
+
raise HumanHandoffValidationError("human_handoff.enabled must be true or false.")
|
|
48
|
+
|
|
49
|
+
timeout = config.get("idle_timeout_minutes")
|
|
50
|
+
if timeout is not None and (type(timeout) is not int or timeout <= 0):
|
|
51
|
+
raise HumanHandoffValidationError(
|
|
52
|
+
"human_handoff.idle_timeout_minutes must be a positive integer or null."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
instructions = config.get("handoff_instructions")
|
|
56
|
+
if instructions is not None and not isinstance(instructions, str):
|
|
57
|
+
raise HumanHandoffValidationError(
|
|
58
|
+
"human_handoff.handoff_instructions must be a string or null."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
"enabled": enabled,
|
|
63
|
+
"idle_timeout_minutes": timeout,
|
|
64
|
+
"handoff_instructions": instructions,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
27
68
|
def validate_unified_tools(tools: Iterable[dict[str, Any]] | None) -> list[dict[str, Any]]:
|
|
28
69
|
"""Raise ToolValidationError on invalid payloads; otherwise return the list.
|
|
29
70
|
|
|
@@ -7,7 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
from typing import Any, List, Optional
|
|
9
9
|
|
|
10
|
-
from ._validate import validate_unified_tools
|
|
10
|
+
from ._validate import validate_human_handoff, validate_unified_tools
|
|
11
11
|
from .client import CodeerClient
|
|
12
12
|
|
|
13
13
|
|
|
@@ -24,8 +24,10 @@ def create(
|
|
|
24
24
|
suggested_questions: Optional[List[str]] = None,
|
|
25
25
|
primary_object_ids: Optional[List[int]] = None,
|
|
26
26
|
attachment_ids: Optional[List[str]] = None,
|
|
27
|
+
human_handoff: Optional[dict[str, Any]] = None,
|
|
27
28
|
) -> dict:
|
|
28
29
|
validated_tools = validate_unified_tools(unified_tools)
|
|
30
|
+
validated_handoff = validate_human_handoff(human_handoff)
|
|
29
31
|
body: dict[str, Any] = {
|
|
30
32
|
"name": name,
|
|
31
33
|
"system_prompt": system_prompt,
|
|
@@ -39,6 +41,8 @@ def create(
|
|
|
39
41
|
body["description"] = description
|
|
40
42
|
if llm_model is not None:
|
|
41
43
|
body["llm_model"] = llm_model
|
|
44
|
+
if validated_handoff is not None:
|
|
45
|
+
body["human_handoff"] = validated_handoff
|
|
42
46
|
return client.post("/external/agents", json=body)
|
|
43
47
|
|
|
44
48
|
|
|
@@ -56,9 +60,11 @@ def update(
|
|
|
56
60
|
suggested_questions: Optional[List[str]] = None,
|
|
57
61
|
primary_object_ids: Optional[List[int]] = None,
|
|
58
62
|
attachment_ids: Optional[List[str]] = None,
|
|
63
|
+
human_handoff: Optional[dict[str, Any]] = None,
|
|
59
64
|
) -> dict:
|
|
60
65
|
"""PUT creates a new AgentHistory snapshot (draft)."""
|
|
61
66
|
validated_tools = validate_unified_tools(unified_tools)
|
|
67
|
+
validated_handoff = validate_human_handoff(human_handoff)
|
|
62
68
|
body: dict[str, Any] = {
|
|
63
69
|
"name": name,
|
|
64
70
|
"system_prompt": system_prompt,
|
|
@@ -73,6 +79,8 @@ def update(
|
|
|
73
79
|
body["description"] = description
|
|
74
80
|
if llm_model is not None:
|
|
75
81
|
body["llm_model"] = llm_model
|
|
82
|
+
if validated_handoff is not None:
|
|
83
|
+
body["human_handoff"] = validated_handoff
|
|
76
84
|
return client.patch(f"/external/agents/{agent_id}", json=body)
|
|
77
85
|
|
|
78
86
|
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Chat V2 creation, structured SSE responses, and persisted message reads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Iterator, List, Optional
|
|
6
|
+
|
|
7
|
+
from .client import CodeerClient, TransportError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create(
|
|
11
|
+
client: CodeerClient,
|
|
12
|
+
*,
|
|
13
|
+
agent_id: str,
|
|
14
|
+
title: Optional[str] = None,
|
|
15
|
+
external_user_id: Optional[str] = None,
|
|
16
|
+
) -> dict:
|
|
17
|
+
body: dict[str, Any] = {
|
|
18
|
+
"agent_id": agent_id,
|
|
19
|
+
"name": title or "CLI conversation",
|
|
20
|
+
}
|
|
21
|
+
if external_user_id is not None:
|
|
22
|
+
body["external_user_id"] = external_user_id
|
|
23
|
+
return client.post("/chats", api_version="v2", json=body)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def send_published_agent_message(
|
|
27
|
+
client: CodeerClient,
|
|
28
|
+
*,
|
|
29
|
+
chat_id: int,
|
|
30
|
+
message: str,
|
|
31
|
+
agent_id: str,
|
|
32
|
+
external_user_id: Optional[str] = None,
|
|
33
|
+
attachment_ids: Optional[List[str]] = None,
|
|
34
|
+
stream: bool = True,
|
|
35
|
+
timeout: Optional[float] = None,
|
|
36
|
+
) -> Iterator[dict] | dict:
|
|
37
|
+
"""Send a user message through the API-key external chat flow.
|
|
38
|
+
|
|
39
|
+
API-key chat endpoints use the agent's published version. They accept
|
|
40
|
+
``agent_id`` rather than ``agent_history_id``.
|
|
41
|
+
"""
|
|
42
|
+
body: dict[str, Any] = {"message": message, "agent_id": agent_id, "stream": stream}
|
|
43
|
+
if external_user_id is not None:
|
|
44
|
+
body["external_user_id"] = external_user_id
|
|
45
|
+
if attachment_ids:
|
|
46
|
+
body["attached_file_uuids"] = attachment_ids
|
|
47
|
+
|
|
48
|
+
path = f"/chats/{chat_id}/messages"
|
|
49
|
+
if stream:
|
|
50
|
+
return client.stream_sse(
|
|
51
|
+
"POST",
|
|
52
|
+
path,
|
|
53
|
+
api_version="v2",
|
|
54
|
+
json=body,
|
|
55
|
+
timeout=timeout,
|
|
56
|
+
)
|
|
57
|
+
return client.post(path, api_version="v2", json=body, timeout=timeout)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def send_message(
|
|
61
|
+
client: CodeerClient,
|
|
62
|
+
*,
|
|
63
|
+
chat_id: int,
|
|
64
|
+
message: str,
|
|
65
|
+
agent_history_id: str,
|
|
66
|
+
attachment_ids: Optional[List[str]] = None,
|
|
67
|
+
stream: bool = True,
|
|
68
|
+
timeout: Optional[float] = None,
|
|
69
|
+
) -> Iterator[dict] | dict:
|
|
70
|
+
"""Use legacy Chat V1 to pin an unpublished agent version.
|
|
71
|
+
|
|
72
|
+
Chat V2's API-key external flow only accepts the published ``agent_id``.
|
|
73
|
+
Keep this low-level compatibility helper on V1 until V2 supports external
|
|
74
|
+
``agent_history_id`` pinning. It remains streaming by default.
|
|
75
|
+
"""
|
|
76
|
+
body: dict[str, Any] = {
|
|
77
|
+
"message": message,
|
|
78
|
+
"agent_history_id": agent_history_id,
|
|
79
|
+
"stream": stream,
|
|
80
|
+
}
|
|
81
|
+
if attachment_ids:
|
|
82
|
+
body["attachment_ids"] = attachment_ids
|
|
83
|
+
|
|
84
|
+
path = f"/chats/{chat_id}/messages"
|
|
85
|
+
if stream:
|
|
86
|
+
return client.stream_sse(
|
|
87
|
+
"POST",
|
|
88
|
+
path,
|
|
89
|
+
json=body,
|
|
90
|
+
timeout=timeout,
|
|
91
|
+
)
|
|
92
|
+
return client.post(path, json=body, timeout=timeout)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def collect_stream(events: Iterator[dict]) -> dict:
|
|
96
|
+
"""Collect a Chat V2 SSE stream and require an explicit completion event."""
|
|
97
|
+
raw_events: list[dict] = []
|
|
98
|
+
parts: list[dict] = []
|
|
99
|
+
interactions: list[dict] = []
|
|
100
|
+
text_deltas: list[str] = []
|
|
101
|
+
final_text: str | None = None
|
|
102
|
+
response_id: str | None = None
|
|
103
|
+
conversation_group_id: str | None = None
|
|
104
|
+
updated_title: str | None = None
|
|
105
|
+
completed = False
|
|
106
|
+
|
|
107
|
+
for event in events:
|
|
108
|
+
data = event.get("data")
|
|
109
|
+
if data == "[DONE]":
|
|
110
|
+
continue
|
|
111
|
+
raw_events.append(event)
|
|
112
|
+
if not isinstance(data, dict):
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
event_type = str(data.get("type") or event.get("event") or "message")
|
|
116
|
+
response_id = str(data.get("response_id") or response_id or "") or None
|
|
117
|
+
conversation_group_id = str(
|
|
118
|
+
data.get("conversation_group_id") or conversation_group_id or ""
|
|
119
|
+
) or None
|
|
120
|
+
|
|
121
|
+
if event_type == "response.part.delta":
|
|
122
|
+
delta = data.get("delta")
|
|
123
|
+
if data.get("part_kind") == "text" and isinstance(delta, str):
|
|
124
|
+
text_deltas.append(delta)
|
|
125
|
+
elif event_type in ("response.part.created", "response.part.completed"):
|
|
126
|
+
part = data.get("part")
|
|
127
|
+
if isinstance(part, dict):
|
|
128
|
+
parts.append({"event": event_type, "part": part})
|
|
129
|
+
content = part.get("content")
|
|
130
|
+
if (
|
|
131
|
+
event_type == "response.part.completed"
|
|
132
|
+
and part.get("part_kind") == "text"
|
|
133
|
+
and isinstance(content, dict)
|
|
134
|
+
and isinstance(content.get("content"), str)
|
|
135
|
+
):
|
|
136
|
+
final_text = content["content"]
|
|
137
|
+
elif event_type in ("response.interaction.created", "response.interaction.resolved"):
|
|
138
|
+
interactions.append(data)
|
|
139
|
+
elif event_type == "response.chat.title.updated":
|
|
140
|
+
name = data.get("name")
|
|
141
|
+
if isinstance(name, str):
|
|
142
|
+
updated_title = name
|
|
143
|
+
elif event_type == "response.failed":
|
|
144
|
+
message = data.get("message") or "Chat V2 stream failed"
|
|
145
|
+
raise TransportError(
|
|
146
|
+
str(message),
|
|
147
|
+
{
|
|
148
|
+
"code": data.get("code"),
|
|
149
|
+
"response_id": response_id,
|
|
150
|
+
"conversation_group_id": conversation_group_id,
|
|
151
|
+
"outcome_uncertain": True,
|
|
152
|
+
"events": raw_events,
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
elif event_type == "response.completed":
|
|
156
|
+
completed = True
|
|
157
|
+
|
|
158
|
+
if not completed:
|
|
159
|
+
raise TransportError(
|
|
160
|
+
"Chat V2 stream ended before response.completed. Inspect the history before retrying.",
|
|
161
|
+
{
|
|
162
|
+
"response_id": response_id,
|
|
163
|
+
"conversation_group_id": conversation_group_id,
|
|
164
|
+
"outcome_uncertain": True,
|
|
165
|
+
"events": raw_events,
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
"stream": True,
|
|
171
|
+
"completed": True,
|
|
172
|
+
"response_id": response_id,
|
|
173
|
+
"conversation_group_id": conversation_group_id,
|
|
174
|
+
"final_text": final_text if final_text is not None else "".join(text_deltas),
|
|
175
|
+
"updated_title": updated_title,
|
|
176
|
+
"parts": parts,
|
|
177
|
+
"interactions": interactions,
|
|
178
|
+
"events": raw_events,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def list_messages(
|
|
183
|
+
client: CodeerClient,
|
|
184
|
+
chat_id: int,
|
|
185
|
+
*,
|
|
186
|
+
external_user_id: Optional[str] = None,
|
|
187
|
+
limit: int = 500,
|
|
188
|
+
) -> dict:
|
|
189
|
+
params: dict[str, Any] = {"limit": limit, "offset": 0}
|
|
190
|
+
if external_user_id is not None:
|
|
191
|
+
params["external_user_id"] = external_user_id
|
|
192
|
+
return client.get(
|
|
193
|
+
f"/chats/{chat_id}/messages",
|
|
194
|
+
api_version="v2",
|
|
195
|
+
params=params,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def list_chats(client: CodeerClient) -> list[dict]:
|
|
200
|
+
"""List chats through the legacy v1 endpoint; Chat V2 has no list route."""
|
|
201
|
+
return client.get("/chats")
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
codeer check
|
|
4
4
|
codeer agent list|get|apply|diff|versions
|
|
5
|
+
codeer model list
|
|
5
6
|
codeer kb list|files|upload|node-rename|node-delete|faq-list|faq-get|faq-create|faq-update|faq-delete
|
|
6
7
|
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
8
|
codeer history list|get|conversations|negative-feedback|create|send
|
|
@@ -25,6 +26,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
25
26
|
epilog="""\
|
|
26
27
|
Safe workflow for coding agents:
|
|
27
28
|
codeer check --json
|
|
29
|
+
codeer model list --type text
|
|
28
30
|
codeer agent list
|
|
29
31
|
codeer agent get <agent-id> --full
|
|
30
32
|
codeer kb list
|
|
@@ -55,6 +57,12 @@ Use --out <path> for large raw artifacts; stdout defaults to compact summaries.
|
|
|
55
57
|
|
|
56
58
|
check.register(sub)
|
|
57
59
|
|
|
60
|
+
try:
|
|
61
|
+
from .commands import model as model_cmd
|
|
62
|
+
model_cmd.register(sub)
|
|
63
|
+
except ImportError:
|
|
64
|
+
pass
|
|
65
|
+
|
|
58
66
|
# Phase 2-4: agent, kb, eval commands will register here
|
|
59
67
|
try:
|
|
60
68
|
from .commands import agent as agent_cmd
|