tinet-agent-cli 0.1.0.dev36__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.
- taco/__init__.py +1 -0
- taco/agent_openapi/__init__.py +1 -0
- taco/agent_openapi/catalog.py +198 -0
- taco/agent_openapi/models.py +47 -0
- taco/agent_openapi/parameters.py +111 -0
- taco/agent_openapi/service.py +73 -0
- taco/aikb/__init__.py +4 -0
- taco/aikb/catalog.py +380 -0
- taco/aikb/executors.py +157 -0
- taco/aikb/file_input.py +55 -0
- taco/aikb/models.py +42 -0
- taco/aikb/parameters.py +135 -0
- taco/aikb/service.py +68 -0
- taco/assets/__init__.py +1 -0
- taco/assets/agent_openapi/catalog.json +14 -0
- taco/assets/agent_openapi/operations/conversation/list-conversations.json +67 -0
- taco/assets/agent_openapi/operations/message/list-conversation-messages.json +80 -0
- taco/assets/agent_openapi/operations/trace/list-message-traces.json +72 -0
- taco/assets/aikb/catalog.json +41 -0
- taco/assets/aikb/operations/conversation/chat-conversation-on-open.json +99 -0
- taco/assets/aikb/operations/directory/delete-directory.json +81 -0
- taco/assets/aikb/operations/directory/edit-directory.json +97 -0
- taco/assets/aikb/operations/directory/list-directory-tree.json +106 -0
- taco/assets/aikb/operations/directory/save-directory.json +109 -0
- taco/assets/aikb/operations/faq/create-faq.json +193 -0
- taco/assets/aikb/operations/faq/delete-faq.json +81 -0
- taco/assets/aikb/operations/faq/describe-faq.json +82 -0
- taco/assets/aikb/operations/faq/edit-faq.json +193 -0
- taco/assets/aikb/operations/faq/list-faqs.json +136 -0
- taco/assets/aikb/operations/file/create-file.json +145 -0
- taco/assets/aikb/operations/file/delete-files.json +110 -0
- taco/assets/aikb/operations/file/describe-file.json +82 -0
- taco/assets/aikb/operations/file/get-file-upload-url.json +154 -0
- taco/assets/aikb/operations/file/list-files.json +146 -0
- taco/assets/aikb/operations/media/describe-faq-media-url.json +98 -0
- taco/assets/aikb/operations/media/describe-file-media-url.json +90 -0
- taco/assets/aikb/operations/oss/signature-for-upload.json +85 -0
- taco/assets/aikb/operations/recycle-bin/list-recycled-items.json +151 -0
- taco/assets/aikb/operations/repository/describe-bot-repository.json +71 -0
- taco/assets/aikb/operations/repository/list-private-repositories.json +75 -0
- taco/assets/aikb/operations/repository/list-public-repositories.json +75 -0
- taco/assets/aikb/operations/search/search-knowledge-on-open.json +180 -0
- taco/assets/examples/README.md +3 -0
- taco/assets/examples/agent-basic.json +16 -0
- taco/assets/examples/agent-builtin-tool.json +28 -0
- taco/assets/examples/agent-workflow-tool.json +30 -0
- taco/assets/examples/chatflow-basic.json +24 -0
- taco/assets/examples/workflow-http.json +34 -0
- taco/assets/manifest.json +12 -0
- taco/assets/scenarios/README.md +3 -0
- taco/assets/scenarios/agent-basic.json +80 -0
- taco/assets/scenarios/agent-builtin-tool.json +116 -0
- taco/assets/scenarios/agent-workflow-tool.json +277 -0
- taco/assets/schemas/README.md +3 -0
- taco/assets/schemas/agent-tool.json +71 -0
- taco/assets/schemas/agent.json +199 -0
- taco/assets/schemas/chatflow.json +1048 -0
- taco/assets/schemas/workflow.http.json +1052 -0
- taco/assets/schemas/workflow.json +1052 -0
- taco/assets/skills/README.md +3 -0
- taco/assets/skills/taco/SKILL.md +68 -0
- taco/assets/skills/taco/manifest.json +10 -0
- taco/cli.py +127 -0
- taco/commands/__init__.py +1 -0
- taco/commands/agent.py +760 -0
- taco/commands/aikb.py +132 -0
- taco/commands/app.py +151 -0
- taco/commands/category.py +37 -0
- taco/commands/chatflow.py +183 -0
- taco/commands/credential.py +222 -0
- taco/commands/discovery.py +409 -0
- taco/commands/model.py +55 -0
- taco/commands/profile.py +124 -0
- taco/commands/tool.py +61 -0
- taco/commands/workflow.py +714 -0
- taco/core/__init__.py +1 -0
- taco/core/access_token_manager.py +98 -0
- taco/core/api_client.py +263 -0
- taco/core/assets.py +81 -0
- taco/core/config_store.py +209 -0
- taco/core/context.py +19 -0
- taco/core/developer_center_client.py +165 -0
- taco/core/errors.py +19 -0
- taco/core/file_lock.py +80 -0
- taco/core/http_headers.py +21 -0
- taco/core/openapi_signer.py +221 -0
- taco/core/output.py +72 -0
- taco/core/profile_manager.py +217 -0
- taco/core/profile_resolver.py +151 -0
- taco/core/secure_file.py +76 -0
- taco/release.py +363 -0
- taco/services/__init__.py +1 -0
- taco/services/agent_service.py +314 -0
- taco/services/app_service.py +153 -0
- taco/services/category_service.py +57 -0
- taco/services/model_service.py +117 -0
- taco/services/tool_service.py +482 -0
- taco/services/workflow_compiler.py +1665 -0
- taco/services/workflow_service.py +652 -0
- taco/services/workflow_tool_service.py +439 -0
- tinet_agent_cli-0.1.0.dev36.dist-info/METADATA +158 -0
- tinet_agent_cli-0.1.0.dev36.dist-info/RECORD +104 -0
- tinet_agent_cli-0.1.0.dev36.dist-info/WHEEL +4 -0
- tinet_agent_cli-0.1.0.dev36.dist-info/entry_points.txt +2 -0
taco/commands/aikb.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from taco.aikb.catalog import load_catalog
|
|
9
|
+
from taco.aikb.parameters import parse_parameters, thaw
|
|
10
|
+
from taco.aikb.service import AikbService
|
|
11
|
+
from taco.core.context import get_cli_context
|
|
12
|
+
from taco.core.errors import TacoError
|
|
13
|
+
from taco.core.output import emit_success, exit_with_error
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
aikb_app = typer.Typer(help="发现并调用开发者中心 AIKB operation")
|
|
17
|
+
operation_app = typer.Typer(help="发现 AIKB operation")
|
|
18
|
+
aikb_app.add_typer(operation_app, name="operation")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@operation_app.command("list", help="列出 AIKB operation。")
|
|
22
|
+
def list_operations(
|
|
23
|
+
ctx: typer.Context,
|
|
24
|
+
group: str | None = typer.Option(None, "--group", help="按分组筛选。"),
|
|
25
|
+
) -> None:
|
|
26
|
+
operations = [
|
|
27
|
+
_summary(item)
|
|
28
|
+
for item in load_catalog().operations
|
|
29
|
+
if group is None or item.group == group
|
|
30
|
+
]
|
|
31
|
+
emit_success(ctx, operations, json_default=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@operation_app.command("search", help="按关键词搜索 AIKB operation。")
|
|
35
|
+
def search(ctx: typer.Context, query: str = typer.Argument(...)) -> None:
|
|
36
|
+
needle = query.casefold()
|
|
37
|
+
matches = []
|
|
38
|
+
for operation in load_catalog().operations:
|
|
39
|
+
haystack = " ".join(
|
|
40
|
+
[operation.id, operation.title, operation.description, *operation.keywords]
|
|
41
|
+
).casefold()
|
|
42
|
+
if needle in haystack:
|
|
43
|
+
matches.append(_summary(operation))
|
|
44
|
+
emit_success(ctx, matches[:10], json_default=True)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@operation_app.command("describe", help="查看 operation 完整契约。")
|
|
48
|
+
def describe(ctx: typer.Context, operation_id: str = typer.Argument(...)) -> None:
|
|
49
|
+
operation = load_catalog().by_id.get(operation_id)
|
|
50
|
+
if operation is None:
|
|
51
|
+
exit_with_error(ctx, _not_found(operation_id), json_default=True)
|
|
52
|
+
emit_success(
|
|
53
|
+
ctx,
|
|
54
|
+
{
|
|
55
|
+
**_summary(operation),
|
|
56
|
+
"description": operation.description,
|
|
57
|
+
"keywords": list(operation.keywords),
|
|
58
|
+
"http": thaw(operation.http),
|
|
59
|
+
"response": thaw(operation.response),
|
|
60
|
+
"risk": thaw(operation.risk),
|
|
61
|
+
"execution": thaw(operation.execution),
|
|
62
|
+
"file_input": thaw(operation.file_input),
|
|
63
|
+
"examples": thaw(operation.examples),
|
|
64
|
+
},
|
|
65
|
+
json_default=True,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@aikb_app.command("call", help="校验参数并执行 AIKB operation。")
|
|
70
|
+
def call(
|
|
71
|
+
ctx: typer.Context,
|
|
72
|
+
operation_id: str = typer.Argument(...),
|
|
73
|
+
param: list[str] | None = typer.Option(None, "--param", help="语义参数 key=value,可重复。"),
|
|
74
|
+
params_file: Path | None = typer.Option(None, "--params-file", help="语义参数 JSON 文件。"),
|
|
75
|
+
body_file: Path | None = typer.Option(None, "--body-file", help="JSON body 文件。"),
|
|
76
|
+
file: Path | None = typer.Option(None, "--file", help="需要上传的本地文件。"),
|
|
77
|
+
yes: bool = typer.Option(False, "--yes", help="确认写或删除操作。"),
|
|
78
|
+
upload_timeout: float | None = typer.Option(None, "--upload-timeout", min=0.1, help="PUT 上传超时秒数。"),
|
|
79
|
+
) -> None:
|
|
80
|
+
catalog = load_catalog()
|
|
81
|
+
operation = catalog.by_id.get(operation_id)
|
|
82
|
+
if operation is None:
|
|
83
|
+
exit_with_error(ctx, _not_found(operation_id), json_default=True)
|
|
84
|
+
if operation.risk["requires_confirmation"] and not yes:
|
|
85
|
+
exit_with_error(
|
|
86
|
+
ctx,
|
|
87
|
+
TacoError(
|
|
88
|
+
code="AIKB_CONFIRM_REQUIRED",
|
|
89
|
+
message=f"operation `{operation_id}` 需要显式传入 --yes。",
|
|
90
|
+
exit_code=2,
|
|
91
|
+
),
|
|
92
|
+
json_default=True,
|
|
93
|
+
)
|
|
94
|
+
try:
|
|
95
|
+
query, body = parse_parameters(
|
|
96
|
+
operation,
|
|
97
|
+
params=param,
|
|
98
|
+
params_file=params_file,
|
|
99
|
+
body_file=body_file,
|
|
100
|
+
has_file=file is not None,
|
|
101
|
+
)
|
|
102
|
+
result = AikbService().call(
|
|
103
|
+
operation_id,
|
|
104
|
+
query=query,
|
|
105
|
+
body=body,
|
|
106
|
+
profile=get_cli_context(ctx).profile,
|
|
107
|
+
file_path=file,
|
|
108
|
+
upload_timeout=upload_timeout,
|
|
109
|
+
)
|
|
110
|
+
except TacoError as error:
|
|
111
|
+
exit_with_error(ctx, error, json_default=True)
|
|
112
|
+
emit_success(ctx, result, json_default=True)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _summary(operation) -> dict[str, Any]:
|
|
116
|
+
return {
|
|
117
|
+
"id": operation.id,
|
|
118
|
+
"title": operation.title,
|
|
119
|
+
"group": operation.group,
|
|
120
|
+
"method": operation.http["method"],
|
|
121
|
+
"path": operation.http["path"],
|
|
122
|
+
"risk": operation.risk["level"],
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _not_found(operation_id: str) -> TacoError:
|
|
127
|
+
return TacoError(
|
|
128
|
+
code="AIKB_OPERATION_NOT_FOUND",
|
|
129
|
+
message=f"未知 AIKB operation:{operation_id}",
|
|
130
|
+
exit_code=2,
|
|
131
|
+
hint="请执行 taco aikb operation search 或 list 查找候选接口。",
|
|
132
|
+
)
|
taco/commands/app.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from taco.core.context import get_cli_context
|
|
6
|
+
from taco.core.errors import TacoError
|
|
7
|
+
from taco.core.output import emit_success, exit_with_error
|
|
8
|
+
from taco.services.app_service import AppService
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
app_app = typer.Typer(help="应用管理")
|
|
12
|
+
template_app = typer.Typer(help="应用模板发现与创建")
|
|
13
|
+
tag_app = typer.Typer(help="应用标签发现")
|
|
14
|
+
app_app.add_typer(template_app, name="template")
|
|
15
|
+
app_app.add_typer(tag_app, name="tag")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app_app.command("list", help="分页列出应用并按名称、模式或标签过滤。")
|
|
19
|
+
def list_apps(
|
|
20
|
+
ctx: typer.Context,
|
|
21
|
+
page: int = typer.Option(1, "--page", min=1, help="页码。"),
|
|
22
|
+
limit: int = typer.Option(20, "--limit", min=1, help="每页数量。"),
|
|
23
|
+
name: str | None = typer.Option(None, "--name", help="按应用名称过滤。"),
|
|
24
|
+
mode: str | None = typer.Option(None, "--mode", help="按应用模式过滤。"),
|
|
25
|
+
tag_ids: list[str] | None = typer.Option(
|
|
26
|
+
None,
|
|
27
|
+
"--tag-id",
|
|
28
|
+
help="按标签 ID 过滤,可重复;多个标签按任一匹配。",
|
|
29
|
+
),
|
|
30
|
+
) -> None:
|
|
31
|
+
cli_context = get_cli_context(ctx)
|
|
32
|
+
try:
|
|
33
|
+
result = AppService().list_apps(
|
|
34
|
+
page=page,
|
|
35
|
+
limit=limit,
|
|
36
|
+
name=name,
|
|
37
|
+
mode=mode,
|
|
38
|
+
tag_ids=tag_ids or [],
|
|
39
|
+
profile=cli_context.profile,
|
|
40
|
+
)
|
|
41
|
+
except TacoError as error:
|
|
42
|
+
exit_with_error(ctx, error, json_default=True)
|
|
43
|
+
|
|
44
|
+
emit_success(ctx, result, json_default=True)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@tag_app.command("list", help="列出应用标签并可按名称关键词过滤。")
|
|
48
|
+
def list_app_tags(
|
|
49
|
+
ctx: typer.Context,
|
|
50
|
+
keyword: str | None = typer.Option(
|
|
51
|
+
None,
|
|
52
|
+
"--keyword",
|
|
53
|
+
help="按标签名称关键词过滤。",
|
|
54
|
+
),
|
|
55
|
+
) -> None:
|
|
56
|
+
cli_context = get_cli_context(ctx)
|
|
57
|
+
try:
|
|
58
|
+
result = AppService().list_tags(
|
|
59
|
+
keyword=keyword,
|
|
60
|
+
profile=cli_context.profile,
|
|
61
|
+
)
|
|
62
|
+
except TacoError as error:
|
|
63
|
+
exit_with_error(ctx, error, json_default=True)
|
|
64
|
+
|
|
65
|
+
emit_success(ctx, result, json_default=True)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@app_app.command("get", help="按应用 ID 获取详情。")
|
|
69
|
+
def get_app(ctx: typer.Context, app_id: str = typer.Argument(..., help="应用 ID。")) -> None:
|
|
70
|
+
cli_context = get_cli_context(ctx)
|
|
71
|
+
try:
|
|
72
|
+
result = AppService().get_app(app_id, profile=cli_context.profile)
|
|
73
|
+
except TacoError as error:
|
|
74
|
+
exit_with_error(ctx, error, json_default=True)
|
|
75
|
+
|
|
76
|
+
emit_success(ctx, result, json_default=True)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app_app.command("create", help="创建指定模式的通用应用。")
|
|
80
|
+
def create_app(
|
|
81
|
+
ctx: typer.Context,
|
|
82
|
+
name: str = typer.Option(..., "--name", help="应用名称。"),
|
|
83
|
+
mode: str = typer.Option(..., "--mode", help="应用模式。"),
|
|
84
|
+
) -> None:
|
|
85
|
+
cli_context = get_cli_context(ctx)
|
|
86
|
+
try:
|
|
87
|
+
result = AppService().create_app(
|
|
88
|
+
name=name,
|
|
89
|
+
mode=mode,
|
|
90
|
+
profile=cli_context.profile,
|
|
91
|
+
)
|
|
92
|
+
except TacoError as error:
|
|
93
|
+
exit_with_error(ctx, error, json_default=True)
|
|
94
|
+
|
|
95
|
+
emit_success(ctx, result, json_default=True)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@template_app.command("list", help="列出当前企业可用的应用模板。")
|
|
99
|
+
def list_app_templates(
|
|
100
|
+
ctx: typer.Context,
|
|
101
|
+
language: str | None = typer.Option(
|
|
102
|
+
None,
|
|
103
|
+
"--language",
|
|
104
|
+
help="模板语言;不传时使用账号或平台默认语言。",
|
|
105
|
+
),
|
|
106
|
+
) -> None:
|
|
107
|
+
cli_context = get_cli_context(ctx)
|
|
108
|
+
try:
|
|
109
|
+
result = AppService().list_templates(
|
|
110
|
+
language=language,
|
|
111
|
+
profile=cli_context.profile,
|
|
112
|
+
)
|
|
113
|
+
except TacoError as error:
|
|
114
|
+
exit_with_error(ctx, error, json_default=True)
|
|
115
|
+
|
|
116
|
+
emit_success(ctx, result, json_default=True)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@template_app.command("create", help="从应用模板创建远端应用。")
|
|
120
|
+
def create_app_from_template(
|
|
121
|
+
ctx: typer.Context,
|
|
122
|
+
template_app_id: str = typer.Argument(..., help="模板应用 ID。"),
|
|
123
|
+
name: str | None = typer.Option(
|
|
124
|
+
None,
|
|
125
|
+
"--name",
|
|
126
|
+
help="覆盖模板名称;不传时沿用模板原名称。",
|
|
127
|
+
),
|
|
128
|
+
yes: bool = typer.Option(False, "--yes", help="确认创建远端应用。"),
|
|
129
|
+
) -> None:
|
|
130
|
+
if not yes:
|
|
131
|
+
exit_with_error(
|
|
132
|
+
ctx,
|
|
133
|
+
TacoError(
|
|
134
|
+
code="APP_TEMPLATE_CREATE_CONFIRM_REQUIRED",
|
|
135
|
+
message="通过模板创建远端应用前必须显式传入 --yes。",
|
|
136
|
+
exit_code=2,
|
|
137
|
+
),
|
|
138
|
+
json_default=True,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
cli_context = get_cli_context(ctx)
|
|
142
|
+
try:
|
|
143
|
+
result = AppService().create_app_from_template(
|
|
144
|
+
template_app_id,
|
|
145
|
+
name=name,
|
|
146
|
+
profile=cli_context.profile,
|
|
147
|
+
)
|
|
148
|
+
except TacoError as error:
|
|
149
|
+
exit_with_error(ctx, error, json_default=True)
|
|
150
|
+
|
|
151
|
+
emit_success(ctx, result, json_default=True)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from taco.core.context import get_cli_context
|
|
6
|
+
from taco.core.errors import TacoError
|
|
7
|
+
from taco.core.output import emit_success, exit_with_error
|
|
8
|
+
from taco.services.category_service import CategoryService
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
category_app = typer.Typer(help="智能体分类发现")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@category_app.command("list", help="列出服务端当前配置的智能体分类树。")
|
|
15
|
+
def list_categories(ctx: typer.Context) -> None:
|
|
16
|
+
cli_context = get_cli_context(ctx)
|
|
17
|
+
try:
|
|
18
|
+
result = CategoryService().list_categories(profile=cli_context.profile)
|
|
19
|
+
except TacoError as error:
|
|
20
|
+
exit_with_error(ctx, error, json_default=True)
|
|
21
|
+
emit_success(ctx, result, json_default=True)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@category_app.command("get", help="按 code 获取分类,支持嵌套子分类。")
|
|
25
|
+
def get_category(
|
|
26
|
+
ctx: typer.Context,
|
|
27
|
+
code: str = typer.Argument(..., help="分类 code。"),
|
|
28
|
+
) -> None:
|
|
29
|
+
cli_context = get_cli_context(ctx)
|
|
30
|
+
try:
|
|
31
|
+
result = CategoryService().get_category(
|
|
32
|
+
code,
|
|
33
|
+
profile=cli_context.profile,
|
|
34
|
+
)
|
|
35
|
+
except TacoError as error:
|
|
36
|
+
exit_with_error(ctx, error, json_default=True)
|
|
37
|
+
emit_success(ctx, result, json_default=True)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from taco.commands.workflow import (
|
|
8
|
+
apply_workflow,
|
|
9
|
+
publish_workflow,
|
|
10
|
+
run_workflow,
|
|
11
|
+
stop_workflow,
|
|
12
|
+
validate_workflow,
|
|
13
|
+
_cleanup_workflow_temporary,
|
|
14
|
+
_commit_workflow_yaml,
|
|
15
|
+
_prepare_workflow_output,
|
|
16
|
+
_stage_workflow_yaml,
|
|
17
|
+
)
|
|
18
|
+
from taco.core.assets import load_json_asset
|
|
19
|
+
from taco.core.context import get_cli_context
|
|
20
|
+
from taco.core.errors import TacoError
|
|
21
|
+
from taco.core.output import emit_success, exit_with_error
|
|
22
|
+
from taco.services.workflow_service import WorkflowService
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
chatflow_app = typer.Typer(help="Chatflow(advanced-chat)本地 DSL 工具")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@chatflow_app.command("create", help="创建远端 Chatflow 并初始化本地 YAML DSL。")
|
|
29
|
+
def create_chatflow(
|
|
30
|
+
ctx: typer.Context,
|
|
31
|
+
name: str = typer.Option(..., "--name", help="Chatflow 名称。"),
|
|
32
|
+
description: str = typer.Option(
|
|
33
|
+
...,
|
|
34
|
+
"--description",
|
|
35
|
+
help="Chatflow 描述。",
|
|
36
|
+
),
|
|
37
|
+
output: Path = typer.Option(
|
|
38
|
+
...,
|
|
39
|
+
"--output",
|
|
40
|
+
help="本地 Chatflow YAML 输出路径。",
|
|
41
|
+
),
|
|
42
|
+
yes: bool = typer.Option(False, "--yes", help="确认创建远端 Chatflow。"),
|
|
43
|
+
overwrite: bool = typer.Option(
|
|
44
|
+
False,
|
|
45
|
+
"--overwrite",
|
|
46
|
+
help="允许覆盖已存在的本地 Chatflow YAML。",
|
|
47
|
+
),
|
|
48
|
+
) -> None:
|
|
49
|
+
if not yes:
|
|
50
|
+
exit_with_error(
|
|
51
|
+
ctx,
|
|
52
|
+
TacoError(
|
|
53
|
+
code="CHATFLOW_CREATE_CONFIRM_REQUIRED",
|
|
54
|
+
message="创建远端 Chatflow 前必须显式传入 --yes。",
|
|
55
|
+
exit_code=2,
|
|
56
|
+
),
|
|
57
|
+
json_default=True,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
cli_context = get_cli_context(ctx)
|
|
61
|
+
temporary: Path | None = None
|
|
62
|
+
try:
|
|
63
|
+
_prepare_workflow_output(output, overwrite=overwrite)
|
|
64
|
+
chatflow = load_json_asset("examples", "chatflow-basic")
|
|
65
|
+
chatflow["name"] = name
|
|
66
|
+
chatflow["description"] = description
|
|
67
|
+
temporary = _stage_workflow_yaml(output, chatflow)
|
|
68
|
+
app = WorkflowService().create_workflow_app(
|
|
69
|
+
name,
|
|
70
|
+
description,
|
|
71
|
+
profile=cli_context.profile,
|
|
72
|
+
mode="advanced-chat",
|
|
73
|
+
)
|
|
74
|
+
app_id = app.get("id")
|
|
75
|
+
if not isinstance(app_id, str) or not app_id.strip():
|
|
76
|
+
raise TacoError(
|
|
77
|
+
code="WORKFLOW_CREATE_RESPONSE_INVALID",
|
|
78
|
+
message="创建 Chatflow 的响应缺少有效应用 ID。",
|
|
79
|
+
exit_code=4,
|
|
80
|
+
)
|
|
81
|
+
_commit_workflow_yaml(
|
|
82
|
+
temporary,
|
|
83
|
+
output,
|
|
84
|
+
overwrite=overwrite,
|
|
85
|
+
app_id=app_id,
|
|
86
|
+
)
|
|
87
|
+
except TacoError as error:
|
|
88
|
+
exit_with_error(ctx, error, json_default=True)
|
|
89
|
+
finally:
|
|
90
|
+
_cleanup_workflow_temporary(temporary)
|
|
91
|
+
|
|
92
|
+
emit_success(
|
|
93
|
+
ctx,
|
|
94
|
+
{
|
|
95
|
+
"app_id": app_id,
|
|
96
|
+
"name": app.get("name") if isinstance(app.get("name"), str) else name,
|
|
97
|
+
"mode": "advanced-chat",
|
|
98
|
+
"path": str(output),
|
|
99
|
+
},
|
|
100
|
+
json_default=True,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@chatflow_app.command("validate", help="校验 YAML DSL 并本地编译为 Dify graph。")
|
|
105
|
+
def validate_chatflow(
|
|
106
|
+
ctx: typer.Context,
|
|
107
|
+
file: Path = typer.Option(
|
|
108
|
+
...,
|
|
109
|
+
"-f",
|
|
110
|
+
"--file",
|
|
111
|
+
help="本地 Chatflow YAML 文件。",
|
|
112
|
+
),
|
|
113
|
+
) -> None:
|
|
114
|
+
validate_workflow(ctx, file=file)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@chatflow_app.command("apply", help="预览或同步本地 Chatflow graph 到远端 draft。")
|
|
118
|
+
def apply_chatflow(
|
|
119
|
+
ctx: typer.Context,
|
|
120
|
+
app_id: str = typer.Argument(..., help="应用 ID。"),
|
|
121
|
+
file: Path = typer.Option(
|
|
122
|
+
...,
|
|
123
|
+
"-f",
|
|
124
|
+
"--file",
|
|
125
|
+
help="本地 Chatflow YAML 文件。",
|
|
126
|
+
),
|
|
127
|
+
dry_run: bool = typer.Option(
|
|
128
|
+
False,
|
|
129
|
+
"--dry-run",
|
|
130
|
+
help="仅预览变更,不写入远端。",
|
|
131
|
+
),
|
|
132
|
+
yes: bool = typer.Option(False, "--yes", help="确认同步 draft。"),
|
|
133
|
+
) -> None:
|
|
134
|
+
apply_workflow(ctx, app_id=app_id, file=file, dry_run=dry_run, yes=yes)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@chatflow_app.command("run", help="通过 SSE 调试远端 Chatflow draft。")
|
|
138
|
+
def run_chatflow(
|
|
139
|
+
ctx: typer.Context,
|
|
140
|
+
app_id: str = typer.Argument(..., help="应用 ID。"),
|
|
141
|
+
inputs_file: Path | None = typer.Option(
|
|
142
|
+
None,
|
|
143
|
+
"--inputs-file",
|
|
144
|
+
help="Chatflow inputs JSON 对象文件。",
|
|
145
|
+
),
|
|
146
|
+
query: str | None = typer.Option(
|
|
147
|
+
None,
|
|
148
|
+
"--query",
|
|
149
|
+
help="用户问题(advanced-chat draft run)。",
|
|
150
|
+
),
|
|
151
|
+
stream_events: bool = typer.Option(
|
|
152
|
+
False,
|
|
153
|
+
"--stream-events",
|
|
154
|
+
help="实时输出 NDJSON 事件,便于获取 task_id。",
|
|
155
|
+
),
|
|
156
|
+
) -> None:
|
|
157
|
+
run_workflow(
|
|
158
|
+
ctx,
|
|
159
|
+
app_id=app_id,
|
|
160
|
+
inputs_file=inputs_file,
|
|
161
|
+
query=query,
|
|
162
|
+
stream_events=stream_events,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@chatflow_app.command("stop", help="使用 task_id 停止 Chatflow draft 调试任务。")
|
|
167
|
+
def stop_chatflow(
|
|
168
|
+
ctx: typer.Context,
|
|
169
|
+
app_id: str = typer.Argument(..., help="应用 ID。"),
|
|
170
|
+
task_id: str = typer.Option(..., "--task-id", help="SSE 返回的任务 ID。"),
|
|
171
|
+
) -> None:
|
|
172
|
+
stop_workflow(ctx, app_id=app_id, task_id=task_id)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@chatflow_app.command("publish", help="发布当前 Chatflow draft。")
|
|
176
|
+
def publish_chatflow(
|
|
177
|
+
ctx: typer.Context,
|
|
178
|
+
app_id: str = typer.Argument(..., help="应用 ID。"),
|
|
179
|
+
title: str | None = typer.Option(None, "--title", help="发布版本名称。"),
|
|
180
|
+
notes: str | None = typer.Option(None, "--notes", help="发布说明。"),
|
|
181
|
+
yes: bool = typer.Option(False, "--yes", help="确认发布 Chatflow。"),
|
|
182
|
+
) -> None:
|
|
183
|
+
publish_workflow(ctx, app_id=app_id, title=title, notes=notes, yes=yes)
|