@conto_finance/sdk 0.3.1 → 0.4.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 CHANGED
@@ -1,39 +1,20 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __commonJS = (cb, mod) => function __require() {
8
- try {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
- } catch (e) {
11
- throw mod = 0, e;
12
- }
13
- };
14
- var __copyProps = (to, from, except, desc) => {
15
- if (from && typeof from === "object" || typeof from === "function") {
16
- for (let key of __getOwnPropNames(from))
17
- if (!__hasOwnProp.call(to, key) && key !== except)
18
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
- }
20
- return to;
21
- };
22
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
- // If the importer is in node compatibility mode or this is not an ESM
24
- // file that has been converted to a CommonJS file using a Babel-
25
- // compatible transform (i.e. "__esModule" has not been set), then set
26
- // "default" to the CommonJS "module.exports" for node compatibility.
27
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
28
- mod
29
- ));
1
+ import {
2
+ BrowserCheckoutTargetVerificationError,
3
+ canonicalizeBrowserCheckoutTarget,
4
+ createVerifiedBrowserCheckoutDriver,
5
+ fingerprintBrowserCheckoutTarget
6
+ } from "./chunk-O5RAAB2U.mjs";
7
+ import {
8
+ __commonJS,
9
+ __toESM
10
+ } from "./chunk-B7F5QLE5.mjs";
30
11
 
31
12
  // package.json
32
13
  var require_package = __commonJS({
33
14
  "package.json"(exports, module) {
34
15
  module.exports = {
35
16
  name: "@conto_finance/sdk",
36
- version: "0.3.1",
17
+ version: "0.4.0",
37
18
  description: "Conto SDK for AI agent payment authorization",
38
19
  main: "./dist/index.js",
39
20
  module: "./dist/index.mjs",
@@ -43,6 +24,21 @@ var require_package = __commonJS({
43
24
  types: "./dist/index.d.ts",
44
25
  import: "./dist/index.mjs",
45
26
  require: "./dist/index.js"
27
+ },
28
+ "./playwright": {
29
+ types: "./dist/playwright.d.ts",
30
+ import: "./dist/playwright.mjs",
31
+ require: "./dist/playwright.js"
32
+ },
33
+ "./browserbase": {
34
+ types: "./dist/browserbase.d.ts",
35
+ import: "./dist/browserbase.mjs",
36
+ require: "./dist/browserbase.js"
37
+ },
38
+ "./checkout-verifier": {
39
+ types: "./dist/checkout-verifier.d.ts",
40
+ import: "./dist/checkout-verifier.mjs",
41
+ require: "./dist/checkout-verifier.js"
46
42
  }
47
43
  },
48
44
  publishConfig: {
@@ -55,10 +51,11 @@ var require_package = __commonJS({
55
51
  "dist"
56
52
  ],
57
53
  scripts: {
58
- build: "tsup src/index.ts --format cjs,esm --dts",
59
- dev: "tsup src/index.ts --format cjs,esm --dts --watch",
54
+ build: "tsup src/index.ts src/playwright.ts src/browserbase.ts src/checkout-verifier.ts --format cjs,esm --dts",
55
+ dev: "tsup src/index.ts src/playwright.ts src/browserbase.ts src/checkout-verifier.ts --format cjs,esm --dts --watch",
60
56
  lint: "tsc --noEmit",
61
- "test:contract": "npm run build && node --test test/contract.test.mjs",
57
+ test: "npm run lint && npm run test:contract && npm run test:consumer",
58
+ "test:contract": "npm run build && node --test test/contract.test.mjs test/playwright.test.mjs test/browserbase.test.mjs test/checkout-verifier.test.mjs test/developer-experience.test.mjs",
62
59
  "test:consumer": "npm run build && node --test test/consumer.test.mjs",
63
60
  "verify:release": "node scripts/verify-release.mjs",
64
61
  prepublishOnly: "npm run verify:release && npm run lint && npm run test:contract && npm run test:consumer"
@@ -78,18 +75,27 @@ var require_package = __commonJS({
78
75
  url: "https://github.com/conto-finance/conto.git",
79
76
  directory: "packages/sdk"
80
77
  },
81
- homepage: "https://conto.finance/docs/sdk",
78
+ homepage: "https://conto.finance/docs/sdk/installation",
82
79
  bugs: {
83
- url: "https://github.com/conto-finance/conto/issues"
80
+ email: "support@conto.finance"
84
81
  },
85
82
  dependencies: {
86
83
  "@sentry/node": "^10.63.0"
87
84
  },
85
+ peerDependencies: {
86
+ "playwright-core": ">=1.50.0"
87
+ },
88
+ peerDependenciesMeta: {
89
+ "playwright-core": {
90
+ optional: true
91
+ }
92
+ },
88
93
  overrides: {
89
94
  esbuild: "0.28.1"
90
95
  },
91
96
  devDependencies: {
92
97
  "@types/node": "22.19.18",
98
+ "playwright-core": "1.61.0",
93
99
  tsup: "^8.5.1",
94
100
  typescript: "^5.9.3"
95
101
  }
@@ -115,7 +121,66 @@ import * as Sentry2 from "@sentry/node";
115
121
  import { randomUUID } from "crypto";
116
122
 
117
123
  // src/http.ts
124
+ var PUBLIC_PAYMENT_REASONS = /* @__PURE__ */ new Set([
125
+ "The available balance is insufficient for this payment.",
126
+ "This payment exceeds a configured spending limit.",
127
+ "This recipient is not allowed by the current payment controls.",
128
+ "This payment is outside the permitted schedule.",
129
+ "The available payment budget is insufficient.",
130
+ "This payment does not meet the current payment controls.",
131
+ "This payment requires approval before it can proceed."
132
+ ]);
133
+ function customerPaymentReasons(value) {
134
+ return Array.isArray(value) ? [
135
+ ...new Set(
136
+ value.filter(
137
+ (reason) => typeof reason === "string" && PUBLIC_PAYMENT_REASONS.has(reason)
138
+ )
139
+ )
140
+ ] : [];
141
+ }
142
+ var VALIDATION_FIELDS = /* @__PURE__ */ new Set([
143
+ "amount",
144
+ "recipientAddress",
145
+ "senderAddress",
146
+ "recipientName",
147
+ "purpose",
148
+ "category",
149
+ "chainId",
150
+ "walletId",
151
+ "idempotencyKey",
152
+ "txHash",
153
+ "approvalToken"
154
+ ]);
155
+ function customerFieldErrors(body, status) {
156
+ if (![400, 422].includes(status) || !["VALIDATION_ERROR", "INVALID_REQUEST", "INVALID_INPUT"].includes(String(body.code)))
157
+ return void 0;
158
+ const fields = record(body.details).fields;
159
+ if (!Array.isArray(fields)) return void 0;
160
+ const names = [
161
+ ...new Set(
162
+ fields.map((item) => record(item).field).filter(
163
+ (field) => typeof field === "string" && VALIDATION_FIELDS.has(field)
164
+ )
165
+ )
166
+ ];
167
+ return names.length ? names.map((field) => ({
168
+ field,
169
+ message: "Check this field against the documented request schema."
170
+ })) : void 0;
171
+ }
118
172
  var MAX_RETRY_DELAY_MS = 3e4;
173
+ function assertSecureBaseUrl(baseUrl) {
174
+ let parsed;
175
+ try {
176
+ parsed = new URL(baseUrl);
177
+ } catch {
178
+ throw new Error(`Invalid base URL: ${baseUrl}`);
179
+ }
180
+ if (parsed.protocol !== "https:" && parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1") {
181
+ throw new Error("baseUrl must use https (except for localhost)");
182
+ }
183
+ }
119
184
  function abortError() {
120
185
  const error = new Error("Request aborted");
121
186
  error.name = "AbortError";
@@ -246,6 +311,7 @@ function responseErrorMetadata(response, data) {
246
311
  return {
247
312
  code,
248
313
  status: response.status,
314
+ fieldErrors: customerFieldErrors(body, response.status),
249
315
  requestId: safeRequestId(
250
316
  response.headers.get("x-request-id"),
251
317
  response.headers.get("x-correlation-id"),
@@ -288,12 +354,14 @@ var ContoAdmin = class {
288
354
  }
289
355
  this.orgApiKey = config.orgApiKey;
290
356
  this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
357
+ assertSecureBaseUrl(this.baseUrl);
291
358
  this.timeout = config.timeout || 3e4;
292
359
  this.agents = new AgentsClient(this);
293
360
  this.wallets = new WalletsClient(this);
294
361
  this.cards = new AdminCardsClient(this);
295
362
  this.policies = new PoliciesClient(this);
296
363
  this.team = new TeamClient(this);
364
+ this.paymentRails = new PaymentRailsClient(this);
297
365
  }
298
366
  /** Make an authenticated request to the Conto API */
299
367
  async request(method, path, body) {
@@ -348,6 +416,22 @@ var ContoAdmin = class {
348
416
  }
349
417
  }
350
418
  };
419
+ var PaymentRailsClient = class {
420
+ constructor(client) {
421
+ this.client = client;
422
+ }
423
+ /** Read gate and configuration readiness without calling Stripe. */
424
+ async getStripeLinkCheckoutReadiness() {
425
+ return this.client.request("GET", "/api/stripe-link/checkout-readiness");
426
+ }
427
+ /**
428
+ * Ask Conto to verify that its configured Stripe credential resolves to its
429
+ * configured merchant account. This call never returns either identifier.
430
+ */
431
+ async verifyStripeLinkCheckoutAccount() {
432
+ return this.client.request("POST", "/api/stripe-link/checkout-readiness");
433
+ }
434
+ };
351
435
  var AdminCardsClient = class {
352
436
  constructor(client) {
353
437
  this.client = client;
@@ -466,44 +550,55 @@ var AgentsClient = class {
466
550
  }
467
551
  /** Get a single agent by ID */
468
552
  async get(agentId) {
469
- return this.client.request("GET", `/api/agents/${agentId}`);
553
+ return this.client.request("GET", `/api/agents/${encodeURIComponent(agentId)}`);
470
554
  }
471
555
  /** Update an agent */
472
556
  async update(agentId, input) {
473
557
  return this.client.request(
474
558
  "PATCH",
475
- `/api/agents/${agentId}`,
559
+ `/api/agents/${encodeURIComponent(agentId)}`,
476
560
  input
477
561
  );
478
562
  }
479
563
  /** Delete an agent (soft delete) */
480
564
  async delete(agentId) {
481
- return this.client.request("DELETE", `/api/agents/${agentId}`);
565
+ return this.client.request(
566
+ "DELETE",
567
+ `/api/agents/${encodeURIComponent(agentId)}`
568
+ );
482
569
  }
483
570
  /** Freeze an agent, blocking all transactions */
484
571
  async freeze(agentId, input) {
485
- return this.client.request("POST", `/api/agents/${agentId}/freeze`, {
486
- ...input.freezeWallets !== void 0 && { freezeWallets: input.freezeWallets }
487
- });
572
+ return this.client.request(
573
+ "POST",
574
+ `/api/agents/${encodeURIComponent(agentId)}/freeze`,
575
+ {
576
+ ...input.freezeWallets !== void 0 && { freezeWallets: input.freezeWallets }
577
+ }
578
+ );
488
579
  }
489
580
  /** Unfreeze an agent, restoring transaction ability */
490
581
  async unfreeze(agentId, input) {
491
- return this.client.request("POST", `/api/agents/${agentId}/unfreeze`, {
492
- ...input.unfreezeWallets !== void 0 && { unfreezeWallets: input.unfreezeWallets }
493
- });
582
+ return this.client.request(
583
+ "POST",
584
+ `/api/agents/${encodeURIComponent(agentId)}/unfreeze`,
585
+ {
586
+ ...input.unfreezeWallets !== void 0 && { unfreezeWallets: input.unfreezeWallets }
587
+ }
588
+ );
494
589
  }
495
590
  /** List wallets linked to an agent */
496
591
  async listWallets(agentId) {
497
592
  return this.client.request(
498
593
  "GET",
499
- `/api/agents/${agentId}/wallets`
594
+ `/api/agents/${encodeURIComponent(agentId)}/wallets`
500
595
  );
501
596
  }
502
597
  /** Link a wallet to an agent with spending limits */
503
598
  async linkWallet(agentId, input) {
504
599
  return this.client.request(
505
600
  "POST",
506
- `/api/agents/${agentId}/wallets`,
601
+ `/api/agents/${encodeURIComponent(agentId)}/wallets`,
507
602
  input
508
603
  );
509
604
  }
@@ -511,38 +606,45 @@ var AgentsClient = class {
511
606
  async unlinkWallet(agentId, walletId) {
512
607
  return this.client.request(
513
608
  "DELETE",
514
- `/api/agents/${agentId}/wallets/${walletId}`
609
+ `/api/agents/${encodeURIComponent(agentId)}/wallets/${encodeURIComponent(walletId)}`
515
610
  );
516
611
  }
517
612
  /** List policies assigned to an agent */
518
613
  async listPolicies(agentId) {
519
614
  return this.client.request(
520
615
  "GET",
521
- `/api/agents/${agentId}/policies`
616
+ `/api/agents/${encodeURIComponent(agentId)}/policies`
522
617
  );
523
618
  }
524
619
  /** Assign a policy to an agent */
525
620
  async assignPolicy(agentId, policyId) {
526
- return this.client.request("POST", `/api/agents/${agentId}/policies`, {
527
- policyId
528
- });
621
+ return this.client.request(
622
+ "POST",
623
+ `/api/agents/${encodeURIComponent(agentId)}/policies`,
624
+ {
625
+ policyId
626
+ }
627
+ );
529
628
  }
530
629
  /** Unassign a policy from an agent */
531
630
  async unassignPolicy(agentId, policyId) {
532
631
  return this.client.request(
533
632
  "DELETE",
534
- `/api/agents/${agentId}/policies?policyId=${policyId}`
633
+ `/api/agents/${encodeURIComponent(agentId)}/policies?policyId=${encodeURIComponent(policyId)}`
535
634
  );
536
635
  }
537
636
  /** List SDK keys for an agent */
538
637
  async listSdkKeys(agentId) {
539
- return this.client.request("GET", `/api/agents/${agentId}/sdk-keys`);
638
+ return this.client.request(
639
+ "GET",
640
+ `/api/agents/${encodeURIComponent(agentId)}/sdk-keys`
641
+ );
540
642
  }
541
643
  /** Create an SDK key for an agent (key value is returned only once) */
542
644
  async createSdkKey(agentId, input) {
543
645
  return this.client.request(
544
646
  "POST",
545
- `/api/agents/${agentId}/sdk-keys`,
647
+ `/api/agents/${encodeURIComponent(agentId)}/sdk-keys`,
546
648
  input
547
649
  );
548
650
  }
@@ -550,7 +652,7 @@ var AgentsClient = class {
550
652
  async revokeSdkKey(agentId, keyId) {
551
653
  return this.client.request(
552
654
  "DELETE",
553
- `/api/agents/${agentId}/sdk-keys?keyId=${keyId}`
655
+ `/api/agents/${encodeURIComponent(agentId)}/sdk-keys?keyId=${encodeURIComponent(keyId)}`
554
656
  );
555
657
  }
556
658
  };
@@ -579,25 +681,28 @@ var WalletsClient = class {
579
681
  }
580
682
  /** Get a single wallet by ID */
581
683
  async get(walletId) {
582
- return this.client.request("GET", `/api/wallets/${walletId}`);
684
+ return this.client.request("GET", `/api/wallets/${encodeURIComponent(walletId)}`);
583
685
  }
584
686
  /** Update a wallet */
585
687
  async update(walletId, input) {
586
688
  return this.client.request(
587
689
  "PATCH",
588
- `/api/wallets/${walletId}`,
690
+ `/api/wallets/${encodeURIComponent(walletId)}`,
589
691
  input
590
692
  );
591
693
  }
592
694
  /** Delete a wallet (cannot delete if linked to agents) */
593
695
  async delete(walletId) {
594
- return this.client.request("DELETE", `/api/wallets/${walletId}`);
696
+ return this.client.request(
697
+ "DELETE",
698
+ `/api/wallets/${encodeURIComponent(walletId)}`
699
+ );
595
700
  }
596
701
  /** Provision a wallet for onchain transactions */
597
702
  async provision(walletId) {
598
703
  return this.client.request(
599
704
  "POST",
600
- `/api/wallets/${walletId}/provision`,
705
+ `/api/wallets/${encodeURIComponent(walletId)}/provision`,
601
706
  {}
602
707
  );
603
708
  }
@@ -605,7 +710,7 @@ var WalletsClient = class {
605
710
  async refreshBalance(walletId) {
606
711
  return this.client.request(
607
712
  "POST",
608
- `/api/wallets/${walletId}/refresh-balance`,
713
+ `/api/wallets/${encodeURIComponent(walletId)}/refresh-balance`,
609
714
  {}
610
715
  );
611
716
  }
@@ -632,43 +737,53 @@ var PoliciesClient = class {
632
737
  }
633
738
  /** Get a single policy by ID */
634
739
  async get(policyId) {
635
- return this.client.request("GET", `/api/policies/${policyId}`);
740
+ return this.client.request("GET", `/api/policies/${encodeURIComponent(policyId)}`);
636
741
  }
637
742
  /** Update a policy */
638
743
  async update(policyId, input) {
639
744
  return this.client.request(
640
745
  "PATCH",
641
- `/api/policies/${policyId}`,
746
+ `/api/policies/${encodeURIComponent(policyId)}`,
642
747
  input
643
748
  );
644
749
  }
645
750
  /** Delete a policy */
646
751
  async delete(policyId) {
647
- return this.client.request("DELETE", `/api/policies/${policyId}`);
752
+ return this.client.request(
753
+ "DELETE",
754
+ `/api/policies/${encodeURIComponent(policyId)}`
755
+ );
648
756
  }
649
757
  /** List rules for a policy */
650
758
  async listRules(policyId) {
651
- return this.client.request("GET", `/api/policies/${policyId}/rules`);
759
+ return this.client.request(
760
+ "GET",
761
+ `/api/policies/${encodeURIComponent(policyId)}/rules`
762
+ );
652
763
  }
653
764
  /** Add a single rule to a policy */
654
765
  async addRule(policyId, rule) {
655
766
  return this.client.request(
656
767
  "POST",
657
- `/api/policies/${policyId}/rules`,
768
+ `/api/policies/${encodeURIComponent(policyId)}/rules`,
658
769
  rule
659
770
  );
660
771
  }
661
772
  /** Add multiple rules to a policy */
662
773
  async addRules(policyId, rules) {
663
- return this.client.request("POST", `/api/policies/${policyId}/rules`, {
664
- rules
665
- });
774
+ return this.client.request(
775
+ "POST",
776
+ `/api/policies/${encodeURIComponent(policyId)}/rules`,
777
+ {
778
+ rules
779
+ }
780
+ );
666
781
  }
667
782
  /** Update a single rule */
668
783
  async updateRule(policyId, ruleId, input) {
669
784
  return this.client.request(
670
785
  "PATCH",
671
- `/api/policies/${policyId}/rules/${ruleId}`,
786
+ `/api/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`,
672
787
  input
673
788
  );
674
789
  }
@@ -676,14 +791,51 @@ var PoliciesClient = class {
676
791
  async deleteRule(policyId, ruleId) {
677
792
  return this.client.request(
678
793
  "DELETE",
679
- `/api/policies/${policyId}/rules/${ruleId}`
794
+ `/api/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`
680
795
  );
681
796
  }
682
797
  /** Delete all rules for a policy */
683
798
  async deleteAllRules(policyId) {
684
799
  return this.client.request(
685
800
  "DELETE",
686
- `/api/policies/${policyId}/rules`
801
+ `/api/policies/${encodeURIComponent(policyId)}/rules`
802
+ );
803
+ }
804
+ /** List the version history for a policy (newest first). */
805
+ async listVersions(policyId, params) {
806
+ const query = new URLSearchParams();
807
+ if (params?.limit) query.set("limit", String(params.limit));
808
+ if (params?.offset) query.set("offset", String(params.offset));
809
+ const qs = query.toString() ? `?${query.toString()}` : "";
810
+ return this.client.request(
811
+ "GET",
812
+ `/api/policies/${encodeURIComponent(policyId)}/versions${qs}`
813
+ );
814
+ }
815
+ /** Get a single version's snapshot. */
816
+ async getVersion(policyId, version) {
817
+ return this.client.request(
818
+ "GET",
819
+ `/api/policies/${encodeURIComponent(policyId)}/versions/${encodeURIComponent(version)}`
820
+ );
821
+ }
822
+ /** Diff two versions of a policy. */
823
+ async compareVersions(policyId, from, to) {
824
+ return this.client.request(
825
+ "GET",
826
+ `/api/policies/${encodeURIComponent(policyId)}/versions/compare?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`
827
+ );
828
+ }
829
+ /**
830
+ * Restore an earlier version. Appends a new ROLLED_BACK version. If the
831
+ * organization requires governance approval for policy changes, the API
832
+ * returns a pending-approval response instead of applying the rollback.
833
+ */
834
+ async rollbackVersion(policyId, version, options) {
835
+ return this.client.request(
836
+ "POST",
837
+ `/api/policies/${encodeURIComponent(policyId)}/versions/${encodeURIComponent(version)}/rollback`,
838
+ options ?? {}
687
839
  );
688
840
  }
689
841
  };
@@ -709,6 +861,19 @@ if (process.env.SENTRY_DSN) {
709
861
  enableLogs: true
710
862
  });
711
863
  }
864
+ function isValidBrowserActionObservation(value) {
865
+ if (!value || typeof value !== "object") return false;
866
+ const observation = value;
867
+ if (typeof observation.reference !== "string" || !observation.reference.trim() || observation.reference.length > 255 || typeof observation.observedAt !== "string" || Number.isNaN(Date.parse(observation.observedAt)) || typeof observation.pageUrl !== "string" || observation.pageUrl.length > 2048) {
868
+ return false;
869
+ }
870
+ try {
871
+ const pageUrl = new URL(observation.pageUrl);
872
+ return pageUrl.protocol === "https:" && !pageUrl.username && !pageUrl.password;
873
+ } catch {
874
+ return false;
875
+ }
876
+ }
712
877
  var ContoError = class extends Error {
713
878
  constructor(message, metadata) {
714
879
  super(message);
@@ -719,6 +884,8 @@ var ContoError = class extends Error {
719
884
  this.retryAfter = metadata.retryAfter;
720
885
  this.retryGuidance = metadata.retryGuidance;
721
886
  this.actionUrl = metadata.actionUrl;
887
+ this.fieldErrors = metadata.fieldErrors;
888
+ this.reasons = metadata.reasons;
722
889
  }
723
890
  };
724
891
  var ExternalCardConfirmationPendingError = class extends ContoError {
@@ -734,6 +901,20 @@ var ExternalCardConfirmationPendingError = class extends ContoError {
734
901
  this.receipt = receipt;
735
902
  }
736
903
  };
904
+ var BrowserActionFinalizationPendingError = class extends ContoError {
905
+ constructor(input) {
906
+ super("Browser action completed but finalization is pending", {
907
+ code: "BROWSER_ACTION_FINALIZATION_PENDING",
908
+ status: 0,
909
+ retryGuidance: "Persist the result and observation, then retry finalizeBrowserAction with the same dispatch attempt. Do not execute the browser action again."
910
+ });
911
+ this.name = "BrowserActionFinalizationPendingError";
912
+ this.authorizationId = input.authorizationId;
913
+ this.dispatchAttemptId = input.dispatchAttemptId;
914
+ this.result = input.result;
915
+ this.observation = input.observation;
916
+ }
917
+ };
737
918
  function generatedIdempotencyKey(prefix) {
738
919
  return `${prefix}_${randomUUID()}`;
739
920
  }
@@ -763,6 +944,24 @@ function withQuery(path, query) {
763
944
  const queryString = searchParams.toString();
764
945
  return queryString ? `${path}?${queryString}` : path;
765
946
  }
947
+ function rethrowAmbiguousCommerceMutation(error, executionId, action) {
948
+ if (error instanceof ContoError && (error.status === 0 || error.status >= 500)) {
949
+ throw new ContoError(error.message, {
950
+ code: error.code,
951
+ status: error.status,
952
+ requestId: error.requestId,
953
+ retryAfter: error.retryAfter,
954
+ retryGuidance: action === "authorize" ? `Call getCommerceExecution(${JSON.stringify(
955
+ executionId
956
+ )}). If it is PROCESSING, treat the authorization as consumed and do not authorize or click again.` : `Call getCommerceExecution(${JSON.stringify(
957
+ executionId
958
+ )}), then refreshCommerceExecution(${JSON.stringify(
959
+ executionId
960
+ )}). Do not ${action} again until the existing execution is reconciled.`
961
+ });
962
+ }
963
+ throw error;
964
+ }
766
965
  var Conto = class {
767
966
  constructor(config) {
768
967
  if (!config.apiKey) {
@@ -773,13 +972,290 @@ var Conto = class {
773
972
  }
774
973
  this.apiKey = config.apiKey;
775
974
  this.baseUrl = (config.baseUrl || "https://conto.finance").replace(/\/$/, "");
975
+ assertSecureBaseUrl(this.baseUrl);
776
976
  this.timeout = config.timeout || 3e4;
777
977
  this.payments = new PaymentsClient(this);
778
978
  this.cards = new CardsClient(this);
979
+ this.connectedCards = new ConnectedCardsClient(this);
779
980
  this.x402 = new X402Client(this);
780
981
  this.mpp = new MppClient(this);
781
982
  }
782
- /** Make an authenticated request to the Giro API */
983
+ /**
984
+ * List masked Link payment methods for a session-created connection that is
985
+ * explicitly bound to this agent. No Link token or payment artifact is
986
+ * returned through the agent SDK.
987
+ */
988
+ async listStripeLinkPaymentMethods(connectionId) {
989
+ return this.request(
990
+ "GET",
991
+ withQuery("/api/sdk/stripe-link/payment-methods", { connectionId })
992
+ );
993
+ }
994
+ /**
995
+ * Normalize and durably revise a partial agent purchase proposal without
996
+ * creating a payment side effect. Missing facts return as NEEDS_CONTEXT
997
+ * requirements; changed writes require the exact prior revision.
998
+ */
999
+ async normalizeCommerceProposal(input) {
1000
+ const proposalKey = input.proposalKey?.trim();
1001
+ if (!proposalKey || proposalKey.length < 8 || proposalKey.length > 255) {
1002
+ throw new ContoError("proposalKey must contain 8 to 255 characters", {
1003
+ code: "INVALID_PROPOSAL_KEY",
1004
+ status: 0
1005
+ });
1006
+ }
1007
+ return this.request(
1008
+ "POST",
1009
+ "/api/sdk/commerce-proposals/normalize",
1010
+ { ...input, proposalKey },
1011
+ { idempotent: true }
1012
+ );
1013
+ }
1014
+ /** Reload one durable proposal head in this agent and tenant scope. */
1015
+ async getCommerceProposal(proposalId) {
1016
+ return this.request(
1017
+ "GET",
1018
+ `/api/sdk/commerce-proposals/${encodeURIComponent(proposalId)}`
1019
+ );
1020
+ }
1021
+ /**
1022
+ * Evaluate policy and begin one exact Stripe Link commerce intent.
1023
+ *
1024
+ * The stable idempotency key makes transport retries safe. Reusing it with a
1025
+ * different intent is rejected by the API.
1026
+ */
1027
+ async beginCommerceExecution(input) {
1028
+ const idempotencyKey = input.idempotencyKey?.trim();
1029
+ if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 255) {
1030
+ throw new ContoError("idempotencyKey must contain 8 to 255 characters", {
1031
+ code: "INVALID_IDEMPOTENCY_KEY",
1032
+ status: 0,
1033
+ retryGuidance: "Generate one stable identifier per commerce intent and reuse it for retries."
1034
+ });
1035
+ }
1036
+ return this.request(
1037
+ "POST",
1038
+ input.executionMethod === "AGENT_CHECKOUT" || input.executionMethod === "BROWSER_CHECKOUT" ? "/api/sdk/checkout-guard/executions" : "/api/sdk/commerce-executions",
1039
+ {
1040
+ ..."connectionId" in input ? {
1041
+ connectionId: input.connectionId,
1042
+ paymentMethodId: input.paymentMethodId
1043
+ } : {},
1044
+ ..."browserSessionId" in input ? {
1045
+ browserSessionId: input.browserSessionId,
1046
+ action: input.action
1047
+ } : {},
1048
+ executionMethod: input.executionMethod ?? "MPP",
1049
+ idempotencyKey,
1050
+ intentId: input.intentId,
1051
+ amountMinor: input.amountMinor,
1052
+ currency: input.currency,
1053
+ purpose: input.purpose,
1054
+ category: input.category,
1055
+ counterparty: input.counterparty,
1056
+ cart: input.cart,
1057
+ providerPresentation: input.providerPresentation
1058
+ },
1059
+ { idempotent: true }
1060
+ );
1061
+ }
1062
+ /** Atomically consume the Conto authorization for one exact browser action. */
1063
+ async authorizeCheckoutAction(executionId, browserSessionId, action) {
1064
+ try {
1065
+ return await this.request(
1066
+ "POST",
1067
+ `/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/authorize-action`,
1068
+ { browserSessionId, action }
1069
+ );
1070
+ } catch (error) {
1071
+ rethrowAmbiguousCommerceMutation(error, executionId, "authorize");
1072
+ }
1073
+ }
1074
+ /** Claim a durable browser action for one stable privileged-runtime attempt. */
1075
+ async claimBrowserAction(authorizationId, dispatchAttemptId, browserSessionId, action) {
1076
+ return this.request(
1077
+ "POST",
1078
+ `/api/sdk/browser-actions/${encodeURIComponent(authorizationId)}/claim`,
1079
+ { dispatchAttemptId, browserSessionId, action },
1080
+ { idempotent: true }
1081
+ );
1082
+ }
1083
+ /** Finalize the exact dispatch attempt; this never performs the browser side effect. */
1084
+ async finalizeBrowserAction(authorizationId, input) {
1085
+ return this.request(
1086
+ "POST",
1087
+ `/api/sdk/browser-actions/${encodeURIComponent(authorizationId)}/finalize`,
1088
+ input,
1089
+ { idempotent: true }
1090
+ );
1091
+ }
1092
+ /**
1093
+ * Own the complete privileged-browser enforcement sequence. The stable
1094
+ * dispatchAttemptId must be persisted by the runtime before this call.
1095
+ */
1096
+ async runGuardedCheckout(input, runtime) {
1097
+ const begun = await this.beginCommerceExecution(input);
1098
+ if (!["READY", "PROCESSING"].includes(begun.execution.status)) {
1099
+ throw new ContoError("Checkout action is not ready", {
1100
+ code: "COMMERCE_EXECUTION_NOT_READY",
1101
+ status: 409
1102
+ });
1103
+ }
1104
+ const authorized = await this.authorizeCheckoutAction(
1105
+ begun.execution.id,
1106
+ input.browserSessionId,
1107
+ input.action
1108
+ );
1109
+ await this.claimBrowserAction(
1110
+ authorized.authorization.authorizationId,
1111
+ runtime.dispatchAttemptId,
1112
+ input.browserSessionId,
1113
+ input.action
1114
+ );
1115
+ let dispatched;
1116
+ try {
1117
+ dispatched = await runtime.execute(authorized.authorization);
1118
+ } catch (error) {
1119
+ try {
1120
+ await this.finalizeBrowserAction(authorized.authorization.authorizationId, {
1121
+ dispatchAttemptId: runtime.dispatchAttemptId,
1122
+ outcome: "INDETERMINATE"
1123
+ });
1124
+ } catch {
1125
+ throw new ContoError("Browser action failed and its final state is indeterminate", {
1126
+ code: "BROWSER_ACTION_DISPATCH_INDETERMINATE",
1127
+ status: 0,
1128
+ retryGuidance: "Do not execute the browser action again. Reconcile the merchant page or payment provider before taking another action."
1129
+ });
1130
+ }
1131
+ throw error;
1132
+ }
1133
+ if (!dispatched || !isValidBrowserActionObservation(dispatched.observation)) {
1134
+ try {
1135
+ await this.finalizeBrowserAction(authorized.authorization.authorizationId, {
1136
+ dispatchAttemptId: runtime.dispatchAttemptId,
1137
+ outcome: "INDETERMINATE"
1138
+ });
1139
+ } catch {
1140
+ }
1141
+ throw new ContoError("Privileged browser executor returned an invalid observation", {
1142
+ code: "BROWSER_ACTION_OBSERVATION_INVALID",
1143
+ status: 0,
1144
+ retryGuidance: "Do not execute the browser action again. Reconcile the merchant page, then record a valid observation or resolve the indeterminate attempt."
1145
+ });
1146
+ }
1147
+ try {
1148
+ await this.finalizeBrowserAction(authorized.authorization.authorizationId, {
1149
+ dispatchAttemptId: runtime.dispatchAttemptId,
1150
+ outcome: "CONSUMED",
1151
+ observation: dispatched.observation
1152
+ });
1153
+ } catch {
1154
+ throw new BrowserActionFinalizationPendingError({
1155
+ authorizationId: authorized.authorization.authorizationId,
1156
+ dispatchAttemptId: runtime.dispatchAttemptId,
1157
+ result: dispatched.result,
1158
+ observation: dispatched.observation
1159
+ });
1160
+ }
1161
+ return {
1162
+ ...authorized,
1163
+ dispatchAttemptId: runtime.dispatchAttemptId,
1164
+ result: dispatched.result,
1165
+ observation: dispatched.observation
1166
+ };
1167
+ }
1168
+ /**
1169
+ * Reference privileged-driver integration. Only this wrapper should be
1170
+ * exposed as the agent's payment-sensitive browser tool.
1171
+ */
1172
+ async runGuardedCheckoutWithDriver(input, runtime) {
1173
+ return this.runGuardedCheckout(input, {
1174
+ dispatchAttemptId: runtime.dispatchAttemptId,
1175
+ execute: (authorization) => runtime.driver.dispatch({
1176
+ authorization,
1177
+ browserSessionId: input.browserSessionId,
1178
+ action: input.action
1179
+ })
1180
+ });
1181
+ }
1182
+ /**
1183
+ * Policy-check and authorize an exact action before invoking a legacy callback.
1184
+ * @deprecated Use runGuardedCheckout so the durable claim and final
1185
+ * observation cannot be omitted.
1186
+ */
1187
+ async guardCheckout(input, executor) {
1188
+ const begun = await this.beginCommerceExecution(input);
1189
+ if (begun.execution.status !== "READY") {
1190
+ throw new ContoError("Checkout action is not ready", {
1191
+ code: "COMMERCE_EXECUTION_NOT_READY",
1192
+ status: 409
1193
+ });
1194
+ }
1195
+ const authorized = await this.authorizeCheckoutAction(
1196
+ begun.execution.id,
1197
+ input.browserSessionId,
1198
+ input.action
1199
+ );
1200
+ const result = await executor(authorized.authorization);
1201
+ return { ...authorized, result };
1202
+ }
1203
+ /** Read one agent-scoped commerce execution and its normalized evidence. */
1204
+ async getCommerceExecution(executionId) {
1205
+ return this.request(
1206
+ "GET",
1207
+ `/api/sdk/commerce-executions/${encodeURIComponent(executionId)}`
1208
+ );
1209
+ }
1210
+ /**
1211
+ * Observe Link state and reconcile terminal accounting. This operation is
1212
+ * safe to retry because it does not request another checkout execution.
1213
+ */
1214
+ async refreshCommerceExecution(executionId) {
1215
+ return this.request(
1216
+ "POST",
1217
+ `/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/refresh`,
1218
+ {},
1219
+ { idempotent: true }
1220
+ );
1221
+ }
1222
+ /**
1223
+ * Execute the approved provider-direct or server-only checkout handoff. Transport retries are
1224
+ * deliberately disabled. Read or refresh the execution after an ambiguous
1225
+ * failure before taking another execution action.
1226
+ */
1227
+ async executeCommerceExecution(executionId) {
1228
+ try {
1229
+ return await this.request(
1230
+ "POST",
1231
+ `/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/execute`,
1232
+ {}
1233
+ );
1234
+ } catch (error) {
1235
+ rethrowAmbiguousCommerceMutation(error, executionId, "execute");
1236
+ }
1237
+ }
1238
+ /**
1239
+ * Request cancellation and release reserved spend after Link confirms it.
1240
+ * Transport retries are disabled because a lost provider response requires
1241
+ * reconciliation.
1242
+ */
1243
+ async cancelCommerceExecution(executionId) {
1244
+ try {
1245
+ return await this.request(
1246
+ "POST",
1247
+ `/api/sdk/commerce-executions/${encodeURIComponent(executionId)}/cancel`,
1248
+ {}
1249
+ );
1250
+ } catch (error) {
1251
+ rethrowAmbiguousCommerceMutation(error, executionId, "cancel");
1252
+ }
1253
+ }
1254
+ /** Read credentials, linked wallets, effective policies, and scopes without creating a payment. */
1255
+ async setup() {
1256
+ return this.request("GET", "/api/sdk/setup");
1257
+ }
1258
+ /** Make an authenticated request to the Conto API */
783
1259
  async request(method, path, body, options = {}) {
784
1260
  const url = `${this.baseUrl}${path}`;
785
1261
  const controller = new AbortController();
@@ -900,7 +1376,8 @@ var ControlledExternalCardPaymentTool = class {
900
1376
  this.executor = executor;
901
1377
  }
902
1378
  async pay(input) {
903
- const approval = await this.cardsClient.approve(input);
1379
+ const idempotencyKey = input.idempotencyKey.trim();
1380
+ const approval = await this.cardsClient.approve({ ...input, idempotencyKey });
904
1381
  if (!approval.approved || !approval.approvalToken) {
905
1382
  const executionAlreadyClaimed = approval.decision === "EXECUTING" || approval.decision === "CONFIRMING";
906
1383
  throw new ContoError("External card payment was not approved", {
@@ -933,6 +1410,7 @@ var ControlledExternalCardPaymentTool = class {
933
1410
  try {
934
1411
  execution = await this.executor({
935
1412
  requestId: approval.requestId,
1413
+ idempotencyKey,
936
1414
  cardId: input.cardId,
937
1415
  amount: input.amount,
938
1416
  currency: input.currency || "USD",
@@ -957,7 +1435,7 @@ var ControlledExternalCardPaymentTool = class {
957
1435
  Sentry2.captureException(outcomeError, {
958
1436
  extra: {
959
1437
  requestId: approval.requestId,
960
- executorError: error instanceof Error ? error.message : String(error)
1438
+ executorErrorName: error instanceof Error ? error.name : typeof error
961
1439
  }
962
1440
  });
963
1441
  throw outcomeError;
@@ -981,7 +1459,7 @@ var ControlledExternalCardPaymentTool = class {
981
1459
  Sentry2.captureException(pending, {
982
1460
  extra: {
983
1461
  requestId: approval.requestId,
984
- confirmationError: error instanceof Error ? error.message : String(error)
1462
+ confirmationErrorName: error instanceof Error ? error.name : typeof error
985
1463
  }
986
1464
  });
987
1465
  throw pending;
@@ -993,10 +1471,101 @@ var ControlledExternalCardPaymentTool = class {
993
1471
  };
994
1472
  }
995
1473
  };
1474
+ var ConnectedCardsClient = class {
1475
+ constructor(client) {
1476
+ this.client = client;
1477
+ }
1478
+ /**
1479
+ * Draw one single-use card credential against a verified mandate.
1480
+ *
1481
+ * On an APPROVED, minted draw the result carries live card material in
1482
+ * `credential`, returned exactly ONCE.
1483
+ *
1484
+ * CREDENTIAL SAFETY (load-bearing): the credential is the real, usable card.
1485
+ * It is revealed a single time and cannot be re-fetched. Use it in-process at
1486
+ * the intended merchant checkout only. Never log, print, serialize, persist,
1487
+ * cache, or forward it, and never place it in an error, a monitoring event,
1488
+ * or any other sink. See {@link ConnectedCardCredential}.
1489
+ *
1490
+ * Transport-level retry is deliberately OFF for this call. The server is
1491
+ * idempotent on `idempotencyKey`, so an automatic retry after a successful
1492
+ * mint would replay the draw and return the decision WITHOUT the credential,
1493
+ * silently losing the one-shot card material. If a `draw` fails ambiguously,
1494
+ * do not blindly retry: check the outcome, and reuse the same
1495
+ * `idempotencyKey` only to reconcile the same draw.
1496
+ *
1497
+ * @example
1498
+ * ```typescript
1499
+ * const draw = await conto.connectedCards.draw({
1500
+ * cardId: 'cmm59z8fj000l49h7bjqza11p',
1501
+ * mandateId: 'cmm5a1b2c000m49h7defgh22q',
1502
+ * idempotencyKey: 'checkout_01J8...',
1503
+ * amountMinor: 4999,
1504
+ * merchant: { merchantName: 'Example Store', merchantCategory: '5311' },
1505
+ * });
1506
+ * if (draw.decision === 'APPROVED' && draw.credential) {
1507
+ * // Keep the credential in memory, acknowledge receipt, then use it at checkout.
1508
+ * await conto.connectedCards.acknowledgeDelivery(draw.requestId);
1509
+ * // Do not log or store draw.credential.
1510
+ * }
1511
+ * ```
1512
+ */
1513
+ async draw(input) {
1514
+ const idempotencyKey = input.idempotencyKey?.trim();
1515
+ if (!idempotencyKey || idempotencyKey.length < 8 || idempotencyKey.length > 128) {
1516
+ throw new ContoError("idempotencyKey must contain 8 to 128 characters", {
1517
+ code: "INVALID_IDEMPOTENCY_KEY",
1518
+ status: 0,
1519
+ retryGuidance: "Generate one stable identifier per draw and reuse it only to reconcile the same draw."
1520
+ });
1521
+ }
1522
+ return this.client.request("POST", "/api/sdk/connected-cards/draws", {
1523
+ ...input,
1524
+ idempotencyKey,
1525
+ currency: input.currency ?? "USD"
1526
+ });
1527
+ }
1528
+ /**
1529
+ * List the connected cards and active mandates this agent may draw against.
1530
+ *
1531
+ * The response is masked: no PAN and no provider tokens. This is a read-only
1532
+ * GET, so transport retry on transient failures is safe.
1533
+ */
1534
+ /** Record receipt after a fresh draw. Does not confirm checkout or payment settlement. */
1535
+ async acknowledgeDelivery(requestId) {
1536
+ return this.client.request(
1537
+ "POST",
1538
+ `/api/sdk/connected-cards/draws/${encodeURIComponent(requestId)}/acknowledge`,
1539
+ {},
1540
+ { idempotent: true }
1541
+ );
1542
+ }
1543
+ async list() {
1544
+ return this.client.request("GET", "/api/sdk/connected-cards");
1545
+ }
1546
+ };
996
1547
  var PaymentsClient = class {
997
1548
  constructor(client) {
998
1549
  this.client = client;
999
1550
  }
1551
+ /** Authorize an externally signed transfer. This write is not automatically retried. */
1552
+ async approve(input) {
1553
+ return this.client.request(
1554
+ "POST",
1555
+ "/api/sdk/payments/approve",
1556
+ { ...input },
1557
+ { maxRetries: 0 }
1558
+ );
1559
+ }
1560
+ /** Record an existing transfer. Never sends funds; reconcile conflicts using status(). */
1561
+ async confirm(requestId, input) {
1562
+ return this.client.request(
1563
+ "POST",
1564
+ `/api/sdk/payments/${encodeURIComponent(requestId)}/confirm`,
1565
+ { ...input },
1566
+ { maxRetries: 0 }
1567
+ );
1568
+ }
1000
1569
  /**
1001
1570
  * Request authorization for a payment.
1002
1571
  *
@@ -1026,6 +1595,13 @@ var PaymentsClient = class {
1026
1595
  * ```
1027
1596
  */
1028
1597
  async request(input) {
1598
+ if (typeof input.amount !== "number" || !Number.isFinite(input.amount) || input.amount <= 0) {
1599
+ throw new ContoError("amount must be a positive finite number", {
1600
+ code: "INVALID_AMOUNT",
1601
+ status: 0,
1602
+ retryGuidance: "Provide a positive amount before requesting a payment."
1603
+ });
1604
+ }
1029
1605
  const requestInput = {
1030
1606
  ...input,
1031
1607
  idempotencyKey: input.idempotencyKey ?? generatedIdempotencyKey("conto_payment")
@@ -1093,7 +1669,7 @@ var PaymentsClient = class {
1093
1669
  async status(requestId) {
1094
1670
  return this.client.request(
1095
1671
  "GET",
1096
- `/api/sdk/payments/${requestId}`,
1672
+ `/api/sdk/payments/${encodeURIComponent(requestId)}`,
1097
1673
  void 0
1098
1674
  );
1099
1675
  }
@@ -1130,6 +1706,7 @@ var PaymentsClient = class {
1130
1706
  throw new ContoError("Payment was denied.", {
1131
1707
  code: "PAYMENT_DENIED",
1132
1708
  status: 403,
1709
+ reasons: customerPaymentReasons(request.reasons),
1133
1710
  requestId: request.requestId,
1134
1711
  retryGuidance: "Review the payment request before trying again."
1135
1712
  });
@@ -1147,6 +1724,7 @@ var PaymentsClient = class {
1147
1724
  code: "ACTION_REQUIRED",
1148
1725
  status: 402,
1149
1726
  requestId: request.requestId,
1727
+ actionUrl: request.actionUrl,
1150
1728
  retryGuidance: request.actionUrl ? `Complete the required action at ${request.actionUrl}, then check the payment status.` : "Complete the required action, then check the payment status."
1151
1729
  });
1152
1730
  }
@@ -1247,7 +1825,10 @@ var MppClient = class {
1247
1825
  };
1248
1826
  var index_default = Conto;
1249
1827
  export {
1828
+ BrowserActionFinalizationPendingError,
1829
+ BrowserCheckoutTargetVerificationError,
1250
1830
  CardsClient,
1831
+ ConnectedCardsClient,
1251
1832
  Conto,
1252
1833
  ContoAdmin,
1253
1834
  ContoAdminError,
@@ -1255,5 +1836,8 @@ export {
1255
1836
  ControlledExternalCardPaymentTool,
1256
1837
  ExternalCardConfirmationPendingError,
1257
1838
  Conto as Giro,
1258
- index_default as default
1839
+ canonicalizeBrowserCheckoutTarget,
1840
+ createVerifiedBrowserCheckoutDriver,
1841
+ index_default as default,
1842
+ fingerprintBrowserCheckoutTarget
1259
1843
  };