@paygentic/wrap 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,108 @@
1
+ import type { createApiClient } from "./api/index.js";
2
+ import { components } from "./api/schema.js";
3
+ type ApiClient = ReturnType<typeof createApiClient>;
4
+ export declare function createCustomerMethods(api: ApiClient): {
5
+ create: (data: components["schemas"]["Customer"]) => Promise<import("openapi-fetch").FetchResponse<{
6
+ parameters: {
7
+ query?: never;
8
+ header?: never;
9
+ path?: never;
10
+ cookie?: never;
11
+ };
12
+ requestBody: {
13
+ content: {
14
+ "application/json": {
15
+ consumer?: {
16
+ name: string;
17
+ email: string;
18
+ phone?: string;
19
+ address: components["schemas"]["Address"];
20
+ };
21
+ consumerId?: components["schemas"]["OrganizationId"];
22
+ merchantId: components["schemas"]["OrganizationId"];
23
+ taxId?: string;
24
+ taxRates?: components["schemas"]["TaxRates"];
25
+ };
26
+ };
27
+ };
28
+ responses: {
29
+ 200: {
30
+ headers: {
31
+ [name: string]: unknown;
32
+ };
33
+ content: {
34
+ "application/json": {
35
+ customerId: string;
36
+ validTaxAddress: components["schemas"]["ValidTaxAddress"];
37
+ };
38
+ };
39
+ };
40
+ 201: {
41
+ headers: {
42
+ [name: string]: unknown;
43
+ };
44
+ content: {
45
+ "application/json": {
46
+ customerId: string;
47
+ validTaxAddress: components["schemas"]["ValidTaxAddress"];
48
+ };
49
+ };
50
+ };
51
+ 400: components["responses"]["BadRequest"];
52
+ 403: components["responses"]["Forbidden"];
53
+ 404: components["responses"]["NotFound"];
54
+ 500: components["responses"]["InternalServerError"];
55
+ };
56
+ }, {
57
+ body: {
58
+ id: components["schemas"]["CustomerId"];
59
+ object: "customer";
60
+ consumerId: components["schemas"]["OrganizationId"];
61
+ createdAt: string;
62
+ merchantId: components["schemas"]["OrganizationId"];
63
+ organization?: {
64
+ id: string;
65
+ address?: components["schemas"]["Address"];
66
+ billingEmail?: string;
67
+ name: string;
68
+ phone?: string;
69
+ };
70
+ taxId?: string;
71
+ taxRates?: components["schemas"]["TaxRates"];
72
+ updatedAt: string;
73
+ validTaxAddress: components["schemas"]["ValidTaxAddress"];
74
+ };
75
+ }, `${string}/${string}`>>;
76
+ get: (id: string) => Promise<import("openapi-fetch").FetchResponse<{
77
+ parameters: {
78
+ query?: never;
79
+ header?: never;
80
+ path: {
81
+ id: components["schemas"]["CustomerId"];
82
+ };
83
+ cookie?: never;
84
+ };
85
+ requestBody?: never;
86
+ responses: {
87
+ 200: {
88
+ headers: {
89
+ [name: string]: unknown;
90
+ };
91
+ content: {
92
+ "application/json": components["schemas"]["Customer"];
93
+ };
94
+ };
95
+ 403: components["responses"]["Forbidden"];
96
+ 404: components["responses"]["NotFound"];
97
+ 500: components["responses"]["InternalServerError"];
98
+ };
99
+ }, {
100
+ params: {
101
+ path: {
102
+ id: string;
103
+ };
104
+ };
105
+ }, `${string}/${string}`>>;
106
+ };
107
+ export {};
108
+ //# sourceMappingURL=customer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer.d.ts","sourceRoot":"","sources":["../src/customer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAEpD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,SAAS;mBAEjC,UAAU,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC;;iBAIq/8F,CAAC;kBAA2B,CAAC;gBAAyB,CAAC;kBAA2B,CAAC;;;;;4BAA0b,CAAC;;;6BAA2e,CAAC;iCAA2U,qBAAsB;;8BAA0L,CAAC,EAAC,qBAAsB;gCAAgJ,qBAAsB;yBAA+U,CAAC;4BAAsC,CAAC,EAAC,qBAAsB;;;;;;;;;;;;yCAA+uB,qBAAsB;;;;;;;;;;;yCAAmvB,qBAAsB;;;;iBAA8F,uBAAwB;iBAAgC,uBAAwB;iBAA+B,uBAAwB;iBAA8B,uBAAwB;;;;gBAA39qE,qBAAsB;;wBAAsK,qBAAsB;;wBAA+G,qBAAsB;;;uBAAoG,CAAC,EAAC,qBAAsB;4BAAyC,CAAC;;qBAA6D,CAAC;;;uBAAuU,qBAAsB;;6BAA8G,qBAAsB;;;cAHt+7B,MAAM;;iBAGoglG,CAAC;kBAA2B,CAAC;;oBAA2H,qBAAsB;;kBAAiD,CAAC;;;;;;;;;wCAAwnC,qBAAsB;;;iBAAgE,uBAAwB;iBAA+B,uBAAwB;iBAA8B,uBAAwB;;;;;;;;;EAD1koG"}
@@ -0,0 +1,7 @@
1
+ export function createCustomerMethods(api) {
2
+ return {
3
+ create: (data) => api.POST('/v0/customers', { body: data }),
4
+ get: (id) => api.GET('/v0/customers/{id}', { params: { path: { id } } }),
5
+ };
6
+ }
7
+ //# sourceMappingURL=customer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer.js","sourceRoot":"","sources":["../src/customer.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,qBAAqB,CAAC,GAAc;IAClD,OAAO;QACL,MAAM,EAAE,CAAC,IAAuC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC9F,GAAG,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;KACjF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Error thrown when a customer has insufficient funds to process a query.
3
+ * Includes portal URL so customers can easily top up their balance.
4
+ */
5
+ export declare class InsufficientFundsError extends Error {
6
+ readonly portalUrl: string;
7
+ readonly portalExpiresAt: string;
8
+ readonly subscriptionId: string;
9
+ constructor(message: string, portalUrl: string, portalExpiresAt: string, subscriptionId: string);
10
+ }
11
+ /**
12
+ * Checks if an error is an insufficient funds error based on the error message.
13
+ */
14
+ export declare function isInsufficientFundsError(error: unknown): boolean;
15
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAgB,eAAe,EAAE,MAAM,CAAC;IACxC,SAAgB,cAAc,EAAE,MAAM,CAAC;gBAGrC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM;CAQzB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOhE"}
package/dist/errors.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Error thrown when a customer has insufficient funds to process a query.
3
+ * Includes portal URL so customers can easily top up their balance.
4
+ */
5
+ export class InsufficientFundsError extends Error {
6
+ portalUrl;
7
+ portalExpiresAt;
8
+ subscriptionId;
9
+ constructor(message, portalUrl, portalExpiresAt, subscriptionId) {
10
+ super(message);
11
+ this.name = 'InsufficientFundsError';
12
+ this.portalUrl = portalUrl;
13
+ this.portalExpiresAt = portalExpiresAt;
14
+ this.subscriptionId = subscriptionId;
15
+ }
16
+ }
17
+ /**
18
+ * Checks if an error is an insufficient funds error based on the error message.
19
+ */
20
+ export function isInsufficientFundsError(error) {
21
+ if (!(error instanceof Error)) {
22
+ return false;
23
+ }
24
+ const message = error.message.toLowerCase();
25
+ return message.includes('insufficient') ||
26
+ (message.includes('balance') && (message.includes('low') || message.includes('not enough')));
27
+ }
28
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/B,SAAS,CAAS;IAClB,eAAe,CAAS;IACxB,cAAc,CAAS;IAEvC,YACE,OAAe,EACf,SAAiB,EACjB,eAAuB,EACvB,cAAsB;QAEtB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACvC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IAC5C,OAAO,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;QAChC,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AACtG,CAAC"}
@@ -0,0 +1,56 @@
1
+ export type { RedirectUrls, CreateSubscriptionOptions, CustomerDetails } from "./subscription.js";
2
+ export { InsufficientFundsError } from "./errors.js";
3
+ interface WrapClientOptions {
4
+ apiKey?: string;
5
+ env?: 'prod' | 'sandbox';
6
+ }
7
+ export declare function createWrapClient(options?: WrapClientOptions): {
8
+ subscriptions: {
9
+ create: (options: import("./subscription.js").CreateSubscriptionOptions) => Promise<import("./subscription.js").CreateCustomerAndSubscriptionResult>;
10
+ get: (customerId: string, planIdParam?: string) => Promise<{
11
+ id?: string;
12
+ object?: "subscription";
13
+ autoCharge: boolean;
14
+ createdAt?: string;
15
+ customerId?: string;
16
+ endingAt?: string;
17
+ estimatedTaxRate?: number;
18
+ taxExempt: boolean;
19
+ name?: string;
20
+ payment?: {
21
+ amount: string;
22
+ breakdown: {
23
+ upfrontCharges: string;
24
+ walletCharge: string;
25
+ };
26
+ checkoutUrl: string;
27
+ paymentSessionId: string;
28
+ status: "pending";
29
+ };
30
+ planId?: string;
31
+ prefundAmount?: string;
32
+ minimumAccountBalance?: string;
33
+ startedAt?: string;
34
+ status?: "pending_payment" | "active" | "terminated";
35
+ terminatedAt?: string;
36
+ terminatedBy?: string;
37
+ terminationReason?: string;
38
+ testClockId?: string;
39
+ updatedAt?: string;
40
+ walletId?: string;
41
+ renewalReminderEnabled?: boolean | null;
42
+ renewalReminderDays?: number | null;
43
+ } | null>;
44
+ findCustomer: (email: string, merchantIdParam?: string) => Promise<string | null>;
45
+ portal: (subscriptionId: string) => Promise<import("./subscription.js").PortalUrlResult>;
46
+ };
47
+ plans: {
48
+ init: (options?: import("./plan.js").InitPlanOptions) => Promise<{
49
+ wrap: (customerId: string) => (params: {
50
+ prompt: string | AsyncIterable<import("@anthropic-ai/claude-agent-sdk").SDKUserMessage>;
51
+ options?: import("@anthropic-ai/claude-agent-sdk").Options;
52
+ }) => Promise<import("@anthropic-ai/claude-agent-sdk").Query>;
53
+ }>;
54
+ };
55
+ };
56
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EAAE,YAAY,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAErD,UAAU,iBAAiB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAa/D"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ import { createApiClient } from "./api/index.js";
2
+ import { createSubscriptionMethods } from "./subscription.js";
3
+ import { createPlanMethods } from "./plan.js";
4
+ export { InsufficientFundsError } from "./errors.js";
5
+ export function createWrapClient(options = {}) {
6
+ const apiKey = options.apiKey ?? process.env.PAYGENTIC_WRAP_API_KEY;
7
+ const env = options.env ?? process.env.PAYGENTIC_WRAP_ENV ?? 'prod';
8
+ if (!apiKey) {
9
+ throw new Error('API key required: pass apiKey option or set PAYGENTIC_WRAP_API_KEY');
10
+ }
11
+ const api = createApiClient(apiKey, env);
12
+ return {
13
+ subscriptions: createSubscriptionMethods(api),
14
+ plans: createPlanMethods(api),
15
+ };
16
+ }
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAOrD,MAAM,UAAU,gBAAgB,CAAC,UAA6B,EAAE;IAC9D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;IACpE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAK,OAAO,CAAC,GAAG,CAAC,kBAAyC,IAAI,MAAM,CAAC;IAE5F,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzC,OAAO;QACL,aAAa,EAAE,yBAAyB,CAAC,GAAG,CAAC;QAC7C,KAAK,EAAE,iBAAiB,CAAC,GAAG,CAAC;KAC9B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { query, Query, SDKResultMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ type QueryParams = Parameters<typeof query>[0];
3
+ type CostLogger = (result: SDKResultMessage) => void;
4
+ /**
5
+ * Wrapper around query() that captures and logs cost information from the result.
6
+ * Returns the full Query object with all methods (interrupt, setModel, etc.)
7
+ * while intercepting the result message for cost logging.
8
+ * Uses a Proxy to automatically forward any new Query methods.
9
+ */
10
+ export declare function queryWithCostLogging(params: QueryParams, onCost?: CostLogger): Query;
11
+ export {};
12
+ //# sourceMappingURL=logCosts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logCosts.d.ts","sourceRoot":"","sources":["../src/logCosts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAc,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAE5F,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/C,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAErD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,KAAK,CAkCpF"}
@@ -0,0 +1,61 @@
1
+ import { query } from "@anthropic-ai/claude-agent-sdk";
2
+ /**
3
+ * Wrapper around query() that captures and logs cost information from the result.
4
+ * Returns the full Query object with all methods (interrupt, setModel, etc.)
5
+ * while intercepting the result message for cost logging.
6
+ * Uses a Proxy to automatically forward any new Query methods.
7
+ */
8
+ export function queryWithCostLogging(params, onCost) {
9
+ const agentQuery = query(params);
10
+ // Create a wrapped async iterator that intercepts result messages
11
+ async function* wrappedIterator() {
12
+ for await (const message of agentQuery) {
13
+ if (message.type === "result") {
14
+ const result = message;
15
+ if (onCost) {
16
+ onCost(result);
17
+ }
18
+ else {
19
+ logDetailedCost(result);
20
+ }
21
+ }
22
+ yield message;
23
+ }
24
+ }
25
+ const iterator = wrappedIterator();
26
+ // Use Proxy to forward all methods while overriding iterator behavior
27
+ return new Proxy(agentQuery, {
28
+ get(target, prop) {
29
+ // Override iterator methods to use our wrapped iterator
30
+ if (prop === Symbol.asyncIterator)
31
+ return () => iterator;
32
+ if (prop === "next")
33
+ return () => iterator.next();
34
+ if (prop === "return")
35
+ return (value) => iterator.return(value);
36
+ if (prop === "throw")
37
+ return (error) => iterator.throw(error);
38
+ // Forward everything else to the original query
39
+ const value = Reflect.get(target, prop);
40
+ return typeof value === "function" ? value.bind(target) : value;
41
+ },
42
+ });
43
+ }
44
+ function logDetailedCost(result) {
45
+ const timestamp = new Date().toISOString();
46
+ const cost = `$${result.total_cost_usd.toFixed(9)}`;
47
+ const { usage } = result;
48
+ console.log(`[${timestamp}] SESSION COMPLETE | ${result.session_id}`);
49
+ console.log(` Cost: ${cost}`);
50
+ console.log(` Turns: ${result.num_turns}`);
51
+ console.log(` Tokens: ${usage.input_tokens} in / ${usage.output_tokens} out`);
52
+ console.log(` Cache: ${usage.cache_read_input_tokens} read / ${usage.cache_creation_input_tokens} created`);
53
+ // Log per-model usage if multiple models were used
54
+ if (Object.keys(result.modelUsage).length > 1) {
55
+ console.log(` Per-model breakdown:`);
56
+ for (const [model, modelUsage] of Object.entries(result.modelUsage)) {
57
+ console.log(` ${model}: $${modelUsage.costUSD.toFixed(4)}`);
58
+ }
59
+ }
60
+ }
61
+ //# sourceMappingURL=logCosts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logCosts.js","sourceRoot":"","sources":["../src/logCosts.ts"],"names":[],"mappings":"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"}
package/dist/plan.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { query, Query } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { createApiClient } from "./api/index.js";
3
+ type QueryParams = Parameters<typeof query>[0];
4
+ type ApiClient = ReturnType<typeof createApiClient>;
5
+ export interface InitPlanOptions {
6
+ priceId?: string;
7
+ planId?: string;
8
+ }
9
+ /**
10
+ * Creates plan methods for the Wrap client.
11
+ */
12
+ export declare function createPlanMethods(api: ApiClient): {
13
+ /**
14
+ * Initialize a plan for wrapping agent queries with payment tracking.
15
+ *
16
+ * Validates:
17
+ * - Plan exists and contains the specified price
18
+ * - Price model is 'dynamic' or 'percentage'
19
+ * - For dynamic: maxPrice >= $0.20
20
+ * - For percentage: percentage >= 0, minCharge <= maxCharge, maxCharge >= $0.20
21
+ */
22
+ init: (options?: InitPlanOptions) => Promise<{
23
+ wrap: (customerId: string) => (params: QueryParams) => Promise<Query>;
24
+ }>;
25
+ };
26
+ export {};
27
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAgC,MAAM,gCAAgC,CAAC;AAC5F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/C,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAMpD,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoGD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS;IAE5C;;;;;;;;OAQG;qBACmB,eAAe;2BAsId,MAAM,cAwBY,WAAW,KAAG,OAAO,CAAC,KAAK,CAAC;;EApBxE"}
package/dist/plan.js ADDED
@@ -0,0 +1,279 @@
1
+ import { query } from "@anthropic-ai/claude-agent-sdk";
2
+ import { createSubscriptionMethods } from "./subscription.js";
3
+ import { InsufficientFundsError, isInsufficientFundsError } from "./errors.js";
4
+ async function getPlan(api, planId) {
5
+ const { data, error } = await api.GET('/v0/plans/{id}', {
6
+ params: { path: { id: planId } }
7
+ });
8
+ if (error) {
9
+ throw new Error(`Failed to get plan ${planId}: ${JSON.stringify(error)}`);
10
+ }
11
+ return data;
12
+ }
13
+ async function getPrice(api, priceId) {
14
+ const { data, error } = await api.GET('/v0/prices/{id}', {
15
+ params: { path: { id: priceId } }
16
+ });
17
+ if (error) {
18
+ throw new Error(`Failed to get price ${priceId}: ${JSON.stringify(error)}`);
19
+ }
20
+ return data;
21
+ }
22
+ async function createEntitlement(api, customerId, merchantId, billableMetricId, price) {
23
+ const { data, error } = await api.POST('/v0/entitlements', {
24
+ body: {
25
+ customerId,
26
+ merchantId,
27
+ entitlementData: [{
28
+ billableMetricId,
29
+ quantity: 1
30
+ }],
31
+ maxUses: 1,
32
+ price: price.toFixed(6)
33
+ }
34
+ });
35
+ if (error) {
36
+ throw new Error(`Failed to create entitlement: ${JSON.stringify(error)}`);
37
+ }
38
+ return data;
39
+ }
40
+ async function createUsageEvent(api, customerId, merchantId, billableMetricId, cost, entitlementId) {
41
+ const idempotencyKey = `wrap_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
42
+ const { error } = await api.POST('/v0/usage', {
43
+ body: {
44
+ customerId,
45
+ merchantId,
46
+ idempotencyKey,
47
+ timestamp: new Date().toISOString(),
48
+ properties: [{
49
+ billableMetricId,
50
+ price: cost.toFixed(6),
51
+ quantity: 1
52
+ }],
53
+ ...(entitlementId && { entitlementId })
54
+ }
55
+ });
56
+ if (error) {
57
+ throw new Error(`Failed to create usage event: ${JSON.stringify(error)}`);
58
+ }
59
+ }
60
+ /**
61
+ * Creates plan methods for the Wrap client.
62
+ */
63
+ export function createPlanMethods(api) {
64
+ return {
65
+ /**
66
+ * Initialize a plan for wrapping agent queries with payment tracking.
67
+ *
68
+ * Validates:
69
+ * - Plan exists and contains the specified price
70
+ * - Price model is 'dynamic' or 'percentage'
71
+ * - For dynamic: maxPrice >= $0.20
72
+ * - For percentage: percentage >= 0, minCharge <= maxCharge, maxCharge >= $0.20
73
+ */
74
+ init: async (options = {}) => {
75
+ const planId = options.planId ?? process.env.PAYGENTIC_WRAP_PLAN_ID;
76
+ const priceId = options.priceId ?? process.env.PAYGENTIC_WRAP_PRICE_ID;
77
+ if (!planId) {
78
+ throw new Error('Plan ID required: pass planId option or set PAYGENTIC_WRAP_PLAN_ID');
79
+ }
80
+ if (!priceId) {
81
+ throw new Error('Price ID required: pass priceId option or set PAYGENTIC_WRAP_PRICE_ID');
82
+ }
83
+ // Validate plan exists
84
+ const plan = await getPlan(api, planId);
85
+ if (!plan) {
86
+ throw new Error(`Plan not found: ${planId}`);
87
+ }
88
+ // Validate plan has merchantId
89
+ if (!plan.merchantId) {
90
+ throw new Error(`Plan ${planId} missing merchantId`);
91
+ }
92
+ const merchantId = plan.merchantId;
93
+ // Validate plan contains the priceId
94
+ const priceIds = plan.prices?.map(p => p.id) ?? [];
95
+ if (!priceIds.includes(priceId)) {
96
+ throw new Error(`Price ${priceId} not found in plan ${planId}`);
97
+ }
98
+ // Get the price details
99
+ const price = await getPrice(api, priceId);
100
+ if (!price) {
101
+ throw new Error(`Price not found: ${priceId}`);
102
+ }
103
+ // Validate price model is 'dynamic' or 'percentage'
104
+ if (price.model !== 'dynamic' && price.model !== 'percentage') {
105
+ throw new Error(`Price model must be 'dynamic' or 'percentage', got: ${price.model}`);
106
+ }
107
+ // Validate price has billableMetricId
108
+ if (!price.billableMetricId) {
109
+ throw new Error(`Price ${priceId} missing billableMetricId`);
110
+ }
111
+ const billableMetricId = price.billableMetricId;
112
+ // Determine if payment is in arrears
113
+ const isArrears = price.paymentTerm === 'in_arrears';
114
+ let context;
115
+ if (price.model === 'dynamic') {
116
+ // Get maxPrice from properties (dynamic price has minPrice/maxPrice)
117
+ const properties = price.properties;
118
+ const maxPriceStr = properties?.maxPrice;
119
+ if (!maxPriceStr) {
120
+ throw new Error(`Dynamic price ${priceId} missing maxPrice in properties`);
121
+ }
122
+ const maxPrice = parseFloat(maxPriceStr);
123
+ if (maxPrice < 0.20) {
124
+ throw new Error(`Price maxPrice must be >= $0.20, got: $${maxPrice}`);
125
+ }
126
+ // Calculate safe cost: 90% of maxPrice or maxPrice - $0.10, whichever is smaller
127
+ const safeCost = Math.min(maxPrice * 0.9, maxPrice - 0.10);
128
+ context = {
129
+ model: 'dynamic',
130
+ plan,
131
+ price,
132
+ maxPrice,
133
+ safeCost,
134
+ isArrears,
135
+ merchantId,
136
+ billableMetricId,
137
+ };
138
+ }
139
+ else {
140
+ // percentage model
141
+ const properties = price.properties;
142
+ const percentageStr = properties?.percentage;
143
+ const minChargeStr = properties?.minCharge;
144
+ const maxChargeStr = properties?.maxCharge;
145
+ if (!percentageStr) {
146
+ throw new Error(`Percentage price ${priceId} missing percentage in properties`);
147
+ }
148
+ if (!minChargeStr) {
149
+ throw new Error(`Percentage price ${priceId} missing minCharge in properties`);
150
+ }
151
+ if (!maxChargeStr) {
152
+ throw new Error(`Percentage price ${priceId} missing maxCharge in properties`);
153
+ }
154
+ const percentage = parseFloat(percentageStr);
155
+ const minCharge = parseFloat(minChargeStr);
156
+ const maxCharge = parseFloat(maxChargeStr);
157
+ if (percentage < 0) {
158
+ throw new Error(`Percentage must be >= 0, got: ${percentage}`);
159
+ }
160
+ if (minCharge > maxCharge) {
161
+ throw new Error(`minCharge ($${minCharge}) must be <= maxCharge ($${maxCharge})`);
162
+ }
163
+ if (maxCharge < 0.20) {
164
+ throw new Error(`maxCharge must be >= $0.20, got: $${maxCharge}`);
165
+ }
166
+ // Calculate maxBudgetUsd: usage_cost * (1 + percentage) <= maxCharge
167
+ // So: usage_cost <= maxCharge / (1 + percentage)
168
+ const maxBudgetUsd = maxCharge / (1 + percentage);
169
+ const safeCost = Math.min(maxBudgetUsd * 0.9, maxBudgetUsd - 0.10);
170
+ context = {
171
+ model: 'percentage',
172
+ plan,
173
+ price,
174
+ percentage,
175
+ minCharge,
176
+ maxCharge,
177
+ maxBudgetUsd,
178
+ safeCost,
179
+ isArrears,
180
+ merchantId,
181
+ billableMetricId,
182
+ };
183
+ }
184
+ return {
185
+ wrap: (customerId) => createWrappedQuery(api, customerId, planId, context),
186
+ };
187
+ },
188
+ };
189
+ }
190
+ function calculateCharge(totalCost, context) {
191
+ if (context.model === 'dynamic') {
192
+ return totalCost;
193
+ }
194
+ const rawCharge = totalCost * (1 + context.percentage);
195
+ return Math.min(Math.max(rawCharge, context.minCharge), context.maxCharge);
196
+ }
197
+ function getMaxCharge(context) {
198
+ return context.model === 'dynamic' ? context.maxPrice : context.maxCharge;
199
+ }
200
+ function createWrappedQuery(api, customerId, planId, context) {
201
+ return async function wrappedQuery(params) {
202
+ const subscriptionMethods = createSubscriptionMethods(api);
203
+ // Validate customer has active subscription to this plan
204
+ const subscription = await subscriptionMethods.get(customerId, planId);
205
+ if (!subscription) {
206
+ throw new Error(`Customer ${customerId} does not have an active subscription to plan ${planId}`);
207
+ }
208
+ if (!subscription.id) {
209
+ throw new Error(`Subscription for customer ${customerId} missing id`);
210
+ }
211
+ const subscriptionId = subscription.id;
212
+ // For pre-paid (non-arrears), create entitlement first
213
+ let entitlement;
214
+ if (!context.isArrears) {
215
+ try {
216
+ entitlement = await createEntitlement(api, customerId, context.merchantId, context.billableMetricId, getMaxCharge(context));
217
+ }
218
+ catch (error) {
219
+ if (isInsufficientFundsError(error)) {
220
+ const portal = await subscriptionMethods.portal(subscriptionId);
221
+ throw new InsufficientFundsError('Insufficient funds to process query. Please top up your balance.', portal.url, portal.expiresAt, subscriptionId);
222
+ }
223
+ throw error;
224
+ }
225
+ }
226
+ // Pass safeCost in options.maxBudgetUsd
227
+ const wrappedParams = {
228
+ ...params,
229
+ options: {
230
+ ...params.options,
231
+ maxBudgetUsd: context.safeCost,
232
+ },
233
+ };
234
+ const agentQuery = query(wrappedParams);
235
+ // Create a wrapped async iterator that intercepts result messages for cost tracking
236
+ async function* wrappedIterator() {
237
+ for await (const message of agentQuery) {
238
+ if (message.type === "result") {
239
+ const result = message;
240
+ const totalCost = result.total_cost_usd;
241
+ const charge = calculateCharge(totalCost, context);
242
+ const maxCharge = getMaxCharge(context);
243
+ // Handle overage: if charge > maxCharge, split into two usage events
244
+ if (charge > maxCharge) {
245
+ const overageAmount = charge - maxCharge;
246
+ // First event: maxCharge amount
247
+ await createUsageEvent(api, customerId, context.merchantId, context.billableMetricId, maxCharge, entitlement?.id);
248
+ // Second event: overage amount (no entitlement)
249
+ await createUsageEvent(api, customerId, context.merchantId, context.billableMetricId, overageAmount);
250
+ }
251
+ else {
252
+ // Single usage event for the calculated charge
253
+ await createUsageEvent(api, customerId, context.merchantId, context.billableMetricId, charge, entitlement?.id);
254
+ }
255
+ }
256
+ yield message;
257
+ }
258
+ }
259
+ const iterator = wrappedIterator();
260
+ // Use Proxy to forward all methods while overriding iterator behavior
261
+ return new Proxy(agentQuery, {
262
+ get(target, prop) {
263
+ // Override iterator methods to use our wrapped iterator
264
+ if (prop === Symbol.asyncIterator)
265
+ return () => iterator;
266
+ if (prop === "next")
267
+ return () => iterator.next();
268
+ if (prop === "return")
269
+ return (value) => iterator.return(value);
270
+ if (prop === "throw")
271
+ return (error) => iterator.throw(error);
272
+ // Forward everything else to the original query
273
+ const value = Reflect.get(target, prop);
274
+ return typeof value === "function" ? value.bind(target) : value;
275
+ },
276
+ });
277
+ };
278
+ }
279
+ //# sourceMappingURL=plan.js.map