@paybond/kit 0.9.7 → 0.9.8

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.d.ts CHANGED
@@ -4,6 +4,10 @@
4
4
  */
5
5
  import { type BuildSignedCreateIntentParams, type SettlementRail } from "./principal-intent.js";
6
6
  import { type SignPayeeEvidenceParams } from "./payee-evidence.js";
7
+ export type SpendScope = {
8
+ scope_type: string;
9
+ scope_key: string;
10
+ };
7
11
  export type VerifyCapabilityResult = {
8
12
  allow: boolean;
9
13
  auditId: string;
@@ -11,6 +15,28 @@ export type VerifyCapabilityResult = {
11
15
  intentId: string;
12
16
  code?: string;
13
17
  message?: string;
18
+ decisionId?: string;
19
+ approvalRequestId?: string;
20
+ policyVersion?: number;
21
+ reasonCodes?: string[];
22
+ spendScope?: SpendScope;
23
+ remainingCents?: number;
24
+ retryAfter?: number;
25
+ /** True when gateway policy requires operator approval before execution may proceed. */
26
+ approvalRequired?: boolean;
27
+ };
28
+ export type PaybondSpendAuthorizationInput = {
29
+ operation: string;
30
+ requestedSpendCents?: number;
31
+ vendorId?: string;
32
+ taskId?: string;
33
+ workflowId?: string;
34
+ toolCallId?: string;
35
+ toolName?: string;
36
+ currency?: string;
37
+ agentSubject?: string;
38
+ approvalToken?: string;
39
+ idempotencyKey?: string;
14
40
  };
15
41
  export type SubmitEvidenceResult = {
16
42
  intentId: string;
@@ -715,14 +741,19 @@ export declare class PaybondCapabilityBinding {
715
741
  authorizeSpend(input: PaybondSpendAuthorizationInput): Promise<VerifyCapabilityResult>;
716
742
  }
717
743
  export type PaybondSpendGuardInit = {
718
- harbor: Pick<HarborClient | GatewayHarborClient, "tenantId" | "verifyCapability">;
744
+ harbor: Pick<HarborClient | GatewayHarborClient, "tenantId" | "verifyCapability"> & {
745
+ completeSpendDecision?: (input: {
746
+ decisionId: string;
747
+ outcome: "consumed" | "released";
748
+ }) => Promise<void>;
749
+ };
719
750
  intentId: string;
720
751
  capabilityToken: string;
721
752
  };
722
- export type PaybondSpendAuthorizationInput = {
723
- operation: string;
724
- requestedSpendCents?: number;
725
- };
753
+ export declare class PaybondSpendApprovalRequiredError extends Error {
754
+ readonly result: VerifyCapabilityResult;
755
+ constructor(result: VerifyCapabilityResult);
756
+ }
726
757
  export declare class PaybondSpendDeniedError extends Error {
727
758
  readonly result: VerifyCapabilityResult;
728
759
  constructor(result: VerifyCapabilityResult);
@@ -730,13 +761,15 @@ export declare class PaybondSpendDeniedError extends Error {
730
761
  export type PaybondToolHandler<TArgs extends unknown[], TResult> = (...args: TArgs) => TResult | Promise<TResult>;
731
762
  export type PaybondGuardedToolHandler<TArgs extends unknown[], TResult> = (...args: TArgs) => Promise<Awaited<TResult>>;
732
763
  export declare class PaybondSpendGuard {
733
- readonly harbor: Pick<HarborClient | GatewayHarborClient, "tenantId" | "verifyCapability">;
764
+ readonly harbor: PaybondSpendGuardInit["harbor"];
734
765
  readonly intentId: string;
735
766
  readonly capabilityToken: string;
736
767
  constructor(init: PaybondSpendGuardInit | PaybondCapabilityBinding);
737
768
  verifySpendCapability(input: PaybondSpendAuthorizationInput): Promise<VerifyCapabilityResult>;
738
769
  authorizeSpend(input: PaybondSpendAuthorizationInput): Promise<VerifyCapabilityResult>;
739
770
  assertSpendAuthorized(input: PaybondSpendAuthorizationInput): Promise<VerifyCapabilityResult>;
771
+ /** Finalizes scope reservations tied to an authorization decision after tool execution. */
772
+ completeSpendAuthorization(decisionId: string, outcome: "consumed" | "released"): Promise<void>;
740
773
  guardTool<TArgs extends unknown[], TResult>(input: PaybondSpendAuthorizationInput, handler: PaybondToolHandler<TArgs, TResult>): PaybondGuardedToolHandler<TArgs, TResult>;
741
774
  }
742
775
  export declare function authorizeSpend(source: PaybondSpendGuardInit | PaybondCapabilityBinding, input: PaybondSpendAuthorizationInput): Promise<VerifyCapabilityResult>;
@@ -799,11 +832,9 @@ export declare class HarborClient {
799
832
  * @throws HarborHttpError when HTTP fails
800
833
  * @throws Error when Harbor echoes a different tenant / intent than requested
801
834
  */
802
- verifyCapability(input: {
835
+ verifyCapability(input: PaybondSpendAuthorizationInput & {
803
836
  intentId: string;
804
837
  token: string;
805
- operation: string;
806
- requestedSpendCents?: number;
807
838
  }): Promise<VerifyCapabilityResult>;
808
839
  verifySpendCapability(input: {
809
840
  intentId: string;
@@ -897,11 +928,9 @@ export declare class GatewayHarborClient {
897
928
  private fetchWithRetries;
898
929
  private postJSON;
899
930
  private mutationHeaders;
900
- verifyCapability(input: {
931
+ verifyCapability(input: PaybondSpendAuthorizationInput & {
901
932
  intentId: string;
902
933
  token: string;
903
- operation: string;
904
- requestedSpendCents?: number;
905
934
  }): Promise<VerifyCapabilityResult>;
906
935
  verifySpendCapability(input: {
907
936
  intentId: string;
@@ -915,6 +944,11 @@ export declare class GatewayHarborClient {
915
944
  operation: string;
916
945
  requestedSpendCents?: number;
917
946
  }): Promise<VerifyCapabilityResult>;
947
+ /** Finalizes active spend reservations after tool execution completes or is aborted. */
948
+ completeSpendDecision(input: {
949
+ decisionId: string;
950
+ outcome: "consumed" | "released";
951
+ }): Promise<void>;
918
952
  createIntent(body: Record<string, unknown>, options: GatewayHarborMutationOptions): Promise<Record<string, unknown>>;
919
953
  fundIntent(intentId: string, options: GatewayHarborMutationOptions & {
920
954
  paymentSignature?: string;
package/dist/index.js CHANGED
@@ -4,6 +4,77 @@
4
4
  */
5
5
  import { buildSignedCreateIntentBody, } from "./principal-intent.js";
6
6
  import { signPayeeEvidenceBinding } from "./payee-evidence.js";
7
+ function parseVerifyCapabilityBody(body, expectedTenant, expectedIntentId) {
8
+ const tenant = String(body.tenant ?? "");
9
+ const intentId = String(body.intent_id ?? "");
10
+ if (tenant !== expectedTenant) {
11
+ throw new Error(`verify tenant mismatch: client=${expectedTenant} remote=${tenant}`);
12
+ }
13
+ if (intentId !== expectedIntentId) {
14
+ throw new Error(`verify intent mismatch: requested=${expectedIntentId} remote=${intentId}`);
15
+ }
16
+ const reasonCodes = Array.isArray(body.reason_codes)
17
+ ? body.reason_codes.map((value) => String(value))
18
+ : undefined;
19
+ const spendScope = body.spend_scope && typeof body.spend_scope === "object" && !Array.isArray(body.spend_scope)
20
+ ? {
21
+ scope_type: String(body.spend_scope.scope_type ?? ""),
22
+ scope_key: String(body.spend_scope.scope_key ?? ""),
23
+ }
24
+ : undefined;
25
+ const code = body.code != null ? String(body.code) : undefined;
26
+ const approvalRequired = code === "approval_required" ||
27
+ reasonCodes?.includes("approval_threshold_exceeded") ||
28
+ reasonCodes?.includes("approval_required_pending") ||
29
+ reasonCodes?.includes("anomaly_new_vendor") ||
30
+ reasonCodes?.includes("anomaly_amount_spike") ||
31
+ reasonCodes?.includes("anomaly_rapid_auth") ||
32
+ reasonCodes?.includes("anomaly_cap_proximity") ||
33
+ false;
34
+ return {
35
+ allow: Boolean(body.allow),
36
+ auditId: String(body.audit_id ?? ""),
37
+ tenant,
38
+ intentId,
39
+ code,
40
+ message: body.message != null ? String(body.message) : undefined,
41
+ decisionId: body.decision_id != null ? String(body.decision_id) : undefined,
42
+ approvalRequestId: body.approval_request_id != null ? String(body.approval_request_id) : undefined,
43
+ policyVersion: typeof body.policy_version === "number" ? body.policy_version : undefined,
44
+ reasonCodes,
45
+ spendScope,
46
+ remainingCents: typeof body.remaining_cents === "number" ? body.remaining_cents : undefined,
47
+ retryAfter: typeof body.retry_after === "number" ? body.retry_after : undefined,
48
+ approvalRequired,
49
+ };
50
+ }
51
+ function verifyCapabilityPayload(input) {
52
+ const payload = {
53
+ intent_id: input.intentId,
54
+ token: input.token,
55
+ operation: input.operation,
56
+ requested_spend_cents: input.requestedSpendCents ?? 0,
57
+ };
58
+ if (input.vendorId?.trim())
59
+ payload.vendor_id = input.vendorId.trim();
60
+ if (input.taskId?.trim())
61
+ payload.task_id = input.taskId.trim();
62
+ if (input.workflowId?.trim())
63
+ payload.workflow_id = input.workflowId.trim();
64
+ if (input.toolCallId?.trim())
65
+ payload.tool_call_id = input.toolCallId.trim();
66
+ if (input.toolName?.trim())
67
+ payload.tool_name = input.toolName.trim();
68
+ if (input.currency?.trim())
69
+ payload.currency = input.currency.trim();
70
+ if (input.agentSubject?.trim())
71
+ payload.agent_subject = input.agentSubject.trim();
72
+ if (input.approvalToken?.trim())
73
+ payload.approval_token = input.approvalToken.trim();
74
+ if (input.idempotencyKey?.trim())
75
+ payload.idempotency_key = input.idempotencyKey.trim();
76
+ return payload;
77
+ }
7
78
  export const DEFAULT_PAYBOND_GATEWAY_BASE_URL = "https://api.paybond.ai";
8
79
  function defaultGatewayBaseUrl(value) {
9
80
  const trimmed = value?.trim();
@@ -158,16 +229,24 @@ export class PaybondCapabilityBinding {
158
229
  }
159
230
  async verifySpendCapability(input) {
160
231
  return this.harbor.verifyCapability({
232
+ ...input,
161
233
  intentId: this.intentId,
162
234
  token: this.capabilityToken,
163
- operation: input.operation,
164
- requestedSpendCents: input.requestedSpendCents,
165
235
  });
166
236
  }
167
237
  async authorizeSpend(input) {
168
238
  return this.verifySpendCapability(input);
169
239
  }
170
240
  }
241
+ export class PaybondSpendApprovalRequiredError extends Error {
242
+ result;
243
+ constructor(result) {
244
+ const reason = result.message ?? result.code ?? "approval_required";
245
+ super(`Paybond spend authorization requires approval: ${reason}`);
246
+ this.name = "PaybondSpendApprovalRequiredError";
247
+ this.result = result;
248
+ }
249
+ }
171
250
  export class PaybondSpendDeniedError extends Error {
172
251
  result;
173
252
  constructor(result) {
@@ -188,10 +267,9 @@ export class PaybondSpendGuard {
188
267
  }
189
268
  async verifySpendCapability(input) {
190
269
  return this.harbor.verifyCapability({
270
+ ...input,
191
271
  intentId: this.intentId,
192
272
  token: this.capabilityToken,
193
- operation: input.operation,
194
- requestedSpendCents: input.requestedSpendCents,
195
273
  });
196
274
  }
197
275
  async authorizeSpend(input) {
@@ -200,14 +278,42 @@ export class PaybondSpendGuard {
200
278
  async assertSpendAuthorized(input) {
201
279
  const result = await this.authorizeSpend(input);
202
280
  if (!result.allow) {
281
+ if (result.approvalRequired) {
282
+ throw new PaybondSpendApprovalRequiredError(result);
283
+ }
203
284
  throw new PaybondSpendDeniedError(result);
204
285
  }
205
286
  return result;
206
287
  }
288
+ /** Finalizes scope reservations tied to an authorization decision after tool execution. */
289
+ async completeSpendAuthorization(decisionId, outcome) {
290
+ const complete = this.harbor.completeSpendDecision;
291
+ if (!complete) {
292
+ return;
293
+ }
294
+ await complete.call(this.harbor, { decisionId, outcome });
295
+ }
207
296
  guardTool(input, handler) {
208
297
  return async (...args) => {
209
- await this.assertSpendAuthorized(input);
210
- return await handler(...args);
298
+ const auth = await this.assertSpendAuthorized(input);
299
+ try {
300
+ const result = await handler(...args);
301
+ if (auth.decisionId) {
302
+ await this.completeSpendAuthorization(auth.decisionId, "consumed");
303
+ }
304
+ return result;
305
+ }
306
+ catch (err) {
307
+ if (auth.decisionId) {
308
+ try {
309
+ await this.completeSpendAuthorization(auth.decisionId, "released");
310
+ }
311
+ catch {
312
+ // Best-effort release when the guarded handler fails.
313
+ }
314
+ }
315
+ throw err;
316
+ }
211
317
  };
212
318
  }
213
319
  }
@@ -245,6 +351,9 @@ export function paybondRuntimeToolCallAdapter(init) {
245
351
  if (init.onDeny) {
246
352
  return await init.onDeny(result, call);
247
353
  }
354
+ if (result.approvalRequired) {
355
+ throw new PaybondSpendApprovalRequiredError(result);
356
+ }
248
357
  throw new PaybondSpendDeniedError(result);
249
358
  }
250
359
  return await init.execute(call);
@@ -384,17 +493,13 @@ export class HarborClient {
384
493
  */
385
494
  async verifyCapability(input) {
386
495
  const url = `${this.base}verify`;
387
- const payload = {
388
- intent_id: input.intentId,
389
- token: input.token,
390
- operation: input.operation,
391
- requested_spend_cents: input.requestedSpendCents ?? 0,
392
- };
496
+ const payload = verifyCapabilityPayload(input);
393
497
  const res = await this.fetchWithRetries(url, {
394
498
  method: "POST",
395
499
  headers: {
396
500
  "content-type": "application/json",
397
501
  "x-tenant-id": this.tenantId,
502
+ ...(input.idempotencyKey?.trim() ? { "idempotency-key": input.idempotencyKey.trim() } : {}),
398
503
  },
399
504
  body: JSON.stringify(payload),
400
505
  }, { retryBody: payload });
@@ -407,20 +512,7 @@ export class HarborClient {
407
512
  });
408
513
  }
409
514
  const body = JSON.parse(text);
410
- if (body.tenant !== this.tenantId) {
411
- throw new Error(`verify tenant mismatch: client=${this.tenantId} harbor=${body.tenant}`);
412
- }
413
- if (body.intent_id !== input.intentId) {
414
- throw new Error(`verify intent mismatch: requested=${input.intentId} harbor=${body.intent_id}`);
415
- }
416
- return {
417
- allow: body.allow,
418
- auditId: body.audit_id,
419
- tenant: body.tenant,
420
- intentId: body.intent_id,
421
- code: body.code,
422
- message: body.message,
423
- };
515
+ return parseVerifyCapabilityBody(body, this.tenantId, input.intentId);
424
516
  }
425
517
  async verifySpendCapability(input) {
426
518
  return this.verifyCapability(input);
@@ -722,13 +814,12 @@ export class GatewayHarborClient {
722
814
  });
723
815
  }
724
816
  async verifyCapability(input) {
725
- const payload = {
726
- intent_id: input.intentId,
727
- token: input.token,
728
- operation: input.operation,
729
- requested_spend_cents: input.requestedSpendCents ?? 0,
730
- };
731
- const { res, text, url } = await this.postJSON("/verify", payload);
817
+ const payload = verifyCapabilityPayload(input);
818
+ const headers = {};
819
+ if (input.idempotencyKey?.trim()) {
820
+ headers["idempotency-key"] = input.idempotencyKey.trim();
821
+ }
822
+ const { res, text, url } = await this.postJSON("/verify", payload, headers);
732
823
  if (!res.ok) {
733
824
  throw new HarborHttpError(`Gateway verify HTTP ${res.status}: ${text}`, {
734
825
  statusCode: res.status,
@@ -737,20 +828,7 @@ export class GatewayHarborClient {
737
828
  });
738
829
  }
739
830
  const body = JSON.parse(text);
740
- if (body.tenant !== this.tenantId) {
741
- throw new Error(`verify tenant mismatch: client=${this.tenantId} gateway=${body.tenant}`);
742
- }
743
- if (body.intent_id !== input.intentId) {
744
- throw new Error(`verify intent mismatch: requested=${input.intentId} gateway=${body.intent_id}`);
745
- }
746
- return {
747
- allow: body.allow,
748
- auditId: body.audit_id,
749
- tenant: body.tenant,
750
- intentId: body.intent_id,
751
- code: body.code,
752
- message: body.message,
753
- };
831
+ return parseVerifyCapabilityBody(body, this.tenantId, input.intentId);
754
832
  }
755
833
  async verifySpendCapability(input) {
756
834
  return this.verifyCapability(input);
@@ -758,6 +836,17 @@ export class GatewayHarborClient {
758
836
  async authorizeSpend(input) {
759
837
  return this.verifyCapability(input);
760
838
  }
839
+ /** Finalizes active spend reservations after tool execution completes or is aborted. */
840
+ async completeSpendDecision(input) {
841
+ const { res, text, url } = await this.postJSON(`/v1/spend/decisions/${encodeURIComponent(input.decisionId)}/complete`, { outcome: input.outcome });
842
+ if (!res.ok) {
843
+ throw new HarborHttpError(`Gateway spend complete HTTP ${res.status}: ${text}`, {
844
+ statusCode: res.status,
845
+ url,
846
+ bodyText: text,
847
+ });
848
+ }
849
+ }
761
850
  async createIntent(body, options) {
762
851
  const { res, text, url } = await this.postJSON("/harbor/intents", body, this.mutationHeaders("createIntent", options));
763
852
  if (!res.ok) {
@@ -3,7 +3,7 @@
3
3
  import { readFileSync } from "node:fs";
4
4
  import { GatewayAuthError, GatewayFraudClient, GatewaySignalClient, SignalHttpError, DEFAULT_PAYBOND_GATEWAY_BASE_URL, } from "./index.js";
5
5
  const SERVER_NAME = "Paybond MCP";
6
- const SERVER_VERSION = "0.9.7";
6
+ const SERVER_VERSION = "0.9.8";
7
7
  const MCP_PROTOCOL_VERSION = "2025-11-25";
8
8
  const DEFAULT_PRINCIPAL_PATH = "/v1/auth/principal";
9
9
  const DEFAULT_RECOGNITION_VERIFIER_ID = "paybond-gateway";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paybond/kit",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "mcpName": "io.github.nonameuserd/paybond",
5
5
  "description": "Paybond Kit for TypeScript: agent spend governance for paid tool calls with spend authorization, evidence receipts, refunds, disputes, hosted Gateway sessions, and settlement.",
6
6
  "license": "Apache-2.0",