@highway1/core 0.1.30 → 0.1.32
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 +21 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -18
- package/src/discovery/agent-card-encoder.ts +0 -119
- package/src/discovery/agent-card-schema.ts +0 -87
- package/src/discovery/agent-card-types.ts +0 -99
- package/src/discovery/agent-card.ts +0 -190
- package/src/discovery/bootstrap.ts +0 -63
- package/src/discovery/capability-matcher.ts +0 -167
- package/src/discovery/dht.ts +0 -282
- package/src/discovery/index.ts +0 -3
- package/src/discovery/search-index.ts +0 -247
- package/src/discovery/semantic-search.ts +0 -218
- package/src/identity/did.ts +0 -48
- package/src/identity/document.ts +0 -77
- package/src/identity/index.ts +0 -4
- package/src/identity/keys.ts +0 -79
- package/src/identity/signer.ts +0 -55
- package/src/index.ts +0 -33
- package/src/messaging/codec.ts +0 -47
- package/src/messaging/envelope.ts +0 -107
- package/src/messaging/index.ts +0 -3
- package/src/messaging/router.ts +0 -368
- package/src/transport/connection.ts +0 -77
- package/src/transport/index.ts +0 -2
- package/src/transport/node.ts +0 -152
- package/src/trust/endorsement.ts +0 -167
- package/src/trust/index.ts +0 -194
- package/src/trust/interaction-history.ts +0 -155
- package/src/trust/sybil-defense.ts +0 -232
- package/src/trust/trust-score.ts +0 -136
- package/src/utils/errors.ts +0 -38
- package/src/utils/logger.ts +0 -48
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Capability Matcher
|
|
3
|
-
*
|
|
4
|
-
* Matches semantic queries against agent capabilities
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Capability } from './agent-card-types.js';
|
|
8
|
-
import type { SemanticQuery } from './search-index.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Capability Matcher
|
|
12
|
-
*/
|
|
13
|
-
export class CapabilityMatcher {
|
|
14
|
-
/**
|
|
15
|
-
* Match a query against a capability
|
|
16
|
-
* Returns a score between 0 and 1
|
|
17
|
-
*/
|
|
18
|
-
match(query: SemanticQuery, capability: Capability): number {
|
|
19
|
-
let score = 0;
|
|
20
|
-
let weights = 0;
|
|
21
|
-
|
|
22
|
-
// Exact capability ID match
|
|
23
|
-
if (query.capability && capability.id === query.capability) {
|
|
24
|
-
return 1.0;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Fuzzy capability name match
|
|
28
|
-
if (query.capability) {
|
|
29
|
-
const nameScore = this.fuzzyMatch(query.capability, capability.name);
|
|
30
|
-
score += nameScore * 0.4;
|
|
31
|
-
weights += 0.4;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Text keyword match
|
|
35
|
-
if (query.text) {
|
|
36
|
-
const keywords = this.extractKeywords(query.text);
|
|
37
|
-
const keywordScore = this.matchKeywords(keywords, capability);
|
|
38
|
-
score += keywordScore * 0.4;
|
|
39
|
-
weights += 0.4;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Type hierarchy match
|
|
43
|
-
if (query.capability && capability['@type']) {
|
|
44
|
-
const typeScore = this.matchesType(query.capability, capability['@type']) ? 0.6 : 0;
|
|
45
|
-
score += typeScore * 0.2;
|
|
46
|
-
weights += 0.2;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return weights > 0 ? score / weights : 0;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Extract keywords from natural language text
|
|
54
|
-
*/
|
|
55
|
-
extractKeywords(text: string): string[] {
|
|
56
|
-
// Remove common stop words
|
|
57
|
-
const stopWords = new Set([
|
|
58
|
-
'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been',
|
|
59
|
-
'to', 'from', 'in', 'on', 'at', 'by', 'for', 'with', 'about',
|
|
60
|
-
'can', 'could', 'should', 'would', 'will', 'do', 'does', 'did',
|
|
61
|
-
'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her',
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
return text
|
|
65
|
-
.toLowerCase()
|
|
66
|
-
.split(/\W+/)
|
|
67
|
-
.filter(word => word.length > 2 && !stopWords.has(word));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Match keywords against capability
|
|
72
|
-
*/
|
|
73
|
-
matchKeywords(keywords: string[], capability: Capability): number {
|
|
74
|
-
if (keywords.length === 0) return 0;
|
|
75
|
-
|
|
76
|
-
const capText = `${capability.name} ${capability.description}`.toLowerCase();
|
|
77
|
-
const matches = keywords.filter(keyword => capText.includes(keyword));
|
|
78
|
-
|
|
79
|
-
return matches.length / keywords.length;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Check if query matches capability type hierarchy
|
|
84
|
-
*/
|
|
85
|
-
matchesType(query: string, type: string): boolean {
|
|
86
|
-
const queryLower = query.toLowerCase();
|
|
87
|
-
const typeLower = type.toLowerCase();
|
|
88
|
-
|
|
89
|
-
// Direct match
|
|
90
|
-
if (typeLower.includes(queryLower)) return true;
|
|
91
|
-
|
|
92
|
-
// Common type mappings
|
|
93
|
-
const typeMap: Record<string, string[]> = {
|
|
94
|
-
translate: ['translation', 'translationservice'],
|
|
95
|
-
review: ['codereview', 'reviewservice'],
|
|
96
|
-
analyze: ['analysis', 'dataanalysis'],
|
|
97
|
-
generate: ['generation', 'textgeneration', 'imagegeneration'],
|
|
98
|
-
search: ['searchservice', 'query'],
|
|
99
|
-
compute: ['computation', 'computationservice'],
|
|
100
|
-
store: ['storage', 'storageservice'],
|
|
101
|
-
message: ['messaging', 'messagingservice'],
|
|
102
|
-
auth: ['authentication', 'authenticationservice'],
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
for (const [key, values] of Object.entries(typeMap)) {
|
|
106
|
-
if (queryLower.includes(key) && values.some(v => typeLower.includes(v))) {
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Fuzzy string matching using Levenshtein distance
|
|
116
|
-
*/
|
|
117
|
-
private fuzzyMatch(query: string, target: string): number {
|
|
118
|
-
const queryLower = query.toLowerCase();
|
|
119
|
-
const targetLower = target.toLowerCase();
|
|
120
|
-
|
|
121
|
-
// Exact match
|
|
122
|
-
if (queryLower === targetLower) return 1.0;
|
|
123
|
-
|
|
124
|
-
// Substring match
|
|
125
|
-
if (targetLower.includes(queryLower)) return 0.8;
|
|
126
|
-
if (queryLower.includes(targetLower)) return 0.7;
|
|
127
|
-
|
|
128
|
-
// Levenshtein distance
|
|
129
|
-
const distance = this.levenshteinDistance(queryLower, targetLower);
|
|
130
|
-
const maxLen = Math.max(queryLower.length, targetLower.length);
|
|
131
|
-
const similarity = 1 - distance / maxLen;
|
|
132
|
-
|
|
133
|
-
// Only return positive scores for reasonable similarity
|
|
134
|
-
return similarity > 0.5 ? similarity * 0.6 : 0;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Calculate Levenshtein distance between two strings
|
|
139
|
-
*/
|
|
140
|
-
private levenshteinDistance(a: string, b: string): number {
|
|
141
|
-
const matrix: number[][] = [];
|
|
142
|
-
|
|
143
|
-
for (let i = 0; i <= b.length; i++) {
|
|
144
|
-
matrix[i] = [i];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
for (let j = 0; j <= a.length; j++) {
|
|
148
|
-
matrix[0][j] = j;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
for (let i = 1; i <= b.length; i++) {
|
|
152
|
-
for (let j = 1; j <= a.length; j++) {
|
|
153
|
-
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
154
|
-
matrix[i][j] = matrix[i - 1][j - 1];
|
|
155
|
-
} else {
|
|
156
|
-
matrix[i][j] = Math.min(
|
|
157
|
-
matrix[i - 1][j - 1] + 1, // substitution
|
|
158
|
-
matrix[i][j - 1] + 1, // insertion
|
|
159
|
-
matrix[i - 1][j] + 1 // deletion
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return matrix[b.length][a.length];
|
|
166
|
-
}
|
|
167
|
-
}
|
package/src/discovery/dht.ts
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import type { Libp2p } from 'libp2p';
|
|
2
|
-
import type { AgentCard } from './agent-card.js';
|
|
3
|
-
import { createLogger } from '../utils/logger.js';
|
|
4
|
-
import { DiscoveryError } from '../utils/errors.js';
|
|
5
|
-
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string';
|
|
6
|
-
import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
|
|
7
|
-
import { encodeForDHT, decodeFromCBOR } from './agent-card-encoder.js';
|
|
8
|
-
import { createSemanticSearch } from './semantic-search.js';
|
|
9
|
-
import type { SemanticQuery } from './search-index.js';
|
|
10
|
-
|
|
11
|
-
const logger = createLogger('dht');
|
|
12
|
-
|
|
13
|
-
export interface ResolvedDID {
|
|
14
|
-
peerId: string;
|
|
15
|
-
multiaddrs: string[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface DHTOperations {
|
|
19
|
-
publishAgentCard: (card: AgentCard) => Promise<void>;
|
|
20
|
-
queryAgentCard: (did: string) => Promise<AgentCard | null>;
|
|
21
|
-
queryByCapability: (capability: string) => Promise<AgentCard[]>;
|
|
22
|
-
searchSemantic: (query: SemanticQuery) => Promise<AgentCard[]>;
|
|
23
|
-
resolveDID: (did: string) => Promise<ResolvedDID | null>;
|
|
24
|
-
queryRelayPeers: () => Promise<string[]>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Extract a DHT value from a get() event, handling both VALUE and PEER_RESPONSE */
|
|
28
|
-
function extractValue(event: any): Uint8Array | null {
|
|
29
|
-
if (event.name === 'VALUE' && event.value) return event.value;
|
|
30
|
-
if (event.name === 'PEER_RESPONSE' && event.value) return event.value;
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Normalize capability name for use as a DHT key segment */
|
|
35
|
-
function capKey(cap: string): string {
|
|
36
|
-
return cap.toLowerCase().replace(/[^a-z0-9_-]/g, '_');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Read a DID list stored at a DHT key (newline-separated) */
|
|
40
|
-
async function readDIDList(dht: any, key: Uint8Array): Promise<string[]> {
|
|
41
|
-
try {
|
|
42
|
-
for await (const event of dht.get(key, { signal: AbortSignal.timeout(30000) })) {
|
|
43
|
-
const raw = extractValue(event);
|
|
44
|
-
if (raw) {
|
|
45
|
-
const text = uint8ArrayToString(raw);
|
|
46
|
-
return text.split('\n').filter(Boolean);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
} catch {
|
|
50
|
-
// key not found or timeout
|
|
51
|
-
}
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Write a DID list to a DHT key (newline-separated), deduplicating */
|
|
56
|
-
async function writeDIDList(dht: any, key: Uint8Array, dids: string[]): Promise<void> {
|
|
57
|
-
const value = uint8ArrayFromString([...new Set(dids)].join('\n'));
|
|
58
|
-
// Retry up to 3 times with 30s timeout each
|
|
59
|
-
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
60
|
-
try {
|
|
61
|
-
for await (const _ of dht.put(key, value, { signal: AbortSignal.timeout(30000) })) { /* consume */ }
|
|
62
|
-
return; // Success
|
|
63
|
-
} catch (e: any) {
|
|
64
|
-
if (e?.name === 'AbortError' && attempt < 3) {
|
|
65
|
-
logger.debug(`DHT put timeout, retrying (${attempt}/3)...`);
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (e?.name !== 'AbortError') throw e;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Create DHT operations for a libp2p node
|
|
75
|
-
*/
|
|
76
|
-
export function createDHTOperations(libp2p: Libp2p): DHTOperations {
|
|
77
|
-
const operations: DHTOperations = {} as DHTOperations;
|
|
78
|
-
const searchEngine = createSemanticSearch(operations);
|
|
79
|
-
|
|
80
|
-
return Object.assign(operations, {
|
|
81
|
-
publishAgentCard: async (card: AgentCard) => {
|
|
82
|
-
try {
|
|
83
|
-
const dht = (libp2p as any).services?.dht;
|
|
84
|
-
if (!dht) throw new DiscoveryError('DHT service not available');
|
|
85
|
-
|
|
86
|
-
// 1. Store Agent Card under /clawiverse/agent/<did>
|
|
87
|
-
const agentKey = uint8ArrayFromString(`/clawiverse/agent/${card.did}`);
|
|
88
|
-
// Retry up to 3 times with 30s timeout each
|
|
89
|
-
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
90
|
-
try {
|
|
91
|
-
for await (const _ of dht.put(agentKey, encodeForDHT(card), { signal: AbortSignal.timeout(30000) })) { /* consume */ }
|
|
92
|
-
break; // Success
|
|
93
|
-
} catch (e: any) {
|
|
94
|
-
if (e?.name === 'AbortError' && attempt < 3) {
|
|
95
|
-
logger.debug(`DHT put agent card timeout, retrying (${attempt}/3)...`);
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
logger.warn('DHT put agent card failed (non-fatal)', { error: (e as Error).message });
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 2. For each capability, append this DID to /clawiverse/cap/<capability>
|
|
104
|
-
// Skip capability indexing in slow networks - rely on local index and semantic search instead
|
|
105
|
-
const caps: string[] = (card.capabilities ?? []).flatMap((c: any) => {
|
|
106
|
-
if (typeof c === 'string') return [c];
|
|
107
|
-
return [c.name, c.id].filter(Boolean);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Always index under the special "all" key so name/description search works
|
|
111
|
-
caps.push('__all__');
|
|
112
|
-
|
|
113
|
-
// Keep indexing lightweight, but preserve relay discovery path.
|
|
114
|
-
const importantCaps = ['__all__'];
|
|
115
|
-
if (caps.some((cap) => capKey(cap) === 'relay')) {
|
|
116
|
-
importantCaps.push('relay');
|
|
117
|
-
}
|
|
118
|
-
await Promise.all(importantCaps.map(async (cap) => {
|
|
119
|
-
const capKeyStr = `/clawiverse/cap/${capKey(cap)}`;
|
|
120
|
-
const capDHTKey = uint8ArrayFromString(capKeyStr);
|
|
121
|
-
try {
|
|
122
|
-
const existing = await readDIDList(dht, capDHTKey);
|
|
123
|
-
if (!existing.includes(card.did)) {
|
|
124
|
-
await writeDIDList(dht, capDHTKey, [...existing, card.did]);
|
|
125
|
-
logger.debug('Indexed capability in DHT', { cap: capKey(cap), did: card.did });
|
|
126
|
-
}
|
|
127
|
-
} catch (e: any) {
|
|
128
|
-
logger.warn('Failed to index capability (non-fatal)', { cap: capKey(cap), error: (e as Error).message });
|
|
129
|
-
}
|
|
130
|
-
}));
|
|
131
|
-
|
|
132
|
-
// 3. Index locally for fast in-process search
|
|
133
|
-
searchEngine.indexAgentCard(card);
|
|
134
|
-
|
|
135
|
-
logger.info('Published Agent Card to DHT', { did: card.did });
|
|
136
|
-
} catch (error) {
|
|
137
|
-
throw new DiscoveryError('Failed to publish Agent Card', error);
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
|
|
141
|
-
queryAgentCard: async (did: string) => {
|
|
142
|
-
try {
|
|
143
|
-
const dht = (libp2p as any).services?.dht;
|
|
144
|
-
if (!dht) throw new DiscoveryError('DHT service not available');
|
|
145
|
-
|
|
146
|
-
const key = uint8ArrayFromString(`/clawiverse/agent/${did}`);
|
|
147
|
-
for await (const event of dht.get(key)) {
|
|
148
|
-
const raw = extractValue(event);
|
|
149
|
-
if (raw) {
|
|
150
|
-
const card = decodeFromCBOR(raw);
|
|
151
|
-
searchEngine.indexAgentCard(card);
|
|
152
|
-
logger.debug('Found Agent Card in DHT', { did });
|
|
153
|
-
return card;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
logger.debug('Agent Card not found in DHT', { did });
|
|
158
|
-
return null;
|
|
159
|
-
} catch (error) {
|
|
160
|
-
logger.warn('Failed to query Agent Card', { did, error });
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
queryByCapability: async (capability: string) => {
|
|
166
|
-
try {
|
|
167
|
-
const dht = (libp2p as any).services?.dht;
|
|
168
|
-
|
|
169
|
-
// 1. Check local index first
|
|
170
|
-
const local = searchEngine.getAllIndexedCards().filter(card =>
|
|
171
|
-
card.capabilities.some((cap: any) => {
|
|
172
|
-
const name = typeof cap === 'string' ? cap : cap.name;
|
|
173
|
-
return name?.toLowerCase().includes(capability.toLowerCase());
|
|
174
|
-
})
|
|
175
|
-
);
|
|
176
|
-
if (local.length > 0) return local;
|
|
177
|
-
|
|
178
|
-
// 2. Fall back to DHT capability index
|
|
179
|
-
if (!dht) return [];
|
|
180
|
-
|
|
181
|
-
const capDHTKey = uint8ArrayFromString(`/clawiverse/cap/${capKey(capability)}`);
|
|
182
|
-
const dids = await readDIDList(dht, capDHTKey);
|
|
183
|
-
logger.debug('DHT capability index', { capability, dids });
|
|
184
|
-
|
|
185
|
-
// Fetch each Agent Card in parallel
|
|
186
|
-
const cards = await Promise.all(
|
|
187
|
-
dids.map(async (did) => {
|
|
188
|
-
const key = uint8ArrayFromString(`/clawiverse/agent/${did}`);
|
|
189
|
-
try {
|
|
190
|
-
for await (const event of dht.get(key)) {
|
|
191
|
-
const raw = extractValue(event);
|
|
192
|
-
if (raw) {
|
|
193
|
-
const card = decodeFromCBOR(raw);
|
|
194
|
-
searchEngine.indexAgentCard(card);
|
|
195
|
-
return card;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
} catch { /* skip unreachable */ }
|
|
199
|
-
return null;
|
|
200
|
-
})
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
return cards.filter((c): c is AgentCard => c !== null);
|
|
204
|
-
} catch (error) {
|
|
205
|
-
throw new DiscoveryError('Failed to query by capability', error);
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
searchSemantic: async (query: SemanticQuery) => {
|
|
210
|
-
try {
|
|
211
|
-
// Always pull from DHT __all__ index to discover remote nodes
|
|
212
|
-
const dht = (libp2p as any).services?.dht;
|
|
213
|
-
if (dht) {
|
|
214
|
-
const allKey = uint8ArrayFromString('/clawiverse/cap/__all__');
|
|
215
|
-
const dids = await readDIDList(dht, allKey);
|
|
216
|
-
logger.debug('DHT __all__ index', { count: dids.length });
|
|
217
|
-
|
|
218
|
-
await Promise.all(
|
|
219
|
-
dids.map(async (did) => {
|
|
220
|
-
const key = uint8ArrayFromString(`/clawiverse/agent/${did}`);
|
|
221
|
-
try {
|
|
222
|
-
for await (const event of dht.get(key)) {
|
|
223
|
-
const raw = extractValue(event);
|
|
224
|
-
if (raw) {
|
|
225
|
-
searchEngine.indexAgentCard(decodeFromCBOR(raw));
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
} catch { /* skip */ }
|
|
230
|
-
})
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return searchEngine.search(query);
|
|
235
|
-
} catch (error) {
|
|
236
|
-
throw new DiscoveryError('Failed to perform semantic search', error);
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
queryRelayPeers: async (): Promise<string[]> => {
|
|
241
|
-
const dht = (libp2p as any).services?.dht;
|
|
242
|
-
if (!dht) return [];
|
|
243
|
-
const capDHTKey = uint8ArrayFromString('/clawiverse/cap/relay');
|
|
244
|
-
const dids = await readDIDList(dht, capDHTKey);
|
|
245
|
-
const addrs: string[] = [];
|
|
246
|
-
await Promise.all(dids.map(async (did) => {
|
|
247
|
-
const card = await operations.queryAgentCard(did);
|
|
248
|
-
if (card?.endpoints) {
|
|
249
|
-
addrs.push(...card.endpoints.filter((e: string) => !e.includes('/p2p-circuit/')));
|
|
250
|
-
}
|
|
251
|
-
}));
|
|
252
|
-
return addrs;
|
|
253
|
-
},
|
|
254
|
-
|
|
255
|
-
resolveDID: async (did: string) => {
|
|
256
|
-
try {
|
|
257
|
-
const dht = (libp2p as any).services?.dht;
|
|
258
|
-
if (!dht) throw new DiscoveryError('DHT service not available');
|
|
259
|
-
|
|
260
|
-
const key = uint8ArrayFromString(`/clawiverse/agent/${did}`);
|
|
261
|
-
for await (const event of dht.get(key)) {
|
|
262
|
-
const raw = extractValue(event);
|
|
263
|
-
if (raw) {
|
|
264
|
-
const card = decodeFromCBOR(raw);
|
|
265
|
-
if (card.peerId) {
|
|
266
|
-
logger.debug('Resolved DID to peer', { did, peerId: card.peerId });
|
|
267
|
-
return { peerId: card.peerId, multiaddrs: card.endpoints || [] };
|
|
268
|
-
}
|
|
269
|
-
logger.warn('Agent Card found but has no peerId', { did });
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
logger.debug('DID not found in DHT', { did });
|
|
275
|
-
return null;
|
|
276
|
-
} catch (error) {
|
|
277
|
-
logger.warn('Failed to resolve DID', { did, error });
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
});
|
|
282
|
-
}
|
package/src/discovery/index.ts
DELETED
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Search Index for Agent Discovery
|
|
3
|
-
*
|
|
4
|
-
* Maintains a local index of discovered agents for fast semantic search
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import lunr from 'lunr';
|
|
8
|
-
import Fuse from 'fuse.js';
|
|
9
|
-
import type { AgentCard } from './agent-card-types.js';
|
|
10
|
-
import { createLogger } from '../utils/logger.js';
|
|
11
|
-
|
|
12
|
-
const logger = createLogger('search-index');
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Semantic Query Interface
|
|
16
|
-
*/
|
|
17
|
-
export interface SemanticQuery {
|
|
18
|
-
text?: string; // Natural language: "translate Japanese"
|
|
19
|
-
capability?: string; // Structured: "translate"
|
|
20
|
-
filters?: {
|
|
21
|
-
language?: string;
|
|
22
|
-
minTrustScore?: number;
|
|
23
|
-
maxCost?: number;
|
|
24
|
-
tags?: string[];
|
|
25
|
-
};
|
|
26
|
-
limit?: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Search Result with Score
|
|
31
|
-
*/
|
|
32
|
-
export interface SearchResult {
|
|
33
|
-
card: AgentCard;
|
|
34
|
-
score: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Search Index Implementation
|
|
39
|
-
*/
|
|
40
|
-
export class SearchIndex {
|
|
41
|
-
private cards = new Map<string, AgentCard>();
|
|
42
|
-
private lunrIndex?: lunr.Index;
|
|
43
|
-
private fuse?: Fuse<AgentCard>;
|
|
44
|
-
private needsRebuild = false;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Add or update an Agent Card in the index
|
|
48
|
-
*/
|
|
49
|
-
indexAgentCard(card: AgentCard): void {
|
|
50
|
-
this.cards.set(card.did, card);
|
|
51
|
-
this.needsRebuild = true;
|
|
52
|
-
logger.debug('Indexed Agent Card', { did: card.did, capabilities: card.capabilities.length });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Remove an Agent Card from the index
|
|
57
|
-
*/
|
|
58
|
-
removeAgentCard(did: string): void {
|
|
59
|
-
this.cards.delete(did);
|
|
60
|
-
this.needsRebuild = true;
|
|
61
|
-
logger.debug('Removed Agent Card from index', { did });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Search for agents matching a query
|
|
66
|
-
*/
|
|
67
|
-
search(query: SemanticQuery): SearchResult[] {
|
|
68
|
-
if (this.needsRebuild) {
|
|
69
|
-
this.rebuild();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
let results: SearchResult[] = [];
|
|
73
|
-
|
|
74
|
-
// Text search using Lunr
|
|
75
|
-
if (query.text && this.lunrIndex) {
|
|
76
|
-
results = this.searchByText(query.text);
|
|
77
|
-
}
|
|
78
|
-
// Capability search using Fuse
|
|
79
|
-
else if (query.capability && this.fuse) {
|
|
80
|
-
results = this.searchByCapability(query.capability);
|
|
81
|
-
}
|
|
82
|
-
// No query - return all
|
|
83
|
-
else {
|
|
84
|
-
results = Array.from(this.cards.values()).map(card => ({
|
|
85
|
-
card,
|
|
86
|
-
score: 1.0,
|
|
87
|
-
}));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Apply filters
|
|
91
|
-
if (query.filters) {
|
|
92
|
-
results = this.applyFilters(results, query.filters);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Sort by score (descending)
|
|
96
|
-
results.sort((a, b) => b.score - a.score);
|
|
97
|
-
|
|
98
|
-
// Apply limit
|
|
99
|
-
if (query.limit) {
|
|
100
|
-
results = results.slice(0, query.limit);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
logger.debug('Search completed', { query, results: results.length });
|
|
104
|
-
return results;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Get all indexed cards
|
|
109
|
-
*/
|
|
110
|
-
getAllCards(): AgentCard[] {
|
|
111
|
-
return Array.from(this.cards.values());
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Clear the index
|
|
116
|
-
*/
|
|
117
|
-
clear(): void {
|
|
118
|
-
this.cards.clear();
|
|
119
|
-
this.lunrIndex = undefined;
|
|
120
|
-
this.fuse = undefined;
|
|
121
|
-
this.needsRebuild = false;
|
|
122
|
-
logger.info('Search index cleared');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Get index size
|
|
127
|
-
*/
|
|
128
|
-
size(): number {
|
|
129
|
-
return this.cards.size;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Rebuild search indexes
|
|
134
|
-
*/
|
|
135
|
-
private rebuild(): void {
|
|
136
|
-
logger.info('Rebuilding search indexes', { cards: this.cards.size });
|
|
137
|
-
|
|
138
|
-
const cards = Array.from(this.cards.values());
|
|
139
|
-
|
|
140
|
-
// Build Lunr index for full-text search
|
|
141
|
-
this.lunrIndex = lunr(function (this: lunr.Builder) {
|
|
142
|
-
this.ref('did');
|
|
143
|
-
this.field('name', { boost: 10 });
|
|
144
|
-
this.field('description', { boost: 5 });
|
|
145
|
-
this.field('capabilities');
|
|
146
|
-
|
|
147
|
-
for (const card of cards) {
|
|
148
|
-
this.add({
|
|
149
|
-
did: card.did,
|
|
150
|
-
name: card.name,
|
|
151
|
-
description: card.description,
|
|
152
|
-
capabilities: card.capabilities
|
|
153
|
-
.map((cap: any) => `${cap.name} ${cap.description}`)
|
|
154
|
-
.join(' '),
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Build Fuse index for fuzzy matching
|
|
160
|
-
this.fuse = new Fuse(Array.from(this.cards.values()), {
|
|
161
|
-
keys: [
|
|
162
|
-
{ name: 'name', weight: 0.3 },
|
|
163
|
-
{ name: 'description', weight: 0.2 },
|
|
164
|
-
{ name: 'capabilities.name', weight: 0.3 },
|
|
165
|
-
{ name: 'capabilities.description', weight: 0.2 },
|
|
166
|
-
],
|
|
167
|
-
threshold: 0.4,
|
|
168
|
-
includeScore: true,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
this.needsRebuild = false;
|
|
172
|
-
logger.info('Search indexes rebuilt');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Search by text using Lunr
|
|
177
|
-
*/
|
|
178
|
-
private searchByText(text: string): SearchResult[] {
|
|
179
|
-
if (!this.lunrIndex) return [];
|
|
180
|
-
|
|
181
|
-
const lunrResults = this.lunrIndex.search(text);
|
|
182
|
-
return lunrResults.map(result => ({
|
|
183
|
-
card: this.cards.get(result.ref)!,
|
|
184
|
-
score: result.score,
|
|
185
|
-
}));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Search by capability using Fuse
|
|
190
|
-
*/
|
|
191
|
-
private searchByCapability(capability: string): SearchResult[] {
|
|
192
|
-
if (!this.fuse) return [];
|
|
193
|
-
|
|
194
|
-
const fuseResults = this.fuse.search(capability);
|
|
195
|
-
return fuseResults.map(result => ({
|
|
196
|
-
card: result.item,
|
|
197
|
-
score: 1 - (result.score || 0), // Fuse score is distance, convert to similarity
|
|
198
|
-
}));
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Apply filters to search results
|
|
203
|
-
*/
|
|
204
|
-
private applyFilters(
|
|
205
|
-
results: SearchResult[],
|
|
206
|
-
filters: NonNullable<SemanticQuery['filters']>
|
|
207
|
-
): SearchResult[] {
|
|
208
|
-
return results.filter(result => {
|
|
209
|
-
const { card } = result;
|
|
210
|
-
|
|
211
|
-
// Trust score filter
|
|
212
|
-
if (filters.minTrustScore !== undefined && card.trust) {
|
|
213
|
-
const overallTrust =
|
|
214
|
-
card.trust.interactionScore * 0.4 +
|
|
215
|
-
Math.min(card.trust.endorsements / 10, 1) * 0.2 +
|
|
216
|
-
card.trust.completionRate * 0.2 +
|
|
217
|
-
card.trust.uptime * 0.2;
|
|
218
|
-
|
|
219
|
-
if (overallTrust < filters.minTrustScore) {
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Language filter (check capability metadata)
|
|
225
|
-
if (filters.language) {
|
|
226
|
-
const hasLanguage = card.capabilities.some(
|
|
227
|
-
cap => {
|
|
228
|
-
const metadata = cap.metadata as Record<string, any> | undefined;
|
|
229
|
-
if (!metadata) return false;
|
|
230
|
-
return metadata.language === filters.language ||
|
|
231
|
-
(Array.isArray(metadata.languages) && metadata.languages.includes(filters.language));
|
|
232
|
-
}
|
|
233
|
-
);
|
|
234
|
-
if (!hasLanguage) return false;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Tags filter
|
|
238
|
-
if (filters.tags && filters.tags.length > 0) {
|
|
239
|
-
const cardTags = Array.isArray(card.metadata?.tags) ? (card.metadata.tags as string[]) : [];
|
|
240
|
-
const hasAllTags = filters.tags.every(tag => cardTags.includes(tag));
|
|
241
|
-
if (!hasAllTags) return false;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return true;
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|