@cascade-fyi/sati-agent0-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/LICENSE +201 -0
- package/README.md +264 -0
- package/dist/index.cjs +1533 -0
- package/dist/index.d.cts +621 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +621 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1452 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +79 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1452 @@
|
|
|
1
|
+
import { ContentType, ContentType as ContentType$1, MAX_SINGLE_SIGNATURE_CONTENT_SIZE, Outcome, Outcome as Outcome$1, SATI_PROGRAM_ADDRESS, Sati, TOKEN_2022_PROGRAM_ADDRESS, buildCounterpartyMessage, buildRegistrationFile, buildRegistrationFile as buildRegistrationFile$1, createPinataUploader, fetchRegistrationFile, fetchRegistryConfig, findAgentIndexPda, findAssociatedTokenAddress, findRegistryConfigPda, getImageUrl, getRegisterAgentInstructionAsync, handleTransactionError, loadDeployedConfig, parseFeedbackContent, serializeFeedback, stringifyRegistrationFile, zeroDataHash } from "@cascade-fyi/sati-sdk";
|
|
2
|
+
import { address, generateKeyPairSigner, getAddressDecoder, signBytes } from "@solana/kit";
|
|
3
|
+
import { getCreateAssociatedTokenIdempotentInstruction, getTransferInstruction, getUpdateTokenMetadataFieldInstruction, tokenMetadataField } from "@solana-program/token-2022";
|
|
4
|
+
import { EndpointCrawler, EndpointCrawler as EndpointCrawler$1, EndpointType, EndpointType as EndpointType$1, TrustModel, TrustModel as TrustModel$1 } from "agent0-sdk";
|
|
5
|
+
|
|
6
|
+
//#region src/adapters.ts
|
|
7
|
+
/** CAIP-2 chain references for Solana networks */
|
|
8
|
+
const SOLANA_CAIP2_CHAINS = {
|
|
9
|
+
mainnet: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
10
|
+
devnet: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
11
|
+
localnet: "solana:localnet"
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Format a SATI agent as a CAIP-2 compatible AgentId string.
|
|
15
|
+
*
|
|
16
|
+
* Format: `solana:<chainRef>:<mintAddress>`
|
|
17
|
+
*/
|
|
18
|
+
function formatSatiAgentId(mint, chain) {
|
|
19
|
+
return `${chain}:${mint}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse a SATI CAIP-2 AgentId back to a mint address.
|
|
23
|
+
* Returns null if the agentId is not a Solana agent.
|
|
24
|
+
*
|
|
25
|
+
* Expected format: `solana:<chainRef>:<mintAddress>`
|
|
26
|
+
*/
|
|
27
|
+
function parseSatiAgentId(agentId) {
|
|
28
|
+
if (!agentId.startsWith("solana:")) return null;
|
|
29
|
+
const parts = agentId.split(":");
|
|
30
|
+
if (parts.length !== 3) return null;
|
|
31
|
+
return parts[2];
|
|
32
|
+
}
|
|
33
|
+
const SATI_TO_AGENT0_TYPE = {
|
|
34
|
+
MCP: EndpointType$1.MCP,
|
|
35
|
+
A2A: EndpointType$1.A2A,
|
|
36
|
+
ENS: EndpointType$1.ENS,
|
|
37
|
+
DID: EndpointType$1.DID,
|
|
38
|
+
AGENTWALLET: EndpointType$1.WALLET,
|
|
39
|
+
WALLET: EndpointType$1.WALLET,
|
|
40
|
+
OASF: EndpointType$1.OASF
|
|
41
|
+
};
|
|
42
|
+
const AGENT0_TO_SATI_NAME = {
|
|
43
|
+
[EndpointType$1.MCP]: "MCP",
|
|
44
|
+
[EndpointType$1.A2A]: "A2A",
|
|
45
|
+
[EndpointType$1.ENS]: "ENS",
|
|
46
|
+
[EndpointType$1.DID]: "DID",
|
|
47
|
+
[EndpointType$1.WALLET]: "agentWallet",
|
|
48
|
+
[EndpointType$1.OASF]: "OASF"
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Convert SATI endpoints (ERC-8004 format) to agent0 endpoint format.
|
|
52
|
+
*/
|
|
53
|
+
function toAgent0Endpoints(satiEndpoints) {
|
|
54
|
+
return satiEndpoints.map((ep) => {
|
|
55
|
+
const meta = {};
|
|
56
|
+
if (ep.version) meta.version = ep.version;
|
|
57
|
+
if (ep.mcpTools?.length) meta.mcpTools = ep.mcpTools;
|
|
58
|
+
if (ep.mcpPrompts?.length) meta.mcpPrompts = ep.mcpPrompts;
|
|
59
|
+
if (ep.mcpResources?.length) meta.mcpResources = ep.mcpResources;
|
|
60
|
+
if (ep.a2aSkills?.length) meta.a2aSkills = ep.a2aSkills;
|
|
61
|
+
if (ep.skills?.length) meta.skills = ep.skills;
|
|
62
|
+
if (ep.domains?.length) meta.domains = ep.domains;
|
|
63
|
+
return {
|
|
64
|
+
type: SATI_TO_AGENT0_TYPE[ep.name.toUpperCase()] ?? ep.name,
|
|
65
|
+
value: ep.endpoint,
|
|
66
|
+
meta: Object.keys(meta).length > 0 ? meta : void 0
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Convert agent0 endpoints to SATI registration file endpoint format.
|
|
72
|
+
*/
|
|
73
|
+
function fromAgent0Endpoints(agent0Endpoints) {
|
|
74
|
+
return agent0Endpoints.map((ep) => {
|
|
75
|
+
const result = {
|
|
76
|
+
name: AGENT0_TO_SATI_NAME[ep.type] ?? ep.type,
|
|
77
|
+
endpoint: ep.value,
|
|
78
|
+
version: ep.meta?.version
|
|
79
|
+
};
|
|
80
|
+
if (ep.meta?.mcpTools) result.mcpTools = ep.meta.mcpTools;
|
|
81
|
+
if (ep.meta?.mcpPrompts) result.mcpPrompts = ep.meta.mcpPrompts;
|
|
82
|
+
if (ep.meta?.mcpResources) result.mcpResources = ep.meta.mcpResources;
|
|
83
|
+
if (ep.meta?.a2aSkills) result.a2aSkills = ep.meta.a2aSkills;
|
|
84
|
+
if (ep.meta?.skills) result.skills = ep.meta.skills;
|
|
85
|
+
if (ep.meta?.domains) result.domains = ep.meta.domains;
|
|
86
|
+
return result;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Convert a SATI AgentIdentity + optional registration file to agent0 AgentSummary.
|
|
91
|
+
*
|
|
92
|
+
* @param identity - On-chain agent identity
|
|
93
|
+
* @param chain - CAIP-2 chain reference (e.g. "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
|
|
94
|
+
* @param regFile - Off-chain registration file (optional)
|
|
95
|
+
* @param feedbackStats - Pre-computed feedback stats (optional, from includeFeedbackStats)
|
|
96
|
+
*/
|
|
97
|
+
function toAgentSummary(identity, chain, regFile, feedbackStats) {
|
|
98
|
+
const endpoints = regFile?.endpoints ?? [];
|
|
99
|
+
const mcpEp = endpoints.find((e) => e.name.toUpperCase() === "MCP");
|
|
100
|
+
const a2aEp = endpoints.find((e) => e.name.toUpperCase() === "A2A");
|
|
101
|
+
const oasfEp = endpoints.find((e) => e.name.toUpperCase() === "OASF");
|
|
102
|
+
const ensEp = endpoints.find((e) => e.name.toUpperCase() === "ENS");
|
|
103
|
+
const didEp = endpoints.find((e) => e.name.toUpperCase() === "DID");
|
|
104
|
+
const walletEp = endpoints.find((e) => e.name.toUpperCase() === "AGENTWALLET" || e.name.toUpperCase() === "WALLET");
|
|
105
|
+
const webEp = endpoints.find((e) => e.name.toUpperCase() === "WEB");
|
|
106
|
+
return {
|
|
107
|
+
chainId: 0,
|
|
108
|
+
agentId: formatSatiAgentId(identity.mint, chain),
|
|
109
|
+
name: identity.name,
|
|
110
|
+
image: regFile?.image,
|
|
111
|
+
description: regFile?.description ?? "",
|
|
112
|
+
owners: [identity.owner],
|
|
113
|
+
operators: [],
|
|
114
|
+
mcp: mcpEp?.endpoint,
|
|
115
|
+
a2a: a2aEp?.endpoint,
|
|
116
|
+
web: webEp?.endpoint,
|
|
117
|
+
ens: ensEp?.endpoint,
|
|
118
|
+
did: didEp?.endpoint,
|
|
119
|
+
walletAddress: walletEp?.endpoint,
|
|
120
|
+
supportedTrusts: regFile?.supportedTrust ?? [],
|
|
121
|
+
mcpTools: mcpEp?.mcpTools ?? [],
|
|
122
|
+
mcpPrompts: mcpEp?.mcpPrompts ?? [],
|
|
123
|
+
mcpResources: mcpEp?.mcpResources ?? [],
|
|
124
|
+
a2aSkills: a2aEp?.a2aSkills ?? [],
|
|
125
|
+
oasfSkills: oasfEp?.skills ?? [],
|
|
126
|
+
oasfDomains: oasfEp?.domains ?? [],
|
|
127
|
+
active: regFile?.active ?? true,
|
|
128
|
+
x402support: regFile?.x402support ?? false,
|
|
129
|
+
agentURI: identity.uri,
|
|
130
|
+
agentURIType: identity.uri ? detectURIType(identity.uri) : void 0,
|
|
131
|
+
feedbackCount: feedbackStats?.count,
|
|
132
|
+
averageValue: feedbackStats?.averageValue,
|
|
133
|
+
extras: {}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** Detect URI type from prefix. */
|
|
137
|
+
function detectURIType(uri) {
|
|
138
|
+
if (uri.startsWith("ipfs://") || uri.includes("/ipfs/")) return "ipfs";
|
|
139
|
+
if (uri.startsWith("ar://") || uri.includes("arweave.net")) return "arweave";
|
|
140
|
+
if (uri.startsWith("https://") || uri.startsWith("http://")) return "https";
|
|
141
|
+
return "unknown";
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Convert a SATI registration file to agent0 RegistrationFile format.
|
|
145
|
+
*
|
|
146
|
+
* @param satiFile - SATI registration file
|
|
147
|
+
* @param identity - On-chain agent identity (optional)
|
|
148
|
+
* @param chain - CAIP-2 chain reference (required when identity is provided)
|
|
149
|
+
*/
|
|
150
|
+
function toAgent0RegistrationFile(satiFile, identity, chain) {
|
|
151
|
+
const endpoints = toAgent0Endpoints(satiFile.endpoints ?? []);
|
|
152
|
+
const trustModels = (satiFile.supportedTrust ?? []).map((t) => {
|
|
153
|
+
if (t === "reputation") return TrustModel$1.REPUTATION;
|
|
154
|
+
if (t === "crypto-economic") return TrustModel$1.CRYPTO_ECONOMIC;
|
|
155
|
+
if (t === "tee-attestation") return TrustModel$1.TEE_ATTESTATION;
|
|
156
|
+
return t;
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
agentId: identity && chain ? formatSatiAgentId(identity.mint, chain) : void 0,
|
|
160
|
+
agentURI: identity?.uri,
|
|
161
|
+
name: satiFile.name,
|
|
162
|
+
description: satiFile.description,
|
|
163
|
+
image: satiFile.image,
|
|
164
|
+
endpoints,
|
|
165
|
+
trustModels,
|
|
166
|
+
owners: identity ? [identity.owner] : [],
|
|
167
|
+
operators: [],
|
|
168
|
+
active: satiFile.active ?? true,
|
|
169
|
+
x402support: satiFile.x402support ?? false,
|
|
170
|
+
metadata: {},
|
|
171
|
+
updatedAt: Math.floor(Date.now() / 1e3)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Convert agent0 RegistrationFile back to SATI registration file params.
|
|
176
|
+
*/
|
|
177
|
+
function fromAgent0RegistrationFile(agent0File) {
|
|
178
|
+
const endpoints = fromAgent0Endpoints(agent0File.endpoints);
|
|
179
|
+
const supportedTrust = agent0File.trustModels.map((t) => {
|
|
180
|
+
if (t === TrustModel$1.REPUTATION) return "reputation";
|
|
181
|
+
if (t === TrustModel$1.CRYPTO_ECONOMIC) return "crypto-economic";
|
|
182
|
+
if (t === TrustModel$1.TEE_ATTESTATION) return "tee-attestation";
|
|
183
|
+
return null;
|
|
184
|
+
}).filter((t) => t !== null);
|
|
185
|
+
return {
|
|
186
|
+
name: agent0File.name,
|
|
187
|
+
description: agent0File.description,
|
|
188
|
+
image: agent0File.image ?? "",
|
|
189
|
+
endpoints,
|
|
190
|
+
supportedTrust,
|
|
191
|
+
active: agent0File.active,
|
|
192
|
+
x402support: agent0File.x402support
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Convert SATI feedback attestation data to agent0 Feedback format.
|
|
197
|
+
*
|
|
198
|
+
* SATI stores feedback as compressed attestations with:
|
|
199
|
+
* - outcome (always Neutral for FeedbackPublicV1)
|
|
200
|
+
* - content JSON with value, tag1, tag2, endpoint
|
|
201
|
+
*
|
|
202
|
+
* agent0-sdk expects the Feedback interface with value, tags, endpoint, etc.
|
|
203
|
+
*/
|
|
204
|
+
function toFeedback(opts) {
|
|
205
|
+
const agentId = formatSatiAgentId(opts.agentMint, opts.chain);
|
|
206
|
+
const tags = [];
|
|
207
|
+
if (opts.content.tag1) tags.push(opts.content.tag1);
|
|
208
|
+
if (opts.content.tag2) tags.push(opts.content.tag2);
|
|
209
|
+
const id = [
|
|
210
|
+
agentId,
|
|
211
|
+
opts.reviewer,
|
|
212
|
+
opts.feedbackIndex
|
|
213
|
+
];
|
|
214
|
+
const context = { ...opts.content.context };
|
|
215
|
+
if (opts.outcome !== void 0) context.outcome = opts.outcome;
|
|
216
|
+
return {
|
|
217
|
+
id,
|
|
218
|
+
agentId,
|
|
219
|
+
reviewer: opts.reviewer,
|
|
220
|
+
txHash: opts.txSignature,
|
|
221
|
+
value: opts.content.value,
|
|
222
|
+
tags,
|
|
223
|
+
endpoint: opts.content.endpoint,
|
|
224
|
+
text: opts.content.text,
|
|
225
|
+
context: Object.keys(context).length > 0 ? context : opts.content.context,
|
|
226
|
+
createdAt: opts.createdAt ?? Math.floor(Date.now() / 1e3),
|
|
227
|
+
answers: [],
|
|
228
|
+
isRevoked: false
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/agent.ts
|
|
234
|
+
/**
|
|
235
|
+
* Agent0-compatible agent class backed by SATI's Solana infrastructure.
|
|
236
|
+
*
|
|
237
|
+
* Manages an in-memory registration file that can be flushed to chain
|
|
238
|
+
* via `registerIPFS()`.
|
|
239
|
+
*/
|
|
240
|
+
var SatiAgent = class SatiAgent {
|
|
241
|
+
_registrationFile;
|
|
242
|
+
_endpointCrawler;
|
|
243
|
+
_identity;
|
|
244
|
+
_sdk;
|
|
245
|
+
constructor(sdk, registrationFile) {
|
|
246
|
+
this._sdk = sdk;
|
|
247
|
+
this._registrationFile = registrationFile;
|
|
248
|
+
this._endpointCrawler = new EndpointCrawler$1(5e3);
|
|
249
|
+
}
|
|
250
|
+
/** @internal Access the underlying SatiSDK instance. */
|
|
251
|
+
get sdk() {
|
|
252
|
+
return this._sdk;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Create a new agent in memory (not yet registered on-chain).
|
|
256
|
+
* @internal Called by SatiSDK.createAgent()
|
|
257
|
+
*/
|
|
258
|
+
static create(sdk, name, description, image) {
|
|
259
|
+
return new SatiAgent(sdk, {
|
|
260
|
+
name,
|
|
261
|
+
description,
|
|
262
|
+
image,
|
|
263
|
+
endpoints: [],
|
|
264
|
+
trustModels: [TrustModel$1.REPUTATION],
|
|
265
|
+
owners: [],
|
|
266
|
+
operators: [],
|
|
267
|
+
active: false,
|
|
268
|
+
x402support: false,
|
|
269
|
+
metadata: {},
|
|
270
|
+
updatedAt: Math.floor(Date.now() / 1e3)
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Reconstruct an agent from on-chain identity and registration file.
|
|
275
|
+
* @internal Called by SatiSDK.loadAgent()
|
|
276
|
+
*/
|
|
277
|
+
static fromIdentity(sdk, identity, registrationFile) {
|
|
278
|
+
const agent = new SatiAgent(sdk, registrationFile);
|
|
279
|
+
agent._identity = identity;
|
|
280
|
+
agent._registrationFile.agentId = formatSatiAgentId(identity.mint, sdk.chain);
|
|
281
|
+
agent._registrationFile.agentURI = identity.uri;
|
|
282
|
+
return agent;
|
|
283
|
+
}
|
|
284
|
+
get agentId() {
|
|
285
|
+
return this._registrationFile.agentId;
|
|
286
|
+
}
|
|
287
|
+
get agentURI() {
|
|
288
|
+
return this._registrationFile.agentURI;
|
|
289
|
+
}
|
|
290
|
+
get name() {
|
|
291
|
+
return this._registrationFile.name;
|
|
292
|
+
}
|
|
293
|
+
get description() {
|
|
294
|
+
return this._registrationFile.description;
|
|
295
|
+
}
|
|
296
|
+
get image() {
|
|
297
|
+
return this._registrationFile.image;
|
|
298
|
+
}
|
|
299
|
+
get mcpEndpoint() {
|
|
300
|
+
return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.MCP)?.value;
|
|
301
|
+
}
|
|
302
|
+
get a2aEndpoint() {
|
|
303
|
+
return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.A2A)?.value;
|
|
304
|
+
}
|
|
305
|
+
get walletAddress() {
|
|
306
|
+
return this._registrationFile.walletAddress;
|
|
307
|
+
}
|
|
308
|
+
/** SATI-specific: the on-chain agent identity (available after registration). */
|
|
309
|
+
get identity() {
|
|
310
|
+
return this._identity;
|
|
311
|
+
}
|
|
312
|
+
get ensEndpoint() {
|
|
313
|
+
return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.ENS)?.value;
|
|
314
|
+
}
|
|
315
|
+
get didEndpoint() {
|
|
316
|
+
return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.DID)?.value;
|
|
317
|
+
}
|
|
318
|
+
get mcpTools() {
|
|
319
|
+
return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.MCP)?.meta?.mcpTools ?? [];
|
|
320
|
+
}
|
|
321
|
+
get mcpPrompts() {
|
|
322
|
+
return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.MCP)?.meta?.mcpPrompts ?? [];
|
|
323
|
+
}
|
|
324
|
+
get mcpResources() {
|
|
325
|
+
return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.MCP)?.meta?.mcpResources ?? [];
|
|
326
|
+
}
|
|
327
|
+
get a2aSkills() {
|
|
328
|
+
return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.A2A)?.meta?.a2aSkills ?? [];
|
|
329
|
+
}
|
|
330
|
+
get oasfSkills() {
|
|
331
|
+
return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.OASF)?.meta?.skills ?? [];
|
|
332
|
+
}
|
|
333
|
+
get oasfDomains() {
|
|
334
|
+
return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.OASF)?.meta?.domains ?? [];
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Set MCP endpoint. Auto-fetches capabilities by default.
|
|
338
|
+
*/
|
|
339
|
+
async setMCP(endpoint, version = "2025-06-18", autoFetch = true) {
|
|
340
|
+
this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => ep.type !== EndpointType$1.MCP);
|
|
341
|
+
const meta = { version };
|
|
342
|
+
if (autoFetch) try {
|
|
343
|
+
const capabilities = await this._endpointCrawler.fetchMcpCapabilities(endpoint);
|
|
344
|
+
if (capabilities) {
|
|
345
|
+
if (capabilities.mcpTools) meta.mcpTools = capabilities.mcpTools;
|
|
346
|
+
if (capabilities.mcpPrompts) meta.mcpPrompts = capabilities.mcpPrompts;
|
|
347
|
+
if (capabilities.mcpResources) meta.mcpResources = capabilities.mcpResources;
|
|
348
|
+
}
|
|
349
|
+
} catch {}
|
|
350
|
+
this._registrationFile.endpoints.push({
|
|
351
|
+
type: EndpointType$1.MCP,
|
|
352
|
+
value: endpoint,
|
|
353
|
+
meta
|
|
354
|
+
});
|
|
355
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
356
|
+
return this;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Set A2A endpoint. Auto-fetches capabilities by default.
|
|
360
|
+
*/
|
|
361
|
+
async setA2A(agentcard, version = "0.30", autoFetch = true) {
|
|
362
|
+
this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => ep.type !== EndpointType$1.A2A);
|
|
363
|
+
const meta = { version };
|
|
364
|
+
if (autoFetch) try {
|
|
365
|
+
const capabilities = await this._endpointCrawler.fetchA2aCapabilities(agentcard);
|
|
366
|
+
if (capabilities?.a2aSkills) meta.a2aSkills = capabilities.a2aSkills;
|
|
367
|
+
} catch {}
|
|
368
|
+
this._registrationFile.endpoints.push({
|
|
369
|
+
type: EndpointType$1.A2A,
|
|
370
|
+
value: agentcard,
|
|
371
|
+
meta
|
|
372
|
+
});
|
|
373
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
374
|
+
return this;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Set ENS endpoint.
|
|
378
|
+
*/
|
|
379
|
+
setENS(name, version = "1.0") {
|
|
380
|
+
this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => ep.type !== EndpointType$1.ENS);
|
|
381
|
+
this._registrationFile.endpoints.push({
|
|
382
|
+
type: EndpointType$1.ENS,
|
|
383
|
+
value: name,
|
|
384
|
+
meta: { version }
|
|
385
|
+
});
|
|
386
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
387
|
+
return this;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Remove endpoint(s).
|
|
391
|
+
*/
|
|
392
|
+
removeEndpoint(opts) {
|
|
393
|
+
if (!opts || opts.type === void 0 && opts.value === void 0) this._registrationFile.endpoints = [];
|
|
394
|
+
else this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => {
|
|
395
|
+
const typeMatches = opts.type === void 0 || ep.type === opts.type;
|
|
396
|
+
const valueMatches = opts.value === void 0 || ep.value === opts.value;
|
|
397
|
+
return !(typeMatches && valueMatches);
|
|
398
|
+
});
|
|
399
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
400
|
+
return this;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Remove all endpoints (alias for removeEndpoint() with no args).
|
|
404
|
+
*/
|
|
405
|
+
removeEndpoints() {
|
|
406
|
+
return this.removeEndpoint();
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Get wallet address from the registration file.
|
|
410
|
+
*/
|
|
411
|
+
getWallet() {
|
|
412
|
+
return this._registrationFile.walletAddress;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Set wallet address and add a WALLET endpoint.
|
|
416
|
+
* In-memory only - call registerIPFS() or registerHTTP() to persist on-chain.
|
|
417
|
+
*/
|
|
418
|
+
setWallet(addr) {
|
|
419
|
+
this._registrationFile.walletAddress = addr;
|
|
420
|
+
this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => ep.type !== EndpointType$1.WALLET);
|
|
421
|
+
this._registrationFile.endpoints.push({
|
|
422
|
+
type: EndpointType$1.WALLET,
|
|
423
|
+
value: addr
|
|
424
|
+
});
|
|
425
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
426
|
+
return this;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Remove wallet address and wallet endpoint.
|
|
430
|
+
*/
|
|
431
|
+
unsetWallet() {
|
|
432
|
+
this._registrationFile.walletAddress = void 0;
|
|
433
|
+
this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => ep.type !== EndpointType$1.WALLET);
|
|
434
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
435
|
+
return this;
|
|
436
|
+
}
|
|
437
|
+
addSkill(slug) {
|
|
438
|
+
const oasfEp = this._getOrCreateOasfEndpoint();
|
|
439
|
+
if (!oasfEp.meta) oasfEp.meta = {};
|
|
440
|
+
if (!Array.isArray(oasfEp.meta.skills)) oasfEp.meta.skills = [];
|
|
441
|
+
const skills = oasfEp.meta.skills;
|
|
442
|
+
if (!skills.includes(slug)) skills.push(slug);
|
|
443
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
444
|
+
return this;
|
|
445
|
+
}
|
|
446
|
+
removeSkill(slug) {
|
|
447
|
+
const oasfEp = this._registrationFile.endpoints.find((ep) => ep.type === EndpointType$1.OASF);
|
|
448
|
+
if (oasfEp?.meta) {
|
|
449
|
+
const skills = oasfEp.meta.skills;
|
|
450
|
+
if (Array.isArray(skills)) {
|
|
451
|
+
const idx = skills.indexOf(slug);
|
|
452
|
+
if (idx !== -1) skills.splice(idx, 1);
|
|
453
|
+
}
|
|
454
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
455
|
+
}
|
|
456
|
+
return this;
|
|
457
|
+
}
|
|
458
|
+
addDomain(slug) {
|
|
459
|
+
const oasfEp = this._getOrCreateOasfEndpoint();
|
|
460
|
+
if (!oasfEp.meta) oasfEp.meta = {};
|
|
461
|
+
if (!Array.isArray(oasfEp.meta.domains)) oasfEp.meta.domains = [];
|
|
462
|
+
const domains = oasfEp.meta.domains;
|
|
463
|
+
if (!domains.includes(slug)) domains.push(slug);
|
|
464
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
465
|
+
return this;
|
|
466
|
+
}
|
|
467
|
+
removeDomain(slug) {
|
|
468
|
+
const oasfEp = this._registrationFile.endpoints.find((ep) => ep.type === EndpointType$1.OASF);
|
|
469
|
+
if (oasfEp?.meta) {
|
|
470
|
+
const domains = oasfEp.meta.domains;
|
|
471
|
+
if (Array.isArray(domains)) {
|
|
472
|
+
const idx = domains.indexOf(slug);
|
|
473
|
+
if (idx !== -1) domains.splice(idx, 1);
|
|
474
|
+
}
|
|
475
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
476
|
+
}
|
|
477
|
+
return this;
|
|
478
|
+
}
|
|
479
|
+
setActive(active) {
|
|
480
|
+
this._registrationFile.active = active;
|
|
481
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
482
|
+
return this;
|
|
483
|
+
}
|
|
484
|
+
setX402Support(x402Support) {
|
|
485
|
+
this._registrationFile.x402support = x402Support;
|
|
486
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
487
|
+
return this;
|
|
488
|
+
}
|
|
489
|
+
setTrust(reputation = false, cryptoEconomic = false, teeAttestation = false) {
|
|
490
|
+
const trustModels = [];
|
|
491
|
+
if (reputation) trustModels.push(TrustModel$1.REPUTATION);
|
|
492
|
+
if (cryptoEconomic) trustModels.push(TrustModel$1.CRYPTO_ECONOMIC);
|
|
493
|
+
if (teeAttestation) trustModels.push(TrustModel$1.TEE_ATTESTATION);
|
|
494
|
+
this._registrationFile.trustModels = trustModels;
|
|
495
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
496
|
+
return this;
|
|
497
|
+
}
|
|
498
|
+
setMetadata(kv) {
|
|
499
|
+
Object.assign(this._registrationFile.metadata, kv);
|
|
500
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
501
|
+
return this;
|
|
502
|
+
}
|
|
503
|
+
getMetadata() {
|
|
504
|
+
return { ...this._registrationFile.metadata };
|
|
505
|
+
}
|
|
506
|
+
delMetadata(key) {
|
|
507
|
+
if (key in this._registrationFile.metadata) {
|
|
508
|
+
delete this._registrationFile.metadata[key];
|
|
509
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
510
|
+
}
|
|
511
|
+
return this;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Update basic agent information.
|
|
515
|
+
*/
|
|
516
|
+
updateInfo(name, description, image) {
|
|
517
|
+
if (name !== void 0) this._registrationFile.name = name;
|
|
518
|
+
if (description !== void 0) this._registrationFile.description = description;
|
|
519
|
+
if (image !== void 0) this._registrationFile.image = image;
|
|
520
|
+
this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
|
|
521
|
+
return this;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Get the current in-memory registration file.
|
|
525
|
+
*/
|
|
526
|
+
getRegistrationFile() {
|
|
527
|
+
return this._registrationFile;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Register agent on-chain with IPFS.
|
|
531
|
+
*
|
|
532
|
+
* Converts the in-memory agent0 registration file to SATI format,
|
|
533
|
+
* uploads to IPFS via Pinata, then mints the agent NFT on Solana.
|
|
534
|
+
*
|
|
535
|
+
* @throws Error if image URL is not set
|
|
536
|
+
* @throws Error if pinataJwt is not configured
|
|
537
|
+
* @throws Error if SDK is in read-only mode
|
|
538
|
+
*/
|
|
539
|
+
async registerIPFS() {
|
|
540
|
+
if (!this._registrationFile.image) throw new Error("Image URL is required for registration. Set it via updateInfo() or createAgent().");
|
|
541
|
+
const pinataJwt = this._sdk.config.pinataJwt;
|
|
542
|
+
if (!pinataJwt) throw new Error("pinataJwt is required for IPFS uploads. Set it in SatiSDKConfig.");
|
|
543
|
+
const satiParams = fromAgent0RegistrationFile(this._registrationFile);
|
|
544
|
+
const uploader = createPinataUploader(pinataJwt);
|
|
545
|
+
const uri = await this._sdk.sati.uploadRegistrationFile(satiParams, uploader);
|
|
546
|
+
return this._registerOnChain(uri);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Register agent on-chain with an HTTP URI.
|
|
550
|
+
*
|
|
551
|
+
* Same as registerIPFS but takes a URI directly instead of uploading to IPFS.
|
|
552
|
+
* Use this when you already have a hosted registration file.
|
|
553
|
+
*
|
|
554
|
+
* @throws Error if SDK is in read-only mode
|
|
555
|
+
*/
|
|
556
|
+
async registerHTTP(agentUri) {
|
|
557
|
+
return this._registerOnChain(agentUri);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Update the agent's on-chain URI (metadata pointer).
|
|
561
|
+
*
|
|
562
|
+
* @throws Error if agent is not registered on-chain
|
|
563
|
+
* @throws Error if SDK is in read-only mode
|
|
564
|
+
*/
|
|
565
|
+
async setAgentURI(agentURI) {
|
|
566
|
+
if (!this._identity) throw new Error("Agent is not registered on-chain. Call registerIPFS() or registerHTTP() first.");
|
|
567
|
+
const access = this._requireWriteAccess();
|
|
568
|
+
if (access.type === "keypair") {
|
|
569
|
+
const result = await this._sdk.sati.updateAgentMetadata({
|
|
570
|
+
payer: access.signer,
|
|
571
|
+
owner: access.signer,
|
|
572
|
+
mint: address(this._identity.mint),
|
|
573
|
+
updates: { uri: agentURI }
|
|
574
|
+
});
|
|
575
|
+
this._identity.uri = agentURI;
|
|
576
|
+
this._registrationFile.agentURI = agentURI;
|
|
577
|
+
return result;
|
|
578
|
+
}
|
|
579
|
+
const updateIx = getUpdateTokenMetadataFieldInstruction({
|
|
580
|
+
metadata: this._identity.mint,
|
|
581
|
+
updateAuthority: { address: address(access.sender.address) },
|
|
582
|
+
field: tokenMetadataField("Uri"),
|
|
583
|
+
value: agentURI
|
|
584
|
+
});
|
|
585
|
+
const signature = await access.sender.signAndSend([updateIx]);
|
|
586
|
+
this._identity.uri = agentURI;
|
|
587
|
+
this._registrationFile.agentURI = agentURI;
|
|
588
|
+
return { signature };
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Transfer agent ownership to a new Solana address.
|
|
592
|
+
*
|
|
593
|
+
* @throws Error if agent is not registered on-chain
|
|
594
|
+
* @throws Error if SDK is in read-only mode
|
|
595
|
+
*/
|
|
596
|
+
async transfer(newOwner) {
|
|
597
|
+
if (!this._identity) throw new Error("Agent is not registered on-chain. Call registerIPFS() or registerHTTP() first.");
|
|
598
|
+
const access = this._requireWriteAccess();
|
|
599
|
+
if (access.type === "keypair") return this._sdk.sati.transferAgent({
|
|
600
|
+
payer: access.signer,
|
|
601
|
+
owner: access.signer,
|
|
602
|
+
mint: this._identity.mint,
|
|
603
|
+
newOwner: address(newOwner)
|
|
604
|
+
});
|
|
605
|
+
const ownerAddr = address(access.sender.address);
|
|
606
|
+
const [sourceAta] = await findAssociatedTokenAddress(this._identity.mint, ownerAddr);
|
|
607
|
+
const [destAta] = await findAssociatedTokenAddress(this._identity.mint, address(newOwner));
|
|
608
|
+
const createAtaIx = getCreateAssociatedTokenIdempotentInstruction({
|
|
609
|
+
payer: { address: ownerAddr },
|
|
610
|
+
owner: address(newOwner),
|
|
611
|
+
mint: this._identity.mint,
|
|
612
|
+
ata: destAta,
|
|
613
|
+
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
|
|
614
|
+
});
|
|
615
|
+
const transferIx = getTransferInstruction({
|
|
616
|
+
source: sourceAta,
|
|
617
|
+
destination: destAta,
|
|
618
|
+
authority: { address: ownerAddr },
|
|
619
|
+
amount: 1n
|
|
620
|
+
});
|
|
621
|
+
return { signature: await access.sender.signAndSend([createAtaIx, transferIx]) };
|
|
622
|
+
}
|
|
623
|
+
_requireWriteAccess() {
|
|
624
|
+
if (this._sdk.config.signer) return {
|
|
625
|
+
type: "keypair",
|
|
626
|
+
signer: this._sdk.config.signer
|
|
627
|
+
};
|
|
628
|
+
if (this._sdk.config.transactionSender) return {
|
|
629
|
+
type: "sender",
|
|
630
|
+
sender: this._sdk.config.transactionSender
|
|
631
|
+
};
|
|
632
|
+
throw new Error("This operation requires a signer or transactionSender. Initialize SatiSDK with one for write operations.");
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Shared registration logic for registerIPFS/registerHTTP.
|
|
636
|
+
* Supports both keypair and sender paths.
|
|
637
|
+
*/
|
|
638
|
+
async _registerOnChain(uri) {
|
|
639
|
+
const access = this._requireWriteAccess();
|
|
640
|
+
if (access.type === "keypair") {
|
|
641
|
+
const result = await this._sdk.sati.registerAgent({
|
|
642
|
+
payer: access.signer,
|
|
643
|
+
name: this._registrationFile.name,
|
|
644
|
+
uri
|
|
645
|
+
});
|
|
646
|
+
const agentId = this._storeIdentity(result.mint, access.signer.address, uri, result.memberNumber);
|
|
647
|
+
return {
|
|
648
|
+
signature: result.signature,
|
|
649
|
+
agentId
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
const agentMint = await generateKeyPairSigner();
|
|
653
|
+
const rpc = this._sdk.sati.getRpc();
|
|
654
|
+
const [registryConfigAddress] = await findRegistryConfigPda();
|
|
655
|
+
const registryConfig = await fetchRegistryConfig(rpc, registryConfigAddress);
|
|
656
|
+
const groupMint = registryConfig.data.groupMint;
|
|
657
|
+
const ownerAddress = address(access.sender.address);
|
|
658
|
+
const [agentTokenAccount] = await findAssociatedTokenAddress(agentMint.address, ownerAddress);
|
|
659
|
+
const [agentIndex] = await findAgentIndexPda(registryConfig.data.totalAgents + 1n);
|
|
660
|
+
const registerIx = await getRegisterAgentInstructionAsync({
|
|
661
|
+
payer: { address: ownerAddress },
|
|
662
|
+
owner: ownerAddress,
|
|
663
|
+
groupMint,
|
|
664
|
+
agentMint,
|
|
665
|
+
agentTokenAccount,
|
|
666
|
+
agentIndex,
|
|
667
|
+
name: this._registrationFile.name,
|
|
668
|
+
symbol: "",
|
|
669
|
+
uri,
|
|
670
|
+
additionalMetadata: null,
|
|
671
|
+
nonTransferable: false
|
|
672
|
+
});
|
|
673
|
+
const signature = await access.sender.signAndSend([registerIx], [agentMint]);
|
|
674
|
+
const finalMemberNumber = (await fetchRegistryConfig(rpc, registryConfigAddress)).data.totalAgents;
|
|
675
|
+
return {
|
|
676
|
+
signature,
|
|
677
|
+
agentId: this._storeIdentity(agentMint.address, ownerAddress, uri, finalMemberNumber)
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
_storeIdentity(mint, owner, uri, memberNumber) {
|
|
681
|
+
this._identity = {
|
|
682
|
+
mint,
|
|
683
|
+
owner,
|
|
684
|
+
name: this._registrationFile.name,
|
|
685
|
+
uri,
|
|
686
|
+
memberNumber,
|
|
687
|
+
additionalMetadata: {},
|
|
688
|
+
nonTransferable: false
|
|
689
|
+
};
|
|
690
|
+
const agentId = formatSatiAgentId(mint, this._sdk.chain);
|
|
691
|
+
this._registrationFile.agentId = agentId;
|
|
692
|
+
this._registrationFile.agentURI = uri;
|
|
693
|
+
return agentId;
|
|
694
|
+
}
|
|
695
|
+
_getOrCreateOasfEndpoint() {
|
|
696
|
+
const existing = this._registrationFile.endpoints.find((ep) => ep.type === EndpointType$1.OASF);
|
|
697
|
+
if (existing) return existing;
|
|
698
|
+
const oasfEndpoint = {
|
|
699
|
+
type: EndpointType$1.OASF,
|
|
700
|
+
value: "https://github.com/agntcy/oasf/",
|
|
701
|
+
meta: {
|
|
702
|
+
version: "v0.8.0",
|
|
703
|
+
skills: [],
|
|
704
|
+
domains: []
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
this._registrationFile.endpoints.push(oasfEndpoint);
|
|
708
|
+
return oasfEndpoint;
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
//#endregion
|
|
713
|
+
//#region src/sdk.ts
|
|
714
|
+
/**
|
|
715
|
+
* Main SDK class for the SATI Agent0 adapter.
|
|
716
|
+
*
|
|
717
|
+
* Provides agent0-sdk compatible method signatures backed by SATI's
|
|
718
|
+
* Solana infrastructure. Drop-in replacement for agent0-sdk's SDK class
|
|
719
|
+
* when working with SATI agents.
|
|
720
|
+
*/
|
|
721
|
+
/**
|
|
722
|
+
* SATI Agent0 SDK - agent0-sdk compatible interface backed by Solana.
|
|
723
|
+
*
|
|
724
|
+
* Method signatures match agent0-sdk's `SDK` class so that example code
|
|
725
|
+
* can switch between EVM and Solana by changing only the import and config.
|
|
726
|
+
*
|
|
727
|
+
* @example
|
|
728
|
+
* ```typescript
|
|
729
|
+
* import { SatiSDK } from "@cascade-fyi/sati-agent0-sdk";
|
|
730
|
+
* import { generateKeyPairSigner } from "@solana/kit";
|
|
731
|
+
*
|
|
732
|
+
* const signer = await generateKeyPairSigner();
|
|
733
|
+
* const sdk = new SatiSDK({
|
|
734
|
+
* network: "devnet",
|
|
735
|
+
* signer,
|
|
736
|
+
* });
|
|
737
|
+
*
|
|
738
|
+
* const agent = sdk.createAgent("MyAgent", "An AI assistant");
|
|
739
|
+
* ```
|
|
740
|
+
*/
|
|
741
|
+
var SatiSDK = class {
|
|
742
|
+
_config;
|
|
743
|
+
_sati;
|
|
744
|
+
_sasConfig;
|
|
745
|
+
_chain;
|
|
746
|
+
constructor(config) {
|
|
747
|
+
this._config = config;
|
|
748
|
+
this._sati = new Sati({
|
|
749
|
+
network: config.network,
|
|
750
|
+
rpcUrl: config.rpcUrl
|
|
751
|
+
});
|
|
752
|
+
this._sasConfig = loadDeployedConfig(config.network);
|
|
753
|
+
this._chain = SOLANA_CAIP2_CHAINS[config.network] ?? `solana:${config.network}`;
|
|
754
|
+
}
|
|
755
|
+
/** Get the SATI network. */
|
|
756
|
+
get network() {
|
|
757
|
+
return this._config.network;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Chain ID (0 for Solana - not an EVM chain).
|
|
761
|
+
* Use `chain` property for the CAIP-2 identifier.
|
|
762
|
+
*/
|
|
763
|
+
get chainId() {
|
|
764
|
+
return 0;
|
|
765
|
+
}
|
|
766
|
+
/** Get the CAIP-2 chain reference. */
|
|
767
|
+
get chain() {
|
|
768
|
+
return this._chain;
|
|
769
|
+
}
|
|
770
|
+
/** True if SDK has no signer or transaction sender configured (read-only mode). */
|
|
771
|
+
get isReadOnly() {
|
|
772
|
+
return !this._config.signer && !this._config.transactionSender;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Access the underlying SATI client for SATI-specific operations
|
|
776
|
+
* not covered by the agent0-sdk interface (e.g. validations, compression).
|
|
777
|
+
*/
|
|
778
|
+
get sati() {
|
|
779
|
+
return this._sati;
|
|
780
|
+
}
|
|
781
|
+
/** @internal Access the SAS schema config. */
|
|
782
|
+
get sasConfig() {
|
|
783
|
+
return this._sasConfig;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Create a new agent (off-chain object in memory).
|
|
787
|
+
* Call `agent.registerIPFS()` to register on-chain.
|
|
788
|
+
*/
|
|
789
|
+
createAgent(name, description, image) {
|
|
790
|
+
return SatiAgent.create(this, name, description, image);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Load an existing agent by agent0-compatible AgentId.
|
|
794
|
+
*/
|
|
795
|
+
async loadAgent(agentId) {
|
|
796
|
+
const identity = await this._resolveIdentity(agentId);
|
|
797
|
+
const agent0RegFile = toAgent0RegistrationFile(await fetchRegistrationFile(identity.uri) ?? buildRegistrationFile$1({
|
|
798
|
+
name: identity.name,
|
|
799
|
+
description: "",
|
|
800
|
+
image: "https://placehold.co/256"
|
|
801
|
+
}), identity, this._chain);
|
|
802
|
+
return SatiAgent.fromIdentity(this, identity, agent0RegFile);
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Get agent summary (read-only).
|
|
806
|
+
*/
|
|
807
|
+
async getAgent(agentId) {
|
|
808
|
+
const mint = parseSatiAgentId(agentId);
|
|
809
|
+
if (mint === null) return null;
|
|
810
|
+
const identity = await this._sati.loadAgent(address(mint));
|
|
811
|
+
if (!identity) return null;
|
|
812
|
+
const regFile = await fetchRegistrationFile(identity.uri);
|
|
813
|
+
return toAgentSummary(identity, this._chain, regFile);
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Search agents with filters.
|
|
817
|
+
*
|
|
818
|
+
* Fetches agents from on-chain registry and applies client-side filtering
|
|
819
|
+
* against on-chain data and registration files.
|
|
820
|
+
*
|
|
821
|
+
* Supports most agent0-sdk SearchFilters. Unsupported filters (require indexer):
|
|
822
|
+
* `keyword`, `registeredAtFrom/To`, `updatedAtFrom/To`, `hasMetadataKey`,
|
|
823
|
+
* `metadataValue`, `operators`.
|
|
824
|
+
*
|
|
825
|
+
* Pass `includeFeedbackStats: true` in options to populate `feedbackCount` and
|
|
826
|
+
* `averageValue` on results (slower - extra RPC calls per agent).
|
|
827
|
+
* Automatically enabled when `filters.feedback` is set.
|
|
828
|
+
*/
|
|
829
|
+
async searchAgents(filters, options) {
|
|
830
|
+
let agents;
|
|
831
|
+
if (filters?.owners?.length) agents = (await Promise.all(filters.owners.map((o) => this._sati.listAgentsByOwner(address(o))))).flat();
|
|
832
|
+
else agents = await this._sati.listAllAgents({ limit: 1e3 });
|
|
833
|
+
if (filters?.agentIds?.length) {
|
|
834
|
+
const mintSet = new Set(filters.agentIds.map((id) => parseSatiAgentId(id)).filter((m) => m !== null));
|
|
835
|
+
agents = agents.filter((a) => mintSet.has(a.mint));
|
|
836
|
+
}
|
|
837
|
+
if (filters?.name) {
|
|
838
|
+
const lower = filters.name.toLowerCase();
|
|
839
|
+
agents = agents.filter((a) => a.name.toLowerCase().includes(lower));
|
|
840
|
+
}
|
|
841
|
+
const regFiles = !!(filters?.description || filters?.active !== void 0 || filters?.hasMCP || filters?.hasA2A || filters?.hasOASF || filters?.hasWeb || filters?.hasEndpoints !== void 0 || filters?.hasRegistrationFile !== void 0 || filters?.x402support !== void 0 || filters?.supportedTrust?.length || filters?.mcpTools?.length || filters?.mcpPrompts?.length || filters?.mcpResources?.length || filters?.a2aSkills?.length || filters?.oasfSkills?.length || filters?.oasfDomains?.length || filters?.walletAddress || filters?.webContains || filters?.mcpContains || filters?.a2aContains || filters?.ensContains || filters?.didContains) ? await Promise.all(agents.map((a) => fetchRegistrationFile(a.uri))) : agents.map(() => null);
|
|
842
|
+
const wantFeedbackStats = !!(options?.includeFeedbackStats || filters?.feedback);
|
|
843
|
+
let feedbackStatsMap = null;
|
|
844
|
+
if (wantFeedbackStats && this._sasConfig) {
|
|
845
|
+
feedbackStatsMap = /* @__PURE__ */ new Map();
|
|
846
|
+
const schema = this._sasConfig.schemas.feedbackPublic ?? this._sasConfig.schemas.feedback;
|
|
847
|
+
await Promise.all(agents.map(async (agent) => {
|
|
848
|
+
try {
|
|
849
|
+
const scores = (await this._sati.listFeedbacks({
|
|
850
|
+
sasSchema: schema,
|
|
851
|
+
agentMint: agent.mint
|
|
852
|
+
})).items.map((item) => {
|
|
853
|
+
if (item.data.contentType === ContentType$1.JSON && item.data.content.length > 0) return JSON.parse(new TextDecoder().decode(item.data.content))?.score;
|
|
854
|
+
}).filter((s) => s !== void 0);
|
|
855
|
+
const count = scores.length;
|
|
856
|
+
const averageValue = count > 0 ? scores.reduce((a, b) => a + b, 0) / count : 0;
|
|
857
|
+
feedbackStatsMap?.set(agent.mint, {
|
|
858
|
+
count,
|
|
859
|
+
averageValue
|
|
860
|
+
});
|
|
861
|
+
} catch {}
|
|
862
|
+
}));
|
|
863
|
+
}
|
|
864
|
+
const results = [];
|
|
865
|
+
for (let i = 0; i < agents.length; i++) {
|
|
866
|
+
const identity = agents[i];
|
|
867
|
+
const regFile = regFiles[i];
|
|
868
|
+
const endpoints = regFile?.endpoints ?? [];
|
|
869
|
+
if (filters?.hasRegistrationFile === true && !regFile) continue;
|
|
870
|
+
if (filters?.hasRegistrationFile === false && regFile) continue;
|
|
871
|
+
if (filters?.description) {
|
|
872
|
+
if (!regFile?.description?.toLowerCase().includes(filters.description.toLowerCase())) continue;
|
|
873
|
+
}
|
|
874
|
+
if (filters?.active !== void 0 && (regFile?.active ?? true) !== filters.active) continue;
|
|
875
|
+
if (filters?.x402support !== void 0 && (regFile?.x402support ?? false) !== filters.x402support) continue;
|
|
876
|
+
if (filters?.hasMCP && !endpoints.some((e) => e.name.toUpperCase() === "MCP")) continue;
|
|
877
|
+
if (filters?.hasA2A && !endpoints.some((e) => e.name.toUpperCase() === "A2A")) continue;
|
|
878
|
+
if (filters?.hasOASF && !endpoints.some((e) => e.name.toUpperCase() === "OASF")) continue;
|
|
879
|
+
if (filters?.hasWeb && !endpoints.some((e) => e.name.toUpperCase() === "WEB")) continue;
|
|
880
|
+
if (filters?.hasEndpoints === true && endpoints.length === 0) continue;
|
|
881
|
+
if (filters?.mcpContains) {
|
|
882
|
+
if (!endpoints.find((e) => e.name.toUpperCase() === "MCP")?.endpoint.includes(filters.mcpContains)) continue;
|
|
883
|
+
}
|
|
884
|
+
if (filters?.a2aContains) {
|
|
885
|
+
if (!endpoints.find((e) => e.name.toUpperCase() === "A2A")?.endpoint.includes(filters.a2aContains)) continue;
|
|
886
|
+
}
|
|
887
|
+
if (filters?.ensContains) {
|
|
888
|
+
if (!endpoints.find((e) => e.name.toUpperCase() === "ENS")?.endpoint.includes(filters.ensContains)) continue;
|
|
889
|
+
}
|
|
890
|
+
if (filters?.didContains) {
|
|
891
|
+
if (!endpoints.find((e) => e.name.toUpperCase() === "DID")?.endpoint.includes(filters.didContains)) continue;
|
|
892
|
+
}
|
|
893
|
+
if (filters?.webContains) {
|
|
894
|
+
if (!endpoints.find((e) => e.name.toUpperCase() === "WEB")?.endpoint.includes(filters.webContains)) continue;
|
|
895
|
+
}
|
|
896
|
+
if (filters?.walletAddress) {
|
|
897
|
+
if (!endpoints.find((e) => e.name.toUpperCase() === "AGENTWALLET" || e.name.toUpperCase() === "WALLET")?.endpoint.includes(filters.walletAddress)) continue;
|
|
898
|
+
}
|
|
899
|
+
if (filters?.supportedTrust?.length) {
|
|
900
|
+
const trusts = regFile?.supportedTrust ?? [];
|
|
901
|
+
if (!filters.supportedTrust.every((t) => trusts.includes(t))) continue;
|
|
902
|
+
}
|
|
903
|
+
if (filters?.mcpTools?.length) {
|
|
904
|
+
const tools = endpoints.find((e) => e.name.toUpperCase() === "MCP")?.mcpTools ?? [];
|
|
905
|
+
if (!filters.mcpTools.some((t) => tools.includes(t))) continue;
|
|
906
|
+
}
|
|
907
|
+
if (filters?.mcpPrompts?.length) {
|
|
908
|
+
const prompts = endpoints.find((e) => e.name.toUpperCase() === "MCP")?.mcpPrompts ?? [];
|
|
909
|
+
if (!filters.mcpPrompts.some((t) => prompts.includes(t))) continue;
|
|
910
|
+
}
|
|
911
|
+
if (filters?.mcpResources?.length) {
|
|
912
|
+
const resources = endpoints.find((e) => e.name.toUpperCase() === "MCP")?.mcpResources ?? [];
|
|
913
|
+
if (!filters.mcpResources.some((t) => resources.includes(t))) continue;
|
|
914
|
+
}
|
|
915
|
+
if (filters?.a2aSkills?.length) {
|
|
916
|
+
const skills = endpoints.find((e) => e.name.toUpperCase() === "A2A")?.a2aSkills ?? [];
|
|
917
|
+
if (!filters.a2aSkills.some((t) => skills.includes(t))) continue;
|
|
918
|
+
}
|
|
919
|
+
if (filters?.oasfSkills?.length) {
|
|
920
|
+
const skills = endpoints.find((e) => e.name.toUpperCase() === "OASF")?.skills ?? [];
|
|
921
|
+
if (!filters.oasfSkills.some((t) => skills.includes(t))) continue;
|
|
922
|
+
}
|
|
923
|
+
if (filters?.oasfDomains?.length) {
|
|
924
|
+
const domains = endpoints.find((e) => e.name.toUpperCase() === "OASF")?.domains ?? [];
|
|
925
|
+
if (!filters.oasfDomains.some((t) => domains.includes(t))) continue;
|
|
926
|
+
}
|
|
927
|
+
if (filters?.feedback) {
|
|
928
|
+
const fbStats = feedbackStatsMap?.get(identity.mint);
|
|
929
|
+
const fb = filters.feedback;
|
|
930
|
+
if (fb.hasFeedback === true && (!fbStats || fbStats.count === 0)) continue;
|
|
931
|
+
if (fb.hasNoFeedback === true && fbStats && fbStats.count > 0) continue;
|
|
932
|
+
if (fb.minValue !== void 0 && (!fbStats || fbStats.averageValue < fb.minValue)) continue;
|
|
933
|
+
if (fb.maxValue !== void 0 && (!fbStats || fbStats.averageValue > fb.maxValue)) continue;
|
|
934
|
+
if (fb.minCount !== void 0 && (!fbStats || fbStats.count < fb.minCount)) continue;
|
|
935
|
+
if (fb.maxCount !== void 0 && (!fbStats || fbStats.count > fb.maxCount)) continue;
|
|
936
|
+
}
|
|
937
|
+
const stats = feedbackStatsMap?.get(identity.mint) ?? null;
|
|
938
|
+
results.push(toAgentSummary(identity, this._chain, regFile, stats));
|
|
939
|
+
}
|
|
940
|
+
const sortFields = options?.sort;
|
|
941
|
+
if (sortFields?.length) results.sort((a, b) => {
|
|
942
|
+
for (const sortStr of sortFields) {
|
|
943
|
+
const [field, dir] = sortStr.split(":");
|
|
944
|
+
const mul = dir === "asc" ? 1 : -1;
|
|
945
|
+
const aVal = a[field] ?? 0;
|
|
946
|
+
const bVal = b[field] ?? 0;
|
|
947
|
+
if (aVal !== bVal) return (aVal > bVal ? 1 : -1) * mul;
|
|
948
|
+
}
|
|
949
|
+
return 0;
|
|
950
|
+
});
|
|
951
|
+
return results;
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Transfer agent ownership to a new Solana address.
|
|
955
|
+
*/
|
|
956
|
+
async transferAgent(agentId, newOwner) {
|
|
957
|
+
const identity = await this._resolveIdentity(agentId);
|
|
958
|
+
const access = this._requireWriteAccess();
|
|
959
|
+
if (access.type === "keypair") return this._sati.transferAgent({
|
|
960
|
+
payer: access.signer,
|
|
961
|
+
owner: access.signer,
|
|
962
|
+
mint: identity.mint,
|
|
963
|
+
newOwner: address(newOwner)
|
|
964
|
+
});
|
|
965
|
+
const ownerAddr = address(access.sender.address);
|
|
966
|
+
const [sourceAta] = await findAssociatedTokenAddress(identity.mint, ownerAddr);
|
|
967
|
+
const [destAta] = await findAssociatedTokenAddress(identity.mint, address(newOwner));
|
|
968
|
+
const createAtaIx = getCreateAssociatedTokenIdempotentInstruction({
|
|
969
|
+
payer: { address: ownerAddr },
|
|
970
|
+
owner: address(newOwner),
|
|
971
|
+
mint: identity.mint,
|
|
972
|
+
ata: destAta,
|
|
973
|
+
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
|
|
974
|
+
});
|
|
975
|
+
const transferIx = getTransferInstruction({
|
|
976
|
+
source: sourceAta,
|
|
977
|
+
destination: destAta,
|
|
978
|
+
authority: { address: ownerAddr },
|
|
979
|
+
amount: 1n
|
|
980
|
+
});
|
|
981
|
+
return { signature: await access.sender.signAndSend([createAtaIx, transferIx]) };
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Check if address owns the agent.
|
|
985
|
+
*/
|
|
986
|
+
async isAgentOwner(agentId, addr) {
|
|
987
|
+
return await this.getAgentOwner(agentId) === addr;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Get agent owner address.
|
|
991
|
+
*/
|
|
992
|
+
async getAgentOwner(agentId) {
|
|
993
|
+
const identity = await this._resolveIdentity(agentId);
|
|
994
|
+
return await this._sati.getAgentOwner(identity.mint);
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Prepare an off-chain feedback file.
|
|
998
|
+
*/
|
|
999
|
+
prepareFeedbackFile(input, extra) {
|
|
1000
|
+
return {
|
|
1001
|
+
...input,
|
|
1002
|
+
...extra
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Give feedback to an agent.
|
|
1007
|
+
*
|
|
1008
|
+
* Uses FeedbackPublicV1 schema (CounterpartySigned mode).
|
|
1009
|
+
* Value/tags stored in content JSON.
|
|
1010
|
+
*
|
|
1011
|
+
* @param satiOptions - SATI-specific overrides (outcome, taskRef). When omitted,
|
|
1012
|
+
* outcome defaults to Neutral and taskRef is random.
|
|
1013
|
+
*/
|
|
1014
|
+
async giveFeedback(agentId, value, tag1, tag2, endpoint, feedbackFile, satiOptions) {
|
|
1015
|
+
const sasConfig = this._requireSASConfig();
|
|
1016
|
+
const feedbackPublicSchema = sasConfig.schemas.feedbackPublic;
|
|
1017
|
+
if (!feedbackPublicSchema) throw new Error("FeedbackPublic schema not deployed on this network");
|
|
1018
|
+
const identity = await this._resolveIdentity(agentId);
|
|
1019
|
+
const access = this._requireWriteAccess();
|
|
1020
|
+
const contentObj = {};
|
|
1021
|
+
if (value !== void 0) contentObj.score = typeof value === "string" ? Number.parseFloat(value) : value;
|
|
1022
|
+
if (tag1) contentObj.tags = tag2 ? [tag1, tag2] : [tag1];
|
|
1023
|
+
if (endpoint) contentObj.endpoint = endpoint;
|
|
1024
|
+
if (feedbackFile?.text) contentObj.m = feedbackFile.text;
|
|
1025
|
+
const content = Object.keys(contentObj).length > 0 ? new TextEncoder().encode(JSON.stringify(contentObj)) : new Uint8Array(0);
|
|
1026
|
+
const contentType = content.length > 0 ? ContentType$1.JSON : ContentType$1.None;
|
|
1027
|
+
const taskRef = satiOptions?.taskRef ?? globalThis.crypto.getRandomValues(new Uint8Array(32));
|
|
1028
|
+
const outcome = satiOptions?.outcome ?? Outcome$1.Neutral;
|
|
1029
|
+
const numericValue = typeof value === "string" ? Number.parseFloat(value) : value;
|
|
1030
|
+
if (access.type === "keypair") {
|
|
1031
|
+
const signer = access.signer;
|
|
1032
|
+
const { messageBytes } = buildCounterpartyMessage({
|
|
1033
|
+
schemaName: "FeedbackPublic",
|
|
1034
|
+
data: serializeFeedback({
|
|
1035
|
+
taskRef,
|
|
1036
|
+
agentMint: identity.mint,
|
|
1037
|
+
counterparty: signer.address,
|
|
1038
|
+
dataHash: zeroDataHash(),
|
|
1039
|
+
outcome,
|
|
1040
|
+
contentType,
|
|
1041
|
+
content
|
|
1042
|
+
})
|
|
1043
|
+
});
|
|
1044
|
+
const sig = await signBytes(signer.keyPair.privateKey, messageBytes);
|
|
1045
|
+
const result = await this._sati.createFeedback({
|
|
1046
|
+
payer: signer,
|
|
1047
|
+
sasSchema: feedbackPublicSchema,
|
|
1048
|
+
taskRef,
|
|
1049
|
+
agentMint: identity.mint,
|
|
1050
|
+
counterparty: signer.address,
|
|
1051
|
+
dataHash: zeroDataHash(),
|
|
1052
|
+
outcome,
|
|
1053
|
+
contentType,
|
|
1054
|
+
content,
|
|
1055
|
+
agentSignature: {
|
|
1056
|
+
pubkey: signer.address,
|
|
1057
|
+
signature: new Uint8Array(sig)
|
|
1058
|
+
},
|
|
1059
|
+
counterpartyMessage: messageBytes,
|
|
1060
|
+
lookupTableAddress: sasConfig.lookupTable
|
|
1061
|
+
});
|
|
1062
|
+
const feedback = toFeedback({
|
|
1063
|
+
agentMint: identity.mint,
|
|
1064
|
+
chain: this._chain,
|
|
1065
|
+
reviewer: signer.address,
|
|
1066
|
+
feedbackIndex: 0,
|
|
1067
|
+
content: {
|
|
1068
|
+
value: numericValue,
|
|
1069
|
+
tag1,
|
|
1070
|
+
tag2,
|
|
1071
|
+
endpoint,
|
|
1072
|
+
text: feedbackFile?.text
|
|
1073
|
+
},
|
|
1074
|
+
txSignature: result.signature,
|
|
1075
|
+
outcome
|
|
1076
|
+
});
|
|
1077
|
+
return {
|
|
1078
|
+
signature: result.signature,
|
|
1079
|
+
feedback
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
throw new Error("giveFeedback is not supported via transactionSender (browser wallet). Use prepareFeedback() to get SIWS message bytes, have the wallet sign them, then call submitPreparedFeedback() with a server-side KeyPairSigner to submit.");
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Prepare a feedback submission for browser wallet signing.
|
|
1086
|
+
*
|
|
1087
|
+
* Returns SIWS message bytes that the counterparty wallet must sign.
|
|
1088
|
+
* Pass the result + signature to `submitPreparedFeedback()`.
|
|
1089
|
+
*
|
|
1090
|
+
* @param agentId - Agent to give feedback to
|
|
1091
|
+
* @param value - Numeric feedback value
|
|
1092
|
+
* @param tag1 - Optional first tag
|
|
1093
|
+
* @param tag2 - Optional second tag
|
|
1094
|
+
* @param opts - Optional: endpoint, text, counterparty address (defaults to transactionSender address)
|
|
1095
|
+
* @param satiOptions - SATI-specific overrides (outcome, taskRef)
|
|
1096
|
+
*/
|
|
1097
|
+
async prepareFeedback(agentId, value, tag1, tag2, opts, satiOptions) {
|
|
1098
|
+
const sasConfig = this._requireSASConfig();
|
|
1099
|
+
const feedbackPublicSchema = sasConfig.schemas.feedbackPublic;
|
|
1100
|
+
if (!feedbackPublicSchema) throw new Error("FeedbackPublic schema not deployed on this network");
|
|
1101
|
+
const identity = await this._resolveIdentity(agentId);
|
|
1102
|
+
const counterpartyAddr = opts?.counterparty ?? this._config.transactionSender?.address;
|
|
1103
|
+
if (!counterpartyAddr) throw new Error("counterparty address required - provide via opts.counterparty or configure transactionSender");
|
|
1104
|
+
const contentObj = {};
|
|
1105
|
+
if (value !== void 0) contentObj.score = value;
|
|
1106
|
+
if (tag1) contentObj.tags = tag2 ? [tag1, tag2] : [tag1];
|
|
1107
|
+
if (opts?.endpoint) contentObj.endpoint = opts.endpoint;
|
|
1108
|
+
if (opts?.text) contentObj.m = opts.text;
|
|
1109
|
+
const content = Object.keys(contentObj).length > 0 ? new TextEncoder().encode(JSON.stringify(contentObj)) : new Uint8Array(0);
|
|
1110
|
+
const contentType = content.length > 0 ? ContentType$1.JSON : ContentType$1.None;
|
|
1111
|
+
const taskRef = satiOptions?.taskRef ?? globalThis.crypto.getRandomValues(new Uint8Array(32));
|
|
1112
|
+
const outcome = satiOptions?.outcome ?? Outcome$1.Neutral;
|
|
1113
|
+
const { messageBytes } = buildCounterpartyMessage({
|
|
1114
|
+
schemaName: "FeedbackPublic",
|
|
1115
|
+
data: serializeFeedback({
|
|
1116
|
+
taskRef,
|
|
1117
|
+
agentMint: identity.mint,
|
|
1118
|
+
counterparty: address(counterpartyAddr),
|
|
1119
|
+
dataHash: zeroDataHash(),
|
|
1120
|
+
outcome,
|
|
1121
|
+
contentType,
|
|
1122
|
+
content
|
|
1123
|
+
})
|
|
1124
|
+
});
|
|
1125
|
+
return {
|
|
1126
|
+
messageBytes,
|
|
1127
|
+
agentMint: identity.mint,
|
|
1128
|
+
counterparty: address(counterpartyAddr),
|
|
1129
|
+
taskRef,
|
|
1130
|
+
dataHash: zeroDataHash(),
|
|
1131
|
+
outcome,
|
|
1132
|
+
contentType,
|
|
1133
|
+
content,
|
|
1134
|
+
sasSchema: feedbackPublicSchema,
|
|
1135
|
+
lookupTable: sasConfig.lookupTable,
|
|
1136
|
+
feedbackMeta: {
|
|
1137
|
+
value,
|
|
1138
|
+
tag1,
|
|
1139
|
+
tag2,
|
|
1140
|
+
endpoint: opts?.endpoint,
|
|
1141
|
+
text: opts?.text
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Submit a prepared feedback using a server-side KeyPairSigner.
|
|
1147
|
+
*
|
|
1148
|
+
* The signer pays gas. The counterparty's SIWS signature proves consent.
|
|
1149
|
+
*
|
|
1150
|
+
* @param prepared - Result from `prepareFeedback()`
|
|
1151
|
+
* @param counterpartySignature - Counterparty wallet's signature of `prepared.messageBytes`
|
|
1152
|
+
*/
|
|
1153
|
+
async submitPreparedFeedback(prepared, counterpartySignature) {
|
|
1154
|
+
const signer = this._requireSigner();
|
|
1155
|
+
const result = await this._sati.createFeedback({
|
|
1156
|
+
payer: signer,
|
|
1157
|
+
sasSchema: prepared.sasSchema,
|
|
1158
|
+
taskRef: prepared.taskRef,
|
|
1159
|
+
agentMint: prepared.agentMint,
|
|
1160
|
+
counterparty: prepared.counterparty,
|
|
1161
|
+
dataHash: prepared.dataHash,
|
|
1162
|
+
outcome: prepared.outcome,
|
|
1163
|
+
contentType: prepared.contentType,
|
|
1164
|
+
content: prepared.content,
|
|
1165
|
+
agentSignature: {
|
|
1166
|
+
pubkey: prepared.counterparty,
|
|
1167
|
+
signature: new Uint8Array(counterpartySignature)
|
|
1168
|
+
},
|
|
1169
|
+
counterpartyMessage: prepared.messageBytes,
|
|
1170
|
+
lookupTableAddress: prepared.lookupTable
|
|
1171
|
+
});
|
|
1172
|
+
const feedback = toFeedback({
|
|
1173
|
+
agentMint: prepared.agentMint,
|
|
1174
|
+
chain: this._chain,
|
|
1175
|
+
reviewer: prepared.counterparty,
|
|
1176
|
+
feedbackIndex: 0,
|
|
1177
|
+
content: prepared.feedbackMeta,
|
|
1178
|
+
txSignature: result.signature,
|
|
1179
|
+
outcome: prepared.outcome
|
|
1180
|
+
});
|
|
1181
|
+
return {
|
|
1182
|
+
signature: result.signature,
|
|
1183
|
+
feedback
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Search feedback with filters.
|
|
1188
|
+
*
|
|
1189
|
+
* Supports most agent0-sdk FeedbackSearchFilters. The `includeRevoked`
|
|
1190
|
+
* filter is a no-op on SATI (closed attestations are permanently deleted).
|
|
1191
|
+
*/
|
|
1192
|
+
async searchFeedback(filters, options) {
|
|
1193
|
+
const sasConfig = this._requireSASConfig();
|
|
1194
|
+
const satiFilter = { sasSchema: sasConfig.schemas.feedbackPublic ?? sasConfig.schemas.feedback };
|
|
1195
|
+
let knownIdentity = null;
|
|
1196
|
+
const targetAgentId = filters.agentId ?? filters.agents?.[0];
|
|
1197
|
+
if (targetAgentId) {
|
|
1198
|
+
knownIdentity = await this._resolveIdentity(targetAgentId);
|
|
1199
|
+
satiFilter.agentMint = knownIdentity.mint;
|
|
1200
|
+
}
|
|
1201
|
+
if (filters.reviewers?.length) satiFilter.counterparty = address(filters.reviewers[0]);
|
|
1202
|
+
const result = await this._sati.listFeedbacks(satiFilter);
|
|
1203
|
+
const currentSlot = await this._sati.getRpc().getSlot({ commitment: "confirmed" }).send();
|
|
1204
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
1205
|
+
const feedbacks = [];
|
|
1206
|
+
for (let i = 0; i < result.items.length; i++) {
|
|
1207
|
+
const item = result.items[i];
|
|
1208
|
+
const rawContent = item.data.contentType === ContentType$1.JSON && item.data.content.length > 0 ? JSON.parse(new TextDecoder().decode(item.data.content)) : null;
|
|
1209
|
+
const score = rawContent?.score;
|
|
1210
|
+
const tags = rawContent?.tags ?? [];
|
|
1211
|
+
const text = rawContent?.m;
|
|
1212
|
+
const endpointVal = rawContent?.endpoint;
|
|
1213
|
+
const capability = rawContent?.cap;
|
|
1214
|
+
const name = rawContent?.n;
|
|
1215
|
+
const skill = rawContent?.sk;
|
|
1216
|
+
const task = rawContent?.tsk;
|
|
1217
|
+
const proofOfPayment = rawContent?.pop;
|
|
1218
|
+
const fileURI = rawContent?.fileURI;
|
|
1219
|
+
if (filters.tags?.length) {
|
|
1220
|
+
if (!filters.tags.every((t) => tags.includes(t))) continue;
|
|
1221
|
+
}
|
|
1222
|
+
if (options?.minValue !== void 0 && (score === void 0 || score < options.minValue)) continue;
|
|
1223
|
+
if (options?.maxValue !== void 0 && (score === void 0 || score > options.maxValue)) continue;
|
|
1224
|
+
if (filters.capabilities?.length && (!capability || !filters.capabilities.includes(capability))) continue;
|
|
1225
|
+
if (filters.skills?.length && (!skill || !filters.skills.includes(skill))) continue;
|
|
1226
|
+
if (filters.tasks?.length && (!task || !filters.tasks.includes(task))) continue;
|
|
1227
|
+
if (filters.names?.length && (!name || !filters.names.includes(name))) continue;
|
|
1228
|
+
const slotDiff = Number(BigInt(currentSlot) - item.raw.slotCreated);
|
|
1229
|
+
const createdAt = nowSec - Math.floor(slotDiff * .4);
|
|
1230
|
+
const [compressedAddress] = getAddressDecoder().read(item.address, 0);
|
|
1231
|
+
const feedback = toFeedback({
|
|
1232
|
+
agentMint: item.data.agentMint,
|
|
1233
|
+
chain: this._chain,
|
|
1234
|
+
reviewer: item.data.counterparty,
|
|
1235
|
+
feedbackIndex: i,
|
|
1236
|
+
content: {
|
|
1237
|
+
value: score,
|
|
1238
|
+
tag1: tags[0],
|
|
1239
|
+
tag2: tags[1],
|
|
1240
|
+
endpoint: endpointVal,
|
|
1241
|
+
text,
|
|
1242
|
+
context: {
|
|
1243
|
+
satiCompressedAddress: compressedAddress,
|
|
1244
|
+
counterparty: item.data.counterparty,
|
|
1245
|
+
agentMint: item.data.agentMint
|
|
1246
|
+
}
|
|
1247
|
+
},
|
|
1248
|
+
createdAt,
|
|
1249
|
+
outcome: item.data.outcome
|
|
1250
|
+
});
|
|
1251
|
+
if (capability) feedback.capability = capability;
|
|
1252
|
+
if (name) feedback.name = name;
|
|
1253
|
+
if (skill) feedback.skill = skill;
|
|
1254
|
+
if (task) feedback.task = task;
|
|
1255
|
+
if (proofOfPayment) feedback.proofOfPayment = proofOfPayment;
|
|
1256
|
+
if (fileURI) feedback.fileURI = fileURI;
|
|
1257
|
+
feedbacks.push(feedback);
|
|
1258
|
+
}
|
|
1259
|
+
if (options?.includeTxHash) {
|
|
1260
|
+
const photon = this._sati.getLightClient().getRpc();
|
|
1261
|
+
await Promise.all(feedbacks.map(async (fb) => {
|
|
1262
|
+
const addr = fb.context?.satiCompressedAddress;
|
|
1263
|
+
if (!addr) return;
|
|
1264
|
+
try {
|
|
1265
|
+
fb.txHash = (await photon.getCompressionSignaturesForAddress(address(addr), { limit: 1 })).items[0]?.signature;
|
|
1266
|
+
} catch {}
|
|
1267
|
+
}));
|
|
1268
|
+
}
|
|
1269
|
+
return feedbacks;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Append response to feedback.
|
|
1273
|
+
*
|
|
1274
|
+
* @throws Error - Not supported on SATI at the moment.
|
|
1275
|
+
*/
|
|
1276
|
+
async appendResponse(_agentId, _clientAddress, _feedbackIndex, _response) {
|
|
1277
|
+
throw new Error("appendResponse is not supported on SATI at the moment");
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Get reputation summary for an agent.
|
|
1281
|
+
*
|
|
1282
|
+
* Computes average score from all FeedbackPublic attestations,
|
|
1283
|
+
* optionally filtered by tags.
|
|
1284
|
+
*/
|
|
1285
|
+
async getReputationSummary(agentId, tag1, tag2) {
|
|
1286
|
+
const sasConfig = this._requireSASConfig();
|
|
1287
|
+
const identity = await this._resolveIdentity(agentId);
|
|
1288
|
+
const result = await this._sati.listFeedbacks({
|
|
1289
|
+
sasSchema: sasConfig.schemas.feedbackPublic ?? sasConfig.schemas.feedback,
|
|
1290
|
+
agentMint: identity.mint
|
|
1291
|
+
});
|
|
1292
|
+
if (result.items.length === 0) return {
|
|
1293
|
+
count: 0,
|
|
1294
|
+
averageValue: 0
|
|
1295
|
+
};
|
|
1296
|
+
let sum = 0;
|
|
1297
|
+
let count = 0;
|
|
1298
|
+
for (const item of result.items) {
|
|
1299
|
+
const rawContent = item.data.contentType === ContentType$1.JSON && item.data.content.length > 0 ? JSON.parse(new TextDecoder().decode(item.data.content)) : null;
|
|
1300
|
+
const score = rawContent?.score;
|
|
1301
|
+
const tags = rawContent?.tags ?? [];
|
|
1302
|
+
if (tag1 && !tags.includes(tag1)) continue;
|
|
1303
|
+
if (tag2 && !tags.includes(tag2)) continue;
|
|
1304
|
+
if (score !== void 0) {
|
|
1305
|
+
sum += score;
|
|
1306
|
+
count++;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return {
|
|
1310
|
+
count,
|
|
1311
|
+
averageValue: count > 0 ? sum / count : 0
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Get a single feedback entry.
|
|
1316
|
+
*
|
|
1317
|
+
* Fetches feedbacks for the given agent + reviewer, then returns the one at feedbackIndex.
|
|
1318
|
+
*/
|
|
1319
|
+
async getFeedback(agentId, clientAddress, feedbackIndex) {
|
|
1320
|
+
const fb = (await this.searchFeedback({
|
|
1321
|
+
agentId,
|
|
1322
|
+
reviewers: [clientAddress]
|
|
1323
|
+
}))[feedbackIndex];
|
|
1324
|
+
if (!fb) throw new Error(`Feedback not found at index ${feedbackIndex} for agent ${agentId} reviewer ${clientAddress}`);
|
|
1325
|
+
return fb;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Look up the creation transaction signature for a compressed attestation.
|
|
1329
|
+
*
|
|
1330
|
+
* Use `feedback.context.satiCompressedAddress` from `searchFeedback()` results
|
|
1331
|
+
* as the `compressedAddress` parameter.
|
|
1332
|
+
*
|
|
1333
|
+
* @param compressedAddress - Base58 address of the compressed account
|
|
1334
|
+
* @returns Transaction signature or null if not found
|
|
1335
|
+
*/
|
|
1336
|
+
async getCreationSignature(compressedAddress) {
|
|
1337
|
+
try {
|
|
1338
|
+
return (await this._sati.getLightClient().getRpc().getCompressionSignaturesForAddress(address(compressedAddress), { limit: 1 })).items[0]?.signature ?? null;
|
|
1339
|
+
} catch {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Revoke (close) a previously submitted feedback.
|
|
1345
|
+
*
|
|
1346
|
+
* Closes the compressed attestation on-chain. The signer must be the
|
|
1347
|
+
* counterparty who originally submitted the feedback.
|
|
1348
|
+
*
|
|
1349
|
+
* Note: On SATI, revoked feedbacks are permanently deleted (Light Protocol
|
|
1350
|
+
* compressed accounts are closed). They cannot be recovered.
|
|
1351
|
+
*/
|
|
1352
|
+
async revokeFeedback(agentId, feedbackIndex) {
|
|
1353
|
+
const sasConfig = this._requireSASConfig();
|
|
1354
|
+
const signer = this._requireSigner();
|
|
1355
|
+
const identity = await this._resolveIdentity(agentId);
|
|
1356
|
+
const schema = sasConfig.schemas.feedbackPublic ?? sasConfig.schemas.feedback;
|
|
1357
|
+
const item = (await this._sati.listFeedbacks({
|
|
1358
|
+
sasSchema: schema,
|
|
1359
|
+
agentMint: identity.mint,
|
|
1360
|
+
counterparty: signer.address
|
|
1361
|
+
})).items[feedbackIndex];
|
|
1362
|
+
if (!item) throw new Error(`Feedback not found at index ${feedbackIndex}`);
|
|
1363
|
+
const [attestationAddress] = getAddressDecoder().read(item.address, 0);
|
|
1364
|
+
return this._sati.closeCompressedAttestation({
|
|
1365
|
+
payer: signer,
|
|
1366
|
+
counterparty: signer,
|
|
1367
|
+
sasSchema: item.attestation.sasSchema,
|
|
1368
|
+
attestationAddress,
|
|
1369
|
+
lookupTableAddress: sasConfig.lookupTable
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Search validation attestations for an agent.
|
|
1374
|
+
*
|
|
1375
|
+
* Validations are on-chain attestations from validators (TEE, zkML, re-execution, etc.)
|
|
1376
|
+
* that verify an agent's behavior. Unlike feedback, validations are typically automated.
|
|
1377
|
+
*/
|
|
1378
|
+
async searchValidations(agentId) {
|
|
1379
|
+
const validationSchema = this._requireSASConfig().schemas.validation;
|
|
1380
|
+
if (!validationSchema) throw new Error("Validation schema not deployed on this network");
|
|
1381
|
+
const identity = await this._resolveIdentity(agentId);
|
|
1382
|
+
const result = await this._sati.listValidations({
|
|
1383
|
+
sasSchema: validationSchema,
|
|
1384
|
+
agentMint: identity.mint
|
|
1385
|
+
});
|
|
1386
|
+
const currentSlot = await this._sati.getRpc().getSlot({ commitment: "confirmed" }).send();
|
|
1387
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
1388
|
+
return result.items.map((item) => {
|
|
1389
|
+
const slotDiff = Number(BigInt(currentSlot) - item.raw.slotCreated);
|
|
1390
|
+
const createdAt = nowSec - Math.floor(slotDiff * .4);
|
|
1391
|
+
const [compressedAddress] = getAddressDecoder().read(item.address, 0);
|
|
1392
|
+
return {
|
|
1393
|
+
outcome: item.data.outcome,
|
|
1394
|
+
agentMint: item.data.agentMint,
|
|
1395
|
+
counterparty: item.data.counterparty,
|
|
1396
|
+
createdAt,
|
|
1397
|
+
compressedAddress
|
|
1398
|
+
};
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
/** Get the feedback schema address for the current network. */
|
|
1402
|
+
get feedbackSchema() {
|
|
1403
|
+
return this._sasConfig?.schemas.feedback;
|
|
1404
|
+
}
|
|
1405
|
+
/** Get the feedbackPublic schema address for the current network. */
|
|
1406
|
+
get feedbackPublicSchema() {
|
|
1407
|
+
return this._sasConfig?.schemas.feedbackPublic;
|
|
1408
|
+
}
|
|
1409
|
+
/** Get the validation schema address for the current network. */
|
|
1410
|
+
get validationSchema() {
|
|
1411
|
+
return this._sasConfig?.schemas.validation;
|
|
1412
|
+
}
|
|
1413
|
+
/** Get the lookup table address for the current network. */
|
|
1414
|
+
get lookupTable() {
|
|
1415
|
+
return this._sasConfig?.lookupTable;
|
|
1416
|
+
}
|
|
1417
|
+
/** @internal */
|
|
1418
|
+
get config() {
|
|
1419
|
+
return this._config;
|
|
1420
|
+
}
|
|
1421
|
+
_requireSigner() {
|
|
1422
|
+
if (!this._config.signer) throw new Error("This operation requires a KeyPairSigner. Initialize SatiSDK with a signer for server-side write operations.");
|
|
1423
|
+
return this._config.signer;
|
|
1424
|
+
}
|
|
1425
|
+
/** Returns either a KeyPairSigner or a TransactionSender, or throws if read-only. */
|
|
1426
|
+
_requireWriteAccess() {
|
|
1427
|
+
if (this._config.signer) return {
|
|
1428
|
+
type: "keypair",
|
|
1429
|
+
signer: this._config.signer
|
|
1430
|
+
};
|
|
1431
|
+
if (this._config.transactionSender) return {
|
|
1432
|
+
type: "sender",
|
|
1433
|
+
sender: this._config.transactionSender
|
|
1434
|
+
};
|
|
1435
|
+
throw new Error("This operation requires a signer or transactionSender. Initialize SatiSDK with one for write operations.");
|
|
1436
|
+
}
|
|
1437
|
+
_requireSASConfig() {
|
|
1438
|
+
if (!this._sasConfig) throw new Error(`No SAS config deployed for network "${this._config.network}". Deploy schemas first.`);
|
|
1439
|
+
return this._sasConfig;
|
|
1440
|
+
}
|
|
1441
|
+
async _resolveIdentity(agentId) {
|
|
1442
|
+
const mint = parseSatiAgentId(agentId);
|
|
1443
|
+
if (mint === null) throw new Error(`Invalid SATI agent ID: ${agentId}. Expected format: solana:<chainRef>:<mintAddress>`);
|
|
1444
|
+
const identity = await this._sati.loadAgent(address(mint));
|
|
1445
|
+
if (!identity) throw new Error(`Agent not found: ${agentId}`);
|
|
1446
|
+
return identity;
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1450
|
+
//#endregion
|
|
1451
|
+
export { ContentType, EndpointCrawler, EndpointType, MAX_SINGLE_SIGNATURE_CONTENT_SIZE, Outcome, SATI_PROGRAM_ADDRESS, SOLANA_CAIP2_CHAINS, SatiAgent, SatiSDK, TrustModel, buildRegistrationFile, formatSatiAgentId, fromAgent0Endpoints, fromAgent0RegistrationFile, getImageUrl, handleTransactionError, parseFeedbackContent, parseSatiAgentId, stringifyRegistrationFile, toAgent0Endpoints, toAgent0RegistrationFile, toAgentSummary, toFeedback };
|
|
1452
|
+
//# sourceMappingURL=index.mjs.map
|