@intentfi/agent-sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -1,15 +1,96 @@
1
1
  const MCP_PROTOCOL_VERSION = "2025-11-25";
2
+ const X402_VERSION = 2;
3
+ const PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
4
+ const PAYMENT_SIGNATURE_HEADER = "PAYMENT-SIGNATURE";
5
+ const PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE";
6
+ const PAYMENT_IDENTIFIER_EXTENSION = "payment-identifier";
7
+ export class DefaultX402PaymentManager {
8
+ policy;
9
+ approve;
10
+ schemes = new Map();
11
+ constructor(options = {}) {
12
+ this.policy = options.policy ?? {};
13
+ this.approve = options.approve;
14
+ for (const [scheme, handler] of Object.entries(options.schemes ?? {})) {
15
+ this.schemes.set(scheme.toLowerCase(), handler);
16
+ }
17
+ }
18
+ registerScheme(scheme, handler) {
19
+ this.schemes.set(scheme.trim().toLowerCase(), handler);
20
+ return this;
21
+ }
22
+ async buildPaymentPayload(args) {
23
+ const requirement = this.selectRequirement(args.paymentRequired);
24
+ const context = {
25
+ transport: args.transport,
26
+ operation: args.operation,
27
+ ...(args.idempotencyKey ? { idempotencyKey: args.idempotencyKey } : {}),
28
+ paymentRequired: args.paymentRequired,
29
+ requirement,
30
+ };
31
+ if (this.approve) {
32
+ const approved = await this.approve({ context });
33
+ if (!approved) {
34
+ throw new Error("x402 payment was not approved.");
35
+ }
36
+ }
37
+ const handler = this.schemes.get(requirement.scheme.trim().toLowerCase());
38
+ if (!handler) {
39
+ throw new Error(`No x402 scheme handler is registered for '${requirement.scheme}'.`);
40
+ }
41
+ const payload = await handler({ context });
42
+ const extensions = echoRequiredExtensions(args.paymentRequired.extensions, args.idempotencyKey);
43
+ return {
44
+ x402Version: X402_VERSION,
45
+ resource: args.paymentRequired.resource,
46
+ accepted: requirement,
47
+ payload,
48
+ ...(extensions ? { extensions } : {}),
49
+ };
50
+ }
51
+ async onSettlement(args) {
52
+ void args;
53
+ }
54
+ selectRequirement(paymentRequired) {
55
+ for (const requirement of paymentRequired.accepts) {
56
+ if (!this.passesPolicy(requirement)) {
57
+ continue;
58
+ }
59
+ if (!this.schemes.has(requirement.scheme.trim().toLowerCase())) {
60
+ continue;
61
+ }
62
+ return requirement;
63
+ }
64
+ throw new Error("No x402 payment requirement matched the configured policy or available schemes.");
65
+ }
66
+ passesPolicy(requirement) {
67
+ if (this.policy.allowedNetworks
68
+ && this.policy.allowedNetworks.length > 0
69
+ && !this.policy.allowedNetworks.includes(requirement.network)) {
70
+ return false;
71
+ }
72
+ if (this.policy.maxAmountAtomic !== undefined) {
73
+ const amount = parseAtomicAmount(requirement.amount);
74
+ if (amount > this.policy.maxAmountAtomic) {
75
+ return false;
76
+ }
77
+ }
78
+ return true;
79
+ }
80
+ }
2
81
  export class IntentFiClient {
3
82
  baseUrl;
4
83
  apiKey;
5
84
  transport;
6
85
  fetchImpl;
86
+ paymentManager;
7
87
  mcpSessionId;
8
88
  constructor(options = {}) {
9
89
  this.baseUrl = (options.baseUrl ?? process.env.INTENTFI_BASE_URL ?? "https://agentic.intentfi.io").replace(/\/+$/, "");
10
90
  this.apiKey = options.apiKey ?? process.env.INTENTFI_API_KEY;
11
91
  this.transport = resolveTransportMode(options.transport ?? process.env.INTENTFI_TRANSPORT);
12
92
  this.fetchImpl = options.fetchImpl ?? fetch;
93
+ this.paymentManager = options.paymentManager;
13
94
  }
14
95
  async requestSiweChallenge(args) {
15
96
  const response = await this.fetchJson("/agent/v1/auth/siwe/challenge", {
@@ -31,14 +112,14 @@ export class IntentFiClient {
31
112
  });
32
113
  }
33
114
  async createWorkflow(args) {
34
- this.assertApiKey("createWorkflow");
115
+ this.assertCredential("createWorkflow", { allowX402: true });
35
116
  if (this.transport === "mcp") {
36
- return await this.mcpToolCall("intentfi.create_workflow", args);
117
+ return await this.mcpToolCall("intentfi.create_workflow", args, { idempotencyKey: args.operationId });
37
118
  }
38
119
  return await this.createWorkflowRest(args);
39
120
  }
40
121
  async getWorkflow(args) {
41
- this.assertApiKey("getWorkflow");
122
+ this.assertCredential("getWorkflow");
42
123
  if (this.transport === "mcp") {
43
124
  const result = await this.mcpToolCall("intentfi.get_workflow", args, { stream: (args.waitMs ?? 0) > 0 });
44
125
  const workflow = (result.workflow ??
@@ -54,9 +135,12 @@ export class IntentFiClient {
54
135
  return await this.getWorkflowRest(args);
55
136
  }
56
137
  async prepareActive(args) {
57
- this.assertApiKey("prepareActive");
138
+ this.assertCredential("prepareActive", { allowX402: true });
58
139
  if (this.transport === "mcp") {
59
- const result = await this.mcpToolCall("intentfi.prepare_active", args, { stream: (args.waitMs ?? 0) > 0 });
140
+ const result = await this.mcpToolCall("intentfi.prepare_active", args, {
141
+ stream: (args.waitMs ?? 0) > 0,
142
+ idempotencyKey: args.requestId,
143
+ });
60
144
  return {
61
145
  result,
62
146
  status: result.kind === "pending" ? 202 : 200,
@@ -64,8 +148,16 @@ export class IntentFiClient {
64
148
  }
65
149
  return await this.prepareActiveRest(args);
66
150
  }
151
+ async prepareOffchainActive(args) {
152
+ this.assertCredential("prepareOffchainActive");
153
+ if (this.transport === "mcp") {
154
+ const result = await this.mcpToolCall("intentfi.prepare_offchain_active", args);
155
+ return { result, status: 200 };
156
+ }
157
+ return await this.prepareOffchainActiveRest(args);
158
+ }
67
159
  async getActiveSubmissionContext(args) {
68
- this.assertApiKey("getActiveSubmissionContext");
160
+ this.assertCredential("getActiveSubmissionContext");
69
161
  if (this.transport === "mcp") {
70
162
  return await this.mcpToolCall("intentfi.get_active_submission_context", args);
71
163
  }
@@ -73,8 +165,56 @@ export class IntentFiClient {
73
165
  headers: this.withApiKey(),
74
166
  });
75
167
  }
168
+ async submitOffchainActive(args) {
169
+ this.assertCredential("submitOffchainActive");
170
+ if (this.transport === "mcp") {
171
+ const result = await this.mcpToolCall("intentfi.submit_offchain_active", args, { stream: (args.waitMs ?? 0) > 0 });
172
+ const waitKind = result.wait?.kind;
173
+ return {
174
+ result,
175
+ status: waitKind === "timeout" ? 202 : 200,
176
+ };
177
+ }
178
+ return await this.submitOffchainActiveRest(args);
179
+ }
180
+ async reportOffchainResultActive(args) {
181
+ this.assertCredential("reportOffchainResultActive");
182
+ if (this.transport === "mcp") {
183
+ const result = await this.mcpToolCall("intentfi.report_offchain_result_active", args, { stream: (args.waitMs ?? 0) > 0 });
184
+ const waitKind = result.wait?.kind;
185
+ return {
186
+ result,
187
+ status: waitKind === "timeout" ? 202 : 200,
188
+ };
189
+ }
190
+ return await this.reportOffchainResultActiveRest(args);
191
+ }
192
+ async reportOffchainFailureActive(args) {
193
+ this.assertCredential("reportOffchainFailureActive");
194
+ if (this.transport === "mcp") {
195
+ const result = await this.mcpToolCall("intentfi.report_offchain_failure_active", args, { stream: (args.waitMs ?? 0) > 0 });
196
+ const waitKind = result.wait?.kind;
197
+ return {
198
+ result,
199
+ status: waitKind === "timeout" ? 202 : 200,
200
+ };
201
+ }
202
+ return await this.reportOffchainFailureActiveRest(args);
203
+ }
204
+ async cancelOffchainActive(args) {
205
+ this.assertCredential("cancelOffchainActive");
206
+ if (this.transport === "mcp") {
207
+ const result = await this.mcpToolCall("intentfi.cancel_offchain_active", args, { stream: (args.waitMs ?? 0) > 0 });
208
+ const waitKind = result.wait?.kind;
209
+ return {
210
+ result,
211
+ status: waitKind === "timeout" ? 202 : 200,
212
+ };
213
+ }
214
+ return await this.cancelOffchainActiveRest(args);
215
+ }
76
216
  async submitActiveTransactions(args) {
77
- this.assertApiKey("submitActiveTransactions");
217
+ this.assertCredential("submitActiveTransactions");
78
218
  if (this.transport === "mcp") {
79
219
  const result = await this.mcpToolCall("intentfi.submit_active_transactions", args, { stream: (args.waitMs ?? 0) > 0 });
80
220
  const waitKind = result.wait?.kind;
@@ -86,7 +226,7 @@ export class IntentFiClient {
86
226
  return await this.submitActiveTransactionsRest(args);
87
227
  }
88
228
  async submitActiveSendCalls(args) {
89
- this.assertApiKey("submitActiveSendCalls");
229
+ this.assertCredential("submitActiveSendCalls");
90
230
  if (this.transport === "mcp") {
91
231
  const result = await this.mcpToolCall("intentfi.submit_active_sendcalls", args, { stream: (args.waitMs ?? 0) > 0 });
92
232
  const waitKind = result.wait?.kind;
@@ -98,13 +238,17 @@ export class IntentFiClient {
98
238
  return await this.submitActiveSendCallsRest(args);
99
239
  }
100
240
  async createWorkflowRest(args) {
101
- return await this.fetchJson("/agent/v1/workflows", {
241
+ const response = await this.fetchPaidEnvelope("/agent/v1/workflows", {
102
242
  method: "POST",
103
243
  headers: this.withApiKey({
104
244
  "content-type": "application/json",
105
245
  }),
106
246
  body: JSON.stringify(args),
247
+ }, {
248
+ operation: "createWorkflow",
249
+ idempotencyKey: args.operationId,
107
250
  });
251
+ return response.data;
108
252
  }
109
253
  async getWorkflowRest(args) {
110
254
  const query = args.waitMs !== undefined ? `?waitMs=${args.waitMs}` : "";
@@ -120,7 +264,21 @@ export class IntentFiClient {
120
264
  };
121
265
  }
122
266
  async prepareActiveRest(args) {
123
- const response = await this.fetchEnvelope(`/agent/v1/workflows/${args.workflowId}/prepare`, {
267
+ const response = await this.fetchPaidEnvelope(`/agent/v1/workflows/${args.workflowId}/prepare`, {
268
+ method: "POST",
269
+ headers: this.withApiKey({ "content-type": "application/json" }),
270
+ body: JSON.stringify({
271
+ requestId: args.requestId,
272
+ ...(args.waitMs !== undefined ? { waitMs: args.waitMs } : {}),
273
+ }),
274
+ }, {
275
+ operation: "prepareActive",
276
+ idempotencyKey: args.requestId,
277
+ });
278
+ return { result: response.data, status: response.status };
279
+ }
280
+ async prepareOffchainActiveRest(args) {
281
+ const response = await this.fetchEnvelope(`/agent/v1/workflows/${args.workflowId}/prepare-offchain`, {
124
282
  method: "POST",
125
283
  headers: this.withApiKey({ "content-type": "application/json" }),
126
284
  body: JSON.stringify({
@@ -130,19 +288,57 @@ export class IntentFiClient {
130
288
  });
131
289
  return { result: response.data, status: response.status };
132
290
  }
291
+ async submitOffchainActiveRest(args) {
292
+ const { workflowId, ...payload } = args;
293
+ const response = await this.fetchEnvelope(`/agent/v1/workflows/${workflowId}/submit-offchain`, {
294
+ method: "POST",
295
+ headers: this.withApiKey({ "content-type": "application/json" }),
296
+ body: JSON.stringify(payload),
297
+ });
298
+ return { result: response.data, status: response.status };
299
+ }
300
+ async reportOffchainResultActiveRest(args) {
301
+ const { workflowId, ...payload } = args;
302
+ const response = await this.fetchEnvelope(`/agent/v1/workflows/${workflowId}/report-offchain-result`, {
303
+ method: "POST",
304
+ headers: this.withApiKey({ "content-type": "application/json" }),
305
+ body: JSON.stringify(payload),
306
+ });
307
+ return { result: response.data, status: response.status };
308
+ }
309
+ async reportOffchainFailureActiveRest(args) {
310
+ const { workflowId, ...payload } = args;
311
+ const response = await this.fetchEnvelope(`/agent/v1/workflows/${workflowId}/report-offchain-failure`, {
312
+ method: "POST",
313
+ headers: this.withApiKey({ "content-type": "application/json" }),
314
+ body: JSON.stringify(payload),
315
+ });
316
+ return { result: response.data, status: response.status };
317
+ }
318
+ async cancelOffchainActiveRest(args) {
319
+ const { workflowId, ...payload } = args;
320
+ const response = await this.fetchEnvelope(`/agent/v1/workflows/${workflowId}/cancel-offchain`, {
321
+ method: "POST",
322
+ headers: this.withApiKey({ "content-type": "application/json" }),
323
+ body: JSON.stringify(payload),
324
+ });
325
+ return { result: response.data, status: response.status };
326
+ }
133
327
  async submitActiveTransactionsRest(args) {
134
- const response = await this.fetchEnvelope(`/agent/v1/workflows/${args.workflowId}/submit-transactions`, {
328
+ const { workflowId, ...payload } = args;
329
+ const response = await this.fetchEnvelope(`/agent/v1/workflows/${workflowId}/submit-transactions`, {
135
330
  method: "POST",
136
331
  headers: this.withApiKey({ "content-type": "application/json" }),
137
- body: JSON.stringify(args),
332
+ body: JSON.stringify(payload),
138
333
  });
139
334
  return { result: response.data, status: response.status };
140
335
  }
141
336
  async submitActiveSendCallsRest(args) {
142
- const response = await this.fetchEnvelope(`/agent/v1/workflows/${args.workflowId}/submit-sendcalls`, {
337
+ const { workflowId, ...payload } = args;
338
+ const response = await this.fetchEnvelope(`/agent/v1/workflows/${workflowId}/submit-sendcalls`, {
143
339
  method: "POST",
144
340
  headers: this.withApiKey({ "content-type": "application/json" }),
145
- body: JSON.stringify(args),
341
+ body: JSON.stringify(payload),
146
342
  });
147
343
  return { result: response.data, status: response.status };
148
344
  }
@@ -185,10 +381,19 @@ export class IntentFiClient {
185
381
  throw new Error(`MCP initialized notification failed (${notification.status})`);
186
382
  }
187
383
  }
188
- async mcpToolCall(name, args, options, attempt = 0) {
384
+ async mcpToolCall(name, args, options, attempt = 0, paymentAttempt = 0) {
189
385
  if (!this.mcpSessionId) {
190
386
  await this.mcpInitialize();
191
387
  }
388
+ const params = {
389
+ name,
390
+ arguments: args,
391
+ };
392
+ if (options?.paymentPayload) {
393
+ params._meta = {
394
+ "x402/payment": options.paymentPayload,
395
+ };
396
+ }
192
397
  const response = await this.fetchImpl(`${this.baseUrl}/mcp`, {
193
398
  method: "POST",
194
399
  headers: {
@@ -202,52 +407,63 @@ export class IntentFiClient {
202
407
  jsonrpc: "2.0",
203
408
  id: Date.now(),
204
409
  method: "tools/call",
205
- params: {
206
- name,
207
- arguments: args,
208
- },
410
+ params,
209
411
  }),
210
412
  });
211
413
  const returnedSessionId = response.headers.get("MCP-Session-Id");
212
414
  if (returnedSessionId) {
213
415
  this.mcpSessionId = returnedSessionId;
214
416
  }
417
+ let body;
215
418
  const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
216
419
  if (contentType.includes("text/event-stream")) {
217
- const text = await response.text();
218
- const messages = text
219
- .split("\n\n")
220
- .map((chunk) => chunk.trim())
221
- .filter((chunk) => chunk.startsWith("data:"))
222
- .map((chunk) => chunk.slice(5).trim())
223
- .map((chunk) => JSON.parse(chunk));
224
- const final = messages.reverse().find((entry) => entry.result !== undefined || entry.error !== undefined);
420
+ const parsed = await parseMcpSseResponse(response);
421
+ const final = parsed.finalMessage;
225
422
  if (!final) {
226
- throw new Error("MCP stream response did not contain a final message");
227
- }
228
- if (final.error) {
229
- if (final.error.data?.code === "MCP_SESSION_REQUIRED" && attempt === 0) {
230
- await this.mcpInitialize();
231
- return await this.mcpToolCall(name, args, options, attempt + 1);
423
+ if (parsed.parseErrorCount > 0) {
424
+ throw new Error(`MCP stream response did not contain a final message (failed to parse ${parsed.parseErrorCount} JSON payload(s))`);
232
425
  }
233
- throw createClientError(final.error.message ?? "MCP tool call failed", final.error.data?.code, response.status);
426
+ throw new Error("MCP stream response did not contain a final message");
234
427
  }
235
- return final.result;
236
- }
237
- const text = await response.text();
238
- let body;
239
- try {
240
- body = JSON.parse(text);
428
+ body = final;
241
429
  }
242
- catch {
243
- throw new Error(`MCP tool call failed (${response.status})`);
430
+ else {
431
+ const text = await response.text();
432
+ try {
433
+ body = JSON.parse(text);
434
+ }
435
+ catch {
436
+ throw new Error(`MCP tool call failed (${response.status})`);
437
+ }
244
438
  }
245
439
  if (body.error?.data?.code === "MCP_SESSION_REQUIRED" && attempt === 0) {
246
440
  await this.mcpInitialize();
247
- return await this.mcpToolCall(name, args, options, attempt + 1);
441
+ return await this.mcpToolCall(name, args, options, attempt + 1, paymentAttempt);
248
442
  }
249
443
  if (!response.ok || body.error) {
250
- throw createClientError(body.error?.message ?? `MCP tool call failed (${response.status})`, body.error?.data?.code, response.status);
444
+ throw createClientError(body.error?.message ?? `MCP tool call failed (${response.status})`, body.error?.data?.code, response.status, toErrorDetails(body.error?.data?.details));
445
+ }
446
+ await this.handleSettlement({
447
+ transport: "mcp",
448
+ operation: name,
449
+ result: body.result,
450
+ });
451
+ const paymentRequired = parseMcpPaymentChallenge(body.result);
452
+ if (paymentRequired) {
453
+ const challengeError = parseMcpChallengeError(body.result);
454
+ if (paymentAttempt > 0) {
455
+ throw createClientError(paymentRequired.error ?? "x402 payment challenge persisted after retry.", challengeError?.code ?? "PAYMENT_REQUIRED", challengeError?.status ?? 402, challengeError?.details);
456
+ }
457
+ const paymentPayload = await this.buildPaymentPayload({
458
+ transport: "mcp",
459
+ operation: name,
460
+ idempotencyKey: options?.idempotencyKey,
461
+ paymentRequired,
462
+ });
463
+ return await this.mcpToolCall(name, args, {
464
+ ...options,
465
+ paymentPayload,
466
+ }, attempt, paymentAttempt + 1);
251
467
  }
252
468
  return body.result;
253
469
  }
@@ -260,22 +476,95 @@ export class IntentFiClient {
260
476
  "x-api-key": this.apiKey,
261
477
  };
262
478
  }
263
- assertApiKey(operation) {
479
+ assertCredential(operation, options = {}) {
480
+ if (this.apiKey) {
481
+ return;
482
+ }
483
+ if (options.allowX402 && this.paymentManager) {
484
+ return;
485
+ }
486
+ if (options.allowX402) {
487
+ throw new Error(`INTENTFI_API_KEY or paymentManager is required for ${operation}. Set apiKey in IntentFiClient options/INTENTFI_API_KEY, or provide an x402 paymentManager.`);
488
+ }
264
489
  if (!this.apiKey) {
265
490
  throw new Error(`INTENTFI_API_KEY is required for ${operation}. Set apiKey in IntentFiClient options or INTENTFI_API_KEY in the environment.`);
266
491
  }
267
492
  }
493
+ async buildPaymentPayload(args) {
494
+ if (!this.paymentManager) {
495
+ throw createClientError("x402 payment challenge received but no paymentManager is configured.", "PAYMENT_REQUIRED", 402);
496
+ }
497
+ return await this.paymentManager.buildPaymentPayload(args);
498
+ }
499
+ async handleSettlement(args) {
500
+ if (!this.paymentManager?.onSettlement) {
501
+ return;
502
+ }
503
+ const settlement = parseSettlementFromResultMeta(args.result);
504
+ if (!settlement) {
505
+ return;
506
+ }
507
+ await this.paymentManager.onSettlement({
508
+ transport: args.transport,
509
+ operation: args.operation,
510
+ settlement,
511
+ });
512
+ }
268
513
  async fetchJson(path, init) {
269
514
  const result = await this.fetchEnvelope(path, init);
270
515
  return result.data;
271
516
  }
517
+ async fetchPaidEnvelope(path, init, context) {
518
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, init);
519
+ const encodedPaymentRequired = response.headers.get(PAYMENT_REQUIRED_HEADER);
520
+ if (response.status !== 402 || !encodedPaymentRequired) {
521
+ return await this.parseEnvelopeResponse(response);
522
+ }
523
+ const paymentRequired = decodePaymentRequiredHeader(encodedPaymentRequired);
524
+ const paymentPayload = await this.buildPaymentPayload({
525
+ transport: "rest",
526
+ operation: context.operation,
527
+ idempotencyKey: context.idempotencyKey,
528
+ paymentRequired,
529
+ });
530
+ const retryHeaders = mergeHeaders(init.headers, {
531
+ [PAYMENT_SIGNATURE_HEADER]: encodeBase64Json(paymentPayload),
532
+ });
533
+ const retryResponse = await this.fetchImpl(`${this.baseUrl}${path}`, {
534
+ ...init,
535
+ headers: retryHeaders,
536
+ });
537
+ const retryPaymentRequired = retryResponse.headers.get(PAYMENT_REQUIRED_HEADER);
538
+ if (retryResponse.status === 402 && retryPaymentRequired) {
539
+ await this.handleSettlement({
540
+ transport: "rest",
541
+ operation: context.operation,
542
+ result: { _meta: decodeSettlementMetaFromHeaders(retryResponse.headers) },
543
+ });
544
+ const retryChallenge = decodePaymentRequiredHeader(retryPaymentRequired);
545
+ const retryError = await readEnvelopeError(retryResponse);
546
+ throw createClientError(retryError?.message
547
+ ?? retryChallenge.error
548
+ ?? "x402 payment challenge persisted after retry.", retryError?.code ?? "PAYMENT_REQUIRED", 402, retryError?.details);
549
+ }
550
+ const envelope = await this.parseEnvelopeResponse(retryResponse);
551
+ await this.handleSettlement({
552
+ transport: "rest",
553
+ operation: context.operation,
554
+ result: { _meta: decodeSettlementMetaFromHeaders(retryResponse.headers) },
555
+ });
556
+ return envelope;
557
+ }
272
558
  async fetchEnvelope(path, init) {
273
559
  const response = await this.fetchImpl(`${this.baseUrl}${path}`, init);
560
+ return await this.parseEnvelopeResponse(response);
561
+ }
562
+ async parseEnvelopeResponse(response) {
274
563
  const body = (await response.json());
275
564
  if (!response.ok || body.ok === false) {
276
565
  const errorCode = body.ok === false ? body.error.code : "HTTP_ERROR";
277
566
  const errorMessage = body.ok === false ? body.error.message : `HTTP ${response.status}`;
278
- throw createClientError(`${errorCode}: ${errorMessage}`, errorCode, response.status);
567
+ throw createClientError(`${errorCode}: ${errorMessage}`, errorCode, response.status, body.ok === false ? toErrorDetails(body.error.details) : undefined);
279
568
  }
280
569
  return {
281
570
  data: body.data,
@@ -284,7 +573,7 @@ export class IntentFiClient {
284
573
  };
285
574
  }
286
575
  }
287
- function createClientError(message, code, status) {
576
+ function createClientError(message, code, status, details) {
288
577
  const error = new Error(message);
289
578
  if (code) {
290
579
  error.code = code;
@@ -292,8 +581,324 @@ function createClientError(message, code, status) {
292
581
  if (status !== undefined) {
293
582
  error.status = status;
294
583
  }
584
+ if (details) {
585
+ error.details = details;
586
+ }
295
587
  return error;
296
588
  }
589
+ function parseMcpSseMessages(payload) {
590
+ const lines = payload.split(/\r?\n/);
591
+ let currentDataLines = [];
592
+ let finalMessage = null;
593
+ let parseErrorCount = 0;
594
+ const flushCurrentData = () => {
595
+ if (currentDataLines.length === 0) {
596
+ return;
597
+ }
598
+ const chunk = currentDataLines.join("\n");
599
+ currentDataLines = [];
600
+ const trimmed = chunk.trim();
601
+ if (!trimmed || trimmed === "[DONE]") {
602
+ return;
603
+ }
604
+ try {
605
+ const parsed = JSON.parse(trimmed);
606
+ if (parsed.result !== undefined || parsed.error !== undefined) {
607
+ finalMessage = parsed;
608
+ }
609
+ }
610
+ catch {
611
+ parseErrorCount += 1;
612
+ }
613
+ };
614
+ for (const rawLine of lines) {
615
+ const line = rawLine.trimEnd();
616
+ if (line.length === 0) {
617
+ flushCurrentData();
618
+ continue;
619
+ }
620
+ if (!line.startsWith("data:")) {
621
+ continue;
622
+ }
623
+ const value = line.slice(5);
624
+ currentDataLines.push(value.startsWith(" ") ? value.slice(1) : value);
625
+ }
626
+ flushCurrentData();
627
+ return {
628
+ finalMessage,
629
+ parseErrorCount,
630
+ };
631
+ }
632
+ async function parseMcpSseResponse(response) {
633
+ if (!response.body) {
634
+ return parseMcpSseMessages(await response.text());
635
+ }
636
+ const reader = response.body.getReader();
637
+ const decoder = new TextDecoder();
638
+ let parseErrorCount = 0;
639
+ let finalMessage = null;
640
+ let currentDataLines = [];
641
+ let pendingLine = "";
642
+ const flushCurrentData = () => {
643
+ if (currentDataLines.length === 0) {
644
+ return;
645
+ }
646
+ const chunk = currentDataLines.join("\n");
647
+ currentDataLines = [];
648
+ const trimmed = chunk.trim();
649
+ if (!trimmed || trimmed === "[DONE]") {
650
+ return;
651
+ }
652
+ try {
653
+ const parsed = JSON.parse(trimmed);
654
+ if (parsed.result !== undefined || parsed.error !== undefined) {
655
+ finalMessage = parsed;
656
+ }
657
+ }
658
+ catch {
659
+ parseErrorCount += 1;
660
+ }
661
+ };
662
+ const processLines = (text) => {
663
+ const lines = text.split(/\r?\n/);
664
+ for (const rawLine of lines) {
665
+ const line = rawLine.trimEnd();
666
+ if (line.length === 0) {
667
+ flushCurrentData();
668
+ continue;
669
+ }
670
+ if (!line.startsWith("data:")) {
671
+ continue;
672
+ }
673
+ const value = line.slice(5);
674
+ currentDataLines.push(value.startsWith(" ") ? value.slice(1) : value);
675
+ }
676
+ };
677
+ try {
678
+ while (true) {
679
+ const { done, value } = await reader.read();
680
+ if (done) {
681
+ break;
682
+ }
683
+ pendingLine += decoder.decode(value, { stream: true });
684
+ const lastBreak = pendingLine.lastIndexOf("\n");
685
+ if (lastBreak < 0) {
686
+ continue;
687
+ }
688
+ const completeText = pendingLine.slice(0, lastBreak + 1);
689
+ pendingLine = pendingLine.slice(lastBreak + 1);
690
+ processLines(completeText);
691
+ }
692
+ pendingLine += decoder.decode();
693
+ if (pendingLine.length > 0) {
694
+ processLines(`${pendingLine}\n`);
695
+ }
696
+ flushCurrentData();
697
+ }
698
+ finally {
699
+ reader.releaseLock();
700
+ }
701
+ return {
702
+ finalMessage,
703
+ parseErrorCount,
704
+ };
705
+ }
706
+ async function readEnvelopeError(response) {
707
+ try {
708
+ const body = (await response.json());
709
+ if (body.ok === false
710
+ && body.error
711
+ && typeof body.error.code === "string"
712
+ && typeof body.error.message === "string") {
713
+ const details = toErrorDetails(body.error.details);
714
+ return {
715
+ code: body.error.code,
716
+ message: `${body.error.code}: ${body.error.message}`,
717
+ ...(details ? { details } : {}),
718
+ };
719
+ }
720
+ }
721
+ catch {
722
+ return null;
723
+ }
724
+ return null;
725
+ }
726
+ function parseAtomicAmount(value) {
727
+ try {
728
+ return BigInt(value);
729
+ }
730
+ catch {
731
+ throw new Error(`Invalid x402 amount '${value}'.`);
732
+ }
733
+ }
734
+ function echoRequiredExtensions(extensions, idempotencyKey) {
735
+ if (!extensions) {
736
+ return undefined;
737
+ }
738
+ const echoedExtensions = {
739
+ ...extensions,
740
+ };
741
+ if (!idempotencyKey) {
742
+ return echoedExtensions;
743
+ }
744
+ const paymentIdentifier = extensions[PAYMENT_IDENTIFIER_EXTENSION];
745
+ if (!paymentIdentifier) {
746
+ return echoedExtensions;
747
+ }
748
+ return {
749
+ ...echoedExtensions,
750
+ [PAYMENT_IDENTIFIER_EXTENSION]: {
751
+ schema: paymentIdentifier.schema,
752
+ info: {
753
+ ...paymentIdentifier.info,
754
+ id: idempotencyKey,
755
+ },
756
+ },
757
+ };
758
+ }
759
+ function parseMcpPaymentChallenge(value) {
760
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
761
+ return null;
762
+ }
763
+ const record = value;
764
+ if (record.isError !== true) {
765
+ return null;
766
+ }
767
+ const structured = record.structuredContent;
768
+ if (isX402PaymentRequired(structured)) {
769
+ return structured;
770
+ }
771
+ const content = record.content;
772
+ if (!Array.isArray(content)) {
773
+ return null;
774
+ }
775
+ const first = content[0];
776
+ if (!first || typeof first !== "object" || Array.isArray(first)) {
777
+ return null;
778
+ }
779
+ const text = first.text;
780
+ if (typeof text !== "string") {
781
+ return null;
782
+ }
783
+ try {
784
+ const parsed = JSON.parse(text);
785
+ return isX402PaymentRequired(parsed) ? parsed : null;
786
+ }
787
+ catch {
788
+ return null;
789
+ }
790
+ }
791
+ function parseMcpChallengeError(value) {
792
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
793
+ return null;
794
+ }
795
+ const meta = value._meta;
796
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
797
+ return null;
798
+ }
799
+ const challengeError = meta["x402/error"];
800
+ if (!challengeError || typeof challengeError !== "object" || Array.isArray(challengeError)) {
801
+ return null;
802
+ }
803
+ const code = challengeError.code;
804
+ const status = challengeError.status;
805
+ if (typeof code !== "string" || typeof status !== "number" || !Number.isFinite(status)) {
806
+ return null;
807
+ }
808
+ const details = toErrorDetails(challengeError.details);
809
+ return {
810
+ code,
811
+ status: Math.trunc(status),
812
+ ...(details ? { details } : {}),
813
+ };
814
+ }
815
+ function toErrorDetails(value) {
816
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
817
+ return undefined;
818
+ }
819
+ return value;
820
+ }
821
+ function parseSettlementFromResultMeta(result) {
822
+ if (!result || typeof result !== "object" || Array.isArray(result)) {
823
+ return null;
824
+ }
825
+ const meta = result._meta;
826
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
827
+ return null;
828
+ }
829
+ const settlement = meta["x402/payment-response"];
830
+ return isX402SettlementResponse(settlement) ? settlement : null;
831
+ }
832
+ function decodeSettlementMetaFromHeaders(headers) {
833
+ const encoded = headers.get(PAYMENT_RESPONSE_HEADER);
834
+ if (!encoded) {
835
+ return undefined;
836
+ }
837
+ const decoded = decodeBase64Json(encoded);
838
+ if (!isX402SettlementResponse(decoded)) {
839
+ return undefined;
840
+ }
841
+ return {
842
+ "x402/payment-response": decoded,
843
+ };
844
+ }
845
+ function decodePaymentRequiredHeader(header) {
846
+ const decoded = decodeBase64Json(header);
847
+ if (!isX402PaymentRequired(decoded)) {
848
+ throw createClientError("PAYMENT-REQUIRED header contained an invalid x402 payload.", "PAYMENT_REQUIRED", 402);
849
+ }
850
+ return decoded;
851
+ }
852
+ function mergeHeaders(original, extra) {
853
+ const headers = new Headers(original ?? {});
854
+ for (const [key, value] of Object.entries(extra)) {
855
+ headers.set(key, value);
856
+ }
857
+ return headers;
858
+ }
859
+ function encodeBase64Json(value) {
860
+ const json = JSON.stringify(value);
861
+ const bytes = new TextEncoder().encode(json);
862
+ let binary = "";
863
+ for (const byte of bytes) {
864
+ binary += String.fromCharCode(byte);
865
+ }
866
+ return btoa(binary);
867
+ }
868
+ function decodeBase64Json(encoded) {
869
+ const normalized = encoded.trim().replace(/-/g, "+").replace(/_/g, "/");
870
+ const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
871
+ const binary = atob(padded);
872
+ const bytes = new Uint8Array(binary.length);
873
+ for (let index = 0; index < binary.length; index += 1) {
874
+ bytes[index] = binary.charCodeAt(index);
875
+ }
876
+ const text = new TextDecoder().decode(bytes);
877
+ return JSON.parse(text);
878
+ }
879
+ function isX402PaymentRequired(value) {
880
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
881
+ return false;
882
+ }
883
+ const record = value;
884
+ if (record.x402Version !== X402_VERSION) {
885
+ return false;
886
+ }
887
+ if (!record.resource || typeof record.resource !== "object" || Array.isArray(record.resource)) {
888
+ return false;
889
+ }
890
+ const accepts = record.accepts;
891
+ return Array.isArray(accepts) && accepts.length > 0;
892
+ }
893
+ function isX402SettlementResponse(value) {
894
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
895
+ return false;
896
+ }
897
+ const record = value;
898
+ return (typeof record.success === "boolean"
899
+ && typeof record.transaction === "string"
900
+ && typeof record.network === "string");
901
+ }
297
902
  function parseRetryAfterHeader(value) {
298
903
  if (!value) {
299
904
  return undefined;