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,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 ACP Project
|
|
3
|
+
* Licensed under the Apache License, Version 2.0
|
|
4
|
+
* See LICENSE file for details.
|
|
5
|
+
*/
|
|
6
|
+
import { validationError } from "./errors.js";
|
|
7
|
+
function normalizeJson(value) {
|
|
8
|
+
if (Array.isArray(value)) {
|
|
9
|
+
return value.map((item) => normalizeJson(item));
|
|
10
|
+
}
|
|
11
|
+
if (value === null || typeof value !== "object") {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
const output = {};
|
|
15
|
+
const keys = Object.keys(value).sort();
|
|
16
|
+
for (const key of keys) {
|
|
17
|
+
output[key] = normalizeJson(value[key]);
|
|
18
|
+
}
|
|
19
|
+
return output;
|
|
20
|
+
}
|
|
21
|
+
export function canonicalJsonString(value) {
|
|
22
|
+
return JSON.stringify(normalizeJson(value));
|
|
23
|
+
}
|
|
24
|
+
export function canonicalJsonBytes(value) {
|
|
25
|
+
return new TextEncoder().encode(canonicalJsonString(value));
|
|
26
|
+
}
|
|
27
|
+
export function parseJsonMap(raw) {
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
30
|
+
throw validationError("unable to parse JSON object");
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
export function toJsonMap(value) {
|
|
35
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
36
|
+
throw validationError("expected JSON object");
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { JsonMap } from "./jsonSupport.js";
|
|
2
|
+
export interface IdentityKeyMaterial {
|
|
3
|
+
signing_private_key: string;
|
|
4
|
+
encryption_private_key: string;
|
|
5
|
+
signing_public_key?: string;
|
|
6
|
+
encryption_public_key?: string;
|
|
7
|
+
signing_kid?: string;
|
|
8
|
+
encryption_kid?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface TlsMaterial {
|
|
11
|
+
cert_file?: string;
|
|
12
|
+
key_file?: string;
|
|
13
|
+
ca_file?: string;
|
|
14
|
+
}
|
|
15
|
+
export type KeyProviderInfo = JsonMap;
|
|
16
|
+
export interface KeyProvider {
|
|
17
|
+
loadIdentityKeys(agentId: string): Promise<IdentityKeyMaterial>;
|
|
18
|
+
loadTlsMaterial(agentId: string): Promise<TlsMaterial>;
|
|
19
|
+
loadCaBundle(agentId: string): Promise<string | undefined>;
|
|
20
|
+
describe(): KeyProviderInfo;
|
|
21
|
+
}
|
|
22
|
+
export declare class LocalKeyProvider implements KeyProvider {
|
|
23
|
+
private readonly storageDir;
|
|
24
|
+
private readonly certFile?;
|
|
25
|
+
private readonly keyFile?;
|
|
26
|
+
private readonly caFile?;
|
|
27
|
+
constructor(storageDir: string, certFile?: string | undefined, keyFile?: string | undefined, caFile?: string | undefined);
|
|
28
|
+
loadIdentityKeys(agentId: string): Promise<IdentityKeyMaterial>;
|
|
29
|
+
loadTlsMaterial(_agentId: string): Promise<TlsMaterial>;
|
|
30
|
+
loadCaBundle(_agentId: string): Promise<string | undefined>;
|
|
31
|
+
describe(): KeyProviderInfo;
|
|
32
|
+
}
|
|
33
|
+
export declare class VaultKeyProvider implements KeyProvider {
|
|
34
|
+
private readonly vaultUrl;
|
|
35
|
+
private readonly vaultPath;
|
|
36
|
+
private readonly vaultTokenEnv;
|
|
37
|
+
private readonly token?;
|
|
38
|
+
private readonly timeoutSeconds;
|
|
39
|
+
private readonly cache;
|
|
40
|
+
private readonly fetchOptions;
|
|
41
|
+
constructor(vaultUrl: string, vaultPath: string, vaultTokenEnv?: string, token?: string | undefined, timeoutSeconds?: number, caFile?: string, allowInsecureTls?: boolean, allowInsecureHttp?: boolean);
|
|
42
|
+
describe(): KeyProviderInfo;
|
|
43
|
+
private resolveToken;
|
|
44
|
+
private secretPath;
|
|
45
|
+
private loadSecret;
|
|
46
|
+
loadIdentityKeys(agentId: string): Promise<IdentityKeyMaterial>;
|
|
47
|
+
loadTlsMaterial(agentId: string): Promise<TlsMaterial>;
|
|
48
|
+
loadCaBundle(agentId: string): Promise<string | undefined>;
|
|
49
|
+
}
|
|
50
|
+
export declare function readCaFile(path: string | undefined): Promise<string | undefined>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 ACP Project
|
|
3
|
+
* Licensed under the Apache License, Version 2.0
|
|
4
|
+
* See LICENSE file for details.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { buildFetchOptions, validateHttpUrl } from "./httpSecurity.js";
|
|
8
|
+
import { toJsonMap } from "./jsonSupport.js";
|
|
9
|
+
import { keyProviderError } from "./errors.js";
|
|
10
|
+
import { readIdentity, sanitizeAgentId } from "./identity.js";
|
|
11
|
+
function normalizeOptional(value) {
|
|
12
|
+
if (!value) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const normalized = value.trim();
|
|
16
|
+
return normalized ? normalized : undefined;
|
|
17
|
+
}
|
|
18
|
+
function secretValue(secret, keys) {
|
|
19
|
+
for (const key of keys) {
|
|
20
|
+
const value = secret[key];
|
|
21
|
+
if (typeof value === "string" && value.trim()) {
|
|
22
|
+
return value.trim();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
export class LocalKeyProvider {
|
|
28
|
+
storageDir;
|
|
29
|
+
certFile;
|
|
30
|
+
keyFile;
|
|
31
|
+
caFile;
|
|
32
|
+
constructor(storageDir, certFile, keyFile, caFile) {
|
|
33
|
+
this.storageDir = storageDir;
|
|
34
|
+
this.certFile = certFile;
|
|
35
|
+
this.keyFile = keyFile;
|
|
36
|
+
this.caFile = caFile;
|
|
37
|
+
}
|
|
38
|
+
async loadIdentityKeys(agentId) {
|
|
39
|
+
const bundle = readIdentity(this.storageDir, agentId);
|
|
40
|
+
if (!bundle) {
|
|
41
|
+
throw keyProviderError(`Local identity not found for ${agentId}`);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
signing_private_key: bundle.identity.signing_private_key,
|
|
45
|
+
encryption_private_key: bundle.identity.encryption_private_key,
|
|
46
|
+
signing_public_key: bundle.identity.signing_public_key,
|
|
47
|
+
encryption_public_key: bundle.identity.encryption_public_key,
|
|
48
|
+
signing_kid: bundle.identity.signing_kid,
|
|
49
|
+
encryption_kid: bundle.identity.encryption_kid
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async loadTlsMaterial(_agentId) {
|
|
53
|
+
return {
|
|
54
|
+
cert_file: normalizeOptional(this.certFile),
|
|
55
|
+
key_file: normalizeOptional(this.keyFile),
|
|
56
|
+
ca_file: normalizeOptional(this.caFile)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async loadCaBundle(_agentId) {
|
|
60
|
+
return normalizeOptional(this.caFile);
|
|
61
|
+
}
|
|
62
|
+
describe() {
|
|
63
|
+
return {
|
|
64
|
+
provider: "local",
|
|
65
|
+
storage_dir: this.storageDir
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export class VaultKeyProvider {
|
|
70
|
+
vaultUrl;
|
|
71
|
+
vaultPath;
|
|
72
|
+
vaultTokenEnv;
|
|
73
|
+
token;
|
|
74
|
+
timeoutSeconds;
|
|
75
|
+
cache = new Map();
|
|
76
|
+
fetchOptions;
|
|
77
|
+
constructor(vaultUrl, vaultPath, vaultTokenEnv = "VAULT_TOKEN", token, timeoutSeconds = 10, caFile, allowInsecureTls = false, allowInsecureHttp = false) {
|
|
78
|
+
this.vaultUrl = vaultUrl;
|
|
79
|
+
this.vaultPath = vaultPath;
|
|
80
|
+
this.vaultTokenEnv = vaultTokenEnv;
|
|
81
|
+
this.token = token;
|
|
82
|
+
this.timeoutSeconds = timeoutSeconds;
|
|
83
|
+
if (!vaultUrl.trim()) {
|
|
84
|
+
throw keyProviderError("vault_url is required for VaultKeyProvider");
|
|
85
|
+
}
|
|
86
|
+
if (!vaultPath.trim()) {
|
|
87
|
+
throw keyProviderError("vault_path is required for VaultKeyProvider");
|
|
88
|
+
}
|
|
89
|
+
validateHttpUrl(vaultUrl, allowInsecureHttp, false, "Vault key provider URL");
|
|
90
|
+
const policy = {
|
|
91
|
+
allow_insecure_http: allowInsecureHttp,
|
|
92
|
+
allow_insecure_tls: allowInsecureTls,
|
|
93
|
+
mtls_enabled: false,
|
|
94
|
+
ca_file: caFile,
|
|
95
|
+
cert_file: undefined,
|
|
96
|
+
key_file: undefined
|
|
97
|
+
};
|
|
98
|
+
this.fetchOptions = buildFetchOptions(policy);
|
|
99
|
+
}
|
|
100
|
+
describe() {
|
|
101
|
+
return {
|
|
102
|
+
provider: "vault",
|
|
103
|
+
vault_url: this.vaultUrl.replace(/\/+$/, ""),
|
|
104
|
+
vault_path: this.vaultPath.replace(/^\/+/, ""),
|
|
105
|
+
vault_token_env: this.vaultTokenEnv
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
resolveToken() {
|
|
109
|
+
if (this.token && this.token.trim()) {
|
|
110
|
+
return this.token.trim();
|
|
111
|
+
}
|
|
112
|
+
const envValue = process.env[this.vaultTokenEnv];
|
|
113
|
+
return envValue?.trim() || undefined;
|
|
114
|
+
}
|
|
115
|
+
secretPath(agentId) {
|
|
116
|
+
const sanitized = sanitizeAgentId(agentId);
|
|
117
|
+
if (this.vaultPath.includes("{agent_id}")) {
|
|
118
|
+
return this.vaultPath.replace("{agent_id}", sanitized);
|
|
119
|
+
}
|
|
120
|
+
return `${this.vaultPath.replace(/\/+$/, "")}/${sanitized}`;
|
|
121
|
+
}
|
|
122
|
+
async loadSecret(agentId) {
|
|
123
|
+
const path = this.secretPath(agentId);
|
|
124
|
+
const cached = this.cache.get(path);
|
|
125
|
+
if (cached) {
|
|
126
|
+
return cached;
|
|
127
|
+
}
|
|
128
|
+
const token = this.resolveToken();
|
|
129
|
+
if (!token) {
|
|
130
|
+
throw keyProviderError(`Vault token is missing. Set token or environment variable ${this.vaultTokenEnv}.`);
|
|
131
|
+
}
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
const timeout = setTimeout(() => controller.abort(), Math.max(1, this.timeoutSeconds) * 1000);
|
|
134
|
+
try {
|
|
135
|
+
const url = `${this.vaultUrl.replace(/\/+$/, "")}/v1/${path.replace(/^\/+/, "")}`;
|
|
136
|
+
const response = await fetch(url, {
|
|
137
|
+
method: "GET",
|
|
138
|
+
headers: {
|
|
139
|
+
Accept: "application/json",
|
|
140
|
+
"X-Vault-Token": token
|
|
141
|
+
},
|
|
142
|
+
signal: controller.signal,
|
|
143
|
+
...this.fetchOptions
|
|
144
|
+
});
|
|
145
|
+
if (response.status !== 200) {
|
|
146
|
+
throw keyProviderError(`Vault returned HTTP ${response.status} for path ${path}`);
|
|
147
|
+
}
|
|
148
|
+
const parsed = toJsonMap((await response.json()));
|
|
149
|
+
const dataValue = parsed.data;
|
|
150
|
+
if (!dataValue || typeof dataValue !== "object" || Array.isArray(dataValue)) {
|
|
151
|
+
throw keyProviderError(`Vault response for path ${path} is missing data object`);
|
|
152
|
+
}
|
|
153
|
+
const topData = dataValue;
|
|
154
|
+
const nestedData = topData.data && typeof topData.data === "object" && !Array.isArray(topData.data)
|
|
155
|
+
? topData.data
|
|
156
|
+
: topData;
|
|
157
|
+
this.cache.set(path, nestedData);
|
|
158
|
+
return nestedData;
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
clearTimeout(timeout);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async loadIdentityKeys(agentId) {
|
|
165
|
+
const secret = await this.loadSecret(agentId);
|
|
166
|
+
const signingPrivateKey = secretValue(secret, [
|
|
167
|
+
"signing_key",
|
|
168
|
+
"identity_signing_key",
|
|
169
|
+
"signing_private_key"
|
|
170
|
+
]);
|
|
171
|
+
const encryptionPrivateKey = secretValue(secret, [
|
|
172
|
+
"encryption_key",
|
|
173
|
+
"identity_encryption_key",
|
|
174
|
+
"encryption_private_key"
|
|
175
|
+
]);
|
|
176
|
+
if (!signingPrivateKey) {
|
|
177
|
+
throw keyProviderError(`Vault secret for ${agentId} is missing signing_key`);
|
|
178
|
+
}
|
|
179
|
+
if (!encryptionPrivateKey) {
|
|
180
|
+
throw keyProviderError(`Vault secret for ${agentId} is missing encryption_key`);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
signing_private_key: signingPrivateKey,
|
|
184
|
+
encryption_private_key: encryptionPrivateKey,
|
|
185
|
+
signing_public_key: secretValue(secret, ["signing_public_key"]),
|
|
186
|
+
encryption_public_key: secretValue(secret, ["encryption_public_key"]),
|
|
187
|
+
signing_kid: secretValue(secret, ["signing_kid"]),
|
|
188
|
+
encryption_kid: secretValue(secret, ["encryption_kid"])
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async loadTlsMaterial(agentId) {
|
|
192
|
+
const secret = await this.loadSecret(agentId);
|
|
193
|
+
return {
|
|
194
|
+
cert_file: secretValue(secret, ["tls_cert_file", "tls_cert", "cert_file"]),
|
|
195
|
+
key_file: secretValue(secret, ["tls_key_file", "tls_key", "key_file"]),
|
|
196
|
+
ca_file: secretValue(secret, ["ca_bundle_file", "ca_file", "ca_bundle"])
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async loadCaBundle(agentId) {
|
|
200
|
+
const secret = await this.loadSecret(agentId);
|
|
201
|
+
return secretValue(secret, ["ca_bundle_file", "ca_file", "ca_bundle"]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export async function readCaFile(path) {
|
|
205
|
+
if (!path) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
return readFileSync(path, "utf-8");
|
|
209
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { JsonMap } from "./jsonSupport.js";
|
|
2
|
+
export type MessageClass = "SEND" | "ACK" | "FAIL" | "CAPABILITIES" | "COMPENSATE";
|
|
3
|
+
export type DeliveryState = "PENDING" | "DELIVERED" | "ACKNOWLEDGED" | "FAILED" | "DECLINED" | "EXPIRED";
|
|
4
|
+
export type DeliveryMode = "auto" | "direct" | "relay" | "amqp" | "mqtt";
|
|
5
|
+
export interface WrappedContentKey {
|
|
6
|
+
recipient: string;
|
|
7
|
+
ephemeral_public_key: string;
|
|
8
|
+
nonce: string;
|
|
9
|
+
ciphertext: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Envelope {
|
|
12
|
+
acp_version: string;
|
|
13
|
+
message_class: MessageClass;
|
|
14
|
+
message_id: string;
|
|
15
|
+
operation_id: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
expires_at: string;
|
|
18
|
+
sender: string;
|
|
19
|
+
recipients: string[];
|
|
20
|
+
context_id: string;
|
|
21
|
+
crypto_suite: string;
|
|
22
|
+
correlation_id?: string;
|
|
23
|
+
in_reply_to?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ProtectedPayload {
|
|
26
|
+
nonce: string;
|
|
27
|
+
ciphertext: string;
|
|
28
|
+
wrapped_content_keys: WrappedContentKey[];
|
|
29
|
+
payload_hash: string;
|
|
30
|
+
signature_kid: string;
|
|
31
|
+
signature: string;
|
|
32
|
+
}
|
|
33
|
+
export interface AcpMessage {
|
|
34
|
+
envelope: Envelope;
|
|
35
|
+
protected: ProtectedPayload;
|
|
36
|
+
sender_identity_document?: JsonMap;
|
|
37
|
+
}
|
|
38
|
+
export interface DeliveryOutcome {
|
|
39
|
+
recipient: string;
|
|
40
|
+
state: DeliveryState;
|
|
41
|
+
status_code?: number;
|
|
42
|
+
response_class?: MessageClass;
|
|
43
|
+
reason_code?: string;
|
|
44
|
+
detail?: string;
|
|
45
|
+
response_message?: JsonMap;
|
|
46
|
+
}
|
|
47
|
+
export interface SendResult {
|
|
48
|
+
operation_id: string;
|
|
49
|
+
message_id: string;
|
|
50
|
+
message_ids: string[];
|
|
51
|
+
outcomes: DeliveryOutcome[];
|
|
52
|
+
}
|
|
53
|
+
export interface CompensateInstruction {
|
|
54
|
+
operation_id: string;
|
|
55
|
+
reason: string;
|
|
56
|
+
actions: JsonMap[];
|
|
57
|
+
}
|
|
58
|
+
export declare function parseMessageClass(value: string | undefined): MessageClass | undefined;
|
|
59
|
+
export declare function buildEnvelope(input: {
|
|
60
|
+
sender: string;
|
|
61
|
+
recipients: string[];
|
|
62
|
+
message_class: MessageClass;
|
|
63
|
+
context_id: string;
|
|
64
|
+
expires_in_seconds: number;
|
|
65
|
+
operation_id?: string;
|
|
66
|
+
correlation_id?: string;
|
|
67
|
+
in_reply_to?: string;
|
|
68
|
+
crypto_suite?: string;
|
|
69
|
+
}): Envelope;
|
|
70
|
+
export declare function validateEnvelope(envelope: Envelope): void;
|
|
71
|
+
export declare function isExpired(envelope: Envelope): boolean;
|
|
72
|
+
export declare function parseAcpMessage(map: JsonMap): AcpMessage;
|
|
73
|
+
export declare function messageToMap(message: AcpMessage): JsonMap;
|
|
74
|
+
export declare function buildAckPayload(receivedMessageId: string, status: string): JsonMap;
|
|
75
|
+
export declare function buildFailPayload(reasonCode: string, detail: string, retriable: boolean): JsonMap;
|
package/dist/messages.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 ACP Project
|
|
3
|
+
* Licensed under the Apache License, Version 2.0
|
|
4
|
+
* See LICENSE file for details.
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { ACP_VERSION, DEFAULT_CRYPTO_SUITE } from "./constants.js";
|
|
8
|
+
import { toJsonMap } from "./jsonSupport.js";
|
|
9
|
+
import { validationError } from "./errors.js";
|
|
10
|
+
export function parseMessageClass(value) {
|
|
11
|
+
if (!value) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
if (value === "SEND" ||
|
|
15
|
+
value === "ACK" ||
|
|
16
|
+
value === "FAIL" ||
|
|
17
|
+
value === "CAPABILITIES" ||
|
|
18
|
+
value === "COMPENSATE") {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
export function buildEnvelope(input) {
|
|
24
|
+
const now = new Date();
|
|
25
|
+
const expires = new Date(now.getTime() + Math.max(1, input.expires_in_seconds) * 1000);
|
|
26
|
+
const envelope = {
|
|
27
|
+
acp_version: ACP_VERSION,
|
|
28
|
+
message_class: input.message_class,
|
|
29
|
+
message_id: randomUUID(),
|
|
30
|
+
operation_id: input.operation_id ?? randomUUID(),
|
|
31
|
+
timestamp: now.toISOString(),
|
|
32
|
+
expires_at: expires.toISOString(),
|
|
33
|
+
sender: input.sender,
|
|
34
|
+
recipients: input.recipients,
|
|
35
|
+
context_id: input.context_id,
|
|
36
|
+
crypto_suite: input.crypto_suite ?? DEFAULT_CRYPTO_SUITE
|
|
37
|
+
};
|
|
38
|
+
if (input.correlation_id) {
|
|
39
|
+
envelope.correlation_id = input.correlation_id;
|
|
40
|
+
}
|
|
41
|
+
if (input.in_reply_to) {
|
|
42
|
+
envelope.in_reply_to = input.in_reply_to;
|
|
43
|
+
}
|
|
44
|
+
validateEnvelope(envelope);
|
|
45
|
+
return envelope;
|
|
46
|
+
}
|
|
47
|
+
export function validateEnvelope(envelope) {
|
|
48
|
+
if (!envelope.sender.trim()) {
|
|
49
|
+
throw validationError("Envelope sender is required");
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(envelope.recipients) || envelope.recipients.length === 0) {
|
|
52
|
+
throw validationError("Envelope recipients must not be empty");
|
|
53
|
+
}
|
|
54
|
+
const timestamp = Date.parse(envelope.timestamp);
|
|
55
|
+
const expires = Date.parse(envelope.expires_at);
|
|
56
|
+
if (Number.isNaN(timestamp) || Number.isNaN(expires)) {
|
|
57
|
+
throw validationError("Envelope timestamps must be RFC3339 strings");
|
|
58
|
+
}
|
|
59
|
+
if (expires <= timestamp) {
|
|
60
|
+
throw validationError("Envelope expires_at must be after timestamp");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function isExpired(envelope) {
|
|
64
|
+
const expires = Date.parse(envelope.expires_at);
|
|
65
|
+
return Number.isNaN(expires) || expires <= Date.now();
|
|
66
|
+
}
|
|
67
|
+
export function parseAcpMessage(map) {
|
|
68
|
+
const envelope = toJsonMap(map.envelope);
|
|
69
|
+
const protectedPayload = toJsonMap(map.protected);
|
|
70
|
+
const message = {
|
|
71
|
+
envelope,
|
|
72
|
+
protected: protectedPayload
|
|
73
|
+
};
|
|
74
|
+
if (map.sender_identity_document !== undefined && map.sender_identity_document !== null) {
|
|
75
|
+
message.sender_identity_document = toJsonMap(map.sender_identity_document);
|
|
76
|
+
}
|
|
77
|
+
validateEnvelope(envelope);
|
|
78
|
+
return message;
|
|
79
|
+
}
|
|
80
|
+
export function messageToMap(message) {
|
|
81
|
+
const output = {
|
|
82
|
+
envelope: message.envelope,
|
|
83
|
+
protected: message.protected
|
|
84
|
+
};
|
|
85
|
+
if (message.sender_identity_document) {
|
|
86
|
+
output.sender_identity_document = message.sender_identity_document;
|
|
87
|
+
}
|
|
88
|
+
return output;
|
|
89
|
+
}
|
|
90
|
+
export function buildAckPayload(receivedMessageId, status) {
|
|
91
|
+
return {
|
|
92
|
+
status,
|
|
93
|
+
received_message_id: receivedMessageId
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function buildFailPayload(reasonCode, detail, retriable) {
|
|
97
|
+
return {
|
|
98
|
+
reason_code: reasonCode,
|
|
99
|
+
detail,
|
|
100
|
+
retriable
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { JsonMap } from "./jsonSupport.js";
|
|
2
|
+
export declare const DEFAULT_MQTT_QOS = 1;
|
|
3
|
+
export declare const DEFAULT_MQTT_TOPIC_PREFIX = "acp/agent";
|
|
4
|
+
export type MqttMessageHandler = (message: JsonMap) => boolean | Promise<boolean>;
|
|
5
|
+
export declare function metadataProperties(message: JsonMap): Record<string, string>;
|
|
6
|
+
export declare class MqttTransportClient {
|
|
7
|
+
readonly broker_url: string;
|
|
8
|
+
readonly qos: number;
|
|
9
|
+
readonly topic_prefix: string;
|
|
10
|
+
readonly timeout_seconds: number;
|
|
11
|
+
readonly keepalive_seconds: number;
|
|
12
|
+
constructor(brokerUrl: string, qos?: number, topicPrefix?: string, timeoutSeconds?: number, keepaliveSeconds?: number);
|
|
13
|
+
static agentIdentifierToken(agentId: string): string;
|
|
14
|
+
static topicForAgent(agentId: string, topicPrefix?: string): string;
|
|
15
|
+
static buildServiceHint(agentId: string, brokerUrl: string, topic?: string, qos?: number, topicPrefix?: string): JsonMap;
|
|
16
|
+
private connectClient;
|
|
17
|
+
publish(message: JsonMap, recipientAgentId: string, service?: JsonMap): Promise<void>;
|
|
18
|
+
consume(agentId: string, handler: MqttMessageHandler, service?: JsonMap, maxMessages?: number, pollTimeoutMs?: number): Promise<number>;
|
|
19
|
+
}
|