deepflow-openagent-cli 0.1.4__tar.gz → 0.1.5__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.
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/PKG-INFO +16 -1
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/README.md +15 -0
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/pyproject.toml +1 -1
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli/__init__.py +1 -1
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli/cli.py +739 -2
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli.egg-info/PKG-INFO +16 -1
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/tests/test_cli.py +129 -0
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/setup.cfg +0 -0
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli/__main__.py +0 -0
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli.egg-info/SOURCES.txt +0 -0
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli.egg-info/dependency_links.txt +0 -0
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli.egg-info/entry_points.txt +0 -0
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli.egg-info/requires.txt +0 -0
- {deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepflow-openagent-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Command-line client for OpenAgent OpenAPI (agents, conversations, SSE chat, tools, channels, knowledge bases).
|
|
5
5
|
Author: DeepFlow AI
|
|
6
6
|
Project-URL: Repository, https://github.com/DeepFlowAI/OpenAgent
|
|
@@ -66,6 +66,21 @@ deepflow_openagent_cli docs show chat.stream --json
|
|
|
66
66
|
deepflow_openagent_cli knowledge list --page 1 --per-page 10
|
|
67
67
|
deepflow_openagent_cli knowledge documents 1
|
|
68
68
|
deepflow_openagent_cli knowledge query-document 6670 --slice-id 512208 --line 1720
|
|
69
|
+
|
|
70
|
+
# Knowledge-base file library (requires config scope)
|
|
71
|
+
deepflow_openagent_cli knowledge directories create 1 --name "Policies"
|
|
72
|
+
deepflow_openagent_cli knowledge files create 1 \
|
|
73
|
+
--directory-id 2 --name "Product Manual" --custom-version v1 \
|
|
74
|
+
--original ./manual.pdf --markdown ./manual.md
|
|
75
|
+
deepflow_openagent_cli knowledge files list 1 --markdown-filter has-missing
|
|
76
|
+
deepflow_openagent_cli knowledge versions upload 1 3 \
|
|
77
|
+
--custom-version v2 --original ./manual-v2.pdf
|
|
78
|
+
deepflow_openagent_cli knowledge versions attach-markdown 1 3 4 \
|
|
79
|
+
--markdown ./manual-v2.md
|
|
80
|
+
deepflow_openagent_cli knowledge versions set-current 1 3 4
|
|
81
|
+
deepflow_openagent_cli knowledge versions original 1 3 4 --output ./manual-v2.pdf
|
|
82
|
+
deepflow_openagent_cli knowledge versions markdown 1 3 4 --output ./manual-v2.md
|
|
83
|
+
|
|
69
84
|
deepflow_openagent_cli chat 1 --message "Hello"
|
|
70
85
|
deepflow_openagent_cli steps 1 42
|
|
71
86
|
deepflow_openagent_cli steps get 1 42 100
|
|
@@ -45,6 +45,21 @@ deepflow_openagent_cli docs show chat.stream --json
|
|
|
45
45
|
deepflow_openagent_cli knowledge list --page 1 --per-page 10
|
|
46
46
|
deepflow_openagent_cli knowledge documents 1
|
|
47
47
|
deepflow_openagent_cli knowledge query-document 6670 --slice-id 512208 --line 1720
|
|
48
|
+
|
|
49
|
+
# Knowledge-base file library (requires config scope)
|
|
50
|
+
deepflow_openagent_cli knowledge directories create 1 --name "Policies"
|
|
51
|
+
deepflow_openagent_cli knowledge files create 1 \
|
|
52
|
+
--directory-id 2 --name "Product Manual" --custom-version v1 \
|
|
53
|
+
--original ./manual.pdf --markdown ./manual.md
|
|
54
|
+
deepflow_openagent_cli knowledge files list 1 --markdown-filter has-missing
|
|
55
|
+
deepflow_openagent_cli knowledge versions upload 1 3 \
|
|
56
|
+
--custom-version v2 --original ./manual-v2.pdf
|
|
57
|
+
deepflow_openagent_cli knowledge versions attach-markdown 1 3 4 \
|
|
58
|
+
--markdown ./manual-v2.md
|
|
59
|
+
deepflow_openagent_cli knowledge versions set-current 1 3 4
|
|
60
|
+
deepflow_openagent_cli knowledge versions original 1 3 4 --output ./manual-v2.pdf
|
|
61
|
+
deepflow_openagent_cli knowledge versions markdown 1 3 4 --output ./manual-v2.md
|
|
62
|
+
|
|
48
63
|
deepflow_openagent_cli chat 1 --message "Hello"
|
|
49
64
|
deepflow_openagent_cli steps 1 42
|
|
50
65
|
deepflow_openagent_cli steps get 1 42 100
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deepflow-openagent-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "Command-line client for OpenAgent OpenAPI (agents, conversations, SSE chat, tools, channels, knowledge bases)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
{deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli/cli.py
RENAMED
|
@@ -9,14 +9,18 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import argparse
|
|
11
11
|
import json
|
|
12
|
+
import mimetypes
|
|
12
13
|
import os
|
|
13
14
|
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
from collections.abc import Iterator
|
|
14
17
|
from dataclasses import asdict, dataclass, field
|
|
15
18
|
from pathlib import Path
|
|
16
19
|
from typing import Any
|
|
17
20
|
from urllib.error import HTTPError, URLError
|
|
18
21
|
from urllib.parse import urlencode
|
|
19
22
|
from urllib.request import Request, urlopen
|
|
23
|
+
from uuid import uuid4
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
DEFAULT_HOST = "http://localhost:5001"
|
|
@@ -53,6 +57,72 @@ class EndpointDoc:
|
|
|
53
57
|
example: str = ""
|
|
54
58
|
|
|
55
59
|
|
|
60
|
+
class MultipartBody:
|
|
61
|
+
"""Replayable multipart body that streams file content in bounded chunks."""
|
|
62
|
+
|
|
63
|
+
chunk_size = 1024 * 1024
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
fields: dict[str, str | int | None],
|
|
68
|
+
files: dict[str, Path | None],
|
|
69
|
+
) -> None:
|
|
70
|
+
self.boundary = f"openagent-{uuid4().hex}"
|
|
71
|
+
self._parts: list[bytes | Path] = []
|
|
72
|
+
boundary = self.boundary.encode("ascii")
|
|
73
|
+
for name, value in fields.items():
|
|
74
|
+
if value is None:
|
|
75
|
+
continue
|
|
76
|
+
self._parts.extend(
|
|
77
|
+
[
|
|
78
|
+
b"--" + boundary + b"\r\n",
|
|
79
|
+
f'Content-Disposition: form-data; name="{name}"\r\n\r\n'.encode(
|
|
80
|
+
"utf-8"
|
|
81
|
+
),
|
|
82
|
+
str(value).encode("utf-8"),
|
|
83
|
+
b"\r\n",
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
for name, path in files.items():
|
|
87
|
+
if path is None:
|
|
88
|
+
continue
|
|
89
|
+
resolved = path.expanduser()
|
|
90
|
+
if not resolved.is_file():
|
|
91
|
+
raise CliError(f"Upload file does not exist: {path}")
|
|
92
|
+
content_type = mimetypes.guess_type(resolved.name)[0] or "application/octet-stream"
|
|
93
|
+
escaped_name = resolved.name.replace('"', "")
|
|
94
|
+
self._parts.extend(
|
|
95
|
+
[
|
|
96
|
+
b"--" + boundary + b"\r\n",
|
|
97
|
+
(
|
|
98
|
+
f'Content-Disposition: form-data; name="{name}"; '
|
|
99
|
+
f'filename="{escaped_name}"\r\n'
|
|
100
|
+
f"Content-Type: {content_type}\r\n\r\n"
|
|
101
|
+
).encode("utf-8"),
|
|
102
|
+
resolved,
|
|
103
|
+
b"\r\n",
|
|
104
|
+
]
|
|
105
|
+
)
|
|
106
|
+
self._parts.append(b"--" + boundary + b"--\r\n")
|
|
107
|
+
self.content_length = sum(
|
|
108
|
+
part.stat().st_size if isinstance(part, Path) else len(part)
|
|
109
|
+
for part in self._parts
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def content_type(self) -> str:
|
|
114
|
+
return f"multipart/form-data; boundary={self.boundary}"
|
|
115
|
+
|
|
116
|
+
def __iter__(self) -> Iterator[bytes]:
|
|
117
|
+
for part in self._parts:
|
|
118
|
+
if isinstance(part, Path):
|
|
119
|
+
with part.open("rb") as file_object:
|
|
120
|
+
while chunk := file_object.read(self.chunk_size):
|
|
121
|
+
yield chunk
|
|
122
|
+
else:
|
|
123
|
+
yield part
|
|
124
|
+
|
|
125
|
+
|
|
56
126
|
def _param(name: str, type_: str, required: bool, description: str) -> ParamDoc:
|
|
57
127
|
return ParamDoc(name=name, type=type_, required=required, description=description)
|
|
58
128
|
|
|
@@ -121,6 +191,326 @@ ENDPOINTS: tuple[EndpointDoc, ...] = (
|
|
|
121
191
|
errors=["401 missing or invalid API key", "403 scope missing", "404 knowledge base not found", "422 validation error"],
|
|
122
192
|
example="deepflow_openagent_cli knowledge documents 1 --page 1 --per-page 20",
|
|
123
193
|
),
|
|
194
|
+
EndpointDoc(
|
|
195
|
+
key="knowledge.file_directories.list",
|
|
196
|
+
method="GET",
|
|
197
|
+
path="/api/v1/knowledge-bases/{kb_id}/file-directories",
|
|
198
|
+
scope="config",
|
|
199
|
+
summary="List the file-library directory tree.",
|
|
200
|
+
path_params=[_param("kb_id", "integer", True, "Knowledge base ID.")],
|
|
201
|
+
response="Directory list and total file count.",
|
|
202
|
+
errors=["401 invalid API key", "403 config scope missing", "404 knowledge base not found"],
|
|
203
|
+
example="deepflow_openagent_cli knowledge directories list 1",
|
|
204
|
+
),
|
|
205
|
+
EndpointDoc(
|
|
206
|
+
key="knowledge.file_directories.create",
|
|
207
|
+
method="POST",
|
|
208
|
+
path="/api/v1/knowledge-bases/{kb_id}/file-directories",
|
|
209
|
+
scope="config",
|
|
210
|
+
summary="Create a file-library directory.",
|
|
211
|
+
path_params=[_param("kb_id", "integer", True, "Knowledge base ID.")],
|
|
212
|
+
body_fields=[
|
|
213
|
+
_param("name", "string", True, "Directory name, 1-50 chars."),
|
|
214
|
+
_param("parent_id", "integer", False, "Parent directory ID."),
|
|
215
|
+
],
|
|
216
|
+
response="Created directory.",
|
|
217
|
+
errors=["400 validation error", "401 invalid API key", "403 config scope missing", "404 knowledge base not found"],
|
|
218
|
+
example="deepflow_openagent_cli knowledge directories create 1 --name Policies",
|
|
219
|
+
),
|
|
220
|
+
EndpointDoc(
|
|
221
|
+
key="knowledge.file_directories.update",
|
|
222
|
+
method="PUT",
|
|
223
|
+
path="/api/v1/knowledge-bases/{kb_id}/file-directories/{directory_id}",
|
|
224
|
+
scope="config",
|
|
225
|
+
summary="Rename, move, or reorder a file-library directory.",
|
|
226
|
+
path_params=[
|
|
227
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
228
|
+
_param("directory_id", "integer", True, "Directory ID."),
|
|
229
|
+
],
|
|
230
|
+
body_fields=[
|
|
231
|
+
_param("name", "string", False, "Directory name, 1-50 chars."),
|
|
232
|
+
_param("parent_id", "integer|null", False, "Parent directory ID."),
|
|
233
|
+
_param("sort_order", "integer", False, "Non-negative sort order."),
|
|
234
|
+
],
|
|
235
|
+
response="Updated directory.",
|
|
236
|
+
errors=["400 validation error", "401 invalid API key", "403 config scope missing", "404 directory not found"],
|
|
237
|
+
example="deepflow_openagent_cli knowledge directories update 1 2 --name Regulations",
|
|
238
|
+
),
|
|
239
|
+
EndpointDoc(
|
|
240
|
+
key="knowledge.file_directories.delete",
|
|
241
|
+
method="DELETE",
|
|
242
|
+
path="/api/v1/knowledge-bases/{kb_id}/file-directories/{directory_id}",
|
|
243
|
+
scope="config",
|
|
244
|
+
summary="Delete an empty file-library directory.",
|
|
245
|
+
path_params=[
|
|
246
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
247
|
+
_param("directory_id", "integer", True, "Directory ID."),
|
|
248
|
+
],
|
|
249
|
+
response="Deletion confirmation.",
|
|
250
|
+
errors=["400 directory not empty", "401 invalid API key", "403 config scope missing", "404 directory not found"],
|
|
251
|
+
example="deepflow_openagent_cli knowledge directories delete 1 2",
|
|
252
|
+
),
|
|
253
|
+
EndpointDoc(
|
|
254
|
+
key="knowledge.files.list",
|
|
255
|
+
method="GET",
|
|
256
|
+
path="/api/v1/knowledge-bases/{kb_id}/files",
|
|
257
|
+
scope="config",
|
|
258
|
+
summary="List and filter file-library file objects.",
|
|
259
|
+
path_params=[_param("kb_id", "integer", True, "Knowledge base ID.")],
|
|
260
|
+
query_params=[
|
|
261
|
+
_param("directory_id", "integer", False, "Directory ID."),
|
|
262
|
+
_param("search", "string", False, "Name, description, or version search."),
|
|
263
|
+
_param("markdown", "string", False, "all, missing-current, has-missing, or complete."),
|
|
264
|
+
_param("page", "integer", False, "Page number."),
|
|
265
|
+
_param("per_page", "integer", False, "Items per page, 1-50."),
|
|
266
|
+
],
|
|
267
|
+
response="Paginated file objects with current version and version counts.",
|
|
268
|
+
errors=["401 invalid API key", "403 config scope missing", "404 knowledge base not found", "422 validation error"],
|
|
269
|
+
example="deepflow_openagent_cli knowledge files list 1 --markdown-filter has-missing",
|
|
270
|
+
),
|
|
271
|
+
EndpointDoc(
|
|
272
|
+
key="knowledge.files.create",
|
|
273
|
+
method="POST",
|
|
274
|
+
path="/api/v1/knowledge-bases/{kb_id}/files",
|
|
275
|
+
scope="config",
|
|
276
|
+
summary="Create a file object and its first version using multipart upload.",
|
|
277
|
+
path_params=[_param("kb_id", "integer", True, "Knowledge base ID.")],
|
|
278
|
+
body_fields=[
|
|
279
|
+
_param("directory_id", "integer", True, "Directory ID."),
|
|
280
|
+
_param("name", "string", True, "File object name, 1-200 chars."),
|
|
281
|
+
_param("custom_version", "string", True, "Business version, 1-100 chars."),
|
|
282
|
+
_param("description", "string", False, "Description, max 500 chars."),
|
|
283
|
+
_param("note", "string", False, "Version note, max 200 chars."),
|
|
284
|
+
_param("original", "file", True, "Original file."),
|
|
285
|
+
_param("markdown", "file", False, "Optional Markdown file."),
|
|
286
|
+
],
|
|
287
|
+
response="Created file detail.",
|
|
288
|
+
errors=["400 validation or duplicate error", "401 invalid API key", "403 config scope missing", "404 directory not found", "413 file too large"],
|
|
289
|
+
example="deepflow_openagent_cli knowledge files create 1 --directory-id 2 --name Manual --custom-version v1 --original manual.pdf",
|
|
290
|
+
),
|
|
291
|
+
EndpointDoc(
|
|
292
|
+
key="knowledge.files.get",
|
|
293
|
+
method="GET",
|
|
294
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}",
|
|
295
|
+
scope="config",
|
|
296
|
+
summary="Get a file object and filtered version history.",
|
|
297
|
+
path_params=[
|
|
298
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
299
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
300
|
+
],
|
|
301
|
+
query_params=[_param("markdown", "string", False, "Version Markdown filter.")],
|
|
302
|
+
response="File detail and version history.",
|
|
303
|
+
errors=["401 invalid API key", "403 config scope missing", "404 file not found"],
|
|
304
|
+
example="deepflow_openagent_cli knowledge files get 1 3 --markdown-filter missing",
|
|
305
|
+
),
|
|
306
|
+
EndpointDoc(
|
|
307
|
+
key="knowledge.files.update",
|
|
308
|
+
method="PUT",
|
|
309
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}",
|
|
310
|
+
scope="config",
|
|
311
|
+
summary="Rename, describe, or move a file object.",
|
|
312
|
+
path_params=[
|
|
313
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
314
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
315
|
+
],
|
|
316
|
+
body_fields=[
|
|
317
|
+
_param("name", "string", False, "File object name."),
|
|
318
|
+
_param("description", "string|null", False, "Description."),
|
|
319
|
+
_param("directory_id", "integer", False, "Target directory ID."),
|
|
320
|
+
],
|
|
321
|
+
response="Updated file detail.",
|
|
322
|
+
errors=["400 validation or duplicate error", "401 invalid API key", "403 config scope missing", "404 file not found"],
|
|
323
|
+
example="deepflow_openagent_cli knowledge files update 1 3 --name NewManual",
|
|
324
|
+
),
|
|
325
|
+
EndpointDoc(
|
|
326
|
+
key="knowledge.files.delete",
|
|
327
|
+
method="DELETE",
|
|
328
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}",
|
|
329
|
+
scope="config",
|
|
330
|
+
summary="Delete a file object and all versions.",
|
|
331
|
+
path_params=[
|
|
332
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
333
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
334
|
+
],
|
|
335
|
+
response="Deletion confirmation.",
|
|
336
|
+
errors=["401 invalid API key", "403 config scope missing", "404 file not found"],
|
|
337
|
+
example="deepflow_openagent_cli knowledge files delete 1 3",
|
|
338
|
+
),
|
|
339
|
+
EndpointDoc(
|
|
340
|
+
key="knowledge.file_versions.create",
|
|
341
|
+
method="POST",
|
|
342
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions",
|
|
343
|
+
scope="config",
|
|
344
|
+
summary="Upload a new version using multipart upload.",
|
|
345
|
+
path_params=[
|
|
346
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
347
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
348
|
+
],
|
|
349
|
+
body_fields=[
|
|
350
|
+
_param("custom_version", "string", True, "Business version."),
|
|
351
|
+
_param("note", "string", False, "Version note."),
|
|
352
|
+
_param("original", "file", True, "Original file."),
|
|
353
|
+
_param("markdown", "file", False, "Optional Markdown file."),
|
|
354
|
+
],
|
|
355
|
+
response="Created version detail.",
|
|
356
|
+
errors=["400 validation or duplicate error", "401 invalid API key", "403 config scope missing", "404 file not found", "413 file too large"],
|
|
357
|
+
example="deepflow_openagent_cli knowledge versions upload 1 3 --custom-version v2 --original manual-v2.pdf",
|
|
358
|
+
),
|
|
359
|
+
EndpointDoc(
|
|
360
|
+
key="knowledge.file_versions.get",
|
|
361
|
+
method="GET",
|
|
362
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions/{version_id}",
|
|
363
|
+
scope="config",
|
|
364
|
+
summary="Get one file version.",
|
|
365
|
+
path_params=[
|
|
366
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
367
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
368
|
+
_param("version_id", "integer", True, "Version ID."),
|
|
369
|
+
],
|
|
370
|
+
response="Version detail.",
|
|
371
|
+
errors=["401 invalid API key", "403 config scope missing", "404 version not found"],
|
|
372
|
+
example="deepflow_openagent_cli knowledge versions get 1 3 4",
|
|
373
|
+
),
|
|
374
|
+
EndpointDoc(
|
|
375
|
+
key="knowledge.file_versions.update",
|
|
376
|
+
method="PUT",
|
|
377
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions/{version_id}",
|
|
378
|
+
scope="config",
|
|
379
|
+
summary="Update a custom version label or note.",
|
|
380
|
+
path_params=[
|
|
381
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
382
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
383
|
+
_param("version_id", "integer", True, "Version ID."),
|
|
384
|
+
],
|
|
385
|
+
body_fields=[
|
|
386
|
+
_param("custom_version", "string", False, "Business version."),
|
|
387
|
+
_param("note", "string|null", False, "Version note."),
|
|
388
|
+
],
|
|
389
|
+
response="Updated version detail.",
|
|
390
|
+
errors=["400 validation or duplicate error", "401 invalid API key", "403 config scope missing", "404 version not found"],
|
|
391
|
+
example="deepflow_openagent_cli knowledge versions update 1 3 4 --custom-version v2.1",
|
|
392
|
+
),
|
|
393
|
+
EndpointDoc(
|
|
394
|
+
key="knowledge.file_versions.delete",
|
|
395
|
+
method="DELETE",
|
|
396
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions/{version_id}",
|
|
397
|
+
scope="config",
|
|
398
|
+
summary="Delete a non-current file version.",
|
|
399
|
+
path_params=[
|
|
400
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
401
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
402
|
+
_param("version_id", "integer", True, "Version ID."),
|
|
403
|
+
],
|
|
404
|
+
response="Deletion confirmation.",
|
|
405
|
+
errors=["401 invalid API key", "403 config scope missing", "404 version not found", "409 current version conflict"],
|
|
406
|
+
example="deepflow_openagent_cli knowledge versions delete 1 3 4",
|
|
407
|
+
),
|
|
408
|
+
EndpointDoc(
|
|
409
|
+
key="knowledge.file_versions.original",
|
|
410
|
+
method="GET",
|
|
411
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions/{version_id}/original",
|
|
412
|
+
scope="config",
|
|
413
|
+
summary="Download the original file for a version.",
|
|
414
|
+
path_params=[
|
|
415
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
416
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
417
|
+
_param("version_id", "integer", True, "Version ID."),
|
|
418
|
+
],
|
|
419
|
+
response="Original file bytes.",
|
|
420
|
+
errors=["401 invalid API key", "403 config scope missing", "404 version not found"],
|
|
421
|
+
example="deepflow_openagent_cli knowledge versions original 1 3 4 --output manual.pdf",
|
|
422
|
+
),
|
|
423
|
+
EndpointDoc(
|
|
424
|
+
key="knowledge.file_versions.attach_markdown",
|
|
425
|
+
method="POST",
|
|
426
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions/{version_id}/markdown",
|
|
427
|
+
scope="config",
|
|
428
|
+
summary="Attach or replace a version Markdown file.",
|
|
429
|
+
path_params=[
|
|
430
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
431
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
432
|
+
_param("version_id", "integer", True, "Version ID."),
|
|
433
|
+
],
|
|
434
|
+
body_fields=[_param("markdown", "file", True, "Markdown file.")],
|
|
435
|
+
response="Updated version detail with processing status.",
|
|
436
|
+
errors=["400 invalid Markdown", "401 invalid API key", "403 config scope missing", "404 version not found", "413 file too large"],
|
|
437
|
+
example="deepflow_openagent_cli knowledge versions attach-markdown 1 3 4 --markdown manual.md",
|
|
438
|
+
),
|
|
439
|
+
EndpointDoc(
|
|
440
|
+
key="knowledge.file_versions.markdown",
|
|
441
|
+
method="GET",
|
|
442
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions/{version_id}/markdown",
|
|
443
|
+
scope="config",
|
|
444
|
+
summary="Download the bound Markdown file.",
|
|
445
|
+
path_params=[
|
|
446
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
447
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
448
|
+
_param("version_id", "integer", True, "Version ID."),
|
|
449
|
+
],
|
|
450
|
+
response="Markdown file bytes.",
|
|
451
|
+
errors=["401 invalid API key", "403 config scope missing", "404 Markdown not found"],
|
|
452
|
+
example="deepflow_openagent_cli knowledge versions markdown 1 3 4 --output manual.md",
|
|
453
|
+
),
|
|
454
|
+
EndpointDoc(
|
|
455
|
+
key="knowledge.file_versions.remove_markdown",
|
|
456
|
+
method="DELETE",
|
|
457
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions/{version_id}/markdown",
|
|
458
|
+
scope="config",
|
|
459
|
+
summary="Remove the bound Markdown and parsed content.",
|
|
460
|
+
path_params=[
|
|
461
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
462
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
463
|
+
_param("version_id", "integer", True, "Version ID."),
|
|
464
|
+
],
|
|
465
|
+
response="Updated version detail.",
|
|
466
|
+
errors=["401 invalid API key", "403 config scope missing", "404 version not found"],
|
|
467
|
+
example="deepflow_openagent_cli knowledge versions remove-markdown 1 3 4",
|
|
468
|
+
),
|
|
469
|
+
EndpointDoc(
|
|
470
|
+
key="knowledge.file_versions.reprocess",
|
|
471
|
+
method="POST",
|
|
472
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions/{version_id}/reprocess",
|
|
473
|
+
scope="config",
|
|
474
|
+
summary="Reprocess the bound Markdown.",
|
|
475
|
+
path_params=[
|
|
476
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
477
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
478
|
+
_param("version_id", "integer", True, "Version ID."),
|
|
479
|
+
],
|
|
480
|
+
response="Updated version detail with processing status.",
|
|
481
|
+
errors=["400 Markdown missing", "401 invalid API key", "403 config scope missing", "404 version not found"],
|
|
482
|
+
example="deepflow_openagent_cli knowledge versions reprocess 1 3 4",
|
|
483
|
+
),
|
|
484
|
+
EndpointDoc(
|
|
485
|
+
key="knowledge.file_versions.current",
|
|
486
|
+
method="POST",
|
|
487
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions/{version_id}/current",
|
|
488
|
+
scope="config",
|
|
489
|
+
summary="Set a version as the current searchable version.",
|
|
490
|
+
path_params=[
|
|
491
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
492
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
493
|
+
_param("version_id", "integer", True, "Version ID."),
|
|
494
|
+
],
|
|
495
|
+
response="Updated current version detail.",
|
|
496
|
+
errors=["401 invalid API key", "403 config scope missing", "404 version not found"],
|
|
497
|
+
example="deepflow_openagent_cli knowledge versions set-current 1 3 4",
|
|
498
|
+
),
|
|
499
|
+
EndpointDoc(
|
|
500
|
+
key="knowledge.file_versions.content",
|
|
501
|
+
method="GET",
|
|
502
|
+
path="/api/v1/knowledge-bases/{kb_id}/files/{file_id}/versions/{version_id}/content",
|
|
503
|
+
scope="config",
|
|
504
|
+
summary="Get parsed Markdown, metadata, TOC, and slices.",
|
|
505
|
+
path_params=[
|
|
506
|
+
_param("kb_id", "integer", True, "Knowledge base ID."),
|
|
507
|
+
_param("file_id", "integer", True, "File object ID."),
|
|
508
|
+
_param("version_id", "integer", True, "Version ID."),
|
|
509
|
+
],
|
|
510
|
+
response="Processed document content and slices.",
|
|
511
|
+
errors=["400 processing not ready", "401 invalid API key", "403 config scope missing", "404 version not found"],
|
|
512
|
+
example="deepflow_openagent_cli knowledge versions content 1 3 4",
|
|
513
|
+
),
|
|
124
514
|
EndpointDoc(
|
|
125
515
|
key="documents.query",
|
|
126
516
|
method="POST",
|
|
@@ -347,10 +737,11 @@ ENDPOINTS: tuple[EndpointDoc, ...] = (
|
|
|
347
737
|
method="PUT",
|
|
348
738
|
path="/api/v1/agents/{agent_id}/engine-config",
|
|
349
739
|
scope="config",
|
|
350
|
-
summary="Partially update system_prompt, model, selected_tool_ids, context, or pre_recall.",
|
|
740
|
+
summary="Partially update system_prompt, runtime_context, model, selected_tool_ids, context, or pre_recall.",
|
|
351
741
|
path_params=[_param("agent_id", "integer", True, "Agent ID.")],
|
|
352
742
|
body_fields=[
|
|
353
743
|
_param("system_prompt", "string", False, "System prompt, max 30000 chars."),
|
|
744
|
+
_param("runtime_context", "string", False, "Per-LLM-call runtime context template, max 30000 chars."),
|
|
354
745
|
_param("model", "object", False, "Model config."),
|
|
355
746
|
_param("selected_tool_ids", "array[integer]", False, "Enabled tool IDs."),
|
|
356
747
|
_param("context", "object", False, "Context policy."),
|
|
@@ -358,7 +749,7 @@ ENDPOINTS: tuple[EndpointDoc, ...] = (
|
|
|
358
749
|
],
|
|
359
750
|
response="AgentResponse with updated engine_config.",
|
|
360
751
|
errors=["401 missing or invalid API key", "403 scope missing", "404 agent not found", "422 validation error"],
|
|
361
|
-
example="deepflow_openagent_cli agents engine-config 1 --json '{\"
|
|
752
|
+
example="deepflow_openagent_cli agents engine-config 1 --json '{\"runtime_context\":\"Date: {{current_date}}\"}'",
|
|
362
753
|
),
|
|
363
754
|
EndpointDoc(
|
|
364
755
|
key="tools.list",
|
|
@@ -619,6 +1010,82 @@ class OpenApiClient:
|
|
|
619
1010
|
except URLError as exc:
|
|
620
1011
|
raise CliError(f"Failed to connect to {url}: {exc.reason}") from exc
|
|
621
1012
|
|
|
1013
|
+
def request_multipart(
|
|
1014
|
+
self,
|
|
1015
|
+
endpoint_key: str,
|
|
1016
|
+
path_params: dict[str, Any],
|
|
1017
|
+
fields: dict[str, str | int | None],
|
|
1018
|
+
files: dict[str, Path | None],
|
|
1019
|
+
) -> bytes:
|
|
1020
|
+
endpoint = get_endpoint(endpoint_key)
|
|
1021
|
+
if endpoint.scope and not self.api_key:
|
|
1022
|
+
raise CliError(f"Missing API key. Set {ENV_API_KEY} or pass --api-key.")
|
|
1023
|
+
|
|
1024
|
+
url = self._build_url(endpoint, path_params, {})
|
|
1025
|
+
body = MultipartBody(fields, files)
|
|
1026
|
+
headers = {
|
|
1027
|
+
"Accept": "application/json",
|
|
1028
|
+
"Content-Type": body.content_type,
|
|
1029
|
+
"Content-Length": str(body.content_length),
|
|
1030
|
+
}
|
|
1031
|
+
if self.api_key:
|
|
1032
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
1033
|
+
request = Request(url, data=body, headers=headers, method=endpoint.method)
|
|
1034
|
+
try:
|
|
1035
|
+
with urlopen(request) as response: # noqa: S310 - user-provided API host.
|
|
1036
|
+
return response.read()
|
|
1037
|
+
except HTTPError as exc:
|
|
1038
|
+
payload = exc.read().decode("utf-8", errors="replace")
|
|
1039
|
+
raise CliError(f"HTTP {exc.code} for {endpoint_key}: {payload}") from exc
|
|
1040
|
+
except URLError as exc:
|
|
1041
|
+
raise CliError(f"Failed to connect to {url}: {exc.reason}") from exc
|
|
1042
|
+
|
|
1043
|
+
def download(
|
|
1044
|
+
self,
|
|
1045
|
+
endpoint_key: str,
|
|
1046
|
+
output_path: Path,
|
|
1047
|
+
path_params: dict[str, Any],
|
|
1048
|
+
query: dict[str, Any] | None = None,
|
|
1049
|
+
) -> int:
|
|
1050
|
+
endpoint = get_endpoint(endpoint_key)
|
|
1051
|
+
if endpoint.scope and not self.api_key:
|
|
1052
|
+
raise CliError(f"Missing API key. Set {ENV_API_KEY} or pass --api-key.")
|
|
1053
|
+
output_path = output_path.expanduser()
|
|
1054
|
+
if not output_path.parent.is_dir():
|
|
1055
|
+
raise CliError(f"Output directory does not exist: {output_path.parent}")
|
|
1056
|
+
if output_path.is_dir():
|
|
1057
|
+
raise CliError(f"Output path is a directory: {output_path}")
|
|
1058
|
+
|
|
1059
|
+
url = self._build_url(endpoint, path_params, query or {})
|
|
1060
|
+
headers = {"Accept": "application/octet-stream"}
|
|
1061
|
+
if self.api_key:
|
|
1062
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
1063
|
+
request = Request(url, headers=headers, method=endpoint.method)
|
|
1064
|
+
temporary_name: str | None = None
|
|
1065
|
+
try:
|
|
1066
|
+
with urlopen(request) as response: # noqa: S310 - user-provided API host.
|
|
1067
|
+
with tempfile.NamedTemporaryFile(
|
|
1068
|
+
mode="wb",
|
|
1069
|
+
dir=output_path.parent,
|
|
1070
|
+
prefix=f".{output_path.name}.",
|
|
1071
|
+
delete=False,
|
|
1072
|
+
) as temporary:
|
|
1073
|
+
temporary_name = temporary.name
|
|
1074
|
+
size = 0
|
|
1075
|
+
while chunk := response.read(1024 * 1024):
|
|
1076
|
+
temporary.write(chunk)
|
|
1077
|
+
size += len(chunk)
|
|
1078
|
+
os.replace(temporary_name, output_path)
|
|
1079
|
+
return size
|
|
1080
|
+
except HTTPError as exc:
|
|
1081
|
+
payload = exc.read().decode("utf-8", errors="replace")
|
|
1082
|
+
raise CliError(f"HTTP {exc.code} for {endpoint_key}: {payload}") from exc
|
|
1083
|
+
except URLError as exc:
|
|
1084
|
+
raise CliError(f"Failed to connect to {url}: {exc.reason}") from exc
|
|
1085
|
+
finally:
|
|
1086
|
+
if temporary_name and os.path.exists(temporary_name):
|
|
1087
|
+
os.unlink(temporary_name)
|
|
1088
|
+
|
|
622
1089
|
def _build_url(self, endpoint: EndpointDoc, path_params: dict[str, Any], query: dict[str, Any]) -> str:
|
|
623
1090
|
path = endpoint.path
|
|
624
1091
|
for param in endpoint.path_params:
|
|
@@ -765,6 +1232,51 @@ def run_request(
|
|
|
765
1232
|
return 0
|
|
766
1233
|
|
|
767
1234
|
|
|
1235
|
+
def run_multipart_request(
|
|
1236
|
+
args: argparse.Namespace,
|
|
1237
|
+
endpoint_key: str,
|
|
1238
|
+
path_params: dict[str, Any],
|
|
1239
|
+
fields: dict[str, str | int | None],
|
|
1240
|
+
files: dict[str, Path | None],
|
|
1241
|
+
) -> int:
|
|
1242
|
+
data = client_from_args(args).request_multipart(
|
|
1243
|
+
endpoint_key,
|
|
1244
|
+
path_params,
|
|
1245
|
+
fields,
|
|
1246
|
+
files,
|
|
1247
|
+
)
|
|
1248
|
+
print_output(data, pretty=args.pretty, raw=args.raw)
|
|
1249
|
+
return 0
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
def run_download(
|
|
1253
|
+
args: argparse.Namespace,
|
|
1254
|
+
endpoint_key: str,
|
|
1255
|
+
path_params: dict[str, Any],
|
|
1256
|
+
) -> int:
|
|
1257
|
+
output = Path(args.output).expanduser()
|
|
1258
|
+
size = client_from_args(args).download(
|
|
1259
|
+
endpoint_key,
|
|
1260
|
+
output,
|
|
1261
|
+
path_params,
|
|
1262
|
+
query={"download": True},
|
|
1263
|
+
)
|
|
1264
|
+
payload = json.dumps(
|
|
1265
|
+
{"output": str(output.absolute()), "size": size},
|
|
1266
|
+
ensure_ascii=False,
|
|
1267
|
+
).encode("utf-8")
|
|
1268
|
+
print_output(payload, pretty=args.pretty, raw=args.raw)
|
|
1269
|
+
return 0
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
def _file_version_path(args: argparse.Namespace) -> dict[str, int]:
|
|
1273
|
+
return {
|
|
1274
|
+
"kb_id": args.kb_id,
|
|
1275
|
+
"file_id": args.file_id,
|
|
1276
|
+
"version_id": args.version_id,
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
|
|
768
1280
|
def handle_knowledge(args: argparse.Namespace) -> int:
|
|
769
1281
|
if args.knowledge_action == "list":
|
|
770
1282
|
return run_request(args, "knowledge.list", query={"page": args.page, "per_page": args.per_page})
|
|
@@ -788,6 +1300,124 @@ def handle_knowledge(args: argparse.Namespace) -> int:
|
|
|
788
1300
|
"line": args.line,
|
|
789
1301
|
},
|
|
790
1302
|
)
|
|
1303
|
+
if args.knowledge_action == "directories":
|
|
1304
|
+
path = {"kb_id": args.kb_id}
|
|
1305
|
+
if args.directory_action == "list":
|
|
1306
|
+
return run_request(args, "knowledge.file_directories.list", path)
|
|
1307
|
+
if args.directory_action == "create":
|
|
1308
|
+
body = merge_payload(
|
|
1309
|
+
args,
|
|
1310
|
+
{"name": args.name, "parent_id": args.parent_id},
|
|
1311
|
+
)
|
|
1312
|
+
return run_request(args, "knowledge.file_directories.create", path, body=body)
|
|
1313
|
+
path["directory_id"] = args.directory_id
|
|
1314
|
+
if args.directory_action == "update":
|
|
1315
|
+
body = merge_payload(
|
|
1316
|
+
args,
|
|
1317
|
+
{
|
|
1318
|
+
"name": args.name,
|
|
1319
|
+
"parent_id": args.parent_id,
|
|
1320
|
+
"sort_order": args.sort_order,
|
|
1321
|
+
},
|
|
1322
|
+
)
|
|
1323
|
+
return run_request(args, "knowledge.file_directories.update", path, body=body)
|
|
1324
|
+
return run_request(args, "knowledge.file_directories.delete", path)
|
|
1325
|
+
if args.knowledge_action == "files":
|
|
1326
|
+
path = {"kb_id": args.kb_id}
|
|
1327
|
+
if args.file_action == "list":
|
|
1328
|
+
return run_request(
|
|
1329
|
+
args,
|
|
1330
|
+
"knowledge.files.list",
|
|
1331
|
+
path,
|
|
1332
|
+
query={
|
|
1333
|
+
"directory_id": args.directory_id,
|
|
1334
|
+
"search": args.search,
|
|
1335
|
+
"markdown": args.markdown_filter,
|
|
1336
|
+
"page": args.page,
|
|
1337
|
+
"per_page": args.per_page,
|
|
1338
|
+
},
|
|
1339
|
+
)
|
|
1340
|
+
if args.file_action == "create":
|
|
1341
|
+
return run_multipart_request(
|
|
1342
|
+
args,
|
|
1343
|
+
"knowledge.files.create",
|
|
1344
|
+
path,
|
|
1345
|
+
fields={
|
|
1346
|
+
"directory_id": args.directory_id,
|
|
1347
|
+
"name": args.name,
|
|
1348
|
+
"custom_version": args.custom_version,
|
|
1349
|
+
"description": args.description,
|
|
1350
|
+
"note": args.note,
|
|
1351
|
+
},
|
|
1352
|
+
files={
|
|
1353
|
+
"original": Path(args.original),
|
|
1354
|
+
"markdown": Path(args.markdown) if args.markdown else None,
|
|
1355
|
+
},
|
|
1356
|
+
)
|
|
1357
|
+
path["file_id"] = args.file_id
|
|
1358
|
+
if args.file_action == "get":
|
|
1359
|
+
return run_request(
|
|
1360
|
+
args,
|
|
1361
|
+
"knowledge.files.get",
|
|
1362
|
+
path,
|
|
1363
|
+
query={"markdown": args.markdown_filter},
|
|
1364
|
+
)
|
|
1365
|
+
if args.file_action == "update":
|
|
1366
|
+
body = merge_payload(
|
|
1367
|
+
args,
|
|
1368
|
+
{
|
|
1369
|
+
"name": args.name,
|
|
1370
|
+
"description": args.description,
|
|
1371
|
+
"directory_id": args.directory_id,
|
|
1372
|
+
},
|
|
1373
|
+
)
|
|
1374
|
+
return run_request(args, "knowledge.files.update", path, body=body)
|
|
1375
|
+
return run_request(args, "knowledge.files.delete", path)
|
|
1376
|
+
if args.knowledge_action == "versions":
|
|
1377
|
+
if args.version_action == "upload":
|
|
1378
|
+
return run_multipart_request(
|
|
1379
|
+
args,
|
|
1380
|
+
"knowledge.file_versions.create",
|
|
1381
|
+
{"kb_id": args.kb_id, "file_id": args.file_id},
|
|
1382
|
+
fields={
|
|
1383
|
+
"custom_version": args.custom_version,
|
|
1384
|
+
"note": args.note,
|
|
1385
|
+
},
|
|
1386
|
+
files={
|
|
1387
|
+
"original": Path(args.original),
|
|
1388
|
+
"markdown": Path(args.markdown) if args.markdown else None,
|
|
1389
|
+
},
|
|
1390
|
+
)
|
|
1391
|
+
path = _file_version_path(args)
|
|
1392
|
+
if args.version_action == "get":
|
|
1393
|
+
return run_request(args, "knowledge.file_versions.get", path)
|
|
1394
|
+
if args.version_action == "update":
|
|
1395
|
+
body = merge_payload(
|
|
1396
|
+
args,
|
|
1397
|
+
{"custom_version": args.custom_version, "note": args.note},
|
|
1398
|
+
)
|
|
1399
|
+
return run_request(args, "knowledge.file_versions.update", path, body=body)
|
|
1400
|
+
if args.version_action == "delete":
|
|
1401
|
+
return run_request(args, "knowledge.file_versions.delete", path)
|
|
1402
|
+
if args.version_action == "original":
|
|
1403
|
+
return run_download(args, "knowledge.file_versions.original", path)
|
|
1404
|
+
if args.version_action == "attach-markdown":
|
|
1405
|
+
return run_multipart_request(
|
|
1406
|
+
args,
|
|
1407
|
+
"knowledge.file_versions.attach_markdown",
|
|
1408
|
+
path,
|
|
1409
|
+
fields={},
|
|
1410
|
+
files={"markdown": Path(args.markdown)},
|
|
1411
|
+
)
|
|
1412
|
+
if args.version_action == "markdown":
|
|
1413
|
+
return run_download(args, "knowledge.file_versions.markdown", path)
|
|
1414
|
+
endpoint = {
|
|
1415
|
+
"remove-markdown": "knowledge.file_versions.remove_markdown",
|
|
1416
|
+
"reprocess": "knowledge.file_versions.reprocess",
|
|
1417
|
+
"set-current": "knowledge.file_versions.current",
|
|
1418
|
+
"content": "knowledge.file_versions.content",
|
|
1419
|
+
}[args.version_action]
|
|
1420
|
+
return run_request(args, endpoint, path)
|
|
791
1421
|
return run_request(args, "knowledge.markdown", {"kb_id": args.kb_id, "doc_id": args.doc_id})
|
|
792
1422
|
|
|
793
1423
|
|
|
@@ -1010,6 +1640,113 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1010
1640
|
knowledge_markdown = knowledge_sub.add_parser("markdown")
|
|
1011
1641
|
knowledge_markdown.add_argument("kb_id", type=int)
|
|
1012
1642
|
knowledge_markdown.add_argument("doc_id", type=int)
|
|
1643
|
+
|
|
1644
|
+
knowledge_directories = knowledge_sub.add_parser("directories")
|
|
1645
|
+
directory_sub = knowledge_directories.add_subparsers(
|
|
1646
|
+
dest="directory_action", required=True
|
|
1647
|
+
)
|
|
1648
|
+
directory_list = directory_sub.add_parser("list")
|
|
1649
|
+
directory_list.add_argument("kb_id", type=int)
|
|
1650
|
+
directory_create = directory_sub.add_parser("create")
|
|
1651
|
+
directory_create.add_argument("kb_id", type=int)
|
|
1652
|
+
directory_create.add_argument("--name", required=True)
|
|
1653
|
+
directory_create.add_argument("--parent-id", type=int)
|
|
1654
|
+
add_json_args(directory_create)
|
|
1655
|
+
directory_update = directory_sub.add_parser("update")
|
|
1656
|
+
directory_update.add_argument("kb_id", type=int)
|
|
1657
|
+
directory_update.add_argument("directory_id", type=int)
|
|
1658
|
+
directory_update.add_argument("--name")
|
|
1659
|
+
directory_update.add_argument("--parent-id", type=int)
|
|
1660
|
+
directory_update.add_argument("--sort-order", type=int)
|
|
1661
|
+
add_json_args(directory_update)
|
|
1662
|
+
directory_delete = directory_sub.add_parser("delete")
|
|
1663
|
+
directory_delete.add_argument("kb_id", type=int)
|
|
1664
|
+
directory_delete.add_argument("directory_id", type=int)
|
|
1665
|
+
|
|
1666
|
+
knowledge_files = knowledge_sub.add_parser("files")
|
|
1667
|
+
file_sub = knowledge_files.add_subparsers(dest="file_action", required=True)
|
|
1668
|
+
file_list = file_sub.add_parser("list")
|
|
1669
|
+
file_list.add_argument("kb_id", type=int)
|
|
1670
|
+
file_list.add_argument("--directory-id", type=int)
|
|
1671
|
+
file_list.add_argument("--search")
|
|
1672
|
+
file_list.add_argument(
|
|
1673
|
+
"--markdown-filter",
|
|
1674
|
+
choices=["all", "missing-current", "has-missing", "complete"],
|
|
1675
|
+
)
|
|
1676
|
+
file_list.add_argument("--page", type=int)
|
|
1677
|
+
file_list.add_argument("--per-page", type=int)
|
|
1678
|
+
file_create = file_sub.add_parser("create")
|
|
1679
|
+
file_create.add_argument("kb_id", type=int)
|
|
1680
|
+
file_create.add_argument("--directory-id", type=int, required=True)
|
|
1681
|
+
file_create.add_argument("--name", required=True)
|
|
1682
|
+
file_create.add_argument("--custom-version", required=True)
|
|
1683
|
+
file_create.add_argument("--description")
|
|
1684
|
+
file_create.add_argument("--note")
|
|
1685
|
+
file_create.add_argument("--original", required=True)
|
|
1686
|
+
file_create.add_argument("--markdown")
|
|
1687
|
+
file_get = file_sub.add_parser("get")
|
|
1688
|
+
file_get.add_argument("kb_id", type=int)
|
|
1689
|
+
file_get.add_argument("file_id", type=int)
|
|
1690
|
+
file_get.add_argument(
|
|
1691
|
+
"--markdown-filter",
|
|
1692
|
+
choices=["all", "missing", "bound", "processing", "ready", "failed"],
|
|
1693
|
+
)
|
|
1694
|
+
file_update = file_sub.add_parser("update")
|
|
1695
|
+
file_update.add_argument("kb_id", type=int)
|
|
1696
|
+
file_update.add_argument("file_id", type=int)
|
|
1697
|
+
file_update.add_argument("--name")
|
|
1698
|
+
file_update.add_argument("--description")
|
|
1699
|
+
file_update.add_argument("--directory-id", type=int)
|
|
1700
|
+
add_json_args(file_update)
|
|
1701
|
+
file_delete = file_sub.add_parser("delete")
|
|
1702
|
+
file_delete.add_argument("kb_id", type=int)
|
|
1703
|
+
file_delete.add_argument("file_id", type=int)
|
|
1704
|
+
|
|
1705
|
+
knowledge_versions = knowledge_sub.add_parser("versions")
|
|
1706
|
+
version_sub = knowledge_versions.add_subparsers(
|
|
1707
|
+
dest="version_action", required=True
|
|
1708
|
+
)
|
|
1709
|
+
version_upload = version_sub.add_parser("upload")
|
|
1710
|
+
version_upload.add_argument("kb_id", type=int)
|
|
1711
|
+
version_upload.add_argument("file_id", type=int)
|
|
1712
|
+
version_upload.add_argument("--custom-version", required=True)
|
|
1713
|
+
version_upload.add_argument("--note")
|
|
1714
|
+
version_upload.add_argument("--original", required=True)
|
|
1715
|
+
version_upload.add_argument("--markdown")
|
|
1716
|
+
for action in [
|
|
1717
|
+
"get",
|
|
1718
|
+
"delete",
|
|
1719
|
+
"remove-markdown",
|
|
1720
|
+
"reprocess",
|
|
1721
|
+
"set-current",
|
|
1722
|
+
"content",
|
|
1723
|
+
]:
|
|
1724
|
+
version_parser = version_sub.add_parser(action)
|
|
1725
|
+
version_parser.add_argument("kb_id", type=int)
|
|
1726
|
+
version_parser.add_argument("file_id", type=int)
|
|
1727
|
+
version_parser.add_argument("version_id", type=int)
|
|
1728
|
+
version_update = version_sub.add_parser("update")
|
|
1729
|
+
version_update.add_argument("kb_id", type=int)
|
|
1730
|
+
version_update.add_argument("file_id", type=int)
|
|
1731
|
+
version_update.add_argument("version_id", type=int)
|
|
1732
|
+
version_update.add_argument("--custom-version")
|
|
1733
|
+
version_update.add_argument("--note")
|
|
1734
|
+
add_json_args(version_update)
|
|
1735
|
+
version_original = version_sub.add_parser("original")
|
|
1736
|
+
version_original.add_argument("kb_id", type=int)
|
|
1737
|
+
version_original.add_argument("file_id", type=int)
|
|
1738
|
+
version_original.add_argument("version_id", type=int)
|
|
1739
|
+
version_original.add_argument("--output", required=True)
|
|
1740
|
+
version_attach = version_sub.add_parser("attach-markdown")
|
|
1741
|
+
version_attach.add_argument("kb_id", type=int)
|
|
1742
|
+
version_attach.add_argument("file_id", type=int)
|
|
1743
|
+
version_attach.add_argument("version_id", type=int)
|
|
1744
|
+
version_attach.add_argument("--markdown", required=True)
|
|
1745
|
+
version_markdown = version_sub.add_parser("markdown")
|
|
1746
|
+
version_markdown.add_argument("kb_id", type=int)
|
|
1747
|
+
version_markdown.add_argument("file_id", type=int)
|
|
1748
|
+
version_markdown.add_argument("version_id", type=int)
|
|
1749
|
+
version_markdown.add_argument("--output", required=True)
|
|
1013
1750
|
knowledge.set_defaults(handler=handle_knowledge)
|
|
1014
1751
|
|
|
1015
1752
|
conversations = subparsers.add_parser("conversations", help="Conversation endpoints.")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepflow-openagent-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Command-line client for OpenAgent OpenAPI (agents, conversations, SSE chat, tools, channels, knowledge bases).
|
|
5
5
|
Author: DeepFlow AI
|
|
6
6
|
Project-URL: Repository, https://github.com/DeepFlowAI/OpenAgent
|
|
@@ -66,6 +66,21 @@ deepflow_openagent_cli docs show chat.stream --json
|
|
|
66
66
|
deepflow_openagent_cli knowledge list --page 1 --per-page 10
|
|
67
67
|
deepflow_openagent_cli knowledge documents 1
|
|
68
68
|
deepflow_openagent_cli knowledge query-document 6670 --slice-id 512208 --line 1720
|
|
69
|
+
|
|
70
|
+
# Knowledge-base file library (requires config scope)
|
|
71
|
+
deepflow_openagent_cli knowledge directories create 1 --name "Policies"
|
|
72
|
+
deepflow_openagent_cli knowledge files create 1 \
|
|
73
|
+
--directory-id 2 --name "Product Manual" --custom-version v1 \
|
|
74
|
+
--original ./manual.pdf --markdown ./manual.md
|
|
75
|
+
deepflow_openagent_cli knowledge files list 1 --markdown-filter has-missing
|
|
76
|
+
deepflow_openagent_cli knowledge versions upload 1 3 \
|
|
77
|
+
--custom-version v2 --original ./manual-v2.pdf
|
|
78
|
+
deepflow_openagent_cli knowledge versions attach-markdown 1 3 4 \
|
|
79
|
+
--markdown ./manual-v2.md
|
|
80
|
+
deepflow_openagent_cli knowledge versions set-current 1 3 4
|
|
81
|
+
deepflow_openagent_cli knowledge versions original 1 3 4 --output ./manual-v2.pdf
|
|
82
|
+
deepflow_openagent_cli knowledge versions markdown 1 3 4 --output ./manual-v2.md
|
|
83
|
+
|
|
69
84
|
deepflow_openagent_cli chat 1 --message "Hello"
|
|
70
85
|
deepflow_openagent_cli steps 1 42
|
|
71
86
|
deepflow_openagent_cli steps get 1 42 100
|
|
@@ -8,6 +8,7 @@ from deepflow_openagent_cli import __version__
|
|
|
8
8
|
from deepflow_openagent_cli.cli import (
|
|
9
9
|
CliError,
|
|
10
10
|
ENDPOINTS,
|
|
11
|
+
MultipartBody,
|
|
11
12
|
OpenApiClient,
|
|
12
13
|
build_parser,
|
|
13
14
|
client_from_args,
|
|
@@ -30,12 +31,26 @@ def test_catalog_has_stable_unique_keys() -> None:
|
|
|
30
31
|
assert len(keys) == len(set(keys))
|
|
31
32
|
assert "knowledge.list" in keys
|
|
32
33
|
assert "knowledge.documents" in keys
|
|
34
|
+
assert "knowledge.files.create" in keys
|
|
35
|
+
assert "knowledge.file_versions.attach_markdown" in keys
|
|
33
36
|
assert "documents.query" in keys
|
|
34
37
|
assert "chat.stream" in keys
|
|
35
38
|
assert "steps.get" in keys
|
|
36
39
|
assert "channels.secret_key" in keys
|
|
37
40
|
|
|
38
41
|
|
|
42
|
+
def test_agent_engine_config_docs_include_runtime_context() -> None:
|
|
43
|
+
endpoint = get_endpoint("agents.engine_config")
|
|
44
|
+
|
|
45
|
+
runtime_context = next(
|
|
46
|
+
field for field in endpoint.body_fields if field.name == "runtime_context"
|
|
47
|
+
)
|
|
48
|
+
assert runtime_context.type == "string"
|
|
49
|
+
assert runtime_context.required is False
|
|
50
|
+
assert "runtime_context" in endpoint.summary
|
|
51
|
+
assert "runtime_context" in endpoint.example
|
|
52
|
+
|
|
53
|
+
|
|
39
54
|
def test_get_endpoint_unknown_key_raises_clear_error() -> None:
|
|
40
55
|
with pytest.raises(CliError, match="Unknown endpoint"):
|
|
41
56
|
get_endpoint("missing.endpoint")
|
|
@@ -59,6 +74,29 @@ def test_build_url_formats_path_and_query() -> None:
|
|
|
59
74
|
assert url == "http://localhost:5001/api/v1/knowledge-bases/1/documents?page=2&per_page=20"
|
|
60
75
|
|
|
61
76
|
|
|
77
|
+
def test_multipart_body_streams_fields_and_files(tmp_path: Path) -> None:
|
|
78
|
+
original = tmp_path / "manual.pdf"
|
|
79
|
+
original.write_bytes(b"pdf-content")
|
|
80
|
+
body = MultipartBody(
|
|
81
|
+
{"name": "Manual", "directory_id": 2, "description": None},
|
|
82
|
+
{"original": original, "markdown": None},
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
payload = b"".join(body)
|
|
86
|
+
|
|
87
|
+
assert len(payload) == body.content_length
|
|
88
|
+
assert b'name="name"' in payload
|
|
89
|
+
assert b"Manual" in payload
|
|
90
|
+
assert b'name="original"; filename="manual.pdf"' in payload
|
|
91
|
+
assert b"pdf-content" in payload
|
|
92
|
+
assert body.content_type.startswith("multipart/form-data; boundary=openagent-")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_multipart_body_rejects_missing_file(tmp_path: Path) -> None:
|
|
96
|
+
with pytest.raises(CliError, match="Upload file does not exist"):
|
|
97
|
+
MultipartBody({}, {"original": tmp_path / "missing.pdf"})
|
|
98
|
+
|
|
99
|
+
|
|
62
100
|
def test_client_from_args_reads_openagent_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
63
101
|
monkeypatch.setenv("OPENAGENT_HOST", "https://api.example.com")
|
|
64
102
|
monkeypatch.setenv("OPENAGENT_API_KEY", "sk-openagent")
|
|
@@ -135,6 +173,97 @@ def test_query_document_command_posts_body(
|
|
|
135
173
|
]
|
|
136
174
|
|
|
137
175
|
|
|
176
|
+
def test_create_knowledge_file_posts_multipart(
|
|
177
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
178
|
+
capsys: pytest.CaptureFixture[str],
|
|
179
|
+
tmp_path: Path,
|
|
180
|
+
) -> None:
|
|
181
|
+
original = tmp_path / "manual.pdf"
|
|
182
|
+
markdown = tmp_path / "manual.md"
|
|
183
|
+
original.write_bytes(b"pdf")
|
|
184
|
+
markdown.write_text("# Manual", encoding="utf-8")
|
|
185
|
+
calls = []
|
|
186
|
+
|
|
187
|
+
def fake_request_multipart(self, endpoint_key, path_params, fields, files):
|
|
188
|
+
calls.append((endpoint_key, path_params, fields, files))
|
|
189
|
+
return b'{"id": 3}'
|
|
190
|
+
|
|
191
|
+
monkeypatch.setattr(OpenApiClient, "request_multipart", fake_request_multipart)
|
|
192
|
+
|
|
193
|
+
exit_code = main(
|
|
194
|
+
[
|
|
195
|
+
"--api-key",
|
|
196
|
+
"sk-test",
|
|
197
|
+
"knowledge",
|
|
198
|
+
"files",
|
|
199
|
+
"create",
|
|
200
|
+
"1",
|
|
201
|
+
"--directory-id",
|
|
202
|
+
"2",
|
|
203
|
+
"--name",
|
|
204
|
+
"Manual",
|
|
205
|
+
"--custom-version",
|
|
206
|
+
"v1",
|
|
207
|
+
"--original",
|
|
208
|
+
str(original),
|
|
209
|
+
"--markdown",
|
|
210
|
+
str(markdown),
|
|
211
|
+
]
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
captured = capsys.readouterr()
|
|
215
|
+
assert exit_code == 0
|
|
216
|
+
assert json.loads(captured.out) == {"id": 3}
|
|
217
|
+
assert calls == [
|
|
218
|
+
(
|
|
219
|
+
"knowledge.files.create",
|
|
220
|
+
{"kb_id": 1},
|
|
221
|
+
{
|
|
222
|
+
"directory_id": 2,
|
|
223
|
+
"name": "Manual",
|
|
224
|
+
"custom_version": "v1",
|
|
225
|
+
"description": None,
|
|
226
|
+
"note": None,
|
|
227
|
+
},
|
|
228
|
+
{"original": original, "markdown": markdown},
|
|
229
|
+
)
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_download_version_original_writes_output_atomically(
|
|
234
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
235
|
+
tmp_path: Path,
|
|
236
|
+
) -> None:
|
|
237
|
+
class FakeResponse:
|
|
238
|
+
def __init__(self) -> None:
|
|
239
|
+
self.chunks = [b"first", b"second", b""]
|
|
240
|
+
|
|
241
|
+
def __enter__(self):
|
|
242
|
+
return self
|
|
243
|
+
|
|
244
|
+
def __exit__(self, *_args):
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
def read(self, _size: int) -> bytes:
|
|
248
|
+
return self.chunks.pop(0)
|
|
249
|
+
|
|
250
|
+
monkeypatch.setattr(
|
|
251
|
+
"deepflow_openagent_cli.cli.urlopen",
|
|
252
|
+
lambda _request: FakeResponse(),
|
|
253
|
+
)
|
|
254
|
+
output = tmp_path / "manual.pdf"
|
|
255
|
+
|
|
256
|
+
size = OpenApiClient("http://localhost:5001", "sk-test").download(
|
|
257
|
+
"knowledge.file_versions.original",
|
|
258
|
+
output,
|
|
259
|
+
{"kb_id": 1, "file_id": 2, "version_id": 3},
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
assert size == 11
|
|
263
|
+
assert output.read_bytes() == b"firstsecond"
|
|
264
|
+
assert list(tmp_path.iterdir()) == [output]
|
|
265
|
+
|
|
266
|
+
|
|
138
267
|
def test_steps_list_keeps_legacy_short_form(
|
|
139
268
|
monkeypatch: pytest.MonkeyPatch,
|
|
140
269
|
capsys: pytest.CaptureFixture[str],
|
|
File without changes
|
{deepflow_openagent_cli-0.1.4 → deepflow_openagent_cli-0.1.5}/src/deepflow_openagent_cli/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|