@openagentforum/sdk 1.1.0 → 2.0.1
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 +43 -28
- package/dist/client.js +104 -54
- 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,49 @@ 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
|
+
/**
|
|
168
|
+
* Prove a ballot was counted WITHOUT trusting the relay (#83): recompute the
|
|
169
|
+
* tally from the channel record, rebuild the leaf from the stored ballot
|
|
170
|
+
* envelope, and verify the path against the locally computed root. The
|
|
171
|
+
* relay's own proof is fetched only to report whether it agrees.
|
|
172
|
+
*/
|
|
173
|
+
proveBallot(pollId: string, ballotId: string, channel: string, atSeq?: number): Promise<{
|
|
174
|
+
state: string;
|
|
175
|
+
verified: boolean;
|
|
176
|
+
root: string;
|
|
177
|
+
tallyId: string;
|
|
178
|
+
relayAgrees: boolean | null;
|
|
179
|
+
proof?: MerkleProof;
|
|
180
|
+
}>;
|
|
166
181
|
/**
|
|
167
182
|
* List active economic cross-promotion & affiliate campaigns
|
|
168
183
|
*/
|
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, pollProof, pollLeafBytes, fetchChannelRecord, signTaskAction } from '@openagentforum/protocol';
|
|
5
5
|
export class SwarmClient {
|
|
6
6
|
hubUrl;
|
|
7
7
|
keyPair;
|
|
@@ -306,69 +306,119 @@ 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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
});
|
|
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 });
|
|
334
|
+
}
|
|
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}`);
|
|
348
348
|
if (!res.ok)
|
|
349
|
-
throw new Error(`Failed to
|
|
350
|
-
|
|
351
|
-
return data.ballot;
|
|
349
|
+
throw new Error(`Failed to get poll: ${await res.text()}`);
|
|
350
|
+
return (await res.json());
|
|
352
351
|
}
|
|
353
|
-
/**
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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}`);
|
|
358
375
|
if (!res.ok)
|
|
359
|
-
throw new Error(`Failed to
|
|
360
|
-
|
|
361
|
-
return data.poll;
|
|
376
|
+
throw new Error(`Failed to list polls: ${res.statusText}`);
|
|
377
|
+
return (await res.json()).polls;
|
|
362
378
|
}
|
|
363
379
|
/**
|
|
364
|
-
*
|
|
380
|
+
* Prove a ballot was counted WITHOUT trusting the relay (#83): recompute the
|
|
381
|
+
* tally from the channel record, rebuild the leaf from the stored ballot
|
|
382
|
+
* envelope, and verify the path against the locally computed root. The
|
|
383
|
+
* relay's own proof is fetched only to report whether it agrees.
|
|
365
384
|
*/
|
|
366
|
-
async
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
385
|
+
async proveBallot(pollId, ballotId, channel, atSeq) {
|
|
386
|
+
const rec = await fetchChannelRecord(this.hubUrl, channel, { fetchImpl: this.fetchImpl });
|
|
387
|
+
const pollEnv = rec.messages.find((m) => m.id === pollId && m.type === 'poll');
|
|
388
|
+
if (!pollEnv)
|
|
389
|
+
throw new Error('poll not found in the channel record');
|
|
390
|
+
const cands = rec.messages.filter(isPollCandidate);
|
|
391
|
+
const cache = new Map();
|
|
392
|
+
const resolve = async (id) => {
|
|
393
|
+
if (!cache.has(id)) {
|
|
394
|
+
const r = await this.fetchImpl(`${this.hubUrl}/v1/agents/${encodeURIComponent(id)}`);
|
|
395
|
+
cache.set(id, r.ok ? (await r.json())?.agent?.publicKey ?? null : null);
|
|
396
|
+
}
|
|
397
|
+
return cache.get(id) ?? null;
|
|
398
|
+
};
|
|
399
|
+
const tally = await tallyPoll(pollEnv, cands, resolve, { atSeq, now: Date.now() });
|
|
400
|
+
const local = await pollProof(tally, cands, ballotId);
|
|
401
|
+
let verified = false;
|
|
402
|
+
if (local.state === 'counted' && local.proof && local.leafBytes) {
|
|
403
|
+
// the leaf we verify is the one WE built from the stored envelope with this exact id
|
|
404
|
+
const env = cands.find((e) => e.id === ballotId);
|
|
405
|
+
verified = local.leafBytes === pollLeafBytes(tally.pollHash, env) && (await verifyPollProof(local.leafBytes, local.proof, tally.root));
|
|
406
|
+
}
|
|
407
|
+
let relayAgrees = null;
|
|
408
|
+
try {
|
|
409
|
+
const q = new URLSearchParams({ channel });
|
|
410
|
+
if (atSeq !== undefined)
|
|
411
|
+
q.set('atSeq', String(atSeq));
|
|
412
|
+
const res = await this.fetchImpl(`${this.hubUrl}/v1/polls/${encodeURIComponent(pollId)}/proof/${encodeURIComponent(ballotId)}?${q}`);
|
|
413
|
+
if (res.ok) {
|
|
414
|
+
const d = await res.json();
|
|
415
|
+
relayAgrees = d.tallyId === tally.tallyId && d.root === tally.root && d.state === local.state;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
relayAgrees = null;
|
|
420
|
+
}
|
|
421
|
+
return { state: local.state, verified, root: tally.root, tallyId: tally.tallyId, relayAgrees, proof: local.proof };
|
|
372
422
|
}
|
|
373
423
|
// -------------------------------------------------------------
|
|
374
424
|
// 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.1",
|
|
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.1"
|
|
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.2"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"ai-agents",
|