@swarmdock/sdk 0.1.0 → 0.2.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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-type-check.log +1 -1
- package/dist/client.d.ts +187 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +307 -7
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
- package/src/client.ts +481 -19
- package/src/index.ts +11 -1
package/src/client.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import nacl from 'tweetnacl';
|
|
2
|
-
import
|
|
2
|
+
import tweetnaclUtil from 'tweetnacl-util';
|
|
3
|
+
const { encodeBase64, decodeBase64 } = tweetnaclUtil;
|
|
4
|
+
import { wrapFetchWithPaymentFromConfig } from '@x402/fetch';
|
|
5
|
+
import { ExactEvmScheme } from '@x402/evm';
|
|
6
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
3
7
|
import type {
|
|
4
8
|
Agent,
|
|
5
9
|
Task,
|
|
@@ -7,10 +11,11 @@ import type {
|
|
|
7
11
|
EscrowTransaction,
|
|
8
12
|
AgentRating,
|
|
9
13
|
AgentSkill,
|
|
14
|
+
Dispute,
|
|
15
|
+
PortfolioItem,
|
|
10
16
|
SSEEvent,
|
|
11
17
|
AgentUpdateInput,
|
|
12
18
|
TaskCreateInput,
|
|
13
|
-
TaskListQuery,
|
|
14
19
|
TaskSubmitInput,
|
|
15
20
|
BidCreateInput,
|
|
16
21
|
RatingCreateInput,
|
|
@@ -19,7 +24,8 @@ import { SwarmDockError } from './errors.js';
|
|
|
19
24
|
|
|
20
25
|
export interface SwarmDockClientOptions {
|
|
21
26
|
baseUrl: string;
|
|
22
|
-
privateKey
|
|
27
|
+
privateKey?: string; // Ed25519 secret key, base64
|
|
28
|
+
paymentPrivateKey?: `0x${string}`;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export interface RegisterParams {
|
|
@@ -58,6 +64,8 @@ export interface BalanceResult {
|
|
|
58
64
|
agentId: string;
|
|
59
65
|
earned: string;
|
|
60
66
|
spent: string;
|
|
67
|
+
escrowed?: string;
|
|
68
|
+
released?: string;
|
|
61
69
|
currency: string;
|
|
62
70
|
network: string;
|
|
63
71
|
}
|
|
@@ -69,22 +77,82 @@ export interface TransactionsResult {
|
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
export interface TaskListResult {
|
|
72
|
-
tasks: Task
|
|
80
|
+
tasks: Array<Task & { bidCount?: number }>;
|
|
73
81
|
limit: number;
|
|
74
82
|
offset: number;
|
|
83
|
+
total?: number;
|
|
75
84
|
}
|
|
76
85
|
|
|
86
|
+
type TaskListFilters = {
|
|
87
|
+
q?: string;
|
|
88
|
+
status?: string;
|
|
89
|
+
skills?: string;
|
|
90
|
+
budgetMin?: string;
|
|
91
|
+
budgetMax?: string;
|
|
92
|
+
requesterId?: string;
|
|
93
|
+
assigneeId?: string;
|
|
94
|
+
limit?: number;
|
|
95
|
+
offset?: number;
|
|
96
|
+
};
|
|
97
|
+
|
|
77
98
|
export interface TaskDetailResult extends Task {
|
|
78
|
-
|
|
99
|
+
requester?: {
|
|
100
|
+
id: string;
|
|
101
|
+
displayName: string;
|
|
102
|
+
trustLevel: number;
|
|
103
|
+
status: string;
|
|
104
|
+
} | null;
|
|
105
|
+
assignee?: {
|
|
106
|
+
id: string;
|
|
107
|
+
displayName: string;
|
|
108
|
+
trustLevel: number;
|
|
109
|
+
status: string;
|
|
110
|
+
} | null;
|
|
111
|
+
bids: Array<TaskBid & {
|
|
112
|
+
bidderDisplayName?: string | null;
|
|
113
|
+
bidder?: {
|
|
114
|
+
id: string;
|
|
115
|
+
displayName: string;
|
|
116
|
+
trustLevel: number;
|
|
117
|
+
status: string;
|
|
118
|
+
} | null;
|
|
119
|
+
}>;
|
|
79
120
|
bidCount: number;
|
|
121
|
+
dispute: Dispute | null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface RatingsSummary {
|
|
125
|
+
ratings: AgentRating[];
|
|
126
|
+
averages: {
|
|
127
|
+
quality: number;
|
|
128
|
+
speed: number | null;
|
|
129
|
+
communication: number | null;
|
|
130
|
+
reliability: number | null;
|
|
131
|
+
} | null;
|
|
132
|
+
count: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface PortfolioResult {
|
|
136
|
+
items: PortfolioItem[];
|
|
137
|
+
count: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface ReputationResult {
|
|
141
|
+
agentId: string;
|
|
142
|
+
trustLevel: number;
|
|
143
|
+
totalTasksCompleted: number;
|
|
144
|
+
totalTasksFailed: number;
|
|
145
|
+
averageRating: number | null;
|
|
146
|
+
specializations: string[];
|
|
80
147
|
}
|
|
81
148
|
|
|
82
149
|
type SSECallback = (event: SSEEvent) => void;
|
|
83
150
|
|
|
84
151
|
export class SwarmDockClient {
|
|
85
152
|
private readonly baseUrl: string;
|
|
86
|
-
private readonly secretKey: Uint8Array;
|
|
87
|
-
private readonly publicKeyBase64: string;
|
|
153
|
+
private readonly secretKey: Uint8Array | null;
|
|
154
|
+
private readonly publicKeyBase64: string | null;
|
|
155
|
+
private readonly fetchImpl: typeof globalThis.fetch;
|
|
88
156
|
private token: string | null = null;
|
|
89
157
|
private agentId: string | null = null;
|
|
90
158
|
|
|
@@ -97,10 +165,25 @@ export class SwarmDockClient {
|
|
|
97
165
|
|
|
98
166
|
constructor(options: SwarmDockClientOptions) {
|
|
99
167
|
this.baseUrl = options.baseUrl.replace(/\/+$/, '');
|
|
100
|
-
|
|
168
|
+
if (options.privateKey) {
|
|
169
|
+
this.secretKey = decodeBase64(options.privateKey);
|
|
170
|
+
const keyPair = nacl.sign.keyPair.fromSecretKey(this.secretKey);
|
|
171
|
+
this.publicKeyBase64 = encodeBase64(keyPair.publicKey);
|
|
172
|
+
} else {
|
|
173
|
+
this.secretKey = null;
|
|
174
|
+
this.publicKeyBase64 = null;
|
|
175
|
+
}
|
|
101
176
|
|
|
102
|
-
|
|
103
|
-
|
|
177
|
+
this.fetchImpl = options.paymentPrivateKey
|
|
178
|
+
? wrapFetchWithPaymentFromConfig(globalThis.fetch, {
|
|
179
|
+
schemes: [
|
|
180
|
+
{
|
|
181
|
+
network: 'eip155:*',
|
|
182
|
+
client: new ExactEvmScheme(privateKeyToAccount(options.paymentPrivateKey)),
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
})
|
|
186
|
+
: globalThis.fetch;
|
|
104
187
|
|
|
105
188
|
this.profile = new ProfileOperations(this);
|
|
106
189
|
this.tasks = new TaskOperations(this);
|
|
@@ -109,8 +192,10 @@ export class SwarmDockClient {
|
|
|
109
192
|
}
|
|
110
193
|
|
|
111
194
|
async register(params: RegisterParams): Promise<RegisterResult> {
|
|
195
|
+
this.requireSigner();
|
|
196
|
+
|
|
112
197
|
const registerBody = {
|
|
113
|
-
publicKey: this.publicKeyBase64
|
|
198
|
+
publicKey: this.publicKeyBase64!,
|
|
114
199
|
displayName: params.displayName,
|
|
115
200
|
description: params.description,
|
|
116
201
|
framework: params.framework,
|
|
@@ -134,7 +219,7 @@ export class SwarmDockClient {
|
|
|
134
219
|
{
|
|
135
220
|
method: 'POST',
|
|
136
221
|
body: {
|
|
137
|
-
publicKey: this.publicKeyBase64
|
|
222
|
+
publicKey: this.publicKeyBase64!,
|
|
138
223
|
challenge: registerRes.challenge,
|
|
139
224
|
signature,
|
|
140
225
|
},
|
|
@@ -148,11 +233,44 @@ export class SwarmDockClient {
|
|
|
148
233
|
return verifyRes;
|
|
149
234
|
}
|
|
150
235
|
|
|
236
|
+
async authenticate(): Promise<void> {
|
|
237
|
+
if (this.token) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.requireSigner();
|
|
242
|
+
|
|
243
|
+
const challengeRes = await this.fetch<{ challenge: string; expiresAt: string }>(
|
|
244
|
+
'/api/v1/agents/login/challenge',
|
|
245
|
+
{
|
|
246
|
+
method: 'POST',
|
|
247
|
+
body: { publicKey: this.publicKeyBase64! },
|
|
248
|
+
auth: false,
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const verifyRes = await this.fetch<RegisterResult>(
|
|
253
|
+
'/api/v1/agents/login/verify',
|
|
254
|
+
{
|
|
255
|
+
method: 'POST',
|
|
256
|
+
body: {
|
|
257
|
+
publicKey: this.publicKeyBase64!,
|
|
258
|
+
challenge: challengeRes.challenge,
|
|
259
|
+
signature: this.sign(challengeRes.challenge),
|
|
260
|
+
},
|
|
261
|
+
auth: false,
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
this.token = verifyRes.token;
|
|
266
|
+
this.agentId = verifyRes.agent.id;
|
|
267
|
+
}
|
|
268
|
+
|
|
151
269
|
async heartbeat(): Promise<{ token: string }> {
|
|
152
|
-
this.
|
|
270
|
+
await this.authenticate();
|
|
153
271
|
|
|
154
272
|
const res = await this.fetch<{ token: string; lastHeartbeat: string }>(
|
|
155
|
-
`/api/v1/agents/${this.agentId}/heartbeat`,
|
|
273
|
+
`/api/v1/agents/${this.agentId!}/heartbeat`,
|
|
156
274
|
{ method: 'POST' },
|
|
157
275
|
);
|
|
158
276
|
|
|
@@ -191,11 +309,11 @@ export class SwarmDockClient {
|
|
|
191
309
|
};
|
|
192
310
|
|
|
193
311
|
if (auth) {
|
|
194
|
-
this.
|
|
195
|
-
headers['Authorization'] = `Bearer ${this.token}`;
|
|
312
|
+
await this.authenticate();
|
|
313
|
+
headers['Authorization'] = `Bearer ${this.token!}`;
|
|
196
314
|
}
|
|
197
315
|
|
|
198
|
-
const res = await
|
|
316
|
+
const res = await this.fetchImpl(url, {
|
|
199
317
|
method,
|
|
200
318
|
headers,
|
|
201
319
|
body: body ? JSON.stringify(body) : undefined,
|
|
@@ -256,9 +374,16 @@ export class SwarmDockClient {
|
|
|
256
374
|
}
|
|
257
375
|
}
|
|
258
376
|
|
|
377
|
+
private requireSigner(): void {
|
|
378
|
+
if (!this.secretKey || !this.publicKeyBase64) {
|
|
379
|
+
throw new SwarmDockError(401, 'This operation requires an Ed25519 private key.');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
259
383
|
private sign(message: string): string {
|
|
384
|
+
this.requireSigner();
|
|
260
385
|
const messageBytes = new TextEncoder().encode(message);
|
|
261
|
-
const signature = nacl.sign.detached(messageBytes, this.secretKey);
|
|
386
|
+
const signature = nacl.sign.detached(messageBytes, this.secretKey!);
|
|
262
387
|
return encodeBase64(signature);
|
|
263
388
|
}
|
|
264
389
|
}
|
|
@@ -276,11 +401,15 @@ class ProfileOperations {
|
|
|
276
401
|
constructor(private readonly client: SwarmDockClient) {}
|
|
277
402
|
|
|
278
403
|
async get(agentId?: string): Promise<Agent & { skills: AgentSkill[] }> {
|
|
404
|
+
if (!agentId) {
|
|
405
|
+
await this.client.authenticate();
|
|
406
|
+
}
|
|
279
407
|
const id = agentId ?? this.client.getAgentId();
|
|
280
408
|
return this.client.fetch(`/api/v1/agents/${id}`, { auth: false });
|
|
281
409
|
}
|
|
282
410
|
|
|
283
411
|
async update(fields: AgentUpdateInput): Promise<Agent> {
|
|
412
|
+
await this.client.authenticate();
|
|
284
413
|
const id = this.client.getAgentId();
|
|
285
414
|
return this.client.fetch(`/api/v1/agents/${id}`, {
|
|
286
415
|
method: 'PATCH',
|
|
@@ -288,6 +417,58 @@ class ProfileOperations {
|
|
|
288
417
|
});
|
|
289
418
|
}
|
|
290
419
|
|
|
420
|
+
async ratings(agentId?: string): Promise<RatingsSummary> {
|
|
421
|
+
if (!agentId) {
|
|
422
|
+
await this.client.authenticate();
|
|
423
|
+
}
|
|
424
|
+
const id = agentId ?? this.client.getAgentId();
|
|
425
|
+
return this.client.fetch(`/api/v1/agents/${id}/ratings`, { auth: false });
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async portfolio(agentId?: string): Promise<PortfolioResult> {
|
|
429
|
+
if (!agentId) {
|
|
430
|
+
await this.client.authenticate();
|
|
431
|
+
}
|
|
432
|
+
const id = agentId ?? this.client.getAgentId();
|
|
433
|
+
return this.client.fetch(`/api/v1/agents/${id}/portfolio`, { auth: false });
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
readonly portfolioManage = {
|
|
437
|
+
create: async (taskId: string): Promise<PortfolioItem> => {
|
|
438
|
+
await this.client.authenticate();
|
|
439
|
+
const id = this.client.getAgentId();
|
|
440
|
+
return this.client.fetch(`/api/v1/agents/${id}/portfolio`, {
|
|
441
|
+
method: 'POST',
|
|
442
|
+
body: { taskId },
|
|
443
|
+
});
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
update: async (itemId: string, updates: { isPinned?: boolean; displayOrder?: number }): Promise<PortfolioItem> => {
|
|
447
|
+
await this.client.authenticate();
|
|
448
|
+
const id = this.client.getAgentId();
|
|
449
|
+
return this.client.fetch(`/api/v1/agents/${id}/portfolio/${itemId}`, {
|
|
450
|
+
method: 'PATCH',
|
|
451
|
+
body: updates,
|
|
452
|
+
});
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
remove: async (itemId: string): Promise<void> => {
|
|
456
|
+
await this.client.authenticate();
|
|
457
|
+
const id = this.client.getAgentId();
|
|
458
|
+
await this.client.fetch(`/api/v1/agents/${id}/portfolio/${itemId}`, {
|
|
459
|
+
method: 'DELETE',
|
|
460
|
+
});
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
async reputation(agentId?: string): Promise<ReputationResult> {
|
|
465
|
+
if (!agentId) {
|
|
466
|
+
await this.client.authenticate();
|
|
467
|
+
}
|
|
468
|
+
const id = agentId ?? this.client.getAgentId();
|
|
469
|
+
return this.client.fetch(`/api/v1/agents/${id}/reputation`, { auth: false });
|
|
470
|
+
}
|
|
471
|
+
|
|
291
472
|
async match(params: { description: string; skills?: string[]; limit?: number }): Promise<{ matches: Agent[] }> {
|
|
292
473
|
return this.client.fetch('/api/v1/agents/match', { method: 'POST', body: params, auth: false });
|
|
293
474
|
}
|
|
@@ -296,9 +477,10 @@ class ProfileOperations {
|
|
|
296
477
|
class TaskOperations {
|
|
297
478
|
constructor(private readonly client: SwarmDockClient) {}
|
|
298
479
|
|
|
299
|
-
async list(filters?:
|
|
480
|
+
async list(filters?: TaskListFilters): Promise<TaskListResult> {
|
|
300
481
|
const query: Record<string, string | number | undefined | null> = {};
|
|
301
482
|
if (filters) {
|
|
483
|
+
if (filters.q) query.q = filters.q;
|
|
302
484
|
if (filters.status) query.status = filters.status;
|
|
303
485
|
if (filters.skills) query.skills = filters.skills;
|
|
304
486
|
if (filters.budgetMin) query.budgetMin = filters.budgetMin;
|
|
@@ -322,6 +504,10 @@ class TaskOperations {
|
|
|
322
504
|
return this.client.fetch(`/api/v1/tasks/${taskId}`, { auth: false });
|
|
323
505
|
}
|
|
324
506
|
|
|
507
|
+
async listBids(taskId: string): Promise<{ bids: TaskBid[] }> {
|
|
508
|
+
return this.client.fetch(`/api/v1/tasks/${taskId}/bids`, { auth: false });
|
|
509
|
+
}
|
|
510
|
+
|
|
325
511
|
async bid(taskId: string, input: BidCreateInput): Promise<TaskBid> {
|
|
326
512
|
return this.client.fetch(`/api/v1/tasks/${taskId}/bids`, {
|
|
327
513
|
method: 'POST',
|
|
@@ -329,6 +515,12 @@ class TaskOperations {
|
|
|
329
515
|
});
|
|
330
516
|
}
|
|
331
517
|
|
|
518
|
+
async acceptBid(taskId: string, bidId: string): Promise<{ task: Task; acceptedBid: TaskBid; escrow?: EscrowTransaction }> {
|
|
519
|
+
return this.client.fetch(`/api/v1/tasks/${taskId}/bids/${bidId}/accept`, {
|
|
520
|
+
method: 'POST',
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
332
524
|
async start(taskId: string): Promise<Task> {
|
|
333
525
|
return this.client.fetch(`/api/v1/tasks/${taskId}/start`, {
|
|
334
526
|
method: 'POST',
|
|
@@ -354,6 +546,13 @@ class TaskOperations {
|
|
|
354
546
|
body: reason ? { reason } : undefined,
|
|
355
547
|
});
|
|
356
548
|
}
|
|
549
|
+
|
|
550
|
+
async dispute(taskId: string, reason: string): Promise<Dispute> {
|
|
551
|
+
return this.client.fetch(`/api/v1/tasks/${taskId}/dispute`, {
|
|
552
|
+
method: 'POST',
|
|
553
|
+
body: { reason },
|
|
554
|
+
});
|
|
555
|
+
}
|
|
357
556
|
}
|
|
358
557
|
|
|
359
558
|
class EventOperations {
|
|
@@ -451,14 +650,277 @@ class PaymentOperations {
|
|
|
451
650
|
constructor(private readonly client: SwarmDockClient) {}
|
|
452
651
|
|
|
453
652
|
async balance(): Promise<BalanceResult> {
|
|
653
|
+
await this.client.authenticate();
|
|
454
654
|
const id = this.client.getAgentId();
|
|
455
655
|
return this.client.fetch(`/api/v1/payments/agents/${id}/balance`);
|
|
456
656
|
}
|
|
457
657
|
|
|
458
658
|
async transactions(limit?: number, offset?: number): Promise<TransactionsResult> {
|
|
659
|
+
await this.client.authenticate();
|
|
459
660
|
const id = this.client.getAgentId();
|
|
460
661
|
return this.client.fetch(`/api/v1/payments/agents/${id}/transactions`, {
|
|
461
662
|
query: { limit: limit ?? undefined, offset: offset ?? undefined },
|
|
462
663
|
});
|
|
463
664
|
}
|
|
464
665
|
}
|
|
666
|
+
|
|
667
|
+
// -- Agent mode types --
|
|
668
|
+
|
|
669
|
+
export interface TaskContext {
|
|
670
|
+
id: string;
|
|
671
|
+
title: string;
|
|
672
|
+
description: string;
|
|
673
|
+
inputData: unknown;
|
|
674
|
+
inputFiles: string[];
|
|
675
|
+
skillRequirements: string[];
|
|
676
|
+
budgetMax: string;
|
|
677
|
+
|
|
678
|
+
/** Mark the task as in-progress */
|
|
679
|
+
start(): Promise<void>;
|
|
680
|
+
/** Submit the completed result */
|
|
681
|
+
complete(result: TaskResult): Promise<void>;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export interface TaskResult {
|
|
685
|
+
artifacts: Array<{ type: string; content: unknown }>;
|
|
686
|
+
files?: string[];
|
|
687
|
+
notes?: string;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export interface TaskListing {
|
|
691
|
+
id: string;
|
|
692
|
+
title: string;
|
|
693
|
+
description: string;
|
|
694
|
+
skillRequirements: string[];
|
|
695
|
+
budgetMin: string | null;
|
|
696
|
+
budgetMax: string;
|
|
697
|
+
matchingMode: string;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export interface SwarmDockAgentOptions {
|
|
701
|
+
baseUrl?: string;
|
|
702
|
+
name: string;
|
|
703
|
+
skills: Array<{
|
|
704
|
+
id: string;
|
|
705
|
+
name: string;
|
|
706
|
+
description: string;
|
|
707
|
+
category: string;
|
|
708
|
+
pricing?: { model?: string; basePrice: number };
|
|
709
|
+
examples?: string[];
|
|
710
|
+
inputModes?: string[];
|
|
711
|
+
outputModes?: string[];
|
|
712
|
+
}>;
|
|
713
|
+
framework?: string;
|
|
714
|
+
modelProvider?: string;
|
|
715
|
+
modelName?: string;
|
|
716
|
+
walletAddress: string;
|
|
717
|
+
privateKey?: string;
|
|
718
|
+
paymentPrivateKey?: `0x${string}`;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
type TaskHandler = (task: TaskContext) => Promise<TaskResult>;
|
|
722
|
+
type TaskAvailableHandler = (listing: TaskListing) => Promise<void>;
|
|
723
|
+
|
|
724
|
+
const HEARTBEAT_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
725
|
+
|
|
726
|
+
export class SwarmDockAgent {
|
|
727
|
+
private client: SwarmDockClient;
|
|
728
|
+
private readonly options: SwarmDockAgentOptions;
|
|
729
|
+
private taskHandlers: Map<string, TaskHandler> = new Map();
|
|
730
|
+
private taskAvailableHandler?: TaskAvailableHandler;
|
|
731
|
+
private heartbeatInterval?: ReturnType<typeof setInterval>;
|
|
732
|
+
private eventUnsubscribe?: () => void;
|
|
733
|
+
private running = false;
|
|
734
|
+
|
|
735
|
+
constructor(options: SwarmDockAgentOptions) {
|
|
736
|
+
this.options = options;
|
|
737
|
+
this.client = new SwarmDockClient({
|
|
738
|
+
baseUrl: options.baseUrl ?? 'https://api.swarmdock.ai',
|
|
739
|
+
privateKey: options.privateKey,
|
|
740
|
+
paymentPrivateKey: options.paymentPrivateKey,
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Register a handler for tasks that match a specific skill.
|
|
746
|
+
* When this agent is assigned a task whose skillRequirements include
|
|
747
|
+
* the given skillId, the handler will be invoked.
|
|
748
|
+
*/
|
|
749
|
+
onTask(skillId: string, handler: TaskHandler): void {
|
|
750
|
+
this.taskHandlers.set(skillId, handler);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Register a handler that fires when a new task is created on the
|
|
755
|
+
* marketplace that matches this agent's skills. Useful for auto-bidding.
|
|
756
|
+
*/
|
|
757
|
+
onTaskAvailable(handler: TaskAvailableHandler): void {
|
|
758
|
+
this.taskAvailableHandler = handler;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Start the agent: register (or authenticate), begin heartbeat,
|
|
763
|
+
* and subscribe to the SSE event stream.
|
|
764
|
+
*/
|
|
765
|
+
async start(): Promise<void> {
|
|
766
|
+
if (this.running) return;
|
|
767
|
+
|
|
768
|
+
// Register or authenticate
|
|
769
|
+
try {
|
|
770
|
+
await this.client.register({
|
|
771
|
+
displayName: this.options.name,
|
|
772
|
+
framework: this.options.framework,
|
|
773
|
+
modelProvider: this.options.modelProvider,
|
|
774
|
+
modelName: this.options.modelName,
|
|
775
|
+
walletAddress: this.options.walletAddress,
|
|
776
|
+
skills: this.options.skills.map((s) => ({
|
|
777
|
+
skillId: s.id,
|
|
778
|
+
skillName: s.name,
|
|
779
|
+
description: s.description,
|
|
780
|
+
category: s.category,
|
|
781
|
+
tags: [],
|
|
782
|
+
pricingModel: s.pricing?.model ?? 'fixed',
|
|
783
|
+
basePrice: String(s.pricing?.basePrice ?? 0),
|
|
784
|
+
examplePrompts: s.examples ?? [],
|
|
785
|
+
})),
|
|
786
|
+
});
|
|
787
|
+
} catch (err) {
|
|
788
|
+
// If already registered, authenticate instead
|
|
789
|
+
if (err instanceof SwarmDockError && (err.status === 409 || err.message.includes('already registered'))) {
|
|
790
|
+
await this.client.authenticate();
|
|
791
|
+
} else {
|
|
792
|
+
throw err;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
this.running = true;
|
|
797
|
+
|
|
798
|
+
// Start heartbeat
|
|
799
|
+
this.heartbeatInterval = setInterval(async () => {
|
|
800
|
+
try {
|
|
801
|
+
await this.client.heartbeat();
|
|
802
|
+
} catch {
|
|
803
|
+
// Heartbeat failures are non-fatal; the next one will retry
|
|
804
|
+
}
|
|
805
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
806
|
+
|
|
807
|
+
// Subscribe to SSE events
|
|
808
|
+
const agentId = this.client.getAgentId();
|
|
809
|
+
|
|
810
|
+
this.client.events.subscribe((event) => {
|
|
811
|
+
this.handleEvent(event, agentId).catch(() => {
|
|
812
|
+
// Event handling errors are non-fatal
|
|
813
|
+
});
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
this.eventUnsubscribe = () => this.client.events.unsubscribe();
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Stop the agent: unsubscribe from events, clear heartbeat,
|
|
821
|
+
* and mark as not running.
|
|
822
|
+
*/
|
|
823
|
+
async stop(): Promise<void> {
|
|
824
|
+
if (!this.running) return;
|
|
825
|
+
this.running = false;
|
|
826
|
+
|
|
827
|
+
if (this.heartbeatInterval) {
|
|
828
|
+
clearInterval(this.heartbeatInterval);
|
|
829
|
+
this.heartbeatInterval = undefined;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (this.eventUnsubscribe) {
|
|
833
|
+
this.eventUnsubscribe();
|
|
834
|
+
this.eventUnsubscribe = undefined;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Submit a bid on a task.
|
|
840
|
+
*/
|
|
841
|
+
async bid(taskId: string, options: { price: number; confidence?: number; proposal?: string }): Promise<TaskBid> {
|
|
842
|
+
return this.client.tasks.bid(taskId, {
|
|
843
|
+
proposedPrice: String(options.price),
|
|
844
|
+
confidenceScore: options.confidence,
|
|
845
|
+
proposal: options.proposal,
|
|
846
|
+
portfolioRefs: [],
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/** Expose the underlying client for advanced use cases */
|
|
851
|
+
getClient(): SwarmDockClient {
|
|
852
|
+
return this.client;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// -- Private helpers --
|
|
856
|
+
|
|
857
|
+
private async handleEvent(event: SSEEvent, agentId: string): Promise<void> {
|
|
858
|
+
if (!this.running) return;
|
|
859
|
+
|
|
860
|
+
const data = event.data as Record<string, unknown>;
|
|
861
|
+
|
|
862
|
+
if (event.type === 'task.assigned' && data.assigneeId === agentId) {
|
|
863
|
+
await this.handleTaskAssigned(data.taskId as string);
|
|
864
|
+
} else if (event.type === 'task.created' && this.taskAvailableHandler) {
|
|
865
|
+
await this.handleTaskCreated(data);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
private async handleTaskAssigned(taskId: string): Promise<void> {
|
|
870
|
+
const detail = await this.client.tasks.get(taskId);
|
|
871
|
+
|
|
872
|
+
// Find the first matching handler based on skill requirements
|
|
873
|
+
let matchedHandler: TaskHandler | undefined;
|
|
874
|
+
for (const skillReq of detail.skillRequirements ?? []) {
|
|
875
|
+
matchedHandler = this.taskHandlers.get(skillReq);
|
|
876
|
+
if (matchedHandler) break;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (!matchedHandler) return;
|
|
880
|
+
|
|
881
|
+
const ctx: TaskContext = {
|
|
882
|
+
id: detail.id,
|
|
883
|
+
title: detail.title,
|
|
884
|
+
description: detail.description ?? '',
|
|
885
|
+
inputData: detail.inputData ?? null,
|
|
886
|
+
inputFiles: detail.inputFiles ?? [],
|
|
887
|
+
skillRequirements: detail.skillRequirements ?? [],
|
|
888
|
+
budgetMax: detail.budgetMax ?? '0',
|
|
889
|
+
|
|
890
|
+
start: async () => {
|
|
891
|
+
await this.client.tasks.start(taskId);
|
|
892
|
+
},
|
|
893
|
+
|
|
894
|
+
complete: async (result: TaskResult) => {
|
|
895
|
+
await this.client.tasks.submit(taskId, {
|
|
896
|
+
artifacts: result.artifacts,
|
|
897
|
+
files: result.files ?? [],
|
|
898
|
+
notes: result.notes,
|
|
899
|
+
});
|
|
900
|
+
},
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
const result = await matchedHandler(ctx);
|
|
904
|
+
|
|
905
|
+
// If the handler returns a result directly, auto-submit it
|
|
906
|
+
if (result && result.artifacts) {
|
|
907
|
+
await ctx.complete(result);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
private async handleTaskCreated(data: Record<string, unknown>): Promise<void> {
|
|
912
|
+
if (!this.taskAvailableHandler) return;
|
|
913
|
+
|
|
914
|
+
const listing: TaskListing = {
|
|
915
|
+
id: data.taskId as string,
|
|
916
|
+
title: (data.title as string) ?? '',
|
|
917
|
+
description: (data.description as string) ?? '',
|
|
918
|
+
skillRequirements: (data.skillRequirements as string[]) ?? [],
|
|
919
|
+
budgetMin: (data.budgetMin as string) ?? null,
|
|
920
|
+
budgetMax: (data.budgetMax as string) ?? '0',
|
|
921
|
+
matchingMode: (data.matchingMode as string) ?? 'manual',
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
await this.taskAvailableHandler(listing);
|
|
925
|
+
}
|
|
926
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { SwarmDockClient } from './client.js';
|
|
1
|
+
export { SwarmDockClient, SwarmDockAgent } from './client.js';
|
|
2
2
|
export type {
|
|
3
3
|
SwarmDockClientOptions,
|
|
4
4
|
RegisterParams,
|
|
@@ -7,6 +7,13 @@ export type {
|
|
|
7
7
|
TransactionsResult,
|
|
8
8
|
TaskListResult,
|
|
9
9
|
TaskDetailResult,
|
|
10
|
+
RatingsSummary,
|
|
11
|
+
PortfolioResult,
|
|
12
|
+
ReputationResult,
|
|
13
|
+
TaskContext,
|
|
14
|
+
TaskResult,
|
|
15
|
+
TaskListing,
|
|
16
|
+
SwarmDockAgentOptions,
|
|
10
17
|
} from './client.js';
|
|
11
18
|
export { SwarmDockError } from './errors.js';
|
|
12
19
|
|
|
@@ -17,9 +24,12 @@ export type {
|
|
|
17
24
|
TaskBid,
|
|
18
25
|
EscrowTransaction,
|
|
19
26
|
AgentRating,
|
|
27
|
+
Dispute,
|
|
20
28
|
AATPayload,
|
|
21
29
|
AgentCard,
|
|
22
30
|
AgentCardSkill,
|
|
31
|
+
PortfolioItem,
|
|
32
|
+
StoredArtifactRef,
|
|
23
33
|
SSEEvent,
|
|
24
34
|
AgentUpdateInput,
|
|
25
35
|
TaskCreateInput,
|