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
taco/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0.dev36"
@@ -0,0 +1 @@
1
+ """Developer-center Agent data OpenAPI support."""
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from importlib import resources
6
+ from pathlib import Path, PurePosixPath
7
+ from types import MappingProxyType
8
+ from typing import Any
9
+ from urllib.parse import urlsplit
10
+
11
+ from jsonschema import Draft202012Validator
12
+
13
+ from taco.agent_openapi.models import AgentCatalog, AgentOperation, freeze
14
+ from taco.core.errors import TacoError
15
+
16
+
17
+ _ID_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
18
+ _ZH_PATTERN = re.compile(r"[\u3400-\u9fff]")
19
+ _RESERVED = {"accesskeyid", "expires", "timestamp", "signature", "authorization"}
20
+
21
+
22
+ def load_catalog(root: Path | None = None) -> AgentCatalog:
23
+ try:
24
+ if root is None:
25
+ package_root = resources.files("taco.assets").joinpath("agent_openapi")
26
+ payload = json.loads(package_root.joinpath("catalog.json").read_text("utf-8"))
27
+ entries = _entries(payload)
28
+ operation_payloads = {
29
+ entry: json.loads(package_root.joinpath("operations", f"{entry}.json").read_text("utf-8"))
30
+ for entry in entries
31
+ }
32
+ actual = sorted(_resource_entries(package_root.joinpath("operations")))
33
+ else:
34
+ payload = json.loads((root / "catalog.json").read_text("utf-8"))
35
+ entries = _entries(payload)
36
+ operation_payloads = {
37
+ entry: json.loads((root / "operations" / f"{entry}.json").read_text("utf-8"))
38
+ for entry in entries
39
+ }
40
+ actual = sorted(
41
+ path.relative_to(root / "operations").as_posix()[:-5]
42
+ for path in (root / "operations").rglob("*.json")
43
+ )
44
+ if sorted(entries) != actual:
45
+ raise _catalog_error("catalog 登记项与 operation 文件不一致")
46
+ operations = tuple(_build_operation(entry, operation_payloads[entry]) for entry in entries)
47
+ if len({item.id for item in operations}) != len(operations):
48
+ raise _catalog_error("operation id 重复")
49
+ return AgentCatalog(
50
+ catalog_version=payload["catalog_version"],
51
+ source=freeze(payload.get("source", {})),
52
+ operations=operations,
53
+ by_id=MappingProxyType({item.id: item for item in operations}),
54
+ )
55
+ except TacoError:
56
+ raise
57
+ except (OSError, ValueError, TypeError, KeyError) as error:
58
+ raise _catalog_error(str(error)) from error
59
+
60
+
61
+ def operation_not_found(operation_id: str) -> TacoError:
62
+ return TacoError(
63
+ code="AGENT_OPENAPI_OPERATION_NOT_FOUND",
64
+ message=f"未知 Agent OpenAPI operation:{operation_id}",
65
+ exit_code=2,
66
+ hint="请执行 taco agent operation search 或 list 查找候选接口。",
67
+ )
68
+
69
+
70
+ def _entries(payload: Any) -> list[str]:
71
+ if not isinstance(payload, dict):
72
+ raise _catalog_error("catalog 必须是对象")
73
+ version = payload.get("catalog_version")
74
+ if not isinstance(version, str) or version.split(".", 1)[0] != "1":
75
+ raise _catalog_error("不支持的 catalog_version")
76
+ if payload.get("json_schema_dialect") != "https://json-schema.org/draft/2020-12/schema":
77
+ raise _catalog_error("不支持的 JSON Schema 方言")
78
+ entries = payload.get("operations")
79
+ if not isinstance(entries, list) or any(not isinstance(item, str) for item in entries):
80
+ raise _catalog_error("operations 必须是字符串数组")
81
+ if len(entries) != len(set(entries)):
82
+ raise _catalog_error("catalog operation 重复")
83
+ for entry in entries:
84
+ path = PurePosixPath(entry)
85
+ if path.is_absolute() or len(path.parts) != 2 or not all(_ID_PATTERN.fullmatch(part) for part in path.parts):
86
+ raise _catalog_error(f"非法 operation 路径:{entry}")
87
+ return entries
88
+
89
+
90
+ def _resource_entries(root: Any, prefix: str = "") -> list[str]:
91
+ result: list[str] = []
92
+ for child in root.iterdir() if root.is_dir() else ():
93
+ relative = f"{prefix}/{child.name}" if prefix else child.name
94
+ if child.is_dir():
95
+ result.extend(_resource_entries(child, relative))
96
+ elif child.name.endswith(".json"):
97
+ result.append(relative[:-5])
98
+ return result
99
+
100
+
101
+ def _build_operation(entry: str, payload: Any) -> AgentOperation:
102
+ if not isinstance(payload, dict) or str(payload.get("schema_version", "")).split(".", 1)[0] != "1":
103
+ raise _catalog_error(f"{entry} schema 不受支持")
104
+ group, filename = entry.split("/")
105
+ if payload.get("id") != filename or payload.get("group") != group:
106
+ raise _catalog_error(f"{entry} 的 id/group 与文件名不一致")
107
+ for field in ("title", "description"):
108
+ if not isinstance(payload.get(field), str) or not payload[field].strip():
109
+ raise _catalog_error(f"{entry} 缺少 {field}")
110
+ keywords = payload.get("keywords")
111
+ source = payload.get("source")
112
+ if not isinstance(keywords, list) or any(not isinstance(item, str) for item in keywords):
113
+ raise _catalog_error(f"{entry} keywords 不合法")
114
+ if not isinstance(source, dict) or not all(isinstance(source.get(key), str) and source[key] for key in ("url", "anchor", "snapshot_date")):
115
+ raise _catalog_error(f"{entry} source 不完整")
116
+ http = payload.get("http")
117
+ if not isinstance(http, dict) or http.get("method") != "POST" or not _safe_path(http.get("path")):
118
+ raise _catalog_error(f"{entry} HTTP method/path 不合法")
119
+ query_schema = http.get("query_schema")
120
+ body_schema = http.get("body_schema")
121
+ for name, schema in (("query", query_schema), ("body", body_schema)):
122
+ if not isinstance(schema, dict) or schema.get("type") != "object" or schema.get("additionalProperties") is not False:
123
+ raise _catalog_error(f"{entry} {name}_schema 不合法")
124
+ Draft202012Validator.check_schema(schema)
125
+ _validate_descriptions(schema, request=True, root=True)
126
+ fields = set(query_schema.get("properties", {})) | set(body_schema.get("properties", {}))
127
+ if set(query_schema.get("properties", {})) & set(body_schema.get("properties", {})):
128
+ raise _catalog_error(f"{entry} query/body 参数重名")
129
+ if any(field.lower() in _RESERVED for field in fields):
130
+ raise _catalog_error(f"{entry} 声明了保留认证参数")
131
+ response = payload.get("response")
132
+ if not isinstance(response, dict) or not isinstance(response.get("schema"), dict):
133
+ raise _catalog_error(f"{entry} response 不合法")
134
+ Draft202012Validator.check_schema(response["schema"])
135
+ _validate_descriptions(response["schema"], request=False, root=True)
136
+ risk = payload.get("risk")
137
+ if risk != {"level": "read", "requires_confirmation": False, "target_fields": []}:
138
+ raise _catalog_error(f"{entry} 只允许只读风险")
139
+ if payload.get("execution") != {"kind": "http"}:
140
+ raise _catalog_error(f"{entry} execution 不合法")
141
+ examples = payload.get("examples")
142
+ if not isinstance(examples, list) or any(not isinstance(item, dict) for item in examples):
143
+ raise _catalog_error(f"{entry} examples 不合法")
144
+ allowed = fields
145
+ for example in examples:
146
+ params = example.get("params")
147
+ if not isinstance(example.get("title"), str) or not isinstance(params, dict) or set(params) - allowed:
148
+ raise _catalog_error(f"{entry} example 含未知参数")
149
+ query = {key: value for key, value in params.items() if key in query_schema.get("properties", {})}
150
+ body = {key: value for key, value in params.items() if key in body_schema.get("properties", {})}
151
+ if next(Draft202012Validator(query_schema).iter_errors(query), None) or next(Draft202012Validator(body_schema).iter_errors(body), None):
152
+ raise _catalog_error(f"{entry} example 未通过 schema")
153
+ return AgentOperation(
154
+ id=filename,
155
+ title=payload["title"],
156
+ description=payload["description"],
157
+ group=group,
158
+ keywords=tuple(keywords),
159
+ source=freeze(source),
160
+ http=freeze(http),
161
+ response=freeze(response),
162
+ risk=freeze(risk),
163
+ execution=freeze(payload["execution"]),
164
+ examples=tuple(freeze(examples)),
165
+ )
166
+
167
+
168
+ def _safe_path(value: Any) -> bool:
169
+ if not isinstance(value, str) or not value.startswith("/agent/v1/data/"):
170
+ return False
171
+ parsed = urlsplit(value)
172
+ return not parsed.scheme and not parsed.netloc and not parsed.query and not parsed.fragment and ".." not in PurePosixPath(parsed.path).parts
173
+
174
+
175
+ def _validate_descriptions(schema: dict[str, Any], *, request: bool, root: bool = False) -> None:
176
+ if not root:
177
+ required = ("title", "description") if request else ("description",)
178
+ if not all(isinstance(schema.get(field), str) and schema[field].strip() for field in required):
179
+ raise _catalog_error("schema 字段缺少说明")
180
+ if not _ZH_PATTERN.search(schema["description"]):
181
+ raise _catalog_error("schema description 必须包含中文")
182
+ if "enum" in schema:
183
+ descriptions = schema.get("x-enum-descriptions")
184
+ if not isinstance(descriptions, dict) or set(map(str, schema["enum"])) != set(descriptions):
185
+ raise _catalog_error("enum 缺少完整说明")
186
+ for child in schema.get("properties", {}).values():
187
+ _validate_descriptions(child, request=request)
188
+ if isinstance(schema.get("items"), dict):
189
+ _validate_descriptions(schema["items"], request=request)
190
+
191
+
192
+ def _catalog_error(detail: str) -> TacoError:
193
+ return TacoError(
194
+ code="AGENT_OPENAPI_CATALOG_INVALID",
195
+ message="内置 Agent OpenAPI operation catalog 不合法。",
196
+ exit_code=2,
197
+ hint=detail,
198
+ )
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from types import MappingProxyType
5
+ from typing import Any, Mapping
6
+
7
+
8
+ JsonMapping = Mapping[str, Any]
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class AgentOperation:
13
+ id: str
14
+ title: str
15
+ description: str
16
+ group: str
17
+ keywords: tuple[str, ...]
18
+ source: JsonMapping
19
+ http: JsonMapping
20
+ response: JsonMapping
21
+ risk: JsonMapping
22
+ execution: JsonMapping
23
+ examples: tuple[JsonMapping, ...]
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class AgentCatalog:
28
+ catalog_version: str
29
+ source: JsonMapping
30
+ operations: tuple[AgentOperation, ...]
31
+ by_id: Mapping[str, AgentOperation]
32
+
33
+
34
+ def freeze(value: Any) -> Any:
35
+ if isinstance(value, dict):
36
+ return MappingProxyType({key: freeze(item) for key, item in value.items()})
37
+ if isinstance(value, list):
38
+ return tuple(freeze(item) for item in value)
39
+ return value
40
+
41
+
42
+ def thaw(value: Any) -> Any:
43
+ if isinstance(value, Mapping):
44
+ return {key: thaw(item) for key, item in value.items()}
45
+ if isinstance(value, tuple):
46
+ return [thaw(item) for item in value]
47
+ return value
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any, Mapping
6
+
7
+ from jsonschema import Draft202012Validator
8
+
9
+ from taco.agent_openapi.models import AgentOperation, thaw
10
+ from taco.core.errors import TacoError
11
+
12
+
13
+ _RESERVED = {"accesskeyid", "expires", "timestamp", "signature", "authorization"}
14
+ _MAX_QUERY_SPAN_SECONDS = 90 * 24 * 60 * 60
15
+
16
+
17
+ def parse_parameters(
18
+ operation: AgentOperation,
19
+ *,
20
+ params: list[str] | None = None,
21
+ params_file: Path | None = None,
22
+ body_file: Path | None = None,
23
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
24
+ query_schema = operation.http["query_schema"]
25
+ body_schema = operation.http["body_schema"]
26
+ query_fields = query_schema.get("properties", {})
27
+ body_fields = body_schema.get("properties", {})
28
+ values: dict[str, Any] = {}
29
+ for expression in params or []:
30
+ key, separator, raw = expression.partition("=")
31
+ if not separator or not key:
32
+ raise _parameter_error("--param 必须使用 key=value 格式。")
33
+ if key in values:
34
+ raise _parameter_error(f"参数重复:/{key}")
35
+ schema = query_fields.get(key) or body_fields.get(key)
36
+ if schema is None:
37
+ raise _parameter_error(f"未知参数:/{key}")
38
+ values[key] = _parse_value(raw, schema, key)
39
+ if params_file is not None:
40
+ file_values = _read_object(params_file)
41
+ conflicts = set(values) & set(file_values)
42
+ if conflicts:
43
+ raise _parameter_error(f"参数重复:/{sorted(conflicts)[0]}")
44
+ values.update(file_values)
45
+ if any(key.lower() in _RESERVED for key in values):
46
+ raise _parameter_error("不能传入公共签名或认证参数。")
47
+ unknown = set(values) - set(query_fields) - set(body_fields)
48
+ if unknown:
49
+ raise _parameter_error(f"未知参数:/{sorted(unknown)[0]}")
50
+ query = {key: value for key, value in values.items() if key in query_fields}
51
+ body = {key: value for key, value in values.items() if key in body_fields}
52
+ if body_file is not None:
53
+ if body:
54
+ raise _parameter_error("--body-file 不能与映射到 body 的 --param 同时使用。")
55
+ body = _read_object(body_file)
56
+ _validate(query_schema, query)
57
+ _validate(body_schema, body)
58
+ _validate_conversation_range(operation, body)
59
+ return query, body
60
+
61
+
62
+ def _parse_value(raw: str, schema: Mapping[str, Any], key: str) -> Any:
63
+ expected = schema.get("type")
64
+ try:
65
+ if expected == "string":
66
+ return raw
67
+ if expected == "integer":
68
+ return int(raw, 10)
69
+ if expected == "number":
70
+ return float(raw)
71
+ if expected == "boolean":
72
+ if raw.casefold() in {"true", "1"}:
73
+ return True
74
+ if raw.casefold() in {"false", "0"}:
75
+ return False
76
+ raise ValueError
77
+ if expected in {"array", "object"}:
78
+ return json.loads(raw)
79
+ except (ValueError, json.JSONDecodeError):
80
+ raise _parameter_error(f"参数 /{key} 类型错误,期望 {expected}。") from None
81
+ raise _parameter_error(f"参数 /{key} 使用了不支持的类型。")
82
+
83
+
84
+ def _read_object(path: Path) -> dict[str, Any]:
85
+ try:
86
+ payload = json.loads(path.read_text("utf-8"))
87
+ except (OSError, UnicodeError, json.JSONDecodeError):
88
+ raise _parameter_error(f"参数文件不是合法 UTF-8 JSON 对象:{path}") from None
89
+ if not isinstance(payload, dict):
90
+ raise _parameter_error(f"参数文件必须是 JSON 对象:{path}")
91
+ return payload
92
+
93
+
94
+ def _validate(schema: Mapping[str, Any], value: Any) -> None:
95
+ error = next(Draft202012Validator(thaw(schema)).iter_errors(value), None)
96
+ if error is not None:
97
+ pointer = "/" + "/".join(str(part) for part in error.absolute_path)
98
+ raise _parameter_error(f"参数 {pointer or '/'} 校验失败,期望 {error.validator}。")
99
+
100
+
101
+ def _validate_conversation_range(operation: AgentOperation, body: dict[str, Any]) -> None:
102
+ if operation.id != "list-conversations":
103
+ return
104
+ start = body["created_at_start"]
105
+ end = body["created_at_end"]
106
+ if end < start or end - start > _MAX_QUERY_SPAN_SECONDS:
107
+ raise _parameter_error("会话创建时间范围必须正序且不能超过 90 天。")
108
+
109
+
110
+ def _parameter_error(message: str) -> TacoError:
111
+ return TacoError(code="AGENT_OPENAPI_PARAMETER_INVALID", message=message, exit_code=2)
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+ from jsonschema import Draft202012Validator
7
+
8
+ from taco.agent_openapi.catalog import load_catalog, operation_not_found
9
+ from taco.agent_openapi.models import AgentOperation, thaw
10
+ from taco.core.config_store import ConfigStore
11
+ from taco.core.developer_center_client import DeveloperCenterClient
12
+ from taco.core.errors import TacoError
13
+ from taco.core.profile_resolver import ProfileResolver
14
+
15
+
16
+ class AgentOpenApiService:
17
+ def __init__(
18
+ self,
19
+ store: ConfigStore | None = None,
20
+ *,
21
+ profile_resolver: ProfileResolver | None = None,
22
+ developer_http_client: httpx.Client | None = None,
23
+ ) -> None:
24
+ self.store = store or ConfigStore()
25
+ self.profile_resolver = profile_resolver or ProfileResolver(self.store)
26
+ self.catalog = load_catalog()
27
+ self.developer_http_client = developer_http_client
28
+
29
+ def call(
30
+ self,
31
+ operation_id: str,
32
+ *,
33
+ query: dict[str, Any],
34
+ body: dict[str, Any],
35
+ profile: str | None = None,
36
+ ) -> dict[str, Any]:
37
+ operation = self.catalog.by_id.get(operation_id)
38
+ if operation is None:
39
+ raise operation_not_found(operation_id)
40
+ resolved = self.profile_resolver.resolve(profile)
41
+ client = DeveloperCenterClient(
42
+ endpoint=resolved.endpoint,
43
+ access_key_id=resolved.access_key_id,
44
+ access_key_secret=resolved.access_key_secret,
45
+ http_client=self.developer_http_client,
46
+ )
47
+ try:
48
+ payload = client.request_json(
49
+ operation.http["method"],
50
+ operation.http["path"],
51
+ query=query or None,
52
+ json_body=body or None,
53
+ )
54
+ return validate_response(operation, payload)
55
+ finally:
56
+ client.close()
57
+
58
+
59
+ def validate_response(operation: AgentOperation, payload: Any) -> dict[str, Any]:
60
+ request_id = payload.get("requestId") if isinstance(payload, dict) and isinstance(payload.get("requestId"), str) else None
61
+ error = next(Draft202012Validator(thaw(operation.response["schema"])).iter_errors(payload), None)
62
+ if error is not None:
63
+ pointer = "/" + "/".join(str(part) for part in error.absolute_path)
64
+ raise TacoError(
65
+ code="AGENT_OPENAPI_RESPONSE_INVALID",
66
+ message=f"Agent OpenAPI 响应 {pointer or '/'} 校验失败,期望 {error.validator}。",
67
+ exit_code=4,
68
+ request_id=request_id,
69
+ path=operation.http["path"],
70
+ )
71
+ business = dict(payload)
72
+ business.pop("requestId", None)
73
+ return {"request_id": request_id, "data": business}
taco/aikb/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from taco.aikb.catalog import load_catalog
2
+ from taco.aikb.models import AikbCatalog, AikbOperation
3
+
4
+ __all__ = ["AikbCatalog", "AikbOperation", "load_catalog"]