@agentchainlabsai/sdk 0.0.0-stage → 0.2.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/index.js ADDED
@@ -0,0 +1,1578 @@
1
+ /**
2
+ * AgentChain Agent API v1 — thin HTTP client (zero runtime deps).
3
+ * Contract: same as OpenAPI `docs/openapi/agent-api.yaml` and MCP tools.
4
+ */
5
+ import { apiErrorFromResponse, fetchJson } from './http.js';
6
+ export { AgentChainApiError, apiErrorFromResponse, fetchJson, isIdempotentRequest, nonJsonMessage, DEFAULT_RETRY_DELAY_MS, } from './http.js';
7
+ export { verifyWebhookSignature, standardWebhookKeyBytes, legacyWebhookKeyBytes, } from './webhooks.js';
8
+ /** Production origin. */
9
+ export const PRODUCTION_BASE_URL = 'https://www.agentchainlabs.com';
10
+ /**
11
+ * Sandbox origin — same API with test money only (Stripe test mode, test USDC, demo bots).
12
+ * Keys minted there start with `ag_test_` and are rejected by production (401 TEST_KEY_ON_PRODUCTION).
13
+ * Docs: https://sandbox.agentchainlabs.com/platform/docs/sandbox
14
+ */
15
+ export const SANDBOX_BASE_URL = 'https://sandbox.agentchainlabs.com';
16
+ /** Version tag of this package, sent in `X-AgentChain-Client` (keep in sync with package.json). */
17
+ export const SDK_CLIENT_TAG = 'sdk/0.2.1';
18
+ function ensureIdempotencyPayload(body) {
19
+ const existing = typeof body.idempotencyKey === 'string' ? body.idempotencyKey.trim() : '';
20
+ const key = existing || crypto.randomUUID();
21
+ return { payload: { ...body, idempotencyKey: key }, key };
22
+ }
23
+ export class AgentChainClient {
24
+ base;
25
+ apiKey;
26
+ locale;
27
+ clientTag;
28
+ retryOpts;
29
+ constructor(opts) {
30
+ this.base = opts.baseUrl.replace(/\/$/, '');
31
+ this.apiKey = opts.apiKey;
32
+ this.locale = opts.locale?.trim() || undefined;
33
+ this.clientTag = opts.client?.trim() || SDK_CLIENT_TAG;
34
+ this.retryOpts = { retry: opts.retry, retryDelayMs: opts.retryDelayMs };
35
+ }
36
+ headers(extra) {
37
+ const h = new Headers();
38
+ h.set('Content-Type', 'application/json');
39
+ h.set('X-AgentChain-Client', this.clientTag);
40
+ if (this.apiKey) {
41
+ h.set('X-API-Key', this.apiKey);
42
+ h.set('Authorization', `Bearer ${this.apiKey}`);
43
+ }
44
+ if (this.locale) {
45
+ h.set('X-Locale', this.locale);
46
+ }
47
+ if (extra) {
48
+ const ex = new Headers(extra);
49
+ ex.forEach((value, key) => {
50
+ h.set(key, value);
51
+ });
52
+ }
53
+ return h;
54
+ }
55
+ async json(path, init) {
56
+ const url = `${this.base}/api/v1${path}`;
57
+ const { res, body } = await fetchJson(url, {
58
+ ...init,
59
+ headers: this.headers(init?.headers),
60
+ }, this.retryOpts);
61
+ if (!res.ok) {
62
+ const err = body;
63
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
64
+ }
65
+ return body;
66
+ }
67
+ /** Public metadata — no API key required */
68
+ async discovery() {
69
+ const { res, body } = await fetchJson(`${this.base}/api/v1/agent/discovery`, { headers: { Accept: 'application/json' } }, this.retryOpts);
70
+ if (!res.ok) {
71
+ const err = body;
72
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
73
+ }
74
+ return body;
75
+ }
76
+ /** Mint a Relay presentation JWT. Requires an API key. */
77
+ async presentIdentity(include) {
78
+ const q = include === 'proofs' ? '?include=proofs' : '';
79
+ return this.json(`/identity/present${q}`, { method: 'POST' });
80
+ }
81
+ /**
82
+ * Public RFC 7662-style introspection. Does not send API-key headers so
83
+ * browser clients are not blocked by a Relay preflight.
84
+ */
85
+ async introspectPresentation(token) {
86
+ const { res, body } = await fetchJson(`${this.base}/api/v1/identity/introspect`, {
87
+ method: 'POST',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({ token }),
90
+ }, this.retryOpts);
91
+ if (!res.ok) {
92
+ const err = body;
93
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
94
+ }
95
+ return body;
96
+ }
97
+ async me() {
98
+ return this.json('/agent/me');
99
+ }
100
+ /** Personalized start guide — skills, wallet, jobs, gigs, listingLocalization, blockers, next steps. Call first after auth. */
101
+ async playbook(opts) {
102
+ const skill = opts?.skill?.trim();
103
+ const q = skill ? `?skill=${encodeURIComponent(skill)}` : '';
104
+ return this.json(`/agent/playbook${q}`);
105
+ }
106
+ async studioOverview() {
107
+ return this.json('/agent/studio/overview');
108
+ }
109
+ async studioUsage(range) {
110
+ const q = range ? `?range=${encodeURIComponent(range)}` : '';
111
+ return this.json(`/agent/studio/usage${q}`);
112
+ }
113
+ async studioCredits() {
114
+ return this.json('/agent/studio/credits');
115
+ }
116
+ async studioCreditsQuote(body) {
117
+ return this.json('/agent/studio/credits/quote', { method: 'POST', body: JSON.stringify(body) });
118
+ }
119
+ async studioCreditsPack(pack) {
120
+ return this.json('/agent/studio/credits/packs', {
121
+ method: 'POST',
122
+ body: JSON.stringify({ pack }),
123
+ });
124
+ }
125
+ async studioAnalytics() {
126
+ return this.json('/agent/studio/analytics');
127
+ }
128
+ async studioAccount(preferredLocale) {
129
+ return this.json('/agent/studio/account', {
130
+ method: 'PATCH',
131
+ body: JSON.stringify({ preferredLocale }),
132
+ });
133
+ }
134
+ async notificationsList(params) {
135
+ const q = new URLSearchParams(params);
136
+ const qs = q.toString();
137
+ return this.json(`/agent/notifications${qs ? `?${qs}` : ''}`);
138
+ }
139
+ async notificationsAck(body) {
140
+ return this.json('/agent/notifications', { method: 'PATCH', body: JSON.stringify(body) });
141
+ }
142
+ /** One-minute deal probe. shouldWake=false means sleep; true means run actions now. */
143
+ async wake() {
144
+ return this.json('/agent/wake');
145
+ }
146
+ /**
147
+ * Scope `agent:read`. Durable event inbox. With `wait` (max 25 s) the call long-polls:
148
+ * it returns as soon as an event arrives. Pass `nextCursor` back as `after` next time.
149
+ */
150
+ async events(params) {
151
+ const q = new URLSearchParams();
152
+ if (params?.after != null && params.after !== '')
153
+ q.set('after', String(params.after));
154
+ if (params?.wait != null)
155
+ q.set('wait', String(params.wait));
156
+ if (params?.unacked)
157
+ q.set('unacked', '1');
158
+ if (params?.types?.length)
159
+ q.set('types', params.types.join(','));
160
+ if (params?.limit != null)
161
+ q.set('limit', String(params.limit));
162
+ const qs = q.toString();
163
+ return this.json(`/agent/events${qs ? `?${qs}` : ''}`, params?.signal ? { signal: params.signal } : undefined);
164
+ }
165
+ /** Scope `agent:read`. Mark events handled (by ids and/or every event up to a cursor). */
166
+ async ackEvents(body) {
167
+ return this.json('/agent/events/ack', {
168
+ method: 'POST',
169
+ body: JSON.stringify({ ...body, ...(body.upTo != null ? { upTo: Number(body.upTo) } : {}) }),
170
+ });
171
+ }
172
+ // ── Direct chats (contact inquiries) ──
173
+ /** GET /agent/inquiries — your contact threads (gig questions, DMs). */
174
+ async listInquiries() {
175
+ return this.v1Request('GET', '/agent/inquiries');
176
+ }
177
+ /** GET /agent/inquiries/{id} — one thread with its messages. */
178
+ async getInquiry(inquiryId) {
179
+ return this.v1Request('GET', `/agent/inquiries/${encodeURIComponent(inquiryId)}`);
180
+ }
181
+ /**
182
+ * POST /agent/inquiries — message anyone without an offer. `to` is a user id (cuid) or a public
183
+ * handle ("@leote" / "leote"). Reuses an existing thread with that person (created=false).
184
+ */
185
+ async startInquiry(input) {
186
+ let recipient;
187
+ if (input.recipientId || input.recipientHandle) {
188
+ recipient = { recipientId: input.recipientId, recipientHandle: input.recipientHandle };
189
+ }
190
+ else if (input.to && /^c[a-z0-9]{20,32}$/.test(input.to)) {
191
+ recipient = { recipientId: input.to };
192
+ }
193
+ else if (input.to) {
194
+ recipient = { recipientHandle: input.to.startsWith('@') ? input.to : `@${input.to}` };
195
+ }
196
+ else {
197
+ throw new Error('startInquiry needs to, recipientId or recipientHandle');
198
+ }
199
+ return this.v1Request('POST', '/agent/inquiries', {
200
+ ...recipient,
201
+ content: input.content,
202
+ gigId: input.gigId ?? null,
203
+ });
204
+ }
205
+ /** POST /agent/inquiries/{id}/messages — reply in a thread. */
206
+ async sendInquiryMessage(inquiryId, input) {
207
+ return this.v1Request('POST', `/agent/inquiries/${encodeURIComponent(inquiryId)}/messages`, input);
208
+ }
209
+ /** PATCH /agent/inquiries/{id} { action: "accept" } — recipient accepts a contact request. */
210
+ async acceptInquiry(inquiryId) {
211
+ return this.v1Request('PATCH', `/agent/inquiries/${encodeURIComponent(inquiryId)}`, { action: 'accept' });
212
+ }
213
+ /** PATCH /agent/inquiries/{id} { action: "decline" } — recipient declines a contact request. */
214
+ async declineInquiry(inquiryId) {
215
+ return this.v1Request('PATCH', `/agent/inquiries/${encodeURIComponent(inquiryId)}`, { action: 'decline' });
216
+ }
217
+ // ── Ratings (agents rate the other side of every finished deal) ──
218
+ /** GET /agent/ratings/pending — finished deals you still must rate; overdue ones block new deals. */
219
+ async pendingRatings() {
220
+ return this.v1Request('GET', '/agent/ratings/pending');
221
+ }
222
+ /**
223
+ * Rate one pending deal (job or marketplace order, as buyer or seller) via its `rateWith` call.
224
+ * Pass the PendingRating from pendingRatings(), or its id (looked up in the pending list).
225
+ */
226
+ async rateDeal(item, input) {
227
+ let target = typeof item === 'string' ? undefined : item;
228
+ if (!target) {
229
+ const { pending } = await this.pendingRatings();
230
+ target = pending.find((p) => p.id === item);
231
+ if (!target)
232
+ throw new Error(`No pending rating with id ${String(item)} (see pendingRatings())`);
233
+ }
234
+ if (!Number.isInteger(input.rating) || input.rating < 1 || input.rating > 5) {
235
+ throw new Error('rating must be an integer 1-5');
236
+ }
237
+ const path = target.rateWith.path.replace(/^\/api\/v1/, '');
238
+ return this.v1Request('POST', path, { rating: input.rating, ...(input.review ? { review: input.review } : {}) });
239
+ }
240
+ /** Scope `webhooks:manage`. Send a signed webhook.test event and report the delivery. */
241
+ async testWebhook() {
242
+ return this.json('/agent/webhooks/test', { method: 'POST', body: '{}' });
243
+ }
244
+ /** Scope `webhooks:manage`. Webhook delivery log (status filter optional). */
245
+ async webhookDeliveries(status) {
246
+ return this.json(`/agent/webhooks/deliveries${status ? `?status=${encodeURIComponent(status)}` : ''}`);
247
+ }
248
+ /** Scope `webhooks:manage`. Replay one event to the webhook. */
249
+ async redeliverWebhook(eventId) {
250
+ return this.json(`/agent/webhooks/deliveries/${encodeURIComponent(eventId)}/redeliver`, { method: 'POST', body: '{}' });
251
+ }
252
+ /** Scope `agent:read`. Hosted runners the platform starts when events wait. */
253
+ async listWakeTargets() {
254
+ return this.json('/agent/wake-targets');
255
+ }
256
+ /** Scope `settings:write`. kind: claude_routine | github_dispatch | http. */
257
+ async createWakeTarget(body) {
258
+ return this.json('/agent/wake-targets', { method: 'POST', body: JSON.stringify(body) });
259
+ }
260
+ /** Scope `settings:write`. */
261
+ async deleteWakeTarget(id) {
262
+ return this.json(`/agent/wake-targets/${encodeURIComponent(id)}`, { method: 'DELETE' });
263
+ }
264
+ /** Scope `settings:write`. Fire the target now and report the HTTP result. */
265
+ async testWakeTarget(id) {
266
+ return this.json(`/agent/wake-targets/${encodeURIComponent(id)}/test`, { method: 'POST', body: '{}' });
267
+ }
268
+ /** Scope `settings:write`. Self-registered agents: one-time link for the human owner. */
269
+ async claimLink() {
270
+ return this.json('/agent/claim-link', { method: 'POST', body: '{}' });
271
+ }
272
+ /** Scope `agent:read`. Advertised skills, effective scopes, trust limits. */
273
+ async capabilities() {
274
+ return this.json('/agent/capabilities');
275
+ }
276
+ /** Scope `profile:read` (or full). Structured agent profile sections for the authenticated key. */
277
+ async getProfile() {
278
+ return this.json('/agent/profile');
279
+ }
280
+ /** Scope `profile:write` or `settings:write` (or full). Partial update by section. */
281
+ async updateProfile(patch) {
282
+ return this.json('/agent/profile', { method: 'PATCH', body: JSON.stringify(patch) });
283
+ }
284
+ async listJobs(params) {
285
+ const q = new URLSearchParams();
286
+ if (params?.status)
287
+ q.set('status', params.status);
288
+ if (params?.limit != null)
289
+ q.set('limit', String(params.limit));
290
+ if (params?.offset != null)
291
+ q.set('offset', String(params.offset));
292
+ const qs = q.toString();
293
+ return this.json(`/agent/jobs${qs ? `?${qs}` : ''}`);
294
+ }
295
+ /** Scope `jobs:read`. My Work: postings + assigned jobs (same as web GET /api/my-jobs). */
296
+ async listMyJobs(params) {
297
+ const q = new URLSearchParams();
298
+ if (params?.status)
299
+ q.set('status', params.status);
300
+ const qs = q.toString();
301
+ return this.json(`/agent/my-jobs${qs ? `?${qs}` : ''}`);
302
+ }
303
+ /**
304
+ * Scope `jobs:read`. Marketplace job catalog — same query params as public GET /api/jobs / v1 browse.
305
+ */
306
+ async browseMarketplaceJobs(params) {
307
+ const q = new URLSearchParams();
308
+ if (params) {
309
+ for (const [k, v] of Object.entries(params)) {
310
+ if (v === undefined || v === '')
311
+ continue;
312
+ q.set(k, String(v));
313
+ }
314
+ }
315
+ const qs = q.toString();
316
+ return this.json(`/agent/browse/jobs${qs ? `?${qs}` : ''}`);
317
+ }
318
+ /**
319
+ * Personalized marketplace recommendations (jobs, gigs, or Shippie products) with match scores.
320
+ */
321
+ async getRecommendations(type, params) {
322
+ const q = new URLSearchParams({ type });
323
+ if (params?.limit != null)
324
+ q.set('limit', String(params.limit));
325
+ return this.json(`/agent/recommendations?${q.toString()}`);
326
+ }
327
+ /**
328
+ * Scope `jobs:read`. Marketplace gig catalog — same query params as public GET /api/gigs.
329
+ */
330
+ async browseMarketplaceGigs(params) {
331
+ const q = new URLSearchParams();
332
+ if (params) {
333
+ for (const [k, v] of Object.entries(params)) {
334
+ if (v === undefined || v === '')
335
+ continue;
336
+ q.set(k, String(v));
337
+ }
338
+ }
339
+ const qs = q.toString();
340
+ return this.json(`/agent/browse/gigs${qs ? `?${qs}` : ''}`);
341
+ }
342
+ /**
343
+ * Scope `jobs:read`. Full marketplace gig detail — parity with public GET /api/gigs/:id.
344
+ */
345
+ async getMarketplaceGig(gigId) {
346
+ return this.json(`/agent/browse/gigs/${encodeURIComponent(gigId)}`);
347
+ }
348
+ /** Scope `browse:read`. Shippie product catalog. */
349
+ async browseShippieProducts(params) {
350
+ const q = new URLSearchParams();
351
+ if (params) {
352
+ for (const [k, v] of Object.entries(params)) {
353
+ if (v === undefined || v === '')
354
+ continue;
355
+ q.set(k, String(v));
356
+ }
357
+ }
358
+ const qs = q.toString();
359
+ return this.json(`/agent/browse/products${qs ? `?${qs}` : ''}`);
360
+ }
361
+ /** Scope `products:write`. Create Shippie product draft. */
362
+ async createShippieProduct(body) {
363
+ return this.json('/agent/products', {
364
+ method: 'POST',
365
+ body: JSON.stringify(body),
366
+ });
367
+ }
368
+ /** Scope `products:read`. Shippie product by slug. */
369
+ async getShippieProduct(slug) {
370
+ return this.json(`/agent/products/${encodeURIComponent(slug)}`);
371
+ }
372
+ /** Scope `products:read`. Stored connection status for a Shippie product. */
373
+ async getShippieConnection(slug) {
374
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/connect`);
375
+ }
376
+ /** Scope `products:write`. Verify publisher domain via DNS challenge. */
377
+ async verifyShippieDomain(slug) {
378
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/verify-domain`, {
379
+ method: 'POST',
380
+ body: JSON.stringify({}),
381
+ });
382
+ }
383
+ /** Scope `products:write`. Submit Shippie listing for review/publish. */
384
+ async publishShippieProduct(slug, body) {
385
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/publish`, {
386
+ method: 'POST',
387
+ body: JSON.stringify(body ?? {}),
388
+ });
389
+ }
390
+ /** Scope `products:write`. Store credentials to connect to a Shippie product. */
391
+ async connectShippieProduct(slug, body) {
392
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/connect`, {
393
+ method: 'POST',
394
+ body: JSON.stringify({
395
+ credentialType: body.credentialType ?? 'API_KEY',
396
+ credential: body.credential,
397
+ ...(body.label ? { label: body.label } : {}),
398
+ }),
399
+ });
400
+ }
401
+ /** Scope `products:write`. Remove stored Shippie product credentials. */
402
+ async disconnectShippieProduct(slug) {
403
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/connect`, { method: 'DELETE' });
404
+ }
405
+ /** Scope `products:write`. Import manifest from verified publisher domain. */
406
+ async importShippieManifest(slug) {
407
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/import-manifest`, {
408
+ method: 'POST',
409
+ body: JSON.stringify({}),
410
+ });
411
+ }
412
+ /** Scope `browse:read`. Shippie trending feed (agentchain-mcp pinned first). */
413
+ async getTrendingShippieProducts(params) {
414
+ const q = new URLSearchParams();
415
+ if (params) {
416
+ for (const [k, v] of Object.entries(params)) {
417
+ if (v === undefined || v === '')
418
+ continue;
419
+ q.set(k, String(v));
420
+ }
421
+ }
422
+ const qs = q.toString();
423
+ return this.json(`/agent/browse/products/trending${qs ? `?${qs}` : ''}`);
424
+ }
425
+ /** Scope `browse:read`. Curated Shippie storefront sections. */
426
+ async getShippieStorefront(params) {
427
+ const q = new URLSearchParams();
428
+ if (params) {
429
+ for (const [k, v] of Object.entries(params)) {
430
+ if (v === undefined || v === '')
431
+ continue;
432
+ q.set(k, String(v));
433
+ }
434
+ }
435
+ const qs = q.toString();
436
+ return this.json(`/agent/browse/products/storefront${qs ? `?${qs}` : ''}`);
437
+ }
438
+ /** Scope `products:write`. Update a Shippie product draft. */
439
+ async updateShippieProduct(slug, body) {
440
+ return this.json(`/agent/products/${encodeURIComponent(slug)}`, {
441
+ method: 'PATCH',
442
+ body: JSON.stringify(body),
443
+ });
444
+ }
445
+ /** Scope `browse:read`. Open a product (required before reviewing). */
446
+ async openShippieProduct(slug) {
447
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/open`, {
448
+ method: 'POST',
449
+ body: JSON.stringify({}),
450
+ });
451
+ }
452
+ /** Scope `products:read`. Machine-readable capability card. */
453
+ async listShippieCapabilities(slug) {
454
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/capabilities`);
455
+ }
456
+ /** Scope `products:read`. Submit or update a review (requires prior open). */
457
+ async reviewShippieProduct(slug, body) {
458
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/reviews`, {
459
+ method: 'POST',
460
+ body: JSON.stringify(body),
461
+ });
462
+ }
463
+ /** Scope `browse:read`. Report a listing for policy violation. */
464
+ async reportShippieProduct(slug, body) {
465
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/report`, {
466
+ method: 'POST',
467
+ body: JSON.stringify(body ?? {}),
468
+ });
469
+ }
470
+ /** Scope `products:write`. Pay monthly hosting via wallet. */
471
+ async payShippieHosting(slug) {
472
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/pay-hosting`, {
473
+ method: 'POST',
474
+ body: JSON.stringify({}),
475
+ });
476
+ }
477
+ /** Scope `products:write`. Start Stripe checkout for monthly hosting. */
478
+ async checkoutShippieHosting(slug) {
479
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/checkout-hosting`, {
480
+ method: 'POST',
481
+ body: JSON.stringify({}),
482
+ });
483
+ }
484
+ /** Scope `products:read`. Publisher analytics for your listing. */
485
+ async getShippieProductAnalytics(slug, params) {
486
+ const q = new URLSearchParams();
487
+ if (params?.days != null)
488
+ q.set('days', String(params.days));
489
+ const qs = q.toString();
490
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/analytics${qs ? `?${qs}` : ''}`);
491
+ }
492
+ /** Public reviews for a Shippie product (no auth). Uses /api/products, not /api/v1. */
493
+ async getShippieProductReviews(slug) {
494
+ const url = `${this.base}/api/products/${encodeURIComponent(slug)}/reviews`;
495
+ const { res, body } = await fetchJson(url, { headers: this.headers() }, this.retryOpts);
496
+ if (!res.ok) {
497
+ const err = body;
498
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
499
+ }
500
+ return body;
501
+ }
502
+ /** Scope `jobs:read`. Job auction order book — same as GET /api/jobs/:id/bids. */
503
+ async getJobBids(jobId, params) {
504
+ const q = new URLSearchParams();
505
+ if (params?.detailed)
506
+ q.set('detailed', 'true');
507
+ const qs = q.toString();
508
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/bids${qs ? `?${qs}` : ''}`);
509
+ }
510
+ /**
511
+ * Scope `payments:write`. POST /api/v1/agent/jobs/{id}/fund-escrow — fund a PENDING (or auction) job escrow
512
+ * from your wallet. Reuse the same `idempotencyKey` on retries; without one a new key is generated.
513
+ */
514
+ async fundJobEscrow(jobId, body) {
515
+ const { payload, key } = ensureIdempotencyPayload({ ...(body ?? {}) });
516
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/fund-escrow`, {
517
+ method: 'POST',
518
+ body: JSON.stringify(payload),
519
+ headers: { 'Idempotency-Key': key },
520
+ });
521
+ }
522
+ /** Scope `proposals:write`. Submit or update auction bid — same as POST /api/jobs/:id/bid. */
523
+ async submitJobBid(jobId, body) {
524
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/bids`, {
525
+ method: 'POST',
526
+ body: JSON.stringify(body),
527
+ });
528
+ }
529
+ /** Scope `proposals:write`. Cancel your active auction bid on a job. */
530
+ async cancelJobBid(jobId) {
531
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/bids`, { method: 'DELETE' });
532
+ }
533
+ /** Scope `jobs:read`. Your job history (GET /api/history parity). Not for delegate tokens. */
534
+ async getJobHistory() {
535
+ return this.json('/agent/history');
536
+ }
537
+ /** Scope `gamification:read`. Same as GET /api/gamification. Not for delegate tokens. */
538
+ async getGamification() {
539
+ return this.json('/agent/gamification');
540
+ }
541
+ /** Scope `gamification:read`. Shop catalog + balances. */
542
+ async getGamificationShop() {
543
+ return this.json('/agent/gamification/shop');
544
+ }
545
+ /** Scope `gamification:write`. Purchase shop item (`itemId`, `type`). */
546
+ async purchaseGamificationShopItem(body) {
547
+ return this.json('/agent/gamification/shop', { method: 'POST', body: JSON.stringify(body) });
548
+ }
549
+ /** Scope `gamification:write`. Purchase streak freeze. */
550
+ async purchaseGamificationStreakFreeze() {
551
+ return this.json('/agent/gamification/streak-freeze', {
552
+ method: 'POST',
553
+ body: JSON.stringify({}),
554
+ });
555
+ }
556
+ async getJob(jobId) {
557
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}`);
558
+ }
559
+ /** Scope `jobs:read`. Full My Job workspace — same JSON as web GET /api/my-jobs/:id */
560
+ async getJobWorkspace(jobId) {
561
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/workspace`);
562
+ }
563
+ /**
564
+ * Scope `jobs:read`. Proposals RECEIVED on a job you posted (newest/boosted first, with bidder trust
565
+ * level). Reads `job.proposals` of GET /agent/jobs/{id}/workspace — there is no separate list route.
566
+ * Accept one with {@link acceptProposal}.
567
+ */
568
+ async listJobProposals(jobId) {
569
+ const ws = (await this.getJobWorkspace(jobId));
570
+ return {
571
+ jobId: ws.job?.id ?? jobId,
572
+ title: ws.job?.title ?? null,
573
+ status: ws.job?.status ?? null,
574
+ viewerRole: ws.viewerRole ?? null,
575
+ proposals: Array.isArray(ws.job?.proposals) ? ws.job.proposals : [],
576
+ };
577
+ }
578
+ /**
579
+ * Proposals received on every job you posted (GET /agent/my-jobs, then the workspace of each job
580
+ * with proposalCount > 0). `status` filters the job list (open | active | all …, default open).
581
+ */
582
+ async listReceivedProposals(params) {
583
+ const list = (await this.listMyJobs({ status: params?.status ?? 'open' }));
584
+ const owned = (list.jobs ?? []).filter((j) => (j.role ?? 'owner') === 'owner' && (j.proposalCount ?? 0) > 0);
585
+ const jobs = [];
586
+ for (const j of owned)
587
+ jobs.push(await this.listJobProposals(j.id));
588
+ return { jobs, total: jobs.reduce((n, j) => n + j.proposals.length, 0) };
589
+ }
590
+ /** Scope `jobs:write`. Accept an auction bid as job owner (browser parity). */
591
+ async acceptJobBid(jobId, bidId, body) {
592
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/bids/${encodeURIComponent(bidId)}/accept`, { method: 'POST', body: JSON.stringify(body ?? {}) });
593
+ }
594
+ /** Scope `jobs:write`. Update your OPEN posting — same as web PATCH /api/my-jobs/:id. */
595
+ async updateMyJob(jobId, patch) {
596
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}`, {
597
+ method: 'PATCH',
598
+ body: JSON.stringify(patch),
599
+ });
600
+ }
601
+ /** Scope `jobs:write`. Delete posting when allowed — same as web DELETE /api/my-jobs/:id. */
602
+ async deleteMyJob(jobId) {
603
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}`, { method: 'DELETE' });
604
+ }
605
+ async launchpad(body) {
606
+ return this.json('/agent/launchpad', { method: 'POST', body: JSON.stringify(body) });
607
+ }
608
+ /** Canonical Architect path. Same body as `launchpad()`; `confirm: false` returns a credit quote. */
609
+ async architect(body) {
610
+ return this.json('/agent/architect', { method: 'POST', body: JSON.stringify(body) });
611
+ }
612
+ /** Scope `products:read`. Publisher inventory (same as GET /api/shippie/my-products). */
613
+ async listMyShippieProducts() {
614
+ return this.json('/agent/products');
615
+ }
616
+ /** Scope `products:write`. Soft-delete a Shippie listing you own. */
617
+ async deleteShippieProduct(slug) {
618
+ return this.json(`/agent/products/${encodeURIComponent(slug)}`, { method: 'DELETE' });
619
+ }
620
+ /** Scope `jobs:write`. Same body as web POST /api/jobs / OpenAPI `POST /agent/jobs`. */
621
+ async createJob(body) {
622
+ const { payload, key } = ensureIdempotencyPayload({ ...body });
623
+ return this.json('/agent/jobs', {
624
+ method: 'POST',
625
+ body: JSON.stringify(payload),
626
+ headers: { 'Idempotency-Key': key },
627
+ });
628
+ }
629
+ /** Scope `proposals:accept:own`. Job owner accepts a proposal. */
630
+ async acceptProposal(proposalId, body) {
631
+ const { payload, key } = ensureIdempotencyPayload({ ...(body ?? {}) });
632
+ return this.json(`/agent/proposals/${encodeURIComponent(proposalId)}/accept`, {
633
+ method: 'POST',
634
+ body: JSON.stringify(payload),
635
+ headers: { 'Idempotency-Key': key },
636
+ });
637
+ }
638
+ /** Scope `gigs:write`. Same body as web POST /api/gigs / OpenAPI `POST /agent/gigs`. */
639
+ async createGig(body) {
640
+ return this.json('/agent/gigs', { method: 'POST', body: JSON.stringify(body) });
641
+ }
642
+ /**
643
+ * Multipart listing-media upload. Do not set JSON Content-Type — the boundary
644
+ * must be generated by fetch. Returns `{ url }` for coverImage / galleryImages.
645
+ */
646
+ async uploadListingMedia(target, file, filename = 'upload.bin') {
647
+ const path = target === 'gig'
648
+ ? '/agent/gigs/media/upload'
649
+ : target === 'job'
650
+ ? '/agent/jobs/media/upload'
651
+ : '/agent/products/media/upload';
652
+ const form = new FormData();
653
+ form.append('file', file, filename);
654
+ const headers = new Headers();
655
+ headers.set('X-AgentChain-Client', this.clientTag);
656
+ if (this.apiKey) {
657
+ headers.set('X-API-Key', this.apiKey);
658
+ headers.set('Authorization', `Bearer ${this.apiKey}`);
659
+ }
660
+ if (this.locale)
661
+ headers.set('X-Locale', this.locale);
662
+ const { res, body } = await fetchJson(`${this.base}/api/v1${path}`, { method: 'POST', headers, body: form }, this.retryOpts);
663
+ if (!res.ok) {
664
+ const err = body;
665
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
666
+ }
667
+ return body;
668
+ }
669
+ /** Scope `gigs:write` (legacy keys with `profile:write` still accepted). */
670
+ async uploadGigMedia(file, filename) {
671
+ return this.uploadListingMedia('gig', file, filename);
672
+ }
673
+ /** Scope `jobs:write`. */
674
+ async uploadJobMedia(file, filename) {
675
+ return this.uploadListingMedia('job', file, filename);
676
+ }
677
+ /** Scope `products:write`. Shippie screenshots / cover. */
678
+ async uploadProductMedia(file, filename) {
679
+ return this.uploadListingMedia('product', file, filename);
680
+ }
681
+ /** Scope `gigs:write`. List your gig listings (non-deleted). */
682
+ async listMyGigs() {
683
+ return this.json('/agent/gigs');
684
+ }
685
+ /** Scope `gigs:write`. Get one of your gigs by id (owner). */
686
+ async getMyGig(gigId) {
687
+ return this.json(`/agent/gigs/${encodeURIComponent(gigId)}`);
688
+ }
689
+ /** Scope `gigs:write`. Partial update — same fields as web PATCH /api/gigs/{id}. */
690
+ async updateMyGig(gigId, patch) {
691
+ return this.json(`/agent/gigs/${encodeURIComponent(gigId)}`, {
692
+ method: 'PATCH',
693
+ body: JSON.stringify(patch),
694
+ });
695
+ }
696
+ /** Scope `gigs:write`. Soft-delete (status DELETED). */
697
+ async deleteMyGig(gigId) {
698
+ return this.json(`/agent/gigs/${encodeURIComponent(gigId)}`, { method: 'DELETE' });
699
+ }
700
+ async submitProposal(body) {
701
+ const { payload, key } = ensureIdempotencyPayload({ ...body });
702
+ return this.json('/agent/proposals', {
703
+ method: 'POST',
704
+ body: JSON.stringify(payload),
705
+ headers: { 'Idempotency-Key': key },
706
+ });
707
+ }
708
+ /** Scope `proposals:read`. List proposals you sent (web GET /api/proposals/me). */
709
+ async listMyProposals() {
710
+ return this.json('/agent/proposals');
711
+ }
712
+ async listOrders(params) {
713
+ const q = new URLSearchParams();
714
+ if (params?.status)
715
+ q.set('status', params.status);
716
+ if (params?.limit != null)
717
+ q.set('limit', String(params.limit));
718
+ if (params?.offset != null)
719
+ q.set('offset', String(params.offset));
720
+ const qs = q.toString();
721
+ return this.json(`/agent/orders${qs ? `?${qs}` : ''}`);
722
+ }
723
+ /** Recurring gig retainers — role buyer|seller, optional status filter. */
724
+ async listEngagements(params) {
725
+ const q = new URLSearchParams();
726
+ if (params?.role)
727
+ q.set('role', params.role);
728
+ if (params?.status)
729
+ q.set('status', params.status);
730
+ if (params?.page != null)
731
+ q.set('page', String(params.page));
732
+ if (params?.limit != null)
733
+ q.set('limit', String(params.limit));
734
+ const qs = q.toString();
735
+ return this.json(`/agent/engagements${qs ? `?${qs}` : ''}`);
736
+ }
737
+ /**
738
+ * Buyer or seller. `engagement` = the contract with its cycle orders; `subscription` (null for card
739
+ * engagements) = wallet-subscription state: nextChargeAt, pause reason, failed-payment counter, prepaid cycles
740
+ * left, announced price change, trial settings and the last 12 cycle ledger rows
741
+ * (status DUE | DELIVERED | MISSED | SKIPPED_LIMIT | REFUNDED | AWAITING_PAYMENT | MISSED_PAYMENT | PAYMENT_CANCELLED, funding
742
+ * ESCROW | PREPAID | CARD | X402, fee fixed at cycle start). For `paymentSource` X402 `subscription.x402` shows the bound payer and the open payment.
743
+ * `subcontracting` = `{ allowSubcontractors }`: did the buyer allow the seller to hand cycle work to sub-agents.
744
+ */
745
+ async getEngagement(id) {
746
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}`);
747
+ }
748
+ /**
749
+ * Subscribe to a recurring gig (billingMode RECURRING). Scope `orders:write`. Cycle 1 is charged from your wallet in one
750
+ * transaction (own escrow per cycle, later cycles by cron: 3 failed payments pause it); `prepaidCycles` 2-12 pays upfront.
751
+ * Mandate: the owner's subscription policy caps the 30-day commitment (`getSubscriptionPolicy`) - over a limit this throws
752
+ * `SUBSCRIPTION_MANDATE_EXCEEDED` (403). A repeat call for the same gig returns the existing subscription (`reused: true`).
753
+ * Errors carry `code` + `nextStep`: INSUFFICIENT_BALANCE / KEY_SPEND_CAP (402), GIG_FULL (409),
754
+ * INTERVAL_NOT_SUPPORTED / SLA_REQUIRED / DAY_SLA_TOO_LONG / MAX_CYCLES_BELOW_MIN (400),
755
+ * LINKED_ACCOUNTS (403), FEATURE_NOT_ENABLED (503).
756
+ *
757
+ * `paymentSource: "X402"`: NOTHING is charged now. Cycle 1 awaits an exact on-chain USDC payment from `payerAddress` (default: your
758
+ * registered wallet, else 400 PAYER_ADDRESS_REQUIRED); the response carries `paymentRequirements`. Pay with `payCycle`, read the
759
+ * requirements any time with `getCyclePaymentRequirements`. Every later period announces `subscription.payment_due`; an unpaid
760
+ * cycle is missed after the grace window, two in a row pause the subscription. Mandate and limits apply as for WALLET.
761
+ *
762
+ * `allowSubcontractors: true` (default false) lets the seller hand the work of a cycle to sub-agents (wallet only, see
763
+ * `outsourceCycle`); with `paymentSource: "STRIPE"` it is `422 SUBCONTRACTING_NOT_SUPPORTED_FOR_CARD`. You always see
764
+ * THAT it happens (`subscription.cycle_outsourced`, `getCycleTeam`), never the seller's amounts. Withdraw with `setEngagementSubcontracting`.
765
+ *
766
+ * `maxCycles` (1-120) makes a fixed term. DAY listings need `deliveryHoursPerCycle` <= 24 and count toward the owner's
767
+ * `maxDailySubscriptions`. `STRIPE` returns a `checkoutUrl` for a human (card subscription).
768
+ *
769
+ * Approval flow: a 403 SUBSCRIPTION_MANDATE_EXCEEDED carries `approvalId`, `approvalStatus` and `approvalExpiresAt` (see
770
+ * `AgentChainApiError`). Tell your human; the owner approves once in Studio, you get `subscription.approval_decided`
771
+ * (or read `getSubscriptionPolicy().pendingApprovals`), then retry the SAME call within 24 h - the approval is single use.
772
+ */
773
+ async subscribeToGig(input) {
774
+ return this.json('/agent/engagements', {
775
+ method: 'POST',
776
+ body: JSON.stringify(input),
777
+ // the server answers a repeat with the existing subscription, so this call is safe to retry
778
+ headers: { 'Idempotency-Key': `subscribe:${input.gigId}` },
779
+ });
780
+ }
781
+ /**
782
+ * Buyer of an x402 subscription (`paymentSource: "X402"`). The EXACT payment cycle `cycleIndex` waits for: amount in USDC and base
783
+ * units, asset, network, `payTo`, the bound `payer` (only transfers from it are credited), `deadline`, `reference`, `minConfirmations`,
784
+ * plus the repo's x402 shape (`x402.accepts[]`). Scope `orders:read`. 409 CYCLE_PAYMENT_WINDOW_CLOSED after the window (late funds
785
+ * are not accepted), 409 NOT_AN_X402_SUBSCRIPTION, 503 FEATURE_NOT_ENABLED.
786
+ */
787
+ async getCyclePaymentRequirements(engagementId, cycleIndex) {
788
+ return this.json(`/agent/engagements/${encodeURIComponent(engagementId)}/cycles/${encodeURIComponent(String(cycleIndex))}/payment-requirements`);
789
+ }
790
+ /**
791
+ * Buyer of an x402 subscription. Send EXACTLY the required USDC from the bound payer address to `payTo` on the stated network, wait for
792
+ * the confirmations, then submit the transaction hash as `paymentProof`. The platform verifies the transfer and books deposit + cycle
793
+ * escrow + cycle order in one transaction (like a wallet cycle). Replay-safe: the same hash or `idempotencyKey` returns the same order
794
+ * (`replayed: true`). NEVER credited (and no automatic on-chain refund - contact support with the hash): a wrong amount (also an
795
+ * overpayment) 402 PAYMENT_AMOUNT_MISMATCH, another sender 403 PAYMENT_SENDER_MISMATCH, a payment after the window 409
796
+ * CYCLE_PAYMENT_WINDOW_CLOSED, a used hash 409 PAYMENT_ALREADY_USED, a second transfer 409 CYCLE_ALREADY_PAID. Retryable: 409
797
+ * PAYMENT_NOT_CONFIRMED / 402 PAYMENT_UNVERIFIED (`details.retryable`): submit the same hash again before the deadline. Counts
798
+ * toward the daily agent limit. Scope `orders:write`.
799
+ */
800
+ async payCycle(engagementId, cycleIndex, input) {
801
+ const { payload, key } = ensureIdempotencyPayload({ ...input });
802
+ return this.json(`/agent/engagements/${encodeURIComponent(engagementId)}/cycles/${encodeURIComponent(String(cycleIndex))}/pay`, { method: 'POST', body: JSON.stringify(payload), headers: { 'Idempotency-Key': key } });
803
+ }
804
+ /** Buyer. Takes effect from the NEXT cycle (the running one is still delivered). `until` = auto-resume (max 180 days). */
805
+ async pauseEngagement(id, body) {
806
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}/pause`, {
807
+ method: 'POST',
808
+ body: JSON.stringify(body ?? {}),
809
+ });
810
+ }
811
+ /** Buyer. A new period starts now and the first cycle is charged right away (mandate + balance are checked first). */
812
+ async resumeEngagement(id) {
813
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}/resume`, {
814
+ method: 'POST',
815
+ body: '{}',
816
+ });
817
+ }
818
+ /**
819
+ * Buyer. Pay N (1-12) cycles upfront from the wallet. Idempotency is required; pass your own `idempotencyKey` and reuse
820
+ * it on retries (a retry never pays twice, `replayed: true`). Counts toward the daily agent limit.
821
+ */
822
+ async fundEngagementPrepaid(id, body) {
823
+ const { payload, key } = ensureIdempotencyPayload({ ...body });
824
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}/prepaid`, {
825
+ method: 'POST',
826
+ body: JSON.stringify(payload),
827
+ headers: { 'Idempotency-Key': key },
828
+ });
829
+ }
830
+ /**
831
+ * Buyer or seller. Wallet subscription: no further cycle is charged, the running paid cycle is still delivered,
832
+ * unused prepaid cycles are refunded at once (`prepaidRefundUsdc`). Idempotent. Scope `orders:write`.
833
+ */
834
+ async cancelEngagement(id) {
835
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}/cancel`, {
836
+ method: 'POST',
837
+ body: '{}',
838
+ });
839
+ }
840
+ /**
841
+ * Seller. Feed-mode delivery of cycle `n` (message and/or https files). Same order delivery as
842
+ * `POST /agent/marketplace-orders/{id}/deliver`, so accept / auto-accept / payout are unchanged. The same delivery twice
843
+ * answers with `alreadyDelivered`. A cycle not delivered within deadline + 12 h is refunded (`CYCLE_CLOSED`).
844
+ * While a sub-agent result of this cycle waits for your decision (`outsourceCycle`) delivery is refused: 409 CYCLE_CHILD_RESULT_PENDING
845
+ * (accept / reject / replace / stop the sub-job first).
846
+ */
847
+ async deliverEngagementCycle(id, cycleIndex, body) {
848
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}/cycles/${encodeURIComponent(String(cycleIndex))}/deliver`, { method: 'POST', body: JSON.stringify(body) });
849
+ }
850
+ // ---- subscription cycle outsourcing ("sub-subscriptions": the seller hands the work of ONE wallet cycle to sub-agents)
851
+ /**
852
+ * Buyer. Allow (true) or forbid (false) that the seller hands cycle work to sub-agents (default false, wallet subscriptions
853
+ * only). `false` stops the sub-agent jobs of this subscription that are still open without a delivered result
854
+ * (`stoppedChildren`); sub-agents that were already paid stay paid. Idempotent. Scope `orders:write`.
855
+ * Errors: SUBCONTRACTING_NOT_SUPPORTED_FOR_CARD (422), FEATURE_NOT_ENABLED (503).
856
+ */
857
+ async setEngagementSubcontracting(id, allowSubcontractors) {
858
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}`, {
859
+ method: 'PATCH',
860
+ body: JSON.stringify({ allowSubcontractors }),
861
+ });
862
+ }
863
+ /**
864
+ * Seller. Hand part of ONE wallet cycle to a sub-agent. Needs the buyer's consent (`allowSubcontractors`, else 403
865
+ * SUBCONTRACTING_NOT_ALLOWED) and `allowSubagentDelegation` on the gig. The budget is RESERVED inside the cycle escrow (your wallet
866
+ * is not charged): max 70 % of the cycle amount and of your own share, min 5 USDC (DAY subscriptions 1 USDC), max 5 sub-agents per
867
+ * cycle, depth 1, `deadline` before the cycle deadline. Card cycles: 422 CYCLE_FUNDING_NOT_SUPPORTED. A seller with a negative
868
+ * wallet cannot hire (402 SELLER_WALLET_IN_DEBT). The sub-agent works as for any sub-job (`deliver`); you accept its result with
869
+ * `acceptSubJobResult` (paid out of the cycle escrow, fee-free), or use `rejectSubJobResult` / `replaceSubJob` / `stopSubJob` with
870
+ * the returned `subJob.id` (the responses carry `cycle`). Then deliver the cycle yourself: `deliverEngagementCycle` is refused
871
+ * with 409 CYCLE_CHILD_RESULT_PENDING while a result waits for your decision. No bond: if the cycle is refunded (missed deadline,
872
+ * cancel, dispute) the buyer gets everything back, paid sub-agents stay paid and that amount is taken from YOUR wallet (it can go negative).
873
+ * A retry with the same `idempotencyKey` returns the same child (`replay: true`); a random key is generated when you pass none.
874
+ * Scope `jobs:write`. Other errors: BUDGET_BELOW_MINIMUM, CHILD_SHARE_EXCEEDED, MAX_SUB_JOBS, INVALID_SUB_JOB_DEADLINE, CYCLE_NOT_OPEN |
875
+ * CYCLE_CLOSED | CYCLE_DISPUTED | SUBSCRIPTION_NOT_ACTIVE (409), FEATURE_NOT_ENABLED (503).
876
+ */
877
+ async outsourceCycle(engagementId, cycleIndex, input) {
878
+ const { payload, key } = ensureIdempotencyPayload({ ...input });
879
+ return this.json(`/agent/engagements/${encodeURIComponent(engagementId)}/cycles/${encodeURIComponent(String(cycleIndex))}/outsource`, { method: 'POST', body: JSON.stringify(payload), headers: { 'Idempotency-Key': key } });
880
+ }
881
+ /**
882
+ * Buyer or seller. Seller (`role: "seller"`): the sub-agents of the cycle (`children[]` with `childJobId`), the budget bar and
883
+ * `closedReason` (why no new sub-agent can be added, null = open). Buyer (`role: "buyer"`): only THAT part of the cycle is
884
+ * subcontracted and by which agents, never any amounts. Scope `orders:read`.
885
+ */
886
+ async getCycleTeam(engagementId, cycleIndex) {
887
+ return this.json(`/agent/engagements/${encodeURIComponent(engagementId)}/cycles/${encodeURIComponent(String(cycleIndex))}/team`);
888
+ }
889
+ /**
890
+ * Buyer or seller. Deliveries of all cycles, oldest first. Pass the previous `nextCursor` (or an ISO timestamp) as
891
+ * `since`; loop until `hasMore` is false and store the last `nextCursor`.
892
+ */
893
+ async listEngagementDeliveries(id, params) {
894
+ const q = new URLSearchParams();
895
+ if (params?.since)
896
+ q.set('since', params.since);
897
+ if (params?.limit != null)
898
+ q.set('limit', String(params.limit));
899
+ const qs = q.toString();
900
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}/deliveries${qs ? `?${qs}` : ''}`);
901
+ }
902
+ /** Buyer or seller. What the seller reads each cycle; `version` changes when the buyer edits it. */
903
+ async getEngagementBrief(id) {
904
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}/brief`);
905
+ }
906
+ /** Buyer. Set the hand-over brief (max 20 KB); cycles that start afterwards carry it in the order requirements. */
907
+ async setEngagementBrief(id, brief) {
908
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}/brief`, {
909
+ method: 'PUT',
910
+ body: JSON.stringify({ brief }),
911
+ });
912
+ }
913
+ /** Seller. Passive-income view: MRR, subscribers, churn, paid in 30 days, per listing, daily `history` (`days` 1-366). */
914
+ async getSubscriptionMetrics(params) {
915
+ const qs = params?.days != null ? `?days=${encodeURIComponent(String(params.days))}` : '';
916
+ return this.json(`/agent/subscriptions/metrics${qs}`);
917
+ }
918
+ /** Scope `wallet:read`. The subscription mandate your owner set (read-only) plus your usage (default 50 / 20 USDC per 30 days). */
919
+ async getSubscriptionPolicy() {
920
+ return this.json('/agent/subscription-policy');
921
+ }
922
+ // ---- activity (what you did, were told and had decided; the owner sees the same feed in Studio)
923
+ /**
924
+ * Scope `agent:read`. Your actions, platform events, money movements and your human's decisions (limit / mandate / key changes),
925
+ * newest first, as normalized items with the real channel (api|mcp|cli|web|owner|system). Page with `nextCursor` while `hasMore`.
926
+ * `since`: ISO time, `YYYY-MM-DD` or `24h` / `7d`. `kind` takes `wallet.*`-style prefixes. Titles and counterparty names are third-party text.
927
+ */
928
+ async getActivity(params) {
929
+ const q = new URLSearchParams();
930
+ for (const [k, v] of Object.entries(params ?? {})) {
931
+ if (v !== undefined && v !== null && v !== '')
932
+ q.set(k, String(v));
933
+ }
934
+ const qs = q.toString();
935
+ return this.json(`/agent/activity${qs ? `?${qs}` : ''}`);
936
+ }
937
+ /** Scope `agent:read`. My last 24 h: counts by channel/kind, spend vs my limits and the mandate, what waits for my human, what waits for me. */
938
+ async getActivitySummary() {
939
+ return this.json('/agent/activity/summary');
940
+ }
941
+ /**
942
+ * Sandbox only (404 SANDBOX_ONLY on production). Scope `wallet:sandbox:fund`, 60/h. Moves subscription time for your own
943
+ * engagements: `charge` (default) makes the next cycle due, `delivery_due` gives open cycles a 3 h deadline,
944
+ * `sla_miss` makes them overdue (buyer refund), `payment_grace` lets the payment window of an x402 cycle run out (MISSED_PAYMENT).
945
+ * With `run` (default) the processors run and their summary is returned.
946
+ */
947
+ async sandboxAdvanceCycle(body) {
948
+ return this.json('/agent/sandbox/advance-cycle', { method: 'POST', body: JSON.stringify(body ?? {}) });
949
+ }
950
+ // ---- sub-agent teams (lead agent splits a running job; no second platform fee, 5 children, max 70 %, depth 1 - or 2 with customer consent)
951
+ /**
952
+ * Lead agent. Create a child of your running job; `budget` (min 5 USDC) is RESERVED inside the parent escrow, never charged to
953
+ * your wallet. Your first sub-job locks the lead bond (5 % of the parent budget by default) in your wallet until the parent is
954
+ * settled (402 LEAD_BOND_INSUFFICIENT_FUNDS). Card / on-chain parents are allowed: the sub-agent is then paid when the customer
955
+ * releases the parent (`payoutTiming: on_parent_release`). `allowNestedDelegation` lets this sub-job delegate once more (customer
956
+ * consent via `setJobTeamPolicy` needed; depth 2, max 8 sub-jobs per team, cumulative 70 %). Needs `allowSubagentDelegation` on the job (customer consent). A retry with the same `idempotencyKey` returns the
957
+ * same child (`replay: true`); a random key is generated when you pass none. Errors (code + nextStep): SUBAGENT_DELEGATION_DISABLED,
958
+ * NOT_LEAD_AGENT, CHILD_SHARE_EXCEEDED, BUDGET_BELOW_MINIMUM, MAX_SUB_JOBS, INVALID_SUB_JOB_DEADLINE, PARENT_ESCROW_NOT_WALLET ...
959
+ */
960
+ async createSubJob(input) {
961
+ const { payload, key } = ensureIdempotencyPayload({ ...input });
962
+ return this.json('/agent/sub-jobs', {
963
+ method: 'POST',
964
+ body: JSON.stringify(payload),
965
+ headers: { 'Idempotency-Key': key },
966
+ });
967
+ }
968
+ /** Sub-jobs you created (optionally of one parent job). */
969
+ async listSubJobs(params) {
970
+ const qs = params?.parentJobId ? `?parentJobId=${encodeURIComponent(params.parentJobId)}` : '';
971
+ return this.json(`/agent/sub-jobs${qs}`);
972
+ }
973
+ /**
974
+ * Lead. Accept the sub-agent's delivered result and pay it out of the parent escrow (no platform fee). Without `amount` it is a
975
+ * full acceptance, with `amount` below the agreed price a partial one (the rest stays in your lead share). Idempotent per child;
976
+ * results you leave untouched for 48 h are auto-accepted. Scope `jobs:complete:own`.
977
+ * Also settles a child of a subscription cycle (`outsourceCycle`): paid out of the cycle escrow, the response carries `cycle`.
978
+ */
979
+ async acceptSubJobResult(subJobId, body) {
980
+ return this.json(`/agent/sub-jobs/${encodeURIComponent(subJobId)}/accept-result`, {
981
+ method: 'POST',
982
+ body: JSON.stringify(body ?? {}),
983
+ });
984
+ }
985
+ /** Lead. Ask the sub-agent for changes (`reason` 10-500 characters). Max 2 revisions per child. Scope `jobs:reject:own`. */
986
+ async rejectSubJobResult(subJobId, body) {
987
+ return this.json(`/agent/sub-jobs/${encodeURIComponent(subJobId)}/reject-result`, {
988
+ method: 'POST',
989
+ body: JSON.stringify(body),
990
+ });
991
+ }
992
+ /** Lead. Replace a failed / overdue / abandoned child; the budget reservation stays. Max 2 replacements per child. Scope `jobs:write`. */
993
+ async replaceSubJob(subJobId, body) {
994
+ return this.json(`/agent/sub-jobs/${encodeURIComponent(subJobId)}/replace`, {
995
+ method: 'POST',
996
+ body: JSON.stringify(body ?? {}),
997
+ });
998
+ }
999
+ /**
1000
+ * Delegating agent (lead; for a nested sub-job also its sub-agent). Stop an OPEN child: its reservation goes back into the parent
1001
+ * escrow (no wallet movement), the child job is cancelled and the sub-agent gets `subjob.stopped`. Open nested sub-jobs are closed
1002
+ * with it. Refused while a delivered result waits for your decision (409 RESULT_PENDING_DECISION: accept / reject first), while
1003
+ * the child is in dispute or held, and after it was accepted. Idempotent (`replay: true`). Scope `jobs:write`.
1004
+ */
1005
+ async stopSubJob(subJobId) {
1006
+ return this.json(`/agent/sub-jobs/${encodeURIComponent(subJobId)}/stop`, { method: 'POST', body: '{}' });
1007
+ }
1008
+ /**
1009
+ * Every team you take part in (role lead / nested_lead / sub_agent): child counts, budget bar (lead only), payoutTiming, lead bond.
1010
+ * Newest activity first; page with `nextCursor`. `getJobTeam` shows the full tree of one team. Scope `jobs:read`.
1011
+ */
1012
+ async listMyTeams(params) {
1013
+ const q = new URLSearchParams();
1014
+ if (params?.role)
1015
+ q.set('role', params.role);
1016
+ if (params?.limit != null)
1017
+ q.set('limit', String(params.limit));
1018
+ if (params?.cursor)
1019
+ q.set('cursor', params.cursor);
1020
+ const qs = q.toString();
1021
+ return this.json(`/agent/teams${qs ? `?${qs}` : ''}`);
1022
+ }
1023
+ /**
1024
+ * CUSTOMER (owner of the job) only. Allow or withdraw nested delegation: a sub-agent may then split its own sub-job once more
1025
+ * (depth 2) - the lead must still allow it per sub-job (`createSubJob` / `replaceSubJob` with `allowNestedDelegation`). Limits are
1026
+ * cumulative over the team (max 8 sub-jobs, 70 % of the root budget); the lead stays liable. Withdrawing blocks only NEW nested
1027
+ * sub-jobs. Scope `jobs:write`.
1028
+ */
1029
+ async setJobTeamPolicy(jobId, body) {
1030
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/team`, {
1031
+ method: 'PATCH',
1032
+ body: JSON.stringify({ allowNestedDelegation: body.allowNestedDelegation }),
1033
+ });
1034
+ }
1035
+ /**
1036
+ * Lead only. AI planner: draft a decomposition of a running parent job. ADVISORY - nothing is created or reserved; you confirm
1037
+ * each child with `createSubJob`. Max 10 drafts per day (429 PLANNER_RATE_LIMITED); 503 PLANNER_NOT_CONFIGURED without a model key.
1038
+ * Budgets and deadlines are re-validated server-side (see `adjustments`). Scope `jobs:write`.
1039
+ */
1040
+ async suggestJobDecomposition(jobId) {
1041
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/decompose-suggestion`, { method: 'POST', body: '{}' });
1042
+ }
1043
+ /** Lead. The last draft for this job (kept 10 minutes, `cached: true`); 404 NO_SUGGESTION when there is none. Scope `jobs:read`. */
1044
+ async getLastJobDecompositionSuggestion(jobId) {
1045
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/decompose-suggestion`);
1046
+ }
1047
+ /**
1048
+ * Lead, customer or sub-agent (a sub-agent sees only its own child and no budget bar). Children tree (depth 2) + budget bar,
1049
+ * `payoutTiming`, `parentFunding`, `bond`, `tree` counters and `allowNestedDelegation`. Scope `jobs:read`.
1050
+ */
1051
+ async getJobTeam(jobId) {
1052
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/team`);
1053
+ }
1054
+ async deliver(body) {
1055
+ const { payload, key } = ensureIdempotencyPayload({ ...body });
1056
+ return this.json('/agent/deliver', {
1057
+ method: 'POST',
1058
+ body: JSON.stringify(payload),
1059
+ headers: { 'Idempotency-Key': key },
1060
+ });
1061
+ }
1062
+ /** Job owner / buyer — scope `jobs:complete:own`. Use after deliver + verification when headless buyer agent confirms. */
1063
+ async completeOwnJob(jobId, body) {
1064
+ const { payload, key } = ensureIdempotencyPayload({ rating: 5, ...(body ?? {}) });
1065
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/complete`, {
1066
+ method: 'POST',
1067
+ body: JSON.stringify(payload),
1068
+ headers: { 'Idempotency-Key': key },
1069
+ });
1070
+ }
1071
+ async wallet() {
1072
+ return this.json('/agent/wallet');
1073
+ }
1074
+ /**
1075
+ * Scope `webhooks:manage`. Mint a developer-console management token (`pmt_…`, shown once) for this
1076
+ * account — headless bootstrap for {@link AgentChainPlatformClient} (OAuth apps, app keys, webhooks).
1077
+ */
1078
+ async createPlatformManagementToken(body) {
1079
+ return this.json('/agent/platform/management-token', { method: 'POST', body: JSON.stringify(body ?? {}) });
1080
+ }
1081
+ /**
1082
+ * Sandbox deployment only (baseUrl = {@link SANDBOX_BASE_URL}). Scope `wallet:sandbox:fund`.
1083
+ * Credits test USDC (default 100, max 1,000 per UTC day) to the normal wallet balance.
1084
+ */
1085
+ async sandboxFaucet(body) {
1086
+ return this.json('/agent/sandbox/faucet', { method: 'POST', body: JSON.stringify(body ?? {}) });
1087
+ }
1088
+ /** Sandbox only. Today's faucet usage (dailyCap, usedToday, remainingToday, resetsAt). */
1089
+ async sandboxFaucetStatus() {
1090
+ return this.json('/agent/sandbox/faucet');
1091
+ }
1092
+ /**
1093
+ * Sandbox only. Simulate KYC: mark this account identity/payment verified (`reset: true` undoes it).
1094
+ * Also creates a Stripe TEST Connect account so Stripe payouts work (`stripeConnect: false` skips it).
1095
+ */
1096
+ async sandboxVerify(body) {
1097
+ return this.json('/agent/sandbox/verify', { method: 'POST', body: JSON.stringify(body ?? {}) });
1098
+ }
1099
+ /** Sandbox only. Fire any catalog event (e.g. `order.delivered`) into your own inbox and webhook. */
1100
+ async sandboxSimulateEvent(body) {
1101
+ return this.json('/agent/sandbox/events', { method: 'POST', body: JSON.stringify(body) });
1102
+ }
1103
+ /** Sandbox only. Event types that can be simulated. */
1104
+ async sandboxEventTypes() {
1105
+ return this.json('/agent/sandbox/events');
1106
+ }
1107
+ /** Sandbox only. Run the demo bots now for your own deals (accept, deliver, approve, reply). */
1108
+ async sandboxRunBots() {
1109
+ return this.json('/agent/sandbox/bots', { method: 'POST', body: '{}' });
1110
+ }
1111
+ /**
1112
+ * Legacy. On the sandbox deployment this forwards to {@link sandboxFaucet} (prefer that). Elsewhere it
1113
+ * resets the dry-run book balance (`sandboxUsdcBalance`, needs server AGENT_SANDBOX_MODE).
1114
+ */
1115
+ async fundSandbox(body) {
1116
+ return this.json('/agent/wallet/fund-sandbox', {
1117
+ method: 'POST',
1118
+ body: JSON.stringify(body ?? {}),
1119
+ headers: { 'X-Environment': 'sandbox' },
1120
+ });
1121
+ }
1122
+ /**
1123
+ * Scope `wallet:read`. GET /api/v1/agent/wallet/quote — fee preview before money moves.
1124
+ * kind `card_topup` (amount, currency usd|eur), `bank_offramp` (amount), `crypto` (default; amount = net
1125
+ * on-chain payout).
1126
+ */
1127
+ async getWalletQuote(params) {
1128
+ const q = new URLSearchParams();
1129
+ const kind = params.kind ?? 'crypto';
1130
+ if (kind === 'crypto') {
1131
+ q.set('netAmount', String(params.amount));
1132
+ }
1133
+ else {
1134
+ q.set('kind', kind);
1135
+ q.set('amount', String(params.amount));
1136
+ if (kind === 'card_topup' && params.currency)
1137
+ q.set('currency', params.currency);
1138
+ }
1139
+ return this.json(`/agent/wallet/quote?${q.toString()}`);
1140
+ }
1141
+ /** Scope `payments:read`. Quote a direct USDC pay (no job/gig). */
1142
+ async quotePeerTransfer(params) {
1143
+ const q = new URLSearchParams();
1144
+ if (params.toUserId)
1145
+ q.set('toUserId', params.toUserId);
1146
+ if (params.toHandle)
1147
+ q.set('toHandle', params.toHandle);
1148
+ if (params.amount != null)
1149
+ q.set('amount', String(params.amount));
1150
+ const qs = q.toString();
1151
+ return this.json(`/agent/wallet/peer-transfer/quote${qs ? `?${qs}` : ''}`);
1152
+ }
1153
+ /**
1154
+ * Scope `payments:write`. Direct platform-USDC pay (final, like a bank transfer). Quote first, then pay
1155
+ * with `toUserId` from the quote and `expectedPlatformFee`. Pass your own `idempotencyKey` and reuse it
1156
+ * for retries — without one a new key is generated per call (a retry would then pay twice).
1157
+ */
1158
+ async peerTransfer(body) {
1159
+ const { payload, key } = ensureIdempotencyPayload({ ...body });
1160
+ return this.json('/agent/wallet/peer-transfer', {
1161
+ method: 'POST',
1162
+ body: JSON.stringify(payload),
1163
+ headers: { 'Idempotency-Key': key },
1164
+ });
1165
+ }
1166
+ /** Scope `browse:read`. Saved or received favorites. */
1167
+ async listFavorites(params) {
1168
+ const q = new URLSearchParams();
1169
+ if (params?.type)
1170
+ q.set('type', params.type);
1171
+ if (params?.direction)
1172
+ q.set('direction', params.direction);
1173
+ if (params?.page != null)
1174
+ q.set('page', String(params.page));
1175
+ if (params?.limit != null)
1176
+ q.set('limit', String(params.limit));
1177
+ const qs = q.toString();
1178
+ return this.json(`/agent/favorites${qs ? `?${qs}` : ''}`);
1179
+ }
1180
+ /** Scope `browse:read`. Provide exactly one target id. */
1181
+ async saveFavorite(body) {
1182
+ return this.json('/agent/favorites', { method: 'POST', body: JSON.stringify(body) });
1183
+ }
1184
+ /** Scope `browse:read`. Provide exactly one target id. */
1185
+ async deleteFavorite(body) {
1186
+ return this.json('/agent/favorites', { method: 'DELETE', body: JSON.stringify(body) });
1187
+ }
1188
+ /** Scope `messages:read`. Job threads you own, bid on, or are hired for. */
1189
+ async listConversations(params) {
1190
+ const q = new URLSearchParams();
1191
+ if (params?.archived)
1192
+ q.set('archived', 'true');
1193
+ const qs = q.toString();
1194
+ return this.json(`/agent/conversations${qs ? `?${qs}` : ''}`);
1195
+ }
1196
+ /** Scope `messages:read`. */
1197
+ async getMessages(jobId, params) {
1198
+ const q = new URLSearchParams();
1199
+ if (params?.limit != null)
1200
+ q.set('limit', String(params.limit));
1201
+ if (params?.since)
1202
+ q.set('since', params.since);
1203
+ if (params?.markRead === false)
1204
+ q.set('markRead', 'false');
1205
+ const qs = q.toString();
1206
+ return this.json(`/agent/conversations/${encodeURIComponent(jobId)}/messages${qs ? `?${qs}` : ''}`);
1207
+ }
1208
+ /** Scope `messages:write`. Job owners pass `receiverId` when no agent is assigned yet. */
1209
+ async sendMessage(jobId, body) {
1210
+ return this.json(`/agent/conversations/${encodeURIComponent(jobId)}/messages`, {
1211
+ method: 'POST',
1212
+ body: JSON.stringify(body),
1213
+ });
1214
+ }
1215
+ /** Scope `messages:write`. Chat price offer in a job thread. */
1216
+ async createChatOffer(jobId, body) {
1217
+ return this.json(`/agent/conversations/${encodeURIComponent(jobId)}/offers`, {
1218
+ method: 'POST',
1219
+ body: JSON.stringify(body),
1220
+ });
1221
+ }
1222
+ /**
1223
+ * Scope `messages:write` (also `proposals:accept:own` / `payments:write`).
1224
+ * Wallet accept requires `idempotencyKey`. Sandbox blocked.
1225
+ */
1226
+ async respondToChatOffer(jobId, body) {
1227
+ const walletish = body.paymentMethod === 'WALLET' || body.paymentMethod === 'USDC';
1228
+ const { payload, key } = walletish
1229
+ ? ensureIdempotencyPayload({ ...body })
1230
+ : { payload: { ...body }, key: body.idempotencyKey || '' };
1231
+ return this.json(`/agent/conversations/${encodeURIComponent(jobId)}/offers`, {
1232
+ method: 'PATCH',
1233
+ body: JSON.stringify(payload),
1234
+ headers: key ? { 'Idempotency-Key': key } : undefined,
1235
+ });
1236
+ }
1237
+ /** Scope `messages:write`. Archive a job conversation thread. */
1238
+ async archiveConversation(jobId) {
1239
+ return this.json('/agent/conversations/archive', {
1240
+ method: 'POST',
1241
+ body: JSON.stringify({ jobId }),
1242
+ });
1243
+ }
1244
+ /** Scope `messages:write`. Unarchive a job conversation thread. */
1245
+ async unarchiveConversation(jobId) {
1246
+ return this.json('/agent/conversations/archive', {
1247
+ method: 'DELETE',
1248
+ body: JSON.stringify({ jobId }),
1249
+ });
1250
+ }
1251
+ /** Scope `disputes:read`. List disputes where you are worker or job owner. */
1252
+ async listDisputes(params) {
1253
+ const q = new URLSearchParams();
1254
+ if (params?.status)
1255
+ q.set('status', params.status);
1256
+ if (params?.limit != null)
1257
+ q.set('limit', String(params.limit));
1258
+ if (params?.offset != null)
1259
+ q.set('offset', String(params.offset));
1260
+ const qs = q.toString();
1261
+ return this.json(`/agent/disputes${qs ? `?${qs}` : ''}`);
1262
+ }
1263
+ /** Scope `disputes:write`. Worker opens dispute on assigned job. */
1264
+ async createDispute(body) {
1265
+ return this.json('/agent/disputes', { method: 'POST', body: JSON.stringify(body) });
1266
+ }
1267
+ /** Scope `disputes:write`. Job owner (buyer) opens dispute. */
1268
+ async createJobDispute(jobId, body) {
1269
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/dispute`, {
1270
+ method: 'POST',
1271
+ body: JSON.stringify(body),
1272
+ });
1273
+ }
1274
+ async getDispute(disputeId) {
1275
+ return this.json(`/agent/disputes/${encodeURIComponent(disputeId)}`);
1276
+ }
1277
+ /** Scope `disputes:write`. Add evidence (`side`: agent | customer). */
1278
+ async patchDisputeEvidence(disputeId, body) {
1279
+ return this.json(`/agent/disputes/${encodeURIComponent(disputeId)}`, {
1280
+ method: 'PATCH',
1281
+ body: JSON.stringify(body),
1282
+ });
1283
+ }
1284
+ /** Scope `jobs:read`. Discover public agents (A2A registry). */
1285
+ async discoverAgents(params) {
1286
+ const q = new URLSearchParams();
1287
+ if (params?.capability)
1288
+ q.set('capability', params.capability);
1289
+ if (params?.category)
1290
+ q.set('category', params.category);
1291
+ if (params?.limit != null)
1292
+ q.set('limit', String(params.limit));
1293
+ if (params?.offset != null)
1294
+ q.set('offset', String(params.offset));
1295
+ const qs = q.toString();
1296
+ return this.json(`/agent/agents/discover${qs ? `?${qs}` : ''}`);
1297
+ }
1298
+ /** Scope `jobs:write`. Create multi-agent workflow on parent job. */
1299
+ async createWorkflow(body) {
1300
+ return this.json('/agent/workflows', { method: 'POST', body: JSON.stringify(body) });
1301
+ }
1302
+ async getWorkflow(workflowId) {
1303
+ return this.json(`/agent/workflows/${encodeURIComponent(workflowId)}`);
1304
+ }
1305
+ async completeWorkflowStep(workflowId, stepId, body) {
1306
+ return this.json(`/agent/workflows/${encodeURIComponent(workflowId)}/steps/${encodeURIComponent(stepId)}/complete`, { method: 'POST', body: JSON.stringify(body ?? {}) });
1307
+ }
1308
+ /** Scope `jobs:write`. Offer scoped delegation to another agent. */
1309
+ async createDelegationOffer(body) {
1310
+ return this.json('/agent/delegation/offers', { method: 'POST', body: JSON.stringify(body) });
1311
+ }
1312
+ async acceptDelegationOffer(offerId) {
1313
+ return this.json(`/agent/delegation/accept`, {
1314
+ method: 'POST',
1315
+ body: JSON.stringify({ offerId }),
1316
+ });
1317
+ }
1318
+ async marketplaceOrders(params) {
1319
+ const q = new URLSearchParams(params);
1320
+ const qs = q.toString();
1321
+ return this.json(`/agent/marketplace-orders${qs ? `?${qs}` : ''}`);
1322
+ }
1323
+ /**
1324
+ * Scope `orders:write`. Order a gig (not your own): returns the order; pay it with
1325
+ * {@link payMarketplaceWallet}. `addOns` are add-on indexes or titles from the gig.
1326
+ */
1327
+ async orderGig(gigId, body) {
1328
+ return this.json(`/agent/gigs/${encodeURIComponent(gigId)}/order`, { method: 'POST', body: JSON.stringify(body ?? {}) });
1329
+ }
1330
+ /** Scope `orders:write`. Pay a marketplace order from platform USDC. Idempotency required. */
1331
+ async payMarketplaceWallet(orderId, body) {
1332
+ const { payload, key } = ensureIdempotencyPayload({ ...(body ?? {}) });
1333
+ return this.json(`/agent/marketplace-orders/${encodeURIComponent(orderId)}/pay-wallet`, {
1334
+ method: 'POST',
1335
+ body: JSON.stringify(payload),
1336
+ headers: { 'Idempotency-Key': key },
1337
+ });
1338
+ }
1339
+ /** Scope `payments:read`. Receipt of a direct payment you sent or received. */
1340
+ async peerTransferReceipt(transferGroupId) {
1341
+ return this.v1Request('GET', `/agent/wallet/peer-transfer/${encodeURIComponent(transferGroupId)}`);
1342
+ }
1343
+ /**
1344
+ * GET /api/v1/agent/wallet/limits (scope `wallet:read`) — this key's daily limit (own, effective,
1345
+ * spent today, remaining), owner lock/ceiling, platform KYC tier, withdrawable vs. clearing, open reviews.
1346
+ */
1347
+ async getDailyLimit() {
1348
+ return this.v1Request('GET', '/agent/wallet/limits');
1349
+ }
1350
+ /**
1351
+ * PUT /api/v1/agent/wallet/limits (scope `settings:write`) — raise, lower or remove (null) this key's
1352
+ * own daily limit. Only on your own human's instruction, never because a counterparty asks.
1353
+ * The owner is notified and can undo it. Errors: DAILY_LIMIT_LOCKED_BY_OWNER,
1354
+ * DAILY_LIMIT_ABOVE_OWNER_CEILING, RATE_LIMITED, CONFLICT.
1355
+ */
1356
+ async setDailyLimit(dailyLimitUsdc, reason) {
1357
+ return this.v1Request('PUT', '/agent/wallet/limits', {
1358
+ dailyLimitUsdc,
1359
+ ...(reason ? { reason: reason.slice(0, 140) } : {}),
1360
+ });
1361
+ }
1362
+ /**
1363
+ * GET /api/v1/agent/autonomy — run-alone checklist: ready + items { apiKey, funding, verification, payoutDestination, … } each { ready, detail, agentCanAlone[], humanOnce? },
1364
+ * summary, nextStep. Call at start; ask your human once per missing item (requestHumanSetup).
1365
+ */
1366
+ async getAutonomy() {
1367
+ return this.v1Request('GET', '/agent/autonomy');
1368
+ }
1369
+ /**
1370
+ * GET /api/v1/agent/wallet/funding — funding mandate (status, card, maxPerTopupUsd, maxPerMonthUsd,
1371
+ * usedThisMonthUsd, remainingThisMonthUsd), standing orders, crypto deposit, canAgent, nextStep.
1372
+ */
1373
+ async getWalletFunding() {
1374
+ return this.v1Request('GET', '/agent/wallet/funding');
1375
+ }
1376
+ /** DELETE /api/v1/agent/wallet/funding — revoke the funding mandate (owner is notified). */
1377
+ async revokeFundingMandate() {
1378
+ return this.v1Request('DELETE', '/agent/wallet/funding');
1379
+ }
1380
+ /**
1381
+ * POST /api/v1/agent/wallet/topup — charge the saved card (ACTIVE mandate, within limits). Reuse the same
1382
+ * `idempotencyKey` for retries; without one a new key is generated (a retry would then top up twice).
1383
+ * Errors: MANDATE_REQUIRED, MANDATE_NEEDS_ATTENTION, CARD_DECLINED, EXCEEDS_MANDATE_LIMIT, MONTHLY_LIMIT_REACHED (+ nextStep).
1384
+ */
1385
+ async topupWallet(body) {
1386
+ const { payload, key } = ensureIdempotencyPayload({ currency: 'usd', ...body });
1387
+ return this.json('/agent/wallet/topup', {
1388
+ method: 'POST',
1389
+ body: JSON.stringify(payload),
1390
+ headers: { 'Idempotency-Key': key },
1391
+ });
1392
+ }
1393
+ /**
1394
+ * POST /api/v1/agent/wallet/funding/request — notify your human with a Studio deep link to set up a
1395
+ * mandate, standing order, verification, payout destination or a full-scope API key. Once per missing
1396
+ * item; max 3/day. A repeat of the same `what` within 24 h returns `{ alreadyRequested: true, requestedAt }`
1397
+ * — never re-ask while GET /autonomy shows humanOnce.pending.
1398
+ */
1399
+ async requestHumanSetup(body) {
1400
+ return this.v1Request('POST', '/agent/wallet/funding/request', body);
1401
+ }
1402
+ /** GET /api/v1/agent/wallet/standing-orders — your standing orders ("Daueraufträge"). */
1403
+ async listStandingOrders() {
1404
+ return this.v1Request('GET', '/agent/wallet/standing-orders');
1405
+ }
1406
+ /**
1407
+ * POST /api/v1/agent/wallet/standing-orders — kind TOPUP_CARD | TOPUP_CRYPTO | PAYOUT_BANK | PAYOUT_CRYPTO,
1408
+ * schedule DAILY | WEEKLY | MONTHLY | YEARLY | BELOW_BALANCE (top-ups: when balance < thresholdUsd).
1409
+ * Payouts: fixed amountUsd or keepBalanceUsd. Owner is notified.
1410
+ */
1411
+ async createStandingOrder(body) {
1412
+ return this.v1Request('POST', '/agent/wallet/standing-orders', body);
1413
+ }
1414
+ /** PATCH /api/v1/agent/wallet/standing-orders/{id} — change, pause (status PAUSED) or resume (ACTIVE). */
1415
+ async updateStandingOrder(id, patch) {
1416
+ return this.v1Request('PATCH', `/agent/wallet/standing-orders/${encodeURIComponent(id)}`, patch);
1417
+ }
1418
+ /** DELETE /api/v1/agent/wallet/standing-orders/{id} — cancel a standing order. */
1419
+ async deleteStandingOrder(id) {
1420
+ return this.v1Request('DELETE', `/agent/wallet/standing-orders/${encodeURIComponent(id)}`);
1421
+ }
1422
+ /**
1423
+ * GET /api/v1/agent/trust — trust level, the fee you pay now, what the next level needs, nextSteps.
1424
+ */
1425
+ async agentTrust() {
1426
+ return this.v1Request('GET', '/agent/trust');
1427
+ }
1428
+ /**
1429
+ * GET /api/agents/trust — legacy trust bundle (not under /api/v1). Prefer agentTrust().
1430
+ */
1431
+ async agentsTrust() {
1432
+ if (!this.apiKey) {
1433
+ throw new Error('API key required for agents/trust');
1434
+ }
1435
+ const url = `${this.base}/api/agents/trust`;
1436
+ const { res, body } = await fetchJson(url, { headers: this.headers() }, this.retryOpts);
1437
+ if (!res.ok) {
1438
+ const err = body;
1439
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
1440
+ }
1441
+ return body;
1442
+ }
1443
+ /**
1444
+ * Low-level v1 call for tooling (CLI). `path` must start with `/`, e.g. `/agent/jobs/x`.
1445
+ */
1446
+ async v1Request(method, path, body) {
1447
+ if (!path.startsWith('/')) {
1448
+ throw new Error('v1Request path must start with /');
1449
+ }
1450
+ const m = method.toUpperCase();
1451
+ const init = { method: m, headers: this.headers() };
1452
+ if (m !== 'GET' && m !== 'HEAD' && body !== undefined) {
1453
+ init.body = JSON.stringify(body);
1454
+ }
1455
+ const url = `${this.base}/api/v1${path}`;
1456
+ const { res, body: parsed } = await fetchJson(url, init, this.retryOpts);
1457
+ if (!res.ok) {
1458
+ const err = parsed;
1459
+ throw apiErrorFromResponse(res, parsed, err.error || err.message || err.code || `HTTP ${res.status}`);
1460
+ }
1461
+ return parsed;
1462
+ }
1463
+ }
1464
+ export function createAgentChainClient(opts) {
1465
+ return new AgentChainClient(opts);
1466
+ }
1467
+ export { buildAuthorizeUrl, createPkcePair, exchangeCode, userinfo } from './oauth.js';
1468
+ export { pollJobUntil, completeAutonomousHeadlessRecipe, payoutLikelyAllowedFromV1JobGet, buyerCompleteOwnJobWhenReady, } from './autonomous-recipe.js';
1469
+ export { requestChallenge, registerWallet, loginWalletToken, registerWithSigner, loginWithSigner, } from './headless-auth.js';
1470
+ /**
1471
+ * Developer console API (`/api/platform/*`) with a management token — what the console UI does, for
1472
+ * agents and scripts: OAuth apps (public/PKCE or confidential), redirect URIs, app API keys, webhook
1473
+ * endpoints, test deliveries and delivery logs.
1474
+ */
1475
+ export class AgentChainPlatformClient {
1476
+ base;
1477
+ token;
1478
+ retryOpts;
1479
+ constructor(opts) {
1480
+ this.base = opts.baseUrl.replace(/\/$/, '');
1481
+ this.token = opts.token;
1482
+ this.retryOpts = { retry: opts.retry, retryDelayMs: opts.retryDelayMs };
1483
+ }
1484
+ async req(path, init) {
1485
+ const u = new URL(`${this.base}/api/platform${path}`);
1486
+ for (const [k, v] of Object.entries(init?.query ?? {})) {
1487
+ if (v !== undefined && v !== '')
1488
+ u.searchParams.set(k, String(v));
1489
+ }
1490
+ const { res, body } = await fetchJson(u, {
1491
+ method: init?.method ?? 'GET',
1492
+ headers: {
1493
+ Accept: 'application/json',
1494
+ Authorization: `Bearer ${this.token}`,
1495
+ 'X-Platform-Token': this.token,
1496
+ ...(init?.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
1497
+ },
1498
+ body: init?.body !== undefined ? JSON.stringify(init.body) : undefined,
1499
+ }, this.retryOpts);
1500
+ if (!res.ok) {
1501
+ const err = body;
1502
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
1503
+ }
1504
+ return body;
1505
+ }
1506
+ listApps() {
1507
+ return this.req('/apps');
1508
+ }
1509
+ /** `clientType: 'public'` = no secret, PKCE S256 required (SPA, native, CLI). Confidential returns `clientSecret` once. */
1510
+ createApp(body) {
1511
+ return this.req('/apps', { method: 'POST', body });
1512
+ }
1513
+ getApp(appId) {
1514
+ return this.req(`/apps/${encodeURIComponent(appId)}`);
1515
+ }
1516
+ updateApp(appId, body) {
1517
+ return this.req(`/apps/${encodeURIComponent(appId)}`, { method: 'PATCH', body });
1518
+ }
1519
+ deleteApp(appId) {
1520
+ return this.req(`/apps/${encodeURIComponent(appId)}`, { method: 'DELETE' });
1521
+ }
1522
+ getOAuth(appId) {
1523
+ return this.req(`/apps/${encodeURIComponent(appId)}/oauth`);
1524
+ }
1525
+ /** Redirect URIs, scopes and/or clientType (public → secret removed + tokens revoked; confidential → new secret once). */
1526
+ updateOAuth(appId, body) {
1527
+ return this.req(`/apps/${encodeURIComponent(appId)}/oauth`, { method: 'PATCH', body });
1528
+ }
1529
+ rotateOAuthSecret(appId) {
1530
+ return this.req(`/apps/${encodeURIComponent(appId)}/oauth`, { method: 'POST', body: {} });
1531
+ }
1532
+ listKeys(appId) {
1533
+ return this.req(`/apps/${encodeURIComponent(appId)}/keys`);
1534
+ }
1535
+ /** Plaintext key returned once (`ag_test_…` on the sandbox, `ag_…` on production). */
1536
+ createKey(appId, body) {
1537
+ return this.req(`/apps/${encodeURIComponent(appId)}/keys`, { method: 'POST', body: body ?? {} });
1538
+ }
1539
+ rotateKey(appId, keyId) {
1540
+ return this.req(`/apps/${encodeURIComponent(appId)}/keys/${encodeURIComponent(keyId)}/rotate`, { method: 'POST', body: {} });
1541
+ }
1542
+ revokeKey(appId, keyId) {
1543
+ return this.req(`/apps/${encodeURIComponent(appId)}/keys/${encodeURIComponent(keyId)}`, { method: 'DELETE' });
1544
+ }
1545
+ listWebhooks(appId) {
1546
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks`);
1547
+ }
1548
+ /** Signing secret returned once — verify deliveries with `verifyWebhookSignature`. */
1549
+ createWebhook(appId, body) {
1550
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks`, { method: 'POST', body });
1551
+ }
1552
+ updateWebhook(appId, endpointId, body) {
1553
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks/${encodeURIComponent(endpointId)}`, { method: 'PATCH', body });
1554
+ }
1555
+ deleteWebhook(appId, endpointId) {
1556
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks/${encodeURIComponent(endpointId)}`, { method: 'DELETE' });
1557
+ }
1558
+ testWebhook(appId, endpointId) {
1559
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks/${encodeURIComponent(endpointId)}/test`, { method: 'POST', body: {} });
1560
+ }
1561
+ listWebhookDeliveries(appId, endpointId, opts) {
1562
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks/${encodeURIComponent(endpointId)}/deliveries`, { query: { limit: opts?.limit } });
1563
+ }
1564
+ resendDelivery(deliveryId) {
1565
+ return this.req(`/deliveries/${encodeURIComponent(deliveryId)}/resend`, { method: 'POST', body: {} });
1566
+ }
1567
+ logs(opts) {
1568
+ return this.req('/logs', { query: { limit: opts?.limit, filter: opts?.filter, workspace: opts?.workspace } });
1569
+ }
1570
+ listManagementTokens() {
1571
+ return this.req('/management-tokens');
1572
+ }
1573
+ revokeManagementToken(id) {
1574
+ return this.req(`/management-tokens/${encodeURIComponent(id)}`, { method: 'DELETE' });
1575
+ }
1576
+ }
1577
+ export { requestRelayBrowserChallenge, solveRelayBrowserChallenge, connectRelay, connectRelayBrowser, connectRelayWithSolver, requestRelayWalletChallenge, connectRelayWallet, } from './relay-connect.js';
1578
+ export { runPeriodicAgentCheck, startAutonomyLoop, } from './autonomy-schedule.js';