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
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import re
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from typing import Any
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
from taco.core.api_client import ApiClient
|
|
11
|
+
from taco.core.errors import TacoError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_WORKFLOW_TOOL_PATH = "/workspaces/current/tool-provider/workflow"
|
|
15
|
+
_TOOL_NAME = re.compile(r"^[A-Za-z0-9_]{1,64}$")
|
|
16
|
+
_DIFY_SELECTOR = re.compile(r"^[A-Za-z_][A-Za-z0-9_]{0,29}$")
|
|
17
|
+
_SUPPORTED_WORKFLOW_INPUT_TYPES = frozenset({"string", "number", "select"})
|
|
18
|
+
_UNSUPPORTED_FILE_TYPES = frozenset({"file", "files", "system-files"})
|
|
19
|
+
_DEFAULT_ICON = {"background": "#E4FBCC", "content": "🔀"}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WorkflowToolService:
|
|
23
|
+
def __init__(self, api_client: ApiClient | None = None) -> None:
|
|
24
|
+
self.api_client = api_client or ApiClient()
|
|
25
|
+
|
|
26
|
+
def get_tool(
|
|
27
|
+
self,
|
|
28
|
+
workflow_app_id: str | None = None,
|
|
29
|
+
workflow_tool_id: str | None = None,
|
|
30
|
+
profile: str | None = None,
|
|
31
|
+
) -> dict[str, Any]:
|
|
32
|
+
app_id = _non_empty_string(workflow_app_id)
|
|
33
|
+
tool_id = _non_empty_string(workflow_tool_id)
|
|
34
|
+
if (app_id is None) == (tool_id is None):
|
|
35
|
+
raise TacoError(
|
|
36
|
+
code="WORKFLOW_TOOL_QUERY_INVALID",
|
|
37
|
+
message="查询 Workflow Tool 必须且只能提供 app ID 或 tool ID。",
|
|
38
|
+
exit_code=2,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
params = (
|
|
42
|
+
{"workflow_app_id": app_id}
|
|
43
|
+
if app_id is not None
|
|
44
|
+
else {"workflow_tool_id": tool_id}
|
|
45
|
+
)
|
|
46
|
+
payload = self.api_client.get(
|
|
47
|
+
f"{_WORKFLOW_TOOL_PATH}/get",
|
|
48
|
+
params=params,
|
|
49
|
+
profile=profile,
|
|
50
|
+
)
|
|
51
|
+
_validate_detail_identity(payload)
|
|
52
|
+
if (
|
|
53
|
+
tool_id is not None
|
|
54
|
+
and not _identifiers_match(tool_id, payload["workflow_tool_id"])
|
|
55
|
+
) or (
|
|
56
|
+
app_id is not None
|
|
57
|
+
and not _identifiers_match(app_id, payload["workflow_app_id"])
|
|
58
|
+
):
|
|
59
|
+
raise _invalid_response()
|
|
60
|
+
return payload
|
|
61
|
+
|
|
62
|
+
def publish_tool(
|
|
63
|
+
self,
|
|
64
|
+
app_id: str,
|
|
65
|
+
workflow: Mapping[str, Any],
|
|
66
|
+
name: str,
|
|
67
|
+
label: str,
|
|
68
|
+
description: str,
|
|
69
|
+
profile: str | None = None,
|
|
70
|
+
) -> Any:
|
|
71
|
+
_validate_machine_name(name)
|
|
72
|
+
_validate_label(label)
|
|
73
|
+
_validate_description(description)
|
|
74
|
+
parameters = _derive_parameters(workflow)
|
|
75
|
+
response = self.api_client.post(
|
|
76
|
+
f"{_WORKFLOW_TOOL_PATH}/create",
|
|
77
|
+
json={
|
|
78
|
+
"workflow_app_id": app_id,
|
|
79
|
+
"name": name,
|
|
80
|
+
"label": label,
|
|
81
|
+
"description": description,
|
|
82
|
+
"icon": dict(_DEFAULT_ICON),
|
|
83
|
+
"parameters": parameters,
|
|
84
|
+
"privacy_policy": "",
|
|
85
|
+
"labels": [],
|
|
86
|
+
},
|
|
87
|
+
profile=profile,
|
|
88
|
+
)
|
|
89
|
+
return _validated_write_response(response)
|
|
90
|
+
|
|
91
|
+
def update_tool(
|
|
92
|
+
self,
|
|
93
|
+
tool_id: str,
|
|
94
|
+
workflow: Mapping[str, Any],
|
|
95
|
+
name: str | None = None,
|
|
96
|
+
label: str | None = None,
|
|
97
|
+
description: str | None = None,
|
|
98
|
+
profile: str | None = None,
|
|
99
|
+
) -> Any:
|
|
100
|
+
detail = self.get_tool(workflow_tool_id=tool_id, profile=profile)
|
|
101
|
+
if not _identifiers_match(tool_id, detail["workflow_tool_id"]):
|
|
102
|
+
raise _invalid_response()
|
|
103
|
+
canonical_tool_id = _canonical_identifier(detail["workflow_tool_id"])
|
|
104
|
+
merged_name = detail["name"] if name is None else name
|
|
105
|
+
merged_label = _merged_string(detail, "label", label)
|
|
106
|
+
merged_description = _merged_string(
|
|
107
|
+
detail,
|
|
108
|
+
"description",
|
|
109
|
+
description,
|
|
110
|
+
allow_empty=True,
|
|
111
|
+
)
|
|
112
|
+
_validate_machine_name(merged_name)
|
|
113
|
+
_validate_label(merged_label)
|
|
114
|
+
_validate_description(merged_description)
|
|
115
|
+
|
|
116
|
+
icon = detail.get("icon")
|
|
117
|
+
privacy_policy = detail.get("privacy_policy", "")
|
|
118
|
+
labels = _detail_labels(detail)
|
|
119
|
+
if not isinstance(icon, dict) or not isinstance(privacy_policy, str):
|
|
120
|
+
raise _invalid_response()
|
|
121
|
+
|
|
122
|
+
response = self.api_client.post(
|
|
123
|
+
f"{_WORKFLOW_TOOL_PATH}/update",
|
|
124
|
+
json={
|
|
125
|
+
"workflow_tool_id": canonical_tool_id,
|
|
126
|
+
"name": merged_name,
|
|
127
|
+
"label": merged_label,
|
|
128
|
+
"description": merged_description,
|
|
129
|
+
"icon": deepcopy(icon),
|
|
130
|
+
"parameters": _derive_parameters(workflow),
|
|
131
|
+
"privacy_policy": privacy_policy,
|
|
132
|
+
"labels": labels,
|
|
133
|
+
},
|
|
134
|
+
profile=profile,
|
|
135
|
+
)
|
|
136
|
+
return _validated_write_response(response)
|
|
137
|
+
|
|
138
|
+
def resolve_agent_tool(
|
|
139
|
+
self,
|
|
140
|
+
workflow_tool_id: str,
|
|
141
|
+
tool_name: str,
|
|
142
|
+
parameters: dict[str, Any],
|
|
143
|
+
profile: str | None = None,
|
|
144
|
+
) -> dict[str, Any]:
|
|
145
|
+
detail = self.get_tool(
|
|
146
|
+
workflow_tool_id=workflow_tool_id,
|
|
147
|
+
profile=profile,
|
|
148
|
+
)
|
|
149
|
+
if (
|
|
150
|
+
not _identifiers_match(
|
|
151
|
+
workflow_tool_id,
|
|
152
|
+
detail["workflow_tool_id"],
|
|
153
|
+
)
|
|
154
|
+
or detail["name"] != tool_name
|
|
155
|
+
):
|
|
156
|
+
raise _tool_mismatch()
|
|
157
|
+
|
|
158
|
+
tool = detail.get("tool")
|
|
159
|
+
if tool is not None and not isinstance(tool, dict):
|
|
160
|
+
raise _invalid_response()
|
|
161
|
+
if isinstance(tool, dict):
|
|
162
|
+
discovered_name = tool.get("name")
|
|
163
|
+
if discovered_name is not None and discovered_name != tool_name:
|
|
164
|
+
raise _tool_mismatch()
|
|
165
|
+
|
|
166
|
+
definitions = (
|
|
167
|
+
tool.get("parameters")
|
|
168
|
+
if isinstance(tool, dict) and "parameters" in tool
|
|
169
|
+
else detail.get("parameters")
|
|
170
|
+
)
|
|
171
|
+
resolved_parameters = _resolve_parameters(definitions, parameters)
|
|
172
|
+
tool_label = _localized_label(
|
|
173
|
+
tool.get("label") if isinstance(tool, dict) else None
|
|
174
|
+
)
|
|
175
|
+
if tool_label is None:
|
|
176
|
+
tool_label = _non_empty_string(detail.get("label")) or tool_name
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
"provider_type": "workflow",
|
|
180
|
+
"provider_id": _canonical_identifier(detail["workflow_tool_id"]),
|
|
181
|
+
"provider_name": detail["name"],
|
|
182
|
+
"tool_name": tool_name,
|
|
183
|
+
"tool_label": tool_label,
|
|
184
|
+
"tool_parameters": resolved_parameters,
|
|
185
|
+
"enabled": True,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _derive_parameters(workflow: Mapping[str, Any]) -> list[dict[str, str]]:
|
|
190
|
+
if not isinstance(workflow, Mapping):
|
|
191
|
+
raise _parameters_invalid()
|
|
192
|
+
inputs = workflow.get("inputs")
|
|
193
|
+
if not isinstance(inputs, Mapping):
|
|
194
|
+
raise _parameters_invalid()
|
|
195
|
+
|
|
196
|
+
parameters: list[dict[str, str]] = []
|
|
197
|
+
seen: set[str] = set()
|
|
198
|
+
for name, configuration in inputs.items():
|
|
199
|
+
if (
|
|
200
|
+
not isinstance(name, str)
|
|
201
|
+
or _DIFY_SELECTOR.fullmatch(name) is None
|
|
202
|
+
or name in seen
|
|
203
|
+
or not isinstance(configuration, Mapping)
|
|
204
|
+
or configuration.get("type") not in _SUPPORTED_WORKFLOW_INPUT_TYPES
|
|
205
|
+
):
|
|
206
|
+
raise _parameters_invalid()
|
|
207
|
+
description = configuration.get("description")
|
|
208
|
+
if description is not None and not isinstance(description, str):
|
|
209
|
+
raise _parameters_invalid()
|
|
210
|
+
seen.add(name)
|
|
211
|
+
parameters.append(
|
|
212
|
+
{
|
|
213
|
+
"name": name,
|
|
214
|
+
"description": description or name,
|
|
215
|
+
"form": "llm",
|
|
216
|
+
"required": bool(configuration.get("required", False)),
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
return parameters
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _resolve_parameters(
|
|
223
|
+
definitions: Any,
|
|
224
|
+
supplied: dict[str, Any],
|
|
225
|
+
) -> dict[str, Any]:
|
|
226
|
+
if (
|
|
227
|
+
not isinstance(definitions, list)
|
|
228
|
+
or any(not isinstance(item, dict) for item in definitions)
|
|
229
|
+
or not isinstance(supplied, dict)
|
|
230
|
+
):
|
|
231
|
+
raise _invalid_response()
|
|
232
|
+
|
|
233
|
+
normalized_definitions: list[tuple[str, dict[str, Any]]] = []
|
|
234
|
+
seen: set[str] = set()
|
|
235
|
+
form_names: set[str] = set()
|
|
236
|
+
for definition in definitions:
|
|
237
|
+
name = definition.get("name")
|
|
238
|
+
form = definition.get("form")
|
|
239
|
+
parameter_type = definition.get("type", "string")
|
|
240
|
+
if (
|
|
241
|
+
not isinstance(name, str)
|
|
242
|
+
or _DIFY_SELECTOR.fullmatch(name) is None
|
|
243
|
+
or name in seen
|
|
244
|
+
or form not in {"llm", "form"}
|
|
245
|
+
):
|
|
246
|
+
raise _invalid_response()
|
|
247
|
+
if parameter_type in _UNSUPPORTED_FILE_TYPES:
|
|
248
|
+
raise TacoError(
|
|
249
|
+
code="WORKFLOW_TOOL_FILE_PARAMETER_UNSUPPORTED",
|
|
250
|
+
message=f"Workflow Tool 参数 {name} 是不支持的文件类型。",
|
|
251
|
+
exit_code=6,
|
|
252
|
+
)
|
|
253
|
+
if parameter_type == "secret-input":
|
|
254
|
+
raise TacoError(
|
|
255
|
+
code="WORKFLOW_TOOL_SECRET_PARAMETER_UNSUPPORTED",
|
|
256
|
+
message=f"Workflow Tool 参数 {name} 是不能写入 Agent DSL 的密钥类型。",
|
|
257
|
+
exit_code=6,
|
|
258
|
+
)
|
|
259
|
+
seen.add(name)
|
|
260
|
+
if form == "form":
|
|
261
|
+
form_names.add(name)
|
|
262
|
+
normalized_definitions.append((name, definition))
|
|
263
|
+
|
|
264
|
+
unknown = sorted(set(supplied) - form_names)
|
|
265
|
+
if unknown:
|
|
266
|
+
raise _parameters_invalid("用户参数包含未发现的 form 字段。")
|
|
267
|
+
|
|
268
|
+
resolved: dict[str, Any] = {}
|
|
269
|
+
for name, definition in normalized_definitions:
|
|
270
|
+
if definition["form"] == "llm":
|
|
271
|
+
resolved[name] = ""
|
|
272
|
+
continue
|
|
273
|
+
if name in supplied:
|
|
274
|
+
value = supplied[name]
|
|
275
|
+
elif definition.get("default") is not None:
|
|
276
|
+
value = definition["default"]
|
|
277
|
+
elif definition.get("required"):
|
|
278
|
+
raise _parameters_invalid("缺少必填 form 参数。")
|
|
279
|
+
else:
|
|
280
|
+
continue
|
|
281
|
+
resolved[name] = _normalize_parameter(definition, value)
|
|
282
|
+
return resolved
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _normalize_parameter(definition: dict[str, Any], value: Any) -> Any:
|
|
286
|
+
parameter_type = definition.get("type", "string")
|
|
287
|
+
valid = False
|
|
288
|
+
if parameter_type == "string":
|
|
289
|
+
valid = isinstance(value, str)
|
|
290
|
+
elif parameter_type == "number":
|
|
291
|
+
valid = (
|
|
292
|
+
isinstance(value, int | float)
|
|
293
|
+
and not isinstance(value, bool)
|
|
294
|
+
and (not isinstance(value, float) or math.isfinite(value))
|
|
295
|
+
)
|
|
296
|
+
elif parameter_type == "boolean":
|
|
297
|
+
valid = isinstance(value, bool)
|
|
298
|
+
elif parameter_type == "select":
|
|
299
|
+
options = definition.get("options") or []
|
|
300
|
+
if not isinstance(options, list):
|
|
301
|
+
raise _invalid_response()
|
|
302
|
+
choices = [
|
|
303
|
+
option.get("value") if isinstance(option, dict) else option
|
|
304
|
+
for option in options
|
|
305
|
+
]
|
|
306
|
+
if any(not isinstance(choice, str) for choice in choices):
|
|
307
|
+
raise _invalid_response()
|
|
308
|
+
valid = isinstance(value, str) and (not choices or value in choices)
|
|
309
|
+
if not valid:
|
|
310
|
+
raise _parameters_invalid("form 参数类型或选项不合法。")
|
|
311
|
+
return value
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _validate_detail_identity(payload: Any) -> None:
|
|
315
|
+
if not isinstance(payload, dict) or any(
|
|
316
|
+
_non_empty_string(payload.get(field)) is None
|
|
317
|
+
for field in ("workflow_tool_id", "workflow_app_id", "name")
|
|
318
|
+
):
|
|
319
|
+
raise _invalid_response()
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _identifiers_match(expected: str, actual: str) -> bool:
|
|
323
|
+
try:
|
|
324
|
+
expected_uuid = UUID(expected)
|
|
325
|
+
actual_uuid = UUID(actual)
|
|
326
|
+
except ValueError:
|
|
327
|
+
return expected == actual
|
|
328
|
+
return expected_uuid == actual_uuid
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _canonical_identifier(identifier: str) -> str:
|
|
332
|
+
try:
|
|
333
|
+
return str(UUID(identifier))
|
|
334
|
+
except ValueError:
|
|
335
|
+
return identifier
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _validated_write_response(payload: Any) -> dict[str, Any]:
|
|
339
|
+
if not isinstance(payload, dict) or payload.get("result") != "success":
|
|
340
|
+
raise _invalid_response()
|
|
341
|
+
return payload
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _detail_labels(detail: dict[str, Any]) -> list[str]:
|
|
345
|
+
labels = detail.get("labels")
|
|
346
|
+
if labels is None:
|
|
347
|
+
tool = detail.get("tool")
|
|
348
|
+
labels = tool.get("labels") if isinstance(tool, dict) else []
|
|
349
|
+
if labels is None:
|
|
350
|
+
labels = []
|
|
351
|
+
if not isinstance(labels, list) or any(
|
|
352
|
+
not isinstance(item, str) for item in labels
|
|
353
|
+
):
|
|
354
|
+
raise _invalid_response()
|
|
355
|
+
return list(labels)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _merged_string(
|
|
359
|
+
detail: dict[str, Any],
|
|
360
|
+
field: str,
|
|
361
|
+
override: str | None,
|
|
362
|
+
*,
|
|
363
|
+
allow_empty: bool = False,
|
|
364
|
+
) -> str:
|
|
365
|
+
value = detail.get(field) if override is None else override
|
|
366
|
+
if not isinstance(value, str) or (not allow_empty and not value.strip()):
|
|
367
|
+
raise _invalid_response()
|
|
368
|
+
return value
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _validate_machine_name(name: Any) -> None:
|
|
372
|
+
if not isinstance(name, str) or _TOOL_NAME.fullmatch(name) is None:
|
|
373
|
+
raise TacoError(
|
|
374
|
+
code="WORKFLOW_TOOL_NAME_INVALID",
|
|
375
|
+
message="Workflow Tool 机器名必须是 1-64 位 ASCII 字母、数字或下划线。",
|
|
376
|
+
exit_code=2,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _validate_label(label: Any) -> None:
|
|
381
|
+
if not isinstance(label, str) or not label.strip():
|
|
382
|
+
raise TacoError(
|
|
383
|
+
code="WORKFLOW_TOOL_LABEL_INVALID",
|
|
384
|
+
message="Workflow Tool label 不能为空。",
|
|
385
|
+
exit_code=2,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _validate_description(description: Any) -> None:
|
|
390
|
+
if not isinstance(description, str):
|
|
391
|
+
raise TacoError(
|
|
392
|
+
code="WORKFLOW_TOOL_DESCRIPTION_INVALID",
|
|
393
|
+
message="Workflow Tool description 必须是字符串。",
|
|
394
|
+
exit_code=2,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _localized_label(label: Any) -> str | None:
|
|
399
|
+
if isinstance(label, str):
|
|
400
|
+
return label or None
|
|
401
|
+
if isinstance(label, dict):
|
|
402
|
+
for locale in ("zh_Hans", "en_US"):
|
|
403
|
+
value = _non_empty_string(label.get(locale))
|
|
404
|
+
if value is not None:
|
|
405
|
+
return value
|
|
406
|
+
return None
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _non_empty_string(value: Any) -> str | None:
|
|
410
|
+
if not isinstance(value, str) or not value.strip():
|
|
411
|
+
return None
|
|
412
|
+
return value
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _parameters_invalid(reason: str | None = None) -> TacoError:
|
|
416
|
+
message = "Workflow Tool 参数配置不合法。"
|
|
417
|
+
if reason:
|
|
418
|
+
message = f"{message}{reason}"
|
|
419
|
+
return TacoError(
|
|
420
|
+
code="WORKFLOW_TOOL_PARAMETERS_INVALID",
|
|
421
|
+
message=message,
|
|
422
|
+
exit_code=6,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _tool_mismatch() -> TacoError:
|
|
427
|
+
return TacoError(
|
|
428
|
+
code="WORKFLOW_TOOL_MISMATCH",
|
|
429
|
+
message="Workflow Tool 详情与请求的 tool ID 或名称不一致。",
|
|
430
|
+
exit_code=6,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _invalid_response() -> TacoError:
|
|
435
|
+
return TacoError(
|
|
436
|
+
code="WORKFLOW_TOOL_RESPONSE_INVALID",
|
|
437
|
+
message="Workflow Tool 接口返回结构不合法。",
|
|
438
|
+
exit_code=4,
|
|
439
|
+
)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tinet-agent-cli
|
|
3
|
+
Version: 0.1.0.dev36
|
|
4
|
+
Summary: Tinet Agent CLI Orchestrator
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: filelock<4.0.0,>=3.32.5
|
|
7
|
+
Requires-Dist: httpx>=0.27.0
|
|
8
|
+
Requires-Dist: jsonschema<5.0.0,>=4.23.0
|
|
9
|
+
Requires-Dist: pyyaml<7.0.0,>=6.0.2
|
|
10
|
+
Requires-Dist: typer<0.27.0,>=0.26.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: build>=1.2.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# TACO:智能体平台 CLI
|
|
18
|
+
|
|
19
|
+
TACO(Tinet Agent CLI Orchestrator)是天润智能体平台的命令行工具,可供开发者、自动化
|
|
20
|
+
流水线,以及 Cursor、Codex、Claude Code 等 AI Agent 使用。
|
|
21
|
+
|
|
22
|
+
它将智能体、应用和智能知识库能力组织成稳定、可发现的命令,并同时提供人工可读输出与 JSON
|
|
23
|
+
输出。
|
|
24
|
+
|
|
25
|
+
## 主要能力
|
|
26
|
+
|
|
27
|
+
- 使用 profile 管理和切换企业授权
|
|
28
|
+
- 查询、创建和管理智能体应用
|
|
29
|
+
- 从应用模板创建应用
|
|
30
|
+
- 拉取、校验、调试和发布智能体 DSL
|
|
31
|
+
- 管理 Workflow、Chatflow 和智能体工具
|
|
32
|
+
- 发现并调用 Agent OpenAPI
|
|
33
|
+
- 发现并调用智能知识库 OpenAPI,包括文档上传
|
|
34
|
+
- 向 AI Agent 提供命令、参数、场景和 Schema 的结构化说明
|
|
35
|
+
|
|
36
|
+
## 安装
|
|
37
|
+
|
|
38
|
+
要求 Python 3.12 或更高版本。推荐使用 `pipx` 安装:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pipx install tinet-agent-cli
|
|
42
|
+
taco --version
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
也可以使用 `pip`:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python -m pip install tinet-agent-cli
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 配置授权
|
|
52
|
+
|
|
53
|
+
使用前需要准备开发者中心 AccessKeyId 和 AccessKeySecret,并为接口密钥分配“智能体 > CLI
|
|
54
|
+
授权”权限。
|
|
55
|
+
|
|
56
|
+
CLINK2 endpoint 示例:
|
|
57
|
+
|
|
58
|
+
- 北京平台:`https://api-bj.clink.cn`
|
|
59
|
+
- 上海平台:`https://api-sh.clink.cn`
|
|
60
|
+
|
|
61
|
+
创建 profile:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
TACO_ACCESS_KEY_SECRET='<access-key-secret>' taco profile set \
|
|
65
|
+
--name clink-bj \
|
|
66
|
+
--endpoint https://api-bj.clink.cn \
|
|
67
|
+
--access-key-id '<access-key-id>'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
列出 Zenava 和 TACO profile。带 `current=true` 的配置会优先展示,Zenava 当前配置排在 TACO
|
|
71
|
+
当前配置之前:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
taco --json profile list
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
多企业场景可以列出、切换 profile,或为单次命令指定 profile:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
taco profile list
|
|
81
|
+
taco profile use clink-bj
|
|
82
|
+
taco --profile clink-sh --json agent app list
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 智能体
|
|
86
|
+
|
|
87
|
+
### 应用与模板
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
taco agent app list --limit 20
|
|
91
|
+
taco --json agent app get <app-id>
|
|
92
|
+
taco agent app tag list --keyword 客服
|
|
93
|
+
taco agent app template list
|
|
94
|
+
taco agent app template create <template-app-id> --name 新应用 --yes
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 智能体 DSL
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
taco agent create \
|
|
101
|
+
--name 示例智能体 \
|
|
102
|
+
--category <category-code> \
|
|
103
|
+
--output agent.json \
|
|
104
|
+
--yes
|
|
105
|
+
|
|
106
|
+
taco agent pull <app-id> --output agent.json
|
|
107
|
+
taco agent validate --dsl agent.json
|
|
108
|
+
taco agent publish <app-id> --dsl agent.json --yes
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
模型、分类、Workflow、Chatflow、工具和内置示例可以从 `taco agent --help` 继续发现。
|
|
112
|
+
|
|
113
|
+
### Agent OpenAPI
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
taco agent operation search <关键词>
|
|
117
|
+
taco agent operation describe <operation-id>
|
|
118
|
+
taco --json agent call <operation-id> --param key=value
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 智能知识库
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
taco aikb operation search <关键词>
|
|
125
|
+
taco aikb operation describe <operation-id>
|
|
126
|
+
taco --json aikb call <operation-id> --param key=value
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
复杂参数可以通过 `--params-file` 或 `--body-file` 提供。文档上传 operation 支持使用 `--file`
|
|
130
|
+
指定本地文件,由 TACO 完成上传流程。
|
|
131
|
+
|
|
132
|
+
## 提供给 AI Agent 使用
|
|
133
|
+
|
|
134
|
+
AI Agent 或 CI 推荐使用 `--json --no-input`,避免依赖终端文本或等待交互输入:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
taco --json --no-input profile list
|
|
138
|
+
taco --json --no-input agent app list --limit 10
|
|
139
|
+
taco --json --no-input aikb operation search 文档
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
TACO 可以输出完整命令契约及内置场景:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
taco help -o json
|
|
146
|
+
taco agent scenario list -o json
|
|
147
|
+
taco agent schema list -o json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
推荐先搜索候选 operation,再查看参数契约,最后执行调用。远端创建、修改、删除、上传和发布
|
|
151
|
+
操作应先取得用户批准,再显式传入 `--yes`。
|
|
152
|
+
|
|
153
|
+
## 安全提示
|
|
154
|
+
|
|
155
|
+
- 不要把 AccessKeySecret 或 token 写入代码、DSL、日志和对话内容。
|
|
156
|
+
- 优先通过隐藏输入、环境变量或 CI 密钥管理提供 AccessKeySecret。
|
|
157
|
+
- 写操作前确认当前 profile、目标资源和变更范围。
|
|
158
|
+
- 自动化程序应判断退出码、`ok` 和 `error.code`。
|