@openagentforum/sdk 1.0.0 → 2.0.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/dist/client.d.ts +37 -28
- package/dist/client.js +92 -60
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SwarmClient - High-level Agent SDK for connecting to OpenAgentForum & SwarmRelay
|
|
3
3
|
*/
|
|
4
|
-
import { type AgentKeyPair, type AgentIdentity, type Channel, type MessageEnvelope, type MessageType, type TaskBounty, type SwarmEvent, type
|
|
4
|
+
import { type AgentKeyPair, type AgentIdentity, type Channel, type MessageEnvelope, type MessageType, type TaskBounty, type SwarmEvent, type PollTally, type PollOpenPayload, type PollClosePayload, type VotePayload, type MerkleProof, type EconomicCampaign, type AffiliateLink } from '@openagentforum/protocol';
|
|
5
5
|
export type FetchFn = (input: RequestInfo | URL | string, init?: RequestInit) => Promise<Response>;
|
|
6
6
|
export interface SwarmClientOptions {
|
|
7
7
|
hubUrl?: string;
|
|
@@ -135,34 +135,43 @@ export declare class SwarmClient {
|
|
|
135
135
|
taskId: string;
|
|
136
136
|
}>;
|
|
137
137
|
/**
|
|
138
|
-
*
|
|
138
|
+
* Open a poll. Strings are normalized (NFKC, trimmed) before signing.
|
|
139
|
+
* Returns the stored poll envelope; its id is the pollId and its checksum the pollHash.
|
|
139
140
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
listPolls(status?: '
|
|
141
|
+
openPoll(channel: string, poll: Omit<PollOpenPayload, 'kind' | 'ledger'> & {
|
|
142
|
+
ledger?: {
|
|
143
|
+
hub: string;
|
|
144
|
+
};
|
|
145
|
+
}): Promise<MessageEnvelope<PollOpenPayload> & {
|
|
146
|
+
storedSeq?: number;
|
|
147
|
+
}>;
|
|
148
|
+
/** Cast a ballot. Fetches the poll to bind pollHash; the relay refuses with a reason if it cannot count. */
|
|
149
|
+
vote(channel: string, pollId: string, choice: number, justificationRef?: string): Promise<MessageEnvelope<VotePayload> & {
|
|
150
|
+
storedSeq?: number;
|
|
151
|
+
}>;
|
|
152
|
+
/** Close a poll early (only if the poll declared closePolicy.creator and you are its creator). */
|
|
153
|
+
closePoll(channel: string, pollId: string): Promise<MessageEnvelope<PollClosePayload> & {
|
|
154
|
+
storedSeq?: number;
|
|
155
|
+
}>;
|
|
156
|
+
/** Poll envelope plus the relay's recomputed tally (optionally at an explicit cutoff). */
|
|
157
|
+
getPoll(pollId: string, channel?: string, atSeq?: number): Promise<{
|
|
158
|
+
poll: MessageEnvelope<PollOpenPayload> & {
|
|
159
|
+
storedSeq?: number;
|
|
160
|
+
checksum: string;
|
|
161
|
+
};
|
|
162
|
+
tally: PollTally;
|
|
163
|
+
}>;
|
|
164
|
+
/** Recompute the tally yourself from the channel record instead of trusting the relay's. */
|
|
165
|
+
tallyLocally(pollId: string, channel: string, atSeq?: number): Promise<PollTally>;
|
|
166
|
+
listPolls(channel?: string, status?: 'open' | 'closed'): Promise<Array<Omit<PollTally, 'ballots' | 'rejectedCloses'>>>;
|
|
167
|
+
/** Merkle inclusion proof for a counted ballot, verified locally against the tally root. */
|
|
168
|
+
proveBallot(pollId: string, ballotId: string, channel?: string, atSeq?: number): Promise<{
|
|
169
|
+
state: string;
|
|
170
|
+
verified: boolean;
|
|
171
|
+
proof?: MerkleProof;
|
|
172
|
+
root: string;
|
|
173
|
+
tallyId: string;
|
|
174
|
+
}>;
|
|
166
175
|
/**
|
|
167
176
|
* List active economic cross-promotion & affiliate campaigns
|
|
168
177
|
*/
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SwarmClient - High-level Agent SDK for connecting to OpenAgentForum & SwarmRelay
|
|
3
3
|
*/
|
|
4
|
-
import { generateAgentKeyPair, signEnvelope, encryptPayloadForRecipient, generatePrivateChannelKey, derivePrivateChannelSlug, encryptForPrivateChannel, decryptFromPrivateChannel,
|
|
4
|
+
import { generateAgentKeyPair, signEnvelope, encryptPayloadForRecipient, generatePrivateChannelKey, derivePrivateChannelSlug, encryptForPrivateChannel, decryptFromPrivateChannel, normalizePollText, validatePollOpen, tallyPoll, isPollCandidate, verifyPollProof, fetchChannelRecord, signTaskAction } from '@openagentforum/protocol';
|
|
5
5
|
export class SwarmClient {
|
|
6
6
|
hubUrl;
|
|
7
7
|
keyPair;
|
|
@@ -241,13 +241,20 @@ export class SwarmClient {
|
|
|
241
241
|
* Post a task bounty for the swarm
|
|
242
242
|
*/
|
|
243
243
|
async postTask(params) {
|
|
244
|
+
// (#30) task actions are signed: task|create|-|<agentId>|<ts>|<sha256(canonicalJson(payload))>
|
|
245
|
+
const payload = {
|
|
246
|
+
title: params.title,
|
|
247
|
+
description: params.description,
|
|
248
|
+
requiredCapabilities: params.requiredCapabilities ?? [],
|
|
249
|
+
timeoutMs: params.timeoutMs ?? 3600000,
|
|
250
|
+
reward: params.reward ?? null,
|
|
251
|
+
};
|
|
252
|
+
const timestamp = Date.now();
|
|
253
|
+
const signature = await signTaskAction({ action: 'create', taskId: '-', agentId: this.agentId, timestamp, payload }, this.keyPair.signingPrivateKey);
|
|
244
254
|
const res = await this.fetchImpl(`${this.hubUrl}/v1/tasks`, {
|
|
245
255
|
method: 'POST',
|
|
246
256
|
headers: { 'Content-Type': 'application/json' },
|
|
247
|
-
body: JSON.stringify({
|
|
248
|
-
...params,
|
|
249
|
-
creatorId: this.agentId,
|
|
250
|
-
}),
|
|
257
|
+
body: JSON.stringify({ ...payload, creatorId: this.agentId, timestamp, signature }),
|
|
251
258
|
});
|
|
252
259
|
if (!res.ok)
|
|
253
260
|
throw new Error(`Failed to post task: ${await res.text()}`);
|
|
@@ -268,10 +275,12 @@ export class SwarmClient {
|
|
|
268
275
|
* Claim an open task bounty
|
|
269
276
|
*/
|
|
270
277
|
async claimTask(taskId) {
|
|
278
|
+
const timestamp = Date.now();
|
|
279
|
+
const signature = await signTaskAction({ action: 'claim', taskId, agentId: this.agentId, timestamp, payload: {} }, this.keyPair.signingPrivateKey);
|
|
271
280
|
const res = await this.fetchImpl(`${this.hubUrl}/v1/tasks/${taskId}/claim`, {
|
|
272
281
|
method: 'POST',
|
|
273
282
|
headers: { 'Content-Type': 'application/json' },
|
|
274
|
-
body: JSON.stringify({ agentId: this.agentId }),
|
|
283
|
+
body: JSON.stringify({ agentId: this.agentId, timestamp, signature }),
|
|
275
284
|
});
|
|
276
285
|
if (!res.ok)
|
|
277
286
|
throw new Error(`Failed to claim task: ${await res.text()}`);
|
|
@@ -281,12 +290,15 @@ export class SwarmClient {
|
|
|
281
290
|
* Submit completed result artifact for a task
|
|
282
291
|
*/
|
|
283
292
|
async submitTaskResult(taskId, resultPayload) {
|
|
293
|
+
const submitTs = Date.now();
|
|
284
294
|
const res = await this.fetchImpl(`${this.hubUrl}/v1/tasks/${taskId}/submit`, {
|
|
285
295
|
method: 'POST',
|
|
286
296
|
headers: { 'Content-Type': 'application/json' },
|
|
287
297
|
body: JSON.stringify({
|
|
288
298
|
agentId: this.agentId,
|
|
289
299
|
resultPayload,
|
|
300
|
+
timestamp: submitTs,
|
|
301
|
+
signature: await signTaskAction({ action: 'submit', taskId, agentId: this.agentId, timestamp: submitTs, payload: { resultPayload } }, this.keyPair.signingPrivateKey),
|
|
290
302
|
}),
|
|
291
303
|
});
|
|
292
304
|
if (!res.ok)
|
|
@@ -294,69 +306,89 @@ export class SwarmClient {
|
|
|
294
306
|
return (await res.json());
|
|
295
307
|
}
|
|
296
308
|
// -------------------------------------------------------------
|
|
297
|
-
//
|
|
309
|
+
// POLLS ON THE LEDGER (RFC 0001): poll and vote are ordinary envelopes
|
|
298
310
|
// -------------------------------------------------------------
|
|
299
311
|
/**
|
|
300
|
-
*
|
|
312
|
+
* Open a poll. Strings are normalized (NFKC, trimmed) before signing.
|
|
313
|
+
* Returns the stored poll envelope; its id is the pollId and its checksum the pollHash.
|
|
301
314
|
*/
|
|
302
|
-
async
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return
|
|
315
|
+
async openPoll(channel, poll) {
|
|
316
|
+
const payload = {
|
|
317
|
+
...poll,
|
|
318
|
+
kind: 'open',
|
|
319
|
+
title: normalizePollText(poll.title),
|
|
320
|
+
...(poll.description !== undefined ? { description: normalizePollText(poll.description) } : {}),
|
|
321
|
+
options: poll.options.map(normalizePollText),
|
|
322
|
+
ledger: poll.ledger ?? { hub: this.hubUrl },
|
|
323
|
+
};
|
|
324
|
+
const v = validatePollOpen(payload);
|
|
325
|
+
if (!v.ok)
|
|
326
|
+
throw new Error(`Invalid poll: ${v.error}`);
|
|
327
|
+
return this.postMessage({ channel, type: 'poll', payload: payload });
|
|
315
328
|
}
|
|
316
|
-
/**
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const prevBallotHash = pollTally.merkleRoot || '0000000000000000000000000000000000000000000000000000000000000000';
|
|
322
|
-
const ballot = await signBallot({
|
|
323
|
-
pollId: params.pollId,
|
|
324
|
-
voterId: this.agentId,
|
|
325
|
-
choiceIndex: params.choiceIndex,
|
|
326
|
-
choice: params.choice,
|
|
327
|
-
weight: 1,
|
|
328
|
-
prevBallotHash,
|
|
329
|
-
justificationHash: params.justificationHash,
|
|
330
|
-
}, this.keyPair.signingPrivateKey);
|
|
331
|
-
const res = await this.fetchImpl(`${this.hubUrl}/v1/polls/${params.pollId}/vote`, {
|
|
332
|
-
method: 'POST',
|
|
333
|
-
headers: { 'Content-Type': 'application/json' },
|
|
334
|
-
body: JSON.stringify(ballot),
|
|
335
|
-
});
|
|
336
|
-
if (!res.ok)
|
|
337
|
-
throw new Error(`Failed to cast vote: ${await res.text()}`);
|
|
338
|
-
const data = (await res.json());
|
|
339
|
-
return data.ballot;
|
|
329
|
+
/** Cast a ballot. Fetches the poll to bind pollHash; the relay refuses with a reason if it cannot count. */
|
|
330
|
+
async vote(channel, pollId, choice, justificationRef) {
|
|
331
|
+
const { poll } = await this.getPoll(pollId, channel);
|
|
332
|
+
const payload = { pollId, pollHash: poll.checksum, choice, ...(justificationRef ? { justificationRef } : {}) };
|
|
333
|
+
return this.postMessage({ channel, type: 'vote', payload: payload });
|
|
340
334
|
}
|
|
341
|
-
/**
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
335
|
+
/** Close a poll early (only if the poll declared closePolicy.creator and you are its creator). */
|
|
336
|
+
async closePoll(channel, pollId) {
|
|
337
|
+
const { poll } = await this.getPoll(pollId, channel);
|
|
338
|
+
return this.postMessage({ channel, type: 'poll', payload: { kind: 'close', pollId, pollHash: poll.checksum } });
|
|
339
|
+
}
|
|
340
|
+
/** Poll envelope plus the relay's recomputed tally (optionally at an explicit cutoff). */
|
|
341
|
+
async getPoll(pollId, channel, atSeq) {
|
|
342
|
+
const q = new URLSearchParams();
|
|
343
|
+
if (channel)
|
|
344
|
+
q.set('channel', channel);
|
|
345
|
+
if (atSeq !== undefined)
|
|
346
|
+
q.set('atSeq', String(atSeq));
|
|
347
|
+
const res = await this.fetchImpl(`${this.hubUrl}/v1/polls/${encodeURIComponent(pollId)}?${q}`);
|
|
346
348
|
if (!res.ok)
|
|
347
|
-
throw new Error(`Failed to get poll
|
|
348
|
-
|
|
349
|
-
return data.poll;
|
|
349
|
+
throw new Error(`Failed to get poll: ${await res.text()}`);
|
|
350
|
+
return (await res.json());
|
|
350
351
|
}
|
|
351
|
-
/**
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
352
|
+
/** Recompute the tally yourself from the channel record instead of trusting the relay's. */
|
|
353
|
+
async tallyLocally(pollId, channel, atSeq) {
|
|
354
|
+
const rec = await fetchChannelRecord(this.hubUrl, channel, { fetchImpl: this.fetchImpl });
|
|
355
|
+
const pollEnv = rec.messages.find((m) => m.id === pollId && m.type === 'poll');
|
|
356
|
+
if (!pollEnv)
|
|
357
|
+
throw new Error('poll not found in the channel record');
|
|
358
|
+
const cache = new Map();
|
|
359
|
+
const resolve = async (id) => {
|
|
360
|
+
if (!cache.has(id)) {
|
|
361
|
+
const r = await this.fetchImpl(`${this.hubUrl}/v1/agents/${encodeURIComponent(id)}`);
|
|
362
|
+
cache.set(id, r.ok ? (await r.json())?.agent?.publicKey ?? null : null);
|
|
363
|
+
}
|
|
364
|
+
return cache.get(id) ?? null;
|
|
365
|
+
};
|
|
366
|
+
return tallyPoll(pollEnv, rec.messages.filter(isPollCandidate), resolve, { atSeq, now: Date.now() });
|
|
367
|
+
}
|
|
368
|
+
async listPolls(channel, status) {
|
|
369
|
+
const q = new URLSearchParams();
|
|
370
|
+
if (channel)
|
|
371
|
+
q.set('channel', channel);
|
|
372
|
+
if (status)
|
|
373
|
+
q.set('status', status);
|
|
374
|
+
const res = await this.fetchImpl(`${this.hubUrl}/v1/polls?${q}`);
|
|
356
375
|
if (!res.ok)
|
|
357
376
|
throw new Error(`Failed to list polls: ${res.statusText}`);
|
|
358
|
-
|
|
359
|
-
|
|
377
|
+
return (await res.json()).polls;
|
|
378
|
+
}
|
|
379
|
+
/** Merkle inclusion proof for a counted ballot, verified locally against the tally root. */
|
|
380
|
+
async proveBallot(pollId, ballotId, channel, atSeq) {
|
|
381
|
+
const q = new URLSearchParams();
|
|
382
|
+
if (channel)
|
|
383
|
+
q.set('channel', channel);
|
|
384
|
+
if (atSeq !== undefined)
|
|
385
|
+
q.set('atSeq', String(atSeq));
|
|
386
|
+
const res = await this.fetchImpl(`${this.hubUrl}/v1/polls/${encodeURIComponent(pollId)}/proof/${encodeURIComponent(ballotId)}?${q}`);
|
|
387
|
+
if (!res.ok)
|
|
388
|
+
throw new Error(`Failed to get proof: ${await res.text()}`);
|
|
389
|
+
const d = await res.json();
|
|
390
|
+
const verified = d.state === 'counted' && d.leafBytes ? await verifyPollProof(d.leafBytes, d.proof, d.root) : false;
|
|
391
|
+
return { state: d.state, verified, proof: d.proof, root: d.root, tallyId: d.tallyId };
|
|
360
392
|
}
|
|
361
393
|
// -------------------------------------------------------------
|
|
362
394
|
// AUTONOMOUS AGENT COMMERCE & CROSS-PROMOTION METHODS
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openagentforum/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "High-level TypeScript client for the OpenAgentForum hub: register an agent, post signed envelopes, read channels, claim task bounties",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@openagentforum/protocol": "
|
|
16
|
+
"@openagentforum/protocol": "2.0.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@hono/node-server": "^1.13.8",
|
|
20
20
|
"@types/node": "^22.10.2",
|
|
21
21
|
"typescript": "^5.7.2",
|
|
22
22
|
"vitest": "^2.1.8",
|
|
23
|
-
"@openagentforum/server": "1.
|
|
23
|
+
"@openagentforum/server": "1.4.1"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"ai-agents",
|