@near-intents-agent-api/sdk 0.1.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.mjs ADDED
@@ -0,0 +1,613 @@
1
+ import { sha256 } from "@noble/hashes/sha2.js";
2
+ import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
3
+ import { base64urlnopad } from "@scure/base";
4
+ //#region src/errors.ts
5
+ /** A transport, cancellation or parsing failure carrying the key of the original request. */
6
+ var AgentApiRequestError = class extends Error {
7
+ idempotencyKey;
8
+ constructor(cause, idempotencyKey) {
9
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
10
+ this.name = cause instanceof Error ? cause.name : "AgentApiRequestError";
11
+ this.idempotencyKey = idempotencyKey;
12
+ }
13
+ };
14
+ /** Preserve SDK error classes and unkeyed failures; wrap other keyed failures for safe retry. */
15
+ function requestError(error, idempotencyKey) {
16
+ if (idempotencyKey === void 0 || error instanceof AgentApiError || error instanceof AgentApiResponseTooLargeError) return error;
17
+ return new AgentApiRequestError(error, idempotencyKey);
18
+ }
19
+ /** A success or error body exceeded the client's byte budget before JSON parsing. */
20
+ var AgentApiResponseTooLargeError = class extends Error {
21
+ code = "response_too_large";
22
+ status;
23
+ maxResponseBytes;
24
+ idempotencyKey;
25
+ constructor(status, maxResponseBytes, idempotencyKey) {
26
+ super(`Response exceeds ${maxResponseBytes} bytes`);
27
+ this.name = "AgentApiResponseTooLargeError";
28
+ this.status = status;
29
+ this.maxResponseBytes = maxResponseBytes;
30
+ this.idempotencyKey = idempotencyKey;
31
+ }
32
+ };
33
+ /**
34
+ * A non-2xx response. `code` is the first error's stable snake_case code; branch on it, never on
35
+ * `title` or `detail`. `errors` holds every entry (validation failures return one per field).
36
+ */
37
+ var AgentApiError = class extends Error {
38
+ status;
39
+ code;
40
+ title;
41
+ detail;
42
+ retryable;
43
+ availableAt;
44
+ requestId;
45
+ errors;
46
+ idempotencyKey;
47
+ constructor(status, document, idempotencyKey) {
48
+ const first = document?.errors?.[0];
49
+ super(first?.detail ?? first?.title ?? `HTTP ${status}`);
50
+ this.name = "AgentApiError";
51
+ this.status = status;
52
+ this.code = first?.code ?? "http_error";
53
+ this.title = first?.title ?? `HTTP ${status}`;
54
+ this.detail = first?.detail;
55
+ this.retryable = first?.meta?.retryable ?? false;
56
+ this.availableAt = first?.meta?.available_at;
57
+ this.requestId = document?.meta?.request_id;
58
+ this.errors = document?.errors ?? [];
59
+ this.idempotencyKey = idempotencyKey;
60
+ }
61
+ };
62
+ //#endregion
63
+ //#region src/generated/routes.ts
64
+ const endpoints = {
65
+ generateIntent: {
66
+ operationId: "generateIntent",
67
+ method: "post",
68
+ path: "/v1/generate-intent",
69
+ idempotency: "optional"
70
+ },
71
+ submitIntent: {
72
+ operationId: "submitIntent",
73
+ method: "post",
74
+ path: "/v1/submit-intent"
75
+ },
76
+ getStatus: {
77
+ operationId: "getStatus",
78
+ method: "get",
79
+ path: "/v1/status"
80
+ },
81
+ getHistory: {
82
+ operationId: "getHistory",
83
+ method: "get",
84
+ path: "/v1/agents/{agent_id}/history"
85
+ },
86
+ listAgents: {
87
+ operationId: "listAgents",
88
+ method: "get",
89
+ path: "/v1/agents"
90
+ },
91
+ getAgent: {
92
+ operationId: "getAgent",
93
+ method: "get",
94
+ path: "/v1/agents/{agent_id}"
95
+ },
96
+ getWallet: {
97
+ operationId: "getWallet",
98
+ method: "get",
99
+ path: "/v1/agents/{agent_id}/wallet"
100
+ },
101
+ getBalances: {
102
+ operationId: "getBalances",
103
+ method: "get",
104
+ path: "/v1/agents/{agent_id}/balances"
105
+ },
106
+ getPolicy: {
107
+ operationId: "getPolicy",
108
+ method: "get",
109
+ path: "/v1/agents/{agent_id}/policy"
110
+ },
111
+ getPolicyHistory: {
112
+ operationId: "getPolicyHistory",
113
+ method: "get",
114
+ path: "/v1/agents/{agent_id}/policy/history"
115
+ },
116
+ listGrants: {
117
+ operationId: "listGrants",
118
+ method: "get",
119
+ path: "/v1/agents/{agent_id}/grants"
120
+ },
121
+ listScheduledExecutions: {
122
+ operationId: "listScheduledExecutions",
123
+ method: "get",
124
+ path: "/v1/agents/{agent_id}/executions/scheduled"
125
+ },
126
+ getContainment: {
127
+ operationId: "getContainment",
128
+ method: "get",
129
+ path: "/v1/agents/{agent_id}/containment"
130
+ },
131
+ listApprovals: {
132
+ operationId: "listApprovals",
133
+ method: "get",
134
+ path: "/v1/agents/{agent_id}/approvals"
135
+ },
136
+ getApproval: {
137
+ operationId: "getApproval",
138
+ method: "get",
139
+ path: "/v1/agents/{agent_id}/approvals/{approval_id}"
140
+ },
141
+ getAddress: {
142
+ operationId: "getAddress",
143
+ method: "get",
144
+ path: "/v1/agents/{agent_id}/addresses/{chain}"
145
+ },
146
+ listProviderRecords: {
147
+ operationId: "listProviderRecords",
148
+ method: "get",
149
+ path: "/v1/agents/{agent_id}/provider/{kind}"
150
+ },
151
+ swap: {
152
+ operationId: "swap",
153
+ method: "post",
154
+ path: "/v1/agents/{agent_id}/swap",
155
+ grant: true,
156
+ idempotency: "required"
157
+ },
158
+ withdraw: {
159
+ operationId: "withdraw",
160
+ method: "post",
161
+ path: "/v1/agents/{agent_id}/withdraw",
162
+ grant: true,
163
+ idempotency: "required"
164
+ },
165
+ transfer: {
166
+ operationId: "transfer",
167
+ method: "post",
168
+ path: "/v1/agents/{agent_id}/transfer",
169
+ grant: true,
170
+ idempotency: "required"
171
+ },
172
+ shield: {
173
+ operationId: "shield",
174
+ method: "post",
175
+ path: "/v1/agents/{agent_id}/shield",
176
+ grant: true,
177
+ idempotency: "required"
178
+ },
179
+ unshield: {
180
+ operationId: "unshield",
181
+ method: "post",
182
+ path: "/v1/agents/{agent_id}/unshield",
183
+ grant: true,
184
+ idempotency: "required"
185
+ },
186
+ deposit: {
187
+ operationId: "deposit",
188
+ method: "post",
189
+ path: "/v1/agents/{agent_id}/deposit",
190
+ idempotency: "required"
191
+ },
192
+ recover: {
193
+ operationId: "recover",
194
+ method: "post",
195
+ path: "/v1/agents/{agent_id}/recover",
196
+ grant: true,
197
+ idempotency: "required"
198
+ },
199
+ signMessage: {
200
+ operationId: "signMessage",
201
+ method: "post",
202
+ path: "/v1/agents/{agent_id}/sign-message",
203
+ grant: true
204
+ },
205
+ getTokens: {
206
+ operationId: "getTokens",
207
+ method: "get",
208
+ path: "/v1/tokens"
209
+ },
210
+ getNetwork: {
211
+ operationId: "getNetwork",
212
+ method: "get",
213
+ path: "/v1/network"
214
+ },
215
+ whoami: {
216
+ operationId: "whoami",
217
+ method: "get",
218
+ path: "/v1/whoami"
219
+ },
220
+ getPartnerQuota: {
221
+ operationId: "getPartnerQuota",
222
+ method: "get",
223
+ path: "/v1/quotas"
224
+ }
225
+ };
226
+ //#endregion
227
+ //#region src/grants.ts
228
+ /** Shape of a grant token: `ngt_` plus 32 random bytes in unpadded base64url. */
229
+ const grantTokenPattern = /^ngt_[A-Za-z0-9_-]{43}$/;
230
+ /** The commitment of an existing grant token. */
231
+ function grantCommitment(token) {
232
+ return bytesToHex(sha256(utf8ToBytes(token)));
233
+ }
234
+ /**
235
+ * Creates the token for one new grant. Send only `commitment` in `grant_issue`; the owner signs
236
+ * it, and afterwards `api.forGrant(token)` runs calls under that grant. Use a new token for every
237
+ * grant.
238
+ */
239
+ function createGrantCredential() {
240
+ const token = `ngt_${base64urlnopad.encode(crypto.getRandomValues(new Uint8Array(32)))}`;
241
+ return {
242
+ token,
243
+ commitment: grantCommitment(token)
244
+ };
245
+ }
246
+ //#endregion
247
+ //#region src/idempotency.ts
248
+ /** Creates a key for one logical request. Save it before sending when recovery must survive a restart. */
249
+ function createIdempotencyKey() {
250
+ return crypto.randomUUID();
251
+ }
252
+ /** Resolve one request's identity before dispatch; recovery must use its existing key. */
253
+ function requestIdempotency(endpoint, call) {
254
+ const dry = (endpoint === endpoints.swap || endpoint === endpoints.withdraw) && call.body?.dry === true;
255
+ const keyed = Boolean(endpoint.idempotency) && !dry;
256
+ return {
257
+ dry,
258
+ keyed,
259
+ idempotencyKey: keyed && endpoint !== endpoints.recover && call.idempotencyKey === void 0 ? createIdempotencyKey() : call.idempotencyKey
260
+ };
261
+ }
262
+ //#endregion
263
+ //#region src/response-utils.ts
264
+ /** Parse a bounded response, preserving HTTP errors even when their body is not JSON. */
265
+ async function readResponseJson(response, maxResponseBytes, idempotencyKey) {
266
+ const text = await readResponseText(response, maxResponseBytes, idempotencyKey);
267
+ let json;
268
+ try {
269
+ json = text ? JSON.parse(text) : void 0;
270
+ } catch (error) {
271
+ if (response.ok) throw error;
272
+ }
273
+ if (!response.ok) throw new AgentApiError(response.status, json, idempotencyKey);
274
+ return json;
275
+ }
276
+ /** Count bytes before retaining chunks; decode once to preserve Fetch's UTF-8 behavior. */
277
+ async function readResponseText(response, maxResponseBytes, idempotencyKey) {
278
+ const reader = response.body?.getReader();
279
+ if (!reader) return "";
280
+ const chunks = [];
281
+ let size = 0;
282
+ try {
283
+ for (;;) {
284
+ const { done, value } = await reader.read();
285
+ if (done) break;
286
+ size += value.byteLength;
287
+ if (size > maxResponseBytes) throw new AgentApiResponseTooLargeError(response.status, maxResponseBytes, idempotencyKey);
288
+ chunks.push(value);
289
+ }
290
+ return new TextDecoder().decode(Buffer.concat(chunks, size));
291
+ } finally {
292
+ reader.cancel().catch(() => void 0);
293
+ reader.releaseLock();
294
+ }
295
+ }
296
+ //#endregion
297
+ //#region src/client.ts
298
+ /** The hosted API. Pass `baseUrl` only for a self-hosted or local deployment. */
299
+ const DEFAULT_BASE_URL = "https://api.agentsonintents.com";
300
+ const settlementEndpoints = new Set([
301
+ endpoints.swap,
302
+ endpoints.withdraw,
303
+ endpoints.transfer,
304
+ endpoints.shield,
305
+ endpoints.unshield
306
+ ]);
307
+ /** `{agent_id}` path segments filled from `params`, each URI-encoded. */
308
+ function fillPath(template, params = {}) {
309
+ return template.replace(/\{([^}]+)\}/g, (_, name) => {
310
+ const value = params[name];
311
+ if (value === void 0) throw new Error(`missing path parameter ${name}`);
312
+ return encodeURIComponent(value);
313
+ });
314
+ }
315
+ /**
316
+ * Typed client for the NEAR Intents Agent API. One method per endpoint; paths and methods come
317
+ * from the shared endpoint registry, so the client cannot drift from the server.
318
+ *
319
+ * Owner actions follow a generate/submit shape: `generateIntent` returns
320
+ * `intent: { standard, payload }`, your frontend has the owner's wallet sign `payload`
321
+ * unchanged, and `submitIntent` sends the wallet's output back. `getStatus` tracks the result.
322
+ */
323
+ var AgentApi = class AgentApi {
324
+ origin;
325
+ fetcher;
326
+ maxResponseBytes;
327
+ constructor(options) {
328
+ this.options = options;
329
+ const url = new URL(options.baseUrl ?? "https://api.agentsonintents.com");
330
+ const local = [
331
+ "localhost",
332
+ "127.0.0.1",
333
+ "[::1]"
334
+ ].includes(url.hostname);
335
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && local)) throw new Error("baseUrl must use HTTPS");
336
+ if (url.username || url.password || url.search || url.hash || url.pathname !== "/") throw new Error("baseUrl must be a plain origin");
337
+ if (!/^naa_[A-Za-z0-9_-]{43}$/.test(options.apiKey)) throw new Error("apiKey is not a naa_ key");
338
+ if (options.grantToken !== void 0 && !grantTokenPattern.test(options.grantToken)) throw new Error("grantToken is not an ngt_ token");
339
+ const maxResponseBytes = options.maxResponseBytes === void 0 ? 8 * 1048576 : options.maxResponseBytes;
340
+ if (!Number.isSafeInteger(maxResponseBytes) || maxResponseBytes <= 0) throw new RangeError("maxResponseBytes must be a positive safe integer");
341
+ this.origin = url.href.replace(/\/$/, "");
342
+ this.fetcher = options.fetch ?? fetch;
343
+ this.maxResponseBytes = maxResponseBytes;
344
+ }
345
+ getPartnerQuota(options = {}) {
346
+ return this.call(endpoints.getPartnerQuota, options);
347
+ }
348
+ headers(endpoint, call) {
349
+ const headers = {
350
+ accept: "application/json",
351
+ "x-api-key": this.options.apiKey
352
+ };
353
+ if (call.body !== void 0) headers["content-type"] = "application/json";
354
+ if (call.idempotencyKey !== void 0) headers["idempotency-key"] = call.idempotencyKey;
355
+ if (endpoint.grant && this.options.grantToken) headers["x-grant-token"] = this.options.grantToken;
356
+ return headers;
357
+ }
358
+ async call(endpoint, call = {}) {
359
+ const { dry, keyed, idempotencyKey } = requestIdempotency(endpoint, call);
360
+ try {
361
+ const url = new URL(`${this.origin}${fillPath(endpoint.path, call.params)}`);
362
+ for (const [key, value] of Object.entries(call.query ?? {})) if (value !== void 0) url.searchParams.set(key, String(value));
363
+ const headers = this.headers(endpoint, {
364
+ ...call,
365
+ idempotencyKey
366
+ });
367
+ const settlement = settlementEndpoints.has(endpoint) && !dry;
368
+ const signals = [AbortSignal.timeout(this.options.timeoutMs ?? (settlement ? 15e4 : 65e3))];
369
+ if (call.signal) signals.push(call.signal);
370
+ const json = await readResponseJson(await this.fetcher(url, {
371
+ method: endpoint.method.toUpperCase(),
372
+ headers,
373
+ body: call.body === void 0 ? void 0 : JSON.stringify(call.body),
374
+ signal: AbortSignal.any(signals),
375
+ redirect: "error",
376
+ credentials: "omit"
377
+ }), this.maxResponseBytes, idempotencyKey);
378
+ return keyed && idempotencyKey !== void 0 ? {
379
+ ...json,
380
+ idempotencyKey
381
+ } : json;
382
+ } catch (error) {
383
+ throw requestError(error, idempotencyKey);
384
+ }
385
+ }
386
+ /**
387
+ * A client that runs delegated calls under one owner grant. Keep one per session, assistant or
388
+ * bot: each carries its own token, so concurrent calls never borrow another grant.
389
+ */
390
+ forGrant(grantToken) {
391
+ return new AgentApi({
392
+ ...this.options,
393
+ grantToken,
394
+ maxResponseBytes: this.maxResponseBytes
395
+ });
396
+ }
397
+ /**
398
+ * Builds the payload the owner's wallet must sign for one owner action. Omission generates
399
+ * a new `idempotencyKey`; reuse the response/error key to retry the same intent.
400
+ * Interrupted generation returns
401
+ * `intent_generation_recovery_required` unless a saved payload or complete preparation can
402
+ * recover the original request; the key never
403
+ * starts another builder. Keep that key for recovery.
404
+ */
405
+ generateIntent(request, options = {}) {
406
+ return this.call(endpoints.generateIntent, {
407
+ body: request,
408
+ ...options
409
+ });
410
+ }
411
+ /** Sends the wallet's output for a generated intent. Resubmitting the same signature is safe. */
412
+ submitIntent(request, options = {}) {
413
+ return this.call(endpoints.submitIntent, {
414
+ body: request,
415
+ ...options
416
+ });
417
+ }
418
+ /**
419
+ * Status of an intent or execution. `waitMs` (≤ 30 000) long-polls until the status is
420
+ * terminal, needs a signature, or the wait ends.
421
+ */
422
+ getStatus(correlationId, options = {}) {
423
+ return this.call(endpoints.getStatus, {
424
+ query: {
425
+ correlation_id: correlationId,
426
+ wait_ms: options.waitMs,
427
+ refresh: options.refresh
428
+ },
429
+ signal: options.signal
430
+ });
431
+ }
432
+ /** An agent's intents and executions, newest first. */
433
+ getHistory(agentId, query = {}, options = {}) {
434
+ return this.call(endpoints.getHistory, {
435
+ params: { agent_id: agentId },
436
+ query,
437
+ ...options
438
+ });
439
+ }
440
+ listAgents(query = {}, options = {}) {
441
+ return this.call(endpoints.listAgents, {
442
+ query,
443
+ ...options
444
+ });
445
+ }
446
+ getAgent(agentId, options = {}) {
447
+ return this.call(endpoints.getAgent, {
448
+ params: { agent_id: agentId },
449
+ ...options
450
+ });
451
+ }
452
+ getWallet(agentId, options = {}) {
453
+ return this.call(endpoints.getWallet, {
454
+ params: { agent_id: agentId },
455
+ ...options
456
+ });
457
+ }
458
+ getBalances(agentId, query = {}, options = {}) {
459
+ return this.call(endpoints.getBalances, {
460
+ params: { agent_id: agentId },
461
+ query,
462
+ ...options
463
+ });
464
+ }
465
+ /** Public: every asset agents can use, with chain and USD price. Needs no API key. */
466
+ async getTokens(options = {}) {
467
+ return (await this.call(endpoints.getTokens, options)).data;
468
+ }
469
+ getPolicy(agentId, options = {}) {
470
+ return this.call(endpoints.getPolicy, {
471
+ params: { agent_id: agentId },
472
+ ...options
473
+ });
474
+ }
475
+ getPolicyHistory(agentId, query = {}, options = {}) {
476
+ return this.call(endpoints.getPolicyHistory, {
477
+ params: { agent_id: agentId },
478
+ query,
479
+ ...options
480
+ });
481
+ }
482
+ async listGrants(agentId, options = {}) {
483
+ return (await this.call(endpoints.listGrants, {
484
+ params: { agent_id: agentId },
485
+ ...options
486
+ })).data;
487
+ }
488
+ /** Executions the timelock holds, earliest release first; follow `next_cursor` for more. */
489
+ listScheduledExecutions(agentId, query = {}, options = {}) {
490
+ return this.call(endpoints.listScheduledExecutions, {
491
+ params: { agent_id: agentId },
492
+ query,
493
+ ...options
494
+ });
495
+ }
496
+ getContainment(agentId, query = {}, options = {}) {
497
+ return this.call(endpoints.getContainment, {
498
+ params: { agent_id: agentId },
499
+ query,
500
+ ...options
501
+ });
502
+ }
503
+ async listApprovals(agentId, options = {}) {
504
+ return (await this.call(endpoints.listApprovals, {
505
+ params: { agent_id: agentId },
506
+ ...options
507
+ })).data;
508
+ }
509
+ getApproval(agentId, approvalId, options = {}) {
510
+ return this.call(endpoints.getApproval, {
511
+ params: {
512
+ agent_id: agentId,
513
+ approval_id: approvalId
514
+ },
515
+ ...options
516
+ });
517
+ }
518
+ getAddress(agentId, chain, options = {}) {
519
+ return this.call(endpoints.getAddress, {
520
+ params: {
521
+ agent_id: agentId,
522
+ chain
523
+ },
524
+ ...options
525
+ });
526
+ }
527
+ listProviderRecords(agentId, kind, query = {}, options = {}) {
528
+ return this.call(endpoints.listProviderRecords, {
529
+ params: {
530
+ agent_id: agentId,
531
+ kind
532
+ },
533
+ query,
534
+ ...options
535
+ });
536
+ }
537
+ swap(agentId, request, options = {}) {
538
+ return this.call(endpoints.swap, {
539
+ params: { agent_id: agentId },
540
+ body: request,
541
+ ...options
542
+ });
543
+ }
544
+ withdraw(agentId, request, options = {}) {
545
+ return this.call(endpoints.withdraw, {
546
+ params: { agent_id: agentId },
547
+ body: request,
548
+ ...options
549
+ });
550
+ }
551
+ transfer(agentId, request, options = {}) {
552
+ return this.call(endpoints.transfer, {
553
+ params: { agent_id: agentId },
554
+ body: request,
555
+ ...options
556
+ });
557
+ }
558
+ shield(agentId, request, options = {}) {
559
+ return this.call(endpoints.shield, {
560
+ params: { agent_id: agentId },
561
+ body: request,
562
+ ...options
563
+ });
564
+ }
565
+ unshield(agentId, request, options = {}) {
566
+ return this.call(endpoints.unshield, {
567
+ params: { agent_id: agentId },
568
+ body: request,
569
+ ...options
570
+ });
571
+ }
572
+ /** Creates an inbound public or confidential deposit address; no grant token is needed. */
573
+ deposit(agentId, request, options = {}) {
574
+ return this.call(endpoints.deposit, {
575
+ params: { agent_id: agentId },
576
+ body: request,
577
+ ...options
578
+ });
579
+ }
580
+ /** First dispatch of an UNCERTAIN execution that provably never reached the provider. Requires its original key. */
581
+ recover(agentId, request, options) {
582
+ return this.call(endpoints.recover, {
583
+ params: { agent_id: agentId },
584
+ body: request,
585
+ ...options
586
+ });
587
+ }
588
+ /**
589
+ * Signs a canonical identity challenge with the agent's NEAR key (NEP-413). Needs a grant token,
590
+ * a policy listing the recipient, and a server with NEAR message signing enabled.
591
+ */
592
+ signMessage(agentId, request, options = {}) {
593
+ return this.call(endpoints.signMessage, {
594
+ params: { agent_id: agentId },
595
+ body: request,
596
+ ...options
597
+ });
598
+ }
599
+ getNetwork(options = {}) {
600
+ return this.call(endpoints.getNetwork, options);
601
+ }
602
+ whoami(options = {}) {
603
+ return this.call(endpoints.whoami, options);
604
+ }
605
+ };
606
+ /** Creates a client. Keep the API key on your backend. */
607
+ function createAgentApi(options) {
608
+ return new AgentApi(options);
609
+ }
610
+ //#endregion
611
+ export { AgentApi, AgentApiError, AgentApiRequestError, AgentApiResponseTooLargeError, DEFAULT_BASE_URL, createAgentApi, createGrantCredential, createIdempotencyKey, grantCommitment };
612
+
613
+ //# sourceMappingURL=index.mjs.map