rathflow-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.
- rathflow_cli/__init__.py +6 -0
- rathflow_cli/__main__.py +6 -0
- rathflow_cli/cli.py +161 -0
- rathflow_cli/commands/__init__.py +0 -0
- rathflow_cli/commands/_common.py +78 -0
- rathflow_cli/commands/admin.py +83 -0
- rathflow_cli/commands/agent.py +288 -0
- rathflow_cli/commands/api_cmd.py +75 -0
- rathflow_cli/commands/asset.py +449 -0
- rathflow_cli/commands/auth_cmd.py +160 -0
- rathflow_cli/commands/billing.py +71 -0
- rathflow_cli/commands/config_cmd.py +98 -0
- rathflow_cli/commands/memory.py +275 -0
- rathflow_cli/commands/org.py +70 -0
- rathflow_cli/commands/project.py +223 -0
- rathflow_cli/commands/sandbox.py +282 -0
- rathflow_cli/commands/session.py +237 -0
- rathflow_cli/commands/workflow.py +81 -0
- rathflow_cli/config.py +69 -0
- rathflow_cli/endpoints.py +207 -0
- rathflow_cli/errors.py +70 -0
- rathflow_cli/http.py +294 -0
- rathflow_cli/output.py +95 -0
- rathflow_cli/selftest.py +120 -0
- rathflow_cli/state.py +129 -0
- rathflow_cli-0.1.0.dist-info/METADATA +164 -0
- rathflow_cli-0.1.0.dist-info/RECORD +31 -0
- rathflow_cli-0.1.0.dist-info/WHEEL +5 -0
- rathflow_cli-0.1.0.dist-info/entry_points.txt +2 -0
- rathflow_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- rathflow_cli-0.1.0.dist-info/top_level.txt +1 -0
rathflow_cli/__init__.py
ADDED
rathflow_cli/__main__.py
ADDED
rathflow_cli/cli.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""typer 根应用与全局选项。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from . import __version__, errors, state as state_mod
|
|
10
|
+
from .state import State
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(
|
|
13
|
+
add_completion=False,
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
help="RathFlow 命令行客户端(JWT 登录;命令覆盖北向 REST 全部端点)。",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _print_version(value: bool) -> None:
|
|
20
|
+
if value:
|
|
21
|
+
typer.echo(f"rathflow {__version__}")
|
|
22
|
+
raise typer.Exit()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.callback()
|
|
26
|
+
def root(
|
|
27
|
+
ctx: typer.Context,
|
|
28
|
+
profile: str = typer.Option(None, "--profile", "-p", help="配置档名(缺省 default)"),
|
|
29
|
+
base_url: str = typer.Option(None, "--base-url", help="gateway 地址(env RATHFLOW_BASE_URL)"),
|
|
30
|
+
project: str = typer.Option(None, "--project", help="项目作用域(env RATHFLOW_PROJECT)"),
|
|
31
|
+
json_out: bool = typer.Option(False, "--json", help="以 JSON 输出(脚本消费的稳定形态)"),
|
|
32
|
+
quiet: bool = typer.Option(False, "--quiet", "-q", help="只输出主键"),
|
|
33
|
+
version: bool = typer.Option(
|
|
34
|
+
False, "--version", callback=_print_version, is_eager=True, help="显示版本并退出"
|
|
35
|
+
),
|
|
36
|
+
) -> None:
|
|
37
|
+
ctx.obj = state_mod.install(
|
|
38
|
+
State(
|
|
39
|
+
profile_name=profile,
|
|
40
|
+
base_url=base_url,
|
|
41
|
+
project=project,
|
|
42
|
+
json_out=json_out,
|
|
43
|
+
quiet=quiet,
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _register() -> None:
|
|
49
|
+
from .commands import (
|
|
50
|
+
admin,
|
|
51
|
+
agent,
|
|
52
|
+
api_cmd,
|
|
53
|
+
asset,
|
|
54
|
+
auth_cmd,
|
|
55
|
+
billing,
|
|
56
|
+
config_cmd,
|
|
57
|
+
memory,
|
|
58
|
+
org,
|
|
59
|
+
project,
|
|
60
|
+
sandbox,
|
|
61
|
+
session,
|
|
62
|
+
workflow,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
app.add_typer(auth_cmd.app, name="auth")
|
|
66
|
+
app.add_typer(config_cmd.app, name="config")
|
|
67
|
+
app.add_typer(org.app, name="org")
|
|
68
|
+
app.add_typer(project.app, name="project")
|
|
69
|
+
app.add_typer(session.app, name="session")
|
|
70
|
+
app.add_typer(memory.app, name="memory")
|
|
71
|
+
app.add_typer(sandbox.app, name="sandbox")
|
|
72
|
+
app.add_typer(agent.app, name="agent")
|
|
73
|
+
app.add_typer(asset.app, name="asset")
|
|
74
|
+
app.add_typer(billing.app, name="billing")
|
|
75
|
+
app.add_typer(workflow.app, name="workflow")
|
|
76
|
+
app.add_typer(admin.app, name="admin")
|
|
77
|
+
|
|
78
|
+
from .commands.auth_cmd import whoami
|
|
79
|
+
|
|
80
|
+
app.command("whoami", help="显示登录身份、作用域与可访问项目")(whoami)
|
|
81
|
+
# 逃生舱直接挂根:`rathflow api <Key>` 不必多打一层 `call`
|
|
82
|
+
app.command("api", help="任意端点调用(`rathflow api --list` 看全部)")(api_cmd.call)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
_register()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# 可以挪到命令行最前的全局选项:前者是布尔旗标,后者各吃一个值。
|
|
89
|
+
_HOIST_FLAGS = frozenset({"--json", "--quiet"})
|
|
90
|
+
_HOIST_OPTS = frozenset({"--profile", "--base-url", "--project"})
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _value_opts() -> set[str]:
|
|
94
|
+
"""全 app 里「吃下一个词」的选项名 —— 用来判断挪旗标安不安全。
|
|
95
|
+
|
|
96
|
+
走 click 命令树而非 Typer 对象:Typer 只是注册表,click Group 是懒构建的。
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def walk(cmd) -> set[str]:
|
|
100
|
+
found: set[str] = set()
|
|
101
|
+
for param in getattr(cmd, "params", []):
|
|
102
|
+
if getattr(param, "is_flag", False) or getattr(param, "count", False):
|
|
103
|
+
continue
|
|
104
|
+
found.update(getattr(param, "opts", []))
|
|
105
|
+
for sub in getattr(cmd, "commands", {}).values():
|
|
106
|
+
found |= walk(sub)
|
|
107
|
+
return found
|
|
108
|
+
|
|
109
|
+
return walk(typer.main.get_command(app))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _hoist_globals(argv: list[str]) -> list[str]:
|
|
113
|
+
"""把写在命令**后面**的全局选项挪到最前。
|
|
114
|
+
|
|
115
|
+
click 只认组级选项出现在子命令之前,故 `rathflow project list --json` 会被
|
|
116
|
+
当成给 `list` 传参而报错。脚本里这个顺序太自然了,于是这里做一次改写:
|
|
117
|
+
只挪白名单里的选项,且前一个词不是吃值的选项(否则那个词是被当值用的,
|
|
118
|
+
例如 `--body --json` 里的 `--json` 是值,不能动)。
|
|
119
|
+
"""
|
|
120
|
+
if len(argv) < 2:
|
|
121
|
+
return argv
|
|
122
|
+
value_opts = _value_opts()
|
|
123
|
+
head, tail = [argv[0]], []
|
|
124
|
+
hoisted: list[str] = []
|
|
125
|
+
idx = 1
|
|
126
|
+
while idx < len(argv):
|
|
127
|
+
tok = argv[idx]
|
|
128
|
+
prev = argv[idx - 1]
|
|
129
|
+
# 前一个词若是吃值的选项,本词就是它的值(click 也这么看),别动
|
|
130
|
+
safe = prev not in value_opts
|
|
131
|
+
if safe and tok in _HOIST_FLAGS:
|
|
132
|
+
hoisted.append(tok)
|
|
133
|
+
idx += 1
|
|
134
|
+
continue
|
|
135
|
+
if safe and tok in _HOIST_OPTS and idx + 1 < len(argv) and not argv[idx + 1].startswith("-"):
|
|
136
|
+
hoisted.extend([tok, argv[idx + 1]])
|
|
137
|
+
idx += 2
|
|
138
|
+
continue
|
|
139
|
+
tail.append(tok)
|
|
140
|
+
idx += 1
|
|
141
|
+
return head + hoisted + tail
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def main() -> None:
|
|
145
|
+
sys.argv = _hoist_globals(sys.argv)
|
|
146
|
+
try:
|
|
147
|
+
app()
|
|
148
|
+
except errors.UsageError as exc:
|
|
149
|
+
print(f"错误:{exc}", file=sys.stderr)
|
|
150
|
+
sys.exit(errors.EXIT_USAGE)
|
|
151
|
+
except errors.ApiError as exc:
|
|
152
|
+
print(f"错误:{exc}", file=sys.stderr)
|
|
153
|
+
for detail in exc.details:
|
|
154
|
+
print(f" - {detail}", file=sys.stderr)
|
|
155
|
+
sys.exit(exc.exit_code)
|
|
156
|
+
except KeyboardInterrupt: # pragma: no cover
|
|
157
|
+
sys.exit(130)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__": # pragma: no cover
|
|
161
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""命令间共用的小工具(参数解析与请求体装配)。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from ..errors import UsageError
|
|
11
|
+
|
|
12
|
+
PATH_OPT = typer.Option(None, "--path", help="路径参数 k=v,可重复(如 --path session_id=abc)")
|
|
13
|
+
QUERY_OPT = typer.Option(None, "--query", help="查询参数 k=v,可重复")
|
|
14
|
+
BODY_OPT = typer.Option(None, "--body", help="请求体 JSON 字面量")
|
|
15
|
+
BODY_FILE_OPT = typer.Option(None, "--body-file", help="从文件读请求体 JSON(- 读 stdin)")
|
|
16
|
+
PAGE_SIZE_OPT = typer.Option(None, "--page-size", help="分页大小")
|
|
17
|
+
PAGE_TOKEN_OPT = typer.Option(None, "--page-token", help="分页游标")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def pairs(items: list[str] | None) -> dict:
|
|
21
|
+
"""['k=v', ...] → {k: v};值里的 = 保留(只切第一个)。"""
|
|
22
|
+
out: dict[str, str] = {}
|
|
23
|
+
for item in items or []:
|
|
24
|
+
if "=" not in item:
|
|
25
|
+
raise UsageError(f"参数需形如 k=v,收到 {item!r}")
|
|
26
|
+
key, _, value = item.partition("=")
|
|
27
|
+
out[key.strip()] = value
|
|
28
|
+
return out
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def body_of(body: str | None, body_file: str | None) -> dict | None:
|
|
32
|
+
"""装配请求体:字面量或文件(`-` = stdin)。两者互斥。"""
|
|
33
|
+
if body and body_file:
|
|
34
|
+
raise UsageError("--body 与 --body-file 不能同时用")
|
|
35
|
+
raw: str | None = None
|
|
36
|
+
if body_file:
|
|
37
|
+
raw = sys.stdin.read() if body_file == "-" else _read(body_file)
|
|
38
|
+
elif body:
|
|
39
|
+
raw = body
|
|
40
|
+
else:
|
|
41
|
+
return None
|
|
42
|
+
raw = raw.strip()
|
|
43
|
+
if not raw:
|
|
44
|
+
return {}
|
|
45
|
+
try:
|
|
46
|
+
parsed = json.loads(raw)
|
|
47
|
+
except json.JSONDecodeError as exc:
|
|
48
|
+
raise UsageError(f"请求体不是合法 JSON:{exc}") from None
|
|
49
|
+
if not isinstance(parsed, dict):
|
|
50
|
+
raise UsageError("请求体必须是 JSON 对象")
|
|
51
|
+
return parsed
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _read(path: str) -> str:
|
|
55
|
+
try:
|
|
56
|
+
with open(path, encoding="utf-8") as fh:
|
|
57
|
+
return fh.read()
|
|
58
|
+
except OSError as exc:
|
|
59
|
+
raise UsageError(f"读取请求体文件失败:{exc}") from None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def pagination(page_size: int | None, page_token: str | None, extra: dict | None = None) -> dict:
|
|
63
|
+
"""装配分页查询串(protojson 查询名用 proto 字段名 snake_case)。"""
|
|
64
|
+
query = dict(extra or {})
|
|
65
|
+
if page_size is not None:
|
|
66
|
+
query["page_size"] = page_size
|
|
67
|
+
if page_token:
|
|
68
|
+
query["page_token"] = page_token
|
|
69
|
+
return query
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def id_of(payload: dict, *keys: str) -> str:
|
|
73
|
+
"""从响应里挑主键用于 -q 输出。"""
|
|
74
|
+
for key in keys:
|
|
75
|
+
value = (payload or {}).get(key)
|
|
76
|
+
if value:
|
|
77
|
+
return str(value)
|
|
78
|
+
return ""
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""admin:平台管理(用户、上下文令牌签名密钥)。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from .. import output
|
|
8
|
+
from ..commands._common import PAGE_SIZE_OPT, PAGE_TOKEN_OPT, pagination
|
|
9
|
+
from ..state import current
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help="平台管理(需平台管理员)")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("users")
|
|
15
|
+
def users(
|
|
16
|
+
page_size: int = PAGE_SIZE_OPT,
|
|
17
|
+
page_token: str = PAGE_TOKEN_OPT,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""列平台用户。"""
|
|
20
|
+
st = current()
|
|
21
|
+
payload = st.client().call("admin.ListUsers", query=pagination(page_size or 50, page_token))
|
|
22
|
+
output.emit_items(
|
|
23
|
+
payload,
|
|
24
|
+
items_key="users",
|
|
25
|
+
columns=[
|
|
26
|
+
("USER ID", "userId"),
|
|
27
|
+
("邮箱", "email"),
|
|
28
|
+
("名称", "displayName"),
|
|
29
|
+
("状态", "status"),
|
|
30
|
+
("管理员", "isPlatformAdmin"),
|
|
31
|
+
("注册时间", "createdAt"),
|
|
32
|
+
],
|
|
33
|
+
json_out=st.json_out,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command("promote")
|
|
38
|
+
def promote(
|
|
39
|
+
user_id: str = typer.Argument(...),
|
|
40
|
+
admin: bool = typer.Option(True, "--admin/--demote", help="授予或收回平台管理员"),
|
|
41
|
+
) -> None:
|
|
42
|
+
"""授予/收回平台管理员。"""
|
|
43
|
+
st = current()
|
|
44
|
+
body = {"isPlatformAdmin": admin}
|
|
45
|
+
payload = st.client().call(
|
|
46
|
+
"admin.PromoteUser", path_params={"user_id": user_id}, body=body
|
|
47
|
+
)
|
|
48
|
+
if st.json_out:
|
|
49
|
+
output.emit_json(payload)
|
|
50
|
+
return
|
|
51
|
+
typer.echo(f"{user_id} → {'平台管理员' if admin else '普通用户'}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.command("set-status")
|
|
55
|
+
def set_status(
|
|
56
|
+
user_id: str = typer.Argument(...),
|
|
57
|
+
status: str = typer.Option(..., "--status", help="active | disabled"),
|
|
58
|
+
) -> None:
|
|
59
|
+
"""启用/停用账号。"""
|
|
60
|
+
st = current()
|
|
61
|
+
body = {"status": f"USER_STATUS_{status.upper()}"}
|
|
62
|
+
payload = st.client().call(
|
|
63
|
+
"admin.SetUserStatus", path_params={"user_id": user_id}, body=body
|
|
64
|
+
)
|
|
65
|
+
if st.json_out:
|
|
66
|
+
output.emit_json(payload)
|
|
67
|
+
return
|
|
68
|
+
typer.echo(f"{user_id} → {status}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.command("rotate-context-key")
|
|
72
|
+
def rotate_context_key(
|
|
73
|
+
reason: str = typer.Option(None, "--reason", help="轮换原因(记入审计)"),
|
|
74
|
+
) -> None:
|
|
75
|
+
"""轮换上下文令牌签名密钥(reason 走 query,该接口无 body)。"""
|
|
76
|
+
st = current()
|
|
77
|
+
payload = st.client().call(
|
|
78
|
+
"admin.RotateContextTokenKey", query={"reason": reason} if reason else None
|
|
79
|
+
)
|
|
80
|
+
if st.json_out:
|
|
81
|
+
output.emit_json(payload)
|
|
82
|
+
return
|
|
83
|
+
typer.echo(f"新密钥 {payload.get('keyId')} 生效于 {payload.get('effectiveAt')}")
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""agent:Agent 定义、版本、运行与 Agent 目录。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from .. import output
|
|
8
|
+
from ..commands._common import (
|
|
9
|
+
BODY_FILE_OPT,
|
|
10
|
+
BODY_OPT,
|
|
11
|
+
PAGE_SIZE_OPT,
|
|
12
|
+
PAGE_TOKEN_OPT,
|
|
13
|
+
body_of,
|
|
14
|
+
pagination,
|
|
15
|
+
)
|
|
16
|
+
from ..errors import UsageError
|
|
17
|
+
from ..state import current
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(no_args_is_help=True, help="Agent 定义 / 版本 / 运行 / 目录")
|
|
20
|
+
|
|
21
|
+
DEF_COLUMNS = [
|
|
22
|
+
("AGENT DEF ID", "agentDefId"),
|
|
23
|
+
("名称", "name"),
|
|
24
|
+
("版本", "version"),
|
|
25
|
+
("状态", "status"),
|
|
26
|
+
("更新时间", "updatedAt"),
|
|
27
|
+
]
|
|
28
|
+
RUN_COLUMNS = [
|
|
29
|
+
("RUN ID", "runId"),
|
|
30
|
+
("状态", "status"),
|
|
31
|
+
("提示词", "promptText"),
|
|
32
|
+
("创建时间", "createdAt"),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ------------------------------------------------------------------ 定义
|
|
37
|
+
|
|
38
|
+
@app.command("defs")
|
|
39
|
+
def defs(
|
|
40
|
+
page_size: int = PAGE_SIZE_OPT,
|
|
41
|
+
page_token: str = PAGE_TOKEN_OPT,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""列 Agent 定义。"""
|
|
44
|
+
st = current()
|
|
45
|
+
payload = st.client().call("agent.ListAgentDefs", query=pagination(page_size or 50, page_token))
|
|
46
|
+
output.emit_items(payload, items_key="agentDefs", columns=DEF_COLUMNS, json_out=st.json_out)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@app.command("def")
|
|
50
|
+
def def_get(agent_def_id: str = typer.Argument(...)) -> None:
|
|
51
|
+
"""看定义详情。"""
|
|
52
|
+
st = current()
|
|
53
|
+
payload = st.client().call("agent.GetAgentDef", path_params={"agent_def_id": agent_def_id})
|
|
54
|
+
output.emit_object(payload, json_out=st.json_out)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command("def-create")
|
|
58
|
+
def def_create(
|
|
59
|
+
name: str = typer.Option(..., "--name", "-n"),
|
|
60
|
+
description: str = typer.Option(None, "--description", "-d"),
|
|
61
|
+
config: str = typer.Option(None, "--config", help="config_json 字面量(自由 JSON 文本)"),
|
|
62
|
+
config_file: str = typer.Option(None, "--config-file", help="从文件读 config_json(- 读 stdin)"),
|
|
63
|
+
memory_binding: str = typer.Option(None, "--memory-binding", help="记忆绑定,如 memories/agents"),
|
|
64
|
+
body: str = BODY_OPT,
|
|
65
|
+
body_file: str = BODY_FILE_OPT,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""建 Agent 定义。"""
|
|
68
|
+
st = current()
|
|
69
|
+
payload_body = body_of(body, body_file) or {}
|
|
70
|
+
payload_body.setdefault("name", name)
|
|
71
|
+
if description:
|
|
72
|
+
payload_body.setdefault("description", description)
|
|
73
|
+
if memory_binding:
|
|
74
|
+
payload_body.setdefault("memoryBinding", memory_binding)
|
|
75
|
+
text = _text_of(config, config_file)
|
|
76
|
+
if text is not None:
|
|
77
|
+
payload_body.setdefault("configJson", text)
|
|
78
|
+
payload = st.client().call("agent.CreateAgentDef", body=payload_body)
|
|
79
|
+
if st.json_out:
|
|
80
|
+
output.emit_json(payload)
|
|
81
|
+
return
|
|
82
|
+
typer.echo(payload.get("agentDefId") or "")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@app.command("def-update")
|
|
86
|
+
def def_update(
|
|
87
|
+
agent_def_id: str = typer.Argument(...),
|
|
88
|
+
name: str = typer.Option(None, "--name", "-n"),
|
|
89
|
+
description: str = typer.Option(None, "--description", "-d"),
|
|
90
|
+
config: str = typer.Option(None, "--config"),
|
|
91
|
+
config_file: str = typer.Option(None, "--config-file"),
|
|
92
|
+
memory_binding: str = typer.Option(None, "--memory-binding"),
|
|
93
|
+
status: str = typer.Option(None, "--status", help="AgentDefStatus 枚举"),
|
|
94
|
+
body: str = BODY_OPT,
|
|
95
|
+
body_file: str = BODY_FILE_OPT,
|
|
96
|
+
) -> None:
|
|
97
|
+
"""改定义(PATCH 语义;改完通常产生新版本)。"""
|
|
98
|
+
st = current()
|
|
99
|
+
payload_body = body_of(body, body_file) or {}
|
|
100
|
+
for key, value in (
|
|
101
|
+
("name", name),
|
|
102
|
+
("description", description),
|
|
103
|
+
("memoryBinding", memory_binding),
|
|
104
|
+
("status", status),
|
|
105
|
+
):
|
|
106
|
+
if value:
|
|
107
|
+
payload_body.setdefault(key, value)
|
|
108
|
+
text = _text_of(config, config_file)
|
|
109
|
+
if text is not None:
|
|
110
|
+
payload_body.setdefault("configJson", text)
|
|
111
|
+
if not payload_body:
|
|
112
|
+
raise UsageError("至少给一项要改的字段(--name/--description/--config/--memory-binding/--status)")
|
|
113
|
+
payload = st.client().call(
|
|
114
|
+
"agent.UpdateAgentDef", path_params={"agent_def_id": agent_def_id}, body=payload_body
|
|
115
|
+
)
|
|
116
|
+
output.emit_object(payload, json_out=st.json_out)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@app.command("def-delete")
|
|
120
|
+
def def_delete(
|
|
121
|
+
agent_def_id: str = typer.Argument(...),
|
|
122
|
+
yes: bool = typer.Option(False, "--yes", "-y"),
|
|
123
|
+
) -> None:
|
|
124
|
+
"""删定义。"""
|
|
125
|
+
st = current()
|
|
126
|
+
if not yes:
|
|
127
|
+
typer.confirm(f"确认删除 Agent 定义 {agent_def_id}?", abort=True)
|
|
128
|
+
st.client().call("agent.DeleteAgentDef", path_params={"agent_def_id": agent_def_id})
|
|
129
|
+
typer.echo(f"已删除 {agent_def_id}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@app.command("versions")
|
|
133
|
+
def versions(
|
|
134
|
+
agent_def_id: str = typer.Argument(...),
|
|
135
|
+
page_size: int = PAGE_SIZE_OPT,
|
|
136
|
+
page_token: str = PAGE_TOKEN_OPT,
|
|
137
|
+
) -> None:
|
|
138
|
+
"""列定义的版本。"""
|
|
139
|
+
st = current()
|
|
140
|
+
payload = st.client().call(
|
|
141
|
+
"agent.ListAgentDefVersions",
|
|
142
|
+
path_params={"agent_def_id": agent_def_id},
|
|
143
|
+
query=pagination(page_size or 50, page_token),
|
|
144
|
+
)
|
|
145
|
+
output.emit_items(
|
|
146
|
+
payload,
|
|
147
|
+
items_key="versions",
|
|
148
|
+
columns=[("版本", "version"), ("创建人", "createdBy"), ("创建时间", "createdAt")],
|
|
149
|
+
json_out=st.json_out,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@app.command("version")
|
|
154
|
+
def version(
|
|
155
|
+
agent_def_id: str = typer.Argument(...),
|
|
156
|
+
version: int = typer.Argument(...),
|
|
157
|
+
) -> None:
|
|
158
|
+
"""看某个版本。"""
|
|
159
|
+
st = current()
|
|
160
|
+
payload = st.client().call(
|
|
161
|
+
"agent.GetAgentDefVersion",
|
|
162
|
+
path_params={"agent_def_id": agent_def_id, "version": version},
|
|
163
|
+
)
|
|
164
|
+
output.emit_object(payload, json_out=st.json_out)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ------------------------------------------------------------------ 运行
|
|
168
|
+
|
|
169
|
+
@app.command("prompt")
|
|
170
|
+
def prompt(
|
|
171
|
+
session_id: str = typer.Argument(...),
|
|
172
|
+
text: str = typer.Argument(None, help="提示词;缺省或 - 读 stdin"),
|
|
173
|
+
body: str = BODY_OPT,
|
|
174
|
+
body_file: str = BODY_FILE_OPT,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""给会话发提示词,起一个 run(返回 runId;进度用 `session watch` 跟)。"""
|
|
177
|
+
st = current()
|
|
178
|
+
payload_body = body_of(body, body_file)
|
|
179
|
+
if payload_body is None:
|
|
180
|
+
if text in (None, "-"):
|
|
181
|
+
import sys
|
|
182
|
+
|
|
183
|
+
text = sys.stdin.read()
|
|
184
|
+
payload_body = {"text": text}
|
|
185
|
+
payload = st.client().call(
|
|
186
|
+
"agent.Prompt", path_params={"session_id": session_id}, body=payload_body
|
|
187
|
+
)
|
|
188
|
+
if st.json_out:
|
|
189
|
+
output.emit_json(payload)
|
|
190
|
+
return
|
|
191
|
+
typer.echo(payload.get("runId") or "")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@app.command("runs")
|
|
195
|
+
def runs(
|
|
196
|
+
session_id: str = typer.Argument(...),
|
|
197
|
+
page_size: int = PAGE_SIZE_OPT,
|
|
198
|
+
page_token: str = PAGE_TOKEN_OPT,
|
|
199
|
+
) -> None:
|
|
200
|
+
"""列会话下的 run。"""
|
|
201
|
+
st = current()
|
|
202
|
+
payload = st.client().call(
|
|
203
|
+
"agent.ListRuns",
|
|
204
|
+
path_params={"session_id": session_id},
|
|
205
|
+
query=pagination(page_size or 50, page_token),
|
|
206
|
+
)
|
|
207
|
+
output.emit_items(payload, items_key="runs", columns=RUN_COLUMNS, json_out=st.json_out)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@app.command("run")
|
|
211
|
+
def run(run_id: str = typer.Argument(...)) -> None:
|
|
212
|
+
"""看单个 run。"""
|
|
213
|
+
st = current()
|
|
214
|
+
payload = st.client().call("agent.GetRun", path_params={"run_id": run_id})
|
|
215
|
+
output.emit_object(payload, json_out=st.json_out)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ------------------------------------------------------------------ 目录
|
|
219
|
+
|
|
220
|
+
@app.command("directory")
|
|
221
|
+
def directory(
|
|
222
|
+
page_size: int = PAGE_SIZE_OPT,
|
|
223
|
+
page_token: str = PAGE_TOKEN_OPT,
|
|
224
|
+
) -> None:
|
|
225
|
+
"""列 Agent 目录(跨 project 可见的 agent 地址)。"""
|
|
226
|
+
st = current()
|
|
227
|
+
payload = st.client().call(
|
|
228
|
+
"agent.ListAgentDirectory", query=pagination(page_size or 50, page_token)
|
|
229
|
+
)
|
|
230
|
+
output.emit_items(
|
|
231
|
+
payload,
|
|
232
|
+
items_key="entries",
|
|
233
|
+
columns=[("AGENT ID", "agentId"), ("地址", "address"), ("状态", "status")],
|
|
234
|
+
json_out=st.json_out,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@app.command("resolve")
|
|
239
|
+
def resolve(agent_id: str = typer.Argument(...)) -> None:
|
|
240
|
+
"""把 agent id 解析成地址。"""
|
|
241
|
+
st = current()
|
|
242
|
+
payload = st.client().call(
|
|
243
|
+
"agent.ResolveAgentAddress", path_params={"agent_id": agent_id}
|
|
244
|
+
)
|
|
245
|
+
output.emit_object(payload, json_out=st.json_out)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@app.command("messages")
|
|
249
|
+
def messages(
|
|
250
|
+
agent_id: str = typer.Argument(...),
|
|
251
|
+
page_size: int = PAGE_SIZE_OPT,
|
|
252
|
+
page_token: str = PAGE_TOKEN_OPT,
|
|
253
|
+
) -> None:
|
|
254
|
+
"""看某个 agent 的消息记录。"""
|
|
255
|
+
st = current()
|
|
256
|
+
payload = st.client().call(
|
|
257
|
+
"agent.ListAgentMessages",
|
|
258
|
+
path_params={"agent_id": agent_id},
|
|
259
|
+
query=pagination(page_size or 50, page_token),
|
|
260
|
+
)
|
|
261
|
+
output.emit_items(
|
|
262
|
+
payload,
|
|
263
|
+
items_key="messages", # 见 ListAgentMessagesResponse
|
|
264
|
+
columns=[
|
|
265
|
+
("MESSAGE ID", "messageId"),
|
|
266
|
+
("方向", "direction"),
|
|
267
|
+
("对端", "peerAgentId"),
|
|
268
|
+
("负载", "payloadJson"),
|
|
269
|
+
],
|
|
270
|
+
json_out=st.json_out,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _text_of(text: str | None, file: str | None) -> str | None:
|
|
275
|
+
"""config_json 是自由 JSON 文本:字面量或文件(- 读 stdin)。"""
|
|
276
|
+
if text and file:
|
|
277
|
+
raise UsageError("--config 与 --config-file 不能同时用")
|
|
278
|
+
if file:
|
|
279
|
+
if file == "-":
|
|
280
|
+
import sys
|
|
281
|
+
|
|
282
|
+
return sys.stdin.read()
|
|
283
|
+
try:
|
|
284
|
+
with open(file, encoding="utf-8") as fh:
|
|
285
|
+
return fh.read()
|
|
286
|
+
except OSError as exc:
|
|
287
|
+
raise UsageError(f"读取失败:{exc}") from None
|
|
288
|
+
return text
|