@conto_finance/sdk 0.5.0 → 0.5.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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @conto_finance/sdk
2
2
 
3
3
  Conto is a buyer-side control layer. Integrate it in the agent owner's runtime or payment tools;
4
- the owner or budget holder sets policy before the agent pays. Merchants do not install Conto.
4
+ the owner or budget holder sets policy before the agent pays.
5
5
  See [buyer-side control](https://conto.finance/docs/guides/buyer-side-control) for the authorization
6
6
  and execution boundary.
7
7
 
@@ -100,34 +100,9 @@ await admin.cards.revokeFromAgent('agent_id', card.id);
100
100
  ```
101
101
 
102
102
  The policy overlay stops at approval, claiming, and reconciliation. For a
103
- customer-hosted payment tool that gates access to customer payment code, use the
104
- separate
105
- [`@conto_finance/card-access`](https://conto.finance/docs/card-access/overview) product
106
- package.
107
-
108
- ```typescript
109
- import { ContoCardAccess } from '@conto_finance/card-access';
110
-
111
- const cardAccess = new ContoCardAccess({
112
- policy: conto.cards,
113
- integrationId: 'procurement-agent-production',
114
- deployment: {
115
- credentialCustody: 'CUSTOMER',
116
- agentHasRawCredentialAccess: false,
117
- uncontrolledAgentPaymentPath: false,
118
- },
119
- executor: customerHostedExecutor,
120
- });
121
- ```
122
-
123
- Card Access claims each approved request exactly once before the executor can run. A
124
- duplicate or uncertain claim cannot invoke the executor again. It remains
125
- bypassable if another person or tool can access the card, and results are
126
- self-reported rather than issuer-verified. The executor receives the normalized
127
- purchase `idempotencyKey`; pass it to the payment processor so ambiguous
128
- processor responses cannot create duplicate charges. The legacy `conto.cards.controlled()`
129
- helper remains available for compatibility, but new customer-hosted enforcement
130
- should use the standalone Card Access package. Use
103
+ customer-hosted payment tool that gates access to customer payment code, see
104
+ [Card Access](https://conto.finance/docs/card-access/overview), available on request.
105
+ The legacy `conto.cards.controlled()` helper remains available for compatibility. Use
131
106
  `admin.cards.listRequests({ status: 'EXECUTING' })` for reconciliation and
132
107
  `admin.cards.pause(card.id)` to revoke unclaimed grants and stop new approvals
133
108
  in the Conto-controlled path.
@@ -130,12 +130,23 @@ declare class BrowserbaseSessionError extends Error {
130
130
  readonly code: BrowserbaseSessionErrorCode;
131
131
  constructor(code: BrowserbaseSessionErrorCode, message: string);
132
132
  }
133
+ interface BrowserbaseSessionManager {
134
+ createSession(): Promise<BrowserbaseOwnedSession>;
135
+ attachSession(input: BrowserbaseSessionHandoff): Promise<BrowserbaseAttachedSession>;
136
+ connectUrlForSession(browserSessionId: string): Promise<string | null>;
137
+ liveViewForSession(browserSessionId: string): Promise<BrowserbaseOwnedLiveView>;
138
+ revokeSession(browserSessionId: string, lifecycleOwner: BrowserbaseSessionLifecycleOwner): Promise<boolean>;
139
+ releaseProviderSession(browserSessionId: string): Promise<void>;
140
+ releaseContextLease(browserSessionId: string): Promise<boolean>;
141
+ }
133
142
  /** Creates a short-lived assertion after the trusted agent host creates its Browserbase session. */
134
143
  declare function createBrowserbaseSessionHandoffAssertion(input: BrowserbaseSessionHandoffAssertionInput, keyValue: Uint8Array): string;
144
+ /** Server-only capability. Never expose connect URLs or this manager as agent tools. */
145
+ declare function createBrowserbaseSessionManager(config: BrowserbaseSessionConfig): BrowserbaseSessionManager;
135
146
  /**
136
147
  * Creates the recommended Browserbase deployment boundary. Provider lifecycle, ownership checks,
137
148
  * remote connection resolution, guarded execution, and cleanup remain in one privileged object.
138
149
  */
139
150
  declare function createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost(config: BrowserbaseGuardedPlaywrightCheckoutRuntimeHostConfig): BrowserbaseGuardedPlaywrightCheckoutRuntimeHost;
140
151
 
141
- export { BROWSERBASE_CONNECT_ORIGINS, type BrowserbaseAttachedSession, type BrowserbaseGuardedPlaywrightCheckoutRuntimeHost, type BrowserbaseGuardedPlaywrightCheckoutRuntimeHostConfig, type BrowserbaseOwnedLiveView, type BrowserbaseOwnedSession, type BrowserbaseSessionBinding, type BrowserbaseSessionBindingState, type BrowserbaseSessionBindingStore, type BrowserbaseSessionConfig, BrowserbaseSessionError, type BrowserbaseSessionErrorCode, type BrowserbaseSessionHandoff, type BrowserbaseSessionHandoffAssertionInput, type BrowserbaseSessionLifecycleOwner, type BrowserbaseSessionStatus, createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost, createBrowserbaseSessionHandoffAssertion };
152
+ export { BROWSERBASE_CONNECT_ORIGINS, type BrowserbaseAttachedSession, type BrowserbaseGuardedPlaywrightCheckoutRuntimeHost, type BrowserbaseGuardedPlaywrightCheckoutRuntimeHostConfig, type BrowserbaseOwnedLiveView, type BrowserbaseOwnedSession, type BrowserbaseSessionBinding, type BrowserbaseSessionBindingState, type BrowserbaseSessionBindingStore, type BrowserbaseSessionConfig, BrowserbaseSessionError, type BrowserbaseSessionErrorCode, type BrowserbaseSessionHandoff, type BrowserbaseSessionHandoffAssertionInput, type BrowserbaseSessionLifecycleOwner, type BrowserbaseSessionManager, type BrowserbaseSessionStatus, createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost, createBrowserbaseSessionHandoffAssertion, createBrowserbaseSessionManager };
@@ -130,12 +130,23 @@ declare class BrowserbaseSessionError extends Error {
130
130
  readonly code: BrowserbaseSessionErrorCode;
131
131
  constructor(code: BrowserbaseSessionErrorCode, message: string);
132
132
  }
133
+ interface BrowserbaseSessionManager {
134
+ createSession(): Promise<BrowserbaseOwnedSession>;
135
+ attachSession(input: BrowserbaseSessionHandoff): Promise<BrowserbaseAttachedSession>;
136
+ connectUrlForSession(browserSessionId: string): Promise<string | null>;
137
+ liveViewForSession(browserSessionId: string): Promise<BrowserbaseOwnedLiveView>;
138
+ revokeSession(browserSessionId: string, lifecycleOwner: BrowserbaseSessionLifecycleOwner): Promise<boolean>;
139
+ releaseProviderSession(browserSessionId: string): Promise<void>;
140
+ releaseContextLease(browserSessionId: string): Promise<boolean>;
141
+ }
133
142
  /** Creates a short-lived assertion after the trusted agent host creates its Browserbase session. */
134
143
  declare function createBrowserbaseSessionHandoffAssertion(input: BrowserbaseSessionHandoffAssertionInput, keyValue: Uint8Array): string;
144
+ /** Server-only capability. Never expose connect URLs or this manager as agent tools. */
145
+ declare function createBrowserbaseSessionManager(config: BrowserbaseSessionConfig): BrowserbaseSessionManager;
135
146
  /**
136
147
  * Creates the recommended Browserbase deployment boundary. Provider lifecycle, ownership checks,
137
148
  * remote connection resolution, guarded execution, and cleanup remain in one privileged object.
138
149
  */
139
150
  declare function createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost(config: BrowserbaseGuardedPlaywrightCheckoutRuntimeHostConfig): BrowserbaseGuardedPlaywrightCheckoutRuntimeHost;
140
151
 
141
- export { BROWSERBASE_CONNECT_ORIGINS, type BrowserbaseAttachedSession, type BrowserbaseGuardedPlaywrightCheckoutRuntimeHost, type BrowserbaseGuardedPlaywrightCheckoutRuntimeHostConfig, type BrowserbaseOwnedLiveView, type BrowserbaseOwnedSession, type BrowserbaseSessionBinding, type BrowserbaseSessionBindingState, type BrowserbaseSessionBindingStore, type BrowserbaseSessionConfig, BrowserbaseSessionError, type BrowserbaseSessionErrorCode, type BrowserbaseSessionHandoff, type BrowserbaseSessionHandoffAssertionInput, type BrowserbaseSessionLifecycleOwner, type BrowserbaseSessionStatus, createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost, createBrowserbaseSessionHandoffAssertion };
152
+ export { BROWSERBASE_CONNECT_ORIGINS, type BrowserbaseAttachedSession, type BrowserbaseGuardedPlaywrightCheckoutRuntimeHost, type BrowserbaseGuardedPlaywrightCheckoutRuntimeHostConfig, type BrowserbaseOwnedLiveView, type BrowserbaseOwnedSession, type BrowserbaseSessionBinding, type BrowserbaseSessionBindingState, type BrowserbaseSessionBindingStore, type BrowserbaseSessionConfig, BrowserbaseSessionError, type BrowserbaseSessionErrorCode, type BrowserbaseSessionHandoff, type BrowserbaseSessionHandoffAssertionInput, type BrowserbaseSessionLifecycleOwner, type BrowserbaseSessionManager, type BrowserbaseSessionStatus, createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost, createBrowserbaseSessionHandoffAssertion, createBrowserbaseSessionManager };
@@ -23,7 +23,8 @@ __export(browserbase_exports, {
23
23
  BROWSERBASE_CONNECT_ORIGINS: () => BROWSERBASE_CONNECT_ORIGINS,
24
24
  BrowserbaseSessionError: () => BrowserbaseSessionError,
25
25
  createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost: () => createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost,
26
- createBrowserbaseSessionHandoffAssertion: () => createBrowserbaseSessionHandoffAssertion
26
+ createBrowserbaseSessionHandoffAssertion: () => createBrowserbaseSessionHandoffAssertion,
27
+ createBrowserbaseSessionManager: () => createBrowserbaseSessionManager
27
28
  });
28
29
  module.exports = __toCommonJS(browserbase_exports);
29
30
  var import_node_crypto3 = require("crypto");
@@ -1572,7 +1573,9 @@ function parseProviderSession(value, expectedProjectId, expectedSessionId) {
1572
1573
  status,
1573
1574
  createdAt,
1574
1575
  expiresAt,
1575
- connectUrl: secureConnectUrl(record.connectUrl)
1576
+ // Completed sessions need not retain a connection capability. Validate it
1577
+ // only for active states; identity and timestamps above still must match.
1578
+ connectUrl: status === "PENDING" || status === "RUNNING" ? secureConnectUrl(record.connectUrl) : null
1576
1579
  });
1577
1580
  }
1578
1581
  async function readProviderJson(response) {
@@ -1668,11 +1671,13 @@ function createBrowserbaseSessionManager(config) {
1668
1671
  projectId,
1669
1672
  timeout: durationSeconds,
1670
1673
  keepAlive: true,
1671
- ...contextId ? {
1672
- browserSettings: {
1673
- context: { id: contextId, persist: true }
1674
- }
1675
- } : {}
1674
+ proxies: false,
1675
+ browserSettings: {
1676
+ recordSession: false,
1677
+ logSession: false,
1678
+ solveCaptchas: false,
1679
+ ...contextId ? { context: { id: contextId, persist: true } } : {}
1680
+ }
1676
1681
  })
1677
1682
  });
1678
1683
  if (response.status !== 201) {
@@ -1981,5 +1986,6 @@ function createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost(config) {
1981
1986
  BROWSERBASE_CONNECT_ORIGINS,
1982
1987
  BrowserbaseSessionError,
1983
1988
  createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost,
1984
- createBrowserbaseSessionHandoffAssertion
1989
+ createBrowserbaseSessionHandoffAssertion,
1990
+ createBrowserbaseSessionManager
1985
1991
  });
@@ -196,7 +196,9 @@ function parseProviderSession(value, expectedProjectId, expectedSessionId) {
196
196
  status,
197
197
  createdAt,
198
198
  expiresAt,
199
- connectUrl: secureConnectUrl(record.connectUrl)
199
+ // Completed sessions need not retain a connection capability. Validate it
200
+ // only for active states; identity and timestamps above still must match.
201
+ connectUrl: status === "PENDING" || status === "RUNNING" ? secureConnectUrl(record.connectUrl) : null
200
202
  });
201
203
  }
202
204
  async function readProviderJson(response) {
@@ -292,11 +294,13 @@ function createBrowserbaseSessionManager(config) {
292
294
  projectId,
293
295
  timeout: durationSeconds,
294
296
  keepAlive: true,
295
- ...contextId ? {
296
- browserSettings: {
297
- context: { id: contextId, persist: true }
298
- }
299
- } : {}
297
+ proxies: false,
298
+ browserSettings: {
299
+ recordSession: false,
300
+ logSession: false,
301
+ solveCaptchas: false,
302
+ ...contextId ? { context: { id: contextId, persist: true } } : {}
303
+ }
300
304
  })
301
305
  });
302
306
  if (response.status !== 201) {
@@ -604,5 +608,6 @@ export {
604
608
  BROWSERBASE_CONNECT_ORIGINS,
605
609
  BrowserbaseSessionError,
606
610
  createBrowserbaseGuardedPlaywrightCheckoutRuntimeHost,
607
- createBrowserbaseSessionHandoffAssertion
611
+ createBrowserbaseSessionHandoffAssertion,
612
+ createBrowserbaseSessionManager
608
613
  };
package/dist/index.d.mts CHANGED
@@ -65,7 +65,7 @@ interface PaginatedResponse<T> {
65
65
  limit: number;
66
66
  offset: number;
67
67
  }
68
- type AgentType = 'OPENAI_ASSISTANT' | 'ANTHROPIC_CLAUDE' | 'LANGCHAIN' | 'AUTOGPT' | 'CUSTOM';
68
+ type AgentType = 'OPENAI_ASSISTANT' | 'ANTHROPIC_CLAUDE' | 'AUTOGPT' | 'CUSTOM';
69
69
  type AgentStatus = 'ACTIVE' | 'PAUSED' | 'SUSPENDED' | 'REVOKED' | 'FROZEN' | 'DELETED';
70
70
  type AgentEnvironment = 'DEV' | 'STAGING' | 'PROD';
71
71
  type AgentRiskTier = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
@@ -1164,6 +1164,11 @@ interface PaymentRequestResult {
1164
1164
  interface PaymentExecuteResult {
1165
1165
  /** Payment request ID */
1166
1166
  requestId: string;
1167
+ /**
1168
+ * True when the payment had already been executed, for example by an
1169
+ * approver in Conto. The receipt is the existing one; nothing was sent again.
1170
+ */
1171
+ alreadyExecuted?: boolean;
1167
1172
  /** Customer-facing execution state */
1168
1173
  status: 'processing' | 'completed';
1169
1174
  /** Amount sent */
@@ -1196,6 +1201,8 @@ interface PaymentStatusResult {
1196
1201
  category: string | null;
1197
1202
  expiresAt: string | null;
1198
1203
  createdAt: string;
1204
+ /** Customer-facing reasons for a declined payment or a pending review. */
1205
+ reasons?: string[];
1199
1206
  action: null;
1200
1207
  transaction: {
1201
1208
  /** Stable Conto transaction ID */
@@ -1855,6 +1862,13 @@ interface ProtocolPreAuthorizeResult {
1855
1862
  authorized: boolean;
1856
1863
  decision: 'approved' | 'declined' | 'review_required';
1857
1864
  message: string;
1865
+ /**
1866
+ * Present when not authorized. Fixed reasons the agent can act on, such as a
1867
+ * spending limit or a service that is not allowed. Never includes rule values.
1868
+ */
1869
+ reasons?: string[];
1870
+ /** Present when not authorized. Codes for `reasons`, for example `SPENDING_LIMIT`. */
1871
+ reasonCodes?: string[];
1858
1872
  /**
1859
1873
  * Present when authorized. Pass grantId and grantSignature to record(). A
1860
1874
  * record without a valid, unexpired grant is accepted but treated as a policy
@@ -2368,7 +2382,9 @@ declare class PaymentsClient {
2368
2382
  * Execute an approved payment.
2369
2383
  *
2370
2384
  * This sends the onchain transaction. Only works for payments
2371
- * with status 'APPROVED'.
2385
+ * with status 'APPROVED'. If an approver already executed the payment by
2386
+ * approving it in Conto, this returns the existing receipt with
2387
+ * `alreadyExecuted: true` and sends nothing.
2372
2388
  *
2373
2389
  * @param requestId - The requestId from a successful payment request
2374
2390
  *
package/dist/index.d.ts CHANGED
@@ -65,7 +65,7 @@ interface PaginatedResponse<T> {
65
65
  limit: number;
66
66
  offset: number;
67
67
  }
68
- type AgentType = 'OPENAI_ASSISTANT' | 'ANTHROPIC_CLAUDE' | 'LANGCHAIN' | 'AUTOGPT' | 'CUSTOM';
68
+ type AgentType = 'OPENAI_ASSISTANT' | 'ANTHROPIC_CLAUDE' | 'AUTOGPT' | 'CUSTOM';
69
69
  type AgentStatus = 'ACTIVE' | 'PAUSED' | 'SUSPENDED' | 'REVOKED' | 'FROZEN' | 'DELETED';
70
70
  type AgentEnvironment = 'DEV' | 'STAGING' | 'PROD';
71
71
  type AgentRiskTier = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
@@ -1164,6 +1164,11 @@ interface PaymentRequestResult {
1164
1164
  interface PaymentExecuteResult {
1165
1165
  /** Payment request ID */
1166
1166
  requestId: string;
1167
+ /**
1168
+ * True when the payment had already been executed, for example by an
1169
+ * approver in Conto. The receipt is the existing one; nothing was sent again.
1170
+ */
1171
+ alreadyExecuted?: boolean;
1167
1172
  /** Customer-facing execution state */
1168
1173
  status: 'processing' | 'completed';
1169
1174
  /** Amount sent */
@@ -1196,6 +1201,8 @@ interface PaymentStatusResult {
1196
1201
  category: string | null;
1197
1202
  expiresAt: string | null;
1198
1203
  createdAt: string;
1204
+ /** Customer-facing reasons for a declined payment or a pending review. */
1205
+ reasons?: string[];
1199
1206
  action: null;
1200
1207
  transaction: {
1201
1208
  /** Stable Conto transaction ID */
@@ -1855,6 +1862,13 @@ interface ProtocolPreAuthorizeResult {
1855
1862
  authorized: boolean;
1856
1863
  decision: 'approved' | 'declined' | 'review_required';
1857
1864
  message: string;
1865
+ /**
1866
+ * Present when not authorized. Fixed reasons the agent can act on, such as a
1867
+ * spending limit or a service that is not allowed. Never includes rule values.
1868
+ */
1869
+ reasons?: string[];
1870
+ /** Present when not authorized. Codes for `reasons`, for example `SPENDING_LIMIT`. */
1871
+ reasonCodes?: string[];
1858
1872
  /**
1859
1873
  * Present when authorized. Pass grantId and grantSignature to record(). A
1860
1874
  * record without a valid, unexpired grant is accepted but treated as a policy
@@ -2368,7 +2382,9 @@ declare class PaymentsClient {
2368
2382
  * Execute an approved payment.
2369
2383
  *
2370
2384
  * This sends the onchain transaction. Only works for payments
2371
- * with status 'APPROVED'.
2385
+ * with status 'APPROVED'. If an approver already executed the payment by
2386
+ * approving it in Conto, this returns the existing receipt with
2387
+ * `alreadyExecuted: true` and sends nothing.
2372
2388
  *
2373
2389
  * @param requestId - The requestId from a successful payment request
2374
2390
  *
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ var require_package = __commonJS({
39
39
  "package.json"(exports2, module2) {
40
40
  module2.exports = {
41
41
  name: "@conto_finance/sdk",
42
- version: "0.5.0",
42
+ version: "0.5.1",
43
43
  description: "Conto SDK for AI agent payment authorization",
44
44
  main: "./dist/index.js",
45
45
  module: "./dist/index.mjs",
@@ -172,7 +172,10 @@ var PUBLIC_PAYMENT_REASONS = /* @__PURE__ */ new Set([
172
172
  "This payment is outside the permitted schedule.",
173
173
  "The available payment budget is insufficient.",
174
174
  "This payment does not meet the current payment controls.",
175
- "This payment requires approval before it can proceed."
175
+ "This payment requires approval before it can proceed.",
176
+ "No usable wallet is linked to this agent.",
177
+ "This payee needs review before payments to it can proceed automatically.",
178
+ "A reviewer declined this payment."
176
179
  ]);
177
180
  function customerPaymentReasons(value) {
178
181
  return Array.isArray(value) ? [
@@ -352,9 +355,11 @@ function responseErrorMetadata(response, data) {
352
355
  const body = record(data);
353
356
  const code = safeErrorCode(body.code, response.status);
354
357
  const bodyRetryAfter = typeof body.retryAfter === "number" || typeof body.retryAfter === "string" ? parseRetryAfter(String(body.retryAfter)) : void 0;
358
+ const reasons = customerPaymentReasons(body.reasons);
355
359
  return {
356
360
  code,
357
361
  status: response.status,
362
+ ...reasons.length > 0 ? { reasons } : {},
358
363
  fieldErrors: customerFieldErrors(body, response.status),
359
364
  requestId: safeRequestId(
360
365
  response.headers.get("x-request-id"),
@@ -1705,6 +1710,27 @@ var ConnectedCardsClient = class {
1705
1710
  return this.client.request("GET", "/api/sdk/connected-cards");
1706
1711
  }
1707
1712
  };
1713
+ function approvalRetryGuidance(requestId) {
1714
+ const id = JSON.stringify(requestId);
1715
+ return `An approver in Conto executes the payment when they approve it. After approval, call payments.execute(${id}) or payments.status(${id}) to get the receipt. Do not request the payment again.`;
1716
+ }
1717
+ function executeRetryGuidance(code, requestId) {
1718
+ const id = JSON.stringify(requestId);
1719
+ switch (code) {
1720
+ case "APPROVAL_PENDING":
1721
+ return approvalRetryGuidance(requestId);
1722
+ case "POLICY_DENIED_AT_EXECUTION":
1723
+ case "PAYMENT_DENIED":
1724
+ return "This payment was declined and will not execute. Do not retry it; review the reasons before requesting a new payment.";
1725
+ case "POLICY_REAPPROVAL_REQUIRED":
1726
+ return "This payment now needs approval and will not execute. Request a new payment and wait for approval.";
1727
+ case "PAYMENT_PROCESSING":
1728
+ case "NEEDS_RECONCILIATION":
1729
+ return `Do not execute again. Check payments.status(${id}) for the outcome.`;
1730
+ default:
1731
+ return void 0;
1732
+ }
1733
+ }
1708
1734
  var PaymentsClient = class {
1709
1735
  constructor(client) {
1710
1736
  this.client = client;
@@ -1778,7 +1804,9 @@ var PaymentsClient = class {
1778
1804
  * Execute an approved payment.
1779
1805
  *
1780
1806
  * This sends the onchain transaction. Only works for payments
1781
- * with status 'APPROVED'.
1807
+ * with status 'APPROVED'. If an approver already executed the payment by
1808
+ * approving it in Conto, this returns the existing receipt with
1809
+ * `alreadyExecuted: true` and sends nothing.
1782
1810
  *
1783
1811
  * @param requestId - The requestId from a successful payment request
1784
1812
  *
@@ -1800,18 +1828,17 @@ var PaymentsClient = class {
1800
1828
  {}
1801
1829
  );
1802
1830
  } catch (error) {
1803
- if (error instanceof ContoError && error.status === 0) {
1804
- throw new ContoError(error.message, {
1805
- code: error.code,
1806
- status: error.status,
1807
- requestId: error.requestId,
1808
- retryAfter: error.retryAfter,
1809
- retryGuidance: `Check payments.status(${JSON.stringify(
1810
- requestId
1811
- )}) before attempting execution again.`
1812
- });
1813
- }
1814
- throw error;
1831
+ if (!(error instanceof ContoError)) throw error;
1832
+ const retryGuidance = error.status === 0 ? `Check payments.status(${JSON.stringify(requestId)}) before attempting execution again.` : executeRetryGuidance(error.code, requestId);
1833
+ if (!retryGuidance) throw error;
1834
+ throw new ContoError(error.message, {
1835
+ code: error.code,
1836
+ status: error.status,
1837
+ requestId: error.requestId,
1838
+ retryAfter: error.retryAfter,
1839
+ reasons: error.reasons,
1840
+ retryGuidance
1841
+ });
1815
1842
  }
1816
1843
  }
1817
1844
  /**
@@ -1876,8 +1903,9 @@ var PaymentsClient = class {
1876
1903
  throw new ContoError("Payment requires manual approval", {
1877
1904
  code: "REQUIRES_APPROVAL",
1878
1905
  status: 202,
1906
+ reasons: customerPaymentReasons(request.reasons),
1879
1907
  requestId: request.requestId,
1880
- retryGuidance: "Wait for approval, then execute the same request ID."
1908
+ retryGuidance: approvalRetryGuidance(request.requestId)
1881
1909
  });
1882
1910
  }
1883
1911
  if (request.status === "PROCESSING") {
package/dist/index.mjs CHANGED
@@ -14,7 +14,7 @@ var require_package = __commonJS({
14
14
  "package.json"(exports, module) {
15
15
  module.exports = {
16
16
  name: "@conto_finance/sdk",
17
- version: "0.5.0",
17
+ version: "0.5.1",
18
18
  description: "Conto SDK for AI agent payment authorization",
19
19
  main: "./dist/index.js",
20
20
  module: "./dist/index.mjs",
@@ -128,7 +128,10 @@ var PUBLIC_PAYMENT_REASONS = /* @__PURE__ */ new Set([
128
128
  "This payment is outside the permitted schedule.",
129
129
  "The available payment budget is insufficient.",
130
130
  "This payment does not meet the current payment controls.",
131
- "This payment requires approval before it can proceed."
131
+ "This payment requires approval before it can proceed.",
132
+ "No usable wallet is linked to this agent.",
133
+ "This payee needs review before payments to it can proceed automatically.",
134
+ "A reviewer declined this payment."
132
135
  ]);
133
136
  function customerPaymentReasons(value) {
134
137
  return Array.isArray(value) ? [
@@ -308,9 +311,11 @@ function responseErrorMetadata(response, data) {
308
311
  const body = record(data);
309
312
  const code = safeErrorCode(body.code, response.status);
310
313
  const bodyRetryAfter = typeof body.retryAfter === "number" || typeof body.retryAfter === "string" ? parseRetryAfter(String(body.retryAfter)) : void 0;
314
+ const reasons = customerPaymentReasons(body.reasons);
311
315
  return {
312
316
  code,
313
317
  status: response.status,
318
+ ...reasons.length > 0 ? { reasons } : {},
314
319
  fieldErrors: customerFieldErrors(body, response.status),
315
320
  requestId: safeRequestId(
316
321
  response.headers.get("x-request-id"),
@@ -1544,6 +1549,27 @@ var ConnectedCardsClient = class {
1544
1549
  return this.client.request("GET", "/api/sdk/connected-cards");
1545
1550
  }
1546
1551
  };
1552
+ function approvalRetryGuidance(requestId) {
1553
+ const id = JSON.stringify(requestId);
1554
+ return `An approver in Conto executes the payment when they approve it. After approval, call payments.execute(${id}) or payments.status(${id}) to get the receipt. Do not request the payment again.`;
1555
+ }
1556
+ function executeRetryGuidance(code, requestId) {
1557
+ const id = JSON.stringify(requestId);
1558
+ switch (code) {
1559
+ case "APPROVAL_PENDING":
1560
+ return approvalRetryGuidance(requestId);
1561
+ case "POLICY_DENIED_AT_EXECUTION":
1562
+ case "PAYMENT_DENIED":
1563
+ return "This payment was declined and will not execute. Do not retry it; review the reasons before requesting a new payment.";
1564
+ case "POLICY_REAPPROVAL_REQUIRED":
1565
+ return "This payment now needs approval and will not execute. Request a new payment and wait for approval.";
1566
+ case "PAYMENT_PROCESSING":
1567
+ case "NEEDS_RECONCILIATION":
1568
+ return `Do not execute again. Check payments.status(${id}) for the outcome.`;
1569
+ default:
1570
+ return void 0;
1571
+ }
1572
+ }
1547
1573
  var PaymentsClient = class {
1548
1574
  constructor(client) {
1549
1575
  this.client = client;
@@ -1617,7 +1643,9 @@ var PaymentsClient = class {
1617
1643
  * Execute an approved payment.
1618
1644
  *
1619
1645
  * This sends the onchain transaction. Only works for payments
1620
- * with status 'APPROVED'.
1646
+ * with status 'APPROVED'. If an approver already executed the payment by
1647
+ * approving it in Conto, this returns the existing receipt with
1648
+ * `alreadyExecuted: true` and sends nothing.
1621
1649
  *
1622
1650
  * @param requestId - The requestId from a successful payment request
1623
1651
  *
@@ -1639,18 +1667,17 @@ var PaymentsClient = class {
1639
1667
  {}
1640
1668
  );
1641
1669
  } catch (error) {
1642
- if (error instanceof ContoError && error.status === 0) {
1643
- throw new ContoError(error.message, {
1644
- code: error.code,
1645
- status: error.status,
1646
- requestId: error.requestId,
1647
- retryAfter: error.retryAfter,
1648
- retryGuidance: `Check payments.status(${JSON.stringify(
1649
- requestId
1650
- )}) before attempting execution again.`
1651
- });
1652
- }
1653
- throw error;
1670
+ if (!(error instanceof ContoError)) throw error;
1671
+ const retryGuidance = error.status === 0 ? `Check payments.status(${JSON.stringify(requestId)}) before attempting execution again.` : executeRetryGuidance(error.code, requestId);
1672
+ if (!retryGuidance) throw error;
1673
+ throw new ContoError(error.message, {
1674
+ code: error.code,
1675
+ status: error.status,
1676
+ requestId: error.requestId,
1677
+ retryAfter: error.retryAfter,
1678
+ reasons: error.reasons,
1679
+ retryGuidance
1680
+ });
1654
1681
  }
1655
1682
  }
1656
1683
  /**
@@ -1715,8 +1742,9 @@ var PaymentsClient = class {
1715
1742
  throw new ContoError("Payment requires manual approval", {
1716
1743
  code: "REQUIRES_APPROVAL",
1717
1744
  status: 202,
1745
+ reasons: customerPaymentReasons(request.reasons),
1718
1746
  requestId: request.requestId,
1719
- retryGuidance: "Wait for approval, then execute the same request ID."
1747
+ retryGuidance: approvalRetryGuidance(request.requestId)
1720
1748
  });
1721
1749
  }
1722
1750
  if (request.status === "PROCESSING") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conto_finance/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Conto SDK for AI agent payment authorization",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",