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.
@@ -0,0 +1,267 @@
1
+ /*
2
+ * Copyright 2026 ACP Project
3
+ * Licensed under the Apache License, Version 2.0
4
+ * See LICENSE file for details.
5
+ */
6
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
7
+ import { dirname } from "node:path";
8
+ import { mkdirSync } from "node:fs";
9
+ import { discoveryError, validationError } from "./errors.js";
10
+ import { buildFetchOptions, validateHttpUrl } from "./httpSecurity.js";
11
+ import { parseAgentId, verifyIdentityDocument } from "./identity.js";
12
+ import { parseJsonMap, toJsonMap } from "./jsonSupport.js";
13
+ import { parseWellKnownDocument, resolveIdentityDocumentReference, wellKnownUrlFromBase } from "./wellKnown.js";
14
+ function cacheValid(identityDocument) {
15
+ const validUntil = identityDocument.valid_until;
16
+ if (typeof validUntil !== "string") {
17
+ return false;
18
+ }
19
+ const expires = Date.parse(validUntil);
20
+ return Number.isFinite(expires) && expires > Date.now();
21
+ }
22
+ function extractIdentityDocument(body) {
23
+ if (body.identity_document && typeof body.identity_document === "object" && !Array.isArray(body.identity_document)) {
24
+ return body.identity_document;
25
+ }
26
+ if (body.agent_id && body.keys && body.service) {
27
+ return body;
28
+ }
29
+ return undefined;
30
+ }
31
+ export class DiscoveryClient {
32
+ cache_path;
33
+ default_scheme;
34
+ relay_hints;
35
+ enterprise_directory_hints;
36
+ timeout_seconds;
37
+ policy;
38
+ cache = new Map();
39
+ registry = new Map();
40
+ fetchOptions;
41
+ constructor(cache_path, default_scheme = "https", relay_hints = [], enterprise_directory_hints = [], timeout_seconds = 10, policy = {
42
+ allow_insecure_http: false,
43
+ allow_insecure_tls: false,
44
+ mtls_enabled: false
45
+ }) {
46
+ this.cache_path = cache_path;
47
+ this.default_scheme = default_scheme;
48
+ this.relay_hints = relay_hints;
49
+ this.enterprise_directory_hints = enterprise_directory_hints;
50
+ this.timeout_seconds = timeout_seconds;
51
+ this.policy = policy;
52
+ this.fetchOptions = buildFetchOptions(policy);
53
+ this.loadCache();
54
+ }
55
+ seed(identityDocument) {
56
+ const agentId = identityDocument.agent_id;
57
+ if (typeof agentId !== "string" || !agentId.trim()) {
58
+ return;
59
+ }
60
+ this.cache.set(agentId, {
61
+ identity_document: identityDocument,
62
+ fetched_at: new Date().toISOString()
63
+ });
64
+ this.persistCache();
65
+ }
66
+ registerIdentityDocument(identityDocument) {
67
+ const agentId = identityDocument.agent_id;
68
+ if (typeof agentId !== "string" || !agentId.trim()) {
69
+ throw validationError("Identity document missing agent_id");
70
+ }
71
+ this.registry.set(agentId, identityDocument);
72
+ this.cache.set(agentId, {
73
+ identity_document: identityDocument,
74
+ fetched_at: new Date().toISOString()
75
+ });
76
+ this.persistCache();
77
+ }
78
+ async resolve(agentId) {
79
+ if (this.registry.has(agentId)) {
80
+ return this.registry.get(agentId);
81
+ }
82
+ const cached = this.tryCache(agentId);
83
+ if (cached) {
84
+ return cached;
85
+ }
86
+ const wellKnown = await this.tryWellKnown(agentId);
87
+ if (wellKnown) {
88
+ this.cacheIdentity(agentId, wellKnown);
89
+ return wellKnown;
90
+ }
91
+ const relayLookup = await this.tryHintLookups(this.relay_hints, agentId);
92
+ if (relayLookup) {
93
+ this.cacheIdentity(agentId, relayLookup);
94
+ return relayLookup;
95
+ }
96
+ const directoryLookup = await this.tryHintLookups(this.enterprise_directory_hints, agentId);
97
+ if (directoryLookup) {
98
+ this.cacheIdentity(agentId, directoryLookup);
99
+ return directoryLookup;
100
+ }
101
+ throw discoveryError(`Unable to resolve identity document for ${agentId}`);
102
+ }
103
+ async resolveWellKnown(baseUrl, expectedAgentId) {
104
+ const wellKnownUrl = wellKnownUrlFromBase(baseUrl);
105
+ const resolved = await this.resolveWellKnownUrl(wellKnownUrl, expectedAgentId);
106
+ if (!resolved) {
107
+ throw discoveryError(`Unable to resolve well-known metadata from ${wellKnownUrl}`);
108
+ }
109
+ const identityDocument = toJsonMap(resolved.identity_document);
110
+ const agentId = identityDocument.agent_id;
111
+ if (typeof agentId !== "string" || !agentId.trim()) {
112
+ throw discoveryError("Well-known discovery returned identity document without agent_id");
113
+ }
114
+ this.cacheIdentity(agentId, identityDocument);
115
+ return {
116
+ ...resolved,
117
+ well_known_url: wellKnownUrl
118
+ };
119
+ }
120
+ tryCache(agentId) {
121
+ const cached = this.cache.get(agentId);
122
+ if (!cached) {
123
+ return undefined;
124
+ }
125
+ if (cacheValid(cached.identity_document)) {
126
+ return cached.identity_document;
127
+ }
128
+ this.cache.delete(agentId);
129
+ this.persistCache();
130
+ return undefined;
131
+ }
132
+ async tryWellKnown(agentId) {
133
+ const parts = parseAgentId(agentId);
134
+ if (!parts.domain) {
135
+ return undefined;
136
+ }
137
+ const wellKnownUrl = `${this.default_scheme}://${parts.domain}/.well-known/acp`;
138
+ const resolved = await this.resolveWellKnownUrl(wellKnownUrl, agentId);
139
+ if (!resolved) {
140
+ return undefined;
141
+ }
142
+ return toJsonMap(resolved.identity_document);
143
+ }
144
+ async tryHintLookups(hints, agentId) {
145
+ for (const hint of hints) {
146
+ const url = `${hint.replace(/\/+$/, "")}/discover`;
147
+ const body = await this.fetchJson(url, [["agent_id", agentId]], "Discovery hint lookup");
148
+ if (!body) {
149
+ continue;
150
+ }
151
+ const identityDocument = extractIdentityDocument(body);
152
+ if (identityDocument && verifyIdentityDocument(identityDocument)) {
153
+ return identityDocument;
154
+ }
155
+ }
156
+ return undefined;
157
+ }
158
+ async resolveWellKnownUrl(wellKnownUrl, expectedAgentId) {
159
+ const body = await this.fetchJson(wellKnownUrl, undefined, "Discovery .well-known lookup");
160
+ if (!body) {
161
+ return undefined;
162
+ }
163
+ let wellKnown;
164
+ try {
165
+ wellKnown = parseWellKnownDocument(body);
166
+ }
167
+ catch {
168
+ return undefined;
169
+ }
170
+ if (expectedAgentId && wellKnown.agent_id !== expectedAgentId) {
171
+ return undefined;
172
+ }
173
+ const identityReference = resolveIdentityDocumentReference(wellKnown, wellKnownUrl);
174
+ const identityBody = await this.fetchJson(identityReference, undefined, "Discovery identity document lookup");
175
+ if (!identityBody) {
176
+ return undefined;
177
+ }
178
+ const identityDocument = extractIdentityDocument(identityBody);
179
+ if (!identityDocument || !verifyIdentityDocument(identityDocument)) {
180
+ return undefined;
181
+ }
182
+ if (expectedAgentId && identityDocument.agent_id !== expectedAgentId) {
183
+ return undefined;
184
+ }
185
+ return {
186
+ well_known: wellKnown,
187
+ identity_document: identityDocument
188
+ };
189
+ }
190
+ async fetchJson(rawUrl, query, context) {
191
+ const parsed = validateHttpUrl(rawUrl, this.policy.allow_insecure_http, this.policy.mtls_enabled, context);
192
+ if (query && query.length > 0) {
193
+ for (const [key, value] of query) {
194
+ parsed.searchParams.set(key, value);
195
+ }
196
+ }
197
+ const controller = new AbortController();
198
+ const timeout = setTimeout(() => controller.abort(), Math.max(1, this.timeout_seconds) * 1000);
199
+ try {
200
+ const response = await fetch(parsed.toString(), {
201
+ method: "GET",
202
+ signal: controller.signal,
203
+ ...this.fetchOptions
204
+ });
205
+ if (response.status !== 200) {
206
+ return undefined;
207
+ }
208
+ const rawBody = await response.text();
209
+ return parseJsonMap(rawBody);
210
+ }
211
+ catch {
212
+ return undefined;
213
+ }
214
+ finally {
215
+ clearTimeout(timeout);
216
+ }
217
+ }
218
+ cacheIdentity(agentId, identityDocument) {
219
+ this.cache.set(agentId, {
220
+ identity_document: identityDocument,
221
+ fetched_at: new Date().toISOString()
222
+ });
223
+ this.persistCache();
224
+ }
225
+ loadCache() {
226
+ if (!this.cache_path || !existsSync(this.cache_path)) {
227
+ return;
228
+ }
229
+ try {
230
+ const raw = parseJsonMap(readFileSync(this.cache_path, "utf-8"));
231
+ for (const [agentId, value] of Object.entries(raw)) {
232
+ if (value && typeof value === "object" && !Array.isArray(value)) {
233
+ const entry = value;
234
+ const identityDocument = extractIdentityDocument(entry);
235
+ if (identityDocument) {
236
+ this.cache.set(agentId, {
237
+ identity_document: identityDocument,
238
+ fetched_at: typeof entry.fetched_at === "string" ? entry.fetched_at : new Date().toISOString()
239
+ });
240
+ }
241
+ }
242
+ }
243
+ }
244
+ catch {
245
+ // Invalid cache is ignored.
246
+ }
247
+ }
248
+ persistCache() {
249
+ if (!this.cache_path) {
250
+ return;
251
+ }
252
+ try {
253
+ mkdirSync(dirname(this.cache_path), { recursive: true });
254
+ const serialized = {};
255
+ for (const [agentId, entry] of this.cache.entries()) {
256
+ serialized[agentId] = {
257
+ identity_document: entry.identity_document,
258
+ fetched_at: entry.fetched_at
259
+ };
260
+ }
261
+ writeFileSync(this.cache_path, JSON.stringify(serialized, null, 2), "utf-8");
262
+ }
263
+ catch {
264
+ // Cache persistence failures are tolerated.
265
+ }
266
+ }
267
+ }
@@ -0,0 +1,13 @@
1
+ export type FailReason = "UNSUPPORTED_VERSION" | "UNSUPPORTED_CRYPTO_SUITE" | "UNSUPPORTED_PROFILE" | "INVALID_SIGNATURE" | "EXPIRED_MESSAGE" | "POLICY_REJECTED";
2
+ export declare class AcpError extends Error {
3
+ readonly code: "INVALID_ARGUMENT" | "VALIDATION" | "DISCOVERY" | "TRANSPORT" | "CRYPTO" | "PROCESSING" | "KEY_PROVIDER";
4
+ readonly reason?: FailReason;
5
+ constructor(code: "INVALID_ARGUMENT" | "VALIDATION" | "DISCOVERY" | "TRANSPORT" | "CRYPTO" | "PROCESSING" | "KEY_PROVIDER", message: string, reason?: FailReason);
6
+ }
7
+ export declare function invalidArgument(message: string): AcpError;
8
+ export declare function validationError(message: string): AcpError;
9
+ export declare function discoveryError(message: string): AcpError;
10
+ export declare function transportError(message: string): AcpError;
11
+ export declare function cryptoError(message: string): AcpError;
12
+ export declare function processingError(reason: FailReason, detail: string): AcpError;
13
+ export declare function keyProviderError(message: string): AcpError;
package/dist/errors.js ADDED
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Copyright 2026 ACP Project
3
+ * Licensed under the Apache License, Version 2.0
4
+ * See LICENSE file for details.
5
+ */
6
+ export class AcpError extends Error {
7
+ code;
8
+ reason;
9
+ constructor(code, message, reason) {
10
+ super(message);
11
+ this.name = "AcpError";
12
+ this.code = code;
13
+ this.reason = reason;
14
+ }
15
+ }
16
+ export function invalidArgument(message) {
17
+ return new AcpError("INVALID_ARGUMENT", message);
18
+ }
19
+ export function validationError(message) {
20
+ return new AcpError("VALIDATION", message);
21
+ }
22
+ export function discoveryError(message) {
23
+ return new AcpError("DISCOVERY", message);
24
+ }
25
+ export function transportError(message) {
26
+ return new AcpError("TRANSPORT", message);
27
+ }
28
+ export function cryptoError(message) {
29
+ return new AcpError("CRYPTO", message);
30
+ }
31
+ export function processingError(reason, detail) {
32
+ return new AcpError("PROCESSING", detail, reason);
33
+ }
34
+ export function keyProviderError(message) {
35
+ return new AcpError("KEY_PROVIDER", message);
36
+ }
@@ -0,0 +1,15 @@
1
+ import { Agent as UndiciAgent } from "undici";
2
+ export interface HttpSecurityPolicy {
3
+ allow_insecure_http: boolean;
4
+ allow_insecure_tls: boolean;
5
+ mtls_enabled: boolean;
6
+ ca_file?: string | null;
7
+ cert_file?: string | null;
8
+ key_file?: string | null;
9
+ }
10
+ export declare function validateHttpUrl(rawUrl: string, allowInsecureHttp: boolean, mtlsEnabled: boolean, context: string): URL;
11
+ export declare function validateHttpClientPolicy(policy: HttpSecurityPolicy, context: string): void;
12
+ export declare function buildFetchOptions(policy: HttpSecurityPolicy): {
13
+ dispatcher?: UndiciAgent;
14
+ };
15
+ export declare function warnIfInsecureHttpUsed(endpoint: string, context: string): void;
@@ -0,0 +1,83 @@
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, statSync } from "node:fs";
7
+ import { Agent as UndiciAgent } from "undici";
8
+ import { validationError } from "./errors.js";
9
+ function normalizeOptionalPath(value) {
10
+ if (!value || !value.trim()) {
11
+ return undefined;
12
+ }
13
+ const normalized = value.trim();
14
+ const stats = statSync(normalized, { throwIfNoEntry: false });
15
+ if (!stats || !stats.isFile()) {
16
+ throw validationError(`configured file does not exist or is not a file: ${normalized}`);
17
+ }
18
+ return normalized;
19
+ }
20
+ export function validateHttpUrl(rawUrl, allowInsecureHttp, mtlsEnabled, context) {
21
+ let parsed;
22
+ try {
23
+ parsed = new URL(rawUrl);
24
+ }
25
+ catch (error) {
26
+ throw validationError(`${context} has invalid URL: ${String(error)}`);
27
+ }
28
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
29
+ throw validationError(`${context} requires an http(s) URL, got: ${rawUrl}`);
30
+ }
31
+ if (!parsed.hostname.trim()) {
32
+ throw validationError(`${context} URL is missing host: ${rawUrl}`);
33
+ }
34
+ if (parsed.protocol === "http:" && mtlsEnabled) {
35
+ throw validationError(`${context} cannot use HTTP (${rawUrl}) when mtls_enabled=true. Use https:// endpoints.`);
36
+ }
37
+ if (parsed.protocol === "http:" && !allowInsecureHttp) {
38
+ throw validationError(`${context} uses insecure HTTP (${rawUrl}). Set allow_insecure_http=true only for local/dev/demo workflows.`);
39
+ }
40
+ return parsed;
41
+ }
42
+ export function validateHttpClientPolicy(policy, context) {
43
+ const certFile = normalizeOptionalPath(policy.cert_file);
44
+ const keyFile = normalizeOptionalPath(policy.key_file);
45
+ normalizeOptionalPath(policy.ca_file);
46
+ if (policy.mtls_enabled) {
47
+ if (!certFile) {
48
+ throw validationError(`${context} requires cert_file when mtls_enabled=true`);
49
+ }
50
+ if (!keyFile) {
51
+ throw validationError(`${context} requires key_file when mtls_enabled=true`);
52
+ }
53
+ }
54
+ else if ((certFile && !keyFile) || (!certFile && keyFile)) {
55
+ throw validationError(`${context} requires both cert_file and key_file when either is configured`);
56
+ }
57
+ }
58
+ export function buildFetchOptions(policy) {
59
+ validateHttpClientPolicy(policy, "HTTP client configuration");
60
+ if (!policy.allow_insecure_tls &&
61
+ !policy.ca_file &&
62
+ !policy.cert_file &&
63
+ !policy.key_file &&
64
+ !policy.mtls_enabled) {
65
+ return {};
66
+ }
67
+ const agent = new UndiciAgent({
68
+ connect: {
69
+ rejectUnauthorized: !policy.allow_insecure_tls,
70
+ ca: policy.ca_file ? readFileSync(policy.ca_file, "utf-8") : undefined,
71
+ cert: policy.cert_file ? readFileSync(policy.cert_file, "utf-8") : undefined,
72
+ key: policy.key_file ? readFileSync(policy.key_file, "utf-8") : undefined
73
+ }
74
+ });
75
+ return { dispatcher: agent };
76
+ }
77
+ export function warnIfInsecureHttpUsed(endpoint, context) {
78
+ if (endpoint.startsWith("http://")) {
79
+ // Keeps visibility similar to Python SDK warnings.
80
+ // eslint-disable-next-line no-console
81
+ console.warn(`${context} is using insecure HTTP (${endpoint}) because allow_insecure_http=true`);
82
+ }
83
+ }
@@ -0,0 +1,45 @@
1
+ import { JsonMap } from "./jsonSupport.js";
2
+ export interface AgentIdentity {
3
+ agent_id: string;
4
+ signing_private_key: string;
5
+ signing_public_key: string;
6
+ encryption_private_key: string;
7
+ encryption_public_key: string;
8
+ signing_kid: string;
9
+ encryption_kid: string;
10
+ }
11
+ export interface AgentIdParts {
12
+ name: string;
13
+ domain?: string;
14
+ }
15
+ export interface IdentityBundle {
16
+ identity: AgentIdentity;
17
+ identity_document: JsonMap;
18
+ }
19
+ export declare function parseAgentId(agentId: string): AgentIdParts;
20
+ export declare function sanitizeAgentId(agentId: string): string;
21
+ export declare function createIdentity(agentId: string): AgentIdentity;
22
+ export declare function buildIdentityDocument(input: {
23
+ identity: AgentIdentity;
24
+ direct_endpoint?: string;
25
+ relay_hints: string[];
26
+ trust_profile: string;
27
+ capabilities?: JsonMap;
28
+ valid_days: number;
29
+ amqp_service?: JsonMap;
30
+ mqtt_service?: JsonMap;
31
+ http_security_profile?: string;
32
+ relay_security_profile?: string;
33
+ }): JsonMap;
34
+ export declare function verifyIdentityDocument(identityDocument: JsonMap): boolean;
35
+ export declare function writeIdentity(storageDir: string, identity: AgentIdentity, identityDocument: JsonMap): void;
36
+ export declare function readIdentity(storageDir: string, agentId: string): IdentityBundle | undefined;
37
+ export declare function identityFromProvider(input: {
38
+ agent_id: string;
39
+ signing_private_key: string;
40
+ encryption_private_key: string;
41
+ signing_public_key?: string;
42
+ encryption_public_key?: string;
43
+ signing_kid?: string;
44
+ encryption_kid?: string;
45
+ }): AgentIdentity;
@@ -0,0 +1,163 @@
1
+ /*
2
+ * Copyright 2026 ACP Project
3
+ * Licensed under the Apache License, Version 2.0
4
+ * See LICENSE file for details.
5
+ */
6
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { randomUUID } from "node:crypto";
9
+ import { ACP_IDENTITY_VERSION, isSupportedTrustProfile } from "./constants.js";
10
+ import { ed25519PublicFromPrivate, generateEd25519Keypair, generateX25519Keypair, signBytes, verifySignature, x25519PublicFromPrivate } from "./crypto.js";
11
+ import { canonicalJsonBytes, parseJsonMap } from "./jsonSupport.js";
12
+ import { validationError } from "./errors.js";
13
+ const IDENTITY_FILE_NAME = "identity.json";
14
+ const IDENTITY_DOC_FILE_NAME = "identity_document.json";
15
+ export function parseAgentId(agentId) {
16
+ const match = /^agent:(?<name>[^@]+)(?:@(?<domain>.+))?$/.exec(agentId);
17
+ if (!match?.groups?.name) {
18
+ throw validationError(`Invalid agent identifier: ${agentId}`);
19
+ }
20
+ return {
21
+ name: match.groups.name,
22
+ domain: match.groups.domain || undefined
23
+ };
24
+ }
25
+ export function sanitizeAgentId(agentId) {
26
+ return agentId
27
+ .split("")
28
+ .map((ch) => (/^[A-Za-z0-9._-]$/.test(ch) ? ch : "_"))
29
+ .join("");
30
+ }
31
+ function identityPath(storageDir, agentId) {
32
+ return join(storageDir, sanitizeAgentId(agentId));
33
+ }
34
+ export function createIdentity(agentId) {
35
+ parseAgentId(agentId);
36
+ const signing = generateEd25519Keypair();
37
+ const encryption = generateX25519Keypair();
38
+ return {
39
+ agent_id: agentId,
40
+ signing_private_key: signing.private_key,
41
+ signing_public_key: signing.public_key,
42
+ encryption_private_key: encryption.private_key,
43
+ encryption_public_key: encryption.public_key,
44
+ signing_kid: `sig-${randomUUID().replace(/-/g, "").slice(0, 12)}`,
45
+ encryption_kid: `enc-${randomUUID().replace(/-/g, "").slice(0, 12)}`
46
+ };
47
+ }
48
+ export function buildIdentityDocument(input) {
49
+ if (!isSupportedTrustProfile(input.trust_profile)) {
50
+ throw validationError(`Unsupported trust profile: ${input.trust_profile}`);
51
+ }
52
+ const now = new Date();
53
+ const validUntil = new Date(now.getTime() + Math.max(1, input.valid_days) * 24 * 60 * 60 * 1000);
54
+ const service = {
55
+ direct_endpoint: input.direct_endpoint ?? null,
56
+ relay_hints: input.relay_hints
57
+ };
58
+ if (input.amqp_service) {
59
+ service.amqp = input.amqp_service;
60
+ }
61
+ if (input.mqtt_service) {
62
+ service.mqtt = input.mqtt_service;
63
+ }
64
+ if (input.direct_endpoint && input.http_security_profile) {
65
+ service.http = {
66
+ endpoint: input.direct_endpoint,
67
+ security_profile: input.http_security_profile
68
+ };
69
+ }
70
+ if (input.relay_hints[0] && input.relay_security_profile) {
71
+ service.relay = {
72
+ endpoint: input.relay_hints[0],
73
+ security_profile: input.relay_security_profile
74
+ };
75
+ }
76
+ const document = {
77
+ acp_identity_version: ACP_IDENTITY_VERSION,
78
+ agent_id: input.identity.agent_id,
79
+ created_at: now.toISOString(),
80
+ valid_until: validUntil.toISOString(),
81
+ trust_profile: input.trust_profile,
82
+ keys: {
83
+ signing: {
84
+ kid: input.identity.signing_kid,
85
+ alg: "Ed25519",
86
+ public_key: input.identity.signing_public_key
87
+ },
88
+ encryption: {
89
+ kid: input.identity.encryption_kid,
90
+ alg: "X25519",
91
+ public_key: input.identity.encryption_public_key
92
+ }
93
+ },
94
+ service,
95
+ capabilities: input.capabilities ?? {}
96
+ };
97
+ const signature = signBytes(canonicalJsonBytes(document), input.identity.signing_private_key);
98
+ document.signature = {
99
+ algorithm: "Ed25519",
100
+ signed_by: input.identity.signing_kid,
101
+ value: signature
102
+ };
103
+ return document;
104
+ }
105
+ export function verifyIdentityDocument(identityDocument) {
106
+ for (const field of ["agent_id", "keys", "service", "signature", "valid_until"]) {
107
+ if (!(field in identityDocument)) {
108
+ return false;
109
+ }
110
+ }
111
+ if (typeof identityDocument.trust_profile !== "string" ||
112
+ !isSupportedTrustProfile(identityDocument.trust_profile)) {
113
+ return false;
114
+ }
115
+ if (typeof identityDocument.valid_until !== "string") {
116
+ return false;
117
+ }
118
+ const expires = Date.parse(identityDocument.valid_until);
119
+ if (Number.isNaN(expires) || expires <= Date.now()) {
120
+ return false;
121
+ }
122
+ const signature = identityDocument.signature;
123
+ const signingPublicKey = (identityDocument.keys.signing.public_key ??
124
+ "")
125
+ .trim();
126
+ const signatureValue = (signature?.value ?? "").trim();
127
+ if (!signingPublicKey || !signatureValue) {
128
+ return false;
129
+ }
130
+ const unsigned = { ...identityDocument };
131
+ delete unsigned.signature;
132
+ return verifySignature(canonicalJsonBytes(unsigned), signatureValue, signingPublicKey);
133
+ }
134
+ export function writeIdentity(storageDir, identity, identityDocument) {
135
+ const path = identityPath(storageDir, identity.agent_id);
136
+ mkdirSync(path, { recursive: true });
137
+ writeFileSync(join(path, IDENTITY_FILE_NAME), JSON.stringify(identity, null, 2), "utf-8");
138
+ writeFileSync(join(path, IDENTITY_DOC_FILE_NAME), JSON.stringify(identityDocument, null, 2), "utf-8");
139
+ }
140
+ export function readIdentity(storageDir, agentId) {
141
+ const path = identityPath(storageDir, agentId);
142
+ try {
143
+ const identity = JSON.parse(readFileSync(join(path, IDENTITY_FILE_NAME), "utf-8"));
144
+ const identityDocument = parseJsonMap(readFileSync(join(path, IDENTITY_DOC_FILE_NAME), "utf-8"));
145
+ return { identity, identity_document: identityDocument };
146
+ }
147
+ catch {
148
+ return undefined;
149
+ }
150
+ }
151
+ export function identityFromProvider(input) {
152
+ const signingPublicKey = input.signing_public_key ?? ed25519PublicFromPrivate(input.signing_private_key);
153
+ const encryptionPublicKey = input.encryption_public_key ?? x25519PublicFromPrivate(input.encryption_private_key);
154
+ return {
155
+ agent_id: input.agent_id,
156
+ signing_private_key: input.signing_private_key,
157
+ signing_public_key: signingPublicKey,
158
+ encryption_private_key: input.encryption_private_key,
159
+ encryption_public_key: encryptionPublicKey,
160
+ signing_kid: input.signing_kid ?? `sig-${randomUUID().replace(/-/g, "").slice(0, 12)}`,
161
+ encryption_kid: input.encryption_kid ?? `enc-${randomUUID().replace(/-/g, "").slice(0, 12)}`
162
+ };
163
+ }
@@ -0,0 +1,18 @@
1
+ export * from "./agent.js";
2
+ export * from "./amqpTransport.js";
3
+ export * from "./capabilities.js";
4
+ export * from "./constants.js";
5
+ export * from "./crypto.js";
6
+ export * from "./discovery.js";
7
+ export * from "./errors.js";
8
+ export * from "./httpSecurity.js";
9
+ export * from "./identity.js";
10
+ export * from "./jsonSupport.js";
11
+ export * from "./keyProvider.js";
12
+ export * from "./messages.js";
13
+ export * from "./mqttTransport.js";
14
+ export * from "./options.js";
15
+ export * from "./overlay.js";
16
+ export * from "./overlayFramework.js";
17
+ export * from "./transport.js";
18
+ export * from "./wellKnown.js";
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export * from "./agent.js";
2
+ export * from "./amqpTransport.js";
3
+ export * from "./capabilities.js";
4
+ export * from "./constants.js";
5
+ export * from "./crypto.js";
6
+ export * from "./discovery.js";
7
+ export * from "./errors.js";
8
+ export * from "./httpSecurity.js";
9
+ export * from "./identity.js";
10
+ export * from "./jsonSupport.js";
11
+ export * from "./keyProvider.js";
12
+ export * from "./messages.js";
13
+ export * from "./mqttTransport.js";
14
+ export * from "./options.js";
15
+ export * from "./overlay.js";
16
+ export * from "./overlayFramework.js";
17
+ export * from "./transport.js";
18
+ export * from "./wellKnown.js";
@@ -0,0 +1,8 @@
1
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
2
+ [key: string]: JsonValue;
3
+ };
4
+ export type JsonMap = Record<string, JsonValue>;
5
+ export declare function canonicalJsonString(value: JsonValue): string;
6
+ export declare function canonicalJsonBytes(value: JsonValue): Uint8Array;
7
+ export declare function parseJsonMap(raw: string): JsonMap;
8
+ export declare function toJsonMap(value: unknown): JsonMap;