deepflow-openagent-cli 0.1.1__tar.gz → 0.1.4__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.
Files changed (15) hide show
  1. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/PKG-INFO +9 -1
  2. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/README.md +8 -0
  3. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/pyproject.toml +1 -1
  4. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/src/deepflow_openagent_cli/__init__.py +1 -1
  5. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/src/deepflow_openagent_cli/cli.py +136 -16
  6. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/src/deepflow_openagent_cli.egg-info/PKG-INFO +9 -1
  7. deepflow_openagent_cli-0.1.4/tests/test_cli.py +354 -0
  8. deepflow_openagent_cli-0.1.1/tests/test_cli.py +0 -165
  9. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/setup.cfg +0 -0
  10. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/src/deepflow_openagent_cli/__main__.py +0 -0
  11. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/src/deepflow_openagent_cli.egg-info/SOURCES.txt +0 -0
  12. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/src/deepflow_openagent_cli.egg-info/dependency_links.txt +0 -0
  13. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/src/deepflow_openagent_cli.egg-info/entry_points.txt +0 -0
  14. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/src/deepflow_openagent_cli.egg-info/requires.txt +0 -0
  15. {deepflow_openagent_cli-0.1.1 → deepflow_openagent_cli-0.1.4}/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.1
3
+ Version: 0.1.4
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
@@ -67,6 +67,14 @@ 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
69
  deepflow_openagent_cli chat 1 --message "Hello"
70
+ deepflow_openagent_cli steps 1 42
71
+ deepflow_openagent_cli steps get 1 42 100
72
+ deepflow_openagent_cli steps feedback 1 42 100 --rating like --comment "Helpful answer"
73
+
74
+ # Python code tool (create → update config → debug execute)
75
+ deepflow_openagent_cli tools create 1 --name order_query --tool-type python_code
76
+ deepflow_openagent_cli tools update 1 2 --json '{"config":{"code":"import openagent\nopenagent.set_output(openagent.args)","parameters_json":"{\"type\":\"object\",\"properties\":{\"order_id\":{\"type\":\"string\"}},\"required\":[\"order_id\"]}"}}'
77
+ deepflow_openagent_cli tools execute 1 2 --json '{"brief":"查订单","order_id":"A1"}'
70
78
  ```
71
79
 
72
80
  ## Development (build wheel/sdist locally)
@@ -46,6 +46,14 @@ 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
48
  deepflow_openagent_cli chat 1 --message "Hello"
49
+ deepflow_openagent_cli steps 1 42
50
+ deepflow_openagent_cli steps get 1 42 100
51
+ deepflow_openagent_cli steps feedback 1 42 100 --rating like --comment "Helpful answer"
52
+
53
+ # Python code tool (create → update config → debug execute)
54
+ deepflow_openagent_cli tools create 1 --name order_query --tool-type python_code
55
+ deepflow_openagent_cli tools update 1 2 --json '{"config":{"code":"import openagent\nopenagent.set_output(openagent.args)","parameters_json":"{\"type\":\"object\",\"properties\":{\"order_id\":{\"type\":\"string\"}},\"required\":[\"order_id\"]}"}}'
56
+ deepflow_openagent_cli tools execute 1 2 --json '{"brief":"查订单","order_id":"A1"}'
49
57
  ```
50
58
 
51
59
  ## Development (build wheel/sdist locally)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepflow-openagent-cli"
7
- version = "0.1.1"
7
+ version = "0.1.4"
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"
@@ -1,3 +1,3 @@
1
1
  """DeepFlow OpenAgent CLI — published PyPI package."""
2
2
 
3
- __version__ = "0.1.1"
3
+ __version__ = "0.1.4"
@@ -241,6 +241,40 @@ ENDPOINTS: tuple[EndpointDoc, ...] = (
241
241
  errors=["401 missing or invalid API key", "404 conversation not found"],
242
242
  example="deepflow_openagent_cli steps 1 42",
243
243
  ),
244
+ EndpointDoc(
245
+ key="steps.get",
246
+ method="GET",
247
+ path="/api/v1/agents/{agent_id}/conversations/{conversation_id}/steps/{step_id}",
248
+ scope="chat",
249
+ summary="Get full execution step detail, including LLM request/response payload fields.",
250
+ path_params=[
251
+ _param("agent_id", "integer", True, "Agent ID."),
252
+ _param("conversation_id", "integer", True, "Conversation ID."),
253
+ _param("step_id", "integer", True, "Step ID."),
254
+ ],
255
+ response="StepDetailResponse with request_messages, request_tools, request_params, response fields, and child tool calls.",
256
+ errors=["401 missing or invalid API key", "404 step not found"],
257
+ example="deepflow_openagent_cli steps get 1 42 100",
258
+ ),
259
+ EndpointDoc(
260
+ key="steps.feedback",
261
+ method="POST",
262
+ path="/api/v1/agents/{agent_id}/conversations/{conversation_id}/steps/{step_id}/feedback",
263
+ scope="chat",
264
+ summary="Submit or overwrite like/dislike feedback for one completed assistant_message step.",
265
+ path_params=[
266
+ _param("agent_id", "integer", True, "Agent ID."),
267
+ _param("conversation_id", "integer", True, "Conversation ID."),
268
+ _param("step_id", "integer", True, "Assistant message Step ID."),
269
+ ],
270
+ body_fields=[
271
+ _param("rating", "string", True, "Feedback rating: like or dislike."),
272
+ _param("comment", "string", False, "Optional feedback comment, max 500 characters."),
273
+ ],
274
+ response="StepFeedbackResponse with step_id, feedback_rating, feedback_comment, and feedback_updated_at.",
275
+ errors=["400 invalid step state", "401 missing or invalid API key", "403 scope missing", "404 step not found", "422 validation error"],
276
+ example="deepflow_openagent_cli steps feedback 1 42 100 --rating like --comment 'Helpful answer'",
277
+ ),
244
278
  EndpointDoc(
245
279
  key="agents.list",
246
280
  method="GET",
@@ -316,7 +350,7 @@ ENDPOINTS: tuple[EndpointDoc, ...] = (
316
350
  summary="Partially update system_prompt, model, selected_tool_ids, context, or pre_recall.",
317
351
  path_params=[_param("agent_id", "integer", True, "Agent ID.")],
318
352
  body_fields=[
319
- _param("system_prompt", "string", False, "System prompt, max 10000 chars."),
353
+ _param("system_prompt", "string", False, "System prompt, max 30000 chars."),
320
354
  _param("model", "object", False, "Model config."),
321
355
  _param("selected_tool_ids", "array[integer]", False, "Enabled tool IDs."),
322
356
  _param("context", "object", False, "Context policy."),
@@ -345,14 +379,24 @@ ENDPOINTS: tuple[EndpointDoc, ...] = (
345
379
  summary="Create a custom tool for an agent.",
346
380
  path_params=[_param("agent_id", "integer", True, "Agent ID.")],
347
381
  body_fields=[
348
- _param("name", "string", True, "Tool name, 1-128 chars."),
382
+ _param("name", "string", True, "Tool name, 1-128 chars, pattern ^[a-zA-Z0-9_-]+$."),
349
383
  _param("description", "string", False, "Tool description."),
350
384
  _param("tool_type", "string", True, "search, doc_query, doc_grep, notebook, tool_response_fetch, or python_code."),
351
- _param("config", "object", False, "Tool config."),
385
+ _param(
386
+ "config",
387
+ "object",
388
+ False,
389
+ "Tool config. python_code uses code (Python source) and parameters_json (JSON Schema text string).",
390
+ ),
352
391
  ],
353
392
  response="AgentToolResponse.",
354
- errors=["401 missing or invalid API key", "403 scope missing", "404 agent not found", "422 validation error"],
355
- example="deepflow_openagent_cli tools create 1 --name Search --tool-type search --json '{\"config\":{\"kb_ids\":[1]}}'",
393
+ errors=["401 missing or invalid API key", "403 scope missing", "404 agent not found", "400/422 validation error"],
394
+ example=(
395
+ "deepflow_openagent_cli tools create 1 --name order_query --tool-type python_code "
396
+ "--json '{\"config\":{\"code\":\"import openagent\\nopenagent.set_output(openagent.args)\","
397
+ "\"parameters_json\":\"{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{\\\"order_id\\\":{\\\"type\\\":\\\"string\\\"}},"
398
+ "\\\"required\\\":[\\\"order_id\\\"]}\"}}'"
399
+ ),
356
400
  ),
357
401
  EndpointDoc(
358
402
  key="tools.get",
@@ -379,13 +423,22 @@ ENDPOINTS: tuple[EndpointDoc, ...] = (
379
423
  _param("tool_id", "integer", True, "Tool ID."),
380
424
  ],
381
425
  body_fields=[
382
- _param("name", "string", False, "Tool name."),
426
+ _param("name", "string", False, "Tool name, pattern ^[a-zA-Z0-9_-]+$."),
383
427
  _param("description", "string", False, "Tool description."),
384
- _param("config", "object", False, "Tool config."),
428
+ _param(
429
+ "config",
430
+ "object",
431
+ False,
432
+ "Tool config. python_code: {code, parameters_json}; search/doc_*: {kb_ids}; human_handoff: {service_hours_id, route_fields}.",
433
+ ),
385
434
  ],
386
435
  response="AgentToolResponse.",
387
- errors=["401 missing or invalid API key", "403 scope missing", "404 tool not found", "422 validation error"],
388
- example="deepflow_openagent_cli tools update 1 2 --json '{\"config\":{\"kb_ids\":[1,2]}}'",
436
+ errors=["401 missing or invalid API key", "403 scope missing", "404 tool not found", "400/422 validation error"],
437
+ example=(
438
+ "deepflow_openagent_cli tools update 1 2 --json "
439
+ "'{\"config\":{\"code\":\"import openagent\\nopenagent.set_output(\\\"ok\\\")\","
440
+ "\"parameters_json\":\"{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{}}\"}}'"
441
+ ),
389
442
  ),
390
443
  EndpointDoc(
391
444
  key="tools.toggle",
@@ -427,10 +480,12 @@ ENDPOINTS: tuple[EndpointDoc, ...] = (
427
480
  _param("tool_id", "integer", True, "Tool ID."),
428
481
  ],
429
482
  query_params=[_param("conversation_id", "integer", False, "Conversation ID for tool context.")],
430
- body_fields=[_param("*", "object", False, "Tool-specific arguments.")],
431
- response="ToolExecuteResponse.",
483
+ body_fields=[
484
+ _param("*", "object", False, "Tool-specific arguments. python_code example: brief + business params from parameters_json."),
485
+ ],
486
+ response="ToolExecuteResponse with tool_name, tool_type, arguments, result, duration_ms.",
432
487
  errors=["401 missing or invalid API key", "403 scope missing", "404 tool not found", "422 validation error"],
433
- example="deepflow_openagent_cli tools execute 1 2 --json '{\"query\":\"pricing\"}'",
488
+ example="deepflow_openagent_cli tools execute 1 2 --json '{\"brief\":\"查订单\",\"order_id\":\"A1\"}'",
434
489
  ),
435
490
  EndpointDoc(
436
491
  key="channels.list",
@@ -785,10 +840,39 @@ def handle_chat(args: argparse.Namespace) -> int:
785
840
 
786
841
 
787
842
  def handle_steps(args: argparse.Namespace) -> int:
843
+ action, values = parse_steps_args(args.steps_args)
844
+ if action == "get":
845
+ agent_id, conversation_id, step_id = values
846
+ return run_request(
847
+ args,
848
+ "steps.get",
849
+ {
850
+ "agent_id": agent_id,
851
+ "conversation_id": conversation_id,
852
+ "step_id": step_id,
853
+ },
854
+ )
855
+ if action == "feedback":
856
+ agent_id, conversation_id, step_id = values
857
+ body = merge_payload(args, {"rating": args.rating, "comment": args.comment})
858
+ if "rating" not in body:
859
+ raise CliError("Missing feedback rating. Pass --rating like|dislike or include rating in --json.")
860
+ return run_request(
861
+ args,
862
+ "steps.feedback",
863
+ {
864
+ "agent_id": agent_id,
865
+ "conversation_id": conversation_id,
866
+ "step_id": step_id,
867
+ },
868
+ body=body,
869
+ )
870
+
871
+ agent_id, conversation_id = values
788
872
  return run_request(
789
873
  args,
790
874
  "steps.list",
791
- {"agent_id": args.agent_id, "conversation_id": args.conversation_id},
875
+ {"agent_id": agent_id, "conversation_id": conversation_id},
792
876
  )
793
877
 
794
878
 
@@ -967,9 +1051,20 @@ def build_parser() -> argparse.ArgumentParser:
967
1051
  add_json_args(chat)
968
1052
  chat.set_defaults(handler=handle_chat)
969
1053
 
970
- steps = subparsers.add_parser("steps", help="List conversation execution steps.")
971
- steps.add_argument("agent_id", type=int)
972
- steps.add_argument("conversation_id", type=int)
1054
+ steps = subparsers.add_parser(
1055
+ "steps",
1056
+ help="List, inspect, or submit feedback for conversation execution steps.",
1057
+ description=(
1058
+ "List conversation execution steps, get full step detail, or submit feedback. "
1059
+ "Forms: steps <agent_id> <conversation_id>; "
1060
+ "steps get <agent_id> <conversation_id> <step_id>; "
1061
+ "steps feedback <agent_id> <conversation_id> <step_id> --rating like."
1062
+ ),
1063
+ )
1064
+ steps.add_argument("steps_args", nargs="+", metavar="ARGS")
1065
+ steps.add_argument("--rating", choices=["like", "dislike"])
1066
+ steps.add_argument("--comment")
1067
+ add_json_args(steps)
973
1068
  steps.set_defaults(handler=handle_steps)
974
1069
 
975
1070
  agents = subparsers.add_parser("agents", help="Agent endpoints.")
@@ -1063,6 +1158,31 @@ def parse_bool(value: str) -> bool:
1063
1158
  raise argparse.ArgumentTypeError("Expected boolean: true/false")
1064
1159
 
1065
1160
 
1161
+ def parse_steps_args(values: list[str]) -> tuple[str, tuple[int, ...]]:
1162
+ action = "list"
1163
+ raw_values = values
1164
+ if values[0] in {"list", "get", "detail", "feedback"}:
1165
+ action = "get" if values[0] == "detail" else values[0]
1166
+ raw_values = values[1:]
1167
+ elif len(values) == 3:
1168
+ action = "get"
1169
+
1170
+ expected = 3 if action in {"get", "feedback"} else 2
1171
+ if len(raw_values) != expected:
1172
+ raise CliError(
1173
+ "Usage: steps <agent_id> <conversation_id> or "
1174
+ "steps get <agent_id> <conversation_id> <step_id> or "
1175
+ "steps feedback <agent_id> <conversation_id> <step_id> --rating like|dislike."
1176
+ )
1177
+
1178
+ try:
1179
+ parsed = tuple(int(value) for value in raw_values)
1180
+ except ValueError as exc:
1181
+ raise CliError("steps arguments must be integers.") from exc
1182
+
1183
+ return action, parsed
1184
+
1185
+
1066
1186
  def main(argv: list[str] | None = None) -> int:
1067
1187
  parser = build_parser()
1068
1188
  args = parser.parse_args(argv)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepflow-openagent-cli
3
- Version: 0.1.1
3
+ Version: 0.1.4
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
@@ -67,6 +67,14 @@ 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
69
  deepflow_openagent_cli chat 1 --message "Hello"
70
+ deepflow_openagent_cli steps 1 42
71
+ deepflow_openagent_cli steps get 1 42 100
72
+ deepflow_openagent_cli steps feedback 1 42 100 --rating like --comment "Helpful answer"
73
+
74
+ # Python code tool (create → update config → debug execute)
75
+ deepflow_openagent_cli tools create 1 --name order_query --tool-type python_code
76
+ deepflow_openagent_cli tools update 1 2 --json '{"config":{"code":"import openagent\nopenagent.set_output(openagent.args)","parameters_json":"{\"type\":\"object\",\"properties\":{\"order_id\":{\"type\":\"string\"}},\"required\":[\"order_id\"]}"}}'
77
+ deepflow_openagent_cli tools execute 1 2 --json '{"brief":"查订单","order_id":"A1"}'
70
78
  ```
71
79
 
72
80
  ## Development (build wheel/sdist locally)
@@ -0,0 +1,354 @@
1
+ import json
2
+ from pathlib import Path
3
+ import tomllib
4
+
5
+ import pytest
6
+
7
+ from deepflow_openagent_cli import __version__
8
+ from deepflow_openagent_cli.cli import (
9
+ CliError,
10
+ ENDPOINTS,
11
+ OpenApiClient,
12
+ build_parser,
13
+ client_from_args,
14
+ get_endpoint,
15
+ load_json_payload,
16
+ main,
17
+ )
18
+
19
+
20
+ def test_version_matches_release() -> None:
21
+ pyproject = Path(__file__).resolve().parents[1] / "pyproject.toml"
22
+ metadata = tomllib.loads(pyproject.read_text(encoding="utf-8"))
23
+
24
+ assert __version__ == metadata["project"]["version"]
25
+
26
+
27
+ def test_catalog_has_stable_unique_keys() -> None:
28
+ keys = [endpoint.key for endpoint in ENDPOINTS]
29
+
30
+ assert len(keys) == len(set(keys))
31
+ assert "knowledge.list" in keys
32
+ assert "knowledge.documents" in keys
33
+ assert "documents.query" in keys
34
+ assert "chat.stream" in keys
35
+ assert "steps.get" in keys
36
+ assert "channels.secret_key" in keys
37
+
38
+
39
+ def test_get_endpoint_unknown_key_raises_clear_error() -> None:
40
+ with pytest.raises(CliError, match="Unknown endpoint"):
41
+ get_endpoint("missing.endpoint")
42
+
43
+
44
+ def test_load_json_payload_rejects_non_object() -> None:
45
+ with pytest.raises(CliError, match="must be an object"):
46
+ load_json_payload("[1, 2, 3]", None)
47
+
48
+
49
+ def test_build_url_formats_path_and_query() -> None:
50
+ client = OpenApiClient("http://localhost:5001/", "sk-test")
51
+ endpoint = get_endpoint("knowledge.documents")
52
+
53
+ url = client._build_url(
54
+ endpoint,
55
+ {"kb_id": 1},
56
+ {"page": 2, "per_page": 20, "source": None},
57
+ )
58
+
59
+ assert url == "http://localhost:5001/api/v1/knowledge-bases/1/documents?page=2&per_page=20"
60
+
61
+
62
+ def test_client_from_args_reads_openagent_env(monkeypatch: pytest.MonkeyPatch) -> None:
63
+ monkeypatch.setenv("OPENAGENT_HOST", "https://api.example.com")
64
+ monkeypatch.setenv("OPENAGENT_API_KEY", "sk-openagent")
65
+ args = build_parser().parse_args(["docs", "list"])
66
+
67
+ client = client_from_args(args)
68
+
69
+ assert client.host == "https://api.example.com"
70
+ assert client.api_key == "sk-openagent"
71
+
72
+
73
+ def test_client_from_args_reads_legacy_newagent_env(monkeypatch: pytest.MonkeyPatch) -> None:
74
+ monkeypatch.delenv("OPENAGENT_HOST", raising=False)
75
+ monkeypatch.delenv("OPENAGENT_API_KEY", raising=False)
76
+ monkeypatch.setenv("NEWAGENT_HOST", "https://legacy.example.com")
77
+ monkeypatch.setenv("NEWAGENT_API_KEY", "sk-legacy")
78
+ args = build_parser().parse_args(["docs", "list"])
79
+
80
+ client = client_from_args(args)
81
+
82
+ assert client.host == "https://legacy.example.com"
83
+ assert client.api_key == "sk-legacy"
84
+
85
+
86
+ def test_docs_show_json_outputs_endpoint_detail(capsys: pytest.CaptureFixture[str]) -> None:
87
+ exit_code = main(["docs", "show", "documents.query", "--json"])
88
+
89
+ captured = capsys.readouterr()
90
+ assert exit_code == 0
91
+ payload = json.loads(captured.out)
92
+ assert payload["method"] == "POST"
93
+ assert payload["scope"] == "chat"
94
+
95
+
96
+ def test_query_document_command_posts_body(
97
+ monkeypatch: pytest.MonkeyPatch,
98
+ capsys: pytest.CaptureFixture[str],
99
+ ) -> None:
100
+ calls = []
101
+
102
+ def fake_request(self, endpoint_key, path_params=None, query=None, body=None, *, stream=False):
103
+ calls.append((endpoint_key, path_params, query, body, stream))
104
+ return b'{"ok": true}'
105
+
106
+ monkeypatch.setattr(OpenApiClient, "request", fake_request)
107
+
108
+ exit_code = main(
109
+ [
110
+ "--host",
111
+ "http://localhost:5001",
112
+ "--api-key",
113
+ "sk-test",
114
+ "knowledge",
115
+ "query-document",
116
+ "6670",
117
+ "--slice-id",
118
+ "512208",
119
+ "--line",
120
+ "1720",
121
+ ]
122
+ )
123
+
124
+ captured = capsys.readouterr()
125
+ assert exit_code == 0
126
+ assert json.loads(captured.out) == {"ok": True}
127
+ assert calls == [
128
+ (
129
+ "documents.query",
130
+ None,
131
+ None,
132
+ {"doc_id": 6670, "slice_id": 512208, "line": 1720},
133
+ False,
134
+ )
135
+ ]
136
+
137
+
138
+ def test_steps_list_keeps_legacy_short_form(
139
+ monkeypatch: pytest.MonkeyPatch,
140
+ capsys: pytest.CaptureFixture[str],
141
+ ) -> None:
142
+ calls = []
143
+
144
+ def fake_request(self, endpoint_key, path_params=None, query=None, body=None, *, stream=False):
145
+ calls.append((endpoint_key, path_params, query, body, stream))
146
+ return b'{"ok": true}'
147
+
148
+ monkeypatch.setattr(OpenApiClient, "request", fake_request)
149
+
150
+ exit_code = main(
151
+ [
152
+ "--host",
153
+ "http://localhost:5001",
154
+ "--api-key",
155
+ "sk-test",
156
+ "steps",
157
+ "1",
158
+ "42",
159
+ ]
160
+ )
161
+
162
+ captured = capsys.readouterr()
163
+ assert exit_code == 0
164
+ assert json.loads(captured.out) == {"ok": True}
165
+ assert calls == [
166
+ (
167
+ "steps.list",
168
+ {"agent_id": 1, "conversation_id": 42},
169
+ None,
170
+ None,
171
+ False,
172
+ )
173
+ ]
174
+
175
+
176
+ def test_steps_get_fetches_full_step_detail(
177
+ monkeypatch: pytest.MonkeyPatch,
178
+ capsys: pytest.CaptureFixture[str],
179
+ ) -> None:
180
+ calls = []
181
+
182
+ def fake_request(self, endpoint_key, path_params=None, query=None, body=None, *, stream=False):
183
+ calls.append((endpoint_key, path_params, query, body, stream))
184
+ return b'{"ok": true}'
185
+
186
+ monkeypatch.setattr(OpenApiClient, "request", fake_request)
187
+
188
+ exit_code = main(
189
+ [
190
+ "--host",
191
+ "http://localhost:5001",
192
+ "--api-key",
193
+ "sk-test",
194
+ "steps",
195
+ "get",
196
+ "1",
197
+ "42",
198
+ "100",
199
+ ]
200
+ )
201
+
202
+ captured = capsys.readouterr()
203
+ assert exit_code == 0
204
+ assert json.loads(captured.out) == {"ok": True}
205
+ assert calls == [
206
+ (
207
+ "steps.get",
208
+ {"agent_id": 1, "conversation_id": 42, "step_id": 100},
209
+ None,
210
+ None,
211
+ False,
212
+ )
213
+ ]
214
+
215
+
216
+ def test_tools_create_accepts_doc_grep(
217
+ monkeypatch: pytest.MonkeyPatch,
218
+ capsys: pytest.CaptureFixture[str],
219
+ ) -> None:
220
+ calls = []
221
+
222
+ def fake_request(self, endpoint_key, path_params=None, query=None, body=None, *, stream=False):
223
+ calls.append((endpoint_key, path_params, query, body, stream))
224
+ return b'{"ok": true}'
225
+
226
+ monkeypatch.setattr(OpenApiClient, "request", fake_request)
227
+
228
+ exit_code = main(
229
+ [
230
+ "--host",
231
+ "http://localhost:5001",
232
+ "--api-key",
233
+ "sk-test",
234
+ "tools",
235
+ "create",
236
+ "1",
237
+ "--name",
238
+ "Doc Grep",
239
+ "--tool-type",
240
+ "doc_grep",
241
+ ]
242
+ )
243
+
244
+ captured = capsys.readouterr()
245
+ assert exit_code == 0
246
+ assert json.loads(captured.out) == {"ok": True}
247
+ assert calls[0][0] == "tools.create"
248
+ assert calls[0][1] == {"agent_id": 1}
249
+ assert calls[0][3]["tool_type"] == "doc_grep"
250
+
251
+
252
+ def test_tools_create_accepts_python_code(
253
+ monkeypatch: pytest.MonkeyPatch,
254
+ capsys: pytest.CaptureFixture[str],
255
+ ) -> None:
256
+ calls = []
257
+
258
+ def fake_request(self, endpoint_key, path_params=None, query=None, body=None, *, stream=False):
259
+ calls.append((endpoint_key, path_params, query, body, stream))
260
+ return b'{"ok": true}'
261
+
262
+ monkeypatch.setattr(OpenApiClient, "request", fake_request)
263
+
264
+ config = {
265
+ "code": "import openagent\nopenagent.set_output(openagent.args)",
266
+ "parameters_json": '{"type":"object","properties":{"order_id":{"type":"string"}},"required":["order_id"]}',
267
+ }
268
+ exit_code = main(
269
+ [
270
+ "--host",
271
+ "http://localhost:5001",
272
+ "--api-key",
273
+ "sk-test",
274
+ "tools",
275
+ "create",
276
+ "1",
277
+ "--name",
278
+ "order_query",
279
+ "--tool-type",
280
+ "python_code",
281
+ "--json",
282
+ json.dumps({"config": config}),
283
+ ]
284
+ )
285
+
286
+ captured = capsys.readouterr()
287
+ assert exit_code == 0
288
+ assert json.loads(captured.out) == {"ok": True}
289
+ assert calls[0][0] == "tools.create"
290
+ assert calls[0][1] == {"agent_id": 1}
291
+ assert calls[0][3]["tool_type"] == "python_code"
292
+ assert calls[0][3]["name"] == "order_query"
293
+ assert calls[0][3]["config"] == config
294
+
295
+
296
+ def test_tools_update_and_execute_python_code(
297
+ monkeypatch: pytest.MonkeyPatch,
298
+ capsys: pytest.CaptureFixture[str],
299
+ ) -> None:
300
+ calls = []
301
+
302
+ def fake_request(self, endpoint_key, path_params=None, query=None, body=None, *, stream=False):
303
+ calls.append((endpoint_key, path_params, query, body, stream))
304
+ return b'{"ok": true}'
305
+
306
+ monkeypatch.setattr(OpenApiClient, "request", fake_request)
307
+
308
+ update_body = {
309
+ "config": {
310
+ "code": "import openagent\nopenagent.set_output('ok')",
311
+ "parameters_json": '{"type":"object","properties":{}}',
312
+ }
313
+ }
314
+ exit_code = main(
315
+ [
316
+ "--host",
317
+ "http://localhost:5001",
318
+ "--api-key",
319
+ "sk-test",
320
+ "tools",
321
+ "update",
322
+ "1",
323
+ "2",
324
+ "--json",
325
+ json.dumps(update_body),
326
+ ]
327
+ )
328
+ captured = capsys.readouterr()
329
+ assert exit_code == 0
330
+ assert json.loads(captured.out) == {"ok": True}
331
+ assert calls[0][0] == "tools.update"
332
+ assert calls[0][1] == {"agent_id": 1, "tool_id": 2}
333
+ assert calls[0][3] == update_body
334
+
335
+ exit_code = main(
336
+ [
337
+ "--host",
338
+ "http://localhost:5001",
339
+ "--api-key",
340
+ "sk-test",
341
+ "tools",
342
+ "execute",
343
+ "1",
344
+ "2",
345
+ "--json",
346
+ '{"brief":"查订单","order_id":"A1"}',
347
+ ]
348
+ )
349
+ captured = capsys.readouterr()
350
+ assert exit_code == 0
351
+ assert json.loads(captured.out) == {"ok": True}
352
+ assert calls[1][0] == "tools.execute"
353
+ assert calls[1][1] == {"agent_id": 1, "tool_id": 2}
354
+ assert calls[1][3] == {"brief": "查订单", "order_id": "A1"}
@@ -1,165 +0,0 @@
1
- import json
2
-
3
- import pytest
4
-
5
- from deepflow_openagent_cli import __version__
6
- from deepflow_openagent_cli.cli import (
7
- CliError,
8
- ENDPOINTS,
9
- OpenApiClient,
10
- build_parser,
11
- client_from_args,
12
- get_endpoint,
13
- load_json_payload,
14
- main,
15
- )
16
-
17
-
18
- def test_version_matches_release() -> None:
19
- assert __version__ == "0.1.1"
20
-
21
-
22
- def test_catalog_has_stable_unique_keys() -> None:
23
- keys = [endpoint.key for endpoint in ENDPOINTS]
24
-
25
- assert len(keys) == len(set(keys))
26
- assert "knowledge.list" in keys
27
- assert "knowledge.documents" in keys
28
- assert "documents.query" in keys
29
- assert "chat.stream" in keys
30
- assert "channels.secret_key" in keys
31
-
32
-
33
- def test_get_endpoint_unknown_key_raises_clear_error() -> None:
34
- with pytest.raises(CliError, match="Unknown endpoint"):
35
- get_endpoint("missing.endpoint")
36
-
37
-
38
- def test_load_json_payload_rejects_non_object() -> None:
39
- with pytest.raises(CliError, match="must be an object"):
40
- load_json_payload("[1, 2, 3]", None)
41
-
42
-
43
- def test_build_url_formats_path_and_query() -> None:
44
- client = OpenApiClient("http://localhost:5001/", "sk-test")
45
- endpoint = get_endpoint("knowledge.documents")
46
-
47
- url = client._build_url(
48
- endpoint,
49
- {"kb_id": 1},
50
- {"page": 2, "per_page": 20, "source": None},
51
- )
52
-
53
- assert url == "http://localhost:5001/api/v1/knowledge-bases/1/documents?page=2&per_page=20"
54
-
55
-
56
- def test_client_from_args_reads_openagent_env(monkeypatch: pytest.MonkeyPatch) -> None:
57
- monkeypatch.setenv("OPENAGENT_HOST", "https://api.example.com")
58
- monkeypatch.setenv("OPENAGENT_API_KEY", "sk-openagent")
59
- args = build_parser().parse_args(["docs", "list"])
60
-
61
- client = client_from_args(args)
62
-
63
- assert client.host == "https://api.example.com"
64
- assert client.api_key == "sk-openagent"
65
-
66
-
67
- def test_client_from_args_reads_legacy_newagent_env(monkeypatch: pytest.MonkeyPatch) -> None:
68
- monkeypatch.delenv("OPENAGENT_HOST", raising=False)
69
- monkeypatch.delenv("OPENAGENT_API_KEY", raising=False)
70
- monkeypatch.setenv("NEWAGENT_HOST", "https://legacy.example.com")
71
- monkeypatch.setenv("NEWAGENT_API_KEY", "sk-legacy")
72
- args = build_parser().parse_args(["docs", "list"])
73
-
74
- client = client_from_args(args)
75
-
76
- assert client.host == "https://legacy.example.com"
77
- assert client.api_key == "sk-legacy"
78
-
79
-
80
- def test_docs_show_json_outputs_endpoint_detail(capsys: pytest.CaptureFixture[str]) -> None:
81
- exit_code = main(["docs", "show", "documents.query", "--json"])
82
-
83
- captured = capsys.readouterr()
84
- assert exit_code == 0
85
- payload = json.loads(captured.out)
86
- assert payload["method"] == "POST"
87
- assert payload["scope"] == "chat"
88
-
89
-
90
- def test_query_document_command_posts_body(
91
- monkeypatch: pytest.MonkeyPatch,
92
- capsys: pytest.CaptureFixture[str],
93
- ) -> None:
94
- calls = []
95
-
96
- def fake_request(self, endpoint_key, path_params=None, query=None, body=None, *, stream=False):
97
- calls.append((endpoint_key, path_params, query, body, stream))
98
- return b'{"ok": true}'
99
-
100
- monkeypatch.setattr(OpenApiClient, "request", fake_request)
101
-
102
- exit_code = main(
103
- [
104
- "--host",
105
- "http://localhost:5001",
106
- "--api-key",
107
- "sk-test",
108
- "knowledge",
109
- "query-document",
110
- "6670",
111
- "--slice-id",
112
- "512208",
113
- "--line",
114
- "1720",
115
- ]
116
- )
117
-
118
- captured = capsys.readouterr()
119
- assert exit_code == 0
120
- assert json.loads(captured.out) == {"ok": True}
121
- assert calls == [
122
- (
123
- "documents.query",
124
- None,
125
- None,
126
- {"doc_id": 6670, "slice_id": 512208, "line": 1720},
127
- False,
128
- )
129
- ]
130
-
131
-
132
- def test_tools_create_accepts_doc_grep(
133
- monkeypatch: pytest.MonkeyPatch,
134
- capsys: pytest.CaptureFixture[str],
135
- ) -> None:
136
- calls = []
137
-
138
- def fake_request(self, endpoint_key, path_params=None, query=None, body=None, *, stream=False):
139
- calls.append((endpoint_key, path_params, query, body, stream))
140
- return b'{"ok": true}'
141
-
142
- monkeypatch.setattr(OpenApiClient, "request", fake_request)
143
-
144
- exit_code = main(
145
- [
146
- "--host",
147
- "http://localhost:5001",
148
- "--api-key",
149
- "sk-test",
150
- "tools",
151
- "create",
152
- "1",
153
- "--name",
154
- "Doc Grep",
155
- "--tool-type",
156
- "doc_grep",
157
- ]
158
- )
159
-
160
- captured = capsys.readouterr()
161
- assert exit_code == 0
162
- assert json.loads(captured.out) == {"ok": True}
163
- assert calls[0][0] == "tools.create"
164
- assert calls[0][1] == {"agent_id": 1}
165
- assert calls[0][3]["tool_type"] == "doc_grep"