acp-runtime 0.1.0
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.
- package/LICENSE +202 -0
- package/README.md +111 -0
- package/dist/agent.d.ts +67 -0
- package/dist/agent.js +798 -0
- package/dist/amqpTransport.d.ts +17 -0
- package/dist/amqpTransport.js +164 -0
- package/dist/capabilities.d.ts +16 -0
- package/dist/capabilities.js +81 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +13 -0
- package/dist/crypto.d.ts +20 -0
- package/dist/crypto.js +173 -0
- package/dist/discovery.d.ts +26 -0
- package/dist/discovery.js +267 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +36 -0
- package/dist/httpSecurity.d.ts +15 -0
- package/dist/httpSecurity.js +83 -0
- package/dist/identity.d.ts +45 -0
- package/dist/identity.js +163 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +18 -0
- package/dist/jsonSupport.d.ts +8 -0
- package/dist/jsonSupport.js +39 -0
- package/dist/keyProvider.d.ts +50 -0
- package/dist/keyProvider.js +209 -0
- package/dist/messages.d.ts +75 -0
- package/dist/messages.js +102 -0
- package/dist/mqttTransport.d.ts +19 -0
- package/dist/mqttTransport.js +215 -0
- package/dist/options.d.ts +34 -0
- package/dist/options.js +109 -0
- package/dist/overlay.d.ts +37 -0
- package/dist/overlay.js +95 -0
- package/dist/overlayFramework.d.ts +44 -0
- package/dist/overlayFramework.js +129 -0
- package/dist/transport.d.ts +16 -0
- package/dist/transport.js +63 -0
- package/dist/wellKnown.d.ts +14 -0
- package/dist/wellKnown.js +202 -0
- package/package.json +51 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 ACP Project
|
|
3
|
+
* Licensed under the Apache License, Version 2.0
|
|
4
|
+
* See LICENSE file for details.
|
|
5
|
+
*/
|
|
6
|
+
import { messageToMap } from "./messages.js";
|
|
7
|
+
import { buildFetchOptions, validateHttpUrl } from "./httpSecurity.js";
|
|
8
|
+
import { parseJsonMap } from "./jsonSupport.js";
|
|
9
|
+
import { transportError } from "./errors.js";
|
|
10
|
+
export class TransportClient {
|
|
11
|
+
timeoutSeconds;
|
|
12
|
+
policy;
|
|
13
|
+
fetchOptions;
|
|
14
|
+
constructor(timeoutSeconds, policy) {
|
|
15
|
+
this.timeoutSeconds = timeoutSeconds;
|
|
16
|
+
this.policy = policy;
|
|
17
|
+
this.fetchOptions = buildFetchOptions(policy);
|
|
18
|
+
}
|
|
19
|
+
async postJson(url, body) {
|
|
20
|
+
validateHttpUrl(url, this.policy.allow_insecure_http, this.policy.mtls_enabled, "HTTP transport request");
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const timeout = setTimeout(() => controller.abort(), Math.max(1, this.timeoutSeconds) * 1000);
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch(url, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: { "Content-Type": "application/json" },
|
|
27
|
+
body: JSON.stringify(body),
|
|
28
|
+
signal: controller.signal,
|
|
29
|
+
...this.fetchOptions
|
|
30
|
+
});
|
|
31
|
+
const rawBody = await response.text();
|
|
32
|
+
let parsedBody;
|
|
33
|
+
try {
|
|
34
|
+
parsedBody = parseJsonMap(rawBody);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
parsedBody = undefined;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
status_code: response.status,
|
|
41
|
+
raw_body: rawBody,
|
|
42
|
+
body: parsedBody
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw transportError(`HTTP request failed: ${String(error)}`);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
clearTimeout(timeout);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async sendToRelay(relayUrl, message) {
|
|
53
|
+
const relayEndpoint = relayUrl.endsWith("/") ? `${relayUrl}messages` : `${relayUrl}/messages`;
|
|
54
|
+
const response = await this.postJson(relayEndpoint, messageToMap(message));
|
|
55
|
+
if (response.status_code !== 200) {
|
|
56
|
+
throw transportError(`Relay returned HTTP ${response.status_code} for message ${message.envelope.message_id}`);
|
|
57
|
+
}
|
|
58
|
+
if (!response.body) {
|
|
59
|
+
throw transportError("Relay returned non-JSON response");
|
|
60
|
+
}
|
|
61
|
+
return response.body;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { JsonMap, JsonValue } from "./jsonSupport.js";
|
|
2
|
+
export declare const WELL_KNOWN_PATH = "/.well-known/acp";
|
|
3
|
+
export declare const SUPPORTED_WELL_KNOWN_VERSION = "1.0";
|
|
4
|
+
export declare const SUPPORTED_SECURITY_PROFILES: readonly ["http", "https", "mtls", "https+mtls"];
|
|
5
|
+
export declare function wellKnownUrlFromBase(baseUrl: string): string;
|
|
6
|
+
export declare function identityDocumentUrlFromBase(baseUrl: string): string;
|
|
7
|
+
export declare function buildWellKnownDocument(input: {
|
|
8
|
+
identity_document: JsonMap;
|
|
9
|
+
base_url: string;
|
|
10
|
+
identity_document_url?: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
}): JsonMap;
|
|
13
|
+
export declare function parseWellKnownDocument(value: JsonValue): JsonMap;
|
|
14
|
+
export declare function resolveIdentityDocumentReference(wellKnown: JsonMap, sourceUrl: string): string;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 ACP Project
|
|
3
|
+
* Licensed under the Apache License, Version 2.0
|
|
4
|
+
* See LICENSE file for details.
|
|
5
|
+
*/
|
|
6
|
+
import { ACP_VERSION, DEFAULT_IDENTITY_DOCUMENT_PATH } from "./constants.js";
|
|
7
|
+
import { toJsonMap } from "./jsonSupport.js";
|
|
8
|
+
import { validationError } from "./errors.js";
|
|
9
|
+
export const WELL_KNOWN_PATH = "/.well-known/acp";
|
|
10
|
+
export const SUPPORTED_WELL_KNOWN_VERSION = ACP_VERSION;
|
|
11
|
+
export const SUPPORTED_SECURITY_PROFILES = ["http", "https", "mtls", "https+mtls"];
|
|
12
|
+
export function wellKnownUrlFromBase(baseUrl) {
|
|
13
|
+
const normalized = baseUrl.trim();
|
|
14
|
+
if (!normalized) {
|
|
15
|
+
throw validationError("base_url is required");
|
|
16
|
+
}
|
|
17
|
+
if (normalized.endsWith(WELL_KNOWN_PATH)) {
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
return `${normalized.replace(/\/+$/, "")}${WELL_KNOWN_PATH}`;
|
|
21
|
+
}
|
|
22
|
+
export function identityDocumentUrlFromBase(baseUrl) {
|
|
23
|
+
const normalized = baseUrl.trim();
|
|
24
|
+
if (!normalized) {
|
|
25
|
+
throw validationError("base_url is required");
|
|
26
|
+
}
|
|
27
|
+
return `${normalized.replace(/\/+$/, "")}${DEFAULT_IDENTITY_DOCUMENT_PATH}`;
|
|
28
|
+
}
|
|
29
|
+
function inferSecurityProfile(transports) {
|
|
30
|
+
for (const transportName of ["http", "relay"]) {
|
|
31
|
+
const transport = transports[transportName];
|
|
32
|
+
if (transport && typeof transport === "object" && !Array.isArray(transport)) {
|
|
33
|
+
const securityProfile = transport.security_profile;
|
|
34
|
+
if (typeof securityProfile === "string" && securityProfile.trim()) {
|
|
35
|
+
return securityProfile.trim();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const endpoint = (transports.http ?? {}).endpoint ?? "";
|
|
40
|
+
if (endpoint.startsWith("https://")) {
|
|
41
|
+
return "https";
|
|
42
|
+
}
|
|
43
|
+
if (endpoint.startsWith("http://")) {
|
|
44
|
+
return "http";
|
|
45
|
+
}
|
|
46
|
+
return "https";
|
|
47
|
+
}
|
|
48
|
+
function validateIdentityDocumentReference(reference) {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = new URL(reference);
|
|
51
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
52
|
+
throw validationError("identity_document URL must use http or https");
|
|
53
|
+
}
|
|
54
|
+
if (!parsed.hostname.trim()) {
|
|
55
|
+
throw validationError("identity_document URL is missing host");
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
if (!reference.startsWith("/")) {
|
|
61
|
+
throw validationError("identity_document URL must be absolute http(s) or root-relative path");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function validateTransports(transports) {
|
|
66
|
+
for (const [transportName, transportValue] of Object.entries(transports)) {
|
|
67
|
+
if (!transportValue || typeof transportValue !== "object" || Array.isArray(transportValue)) {
|
|
68
|
+
throw validationError(`Well-known transport hint ${transportName} must be an object`);
|
|
69
|
+
}
|
|
70
|
+
const hint = transportValue;
|
|
71
|
+
if (hint.endpoint !== undefined) {
|
|
72
|
+
if (typeof hint.endpoint !== "string") {
|
|
73
|
+
throw validationError(`Well-known transport hint ${transportName}.endpoint must be a string`);
|
|
74
|
+
}
|
|
75
|
+
const endpoint = hint.endpoint.trim();
|
|
76
|
+
try {
|
|
77
|
+
const parsed = new URL(endpoint);
|
|
78
|
+
if ((parsed.protocol !== "http:" && parsed.protocol !== "https:") ||
|
|
79
|
+
!parsed.hostname.trim()) {
|
|
80
|
+
throw validationError(`Well-known transport hint ${transportName}.endpoint must be an absolute http(s) URL`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
throw validationError(`Well-known transport hint ${transportName}.endpoint must be an absolute http(s) URL`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (hint.security_profile !== undefined) {
|
|
88
|
+
if (typeof hint.security_profile !== "string" ||
|
|
89
|
+
!SUPPORTED_SECURITY_PROFILES.includes(hint.security_profile)) {
|
|
90
|
+
throw validationError(`Well-known transport hint ${transportName}.security_profile is invalid`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function buildWellKnownDocument(input) {
|
|
96
|
+
const agentId = input.identity_document.agent_id;
|
|
97
|
+
if (typeof agentId !== "string" || !agentId.trim()) {
|
|
98
|
+
throw validationError("identity_document.agent_id is required");
|
|
99
|
+
}
|
|
100
|
+
const version = input.version ?? SUPPORTED_WELL_KNOWN_VERSION;
|
|
101
|
+
if (version !== SUPPORTED_WELL_KNOWN_VERSION) {
|
|
102
|
+
throw validationError(`Unsupported well-known version ${version}; expected ${SUPPORTED_WELL_KNOWN_VERSION}`);
|
|
103
|
+
}
|
|
104
|
+
const serviceRaw = input.identity_document.service;
|
|
105
|
+
const service = serviceRaw && typeof serviceRaw === "object" && !Array.isArray(serviceRaw)
|
|
106
|
+
? serviceRaw
|
|
107
|
+
: {};
|
|
108
|
+
const capabilitiesRaw = input.identity_document.capabilities;
|
|
109
|
+
const capabilities = capabilitiesRaw && typeof capabilitiesRaw === "object" && !Array.isArray(capabilitiesRaw)
|
|
110
|
+
? capabilitiesRaw
|
|
111
|
+
: {};
|
|
112
|
+
const transports = {};
|
|
113
|
+
const directEndpoint = service.direct_endpoint;
|
|
114
|
+
if (typeof directEndpoint === "string" && directEndpoint.trim()) {
|
|
115
|
+
const httpHint = { endpoint: directEndpoint.trim() };
|
|
116
|
+
const httpSecurityProfile = service.http && typeof service.http === "object" && !Array.isArray(service.http)
|
|
117
|
+
? service.http.security_profile
|
|
118
|
+
: undefined;
|
|
119
|
+
if (typeof httpSecurityProfile === "string" && httpSecurityProfile.trim()) {
|
|
120
|
+
httpHint.security_profile = httpSecurityProfile.trim();
|
|
121
|
+
}
|
|
122
|
+
transports.http = httpHint;
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(service.relay_hints)) {
|
|
125
|
+
const relayHints = service.relay_hints
|
|
126
|
+
.filter((item) => typeof item === "string")
|
|
127
|
+
.map((item) => item.trim())
|
|
128
|
+
.filter((item) => item.length > 0);
|
|
129
|
+
if (relayHints.length > 0) {
|
|
130
|
+
const relayHint = { endpoint: relayHints[0] };
|
|
131
|
+
const relaySecurityProfile = service.relay && typeof service.relay === "object" && !Array.isArray(service.relay)
|
|
132
|
+
? service.relay.security_profile
|
|
133
|
+
: undefined;
|
|
134
|
+
if (typeof relaySecurityProfile === "string" && relaySecurityProfile.trim()) {
|
|
135
|
+
relayHint.security_profile = relaySecurityProfile.trim();
|
|
136
|
+
}
|
|
137
|
+
if (relayHints.length > 1) {
|
|
138
|
+
relayHint.hints = relayHints;
|
|
139
|
+
}
|
|
140
|
+
transports.relay = relayHint;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (service.amqp && typeof service.amqp === "object" && !Array.isArray(service.amqp)) {
|
|
144
|
+
transports.amqp = service.amqp;
|
|
145
|
+
}
|
|
146
|
+
if (service.mqtt && typeof service.mqtt === "object" && !Array.isArray(service.mqtt)) {
|
|
147
|
+
transports.mqtt = service.mqtt;
|
|
148
|
+
}
|
|
149
|
+
const identityReference = input.identity_document_url ?? identityDocumentUrlFromBase(input.base_url);
|
|
150
|
+
validateIdentityDocumentReference(identityReference);
|
|
151
|
+
const doc = {
|
|
152
|
+
agent_id: agentId,
|
|
153
|
+
identity_document: identityReference,
|
|
154
|
+
transports,
|
|
155
|
+
version,
|
|
156
|
+
security_profile: inferSecurityProfile(transports)
|
|
157
|
+
};
|
|
158
|
+
const supportsRaw = capabilities.supports;
|
|
159
|
+
if (supportsRaw && typeof supportsRaw === "object" && !Array.isArray(supportsRaw)) {
|
|
160
|
+
const supported = Object.entries(supportsRaw)
|
|
161
|
+
.filter(([, enabled]) => Boolean(enabled))
|
|
162
|
+
.map(([name]) => name)
|
|
163
|
+
.sort();
|
|
164
|
+
doc.capabilities = supported;
|
|
165
|
+
}
|
|
166
|
+
return doc;
|
|
167
|
+
}
|
|
168
|
+
export function parseWellKnownDocument(value) {
|
|
169
|
+
const map = toJsonMap(value);
|
|
170
|
+
if (typeof map.agent_id !== "string" || !map.agent_id.trim()) {
|
|
171
|
+
throw validationError("Well-known response missing agent_id");
|
|
172
|
+
}
|
|
173
|
+
if (typeof map.version !== "string" || map.version !== SUPPORTED_WELL_KNOWN_VERSION) {
|
|
174
|
+
throw validationError(`Well-known response version must be ${SUPPORTED_WELL_KNOWN_VERSION}`);
|
|
175
|
+
}
|
|
176
|
+
if (typeof map.identity_document !== "string" || !map.identity_document.trim()) {
|
|
177
|
+
throw validationError("Well-known response identity_document must be a URL string");
|
|
178
|
+
}
|
|
179
|
+
validateIdentityDocumentReference(map.identity_document);
|
|
180
|
+
const transports = toJsonMap(map.transports);
|
|
181
|
+
validateTransports(transports);
|
|
182
|
+
if (map.security_profile !== undefined) {
|
|
183
|
+
if (typeof map.security_profile !== "string" ||
|
|
184
|
+
!SUPPORTED_SECURITY_PROFILES.includes(map.security_profile)) {
|
|
185
|
+
throw validationError("Well-known response security_profile is invalid");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return map;
|
|
189
|
+
}
|
|
190
|
+
export function resolveIdentityDocumentReference(wellKnown, sourceUrl) {
|
|
191
|
+
const reference = wellKnown.identity_document;
|
|
192
|
+
if (typeof reference !== "string" || !reference.trim()) {
|
|
193
|
+
throw validationError("Well-known response identity_document reference is invalid");
|
|
194
|
+
}
|
|
195
|
+
validateIdentityDocumentReference(reference);
|
|
196
|
+
try {
|
|
197
|
+
return new URL(reference).toString();
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return new URL(reference, sourceUrl).toString();
|
|
201
|
+
}
|
|
202
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "acp-runtime",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for the Agent Communication Protocol (ACP)",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/beltxa/acp.git",
|
|
25
|
+
"directory": "sdks/typescript"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/beltxa/acp",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/beltxa/acp/issues"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "node_modules/.bin/tsc -p tsconfig.json",
|
|
33
|
+
"prepack": "npm run build",
|
|
34
|
+
"test": "node_modules/.bin/vitest run",
|
|
35
|
+
"test:watch": "node_modules/.bin/vitest",
|
|
36
|
+
"lint": "node_modules/.bin/tsc -p tsconfig.json --noEmit"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"amqplib": "^0.10.9",
|
|
40
|
+
"mqtt": "5.14.1",
|
|
41
|
+
"tweetnacl": "^1.0.3",
|
|
42
|
+
"undici": "^7.16.0",
|
|
43
|
+
"uuid": "^13.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/amqplib": "^0.10.8",
|
|
47
|
+
"@types/node": "^24.5.2",
|
|
48
|
+
"typescript": "^5.9.2",
|
|
49
|
+
"vitest": "^3.2.4"
|
|
50
|
+
}
|
|
51
|
+
}
|