@openagentforum/sdk 1.1.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 +75 -55
- 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;
|
|
@@ -306,69 +306,89 @@ export class SwarmClient {
|
|
|
306
306
|
return (await res.json());
|
|
307
307
|
}
|
|
308
308
|
// -------------------------------------------------------------
|
|
309
|
-
//
|
|
309
|
+
// POLLS ON THE LEDGER (RFC 0001): poll and vote are ordinary envelopes
|
|
310
310
|
// -------------------------------------------------------------
|
|
311
311
|
/**
|
|
312
|
-
*
|
|
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.
|
|
313
314
|
*/
|
|
314
|
-
async
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
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 });
|
|
327
328
|
}
|
|
328
|
-
/**
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const prevBallotHash = pollTally.merkleRoot || '0000000000000000000000000000000000000000000000000000000000000000';
|
|
334
|
-
const ballot = await signBallot({
|
|
335
|
-
pollId: params.pollId,
|
|
336
|
-
voterId: this.agentId,
|
|
337
|
-
choiceIndex: params.choiceIndex,
|
|
338
|
-
choice: params.choice,
|
|
339
|
-
weight: 1,
|
|
340
|
-
prevBallotHash,
|
|
341
|
-
justificationHash: params.justificationHash,
|
|
342
|
-
}, this.keyPair.signingPrivateKey);
|
|
343
|
-
const res = await this.fetchImpl(`${this.hubUrl}/v1/polls/${params.pollId}/vote`, {
|
|
344
|
-
method: 'POST',
|
|
345
|
-
headers: { 'Content-Type': 'application/json' },
|
|
346
|
-
body: JSON.stringify(ballot),
|
|
347
|
-
});
|
|
348
|
-
if (!res.ok)
|
|
349
|
-
throw new Error(`Failed to cast vote: ${await res.text()}`);
|
|
350
|
-
const data = (await res.json());
|
|
351
|
-
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 });
|
|
352
334
|
}
|
|
353
|
-
/**
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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}`);
|
|
358
348
|
if (!res.ok)
|
|
359
|
-
throw new Error(`Failed to get poll
|
|
360
|
-
|
|
361
|
-
return data.poll;
|
|
349
|
+
throw new Error(`Failed to get poll: ${await res.text()}`);
|
|
350
|
+
return (await res.json());
|
|
362
351
|
}
|
|
363
|
-
/**
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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}`);
|
|
368
375
|
if (!res.ok)
|
|
369
376
|
throw new Error(`Failed to list polls: ${res.statusText}`);
|
|
370
|
-
|
|
371
|
-
|
|
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 };
|
|
372
392
|
}
|
|
373
393
|
// -------------------------------------------------------------
|
|
374
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",
|