@hai.ai/jacs 0.6.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/README.md +237 -0
- package/http.d.ts +13 -0
- package/http.js +154 -0
- package/index.d.ts +178 -0
- package/index.js +343 -0
- package/jacs.darwin-arm64.node +0 -0
- package/jacs.darwin-x64.node +0 -0
- package/jacs.linux-arm-gnueabihf.node +0 -0
- package/jacs.linux-arm-musleabihf.node +0 -0
- package/jacs.linux-arm64-gnu.node +0 -0
- package/jacs.linux-x64-gnu.node +0 -0
- package/jacs.linux-x64-musl.node +0 -0
- package/mcp.d.ts +61 -0
- package/mcp.js +514 -0
- package/mcp.js.map +1 -0
- package/mcp.ts +521 -0
- package/package.json +103 -0
- package/simple.d.ts +573 -0
- package/simple.js +920 -0
- package/src/a2a.d.ts +79 -0
- package/src/a2a.js +617 -0
package/src/a2a.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export const A2A_PROTOCOL_VERSION: string;
|
|
2
|
+
export const JACS_EXTENSION_URI: string;
|
|
3
|
+
|
|
4
|
+
export class A2AAgentInterface {
|
|
5
|
+
constructor(url: string, protocolBinding: string, tenant?: string | null);
|
|
6
|
+
url: string;
|
|
7
|
+
protocolBinding: string;
|
|
8
|
+
tenant?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class A2AAgentSkill {
|
|
12
|
+
constructor(opts: {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
tags: string[];
|
|
17
|
+
examples?: string[] | null;
|
|
18
|
+
inputModes?: string[] | null;
|
|
19
|
+
outputModes?: string[] | null;
|
|
20
|
+
security?: unknown[] | null;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class A2AAgentExtension {
|
|
25
|
+
constructor(uri: string, description?: string | null, required?: boolean | null);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class A2AAgentCapabilities {
|
|
29
|
+
constructor(opts?: {
|
|
30
|
+
streaming?: boolean | null;
|
|
31
|
+
pushNotifications?: boolean | null;
|
|
32
|
+
extendedAgentCard?: boolean | null;
|
|
33
|
+
extensions?: A2AAgentExtension[] | null;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class A2AAgentCardSignature {
|
|
38
|
+
constructor(jws: string, keyId?: string | null);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class A2AAgentCard {
|
|
42
|
+
constructor(opts: {
|
|
43
|
+
name: string;
|
|
44
|
+
description: string;
|
|
45
|
+
version: string;
|
|
46
|
+
protocolVersions: string[];
|
|
47
|
+
supportedInterfaces: A2AAgentInterface[];
|
|
48
|
+
defaultInputModes: string[];
|
|
49
|
+
defaultOutputModes: string[];
|
|
50
|
+
capabilities: A2AAgentCapabilities;
|
|
51
|
+
skills: A2AAgentSkill[];
|
|
52
|
+
provider?: unknown;
|
|
53
|
+
documentationUrl?: string | null;
|
|
54
|
+
iconUrl?: string | null;
|
|
55
|
+
securitySchemes?: Record<string, unknown> | null;
|
|
56
|
+
security?: unknown[] | null;
|
|
57
|
+
signatures?: A2AAgentCardSignature[] | null;
|
|
58
|
+
metadata?: Record<string, unknown> | null;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class JACSA2AIntegration {
|
|
63
|
+
constructor(jacsConfigPath?: string | null);
|
|
64
|
+
exportAgentCard(agentData: Record<string, unknown>): A2AAgentCard;
|
|
65
|
+
createExtensionDescriptor(): Record<string, unknown>;
|
|
66
|
+
wrapArtifactWithProvenance(
|
|
67
|
+
artifact: Record<string, unknown>,
|
|
68
|
+
artifactType: string,
|
|
69
|
+
parentSignatures?: Record<string, unknown>[] | null,
|
|
70
|
+
): Record<string, unknown>;
|
|
71
|
+
verifyWrappedArtifact(wrappedArtifact: Record<string, unknown>): Record<string, unknown>;
|
|
72
|
+
createChainOfCustody(artifacts: Record<string, unknown>[]): Record<string, unknown>;
|
|
73
|
+
generateWellKnownDocuments(
|
|
74
|
+
agentCard: A2AAgentCard,
|
|
75
|
+
jwsSignature: string,
|
|
76
|
+
publicKeyB64: string,
|
|
77
|
+
agentData: Record<string, unknown>,
|
|
78
|
+
): Record<string, unknown>;
|
|
79
|
+
}
|
package/src/a2a.js
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JACS A2A (Agent-to-Agent) Protocol Integration for Node.js
|
|
3
|
+
*
|
|
4
|
+
* This module provides Node.js bindings for JACS's A2A protocol integration,
|
|
5
|
+
* enabling JACS agents to participate in the Agent-to-Agent communication protocol.
|
|
6
|
+
*
|
|
7
|
+
* Implements A2A protocol v0.4.0 (September 2025).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { v4: uuidv4 } = require('uuid');
|
|
11
|
+
const { createPublicKey } = require('crypto');
|
|
12
|
+
const jacs = require('../index');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A2A protocol version (v0.4.0)
|
|
16
|
+
*/
|
|
17
|
+
const A2A_PROTOCOL_VERSION = '0.4.0';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* JACS extension URI for A2A
|
|
21
|
+
*/
|
|
22
|
+
const JACS_EXTENSION_URI = 'urn:hai.ai:jacs-provenance-v1';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A2A Agent Interface (v0.4.0)
|
|
26
|
+
*/
|
|
27
|
+
class A2AAgentInterface {
|
|
28
|
+
constructor(url, protocolBinding, tenant = null) {
|
|
29
|
+
this.url = url;
|
|
30
|
+
this.protocolBinding = protocolBinding;
|
|
31
|
+
if (tenant) {
|
|
32
|
+
this.tenant = tenant;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A2A Agent Skill (v0.4.0)
|
|
39
|
+
*/
|
|
40
|
+
class A2AAgentSkill {
|
|
41
|
+
constructor({ id, name, description, tags, examples = null, inputModes = null, outputModes = null, security = null }) {
|
|
42
|
+
this.id = id;
|
|
43
|
+
this.name = name;
|
|
44
|
+
this.description = description;
|
|
45
|
+
this.tags = tags;
|
|
46
|
+
if (examples) this.examples = examples;
|
|
47
|
+
if (inputModes) this.inputModes = inputModes;
|
|
48
|
+
if (outputModes) this.outputModes = outputModes;
|
|
49
|
+
if (security) this.security = security;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A2A Agent Extension (v0.4.0)
|
|
55
|
+
*/
|
|
56
|
+
class A2AAgentExtension {
|
|
57
|
+
constructor(uri, description = null, required = null) {
|
|
58
|
+
this.uri = uri;
|
|
59
|
+
if (description !== null) this.description = description;
|
|
60
|
+
if (required !== null) this.required = required;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A2A Agent Capabilities (v0.4.0)
|
|
66
|
+
*/
|
|
67
|
+
class A2AAgentCapabilities {
|
|
68
|
+
constructor({ streaming = null, pushNotifications = null, extendedAgentCard = null, extensions = null } = {}) {
|
|
69
|
+
if (streaming !== null) this.streaming = streaming;
|
|
70
|
+
if (pushNotifications !== null) this.pushNotifications = pushNotifications;
|
|
71
|
+
if (extendedAgentCard !== null) this.extendedAgentCard = extendedAgentCard;
|
|
72
|
+
if (extensions) this.extensions = extensions;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A2A Agent Card Signature (v0.4.0)
|
|
78
|
+
*/
|
|
79
|
+
class A2AAgentCardSignature {
|
|
80
|
+
constructor(jws, keyId = null) {
|
|
81
|
+
this.jws = jws;
|
|
82
|
+
if (keyId) this.keyId = keyId;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A2A Agent Card (v0.4.0)
|
|
88
|
+
*
|
|
89
|
+
* Published at /.well-known/agent-card.json for zero-config discovery.
|
|
90
|
+
*/
|
|
91
|
+
class A2AAgentCard {
|
|
92
|
+
constructor({
|
|
93
|
+
name,
|
|
94
|
+
description,
|
|
95
|
+
version,
|
|
96
|
+
protocolVersions,
|
|
97
|
+
supportedInterfaces,
|
|
98
|
+
defaultInputModes,
|
|
99
|
+
defaultOutputModes,
|
|
100
|
+
capabilities,
|
|
101
|
+
skills,
|
|
102
|
+
provider = null,
|
|
103
|
+
documentationUrl = null,
|
|
104
|
+
iconUrl = null,
|
|
105
|
+
securitySchemes = null,
|
|
106
|
+
security = null,
|
|
107
|
+
signatures = null,
|
|
108
|
+
metadata = null
|
|
109
|
+
}) {
|
|
110
|
+
this.name = name;
|
|
111
|
+
this.description = description;
|
|
112
|
+
this.version = version;
|
|
113
|
+
this.protocolVersions = protocolVersions;
|
|
114
|
+
this.supportedInterfaces = supportedInterfaces;
|
|
115
|
+
this.defaultInputModes = defaultInputModes;
|
|
116
|
+
this.defaultOutputModes = defaultOutputModes;
|
|
117
|
+
this.capabilities = capabilities;
|
|
118
|
+
this.skills = skills;
|
|
119
|
+
if (provider) this.provider = provider;
|
|
120
|
+
if (documentationUrl) this.documentationUrl = documentationUrl;
|
|
121
|
+
if (iconUrl) this.iconUrl = iconUrl;
|
|
122
|
+
if (securitySchemes) this.securitySchemes = securitySchemes;
|
|
123
|
+
if (security) this.security = security;
|
|
124
|
+
if (signatures) this.signatures = signatures;
|
|
125
|
+
if (metadata) this.metadata = metadata;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* JACS A2A Integration class (v0.4.0)
|
|
131
|
+
*/
|
|
132
|
+
class JACSA2AIntegration {
|
|
133
|
+
constructor(jacsConfigPath = null) {
|
|
134
|
+
if (jacsConfigPath) {
|
|
135
|
+
jacs.load(jacsConfigPath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Export a JACS agent as an A2A Agent Card (v0.4.0)
|
|
141
|
+
* @param {Object} agentData - JACS agent data
|
|
142
|
+
* @returns {A2AAgentCard} A2A Agent Card
|
|
143
|
+
*/
|
|
144
|
+
exportAgentCard(agentData) {
|
|
145
|
+
const agentId = agentData.jacsId || 'unknown';
|
|
146
|
+
const agentName = agentData.jacsName || 'Unnamed JACS Agent';
|
|
147
|
+
const agentDescription = agentData.jacsDescription || 'JACS-enabled agent';
|
|
148
|
+
const agentVersion = agentData.jacsVersion || '1';
|
|
149
|
+
|
|
150
|
+
// Build supported interfaces from jacsAgentDomain or agent ID
|
|
151
|
+
const domain = agentData.jacsAgentDomain;
|
|
152
|
+
const baseUrl = domain
|
|
153
|
+
? `https://${domain}/agent/${agentId}`
|
|
154
|
+
: `https://agent-${agentId}.example.com`;
|
|
155
|
+
|
|
156
|
+
const supportedInterfaces = [
|
|
157
|
+
new A2AAgentInterface(baseUrl, 'jsonrpc')
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
// Convert JACS services to A2A skills
|
|
161
|
+
const skills = this._convertServicesToSkills(agentData.jacsServices || []);
|
|
162
|
+
|
|
163
|
+
// Define security schemes as a keyed map
|
|
164
|
+
const securitySchemes = {
|
|
165
|
+
'bearer-jwt': {
|
|
166
|
+
type: 'http',
|
|
167
|
+
scheme: 'Bearer',
|
|
168
|
+
bearerFormat: 'JWT'
|
|
169
|
+
},
|
|
170
|
+
'api-key': {
|
|
171
|
+
type: 'apiKey',
|
|
172
|
+
in: 'header',
|
|
173
|
+
name: 'X-API-Key'
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Create JACS extension
|
|
178
|
+
const jacsExtension = new A2AAgentExtension(
|
|
179
|
+
JACS_EXTENSION_URI,
|
|
180
|
+
'JACS cryptographic document signing and verification',
|
|
181
|
+
false
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const capabilities = new A2AAgentCapabilities({
|
|
185
|
+
extensions: [jacsExtension]
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Create metadata
|
|
189
|
+
const metadata = {
|
|
190
|
+
jacsAgentType: agentData.jacsAgentType,
|
|
191
|
+
jacsId: agentId,
|
|
192
|
+
jacsVersion: agentData.jacsVersion
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return new A2AAgentCard({
|
|
196
|
+
name: agentName,
|
|
197
|
+
description: agentDescription,
|
|
198
|
+
version: String(agentVersion),
|
|
199
|
+
protocolVersions: [A2A_PROTOCOL_VERSION],
|
|
200
|
+
supportedInterfaces,
|
|
201
|
+
defaultInputModes: ['text/plain', 'application/json'],
|
|
202
|
+
defaultOutputModes: ['text/plain', 'application/json'],
|
|
203
|
+
capabilities,
|
|
204
|
+
skills,
|
|
205
|
+
securitySchemes,
|
|
206
|
+
metadata
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Convert JACS services to A2A skills (v0.4.0)
|
|
212
|
+
* @private
|
|
213
|
+
*/
|
|
214
|
+
_convertServicesToSkills(services) {
|
|
215
|
+
const skills = [];
|
|
216
|
+
|
|
217
|
+
for (const service of services) {
|
|
218
|
+
const serviceName = service.name || service.serviceDescription || 'unnamed_service';
|
|
219
|
+
const serviceDesc = service.serviceDescription || 'No description';
|
|
220
|
+
|
|
221
|
+
const tools = service.tools || [];
|
|
222
|
+
if (tools.length > 0) {
|
|
223
|
+
for (const tool of tools) {
|
|
224
|
+
if (tool.function) {
|
|
225
|
+
const fnName = tool.function.name || serviceName;
|
|
226
|
+
const fnDesc = tool.function.description || serviceDesc;
|
|
227
|
+
|
|
228
|
+
skills.push(new A2AAgentSkill({
|
|
229
|
+
id: this._slugify(fnName),
|
|
230
|
+
name: fnName,
|
|
231
|
+
description: fnDesc,
|
|
232
|
+
tags: this._deriveTags(serviceName, fnName),
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
skills.push(new A2AAgentSkill({
|
|
238
|
+
id: this._slugify(serviceName),
|
|
239
|
+
name: serviceName,
|
|
240
|
+
description: serviceDesc,
|
|
241
|
+
tags: this._deriveTags(serviceName, serviceName),
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Add default verification skill if none exist
|
|
247
|
+
if (skills.length === 0) {
|
|
248
|
+
skills.push(new A2AAgentSkill({
|
|
249
|
+
id: 'verify-signature',
|
|
250
|
+
name: 'verify_signature',
|
|
251
|
+
description: 'Verify JACS document signatures',
|
|
252
|
+
tags: ['jacs', 'verification', 'cryptography'],
|
|
253
|
+
examples: [
|
|
254
|
+
'Verify a signed JACS document',
|
|
255
|
+
'Check document signature integrity'
|
|
256
|
+
],
|
|
257
|
+
inputModes: ['application/json'],
|
|
258
|
+
outputModes: ['application/json'],
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return skills;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Create JACS extension descriptor for A2A
|
|
267
|
+
* @returns {Object} Extension descriptor
|
|
268
|
+
*/
|
|
269
|
+
createExtensionDescriptor() {
|
|
270
|
+
return {
|
|
271
|
+
uri: JACS_EXTENSION_URI,
|
|
272
|
+
name: 'JACS Document Provenance',
|
|
273
|
+
version: '1.0',
|
|
274
|
+
a2aProtocolVersion: A2A_PROTOCOL_VERSION,
|
|
275
|
+
description: 'Provides cryptographic document signing and verification with post-quantum support',
|
|
276
|
+
specification: 'https://hai.ai/jacs/specs/a2a-extension',
|
|
277
|
+
capabilities: {
|
|
278
|
+
documentSigning: {
|
|
279
|
+
description: 'Sign documents with JACS signatures',
|
|
280
|
+
algorithms: ['dilithium', 'falcon', 'sphincs+', 'rsa', 'ecdsa'],
|
|
281
|
+
formats: ['jacs-v1', 'jws-detached']
|
|
282
|
+
},
|
|
283
|
+
documentVerification: {
|
|
284
|
+
description: 'Verify JACS signatures on documents',
|
|
285
|
+
offlineCapable: true,
|
|
286
|
+
chainOfCustody: true
|
|
287
|
+
},
|
|
288
|
+
postQuantumCrypto: {
|
|
289
|
+
description: 'Support for quantum-resistant signatures',
|
|
290
|
+
algorithms: ['dilithium', 'falcon', 'sphincs+']
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
endpoints: {
|
|
294
|
+
sign: {
|
|
295
|
+
path: '/jacs/sign',
|
|
296
|
+
method: 'POST',
|
|
297
|
+
description: 'Sign a document with JACS'
|
|
298
|
+
},
|
|
299
|
+
verify: {
|
|
300
|
+
path: '/jacs/verify',
|
|
301
|
+
method: 'POST',
|
|
302
|
+
description: 'Verify a JACS signature'
|
|
303
|
+
},
|
|
304
|
+
publicKey: {
|
|
305
|
+
path: '/.well-known/jacs-pubkey.json',
|
|
306
|
+
method: 'GET',
|
|
307
|
+
description: "Retrieve agent's public key"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Wrap an A2A artifact with JACS provenance signature
|
|
315
|
+
* @param {Object} artifact - The A2A artifact to wrap
|
|
316
|
+
* @param {string} artifactType - Type of artifact (e.g., "task", "message")
|
|
317
|
+
* @param {Array} parentSignatures - Optional parent signatures for chain of custody
|
|
318
|
+
* @returns {Object} JACS-wrapped artifact with signature
|
|
319
|
+
*/
|
|
320
|
+
wrapArtifactWithProvenance(artifact, artifactType, parentSignatures = null) {
|
|
321
|
+
const wrapped = {
|
|
322
|
+
jacsId: uuidv4(),
|
|
323
|
+
jacsVersion: uuidv4(),
|
|
324
|
+
jacsType: `a2a-${artifactType}`,
|
|
325
|
+
jacsLevel: 'artifact',
|
|
326
|
+
jacsVersionDate: new Date().toISOString(),
|
|
327
|
+
$schema: 'https://hai.ai/schemas/header/v1/header.schema.json',
|
|
328
|
+
a2aArtifact: artifact
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
if (parentSignatures) {
|
|
332
|
+
wrapped.jacsParentSignatures = parentSignatures;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return jacs.signRequest(wrapped);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Verify a JACS-wrapped A2A artifact
|
|
340
|
+
* @param {Object} wrappedArtifact - The wrapped artifact to verify
|
|
341
|
+
* @returns {Object} Verification result
|
|
342
|
+
*/
|
|
343
|
+
verifyWrappedArtifact(wrappedArtifact) {
|
|
344
|
+
return this._verifyWrappedArtifactInternal(wrappedArtifact, new Set());
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Create a chain of custody document for multi-agent workflows
|
|
349
|
+
* @param {Array} artifacts - List of JACS-wrapped artifacts
|
|
350
|
+
* @returns {Object} Chain of custody document
|
|
351
|
+
*/
|
|
352
|
+
createChainOfCustody(artifacts) {
|
|
353
|
+
const chain = [];
|
|
354
|
+
|
|
355
|
+
for (const artifact of artifacts) {
|
|
356
|
+
if (artifact.jacsSignature) {
|
|
357
|
+
const entry = {
|
|
358
|
+
artifactId: artifact.jacsId,
|
|
359
|
+
artifactType: artifact.jacsType,
|
|
360
|
+
timestamp: artifact.jacsVersionDate,
|
|
361
|
+
agentId: artifact.jacsSignature.agentID,
|
|
362
|
+
agentVersion: artifact.jacsSignature.agentVersion,
|
|
363
|
+
signatureHash: artifact.jacsSignature.publicKeyHash
|
|
364
|
+
};
|
|
365
|
+
chain.push(entry);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
chainOfCustody: chain,
|
|
371
|
+
created: new Date().toISOString(),
|
|
372
|
+
totalArtifacts: chain.length
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Generate .well-known documents for A2A integration (v0.4.0)
|
|
378
|
+
* @param {A2AAgentCard} agentCard - The A2A Agent Card
|
|
379
|
+
* @param {string} jwsSignature - JWS signature of the Agent Card
|
|
380
|
+
* @param {string} publicKeyB64 - Base64-encoded public key
|
|
381
|
+
* @param {Object} agentData - JACS agent data
|
|
382
|
+
* @returns {Object} Map of paths to document contents
|
|
383
|
+
*/
|
|
384
|
+
generateWellKnownDocuments(agentCard, jwsSignature, publicKeyB64, agentData) {
|
|
385
|
+
const documents = {};
|
|
386
|
+
const keyAlgorithm = agentData.keyAlgorithm || 'RSA-PSS';
|
|
387
|
+
const postQuantum = /(pq|dilithium|falcon|sphincs|ml-dsa|pq2025)/i.test(keyAlgorithm);
|
|
388
|
+
|
|
389
|
+
// 1. Agent Card with embedded signature (v0.4.0)
|
|
390
|
+
const cardObj = JSON.parse(JSON.stringify(agentCard));
|
|
391
|
+
cardObj.signatures = [{ jws: jwsSignature }];
|
|
392
|
+
documents['/.well-known/agent-card.json'] = cardObj;
|
|
393
|
+
|
|
394
|
+
// 2. JWK Set for A2A verifiers
|
|
395
|
+
documents['/.well-known/jwks.json'] = this._buildJwks(publicKeyB64, agentData);
|
|
396
|
+
|
|
397
|
+
// 3. JACS Agent Descriptor
|
|
398
|
+
documents['/.well-known/jacs-agent.json'] = {
|
|
399
|
+
jacsVersion: '1.0',
|
|
400
|
+
agentId: agentData.jacsId,
|
|
401
|
+
agentVersion: agentData.jacsVersion,
|
|
402
|
+
agentType: agentData.jacsAgentType,
|
|
403
|
+
publicKeyHash: jacs.hashString(publicKeyB64),
|
|
404
|
+
keyAlgorithm,
|
|
405
|
+
capabilities: {
|
|
406
|
+
signing: true,
|
|
407
|
+
verification: true,
|
|
408
|
+
postQuantum
|
|
409
|
+
},
|
|
410
|
+
schemas: {
|
|
411
|
+
agent: 'https://hai.ai/schemas/agent/v1/agent.schema.json',
|
|
412
|
+
header: 'https://hai.ai/schemas/header/v1/header.schema.json',
|
|
413
|
+
signature: 'https://hai.ai/schemas/components/signature/v1/signature.schema.json'
|
|
414
|
+
},
|
|
415
|
+
endpoints: {
|
|
416
|
+
verify: '/jacs/verify',
|
|
417
|
+
sign: '/jacs/sign',
|
|
418
|
+
agent: '/jacs/agent'
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// 4. JACS Public Key
|
|
423
|
+
documents['/.well-known/jacs-pubkey.json'] = {
|
|
424
|
+
publicKey: publicKeyB64,
|
|
425
|
+
publicKeyHash: jacs.hashString(publicKeyB64),
|
|
426
|
+
algorithm: keyAlgorithm,
|
|
427
|
+
agentId: agentData.jacsId,
|
|
428
|
+
agentVersion: agentData.jacsVersion,
|
|
429
|
+
timestamp: new Date().toISOString()
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// 5. Extension descriptor
|
|
433
|
+
documents['/.well-known/jacs-extension.json'] = this.createExtensionDescriptor();
|
|
434
|
+
|
|
435
|
+
return documents;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Internal recursive verifier with cycle protection for parent signature chains.
|
|
440
|
+
* @private
|
|
441
|
+
*/
|
|
442
|
+
_verifyWrappedArtifactInternal(wrappedArtifact, visited) {
|
|
443
|
+
const artifactId = wrappedArtifact && wrappedArtifact.jacsId;
|
|
444
|
+
if (artifactId && visited.has(artifactId)) {
|
|
445
|
+
throw new Error(`Cycle detected in parent signature chain at artifact ${artifactId}`);
|
|
446
|
+
}
|
|
447
|
+
if (artifactId) {
|
|
448
|
+
visited.add(artifactId);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const isValid = jacs.verifyResponse(wrappedArtifact);
|
|
453
|
+
const signatureInfo = wrappedArtifact.jacsSignature || {};
|
|
454
|
+
|
|
455
|
+
const result = {
|
|
456
|
+
valid: isValid,
|
|
457
|
+
signerId: signatureInfo.agentID || 'unknown',
|
|
458
|
+
signerVersion: signatureInfo.agentVersion || 'unknown',
|
|
459
|
+
artifactType: wrappedArtifact.jacsType || 'unknown',
|
|
460
|
+
timestamp: wrappedArtifact.jacsVersionDate || '',
|
|
461
|
+
originalArtifact: wrappedArtifact.a2aArtifact || {}
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const parents = wrappedArtifact.jacsParentSignatures;
|
|
465
|
+
if (Array.isArray(parents) && parents.length > 0) {
|
|
466
|
+
const parentResults = parents.map((parent, index) => {
|
|
467
|
+
try {
|
|
468
|
+
const parentResult = this._verifyWrappedArtifactInternal(parent, visited);
|
|
469
|
+
return {
|
|
470
|
+
index,
|
|
471
|
+
artifactId: parent.jacsId || 'unknown',
|
|
472
|
+
valid: !!parentResult.valid,
|
|
473
|
+
parentSignaturesValid: parentResult.parentSignaturesValid !== false
|
|
474
|
+
};
|
|
475
|
+
} catch (error) {
|
|
476
|
+
return {
|
|
477
|
+
index,
|
|
478
|
+
artifactId: parent && parent.jacsId ? parent.jacsId : 'unknown',
|
|
479
|
+
valid: false,
|
|
480
|
+
parentSignaturesValid: false,
|
|
481
|
+
error: error instanceof Error ? error.message : String(error)
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
result.parentSignaturesCount = parentResults.length;
|
|
487
|
+
result.parentVerificationResults = parentResults;
|
|
488
|
+
result.parentSignaturesValid = parentResults.every(
|
|
489
|
+
(entry) => entry.valid && entry.parentSignaturesValid
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return result;
|
|
494
|
+
} finally {
|
|
495
|
+
if (artifactId) {
|
|
496
|
+
visited.delete(artifactId);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Build a JWKS document from a base64-encoded public key.
|
|
503
|
+
* @private
|
|
504
|
+
*/
|
|
505
|
+
_buildJwks(publicKeyB64, agentData = {}) {
|
|
506
|
+
if (agentData.jwks && Array.isArray(agentData.jwks.keys)) {
|
|
507
|
+
return agentData.jwks;
|
|
508
|
+
}
|
|
509
|
+
if (agentData.jwk && typeof agentData.jwk === 'object') {
|
|
510
|
+
return { keys: [agentData.jwk] };
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const keyAlgorithm = String(agentData.keyAlgorithm || '').toLowerCase();
|
|
514
|
+
const kid = String(agentData.jacsId || 'jacs-agent');
|
|
515
|
+
|
|
516
|
+
try {
|
|
517
|
+
const keyBytes = Buffer.from(publicKeyB64, 'base64');
|
|
518
|
+
if (keyBytes.length === 32) {
|
|
519
|
+
return {
|
|
520
|
+
keys: [{
|
|
521
|
+
kty: 'OKP',
|
|
522
|
+
crv: 'Ed25519',
|
|
523
|
+
x: keyBytes.toString('base64url'),
|
|
524
|
+
kid,
|
|
525
|
+
use: 'sig',
|
|
526
|
+
alg: 'EdDSA'
|
|
527
|
+
}]
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
let keyObject;
|
|
532
|
+
try {
|
|
533
|
+
keyObject = createPublicKey({ key: keyBytes, format: 'der', type: 'spki' });
|
|
534
|
+
} catch {
|
|
535
|
+
keyObject = createPublicKey(keyBytes.toString('utf8'));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const jwk = keyObject.export({ format: 'jwk' });
|
|
539
|
+
const alg = this._inferJwsAlg(keyAlgorithm, jwk);
|
|
540
|
+
return {
|
|
541
|
+
keys: [{
|
|
542
|
+
...jwk,
|
|
543
|
+
kid,
|
|
544
|
+
use: 'sig',
|
|
545
|
+
...(alg ? { alg } : {})
|
|
546
|
+
}]
|
|
547
|
+
};
|
|
548
|
+
} catch {
|
|
549
|
+
return { keys: [] };
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Infer a JWS `alg` for a generated JWK.
|
|
555
|
+
* @private
|
|
556
|
+
*/
|
|
557
|
+
_inferJwsAlg(keyAlgorithm, jwk) {
|
|
558
|
+
if (keyAlgorithm.includes('ring-ed25519') || keyAlgorithm.includes('ed25519')) {
|
|
559
|
+
return 'EdDSA';
|
|
560
|
+
}
|
|
561
|
+
if (keyAlgorithm.includes('rsa')) {
|
|
562
|
+
return 'RS256';
|
|
563
|
+
}
|
|
564
|
+
if (keyAlgorithm.includes('ecdsa') || keyAlgorithm.includes('es256')) {
|
|
565
|
+
return 'ES256';
|
|
566
|
+
}
|
|
567
|
+
if (jwk && jwk.kty === 'RSA') {
|
|
568
|
+
return 'RS256';
|
|
569
|
+
}
|
|
570
|
+
if (jwk && jwk.kty === 'OKP' && jwk.crv === 'Ed25519') {
|
|
571
|
+
return 'EdDSA';
|
|
572
|
+
}
|
|
573
|
+
if (jwk && jwk.kty === 'EC' && jwk.crv === 'P-256') {
|
|
574
|
+
return 'ES256';
|
|
575
|
+
}
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Convert a name to a URL-friendly slug for skill IDs.
|
|
581
|
+
* @private
|
|
582
|
+
*/
|
|
583
|
+
_slugify(name) {
|
|
584
|
+
return name
|
|
585
|
+
.toLowerCase()
|
|
586
|
+
.replace(/[\s_]+/g, '-')
|
|
587
|
+
.replace(/[^a-z0-9-]/g, '');
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Derive tags from service/function context.
|
|
592
|
+
* @private
|
|
593
|
+
*/
|
|
594
|
+
_deriveTags(serviceName, fnName) {
|
|
595
|
+
const tags = ['jacs'];
|
|
596
|
+
const serviceSlug = this._slugify(serviceName);
|
|
597
|
+
const fnSlug = this._slugify(fnName);
|
|
598
|
+
if (serviceSlug !== fnSlug) {
|
|
599
|
+
tags.push(serviceSlug);
|
|
600
|
+
}
|
|
601
|
+
tags.push(fnSlug);
|
|
602
|
+
return tags;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Export classes and functions
|
|
607
|
+
module.exports = {
|
|
608
|
+
JACSA2AIntegration,
|
|
609
|
+
A2AAgentInterface,
|
|
610
|
+
A2AAgentSkill,
|
|
611
|
+
A2AAgentExtension,
|
|
612
|
+
A2AAgentCapabilities,
|
|
613
|
+
A2AAgentCardSignature,
|
|
614
|
+
A2AAgentCard,
|
|
615
|
+
A2A_PROTOCOL_VERSION,
|
|
616
|
+
JACS_EXTENSION_URI
|
|
617
|
+
};
|