@vallum/registry 0.0.0-prerelease
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 +36 -0
- package/dist/a2aCard.d.ts +149 -0
- package/dist/a2aCard.js +387 -0
- package/dist/a2aDiscoveryBundle.d.ts +53 -0
- package/dist/a2aDiscoveryBundle.js +326 -0
- package/dist/a2aJwks.d.ts +37 -0
- package/dist/a2aJwks.js +102 -0
- package/dist/a2aWellKnown.d.ts +28 -0
- package/dist/a2aWellKnown.js +58 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/iotaIdentityAdapter.d.ts +92 -0
- package/dist/iotaIdentityAdapter.js +329 -0
- package/dist/iotaNamesAdapter.d.ts +49 -0
- package/dist/iotaNamesAdapter.js +188 -0
- package/dist/profileSchema.d.ts +76 -0
- package/dist/profileSchema.js +187 -0
- package/dist/resolveAgent.d.ts +26 -0
- package/dist/resolveAgent.js +63 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @vallum/registry
|
|
2
|
+
|
|
3
|
+
Versioned Agent Profile schema and validation helpers for Vallum.
|
|
4
|
+
|
|
5
|
+
Current surface:
|
|
6
|
+
|
|
7
|
+
- Agent Profile schema validation with revoked/expired states.
|
|
8
|
+
- Local fixture profile resolution.
|
|
9
|
+
- Dependency-injected IOTA Names and IOTA Identity adapter interfaces.
|
|
10
|
+
- Bounded in-memory IOTA Identity verification cache helpers that fail closed
|
|
11
|
+
after TTL expiry if DID or credential evidence cannot refresh.
|
|
12
|
+
- Optional force-refresh behavior for protected actions that must bypass
|
|
13
|
+
current cache entries.
|
|
14
|
+
- A2A Agent Card generation from active Agent Profiles.
|
|
15
|
+
- Local A2A Agent Card JWS signing and trusted-key verification helpers.
|
|
16
|
+
- Local A2A Agent Card well-known response helpers for the canonical
|
|
17
|
+
`/.well-known/agent-card.json` path.
|
|
18
|
+
- Local public JWKS response helpers for Agent Card signing public keys at the
|
|
19
|
+
canonical `/.well-known/jwks.json` path.
|
|
20
|
+
- Local static A2A discovery bundle generation for signed Agent Card and public
|
|
21
|
+
JWKS JSON artifacts at canonical well-known paths.
|
|
22
|
+
- Local static A2A discovery artifact writing for canonical `.well-known`
|
|
23
|
+
files plus a sanitized header manifest.
|
|
24
|
+
- Local static A2A discovery artifact validation for generated `.well-known`
|
|
25
|
+
files and manifest metadata before public hosting review.
|
|
26
|
+
- Local static A2A discovery loopback host smoke support for serving validated
|
|
27
|
+
`.well-known` files with manifest-declared headers before public hosting.
|
|
28
|
+
|
|
29
|
+
This package is local-first today. It does not resolve live IOTA Names,
|
|
30
|
+
validate live IOTA Identity credentials, run A2A task/message operations, host
|
|
31
|
+
public A2A discovery, prove external A2A conformance, provide production key
|
|
32
|
+
management or key rotation, deploy static discovery artifacts to a public host,
|
|
33
|
+
prove endpoint ownership, fetch public discovery endpoints, prove public
|
|
34
|
+
hosting from local loopback serving, or contact testnet/mainnet services. The
|
|
35
|
+
identity cache records only successful local/mock verification evidence and
|
|
36
|
+
does not turn mock credentials into live credential validation.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { type KeyLike } from "node:crypto";
|
|
2
|
+
import { type AgentProfileValidationErrorCode } from "./profileSchema.js";
|
|
3
|
+
export declare const A2A_AGENT_CARD_PROTOCOL_VERSION: "1.0";
|
|
4
|
+
export declare const A2A_AGENT_CARD_WELL_KNOWN_PATH: "/.well-known/agent-card.json";
|
|
5
|
+
export declare const AGENTIC_VALLUM_A2A_PROFILE_EXTENSION_URI: "https://vallum.dev/a2a/extensions/profile/v1";
|
|
6
|
+
export type A2AProtocolBinding = "JSONRPC" | "GRPC" | "HTTP+JSON";
|
|
7
|
+
export interface A2AAgentInterface {
|
|
8
|
+
readonly url: string;
|
|
9
|
+
readonly protocolBinding: A2AProtocolBinding | string;
|
|
10
|
+
readonly tenant?: string;
|
|
11
|
+
readonly protocolVersion: typeof A2A_AGENT_CARD_PROTOCOL_VERSION;
|
|
12
|
+
}
|
|
13
|
+
export interface A2AAgentProvider {
|
|
14
|
+
readonly url: string;
|
|
15
|
+
readonly organization: string;
|
|
16
|
+
}
|
|
17
|
+
export interface A2AAgentExtension {
|
|
18
|
+
readonly uri: string;
|
|
19
|
+
readonly description?: string;
|
|
20
|
+
readonly required?: boolean;
|
|
21
|
+
readonly params?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
export interface A2AAgentCapabilities {
|
|
24
|
+
readonly streaming?: boolean;
|
|
25
|
+
readonly pushNotifications?: boolean;
|
|
26
|
+
readonly extensions?: readonly A2AAgentExtension[];
|
|
27
|
+
readonly extendedAgentCard?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface A2ASecurityRequirement {
|
|
30
|
+
readonly schemes: Record<string, readonly string[]>;
|
|
31
|
+
}
|
|
32
|
+
export type A2ASecurityScheme = {
|
|
33
|
+
readonly apiKeySecurityScheme: {
|
|
34
|
+
readonly description?: string;
|
|
35
|
+
readonly location: "query" | "header" | "cookie";
|
|
36
|
+
readonly name: string;
|
|
37
|
+
};
|
|
38
|
+
} | {
|
|
39
|
+
readonly httpAuthSecurityScheme: {
|
|
40
|
+
readonly description?: string;
|
|
41
|
+
readonly scheme: string;
|
|
42
|
+
readonly bearerFormat?: string;
|
|
43
|
+
};
|
|
44
|
+
} | {
|
|
45
|
+
readonly oauth2SecurityScheme: {
|
|
46
|
+
readonly description?: string;
|
|
47
|
+
readonly flows: Record<string, unknown>;
|
|
48
|
+
readonly oauth2MetadataUrl?: string;
|
|
49
|
+
};
|
|
50
|
+
} | {
|
|
51
|
+
readonly openIdConnectSecurityScheme: {
|
|
52
|
+
readonly description?: string;
|
|
53
|
+
readonly openIdConnectUrl: string;
|
|
54
|
+
};
|
|
55
|
+
} | {
|
|
56
|
+
readonly mtlsSecurityScheme: {
|
|
57
|
+
readonly description?: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
export interface A2AAgentSkill {
|
|
61
|
+
readonly id: string;
|
|
62
|
+
readonly name: string;
|
|
63
|
+
readonly description: string;
|
|
64
|
+
readonly tags: readonly string[];
|
|
65
|
+
readonly examples?: readonly string[];
|
|
66
|
+
readonly inputModes?: readonly string[];
|
|
67
|
+
readonly outputModes?: readonly string[];
|
|
68
|
+
readonly securityRequirements?: readonly A2ASecurityRequirement[];
|
|
69
|
+
}
|
|
70
|
+
export type A2AAgentCardSignatureAlgorithm = "EdDSA";
|
|
71
|
+
export interface A2AAgentCardSignature {
|
|
72
|
+
readonly protected: string;
|
|
73
|
+
readonly signature: string;
|
|
74
|
+
readonly header?: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
export interface A2AAgentCard {
|
|
77
|
+
readonly name: string;
|
|
78
|
+
readonly description: string;
|
|
79
|
+
readonly supportedInterfaces: readonly A2AAgentInterface[];
|
|
80
|
+
readonly provider?: A2AAgentProvider;
|
|
81
|
+
readonly version: string;
|
|
82
|
+
readonly documentationUrl?: string;
|
|
83
|
+
readonly capabilities: A2AAgentCapabilities;
|
|
84
|
+
readonly securitySchemes?: Record<string, A2ASecurityScheme>;
|
|
85
|
+
readonly securityRequirements?: readonly A2ASecurityRequirement[];
|
|
86
|
+
readonly defaultInputModes: readonly string[];
|
|
87
|
+
readonly defaultOutputModes: readonly string[];
|
|
88
|
+
readonly skills: readonly A2AAgentSkill[];
|
|
89
|
+
readonly iconUrl?: string;
|
|
90
|
+
readonly signatures?: readonly A2AAgentCardSignature[];
|
|
91
|
+
}
|
|
92
|
+
export type SignedA2AAgentCard = A2AAgentCard & {
|
|
93
|
+
readonly signatures: readonly A2AAgentCardSignature[];
|
|
94
|
+
};
|
|
95
|
+
export type A2AAgentCardErrorCode = AgentProfileValidationErrorCode | "A2A_ENDPOINT_MISSING" | "A2A_ENDPOINT_INVALID" | "UNSUPPORTED_A2A_PROTOCOL_VERSION" | "A2A_SECURITY_SCHEME_INVALID" | "A2A_SIGNATURE_ALGORITHM_UNSUPPORTED" | "A2A_SIGNATURE_INVALID" | "A2A_SIGNATURE_KEY_NOT_TRUSTED" | "A2A_SIGNATURE_MALFORMED" | "A2A_SIGNATURE_MISSING" | "A2A_SIGNATURE_EXPIRED" | "A2A_SIGNATURE_NOT_YET_VALID" | "PRIVATE_PROFILE_FIELD_NOT_ALLOWED";
|
|
96
|
+
export declare class A2AAgentCardError extends Error {
|
|
97
|
+
readonly code: A2AAgentCardErrorCode;
|
|
98
|
+
readonly path: string;
|
|
99
|
+
constructor(code: A2AAgentCardErrorCode, message: string, path?: string);
|
|
100
|
+
}
|
|
101
|
+
export interface CreateA2AAgentCardOptions {
|
|
102
|
+
readonly now?: Date;
|
|
103
|
+
readonly description?: string;
|
|
104
|
+
readonly agentVersion?: string;
|
|
105
|
+
readonly provider?: A2AAgentProvider;
|
|
106
|
+
readonly documentationUrl?: string;
|
|
107
|
+
readonly iconUrl?: string;
|
|
108
|
+
readonly defaultInputModes?: readonly string[];
|
|
109
|
+
readonly defaultOutputModes?: readonly string[];
|
|
110
|
+
readonly protocolBinding?: A2AProtocolBinding | string;
|
|
111
|
+
readonly protocolVersion?: string;
|
|
112
|
+
readonly endpointUrl?: string;
|
|
113
|
+
readonly tenant?: string;
|
|
114
|
+
readonly securitySchemeName?: string;
|
|
115
|
+
readonly securitySchemes?: Record<string, A2ASecurityScheme>;
|
|
116
|
+
readonly securityRequirements?: readonly A2ASecurityRequirement[];
|
|
117
|
+
readonly capabilities?: Partial<Omit<A2AAgentCapabilities, "extensions">> & {
|
|
118
|
+
readonly extensions?: readonly A2AAgentExtension[];
|
|
119
|
+
};
|
|
120
|
+
readonly signature?: SignA2AAgentCardOptions;
|
|
121
|
+
}
|
|
122
|
+
export interface SignA2AAgentCardOptions {
|
|
123
|
+
readonly keyId: string;
|
|
124
|
+
readonly privateKey: KeyLike;
|
|
125
|
+
readonly algorithm?: A2AAgentCardSignatureAlgorithm;
|
|
126
|
+
readonly jwksUrl?: string;
|
|
127
|
+
readonly signedAt?: Date;
|
|
128
|
+
readonly notBefore?: Date;
|
|
129
|
+
readonly expiresAt?: Date;
|
|
130
|
+
}
|
|
131
|
+
export interface VerifyA2AAgentCardSignatureOptions {
|
|
132
|
+
readonly trustedKeys: Record<string, KeyLike | undefined>;
|
|
133
|
+
readonly now?: Date;
|
|
134
|
+
readonly requiredKeyId?: string;
|
|
135
|
+
}
|
|
136
|
+
export type A2AAgentCardSignatureVerification = {
|
|
137
|
+
readonly ok: true;
|
|
138
|
+
readonly keyId: string;
|
|
139
|
+
readonly algorithm: A2AAgentCardSignatureAlgorithm;
|
|
140
|
+
} | {
|
|
141
|
+
readonly ok: false;
|
|
142
|
+
readonly code: Extract<A2AAgentCardErrorCode, "A2A_SIGNATURE_ALGORITHM_UNSUPPORTED" | "A2A_SIGNATURE_INVALID" | "A2A_SIGNATURE_KEY_NOT_TRUSTED" | "A2A_SIGNATURE_MALFORMED" | "A2A_SIGNATURE_MISSING" | "A2A_SIGNATURE_EXPIRED" | "A2A_SIGNATURE_NOT_YET_VALID">;
|
|
143
|
+
readonly message: string;
|
|
144
|
+
};
|
|
145
|
+
export declare function createA2AAgentCardFromProfile(value: unknown, options?: CreateA2AAgentCardOptions): A2AAgentCard;
|
|
146
|
+
export declare function signA2AAgentCard(card: A2AAgentCard, options: SignA2AAgentCardOptions): SignedA2AAgentCard;
|
|
147
|
+
export declare function verifyA2AAgentCardSignature(card: A2AAgentCard, options: VerifyA2AAgentCardSignatureOptions): A2AAgentCardSignatureVerification;
|
|
148
|
+
export declare function canonicalizeA2AAgentCard(card: A2AAgentCard): string;
|
|
149
|
+
//# sourceMappingURL=a2aCard.d.ts.map
|
package/dist/a2aCard.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { sign as cryptoSign, verify as cryptoVerify, } from "node:crypto";
|
|
2
|
+
import { validateAgentProfile, } from "./profileSchema.js";
|
|
3
|
+
export const A2A_AGENT_CARD_PROTOCOL_VERSION = "1.0";
|
|
4
|
+
export const A2A_AGENT_CARD_WELL_KNOWN_PATH = "/.well-known/agent-card.json";
|
|
5
|
+
export const AGENTIC_VALLUM_A2A_PROFILE_EXTENSION_URI = "https://vallum.dev/a2a/extensions/profile/v1";
|
|
6
|
+
export class A2AAgentCardError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
path;
|
|
9
|
+
constructor(code, message, path = "$") {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "A2AAgentCardError";
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.path = path;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function createA2AAgentCardFromProfile(value, options = {}) {
|
|
17
|
+
const validation = validateAgentProfile(value, { now: options.now });
|
|
18
|
+
if (!validation.ok) {
|
|
19
|
+
const first = validation.errors[0];
|
|
20
|
+
throw new A2AAgentCardError(first?.code ?? "FIELD_INVALID", first?.message ?? "Agent profile cannot be exposed as an A2A Agent Card.", first?.path);
|
|
21
|
+
}
|
|
22
|
+
const protocolVersion = options.protocolVersion ?? A2A_AGENT_CARD_PROTOCOL_VERSION;
|
|
23
|
+
if (protocolVersion !== A2A_AGENT_CARD_PROTOCOL_VERSION) {
|
|
24
|
+
throw new A2AAgentCardError("UNSUPPORTED_A2A_PROTOCOL_VERSION", "A2A Agent Card protocol version is unsupported.", "$.protocolVersion");
|
|
25
|
+
}
|
|
26
|
+
const defaultInputModes = nonEmptyList(options.defaultInputModes ?? ["text/plain", "application/json"]);
|
|
27
|
+
const defaultOutputModes = nonEmptyList(options.defaultOutputModes ?? ["text/plain", "application/json"]);
|
|
28
|
+
const securitySchemeName = nonEmptyString(options.securitySchemeName ?? "vallumBearer", "$.securitySchemeName");
|
|
29
|
+
const securitySchemes = options.securitySchemes ?? defaultSecuritySchemes(securitySchemeName);
|
|
30
|
+
assertValidSecuritySchemes(securitySchemes);
|
|
31
|
+
const securityRequirements = options.securityRequirements ?? [{ schemes: { [securitySchemeName]: [] } }];
|
|
32
|
+
assertValidSecurityRequirements(securityRequirements, securitySchemes);
|
|
33
|
+
const profile = validation.profile;
|
|
34
|
+
const card = {
|
|
35
|
+
name: profile.name,
|
|
36
|
+
description: options.description ?? `Vallum agent ${profile.name}.`,
|
|
37
|
+
supportedInterfaces: [{
|
|
38
|
+
url: resolveA2AEndpoint(profile, options.endpointUrl),
|
|
39
|
+
protocolBinding: options.protocolBinding ?? "HTTP+JSON",
|
|
40
|
+
...(options.tenant ? { tenant: options.tenant } : {}),
|
|
41
|
+
protocolVersion: A2A_AGENT_CARD_PROTOCOL_VERSION,
|
|
42
|
+
}],
|
|
43
|
+
...(options.provider ? { provider: options.provider } : {}),
|
|
44
|
+
version: options.agentVersion ?? profile.version,
|
|
45
|
+
...(options.documentationUrl ? { documentationUrl: options.documentationUrl } : {}),
|
|
46
|
+
capabilities: {
|
|
47
|
+
streaming: options.capabilities?.streaming ?? false,
|
|
48
|
+
pushNotifications: options.capabilities?.pushNotifications ?? false,
|
|
49
|
+
extendedAgentCard: options.capabilities?.extendedAgentCard ?? false,
|
|
50
|
+
extensions: [
|
|
51
|
+
publicProfileExtension(profile),
|
|
52
|
+
...(options.capabilities?.extensions ?? []),
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
securitySchemes,
|
|
56
|
+
securityRequirements,
|
|
57
|
+
defaultInputModes,
|
|
58
|
+
defaultOutputModes,
|
|
59
|
+
skills: profile.capabilities.map((capability) => capabilityToSkill(capability, defaultInputModes, defaultOutputModes, securityRequirements)),
|
|
60
|
+
...(options.iconUrl ? { iconUrl: options.iconUrl } : {}),
|
|
61
|
+
};
|
|
62
|
+
assertNoPrivateProfileFields(card);
|
|
63
|
+
return options.signature ? signA2AAgentCard(card, options.signature) : card;
|
|
64
|
+
}
|
|
65
|
+
export function signA2AAgentCard(card, options) {
|
|
66
|
+
const algorithm = options.algorithm ?? "EdDSA";
|
|
67
|
+
if (algorithm !== "EdDSA") {
|
|
68
|
+
throw new A2AAgentCardError("A2A_SIGNATURE_ALGORITHM_UNSUPPORTED", "A2A Agent Card signature algorithm is unsupported.", "$.signatures");
|
|
69
|
+
}
|
|
70
|
+
const keyId = options.keyId.trim();
|
|
71
|
+
if (keyId === "") {
|
|
72
|
+
throw new A2AAgentCardError("A2A_SIGNATURE_INVALID", "A2A Agent Card signature key id is required.", "$.signatures");
|
|
73
|
+
}
|
|
74
|
+
if (options.jwksUrl && !isHttpsUrl(options.jwksUrl)) {
|
|
75
|
+
throw new A2AAgentCardError("A2A_SIGNATURE_INVALID", "A2A Agent Card JWKS URL must be HTTPS.", "$.signatures");
|
|
76
|
+
}
|
|
77
|
+
assertNoPrivateProfileFields(card);
|
|
78
|
+
const protectedHeader = {
|
|
79
|
+
alg: algorithm,
|
|
80
|
+
typ: "JOSE",
|
|
81
|
+
kid: keyId,
|
|
82
|
+
...(options.jwksUrl ? { jku: options.jwksUrl } : {}),
|
|
83
|
+
...(options.signedAt ? { iat: isoDate(options.signedAt, "$.signatures.iat") } : {}),
|
|
84
|
+
...(options.notBefore ? { nbf: isoDate(options.notBefore, "$.signatures.nbf") } : {}),
|
|
85
|
+
...(options.expiresAt ? { exp: isoDate(options.expiresAt, "$.signatures.exp") } : {}),
|
|
86
|
+
};
|
|
87
|
+
const protectedHeaderEncoded = base64UrlJson(protectedHeader);
|
|
88
|
+
const payloadEncoded = base64Url(canonicalizeA2AAgentCard(card));
|
|
89
|
+
const signingInput = Buffer.from(`${protectedHeaderEncoded}.${payloadEncoded}`, "ascii");
|
|
90
|
+
const signature = cryptoSign(null, signingInput, options.privateKey);
|
|
91
|
+
return {
|
|
92
|
+
...card,
|
|
93
|
+
signatures: [
|
|
94
|
+
...(card.signatures ?? []),
|
|
95
|
+
{
|
|
96
|
+
protected: protectedHeaderEncoded,
|
|
97
|
+
signature: signature.toString("base64url"),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export function verifyA2AAgentCardSignature(card, options) {
|
|
103
|
+
if (!card.signatures || card.signatures.length === 0) {
|
|
104
|
+
return signatureFailure("A2A_SIGNATURE_MISSING", "A2A Agent Card does not include a signature.");
|
|
105
|
+
}
|
|
106
|
+
let firstFailure;
|
|
107
|
+
for (const signature of card.signatures) {
|
|
108
|
+
const protectedHeader = parseProtectedHeader(signature.protected);
|
|
109
|
+
if (!protectedHeader) {
|
|
110
|
+
firstFailure ??= signatureFailure("A2A_SIGNATURE_MALFORMED", "A2A Agent Card signature is malformed.");
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (protectedHeader.alg !== "EdDSA") {
|
|
114
|
+
firstFailure ??= signatureFailure("A2A_SIGNATURE_ALGORITHM_UNSUPPORTED", "A2A Agent Card signature algorithm is unsupported.");
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (protectedHeader.typ !== undefined && protectedHeader.typ !== "JOSE") {
|
|
118
|
+
firstFailure ??= signatureFailure("A2A_SIGNATURE_MALFORMED", "A2A Agent Card signature is malformed.");
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (typeof protectedHeader.kid !== "string" || protectedHeader.kid.trim() === "") {
|
|
122
|
+
firstFailure ??= signatureFailure("A2A_SIGNATURE_MALFORMED", "A2A Agent Card signature is malformed.");
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (options.requiredKeyId && protectedHeader.kid !== options.requiredKeyId) {
|
|
126
|
+
firstFailure ??= signatureFailure("A2A_SIGNATURE_KEY_NOT_TRUSTED", "A2A Agent Card signing key is not trusted.");
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const temporalFailure = validateSignatureTime(protectedHeader, options.now ?? new Date());
|
|
130
|
+
if (temporalFailure) {
|
|
131
|
+
firstFailure ??= temporalFailure;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const trustedKey = options.trustedKeys[protectedHeader.kid];
|
|
135
|
+
if (!trustedKey) {
|
|
136
|
+
firstFailure ??= signatureFailure("A2A_SIGNATURE_KEY_NOT_TRUSTED", "A2A Agent Card signing key is not trusted.");
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (!isBase64Url(signature.signature)) {
|
|
140
|
+
firstFailure ??= signatureFailure("A2A_SIGNATURE_MALFORMED", "A2A Agent Card signature is malformed.");
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const payloadEncoded = base64Url(canonicalizeA2AAgentCard(card));
|
|
144
|
+
const signingInput = Buffer.from(`${signature.protected}.${payloadEncoded}`, "ascii");
|
|
145
|
+
const verified = cryptoVerify(null, signingInput, trustedKey, Buffer.from(signature.signature, "base64url"));
|
|
146
|
+
if (verified) {
|
|
147
|
+
return {
|
|
148
|
+
ok: true,
|
|
149
|
+
keyId: protectedHeader.kid,
|
|
150
|
+
algorithm: protectedHeader.alg,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
firstFailure ??= signatureFailure("A2A_SIGNATURE_INVALID", "A2A Agent Card signature verification failed.");
|
|
154
|
+
}
|
|
155
|
+
return firstFailure ?? signatureFailure("A2A_SIGNATURE_INVALID", "A2A Agent Card signature verification failed.");
|
|
156
|
+
}
|
|
157
|
+
export function canonicalizeA2AAgentCard(card) {
|
|
158
|
+
return JSON.stringify(canonicalizeValue(card, "$", true));
|
|
159
|
+
}
|
|
160
|
+
function resolveA2AEndpoint(profile, optionEndpointUrl) {
|
|
161
|
+
const endpointUrl = optionEndpointUrl ?? profile.endpoints.find((endpoint) => endpoint.type === "a2a")?.url;
|
|
162
|
+
if (!endpointUrl) {
|
|
163
|
+
throw new A2AAgentCardError("A2A_ENDPOINT_MISSING", "An A2A endpoint is required to generate an Agent Card.", "$.endpoints");
|
|
164
|
+
}
|
|
165
|
+
if (!isHttpUrl(endpointUrl)) {
|
|
166
|
+
throw new A2AAgentCardError("A2A_ENDPOINT_INVALID", "A2A endpoint must be an absolute HTTP(S) URL.", "$.endpoints");
|
|
167
|
+
}
|
|
168
|
+
return endpointUrl;
|
|
169
|
+
}
|
|
170
|
+
function capabilityToSkill(capability, inputModes, outputModes, securityRequirements) {
|
|
171
|
+
return {
|
|
172
|
+
id: capability.id,
|
|
173
|
+
name: capability.displayName ?? capability.id,
|
|
174
|
+
description: `Vallum capability ${capability.id}.`,
|
|
175
|
+
tags: unique(["vallum", ...(capability.scopes ?? []), ...(capability.contracts ?? [])]),
|
|
176
|
+
inputModes,
|
|
177
|
+
outputModes,
|
|
178
|
+
securityRequirements,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function publicProfileExtension(profile) {
|
|
182
|
+
return {
|
|
183
|
+
uri: AGENTIC_VALLUM_A2A_PROFILE_EXTENSION_URI,
|
|
184
|
+
description: "Public Vallum profile context.",
|
|
185
|
+
required: false,
|
|
186
|
+
params: {
|
|
187
|
+
profileVersion: profile.version,
|
|
188
|
+
agentDid: profile.agentDid,
|
|
189
|
+
ownerDid: profile.ownerDid,
|
|
190
|
+
supportedContracts: profile.supportedContracts?.map((contract) => contract.id) ?? [],
|
|
191
|
+
paymentMethods: publicPaymentMethods(profile.paymentMethods),
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function publicPaymentMethods(methods) {
|
|
196
|
+
return (methods ?? []).map((method) => ({
|
|
197
|
+
type: method.type,
|
|
198
|
+
asset: method.asset,
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
function defaultSecuritySchemes(schemeName) {
|
|
202
|
+
return {
|
|
203
|
+
[schemeName]: {
|
|
204
|
+
httpAuthSecurityScheme: {
|
|
205
|
+
scheme: "Bearer",
|
|
206
|
+
bearerFormat: "JWT",
|
|
207
|
+
description: "Bearer token accepted by the Vallum A2A endpoint.",
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function assertValidSecuritySchemes(schemes) {
|
|
213
|
+
for (const [name, scheme] of Object.entries(schemes)) {
|
|
214
|
+
const oneOfKeys = [
|
|
215
|
+
"apiKeySecurityScheme",
|
|
216
|
+
"httpAuthSecurityScheme",
|
|
217
|
+
"oauth2SecurityScheme",
|
|
218
|
+
"openIdConnectSecurityScheme",
|
|
219
|
+
"mtlsSecurityScheme",
|
|
220
|
+
].filter((key) => key in scheme);
|
|
221
|
+
if (oneOfKeys.length !== 1) {
|
|
222
|
+
throw new A2AAgentCardError("A2A_SECURITY_SCHEME_INVALID", "A2A security schemes must contain exactly one scheme variant.", `$.securitySchemes.${name}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function assertValidSecurityRequirements(requirements, schemes) {
|
|
227
|
+
const knownSchemes = new Set(Object.keys(schemes));
|
|
228
|
+
requirements.forEach((requirement, index) => {
|
|
229
|
+
if (!isRecord(requirement.schemes)) {
|
|
230
|
+
throw new A2AAgentCardError("A2A_SECURITY_SCHEME_INVALID", "A2A security requirements must declare scheme scopes.", `$.securityRequirements[${index}].schemes`);
|
|
231
|
+
}
|
|
232
|
+
for (const [schemeName, scopes] of Object.entries(requirement.schemes)) {
|
|
233
|
+
if (!knownSchemes.has(schemeName) || !Array.isArray(scopes)) {
|
|
234
|
+
throw new A2AAgentCardError("A2A_SECURITY_SCHEME_INVALID", "A2A security requirements must reference declared security schemes.", `$.securityRequirements[${index}].schemes.${schemeName}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function assertNoPrivateProfileFields(value, path = "$") {
|
|
240
|
+
if (Array.isArray(value)) {
|
|
241
|
+
value.forEach((child, index) => assertNoPrivateProfileFields(child, `${path}[${index}]`));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (!isRecord(value))
|
|
245
|
+
return;
|
|
246
|
+
for (const [key, child] of Object.entries(value)) {
|
|
247
|
+
const childPath = `${path}.${key}`;
|
|
248
|
+
if (isPrivateProfileField(key)) {
|
|
249
|
+
throw new A2AAgentCardError("PRIVATE_PROFILE_FIELD_NOT_ALLOWED", "A2A Agent Card must not expose private Agent Profile fields.", childPath);
|
|
250
|
+
}
|
|
251
|
+
assertNoPrivateProfileFields(child, childPath);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function nonEmptyList(values) {
|
|
255
|
+
const normalized = values.map((value) => value.trim()).filter(Boolean);
|
|
256
|
+
if (normalized.length === 0) {
|
|
257
|
+
throw new A2AAgentCardError("FIELD_INVALID", "A2A media mode lists cannot be empty.");
|
|
258
|
+
}
|
|
259
|
+
return normalized;
|
|
260
|
+
}
|
|
261
|
+
function nonEmptyString(value, path) {
|
|
262
|
+
const normalized = value.trim();
|
|
263
|
+
if (normalized === "") {
|
|
264
|
+
throw new A2AAgentCardError("A2A_SECURITY_SCHEME_INVALID", "A2A security scheme names cannot be blank.", path);
|
|
265
|
+
}
|
|
266
|
+
return normalized;
|
|
267
|
+
}
|
|
268
|
+
function unique(values) {
|
|
269
|
+
return [...new Set(values.filter((value) => value.trim() !== ""))];
|
|
270
|
+
}
|
|
271
|
+
function isHttpUrl(value) {
|
|
272
|
+
try {
|
|
273
|
+
const url = new URL(value);
|
|
274
|
+
return url.protocol === "https:" || url.protocol === "http:";
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function isHttpsUrl(value) {
|
|
281
|
+
try {
|
|
282
|
+
return new URL(value).protocol === "https:";
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function isRecord(value) {
|
|
289
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
290
|
+
}
|
|
291
|
+
function isPrivateProfileField(key) {
|
|
292
|
+
return /^(seed|mnemonic|privateKey|private_key|rawKeypair|raw_keypair|rawTransactionBytes|raw_transaction_bytes|userSignature|user_signature|sponsorKey|sponsor_key|appApiKey|app_api_key|bearerToken|bearer_token|paymentCredential|payment_credential|signerSecret|signer_secret|signerRef|signer_ref|walletId|wallet_id|rotatedToWalletId|rotated_to_wallet_id|credentialRefs|credential_refs|revocation|metadata)$/i.test(key);
|
|
293
|
+
}
|
|
294
|
+
function base64Url(value) {
|
|
295
|
+
return Buffer.from(value, "utf8").toString("base64url");
|
|
296
|
+
}
|
|
297
|
+
function base64UrlJson(value) {
|
|
298
|
+
return base64Url(JSON.stringify(value));
|
|
299
|
+
}
|
|
300
|
+
function parseProtectedHeader(value) {
|
|
301
|
+
if (!isBase64Url(value))
|
|
302
|
+
return undefined;
|
|
303
|
+
try {
|
|
304
|
+
const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
305
|
+
if (!isRecord(parsed) || typeof parsed.alg !== "string")
|
|
306
|
+
return undefined;
|
|
307
|
+
return {
|
|
308
|
+
alg: parsed.alg,
|
|
309
|
+
...(typeof parsed.typ === "string" ? { typ: parsed.typ } : {}),
|
|
310
|
+
...(typeof parsed.kid === "string" ? { kid: parsed.kid } : {}),
|
|
311
|
+
...(typeof parsed.iat === "string" ? { iat: parsed.iat } : {}),
|
|
312
|
+
...(typeof parsed.nbf === "string" ? { nbf: parsed.nbf } : {}),
|
|
313
|
+
...(typeof parsed.exp === "string" ? { exp: parsed.exp } : {}),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function validateSignatureTime(protectedHeader, now) {
|
|
321
|
+
const nowMs = now.getTime();
|
|
322
|
+
if (!Number.isFinite(nowMs)) {
|
|
323
|
+
return signatureFailure("A2A_SIGNATURE_MALFORMED", "A2A Agent Card signature is malformed.");
|
|
324
|
+
}
|
|
325
|
+
if (protectedHeader.nbf !== undefined) {
|
|
326
|
+
const notBeforeMs = Date.parse(protectedHeader.nbf);
|
|
327
|
+
if (!Number.isFinite(notBeforeMs)) {
|
|
328
|
+
return signatureFailure("A2A_SIGNATURE_MALFORMED", "A2A Agent Card signature is malformed.");
|
|
329
|
+
}
|
|
330
|
+
if (nowMs < notBeforeMs) {
|
|
331
|
+
return signatureFailure("A2A_SIGNATURE_NOT_YET_VALID", "A2A Agent Card signature is not yet valid.");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (protectedHeader.exp !== undefined) {
|
|
335
|
+
const expiresMs = Date.parse(protectedHeader.exp);
|
|
336
|
+
if (!Number.isFinite(expiresMs)) {
|
|
337
|
+
return signatureFailure("A2A_SIGNATURE_MALFORMED", "A2A Agent Card signature is malformed.");
|
|
338
|
+
}
|
|
339
|
+
if (nowMs >= expiresMs) {
|
|
340
|
+
return signatureFailure("A2A_SIGNATURE_EXPIRED", "A2A Agent Card signature is expired.");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
function isoDate(value, path) {
|
|
346
|
+
const time = value.getTime();
|
|
347
|
+
if (!Number.isFinite(time)) {
|
|
348
|
+
throw new A2AAgentCardError("A2A_SIGNATURE_INVALID", "A2A Agent Card signature time is invalid.", path);
|
|
349
|
+
}
|
|
350
|
+
return value.toISOString();
|
|
351
|
+
}
|
|
352
|
+
function isBase64Url(value) {
|
|
353
|
+
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
354
|
+
}
|
|
355
|
+
function signatureFailure(code, message) {
|
|
356
|
+
return { ok: false, code, message };
|
|
357
|
+
}
|
|
358
|
+
function canonicalizeValue(value, path, isRoot = false) {
|
|
359
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
360
|
+
return value;
|
|
361
|
+
if (typeof value === "number") {
|
|
362
|
+
if (!Number.isFinite(value)) {
|
|
363
|
+
throw new A2AAgentCardError("A2A_SIGNATURE_INVALID", "A2A Agent Card contains a non-finite number.", path);
|
|
364
|
+
}
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
if (typeof value === "bigint" || typeof value === "function" || typeof value === "symbol") {
|
|
368
|
+
throw new A2AAgentCardError("A2A_SIGNATURE_INVALID", "A2A Agent Card contains non-JSON data.", path);
|
|
369
|
+
}
|
|
370
|
+
if (Array.isArray(value)) {
|
|
371
|
+
return value.map((child, index) => canonicalizeValue(child, `${path}[${index}]`));
|
|
372
|
+
}
|
|
373
|
+
if (!isRecord(value))
|
|
374
|
+
return value;
|
|
375
|
+
return Object.keys(value)
|
|
376
|
+
.sort()
|
|
377
|
+
.reduce((canonical, key) => {
|
|
378
|
+
if (isRoot && key === "signatures")
|
|
379
|
+
return canonical;
|
|
380
|
+
const child = value[key];
|
|
381
|
+
if (child === undefined)
|
|
382
|
+
return canonical;
|
|
383
|
+
canonical[key] = canonicalizeValue(child, `${path}.${key}`);
|
|
384
|
+
return canonical;
|
|
385
|
+
}, {});
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=a2aCard.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { A2A_AGENT_CARD_WELL_KNOWN_PATH, type A2AAgentCard } from "./a2aCard.js";
|
|
2
|
+
import { A2A_JWKS_WELL_KNOWN_PATH, type A2APublicJwksResponse } from "./a2aJwks.js";
|
|
3
|
+
export interface A2APublicDiscoveryBundleOptions {
|
|
4
|
+
readonly agentCard: A2AAgentCard;
|
|
5
|
+
readonly jwks: A2APublicJwksResponse;
|
|
6
|
+
readonly publicBaseUrl: string;
|
|
7
|
+
readonly publicJwksUrl: string;
|
|
8
|
+
readonly cacheControl?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface A2APublicDiscoveryBundleFile {
|
|
11
|
+
readonly path: typeof A2A_AGENT_CARD_WELL_KNOWN_PATH | typeof A2A_JWKS_WELL_KNOWN_PATH;
|
|
12
|
+
readonly headers: Record<string, string>;
|
|
13
|
+
readonly json: string;
|
|
14
|
+
}
|
|
15
|
+
export interface A2APublicDiscoveryBundle {
|
|
16
|
+
readonly publicBaseUrl: string;
|
|
17
|
+
readonly publicJwksUrl: string;
|
|
18
|
+
readonly files: readonly [A2APublicDiscoveryBundleFile, A2APublicDiscoveryBundleFile];
|
|
19
|
+
}
|
|
20
|
+
export interface WriteA2APublicDiscoveryBundleOptions {
|
|
21
|
+
readonly bundle: A2APublicDiscoveryBundle;
|
|
22
|
+
readonly outDir: string;
|
|
23
|
+
readonly manifestFileName?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface WrittenA2APublicDiscoveryBundleFile {
|
|
26
|
+
readonly path: string;
|
|
27
|
+
readonly sourcePath: typeof A2A_AGENT_CARD_WELL_KNOWN_PATH | typeof A2A_JWKS_WELL_KNOWN_PATH;
|
|
28
|
+
readonly headers: Record<string, string>;
|
|
29
|
+
}
|
|
30
|
+
export interface WrittenA2APublicDiscoveryBundle {
|
|
31
|
+
readonly outDir: string;
|
|
32
|
+
readonly publicBaseUrl: string;
|
|
33
|
+
readonly publicJwksUrl: string;
|
|
34
|
+
readonly files: readonly WrittenA2APublicDiscoveryBundleFile[];
|
|
35
|
+
readonly manifestPath: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ValidateA2APublicDiscoveryBundleArtifactsOptions {
|
|
38
|
+
readonly outDir: string;
|
|
39
|
+
readonly expectedPublicBaseUrl?: string;
|
|
40
|
+
readonly expectedPublicJwksUrl?: string;
|
|
41
|
+
readonly manifestFileName?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface ValidatedA2APublicDiscoveryBundleArtifacts {
|
|
44
|
+
readonly outDir: string;
|
|
45
|
+
readonly publicBaseUrl: string;
|
|
46
|
+
readonly publicJwksUrl: string;
|
|
47
|
+
readonly files: readonly WrittenA2APublicDiscoveryBundleFile[];
|
|
48
|
+
readonly manifestPath: string;
|
|
49
|
+
}
|
|
50
|
+
export declare function createA2APublicDiscoveryBundle(options: A2APublicDiscoveryBundleOptions): A2APublicDiscoveryBundle;
|
|
51
|
+
export declare function writeA2APublicDiscoveryBundle(options: WriteA2APublicDiscoveryBundleOptions): Promise<WrittenA2APublicDiscoveryBundle>;
|
|
52
|
+
export declare function validateA2APublicDiscoveryBundleArtifacts(options: ValidateA2APublicDiscoveryBundleArtifactsOptions): Promise<ValidatedA2APublicDiscoveryBundleArtifacts>;
|
|
53
|
+
//# sourceMappingURL=a2aDiscoveryBundle.d.ts.map
|