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,482 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from urllib.parse import quote
|
|
5
|
+
|
|
6
|
+
from taco.core.api_client import ApiClient
|
|
7
|
+
from taco.core.errors import TacoError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ToolService:
|
|
11
|
+
def __init__(self, api_client: ApiClient | None = None) -> None:
|
|
12
|
+
self.api_client = api_client or ApiClient()
|
|
13
|
+
|
|
14
|
+
def list_providers(self, *, profile: str | None = None) -> dict[str, Any]:
|
|
15
|
+
providers = self._list_builtin(profile=profile)
|
|
16
|
+
return {
|
|
17
|
+
"items": [
|
|
18
|
+
{
|
|
19
|
+
"provider_id": provider["id"],
|
|
20
|
+
"provider_name": provider.get("name"),
|
|
21
|
+
"provider_type": "builtin",
|
|
22
|
+
"label": provider.get("label"),
|
|
23
|
+
"credential_configured": bool(
|
|
24
|
+
provider.get("is_team_authorization")
|
|
25
|
+
),
|
|
26
|
+
"credential_fields": sorted(
|
|
27
|
+
(provider.get("team_credentials") or {}).keys()
|
|
28
|
+
),
|
|
29
|
+
"allow_delete": bool(provider.get("allow_delete")),
|
|
30
|
+
"tools_count": len(provider.get("tools") or []),
|
|
31
|
+
}
|
|
32
|
+
for provider in providers
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def list_tools(
|
|
37
|
+
self,
|
|
38
|
+
*,
|
|
39
|
+
provider: str | None = None,
|
|
40
|
+
profile: str | None = None,
|
|
41
|
+
) -> dict[str, Any]:
|
|
42
|
+
items: list[dict[str, Any]] = []
|
|
43
|
+
providers = self._list_builtin(profile=profile)
|
|
44
|
+
for provider_payload in providers:
|
|
45
|
+
provider_id = provider_payload["id"]
|
|
46
|
+
if provider is not None and provider_id != provider:
|
|
47
|
+
continue
|
|
48
|
+
for tool in provider_payload.get("tools") or []:
|
|
49
|
+
if not isinstance(tool, dict) or not isinstance(
|
|
50
|
+
tool.get("name"),
|
|
51
|
+
str,
|
|
52
|
+
):
|
|
53
|
+
raise _invalid_tool_response()
|
|
54
|
+
items.append(
|
|
55
|
+
{
|
|
56
|
+
"author": tool.get("author"),
|
|
57
|
+
"name": tool["name"],
|
|
58
|
+
"label": tool.get("label"),
|
|
59
|
+
"description": tool.get("description"),
|
|
60
|
+
"labels": tool.get("labels") or [],
|
|
61
|
+
"provider_id": provider_id,
|
|
62
|
+
"provider_type": "builtin",
|
|
63
|
+
"provider_label": provider_payload.get("label"),
|
|
64
|
+
"tool_name": tool["name"],
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
if provider is not None and not any(
|
|
68
|
+
item["provider_id"] == provider for item in items
|
|
69
|
+
):
|
|
70
|
+
self._find_provider(providers, provider)
|
|
71
|
+
return {"provider": provider, "items": items}
|
|
72
|
+
|
|
73
|
+
def describe_tool(
|
|
74
|
+
self,
|
|
75
|
+
*,
|
|
76
|
+
provider: str,
|
|
77
|
+
tool: str,
|
|
78
|
+
profile: str | None = None,
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
providers = self._list_builtin(profile=profile)
|
|
81
|
+
provider_payload = self._find_provider(providers, provider)
|
|
82
|
+
tool_payload = self._find_tool(provider_payload, tool)
|
|
83
|
+
return {
|
|
84
|
+
**tool_payload,
|
|
85
|
+
"provider_id": provider,
|
|
86
|
+
"provider_type": "builtin",
|
|
87
|
+
"tool_name": tool,
|
|
88
|
+
"provider_label": provider_payload.get("label"),
|
|
89
|
+
"credentials_schema": self.credential_schema(
|
|
90
|
+
provider,
|
|
91
|
+
profile=profile,
|
|
92
|
+
),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
def credential_schema(
|
|
96
|
+
self,
|
|
97
|
+
provider: str,
|
|
98
|
+
*,
|
|
99
|
+
profile: str | None = None,
|
|
100
|
+
) -> list[dict[str, Any]]:
|
|
101
|
+
payload = self.api_client.get(
|
|
102
|
+
self._provider_path(provider, "credentials_schema"),
|
|
103
|
+
profile=profile,
|
|
104
|
+
)
|
|
105
|
+
if not isinstance(payload, list) or any(
|
|
106
|
+
not isinstance(item, dict)
|
|
107
|
+
or not isinstance(item.get("name"), str)
|
|
108
|
+
or not item["name"]
|
|
109
|
+
for item in payload
|
|
110
|
+
):
|
|
111
|
+
raise _invalid_tool_response()
|
|
112
|
+
return payload
|
|
113
|
+
|
|
114
|
+
def credential_status(
|
|
115
|
+
self,
|
|
116
|
+
provider: str,
|
|
117
|
+
*,
|
|
118
|
+
profile: str | None = None,
|
|
119
|
+
) -> dict[str, Any]:
|
|
120
|
+
schema = self.credential_schema(provider, profile=profile)
|
|
121
|
+
credentials = self.api_client.get(
|
|
122
|
+
self._provider_path(provider, "credentials"),
|
|
123
|
+
profile=profile,
|
|
124
|
+
)
|
|
125
|
+
if not isinstance(credentials, dict):
|
|
126
|
+
raise _invalid_tool_response()
|
|
127
|
+
return {
|
|
128
|
+
"provider": provider,
|
|
129
|
+
"configured": not schema or bool(credentials),
|
|
130
|
+
"credentials": _mask_credentials(credentials),
|
|
131
|
+
"schema": schema,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
def set_credentials(
|
|
135
|
+
self,
|
|
136
|
+
provider: str,
|
|
137
|
+
credentials: dict[str, Any],
|
|
138
|
+
*,
|
|
139
|
+
profile: str | None = None,
|
|
140
|
+
) -> Any:
|
|
141
|
+
try:
|
|
142
|
+
return self.api_client.post(
|
|
143
|
+
self._provider_path(provider, "update"),
|
|
144
|
+
json={"credentials": credentials},
|
|
145
|
+
profile=profile,
|
|
146
|
+
)
|
|
147
|
+
except TacoError as error:
|
|
148
|
+
raise TacoError(
|
|
149
|
+
code="CREDENTIAL_UPDATE_FAILED",
|
|
150
|
+
message="Provider 凭据写入失败,服务端未接受该配置。",
|
|
151
|
+
exit_code=error.exit_code,
|
|
152
|
+
status=error.status,
|
|
153
|
+
hint="请核对 credential schema、权限和网络状态后重试。",
|
|
154
|
+
) from error
|
|
155
|
+
|
|
156
|
+
def delete_credentials(
|
|
157
|
+
self,
|
|
158
|
+
provider: str,
|
|
159
|
+
*,
|
|
160
|
+
profile: str | None = None,
|
|
161
|
+
) -> Any:
|
|
162
|
+
return self.api_client.post(
|
|
163
|
+
self._provider_path(provider, "delete"),
|
|
164
|
+
json={},
|
|
165
|
+
profile=profile,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def resolve_agent_tool(
|
|
169
|
+
self,
|
|
170
|
+
*,
|
|
171
|
+
provider: str,
|
|
172
|
+
tool: str,
|
|
173
|
+
parameters: dict[str, Any],
|
|
174
|
+
profile: str | None = None,
|
|
175
|
+
) -> dict[str, Any]:
|
|
176
|
+
provider_payload = self._find_provider(
|
|
177
|
+
self._list_builtin(profile=profile),
|
|
178
|
+
provider,
|
|
179
|
+
)
|
|
180
|
+
tool_payload = self._find_tool(provider_payload, tool)
|
|
181
|
+
return self.build_agent_tool(
|
|
182
|
+
provider=provider_payload,
|
|
183
|
+
tool=tool_payload,
|
|
184
|
+
parameters=parameters,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def build_agent_tool(
|
|
189
|
+
*,
|
|
190
|
+
provider: dict[str, Any],
|
|
191
|
+
tool: dict[str, Any],
|
|
192
|
+
parameters: dict[str, Any],
|
|
193
|
+
) -> dict[str, Any]:
|
|
194
|
+
tool_parameters = tool.get("parameters") or []
|
|
195
|
+
if not isinstance(tool_parameters, list) or any(
|
|
196
|
+
not isinstance(item, dict) for item in tool_parameters
|
|
197
|
+
):
|
|
198
|
+
raise _invalid_tool_response()
|
|
199
|
+
for definition in tool_parameters:
|
|
200
|
+
if (
|
|
201
|
+
definition.get("required")
|
|
202
|
+
and definition.get("type")
|
|
203
|
+
in {"file", "files", "system-files"}
|
|
204
|
+
):
|
|
205
|
+
name = definition.get("name") or "<unknown>"
|
|
206
|
+
raise TacoError(
|
|
207
|
+
code="TOOL_FILE_PARAMETER_UNSUPPORTED",
|
|
208
|
+
message=(
|
|
209
|
+
f"工具参数 {name} 是必填文件参数,"
|
|
210
|
+
"当前 Agent 工具调用不支持。"
|
|
211
|
+
),
|
|
212
|
+
exit_code=6,
|
|
213
|
+
hint="请选择不含必填文件参数的工具。",
|
|
214
|
+
)
|
|
215
|
+
form_parameters = {
|
|
216
|
+
item.get("name"): item
|
|
217
|
+
for item in tool_parameters
|
|
218
|
+
if isinstance(item, dict)
|
|
219
|
+
and item.get("form") == "form"
|
|
220
|
+
and isinstance(item.get("name"), str)
|
|
221
|
+
}
|
|
222
|
+
unknown = sorted(set(parameters) - set(form_parameters))
|
|
223
|
+
if unknown:
|
|
224
|
+
raise TacoError(
|
|
225
|
+
code="TOOL_PARAMETERS_INVALID",
|
|
226
|
+
message="工具参数包含未发现字段:" + ", ".join(unknown),
|
|
227
|
+
exit_code=6,
|
|
228
|
+
hint="请先执行 taco tool describe 获取 form 参数。",
|
|
229
|
+
)
|
|
230
|
+
resolved: dict[str, Any] = {}
|
|
231
|
+
for name, definition in form_parameters.items():
|
|
232
|
+
if definition.get("type") in {"file", "files", "system-files"}:
|
|
233
|
+
if (
|
|
234
|
+
name in parameters
|
|
235
|
+
or definition.get("default") is not None
|
|
236
|
+
or definition.get("required")
|
|
237
|
+
):
|
|
238
|
+
raise TacoError(
|
|
239
|
+
code="TOOL_FILE_PARAMETER_UNSUPPORTED",
|
|
240
|
+
message=(
|
|
241
|
+
f"工具参数 {name} 需要文件,"
|
|
242
|
+
"当前 Agent 本地 DSL 挂载不支持此类 form 参数。"
|
|
243
|
+
),
|
|
244
|
+
exit_code=6,
|
|
245
|
+
hint="请选择不需要文件 form 参数的工具。",
|
|
246
|
+
)
|
|
247
|
+
continue
|
|
248
|
+
if definition.get("type") == "secret-input":
|
|
249
|
+
if name in parameters or definition.get("required"):
|
|
250
|
+
raise TacoError(
|
|
251
|
+
code="TOOL_SECRET_PARAMETER_UNSUPPORTED",
|
|
252
|
+
message=(
|
|
253
|
+
f"工具参数 {name} 是 secret-input,"
|
|
254
|
+
"不能写入本地 Agent DSL。"
|
|
255
|
+
),
|
|
256
|
+
exit_code=6,
|
|
257
|
+
hint="请改用 Provider 凭据管理,或选择不含本地密钥的工具。",
|
|
258
|
+
)
|
|
259
|
+
continue
|
|
260
|
+
if name in parameters:
|
|
261
|
+
value = parameters[name]
|
|
262
|
+
elif definition.get("default") is not None:
|
|
263
|
+
value = definition["default"]
|
|
264
|
+
elif definition.get("required"):
|
|
265
|
+
raise TacoError(
|
|
266
|
+
code="TOOL_PARAMETERS_INVALID",
|
|
267
|
+
message=f"缺少必填工具参数:{name}",
|
|
268
|
+
exit_code=6,
|
|
269
|
+
)
|
|
270
|
+
else:
|
|
271
|
+
continue
|
|
272
|
+
resolved[name] = _normalize_tool_parameter(name, definition, value)
|
|
273
|
+
|
|
274
|
+
provider_id = provider.get("id")
|
|
275
|
+
tool_name = tool.get("name")
|
|
276
|
+
if not isinstance(provider_id, str) or not isinstance(tool_name, str):
|
|
277
|
+
raise _invalid_tool_response()
|
|
278
|
+
payload: dict[str, Any] = {
|
|
279
|
+
"provider_type": "builtin",
|
|
280
|
+
"provider_id": provider_id,
|
|
281
|
+
"provider_name": provider.get("name") or provider_id,
|
|
282
|
+
"tool_name": tool_name,
|
|
283
|
+
"tool_label": _localized_label(tool.get("label")) or tool_name,
|
|
284
|
+
"tool_parameters": resolved,
|
|
285
|
+
"enabled": True,
|
|
286
|
+
}
|
|
287
|
+
plugin_identifier = provider.get("plugin_unique_identifier")
|
|
288
|
+
if isinstance(plugin_identifier, str) and plugin_identifier:
|
|
289
|
+
payload["plugin_unique_identifier"] = plugin_identifier
|
|
290
|
+
return payload
|
|
291
|
+
|
|
292
|
+
@staticmethod
|
|
293
|
+
def validate_credentials_payload(
|
|
294
|
+
credentials: dict[str, Any],
|
|
295
|
+
schema: list[dict[str, Any]],
|
|
296
|
+
) -> None:
|
|
297
|
+
validate_credentials_payload(credentials, schema)
|
|
298
|
+
|
|
299
|
+
def _list_builtin(
|
|
300
|
+
self,
|
|
301
|
+
*,
|
|
302
|
+
profile: str | None = None,
|
|
303
|
+
) -> list[dict[str, Any]]:
|
|
304
|
+
payload = self.api_client.get(
|
|
305
|
+
"/workspaces/current/tools/builtin",
|
|
306
|
+
profile=profile,
|
|
307
|
+
)
|
|
308
|
+
if not isinstance(payload, list) or any(
|
|
309
|
+
not isinstance(item, dict)
|
|
310
|
+
or not isinstance(item.get("id"), str)
|
|
311
|
+
or not isinstance(item.get("tools"), list)
|
|
312
|
+
for item in payload
|
|
313
|
+
):
|
|
314
|
+
raise _invalid_tool_response()
|
|
315
|
+
return payload
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def _find_provider(
|
|
319
|
+
providers: list[dict[str, Any]],
|
|
320
|
+
provider: str,
|
|
321
|
+
) -> dict[str, Any]:
|
|
322
|
+
result = next((item for item in providers if item.get("id") == provider), None)
|
|
323
|
+
if result is None:
|
|
324
|
+
raise TacoError(
|
|
325
|
+
code="TOOL_PROVIDER_NOT_FOUND",
|
|
326
|
+
message=f"未找到内置工具 Provider:{provider}",
|
|
327
|
+
exit_code=6,
|
|
328
|
+
hint="请先执行 taco tool provider list。",
|
|
329
|
+
)
|
|
330
|
+
return result
|
|
331
|
+
|
|
332
|
+
@staticmethod
|
|
333
|
+
def _find_tool(provider: dict[str, Any], tool: str) -> dict[str, Any]:
|
|
334
|
+
result = next(
|
|
335
|
+
(
|
|
336
|
+
item
|
|
337
|
+
for item in provider.get("tools") or []
|
|
338
|
+
if isinstance(item, dict) and item.get("name") == tool
|
|
339
|
+
),
|
|
340
|
+
None,
|
|
341
|
+
)
|
|
342
|
+
if result is None:
|
|
343
|
+
raise TacoError(
|
|
344
|
+
code="TOOL_NOT_FOUND",
|
|
345
|
+
message=f"Provider 中未找到工具:{tool}",
|
|
346
|
+
exit_code=6,
|
|
347
|
+
hint="请先执行 taco tool list --provider <provider>。",
|
|
348
|
+
)
|
|
349
|
+
return result
|
|
350
|
+
|
|
351
|
+
@staticmethod
|
|
352
|
+
def _provider_path(provider: str, operation: str) -> str:
|
|
353
|
+
return (
|
|
354
|
+
"/workspaces/current/tool-provider/builtin/"
|
|
355
|
+
f"{quote(provider, safe='')}/{operation}"
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _localized_label(label: Any) -> str | None:
|
|
360
|
+
if isinstance(label, str):
|
|
361
|
+
return label
|
|
362
|
+
if isinstance(label, dict):
|
|
363
|
+
for locale in ("zh_Hans", "en_US"):
|
|
364
|
+
value = label.get(locale)
|
|
365
|
+
if isinstance(value, str) and value:
|
|
366
|
+
return value
|
|
367
|
+
return None
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _mask_credentials(credentials: dict[str, Any]) -> dict[str, str]:
|
|
371
|
+
return {
|
|
372
|
+
name: "********" if value is not None and value != "" else ""
|
|
373
|
+
for name, value in credentials.items()
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _normalize_tool_parameter(
|
|
378
|
+
name: str,
|
|
379
|
+
definition: dict[str, Any],
|
|
380
|
+
value: Any,
|
|
381
|
+
) -> Any:
|
|
382
|
+
parameter_type = definition.get("type")
|
|
383
|
+
valid = True
|
|
384
|
+
if parameter_type == "number":
|
|
385
|
+
valid = isinstance(value, int | float) and not isinstance(value, bool)
|
|
386
|
+
elif parameter_type == "boolean":
|
|
387
|
+
valid = isinstance(value, bool) or (
|
|
388
|
+
isinstance(value, int) and value in {0, 1}
|
|
389
|
+
)
|
|
390
|
+
if isinstance(value, str):
|
|
391
|
+
valid = False
|
|
392
|
+
normalized = value.lower()
|
|
393
|
+
if normalized in {"true", "yes", "y", "1"}:
|
|
394
|
+
value = True
|
|
395
|
+
valid = True
|
|
396
|
+
elif normalized in {"false", "no", "n", "0"}:
|
|
397
|
+
value = False
|
|
398
|
+
valid = True
|
|
399
|
+
if valid:
|
|
400
|
+
value = bool(value)
|
|
401
|
+
elif parameter_type == "string":
|
|
402
|
+
valid = isinstance(value, str)
|
|
403
|
+
elif parameter_type == "select":
|
|
404
|
+
options = definition.get("options") or []
|
|
405
|
+
choices = {
|
|
406
|
+
option.get("value") if isinstance(option, dict) else option
|
|
407
|
+
for option in options
|
|
408
|
+
}
|
|
409
|
+
if not isinstance(value, str):
|
|
410
|
+
direct_value = str(value).lower()
|
|
411
|
+
boolean_value = None
|
|
412
|
+
if isinstance(value, bool) or (
|
|
413
|
+
isinstance(value, int) and value in {0, 1}
|
|
414
|
+
):
|
|
415
|
+
boolean_value = "true" if bool(value) else "false"
|
|
416
|
+
if direct_value in choices or not choices:
|
|
417
|
+
value = direct_value
|
|
418
|
+
elif boolean_value in choices:
|
|
419
|
+
value = boolean_value
|
|
420
|
+
valid = isinstance(value, str)
|
|
421
|
+
elif parameter_type == "file":
|
|
422
|
+
valid = isinstance(value, dict)
|
|
423
|
+
elif parameter_type in {"files", "system-files"}:
|
|
424
|
+
valid = isinstance(value, list)
|
|
425
|
+
elif parameter_type in {"app-selector", "model-selector"}:
|
|
426
|
+
valid = isinstance(value, dict)
|
|
427
|
+
|
|
428
|
+
if valid and parameter_type == "select":
|
|
429
|
+
valid = not choices or value in choices
|
|
430
|
+
if valid and parameter_type == "number":
|
|
431
|
+
minimum = definition.get("min")
|
|
432
|
+
maximum = definition.get("max")
|
|
433
|
+
valid = not (
|
|
434
|
+
isinstance(minimum, int | float) and value < minimum
|
|
435
|
+
or isinstance(maximum, int | float) and value > maximum
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
if not valid:
|
|
439
|
+
raise TacoError(
|
|
440
|
+
code="TOOL_PARAMETERS_INVALID",
|
|
441
|
+
message=f"工具参数 {name} 不符合发现到的 {parameter_type} 类型或选项。",
|
|
442
|
+
exit_code=6,
|
|
443
|
+
hint="请先执行 taco tool describe 获取参数 schema。",
|
|
444
|
+
)
|
|
445
|
+
return value
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def validate_credentials_payload(
|
|
449
|
+
credentials: dict[str, Any],
|
|
450
|
+
schema: list[dict[str, Any]],
|
|
451
|
+
) -> None:
|
|
452
|
+
field_names = {
|
|
453
|
+
field.get("name")
|
|
454
|
+
for field in schema
|
|
455
|
+
if isinstance(field.get("name"), str)
|
|
456
|
+
}
|
|
457
|
+
unknown = sorted(set(credentials) - field_names)
|
|
458
|
+
missing = sorted(
|
|
459
|
+
field["name"]
|
|
460
|
+
for field in schema
|
|
461
|
+
if field.get("required") and field.get("name") not in credentials
|
|
462
|
+
)
|
|
463
|
+
if unknown or missing:
|
|
464
|
+
parts = []
|
|
465
|
+
if unknown:
|
|
466
|
+
parts.append("未知字段:" + ", ".join(unknown))
|
|
467
|
+
if missing:
|
|
468
|
+
parts.append("缺少字段:" + ", ".join(missing))
|
|
469
|
+
raise TacoError(
|
|
470
|
+
code="CREDENTIAL_INPUT_INVALID",
|
|
471
|
+
message=";".join(parts),
|
|
472
|
+
exit_code=6,
|
|
473
|
+
hint="请先执行 taco credential status 查看 schema。",
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _invalid_tool_response() -> TacoError:
|
|
478
|
+
return TacoError(
|
|
479
|
+
code="TOOL_RESPONSE_INVALID",
|
|
480
|
+
message="工具接口返回结构不合法。",
|
|
481
|
+
exit_code=4,
|
|
482
|
+
)
|