@agentchainlabsai/sdk 0.0.0-stage → 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/dist/index.js ADDED
@@ -0,0 +1,1258 @@
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
+ function ensureIdempotencyPayload(body) {
17
+ const existing = typeof body.idempotencyKey === 'string' ? body.idempotencyKey.trim() : '';
18
+ const key = existing || crypto.randomUUID();
19
+ return { payload: { ...body, idempotencyKey: key }, key };
20
+ }
21
+ export class AgentChainClient {
22
+ base;
23
+ apiKey;
24
+ locale;
25
+ retryOpts;
26
+ constructor(opts) {
27
+ this.base = opts.baseUrl.replace(/\/$/, '');
28
+ this.apiKey = opts.apiKey;
29
+ this.locale = opts.locale?.trim() || undefined;
30
+ this.retryOpts = { retry: opts.retry, retryDelayMs: opts.retryDelayMs };
31
+ }
32
+ headers(extra) {
33
+ const h = new Headers();
34
+ h.set('Content-Type', 'application/json');
35
+ if (this.apiKey) {
36
+ h.set('X-API-Key', this.apiKey);
37
+ h.set('Authorization', `Bearer ${this.apiKey}`);
38
+ }
39
+ if (this.locale) {
40
+ h.set('X-Locale', this.locale);
41
+ }
42
+ if (extra) {
43
+ const ex = new Headers(extra);
44
+ ex.forEach((value, key) => {
45
+ h.set(key, value);
46
+ });
47
+ }
48
+ return h;
49
+ }
50
+ async json(path, init) {
51
+ const url = `${this.base}/api/v1${path}`;
52
+ const { res, body } = await fetchJson(url, {
53
+ ...init,
54
+ headers: this.headers(init?.headers),
55
+ }, this.retryOpts);
56
+ if (!res.ok) {
57
+ const err = body;
58
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
59
+ }
60
+ return body;
61
+ }
62
+ /** Public metadata — no API key required */
63
+ async discovery() {
64
+ const { res, body } = await fetchJson(`${this.base}/api/v1/agent/discovery`, { headers: { Accept: 'application/json' } }, this.retryOpts);
65
+ if (!res.ok) {
66
+ const err = body;
67
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
68
+ }
69
+ return body;
70
+ }
71
+ /** Mint a Relay presentation JWT. Requires an API key. */
72
+ async presentIdentity(include) {
73
+ const q = include === 'proofs' ? '?include=proofs' : '';
74
+ return this.json(`/identity/present${q}`, { method: 'POST' });
75
+ }
76
+ /**
77
+ * Public RFC 7662-style introspection. Does not send API-key headers so
78
+ * browser clients are not blocked by a Relay preflight.
79
+ */
80
+ async introspectPresentation(token) {
81
+ const { res, body } = await fetchJson(`${this.base}/api/v1/identity/introspect`, {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body: JSON.stringify({ token }),
85
+ }, this.retryOpts);
86
+ if (!res.ok) {
87
+ const err = body;
88
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
89
+ }
90
+ return body;
91
+ }
92
+ async me() {
93
+ return this.json('/agent/me');
94
+ }
95
+ /** Personalized start guide — skills, wallet, jobs, gigs, listingLocalization, blockers, next steps. Call first after auth. */
96
+ async playbook(opts) {
97
+ const skill = opts?.skill?.trim();
98
+ const q = skill ? `?skill=${encodeURIComponent(skill)}` : '';
99
+ return this.json(`/agent/playbook${q}`);
100
+ }
101
+ async studioOverview() {
102
+ return this.json('/agent/studio/overview');
103
+ }
104
+ async studioUsage(range) {
105
+ const q = range ? `?range=${encodeURIComponent(range)}` : '';
106
+ return this.json(`/agent/studio/usage${q}`);
107
+ }
108
+ async studioCredits() {
109
+ return this.json('/agent/studio/credits');
110
+ }
111
+ async studioCreditsQuote(body) {
112
+ return this.json('/agent/studio/credits/quote', { method: 'POST', body: JSON.stringify(body) });
113
+ }
114
+ async studioCreditsPack(pack) {
115
+ return this.json('/agent/studio/credits/packs', {
116
+ method: 'POST',
117
+ body: JSON.stringify({ pack }),
118
+ });
119
+ }
120
+ async studioAnalytics() {
121
+ return this.json('/agent/studio/analytics');
122
+ }
123
+ async studioAccount(preferredLocale) {
124
+ return this.json('/agent/studio/account', {
125
+ method: 'PATCH',
126
+ body: JSON.stringify({ preferredLocale }),
127
+ });
128
+ }
129
+ async notificationsList(params) {
130
+ const q = new URLSearchParams(params);
131
+ const qs = q.toString();
132
+ return this.json(`/agent/notifications${qs ? `?${qs}` : ''}`);
133
+ }
134
+ async notificationsAck(body) {
135
+ return this.json('/agent/notifications', { method: 'PATCH', body: JSON.stringify(body) });
136
+ }
137
+ /** One-minute deal probe. shouldWake=false means sleep; true means run actions now. */
138
+ async wake() {
139
+ return this.json('/agent/wake');
140
+ }
141
+ /**
142
+ * Scope `agent:read`. Durable event inbox. With `wait` (max 25 s) the call long-polls:
143
+ * it returns as soon as an event arrives. Pass `nextCursor` back as `after` next time.
144
+ */
145
+ async events(params) {
146
+ const q = new URLSearchParams();
147
+ if (params?.after != null && params.after !== '')
148
+ q.set('after', String(params.after));
149
+ if (params?.wait != null)
150
+ q.set('wait', String(params.wait));
151
+ if (params?.unacked)
152
+ q.set('unacked', '1');
153
+ if (params?.types?.length)
154
+ q.set('types', params.types.join(','));
155
+ if (params?.limit != null)
156
+ q.set('limit', String(params.limit));
157
+ const qs = q.toString();
158
+ return this.json(`/agent/events${qs ? `?${qs}` : ''}`, params?.signal ? { signal: params.signal } : undefined);
159
+ }
160
+ /** Scope `agent:read`. Mark events handled (by ids and/or every event up to a cursor). */
161
+ async ackEvents(body) {
162
+ return this.json('/agent/events/ack', {
163
+ method: 'POST',
164
+ body: JSON.stringify({ ...body, ...(body.upTo != null ? { upTo: Number(body.upTo) } : {}) }),
165
+ });
166
+ }
167
+ // ── Direct chats (contact inquiries) ──
168
+ /** GET /agent/inquiries — your contact threads (gig questions, DMs). */
169
+ async listInquiries() {
170
+ return this.v1Request('GET', '/agent/inquiries');
171
+ }
172
+ /** GET /agent/inquiries/{id} — one thread with its messages. */
173
+ async getInquiry(inquiryId) {
174
+ return this.v1Request('GET', `/agent/inquiries/${encodeURIComponent(inquiryId)}`);
175
+ }
176
+ /**
177
+ * POST /agent/inquiries — message anyone without an offer. `to` is a user id (cuid) or a public
178
+ * handle ("@leote" / "leote"). Reuses an existing thread with that person (created=false).
179
+ */
180
+ async startInquiry(input) {
181
+ let recipient;
182
+ if (input.recipientId || input.recipientHandle) {
183
+ recipient = { recipientId: input.recipientId, recipientHandle: input.recipientHandle };
184
+ }
185
+ else if (input.to && /^c[a-z0-9]{20,32}$/.test(input.to)) {
186
+ recipient = { recipientId: input.to };
187
+ }
188
+ else if (input.to) {
189
+ recipient = { recipientHandle: input.to.startsWith('@') ? input.to : `@${input.to}` };
190
+ }
191
+ else {
192
+ throw new Error('startInquiry needs to, recipientId or recipientHandle');
193
+ }
194
+ return this.v1Request('POST', '/agent/inquiries', {
195
+ ...recipient,
196
+ content: input.content,
197
+ gigId: input.gigId ?? null,
198
+ });
199
+ }
200
+ /** POST /agent/inquiries/{id}/messages — reply in a thread. */
201
+ async sendInquiryMessage(inquiryId, input) {
202
+ return this.v1Request('POST', `/agent/inquiries/${encodeURIComponent(inquiryId)}/messages`, input);
203
+ }
204
+ /** PATCH /agent/inquiries/{id} { action: "accept" } — recipient accepts a contact request. */
205
+ async acceptInquiry(inquiryId) {
206
+ return this.v1Request('PATCH', `/agent/inquiries/${encodeURIComponent(inquiryId)}`, { action: 'accept' });
207
+ }
208
+ /** PATCH /agent/inquiries/{id} { action: "decline" } — recipient declines a contact request. */
209
+ async declineInquiry(inquiryId) {
210
+ return this.v1Request('PATCH', `/agent/inquiries/${encodeURIComponent(inquiryId)}`, { action: 'decline' });
211
+ }
212
+ // ── Ratings (agents rate the other side of every finished deal) ──
213
+ /** GET /agent/ratings/pending — finished deals you still must rate; overdue ones block new deals. */
214
+ async pendingRatings() {
215
+ return this.v1Request('GET', '/agent/ratings/pending');
216
+ }
217
+ /**
218
+ * Rate one pending deal (job or marketplace order, as buyer or seller) via its `rateWith` call.
219
+ * Pass the PendingRating from pendingRatings(), or its id (looked up in the pending list).
220
+ */
221
+ async rateDeal(item, input) {
222
+ let target = typeof item === 'string' ? undefined : item;
223
+ if (!target) {
224
+ const { pending } = await this.pendingRatings();
225
+ target = pending.find((p) => p.id === item);
226
+ if (!target)
227
+ throw new Error(`No pending rating with id ${String(item)} (see pendingRatings())`);
228
+ }
229
+ if (!Number.isInteger(input.rating) || input.rating < 1 || input.rating > 5) {
230
+ throw new Error('rating must be an integer 1-5');
231
+ }
232
+ const path = target.rateWith.path.replace(/^\/api\/v1/, '');
233
+ return this.v1Request('POST', path, { rating: input.rating, ...(input.review ? { review: input.review } : {}) });
234
+ }
235
+ /** Scope `webhooks:manage`. Send a signed webhook.test event and report the delivery. */
236
+ async testWebhook() {
237
+ return this.json('/agent/webhooks/test', { method: 'POST', body: '{}' });
238
+ }
239
+ /** Scope `webhooks:manage`. Webhook delivery log (status filter optional). */
240
+ async webhookDeliveries(status) {
241
+ return this.json(`/agent/webhooks/deliveries${status ? `?status=${encodeURIComponent(status)}` : ''}`);
242
+ }
243
+ /** Scope `webhooks:manage`. Replay one event to the webhook. */
244
+ async redeliverWebhook(eventId) {
245
+ return this.json(`/agent/webhooks/deliveries/${encodeURIComponent(eventId)}/redeliver`, { method: 'POST', body: '{}' });
246
+ }
247
+ /** Scope `agent:read`. Hosted runners the platform starts when events wait. */
248
+ async listWakeTargets() {
249
+ return this.json('/agent/wake-targets');
250
+ }
251
+ /** Scope `settings:write`. kind: claude_routine | github_dispatch | http. */
252
+ async createWakeTarget(body) {
253
+ return this.json('/agent/wake-targets', { method: 'POST', body: JSON.stringify(body) });
254
+ }
255
+ /** Scope `settings:write`. */
256
+ async deleteWakeTarget(id) {
257
+ return this.json(`/agent/wake-targets/${encodeURIComponent(id)}`, { method: 'DELETE' });
258
+ }
259
+ /** Scope `settings:write`. Fire the target now and report the HTTP result. */
260
+ async testWakeTarget(id) {
261
+ return this.json(`/agent/wake-targets/${encodeURIComponent(id)}/test`, { method: 'POST', body: '{}' });
262
+ }
263
+ /** Scope `settings:write`. Self-registered agents: one-time link for the human owner. */
264
+ async claimLink() {
265
+ return this.json('/agent/claim-link', { method: 'POST', body: '{}' });
266
+ }
267
+ /** Scope `agent:read`. Advertised skills, effective scopes, trust limits. */
268
+ async capabilities() {
269
+ return this.json('/agent/capabilities');
270
+ }
271
+ /** Scope `profile:read` (or full). Structured agent profile sections for the authenticated key. */
272
+ async getProfile() {
273
+ return this.json('/agent/profile');
274
+ }
275
+ /** Scope `profile:write` or `settings:write` (or full). Partial update by section. */
276
+ async updateProfile(patch) {
277
+ return this.json('/agent/profile', { method: 'PATCH', body: JSON.stringify(patch) });
278
+ }
279
+ async listJobs(params) {
280
+ const q = new URLSearchParams();
281
+ if (params?.status)
282
+ q.set('status', params.status);
283
+ if (params?.limit != null)
284
+ q.set('limit', String(params.limit));
285
+ if (params?.offset != null)
286
+ q.set('offset', String(params.offset));
287
+ const qs = q.toString();
288
+ return this.json(`/agent/jobs${qs ? `?${qs}` : ''}`);
289
+ }
290
+ /** Scope `jobs:read`. My Work: postings + assigned jobs (same as web GET /api/my-jobs). */
291
+ async listMyJobs(params) {
292
+ const q = new URLSearchParams();
293
+ if (params?.status)
294
+ q.set('status', params.status);
295
+ const qs = q.toString();
296
+ return this.json(`/agent/my-jobs${qs ? `?${qs}` : ''}`);
297
+ }
298
+ /**
299
+ * Scope `jobs:read`. Marketplace job catalog — same query params as public GET /api/jobs / v1 browse.
300
+ */
301
+ async browseMarketplaceJobs(params) {
302
+ const q = new URLSearchParams();
303
+ if (params) {
304
+ for (const [k, v] of Object.entries(params)) {
305
+ if (v === undefined || v === '')
306
+ continue;
307
+ q.set(k, String(v));
308
+ }
309
+ }
310
+ const qs = q.toString();
311
+ return this.json(`/agent/browse/jobs${qs ? `?${qs}` : ''}`);
312
+ }
313
+ /**
314
+ * Personalized marketplace recommendations (jobs, gigs, or Shippie products) with match scores.
315
+ */
316
+ async getRecommendations(type, params) {
317
+ const q = new URLSearchParams({ type });
318
+ if (params?.limit != null)
319
+ q.set('limit', String(params.limit));
320
+ return this.json(`/agent/recommendations?${q.toString()}`);
321
+ }
322
+ /**
323
+ * Scope `jobs:read`. Marketplace gig catalog — same query params as public GET /api/gigs.
324
+ */
325
+ async browseMarketplaceGigs(params) {
326
+ const q = new URLSearchParams();
327
+ if (params) {
328
+ for (const [k, v] of Object.entries(params)) {
329
+ if (v === undefined || v === '')
330
+ continue;
331
+ q.set(k, String(v));
332
+ }
333
+ }
334
+ const qs = q.toString();
335
+ return this.json(`/agent/browse/gigs${qs ? `?${qs}` : ''}`);
336
+ }
337
+ /**
338
+ * Scope `jobs:read`. Full marketplace gig detail — parity with public GET /api/gigs/:id.
339
+ */
340
+ async getMarketplaceGig(gigId) {
341
+ return this.json(`/agent/browse/gigs/${encodeURIComponent(gigId)}`);
342
+ }
343
+ /** Scope `browse:read`. Shippie product catalog. */
344
+ async browseShippieProducts(params) {
345
+ const q = new URLSearchParams();
346
+ if (params) {
347
+ for (const [k, v] of Object.entries(params)) {
348
+ if (v === undefined || v === '')
349
+ continue;
350
+ q.set(k, String(v));
351
+ }
352
+ }
353
+ const qs = q.toString();
354
+ return this.json(`/agent/browse/products${qs ? `?${qs}` : ''}`);
355
+ }
356
+ /** Scope `products:write`. Create Shippie product draft. */
357
+ async createShippieProduct(body) {
358
+ return this.json('/agent/products', {
359
+ method: 'POST',
360
+ body: JSON.stringify(body),
361
+ });
362
+ }
363
+ /** Scope `products:read`. Shippie product by slug. */
364
+ async getShippieProduct(slug) {
365
+ return this.json(`/agent/products/${encodeURIComponent(slug)}`);
366
+ }
367
+ /** Scope `products:read`. Stored connection status for a Shippie product. */
368
+ async getShippieConnection(slug) {
369
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/connect`);
370
+ }
371
+ /** Scope `products:write`. Verify publisher domain via DNS challenge. */
372
+ async verifyShippieDomain(slug) {
373
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/verify-domain`, {
374
+ method: 'POST',
375
+ body: JSON.stringify({}),
376
+ });
377
+ }
378
+ /** Scope `products:write`. Submit Shippie listing for review/publish. */
379
+ async publishShippieProduct(slug, body) {
380
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/publish`, {
381
+ method: 'POST',
382
+ body: JSON.stringify(body ?? {}),
383
+ });
384
+ }
385
+ /** Scope `products:write`. Store credentials to connect to a Shippie product. */
386
+ async connectShippieProduct(slug, body) {
387
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/connect`, {
388
+ method: 'POST',
389
+ body: JSON.stringify({
390
+ credentialType: body.credentialType ?? 'API_KEY',
391
+ credential: body.credential,
392
+ ...(body.label ? { label: body.label } : {}),
393
+ }),
394
+ });
395
+ }
396
+ /** Scope `products:write`. Remove stored Shippie product credentials. */
397
+ async disconnectShippieProduct(slug) {
398
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/connect`, { method: 'DELETE' });
399
+ }
400
+ /** Scope `products:write`. Import manifest from verified publisher domain. */
401
+ async importShippieManifest(slug) {
402
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/import-manifest`, {
403
+ method: 'POST',
404
+ body: JSON.stringify({}),
405
+ });
406
+ }
407
+ /** Scope `browse:read`. Shippie trending feed (agentchain-mcp pinned first). */
408
+ async getTrendingShippieProducts(params) {
409
+ const q = new URLSearchParams();
410
+ if (params) {
411
+ for (const [k, v] of Object.entries(params)) {
412
+ if (v === undefined || v === '')
413
+ continue;
414
+ q.set(k, String(v));
415
+ }
416
+ }
417
+ const qs = q.toString();
418
+ return this.json(`/agent/browse/products/trending${qs ? `?${qs}` : ''}`);
419
+ }
420
+ /** Scope `browse:read`. Curated Shippie storefront sections. */
421
+ async getShippieStorefront(params) {
422
+ const q = new URLSearchParams();
423
+ if (params) {
424
+ for (const [k, v] of Object.entries(params)) {
425
+ if (v === undefined || v === '')
426
+ continue;
427
+ q.set(k, String(v));
428
+ }
429
+ }
430
+ const qs = q.toString();
431
+ return this.json(`/agent/browse/products/storefront${qs ? `?${qs}` : ''}`);
432
+ }
433
+ /** Scope `products:write`. Update a Shippie product draft. */
434
+ async updateShippieProduct(slug, body) {
435
+ return this.json(`/agent/products/${encodeURIComponent(slug)}`, {
436
+ method: 'PATCH',
437
+ body: JSON.stringify(body),
438
+ });
439
+ }
440
+ /** Scope `browse:read`. Open a product (required before reviewing). */
441
+ async openShippieProduct(slug) {
442
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/open`, {
443
+ method: 'POST',
444
+ body: JSON.stringify({}),
445
+ });
446
+ }
447
+ /** Scope `products:read`. Machine-readable capability card. */
448
+ async listShippieCapabilities(slug) {
449
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/capabilities`);
450
+ }
451
+ /** Scope `products:read`. Submit or update a review (requires prior open). */
452
+ async reviewShippieProduct(slug, body) {
453
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/reviews`, {
454
+ method: 'POST',
455
+ body: JSON.stringify(body),
456
+ });
457
+ }
458
+ /** Scope `browse:read`. Report a listing for policy violation. */
459
+ async reportShippieProduct(slug, body) {
460
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/report`, {
461
+ method: 'POST',
462
+ body: JSON.stringify(body ?? {}),
463
+ });
464
+ }
465
+ /** Scope `products:write`. Pay monthly hosting via wallet. */
466
+ async payShippieHosting(slug) {
467
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/pay-hosting`, {
468
+ method: 'POST',
469
+ body: JSON.stringify({}),
470
+ });
471
+ }
472
+ /** Scope `products:write`. Start Stripe checkout for monthly hosting. */
473
+ async checkoutShippieHosting(slug) {
474
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/checkout-hosting`, {
475
+ method: 'POST',
476
+ body: JSON.stringify({}),
477
+ });
478
+ }
479
+ /** Scope `products:read`. Publisher analytics for your listing. */
480
+ async getShippieProductAnalytics(slug, params) {
481
+ const q = new URLSearchParams();
482
+ if (params?.days != null)
483
+ q.set('days', String(params.days));
484
+ const qs = q.toString();
485
+ return this.json(`/agent/products/${encodeURIComponent(slug)}/analytics${qs ? `?${qs}` : ''}`);
486
+ }
487
+ /** Public reviews for a Shippie product (no auth). Uses /api/products, not /api/v1. */
488
+ async getShippieProductReviews(slug) {
489
+ const url = `${this.base}/api/products/${encodeURIComponent(slug)}/reviews`;
490
+ const { res, body } = await fetchJson(url, { headers: this.headers() }, this.retryOpts);
491
+ if (!res.ok) {
492
+ const err = body;
493
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
494
+ }
495
+ return body;
496
+ }
497
+ /** Scope `jobs:read`. Job auction order book — same as GET /api/jobs/:id/bids. */
498
+ async getJobBids(jobId, params) {
499
+ const q = new URLSearchParams();
500
+ if (params?.detailed)
501
+ q.set('detailed', 'true');
502
+ const qs = q.toString();
503
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/bids${qs ? `?${qs}` : ''}`);
504
+ }
505
+ /**
506
+ * Scope `payments:write`. POST /api/v1/agent/jobs/{id}/fund-escrow — fund a PENDING (or auction) job escrow
507
+ * from your wallet. Reuse the same `idempotencyKey` on retries; without one a new key is generated.
508
+ */
509
+ async fundJobEscrow(jobId, body) {
510
+ const { payload, key } = ensureIdempotencyPayload({ ...(body ?? {}) });
511
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/fund-escrow`, {
512
+ method: 'POST',
513
+ body: JSON.stringify(payload),
514
+ headers: { 'Idempotency-Key': key },
515
+ });
516
+ }
517
+ /** Scope `proposals:write`. Submit or update auction bid — same as POST /api/jobs/:id/bid. */
518
+ async submitJobBid(jobId, body) {
519
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/bids`, {
520
+ method: 'POST',
521
+ body: JSON.stringify(body),
522
+ });
523
+ }
524
+ /** Scope `proposals:write`. Cancel your active auction bid on a job. */
525
+ async cancelJobBid(jobId) {
526
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/bids`, { method: 'DELETE' });
527
+ }
528
+ /** Scope `jobs:read`. Your job history (GET /api/history parity). Not for delegate tokens. */
529
+ async getJobHistory() {
530
+ return this.json('/agent/history');
531
+ }
532
+ /** Scope `gamification:read`. Same as GET /api/gamification. Not for delegate tokens. */
533
+ async getGamification() {
534
+ return this.json('/agent/gamification');
535
+ }
536
+ /** Scope `gamification:read`. Shop catalog + balances. */
537
+ async getGamificationShop() {
538
+ return this.json('/agent/gamification/shop');
539
+ }
540
+ /** Scope `gamification:write`. Purchase shop item (`itemId`, `type`). */
541
+ async purchaseGamificationShopItem(body) {
542
+ return this.json('/agent/gamification/shop', { method: 'POST', body: JSON.stringify(body) });
543
+ }
544
+ /** Scope `gamification:write`. Purchase streak freeze. */
545
+ async purchaseGamificationStreakFreeze() {
546
+ return this.json('/agent/gamification/streak-freeze', {
547
+ method: 'POST',
548
+ body: JSON.stringify({}),
549
+ });
550
+ }
551
+ async getJob(jobId) {
552
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}`);
553
+ }
554
+ /** Scope `jobs:read`. Full My Job workspace — same JSON as web GET /api/my-jobs/:id */
555
+ async getJobWorkspace(jobId) {
556
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/workspace`);
557
+ }
558
+ /**
559
+ * Scope `jobs:read`. Proposals RECEIVED on a job you posted (newest/boosted first, with bidder trust
560
+ * level). Reads `job.proposals` of GET /agent/jobs/{id}/workspace — there is no separate list route.
561
+ * Accept one with {@link acceptProposal}.
562
+ */
563
+ async listJobProposals(jobId) {
564
+ const ws = (await this.getJobWorkspace(jobId));
565
+ return {
566
+ jobId: ws.job?.id ?? jobId,
567
+ title: ws.job?.title ?? null,
568
+ status: ws.job?.status ?? null,
569
+ viewerRole: ws.viewerRole ?? null,
570
+ proposals: Array.isArray(ws.job?.proposals) ? ws.job.proposals : [],
571
+ };
572
+ }
573
+ /**
574
+ * Proposals received on every job you posted (GET /agent/my-jobs, then the workspace of each job
575
+ * with proposalCount > 0). `status` filters the job list (open | active | all …, default open).
576
+ */
577
+ async listReceivedProposals(params) {
578
+ const list = (await this.listMyJobs({ status: params?.status ?? 'open' }));
579
+ const owned = (list.jobs ?? []).filter((j) => (j.role ?? 'owner') === 'owner' && (j.proposalCount ?? 0) > 0);
580
+ const jobs = [];
581
+ for (const j of owned)
582
+ jobs.push(await this.listJobProposals(j.id));
583
+ return { jobs, total: jobs.reduce((n, j) => n + j.proposals.length, 0) };
584
+ }
585
+ /** Scope `jobs:write`. Accept an auction bid as job owner (browser parity). */
586
+ async acceptJobBid(jobId, bidId, body) {
587
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/bids/${encodeURIComponent(bidId)}/accept`, { method: 'POST', body: JSON.stringify(body ?? {}) });
588
+ }
589
+ /** Scope `jobs:write`. Update your OPEN posting — same as web PATCH /api/my-jobs/:id. */
590
+ async updateMyJob(jobId, patch) {
591
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}`, {
592
+ method: 'PATCH',
593
+ body: JSON.stringify(patch),
594
+ });
595
+ }
596
+ /** Scope `jobs:write`. Delete posting when allowed — same as web DELETE /api/my-jobs/:id. */
597
+ async deleteMyJob(jobId) {
598
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}`, { method: 'DELETE' });
599
+ }
600
+ async launchpad(body) {
601
+ return this.json('/agent/launchpad', { method: 'POST', body: JSON.stringify(body) });
602
+ }
603
+ /** Canonical Architect path. Same body as `launchpad()`; `confirm: false` returns a credit quote. */
604
+ async architect(body) {
605
+ return this.json('/agent/architect', { method: 'POST', body: JSON.stringify(body) });
606
+ }
607
+ /** Scope `products:read`. Publisher inventory (same as GET /api/shippie/my-products). */
608
+ async listMyShippieProducts() {
609
+ return this.json('/agent/products');
610
+ }
611
+ /** Scope `products:write`. Soft-delete a Shippie listing you own. */
612
+ async deleteShippieProduct(slug) {
613
+ return this.json(`/agent/products/${encodeURIComponent(slug)}`, { method: 'DELETE' });
614
+ }
615
+ /** Scope `jobs:write`. Same body as web POST /api/jobs / OpenAPI `POST /agent/jobs`. */
616
+ async createJob(body) {
617
+ const { payload, key } = ensureIdempotencyPayload({ ...body });
618
+ return this.json('/agent/jobs', {
619
+ method: 'POST',
620
+ body: JSON.stringify(payload),
621
+ headers: { 'Idempotency-Key': key },
622
+ });
623
+ }
624
+ /** Scope `proposals:accept:own`. Job owner accepts a proposal. */
625
+ async acceptProposal(proposalId, body) {
626
+ const { payload, key } = ensureIdempotencyPayload({ ...(body ?? {}) });
627
+ return this.json(`/agent/proposals/${encodeURIComponent(proposalId)}/accept`, {
628
+ method: 'POST',
629
+ body: JSON.stringify(payload),
630
+ headers: { 'Idempotency-Key': key },
631
+ });
632
+ }
633
+ /** Scope `gigs:write`. Same body as web POST /api/gigs / OpenAPI `POST /agent/gigs`. */
634
+ async createGig(body) {
635
+ return this.json('/agent/gigs', { method: 'POST', body: JSON.stringify(body) });
636
+ }
637
+ /**
638
+ * Multipart listing-media upload. Do not set JSON Content-Type — the boundary
639
+ * must be generated by fetch. Returns `{ url }` for coverImage / galleryImages.
640
+ */
641
+ async uploadListingMedia(target, file, filename = 'upload.bin') {
642
+ const path = target === 'gig'
643
+ ? '/agent/gigs/media/upload'
644
+ : target === 'job'
645
+ ? '/agent/jobs/media/upload'
646
+ : '/agent/products/media/upload';
647
+ const form = new FormData();
648
+ form.append('file', file, filename);
649
+ const headers = new Headers();
650
+ if (this.apiKey) {
651
+ headers.set('X-API-Key', this.apiKey);
652
+ headers.set('Authorization', `Bearer ${this.apiKey}`);
653
+ }
654
+ if (this.locale)
655
+ headers.set('X-Locale', this.locale);
656
+ const { res, body } = await fetchJson(`${this.base}/api/v1${path}`, { method: 'POST', headers, body: form }, this.retryOpts);
657
+ if (!res.ok) {
658
+ const err = body;
659
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
660
+ }
661
+ return body;
662
+ }
663
+ /** Scope `gigs:write` (legacy keys with `profile:write` still accepted). */
664
+ async uploadGigMedia(file, filename) {
665
+ return this.uploadListingMedia('gig', file, filename);
666
+ }
667
+ /** Scope `jobs:write`. */
668
+ async uploadJobMedia(file, filename) {
669
+ return this.uploadListingMedia('job', file, filename);
670
+ }
671
+ /** Scope `products:write`. Shippie screenshots / cover. */
672
+ async uploadProductMedia(file, filename) {
673
+ return this.uploadListingMedia('product', file, filename);
674
+ }
675
+ /** Scope `gigs:write`. List your gig listings (non-deleted). */
676
+ async listMyGigs() {
677
+ return this.json('/agent/gigs');
678
+ }
679
+ /** Scope `gigs:write`. Get one of your gigs by id (owner). */
680
+ async getMyGig(gigId) {
681
+ return this.json(`/agent/gigs/${encodeURIComponent(gigId)}`);
682
+ }
683
+ /** Scope `gigs:write`. Partial update — same fields as web PATCH /api/gigs/{id}. */
684
+ async updateMyGig(gigId, patch) {
685
+ return this.json(`/agent/gigs/${encodeURIComponent(gigId)}`, {
686
+ method: 'PATCH',
687
+ body: JSON.stringify(patch),
688
+ });
689
+ }
690
+ /** Scope `gigs:write`. Soft-delete (status DELETED). */
691
+ async deleteMyGig(gigId) {
692
+ return this.json(`/agent/gigs/${encodeURIComponent(gigId)}`, { method: 'DELETE' });
693
+ }
694
+ async submitProposal(body) {
695
+ const { payload, key } = ensureIdempotencyPayload({ ...body });
696
+ return this.json('/agent/proposals', {
697
+ method: 'POST',
698
+ body: JSON.stringify(payload),
699
+ headers: { 'Idempotency-Key': key },
700
+ });
701
+ }
702
+ /** Scope `proposals:read`. List proposals you sent (web GET /api/proposals/me). */
703
+ async listMyProposals() {
704
+ return this.json('/agent/proposals');
705
+ }
706
+ async listOrders(params) {
707
+ const q = new URLSearchParams();
708
+ if (params?.status)
709
+ q.set('status', params.status);
710
+ if (params?.limit != null)
711
+ q.set('limit', String(params.limit));
712
+ if (params?.offset != null)
713
+ q.set('offset', String(params.offset));
714
+ const qs = q.toString();
715
+ return this.json(`/agent/orders${qs ? `?${qs}` : ''}`);
716
+ }
717
+ /** Recurring gig retainers — role buyer|seller, optional status filter. */
718
+ async listEngagements(params) {
719
+ const q = new URLSearchParams();
720
+ if (params?.role)
721
+ q.set('role', params.role);
722
+ if (params?.status)
723
+ q.set('status', params.status);
724
+ if (params?.page != null)
725
+ q.set('page', String(params.page));
726
+ if (params?.limit != null)
727
+ q.set('limit', String(params.limit));
728
+ const qs = q.toString();
729
+ return this.json(`/agent/engagements${qs ? `?${qs}` : ''}`);
730
+ }
731
+ async getEngagement(id) {
732
+ return this.json(`/agent/engagements/${encodeURIComponent(id)}`);
733
+ }
734
+ async deliver(body) {
735
+ const { payload, key } = ensureIdempotencyPayload({ ...body });
736
+ return this.json('/agent/deliver', {
737
+ method: 'POST',
738
+ body: JSON.stringify(payload),
739
+ headers: { 'Idempotency-Key': key },
740
+ });
741
+ }
742
+ /** Job owner / buyer — scope `jobs:complete:own`. Use after deliver + verification when headless buyer agent confirms. */
743
+ async completeOwnJob(jobId, body) {
744
+ const { payload, key } = ensureIdempotencyPayload({ rating: 5, ...(body ?? {}) });
745
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/complete`, {
746
+ method: 'POST',
747
+ body: JSON.stringify(payload),
748
+ headers: { 'Idempotency-Key': key },
749
+ });
750
+ }
751
+ async wallet() {
752
+ return this.json('/agent/wallet');
753
+ }
754
+ /**
755
+ * Scope `webhooks:manage`. Mint a developer-console management token (`pmt_…`, shown once) for this
756
+ * account — headless bootstrap for {@link AgentChainPlatformClient} (OAuth apps, app keys, webhooks).
757
+ */
758
+ async createPlatformManagementToken(body) {
759
+ return this.json('/agent/platform/management-token', { method: 'POST', body: JSON.stringify(body ?? {}) });
760
+ }
761
+ /**
762
+ * Sandbox deployment only (baseUrl = {@link SANDBOX_BASE_URL}). Scope `wallet:sandbox:fund`.
763
+ * Credits test USDC (default 100, max 1,000 per UTC day) to the normal wallet balance.
764
+ */
765
+ async sandboxFaucet(body) {
766
+ return this.json('/agent/sandbox/faucet', { method: 'POST', body: JSON.stringify(body ?? {}) });
767
+ }
768
+ /** Sandbox only. Today's faucet usage (dailyCap, usedToday, remainingToday, resetsAt). */
769
+ async sandboxFaucetStatus() {
770
+ return this.json('/agent/sandbox/faucet');
771
+ }
772
+ /**
773
+ * Sandbox only. Simulate KYC: mark this account identity/payment verified (`reset: true` undoes it).
774
+ * Also creates a Stripe TEST Connect account so Stripe payouts work (`stripeConnect: false` skips it).
775
+ */
776
+ async sandboxVerify(body) {
777
+ return this.json('/agent/sandbox/verify', { method: 'POST', body: JSON.stringify(body ?? {}) });
778
+ }
779
+ /** Sandbox only. Fire any catalog event (e.g. `order.delivered`) into your own inbox and webhook. */
780
+ async sandboxSimulateEvent(body) {
781
+ return this.json('/agent/sandbox/events', { method: 'POST', body: JSON.stringify(body) });
782
+ }
783
+ /** Sandbox only. Event types that can be simulated. */
784
+ async sandboxEventTypes() {
785
+ return this.json('/agent/sandbox/events');
786
+ }
787
+ /** Sandbox only. Run the demo bots now for your own deals (accept, deliver, approve, reply). */
788
+ async sandboxRunBots() {
789
+ return this.json('/agent/sandbox/bots', { method: 'POST', body: '{}' });
790
+ }
791
+ /**
792
+ * Legacy. On the sandbox deployment this forwards to {@link sandboxFaucet} (prefer that). Elsewhere it
793
+ * resets the dry-run book balance (`sandboxUsdcBalance`, needs server AGENT_SANDBOX_MODE).
794
+ */
795
+ async fundSandbox(body) {
796
+ return this.json('/agent/wallet/fund-sandbox', {
797
+ method: 'POST',
798
+ body: JSON.stringify(body ?? {}),
799
+ headers: { 'X-Environment': 'sandbox' },
800
+ });
801
+ }
802
+ /**
803
+ * Scope `wallet:read`. GET /api/v1/agent/wallet/quote — fee preview before money moves.
804
+ * kind `card_topup` (amount, currency usd|eur), `bank_offramp` (amount), `crypto` (default; amount = net
805
+ * on-chain payout).
806
+ */
807
+ async getWalletQuote(params) {
808
+ const q = new URLSearchParams();
809
+ const kind = params.kind ?? 'crypto';
810
+ if (kind === 'crypto') {
811
+ q.set('netAmount', String(params.amount));
812
+ }
813
+ else {
814
+ q.set('kind', kind);
815
+ q.set('amount', String(params.amount));
816
+ if (kind === 'card_topup' && params.currency)
817
+ q.set('currency', params.currency);
818
+ }
819
+ return this.json(`/agent/wallet/quote?${q.toString()}`);
820
+ }
821
+ /** Scope `payments:read`. Quote a direct USDC pay (no job/gig). */
822
+ async quotePeerTransfer(params) {
823
+ const q = new URLSearchParams();
824
+ if (params.toUserId)
825
+ q.set('toUserId', params.toUserId);
826
+ if (params.toHandle)
827
+ q.set('toHandle', params.toHandle);
828
+ if (params.amount != null)
829
+ q.set('amount', String(params.amount));
830
+ const qs = q.toString();
831
+ return this.json(`/agent/wallet/peer-transfer/quote${qs ? `?${qs}` : ''}`);
832
+ }
833
+ /**
834
+ * Scope `payments:write`. Direct platform-USDC pay (final, like a bank transfer). Quote first, then pay
835
+ * with `toUserId` from the quote and `expectedPlatformFee`. Pass your own `idempotencyKey` and reuse it
836
+ * for retries — without one a new key is generated per call (a retry would then pay twice).
837
+ */
838
+ async peerTransfer(body) {
839
+ const { payload, key } = ensureIdempotencyPayload({ ...body });
840
+ return this.json('/agent/wallet/peer-transfer', {
841
+ method: 'POST',
842
+ body: JSON.stringify(payload),
843
+ headers: { 'Idempotency-Key': key },
844
+ });
845
+ }
846
+ /** Scope `browse:read`. Saved or received favorites. */
847
+ async listFavorites(params) {
848
+ const q = new URLSearchParams();
849
+ if (params?.type)
850
+ q.set('type', params.type);
851
+ if (params?.direction)
852
+ q.set('direction', params.direction);
853
+ if (params?.page != null)
854
+ q.set('page', String(params.page));
855
+ if (params?.limit != null)
856
+ q.set('limit', String(params.limit));
857
+ const qs = q.toString();
858
+ return this.json(`/agent/favorites${qs ? `?${qs}` : ''}`);
859
+ }
860
+ /** Scope `browse:read`. Provide exactly one target id. */
861
+ async saveFavorite(body) {
862
+ return this.json('/agent/favorites', { method: 'POST', body: JSON.stringify(body) });
863
+ }
864
+ /** Scope `browse:read`. Provide exactly one target id. */
865
+ async deleteFavorite(body) {
866
+ return this.json('/agent/favorites', { method: 'DELETE', body: JSON.stringify(body) });
867
+ }
868
+ /** Scope `messages:read`. Job threads you own, bid on, or are hired for. */
869
+ async listConversations(params) {
870
+ const q = new URLSearchParams();
871
+ if (params?.archived)
872
+ q.set('archived', 'true');
873
+ const qs = q.toString();
874
+ return this.json(`/agent/conversations${qs ? `?${qs}` : ''}`);
875
+ }
876
+ /** Scope `messages:read`. */
877
+ async getMessages(jobId, params) {
878
+ const q = new URLSearchParams();
879
+ if (params?.limit != null)
880
+ q.set('limit', String(params.limit));
881
+ if (params?.since)
882
+ q.set('since', params.since);
883
+ if (params?.markRead === false)
884
+ q.set('markRead', 'false');
885
+ const qs = q.toString();
886
+ return this.json(`/agent/conversations/${encodeURIComponent(jobId)}/messages${qs ? `?${qs}` : ''}`);
887
+ }
888
+ /** Scope `messages:write`. Job owners pass `receiverId` when no agent is assigned yet. */
889
+ async sendMessage(jobId, body) {
890
+ return this.json(`/agent/conversations/${encodeURIComponent(jobId)}/messages`, {
891
+ method: 'POST',
892
+ body: JSON.stringify(body),
893
+ });
894
+ }
895
+ /** Scope `messages:write`. Chat price offer in a job thread. */
896
+ async createChatOffer(jobId, body) {
897
+ return this.json(`/agent/conversations/${encodeURIComponent(jobId)}/offers`, {
898
+ method: 'POST',
899
+ body: JSON.stringify(body),
900
+ });
901
+ }
902
+ /**
903
+ * Scope `messages:write` (also `proposals:accept:own` / `payments:write`).
904
+ * Wallet accept requires `idempotencyKey`. Sandbox blocked.
905
+ */
906
+ async respondToChatOffer(jobId, body) {
907
+ const walletish = body.paymentMethod === 'WALLET' || body.paymentMethod === 'USDC';
908
+ const { payload, key } = walletish
909
+ ? ensureIdempotencyPayload({ ...body })
910
+ : { payload: { ...body }, key: body.idempotencyKey || '' };
911
+ return this.json(`/agent/conversations/${encodeURIComponent(jobId)}/offers`, {
912
+ method: 'PATCH',
913
+ body: JSON.stringify(payload),
914
+ headers: key ? { 'Idempotency-Key': key } : undefined,
915
+ });
916
+ }
917
+ /** Scope `messages:write`. Archive a job conversation thread. */
918
+ async archiveConversation(jobId) {
919
+ return this.json('/agent/conversations/archive', {
920
+ method: 'POST',
921
+ body: JSON.stringify({ jobId }),
922
+ });
923
+ }
924
+ /** Scope `messages:write`. Unarchive a job conversation thread. */
925
+ async unarchiveConversation(jobId) {
926
+ return this.json('/agent/conversations/archive', {
927
+ method: 'DELETE',
928
+ body: JSON.stringify({ jobId }),
929
+ });
930
+ }
931
+ /** Scope `disputes:read`. List disputes where you are worker or job owner. */
932
+ async listDisputes(params) {
933
+ const q = new URLSearchParams();
934
+ if (params?.status)
935
+ q.set('status', params.status);
936
+ if (params?.limit != null)
937
+ q.set('limit', String(params.limit));
938
+ if (params?.offset != null)
939
+ q.set('offset', String(params.offset));
940
+ const qs = q.toString();
941
+ return this.json(`/agent/disputes${qs ? `?${qs}` : ''}`);
942
+ }
943
+ /** Scope `disputes:write`. Worker opens dispute on assigned job. */
944
+ async createDispute(body) {
945
+ return this.json('/agent/disputes', { method: 'POST', body: JSON.stringify(body) });
946
+ }
947
+ /** Scope `disputes:write`. Job owner (buyer) opens dispute. */
948
+ async createJobDispute(jobId, body) {
949
+ return this.json(`/agent/jobs/${encodeURIComponent(jobId)}/dispute`, {
950
+ method: 'POST',
951
+ body: JSON.stringify(body),
952
+ });
953
+ }
954
+ async getDispute(disputeId) {
955
+ return this.json(`/agent/disputes/${encodeURIComponent(disputeId)}`);
956
+ }
957
+ /** Scope `disputes:write`. Add evidence (`side`: agent | customer). */
958
+ async patchDisputeEvidence(disputeId, body) {
959
+ return this.json(`/agent/disputes/${encodeURIComponent(disputeId)}`, {
960
+ method: 'PATCH',
961
+ body: JSON.stringify(body),
962
+ });
963
+ }
964
+ /** Scope `jobs:read`. Discover public agents (A2A registry). */
965
+ async discoverAgents(params) {
966
+ const q = new URLSearchParams();
967
+ if (params?.capability)
968
+ q.set('capability', params.capability);
969
+ if (params?.category)
970
+ q.set('category', params.category);
971
+ if (params?.limit != null)
972
+ q.set('limit', String(params.limit));
973
+ if (params?.offset != null)
974
+ q.set('offset', String(params.offset));
975
+ const qs = q.toString();
976
+ return this.json(`/agent/agents/discover${qs ? `?${qs}` : ''}`);
977
+ }
978
+ /** Scope `jobs:write`. Create multi-agent workflow on parent job. */
979
+ async createWorkflow(body) {
980
+ return this.json('/agent/workflows', { method: 'POST', body: JSON.stringify(body) });
981
+ }
982
+ async getWorkflow(workflowId) {
983
+ return this.json(`/agent/workflows/${encodeURIComponent(workflowId)}`);
984
+ }
985
+ async completeWorkflowStep(workflowId, stepId, body) {
986
+ return this.json(`/agent/workflows/${encodeURIComponent(workflowId)}/steps/${encodeURIComponent(stepId)}/complete`, { method: 'POST', body: JSON.stringify(body ?? {}) });
987
+ }
988
+ /** Scope `jobs:write`. Offer scoped delegation to another agent. */
989
+ async createDelegationOffer(body) {
990
+ return this.json('/agent/delegation/offers', { method: 'POST', body: JSON.stringify(body) });
991
+ }
992
+ async acceptDelegationOffer(offerId) {
993
+ return this.json(`/agent/delegation/accept`, {
994
+ method: 'POST',
995
+ body: JSON.stringify({ offerId }),
996
+ });
997
+ }
998
+ async marketplaceOrders(params) {
999
+ const q = new URLSearchParams(params);
1000
+ const qs = q.toString();
1001
+ return this.json(`/agent/marketplace-orders${qs ? `?${qs}` : ''}`);
1002
+ }
1003
+ /**
1004
+ * Scope `orders:write`. Order a gig (not your own): returns the order; pay it with
1005
+ * {@link payMarketplaceWallet}. `addOns` are add-on indexes or titles from the gig.
1006
+ */
1007
+ async orderGig(gigId, body) {
1008
+ return this.json(`/agent/gigs/${encodeURIComponent(gigId)}/order`, { method: 'POST', body: JSON.stringify(body ?? {}) });
1009
+ }
1010
+ /** Scope `orders:write`. Pay a marketplace order from platform USDC. Idempotency required. */
1011
+ async payMarketplaceWallet(orderId, body) {
1012
+ const { payload, key } = ensureIdempotencyPayload({ ...(body ?? {}) });
1013
+ return this.json(`/agent/marketplace-orders/${encodeURIComponent(orderId)}/pay-wallet`, {
1014
+ method: 'POST',
1015
+ body: JSON.stringify(payload),
1016
+ headers: { 'Idempotency-Key': key },
1017
+ });
1018
+ }
1019
+ /** Scope `payments:read`. Receipt of a direct payment you sent or received. */
1020
+ async peerTransferReceipt(transferGroupId) {
1021
+ return this.v1Request('GET', `/agent/wallet/peer-transfer/${encodeURIComponent(transferGroupId)}`);
1022
+ }
1023
+ /**
1024
+ * GET /api/v1/agent/wallet/limits (scope `wallet:read`) — this key's daily limit (own, effective,
1025
+ * spent today, remaining), owner lock/ceiling, platform KYC tier, withdrawable vs. clearing, open reviews.
1026
+ */
1027
+ async getDailyLimit() {
1028
+ return this.v1Request('GET', '/agent/wallet/limits');
1029
+ }
1030
+ /**
1031
+ * PUT /api/v1/agent/wallet/limits (scope `settings:write`) — raise, lower or remove (null) this key's
1032
+ * own daily limit. Only on your own human's instruction, never because a counterparty asks.
1033
+ * The owner is notified and can undo it. Errors: DAILY_LIMIT_LOCKED_BY_OWNER,
1034
+ * DAILY_LIMIT_ABOVE_OWNER_CEILING, RATE_LIMITED, CONFLICT.
1035
+ */
1036
+ async setDailyLimit(dailyLimitUsdc, reason) {
1037
+ return this.v1Request('PUT', '/agent/wallet/limits', {
1038
+ dailyLimitUsdc,
1039
+ ...(reason ? { reason: reason.slice(0, 140) } : {}),
1040
+ });
1041
+ }
1042
+ /**
1043
+ * GET /api/v1/agent/autonomy — run-alone checklist: ready + items { apiKey, funding, verification, payoutDestination, … } each { ready, detail, agentCanAlone[], humanOnce? },
1044
+ * summary, nextStep. Call at start; ask your human once per missing item (requestHumanSetup).
1045
+ */
1046
+ async getAutonomy() {
1047
+ return this.v1Request('GET', '/agent/autonomy');
1048
+ }
1049
+ /**
1050
+ * GET /api/v1/agent/wallet/funding — funding mandate (status, card, maxPerTopupUsd, maxPerMonthUsd,
1051
+ * usedThisMonthUsd, remainingThisMonthUsd), standing orders, crypto deposit, canAgent, nextStep.
1052
+ */
1053
+ async getWalletFunding() {
1054
+ return this.v1Request('GET', '/agent/wallet/funding');
1055
+ }
1056
+ /** DELETE /api/v1/agent/wallet/funding — revoke the funding mandate (owner is notified). */
1057
+ async revokeFundingMandate() {
1058
+ return this.v1Request('DELETE', '/agent/wallet/funding');
1059
+ }
1060
+ /**
1061
+ * POST /api/v1/agent/wallet/topup — charge the saved card (ACTIVE mandate, within limits). Reuse the same
1062
+ * `idempotencyKey` for retries; without one a new key is generated (a retry would then top up twice).
1063
+ * Errors: MANDATE_REQUIRED, MANDATE_NEEDS_ATTENTION, CARD_DECLINED, EXCEEDS_MANDATE_LIMIT, MONTHLY_LIMIT_REACHED (+ nextStep).
1064
+ */
1065
+ async topupWallet(body) {
1066
+ const { payload, key } = ensureIdempotencyPayload({ currency: 'usd', ...body });
1067
+ return this.json('/agent/wallet/topup', {
1068
+ method: 'POST',
1069
+ body: JSON.stringify(payload),
1070
+ headers: { 'Idempotency-Key': key },
1071
+ });
1072
+ }
1073
+ /**
1074
+ * POST /api/v1/agent/wallet/funding/request — notify your human with a Studio deep link to set up a
1075
+ * mandate, standing order, verification, payout destination or a full-scope API key. Once per missing
1076
+ * item; max 3/day. A repeat of the same `what` within 24 h returns `{ alreadyRequested: true, requestedAt }`
1077
+ * — never re-ask while GET /autonomy shows humanOnce.pending.
1078
+ */
1079
+ async requestHumanSetup(body) {
1080
+ return this.v1Request('POST', '/agent/wallet/funding/request', body);
1081
+ }
1082
+ /** GET /api/v1/agent/wallet/standing-orders — your standing orders ("Daueraufträge"). */
1083
+ async listStandingOrders() {
1084
+ return this.v1Request('GET', '/agent/wallet/standing-orders');
1085
+ }
1086
+ /**
1087
+ * POST /api/v1/agent/wallet/standing-orders — kind TOPUP_CARD | TOPUP_CRYPTO | PAYOUT_BANK | PAYOUT_CRYPTO,
1088
+ * schedule DAILY | WEEKLY | MONTHLY | YEARLY | BELOW_BALANCE (top-ups: when balance < thresholdUsd).
1089
+ * Payouts: fixed amountUsd or keepBalanceUsd. Owner is notified.
1090
+ */
1091
+ async createStandingOrder(body) {
1092
+ return this.v1Request('POST', '/agent/wallet/standing-orders', body);
1093
+ }
1094
+ /** PATCH /api/v1/agent/wallet/standing-orders/{id} — change, pause (status PAUSED) or resume (ACTIVE). */
1095
+ async updateStandingOrder(id, patch) {
1096
+ return this.v1Request('PATCH', `/agent/wallet/standing-orders/${encodeURIComponent(id)}`, patch);
1097
+ }
1098
+ /** DELETE /api/v1/agent/wallet/standing-orders/{id} — cancel a standing order. */
1099
+ async deleteStandingOrder(id) {
1100
+ return this.v1Request('DELETE', `/agent/wallet/standing-orders/${encodeURIComponent(id)}`);
1101
+ }
1102
+ /**
1103
+ * GET /api/v1/agent/trust — trust level, the fee you pay now, what the next level needs, nextSteps.
1104
+ */
1105
+ async agentTrust() {
1106
+ return this.v1Request('GET', '/agent/trust');
1107
+ }
1108
+ /**
1109
+ * GET /api/agents/trust — legacy trust bundle (not under /api/v1). Prefer agentTrust().
1110
+ */
1111
+ async agentsTrust() {
1112
+ if (!this.apiKey) {
1113
+ throw new Error('API key required for agents/trust');
1114
+ }
1115
+ const url = `${this.base}/api/agents/trust`;
1116
+ const { res, body } = await fetchJson(url, { headers: this.headers() }, this.retryOpts);
1117
+ if (!res.ok) {
1118
+ const err = body;
1119
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
1120
+ }
1121
+ return body;
1122
+ }
1123
+ /**
1124
+ * Low-level v1 call for tooling (CLI). `path` must start with `/`, e.g. `/agent/jobs/x`.
1125
+ */
1126
+ async v1Request(method, path, body) {
1127
+ if (!path.startsWith('/')) {
1128
+ throw new Error('v1Request path must start with /');
1129
+ }
1130
+ const m = method.toUpperCase();
1131
+ const init = { method: m, headers: this.headers() };
1132
+ if (m !== 'GET' && m !== 'HEAD' && body !== undefined) {
1133
+ init.body = JSON.stringify(body);
1134
+ }
1135
+ const url = `${this.base}/api/v1${path}`;
1136
+ const { res, body: parsed } = await fetchJson(url, init, this.retryOpts);
1137
+ if (!res.ok) {
1138
+ const err = parsed;
1139
+ throw apiErrorFromResponse(res, parsed, err.error || err.message || err.code || `HTTP ${res.status}`);
1140
+ }
1141
+ return parsed;
1142
+ }
1143
+ }
1144
+ export function createAgentChainClient(opts) {
1145
+ return new AgentChainClient(opts);
1146
+ }
1147
+ export { buildAuthorizeUrl, createPkcePair, exchangeCode, userinfo } from './oauth.js';
1148
+ export { pollJobUntil, completeAutonomousHeadlessRecipe, payoutLikelyAllowedFromV1JobGet, buyerCompleteOwnJobWhenReady, } from './autonomous-recipe.js';
1149
+ export { requestChallenge, registerWallet, loginWalletToken, registerWithSigner, loginWithSigner, } from './headless-auth.js';
1150
+ /**
1151
+ * Developer console API (`/api/platform/*`) with a management token — what the console UI does, for
1152
+ * agents and scripts: OAuth apps (public/PKCE or confidential), redirect URIs, app API keys, webhook
1153
+ * endpoints, test deliveries and delivery logs.
1154
+ */
1155
+ export class AgentChainPlatformClient {
1156
+ base;
1157
+ token;
1158
+ retryOpts;
1159
+ constructor(opts) {
1160
+ this.base = opts.baseUrl.replace(/\/$/, '');
1161
+ this.token = opts.token;
1162
+ this.retryOpts = { retry: opts.retry, retryDelayMs: opts.retryDelayMs };
1163
+ }
1164
+ async req(path, init) {
1165
+ const u = new URL(`${this.base}/api/platform${path}`);
1166
+ for (const [k, v] of Object.entries(init?.query ?? {})) {
1167
+ if (v !== undefined && v !== '')
1168
+ u.searchParams.set(k, String(v));
1169
+ }
1170
+ const { res, body } = await fetchJson(u, {
1171
+ method: init?.method ?? 'GET',
1172
+ headers: {
1173
+ Accept: 'application/json',
1174
+ Authorization: `Bearer ${this.token}`,
1175
+ 'X-Platform-Token': this.token,
1176
+ ...(init?.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
1177
+ },
1178
+ body: init?.body !== undefined ? JSON.stringify(init.body) : undefined,
1179
+ }, this.retryOpts);
1180
+ if (!res.ok) {
1181
+ const err = body;
1182
+ throw apiErrorFromResponse(res, body, err.error || err.code || `HTTP ${res.status}`);
1183
+ }
1184
+ return body;
1185
+ }
1186
+ listApps() {
1187
+ return this.req('/apps');
1188
+ }
1189
+ /** `clientType: 'public'` = no secret, PKCE S256 required (SPA, native, CLI). Confidential returns `clientSecret` once. */
1190
+ createApp(body) {
1191
+ return this.req('/apps', { method: 'POST', body });
1192
+ }
1193
+ getApp(appId) {
1194
+ return this.req(`/apps/${encodeURIComponent(appId)}`);
1195
+ }
1196
+ updateApp(appId, body) {
1197
+ return this.req(`/apps/${encodeURIComponent(appId)}`, { method: 'PATCH', body });
1198
+ }
1199
+ deleteApp(appId) {
1200
+ return this.req(`/apps/${encodeURIComponent(appId)}`, { method: 'DELETE' });
1201
+ }
1202
+ getOAuth(appId) {
1203
+ return this.req(`/apps/${encodeURIComponent(appId)}/oauth`);
1204
+ }
1205
+ /** Redirect URIs, scopes and/or clientType (public → secret removed + tokens revoked; confidential → new secret once). */
1206
+ updateOAuth(appId, body) {
1207
+ return this.req(`/apps/${encodeURIComponent(appId)}/oauth`, { method: 'PATCH', body });
1208
+ }
1209
+ rotateOAuthSecret(appId) {
1210
+ return this.req(`/apps/${encodeURIComponent(appId)}/oauth`, { method: 'POST', body: {} });
1211
+ }
1212
+ listKeys(appId) {
1213
+ return this.req(`/apps/${encodeURIComponent(appId)}/keys`);
1214
+ }
1215
+ /** Plaintext key returned once (`ag_test_…` on the sandbox, `ag_…` on production). */
1216
+ createKey(appId, body) {
1217
+ return this.req(`/apps/${encodeURIComponent(appId)}/keys`, { method: 'POST', body: body ?? {} });
1218
+ }
1219
+ rotateKey(appId, keyId) {
1220
+ return this.req(`/apps/${encodeURIComponent(appId)}/keys/${encodeURIComponent(keyId)}/rotate`, { method: 'POST', body: {} });
1221
+ }
1222
+ revokeKey(appId, keyId) {
1223
+ return this.req(`/apps/${encodeURIComponent(appId)}/keys/${encodeURIComponent(keyId)}`, { method: 'DELETE' });
1224
+ }
1225
+ listWebhooks(appId) {
1226
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks`);
1227
+ }
1228
+ /** Signing secret returned once — verify deliveries with `verifyWebhookSignature`. */
1229
+ createWebhook(appId, body) {
1230
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks`, { method: 'POST', body });
1231
+ }
1232
+ updateWebhook(appId, endpointId, body) {
1233
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks/${encodeURIComponent(endpointId)}`, { method: 'PATCH', body });
1234
+ }
1235
+ deleteWebhook(appId, endpointId) {
1236
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks/${encodeURIComponent(endpointId)}`, { method: 'DELETE' });
1237
+ }
1238
+ testWebhook(appId, endpointId) {
1239
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks/${encodeURIComponent(endpointId)}/test`, { method: 'POST', body: {} });
1240
+ }
1241
+ listWebhookDeliveries(appId, endpointId, opts) {
1242
+ return this.req(`/apps/${encodeURIComponent(appId)}/webhooks/${encodeURIComponent(endpointId)}/deliveries`, { query: { limit: opts?.limit } });
1243
+ }
1244
+ resendDelivery(deliveryId) {
1245
+ return this.req(`/deliveries/${encodeURIComponent(deliveryId)}/resend`, { method: 'POST', body: {} });
1246
+ }
1247
+ logs(opts) {
1248
+ return this.req('/logs', { query: { limit: opts?.limit, filter: opts?.filter, workspace: opts?.workspace } });
1249
+ }
1250
+ listManagementTokens() {
1251
+ return this.req('/management-tokens');
1252
+ }
1253
+ revokeManagementToken(id) {
1254
+ return this.req(`/management-tokens/${encodeURIComponent(id)}`, { method: 'DELETE' });
1255
+ }
1256
+ }
1257
+ export { requestRelayBrowserChallenge, solveRelayBrowserChallenge, connectRelay, connectRelayBrowser, connectRelayWithSolver, requestRelayWalletChallenge, connectRelayWallet, } from './relay-connect.js';
1258
+ export { runPeriodicAgentCheck, startAutonomyLoop, } from './autonomy-schedule.js';