@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
@@ -0,0 +1,121 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import {
4
+ mkdirSync,
5
+ mkdtempSync,
6
+ readFileSync,
7
+ readdirSync,
8
+ rmSync,
9
+ writeFileSync,
10
+ } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { dirname, join, resolve } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ const workspaceRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
16
+ const tempRoot = mkdtempSync(join(tmpdir(), "agentlayer-connector-pack-"));
17
+
18
+ function run(command, args, cwd = tempRoot) {
19
+ return execFileSync(command, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
20
+ }
21
+
22
+ function sha256(path) {
23
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
24
+ }
25
+
26
+ try {
27
+ run("npm", ["pack", join(workspaceRoot, "sdk-typescript"), "--pack-destination", tempRoot]);
28
+ run("npm", ["pack", join(workspaceRoot, "conformance"), "--pack-destination", tempRoot]);
29
+ const archives = readdirSync(tempRoot).filter((name) => name.endsWith(".tgz")).sort();
30
+ if (archives.length !== 2) throw new Error(`Expected two package archives, found ${archives.length}.`);
31
+
32
+ const repeatRoot = join(tempRoot, "repeat");
33
+ mkdirSync(repeatRoot);
34
+ run("npm", ["pack", join(workspaceRoot, "sdk-typescript"), "--pack-destination", repeatRoot]);
35
+ run("npm", ["pack", join(workspaceRoot, "conformance"), "--pack-destination", repeatRoot]);
36
+ for (const archive of archives) {
37
+ if (sha256(join(tempRoot, archive)) !== sha256(join(repeatRoot, archive))) {
38
+ throw new Error(`${archive} is not reproducible across consecutive npm pack runs.`);
39
+ }
40
+ }
41
+
42
+ for (const archive of archives) {
43
+ const listing = run("tar", ["-tzf", join(tempRoot, archive)]);
44
+ for (const required of ["package/package.json", "package/README.md", "package/LICENSE"]) {
45
+ if (!listing.split("\n").includes(required)) {
46
+ throw new Error(`${archive} is missing ${required}.`);
47
+ }
48
+ }
49
+ if (!listing.split("\n").some((entry) => entry.startsWith("package/dist/") && entry.endsWith(".js"))) {
50
+ throw new Error(`${archive} is missing compiled JavaScript.`);
51
+ }
52
+ }
53
+
54
+ run("npm", ["init", "--yes"], tempRoot);
55
+ const packageJsonPath = join(tempRoot, "package.json");
56
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
57
+ writeFileSync(packageJsonPath, `${JSON.stringify({ ...packageJson, type: "module" }, null, 2)}\n`);
58
+ run(
59
+ "npm",
60
+ [
61
+ "install",
62
+ "--ignore-scripts",
63
+ ...archives.map((archive) => join(tempRoot, archive)),
64
+ "typescript@7.0.2",
65
+ "@types/node@24.13.3",
66
+ ],
67
+ tempRoot
68
+ );
69
+ writeFileSync(
70
+ join(tempRoot, "consumer.ts"),
71
+ `import { defineReadOnlyConnector } from "@agentlayer.tech/connector-sdk";
72
+ import { assertFixture } from "@agentlayer.tech/connector-conformance";
73
+
74
+ assertFixture({ schema_version: 1, tools: { ping: { arguments: {} } } });
75
+ const connector = defineReadOnlyConnector({
76
+ manifest: {
77
+ schema_version: 1,
78
+ id: "com.example.external",
79
+ name: "External Consumer",
80
+ version: "1.0.0",
81
+ publisher: { id: "example", name: "Example" },
82
+ agentlayer: { protocol_version: 1, runtime_range: ">=0.1.101" },
83
+ trust: "community_read_only",
84
+ transport: { type: "https", url: "https://connector.example.com" },
85
+ permissions: { wallet_address: false, transaction_intents: false, network_hosts: [] },
86
+ tools: [{
87
+ name: "ping",
88
+ description: "Return public health data.",
89
+ read_only: true,
90
+ risk_level: "low",
91
+ input_schema: { type: "object" },
92
+ output_schema: { type: "object" },
93
+ }],
94
+ },
95
+ handlers: { ping: () => ({ ok: true }) },
96
+ });
97
+ if (connector.manifest.id !== "com.example.external") throw new Error("SDK import failed");
98
+ `,
99
+ "utf8"
100
+ );
101
+ writeFileSync(
102
+ join(tempRoot, "tsconfig.json"),
103
+ `${JSON.stringify({
104
+ compilerOptions: {
105
+ module: "NodeNext",
106
+ moduleResolution: "NodeNext",
107
+ target: "ES2022",
108
+ strict: true,
109
+ types: ["node"],
110
+ outDir: "dist",
111
+ },
112
+ include: ["consumer.ts"],
113
+ }, null, 2)}\n`,
114
+ "utf8"
115
+ );
116
+ run(join(tempRoot, "node_modules", ".bin", "tsc"), ["-p", "tsconfig.json"]);
117
+ run(process.execPath, [join(tempRoot, "dist", "consumer.js")]);
118
+ console.log("smoke-packed-packages: ok");
119
+ } finally {
120
+ rmSync(tempRoot, { recursive: true, force: true });
121
+ }
@@ -0,0 +1,47 @@
1
+ # AgentLayer Connector SDK
2
+
3
+ TypeScript types and runtime helpers for AgentLayer Connector Protocol v1.
4
+
5
+ The initial SDK supports read-only connectors only. It does not expose wallet
6
+ signing, transaction execution, approval tokens, or private wallet material.
7
+
8
+ ```bash
9
+ npm install @agentlayer.tech/connector-sdk@beta
10
+ ```
11
+
12
+ Node.js 24 or newer is required.
13
+
14
+ ## Example
15
+
16
+ ```ts
17
+ import { readFileSync } from "node:fs";
18
+ import {
19
+ defineReadOnlyConnector,
20
+ startConnectorServer,
21
+ type ConnectorManifest,
22
+ } from "@agentlayer.tech/connector-sdk";
23
+
24
+ const manifest = JSON.parse(
25
+ readFileSync(new URL("./connector.json", import.meta.url), "utf8")
26
+ ) as ConnectorManifest;
27
+
28
+ const connector = defineReadOnlyConnector({
29
+ manifest,
30
+ handlers: {
31
+ async get_pools(arguments_, context) {
32
+ return { pools: [] };
33
+ },
34
+ },
35
+ });
36
+
37
+ await startConnectorServer(connector);
38
+ ```
39
+
40
+ `defineReadOnlyConnector` rejects write-capable manifests, undeclared handlers,
41
+ identity drift, invalid arguments, invalid handler output, non-JSON values, and
42
+ response TTLs longer than five minutes. `startConnectorServer` exposes only
43
+ `GET /healthz` and `POST /invoke`, limits request bodies, and returns generic
44
+ errors for unexpected failures.
45
+
46
+ Use the complete starter in `../templates/read-only` rather than beginning from
47
+ the abbreviated snippet above.
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@agentlayer.tech/connector-sdk",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "TypeScript SDK for building AgentLayer read-only connectors.",
5
+ "type": "module",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/lopushok9/Agent-Layer.git",
10
+ "directory": "connectors/sdk-typescript"
11
+ },
12
+ "homepage": "https://github.com/lopushok9/Agent-Layer/tree/main/connectors/sdk-typescript#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/lopushok9/Agent-Layer/issues"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "sideEffects": false,
20
+ "engines": {
21
+ "node": ">=24"
22
+ },
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ }
28
+ },
29
+ "types": "./dist/index.d.ts",
30
+ "files": [
31
+ "dist/",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "scripts": {
36
+ "sync-license": "node scripts/sync-license.mjs",
37
+ "build": "tsc -p tsconfig.json",
38
+ "check": "npm run build && node --test test/*.test.mjs",
39
+ "prepack": "npm run sync-license && npm run build"
40
+ },
41
+ "dependencies": {
42
+ "ajv": "^8.20.0",
43
+ "ajv-formats": "^3.0.1"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^24.0.0",
47
+ "typescript": "^7.0.2"
48
+ }
49
+ }
@@ -0,0 +1,3 @@
1
+ import { copyFileSync } from "node:fs";
2
+
3
+ copyFileSync(new URL("../../../LICENSE", import.meta.url), new URL("../LICENSE", import.meta.url));
@@ -0,0 +1,204 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ import { Ajv, type ValidateFunction } from "ajv";
4
+ import type { FormatsPlugin } from "ajv-formats";
5
+
6
+ import { ConnectorSdkError } from "./errors.js";
7
+ import type {
8
+ ConnectorInvocationRequest,
9
+ ConnectorManifest,
10
+ ConnectorReadResponse,
11
+ ConnectorToolManifest,
12
+ JsonValue,
13
+ ReadOnlyConnector,
14
+ ReadOnlyConnectorDefinition,
15
+ } from "./types.js";
16
+
17
+ const TOOL_NAME_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
18
+ const MAX_RESPONSE_BYTES = 1024 * 1024;
19
+ const MAX_RESPONSE_TTL_SECONDS = 300;
20
+ const RESERVED_WRITE_RESULT_KEYS = new Set([
21
+ "approval_token",
22
+ "broadcast_request",
23
+ "evm_transaction_intent",
24
+ "payment_intent",
25
+ "raw_transaction",
26
+ "signed_transaction",
27
+ "signing_request",
28
+ "solana_transaction_intent",
29
+ "transaction_intent",
30
+ ]);
31
+ const READ_ONLY_TRUST = new Set([
32
+ "community_read_only",
33
+ "verified_read_only",
34
+ "local_development",
35
+ ]);
36
+ const require = createRequire(import.meta.url);
37
+ const addFormats = require("ajv-formats") as FormatsPlugin;
38
+
39
+ interface CompiledTool {
40
+ manifest: ConnectorToolManifest;
41
+ validateInput: ValidateFunction;
42
+ validateOutput: ValidateFunction;
43
+ }
44
+
45
+ function fail(code: string, message: string, statusCode = 400): never {
46
+ throw new ConnectorSdkError(code, message, statusCode);
47
+ }
48
+
49
+ function assertJsonValue(value: unknown, path = "result"): asserts value is JsonValue {
50
+ if (value === null || ["string", "number", "boolean"].includes(typeof value)) {
51
+ if (typeof value === "number" && !Number.isFinite(value)) {
52
+ fail("invalid_json", `${path} contains a non-finite number.`, 500);
53
+ }
54
+ return;
55
+ }
56
+ if (Array.isArray(value)) {
57
+ value.forEach((item, index) => assertJsonValue(item, `${path}[${index}]`));
58
+ return;
59
+ }
60
+ if (typeof value === "object") {
61
+ for (const [key, item] of Object.entries(value)) {
62
+ if (RESERVED_WRITE_RESULT_KEYS.has(key.toLowerCase())) {
63
+ fail("write_not_supported", `${path} contains reserved write field: ${key}.`, 500);
64
+ }
65
+ assertJsonValue(item, `${path}.${key}`);
66
+ }
67
+ return;
68
+ }
69
+ fail("invalid_json", `${path} is not JSON-serializable.`, 500);
70
+ }
71
+
72
+ function assertManifest(definition: ReadOnlyConnectorDefinition): void {
73
+ const { manifest, handlers } = definition;
74
+ if (manifest.schema_version !== 1 || manifest.agentlayer?.protocol_version !== 1) {
75
+ fail("invalid_manifest", "Connector must use manifest and protocol version 1.");
76
+ }
77
+ if (!READ_ONLY_TRUST.has(manifest.trust)) {
78
+ fail("write_not_supported", "The TypeScript SDK currently accepts read-only connectors only.");
79
+ }
80
+ if (manifest.permissions?.transaction_intents !== false) {
81
+ fail("write_not_supported", "Read-only connectors must disable transaction_intents.");
82
+ }
83
+ if (!Array.isArray(manifest.tools) || manifest.tools.length === 0) {
84
+ fail("invalid_manifest", "Connector manifest must declare at least one tool.");
85
+ }
86
+ const names = new Set<string>();
87
+ for (const tool of manifest.tools) {
88
+ if (!TOOL_NAME_PATTERN.test(tool.name)) {
89
+ fail("invalid_manifest", `Invalid connector tool name: ${tool.name}.`);
90
+ }
91
+ if (names.has(tool.name)) {
92
+ fail("invalid_manifest", `Duplicate connector tool name: ${tool.name}.`);
93
+ }
94
+ names.add(tool.name);
95
+ if (tool.read_only !== true) {
96
+ fail("write_not_supported", `Tool ${tool.name} is not read-only.`);
97
+ }
98
+ if (typeof handlers[tool.name] !== "function") {
99
+ fail("missing_handler", `Missing handler for connector tool: ${tool.name}.`);
100
+ }
101
+ }
102
+ for (const handlerName of Object.keys(handlers)) {
103
+ if (!names.has(handlerName)) {
104
+ fail("unknown_handler", `Handler is not declared in the manifest: ${handlerName}.`);
105
+ }
106
+ }
107
+ }
108
+
109
+ function assertRequestIdentity(
110
+ request: ConnectorInvocationRequest,
111
+ manifest: ConnectorManifest
112
+ ): void {
113
+ if (request.protocol_version !== 1) {
114
+ fail("protocol_mismatch", "Unsupported connector protocol version.");
115
+ }
116
+ if (typeof request.request_id !== "string" || request.request_id.length < 8) {
117
+ fail("invalid_request", "request_id is required.");
118
+ }
119
+ if (
120
+ request.connector?.id !== manifest.id ||
121
+ request.connector?.version !== manifest.version ||
122
+ request.connector?.artifact_digest !== manifest.artifact_digest
123
+ ) {
124
+ fail("identity_mismatch", "Connector request identity does not match this deployment.", 409);
125
+ }
126
+ if (!request.arguments || Array.isArray(request.arguments) || typeof request.arguments !== "object") {
127
+ fail("invalid_request", "Connector arguments must be an object.");
128
+ }
129
+ if (!request.context || Array.isArray(request.context) || typeof request.context !== "object") {
130
+ fail("invalid_request", "Connector context must be an object.");
131
+ }
132
+ if (request.expires_at) {
133
+ const expiry = Date.parse(request.expires_at);
134
+ if (!Number.isFinite(expiry) || expiry <= Date.now()) {
135
+ fail("expired_request", "Connector request is expired.");
136
+ }
137
+ }
138
+ }
139
+
140
+ export function defineReadOnlyConnector(
141
+ definition: ReadOnlyConnectorDefinition
142
+ ): ReadOnlyConnector {
143
+ assertManifest(definition);
144
+ const ttlSeconds = definition.responseTtlSeconds ?? 60;
145
+ if (!Number.isInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > MAX_RESPONSE_TTL_SECONDS) {
146
+ fail("invalid_ttl", `responseTtlSeconds must be between 1 and ${MAX_RESPONSE_TTL_SECONDS}.`);
147
+ }
148
+
149
+ const ajv = new Ajv({ allErrors: true, strict: true });
150
+ addFormats(ajv);
151
+ const tools = new Map<string, CompiledTool>();
152
+ try {
153
+ for (const tool of definition.manifest.tools) {
154
+ tools.set(tool.name, {
155
+ manifest: tool,
156
+ validateInput: ajv.compile(tool.input_schema),
157
+ validateOutput: ajv.compile(tool.output_schema),
158
+ });
159
+ }
160
+ } catch (error) {
161
+ fail("invalid_schema", `Connector tool schema is invalid: ${String(error)}`);
162
+ }
163
+
164
+ const manifest = structuredClone(definition.manifest);
165
+ return Object.freeze({
166
+ manifest: Object.freeze(manifest),
167
+ async invoke(request: ConnectorInvocationRequest): Promise<ConnectorReadResponse> {
168
+ assertRequestIdentity(request, manifest);
169
+ const tool = tools.get(request.tool);
170
+ if (!tool) {
171
+ fail("unknown_tool", `Connector tool is not declared: ${request.tool}.`, 404);
172
+ }
173
+ if (!tool.validateInput(request.arguments)) {
174
+ fail("invalid_arguments", `Arguments do not match the schema for ${request.tool}.`);
175
+ }
176
+ const result: unknown = await definition.handlers[request.tool]!(
177
+ request.arguments,
178
+ Object.freeze({ ...request.context })
179
+ );
180
+ assertJsonValue(result);
181
+ if (!tool.validateOutput(result)) {
182
+ fail("invalid_result", `Result does not match the schema for ${request.tool}.`, 500);
183
+ }
184
+ const connector = {
185
+ id: manifest.id,
186
+ version: manifest.version,
187
+ ...(manifest.artifact_digest ? { artifact_digest: manifest.artifact_digest } : {}),
188
+ };
189
+ const response: ConnectorReadResponse = {
190
+ protocol_version: 1,
191
+ request_id: request.request_id,
192
+ connector,
193
+ tool: request.tool,
194
+ kind: "read_result",
195
+ result,
196
+ expires_at: new Date(Date.now() + ttlSeconds * 1000).toISOString(),
197
+ };
198
+ if (Buffer.byteLength(JSON.stringify(response)) > MAX_RESPONSE_BYTES) {
199
+ fail("response_too_large", "Connector response exceeds the 1 MiB protocol limit.", 500);
200
+ }
201
+ return response;
202
+ },
203
+ });
204
+ }
@@ -0,0 +1,11 @@
1
+ export class ConnectorSdkError extends Error {
2
+ readonly code: string;
3
+ readonly statusCode: number;
4
+
5
+ constructor(code: string, message: string, statusCode = 400) {
6
+ super(message);
7
+ this.name = "ConnectorSdkError";
8
+ this.code = code;
9
+ this.statusCode = statusCode;
10
+ }
11
+ }
@@ -0,0 +1,116 @@
1
+ import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
2
+
3
+ import { ConnectorSdkError } from "./errors.js";
4
+ import type { ConnectorInvocationRequest, ReadOnlyConnector } from "./types.js";
5
+
6
+ const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
7
+ const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
8
+
9
+ export interface ConnectorHttpOptions {
10
+ maxBodyBytes?: number;
11
+ }
12
+
13
+ export interface StartConnectorServerOptions extends ConnectorHttpOptions {
14
+ host?: string;
15
+ port?: number;
16
+ }
17
+
18
+ function sendJson(response: ServerResponse, statusCode: number, payload: unknown): void {
19
+ const body = JSON.stringify(payload);
20
+ response.writeHead(statusCode, {
21
+ "content-type": "application/json; charset=utf-8",
22
+ "content-length": Buffer.byteLength(body),
23
+ "cache-control": "no-store",
24
+ "x-content-type-options": "nosniff",
25
+ });
26
+ response.end(body);
27
+ }
28
+
29
+ async function readJson(request: IncomingMessage, maxBodyBytes: number): Promise<unknown> {
30
+ const contentLength = Number(request.headers["content-length"] ?? 0);
31
+ if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {
32
+ throw new ConnectorSdkError("request_too_large", "Request body exceeds the size limit.", 413);
33
+ }
34
+ const chunks: Buffer[] = [];
35
+ let size = 0;
36
+ for await (const chunk of request) {
37
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
38
+ size += buffer.length;
39
+ if (size > maxBodyBytes) {
40
+ throw new ConnectorSdkError("request_too_large", "Request body exceeds the size limit.", 413);
41
+ }
42
+ chunks.push(buffer);
43
+ }
44
+ try {
45
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
46
+ } catch {
47
+ throw new ConnectorSdkError("invalid_json", "Request body must be valid JSON.");
48
+ }
49
+ }
50
+
51
+ export function createConnectorHttpHandler(
52
+ connector: ReadOnlyConnector,
53
+ options: ConnectorHttpOptions = {}
54
+ ): (request: IncomingMessage, response: ServerResponse) => Promise<void> {
55
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
56
+ if (!Number.isInteger(maxBodyBytes) || maxBodyBytes < 1024) {
57
+ throw new ConnectorSdkError("invalid_http_options", "maxBodyBytes must be at least 1024.");
58
+ }
59
+ return async (request, response) => {
60
+ try {
61
+ if (request.method === "GET" && request.url === "/healthz") {
62
+ sendJson(response, 200, {
63
+ ok: true,
64
+ connector: {
65
+ id: connector.manifest.id,
66
+ version: connector.manifest.version,
67
+ },
68
+ });
69
+ return;
70
+ }
71
+ if (request.method !== "POST" || request.url !== "/invoke") {
72
+ sendJson(response, 404, { ok: false, error: { code: "not_found", message: "Not found." } });
73
+ return;
74
+ }
75
+ if (!String(request.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) {
76
+ throw new ConnectorSdkError("unsupported_media_type", "Content-Type must be application/json.", 415);
77
+ }
78
+ const payload = await readJson(request, maxBodyBytes);
79
+ const result = await connector.invoke(payload as ConnectorInvocationRequest);
80
+ sendJson(response, 200, result);
81
+ } catch (error) {
82
+ const sdkError =
83
+ error instanceof ConnectorSdkError
84
+ ? error
85
+ : new ConnectorSdkError("internal_error", "Connector invocation failed.", 500);
86
+ sendJson(response, sdkError.statusCode, {
87
+ ok: false,
88
+ error: { code: sdkError.code, message: sdkError.message },
89
+ });
90
+ }
91
+ };
92
+ }
93
+
94
+ export async function startConnectorServer(
95
+ connector: ReadOnlyConnector,
96
+ options: StartConnectorServerOptions = {}
97
+ ): Promise<Server> {
98
+ const handler = createConnectorHttpHandler(connector, options);
99
+ const host = options.host ?? "0.0.0.0";
100
+ const port = options.port ?? Number(process.env.PORT ?? 3000);
101
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
102
+ throw new ConnectorSdkError("invalid_http_options", "port must be between 1 and 65535.");
103
+ }
104
+ const server = createServer((request, response) => void handler(request, response));
105
+ server.requestTimeout = DEFAULT_REQUEST_TIMEOUT_MS;
106
+ server.headersTimeout = 10_000;
107
+ server.keepAliveTimeout = 5_000;
108
+ await new Promise<void>((resolve, reject) => {
109
+ server.once("error", reject);
110
+ server.listen(port, host, () => {
111
+ server.off("error", reject);
112
+ resolve();
113
+ });
114
+ });
115
+ return server;
116
+ }
@@ -0,0 +1,26 @@
1
+ export { defineReadOnlyConnector } from "./connector.js";
2
+ export { ConnectorSdkError } from "./errors.js";
3
+ export {
4
+ createConnectorHttpHandler,
5
+ startConnectorServer,
6
+ type ConnectorHttpOptions,
7
+ type StartConnectorServerOptions,
8
+ } from "./http.js";
9
+
10
+ export type {
11
+ ConnectorIdentity,
12
+ ConnectorInvocationContext,
13
+ ConnectorInvocationRequest,
14
+ ConnectorManifest,
15
+ ConnectorReadHandler,
16
+ ConnectorReadHandlers,
17
+ ConnectorReadResponse,
18
+ ConnectorToolManifest,
19
+ ConnectorTrust,
20
+ JsonObject,
21
+ JsonPrimitive,
22
+ JsonSchema,
23
+ JsonValue,
24
+ ReadOnlyConnector,
25
+ ReadOnlyConnectorDefinition,
26
+ } from "./types.js";
@@ -0,0 +1,101 @@
1
+ export type JsonPrimitive = string | number | boolean | null;
2
+ export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
3
+ export type JsonObject = { [key: string]: JsonValue };
4
+ export type JsonSchema = Record<string, unknown>;
5
+
6
+ export type ConnectorTrust =
7
+ | "community_read_only"
8
+ | "verified_read_only"
9
+ | "verified_write"
10
+ | "local_development";
11
+
12
+ export interface ConnectorToolManifest {
13
+ name: string;
14
+ description: string;
15
+ read_only: boolean;
16
+ risk_level: "low" | "medium" | "high";
17
+ input_schema: JsonSchema;
18
+ output_schema: JsonSchema;
19
+ }
20
+
21
+ export interface ConnectorManifest {
22
+ schema_version: 1;
23
+ id: string;
24
+ name: string;
25
+ version: string;
26
+ artifact_digest?: string;
27
+ publisher: {
28
+ id: string;
29
+ name: string;
30
+ url?: string;
31
+ };
32
+ agentlayer: {
33
+ protocol_version: 1;
34
+ runtime_range: string;
35
+ };
36
+ trust: ConnectorTrust;
37
+ transport: {
38
+ type: "https";
39
+ url: string;
40
+ timeout_ms?: number;
41
+ };
42
+ permissions: {
43
+ wallet_address: boolean;
44
+ transaction_intents: boolean;
45
+ network_hosts: string[];
46
+ };
47
+ tools: ConnectorToolManifest[];
48
+ }
49
+
50
+ export interface ConnectorIdentity {
51
+ id: string;
52
+ version: string;
53
+ artifact_digest?: string;
54
+ }
55
+
56
+ export interface ConnectorInvocationContext {
57
+ chain?: string;
58
+ network?: string;
59
+ chain_id?: string | number;
60
+ wallet_address?: string;
61
+ }
62
+
63
+ export interface ConnectorInvocationRequest {
64
+ protocol_version: 1;
65
+ request_id: string;
66
+ connector: ConnectorIdentity;
67
+ tool: string;
68
+ arguments: JsonObject;
69
+ context: ConnectorInvocationContext;
70
+ issued_at?: string;
71
+ expires_at?: string;
72
+ nonce?: string;
73
+ }
74
+
75
+ export interface ConnectorReadResponse {
76
+ protocol_version: 1;
77
+ request_id: string;
78
+ connector: ConnectorIdentity;
79
+ tool: string;
80
+ kind: "read_result";
81
+ result: JsonValue;
82
+ expires_at: string;
83
+ }
84
+
85
+ export type ConnectorReadHandler = (
86
+ arguments_: JsonObject,
87
+ context: Readonly<ConnectorInvocationContext>
88
+ ) => JsonValue | Promise<JsonValue>;
89
+
90
+ export type ConnectorReadHandlers = Record<string, ConnectorReadHandler>;
91
+
92
+ export interface ReadOnlyConnectorDefinition {
93
+ manifest: ConnectorManifest;
94
+ handlers: ConnectorReadHandlers;
95
+ responseTtlSeconds?: number;
96
+ }
97
+
98
+ export interface ReadOnlyConnector {
99
+ readonly manifest: Readonly<ConnectorManifest>;
100
+ invoke(request: ConnectorInvocationRequest): Promise<ConnectorReadResponse>;
101
+ }