@agentlayer.tech/wallet 0.1.101 → 0.1.102

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 (81) hide show
  1. package/.openclaw/extensions/agent-wallet/dist/index.js +46 -1
  2. package/.openclaw/extensions/agent-wallet/index.ts +46 -1
  3. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -1
  4. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  5. package/README.md +18 -0
  6. package/VERSION +1 -1
  7. package/agent-wallet/agent_wallet/__init__.py +1 -1
  8. package/agent-wallet/agent_wallet/connector_cli.py +198 -0
  9. package/agent-wallet/agent_wallet/connectors/__init__.py +28 -0
  10. package/agent-wallet/agent_wallet/connectors/catalog.py +68 -0
  11. package/agent-wallet/agent_wallet/connectors/client.py +308 -0
  12. package/agent-wallet/agent_wallet/connectors/intent_policy.py +243 -0
  13. package/agent-wallet/agent_wallet/connectors/manifest.py +192 -0
  14. package/agent-wallet/agent_wallet/connectors/registry.py +341 -0
  15. package/agent-wallet/agent_wallet/openclaw_adapter.py +56 -5
  16. package/agent-wallet/openclaw.plugin.json +1 -1
  17. package/agent-wallet/pyproject.toml +4 -2
  18. package/agent-wallet/scripts/build_release_bundle.py +1 -0
  19. package/agent-wallet/scripts/install_agent_wallet.py +1 -1
  20. package/bin/openclaw-agent-wallet.mjs +38 -0
  21. package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
  22. package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
  23. package/codex/plugins/agent-wallet/server.py +4 -1
  24. package/connectors/AGENTS.md +37 -0
  25. package/connectors/DEVELOPER_GUIDE.md +131 -0
  26. package/connectors/README.md +139 -0
  27. package/connectors/READ_ONLY_BETA.md +54 -0
  28. package/connectors/RELEASING.md +48 -0
  29. package/connectors/conformance/README.md +31 -0
  30. package/connectors/conformance/package.json +54 -0
  31. package/connectors/conformance/scripts/sync-license.mjs +3 -0
  32. package/connectors/conformance/scripts/sync-spec.mjs +16 -0
  33. package/connectors/conformance/src/cli.ts +43 -0
  34. package/connectors/conformance/src/index.ts +7 -0
  35. package/connectors/conformance/src/runner.ts +314 -0
  36. package/connectors/conformance/src/types.ts +27 -0
  37. package/connectors/conformance/test/fixtures/invalid-response-identity.json +14 -0
  38. package/connectors/conformance/test/fixtures/missing-tool-fixture.json +4 -0
  39. package/connectors/conformance/test/runner.test.mjs +151 -0
  40. package/connectors/conformance/tsconfig.json +17 -0
  41. package/connectors/examples/reference-crypto-data/Dockerfile +21 -0
  42. package/connectors/examples/reference-crypto-data/README.md +51 -0
  43. package/connectors/examples/reference-crypto-data/conformance.json +11 -0
  44. package/connectors/examples/reference-crypto-data/connector.json +95 -0
  45. package/connectors/examples/reference-crypto-data/package.json +21 -0
  46. package/connectors/examples/reference-crypto-data/railway.toml +9 -0
  47. package/connectors/examples/reference-crypto-data/src/connector.ts +137 -0
  48. package/connectors/examples/reference-crypto-data/src/server.ts +13 -0
  49. package/connectors/examples/reference-crypto-data/test/connector.test.mjs +97 -0
  50. package/connectors/examples/reference-crypto-data/tsconfig.json +17 -0
  51. package/connectors/package-lock.json +561 -0
  52. package/connectors/package.json +19 -0
  53. package/connectors/scripts/smoke-packed-packages.mjs +121 -0
  54. package/connectors/sdk-typescript/README.md +47 -0
  55. package/connectors/sdk-typescript/package.json +49 -0
  56. package/connectors/sdk-typescript/scripts/sync-license.mjs +3 -0
  57. package/connectors/sdk-typescript/src/connector.ts +204 -0
  58. package/connectors/sdk-typescript/src/errors.ts +11 -0
  59. package/connectors/sdk-typescript/src/http.ts +116 -0
  60. package/connectors/sdk-typescript/src/index.ts +26 -0
  61. package/connectors/sdk-typescript/src/types.ts +101 -0
  62. package/connectors/sdk-typescript/test/sdk.test.mjs +181 -0
  63. package/connectors/sdk-typescript/tsconfig.json +21 -0
  64. package/connectors/spec/connector-manifest.schema.json +340 -0
  65. package/connectors/spec/connector-protocol.md +159 -0
  66. package/connectors/spec/transaction-intent.schema.json +281 -0
  67. package/connectors/templates/read-only/.env.example +1 -0
  68. package/connectors/templates/read-only/Dockerfile +11 -0
  69. package/connectors/templates/read-only/README.md +33 -0
  70. package/connectors/templates/read-only/conformance.json +11 -0
  71. package/connectors/templates/read-only/connector.json +64 -0
  72. package/connectors/templates/read-only/package.json +21 -0
  73. package/connectors/templates/read-only/railway.toml +9 -0
  74. package/connectors/templates/read-only/src/connector.ts +61 -0
  75. package/connectors/templates/read-only/src/server.ts +14 -0
  76. package/connectors/templates/read-only/test/connector.test.mjs +62 -0
  77. package/connectors/templates/read-only/tsconfig.json +17 -0
  78. package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
  79. package/package.json +2 -1
  80. package/wdk-btc-wallet/package.json +1 -1
  81. package/wdk-evm-wallet/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { execFile } from "node:child_process";
1
+ import { execFile, execFileSync } from "node:child_process";
2
2
  import crypto from "node:crypto";
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
@@ -423,6 +423,50 @@ function buildCliEnv(packageRoot) {
423
423
  return env;
424
424
  }
425
425
 
426
+ function loadConnectorToolDefinitions(api) {
427
+ const config = resolvePluginConfig(api);
428
+ try {
429
+ const packageRoot = resolvePackageRoot(config);
430
+ const stdout = execFileSync(
431
+ resolvePythonBin(config),
432
+ ["-m", "agent_wallet.connector_cli", "tools"],
433
+ {
434
+ cwd: packageRoot,
435
+ env: {
436
+ ...buildCliEnv(packageRoot),
437
+ OPENCLAW_HOME: resolveOpenclawHome(config),
438
+ },
439
+ encoding: "utf8",
440
+ maxBuffer: 1024 * 1024 * 2,
441
+ timeout: 10_000,
442
+ }
443
+ );
444
+ const payload = JSON.parse(stdout);
445
+ if (payload?.ok !== true || !Array.isArray(payload?.tools)) return [];
446
+ return payload.tools
447
+ .filter(
448
+ (tool) =>
449
+ tool?.read_only === true &&
450
+ typeof tool?.name === "string" &&
451
+ tool.name.startsWith("connector__") &&
452
+ typeof tool?.description === "string" &&
453
+ tool?.input_schema &&
454
+ typeof tool.input_schema === "object"
455
+ )
456
+ .map((tool) => ({
457
+ name: tool.name,
458
+ description: tool.description,
459
+ parameters: tool.input_schema,
460
+ optional: true,
461
+ }));
462
+ } catch (error) {
463
+ api?.logger?.warn?.(
464
+ `[agent-wallet] connector tools unavailable; built-in tools remain active: ${String(error?.message || error)}`
465
+ );
466
+ return [];
467
+ }
468
+ }
469
+
426
470
  async function callWalletCli(api, command, extraArgs = [], configOverride = null) {
427
471
  const config = configOverride || resolvePluginConfig(api);
428
472
  const packageRoot = resolvePackageRoot(config);
@@ -2184,6 +2228,7 @@ export default function registerAgentWalletPlugin(api) {
2184
2228
  ...solanaToolDefinitions.filter((item) => !duplicateSessionToolNames.has(item.name)),
2185
2229
  ...btcToolDefinitions.filter((item) => !duplicateSessionToolNames.has(item.name)),
2186
2230
  ...evmToolDefinitions.filter((item) => !duplicateSessionToolNames.has(item.name)),
2231
+ ...loadConnectorToolDefinitions(api),
2187
2232
  ]) {
2188
2233
  if (seen.has(definition.name)) {
2189
2234
  continue;
@@ -1,4 +1,4 @@
1
- import { execFile } from "node:child_process";
1
+ import { execFile, execFileSync } from "node:child_process";
2
2
  import crypto from "node:crypto";
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
@@ -423,6 +423,50 @@ function buildCliEnv(packageRoot) {
423
423
  return env;
424
424
  }
425
425
 
426
+ function loadConnectorToolDefinitions(api) {
427
+ const config = resolvePluginConfig(api);
428
+ try {
429
+ const packageRoot = resolvePackageRoot(config);
430
+ const stdout = execFileSync(
431
+ resolvePythonBin(config),
432
+ ["-m", "agent_wallet.connector_cli", "tools"],
433
+ {
434
+ cwd: packageRoot,
435
+ env: {
436
+ ...buildCliEnv(packageRoot),
437
+ OPENCLAW_HOME: resolveOpenclawHome(config),
438
+ },
439
+ encoding: "utf8",
440
+ maxBuffer: 1024 * 1024 * 2,
441
+ timeout: 10_000,
442
+ }
443
+ );
444
+ const payload = JSON.parse(stdout);
445
+ if (payload?.ok !== true || !Array.isArray(payload?.tools)) return [];
446
+ return payload.tools
447
+ .filter(
448
+ (tool) =>
449
+ tool?.read_only === true &&
450
+ typeof tool?.name === "string" &&
451
+ tool.name.startsWith("connector__") &&
452
+ typeof tool?.description === "string" &&
453
+ tool?.input_schema &&
454
+ typeof tool.input_schema === "object"
455
+ )
456
+ .map((tool) => ({
457
+ name: tool.name,
458
+ description: tool.description,
459
+ parameters: tool.input_schema,
460
+ optional: true,
461
+ }));
462
+ } catch (error) {
463
+ api?.logger?.warn?.(
464
+ `[agent-wallet] connector tools unavailable; built-in tools remain active: ${String(error?.message || error)}`
465
+ );
466
+ return [];
467
+ }
468
+ }
469
+
426
470
  async function callWalletCli(api, command, extraArgs = [], configOverride = null) {
427
471
  const config = configOverride || resolvePluginConfig(api);
428
472
  const packageRoot = resolvePackageRoot(config);
@@ -2184,6 +2228,7 @@ export default function registerAgentWalletPlugin(api) {
2184
2228
  ...solanaToolDefinitions.filter((item) => !duplicateSessionToolNames.has(item.name)),
2185
2229
  ...btcToolDefinitions.filter((item) => !duplicateSessionToolNames.has(item.name)),
2186
2230
  ...evmToolDefinitions.filter((item) => !duplicateSessionToolNames.has(item.name)),
2231
+ ...loadConnectorToolDefinitions(api),
2187
2232
  ]) {
2188
2233
  if (seen.has(definition.name)) {
2189
2234
  continue;
@@ -2,7 +2,7 @@
2
2
  "id": "agent-wallet",
3
3
  "name": "Agent Wallet",
4
4
  "description": "Official OpenClaw plugin bridge for the agent-wallet backends, including Solana, local BTC, and local EVM.",
5
- "version": "0.1.101",
5
+ "version": "0.1.102",
6
6
  "contracts": {
7
7
  "tools": [
8
8
  "agentlayer_autonomous_approve",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayertech/agent-wallet-plugin",
3
- "version": "0.1.101",
3
+ "version": "0.1.102",
4
4
  "description": "OpenClaw plugin bridge for the AgentLayer wallet runtime.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN ../../../LICENSE",
package/README.md CHANGED
@@ -114,6 +114,24 @@ wallet update --yes
114
114
  wallet rollback
115
115
  ```
116
116
 
117
+ ## Optional connectors
118
+
119
+ Connectors add optional third-party crypto and DeFi read tools without moving
120
+ existing integrations such as Aave, Kamino, or Morpho out of the wallet core.
121
+
122
+ ```bash
123
+ wallet connectors inspect ./connector.json
124
+ wallet connectors install ./connector.json --enable --yes
125
+ wallet connectors list
126
+ wallet connectors disable com.example.protocol
127
+ wallet connectors doctor
128
+ ```
129
+
130
+ Enabled read-only connectors appear in OpenClaw and Codex after the host is
131
+ restarted. Write-capable connectors remain unavailable until their verified,
132
+ AgentLayer-hosted execution path is fully enabled. See
133
+ [`connectors/README.md`](connectors/README.md) for the trust model and protocol.
134
+
117
135
  ## What AgentLayer provides
118
136
 
119
137
  AgentLayer connects supported agents to one local wallet runtime. The agent can:
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.101
1
+ 0.1.102
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Keep in sync with package.json, pyproject.toml, and the npm installer version.
4
4
  # scripts/check_release_version.mjs enforces this on release.
5
- __version__ = "0.1.101"
5
+ __version__ = "0.1.102"
6
6
 
7
7
  __all__ = [
8
8
  "config",
@@ -0,0 +1,198 @@
1
+ """JSON CLI for installing and managing optional AgentLayer connectors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from agent_wallet.connectors.catalog import enabled_connector_tools
12
+ from agent_wallet.connectors.manifest import ConnectorManifestError, validate_connector_manifest
13
+ from agent_wallet.connectors.registry import ConnectorRegistry, ConnectorRegistryError
14
+
15
+
16
+ MAX_MANIFEST_BYTES = 1024 * 1024
17
+
18
+
19
+ def _print(payload: Any) -> None:
20
+ print(json.dumps(payload, indent=2, sort_keys=True))
21
+
22
+
23
+ def _load_manifest_file(value: str) -> tuple[dict[str, Any], str]:
24
+ path = Path(value).expanduser().resolve()
25
+ try:
26
+ size = path.stat().st_size
27
+ except OSError as exc:
28
+ raise ConnectorRegistryError(f"Connector manifest cannot be read: {path}: {exc}") from exc
29
+ if size > MAX_MANIFEST_BYTES:
30
+ raise ConnectorRegistryError(
31
+ f"Connector manifest exceeds the {MAX_MANIFEST_BYTES}-byte limit."
32
+ )
33
+ try:
34
+ payload = json.loads(path.read_text(encoding="utf-8"))
35
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
36
+ raise ConnectorRegistryError(f"Connector manifest is invalid JSON: {path}: {exc}") from exc
37
+ if not isinstance(payload, dict):
38
+ raise ConnectorRegistryError("Connector manifest root must be an object.")
39
+ return payload, str(path)
40
+
41
+
42
+ def _permission_review(manifest_payload: dict[str, Any]) -> dict[str, Any]:
43
+ manifest = validate_connector_manifest(manifest_payload)
44
+ permissions = manifest["permissions"]
45
+ warnings = ["Connector responses are untrusted external data."]
46
+ if permissions["wallet_address"]:
47
+ warnings.append("The connector may receive the active public wallet address.")
48
+ return {
49
+ "id": manifest["id"],
50
+ "version": manifest["version"],
51
+ "publisher": manifest["publisher"],
52
+ "trust": manifest["trust"],
53
+ "endpoint": manifest["transport"]["url"],
54
+ "permissions": permissions,
55
+ "tools": [tool["name"] for tool in manifest["tools"]],
56
+ "warnings": warnings,
57
+ }
58
+
59
+
60
+ def _build_parser() -> argparse.ArgumentParser:
61
+ parser = argparse.ArgumentParser(prog="wallet connectors")
62
+ subparsers = parser.add_subparsers(dest="command", required=True)
63
+
64
+ subparsers.add_parser("list", help="List locally installed connectors.")
65
+ subparsers.add_parser("tools", help="List enabled read-only connector tools for host registration.")
66
+
67
+ info = subparsers.add_parser("info", help="Show one installed connector.")
68
+ info.add_argument("connector_id")
69
+
70
+ inspect = subparsers.add_parser("inspect", help="Review a manifest before installation.")
71
+ inspect.add_argument("manifest", help="Path to a connector manifest JSON file.")
72
+
73
+ install = subparsers.add_parser("install", help="Install an immutable connector manifest.")
74
+ install.add_argument("manifest", help="Path to a connector manifest JSON file.")
75
+ install.add_argument("--enable", action="store_true", help="Enable this exact version.")
76
+ install.add_argument("--yes", action="store_true", help="Confirm permissions when enabling.")
77
+
78
+ enable = subparsers.add_parser("enable", help="Enable an installed connector version.")
79
+ enable.add_argument("connector_id")
80
+ enable.add_argument("--version", default=None)
81
+ enable.add_argument("--yes", action="store_true", help="Confirm the installed permissions.")
82
+
83
+ disable = subparsers.add_parser("disable", help="Disable an installed connector.")
84
+ disable.add_argument("connector_id")
85
+
86
+ remove = subparsers.add_parser("remove", help="Remove a disabled connector version.")
87
+ remove.add_argument("connector_id")
88
+ remove.add_argument("--version", default=None)
89
+ remove.add_argument("--yes", action="store_true", help="Confirm removal.")
90
+
91
+ subparsers.add_parser("doctor", help="Validate the registry and all installed manifests.")
92
+ return parser
93
+
94
+
95
+ def _doctor(registry: ConnectorRegistry) -> dict[str, Any]:
96
+ entries = registry.list()
97
+ checks: list[dict[str, Any]] = []
98
+ ok = True
99
+ for entry in entries:
100
+ connector_id = str(entry["id"])
101
+ for version in entry["installed_versions"]:
102
+ try:
103
+ registry.load_manifest(connector_id, str(version))
104
+ checks.append(
105
+ {
106
+ "connector_id": connector_id,
107
+ "version": version,
108
+ "ok": True,
109
+ }
110
+ )
111
+ except (ConnectorManifestError, ConnectorRegistryError) as exc:
112
+ ok = False
113
+ checks.append(
114
+ {
115
+ "connector_id": connector_id,
116
+ "version": version,
117
+ "ok": False,
118
+ "error": str(exc),
119
+ }
120
+ )
121
+ return {
122
+ "ok": ok,
123
+ "registry_path": str(registry.registry_path),
124
+ "connector_count": len(entries),
125
+ "checks": checks,
126
+ }
127
+
128
+
129
+ def main(argv: list[str] | None = None) -> int:
130
+ parser = _build_parser()
131
+ args = parser.parse_args(argv)
132
+ registry = ConnectorRegistry()
133
+ try:
134
+ if args.command == "list":
135
+ _print({"ok": True, "connectors": registry.list()})
136
+ return 0
137
+ if args.command == "tools":
138
+ _print({"ok": True, "tools": enabled_connector_tools(registry, include_write=False)})
139
+ return 0
140
+ if args.command == "info":
141
+ _print({"ok": True, "connector": registry.describe(args.connector_id)})
142
+ return 0
143
+ if args.command == "inspect":
144
+ manifest, source = _load_manifest_file(args.manifest)
145
+ _print({"ok": True, "source": source, "review": _permission_review(manifest)})
146
+ return 0
147
+ if args.command == "install":
148
+ manifest, source = _load_manifest_file(args.manifest)
149
+ review = _permission_review(manifest)
150
+ if args.enable and not args.yes:
151
+ raise ConnectorRegistryError(
152
+ "install --enable requires --yes after reviewing connector permissions "
153
+ "with `wallet connectors inspect <manifest>`."
154
+ )
155
+ connector = registry.install(manifest, source=source, enable=bool(args.enable))
156
+ _print({"ok": True, "connector": connector, "review": review})
157
+ return 0
158
+ if args.command == "enable":
159
+ if not args.yes:
160
+ raise ConnectorRegistryError(
161
+ "enable requires --yes after reviewing the installed connector with "
162
+ "`wallet connectors info <connector_id>`."
163
+ )
164
+ connector = registry.enable(args.connector_id, version=args.version)
165
+ _print({"ok": True, "connector": connector})
166
+ return 0
167
+ if args.command == "disable":
168
+ connector = registry.disable(args.connector_id)
169
+ _print({"ok": True, "connector": connector})
170
+ return 0
171
+ if args.command == "remove":
172
+ if not args.yes:
173
+ raise ConnectorRegistryError("remove requires --yes after the connector is disabled.")
174
+ result = registry.remove(args.connector_id, version=args.version)
175
+ _print({"ok": True, **result})
176
+ return 0
177
+ if args.command == "doctor":
178
+ result = _doctor(registry)
179
+ _print(result)
180
+ return 0 if result["ok"] else 1
181
+ raise ConnectorRegistryError(f"Unsupported connectors command: {args.command}.")
182
+ except (ConnectorManifestError, ConnectorRegistryError) as exc:
183
+ print(
184
+ json.dumps(
185
+ {
186
+ "ok": False,
187
+ "error": str(exc),
188
+ "error_type": type(exc).__name__,
189
+ },
190
+ sort_keys=True,
191
+ ),
192
+ file=sys.stderr,
193
+ )
194
+ return 2
195
+
196
+
197
+ if __name__ == "__main__":
198
+ raise SystemExit(main())
@@ -0,0 +1,28 @@
1
+ """Optional AgentLayer connector contracts and local registry support."""
2
+
3
+ from agent_wallet.connectors.catalog import enabled_connector_tools, resolve_connector_tool
4
+ from agent_wallet.connectors.client import ConnectorInvocationError, ConnectorReadClient
5
+ from agent_wallet.connectors.intent_policy import (
6
+ ConnectorIntentPolicyError,
7
+ validate_evm_transaction_intent,
8
+ )
9
+ from agent_wallet.connectors.manifest import (
10
+ ConnectorManifestError,
11
+ connector_tool_name,
12
+ validate_connector_manifest,
13
+ )
14
+ from agent_wallet.connectors.registry import ConnectorRegistry, ConnectorRegistryError
15
+
16
+ __all__ = [
17
+ "ConnectorManifestError",
18
+ "ConnectorInvocationError",
19
+ "ConnectorIntentPolicyError",
20
+ "ConnectorReadClient",
21
+ "ConnectorRegistry",
22
+ "ConnectorRegistryError",
23
+ "connector_tool_name",
24
+ "enabled_connector_tools",
25
+ "resolve_connector_tool",
26
+ "validate_connector_manifest",
27
+ "validate_evm_transaction_intent",
28
+ ]
@@ -0,0 +1,68 @@
1
+ """Build the safe host-facing catalog of enabled connector tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from agent_wallet.connectors.manifest import connector_tool_name
8
+ from agent_wallet.connectors.registry import ConnectorRegistry, ConnectorRegistryError
9
+
10
+
11
+ def enabled_connector_tools(
12
+ registry: ConnectorRegistry | None = None,
13
+ *,
14
+ include_write: bool = False,
15
+ ) -> list[dict[str, Any]]:
16
+ """Return enabled tools, withholding writes until their policy is available."""
17
+
18
+ active_registry = registry or ConnectorRegistry()
19
+ result: list[dict[str, Any]] = []
20
+ seen: set[str] = set()
21
+ for entry in active_registry.list():
22
+ if not entry["enabled"]:
23
+ continue
24
+ connector_id = str(entry["id"])
25
+ version = str(entry["enabled_version"])
26
+ manifest = active_registry.load_manifest(connector_id, version)
27
+ for raw_tool in manifest["tools"]:
28
+ if not isinstance(raw_tool, dict):
29
+ raise ConnectorRegistryError(f"Connector tool is invalid: {connector_id}.")
30
+ read_only = raw_tool.get("read_only") is True
31
+ if not read_only and not include_write:
32
+ continue
33
+ host_name = connector_tool_name(connector_id, str(raw_tool.get("name") or ""))
34
+ if host_name in seen:
35
+ raise ConnectorRegistryError(f"Duplicate host connector tool name: {host_name}.")
36
+ seen.add(host_name)
37
+ result.append(
38
+ {
39
+ "name": host_name,
40
+ "description": (
41
+ "[External connector: untrusted read-only data] "
42
+ + str(raw_tool["description"])
43
+ ),
44
+ "input_schema": raw_tool["input_schema"],
45
+ "output_schema": raw_tool["output_schema"],
46
+ "read_only": read_only,
47
+ "risk_level": str(raw_tool["risk_level"]),
48
+ "connector_id": connector_id,
49
+ "connector_version": version,
50
+ "connector_tool": str(raw_tool["name"]),
51
+ "connector_trust": str(manifest["trust"]),
52
+ }
53
+ )
54
+ return sorted(result, key=lambda item: str(item["name"]))
55
+
56
+
57
+ def resolve_connector_tool(
58
+ host_tool_name: str,
59
+ registry: ConnectorRegistry | None = None,
60
+ *,
61
+ include_write: bool = False,
62
+ ) -> dict[str, Any]:
63
+ """Resolve one host tool through the enabled immutable catalog."""
64
+
65
+ for tool in enabled_connector_tools(registry, include_write=include_write):
66
+ if tool["name"] == host_tool_name:
67
+ return tool
68
+ raise ConnectorRegistryError(f"Enabled connector tool was not found: {host_tool_name}.")