@agentic-trust/agentic-trust-sdk 1.0.43
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/LICENSE +22 -0
- package/README.md +213 -0
- package/abis/BaseRegistrarImplementation.json +1013 -0
- package/abis/ETHRegistrarController.json +1004 -0
- package/abis/IdentityRegistry.json +1044 -0
- package/abis/NameWrapper.json +2026 -0
- package/abis/PublicResolver.json +1772 -0
- package/abis/ReputationRegistry.json +701 -0
- package/abis/ValidationRegistry.json +505 -0
- package/dist/AIAgentAssociationClient.d.ts +58 -0
- package/dist/AIAgentAssociationClient.d.ts.map +1 -0
- package/dist/AIAgentAssociationClient.js +100 -0
- package/dist/AIAgentAssociationClient.js.map +1 -0
- package/dist/AIAgentDiscoveryClient.d.ts +673 -0
- package/dist/AIAgentDiscoveryClient.d.ts.map +1 -0
- package/dist/AIAgentDiscoveryClient.js +3184 -0
- package/dist/AIAgentDiscoveryClient.js.map +1 -0
- package/dist/AIAgentENSClient.d.ts +149 -0
- package/dist/AIAgentENSClient.d.ts.map +1 -0
- package/dist/AIAgentENSClient.js +958 -0
- package/dist/AIAgentENSClient.js.map +1 -0
- package/dist/AIAgentIdentityClient.d.ts +159 -0
- package/dist/AIAgentIdentityClient.d.ts.map +1 -0
- package/dist/AIAgentIdentityClient.js +660 -0
- package/dist/AIAgentIdentityClient.js.map +1 -0
- package/dist/AIAgentL2ENSDurenClient.d.ts +120 -0
- package/dist/AIAgentL2ENSDurenClient.d.ts.map +1 -0
- package/dist/AIAgentL2ENSDurenClient.js +735 -0
- package/dist/AIAgentL2ENSDurenClient.js.map +1 -0
- package/dist/AIAgentL2ENSNamespaceClient.d.ts +58 -0
- package/dist/AIAgentL2ENSNamespaceClient.d.ts.map +1 -0
- package/dist/AIAgentL2ENSNamespaceClient.js +214 -0
- package/dist/AIAgentL2ENSNamespaceClient.js.map +1 -0
- package/dist/AIAgentReputationClient.d.ts +69 -0
- package/dist/AIAgentReputationClient.d.ts.map +1 -0
- package/dist/AIAgentReputationClient.js +203 -0
- package/dist/AIAgentReputationClient.js.map +1 -0
- package/dist/AIAgentValidationClient.d.ts +60 -0
- package/dist/AIAgentValidationClient.d.ts.map +1 -0
- package/dist/AIAgentValidationClient.js +123 -0
- package/dist/AIAgentValidationClient.js.map +1 -0
- package/dist/OrgIdentityClient.d.ts +27 -0
- package/dist/OrgIdentityClient.d.ts.map +1 -0
- package/dist/OrgIdentityClient.js +169 -0
- package/dist/OrgIdentityClient.js.map +1 -0
- package/dist/abis/BaseRegistrarImplementation.json +1013 -0
- package/dist/abis/ETHRegistrarController.json +1004 -0
- package/dist/abis/IdentityRegistry.json +1044 -0
- package/dist/abis/NameWrapper.json +2026 -0
- package/dist/abis/PublicResolver.json +1772 -0
- package/dist/abis/ReputationRegistry.json +701 -0
- package/dist/abis/ValidationRegistry.json +505 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/schema.d.ts +13 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +696 -0
- package/dist/schema.js.map +1 -0
- package/dist/schemaKb.d.ts +12 -0
- package/dist/schemaKb.d.ts.map +1 -0
- package/dist/schemaKb.js +593 -0
- package/dist/schemaKb.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/did8004.d.ts +57 -0
- package/dist/utils/did8004.d.ts.map +1 -0
- package/dist/utils/did8004.js +127 -0
- package/dist/utils/did8004.js.map +1 -0
- package/dist/utils/didEns.d.ts +46 -0
- package/dist/utils/didEns.d.ts.map +1 -0
- package/dist/utils/didEns.js +107 -0
- package/dist/utils/didEns.js.map +1 -0
- package/dist/utils/didEthr.d.ts +40 -0
- package/dist/utils/didEthr.d.ts.map +1 -0
- package/dist/utils/didEthr.js +87 -0
- package/dist/utils/didEthr.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,3184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Agent Discovery Client
|
|
3
|
+
*
|
|
4
|
+
* Fronts for discovery-index GraphQL requests to the indexer
|
|
5
|
+
* Provides a clean interface for querying agent data
|
|
6
|
+
*/
|
|
7
|
+
import { GraphQLClient } from 'graphql-request';
|
|
8
|
+
const INTROSPECTION_QUERY = `
|
|
9
|
+
query SearchCapabilities {
|
|
10
|
+
__schema {
|
|
11
|
+
queryType {
|
|
12
|
+
fields {
|
|
13
|
+
name
|
|
14
|
+
args {
|
|
15
|
+
name
|
|
16
|
+
type {
|
|
17
|
+
...TypeRef
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
type {
|
|
21
|
+
...TypeRef
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
fragment TypeRef on __Type {
|
|
28
|
+
kind
|
|
29
|
+
name
|
|
30
|
+
ofType {
|
|
31
|
+
kind
|
|
32
|
+
name
|
|
33
|
+
ofType {
|
|
34
|
+
kind
|
|
35
|
+
name
|
|
36
|
+
ofType {
|
|
37
|
+
kind
|
|
38
|
+
name
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
const TYPE_FIELDS_QUERY = `
|
|
45
|
+
query TypeFields($name: String!) {
|
|
46
|
+
__type(name: $name) {
|
|
47
|
+
kind
|
|
48
|
+
fields {
|
|
49
|
+
name
|
|
50
|
+
type {
|
|
51
|
+
...TypeRef
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
inputFields {
|
|
55
|
+
name
|
|
56
|
+
type {
|
|
57
|
+
...TypeRef
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
fragment TypeRef on __Type {
|
|
63
|
+
kind
|
|
64
|
+
name
|
|
65
|
+
ofType {
|
|
66
|
+
kind
|
|
67
|
+
name
|
|
68
|
+
ofType {
|
|
69
|
+
kind
|
|
70
|
+
name
|
|
71
|
+
ofType {
|
|
72
|
+
kind
|
|
73
|
+
name
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
function unwrapType(type) {
|
|
80
|
+
let current = type;
|
|
81
|
+
while (current && (current.kind === 'NON_NULL' || current.kind === 'LIST')) {
|
|
82
|
+
current = current.ofType ?? null;
|
|
83
|
+
}
|
|
84
|
+
return current ?? null;
|
|
85
|
+
}
|
|
86
|
+
function unwrapToTypeName(type) {
|
|
87
|
+
const named = unwrapType(type);
|
|
88
|
+
return named?.name ?? null;
|
|
89
|
+
}
|
|
90
|
+
function isNonNull(type) {
|
|
91
|
+
return type?.kind === 'NON_NULL';
|
|
92
|
+
}
|
|
93
|
+
function isListOf(type, expectedName) {
|
|
94
|
+
if (!type)
|
|
95
|
+
return false;
|
|
96
|
+
if (type.kind === 'NON_NULL')
|
|
97
|
+
return isListOf(type.ofType, expectedName);
|
|
98
|
+
if (type.kind === 'LIST') {
|
|
99
|
+
const inner = type.ofType || null;
|
|
100
|
+
if (!inner)
|
|
101
|
+
return false;
|
|
102
|
+
if (inner.kind === 'NON_NULL') {
|
|
103
|
+
return isListOf(inner.ofType, expectedName);
|
|
104
|
+
}
|
|
105
|
+
return inner.kind === 'OBJECT' && inner.name === expectedName;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* AI Agent Discovery Client
|
|
111
|
+
*
|
|
112
|
+
* Provides methods for querying agent data from the indexer
|
|
113
|
+
*/
|
|
114
|
+
export class AIAgentDiscoveryClient {
|
|
115
|
+
client;
|
|
116
|
+
config;
|
|
117
|
+
searchStrategy;
|
|
118
|
+
searchStrategyPromise;
|
|
119
|
+
typeFieldsCache = new Map();
|
|
120
|
+
enumValuesCache = new Map();
|
|
121
|
+
tokenMetadataCollectionSupported;
|
|
122
|
+
agentMetadataValueField;
|
|
123
|
+
queryFieldsCache;
|
|
124
|
+
queryFieldsPromise;
|
|
125
|
+
kbV2SupportCache;
|
|
126
|
+
kbV2SupportPromise;
|
|
127
|
+
constructor(config) {
|
|
128
|
+
const endpoint = (() => {
|
|
129
|
+
const raw = (config.endpoint || '').toString().trim().replace(/\/+$/, '');
|
|
130
|
+
if (!raw)
|
|
131
|
+
return raw;
|
|
132
|
+
// Force KB endpoint:
|
|
133
|
+
// - if caller passed ".../graphql", replace with ".../graphql-kb"
|
|
134
|
+
// - if caller passed base URL, append "/graphql-kb"
|
|
135
|
+
if (/\/graphql$/i.test(raw))
|
|
136
|
+
return raw.replace(/\/graphql$/i, '/graphql-kb');
|
|
137
|
+
if (/\/graphql-kb$/i.test(raw))
|
|
138
|
+
return raw;
|
|
139
|
+
return `${raw}/graphql-kb`;
|
|
140
|
+
})();
|
|
141
|
+
this.config = { ...config, endpoint };
|
|
142
|
+
const headers = {
|
|
143
|
+
'Content-Type': 'application/json',
|
|
144
|
+
...(config.headers || {}),
|
|
145
|
+
};
|
|
146
|
+
if (config.apiKey) {
|
|
147
|
+
headers['Authorization'] = `Bearer ${config.apiKey}`;
|
|
148
|
+
// Also support API key in header
|
|
149
|
+
headers['X-API-Key'] = config.apiKey;
|
|
150
|
+
// Some deployments use an explicit access-code header
|
|
151
|
+
headers['X-Access-Code'] = config.apiKey;
|
|
152
|
+
}
|
|
153
|
+
this.client = new GraphQLClient(endpoint, {
|
|
154
|
+
headers,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
extractOperationName(query) {
|
|
158
|
+
const m = /\b(query|mutation)\s+([A-Za-z0-9_]+)/.exec(query);
|
|
159
|
+
return m?.[2] ? String(m[2]) : null;
|
|
160
|
+
}
|
|
161
|
+
decorateGraphqlError(error, query) {
|
|
162
|
+
const op = this.extractOperationName(query) ?? 'unknown_operation';
|
|
163
|
+
const endpoint = this.config.endpoint;
|
|
164
|
+
const status = typeof error === 'object' &&
|
|
165
|
+
error !== null &&
|
|
166
|
+
'response' in error &&
|
|
167
|
+
typeof error.response?.status === 'number'
|
|
168
|
+
? error.response.status
|
|
169
|
+
: undefined;
|
|
170
|
+
const gqlMessages = [];
|
|
171
|
+
const responseErrors = error?.response?.errors;
|
|
172
|
+
if (Array.isArray(responseErrors)) {
|
|
173
|
+
for (const e of responseErrors) {
|
|
174
|
+
if (typeof e?.message === 'string' && e.message.trim())
|
|
175
|
+
gqlMessages.push(e.message.trim());
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const combined = (gqlMessages.join(' ') || (error instanceof Error ? error.message : '')).trim();
|
|
179
|
+
const lower = combined.toLowerCase();
|
|
180
|
+
const kind = status === 401 || status === 403
|
|
181
|
+
? 'auth'
|
|
182
|
+
: status === 404
|
|
183
|
+
? 'missing_endpoint'
|
|
184
|
+
: lower.includes('cannot query field') || lower.includes('unknown argument')
|
|
185
|
+
? 'schema_mismatch'
|
|
186
|
+
: 'unknown';
|
|
187
|
+
const msg = `[DiscoveryGraphQL:${kind}] ` +
|
|
188
|
+
`operation=${op} status=${typeof status === 'number' ? status : 'unknown'} ` +
|
|
189
|
+
`endpoint=${endpoint} ` +
|
|
190
|
+
(combined ? `message=${combined}` : 'message=Unknown error');
|
|
191
|
+
const wrapped = new Error(msg);
|
|
192
|
+
if (error instanceof Error)
|
|
193
|
+
wrapped.cause = error;
|
|
194
|
+
return wrapped;
|
|
195
|
+
}
|
|
196
|
+
async gqlRequest(query, variables) {
|
|
197
|
+
try {
|
|
198
|
+
return await this.client.request(query, variables);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
throw this.decorateGraphqlError(error, query);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async getQueryFields() {
|
|
205
|
+
if (this.queryFieldsCache !== undefined) {
|
|
206
|
+
return this.queryFieldsCache;
|
|
207
|
+
}
|
|
208
|
+
if (this.queryFieldsPromise) {
|
|
209
|
+
return this.queryFieldsPromise;
|
|
210
|
+
}
|
|
211
|
+
this.queryFieldsPromise = (async () => {
|
|
212
|
+
try {
|
|
213
|
+
const data = await this.gqlRequest(INTROSPECTION_QUERY);
|
|
214
|
+
const fields = data.__schema?.queryType?.fields ?? [];
|
|
215
|
+
this.queryFieldsCache = fields;
|
|
216
|
+
return fields;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
console.warn('[AIAgentDiscoveryClient] Failed to introspect query fields:', error);
|
|
220
|
+
this.queryFieldsCache = null;
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
this.queryFieldsPromise = undefined;
|
|
225
|
+
}
|
|
226
|
+
})();
|
|
227
|
+
return this.queryFieldsPromise;
|
|
228
|
+
}
|
|
229
|
+
async getEnumValues(enumName) {
|
|
230
|
+
const cached = this.enumValuesCache.get(enumName);
|
|
231
|
+
if (cached !== undefined)
|
|
232
|
+
return cached;
|
|
233
|
+
try {
|
|
234
|
+
const query = `
|
|
235
|
+
query EnumValues($name: String!) {
|
|
236
|
+
__type(name: $name) {
|
|
237
|
+
kind
|
|
238
|
+
enumValues { name }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
`;
|
|
242
|
+
const data = await this.gqlRequest(query, { name: enumName });
|
|
243
|
+
const values = data?.__type?.kind === 'ENUM' && Array.isArray(data.__type.enumValues)
|
|
244
|
+
? data.__type.enumValues
|
|
245
|
+
.map((v) => (typeof v?.name === 'string' ? v.name : ''))
|
|
246
|
+
.filter((v) => v.length > 0)
|
|
247
|
+
: null;
|
|
248
|
+
this.enumValuesCache.set(enumName, values);
|
|
249
|
+
return values;
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
this.enumValuesCache.set(enumName, null);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async pickKbAgentOrderBy(preferred) {
|
|
257
|
+
const values = await this.getEnumValues('KbAgentOrderBy');
|
|
258
|
+
if (!values || values.length === 0)
|
|
259
|
+
return null;
|
|
260
|
+
const set = new Set(values);
|
|
261
|
+
for (const p of preferred) {
|
|
262
|
+
if (set.has(p))
|
|
263
|
+
return p;
|
|
264
|
+
}
|
|
265
|
+
// If nothing matches, use the first enum value to avoid schema mismatch.
|
|
266
|
+
return values[0] ?? null;
|
|
267
|
+
}
|
|
268
|
+
async buildKbIdentityAndAccountsSelectionBase() {
|
|
269
|
+
const fieldNames = async (typeName) => {
|
|
270
|
+
const fields = await this.getTypeFields(typeName);
|
|
271
|
+
return new Set((fields ?? [])
|
|
272
|
+
.map((f) => f?.name)
|
|
273
|
+
.filter((n) => typeof n === 'string' && n.length > 0));
|
|
274
|
+
};
|
|
275
|
+
const typeOfField = async (parentType, fieldName) => {
|
|
276
|
+
const fields = await this.getTypeFields(parentType);
|
|
277
|
+
const f = (fields ?? []).find((x) => x?.name === fieldName);
|
|
278
|
+
return unwrapToTypeName(f?.type);
|
|
279
|
+
};
|
|
280
|
+
const buildDescriptorSelection = async (typeName, opts) => {
|
|
281
|
+
if (!typeName)
|
|
282
|
+
return 'iri name description image';
|
|
283
|
+
const names = await fieldNames(typeName);
|
|
284
|
+
const parts = ['iri'];
|
|
285
|
+
if (names.has('name'))
|
|
286
|
+
parts.push('name');
|
|
287
|
+
if (names.has('description'))
|
|
288
|
+
parts.push('description');
|
|
289
|
+
if (names.has('image'))
|
|
290
|
+
parts.push('image');
|
|
291
|
+
// Optional payload fields (schema-dependent)
|
|
292
|
+
if (opts?.preferAgentCardJson && names.has('agentCardJson'))
|
|
293
|
+
parts.push('agentCardJson');
|
|
294
|
+
else if (names.has('agentCardJson'))
|
|
295
|
+
parts.push('agentCardJson');
|
|
296
|
+
return parts.join(' ');
|
|
297
|
+
};
|
|
298
|
+
// Newer KB schemas expose identities as a list (`KbAgent.identities: [KbAgentIdentity!]!`)
|
|
299
|
+
// instead of named fields (`identity8004`, `identityEns`, etc).
|
|
300
|
+
const kbAgentFields = await fieldNames('KbAgent');
|
|
301
|
+
const hasIdentitiesList = kbAgentFields.has('identities');
|
|
302
|
+
const identityDescriptorType = await typeOfField(hasIdentitiesList ? 'KbAgentIdentity' : 'KbIdentity', 'descriptor');
|
|
303
|
+
const identityDescriptorFields = await fieldNames(identityDescriptorType ?? 'KbIdentityDescriptor');
|
|
304
|
+
if (!identityDescriptorFields.has('registrationJson')) {
|
|
305
|
+
throw new Error('KB schema mismatch: KbIdentityDescriptor.registrationJson is required.');
|
|
306
|
+
}
|
|
307
|
+
if (!identityDescriptorFields.has('nftMetadataJson')) {
|
|
308
|
+
throw new Error('KB schema mismatch: KbIdentityDescriptor.nftMetadataJson is required.');
|
|
309
|
+
}
|
|
310
|
+
const identityDescriptorSelection = [
|
|
311
|
+
'iri',
|
|
312
|
+
identityDescriptorFields.has('kind') ? 'kind' : '',
|
|
313
|
+
identityDescriptorFields.has('name') ? 'name' : '',
|
|
314
|
+
identityDescriptorFields.has('description') ? 'description' : '',
|
|
315
|
+
identityDescriptorFields.has('image') ? 'image' : '',
|
|
316
|
+
'registrationJson',
|
|
317
|
+
'nftMetadataJson',
|
|
318
|
+
identityDescriptorFields.has('registeredBy') ? 'registeredBy' : '',
|
|
319
|
+
identityDescriptorFields.has('registryNamespace') ? 'registryNamespace' : '',
|
|
320
|
+
identityDescriptorFields.has('skills') ? 'skills' : '',
|
|
321
|
+
identityDescriptorFields.has('domains') ? 'domains' : '',
|
|
322
|
+
]
|
|
323
|
+
.filter(Boolean)
|
|
324
|
+
.join('\n ');
|
|
325
|
+
const endpointDescriptorType = await typeOfField('KbServiceEndpoint', 'descriptor');
|
|
326
|
+
const protocolType = await typeOfField('KbServiceEndpoint', 'protocol');
|
|
327
|
+
const protocolDescriptorType = protocolType ? await typeOfField(protocolType, 'descriptor') : null;
|
|
328
|
+
const endpointDescriptorSelection = await buildDescriptorSelection(endpointDescriptorType, {
|
|
329
|
+
preferAgentCardJson: false,
|
|
330
|
+
});
|
|
331
|
+
const protocolDescriptorSelection = await buildDescriptorSelection(protocolDescriptorType ?? 'KbProtocolDescriptor', {
|
|
332
|
+
preferAgentCardJson: true,
|
|
333
|
+
});
|
|
334
|
+
const serviceEndpointsBlock = `
|
|
335
|
+
serviceEndpoints {
|
|
336
|
+
iri
|
|
337
|
+
name
|
|
338
|
+
descriptor { ${endpointDescriptorSelection} }
|
|
339
|
+
protocol {
|
|
340
|
+
iri
|
|
341
|
+
protocol
|
|
342
|
+
serviceUrl
|
|
343
|
+
protocolVersion
|
|
344
|
+
descriptor { ${protocolDescriptorSelection} }
|
|
345
|
+
skills
|
|
346
|
+
domains
|
|
347
|
+
}
|
|
348
|
+
}`;
|
|
349
|
+
// Identity fields are type-dependent; introspect the concrete types directly when possible.
|
|
350
|
+
const identity8004Fields = await fieldNames('KbIdentity8004');
|
|
351
|
+
const identity8004Extras = [
|
|
352
|
+
identity8004Fields.has('did8004') ? 'did8004' : '',
|
|
353
|
+
identity8004Fields.has('agentId8004') ? 'agentId8004' : '',
|
|
354
|
+
identity8004Fields.has('isSmartAgent') ? 'isSmartAgent' : '',
|
|
355
|
+
]
|
|
356
|
+
.filter(Boolean)
|
|
357
|
+
.join('\n ');
|
|
358
|
+
const identity8122Fields = await fieldNames('KbIdentity8122');
|
|
359
|
+
const identity8122Extras = [
|
|
360
|
+
identity8122Fields.has('registryName') ? 'registryName' : '',
|
|
361
|
+
identity8122Fields.has('collectionName') ? 'collectionName' : '',
|
|
362
|
+
identity8122Fields.has('registrarName') ? 'registrarName' : '',
|
|
363
|
+
]
|
|
364
|
+
.filter(Boolean)
|
|
365
|
+
.join('\n ');
|
|
366
|
+
// New schema: identities list with inline fragments.
|
|
367
|
+
const identitiesListBlock = hasIdentitiesList
|
|
368
|
+
? `
|
|
369
|
+
identities {
|
|
370
|
+
iri
|
|
371
|
+
kind
|
|
372
|
+
did
|
|
373
|
+
descriptor {
|
|
374
|
+
${identityDescriptorSelection}
|
|
375
|
+
}
|
|
376
|
+
${serviceEndpointsBlock}
|
|
377
|
+
... on KbIdentity8004 {
|
|
378
|
+
${identity8004Extras}
|
|
379
|
+
ownerAccount { iri chainId address accountType didEthr }
|
|
380
|
+
operatorAccount { iri chainId address accountType didEthr }
|
|
381
|
+
walletAccount { iri chainId address accountType didEthr }
|
|
382
|
+
ownerEOAAccount { iri chainId address accountType didEthr }
|
|
383
|
+
agentAccount { iri chainId address accountType didEthr }
|
|
384
|
+
}
|
|
385
|
+
... on KbIdentity8122 {
|
|
386
|
+
did8122
|
|
387
|
+
agentId8122
|
|
388
|
+
registryAddress
|
|
389
|
+
${identity8122Extras}
|
|
390
|
+
endpointType
|
|
391
|
+
endpoint
|
|
392
|
+
ownerAccount { iri chainId address accountType didEthr }
|
|
393
|
+
agentAccount { iri chainId address accountType didEthr }
|
|
394
|
+
}
|
|
395
|
+
... on KbIdentityEns {
|
|
396
|
+
didEns
|
|
397
|
+
}
|
|
398
|
+
... on KbIdentityHol {
|
|
399
|
+
uaidHOL
|
|
400
|
+
}
|
|
401
|
+
}`
|
|
402
|
+
: '';
|
|
403
|
+
const identity8122Block = !hasIdentitiesList && kbAgentFields.has('identity8122')
|
|
404
|
+
? (() => {
|
|
405
|
+
return `
|
|
406
|
+
identity8122 {
|
|
407
|
+
iri
|
|
408
|
+
kind
|
|
409
|
+
did
|
|
410
|
+
did8122
|
|
411
|
+
agentId8122
|
|
412
|
+
registryAddress
|
|
413
|
+
${identity8122Extras}
|
|
414
|
+
endpointType
|
|
415
|
+
endpoint
|
|
416
|
+
descriptor {
|
|
417
|
+
${identityDescriptorSelection}
|
|
418
|
+
}
|
|
419
|
+
${serviceEndpointsBlock}
|
|
420
|
+
ownerAccount { iri chainId address accountType didEthr }
|
|
421
|
+
agentAccount { iri chainId address accountType didEthr }
|
|
422
|
+
}`;
|
|
423
|
+
})()
|
|
424
|
+
: '';
|
|
425
|
+
const identity8004Block = !hasIdentitiesList && kbAgentFields.has('identity8004')
|
|
426
|
+
? `
|
|
427
|
+
identity8004 {
|
|
428
|
+
iri
|
|
429
|
+
kind
|
|
430
|
+
did
|
|
431
|
+
${identity8004Extras}
|
|
432
|
+
descriptor {
|
|
433
|
+
${identityDescriptorSelection}
|
|
434
|
+
}
|
|
435
|
+
${serviceEndpointsBlock}
|
|
436
|
+
ownerAccount { iri chainId address accountType didEthr }
|
|
437
|
+
operatorAccount { iri chainId address accountType didEthr }
|
|
438
|
+
walletAccount { iri chainId address accountType didEthr }
|
|
439
|
+
ownerEOAAccount { iri chainId address accountType didEthr }
|
|
440
|
+
agentAccount { iri chainId address accountType didEthr }
|
|
441
|
+
}`
|
|
442
|
+
: '';
|
|
443
|
+
const identityEnsBlock = !hasIdentitiesList && kbAgentFields.has('identityEns')
|
|
444
|
+
? `
|
|
445
|
+
identityEns {
|
|
446
|
+
iri
|
|
447
|
+
kind
|
|
448
|
+
did
|
|
449
|
+
uaidHOL
|
|
450
|
+
descriptor {
|
|
451
|
+
${identityDescriptorSelection}
|
|
452
|
+
}
|
|
453
|
+
${serviceEndpointsBlock}
|
|
454
|
+
}`
|
|
455
|
+
: '';
|
|
456
|
+
const identityHolBlock = !hasIdentitiesList && kbAgentFields.has('identityHol')
|
|
457
|
+
? `
|
|
458
|
+
identityHol {
|
|
459
|
+
iri
|
|
460
|
+
kind
|
|
461
|
+
did
|
|
462
|
+
uaidHOL
|
|
463
|
+
descriptor {
|
|
464
|
+
${identityDescriptorSelection}
|
|
465
|
+
}
|
|
466
|
+
${serviceEndpointsBlock}
|
|
467
|
+
}`
|
|
468
|
+
: '';
|
|
469
|
+
return `
|
|
470
|
+
${identitiesListBlock}
|
|
471
|
+
${identity8004Block}
|
|
472
|
+
${identity8122Block}
|
|
473
|
+
${identityEnsBlock}
|
|
474
|
+
${identityHolBlock}`;
|
|
475
|
+
}
|
|
476
|
+
async hasQueryField(fieldName) {
|
|
477
|
+
const fields = await this.getQueryFields();
|
|
478
|
+
return Array.isArray(fields) ? fields.some((f) => f?.name === fieldName) : false;
|
|
479
|
+
}
|
|
480
|
+
async supportsKbV2Queries() {
|
|
481
|
+
if (typeof this.kbV2SupportCache === 'boolean')
|
|
482
|
+
return this.kbV2SupportCache;
|
|
483
|
+
if (this.kbV2SupportPromise)
|
|
484
|
+
return this.kbV2SupportPromise;
|
|
485
|
+
this.kbV2SupportPromise = (async () => {
|
|
486
|
+
try {
|
|
487
|
+
const fields = await this.getQueryFields();
|
|
488
|
+
if (!Array.isArray(fields) || fields.length === 0) {
|
|
489
|
+
// Introspection disabled or failed → assume legacy.
|
|
490
|
+
this.kbV2SupportCache = false;
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
const names = new Set(fields.map((f) => f?.name).filter(Boolean));
|
|
494
|
+
// GraphDB-backed KB may expose kbAgentByUaid instead of kbAgent(chainId, agentId8004).
|
|
495
|
+
const hasAgentLookup = names.has('kbAgent') || names.has('kbAgentByUaid');
|
|
496
|
+
const ok = names.has('kbAgents') && hasAgentLookup && (names.has('kbSemanticAgentSearch') || names.has('kbAgents'));
|
|
497
|
+
this.kbV2SupportCache = ok;
|
|
498
|
+
return ok;
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
this.kbV2SupportCache = false;
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
finally {
|
|
505
|
+
this.kbV2SupportPromise = undefined;
|
|
506
|
+
}
|
|
507
|
+
})();
|
|
508
|
+
return this.kbV2SupportPromise;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Map a KB v2 agent node into the legacy AgentData shape used across the monorepo.
|
|
512
|
+
*/
|
|
513
|
+
mapKbAgentToAgentData(agent) {
|
|
514
|
+
const a = (agent ?? {});
|
|
515
|
+
const aAny = a;
|
|
516
|
+
// Back-compat: newer KB schema returns identities as a list.
|
|
517
|
+
// Normalize into the legacy named slots so the rest of this mapper stays stable.
|
|
518
|
+
const identitiesList = Array.isArray(aAny.identities) ? aAny.identities : null;
|
|
519
|
+
if (identitiesList && identitiesList.length > 0) {
|
|
520
|
+
const pick = (kind) => {
|
|
521
|
+
const k = kind.toLowerCase();
|
|
522
|
+
return (identitiesList.find((x) => String(x?.kind ?? '').toLowerCase() === k) ??
|
|
523
|
+
identitiesList.find((x) => String(x?.kind ?? '').toLowerCase() === (k === '8004' ? 'erc8004' : k)) ??
|
|
524
|
+
null);
|
|
525
|
+
};
|
|
526
|
+
if (!aAny.identity8004)
|
|
527
|
+
aAny.identity8004 = pick('8004') ?? undefined;
|
|
528
|
+
if (!aAny.identity8122)
|
|
529
|
+
aAny.identity8122 = pick('8122') ?? undefined;
|
|
530
|
+
if (!aAny.identityEns)
|
|
531
|
+
aAny.identityEns = pick('ens') ?? undefined;
|
|
532
|
+
if (!aAny.identityHol)
|
|
533
|
+
aAny.identityHol = pick('hol') ?? undefined;
|
|
534
|
+
if (!aAny.identity)
|
|
535
|
+
aAny.identity = aAny.identity8004 ?? aAny.identity8122 ?? aAny.identityEns ?? aAny.identityHol ?? undefined;
|
|
536
|
+
}
|
|
537
|
+
const toFiniteNumberOrUndefined = (value) => {
|
|
538
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
539
|
+
};
|
|
540
|
+
const parseDid8004Parts = (did) => {
|
|
541
|
+
if (typeof did !== 'string')
|
|
542
|
+
return {};
|
|
543
|
+
const trimmed = did.trim();
|
|
544
|
+
// did:8004:<chainId>:<agentId>
|
|
545
|
+
const parts = trimmed.split(':');
|
|
546
|
+
if (parts.length >= 4 && parts[0] === 'did' && parts[1] === '8004') {
|
|
547
|
+
const chainId = Number(parts[2]);
|
|
548
|
+
const agentId = Number(parts[3]);
|
|
549
|
+
return {
|
|
550
|
+
chainId: Number.isFinite(chainId) ? chainId : undefined,
|
|
551
|
+
agentId: Number.isFinite(agentId) ? agentId : undefined,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
return {};
|
|
555
|
+
};
|
|
556
|
+
const parseDid8122Parts = (did) => {
|
|
557
|
+
if (typeof did !== 'string')
|
|
558
|
+
return {};
|
|
559
|
+
const trimmed = did.trim();
|
|
560
|
+
// did:8122:<chainId>:<registryOrAccount>:<agentId>
|
|
561
|
+
const parts = trimmed.split(':');
|
|
562
|
+
if (parts.length >= 5 && parts[0] === 'did' && parts[1] === '8122') {
|
|
563
|
+
const chainId = Number(parts[2]);
|
|
564
|
+
const agentId8122 = String(parts[4] ?? '').trim();
|
|
565
|
+
return {
|
|
566
|
+
chainId: Number.isFinite(chainId) ? chainId : undefined,
|
|
567
|
+
agentId8122: agentId8122 || undefined,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
return {};
|
|
571
|
+
};
|
|
572
|
+
const parseUaidDid8004Parts = (uaid) => {
|
|
573
|
+
if (typeof uaid !== 'string')
|
|
574
|
+
return {};
|
|
575
|
+
const trimmed = uaid.trim();
|
|
576
|
+
// UAID did-target form: uaid:did:<methodSpecificId>;...
|
|
577
|
+
if (!trimmed.startsWith('uaid:did:'))
|
|
578
|
+
return {};
|
|
579
|
+
const rest = trimmed.slice('uaid:did:'.length);
|
|
580
|
+
const msid = rest.split(';')[0] ?? '';
|
|
581
|
+
// For did:8004 target, msid looks like: 8004:<chainId>:<agentId>
|
|
582
|
+
const parts = msid.split(':');
|
|
583
|
+
if (parts.length >= 3 && parts[0] === '8004') {
|
|
584
|
+
const chainId = Number(parts[1]);
|
|
585
|
+
const agentId = Number(parts[2]);
|
|
586
|
+
return {
|
|
587
|
+
chainId: Number.isFinite(chainId) ? chainId : undefined,
|
|
588
|
+
agentId: Number.isFinite(agentId) ? agentId : undefined,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
return {};
|
|
592
|
+
};
|
|
593
|
+
const parseUaidDid8122Parts = (uaid) => {
|
|
594
|
+
if (typeof uaid !== 'string')
|
|
595
|
+
return {};
|
|
596
|
+
const trimmed = uaid.trim();
|
|
597
|
+
// UAID did-target form: uaid:did:<methodSpecificId>;...
|
|
598
|
+
if (!trimmed.startsWith('uaid:did:'))
|
|
599
|
+
return {};
|
|
600
|
+
const rest = trimmed.slice('uaid:did:'.length);
|
|
601
|
+
const msid = rest.split(';')[0] ?? '';
|
|
602
|
+
// For did:8122 target, msid looks like: 8122:<chainId>:<registryOrAccount>:<agentId>
|
|
603
|
+
const parts = msid.split(':');
|
|
604
|
+
if (parts.length >= 4 && parts[0] === '8122') {
|
|
605
|
+
const chainId = Number(parts[1]);
|
|
606
|
+
const agentId8122 = String(parts[3] ?? '').trim();
|
|
607
|
+
return {
|
|
608
|
+
chainId: Number.isFinite(chainId) ? chainId : undefined,
|
|
609
|
+
agentId8122: agentId8122 || undefined,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
return {};
|
|
613
|
+
};
|
|
614
|
+
const pickAccountAddress = (...accounts) => {
|
|
615
|
+
for (const acc of accounts) {
|
|
616
|
+
const addr = acc?.address;
|
|
617
|
+
if (typeof addr === 'string' && addr.trim()) {
|
|
618
|
+
return addr.trim();
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
};
|
|
623
|
+
// KB agentTypes have evolved over time; accept both legacy + current IRIs.
|
|
624
|
+
const SMART_AGENT_TYPE_IRIS = [
|
|
625
|
+
'https://agentictrust.io/ontology/core#AISmartAgent',
|
|
626
|
+
'https://agentictrust.io/ontology/erc8004#SmartAgent',
|
|
627
|
+
];
|
|
628
|
+
const hasSmartAgentType = Array.isArray(a.agentTypes) &&
|
|
629
|
+
a.agentTypes.some((t) => SMART_AGENT_TYPE_IRIS.includes(String(t ?? '').trim()));
|
|
630
|
+
const didPrimary = (typeof a.identity8004?.did === 'string' && String(a.identity8004.did).trim()
|
|
631
|
+
? String(a.identity8004.did).trim()
|
|
632
|
+
: null) ??
|
|
633
|
+
(typeof a.identity8122?.did === 'string' && String(a.identity8122.did).trim()
|
|
634
|
+
? String(a.identity8122.did).trim()
|
|
635
|
+
: null) ??
|
|
636
|
+
(typeof a.did8004 === 'string' && String(a.did8004).trim()
|
|
637
|
+
? String(a.did8004).trim()
|
|
638
|
+
: null);
|
|
639
|
+
const agentId8004FromField = typeof a.agentId8004 === 'number' && Number.isFinite(a.agentId8004)
|
|
640
|
+
? a.agentId8004
|
|
641
|
+
: null;
|
|
642
|
+
const agentIdFromParsedDid = didPrimary ? (parseDid8004Parts(didPrimary).agentId ?? null) : null;
|
|
643
|
+
const agentId8004 = agentId8004FromField ?? agentIdFromParsedDid;
|
|
644
|
+
// Best-effort: infer chainId from the most specific account we have.
|
|
645
|
+
const chainIdFromAccounts = (typeof a.identity8004?.agentAccount?.chainId === 'number'
|
|
646
|
+
? a.identity8004?.agentAccount?.chainId
|
|
647
|
+
: null) ??
|
|
648
|
+
(typeof a.identity8122?.agentAccount?.chainId === 'number'
|
|
649
|
+
? a.identity8122?.agentAccount?.chainId
|
|
650
|
+
: null) ??
|
|
651
|
+
(typeof a.identity8004?.walletAccount?.chainId === 'number' ? a.identity8004?.walletAccount?.chainId : null) ??
|
|
652
|
+
(typeof a.identity8004?.ownerEOAAccount?.chainId === 'number' ? a.identity8004?.ownerEOAAccount?.chainId : null) ??
|
|
653
|
+
(typeof a.identity8004?.ownerAccount?.chainId === 'number' ? a.identity8004?.ownerAccount?.chainId : null) ??
|
|
654
|
+
(typeof a.identity8122?.ownerAccount?.chainId === 'number'
|
|
655
|
+
? a.identity8122?.ownerAccount?.chainId
|
|
656
|
+
: null) ??
|
|
657
|
+
null;
|
|
658
|
+
const chainId = chainIdFromAccounts ??
|
|
659
|
+
parseDid8004Parts(didPrimary).chainId ??
|
|
660
|
+
parseDid8122Parts(didPrimary).chainId ??
|
|
661
|
+
parseUaidDid8004Parts(a.uaid).chainId ??
|
|
662
|
+
parseUaidDid8122Parts(a.uaid).chainId ??
|
|
663
|
+
null;
|
|
664
|
+
const agentIdFromParsed = (didPrimary ? parseDid8004Parts(didPrimary).agentId : undefined) ??
|
|
665
|
+
parseUaidDid8004Parts(a.uaid).agentId ??
|
|
666
|
+
(didPrimary ? parseDid8122Parts(didPrimary).agentId8122 : undefined) ??
|
|
667
|
+
parseUaidDid8122Parts(a.uaid).agentId8122 ??
|
|
668
|
+
undefined;
|
|
669
|
+
// "agentAccount" in KB v2 is the SmartAgent-controlled account (AgentAccount).
|
|
670
|
+
// For non-smart agents, fall back to agent/identity wallet/owner accounts.
|
|
671
|
+
const agentAccount = pickAccountAddress(a.identity8004?.agentAccount, a.identity8122?.agentAccount, a.identity8004?.walletAccount, a.identity8004?.ownerEOAAccount, a.identity8004?.ownerAccount, a.identity8122?.ownerAccount) ?? null;
|
|
672
|
+
const isSmartAgent = (() => {
|
|
673
|
+
// Canonical definition: ontology type (agentTypes). Prefer this over any legacy boolean.
|
|
674
|
+
if (Array.isArray(a.agentTypes))
|
|
675
|
+
return hasSmartAgentType;
|
|
676
|
+
if (typeof a.isSmartAgent === 'boolean') {
|
|
677
|
+
return a.isSmartAgent;
|
|
678
|
+
}
|
|
679
|
+
return false;
|
|
680
|
+
})();
|
|
681
|
+
const identityOwner = pickAccountAddress(a.identity8004?.ownerAccount, a.identity8004?.ownerEOAAccount, a.identity8122?.ownerAccount) ?? null;
|
|
682
|
+
const registeredBy = (typeof a.identity8004?.descriptor?.registeredBy === 'string' && a.identity8004.descriptor.registeredBy.trim()
|
|
683
|
+
? a.identity8004.descriptor.registeredBy.trim()
|
|
684
|
+
: null) ??
|
|
685
|
+
(typeof a.identityEns?.descriptor?.registeredBy === 'string' && a.identityEns.descriptor.registeredBy.trim()
|
|
686
|
+
? a.identityEns.descriptor.registeredBy.trim()
|
|
687
|
+
: null) ??
|
|
688
|
+
(typeof a.identityHol?.descriptor?.registeredBy === 'string' && a.identityHol.descriptor.registeredBy.trim()
|
|
689
|
+
? a.identityHol.descriptor.registeredBy.trim()
|
|
690
|
+
: null) ??
|
|
691
|
+
null;
|
|
692
|
+
const registeredByAddress = registeredBy && /^0x[a-fA-F0-9]{40}$/.test(registeredBy) ? registeredBy : null;
|
|
693
|
+
const isOwnerEoa = (a.identity8004?.ownerEOAAccount?.accountType ?? a.identity8004?.ownerAccount?.accountType ?? '')
|
|
694
|
+
.toString()
|
|
695
|
+
.toLowerCase()
|
|
696
|
+
.includes('eoa');
|
|
697
|
+
const identity8122DescriptorJson = typeof a.identity8122?.descriptor?.registrationJson === 'string'
|
|
698
|
+
? String(a.identity8122.descriptor.registrationJson)
|
|
699
|
+
: null;
|
|
700
|
+
const identity8004DescriptorJson = typeof a.identity8004?.descriptor?.registrationJson === 'string'
|
|
701
|
+
? String(a.identity8004.descriptor.registrationJson)
|
|
702
|
+
: identity8122DescriptorJson;
|
|
703
|
+
const identityEnsDescriptorJson = typeof a.identityEns?.descriptor?.registrationJson === 'string'
|
|
704
|
+
? String(a.identityEns.descriptor.registrationJson)
|
|
705
|
+
: null;
|
|
706
|
+
const identityHolDescriptorJson = typeof a.identityHol?.descriptor?.registrationJson === 'string'
|
|
707
|
+
? String(a.identityHol.descriptor.registrationJson)
|
|
708
|
+
: null;
|
|
709
|
+
// Legacy aggregate: prefer 8004, else ENS, else HOL.
|
|
710
|
+
const rawJson = identity8004DescriptorJson || identityEnsDescriptorJson || identityHolDescriptorJson || null;
|
|
711
|
+
const sanitizeServiceUrl = (value) => {
|
|
712
|
+
if (typeof value !== 'string')
|
|
713
|
+
return null;
|
|
714
|
+
const v = value.trim();
|
|
715
|
+
if (!v)
|
|
716
|
+
return null;
|
|
717
|
+
// Never treat identifiers as endpoints.
|
|
718
|
+
if (/^(uaid:|did:)/i.test(v))
|
|
719
|
+
return null;
|
|
720
|
+
try {
|
|
721
|
+
const u = new URL(v);
|
|
722
|
+
const ok = u.protocol === 'http:' || u.protocol === 'https:' || u.protocol === 'ws:' || u.protocol === 'wss:';
|
|
723
|
+
return ok ? v : null;
|
|
724
|
+
}
|
|
725
|
+
catch {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
const identity8122OnchainMetadataJson = typeof a.identity8122?.descriptor?.nftMetadataJson === 'string' &&
|
|
730
|
+
String(a.identity8122.descriptor.nftMetadataJson).trim()
|
|
731
|
+
? String(a.identity8122.descriptor.nftMetadataJson)
|
|
732
|
+
: null;
|
|
733
|
+
const identity8004OnchainMetadataJson = typeof a.identity8004?.descriptor?.nftMetadataJson === 'string' &&
|
|
734
|
+
String(a.identity8004.descriptor.nftMetadataJson).trim()
|
|
735
|
+
? String(a.identity8004.descriptor.nftMetadataJson)
|
|
736
|
+
: identity8122OnchainMetadataJson;
|
|
737
|
+
const identityEnsOnchainMetadataJson = typeof a.identityEns?.descriptor?.nftMetadataJson === 'string' &&
|
|
738
|
+
String(a.identityEns.descriptor.nftMetadataJson).trim()
|
|
739
|
+
? String(a.identityEns.descriptor.nftMetadataJson)
|
|
740
|
+
: null;
|
|
741
|
+
const identityHolOnchainMetadataJson = typeof a.identityHol?.descriptor?.nftMetadataJson === 'string' &&
|
|
742
|
+
String(a.identityHol.descriptor.nftMetadataJson).trim()
|
|
743
|
+
? String(a.identityHol.descriptor.nftMetadataJson)
|
|
744
|
+
: null;
|
|
745
|
+
const identity8122Did = typeof a.identity8122?.did8122 === 'string' && String(a.identity8122.did8122).trim()
|
|
746
|
+
? String(a.identity8122.did8122).trim()
|
|
747
|
+
: typeof a.identity8122?.did === 'string' && String(a.identity8122.did).trim()
|
|
748
|
+
? String(a.identity8122.did).trim()
|
|
749
|
+
: null;
|
|
750
|
+
// Legacy aggregate: prefer 8004, else ENS, else HOL.
|
|
751
|
+
const onchainMetadataJson = identity8004OnchainMetadataJson ?? identityEnsOnchainMetadataJson ?? identityHolOnchainMetadataJson ?? null;
|
|
752
|
+
// Pull registration URI from onchain metadata if present (KB-backed, not on-chain call).
|
|
753
|
+
// Per UAID migration: expect a single canonical field name `agentUri`.
|
|
754
|
+
const agentUriFromOnchainMetadata = (() => {
|
|
755
|
+
const candidates = [
|
|
756
|
+
identity8004OnchainMetadataJson,
|
|
757
|
+
identityEnsOnchainMetadataJson,
|
|
758
|
+
identityHolOnchainMetadataJson,
|
|
759
|
+
];
|
|
760
|
+
for (const raw of candidates) {
|
|
761
|
+
if (typeof raw !== 'string' || !raw.trim())
|
|
762
|
+
continue;
|
|
763
|
+
try {
|
|
764
|
+
const parsed = JSON.parse(raw);
|
|
765
|
+
const v = typeof parsed.agentUri === 'string' ? parsed.agentUri.trim() : '';
|
|
766
|
+
if (v)
|
|
767
|
+
return v;
|
|
768
|
+
}
|
|
769
|
+
catch {
|
|
770
|
+
// ignore parse errors
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return null;
|
|
774
|
+
})();
|
|
775
|
+
const pickServiceUrl = (identity, protocolName) => {
|
|
776
|
+
const endpoints = Array.isArray(identity?.serviceEndpoints) ? identity.serviceEndpoints : [];
|
|
777
|
+
const target = protocolName.trim().toLowerCase();
|
|
778
|
+
for (const se of endpoints) {
|
|
779
|
+
const p = se?.protocol;
|
|
780
|
+
const proto = typeof p?.protocol === 'string' ? p.protocol.trim().toLowerCase() : '';
|
|
781
|
+
if (proto !== target)
|
|
782
|
+
continue;
|
|
783
|
+
const url = p?.serviceUrl;
|
|
784
|
+
const v = sanitizeServiceUrl(url);
|
|
785
|
+
if (v)
|
|
786
|
+
return v;
|
|
787
|
+
}
|
|
788
|
+
return null;
|
|
789
|
+
};
|
|
790
|
+
// Infer A2A/MCP endpoints from serviceEndpoints (canonical KB v2 shape).
|
|
791
|
+
const a2aEndpoint = pickServiceUrl(a.identity8004, 'a2a') ??
|
|
792
|
+
pickServiceUrl(a.identity8122, 'a2a') ??
|
|
793
|
+
pickServiceUrl(a.identityEns, 'a2a') ??
|
|
794
|
+
pickServiceUrl(a.identityHol, 'a2a') ??
|
|
795
|
+
null;
|
|
796
|
+
const mcpEndpoint = pickServiceUrl(a.identity8004, 'mcp') ??
|
|
797
|
+
pickServiceUrl(a.identity8122, 'mcp') ??
|
|
798
|
+
pickServiceUrl(a.identityEns, 'mcp') ??
|
|
799
|
+
pickServiceUrl(a.identityHol, 'mcp') ??
|
|
800
|
+
null;
|
|
801
|
+
const hasMcp = Boolean(mcpEndpoint);
|
|
802
|
+
// Assertions totals: prefer reviewResponses/validationResponses; fallback to legacy names
|
|
803
|
+
const feedbackCount = toFiniteNumberOrUndefined(a.assertions?.reviewResponses?.total);
|
|
804
|
+
const validationTotal = toFiniteNumberOrUndefined(a.assertions?.validationResponses?.total);
|
|
805
|
+
const parsedIdentity8004Descriptor = (() => {
|
|
806
|
+
if (typeof identity8004DescriptorJson !== 'string' || !identity8004DescriptorJson.trim())
|
|
807
|
+
return null;
|
|
808
|
+
try {
|
|
809
|
+
return JSON.parse(identity8004DescriptorJson);
|
|
810
|
+
}
|
|
811
|
+
catch {
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
})();
|
|
815
|
+
const normalized = {
|
|
816
|
+
agentId: agentId8004 ?? agentIdFromParsed ?? undefined,
|
|
817
|
+
uaid: typeof a.uaid === 'string' && a.uaid.trim().startsWith('uaid:')
|
|
818
|
+
? a.uaid.trim()
|
|
819
|
+
: null,
|
|
820
|
+
agentName: typeof a.agentName === 'string' ? a.agentName : undefined,
|
|
821
|
+
agentTypes: Array.isArray(a.agentTypes) ? a.agentTypes : undefined,
|
|
822
|
+
// Preserve the full identity list when present (new KB schema).
|
|
823
|
+
// This enables UIs to render one tab per identifier (e.g. multiple 8004 identities across chains).
|
|
824
|
+
identities: identitiesList ?? undefined,
|
|
825
|
+
description: typeof a.agentDescription === 'string'
|
|
826
|
+
? a.agentDescription
|
|
827
|
+
: typeof a.agentDescriptor?.description === 'string'
|
|
828
|
+
? a.agentDescriptor.description
|
|
829
|
+
: typeof parsedIdentity8004Descriptor?.description === 'string'
|
|
830
|
+
? String(parsedIdentity8004Descriptor.description)
|
|
831
|
+
: undefined,
|
|
832
|
+
image: typeof a.agentImage === 'string'
|
|
833
|
+
? a.agentImage
|
|
834
|
+
: typeof a.agentDescriptor?.image === 'string'
|
|
835
|
+
? a.agentDescriptor.image
|
|
836
|
+
: typeof parsedIdentity8004Descriptor?.image === 'string'
|
|
837
|
+
? String(parsedIdentity8004Descriptor.image)
|
|
838
|
+
: undefined,
|
|
839
|
+
chainId: chainId ?? undefined,
|
|
840
|
+
createdAtBlock: typeof a.createdAtBlock === 'number' ? a.createdAtBlock : undefined,
|
|
841
|
+
createdAtTime: typeof a.createdAtTime === 'number'
|
|
842
|
+
? a.createdAtTime
|
|
843
|
+
: a.createdAtTime != null
|
|
844
|
+
? Number(a.createdAtTime)
|
|
845
|
+
: undefined,
|
|
846
|
+
updatedAtTime: typeof a.updatedAtTime === 'number'
|
|
847
|
+
? a.updatedAtTime
|
|
848
|
+
: a.updatedAtTime != null
|
|
849
|
+
? Number(a.updatedAtTime)
|
|
850
|
+
: undefined,
|
|
851
|
+
// Trust ledger / ATI (KB v2 fields)
|
|
852
|
+
trustLedgerScore: typeof a.trustLedgerTotalPoints === 'number' && Number.isFinite(a.trustLedgerTotalPoints)
|
|
853
|
+
? a.trustLedgerTotalPoints
|
|
854
|
+
: undefined,
|
|
855
|
+
trustLedgerBadgeCount: typeof a.trustLedgerBadgeCount === 'number' && Number.isFinite(a.trustLedgerBadgeCount)
|
|
856
|
+
? a.trustLedgerBadgeCount
|
|
857
|
+
: undefined,
|
|
858
|
+
trustLedgerBadges: Array.isArray(a.trustLedgerBadges)
|
|
859
|
+
? a.trustLedgerBadges
|
|
860
|
+
: Array.isArray(a.trustLedgerBadgesList)
|
|
861
|
+
? a.trustLedgerBadgesList
|
|
862
|
+
: typeof a.trustLedgerBadgesJson === 'string'
|
|
863
|
+
? (() => {
|
|
864
|
+
try {
|
|
865
|
+
const parsed = JSON.parse(String(a.trustLedgerBadgesJson));
|
|
866
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
867
|
+
}
|
|
868
|
+
catch {
|
|
869
|
+
return null;
|
|
870
|
+
}
|
|
871
|
+
})()
|
|
872
|
+
: null,
|
|
873
|
+
// ATI is already part of AgentData and is displayed in agent cards.
|
|
874
|
+
atiOverallScore: typeof a.atiOverallScore === 'number' && Number.isFinite(a.atiOverallScore)
|
|
875
|
+
? a.atiOverallScore
|
|
876
|
+
: undefined,
|
|
877
|
+
atiOverallConfidence: typeof a.atiOverallConfidence === 'number' && Number.isFinite(a.atiOverallConfidence)
|
|
878
|
+
? a.atiOverallConfidence
|
|
879
|
+
: undefined,
|
|
880
|
+
atiVersion: typeof a.atiVersion === 'string' ? String(a.atiVersion) : undefined,
|
|
881
|
+
atiComputedAt: typeof a.atiComputedAt === 'number' && Number.isFinite(a.atiComputedAt)
|
|
882
|
+
? a.atiComputedAt
|
|
883
|
+
: undefined,
|
|
884
|
+
feedbackCount,
|
|
885
|
+
// KB assertions only provide totals (not pending/requested breakdowns). Treat them as "completed".
|
|
886
|
+
validationCompletedCount: validationTotal ?? undefined,
|
|
887
|
+
validationPendingCount: validationTotal !== undefined ? 0 : undefined,
|
|
888
|
+
validationRequestedCount: validationTotal ?? undefined,
|
|
889
|
+
agentAccount: agentAccount ?? undefined,
|
|
890
|
+
agentIdentityOwnerAccount: (registeredByAddress ?? identityOwner) ?? undefined,
|
|
891
|
+
eoaAgentIdentityOwnerAccount: registeredByAddress ?? (isOwnerEoa ? identityOwner : null),
|
|
892
|
+
eoaAgentAccount: isOwnerEoa ? agentAccount : null,
|
|
893
|
+
// Extra KB v2 account fields (flattened)
|
|
894
|
+
identityOwnerAccount: pickAccountAddress(a.identity8004?.ownerAccount) ?? undefined,
|
|
895
|
+
identityWalletAccount: pickAccountAddress(a.identity8004?.walletAccount) ?? undefined,
|
|
896
|
+
identityOperatorAccount: pickAccountAddress(a.identity8004?.operatorAccount) ?? undefined,
|
|
897
|
+
// Agent-scoped fields are not present in KB v2; mirror identity accounts for legacy consumers.
|
|
898
|
+
agentOwnerAccount: pickAccountAddress(a.identity8004?.ownerAccount) ?? undefined,
|
|
899
|
+
agentWalletAccount: pickAccountAddress(a.identity8004?.walletAccount) ?? undefined,
|
|
900
|
+
agentOperatorAccount: pickAccountAddress(a.identity8004?.operatorAccount) ?? undefined,
|
|
901
|
+
agentOwnerEOAAccount: pickAccountAddress(a.identity8004?.ownerEOAAccount) ?? undefined,
|
|
902
|
+
// Preserve for legacy callers, but do not use it to infer smart-agent-ness.
|
|
903
|
+
smartAgentAccount: pickAccountAddress(a.identity8004?.agentAccount) ?? undefined,
|
|
904
|
+
isSmartAgent: isSmartAgent,
|
|
905
|
+
// Identity tab fields (per-identity)
|
|
906
|
+
identity8004Did: typeof a.identity8004?.did === 'string'
|
|
907
|
+
? a.identity8004.did
|
|
908
|
+
: typeof a.identity8122?.did === 'string'
|
|
909
|
+
? a.identity8122.did
|
|
910
|
+
: undefined,
|
|
911
|
+
identity8122Did: identity8122Did ?? undefined,
|
|
912
|
+
identityEnsDid: typeof a.identityEns?.did === 'string' ? a.identityEns.did : undefined,
|
|
913
|
+
identityHolDid: typeof a.identityHol?.did === 'string' ? a.identityHol.did : undefined,
|
|
914
|
+
identityHolUaid: typeof a.identityHol?.uaidHOL === 'string' ? a.identityHol.uaidHOL : undefined,
|
|
915
|
+
identity8004DescriptorJson: identity8004DescriptorJson ?? undefined,
|
|
916
|
+
identity8122DescriptorJson: identity8122DescriptorJson ?? undefined,
|
|
917
|
+
identityEnsDescriptorJson: identityEnsDescriptorJson ?? undefined,
|
|
918
|
+
identityHolDescriptorJson: identityHolDescriptorJson ?? undefined,
|
|
919
|
+
identity8004OnchainMetadataJson: identity8004OnchainMetadataJson ?? undefined,
|
|
920
|
+
identity8122OnchainMetadataJson: identity8122OnchainMetadataJson ?? undefined,
|
|
921
|
+
identityEnsOnchainMetadataJson: identityEnsOnchainMetadataJson ?? undefined,
|
|
922
|
+
identityHolOnchainMetadataJson: identityHolOnchainMetadataJson ?? undefined,
|
|
923
|
+
identity8122: a.identity8122 ?? undefined,
|
|
924
|
+
didIdentity: didPrimary ?? undefined,
|
|
925
|
+
did: didPrimary ?? undefined,
|
|
926
|
+
agentUri: agentUriFromOnchainMetadata ?? undefined,
|
|
927
|
+
a2aEndpoint,
|
|
928
|
+
mcpEndpoint: mcpEndpoint ?? undefined,
|
|
929
|
+
mcp: hasMcp,
|
|
930
|
+
rawJson,
|
|
931
|
+
onchainMetadataJson,
|
|
932
|
+
// Minimal capability hints
|
|
933
|
+
active: true,
|
|
934
|
+
};
|
|
935
|
+
return this.normalizeAgent(normalized);
|
|
936
|
+
}
|
|
937
|
+
kbAgentSelectionCache = {};
|
|
938
|
+
kbAgentSelectionPromise = {};
|
|
939
|
+
async getKbAgentSelection(options) {
|
|
940
|
+
const mode = options?.includeIdentityAndAccounts ? 'full' : 'light';
|
|
941
|
+
const cached = this.kbAgentSelectionCache[mode];
|
|
942
|
+
if (typeof cached === 'string') {
|
|
943
|
+
return cached;
|
|
944
|
+
}
|
|
945
|
+
const inflight = this.kbAgentSelectionPromise[mode];
|
|
946
|
+
if (inflight) {
|
|
947
|
+
return inflight;
|
|
948
|
+
}
|
|
949
|
+
this.kbAgentSelectionPromise[mode] = (async () => {
|
|
950
|
+
try {
|
|
951
|
+
const fields = await this.getTypeFields('KbAgent');
|
|
952
|
+
const names = new Set((fields ?? []).map((f) => f?.name).filter(Boolean));
|
|
953
|
+
const identityAndAccountsBase = await this.buildKbIdentityAndAccountsSelectionBase();
|
|
954
|
+
const identityAndAccounts = identityAndAccountsBase +
|
|
955
|
+
(mode === 'full' && names.has('agentAccount')
|
|
956
|
+
? `\n\n agentAccount { iri chainId address accountType didEthr }\n`
|
|
957
|
+
: '');
|
|
958
|
+
const baseParts = [];
|
|
959
|
+
if (names.has('iri'))
|
|
960
|
+
baseParts.push('iri');
|
|
961
|
+
if (names.has('uaid'))
|
|
962
|
+
baseParts.push('uaid');
|
|
963
|
+
if (names.has('agentName'))
|
|
964
|
+
baseParts.push('agentName');
|
|
965
|
+
if (names.has('agentDescription'))
|
|
966
|
+
baseParts.push('agentDescription');
|
|
967
|
+
if (names.has('agentImage'))
|
|
968
|
+
baseParts.push('agentImage');
|
|
969
|
+
if (names.has('agentDescriptor'))
|
|
970
|
+
baseParts.push('agentDescriptor { iri name description image }');
|
|
971
|
+
if (names.has('agentTypes'))
|
|
972
|
+
baseParts.push('agentTypes');
|
|
973
|
+
if (names.has('createdAtBlock'))
|
|
974
|
+
baseParts.push('createdAtBlock');
|
|
975
|
+
if (names.has('createdAtTime'))
|
|
976
|
+
baseParts.push('createdAtTime');
|
|
977
|
+
if (names.has('updatedAtTime'))
|
|
978
|
+
baseParts.push('updatedAtTime');
|
|
979
|
+
if (names.has('trustLedgerTotalPoints'))
|
|
980
|
+
baseParts.push('trustLedgerTotalPoints');
|
|
981
|
+
if (names.has('trustLedgerBadgeCount'))
|
|
982
|
+
baseParts.push('trustLedgerBadgeCount');
|
|
983
|
+
if (names.has('trustLedgerComputedAt'))
|
|
984
|
+
baseParts.push('trustLedgerComputedAt');
|
|
985
|
+
if (names.has('atiOverallScore'))
|
|
986
|
+
baseParts.push('atiOverallScore');
|
|
987
|
+
if (names.has('atiOverallConfidence'))
|
|
988
|
+
baseParts.push('atiOverallConfidence');
|
|
989
|
+
if (names.has('atiVersion'))
|
|
990
|
+
baseParts.push('atiVersion');
|
|
991
|
+
if (names.has('atiComputedAt'))
|
|
992
|
+
baseParts.push('atiComputedAt');
|
|
993
|
+
// Optional badge list (schema-dependent).
|
|
994
|
+
// In the KB v2 schema this is a structured list (TrustLedgerBadgeAward), so we must select subfields.
|
|
995
|
+
if (names.has('trustLedgerBadges')) {
|
|
996
|
+
baseParts.push(`
|
|
997
|
+
trustLedgerBadges {
|
|
998
|
+
iri
|
|
999
|
+
awardedAt
|
|
1000
|
+
definition {
|
|
1001
|
+
badgeId
|
|
1002
|
+
name
|
|
1003
|
+
iconRef
|
|
1004
|
+
points
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
`);
|
|
1008
|
+
}
|
|
1009
|
+
// Older experimental schemas (if any) may expose alternative badge encodings.
|
|
1010
|
+
if (names.has('trustLedgerBadgesList'))
|
|
1011
|
+
baseParts.push('trustLedgerBadgesList');
|
|
1012
|
+
if (names.has('trustLedgerBadgesJson'))
|
|
1013
|
+
baseParts.push('trustLedgerBadgesJson');
|
|
1014
|
+
// Optional legacy/derived fields (may not exist in newer KB schemas)
|
|
1015
|
+
if (names.has('did8004'))
|
|
1016
|
+
baseParts.push('did8004');
|
|
1017
|
+
if (names.has('agentId8004'))
|
|
1018
|
+
baseParts.push('agentId8004');
|
|
1019
|
+
if (names.has('isSmartAgent'))
|
|
1020
|
+
baseParts.push('isSmartAgent');
|
|
1021
|
+
const assertionsParts = [];
|
|
1022
|
+
if (names.has('assertions')) {
|
|
1023
|
+
assertionsParts.push(`
|
|
1024
|
+
assertions {
|
|
1025
|
+
reviewResponses { total }
|
|
1026
|
+
validationResponses { total }
|
|
1027
|
+
total
|
|
1028
|
+
}
|
|
1029
|
+
`);
|
|
1030
|
+
}
|
|
1031
|
+
const assertionsBlock = assertionsParts.length > 0
|
|
1032
|
+
? assertionsParts.join('\n')
|
|
1033
|
+
: `
|
|
1034
|
+
assertions {
|
|
1035
|
+
reviewResponses { total }
|
|
1036
|
+
validationResponses { total }
|
|
1037
|
+
total
|
|
1038
|
+
}
|
|
1039
|
+
`;
|
|
1040
|
+
// For agent list views (cards), we need identity descriptors to provide image/description
|
|
1041
|
+
// even when KB does not populate agentImage/agentDescription at the agent root.
|
|
1042
|
+
const identityLightBlock = (() => {
|
|
1043
|
+
if (!names.has('identities'))
|
|
1044
|
+
return '';
|
|
1045
|
+
return `
|
|
1046
|
+
identities {
|
|
1047
|
+
kind
|
|
1048
|
+
did
|
|
1049
|
+
descriptor {
|
|
1050
|
+
iri
|
|
1051
|
+
kind
|
|
1052
|
+
name
|
|
1053
|
+
description
|
|
1054
|
+
image
|
|
1055
|
+
registrationJson
|
|
1056
|
+
nftMetadataJson
|
|
1057
|
+
registeredBy
|
|
1058
|
+
registryNamespace
|
|
1059
|
+
skills
|
|
1060
|
+
domains
|
|
1061
|
+
}
|
|
1062
|
+
... on KbIdentity8122 {
|
|
1063
|
+
registryAddress
|
|
1064
|
+
agentId8122
|
|
1065
|
+
registry { registryName }
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
`;
|
|
1069
|
+
})();
|
|
1070
|
+
const selection = [
|
|
1071
|
+
baseParts.join('\n'),
|
|
1072
|
+
assertionsBlock,
|
|
1073
|
+
mode === 'light' ? identityLightBlock : '',
|
|
1074
|
+
mode === 'full' ? identityAndAccounts : '',
|
|
1075
|
+
]
|
|
1076
|
+
.filter((part) => typeof part === 'string' && part.trim().length > 0)
|
|
1077
|
+
.join('\n');
|
|
1078
|
+
this.kbAgentSelectionCache[mode] = selection;
|
|
1079
|
+
return selection;
|
|
1080
|
+
}
|
|
1081
|
+
catch {
|
|
1082
|
+
// Fallback selection when introspection fails: keep it minimal and schema-stable.
|
|
1083
|
+
const selection = [
|
|
1084
|
+
[
|
|
1085
|
+
'iri',
|
|
1086
|
+
'uaid',
|
|
1087
|
+
'agentName',
|
|
1088
|
+
'agentDescription',
|
|
1089
|
+
'agentImage',
|
|
1090
|
+
'agentDescriptor { iri name description image }',
|
|
1091
|
+
'agentTypes',
|
|
1092
|
+
'createdAtBlock',
|
|
1093
|
+
'createdAtTime',
|
|
1094
|
+
'updatedAtTime',
|
|
1095
|
+
'trustLedgerTotalPoints',
|
|
1096
|
+
'trustLedgerBadgeCount',
|
|
1097
|
+
'trustLedgerComputedAt',
|
|
1098
|
+
`
|
|
1099
|
+
trustLedgerBadges {
|
|
1100
|
+
iri
|
|
1101
|
+
awardedAt
|
|
1102
|
+
definition { badgeId name iconRef points }
|
|
1103
|
+
}
|
|
1104
|
+
`,
|
|
1105
|
+
'atiOverallScore',
|
|
1106
|
+
'atiOverallConfidence',
|
|
1107
|
+
'atiVersion',
|
|
1108
|
+
'atiComputedAt',
|
|
1109
|
+
].join('\n'),
|
|
1110
|
+
`
|
|
1111
|
+
assertions {
|
|
1112
|
+
reviewResponses { total }
|
|
1113
|
+
validationResponses { total }
|
|
1114
|
+
total
|
|
1115
|
+
}
|
|
1116
|
+
`,
|
|
1117
|
+
mode === 'full'
|
|
1118
|
+
? `
|
|
1119
|
+
identities { iri kind did }
|
|
1120
|
+
`
|
|
1121
|
+
: '',
|
|
1122
|
+
]
|
|
1123
|
+
.filter((part) => typeof part === 'string' && part.trim().length > 0)
|
|
1124
|
+
.join('\n');
|
|
1125
|
+
this.kbAgentSelectionCache[mode] = selection;
|
|
1126
|
+
return selection;
|
|
1127
|
+
}
|
|
1128
|
+
finally {
|
|
1129
|
+
this.kbAgentSelectionPromise[mode] = undefined;
|
|
1130
|
+
}
|
|
1131
|
+
})();
|
|
1132
|
+
return this.kbAgentSelectionPromise[mode];
|
|
1133
|
+
}
|
|
1134
|
+
async supportsQueryField(fieldName) {
|
|
1135
|
+
const fields = await this.getQueryFields();
|
|
1136
|
+
if (!fields)
|
|
1137
|
+
return false;
|
|
1138
|
+
return fields.some((f) => f.name === fieldName);
|
|
1139
|
+
}
|
|
1140
|
+
normalizeAgent(agent) {
|
|
1141
|
+
const record = (agent ?? {});
|
|
1142
|
+
const toOptionalString = (value) => {
|
|
1143
|
+
if (value === undefined || value === null) {
|
|
1144
|
+
return undefined;
|
|
1145
|
+
}
|
|
1146
|
+
return String(value);
|
|
1147
|
+
};
|
|
1148
|
+
const toOptionalStringOrNull = (value) => {
|
|
1149
|
+
if (value === undefined) {
|
|
1150
|
+
return undefined;
|
|
1151
|
+
}
|
|
1152
|
+
if (value === null) {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
return String(value);
|
|
1156
|
+
};
|
|
1157
|
+
const toOptionalNumber = (value) => {
|
|
1158
|
+
if (value === undefined || value === null) {
|
|
1159
|
+
return undefined;
|
|
1160
|
+
}
|
|
1161
|
+
const numeric = typeof value === 'number' ? value : Number(value);
|
|
1162
|
+
return Number.isFinite(numeric) ? numeric : undefined;
|
|
1163
|
+
};
|
|
1164
|
+
const toOptionalNumberOrNull = (value) => {
|
|
1165
|
+
if (value === undefined) {
|
|
1166
|
+
return undefined;
|
|
1167
|
+
}
|
|
1168
|
+
if (value === null) {
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
const numeric = typeof value === 'number' ? value : Number(value);
|
|
1172
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
1173
|
+
};
|
|
1174
|
+
// Parse rawJson to extract all metadata fields
|
|
1175
|
+
let parsedMetadata = {};
|
|
1176
|
+
if (record.rawJson && typeof record.rawJson === 'string') {
|
|
1177
|
+
try {
|
|
1178
|
+
const parsed = JSON.parse(record.rawJson);
|
|
1179
|
+
if (parsed && typeof parsed === 'object') {
|
|
1180
|
+
// Extract all fields from the registration JSON
|
|
1181
|
+
parsedMetadata = parsed;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
catch (error) {
|
|
1185
|
+
// Silently ignore JSON parse errors
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const normalized = {
|
|
1189
|
+
...record,
|
|
1190
|
+
// Merge all metadata from parsed rawJson
|
|
1191
|
+
...parsedMetadata,
|
|
1192
|
+
};
|
|
1193
|
+
// UAID is required (do not synthesize from did:8004).
|
|
1194
|
+
const uaidRaw = record.uaid;
|
|
1195
|
+
const uaidStr = typeof uaidRaw === 'string' ? uaidRaw.trim() : '';
|
|
1196
|
+
if (!uaidStr) {
|
|
1197
|
+
const agentId8004 = typeof record.agentId === 'string' || typeof record.agentId === 'number'
|
|
1198
|
+
? String(record.agentId)
|
|
1199
|
+
: '';
|
|
1200
|
+
const chainId = typeof record.chainId === 'number' || typeof record.chainId === 'string'
|
|
1201
|
+
? String(record.chainId)
|
|
1202
|
+
: '';
|
|
1203
|
+
throw new Error(`[Discovery] Missing uaid for agent (chainId=${chainId || '?'}, agentId=${agentId8004 || '?'}) from KB GraphQL. Ensure Query.kbAgents / Query.kbOwnedAgentsAllChains returns KbAgent.uaid.`);
|
|
1204
|
+
}
|
|
1205
|
+
if (!uaidStr.startsWith('uaid:')) {
|
|
1206
|
+
const agentId8004 = typeof record.agentId === 'string' || typeof record.agentId === 'number'
|
|
1207
|
+
? String(record.agentId)
|
|
1208
|
+
: '';
|
|
1209
|
+
const chainId = typeof record.chainId === 'number' || typeof record.chainId === 'string'
|
|
1210
|
+
? String(record.chainId)
|
|
1211
|
+
: '';
|
|
1212
|
+
throw new Error(`[Discovery] Invalid uaid value for agent (chainId=${chainId || '?'}, agentId=${agentId8004 || '?'}, uaid=${uaidStr}). Expected uaid to start with "uaid:". Your KB is currently returning a DID (e.g. "did:8004:...") in the uaid field.`);
|
|
1213
|
+
}
|
|
1214
|
+
normalized.uaid = uaidStr;
|
|
1215
|
+
const agentAccount = toOptionalString(record.agentAccount);
|
|
1216
|
+
if (agentAccount !== undefined) {
|
|
1217
|
+
normalized.agentAccount = agentAccount;
|
|
1218
|
+
}
|
|
1219
|
+
const agentIdentityOwnerAccount = toOptionalString(record.agentIdentityOwnerAccount);
|
|
1220
|
+
if (agentIdentityOwnerAccount !== undefined) {
|
|
1221
|
+
normalized.agentIdentityOwnerAccount = agentIdentityOwnerAccount;
|
|
1222
|
+
}
|
|
1223
|
+
const eoaAgentIdentityOwnerAccount = toOptionalStringOrNull(record.eoaAgentIdentityOwnerAccount);
|
|
1224
|
+
if (eoaAgentIdentityOwnerAccount !== undefined) {
|
|
1225
|
+
normalized.eoaAgentIdentityOwnerAccount = eoaAgentIdentityOwnerAccount;
|
|
1226
|
+
}
|
|
1227
|
+
const eoaAgentAccount = toOptionalStringOrNull(record.eoaAgentAccount);
|
|
1228
|
+
if (eoaAgentAccount !== undefined) {
|
|
1229
|
+
normalized.eoaAgentAccount = eoaAgentAccount;
|
|
1230
|
+
}
|
|
1231
|
+
const agentCategory = toOptionalStringOrNull(record.agentCategory);
|
|
1232
|
+
if (agentCategory !== undefined) {
|
|
1233
|
+
normalized.agentCategory = agentCategory;
|
|
1234
|
+
}
|
|
1235
|
+
const didIdentity = toOptionalStringOrNull(record.didIdentity);
|
|
1236
|
+
if (didIdentity !== undefined) {
|
|
1237
|
+
normalized.didIdentity = didIdentity;
|
|
1238
|
+
}
|
|
1239
|
+
const didAccount = toOptionalStringOrNull(record.didAccount);
|
|
1240
|
+
if (didAccount !== undefined) {
|
|
1241
|
+
normalized.didAccount = didAccount;
|
|
1242
|
+
}
|
|
1243
|
+
const didName = toOptionalStringOrNull(record.didName);
|
|
1244
|
+
if (didName !== undefined) {
|
|
1245
|
+
normalized.didName = didName;
|
|
1246
|
+
}
|
|
1247
|
+
const agentUri = toOptionalStringOrNull(record.agentUri);
|
|
1248
|
+
if (agentUri !== undefined) {
|
|
1249
|
+
normalized.agentUri = agentUri;
|
|
1250
|
+
}
|
|
1251
|
+
const validationPendingCount = toOptionalNumberOrNull(record.validationPendingCount);
|
|
1252
|
+
if (validationPendingCount !== undefined) {
|
|
1253
|
+
normalized.validationPendingCount = validationPendingCount;
|
|
1254
|
+
}
|
|
1255
|
+
const validationCompletedCount = toOptionalNumberOrNull(record.validationCompletedCount);
|
|
1256
|
+
if (validationCompletedCount !== undefined) {
|
|
1257
|
+
normalized.validationCompletedCount = validationCompletedCount;
|
|
1258
|
+
}
|
|
1259
|
+
const validationRequestedCount = toOptionalNumberOrNull(record.validationRequestedCount);
|
|
1260
|
+
if (validationRequestedCount !== undefined) {
|
|
1261
|
+
normalized.validationRequestedCount = validationRequestedCount;
|
|
1262
|
+
}
|
|
1263
|
+
const initiatedAssociationCount = toOptionalNumberOrNull(record.initiatedAssociationCount);
|
|
1264
|
+
if (initiatedAssociationCount !== undefined) {
|
|
1265
|
+
normalized.initiatedAssociationCount = initiatedAssociationCount;
|
|
1266
|
+
}
|
|
1267
|
+
const approvedAssociationCount = toOptionalNumberOrNull(record.approvedAssociationCount);
|
|
1268
|
+
if (approvedAssociationCount !== undefined) {
|
|
1269
|
+
normalized.approvedAssociationCount = approvedAssociationCount;
|
|
1270
|
+
}
|
|
1271
|
+
const atiOverallScore = toOptionalNumberOrNull(record.atiOverallScore);
|
|
1272
|
+
if (atiOverallScore !== undefined) {
|
|
1273
|
+
normalized.atiOverallScore = atiOverallScore;
|
|
1274
|
+
}
|
|
1275
|
+
const atiOverallConfidence = toOptionalNumberOrNull(record.atiOverallConfidence);
|
|
1276
|
+
if (atiOverallConfidence !== undefined) {
|
|
1277
|
+
normalized.atiOverallConfidence = atiOverallConfidence;
|
|
1278
|
+
}
|
|
1279
|
+
const atiVersion = toOptionalStringOrNull(record.atiVersion);
|
|
1280
|
+
if (atiVersion !== undefined) {
|
|
1281
|
+
normalized.atiVersion = atiVersion;
|
|
1282
|
+
}
|
|
1283
|
+
const atiComputedAt = toOptionalNumberOrNull(record.atiComputedAt);
|
|
1284
|
+
if (atiComputedAt !== undefined) {
|
|
1285
|
+
normalized.atiComputedAt = atiComputedAt;
|
|
1286
|
+
}
|
|
1287
|
+
const atiBundleJson = toOptionalStringOrNull(record.atiBundleJson);
|
|
1288
|
+
if (atiBundleJson !== undefined) {
|
|
1289
|
+
normalized.atiBundleJson = atiBundleJson;
|
|
1290
|
+
}
|
|
1291
|
+
const trustLedgerScore = toOptionalNumberOrNull(record.trustLedgerScore);
|
|
1292
|
+
if (trustLedgerScore !== undefined) {
|
|
1293
|
+
normalized.trustLedgerScore = trustLedgerScore;
|
|
1294
|
+
}
|
|
1295
|
+
const trustLedgerBadgeCount = toOptionalNumberOrNull(record.trustLedgerBadgeCount);
|
|
1296
|
+
if (trustLedgerBadgeCount !== undefined) {
|
|
1297
|
+
normalized.trustLedgerBadgeCount = trustLedgerBadgeCount;
|
|
1298
|
+
}
|
|
1299
|
+
const trustLedgerOverallRank = toOptionalNumberOrNull(record.trustLedgerOverallRank);
|
|
1300
|
+
if (trustLedgerOverallRank !== undefined) {
|
|
1301
|
+
normalized.trustLedgerOverallRank = trustLedgerOverallRank;
|
|
1302
|
+
}
|
|
1303
|
+
const trustLedgerCapabilityRank = toOptionalNumberOrNull(record.trustLedgerCapabilityRank);
|
|
1304
|
+
if (trustLedgerCapabilityRank !== undefined) {
|
|
1305
|
+
normalized.trustLedgerCapabilityRank = trustLedgerCapabilityRank;
|
|
1306
|
+
}
|
|
1307
|
+
const description = toOptionalStringOrNull(record.description);
|
|
1308
|
+
if (description !== undefined) {
|
|
1309
|
+
normalized.description = description;
|
|
1310
|
+
}
|
|
1311
|
+
const image = toOptionalStringOrNull(record.image);
|
|
1312
|
+
if (image !== undefined) {
|
|
1313
|
+
normalized.image = image;
|
|
1314
|
+
}
|
|
1315
|
+
const a2aEndpoint = toOptionalStringOrNull(record.a2aEndpoint);
|
|
1316
|
+
if (a2aEndpoint !== undefined) {
|
|
1317
|
+
normalized.a2aEndpoint = a2aEndpoint;
|
|
1318
|
+
}
|
|
1319
|
+
const agentCardJson = toOptionalStringOrNull(record.agentCardJson);
|
|
1320
|
+
if (agentCardJson !== undefined) {
|
|
1321
|
+
normalized.agentCardJson = agentCardJson;
|
|
1322
|
+
}
|
|
1323
|
+
const agentCardReadAt = toOptionalNumberOrNull(record.agentCardReadAt);
|
|
1324
|
+
if (agentCardReadAt !== undefined) {
|
|
1325
|
+
normalized.agentCardReadAt = agentCardReadAt;
|
|
1326
|
+
}
|
|
1327
|
+
const supportedTrust = toOptionalString(record.supportedTrust);
|
|
1328
|
+
if (supportedTrust !== undefined) {
|
|
1329
|
+
normalized.supportedTrust = supportedTrust;
|
|
1330
|
+
}
|
|
1331
|
+
const did = toOptionalStringOrNull(record.did);
|
|
1332
|
+
if (did !== undefined) {
|
|
1333
|
+
normalized.did = did;
|
|
1334
|
+
}
|
|
1335
|
+
// Handle agentName: prefer non-empty values from multiple sources
|
|
1336
|
+
// Priority: 1) direct agentName field, 2) name from parsedMetadata, 3) agentName from parsedMetadata
|
|
1337
|
+
let agentName = undefined;
|
|
1338
|
+
// Check direct agentName field (must be non-empty after trim)
|
|
1339
|
+
const rawAgentName = record.agentName;
|
|
1340
|
+
const directAgentName = typeof rawAgentName === 'string' && rawAgentName.trim().length > 0
|
|
1341
|
+
? rawAgentName.trim()
|
|
1342
|
+
: undefined;
|
|
1343
|
+
if (directAgentName) {
|
|
1344
|
+
agentName = directAgentName;
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
// Check parsedMetadata for name or agentName
|
|
1348
|
+
const metadataName = typeof parsedMetadata.name === 'string' && parsedMetadata.name.trim().length > 0
|
|
1349
|
+
? parsedMetadata.name.trim()
|
|
1350
|
+
: undefined;
|
|
1351
|
+
const metadataAgentName = typeof parsedMetadata.agentName === 'string' && parsedMetadata.agentName.trim().length > 0
|
|
1352
|
+
? parsedMetadata.agentName.trim()
|
|
1353
|
+
: undefined;
|
|
1354
|
+
agentName = metadataAgentName || metadataName;
|
|
1355
|
+
if (agentName) {
|
|
1356
|
+
console.log('[AIAgentDiscoveryClient.normalizeAgent] Using metadata name:', {
|
|
1357
|
+
fromMetadataAgentName: !!metadataAgentName,
|
|
1358
|
+
fromMetadataName: !!metadataName,
|
|
1359
|
+
agentName,
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
// Set agentName: use found value, or undefined if original was empty and no replacement found
|
|
1364
|
+
// This ensures empty strings are converted to undefined
|
|
1365
|
+
if (agentName && agentName.length > 0) {
|
|
1366
|
+
normalized.agentName = agentName;
|
|
1367
|
+
}
|
|
1368
|
+
else if (typeof rawAgentName === 'string' && rawAgentName.trim().length === 0) {
|
|
1369
|
+
// Original was empty string, and we didn't find a replacement - set to undefined
|
|
1370
|
+
normalized.agentName = undefined;
|
|
1371
|
+
console.log('[AIAgentDiscoveryClient.normalizeAgent] Original was empty string, set to undefined');
|
|
1372
|
+
}
|
|
1373
|
+
// If rawAgentName was undefined/null, leave it as-is (don't overwrite)
|
|
1374
|
+
return normalized;
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* List agents with a deterministic default ordering (agentId DESC).
|
|
1378
|
+
*
|
|
1379
|
+
* @param limit - Maximum number of agents to return per page
|
|
1380
|
+
* @param offset - Number of agents to skip
|
|
1381
|
+
* @returns List of agents
|
|
1382
|
+
*/
|
|
1383
|
+
async listAgents(limit, offset) {
|
|
1384
|
+
const effectiveLimit = limit ?? 100;
|
|
1385
|
+
const effectiveOffset = offset ?? 0;
|
|
1386
|
+
const selection = await this.getKbAgentSelection({ includeIdentityAndAccounts: false });
|
|
1387
|
+
const orderBy = (await this.pickKbAgentOrderBy(['createdAtTime', 'updatedAtTime', 'agentId8004', 'uaid', 'agentName'])) ??
|
|
1388
|
+
'agentName';
|
|
1389
|
+
const query = `
|
|
1390
|
+
query ListKbAgents($first: Int, $skip: Int, $orderBy: KbAgentOrderBy) {
|
|
1391
|
+
kbAgents(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: DESC) {
|
|
1392
|
+
agents { ${selection} }
|
|
1393
|
+
total
|
|
1394
|
+
hasMore
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
`;
|
|
1398
|
+
try {
|
|
1399
|
+
const data = await this.gqlRequest(query, {
|
|
1400
|
+
first: effectiveLimit,
|
|
1401
|
+
skip: effectiveOffset,
|
|
1402
|
+
orderBy,
|
|
1403
|
+
});
|
|
1404
|
+
const list = data?.kbAgents?.agents ?? [];
|
|
1405
|
+
return list.map((a) => this.mapKbAgentToAgentData(a));
|
|
1406
|
+
}
|
|
1407
|
+
catch (error) {
|
|
1408
|
+
throw error;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Run a semantic search over agents using the discovery indexer's
|
|
1413
|
+
* `semanticAgentSearch` GraphQL field.
|
|
1414
|
+
*
|
|
1415
|
+
* NOTE: This expects the KB GraphQL schema. If the backend does not expose
|
|
1416
|
+
* `kbSemanticAgentSearch`, it will throw.
|
|
1417
|
+
*/
|
|
1418
|
+
async semanticAgentSearch(params) {
|
|
1419
|
+
const rawText = typeof params?.text === 'string' ? params.text : '';
|
|
1420
|
+
const text = rawText.trim();
|
|
1421
|
+
const rawIntentJson = typeof params?.intentJson === 'string' ? params.intentJson : '';
|
|
1422
|
+
const intentJson = rawIntentJson.trim();
|
|
1423
|
+
const topK = typeof params?.topK === 'number' && Number.isFinite(params.topK) && params.topK > 0
|
|
1424
|
+
? Math.floor(params.topK)
|
|
1425
|
+
: undefined;
|
|
1426
|
+
// Nothing to search.
|
|
1427
|
+
if (!text && !intentJson) {
|
|
1428
|
+
return { total: 0, matches: [] };
|
|
1429
|
+
}
|
|
1430
|
+
const agentSelection = await this.getKbAgentSelection({ includeIdentityAndAccounts: false });
|
|
1431
|
+
const selection = `
|
|
1432
|
+
total
|
|
1433
|
+
matches {
|
|
1434
|
+
score
|
|
1435
|
+
matchReasons
|
|
1436
|
+
agent {
|
|
1437
|
+
${agentSelection}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
`;
|
|
1441
|
+
const requiredSkills = Array.isArray(params.requiredSkills) ? params.requiredSkills : undefined;
|
|
1442
|
+
// Note: intentType is not sent to GraphQL - backend should extract it from intentJson
|
|
1443
|
+
// We keep it in params for logging/debugging but don't include it in the GraphQL query
|
|
1444
|
+
const query = `
|
|
1445
|
+
query KbSemanticAgentSearch($input: SemanticAgentSearchInput!) {
|
|
1446
|
+
kbSemanticAgentSearch(input: $input) {
|
|
1447
|
+
${selection}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
`;
|
|
1451
|
+
try {
|
|
1452
|
+
const input = {};
|
|
1453
|
+
if (text)
|
|
1454
|
+
input.text = text;
|
|
1455
|
+
if (intentJson)
|
|
1456
|
+
input.intentJson = intentJson;
|
|
1457
|
+
if (typeof topK === 'number')
|
|
1458
|
+
input.topK = topK;
|
|
1459
|
+
if (Array.isArray(requiredSkills) && requiredSkills.length > 0)
|
|
1460
|
+
input.requiredSkills = requiredSkills;
|
|
1461
|
+
const data = await this.client.request(query, {
|
|
1462
|
+
input,
|
|
1463
|
+
});
|
|
1464
|
+
const root = data.kbSemanticAgentSearch;
|
|
1465
|
+
if (!root) {
|
|
1466
|
+
return { total: 0, matches: [] };
|
|
1467
|
+
}
|
|
1468
|
+
const total = typeof root.total === 'number' && Number.isFinite(root.total) && root.total >= 0
|
|
1469
|
+
? root.total
|
|
1470
|
+
: Array.isArray(root.matches)
|
|
1471
|
+
? root.matches.length
|
|
1472
|
+
: 0;
|
|
1473
|
+
const matches = [];
|
|
1474
|
+
const rawMatches = Array.isArray(root.matches) ? root.matches : [];
|
|
1475
|
+
for (const item of rawMatches) {
|
|
1476
|
+
if (!item || !item.agent) {
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
const normalizedAgent = this.mapKbAgentToAgentData(item.agent);
|
|
1480
|
+
matches.push({
|
|
1481
|
+
score: typeof item.score === 'number' && Number.isFinite(item.score)
|
|
1482
|
+
? item.score
|
|
1483
|
+
: null,
|
|
1484
|
+
matchReasons: Array.isArray(item.matchReasons)
|
|
1485
|
+
? item.matchReasons.map((reason) => String(reason))
|
|
1486
|
+
: null,
|
|
1487
|
+
agent: normalizedAgent,
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
return {
|
|
1491
|
+
total,
|
|
1492
|
+
matches,
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
catch (error) {
|
|
1496
|
+
throw error;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Fetch OASF skills taxonomy from the discovery GraphQL endpoint (best-effort).
|
|
1501
|
+
* Returns [] if the backend does not expose `oasfSkills`.
|
|
1502
|
+
*/
|
|
1503
|
+
async oasfSkills(params) {
|
|
1504
|
+
const query = `
|
|
1505
|
+
query OasfSkills(
|
|
1506
|
+
$key: String
|
|
1507
|
+
$nameKey: String
|
|
1508
|
+
$category: String
|
|
1509
|
+
$extendsKey: String
|
|
1510
|
+
$limit: Int
|
|
1511
|
+
$offset: Int
|
|
1512
|
+
$orderBy: String
|
|
1513
|
+
$orderDirection: String
|
|
1514
|
+
) {
|
|
1515
|
+
oasfSkills(
|
|
1516
|
+
key: $key
|
|
1517
|
+
nameKey: $nameKey
|
|
1518
|
+
category: $category
|
|
1519
|
+
extendsKey: $extendsKey
|
|
1520
|
+
limit: $limit
|
|
1521
|
+
offset: $offset
|
|
1522
|
+
orderBy: $orderBy
|
|
1523
|
+
orderDirection: $orderDirection
|
|
1524
|
+
) {
|
|
1525
|
+
key
|
|
1526
|
+
nameKey
|
|
1527
|
+
uid
|
|
1528
|
+
caption
|
|
1529
|
+
extendsKey
|
|
1530
|
+
category
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
`;
|
|
1534
|
+
try {
|
|
1535
|
+
const variables = {};
|
|
1536
|
+
if (typeof params?.limit === 'number')
|
|
1537
|
+
variables.limit = params.limit;
|
|
1538
|
+
if (typeof params?.offset === 'number')
|
|
1539
|
+
variables.offset = params.offset;
|
|
1540
|
+
if (params?.orderBy)
|
|
1541
|
+
variables.orderBy = params.orderBy;
|
|
1542
|
+
if (params?.orderDirection)
|
|
1543
|
+
variables.orderDirection = params.orderDirection;
|
|
1544
|
+
if (params?.key)
|
|
1545
|
+
variables.key = params.key;
|
|
1546
|
+
if (params?.nameKey)
|
|
1547
|
+
variables.nameKey = params.nameKey;
|
|
1548
|
+
if (params?.category)
|
|
1549
|
+
variables.category = params.category;
|
|
1550
|
+
if (params?.extendsKey)
|
|
1551
|
+
variables.extendsKey = params.extendsKey;
|
|
1552
|
+
const data = await this.client.request(query, variables);
|
|
1553
|
+
return Array.isArray(data?.oasfSkills) ? data.oasfSkills : [];
|
|
1554
|
+
}
|
|
1555
|
+
catch (error) {
|
|
1556
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1557
|
+
if (message.includes('Cannot query field "oasfSkills"')) {
|
|
1558
|
+
throw new Error('Discovery KB schema missing Query.oasfSkills');
|
|
1559
|
+
}
|
|
1560
|
+
if (/Cannot return null for non-nullable field\s+Query\.oasfSkills\b/i.test(message)) {
|
|
1561
|
+
throw new Error('Discovery KB resolver bug: Query.oasfSkills returned null');
|
|
1562
|
+
}
|
|
1563
|
+
throw error;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Fetch OASF domains taxonomy from the discovery GraphQL endpoint (best-effort).
|
|
1568
|
+
* Returns [] if the backend does not expose `oasfDomains`.
|
|
1569
|
+
*/
|
|
1570
|
+
async oasfDomains(params) {
|
|
1571
|
+
const query = `
|
|
1572
|
+
query OasfDomains(
|
|
1573
|
+
$key: String
|
|
1574
|
+
$nameKey: String
|
|
1575
|
+
$category: String
|
|
1576
|
+
$extendsKey: String
|
|
1577
|
+
$limit: Int
|
|
1578
|
+
$offset: Int
|
|
1579
|
+
$orderBy: String
|
|
1580
|
+
$orderDirection: String
|
|
1581
|
+
) {
|
|
1582
|
+
oasfDomains(
|
|
1583
|
+
key: $key
|
|
1584
|
+
nameKey: $nameKey
|
|
1585
|
+
category: $category
|
|
1586
|
+
extendsKey: $extendsKey
|
|
1587
|
+
limit: $limit
|
|
1588
|
+
offset: $offset
|
|
1589
|
+
orderBy: $orderBy
|
|
1590
|
+
orderDirection: $orderDirection
|
|
1591
|
+
) {
|
|
1592
|
+
key
|
|
1593
|
+
nameKey
|
|
1594
|
+
uid
|
|
1595
|
+
caption
|
|
1596
|
+
extendsKey
|
|
1597
|
+
category
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
`;
|
|
1601
|
+
try {
|
|
1602
|
+
const variables = {};
|
|
1603
|
+
if (typeof params?.limit === 'number')
|
|
1604
|
+
variables.limit = params.limit;
|
|
1605
|
+
if (typeof params?.offset === 'number')
|
|
1606
|
+
variables.offset = params.offset;
|
|
1607
|
+
if (params?.orderBy)
|
|
1608
|
+
variables.orderBy = params.orderBy;
|
|
1609
|
+
if (params?.orderDirection)
|
|
1610
|
+
variables.orderDirection = params.orderDirection;
|
|
1611
|
+
if (params?.key)
|
|
1612
|
+
variables.key = params.key;
|
|
1613
|
+
if (params?.nameKey)
|
|
1614
|
+
variables.nameKey = params.nameKey;
|
|
1615
|
+
if (params?.category)
|
|
1616
|
+
variables.category = params.category;
|
|
1617
|
+
if (params?.extendsKey)
|
|
1618
|
+
variables.extendsKey = params.extendsKey;
|
|
1619
|
+
const data = await this.client.request(query, variables);
|
|
1620
|
+
return Array.isArray(data?.oasfDomains) ? data.oasfDomains : [];
|
|
1621
|
+
}
|
|
1622
|
+
catch (error) {
|
|
1623
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1624
|
+
if (message.includes('Cannot query field "oasfDomains"')) {
|
|
1625
|
+
throw new Error('Discovery KB schema missing Query.oasfDomains');
|
|
1626
|
+
}
|
|
1627
|
+
if (/Cannot return null for non-nullable field\s+Query\.oasfDomains\b/i.test(message)) {
|
|
1628
|
+
throw new Error('Discovery KB resolver bug: Query.oasfDomains returned null');
|
|
1629
|
+
}
|
|
1630
|
+
throw error;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Fetch intent types from the discovery GraphQL endpoint (best-effort).
|
|
1635
|
+
* Returns [] if the backend does not expose `intentTypes`.
|
|
1636
|
+
*/
|
|
1637
|
+
async intentTypes(params) {
|
|
1638
|
+
const query = `
|
|
1639
|
+
query IntentTypes($key: String, $label: String, $limit: Int, $offset: Int) {
|
|
1640
|
+
intentTypes(key: $key, label: $label, limit: $limit, offset: $offset) {
|
|
1641
|
+
key
|
|
1642
|
+
label
|
|
1643
|
+
description
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
`;
|
|
1647
|
+
try {
|
|
1648
|
+
const variables = {
|
|
1649
|
+
limit: typeof params?.limit === 'number' ? params.limit : 10000,
|
|
1650
|
+
offset: typeof params?.offset === 'number' ? params.offset : 0,
|
|
1651
|
+
};
|
|
1652
|
+
if (params?.key)
|
|
1653
|
+
variables.key = params.key;
|
|
1654
|
+
if (params?.label)
|
|
1655
|
+
variables.label = params.label;
|
|
1656
|
+
const data = await this.client.request(query, variables);
|
|
1657
|
+
return Array.isArray(data?.intentTypes) ? data.intentTypes : [];
|
|
1658
|
+
}
|
|
1659
|
+
catch (error) {
|
|
1660
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1661
|
+
if (message.includes('Cannot query field "intentTypes"')) {
|
|
1662
|
+
throw new Error('Discovery KB schema missing Query.intentTypes');
|
|
1663
|
+
}
|
|
1664
|
+
if (/Cannot return null for non-nullable field\s+Query\.intentTypes\b/i.test(message)) {
|
|
1665
|
+
throw new Error('Discovery KB resolver bug: Query.intentTypes returned null');
|
|
1666
|
+
}
|
|
1667
|
+
throw error;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Fetch task types from the discovery GraphQL endpoint (best-effort).
|
|
1672
|
+
* Returns [] if the backend does not expose `taskTypes`.
|
|
1673
|
+
*/
|
|
1674
|
+
async taskTypes(params) {
|
|
1675
|
+
const query = `
|
|
1676
|
+
query TaskTypes($key: String, $label: String, $limit: Int, $offset: Int) {
|
|
1677
|
+
taskTypes(key: $key, label: $label, limit: $limit, offset: $offset) {
|
|
1678
|
+
key
|
|
1679
|
+
label
|
|
1680
|
+
description
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
`;
|
|
1684
|
+
try {
|
|
1685
|
+
const variables = {
|
|
1686
|
+
limit: typeof params?.limit === 'number' ? params.limit : 10000,
|
|
1687
|
+
offset: typeof params?.offset === 'number' ? params.offset : 0,
|
|
1688
|
+
};
|
|
1689
|
+
if (params?.key)
|
|
1690
|
+
variables.key = params.key;
|
|
1691
|
+
if (params?.label)
|
|
1692
|
+
variables.label = params.label;
|
|
1693
|
+
const data = await this.client.request(query, variables);
|
|
1694
|
+
return Array.isArray(data?.taskTypes) ? data.taskTypes : [];
|
|
1695
|
+
}
|
|
1696
|
+
catch (error) {
|
|
1697
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1698
|
+
if (message.includes('Cannot query field "taskTypes"')) {
|
|
1699
|
+
throw new Error('Discovery KB schema missing Query.taskTypes');
|
|
1700
|
+
}
|
|
1701
|
+
if (/Cannot return null for non-nullable field\s+Query\.taskTypes\b/i.test(message)) {
|
|
1702
|
+
throw new Error('Discovery KB resolver bug: Query.taskTypes returned null');
|
|
1703
|
+
}
|
|
1704
|
+
throw error;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Fetch intent-task mappings from the discovery GraphQL endpoint (best-effort).
|
|
1709
|
+
* Returns [] if the backend does not expose `intentTaskMappings`.
|
|
1710
|
+
*/
|
|
1711
|
+
async intentTaskMappings(params) {
|
|
1712
|
+
const query = `
|
|
1713
|
+
query IntentTaskMappings($intentKey: String, $taskKey: String, $limit: Int, $offset: Int) {
|
|
1714
|
+
intentTaskMappings(intentKey: $intentKey, taskKey: $taskKey, limit: $limit, offset: $offset) {
|
|
1715
|
+
intent { key label description }
|
|
1716
|
+
task { key label description }
|
|
1717
|
+
requiredSkills
|
|
1718
|
+
optionalSkills
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
`;
|
|
1722
|
+
try {
|
|
1723
|
+
const variables = {
|
|
1724
|
+
limit: typeof params?.limit === 'number' ? params.limit : 10000,
|
|
1725
|
+
offset: typeof params?.offset === 'number' ? params.offset : 0,
|
|
1726
|
+
};
|
|
1727
|
+
if (params?.intentKey)
|
|
1728
|
+
variables.intentKey = params.intentKey;
|
|
1729
|
+
if (params?.taskKey)
|
|
1730
|
+
variables.taskKey = params.taskKey;
|
|
1731
|
+
const data = await this.client.request(query, variables);
|
|
1732
|
+
return Array.isArray(data?.intentTaskMappings) ? data.intentTaskMappings : [];
|
|
1733
|
+
}
|
|
1734
|
+
catch (error) {
|
|
1735
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1736
|
+
if (message.includes('Cannot query field "intentTaskMappings"')) {
|
|
1737
|
+
throw new Error('Discovery KB schema missing Query.intentTaskMappings');
|
|
1738
|
+
}
|
|
1739
|
+
if (/Cannot return null for non-nullable field\s+Query\.intentTaskMappings\b/i.test(message)) {
|
|
1740
|
+
throw new Error('Discovery KB resolver bug: Query.intentTaskMappings returned null');
|
|
1741
|
+
}
|
|
1742
|
+
throw error;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
async searchAgentsAdvanced(options) {
|
|
1746
|
+
console.log('>>>>>>>>>>>>>>>>>> searchAgentsAdvanced', options);
|
|
1747
|
+
const strategy = await this.detectSearchStrategy();
|
|
1748
|
+
const { query, params, limit, offset } = options;
|
|
1749
|
+
const trimmedQuery = typeof query === 'string' ? query.trim() : '';
|
|
1750
|
+
const hasQuery = trimmedQuery.length > 0;
|
|
1751
|
+
const hasParams = params && Object.keys(params).length > 0;
|
|
1752
|
+
if (!hasQuery && !hasParams) {
|
|
1753
|
+
return null;
|
|
1754
|
+
}
|
|
1755
|
+
// If no detected strategy (introspection disabled), attempt a direct list-form searchAgents call.
|
|
1756
|
+
// Only use this fallback if we have a query string, since the GraphQL query requires a non-null query parameter.
|
|
1757
|
+
// If we only have params but no query, return null to trigger local filtering fallback.
|
|
1758
|
+
console.log('>>>>>>>>>>>>>>>>>> 012 strategy', strategy);
|
|
1759
|
+
if (!strategy) {
|
|
1760
|
+
console.log('>>>>>>>>>>>>>>>>>> 012 hasQuery', hasQuery);
|
|
1761
|
+
if (hasQuery) {
|
|
1762
|
+
try {
|
|
1763
|
+
console.log('>>>>>>>>>>>>>>>>>> 012 trimmedQuery', trimmedQuery);
|
|
1764
|
+
console.log('>>>>>>>>>>>>>>>>>> 012 limit', limit);
|
|
1765
|
+
console.log('>>>>>>>>>>>>>>>>>> 012 offset', offset);
|
|
1766
|
+
console.log('>>>>>>>>>>>>>>>>>> 012 options.orderBy', options.orderBy);
|
|
1767
|
+
console.log('>>>>>>>>>>>>>>>>>> 012 options.orderDirection', options.orderDirection);
|
|
1768
|
+
const queryText = `
|
|
1769
|
+
query SearchAgentsFallback($query: String!, $limit: Int, $offset: Int, $orderBy: String, $orderDirection: String) {
|
|
1770
|
+
searchAgents(query: $query, limit: $limit, offset: $offset, orderBy: $orderBy, orderDirection: $orderDirection) {
|
|
1771
|
+
chainId
|
|
1772
|
+
agentId
|
|
1773
|
+
agentName
|
|
1774
|
+
agentAccount
|
|
1775
|
+
agentIdentityOwnerAccount
|
|
1776
|
+
eoaAgentIdentityOwnerAccount
|
|
1777
|
+
eoaAgentAccount
|
|
1778
|
+
agentCategory
|
|
1779
|
+
didIdentity
|
|
1780
|
+
didAccount
|
|
1781
|
+
didName
|
|
1782
|
+
agentUri
|
|
1783
|
+
createdAtBlock
|
|
1784
|
+
createdAtTime
|
|
1785
|
+
updatedAtTime
|
|
1786
|
+
type
|
|
1787
|
+
description
|
|
1788
|
+
image
|
|
1789
|
+
a2aEndpoint
|
|
1790
|
+
did
|
|
1791
|
+
mcp
|
|
1792
|
+
x402support
|
|
1793
|
+
active
|
|
1794
|
+
supportedTrust
|
|
1795
|
+
rawJson
|
|
1796
|
+
agentCardJson
|
|
1797
|
+
agentCardReadAt
|
|
1798
|
+
feedbackCount
|
|
1799
|
+
feedbackAverageScore
|
|
1800
|
+
validationPendingCount
|
|
1801
|
+
validationCompletedCount
|
|
1802
|
+
validationRequestedCount
|
|
1803
|
+
initiatedAssociationCount
|
|
1804
|
+
approvedAssociationCount
|
|
1805
|
+
atiOverallScore
|
|
1806
|
+
atiOverallConfidence
|
|
1807
|
+
atiVersion
|
|
1808
|
+
atiComputedAt
|
|
1809
|
+
atiBundleJson
|
|
1810
|
+
trustLedgerScore
|
|
1811
|
+
trustLedgerBadgeCount
|
|
1812
|
+
trustLedgerOverallRank
|
|
1813
|
+
trustLedgerCapabilityRank
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
`;
|
|
1817
|
+
const variables = {
|
|
1818
|
+
query: trimmedQuery,
|
|
1819
|
+
limit: typeof limit === 'number' ? limit : undefined,
|
|
1820
|
+
offset: typeof offset === 'number' ? offset : undefined,
|
|
1821
|
+
orderBy: options.orderBy,
|
|
1822
|
+
orderDirection: options.orderDirection,
|
|
1823
|
+
};
|
|
1824
|
+
const data = await this.client.request(queryText, variables);
|
|
1825
|
+
const list = data?.searchAgents;
|
|
1826
|
+
console.log('>>>>>>>>>>>>>>>>>> 012 list.length', list?.length);
|
|
1827
|
+
if (list && list.length > 0) {
|
|
1828
|
+
console.log('>>>>>>>>>>>>>>>>>> 012 First raw agent sample:', JSON.stringify(list[0], null, 2));
|
|
1829
|
+
}
|
|
1830
|
+
if (Array.isArray(list)) {
|
|
1831
|
+
const normalizedList = list
|
|
1832
|
+
.filter(Boolean)
|
|
1833
|
+
.map((item) => {
|
|
1834
|
+
const rawAgent = item;
|
|
1835
|
+
const normalized = this.normalizeAgent(rawAgent);
|
|
1836
|
+
console.log('[AIAgentDiscoveryClient.searchAgentsAdvanced] Normalized agent (fallback):', {
|
|
1837
|
+
agentId: normalized.agentId,
|
|
1838
|
+
rawAgentName: rawAgent.agentName,
|
|
1839
|
+
normalizedAgentName: normalized.agentName,
|
|
1840
|
+
agentNameType: typeof normalized.agentName,
|
|
1841
|
+
hasRawJson: !!normalized.rawJson,
|
|
1842
|
+
});
|
|
1843
|
+
return normalized;
|
|
1844
|
+
});
|
|
1845
|
+
console.log('[AIAgentDiscoveryClient.searchAgentsAdvanced] Returning normalized agents (fallback):', {
|
|
1846
|
+
count: normalizedList.length,
|
|
1847
|
+
agentNames: normalizedList.map(a => ({
|
|
1848
|
+
agentId: a.agentId,
|
|
1849
|
+
agentName: a.agentName,
|
|
1850
|
+
agentNameType: typeof a.agentName,
|
|
1851
|
+
})),
|
|
1852
|
+
});
|
|
1853
|
+
// Ensure fallback respects the requested ordering, even if the
|
|
1854
|
+
// underlying searchAgents resolver uses its own default order.
|
|
1855
|
+
const orderBy = typeof options.orderBy === 'string' ? options.orderBy.trim() : undefined;
|
|
1856
|
+
const orderDirectionRaw = typeof options.orderDirection === 'string'
|
|
1857
|
+
? options.orderDirection.toUpperCase()
|
|
1858
|
+
: 'DESC';
|
|
1859
|
+
const orderDirection = orderDirectionRaw === 'DESC' ? 'DESC' : 'ASC';
|
|
1860
|
+
if (orderBy === 'agentName') {
|
|
1861
|
+
normalizedList.sort((a, b) => {
|
|
1862
|
+
const aName = (a.agentName ?? '').toLowerCase();
|
|
1863
|
+
const bName = (b.agentName ?? '').toLowerCase();
|
|
1864
|
+
return orderDirection === 'ASC'
|
|
1865
|
+
? aName.localeCompare(bName)
|
|
1866
|
+
: bName.localeCompare(aName);
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
else if (orderBy === 'agentId') {
|
|
1870
|
+
normalizedList.sort((a, b) => {
|
|
1871
|
+
const idA = typeof a.agentId === 'number'
|
|
1872
|
+
? a.agentId
|
|
1873
|
+
: Number(a.agentId ?? 0) || 0;
|
|
1874
|
+
const idB = typeof b.agentId === 'number'
|
|
1875
|
+
? b.agentId
|
|
1876
|
+
: Number(b.agentId ?? 0) || 0;
|
|
1877
|
+
return orderDirection === 'ASC' ? idA - idB : idB - idA;
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
else if (orderBy === 'createdAtTime') {
|
|
1881
|
+
normalizedList.sort((a, b) => {
|
|
1882
|
+
const tA = typeof a.createdAtTime === 'number'
|
|
1883
|
+
? a.createdAtTime
|
|
1884
|
+
: Number(a.createdAtTime ?? 0) || 0;
|
|
1885
|
+
const tB = typeof b.createdAtTime === 'number'
|
|
1886
|
+
? b.createdAtTime
|
|
1887
|
+
: Number(b.createdAtTime ?? 0) || 0;
|
|
1888
|
+
return orderDirection === 'ASC' ? tA - tB : tB - tA;
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
else if (orderBy === 'createdAtBlock') {
|
|
1892
|
+
normalizedList.sort((a, b) => {
|
|
1893
|
+
const bA = typeof a.createdAtBlock === 'number'
|
|
1894
|
+
? a.createdAtBlock
|
|
1895
|
+
: Number(a.createdAtBlock ?? 0) || 0;
|
|
1896
|
+
const bB = typeof b.createdAtBlock === 'number'
|
|
1897
|
+
? b.createdAtBlock
|
|
1898
|
+
: Number(b.createdAtBlock ?? 0) || 0;
|
|
1899
|
+
return orderDirection === 'ASC' ? bA - bB : bB - bA;
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
console.log('>>>>>>>>>>>>>>>>>> 345 AdvancedSearch', normalizedList);
|
|
1903
|
+
return { agents: normalizedList, total: undefined };
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
catch (error) {
|
|
1907
|
+
console.warn('[AIAgentDiscoveryClient] Fallback searchAgents call failed:', error);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
// If no strategy and no query (only params), return null to trigger local filtering fallback
|
|
1911
|
+
return null;
|
|
1912
|
+
}
|
|
1913
|
+
const variables = {};
|
|
1914
|
+
const variableDefinitions = [];
|
|
1915
|
+
const argumentAssignments = [];
|
|
1916
|
+
const agentSelection = `
|
|
1917
|
+
chainId
|
|
1918
|
+
agentId
|
|
1919
|
+
agentName
|
|
1920
|
+
agentAccount
|
|
1921
|
+
agentIdentityOwnerAccount
|
|
1922
|
+
eoaAgentIdentityOwnerAccount
|
|
1923
|
+
eoaAgentAccount
|
|
1924
|
+
agentCategory
|
|
1925
|
+
didIdentity
|
|
1926
|
+
didAccount
|
|
1927
|
+
didName
|
|
1928
|
+
agentUri
|
|
1929
|
+
createdAtBlock
|
|
1930
|
+
createdAtTime
|
|
1931
|
+
updatedAtTime
|
|
1932
|
+
type
|
|
1933
|
+
description
|
|
1934
|
+
image
|
|
1935
|
+
a2aEndpoint
|
|
1936
|
+
did
|
|
1937
|
+
mcp
|
|
1938
|
+
x402support
|
|
1939
|
+
active
|
|
1940
|
+
supportedTrust
|
|
1941
|
+
rawJson
|
|
1942
|
+
feedbackCount
|
|
1943
|
+
feedbackAverageScore
|
|
1944
|
+
validationPendingCount
|
|
1945
|
+
validationCompletedCount
|
|
1946
|
+
validationRequestedCount
|
|
1947
|
+
`;
|
|
1948
|
+
const addStringArg = (arg, value) => {
|
|
1949
|
+
if (!arg)
|
|
1950
|
+
return !value;
|
|
1951
|
+
if (!value) {
|
|
1952
|
+
return arg.isNonNull ? false : true;
|
|
1953
|
+
}
|
|
1954
|
+
const typeName = arg.typeName ?? 'String';
|
|
1955
|
+
variableDefinitions.push(`$${arg.name}: ${typeName}${arg.isNonNull ? '!' : ''}`);
|
|
1956
|
+
argumentAssignments.push(`${arg.name}: $${arg.name}`);
|
|
1957
|
+
variables[arg.name] = value;
|
|
1958
|
+
return true;
|
|
1959
|
+
};
|
|
1960
|
+
const addInputArg = (arg, value) => {
|
|
1961
|
+
if (!arg)
|
|
1962
|
+
return !value;
|
|
1963
|
+
if (!value || Object.keys(value).length === 0) {
|
|
1964
|
+
return arg.isNonNull ? false : true;
|
|
1965
|
+
}
|
|
1966
|
+
const typeName = arg.typeName ?? 'JSON';
|
|
1967
|
+
variableDefinitions.push(`$${arg.name}: ${typeName}${arg.isNonNull ? '!' : ''}`);
|
|
1968
|
+
argumentAssignments.push(`${arg.name}: $${arg.name}`);
|
|
1969
|
+
variables[arg.name] = value;
|
|
1970
|
+
return true;
|
|
1971
|
+
};
|
|
1972
|
+
const addIntArg = (arg, value) => {
|
|
1973
|
+
if (!arg)
|
|
1974
|
+
return;
|
|
1975
|
+
if (value === undefined || value === null) {
|
|
1976
|
+
if (arg.isNonNull) {
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
const typeName = arg.typeName ?? 'Int';
|
|
1982
|
+
variableDefinitions.push(`$${arg.name}: ${typeName}${arg.isNonNull ? '!' : ''}`);
|
|
1983
|
+
argumentAssignments.push(`${arg.name}: $${arg.name}`);
|
|
1984
|
+
variables[arg.name] = value;
|
|
1985
|
+
};
|
|
1986
|
+
if (strategy.kind === 'connection') {
|
|
1987
|
+
// Add query arg only if we have a query, or if queryArg is optional
|
|
1988
|
+
// If queryArg is required (non-null) but we don't have a query, only proceed if we have params
|
|
1989
|
+
const queryArgAdded = addStringArg(strategy.queryArg, hasQuery ? trimmedQuery : undefined);
|
|
1990
|
+
if (!queryArgAdded && strategy.queryArg?.isNonNull && !hasParams) {
|
|
1991
|
+
// Required query arg but no query and no params - can't proceed
|
|
1992
|
+
return null;
|
|
1993
|
+
}
|
|
1994
|
+
// Add filter arg if we have params
|
|
1995
|
+
const filterArgAdded = addInputArg(strategy.filterArg, hasParams ? params : undefined);
|
|
1996
|
+
if (!filterArgAdded && strategy.filterArg?.isNonNull && !hasQuery) {
|
|
1997
|
+
// Required filter arg but no params and no query - can't proceed
|
|
1998
|
+
return null;
|
|
1999
|
+
}
|
|
2000
|
+
// If neither query nor params were added, and both are optional, we need at least one
|
|
2001
|
+
if (!queryArgAdded && !filterArgAdded && (!strategy.queryArg || !strategy.filterArg)) {
|
|
2002
|
+
return null;
|
|
2003
|
+
}
|
|
2004
|
+
addIntArg(strategy.limitArg, typeof limit === 'number' ? limit : undefined);
|
|
2005
|
+
addIntArg(strategy.offsetArg, typeof offset === 'number' ? offset : undefined);
|
|
2006
|
+
addStringArg(strategy.orderByArg, options.orderBy);
|
|
2007
|
+
addStringArg(strategy.orderDirectionArg, options.orderDirection);
|
|
2008
|
+
if (argumentAssignments.length === 0) {
|
|
2009
|
+
return null;
|
|
2010
|
+
}
|
|
2011
|
+
console.log('>>>>>>>>>>>>>>>>>> AdvancedSearch', variableDefinitions, argumentAssignments);
|
|
2012
|
+
const queryText = `
|
|
2013
|
+
query AdvancedSearch(${variableDefinitions.join(', ')}) {
|
|
2014
|
+
${strategy.fieldName}(${argumentAssignments.join(', ')}) {
|
|
2015
|
+
${strategy.totalFieldName ? `${strategy.totalFieldName}` : ''}
|
|
2016
|
+
${strategy.listFieldName} {
|
|
2017
|
+
chainId
|
|
2018
|
+
agentId
|
|
2019
|
+
agentAccount
|
|
2020
|
+
agentName
|
|
2021
|
+
agentIdentityOwnerAccount
|
|
2022
|
+
eoaAgentIdentityOwnerAccount
|
|
2023
|
+
eoaAgentAccount
|
|
2024
|
+
agentCategory
|
|
2025
|
+
didIdentity
|
|
2026
|
+
didAccount
|
|
2027
|
+
didName
|
|
2028
|
+
agentUri
|
|
2029
|
+
createdAtBlock
|
|
2030
|
+
createdAtTime
|
|
2031
|
+
updatedAtTime
|
|
2032
|
+
type
|
|
2033
|
+
description
|
|
2034
|
+
image
|
|
2035
|
+
a2aEndpoint
|
|
2036
|
+
did
|
|
2037
|
+
mcp
|
|
2038
|
+
x402support
|
|
2039
|
+
active
|
|
2040
|
+
supportedTrust
|
|
2041
|
+
rawJson
|
|
2042
|
+
agentCardJson
|
|
2043
|
+
agentCardReadAt
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
`;
|
|
2048
|
+
try {
|
|
2049
|
+
const data = await this.client.request(queryText, variables);
|
|
2050
|
+
const node = data?.[strategy.fieldName];
|
|
2051
|
+
if (!node)
|
|
2052
|
+
return null;
|
|
2053
|
+
const list = node?.[strategy.listFieldName];
|
|
2054
|
+
if (!Array.isArray(list))
|
|
2055
|
+
return null;
|
|
2056
|
+
const totalValue = typeof strategy.totalFieldName === 'string' ? node?.[strategy.totalFieldName] : undefined;
|
|
2057
|
+
console.log('>>>>>>>>>>>>>>>>>> 123 AdvancedSearch', list);
|
|
2058
|
+
return {
|
|
2059
|
+
agents: list.filter(Boolean),
|
|
2060
|
+
total: typeof totalValue === 'number' ? totalValue : undefined,
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
catch (error) {
|
|
2064
|
+
console.warn('[AIAgentDiscoveryClient] Advanced connection search failed:', error);
|
|
2065
|
+
this.searchStrategy = null;
|
|
2066
|
+
return null;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
if (strategy.kind === 'list') {
|
|
2070
|
+
console.log('>>>>>>>>>>>>>>>>>> AdvancedSearchList', variableDefinitions, argumentAssignments);
|
|
2071
|
+
if (!addStringArg(strategy.queryArg, hasQuery ? trimmedQuery : undefined)) {
|
|
2072
|
+
return null;
|
|
2073
|
+
}
|
|
2074
|
+
addIntArg(strategy.limitArg, typeof limit === 'number' ? limit : undefined);
|
|
2075
|
+
addIntArg(strategy.offsetArg, typeof offset === 'number' ? offset : undefined);
|
|
2076
|
+
addStringArg(strategy.orderByArg, options.orderBy);
|
|
2077
|
+
addStringArg(strategy.orderDirectionArg, options.orderDirection);
|
|
2078
|
+
if (argumentAssignments.length === 0) {
|
|
2079
|
+
return null;
|
|
2080
|
+
}
|
|
2081
|
+
const queryText = `
|
|
2082
|
+
query AdvancedSearchList(${variableDefinitions.join(', ')}) {
|
|
2083
|
+
${strategy.fieldName}(${argumentAssignments.join(', ')}) {
|
|
2084
|
+
${agentSelection}
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
`;
|
|
2088
|
+
try {
|
|
2089
|
+
const data = await this.client.request(queryText, variables);
|
|
2090
|
+
const list = data?.[strategy.fieldName];
|
|
2091
|
+
if (!Array.isArray(list))
|
|
2092
|
+
return null;
|
|
2093
|
+
const agents = list
|
|
2094
|
+
.filter(Boolean)
|
|
2095
|
+
.map((item) => {
|
|
2096
|
+
const rawAgent = item;
|
|
2097
|
+
const normalized = this.normalizeAgent(rawAgent);
|
|
2098
|
+
console.log('[AIAgentDiscoveryClient.searchAgentsAdvanced] Normalized agent (strategy):', {
|
|
2099
|
+
agentId: normalized.agentId,
|
|
2100
|
+
rawAgentName: rawAgent.agentName,
|
|
2101
|
+
normalizedAgentName: normalized.agentName,
|
|
2102
|
+
agentNameType: typeof normalized.agentName,
|
|
2103
|
+
hasRawJson: !!normalized.rawJson,
|
|
2104
|
+
});
|
|
2105
|
+
return normalized;
|
|
2106
|
+
});
|
|
2107
|
+
console.log('[AIAgentDiscoveryClient.searchAgentsAdvanced] Returning normalized agents (strategy):', {
|
|
2108
|
+
count: agents.length,
|
|
2109
|
+
agentNames: agents.map(a => ({
|
|
2110
|
+
agentId: a.agentId,
|
|
2111
|
+
agentName: a.agentName,
|
|
2112
|
+
agentNameType: typeof a.agentName,
|
|
2113
|
+
})),
|
|
2114
|
+
});
|
|
2115
|
+
return {
|
|
2116
|
+
agents,
|
|
2117
|
+
total: undefined,
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
catch (error) {
|
|
2121
|
+
console.warn('[AIAgentDiscoveryClient] Advanced list search failed:', error);
|
|
2122
|
+
this.searchStrategy = null;
|
|
2123
|
+
return null;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
return null;
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Search agents using the strongly-typed AgentWhereInput / searchAgentsGraph API.
|
|
2130
|
+
* This is tailored to the indexer schema that exposes AgentWhereInput and
|
|
2131
|
+
* searchAgentsGraph(where:, first:, skip:, orderBy:, orderDirection:).
|
|
2132
|
+
*/
|
|
2133
|
+
async searchAgentsGraph(options) {
|
|
2134
|
+
const selection = await this.getKbAgentSelection({ includeIdentityAndAccounts: false });
|
|
2135
|
+
const query = `
|
|
2136
|
+
query KbAgents(
|
|
2137
|
+
$where: KbAgentWhereInput
|
|
2138
|
+
$first: Int
|
|
2139
|
+
$skip: Int
|
|
2140
|
+
$orderBy: KbAgentOrderBy
|
|
2141
|
+
$orderDirection: OrderDirection
|
|
2142
|
+
) {
|
|
2143
|
+
kbAgents(
|
|
2144
|
+
where: $where
|
|
2145
|
+
first: $first
|
|
2146
|
+
skip: $skip
|
|
2147
|
+
orderBy: $orderBy
|
|
2148
|
+
orderDirection: $orderDirection
|
|
2149
|
+
) {
|
|
2150
|
+
agents {
|
|
2151
|
+
${selection}
|
|
2152
|
+
}
|
|
2153
|
+
total
|
|
2154
|
+
hasMore
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
`;
|
|
2158
|
+
// Default ordering when not explicitly provided: newest agents first
|
|
2159
|
+
// by agentId DESC.
|
|
2160
|
+
const effectiveOrderDirection = (options.orderDirection ?? 'DESC').toUpperCase() === 'ASC' ? 'ASC' : 'DESC';
|
|
2161
|
+
// Map legacy orderBy to KB orderBy (schema-aware).
|
|
2162
|
+
const effectiveOrderByKb = options.orderBy === 'agentName'
|
|
2163
|
+
? ((await this.pickKbAgentOrderBy(['agentName'])) ?? 'agentName')
|
|
2164
|
+
: ((await this.pickKbAgentOrderBy(['createdAtTime', 'updatedAtTime', 'agentId8004', 'uaid', 'agentName'])) ??
|
|
2165
|
+
'agentName');
|
|
2166
|
+
const whereIn = (options.where ?? {});
|
|
2167
|
+
const kbWhere = {};
|
|
2168
|
+
// chainId: v1 can provide chainId or chainId_in.
|
|
2169
|
+
if (typeof whereIn.chainId === 'number')
|
|
2170
|
+
kbWhere.chainId = whereIn.chainId;
|
|
2171
|
+
if (!('chainId' in kbWhere) && Array.isArray(whereIn.chainId_in) && whereIn.chainId_in.length === 1) {
|
|
2172
|
+
const v = whereIn.chainId_in[0];
|
|
2173
|
+
if (typeof v === 'number')
|
|
2174
|
+
kbWhere.chainId = v;
|
|
2175
|
+
}
|
|
2176
|
+
// agentId: v1 can provide agentId or agentId_in.
|
|
2177
|
+
const agentIdCandidate = typeof whereIn.agentId === 'string' || typeof whereIn.agentId === 'number'
|
|
2178
|
+
? whereIn.agentId
|
|
2179
|
+
: Array.isArray(whereIn.agentId_in) && whereIn.agentId_in.length === 1
|
|
2180
|
+
? whereIn.agentId_in[0]
|
|
2181
|
+
: undefined;
|
|
2182
|
+
if (typeof agentIdCandidate === 'string' || typeof agentIdCandidate === 'number') {
|
|
2183
|
+
const n = Number(agentIdCandidate);
|
|
2184
|
+
if (Number.isFinite(n)) {
|
|
2185
|
+
// KB v2 prefers a string matcher instead of numeric agentId8004.
|
|
2186
|
+
kbWhere.agentIdentifierMatch = String(Math.floor(n));
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
// did: v1 can provide did/didIdentity or did_contains_nocase.
|
|
2190
|
+
const didCandidate = (typeof whereIn.didIdentity === 'string' && whereIn.didIdentity) ||
|
|
2191
|
+
(typeof whereIn.did === 'string' && whereIn.did) ||
|
|
2192
|
+
(typeof whereIn.did_contains_nocase === 'string' && whereIn.did_contains_nocase) ||
|
|
2193
|
+
undefined;
|
|
2194
|
+
if (typeof didCandidate === 'string' && didCandidate.trim().startsWith('did:')) {
|
|
2195
|
+
kbWhere.did8004 = didCandidate.trim();
|
|
2196
|
+
}
|
|
2197
|
+
// agentName: v1 commonly uses agentName_contains_nocase.
|
|
2198
|
+
const nameCandidate = (typeof whereIn.agentName_contains === 'string' && whereIn.agentName_contains) ||
|
|
2199
|
+
(typeof whereIn.agentName === 'string' && whereIn.agentName) ||
|
|
2200
|
+
(typeof whereIn.agentName_contains_nocase === 'string' && whereIn.agentName_contains_nocase) ||
|
|
2201
|
+
undefined;
|
|
2202
|
+
if (typeof nameCandidate === 'string' && nameCandidate.trim()) {
|
|
2203
|
+
kbWhere.agentName_contains = nameCandidate.trim();
|
|
2204
|
+
}
|
|
2205
|
+
// A2A: v1 uses hasA2aEndpoint or a2aEndpoint_not: null.
|
|
2206
|
+
const hasA2aEndpoint = (typeof whereIn.hasA2aEndpoint === 'boolean' && whereIn.hasA2aEndpoint) ||
|
|
2207
|
+
(whereIn.a2aEndpoint_not === null);
|
|
2208
|
+
if (hasA2aEndpoint) {
|
|
2209
|
+
kbWhere.hasA2a = true;
|
|
2210
|
+
}
|
|
2211
|
+
// Assertions: allow KB-native hasAssertions filter.
|
|
2212
|
+
if (typeof whereIn.hasAssertions === 'boolean') {
|
|
2213
|
+
kbWhere.hasAssertions = whereIn.hasAssertions;
|
|
2214
|
+
}
|
|
2215
|
+
// Aggregated assertion minimums (KB v2).
|
|
2216
|
+
// The v1 search layer expresses these as *_gte fields; map them onto KbAgentWhereInput.
|
|
2217
|
+
const minFeedback = typeof whereIn.feedbackCount_gte === 'number'
|
|
2218
|
+
? whereIn.feedbackCount_gte
|
|
2219
|
+
: undefined;
|
|
2220
|
+
const minValidations = typeof whereIn.validationCompletedCount_gte === 'number'
|
|
2221
|
+
? whereIn.validationCompletedCount_gte
|
|
2222
|
+
: undefined;
|
|
2223
|
+
const minAvgRating = typeof whereIn.feedbackAverageScore_gte === 'number'
|
|
2224
|
+
? whereIn.feedbackAverageScore_gte
|
|
2225
|
+
: undefined;
|
|
2226
|
+
const hasNumericFiltersRequested = (typeof minFeedback === 'number' && Number.isFinite(minFeedback) && minFeedback > 0) ||
|
|
2227
|
+
(typeof minValidations === 'number' && Number.isFinite(minValidations) && minValidations > 0) ||
|
|
2228
|
+
(typeof minAvgRating === 'number' && Number.isFinite(minAvgRating) && minAvgRating > 0);
|
|
2229
|
+
const kbWhereFieldNames = hasNumericFiltersRequested
|
|
2230
|
+
? new Set((await this.getTypeFields('KbAgentWhereInput') ?? [])
|
|
2231
|
+
.map((f) => f?.name)
|
|
2232
|
+
.filter((name) => typeof name === 'string' && name.length > 0))
|
|
2233
|
+
: new Set();
|
|
2234
|
+
const requireKbWhereField = (fieldName, context) => {
|
|
2235
|
+
if (!kbWhereFieldNames.has(fieldName)) {
|
|
2236
|
+
const hint = Array.from(kbWhereFieldNames)
|
|
2237
|
+
.filter((n) => /feedback|validation|score/i.test(n))
|
|
2238
|
+
.slice(0, 25);
|
|
2239
|
+
throw new Error(`[Discovery][graphql-kb] Unsupported filter (${context}). ` +
|
|
2240
|
+
`KbAgentWhereInput is missing field "${fieldName}". ` +
|
|
2241
|
+
`Available relevant fields: ${hint.join(', ') || '(none)'}`);
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
const pickKbWhereField = (candidates) => {
|
|
2245
|
+
for (const name of candidates) {
|
|
2246
|
+
if (kbWhereFieldNames.has(name))
|
|
2247
|
+
return name;
|
|
2248
|
+
}
|
|
2249
|
+
return null;
|
|
2250
|
+
};
|
|
2251
|
+
if (typeof minFeedback === 'number' && Number.isFinite(minFeedback) && minFeedback > 0) {
|
|
2252
|
+
requireKbWhereField('minReviewAssertionCount', 'minFeedbackCount');
|
|
2253
|
+
requireKbWhereField('hasReviews', 'minFeedbackCount');
|
|
2254
|
+
kbWhere.minReviewAssertionCount = Math.floor(minFeedback);
|
|
2255
|
+
kbWhere.hasReviews = true;
|
|
2256
|
+
}
|
|
2257
|
+
if (typeof minValidations === 'number' && Number.isFinite(minValidations) && minValidations > 0) {
|
|
2258
|
+
requireKbWhereField('minValidationAssertionCount', 'minValidationCompletedCount');
|
|
2259
|
+
requireKbWhereField('hasValidations', 'minValidationCompletedCount');
|
|
2260
|
+
kbWhere.minValidationAssertionCount = Math.floor(minValidations);
|
|
2261
|
+
kbWhere.hasValidations = true;
|
|
2262
|
+
}
|
|
2263
|
+
if (typeof minAvgRating === 'number' && Number.isFinite(minAvgRating) && minAvgRating > 0) {
|
|
2264
|
+
const chosen = pickKbWhereField([
|
|
2265
|
+
'minFeedbackAverageScore8004',
|
|
2266
|
+
'minFeedbackAverageScore',
|
|
2267
|
+
'feedbackAverageScore_gte',
|
|
2268
|
+
]);
|
|
2269
|
+
if (!chosen) {
|
|
2270
|
+
const hint = Array.from(kbWhereFieldNames)
|
|
2271
|
+
.filter((n) => /review|feedback|score|average/i.test(n))
|
|
2272
|
+
.slice(0, 25);
|
|
2273
|
+
throw new Error(`[Discovery][graphql-kb] Unsupported filter (minFeedbackAverageScore). ` +
|
|
2274
|
+
`KbAgentWhereInput does not expose a review average score filter. ` +
|
|
2275
|
+
`Available relevant fields: ${hint.join(', ') || '(none)'}`);
|
|
2276
|
+
}
|
|
2277
|
+
kbWhere[chosen] = minAvgRating;
|
|
2278
|
+
if (kbWhereFieldNames.has('hasReviews')) {
|
|
2279
|
+
kbWhere.hasReviews = true;
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
// Smart agent: v1 may provide isSmartAgent.
|
|
2283
|
+
if (typeof whereIn.isSmartAgent === 'boolean') {
|
|
2284
|
+
const kbWhereInputFields = new Set((await this.getTypeFields('KbAgentWhereInput') ?? [])
|
|
2285
|
+
.map((f) => f?.name)
|
|
2286
|
+
.filter((name) => typeof name === 'string' && name.length > 0));
|
|
2287
|
+
if (kbWhereInputFields.has('isSmartAgent')) {
|
|
2288
|
+
kbWhere.isSmartAgent = whereIn.isSmartAgent;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
const variables = {
|
|
2292
|
+
where: Object.keys(kbWhere).length ? kbWhere : undefined,
|
|
2293
|
+
first: typeof options.first === 'number' ? options.first : undefined,
|
|
2294
|
+
skip: typeof options.skip === 'number' ? options.skip : undefined,
|
|
2295
|
+
orderBy: effectiveOrderByKb,
|
|
2296
|
+
orderDirection: effectiveOrderDirection,
|
|
2297
|
+
};
|
|
2298
|
+
const data = await this.client.request(query, variables);
|
|
2299
|
+
const result = data.kbAgents ?? { agents: [], total: 0, hasMore: false };
|
|
2300
|
+
const agents = (result.agents ?? []).map((agent) => this.mapKbAgentToAgentData(agent));
|
|
2301
|
+
return {
|
|
2302
|
+
agents,
|
|
2303
|
+
total: typeof result.total === 'number' ? result.total : agents.length,
|
|
2304
|
+
hasMore: Boolean(result.hasMore),
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
async erc8122Registries(params) {
|
|
2308
|
+
const chainId = Math.floor(params.chainId);
|
|
2309
|
+
if (!Number.isFinite(chainId)) {
|
|
2310
|
+
throw new Error('erc8122Registries requires chainId');
|
|
2311
|
+
}
|
|
2312
|
+
const first = typeof params.first === 'number' && Number.isFinite(params.first) && params.first > 0
|
|
2313
|
+
? Math.floor(params.first)
|
|
2314
|
+
: 50;
|
|
2315
|
+
const skip = typeof params.skip === 'number' && Number.isFinite(params.skip) && params.skip >= 0
|
|
2316
|
+
? Math.floor(params.skip)
|
|
2317
|
+
: 0;
|
|
2318
|
+
const query = `
|
|
2319
|
+
query Registries8122($chainId: Int!, $first: Int, $skip: Int) {
|
|
2320
|
+
kbErc8122Registries(chainId: $chainId, first: $first, skip: $skip) {
|
|
2321
|
+
iri
|
|
2322
|
+
chainId
|
|
2323
|
+
registryAddress
|
|
2324
|
+
registrarAddress
|
|
2325
|
+
registryName
|
|
2326
|
+
registryImplementationAddress
|
|
2327
|
+
registrarImplementationAddress
|
|
2328
|
+
registeredAgentCount
|
|
2329
|
+
lastAgentUpdatedAtTime
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
`;
|
|
2333
|
+
const data = await this.gqlRequest(query, { chainId, first, skip });
|
|
2334
|
+
const rows = Array.isArray(data?.kbErc8122Registries) ? data.kbErc8122Registries : [];
|
|
2335
|
+
const out = [];
|
|
2336
|
+
for (const r of rows) {
|
|
2337
|
+
if (!r || typeof r !== 'object')
|
|
2338
|
+
continue;
|
|
2339
|
+
const registryAddressRaw = r.registryAddress;
|
|
2340
|
+
const registryAddress = typeof registryAddressRaw === 'string' ? registryAddressRaw : '';
|
|
2341
|
+
if (!registryAddress)
|
|
2342
|
+
continue;
|
|
2343
|
+
out.push({
|
|
2344
|
+
iri: typeof r.iri === 'string' ? r.iri : null,
|
|
2345
|
+
chainId,
|
|
2346
|
+
registryAddress,
|
|
2347
|
+
registrarAddress: typeof r.registrarAddress === 'string' ? r.registrarAddress : null,
|
|
2348
|
+
registryName: typeof r.registryName === 'string' ? r.registryName : null,
|
|
2349
|
+
registryImplementationAddress: typeof r.registryImplementationAddress === 'string'
|
|
2350
|
+
? r.registryImplementationAddress
|
|
2351
|
+
: null,
|
|
2352
|
+
registrarImplementationAddress: typeof r.registrarImplementationAddress === 'string'
|
|
2353
|
+
? r.registrarImplementationAddress
|
|
2354
|
+
: null,
|
|
2355
|
+
registeredAgentCount: typeof r.registeredAgentCount === 'number' ? r.registeredAgentCount : null,
|
|
2356
|
+
lastAgentUpdatedAtTime: typeof r.lastAgentUpdatedAtTime === 'number' ? r.lastAgentUpdatedAtTime : null,
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
return out;
|
|
2360
|
+
}
|
|
2361
|
+
async detectSearchStrategy() {
|
|
2362
|
+
if (this.searchStrategy !== undefined) {
|
|
2363
|
+
return this.searchStrategy;
|
|
2364
|
+
}
|
|
2365
|
+
if (this.searchStrategyPromise) {
|
|
2366
|
+
return this.searchStrategyPromise;
|
|
2367
|
+
}
|
|
2368
|
+
this.searchStrategyPromise = (async () => {
|
|
2369
|
+
try {
|
|
2370
|
+
const data = await this.client.request(INTROSPECTION_QUERY);
|
|
2371
|
+
const fields = data.__schema?.queryType?.fields ?? [];
|
|
2372
|
+
const candidateNames = ['searchAgentsAdvanced', 'searchAgents'];
|
|
2373
|
+
for (const candidate of candidateNames) {
|
|
2374
|
+
const field = fields.find((f) => f.name === candidate);
|
|
2375
|
+
if (!field)
|
|
2376
|
+
continue;
|
|
2377
|
+
const strategy = await this.buildStrategyFromField(field);
|
|
2378
|
+
if (strategy) {
|
|
2379
|
+
this.searchStrategy = strategy;
|
|
2380
|
+
return strategy;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
catch (error) {
|
|
2385
|
+
console.warn('[AIAgentDiscoveryClient] Failed to introspect search capabilities:', error);
|
|
2386
|
+
}
|
|
2387
|
+
finally {
|
|
2388
|
+
this.searchStrategyPromise = undefined;
|
|
2389
|
+
}
|
|
2390
|
+
this.searchStrategy = null;
|
|
2391
|
+
return null;
|
|
2392
|
+
})();
|
|
2393
|
+
return this.searchStrategyPromise;
|
|
2394
|
+
}
|
|
2395
|
+
async buildStrategyFromField(field) {
|
|
2396
|
+
const baseReturn = unwrapType(field.type);
|
|
2397
|
+
if (!baseReturn)
|
|
2398
|
+
return null;
|
|
2399
|
+
const limitArg = field.args.find((arg) => arg.name === 'limit') ??
|
|
2400
|
+
field.args.find((arg) => arg.name === 'first');
|
|
2401
|
+
const offsetArg = field.args.find((arg) => arg.name === 'offset') ??
|
|
2402
|
+
field.args.find((arg) => arg.name === 'skip');
|
|
2403
|
+
const queryArg = field.args.find((arg) => arg.name === 'query') ??
|
|
2404
|
+
field.args.find((arg) => arg.name === 'term') ??
|
|
2405
|
+
field.args.find((arg) => arg.name === 'search');
|
|
2406
|
+
const filterArg = field.args.find((arg) => arg.name === 'params') ??
|
|
2407
|
+
field.args.find((arg) => arg.name === 'filters');
|
|
2408
|
+
const orderByArg = field.args.find((arg) => arg.name === 'orderBy');
|
|
2409
|
+
const orderDirectionArg = field.args.find((arg) => arg.name === 'orderDirection');
|
|
2410
|
+
if (baseReturn.kind === 'OBJECT' && baseReturn.name) {
|
|
2411
|
+
const connectionFields = await this.getTypeFields(baseReturn.name);
|
|
2412
|
+
if (!connectionFields) {
|
|
2413
|
+
return null;
|
|
2414
|
+
}
|
|
2415
|
+
const listField = connectionFields.find((f) => isListOf(f.type, 'Agent'));
|
|
2416
|
+
if (!listField) {
|
|
2417
|
+
return null;
|
|
2418
|
+
}
|
|
2419
|
+
const totalField = connectionFields.find((f) => f.name === 'total') ??
|
|
2420
|
+
connectionFields.find((f) => f.name === 'totalCount') ??
|
|
2421
|
+
connectionFields.find((f) => f.name === 'count');
|
|
2422
|
+
return {
|
|
2423
|
+
kind: 'connection',
|
|
2424
|
+
fieldName: field.name,
|
|
2425
|
+
listFieldName: listField.name,
|
|
2426
|
+
totalFieldName: totalField?.name,
|
|
2427
|
+
queryArg: queryArg
|
|
2428
|
+
? {
|
|
2429
|
+
name: queryArg.name,
|
|
2430
|
+
typeName: unwrapToTypeName(queryArg.type),
|
|
2431
|
+
isNonNull: isNonNull(queryArg.type),
|
|
2432
|
+
}
|
|
2433
|
+
: undefined,
|
|
2434
|
+
filterArg: filterArg
|
|
2435
|
+
? {
|
|
2436
|
+
name: filterArg.name,
|
|
2437
|
+
typeName: unwrapToTypeName(filterArg.type),
|
|
2438
|
+
isNonNull: isNonNull(filterArg.type),
|
|
2439
|
+
}
|
|
2440
|
+
: undefined,
|
|
2441
|
+
limitArg: limitArg
|
|
2442
|
+
? {
|
|
2443
|
+
name: limitArg.name,
|
|
2444
|
+
typeName: unwrapToTypeName(limitArg.type),
|
|
2445
|
+
isNonNull: isNonNull(limitArg.type),
|
|
2446
|
+
}
|
|
2447
|
+
: undefined,
|
|
2448
|
+
offsetArg: offsetArg
|
|
2449
|
+
? {
|
|
2450
|
+
name: offsetArg.name,
|
|
2451
|
+
typeName: unwrapToTypeName(offsetArg.type),
|
|
2452
|
+
isNonNull: isNonNull(offsetArg.type),
|
|
2453
|
+
}
|
|
2454
|
+
: undefined,
|
|
2455
|
+
orderByArg: orderByArg
|
|
2456
|
+
? {
|
|
2457
|
+
name: orderByArg.name,
|
|
2458
|
+
typeName: unwrapToTypeName(orderByArg.type),
|
|
2459
|
+
isNonNull: isNonNull(orderByArg.type),
|
|
2460
|
+
}
|
|
2461
|
+
: undefined,
|
|
2462
|
+
orderDirectionArg: orderDirectionArg
|
|
2463
|
+
? {
|
|
2464
|
+
name: orderDirectionArg.name,
|
|
2465
|
+
typeName: unwrapToTypeName(orderDirectionArg.type),
|
|
2466
|
+
isNonNull: isNonNull(orderDirectionArg.type),
|
|
2467
|
+
}
|
|
2468
|
+
: undefined,
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
if (isListOf(field.type, 'Agent')) {
|
|
2472
|
+
return {
|
|
2473
|
+
kind: 'list',
|
|
2474
|
+
fieldName: field.name,
|
|
2475
|
+
queryArg: queryArg
|
|
2476
|
+
? {
|
|
2477
|
+
name: queryArg.name,
|
|
2478
|
+
typeName: unwrapToTypeName(queryArg.type),
|
|
2479
|
+
isNonNull: isNonNull(queryArg.type),
|
|
2480
|
+
}
|
|
2481
|
+
: undefined,
|
|
2482
|
+
limitArg: limitArg
|
|
2483
|
+
? {
|
|
2484
|
+
name: limitArg.name,
|
|
2485
|
+
typeName: unwrapToTypeName(limitArg.type),
|
|
2486
|
+
isNonNull: isNonNull(limitArg.type),
|
|
2487
|
+
}
|
|
2488
|
+
: undefined,
|
|
2489
|
+
offsetArg: offsetArg
|
|
2490
|
+
? {
|
|
2491
|
+
name: offsetArg.name,
|
|
2492
|
+
typeName: unwrapToTypeName(offsetArg.type),
|
|
2493
|
+
isNonNull: isNonNull(offsetArg.type),
|
|
2494
|
+
}
|
|
2495
|
+
: undefined,
|
|
2496
|
+
orderByArg: orderByArg
|
|
2497
|
+
? {
|
|
2498
|
+
name: orderByArg.name,
|
|
2499
|
+
typeName: unwrapToTypeName(orderByArg.type),
|
|
2500
|
+
isNonNull: isNonNull(orderByArg.type),
|
|
2501
|
+
}
|
|
2502
|
+
: undefined,
|
|
2503
|
+
orderDirectionArg: orderDirectionArg
|
|
2504
|
+
? {
|
|
2505
|
+
name: orderDirectionArg.name,
|
|
2506
|
+
typeName: unwrapToTypeName(orderDirectionArg.type),
|
|
2507
|
+
isNonNull: isNonNull(orderDirectionArg.type),
|
|
2508
|
+
}
|
|
2509
|
+
: undefined,
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
return null;
|
|
2513
|
+
}
|
|
2514
|
+
async getTypeFields(typeName) {
|
|
2515
|
+
if (this.typeFieldsCache.has(typeName)) {
|
|
2516
|
+
return this.typeFieldsCache.get(typeName) ?? null;
|
|
2517
|
+
}
|
|
2518
|
+
try {
|
|
2519
|
+
const data = await this.client.request(TYPE_FIELDS_QUERY, { name: typeName });
|
|
2520
|
+
const kind = data.__type?.kind ?? null;
|
|
2521
|
+
const fields = kind === 'INPUT_OBJECT'
|
|
2522
|
+
? (data.__type?.inputFields ?? null)
|
|
2523
|
+
: (data.__type?.fields ?? null);
|
|
2524
|
+
this.typeFieldsCache.set(typeName, fields ?? null);
|
|
2525
|
+
return fields ?? null;
|
|
2526
|
+
}
|
|
2527
|
+
catch (error) {
|
|
2528
|
+
console.warn(`[AIAgentDiscoveryClient] Failed to introspect type fields for ${typeName}:`, error);
|
|
2529
|
+
this.typeFieldsCache.set(typeName, null);
|
|
2530
|
+
return null;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
/**
|
|
2534
|
+
* Some indexers expose `metadata { key valueText }`, others expose `metadata { key value }`.
|
|
2535
|
+
* Introspect once and cache so we can query metadata reliably.
|
|
2536
|
+
*/
|
|
2537
|
+
async getAgentMetadataValueField() {
|
|
2538
|
+
if (this.agentMetadataValueField !== undefined) {
|
|
2539
|
+
return this.agentMetadataValueField;
|
|
2540
|
+
}
|
|
2541
|
+
try {
|
|
2542
|
+
const agentFields = await this.getTypeFields('Agent');
|
|
2543
|
+
const metadataField = agentFields?.find((f) => f?.name === 'metadata');
|
|
2544
|
+
const metadataType = unwrapType(metadataField?.type);
|
|
2545
|
+
const metadataTypeName = metadataType?.name ?? null;
|
|
2546
|
+
if (!metadataTypeName) {
|
|
2547
|
+
this.agentMetadataValueField = null;
|
|
2548
|
+
return null;
|
|
2549
|
+
}
|
|
2550
|
+
const metadataFields = await this.getTypeFields(metadataTypeName);
|
|
2551
|
+
const fieldNames = new Set((metadataFields ?? [])
|
|
2552
|
+
.map((f) => f?.name)
|
|
2553
|
+
.filter((name) => typeof name === 'string' && name.length > 0));
|
|
2554
|
+
if (fieldNames.has('valueText')) {
|
|
2555
|
+
this.agentMetadataValueField = 'valueText';
|
|
2556
|
+
return 'valueText';
|
|
2557
|
+
}
|
|
2558
|
+
if (fieldNames.has('value')) {
|
|
2559
|
+
this.agentMetadataValueField = 'value';
|
|
2560
|
+
return 'value';
|
|
2561
|
+
}
|
|
2562
|
+
this.agentMetadataValueField = null;
|
|
2563
|
+
return null;
|
|
2564
|
+
}
|
|
2565
|
+
catch {
|
|
2566
|
+
// If schema blocks introspection, fall back to historical `valueText`.
|
|
2567
|
+
this.agentMetadataValueField = 'valueText';
|
|
2568
|
+
return 'valueText';
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
/**
|
|
2572
|
+
* Get all token metadata from The Graph indexer for an agent
|
|
2573
|
+
* Uses agentMetadata_collection (The Graph subgraph) or agentMetadata (custom schema) query
|
|
2574
|
+
* to get all metadata key-value pairs. Tries subgraph format first, falls back to custom schema.
|
|
2575
|
+
* Handles pagination if an agent has more than 1000 metadata entries
|
|
2576
|
+
* @param chainId - Chain ID
|
|
2577
|
+
* @param agentId - Agent ID
|
|
2578
|
+
* @returns Record of all metadata key-value pairs, or null if not available
|
|
2579
|
+
*/
|
|
2580
|
+
/**
|
|
2581
|
+
* @deprecated Use getAllAgentMetadata instead. This method name is misleading.
|
|
2582
|
+
*/
|
|
2583
|
+
async getTokenMetadata(chainId, agentId) {
|
|
2584
|
+
return this.getAllAgentMetadata(chainId, agentId);
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Get all agent metadata entries from the discovery GraphQL backend.
|
|
2588
|
+
* Uses agentMetadata_collection (The Graph subgraph) or agentMetadata (custom schema) query.
|
|
2589
|
+
* Tries subgraph format first, falls back to custom schema.
|
|
2590
|
+
* Handles pagination if an agent has more than 1000 metadata entries.
|
|
2591
|
+
* @param chainId - Chain ID
|
|
2592
|
+
* @param agentId - Agent ID
|
|
2593
|
+
* @returns Record of all metadata key-value pairs, or null if not available
|
|
2594
|
+
*/
|
|
2595
|
+
async getAllAgentMetadata(chainId, agentId) {
|
|
2596
|
+
// Legacy metadata queries are removed. With KB v2, the client should use:
|
|
2597
|
+
// - registration JSON: identity*.descriptor.json (-> `rawJson`)
|
|
2598
|
+
// - info JSON: identity*.descriptor.onchainMetadataJson (-> `onchainMetadataJson`)
|
|
2599
|
+
// Or fall back to on-chain reads.
|
|
2600
|
+
void chainId;
|
|
2601
|
+
void agentId;
|
|
2602
|
+
return null;
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Fallback method: Uses agentMetadata query (custom schema format) to get all metadata key-value pairs
|
|
2606
|
+
* @param chainId - Chain ID
|
|
2607
|
+
* @param agentId - Agent ID
|
|
2608
|
+
* @returns Record of all metadata key-value pairs, or null if not available
|
|
2609
|
+
*/
|
|
2610
|
+
async getTokenMetadataCustomSchema(chainId, agentId) {
|
|
2611
|
+
// Legacy query removed.
|
|
2612
|
+
void chainId;
|
|
2613
|
+
void agentId;
|
|
2614
|
+
return null;
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Get a single agent by chainId+agentId (convenience).
|
|
2618
|
+
* Discovery is UAID-only: builds uaid and calls getAgentByUaid(uaid).
|
|
2619
|
+
*/
|
|
2620
|
+
async getAgent(chainId, agentId) {
|
|
2621
|
+
const id = typeof agentId === 'number' ? agentId : Number.parseInt(String(agentId), 10);
|
|
2622
|
+
if (!Number.isFinite(id))
|
|
2623
|
+
return null;
|
|
2624
|
+
const uaid = `uaid:did:8004:${chainId}:${id}`;
|
|
2625
|
+
return this.getAgentByUaid(uaid);
|
|
2626
|
+
}
|
|
2627
|
+
async getAgentByName(agentName) {
|
|
2628
|
+
const trimmed = agentName?.trim();
|
|
2629
|
+
if (!trimmed)
|
|
2630
|
+
return null;
|
|
2631
|
+
const selection = await this.getKbAgentSelection({ includeIdentityAndAccounts: false });
|
|
2632
|
+
const orderBy = (await this.pickKbAgentOrderBy(['createdAtTime', 'updatedAtTime', 'agentId8004', 'uaid', 'agentName'])) ??
|
|
2633
|
+
'agentName';
|
|
2634
|
+
const query = `
|
|
2635
|
+
query KbAgentsByName($where: KbAgentWhereInput, $first: Int, $orderBy: KbAgentOrderBy) {
|
|
2636
|
+
kbAgents(where: $where, first: $first, orderBy: $orderBy, orderDirection: DESC) {
|
|
2637
|
+
agents { ${selection} }
|
|
2638
|
+
total
|
|
2639
|
+
hasMore
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
`;
|
|
2643
|
+
try {
|
|
2644
|
+
const data = await this.client.request(query, {
|
|
2645
|
+
where: { agentName_contains: trimmed },
|
|
2646
|
+
first: 20,
|
|
2647
|
+
orderBy,
|
|
2648
|
+
});
|
|
2649
|
+
const list = data?.kbAgents?.agents ?? [];
|
|
2650
|
+
if (!list.length)
|
|
2651
|
+
return null;
|
|
2652
|
+
const exact = list.find((a) => String(a.agentName ?? '').toLowerCase() === trimmed.toLowerCase()) ??
|
|
2653
|
+
list[0];
|
|
2654
|
+
return exact ? this.mapKbAgentToAgentData(exact) : null;
|
|
2655
|
+
}
|
|
2656
|
+
catch (error) {
|
|
2657
|
+
console.error('[AIAgentDiscoveryClient.getAgentByName] Error fetching agent:', error);
|
|
2658
|
+
return null;
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
/**
|
|
2662
|
+
* Resolve a single agent by UAID (KB v2). UAID-only; no fallback to chainId/agentId.
|
|
2663
|
+
*/
|
|
2664
|
+
async getAgentByUaid(uaid) {
|
|
2665
|
+
const trimmed = String(uaid ?? '').trim();
|
|
2666
|
+
if (!trimmed)
|
|
2667
|
+
return null;
|
|
2668
|
+
const uaidForKb = this.normalizeUaidForKb(trimmed);
|
|
2669
|
+
const selection = await this.getKbAgentSelection({ includeIdentityAndAccounts: false });
|
|
2670
|
+
const query = `
|
|
2671
|
+
query KbAgentByUaid($uaid: String!) {
|
|
2672
|
+
kbAgentByUaid(uaid: $uaid) {
|
|
2673
|
+
${selection}
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
`;
|
|
2677
|
+
try {
|
|
2678
|
+
const t0 = Date.now();
|
|
2679
|
+
const data = await this.gqlRequest(query, { uaid: uaidForKb });
|
|
2680
|
+
const t1 = Date.now();
|
|
2681
|
+
if (process.env.NODE_ENV === 'development') {
|
|
2682
|
+
console.log('[AIAgentDiscoveryClient.getAgentByUaid] kbAgentByUaid ms:', t1 - t0);
|
|
2683
|
+
}
|
|
2684
|
+
const agent = data?.kbAgentByUaid ?? null;
|
|
2685
|
+
return agent ? this.mapKbAgentToAgentData(agent) : null;
|
|
2686
|
+
}
|
|
2687
|
+
catch (error) {
|
|
2688
|
+
console.warn('[AIAgentDiscoveryClient.getAgentByUaid] kbAgentByUaid failed:', error);
|
|
2689
|
+
return null;
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
/**
|
|
2693
|
+
* Resolve a single agent by UAID including identity/accounts (KB v2). UAID-only; no fallback.
|
|
2694
|
+
*/
|
|
2695
|
+
async getAgentByUaidFull(uaid) {
|
|
2696
|
+
const trimmed = String(uaid ?? '').trim();
|
|
2697
|
+
if (!trimmed)
|
|
2698
|
+
return null;
|
|
2699
|
+
const uaidForKb = this.normalizeUaidForKb(trimmed);
|
|
2700
|
+
const selection = await this.getKbAgentSelection({ includeIdentityAndAccounts: true });
|
|
2701
|
+
const query = `
|
|
2702
|
+
query KbAgentByUaidFull($uaid: String!) {
|
|
2703
|
+
kbAgentByUaid(uaid: $uaid) {
|
|
2704
|
+
${selection}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
`;
|
|
2708
|
+
try {
|
|
2709
|
+
const t0 = Date.now();
|
|
2710
|
+
const data = await this.gqlRequest(query, { uaid: uaidForKb });
|
|
2711
|
+
const t1 = Date.now();
|
|
2712
|
+
if (process.env.NODE_ENV === 'development') {
|
|
2713
|
+
console.log('[AIAgentDiscoveryClient.getAgentByUaidFull] kbAgentByUaid ms:', t1 - t0);
|
|
2714
|
+
}
|
|
2715
|
+
const agent = data?.kbAgentByUaid ?? null;
|
|
2716
|
+
return agent ? this.mapKbAgentToAgentData(agent) : null;
|
|
2717
|
+
}
|
|
2718
|
+
catch (error) {
|
|
2719
|
+
console.warn('[AIAgentDiscoveryClient.getAgentByUaidFull] kbAgentByUaid failed:', error);
|
|
2720
|
+
return null;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
/**
|
|
2724
|
+
* Search agents by name
|
|
2725
|
+
* @param searchTerm - Search term to match against agent names
|
|
2726
|
+
* @param limit - Maximum number of results
|
|
2727
|
+
* @returns List of matching agents
|
|
2728
|
+
*/
|
|
2729
|
+
async searchAgents(searchTerm, limit) {
|
|
2730
|
+
const query = `
|
|
2731
|
+
query SearchAgents($query: String!, $limit: Int) {
|
|
2732
|
+
searchAgents(query: $query, limit: $limit) {
|
|
2733
|
+
chainId
|
|
2734
|
+
agentId
|
|
2735
|
+
agentAccount
|
|
2736
|
+
agentName
|
|
2737
|
+
agentIdentityOwnerAccount
|
|
2738
|
+
eoaAgentIdentityOwnerAccount
|
|
2739
|
+
eoaAgentAccount
|
|
2740
|
+
agentCategory
|
|
2741
|
+
didIdentity
|
|
2742
|
+
didAccount
|
|
2743
|
+
didName
|
|
2744
|
+
agentUri
|
|
2745
|
+
createdAtBlock
|
|
2746
|
+
createdAtTime
|
|
2747
|
+
updatedAtTime
|
|
2748
|
+
type
|
|
2749
|
+
description
|
|
2750
|
+
image
|
|
2751
|
+
a2aEndpoint
|
|
2752
|
+
did
|
|
2753
|
+
mcp
|
|
2754
|
+
x402support
|
|
2755
|
+
active
|
|
2756
|
+
supportedTrust
|
|
2757
|
+
rawJson
|
|
2758
|
+
agentCardJson
|
|
2759
|
+
agentCardReadAt
|
|
2760
|
+
atiOverallScore
|
|
2761
|
+
atiOverallConfidence
|
|
2762
|
+
atiVersion
|
|
2763
|
+
atiComputedAt
|
|
2764
|
+
atiBundleJson
|
|
2765
|
+
trustLedgerScore
|
|
2766
|
+
trustLedgerBadgeCount
|
|
2767
|
+
trustLedgerOverallRank
|
|
2768
|
+
trustLedgerCapabilityRank
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
`;
|
|
2772
|
+
try {
|
|
2773
|
+
const data = await this.client.request(query, {
|
|
2774
|
+
query: searchTerm,
|
|
2775
|
+
limit: limit || 100,
|
|
2776
|
+
});
|
|
2777
|
+
const agents = data.searchAgents || [];
|
|
2778
|
+
return agents.map((agent) => this.normalizeAgent(agent));
|
|
2779
|
+
}
|
|
2780
|
+
catch (error) {
|
|
2781
|
+
console.error('[AIAgentDiscoveryClient.searchAgents] Error searching agents:', error);
|
|
2782
|
+
throw error;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
/**
|
|
2786
|
+
* Refresh/Index an agent in the indexer
|
|
2787
|
+
* Triggers the indexer to re-index the specified agent
|
|
2788
|
+
* @param agentId - Agent ID to refresh (required)
|
|
2789
|
+
* @param chainId - Optional chain ID (if not provided, indexer may use default)
|
|
2790
|
+
* @param apiKey - Optional API key override (uses config API key if not provided)
|
|
2791
|
+
* @returns Refresh result with success status and processed chains
|
|
2792
|
+
*/
|
|
2793
|
+
async refreshAgent(agentId, chainId, apiKey) {
|
|
2794
|
+
const mutation = `
|
|
2795
|
+
mutation IndexAgent($agentId: String!, $chainId: Int) {
|
|
2796
|
+
indexAgent(agentId: $agentId, chainId: $chainId) {
|
|
2797
|
+
success
|
|
2798
|
+
message
|
|
2799
|
+
processedChains
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
`;
|
|
2803
|
+
const variables = {
|
|
2804
|
+
agentId: String(agentId),
|
|
2805
|
+
};
|
|
2806
|
+
if (chainId !== undefined) {
|
|
2807
|
+
variables.chainId = chainId;
|
|
2808
|
+
}
|
|
2809
|
+
// If API key override is provided, create a temporary client with that key
|
|
2810
|
+
let clientToUse = this.client;
|
|
2811
|
+
if (apiKey) {
|
|
2812
|
+
const headers = {
|
|
2813
|
+
'Content-Type': 'application/json',
|
|
2814
|
+
...(this.config.headers || {}),
|
|
2815
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
2816
|
+
};
|
|
2817
|
+
clientToUse = new GraphQLClient(this.config.endpoint, {
|
|
2818
|
+
headers,
|
|
2819
|
+
});
|
|
2820
|
+
}
|
|
2821
|
+
try {
|
|
2822
|
+
const data = await clientToUse.request(mutation, variables);
|
|
2823
|
+
return data.indexAgent;
|
|
2824
|
+
}
|
|
2825
|
+
catch (error) {
|
|
2826
|
+
console.error('[AIAgentDiscoveryClient.refreshAgent] Error refreshing agent:', error);
|
|
2827
|
+
throw new Error(`Failed to refresh agent: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
/**
|
|
2831
|
+
* Normalize identifier to UAID form required by KB GraphQL.
|
|
2832
|
+
*
|
|
2833
|
+
* Important: kbAgentByUaid expects the *canonical UAID key* (prefix up to the first ';').
|
|
2834
|
+
* Any routing metadata after ';' is not part of the lookup key.
|
|
2835
|
+
*/
|
|
2836
|
+
normalizeUaidForKb(uaid) {
|
|
2837
|
+
const t = uaid.trim();
|
|
2838
|
+
const withPrefix = t.startsWith('uaid:') ? t : `uaid:${t}`;
|
|
2839
|
+
const idx = withPrefix.indexOf(';');
|
|
2840
|
+
return idx === -1 ? withPrefix : withPrefix.slice(0, idx);
|
|
2841
|
+
}
|
|
2842
|
+
/**
|
|
2843
|
+
* Search validation requests for an agent by UAID (GraphQL kbAgentByUaid + validationAssertions)
|
|
2844
|
+
*/
|
|
2845
|
+
async searchValidationRequestsAdvanced(options) {
|
|
2846
|
+
const { uaid, limit = 10, offset = 0 } = options;
|
|
2847
|
+
const uaidTrimmed = typeof uaid === 'string' ? uaid.trim() : '';
|
|
2848
|
+
if (!uaidTrimmed) {
|
|
2849
|
+
throw new Error('uaid is required for searchValidationRequestsAdvanced');
|
|
2850
|
+
}
|
|
2851
|
+
const uaidForKb = this.normalizeUaidForKb(uaidTrimmed);
|
|
2852
|
+
const queryText = `
|
|
2853
|
+
query KbValidationAssertionsByUaid($uaid: String!, $first: Int, $skip: Int) {
|
|
2854
|
+
kbAgentByUaid(uaid: $uaid) {
|
|
2855
|
+
validationAssertions(first: $first, skip: $skip) {
|
|
2856
|
+
total
|
|
2857
|
+
items {
|
|
2858
|
+
iri
|
|
2859
|
+
agentDid8004
|
|
2860
|
+
json
|
|
2861
|
+
record { txHash blockNumber timestamp rawJson }
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
`;
|
|
2867
|
+
const variables = {
|
|
2868
|
+
uaid: uaidForKb,
|
|
2869
|
+
first: typeof limit === 'number' ? limit : undefined,
|
|
2870
|
+
skip: typeof offset === 'number' ? offset : undefined,
|
|
2871
|
+
};
|
|
2872
|
+
const data = await this.client.request(queryText, variables);
|
|
2873
|
+
const connection = data?.kbAgentByUaid?.validationAssertions;
|
|
2874
|
+
const items = Array.isArray(connection?.items) ? connection.items : [];
|
|
2875
|
+
const parseJson = (value) => {
|
|
2876
|
+
if (typeof value !== 'string' || !value.trim())
|
|
2877
|
+
return null;
|
|
2878
|
+
try {
|
|
2879
|
+
return JSON.parse(value);
|
|
2880
|
+
}
|
|
2881
|
+
catch {
|
|
2882
|
+
return null;
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
const toNumberOrUndefined = (value) => {
|
|
2886
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
2887
|
+
return value;
|
|
2888
|
+
if (typeof value === 'string' && value.trim()) {
|
|
2889
|
+
const n = Number(value);
|
|
2890
|
+
if (Number.isFinite(n))
|
|
2891
|
+
return n;
|
|
2892
|
+
}
|
|
2893
|
+
return undefined;
|
|
2894
|
+
};
|
|
2895
|
+
const mapped = items
|
|
2896
|
+
.filter(Boolean)
|
|
2897
|
+
.map((item) => {
|
|
2898
|
+
const iri = typeof item?.iri === 'string' ? item.iri : undefined;
|
|
2899
|
+
const record = item?.record ?? null;
|
|
2900
|
+
const recordTxHash = typeof record?.txHash === 'string' ? record.txHash : undefined;
|
|
2901
|
+
const recordBlockNumber = toNumberOrUndefined(record?.blockNumber);
|
|
2902
|
+
const recordTimestamp = typeof record?.timestamp === 'number' || typeof record?.timestamp === 'string'
|
|
2903
|
+
? record.timestamp
|
|
2904
|
+
: undefined;
|
|
2905
|
+
const parsedTop = parseJson(item?.json);
|
|
2906
|
+
const parsedRecord = parseJson(record?.rawJson);
|
|
2907
|
+
const recordResponseJsonText = typeof parsedRecord?.responseJson === 'string' ? parsedRecord.responseJson : null;
|
|
2908
|
+
const parsedResponseJson = parseJson(recordResponseJsonText);
|
|
2909
|
+
const parsed = parsedTop ?? parsedResponseJson;
|
|
2910
|
+
const requestHash = typeof parsed?.requestHash === 'string' ? parsed.requestHash : undefined;
|
|
2911
|
+
const validatorAddress = typeof parsed?.validatorAddress === 'string' ? parsed.validatorAddress : undefined;
|
|
2912
|
+
const createdAt = typeof parsed?.createdAt === 'string' ? parsed.createdAt : undefined;
|
|
2913
|
+
const rawId = typeof parsedRecord?.id === 'string'
|
|
2914
|
+
? parsedRecord.id
|
|
2915
|
+
: typeof parsed?.id === 'string'
|
|
2916
|
+
? parsed.id
|
|
2917
|
+
: undefined;
|
|
2918
|
+
const agentDid = typeof item?.agentDid8004 === 'string' ? item.agentDid8004 : undefined;
|
|
2919
|
+
const didMatch = agentDid ? /^did:8004:(\d+):(\d+)$/.exec(agentDid) : null;
|
|
2920
|
+
const parsedAgentId = didMatch?.[2] != null ? parseInt(didMatch[2], 10) : undefined;
|
|
2921
|
+
return {
|
|
2922
|
+
iri,
|
|
2923
|
+
id: rawId ?? iri,
|
|
2924
|
+
agentId: parsedAgentId != null ? String(parsedAgentId) : undefined,
|
|
2925
|
+
agentId8004: parsedAgentId,
|
|
2926
|
+
validatorAddress,
|
|
2927
|
+
requestUri: iri,
|
|
2928
|
+
responseUri: iri,
|
|
2929
|
+
requestJson: typeof item?.json === 'string'
|
|
2930
|
+
? item.json
|
|
2931
|
+
: typeof recordResponseJsonText === 'string'
|
|
2932
|
+
? recordResponseJsonText
|
|
2933
|
+
: undefined,
|
|
2934
|
+
responseJson: recordResponseJsonText ?? undefined,
|
|
2935
|
+
requestHash,
|
|
2936
|
+
txHash: recordTxHash ?? (typeof parsedRecord?.txHash === 'string' ? parsedRecord.txHash : undefined),
|
|
2937
|
+
blockNumber: recordBlockNumber ??
|
|
2938
|
+
toNumberOrUndefined(parsedRecord?.blockNumber) ??
|
|
2939
|
+
toNumberOrUndefined(parsed?.blockNumber),
|
|
2940
|
+
timestamp: recordTimestamp ??
|
|
2941
|
+
(typeof parsedRecord?.timestamp === 'string' || typeof parsedRecord?.timestamp === 'number'
|
|
2942
|
+
? parsedRecord.timestamp
|
|
2943
|
+
: undefined),
|
|
2944
|
+
createdAt,
|
|
2945
|
+
};
|
|
2946
|
+
});
|
|
2947
|
+
return { validationRequests: mapped };
|
|
2948
|
+
}
|
|
2949
|
+
/**
|
|
2950
|
+
* Search reviews for an agent by UAID (GraphQL kbAgentByUaid + reviewAssertions)
|
|
2951
|
+
*/
|
|
2952
|
+
async searchReviewsAdvanced(options) {
|
|
2953
|
+
const { uaid, limit = 10, offset = 0 } = options;
|
|
2954
|
+
const uaidTrimmed = typeof uaid === 'string' ? uaid.trim() : '';
|
|
2955
|
+
if (!uaidTrimmed) {
|
|
2956
|
+
throw new Error('uaid is required for searchReviewsAdvanced');
|
|
2957
|
+
}
|
|
2958
|
+
const uaidForKb = this.normalizeUaidForKb(uaidTrimmed);
|
|
2959
|
+
const queryText = `
|
|
2960
|
+
query KbReviewAssertionsByUaid($uaid: String!, $first: Int, $skip: Int) {
|
|
2961
|
+
kbAgentByUaid(uaid: $uaid) {
|
|
2962
|
+
reviewAssertions(first: $first, skip: $skip) {
|
|
2963
|
+
total
|
|
2964
|
+
items {
|
|
2965
|
+
iri
|
|
2966
|
+
agentDid8004
|
|
2967
|
+
json
|
|
2968
|
+
record { txHash blockNumber timestamp rawJson }
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
`;
|
|
2974
|
+
const variables = {
|
|
2975
|
+
uaid: uaidForKb,
|
|
2976
|
+
first: typeof limit === 'number' ? limit : undefined,
|
|
2977
|
+
skip: typeof offset === 'number' ? offset : undefined,
|
|
2978
|
+
};
|
|
2979
|
+
const data = await this.client.request(queryText, variables);
|
|
2980
|
+
const connection = data?.kbAgentByUaid?.reviewAssertions;
|
|
2981
|
+
const items = Array.isArray(connection?.items) ? connection.items : [];
|
|
2982
|
+
const parseJson = (value) => {
|
|
2983
|
+
if (typeof value !== 'string' || !value.trim())
|
|
2984
|
+
return null;
|
|
2985
|
+
try {
|
|
2986
|
+
return JSON.parse(value);
|
|
2987
|
+
}
|
|
2988
|
+
catch {
|
|
2989
|
+
return null;
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
const mapped = items
|
|
2993
|
+
.filter(Boolean)
|
|
2994
|
+
.map((item) => {
|
|
2995
|
+
const iri = typeof item?.iri === 'string' ? item.iri : undefined;
|
|
2996
|
+
const record = item?.record ?? null;
|
|
2997
|
+
const recordTxHash = typeof record?.txHash === 'string' ? record.txHash : undefined;
|
|
2998
|
+
const recordBlockNumber = typeof record?.blockNumber === 'number' ? record.blockNumber : undefined;
|
|
2999
|
+
const recordTimestamp = typeof record?.timestamp === 'number' ? record.timestamp : undefined;
|
|
3000
|
+
const parsedTop = parseJson(item?.json);
|
|
3001
|
+
const parsedRecord = parseJson(record?.rawJson);
|
|
3002
|
+
const parsedFeedbackJson = parseJson(parsedRecord?.feedbackJson);
|
|
3003
|
+
const clientAddress = typeof parsedRecord?.clientAddress === 'string'
|
|
3004
|
+
? parsedRecord.clientAddress
|
|
3005
|
+
: typeof parsedFeedbackJson?.proofOfPayment?.fromAddress === 'string'
|
|
3006
|
+
? parsedFeedbackJson.proofOfPayment.fromAddress
|
|
3007
|
+
: undefined;
|
|
3008
|
+
const scoreRaw = parsedTop?.score ??
|
|
3009
|
+
parsedTop?.rating ??
|
|
3010
|
+
parsedFeedbackJson?.score ??
|
|
3011
|
+
parsedFeedbackJson?.rating ??
|
|
3012
|
+
undefined;
|
|
3013
|
+
const score = typeof scoreRaw === 'number'
|
|
3014
|
+
? scoreRaw
|
|
3015
|
+
: typeof scoreRaw === 'string' && scoreRaw.trim()
|
|
3016
|
+
? Number(scoreRaw)
|
|
3017
|
+
: undefined;
|
|
3018
|
+
const comment = typeof parsedTop?.comment === 'string'
|
|
3019
|
+
? parsedTop.comment
|
|
3020
|
+
: typeof parsedTop?.text === 'string'
|
|
3021
|
+
? parsedTop.text
|
|
3022
|
+
: typeof parsedFeedbackJson?.comment === 'string'
|
|
3023
|
+
? parsedFeedbackJson.comment
|
|
3024
|
+
: typeof parsedFeedbackJson?.text === 'string'
|
|
3025
|
+
? parsedFeedbackJson.text
|
|
3026
|
+
: undefined;
|
|
3027
|
+
const agentDid = typeof item?.agentDid8004 === 'string' ? item.agentDid8004 : undefined;
|
|
3028
|
+
const didMatch = agentDid ? /^did:8004:(\d+):(\d+)$/.exec(agentDid) : null;
|
|
3029
|
+
const parsedAgentId = didMatch?.[2] != null ? parseInt(didMatch[2], 10) : undefined;
|
|
3030
|
+
return {
|
|
3031
|
+
iri,
|
|
3032
|
+
id: iri,
|
|
3033
|
+
agentId: parsedAgentId != null ? String(parsedAgentId) : undefined,
|
|
3034
|
+
clientAddress,
|
|
3035
|
+
score: Number.isFinite(score) ? score : undefined,
|
|
3036
|
+
comment,
|
|
3037
|
+
reviewJson: typeof item?.json === 'string' ? item.json : (typeof parsedRecord?.feedbackJson === 'string' ? parsedRecord.feedbackJson : undefined),
|
|
3038
|
+
txHash: recordTxHash ?? (typeof parsedRecord?.txHash === 'string' ? parsedRecord.txHash : undefined),
|
|
3039
|
+
blockNumber: recordBlockNumber ?? (typeof parsedRecord?.blockNumber === 'number' ? parsedRecord.blockNumber : undefined),
|
|
3040
|
+
timestamp: recordTimestamp ?? (typeof parsedRecord?.timestamp === 'number' ? parsedRecord.timestamp : undefined),
|
|
3041
|
+
isRevoked: typeof parsedRecord?.isRevoked === 'boolean' ? parsedRecord.isRevoked : undefined,
|
|
3042
|
+
};
|
|
3043
|
+
});
|
|
3044
|
+
return { reviews: mapped };
|
|
3045
|
+
}
|
|
3046
|
+
/**
|
|
3047
|
+
* Search feedback/reviews for an agent (UAID or legacy chainId+agentId). Prefer searchReviewsAdvanced(uaid).
|
|
3048
|
+
*/
|
|
3049
|
+
async searchFeedbackAdvanced(options) {
|
|
3050
|
+
const { uaid, chainId, agentId, limit = 10, offset = 0 } = options;
|
|
3051
|
+
let uaidResolved;
|
|
3052
|
+
if (typeof uaid === 'string' && uaid.trim()) {
|
|
3053
|
+
uaidResolved = uaid.trim();
|
|
3054
|
+
}
|
|
3055
|
+
else if (typeof chainId === 'number' &&
|
|
3056
|
+
Number.isFinite(chainId) &&
|
|
3057
|
+
(typeof agentId === 'number' || (typeof agentId === 'string' && agentId.trim()))) {
|
|
3058
|
+
const aid = typeof agentId === 'number' ? agentId : parseInt(String(agentId), 10);
|
|
3059
|
+
if (!Number.isFinite(aid) || aid <= 0) {
|
|
3060
|
+
throw new Error(`Invalid agentId for searchFeedbackAdvanced: ${agentId}`);
|
|
3061
|
+
}
|
|
3062
|
+
uaidResolved = this.normalizeUaidForKb(`did:8004:${chainId}:${aid}`);
|
|
3063
|
+
}
|
|
3064
|
+
else {
|
|
3065
|
+
throw new Error('searchFeedbackAdvanced requires uaid or (chainId and agentId)');
|
|
3066
|
+
}
|
|
3067
|
+
const res = await this.searchReviewsAdvanced({ uaid: uaidResolved, limit, offset });
|
|
3068
|
+
if (!res?.reviews)
|
|
3069
|
+
return null;
|
|
3070
|
+
const feedbacks = res.reviews.map((r) => ({
|
|
3071
|
+
...r,
|
|
3072
|
+
feedbackUri: r.feedbackUri,
|
|
3073
|
+
feedbackJson: r.reviewJson ?? r.feedbackJson,
|
|
3074
|
+
}));
|
|
3075
|
+
return { feedbacks };
|
|
3076
|
+
}
|
|
3077
|
+
/**
|
|
3078
|
+
* Execute a raw GraphQL query
|
|
3079
|
+
* @param query - GraphQL query string
|
|
3080
|
+
* @param variables - Query variables
|
|
3081
|
+
* @returns Query response
|
|
3082
|
+
*/
|
|
3083
|
+
async request(query, variables) {
|
|
3084
|
+
return this.client.request(query, variables);
|
|
3085
|
+
}
|
|
3086
|
+
/**
|
|
3087
|
+
* Execute a raw GraphQL mutation
|
|
3088
|
+
* @param mutation - GraphQL mutation string
|
|
3089
|
+
* @param variables - Mutation variables
|
|
3090
|
+
* @returns Mutation response
|
|
3091
|
+
*/
|
|
3092
|
+
async mutate(mutation, variables) {
|
|
3093
|
+
return this.client.request(mutation, variables);
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Get the underlying GraphQLClient instance
|
|
3097
|
+
* @returns The GraphQLClient instance
|
|
3098
|
+
*/
|
|
3099
|
+
getClient() {
|
|
3100
|
+
return this.client;
|
|
3101
|
+
}
|
|
3102
|
+
/**
|
|
3103
|
+
* Get agents owned by a specific EOA address
|
|
3104
|
+
* @param eoaAddress - The EOA (Externally Owned Account) address to search for
|
|
3105
|
+
* @param options - Optional search options (limit, offset, orderBy, orderDirection)
|
|
3106
|
+
* @returns List of agents owned by the EOA address
|
|
3107
|
+
*/
|
|
3108
|
+
async getOwnedAgents(eoaAddress, options) {
|
|
3109
|
+
if (!eoaAddress || typeof eoaAddress !== 'string' || !eoaAddress.startsWith('0x')) {
|
|
3110
|
+
throw new Error('Invalid EOA address. Must be a valid Ethereum address starting with 0x');
|
|
3111
|
+
}
|
|
3112
|
+
const limit = options?.limit ?? 100;
|
|
3113
|
+
const offset = options?.offset ?? 0;
|
|
3114
|
+
const orderBy = options?.orderBy ?? 'agentId';
|
|
3115
|
+
const orderDirection = options?.orderDirection ?? 'DESC';
|
|
3116
|
+
const effectiveOrderDirection = (orderDirection ?? 'DESC').toUpperCase() === 'ASC' ? 'ASC' : 'DESC';
|
|
3117
|
+
const orderByKb = orderBy === 'agentName'
|
|
3118
|
+
? ((await this.pickKbAgentOrderBy(['agentName'])) ?? 'agentName')
|
|
3119
|
+
: ((await this.pickKbAgentOrderBy(['createdAtTime', 'updatedAtTime', 'agentId8004', 'uaid', 'agentName'])) ??
|
|
3120
|
+
'agentName');
|
|
3121
|
+
const selection = await this.getKbAgentSelection({ includeIdentityAndAccounts: false });
|
|
3122
|
+
const query = `
|
|
3123
|
+
query KbOwnedAgentsAllChains(
|
|
3124
|
+
$ownerAddress: String!
|
|
3125
|
+
$first: Int
|
|
3126
|
+
$skip: Int
|
|
3127
|
+
$orderBy: KbAgentOrderBy
|
|
3128
|
+
$orderDirection: OrderDirection
|
|
3129
|
+
) {
|
|
3130
|
+
kbOwnedAgentsAllChains(
|
|
3131
|
+
ownerAddress: $ownerAddress
|
|
3132
|
+
first: $first
|
|
3133
|
+
skip: $skip
|
|
3134
|
+
orderBy: $orderBy
|
|
3135
|
+
orderDirection: $orderDirection
|
|
3136
|
+
) {
|
|
3137
|
+
agents { ${selection} }
|
|
3138
|
+
total
|
|
3139
|
+
hasMore
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
`;
|
|
3143
|
+
try {
|
|
3144
|
+
const data = await this.gqlRequest(query, {
|
|
3145
|
+
ownerAddress: eoaAddress,
|
|
3146
|
+
first: limit,
|
|
3147
|
+
skip: offset,
|
|
3148
|
+
orderBy: orderByKb,
|
|
3149
|
+
orderDirection: effectiveOrderDirection,
|
|
3150
|
+
});
|
|
3151
|
+
const list = data?.kbOwnedAgentsAllChains?.agents ?? [];
|
|
3152
|
+
return list.map((a) => this.mapKbAgentToAgentData(a));
|
|
3153
|
+
}
|
|
3154
|
+
catch (error) {
|
|
3155
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3156
|
+
if (msg.includes('Cannot return null for non-nullable field') && msg.includes('kbOwnedAgentsAllChains')) {
|
|
3157
|
+
console.warn('[AIAgentDiscoveryClient.getOwnedAgents] Backend returned null for kbOwnedAgentsAllChains (resolver bug); returning empty list.');
|
|
3158
|
+
return [];
|
|
3159
|
+
}
|
|
3160
|
+
throw error;
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
/**
|
|
3164
|
+
* UAID-native ownership check (KB v2).
|
|
3165
|
+
*/
|
|
3166
|
+
async isOwnerByUaid(uaid, walletAddress) {
|
|
3167
|
+
const u = String(uaid ?? '').trim();
|
|
3168
|
+
const w = String(walletAddress ?? '').trim();
|
|
3169
|
+
if (!u || !w)
|
|
3170
|
+
return false;
|
|
3171
|
+
const uaidForKb = this.normalizeUaidForKb(u);
|
|
3172
|
+
const query = `
|
|
3173
|
+
query KbIsOwner($uaid: String!, $walletAddress: String!) {
|
|
3174
|
+
kbIsOwner(uaid: $uaid, walletAddress: $walletAddress)
|
|
3175
|
+
}
|
|
3176
|
+
`;
|
|
3177
|
+
const data = await this.gqlRequest(query, {
|
|
3178
|
+
uaid: uaidForKb,
|
|
3179
|
+
walletAddress: w,
|
|
3180
|
+
});
|
|
3181
|
+
return Boolean(data?.kbIsOwner);
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
//# sourceMappingURL=AIAgentDiscoveryClient.js.map
|