@highway1/core 0.1.43 → 0.1.45
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/dist/index.js +68 -40
- package/dist/index.js.map +1 -1
- package/package.json +18 -4
- package/src/discovery/agent-card-encoder.ts +119 -0
- package/src/discovery/agent-card-schema.ts +87 -0
- package/src/discovery/agent-card-types.ts +99 -0
- package/src/discovery/agent-card.ts +190 -0
- package/src/discovery/bootstrap.ts +63 -0
- package/src/discovery/capability-matcher.ts +167 -0
- package/src/discovery/dht.ts +310 -0
- package/src/discovery/index.ts +3 -0
- package/src/discovery/search-index.ts +247 -0
- package/src/discovery/semantic-search.ts +218 -0
- package/src/identity/did.ts +48 -0
- package/src/identity/document.ts +77 -0
- package/src/identity/index.ts +4 -0
- package/src/identity/keys.ts +79 -0
- package/src/identity/signer.ts +55 -0
- package/src/index.ts +33 -0
- package/src/messaging/codec.ts +47 -0
- package/src/messaging/envelope.ts +107 -0
- package/src/messaging/index.ts +3 -0
- package/src/messaging/router.ts +384 -0
- package/src/transport/connection.ts +77 -0
- package/src/transport/index.ts +2 -0
- package/src/transport/node.ts +152 -0
- package/src/trust/endorsement.ts +167 -0
- package/src/trust/index.ts +194 -0
- package/src/trust/interaction-history.ts +155 -0
- package/src/trust/sybil-defense.ts +232 -0
- package/src/trust/trust-score.ts +136 -0
- package/src/utils/errors.ts +38 -0
- package/src/utils/logger.ts +48 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@highway1/core",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Core protocol
|
|
3
|
+
"version": "0.1.45",
|
|
4
|
+
"description": "Core protocol implementation for Clawiverse",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -11,11 +11,16 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"
|
|
15
|
-
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"dev": "tsup --watch",
|
|
17
|
+
"test": "vitest",
|
|
18
|
+
"clean": "rm -rf dist"
|
|
19
|
+
},
|
|
16
20
|
"dependencies": {
|
|
17
21
|
"@chainsafe/libp2p-noise": "^16.0.0",
|
|
18
22
|
"@libp2p/bootstrap": "^11.0.0",
|
|
23
|
+
"@libp2p/mdns": "^12.0.0",
|
|
19
24
|
"@libp2p/circuit-relay-v2": "^3.1.0",
|
|
20
25
|
"@libp2p/identify": "^3.0.0",
|
|
21
26
|
"@libp2p/kad-dht": "^13.0.0",
|
|
@@ -35,5 +40,14 @@
|
|
|
35
40
|
"lunr": "^2.3.9",
|
|
36
41
|
"multiformats": "^13.3.1",
|
|
37
42
|
"uint8arrays": "^5.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@libp2p/interface": "^2.11.0",
|
|
46
|
+
"@types/level": "^6.0.0",
|
|
47
|
+
"@types/lunr": "^2.3.7",
|
|
48
|
+
"@types/node": "^22.10.2",
|
|
49
|
+
"tsup": "^8.3.5",
|
|
50
|
+
"typescript": "^5.7.2",
|
|
51
|
+
"vitest": "^2.1.8"
|
|
38
52
|
}
|
|
39
53
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Card Encoder - Dual Encoding Support
|
|
3
|
+
*
|
|
4
|
+
* Provides bidirectional conversion between:
|
|
5
|
+
* - CBOR (compact binary) for DHT storage
|
|
6
|
+
* - JSON-LD (semantic) for Web publishing
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { encode as cborEncode, decode as cborDecode } from 'cbor-x';
|
|
10
|
+
import type { AgentCard, LegacyAgentCard } from './agent-card-types.js';
|
|
11
|
+
import { isLegacyCard, upgradeLegacyCard } from './agent-card-types.js';
|
|
12
|
+
import { getAgentCardContext } from './agent-card-schema.js';
|
|
13
|
+
import { DiscoveryError } from '../utils/errors.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Encode Agent Card as CBOR for DHT storage
|
|
17
|
+
*/
|
|
18
|
+
export function encodeForDHT(card: AgentCard): Uint8Array {
|
|
19
|
+
try {
|
|
20
|
+
// Remove JSON-LD context for compact storage
|
|
21
|
+
const { '@context': _, ...cardWithoutContext } = card;
|
|
22
|
+
return cborEncode(cardWithoutContext);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw new DiscoveryError('Failed to encode Agent Card as CBOR', error);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Encode Agent Card as JSON-LD for Web publishing
|
|
30
|
+
*/
|
|
31
|
+
export function encodeForWeb(card: AgentCard): string {
|
|
32
|
+
try {
|
|
33
|
+
// Ensure JSON-LD context is present
|
|
34
|
+
const cardWithContext: AgentCard = {
|
|
35
|
+
'@context': card['@context'] || getAgentCardContext(),
|
|
36
|
+
...card,
|
|
37
|
+
};
|
|
38
|
+
return JSON.stringify(cardWithContext, null, 2);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new DiscoveryError('Failed to encode Agent Card as JSON-LD', error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Decode Agent Card from CBOR
|
|
46
|
+
*/
|
|
47
|
+
export function decodeFromCBOR(data: Uint8Array): AgentCard {
|
|
48
|
+
try {
|
|
49
|
+
const decoded = cborDecode(data) as AgentCard | LegacyAgentCard;
|
|
50
|
+
|
|
51
|
+
// Handle legacy format
|
|
52
|
+
if (isLegacyCard(decoded)) {
|
|
53
|
+
return upgradeLegacyCard(decoded);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Add default context if missing
|
|
57
|
+
if (!decoded['@context']) {
|
|
58
|
+
decoded['@context'] = getAgentCardContext();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return decoded;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
throw new DiscoveryError('Failed to decode Agent Card from CBOR', error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Decode Agent Card from JSON-LD
|
|
69
|
+
*/
|
|
70
|
+
export function decodeFromJSON(json: string): AgentCard {
|
|
71
|
+
try {
|
|
72
|
+
const decoded = JSON.parse(json) as AgentCard | LegacyAgentCard;
|
|
73
|
+
|
|
74
|
+
// Handle legacy format
|
|
75
|
+
if (isLegacyCard(decoded)) {
|
|
76
|
+
return upgradeLegacyCard(decoded);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Add default context if missing
|
|
80
|
+
if (!decoded['@context']) {
|
|
81
|
+
decoded['@context'] = getAgentCardContext();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return decoded;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new DiscoveryError('Failed to decode Agent Card from JSON', error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Auto-detect format and decode
|
|
92
|
+
*/
|
|
93
|
+
export function decodeAgentCard(data: Uint8Array | string): AgentCard {
|
|
94
|
+
if (typeof data === 'string') {
|
|
95
|
+
return decodeFromJSON(data);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Try CBOR first, fallback to JSON if it looks like text
|
|
99
|
+
try {
|
|
100
|
+
return decodeFromCBOR(data);
|
|
101
|
+
} catch {
|
|
102
|
+
// Check if it's actually JSON text
|
|
103
|
+
const text = new TextDecoder().decode(data);
|
|
104
|
+
if (text.trim().startsWith('{')) {
|
|
105
|
+
return decodeFromJSON(text);
|
|
106
|
+
}
|
|
107
|
+
throw new DiscoveryError('Unable to decode Agent Card: unknown format');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Calculate encoded size for comparison
|
|
113
|
+
*/
|
|
114
|
+
export function getEncodedSize(card: AgentCard): { cbor: number; json: number } {
|
|
115
|
+
return {
|
|
116
|
+
cbor: encodeForDHT(card).length,
|
|
117
|
+
json: encodeForWeb(card).length,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-LD Schema Definitions for Agent Cards
|
|
3
|
+
*
|
|
4
|
+
* Defines the Clawiverse vocabulary and integrates with Schema.org
|
|
5
|
+
* for semantic interoperability.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const CLAWIVERSE_CONTEXT = 'https://clawiverse.org/context/v1';
|
|
9
|
+
export const SCHEMA_ORG_CONTEXT = 'https://schema.org';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Clawiverse JSON-LD Context
|
|
13
|
+
* Defines the vocabulary for Agent Cards and Capabilities
|
|
14
|
+
*/
|
|
15
|
+
export const clawiverseContext = {
|
|
16
|
+
'@context': {
|
|
17
|
+
'@vocab': CLAWIVERSE_CONTEXT,
|
|
18
|
+
'schema': SCHEMA_ORG_CONTEXT,
|
|
19
|
+
'AgentCard': 'schema:SoftwareApplication',
|
|
20
|
+
'Capability': 'schema:Action',
|
|
21
|
+
'did': '@id',
|
|
22
|
+
'name': 'schema:name',
|
|
23
|
+
'description': 'schema:description',
|
|
24
|
+
'version': 'schema:softwareVersion',
|
|
25
|
+
'capabilities': {
|
|
26
|
+
'@id': 'schema:potentialAction',
|
|
27
|
+
'@type': '@id',
|
|
28
|
+
'@container': '@list'
|
|
29
|
+
},
|
|
30
|
+
'endpoints': {
|
|
31
|
+
'@id': 'schema:url',
|
|
32
|
+
'@container': '@list'
|
|
33
|
+
},
|
|
34
|
+
'peerId': 'clawiverse:peerId',
|
|
35
|
+
'trust': 'clawiverse:trustScore',
|
|
36
|
+
'metadata': 'schema:additionalProperty',
|
|
37
|
+
'timestamp': 'schema:dateModified',
|
|
38
|
+
'signature': 'clawiverse:signature',
|
|
39
|
+
'parameters': {
|
|
40
|
+
'@id': 'schema:object',
|
|
41
|
+
'@container': '@list'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Capability Type Definitions
|
|
48
|
+
* Common capability types with semantic meaning
|
|
49
|
+
*/
|
|
50
|
+
export const CapabilityTypes = {
|
|
51
|
+
TRANSLATION: 'TranslationService',
|
|
52
|
+
CODE_REVIEW: 'CodeReviewService',
|
|
53
|
+
DATA_ANALYSIS: 'DataAnalysisService',
|
|
54
|
+
TEXT_GENERATION: 'TextGenerationService',
|
|
55
|
+
IMAGE_GENERATION: 'ImageGenerationService',
|
|
56
|
+
SEARCH: 'SearchService',
|
|
57
|
+
COMPUTATION: 'ComputationService',
|
|
58
|
+
STORAGE: 'StorageService',
|
|
59
|
+
MESSAGING: 'MessagingService',
|
|
60
|
+
AUTHENTICATION: 'AuthenticationService',
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parameter Type Definitions
|
|
65
|
+
*/
|
|
66
|
+
export const ParameterTypes = {
|
|
67
|
+
STRING: 'string',
|
|
68
|
+
NUMBER: 'number',
|
|
69
|
+
BOOLEAN: 'boolean',
|
|
70
|
+
OBJECT: 'object',
|
|
71
|
+
ARRAY: 'array',
|
|
72
|
+
} as const;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get JSON-LD context for Agent Card
|
|
76
|
+
*/
|
|
77
|
+
export function getAgentCardContext(): string[] {
|
|
78
|
+
return [SCHEMA_ORG_CONTEXT, CLAWIVERSE_CONTEXT];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Validate JSON-LD context
|
|
83
|
+
*/
|
|
84
|
+
export function isValidContext(context: unknown): boolean {
|
|
85
|
+
if (!Array.isArray(context)) return false;
|
|
86
|
+
return context.every(c => typeof c === 'string');
|
|
87
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Agent Card Types for Phase 2
|
|
3
|
+
*
|
|
4
|
+
* Adds structured capabilities, JSON-LD support, and trust metrics
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { TrustScore } from '../trust/trust-score.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Capability Parameter Definition
|
|
11
|
+
*/
|
|
12
|
+
export interface CapabilityParameter {
|
|
13
|
+
name: string;
|
|
14
|
+
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
15
|
+
required: boolean;
|
|
16
|
+
description?: string;
|
|
17
|
+
enum?: string[];
|
|
18
|
+
default?: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Structured Capability Definition
|
|
23
|
+
*/
|
|
24
|
+
export interface Capability {
|
|
25
|
+
'@type'?: string; // JSON-LD type (e.g., "TranslationService")
|
|
26
|
+
id: string; // Unique capability ID
|
|
27
|
+
name: string; // Human-readable name
|
|
28
|
+
description: string;
|
|
29
|
+
parameters?: CapabilityParameter[];
|
|
30
|
+
metadata?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Enhanced Agent Card with JSON-LD support
|
|
35
|
+
*/
|
|
36
|
+
export interface AgentCard {
|
|
37
|
+
'@context'?: string[]; // JSON-LD context
|
|
38
|
+
did: string;
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
version: string;
|
|
42
|
+
capabilities: Capability[]; // Changed from string[] to Capability[]
|
|
43
|
+
endpoints: string[];
|
|
44
|
+
peerId?: string;
|
|
45
|
+
trust?: TrustScore; // Trust metrics
|
|
46
|
+
metadata?: Record<string, unknown>;
|
|
47
|
+
timestamp: number;
|
|
48
|
+
signature: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Legacy Agent Card (Phase 1 compatibility)
|
|
53
|
+
*/
|
|
54
|
+
export interface LegacyAgentCard {
|
|
55
|
+
did: string;
|
|
56
|
+
name: string;
|
|
57
|
+
description: string;
|
|
58
|
+
version: string;
|
|
59
|
+
capabilities: string[]; // Flat string array
|
|
60
|
+
endpoints: string[];
|
|
61
|
+
peerId?: string;
|
|
62
|
+
metadata?: Record<string, unknown>;
|
|
63
|
+
timestamp: number;
|
|
64
|
+
signature: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if card is legacy format
|
|
69
|
+
*/
|
|
70
|
+
export function isLegacyCard(card: AgentCard | LegacyAgentCard): card is LegacyAgentCard {
|
|
71
|
+
return Array.isArray(card.capabilities) &&
|
|
72
|
+
card.capabilities.length > 0 &&
|
|
73
|
+
typeof card.capabilities[0] === 'string';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Convert legacy card to new format
|
|
78
|
+
*/
|
|
79
|
+
export function upgradeLegacyCard(legacy: LegacyAgentCard): AgentCard {
|
|
80
|
+
return {
|
|
81
|
+
...legacy,
|
|
82
|
+
capabilities: legacy.capabilities.map(cap => ({
|
|
83
|
+
id: cap,
|
|
84
|
+
name: cap,
|
|
85
|
+
description: `Capability: ${cap}`,
|
|
86
|
+
})),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Convert new card to legacy format (for backward compatibility)
|
|
92
|
+
*/
|
|
93
|
+
export function downgradeToLegacyCard(card: AgentCard): LegacyAgentCard {
|
|
94
|
+
const { '@context': _, trust: __, ...rest } = card;
|
|
95
|
+
return {
|
|
96
|
+
...rest,
|
|
97
|
+
capabilities: card.capabilities.map(cap => cap.id),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import Ajv, { JSONSchemaType } from 'ajv';
|
|
2
|
+
import { DiscoveryError } from '../utils/errors.js';
|
|
3
|
+
|
|
4
|
+
// Re-export Phase 2 types
|
|
5
|
+
export type { AgentCard, Capability, CapabilityParameter, LegacyAgentCard } from './agent-card-types.js';
|
|
6
|
+
export { isLegacyCard, upgradeLegacyCard, downgradeToLegacyCard } from './agent-card-types.js';
|
|
7
|
+
|
|
8
|
+
// Import for internal use
|
|
9
|
+
import type { AgentCard, Capability, LegacyAgentCard } from './agent-card-types.js';
|
|
10
|
+
import { isLegacyCard } from './agent-card-types.js';
|
|
11
|
+
|
|
12
|
+
// Legacy schema for backward compatibility
|
|
13
|
+
const legacyAgentCardSchema: JSONSchemaType<Omit<LegacyAgentCard, 'signature'>> = {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
did: { type: 'string', pattern: '^did:clawiverse:[1-9A-HJ-NP-Za-km-z]+$' },
|
|
17
|
+
name: { type: 'string', minLength: 1, maxLength: 100 },
|
|
18
|
+
description: { type: 'string', maxLength: 500 },
|
|
19
|
+
version: { type: 'string', pattern: '^\\d+\\.\\d+\\.\\d+$' },
|
|
20
|
+
capabilities: {
|
|
21
|
+
type: 'array',
|
|
22
|
+
items: { type: 'string' },
|
|
23
|
+
minItems: 0,
|
|
24
|
+
maxItems: 50,
|
|
25
|
+
},
|
|
26
|
+
endpoints: {
|
|
27
|
+
type: 'array',
|
|
28
|
+
items: { type: 'string' },
|
|
29
|
+
minItems: 0,
|
|
30
|
+
maxItems: 10,
|
|
31
|
+
},
|
|
32
|
+
peerId: { type: 'string', nullable: true },
|
|
33
|
+
metadata: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
nullable: true,
|
|
36
|
+
required: [],
|
|
37
|
+
},
|
|
38
|
+
timestamp: { type: 'number' },
|
|
39
|
+
},
|
|
40
|
+
required: ['did', 'name', 'description', 'version', 'capabilities', 'endpoints', 'timestamp'],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const ajv = new Ajv();
|
|
45
|
+
const validateLegacySchema = ajv.compile(legacyAgentCardSchema);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a new Agent Card (Phase 2 format with structured capabilities)
|
|
49
|
+
*/
|
|
50
|
+
export function createAgentCard(
|
|
51
|
+
did: string,
|
|
52
|
+
name: string,
|
|
53
|
+
description: string,
|
|
54
|
+
capabilities: Capability[],
|
|
55
|
+
endpoints: string[] = [],
|
|
56
|
+
peerId?: string,
|
|
57
|
+
metadata?: Record<string, unknown>
|
|
58
|
+
): Omit<AgentCard, 'signature'> {
|
|
59
|
+
return {
|
|
60
|
+
did,
|
|
61
|
+
name,
|
|
62
|
+
description,
|
|
63
|
+
version: '1.0.0',
|
|
64
|
+
capabilities,
|
|
65
|
+
endpoints,
|
|
66
|
+
peerId,
|
|
67
|
+
metadata,
|
|
68
|
+
timestamp: Date.now(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a legacy Agent Card (Phase 1 format with string capabilities)
|
|
74
|
+
* For backward compatibility
|
|
75
|
+
*/
|
|
76
|
+
export function createLegacyAgentCard(
|
|
77
|
+
did: string,
|
|
78
|
+
name: string,
|
|
79
|
+
description: string,
|
|
80
|
+
capabilities: string[],
|
|
81
|
+
endpoints: string[] = [],
|
|
82
|
+
peerId?: string,
|
|
83
|
+
metadata?: Record<string, unknown>
|
|
84
|
+
): Omit<LegacyAgentCard, 'signature'> {
|
|
85
|
+
return {
|
|
86
|
+
did,
|
|
87
|
+
name,
|
|
88
|
+
description,
|
|
89
|
+
version: '1.0.0',
|
|
90
|
+
capabilities,
|
|
91
|
+
endpoints,
|
|
92
|
+
peerId,
|
|
93
|
+
metadata,
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Validate an Agent Card structure (supports both Phase 1 and Phase 2 formats)
|
|
100
|
+
*/
|
|
101
|
+
export function validateAgentCard(card: unknown): card is AgentCard | LegacyAgentCard {
|
|
102
|
+
if (typeof card !== 'object' || card === null) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const c = card as Partial<AgentCard | LegacyAgentCard>;
|
|
107
|
+
|
|
108
|
+
// Check signature exists
|
|
109
|
+
if (typeof c.signature !== 'string') {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check if it's legacy format
|
|
114
|
+
if (isLegacyCard(c as any)) {
|
|
115
|
+
return validateLegacySchema(c);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Phase 2 format - basic validation
|
|
119
|
+
// (Full JSON-LD validation would be more complex)
|
|
120
|
+
return (
|
|
121
|
+
typeof c.did === 'string' &&
|
|
122
|
+
typeof c.name === 'string' &&
|
|
123
|
+
typeof c.description === 'string' &&
|
|
124
|
+
typeof c.version === 'string' &&
|
|
125
|
+
Array.isArray(c.capabilities) &&
|
|
126
|
+
Array.isArray(c.endpoints) &&
|
|
127
|
+
typeof c.timestamp === 'number'
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Sign an Agent Card
|
|
133
|
+
*/
|
|
134
|
+
export async function signAgentCard(
|
|
135
|
+
card: Omit<AgentCard, 'signature'>,
|
|
136
|
+
signFn: (data: Uint8Array) => Promise<Uint8Array>
|
|
137
|
+
): Promise<AgentCard> {
|
|
138
|
+
try {
|
|
139
|
+
const cardJson = JSON.stringify(card);
|
|
140
|
+
const cardBytes = new TextEncoder().encode(cardJson);
|
|
141
|
+
const signature = await signFn(cardBytes);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
...card,
|
|
145
|
+
signature: Buffer.from(signature).toString('hex'),
|
|
146
|
+
};
|
|
147
|
+
} catch (error) {
|
|
148
|
+
throw new DiscoveryError('Failed to sign Agent Card', error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Verify an Agent Card signature
|
|
154
|
+
*/
|
|
155
|
+
export async function verifyAgentCard(
|
|
156
|
+
card: AgentCard,
|
|
157
|
+
verifyFn: (signature: Uint8Array, data: Uint8Array) => Promise<boolean>
|
|
158
|
+
): Promise<boolean> {
|
|
159
|
+
try {
|
|
160
|
+
const { signature, ...cardWithoutSig } = card;
|
|
161
|
+
const cardJson = JSON.stringify(cardWithoutSig);
|
|
162
|
+
const cardBytes = new TextEncoder().encode(cardJson);
|
|
163
|
+
const signatureBytes = Buffer.from(signature, 'hex');
|
|
164
|
+
|
|
165
|
+
return await verifyFn(signatureBytes, cardBytes);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
throw new DiscoveryError('Failed to verify Agent Card', error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if an Agent Card matches a capability query
|
|
173
|
+
* Supports both Phase 1 (string[]) and Phase 2 (Capability[]) formats
|
|
174
|
+
*/
|
|
175
|
+
export function matchesCapability(
|
|
176
|
+
card: AgentCard | LegacyAgentCard,
|
|
177
|
+
capability: string
|
|
178
|
+
): boolean {
|
|
179
|
+
if (isLegacyCard(card)) {
|
|
180
|
+
return card.capabilities.some((cap) =>
|
|
181
|
+
cap.toLowerCase().includes(capability.toLowerCase())
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return card.capabilities.some((cap) =>
|
|
186
|
+
cap.id.toLowerCase().includes(capability.toLowerCase()) ||
|
|
187
|
+
cap.name.toLowerCase().includes(capability.toLowerCase()) ||
|
|
188
|
+
cap.description.toLowerCase().includes(capability.toLowerCase())
|
|
189
|
+
);
|
|
190
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Libp2p } from 'libp2p';
|
|
2
|
+
import { createLogger } from '../utils/logger.js';
|
|
3
|
+
import { DiscoveryError } from '../utils/errors.js';
|
|
4
|
+
|
|
5
|
+
const logger = createLogger('bootstrap');
|
|
6
|
+
|
|
7
|
+
export interface BootstrapConfig {
|
|
8
|
+
peers: string[];
|
|
9
|
+
timeout?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Connect to bootstrap peers
|
|
14
|
+
*/
|
|
15
|
+
export async function connectToBootstrap(
|
|
16
|
+
_libp2p: Libp2p,
|
|
17
|
+
config: BootstrapConfig
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
const { peers } = config;
|
|
20
|
+
|
|
21
|
+
if (peers.length === 0) {
|
|
22
|
+
logger.warn('No bootstrap peers configured');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
logger.info('Connecting to bootstrap peers', { count: peers.length });
|
|
27
|
+
|
|
28
|
+
const connectPromises = peers.map(async (peerAddr) => {
|
|
29
|
+
try {
|
|
30
|
+
// In a real implementation, we would parse the multiaddr and dial
|
|
31
|
+
// For now, we rely on libp2p's bootstrap discovery
|
|
32
|
+
logger.debug('Bootstrap peer configured', { peer: peerAddr });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.warn('Failed to connect to bootstrap peer', { peer: peerAddr, error });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await Promise.allSettled(connectPromises);
|
|
39
|
+
|
|
40
|
+
logger.info('Bootstrap connection complete');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Wait for DHT to be ready
|
|
45
|
+
*/
|
|
46
|
+
export async function waitForDHTReady(
|
|
47
|
+
libp2p: Libp2p,
|
|
48
|
+
timeout: number = 10000
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
const startTime = Date.now();
|
|
51
|
+
|
|
52
|
+
while (Date.now() - startTime < timeout) {
|
|
53
|
+
const dht = (libp2p as any).services?.dht;
|
|
54
|
+
if (dht) {
|
|
55
|
+
logger.info('DHT is ready');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw new DiscoveryError('DHT not ready within timeout');
|
|
63
|
+
}
|