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.
Files changed (104) hide show
  1. taco/__init__.py +1 -0
  2. taco/agent_openapi/__init__.py +1 -0
  3. taco/agent_openapi/catalog.py +198 -0
  4. taco/agent_openapi/models.py +47 -0
  5. taco/agent_openapi/parameters.py +111 -0
  6. taco/agent_openapi/service.py +73 -0
  7. taco/aikb/__init__.py +4 -0
  8. taco/aikb/catalog.py +380 -0
  9. taco/aikb/executors.py +157 -0
  10. taco/aikb/file_input.py +55 -0
  11. taco/aikb/models.py +42 -0
  12. taco/aikb/parameters.py +135 -0
  13. taco/aikb/service.py +68 -0
  14. taco/assets/__init__.py +1 -0
  15. taco/assets/agent_openapi/catalog.json +14 -0
  16. taco/assets/agent_openapi/operations/conversation/list-conversations.json +67 -0
  17. taco/assets/agent_openapi/operations/message/list-conversation-messages.json +80 -0
  18. taco/assets/agent_openapi/operations/trace/list-message-traces.json +72 -0
  19. taco/assets/aikb/catalog.json +41 -0
  20. taco/assets/aikb/operations/conversation/chat-conversation-on-open.json +99 -0
  21. taco/assets/aikb/operations/directory/delete-directory.json +81 -0
  22. taco/assets/aikb/operations/directory/edit-directory.json +97 -0
  23. taco/assets/aikb/operations/directory/list-directory-tree.json +106 -0
  24. taco/assets/aikb/operations/directory/save-directory.json +109 -0
  25. taco/assets/aikb/operations/faq/create-faq.json +193 -0
  26. taco/assets/aikb/operations/faq/delete-faq.json +81 -0
  27. taco/assets/aikb/operations/faq/describe-faq.json +82 -0
  28. taco/assets/aikb/operations/faq/edit-faq.json +193 -0
  29. taco/assets/aikb/operations/faq/list-faqs.json +136 -0
  30. taco/assets/aikb/operations/file/create-file.json +145 -0
  31. taco/assets/aikb/operations/file/delete-files.json +110 -0
  32. taco/assets/aikb/operations/file/describe-file.json +82 -0
  33. taco/assets/aikb/operations/file/get-file-upload-url.json +154 -0
  34. taco/assets/aikb/operations/file/list-files.json +146 -0
  35. taco/assets/aikb/operations/media/describe-faq-media-url.json +98 -0
  36. taco/assets/aikb/operations/media/describe-file-media-url.json +90 -0
  37. taco/assets/aikb/operations/oss/signature-for-upload.json +85 -0
  38. taco/assets/aikb/operations/recycle-bin/list-recycled-items.json +151 -0
  39. taco/assets/aikb/operations/repository/describe-bot-repository.json +71 -0
  40. taco/assets/aikb/operations/repository/list-private-repositories.json +75 -0
  41. taco/assets/aikb/operations/repository/list-public-repositories.json +75 -0
  42. taco/assets/aikb/operations/search/search-knowledge-on-open.json +180 -0
  43. taco/assets/examples/README.md +3 -0
  44. taco/assets/examples/agent-basic.json +16 -0
  45. taco/assets/examples/agent-builtin-tool.json +28 -0
  46. taco/assets/examples/agent-workflow-tool.json +30 -0
  47. taco/assets/examples/chatflow-basic.json +24 -0
  48. taco/assets/examples/workflow-http.json +34 -0
  49. taco/assets/manifest.json +12 -0
  50. taco/assets/scenarios/README.md +3 -0
  51. taco/assets/scenarios/agent-basic.json +80 -0
  52. taco/assets/scenarios/agent-builtin-tool.json +116 -0
  53. taco/assets/scenarios/agent-workflow-tool.json +277 -0
  54. taco/assets/schemas/README.md +3 -0
  55. taco/assets/schemas/agent-tool.json +71 -0
  56. taco/assets/schemas/agent.json +199 -0
  57. taco/assets/schemas/chatflow.json +1048 -0
  58. taco/assets/schemas/workflow.http.json +1052 -0
  59. taco/assets/schemas/workflow.json +1052 -0
  60. taco/assets/skills/README.md +3 -0
  61. taco/assets/skills/taco/SKILL.md +68 -0
  62. taco/assets/skills/taco/manifest.json +10 -0
  63. taco/cli.py +127 -0
  64. taco/commands/__init__.py +1 -0
  65. taco/commands/agent.py +760 -0
  66. taco/commands/aikb.py +132 -0
  67. taco/commands/app.py +151 -0
  68. taco/commands/category.py +37 -0
  69. taco/commands/chatflow.py +183 -0
  70. taco/commands/credential.py +222 -0
  71. taco/commands/discovery.py +409 -0
  72. taco/commands/model.py +55 -0
  73. taco/commands/profile.py +124 -0
  74. taco/commands/tool.py +61 -0
  75. taco/commands/workflow.py +714 -0
  76. taco/core/__init__.py +1 -0
  77. taco/core/access_token_manager.py +98 -0
  78. taco/core/api_client.py +263 -0
  79. taco/core/assets.py +81 -0
  80. taco/core/config_store.py +209 -0
  81. taco/core/context.py +19 -0
  82. taco/core/developer_center_client.py +165 -0
  83. taco/core/errors.py +19 -0
  84. taco/core/file_lock.py +80 -0
  85. taco/core/http_headers.py +21 -0
  86. taco/core/openapi_signer.py +221 -0
  87. taco/core/output.py +72 -0
  88. taco/core/profile_manager.py +217 -0
  89. taco/core/profile_resolver.py +151 -0
  90. taco/core/secure_file.py +76 -0
  91. taco/release.py +363 -0
  92. taco/services/__init__.py +1 -0
  93. taco/services/agent_service.py +314 -0
  94. taco/services/app_service.py +153 -0
  95. taco/services/category_service.py +57 -0
  96. taco/services/model_service.py +117 -0
  97. taco/services/tool_service.py +482 -0
  98. taco/services/workflow_compiler.py +1665 -0
  99. taco/services/workflow_service.py +652 -0
  100. taco/services/workflow_tool_service.py +439 -0
  101. tinet_agent_cli-0.1.0.dev36.dist-info/METADATA +158 -0
  102. tinet_agent_cli-0.1.0.dev36.dist-info/RECORD +104 -0
  103. tinet_agent_cli-0.1.0.dev36.dist-info/WHEEL +4 -0
  104. 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
+ )