deepflow-openagent-cli 0.1.0__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.
- deepflow_openagent_cli/__init__.py +3 -0
- deepflow_openagent_cli/__main__.py +6 -0
- deepflow_openagent_cli/cli.py +1001 -0
- deepflow_openagent_cli-0.1.0.dist-info/METADATA +68 -0
- deepflow_openagent_cli-0.1.0.dist-info/RECORD +8 -0
- deepflow_openagent_cli-0.1.0.dist-info/WHEEL +5 -0
- deepflow_openagent_cli-0.1.0.dist-info/entry_points.txt +2 -0
- deepflow_openagent_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NewAgent OpenAPI command line client.
|
|
3
|
+
|
|
4
|
+
Run with:
|
|
5
|
+
deepflow_openagent_cli docs list
|
|
6
|
+
python -m deepflow_openagent_cli docs list
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from dataclasses import asdict, dataclass, field
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
from urllib.error import HTTPError, URLError
|
|
18
|
+
from urllib.parse import urlencode
|
|
19
|
+
from urllib.request import Request, urlopen
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
DEFAULT_HOST = "http://localhost:5001"
|
|
23
|
+
ENV_HOST = "NEWAGENT_HOST"
|
|
24
|
+
ENV_API_KEY = "NEWAGENT_API_KEY"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CliError(Exception):
|
|
28
|
+
"""Expected CLI error with a user-facing message."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class ParamDoc:
|
|
33
|
+
name: str
|
|
34
|
+
type: str
|
|
35
|
+
required: bool
|
|
36
|
+
description: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class EndpointDoc:
|
|
41
|
+
key: str
|
|
42
|
+
method: str
|
|
43
|
+
path: str
|
|
44
|
+
scope: str | None
|
|
45
|
+
summary: str
|
|
46
|
+
path_params: list[ParamDoc] = field(default_factory=list)
|
|
47
|
+
query_params: list[ParamDoc] = field(default_factory=list)
|
|
48
|
+
body_fields: list[ParamDoc] = field(default_factory=list)
|
|
49
|
+
response: str = ""
|
|
50
|
+
errors: list[str] = field(default_factory=list)
|
|
51
|
+
example: str = ""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _param(name: str, type_: str, required: bool, description: str) -> ParamDoc:
|
|
55
|
+
return ParamDoc(name=name, type=type_, required=required, description=description)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
ENDPOINTS: tuple[EndpointDoc, ...] = (
|
|
59
|
+
EndpointDoc(
|
|
60
|
+
key="knowledge.search",
|
|
61
|
+
method="POST",
|
|
62
|
+
path="/api/v1/knowledge-bases/{kb_id}/search",
|
|
63
|
+
scope="chat",
|
|
64
|
+
summary="Search slices in a knowledge base with hybrid, BM25, vector, optional reranker, and permission context.",
|
|
65
|
+
path_params=[_param("kb_id", "integer", True, "Knowledge base ID.")],
|
|
66
|
+
body_fields=[
|
|
67
|
+
_param("query", "string", True, "Search query, 1-512 characters."),
|
|
68
|
+
_param("filter", "object", False, "Optional doc_ids, doc_meta, and slice_meta filter AST."),
|
|
69
|
+
_param("search", "object", False, "Search config: mode hybrid/bm25/vector and weights."),
|
|
70
|
+
_param("reranker", "object", False, "Optional reranker config."),
|
|
71
|
+
_param("highlight", "object", False, "Optional highlight config."),
|
|
72
|
+
_param("pagination", "object", False, "Pagination config: limit 1-500, offset >= 0."),
|
|
73
|
+
_param("subject_context", "object", False, "Optional customer context for permission evaluation."),
|
|
74
|
+
],
|
|
75
|
+
response="JSON object with total and items[].",
|
|
76
|
+
errors=["401 missing or invalid API key", "404 knowledge base not found", "422 validation error"],
|
|
77
|
+
example="deepflow_openagent_cli knowledge search 1 --query 'pricing'",
|
|
78
|
+
),
|
|
79
|
+
EndpointDoc(
|
|
80
|
+
key="knowledge.markdown",
|
|
81
|
+
method="GET",
|
|
82
|
+
path="/api/v1/knowledge-bases/{kb_id}/documents/{doc_id}/markdown",
|
|
83
|
+
scope="chat",
|
|
84
|
+
summary="Fetch readable Markdown content for a document.",
|
|
85
|
+
path_params=[
|
|
86
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
87
|
+
_param("doc_id", "integer", True, "Document ID."),
|
|
88
|
+
],
|
|
89
|
+
response="Markdown text.",
|
|
90
|
+
errors=["401 missing or invalid API key", "404 document not found"],
|
|
91
|
+
example="deepflow_openagent_cli knowledge markdown 1 20 --raw",
|
|
92
|
+
),
|
|
93
|
+
EndpointDoc(
|
|
94
|
+
key="conversations.create",
|
|
95
|
+
method="POST",
|
|
96
|
+
path="/api/v1/agents/{agent_id}/conversations",
|
|
97
|
+
scope="chat",
|
|
98
|
+
summary="Create a conversation for an agent. tenant_id is injected by the server from the API key.",
|
|
99
|
+
path_params=[_param("agent_id", "integer", True, "Agent ID.")],
|
|
100
|
+
body_fields=[
|
|
101
|
+
_param("agent_id", "integer", True, "Agent ID. The CLI fills this from the path when omitted."),
|
|
102
|
+
_param("external_user_id", "string", False, "External user ID, max 128 chars."),
|
|
103
|
+
_param("source", "string", False, "chat, api, or embed. Defaults to api in the CLI."),
|
|
104
|
+
_param("title", "string", False, "Conversation title."),
|
|
105
|
+
_param("display_name", "string", False, "Customer display name."),
|
|
106
|
+
_param("email", "string", False, "Customer email."),
|
|
107
|
+
_param("phone", "string", False, "Customer phone."),
|
|
108
|
+
_param("avatar_url", "string", False, "Customer avatar URL."),
|
|
109
|
+
_param("metadata", "object", False, "Arbitrary metadata."),
|
|
110
|
+
],
|
|
111
|
+
response="ConversationResponse.",
|
|
112
|
+
errors=["401 missing or invalid API key", "404 agent not found", "422 validation error"],
|
|
113
|
+
example="deepflow_openagent_cli conversations create 1 --title 'Support' --source api",
|
|
114
|
+
),
|
|
115
|
+
EndpointDoc(
|
|
116
|
+
key="conversations.list",
|
|
117
|
+
method="GET",
|
|
118
|
+
path="/api/v1/agents/{agent_id}/conversations",
|
|
119
|
+
scope="chat",
|
|
120
|
+
summary="List conversations for an agent with pagination and filters.",
|
|
121
|
+
path_params=[_param("agent_id", "integer", True, "Agent ID.")],
|
|
122
|
+
query_params=[
|
|
123
|
+
_param("page", "integer", False, "Page number."),
|
|
124
|
+
_param("per_page", "integer", False, "Items per page."),
|
|
125
|
+
_param("start_time", "string", False, "Start time filter."),
|
|
126
|
+
_param("end_time", "string", False, "End time filter."),
|
|
127
|
+
_param("status_filter", "string", False, "Conversation status."),
|
|
128
|
+
_param("source", "string", False, "chat, api, or embed."),
|
|
129
|
+
_param("conversation_id", "string", False, "Conversation external ID filter."),
|
|
130
|
+
_param("external_user_id", "string", False, "External user ID filter."),
|
|
131
|
+
_param("search", "string", False, "Keyword search."),
|
|
132
|
+
],
|
|
133
|
+
response="ConversationListResponse.",
|
|
134
|
+
errors=["401 missing or invalid API key", "404 agent not found"],
|
|
135
|
+
example="deepflow_openagent_cli conversations list 1 --source api --page 1 --per-page 10",
|
|
136
|
+
),
|
|
137
|
+
EndpointDoc(
|
|
138
|
+
key="conversations.get",
|
|
139
|
+
method="GET",
|
|
140
|
+
path="/api/v1/agents/{agent_id}/conversations/{conversation_id}",
|
|
141
|
+
scope="chat",
|
|
142
|
+
summary="Get conversation detail.",
|
|
143
|
+
path_params=[
|
|
144
|
+
_param("agent_id", "integer", True, "Agent ID."),
|
|
145
|
+
_param("conversation_id", "integer", True, "Conversation ID."),
|
|
146
|
+
],
|
|
147
|
+
response="ConversationDetailResponse.",
|
|
148
|
+
errors=["401 missing or invalid API key", "404 conversation not found"],
|
|
149
|
+
example="deepflow_openagent_cli conversations get 1 42",
|
|
150
|
+
),
|
|
151
|
+
EndpointDoc(
|
|
152
|
+
key="conversations.end",
|
|
153
|
+
method="POST",
|
|
154
|
+
path="/api/v1/agents/{agent_id}/conversations/{conversation_id}/end",
|
|
155
|
+
scope="chat",
|
|
156
|
+
summary="Mark a conversation as ended.",
|
|
157
|
+
path_params=[
|
|
158
|
+
_param("agent_id", "integer", True, "Agent ID."),
|
|
159
|
+
_param("conversation_id", "integer", True, "Conversation ID."),
|
|
160
|
+
],
|
|
161
|
+
response="ConversationResponse.",
|
|
162
|
+
errors=["401 missing or invalid API key", "404 conversation not found"],
|
|
163
|
+
example="deepflow_openagent_cli conversations end 1 42",
|
|
164
|
+
),
|
|
165
|
+
EndpointDoc(
|
|
166
|
+
key="chat.stream",
|
|
167
|
+
method="POST",
|
|
168
|
+
path="/api/v1/agents/{agent_id}/chat",
|
|
169
|
+
scope="chat",
|
|
170
|
+
summary="Send a message to an agent and stream Server-Sent Events.",
|
|
171
|
+
path_params=[_param("agent_id", "integer", True, "Agent ID.")],
|
|
172
|
+
body_fields=[
|
|
173
|
+
_param("message", "string", True, "User message, 1-32000 characters."),
|
|
174
|
+
_param("conversation_id", "integer", False, "Existing conversation ID for continuation."),
|
|
175
|
+
_param("conversation_external_id", "string", False, "External conversation ID for log correlation."),
|
|
176
|
+
_param("request_id", "string", False, "Client request ID for log correlation."),
|
|
177
|
+
_param("customer_context", "object", False, "Customer context used only when creating a conversation."),
|
|
178
|
+
_param("resume", "boolean", False, "Resume an interrupted stream."),
|
|
179
|
+
],
|
|
180
|
+
response="text/event-stream with conversation_created, content, thinking, tool_call, tool_result, done, and error events.",
|
|
181
|
+
errors=["401 missing or invalid API key", "404 agent not found", "422 validation error"],
|
|
182
|
+
example="deepflow_openagent_cli chat 1 --message 'Hello' --request-id req_001",
|
|
183
|
+
),
|
|
184
|
+
EndpointDoc(
|
|
185
|
+
key="steps.list",
|
|
186
|
+
method="GET",
|
|
187
|
+
path="/api/v1/agents/{agent_id}/conversations/{conversation_id}/steps",
|
|
188
|
+
scope="chat",
|
|
189
|
+
summary="List execution timeline steps for a conversation.",
|
|
190
|
+
path_params=[
|
|
191
|
+
_param("agent_id", "integer", True, "Agent ID."),
|
|
192
|
+
_param("conversation_id", "integer", True, "Conversation ID."),
|
|
193
|
+
],
|
|
194
|
+
response="Object with conversation_id, total_steps, and steps[].",
|
|
195
|
+
errors=["401 missing or invalid API key", "404 conversation not found"],
|
|
196
|
+
example="deepflow_openagent_cli steps 1 42",
|
|
197
|
+
),
|
|
198
|
+
EndpointDoc(
|
|
199
|
+
key="agents.list",
|
|
200
|
+
method="GET",
|
|
201
|
+
path="/api/v1/agents",
|
|
202
|
+
scope="config",
|
|
203
|
+
summary="List agents for the current tenant.",
|
|
204
|
+
query_params=[
|
|
205
|
+
_param("status_filter", "string", False, "active or inactive."),
|
|
206
|
+
_param("page", "integer", False, "Page number."),
|
|
207
|
+
_param("per_page", "integer", False, "Items per page."),
|
|
208
|
+
],
|
|
209
|
+
response="AgentListResponse.",
|
|
210
|
+
errors=["401 missing or invalid API key", "403 scope missing"],
|
|
211
|
+
example="deepflow_openagent_cli agents list --status-filter active",
|
|
212
|
+
),
|
|
213
|
+
EndpointDoc(
|
|
214
|
+
key="agents.create",
|
|
215
|
+
method="POST",
|
|
216
|
+
path="/api/v1/agents",
|
|
217
|
+
scope="config",
|
|
218
|
+
summary="Create an agent for the current tenant.",
|
|
219
|
+
body_fields=[
|
|
220
|
+
_param("name", "string", True, "Agent name, 1-64 chars."),
|
|
221
|
+
_param("description", "string", False, "Agent description, max 256 chars."),
|
|
222
|
+
],
|
|
223
|
+
response="AgentResponse.",
|
|
224
|
+
errors=["401 missing or invalid API key", "403 scope missing", "422 validation error"],
|
|
225
|
+
example="deepflow_openagent_cli agents create --name 'Support Agent'",
|
|
226
|
+
),
|
|
227
|
+
EndpointDoc(
|
|
228
|
+
key="agents.get",
|
|
229
|
+
method="GET",
|
|
230
|
+
path="/api/v1/agents/{agent_id}",
|
|
231
|
+
scope="config",
|
|
232
|
+
summary="Get agent detail including engine_config.",
|
|
233
|
+
path_params=[_param("agent_id", "integer", True, "Agent ID.")],
|
|
234
|
+
response="AgentResponse.",
|
|
235
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 agent not found"],
|
|
236
|
+
example="deepflow_openagent_cli agents get 1",
|
|
237
|
+
),
|
|
238
|
+
EndpointDoc(
|
|
239
|
+
key="agents.update",
|
|
240
|
+
method="PUT",
|
|
241
|
+
path="/api/v1/agents/{agent_id}",
|
|
242
|
+
scope="config",
|
|
243
|
+
summary="Update agent name or description.",
|
|
244
|
+
path_params=[_param("agent_id", "integer", True, "Agent ID.")],
|
|
245
|
+
body_fields=[
|
|
246
|
+
_param("name", "string", False, "Agent name, 1-64 chars."),
|
|
247
|
+
_param("description", "string", False, "Agent description, max 256 chars."),
|
|
248
|
+
],
|
|
249
|
+
response="AgentResponse.",
|
|
250
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 agent not found", "422 validation error"],
|
|
251
|
+
example="deepflow_openagent_cli agents update 1 --name 'New name'",
|
|
252
|
+
),
|
|
253
|
+
EndpointDoc(
|
|
254
|
+
key="agents.status",
|
|
255
|
+
method="PUT",
|
|
256
|
+
path="/api/v1/agents/{agent_id}/status",
|
|
257
|
+
scope="config",
|
|
258
|
+
summary="Set agent status to active or inactive.",
|
|
259
|
+
path_params=[_param("agent_id", "integer", True, "Agent ID.")],
|
|
260
|
+
body_fields=[_param("status", "string", True, "active or inactive.")],
|
|
261
|
+
response="AgentResponse.",
|
|
262
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 agent not found", "422 validation error"],
|
|
263
|
+
example="deepflow_openagent_cli agents status 1 inactive",
|
|
264
|
+
),
|
|
265
|
+
EndpointDoc(
|
|
266
|
+
key="agents.engine_config",
|
|
267
|
+
method="PUT",
|
|
268
|
+
path="/api/v1/agents/{agent_id}/engine-config",
|
|
269
|
+
scope="config",
|
|
270
|
+
summary="Partially update system_prompt, model, selected_tool_ids, context, or pre_recall.",
|
|
271
|
+
path_params=[_param("agent_id", "integer", True, "Agent ID.")],
|
|
272
|
+
body_fields=[
|
|
273
|
+
_param("system_prompt", "string", False, "System prompt, max 10000 chars."),
|
|
274
|
+
_param("model", "object", False, "Model config."),
|
|
275
|
+
_param("selected_tool_ids", "array[integer]", False, "Enabled tool IDs."),
|
|
276
|
+
_param("context", "object", False, "Context policy."),
|
|
277
|
+
_param("pre_recall", "object", False, "Pre-recall config."),
|
|
278
|
+
],
|
|
279
|
+
response="AgentResponse with updated engine_config.",
|
|
280
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 agent not found", "422 validation error"],
|
|
281
|
+
example="deepflow_openagent_cli agents engine-config 1 --json '{\"system_prompt\":\"You are helpful.\"}'",
|
|
282
|
+
),
|
|
283
|
+
EndpointDoc(
|
|
284
|
+
key="tools.list",
|
|
285
|
+
method="GET",
|
|
286
|
+
path="/api/v1/agents/{agent_id}/tools",
|
|
287
|
+
scope="config",
|
|
288
|
+
summary="List system and custom tools for an agent.",
|
|
289
|
+
path_params=[_param("agent_id", "integer", True, "Agent ID.")],
|
|
290
|
+
response="AgentToolListResponse.",
|
|
291
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 agent not found"],
|
|
292
|
+
example="deepflow_openagent_cli tools list 1",
|
|
293
|
+
),
|
|
294
|
+
EndpointDoc(
|
|
295
|
+
key="tools.create",
|
|
296
|
+
method="POST",
|
|
297
|
+
path="/api/v1/agents/{agent_id}/tools",
|
|
298
|
+
scope="config",
|
|
299
|
+
summary="Create a custom tool for an agent.",
|
|
300
|
+
path_params=[_param("agent_id", "integer", True, "Agent ID.")],
|
|
301
|
+
body_fields=[
|
|
302
|
+
_param("name", "string", True, "Tool name, 1-128 chars."),
|
|
303
|
+
_param("description", "string", False, "Tool description."),
|
|
304
|
+
_param("tool_type", "string", True, "search, doc_query, notebook, tool_response_fetch, or python_code."),
|
|
305
|
+
_param("config", "object", False, "Tool config."),
|
|
306
|
+
],
|
|
307
|
+
response="AgentToolResponse.",
|
|
308
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 agent not found", "422 validation error"],
|
|
309
|
+
example="deepflow_openagent_cli tools create 1 --name Search --tool-type search --json '{\"config\":{\"kb_ids\":[1]}}'",
|
|
310
|
+
),
|
|
311
|
+
EndpointDoc(
|
|
312
|
+
key="tools.get",
|
|
313
|
+
method="GET",
|
|
314
|
+
path="/api/v1/agents/{agent_id}/tools/{tool_id}",
|
|
315
|
+
scope="config",
|
|
316
|
+
summary="Get tool detail.",
|
|
317
|
+
path_params=[
|
|
318
|
+
_param("agent_id", "integer", True, "Agent ID."),
|
|
319
|
+
_param("tool_id", "integer", True, "Tool ID."),
|
|
320
|
+
],
|
|
321
|
+
response="AgentToolResponse.",
|
|
322
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 tool not found"],
|
|
323
|
+
example="deepflow_openagent_cli tools get 1 2",
|
|
324
|
+
),
|
|
325
|
+
EndpointDoc(
|
|
326
|
+
key="tools.update",
|
|
327
|
+
method="PUT",
|
|
328
|
+
path="/api/v1/agents/{agent_id}/tools/{tool_id}",
|
|
329
|
+
scope="config",
|
|
330
|
+
summary="Update a custom tool. System tools cannot be modified.",
|
|
331
|
+
path_params=[
|
|
332
|
+
_param("agent_id", "integer", True, "Agent ID."),
|
|
333
|
+
_param("tool_id", "integer", True, "Tool ID."),
|
|
334
|
+
],
|
|
335
|
+
body_fields=[
|
|
336
|
+
_param("name", "string", False, "Tool name."),
|
|
337
|
+
_param("description", "string", False, "Tool description."),
|
|
338
|
+
_param("config", "object", False, "Tool config."),
|
|
339
|
+
],
|
|
340
|
+
response="AgentToolResponse.",
|
|
341
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 tool not found", "422 validation error"],
|
|
342
|
+
example="deepflow_openagent_cli tools update 1 2 --json '{\"config\":{\"kb_ids\":[1,2]}}'",
|
|
343
|
+
),
|
|
344
|
+
EndpointDoc(
|
|
345
|
+
key="tools.toggle",
|
|
346
|
+
method="PUT",
|
|
347
|
+
path="/api/v1/agents/{agent_id}/tools/{tool_id}/toggle",
|
|
348
|
+
scope="config",
|
|
349
|
+
summary="Enable or disable a tool.",
|
|
350
|
+
path_params=[
|
|
351
|
+
_param("agent_id", "integer", True, "Agent ID."),
|
|
352
|
+
_param("tool_id", "integer", True, "Tool ID."),
|
|
353
|
+
],
|
|
354
|
+
body_fields=[_param("is_enabled", "boolean", True, "true to enable, false to disable.")],
|
|
355
|
+
response="AgentToolResponse.",
|
|
356
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 tool not found"],
|
|
357
|
+
example="deepflow_openagent_cli tools toggle 1 2 false",
|
|
358
|
+
),
|
|
359
|
+
EndpointDoc(
|
|
360
|
+
key="tools.delete",
|
|
361
|
+
method="DELETE",
|
|
362
|
+
path="/api/v1/agents/{agent_id}/tools/{tool_id}",
|
|
363
|
+
scope="config",
|
|
364
|
+
summary="Delete a custom tool. System tools cannot be deleted.",
|
|
365
|
+
path_params=[
|
|
366
|
+
_param("agent_id", "integer", True, "Agent ID."),
|
|
367
|
+
_param("tool_id", "integer", True, "Tool ID."),
|
|
368
|
+
],
|
|
369
|
+
response='{"message": "Tool removed successfully"}',
|
|
370
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 tool not found", "422 system tool cannot be deleted"],
|
|
371
|
+
example="deepflow_openagent_cli tools delete 1 2",
|
|
372
|
+
),
|
|
373
|
+
EndpointDoc(
|
|
374
|
+
key="tools.execute",
|
|
375
|
+
method="POST",
|
|
376
|
+
path="/api/v1/agents/{agent_id}/tools/{tool_id}/execute",
|
|
377
|
+
scope="config",
|
|
378
|
+
summary="Debug execute a tool with arbitrary JSON arguments.",
|
|
379
|
+
path_params=[
|
|
380
|
+
_param("agent_id", "integer", True, "Agent ID."),
|
|
381
|
+
_param("tool_id", "integer", True, "Tool ID."),
|
|
382
|
+
],
|
|
383
|
+
query_params=[_param("conversation_id", "integer", False, "Conversation ID for tool context.")],
|
|
384
|
+
body_fields=[_param("*", "object", False, "Tool-specific arguments.")],
|
|
385
|
+
response="ToolExecuteResponse.",
|
|
386
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 tool not found", "422 validation error"],
|
|
387
|
+
example="deepflow_openagent_cli tools execute 1 2 --json '{\"query\":\"pricing\"}'",
|
|
388
|
+
),
|
|
389
|
+
EndpointDoc(
|
|
390
|
+
key="channels.list",
|
|
391
|
+
method="GET",
|
|
392
|
+
path="/api/v1/channels",
|
|
393
|
+
scope="config",
|
|
394
|
+
summary="List Web SDK channels for the current tenant.",
|
|
395
|
+
query_params=[
|
|
396
|
+
_param("page", "integer", False, "Page number."),
|
|
397
|
+
_param("per_page", "integer", False, "Items per page."),
|
|
398
|
+
],
|
|
399
|
+
response="ChannelListResponse.",
|
|
400
|
+
errors=["401 missing or invalid API key", "403 scope missing"],
|
|
401
|
+
example="deepflow_openagent_cli channels list --page 1 --per-page 10",
|
|
402
|
+
),
|
|
403
|
+
EndpointDoc(
|
|
404
|
+
key="channels.create",
|
|
405
|
+
method="POST",
|
|
406
|
+
path="/api/v1/channels",
|
|
407
|
+
scope="config",
|
|
408
|
+
summary="Create a Web SDK channel. tenant_id is injected by the server.",
|
|
409
|
+
body_fields=[
|
|
410
|
+
_param("name", "string", True, "Channel name, 1-64 chars."),
|
|
411
|
+
_param("description", "string", False, "Description, max 500 chars."),
|
|
412
|
+
_param("channel_type", "string", False, "Defaults to web-sdk."),
|
|
413
|
+
_param("agent_id", "integer", False, "Bound agent ID."),
|
|
414
|
+
_param("access_mode", "string", False, "url or embed."),
|
|
415
|
+
_param("config", "object", False, "Appearance and behavior config."),
|
|
416
|
+
],
|
|
417
|
+
response="ChannelResponse.",
|
|
418
|
+
errors=["400 duplicate name", "401 missing or invalid API key", "403 scope missing", "422 validation error"],
|
|
419
|
+
example="deepflow_openagent_cli channels create --name Website --agent-id 1 --access-mode embed",
|
|
420
|
+
),
|
|
421
|
+
EndpointDoc(
|
|
422
|
+
key="channels.get",
|
|
423
|
+
method="GET",
|
|
424
|
+
path="/api/v1/channels/{channel_id}",
|
|
425
|
+
scope="config",
|
|
426
|
+
summary="Get channel detail.",
|
|
427
|
+
path_params=[_param("channel_id", "integer", True, "Channel ID.")],
|
|
428
|
+
response="ChannelResponse.",
|
|
429
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 channel not found"],
|
|
430
|
+
example="deepflow_openagent_cli channels get 1",
|
|
431
|
+
),
|
|
432
|
+
EndpointDoc(
|
|
433
|
+
key="channels.update",
|
|
434
|
+
method="PUT",
|
|
435
|
+
path="/api/v1/channels/{channel_id}",
|
|
436
|
+
scope="config",
|
|
437
|
+
summary="Update channel name, description, agent_id, access_mode, or config.",
|
|
438
|
+
path_params=[_param("channel_id", "integer", True, "Channel ID.")],
|
|
439
|
+
body_fields=[
|
|
440
|
+
_param("name", "string", False, "Channel name."),
|
|
441
|
+
_param("description", "string", False, "Channel description."),
|
|
442
|
+
_param("agent_id", "integer", False, "Bound agent ID."),
|
|
443
|
+
_param("access_mode", "string", False, "url or embed."),
|
|
444
|
+
_param("config", "object", False, "Appearance and behavior config."),
|
|
445
|
+
],
|
|
446
|
+
response="ChannelResponse.",
|
|
447
|
+
errors=["400 duplicate name", "401 missing or invalid API key", "403 scope missing", "404 channel not found", "422 validation error"],
|
|
448
|
+
example="deepflow_openagent_cli channels update 1 --access-mode url",
|
|
449
|
+
),
|
|
450
|
+
EndpointDoc(
|
|
451
|
+
key="channels.delete",
|
|
452
|
+
method="DELETE",
|
|
453
|
+
path="/api/v1/channels/{channel_id}",
|
|
454
|
+
scope="config",
|
|
455
|
+
summary="Delete a channel.",
|
|
456
|
+
path_params=[_param("channel_id", "integer", True, "Channel ID.")],
|
|
457
|
+
response='{"message": "Deleted successfully"}',
|
|
458
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 channel not found"],
|
|
459
|
+
example="deepflow_openagent_cli channels delete 1",
|
|
460
|
+
),
|
|
461
|
+
EndpointDoc(
|
|
462
|
+
key="channels.secret_key",
|
|
463
|
+
method="POST",
|
|
464
|
+
path="/api/v1/channels/{channel_id}/secret-key",
|
|
465
|
+
scope="config",
|
|
466
|
+
summary="Generate or rotate the channel secret key for embed token signing.",
|
|
467
|
+
path_params=[_param("channel_id", "integer", True, "Channel ID.")],
|
|
468
|
+
response="ChannelResponse with secret_key.",
|
|
469
|
+
errors=["401 missing or invalid API key", "403 scope missing", "404 channel not found"],
|
|
470
|
+
example="deepflow_openagent_cli channels secret-key 1",
|
|
471
|
+
),
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
ENDPOINT_BY_KEY = {endpoint.key: endpoint for endpoint in ENDPOINTS}
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
class OpenApiClient:
|
|
478
|
+
"""Small HTTP client for NewAgent OpenAPI endpoints."""
|
|
479
|
+
|
|
480
|
+
def __init__(self, host: str, api_key: str | None) -> None:
|
|
481
|
+
self.host = host.rstrip("/")
|
|
482
|
+
self.api_key = api_key
|
|
483
|
+
|
|
484
|
+
def request(
|
|
485
|
+
self,
|
|
486
|
+
endpoint_key: str,
|
|
487
|
+
path_params: dict[str, Any] | None = None,
|
|
488
|
+
query: dict[str, Any] | None = None,
|
|
489
|
+
body: dict[str, Any] | None = None,
|
|
490
|
+
*,
|
|
491
|
+
stream: bool = False,
|
|
492
|
+
) -> bytes | None:
|
|
493
|
+
endpoint = get_endpoint(endpoint_key)
|
|
494
|
+
if endpoint.scope and not self.api_key:
|
|
495
|
+
raise CliError(f"Missing API key. Set {ENV_API_KEY} or pass --api-key.")
|
|
496
|
+
|
|
497
|
+
url = self._build_url(endpoint, path_params or {}, query or {})
|
|
498
|
+
data = None
|
|
499
|
+
headers = {"Accept": "text/event-stream" if stream else "application/json"}
|
|
500
|
+
if self.api_key:
|
|
501
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
502
|
+
if body is not None:
|
|
503
|
+
data = json.dumps(body, ensure_ascii=False).encode("utf-8")
|
|
504
|
+
headers["Content-Type"] = "application/json"
|
|
505
|
+
|
|
506
|
+
request = Request(url, data=data, headers=headers, method=endpoint.method)
|
|
507
|
+
try:
|
|
508
|
+
with urlopen(request) as response: # noqa: S310 - CLI calls user-provided API host.
|
|
509
|
+
if stream:
|
|
510
|
+
for line in response:
|
|
511
|
+
sys.stdout.buffer.write(line)
|
|
512
|
+
sys.stdout.buffer.flush()
|
|
513
|
+
return None
|
|
514
|
+
return response.read()
|
|
515
|
+
except HTTPError as exc:
|
|
516
|
+
payload = exc.read().decode("utf-8", errors="replace")
|
|
517
|
+
raise CliError(f"HTTP {exc.code} for {endpoint_key}: {payload}") from exc
|
|
518
|
+
except URLError as exc:
|
|
519
|
+
raise CliError(f"Failed to connect to {url}: {exc.reason}") from exc
|
|
520
|
+
|
|
521
|
+
def _build_url(self, endpoint: EndpointDoc, path_params: dict[str, Any], query: dict[str, Any]) -> str:
|
|
522
|
+
path = endpoint.path
|
|
523
|
+
for param in endpoint.path_params:
|
|
524
|
+
if param.name not in path_params:
|
|
525
|
+
raise CliError(f"Missing path parameter: {param.name}")
|
|
526
|
+
path = path.replace("{" + param.name + "}", str(path_params[param.name]))
|
|
527
|
+
|
|
528
|
+
clean_query = {
|
|
529
|
+
key: value
|
|
530
|
+
for key, value in query.items()
|
|
531
|
+
if value is not None and value is not False
|
|
532
|
+
}
|
|
533
|
+
suffix = f"?{urlencode(clean_query, doseq=True)}" if clean_query else ""
|
|
534
|
+
return f"{self.host}{path}{suffix}"
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def get_endpoint(key: str) -> EndpointDoc:
|
|
538
|
+
try:
|
|
539
|
+
return ENDPOINT_BY_KEY[key]
|
|
540
|
+
except KeyError as exc:
|
|
541
|
+
choices = ", ".join(sorted(ENDPOINT_BY_KEY))
|
|
542
|
+
raise CliError(f"Unknown endpoint '{key}'. Available endpoints: {choices}") from exc
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def load_json_payload(json_text: str | None, json_file: str | None) -> dict[str, Any]:
|
|
546
|
+
"""Load an object JSON payload from text or file."""
|
|
547
|
+
if json_text and json_file:
|
|
548
|
+
raise CliError("--json and --json-file cannot be used together.")
|
|
549
|
+
if not json_text and not json_file:
|
|
550
|
+
return {}
|
|
551
|
+
source = json_text
|
|
552
|
+
if json_file:
|
|
553
|
+
source = Path(json_file).read_text(encoding="utf-8")
|
|
554
|
+
try:
|
|
555
|
+
payload = json.loads(source or "{}")
|
|
556
|
+
except json.JSONDecodeError as exc:
|
|
557
|
+
raise CliError(f"Invalid JSON at line {exc.lineno}, column {exc.colno}: {exc.msg}") from exc
|
|
558
|
+
if not isinstance(payload, dict):
|
|
559
|
+
raise CliError("JSON payload must be an object.")
|
|
560
|
+
return payload
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def merge_payload(args: argparse.Namespace, extra: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
564
|
+
payload = load_json_payload(getattr(args, "json", None), getattr(args, "json_file", None))
|
|
565
|
+
for key, value in (extra or {}).items():
|
|
566
|
+
if value is not None:
|
|
567
|
+
payload[key] = value
|
|
568
|
+
return payload
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def print_output(data: bytes | None, *, pretty: bool, raw: bool) -> None:
|
|
572
|
+
if data is None:
|
|
573
|
+
return
|
|
574
|
+
text = data.decode("utf-8", errors="replace")
|
|
575
|
+
if raw:
|
|
576
|
+
print(text, end="" if text.endswith("\n") else "\n")
|
|
577
|
+
return
|
|
578
|
+
try:
|
|
579
|
+
parsed = json.loads(text)
|
|
580
|
+
except json.JSONDecodeError:
|
|
581
|
+
print(text, end="" if text.endswith("\n") else "\n")
|
|
582
|
+
return
|
|
583
|
+
if pretty:
|
|
584
|
+
print(json.dumps(parsed, ensure_ascii=False, indent=2))
|
|
585
|
+
else:
|
|
586
|
+
print(json.dumps(parsed, ensure_ascii=False, separators=(",", ":")))
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def endpoint_as_dict(endpoint: EndpointDoc) -> dict[str, Any]:
|
|
590
|
+
return asdict(endpoint)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def handle_docs(args: argparse.Namespace) -> int:
|
|
594
|
+
if args.docs_action == "list":
|
|
595
|
+
items = [
|
|
596
|
+
{
|
|
597
|
+
"key": item.key,
|
|
598
|
+
"method": item.method,
|
|
599
|
+
"path": item.path,
|
|
600
|
+
"scope": item.scope,
|
|
601
|
+
"summary": item.summary,
|
|
602
|
+
}
|
|
603
|
+
for item in ENDPOINTS
|
|
604
|
+
]
|
|
605
|
+
print(json.dumps(items, ensure_ascii=False, indent=2 if args.pretty else None))
|
|
606
|
+
return 0
|
|
607
|
+
|
|
608
|
+
endpoint = get_endpoint(args.endpoint)
|
|
609
|
+
doc = endpoint_as_dict(endpoint)
|
|
610
|
+
if args.json_doc:
|
|
611
|
+
print(json.dumps(doc, ensure_ascii=False, indent=2 if args.pretty else None))
|
|
612
|
+
return 0
|
|
613
|
+
|
|
614
|
+
print(f"{endpoint.key}")
|
|
615
|
+
print(f" {endpoint.method} {endpoint.path}")
|
|
616
|
+
print(f" scope: {endpoint.scope or 'none'}")
|
|
617
|
+
print(f" summary: {endpoint.summary}")
|
|
618
|
+
if endpoint.path_params:
|
|
619
|
+
print(" path params:")
|
|
620
|
+
for item in endpoint.path_params:
|
|
621
|
+
print(f" - {item.name} ({item.type}, required={item.required}): {item.description}")
|
|
622
|
+
if endpoint.query_params:
|
|
623
|
+
print(" query params:")
|
|
624
|
+
for item in endpoint.query_params:
|
|
625
|
+
print(f" - {item.name} ({item.type}, required={item.required}): {item.description}")
|
|
626
|
+
if endpoint.body_fields:
|
|
627
|
+
print(" body fields:")
|
|
628
|
+
for item in endpoint.body_fields:
|
|
629
|
+
print(f" - {item.name} ({item.type}, required={item.required}): {item.description}")
|
|
630
|
+
if endpoint.response:
|
|
631
|
+
print(f" response: {endpoint.response}")
|
|
632
|
+
if endpoint.errors:
|
|
633
|
+
print(" errors:")
|
|
634
|
+
for item in endpoint.errors:
|
|
635
|
+
print(f" - {item}")
|
|
636
|
+
if endpoint.example:
|
|
637
|
+
print(f" example: {endpoint.example}")
|
|
638
|
+
return 0
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def client_from_args(args: argparse.Namespace) -> OpenApiClient:
|
|
642
|
+
host = args.host or os.getenv(ENV_HOST) or DEFAULT_HOST
|
|
643
|
+
api_key = args.api_key or os.getenv(ENV_API_KEY)
|
|
644
|
+
return OpenApiClient(host=host, api_key=api_key)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def run_request(
|
|
648
|
+
args: argparse.Namespace,
|
|
649
|
+
endpoint_key: str,
|
|
650
|
+
path_params: dict[str, Any] | None = None,
|
|
651
|
+
query: dict[str, Any] | None = None,
|
|
652
|
+
body: dict[str, Any] | None = None,
|
|
653
|
+
*,
|
|
654
|
+
stream: bool = False,
|
|
655
|
+
) -> int:
|
|
656
|
+
data = client_from_args(args).request(
|
|
657
|
+
endpoint_key,
|
|
658
|
+
path_params=path_params,
|
|
659
|
+
query=query,
|
|
660
|
+
body=body,
|
|
661
|
+
stream=stream,
|
|
662
|
+
)
|
|
663
|
+
print_output(data, pretty=args.pretty, raw=args.raw)
|
|
664
|
+
return 0
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def handle_knowledge(args: argparse.Namespace) -> int:
|
|
668
|
+
if args.knowledge_action == "search":
|
|
669
|
+
body = merge_payload(args, {"query": args.query})
|
|
670
|
+
return run_request(args, "knowledge.search", {"kb_id": args.kb_id}, body=body)
|
|
671
|
+
return run_request(args, "knowledge.markdown", {"kb_id": args.kb_id, "doc_id": args.doc_id})
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def handle_conversations(args: argparse.Namespace) -> int:
|
|
675
|
+
action = args.conversation_action
|
|
676
|
+
if action == "create":
|
|
677
|
+
body = merge_payload(
|
|
678
|
+
args,
|
|
679
|
+
{
|
|
680
|
+
"agent_id": args.agent_id,
|
|
681
|
+
"external_user_id": args.external_user_id,
|
|
682
|
+
"source": args.source,
|
|
683
|
+
"title": args.title,
|
|
684
|
+
"display_name": args.display_name,
|
|
685
|
+
"email": args.email,
|
|
686
|
+
"phone": args.phone,
|
|
687
|
+
"avatar_url": args.avatar_url,
|
|
688
|
+
},
|
|
689
|
+
)
|
|
690
|
+
return run_request(args, "conversations.create", {"agent_id": args.agent_id}, body=body)
|
|
691
|
+
if action == "list":
|
|
692
|
+
query = {
|
|
693
|
+
"page": args.page,
|
|
694
|
+
"per_page": args.per_page,
|
|
695
|
+
"start_time": args.start_time,
|
|
696
|
+
"end_time": args.end_time,
|
|
697
|
+
"status_filter": args.status_filter,
|
|
698
|
+
"source": args.source,
|
|
699
|
+
"conversation_id": args.conversation_external_id,
|
|
700
|
+
"external_user_id": args.external_user_id,
|
|
701
|
+
"search": args.search,
|
|
702
|
+
}
|
|
703
|
+
return run_request(args, "conversations.list", {"agent_id": args.agent_id}, query=query)
|
|
704
|
+
path_params = {"agent_id": args.agent_id, "conversation_id": args.conversation_id}
|
|
705
|
+
return run_request(args, f"conversations.{action}", path_params)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def handle_chat(args: argparse.Namespace) -> int:
|
|
709
|
+
body = merge_payload(
|
|
710
|
+
args,
|
|
711
|
+
{
|
|
712
|
+
"message": args.message,
|
|
713
|
+
"conversation_id": args.conversation_id,
|
|
714
|
+
"conversation_external_id": args.conversation_external_id,
|
|
715
|
+
"request_id": args.request_id,
|
|
716
|
+
"resume": True if args.resume else None,
|
|
717
|
+
},
|
|
718
|
+
)
|
|
719
|
+
return run_request(args, "chat.stream", {"agent_id": args.agent_id}, body=body, stream=True)
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def handle_steps(args: argparse.Namespace) -> int:
|
|
723
|
+
return run_request(
|
|
724
|
+
args,
|
|
725
|
+
"steps.list",
|
|
726
|
+
{"agent_id": args.agent_id, "conversation_id": args.conversation_id},
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def handle_agents(args: argparse.Namespace) -> int:
|
|
731
|
+
action = args.agent_action
|
|
732
|
+
if action == "list":
|
|
733
|
+
return run_request(
|
|
734
|
+
args,
|
|
735
|
+
"agents.list",
|
|
736
|
+
query={"status_filter": args.status_filter, "page": args.page, "per_page": args.per_page},
|
|
737
|
+
)
|
|
738
|
+
if action == "create":
|
|
739
|
+
body = merge_payload(args, {"name": args.name, "description": args.description})
|
|
740
|
+
return run_request(args, "agents.create", body=body)
|
|
741
|
+
if action == "get":
|
|
742
|
+
return run_request(args, "agents.get", {"agent_id": args.agent_id})
|
|
743
|
+
if action == "update":
|
|
744
|
+
body = merge_payload(args, {"name": args.name, "description": args.description})
|
|
745
|
+
return run_request(args, "agents.update", {"agent_id": args.agent_id}, body=body)
|
|
746
|
+
if action == "status":
|
|
747
|
+
return run_request(args, "agents.status", {"agent_id": args.agent_id}, body={"status": args.status})
|
|
748
|
+
body = merge_payload(args)
|
|
749
|
+
return run_request(args, "agents.engine_config", {"agent_id": args.agent_id}, body=body)
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def handle_tools(args: argparse.Namespace) -> int:
|
|
753
|
+
action = args.tool_action
|
|
754
|
+
if action == "list":
|
|
755
|
+
return run_request(args, "tools.list", {"agent_id": args.agent_id})
|
|
756
|
+
if action == "create":
|
|
757
|
+
body = merge_payload(
|
|
758
|
+
args,
|
|
759
|
+
{"name": args.name, "description": args.description, "tool_type": args.tool_type},
|
|
760
|
+
)
|
|
761
|
+
return run_request(args, "tools.create", {"agent_id": args.agent_id}, body=body)
|
|
762
|
+
if action == "get":
|
|
763
|
+
return run_request(args, "tools.get", {"agent_id": args.agent_id, "tool_id": args.tool_id})
|
|
764
|
+
if action == "update":
|
|
765
|
+
body = merge_payload(args, {"name": args.name, "description": args.description})
|
|
766
|
+
return run_request(args, "tools.update", {"agent_id": args.agent_id, "tool_id": args.tool_id}, body=body)
|
|
767
|
+
if action == "toggle":
|
|
768
|
+
return run_request(
|
|
769
|
+
args,
|
|
770
|
+
"tools.toggle",
|
|
771
|
+
{"agent_id": args.agent_id, "tool_id": args.tool_id},
|
|
772
|
+
body={"is_enabled": args.is_enabled},
|
|
773
|
+
)
|
|
774
|
+
if action == "delete":
|
|
775
|
+
return run_request(args, "tools.delete", {"agent_id": args.agent_id, "tool_id": args.tool_id})
|
|
776
|
+
body = merge_payload(args)
|
|
777
|
+
return run_request(
|
|
778
|
+
args,
|
|
779
|
+
"tools.execute",
|
|
780
|
+
{"agent_id": args.agent_id, "tool_id": args.tool_id},
|
|
781
|
+
query={"conversation_id": args.conversation_id},
|
|
782
|
+
body=body,
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def handle_channels(args: argparse.Namespace) -> int:
|
|
787
|
+
action = args.channel_action
|
|
788
|
+
if action == "list":
|
|
789
|
+
return run_request(args, "channels.list", query={"page": args.page, "per_page": args.per_page})
|
|
790
|
+
if action == "create":
|
|
791
|
+
body = merge_payload(
|
|
792
|
+
args,
|
|
793
|
+
{
|
|
794
|
+
"name": args.name,
|
|
795
|
+
"description": args.description,
|
|
796
|
+
"agent_id": args.agent_id,
|
|
797
|
+
"access_mode": args.access_mode,
|
|
798
|
+
},
|
|
799
|
+
)
|
|
800
|
+
return run_request(args, "channels.create", body=body)
|
|
801
|
+
if action == "get":
|
|
802
|
+
return run_request(args, "channels.get", {"channel_id": args.channel_id})
|
|
803
|
+
if action == "update":
|
|
804
|
+
body = merge_payload(
|
|
805
|
+
args,
|
|
806
|
+
{
|
|
807
|
+
"name": args.name,
|
|
808
|
+
"description": args.description,
|
|
809
|
+
"agent_id": args.agent_id,
|
|
810
|
+
"access_mode": args.access_mode,
|
|
811
|
+
},
|
|
812
|
+
)
|
|
813
|
+
return run_request(args, "channels.update", {"channel_id": args.channel_id}, body=body)
|
|
814
|
+
if action == "delete":
|
|
815
|
+
return run_request(args, "channels.delete", {"channel_id": args.channel_id})
|
|
816
|
+
return run_request(args, "channels.secret_key", {"channel_id": args.channel_id})
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def add_json_args(parser: argparse.ArgumentParser) -> None:
|
|
820
|
+
parser.add_argument("--json", help="JSON object payload.")
|
|
821
|
+
parser.add_argument("--json-file", help="Read JSON object payload from file.")
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def add_common_args(parser: argparse.ArgumentParser) -> None:
|
|
825
|
+
parser.add_argument("--host", help=f"API host. Defaults to ${ENV_HOST} or {DEFAULT_HOST}.")
|
|
826
|
+
parser.add_argument("--api-key", help=f"API key. Defaults to ${ENV_API_KEY}.")
|
|
827
|
+
parser.add_argument("--pretty", action=argparse.BooleanOptionalAction, default=True, help="Pretty-print JSON output.")
|
|
828
|
+
parser.add_argument("--raw", action="store_true", help="Print raw response text.")
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
832
|
+
parser = argparse.ArgumentParser(description="NewAgent OpenAPI CLI")
|
|
833
|
+
add_common_args(parser)
|
|
834
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
835
|
+
|
|
836
|
+
docs = subparsers.add_parser("docs", help="Query built-in API documentation for humans and AI agents.")
|
|
837
|
+
docs_sub = docs.add_subparsers(dest="docs_action", required=True)
|
|
838
|
+
docs_sub.add_parser("list", help="List endpoint keys.")
|
|
839
|
+
docs_show = docs_sub.add_parser("show", help="Show endpoint documentation.")
|
|
840
|
+
docs_show.add_argument("endpoint")
|
|
841
|
+
docs_show.add_argument("--json", dest="json_doc", action="store_true", help="Output machine-readable JSON.")
|
|
842
|
+
docs.set_defaults(handler=handle_docs)
|
|
843
|
+
|
|
844
|
+
knowledge = subparsers.add_parser("knowledge", help="Knowledge base endpoints.")
|
|
845
|
+
knowledge_sub = knowledge.add_subparsers(dest="knowledge_action", required=True)
|
|
846
|
+
knowledge_search = knowledge_sub.add_parser("search")
|
|
847
|
+
knowledge_search.add_argument("kb_id", type=int)
|
|
848
|
+
knowledge_search.add_argument("--query", required=True)
|
|
849
|
+
add_json_args(knowledge_search)
|
|
850
|
+
knowledge_markdown = knowledge_sub.add_parser("markdown")
|
|
851
|
+
knowledge_markdown.add_argument("kb_id", type=int)
|
|
852
|
+
knowledge_markdown.add_argument("doc_id", type=int)
|
|
853
|
+
knowledge.set_defaults(handler=handle_knowledge)
|
|
854
|
+
|
|
855
|
+
conversations = subparsers.add_parser("conversations", help="Conversation endpoints.")
|
|
856
|
+
conv_sub = conversations.add_subparsers(dest="conversation_action", required=True)
|
|
857
|
+
conv_create = conv_sub.add_parser("create")
|
|
858
|
+
conv_create.add_argument("agent_id", type=int)
|
|
859
|
+
conv_create.add_argument("--source", default="api", choices=["chat", "api", "embed"])
|
|
860
|
+
conv_create.add_argument("--title")
|
|
861
|
+
conv_create.add_argument("--external-user-id")
|
|
862
|
+
conv_create.add_argument("--display-name")
|
|
863
|
+
conv_create.add_argument("--email")
|
|
864
|
+
conv_create.add_argument("--phone")
|
|
865
|
+
conv_create.add_argument("--avatar-url")
|
|
866
|
+
add_json_args(conv_create)
|
|
867
|
+
conv_list = conv_sub.add_parser("list")
|
|
868
|
+
conv_list.add_argument("agent_id", type=int)
|
|
869
|
+
conv_list.add_argument("--page", type=int)
|
|
870
|
+
conv_list.add_argument("--per-page", type=int)
|
|
871
|
+
conv_list.add_argument("--start-time")
|
|
872
|
+
conv_list.add_argument("--end-time")
|
|
873
|
+
conv_list.add_argument("--status-filter")
|
|
874
|
+
conv_list.add_argument("--source", choices=["chat", "api", "embed"])
|
|
875
|
+
conv_list.add_argument("--conversation-external-id")
|
|
876
|
+
conv_list.add_argument("--external-user-id")
|
|
877
|
+
conv_list.add_argument("--search")
|
|
878
|
+
for name in ("get", "end"):
|
|
879
|
+
conv_item = conv_sub.add_parser(name)
|
|
880
|
+
conv_item.add_argument("agent_id", type=int)
|
|
881
|
+
conv_item.add_argument("conversation_id", type=int)
|
|
882
|
+
conversations.set_defaults(handler=handle_conversations)
|
|
883
|
+
|
|
884
|
+
chat = subparsers.add_parser("chat", help="Stream chat with an agent.")
|
|
885
|
+
chat.add_argument("agent_id", type=int)
|
|
886
|
+
chat.add_argument("--message", required=True)
|
|
887
|
+
chat.add_argument("--conversation-id", type=int)
|
|
888
|
+
chat.add_argument("--conversation-external-id")
|
|
889
|
+
chat.add_argument("--request-id")
|
|
890
|
+
chat.add_argument("--resume", action="store_true")
|
|
891
|
+
add_json_args(chat)
|
|
892
|
+
chat.set_defaults(handler=handle_chat)
|
|
893
|
+
|
|
894
|
+
steps = subparsers.add_parser("steps", help="List conversation execution steps.")
|
|
895
|
+
steps.add_argument("agent_id", type=int)
|
|
896
|
+
steps.add_argument("conversation_id", type=int)
|
|
897
|
+
steps.set_defaults(handler=handle_steps)
|
|
898
|
+
|
|
899
|
+
agents = subparsers.add_parser("agents", help="Agent endpoints.")
|
|
900
|
+
agent_sub = agents.add_subparsers(dest="agent_action", required=True)
|
|
901
|
+
agent_list = agent_sub.add_parser("list")
|
|
902
|
+
agent_list.add_argument("--status-filter", default="active", choices=["active", "inactive"])
|
|
903
|
+
agent_list.add_argument("--page", type=int)
|
|
904
|
+
agent_list.add_argument("--per-page", type=int)
|
|
905
|
+
agent_create = agent_sub.add_parser("create")
|
|
906
|
+
agent_create.add_argument("--name", required=True)
|
|
907
|
+
agent_create.add_argument("--description")
|
|
908
|
+
add_json_args(agent_create)
|
|
909
|
+
agent_get = agent_sub.add_parser("get")
|
|
910
|
+
agent_get.add_argument("agent_id", type=int)
|
|
911
|
+
agent_update = agent_sub.add_parser("update")
|
|
912
|
+
agent_update.add_argument("agent_id", type=int)
|
|
913
|
+
agent_update.add_argument("--name")
|
|
914
|
+
agent_update.add_argument("--description")
|
|
915
|
+
add_json_args(agent_update)
|
|
916
|
+
agent_status = agent_sub.add_parser("status")
|
|
917
|
+
agent_status.add_argument("agent_id", type=int)
|
|
918
|
+
agent_status.add_argument("status", choices=["active", "inactive"])
|
|
919
|
+
agent_engine = agent_sub.add_parser("engine-config")
|
|
920
|
+
agent_engine.add_argument("agent_id", type=int)
|
|
921
|
+
add_json_args(agent_engine)
|
|
922
|
+
agents.set_defaults(handler=handle_agents)
|
|
923
|
+
|
|
924
|
+
tools = subparsers.add_parser("tools", help="Agent tool endpoints.")
|
|
925
|
+
tool_sub = tools.add_subparsers(dest="tool_action", required=True)
|
|
926
|
+
tool_list = tool_sub.add_parser("list")
|
|
927
|
+
tool_list.add_argument("agent_id", type=int)
|
|
928
|
+
tool_create = tool_sub.add_parser("create")
|
|
929
|
+
tool_create.add_argument("agent_id", type=int)
|
|
930
|
+
tool_create.add_argument("--name", required=True)
|
|
931
|
+
tool_create.add_argument("--description")
|
|
932
|
+
tool_create.add_argument("--tool-type", required=True, choices=["search", "doc_query", "notebook", "tool_response_fetch", "python_code"])
|
|
933
|
+
add_json_args(tool_create)
|
|
934
|
+
for name in ("get", "delete"):
|
|
935
|
+
tool_item = tool_sub.add_parser(name)
|
|
936
|
+
tool_item.add_argument("agent_id", type=int)
|
|
937
|
+
tool_item.add_argument("tool_id", type=int)
|
|
938
|
+
tool_update = tool_sub.add_parser("update")
|
|
939
|
+
tool_update.add_argument("agent_id", type=int)
|
|
940
|
+
tool_update.add_argument("tool_id", type=int)
|
|
941
|
+
tool_update.add_argument("--name")
|
|
942
|
+
tool_update.add_argument("--description")
|
|
943
|
+
add_json_args(tool_update)
|
|
944
|
+
tool_toggle = tool_sub.add_parser("toggle")
|
|
945
|
+
tool_toggle.add_argument("agent_id", type=int)
|
|
946
|
+
tool_toggle.add_argument("tool_id", type=int)
|
|
947
|
+
tool_toggle.add_argument("is_enabled", type=parse_bool)
|
|
948
|
+
tool_execute = tool_sub.add_parser("execute")
|
|
949
|
+
tool_execute.add_argument("agent_id", type=int)
|
|
950
|
+
tool_execute.add_argument("tool_id", type=int)
|
|
951
|
+
tool_execute.add_argument("--conversation-id", type=int)
|
|
952
|
+
add_json_args(tool_execute)
|
|
953
|
+
tools.set_defaults(handler=handle_tools)
|
|
954
|
+
|
|
955
|
+
channels = subparsers.add_parser("channels", help="Web SDK channel endpoints.")
|
|
956
|
+
channel_sub = channels.add_subparsers(dest="channel_action", required=True)
|
|
957
|
+
channel_list = channel_sub.add_parser("list")
|
|
958
|
+
channel_list.add_argument("--page", type=int)
|
|
959
|
+
channel_list.add_argument("--per-page", type=int)
|
|
960
|
+
channel_create = channel_sub.add_parser("create")
|
|
961
|
+
channel_create.add_argument("--name", required=True)
|
|
962
|
+
channel_create.add_argument("--description")
|
|
963
|
+
channel_create.add_argument("--agent-id", type=int)
|
|
964
|
+
channel_create.add_argument("--access-mode", choices=["url", "embed"])
|
|
965
|
+
add_json_args(channel_create)
|
|
966
|
+
for name in ("get", "delete", "secret-key"):
|
|
967
|
+
channel_item = channel_sub.add_parser(name)
|
|
968
|
+
channel_item.add_argument("channel_id", type=int)
|
|
969
|
+
channel_update = channel_sub.add_parser("update")
|
|
970
|
+
channel_update.add_argument("channel_id", type=int)
|
|
971
|
+
channel_update.add_argument("--name")
|
|
972
|
+
channel_update.add_argument("--description")
|
|
973
|
+
channel_update.add_argument("--agent-id", type=int)
|
|
974
|
+
channel_update.add_argument("--access-mode", choices=["url", "embed"])
|
|
975
|
+
add_json_args(channel_update)
|
|
976
|
+
channels.set_defaults(handler=handle_channels)
|
|
977
|
+
|
|
978
|
+
return parser
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
def parse_bool(value: str) -> bool:
|
|
982
|
+
lowered = value.lower()
|
|
983
|
+
if lowered in {"1", "true", "yes", "y", "on"}:
|
|
984
|
+
return True
|
|
985
|
+
if lowered in {"0", "false", "no", "n", "off"}:
|
|
986
|
+
return False
|
|
987
|
+
raise argparse.ArgumentTypeError("Expected boolean: true/false")
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def main(argv: list[str] | None = None) -> int:
|
|
991
|
+
parser = build_parser()
|
|
992
|
+
args = parser.parse_args(argv)
|
|
993
|
+
try:
|
|
994
|
+
return args.handler(args)
|
|
995
|
+
except CliError as exc:
|
|
996
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
997
|
+
return 1
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
if __name__ == "__main__":
|
|
1001
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepflow-openagent-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line client for NewAgent OpenAPI (agents, conversations, SSE chat, tools, channels, knowledge bases).
|
|
5
|
+
Author: DeepFlow AI
|
|
6
|
+
Project-URL: Repository, https://github.com/DeepFlowAI/newagent
|
|
7
|
+
Keywords: deepflow,openagent,newagent,openapi,cli,sse,chat
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
19
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
20
|
+
|
|
21
|
+
# deepflow-openagent-cli
|
|
22
|
+
|
|
23
|
+
Python command-line client for the NewAgent OpenAPI: agents, conversations, SSE chat, tools, channels, and knowledge bases.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install deepflow-openagent-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export NEWAGENT_HOST="https://your-api-host"
|
|
35
|
+
export NEWAGENT_API_KEY="sk-..."
|
|
36
|
+
deepflow_openagent_cli docs list
|
|
37
|
+
deepflow_openagent_cli agents list
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Equivalent module invocation:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python -m deepflow_openagent_cli docs list
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Configure API host and key
|
|
47
|
+
|
|
48
|
+
| Method | Variable / flag |
|
|
49
|
+
|--------|------------------|
|
|
50
|
+
| Host | `NEWAGENT_HOST` or `--host` |
|
|
51
|
+
| API key | `NEWAGENT_API_KEY` or `--api-key` |
|
|
52
|
+
|
|
53
|
+
## Development (build wheel/sdist locally)
|
|
54
|
+
|
|
55
|
+
From this directory:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install build twine
|
|
59
|
+
python -m build
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Upload to PyPI (use a **new** API token; never commit tokens):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python -m twine upload dist/*
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
When prompted, username is `__token__` and password is the token value.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
deepflow_openagent_cli/__init__.py,sha256=w928fxQRu-8lYu3dyQKNN8qHlqi3986H7KE0AGyQW_0,80
|
|
2
|
+
deepflow_openagent_cli/__main__.py,sha256=McCOca5g_hDYxTZnTjfcZndKb1vRWAot2-SraerWG0E,150
|
|
3
|
+
deepflow_openagent_cli/cli.py,sha256=Lv0iU7JQVamxPMi3J4cBXwZgNSQGQ8ObIzYar1X7zhc,43481
|
|
4
|
+
deepflow_openagent_cli-0.1.0.dist-info/METADATA,sha256=sU-Z2NR9dmdvjR1rDqK9WJQ7QOMBevAsHsvFmc1fx94,1763
|
|
5
|
+
deepflow_openagent_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
deepflow_openagent_cli-0.1.0.dist-info/entry_points.txt,sha256=WQx_7u7RQesp3jxmMelEWH5eTP-iuMYiO4C_5wrhGTQ,75
|
|
7
|
+
deepflow_openagent_cli-0.1.0.dist-info/top_level.txt,sha256=cobuh7IzAHrGniGlRzlvJcBbRLGlN-wFXXy3JtT1LeY,23
|
|
8
|
+
deepflow_openagent_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
deepflow_openagent_cli
|