@drip-sdk/node 1.0.1 → 1.0.4
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 +102 -3
- package/dist/express.cjs +2 -2
- package/dist/express.cjs.map +1 -1
- package/dist/express.js +2 -2
- package/dist/express.js.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +224 -5
- package/dist/index.d.ts +224 -5
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +2 -2
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.js +2 -2
- package/dist/middleware.js.map +1 -1
- package/dist/next.cjs +2 -2
- package/dist/next.cjs.map +1 -1
- package/dist/next.js +2 -2
- package/dist/next.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -215,6 +215,105 @@ if (status.status === 'CONFIRMED') {
|
|
|
215
215
|
}
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
+
### Streaming Meter
|
|
219
|
+
|
|
220
|
+
For LLM token streaming and other high-frequency metering scenarios, use the streaming meter to accumulate usage locally and charge once at the end:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { Drip } from '@drip-sdk/node';
|
|
224
|
+
|
|
225
|
+
const drip = new Drip({ apiKey: process.env.DRIP_API_KEY! });
|
|
226
|
+
|
|
227
|
+
// Create a stream meter for a customer
|
|
228
|
+
const meter = drip.createStreamMeter({
|
|
229
|
+
customerId: 'cust_abc123',
|
|
230
|
+
meter: 'tokens',
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Stream from your LLM provider
|
|
234
|
+
const stream = await openai.chat.completions.create({
|
|
235
|
+
model: 'gpt-4',
|
|
236
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
237
|
+
stream: true,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Accumulate tokens as they stream
|
|
241
|
+
for await (const chunk of stream) {
|
|
242
|
+
const tokens = chunk.usage?.completion_tokens ?? 1;
|
|
243
|
+
meter.add(tokens); // Accumulates locally, no API call
|
|
244
|
+
yield chunk;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Single charge at end of stream
|
|
248
|
+
const result = await meter.flush();
|
|
249
|
+
console.log(`Charged ${result.charge.amountUsdc} USDC for ${result.quantity} tokens`);
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### Stream Meter Options
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const meter = drip.createStreamMeter({
|
|
256
|
+
customerId: 'cust_abc123',
|
|
257
|
+
meter: 'tokens',
|
|
258
|
+
|
|
259
|
+
// Optional: Custom idempotency key
|
|
260
|
+
idempotencyKey: 'stream_req_123',
|
|
261
|
+
|
|
262
|
+
// Optional: Metadata attached to the charge
|
|
263
|
+
metadata: { model: 'gpt-4', endpoint: '/v1/chat' },
|
|
264
|
+
|
|
265
|
+
// Optional: Auto-flush when threshold reached
|
|
266
|
+
flushThreshold: 10000, // Flush every 10k tokens
|
|
267
|
+
|
|
268
|
+
// Optional: Callback on each add
|
|
269
|
+
onAdd: (quantity, total) => console.log(`Added ${quantity}, total: ${total}`),
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
#### Handling Partial Failures
|
|
274
|
+
|
|
275
|
+
If the stream fails mid-way, you can still charge for what was delivered:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
const meter = drip.createStreamMeter({
|
|
279
|
+
customerId: 'cust_abc123',
|
|
280
|
+
meter: 'tokens',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
for await (const chunk of stream) {
|
|
285
|
+
meter.add(chunk.tokens);
|
|
286
|
+
yield chunk;
|
|
287
|
+
}
|
|
288
|
+
await meter.flush();
|
|
289
|
+
} catch (error) {
|
|
290
|
+
// Charge for tokens delivered before failure
|
|
291
|
+
if (meter.total > 0) {
|
|
292
|
+
await meter.flush();
|
|
293
|
+
}
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### Multiple Meters in One Request
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
const tokenMeter = drip.createStreamMeter({ customerId, meter: 'tokens' });
|
|
302
|
+
const toolMeter = drip.createStreamMeter({ customerId, meter: 'tool_calls' });
|
|
303
|
+
|
|
304
|
+
for await (const chunk of agentStream) {
|
|
305
|
+
if (chunk.type === 'token') {
|
|
306
|
+
tokenMeter.add(1);
|
|
307
|
+
} else if (chunk.type === 'tool_call') {
|
|
308
|
+
toolMeter.add(1);
|
|
309
|
+
}
|
|
310
|
+
yield chunk;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Flush all meters
|
|
314
|
+
await Promise.all([tokenMeter.flush(), toolMeter.flush()]);
|
|
315
|
+
```
|
|
316
|
+
|
|
218
317
|
### Run Tracking (Simplified API)
|
|
219
318
|
|
|
220
319
|
Track agent executions with a single API call instead of multiple separate calls.
|
|
@@ -369,6 +468,8 @@ import {
|
|
|
369
468
|
ChargeStatus,
|
|
370
469
|
Webhook,
|
|
371
470
|
WebhookEventType,
|
|
471
|
+
StreamMeter,
|
|
472
|
+
StreamMeterOptions,
|
|
372
473
|
} from '@drip-sdk/node';
|
|
373
474
|
|
|
374
475
|
// All types are available for use
|
|
@@ -583,9 +684,7 @@ export const POST = withDrip({ meter: 'api_calls', quantity: 1 }, handler);
|
|
|
583
684
|
| Dev Mode Skip | ✅ | Skip in development |
|
|
584
685
|
| Metadata | ✅ | Attach to charges |
|
|
585
686
|
| TypeScript | ✅ | Full type definitions |
|
|
586
|
-
|
|
|
587
|
-
| Rate Limiting | ❌ | Planned |
|
|
588
|
-
| Balance Caching | ❌ | Planned |
|
|
687
|
+
| Streaming Meter | ✅ | For LLM token streams |
|
|
589
688
|
|
|
590
689
|
## Contributing
|
|
591
690
|
|
package/dist/express.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
'use strict';var crypto=require('crypto');var f=class t extends Error{constructor(r,s,n){super(r);this.statusCode=s;this.code=n;this.name="DripError",Object.setPrototypeOf(this,t.prototype);}},x=class{apiKey;baseUrl;timeout;constructor(e){if(!e.apiKey)throw new Error("Drip API key is required");this.apiKey=e.apiKey,this.baseUrl=e.baseUrl||"https://api.drip.dev/v1",this.timeout=e.timeout||3e4;}async request(e,r={}){let s=new AbortController,n=setTimeout(()=>s.abort(),this.timeout);try{let i=await fetch(`${this.baseUrl}${e}`,{...r,signal:s.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...r.headers}});if(i.status===204)return {success:!0};let o=await i.json();if(!i.ok)throw new f(o.message||o.error||"Request failed",i.status,o.code);return o}catch(i){throw i instanceof f?i:i instanceof Error&&i.name==="AbortError"?new f("Request timed out",408,"TIMEOUT"):new f(i instanceof Error?i.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(n);}}async createCustomer(e){return this.request("/customers",{method:"POST",body:JSON.stringify(e)})}async getCustomer(e){return this.request(`/customers/${e}`)}async listCustomers(e){let r=new URLSearchParams;e?.limit&&r.set("limit",e.limit.toString()),e?.status&&r.set("status",e.status);let s=r.toString(),n=s?`/customers?${s}`:"/customers";return this.request(n)}async getBalance(e){return this.request(`/customers/${e}/balance`)}async charge(e){return this.request("/usage",{method:"POST",body:JSON.stringify({customerId:e.customerId,usageType:e.meter,quantity:e.quantity,idempotencyKey:e.idempotencyKey,metadata:e.metadata})})}async getCharge(e){return this.request(`/charges/${e}`)}async listCharges(e){let r=new URLSearchParams;e?.customerId&&r.set("customerId",e.customerId),e?.status&&r.set("status",e.status),e?.limit&&r.set("limit",e.limit.toString()),e?.offset&&r.set("offset",e.offset.toString());let s=r.toString(),n=s?`/charges?${s}`:"/charges";return this.request(n)}async getChargeStatus(e){return this.request(`/charges/${e}/status`)}async checkout(e){let r=await this.request("/checkout",{method:"POST",body:JSON.stringify({customer_id:e.customerId,external_customer_id:e.externalCustomerId,amount:e.amount,return_url:e.returnUrl,cancel_url:e.cancelUrl,metadata:e.metadata})});return {id:r.id,url:r.url,expiresAt:r.expires_at,amountUsd:r.amount_usd}}async createWebhook(e){return this.request("/webhooks",{method:"POST",body:JSON.stringify(e)})}async listWebhooks(){return this.request("/webhooks")}async getWebhook(e){return this.request(`/webhooks/${e}`)}async deleteWebhook(e){return this.request(`/webhooks/${e}`,{method:"DELETE"})}async testWebhook(e){return this.request(`/webhooks/${e}/test`,{method:"POST"})}async rotateWebhookSecret(e){return this.request(`/webhooks/${e}/rotate-secret`,{method:"POST"})}async createWorkflow(e){return this.request("/workflows",{method:"POST",body:JSON.stringify(e)})}async listWorkflows(){return this.request("/workflows")}async startRun(e){return this.request("/runs",{method:"POST",body:JSON.stringify(e)})}async endRun(e,r){return this.request(`/runs/${e}`,{method:"PATCH",body:JSON.stringify(r)})}async getRunTimeline(e){return this.request(`/runs/${e}`)}async emitEvent(e){return this.request("/events",{method:"POST",body:JSON.stringify(e)})}async emitEventsBatch(e){return this.request("/events/batch",{method:"POST",body:JSON.stringify({events:e})})}async listMeters(){let e=await this.request("/pricing-plans");return {data:e.data.map(r=>({id:r.id,name:r.name,meter:r.unitType,unitPriceUsd:r.unitPriceUsd,isActive:r.isActive})),count:e.count}}async recordRun(e){let r=Date.now(),s=e.workflow,n=e.workflow;if(!e.workflow.startsWith("wf_"))try{let y=(await this.listWorkflows()).data.find(d=>d.slug===e.workflow||d.id===e.workflow);if(y)s=y.id,n=y.name;else {let d=await this.createWorkflow({name:e.workflow.replace(/[_-]/g," ").replace(/\b\w/g,E=>E.toUpperCase()),slug:e.workflow,productSurface:"AGENT"});s=d.id,n=d.name;}}catch{s=e.workflow;}let i=await this.startRun({customerId:e.customerId,workflowId:s,externalRunId:e.externalRunId,correlationId:e.correlationId,metadata:e.metadata}),o=0,c=0;if(e.events.length>0){let l=e.events.map((d,E)=>({runId:i.id,eventType:d.eventType,quantity:d.quantity,units:d.units,description:d.description,costUnits:d.costUnits,metadata:d.metadata,idempotencyKey:e.externalRunId?`${e.externalRunId}:${d.eventType}:${E}`:void 0})),y=await this.emitEventsBatch(l);o=y.created,c=y.duplicates;}let m=await this.endRun(i.id,{status:e.status,errorMessage:e.errorMessage,errorCode:e.errorCode}),g=Date.now()-r,u=e.events.length>0?`${o} events recorded`:"no events",h=`${e.status==="COMPLETED"?"\u2713":e.status==="FAILED"?"\u2717":"\u25CB"} ${n}: ${u} (${m.durationMs??g}ms)`;return {run:{id:i.id,workflowId:s,workflowName:n,status:e.status,durationMs:m.durationMs},events:{created:o,duplicates:c},totalCostUnits:m.totalCostUnits,summary:h}}static generateIdempotencyKey(e){let r=[e.customerId,e.runId??"no_run",e.stepName,String(e.sequence??0)],s=0,n=r.join("|");for(let i=0;i<n.length;i++){let o=n.charCodeAt(i);s=(s<<5)-s+o,s=s&s;}return `drip_${Math.abs(s).toString(36)}_${e.stepName.slice(0,16)}`}static verifyWebhookSignature(e,r,s){let n=new TextEncoder,i=n.encode(e),o=n.encode(s),c=0;for(let g=0;g<i.length;g++)c=(c<<5)-c+i[g]+o[g%o.length]|0;let m=Math.abs(c).toString(16);return r===m||r.includes(m)}};var p=class t extends Error{constructor(r,s,n,i){super(r);this.code=s;this.statusCode=n;this.details=i;this.name="DripMiddlewareError",Object.setPrototypeOf(this,t.prototype);}};var v=300,A=300,S=["x-payment-signature","x-payment-session-key","x-payment-smart-account","x-payment-timestamp","x-payment-amount","x-payment-recipient","x-payment-usage-id","x-payment-nonce"];function N(t){return t.toLowerCase()}function R(t,e){let r=N(e);if(t[r]!==void 0){let s=t[r];return Array.isArray(s)?s[0]:s}for(let[s,n]of Object.entries(t))if(s.toLowerCase()===r)return Array.isArray(n)?n[0]:n}function P(t){return S.every(e=>R(t,e)!==void 0)}function O(t){let e=R(t,"x-payment-signature"),r=R(t,"x-payment-session-key"),s=R(t,"x-payment-smart-account"),n=R(t,"x-payment-timestamp"),i=R(t,"x-payment-amount"),o=R(t,"x-payment-recipient"),c=R(t,"x-payment-usage-id"),m=R(t,"x-payment-nonce");if(!e||!r||!s||!n||!i||!o||!c||!m)return null;let g=parseInt(n,10);if(isNaN(g)||Math.floor(Date.now()/1e3)-g>A)return null;let a=(h,l)=>{if(!h.startsWith("0x"))return false;let y=h.slice(2);return y.length<l?false:/^[a-fA-F0-9]+$/.test(y)};return !a(e,130)||!a(r,64)||!a(s,40)?null:{signature:e,sessionKeyId:r,smartAccount:s,timestamp:g,amount:i,recipient:o,usageId:c,nonce:m}}function U(t){let e=Math.floor(Date.now()/1e3),r=e+(t.expiresInSec??v),s=`${e}-${crypto.randomBytes(16).toString("hex")}`,n=t.usageId;n.startsWith("0x")||(n=b(n));let i={"X-Payment-Required":"true","X-Payment-Amount":t.amount,"X-Payment-Recipient":t.recipient,"X-Payment-Usage-Id":n,"X-Payment-Description":t.description??"API usage charge","X-Payment-Expires":String(r),"X-Payment-Nonce":s,"X-Payment-Timestamp":String(e)},o={amount:t.amount,recipient:t.recipient,usageId:n,description:t.description??"API usage charge",expiresAt:r,nonce:s,timestamp:e};return {headers:i,paymentRequest:o}}function b(t){let e=5381,r=52711;for(let i=0;i<t.length;i++){let o=t.charCodeAt(i);e=(e<<5)+e^o,r=(r<<5)+r^o;}return `0x${Math.abs(e*31+r).toString(16).padStart(16,"0").slice(0,16).padEnd(64,"0")}`}async function M(t,e){let r=e.customerResolver??"header";if(typeof r=="function")return r(t);if(r==="header"){let s=R(t.headers,"x-drip-customer-id")??R(t.headers,"x-customer-id");if(!s)throw new p("Missing customer ID. Include X-Drip-Customer-Id header.","CUSTOMER_RESOLUTION_FAILED",400);return s}if(r==="query"){let s=t.query??{},n=s.drip_customer_id??s.customer_id,i=Array.isArray(n)?n[0]:n;if(!i)throw new p("Missing customer ID. Include drip_customer_id query parameter.","CUSTOMER_RESOLUTION_FAILED",400);return i}throw new p(`Invalid customer resolver: ${r}`,"CONFIGURATION_ERROR",500)}async function _(t,e){return typeof e.quantity=="function"?e.quantity(t):e.quantity}async function W(t,e,r){if(r.idempotencyKey)return r.idempotencyKey(t);let s=Date.now(),n=[t.method,t.url,e,s];return `drip_${b(n.join("|")).slice(2,18)}`}function C(t){let e=t.apiKey??process.env.DRIP_API_KEY;if(!e)throw new p("Missing Drip API key. Set DRIP_API_KEY environment variable or pass apiKey in config.","CONFIGURATION_ERROR",500);return new x({apiKey:e,baseUrl:t.baseUrl??process.env.DRIP_API_URL})}async function I(t,e){if(e.skipInDevelopment&&process.env.NODE_ENV==="development"){console.warn("[Drip] Skipping billing in development mode. Set skipInDevelopment: false or NODE_ENV to production to enable billing.");let r=C(e),s={success:true,usageEventId:"dev_usage_event",isReplay:false,charge:{id:"dev_charge",amountUsdc:"0.00",amountToken:"0",txHash:"0x0",status:"CONFIRMED"}};return {success:true,state:{customerId:"dev_customer",quantity:typeof e.quantity=="number"?e.quantity:1,idempotencyKey:"dev_idempotency",hasPaymentProof:false},charge:s,drip:r,isReplay:false}}try{let r=C(e),s=await M(t,e),n=await _(t,e),i=await W(t,s,e),o=P(t.headers),c=o?O(t.headers):void 0,m={customerId:s,quantity:n,idempotencyKey:i,hasPaymentProof:o,paymentProof:c??void 0},g=typeof e.metadata=="function"?e.metadata(t):e.metadata;try{let u=await r.charge({customerId:s,meter:e.meter,quantity:n,idempotencyKey:i,metadata:g});return e.onCharge&&await e.onCharge(u,t),{success:!0,state:m,charge:u,drip:r,isReplay:u.isReplay??!1}}catch(u){if(u instanceof f){if(u.statusCode===402){let a=process.env.DRIP_RECIPIENT_ADDRESS;if(!a)throw new p("DRIP_RECIPIENT_ADDRESS environment variable must be configured for x402 payment flow.","CONFIGURATION_ERROR",500);let h="0.01",l=u.message.match(/amount[:\s]+([0-9.]+)/i);l?h=l[1]:h=(n*1e-4).toFixed(6);let{headers:y,paymentRequest:d}=U({amount:h,recipient:a,usageId:i,description:`${e.meter} usage charge`});return {success:!1,error:new p("Insufficient balance. Payment required.","PAYMENT_REQUIRED",402),paymentRequired:{headers:y,paymentRequest:d}}}throw e.onError&&await e.onError(u,t),new p(u.message,"CHARGE_FAILED",u.statusCode,{code:u.code})}throw u}}catch(r){if(r instanceof p)return {success:false,error:r};let s=r instanceof Error?r.message:"Unknown error";return {success:false,error:new p(s,"INTERNAL_ERROR",500)}}}function q(t){let e={};for(let[r,s]of Object.entries(t))e[r.toLowerCase()]=Array.isArray(s)?s[0]:s;return e}function L(t,e,r){t.status(402).set(e).json({error:"Payment required",code:"PAYMENT_REQUIRED",paymentRequest:r,instructions:{step1:"Sign the payment request with your session key using EIP-712",step2:"Retry the request with X-Payment-* headers",documentation:"https://docs.drip.dev/x402"}});}function X(t,e,r,s,n){t.status(s).json({error:e,code:r,...n&&{details:n}});}function D(t){let e=t.attachToRequest??true;return async(r,s,n)=>{let i={method:r.method,url:r.originalUrl||r.url,headers:q(r.headers),query:r.query},o=typeof t.quantity=="function"?await t.quantity(r):t.quantity,c;if(typeof t.customerResolver=="function"){let l=t.customerResolver;c=async()=>l(r);}else c=t.customerResolver;let m;if(typeof t.idempotencyKey=="function"){let l=t.idempotencyKey;m=async()=>l(r);}let g=typeof t.metadata=="function"?t.metadata(r):t.metadata,u={meter:t.meter,quantity:o,apiKey:t.apiKey,baseUrl:t.baseUrl,customerResolver:c,idempotencyKey:m,metadata:g,skipInDevelopment:t.skipInDevelopment,onCharge:void 0,onError:void 0},a=await I(i,u);if(!a.success){if(t.errorHandler&&await t.errorHandler(a.error,r,s))return;if(a.paymentRequired){L(s,a.paymentRequired.headers,a.paymentRequired.paymentRequest);return}X(s,a.error.message,a.error.code,a.error.statusCode,a.error.details);return}t.onCharge&&await t.onCharge(a.charge,r);let h={drip:a.drip,customerId:a.state.customerId,charge:a.charge,isReplay:a.isReplay};e&&(r.drip=h),n();}}function K(t){return e=>D({...t,...e})}function $(t){return P(q(t.headers))}function k(t){return "drip"in t&&typeof t.drip=="object"}function F(t){if(!k(t))throw new Error("Drip context not found on request. Ensure dripMiddleware is applied before this route.");return t.drip}
|
|
2
|
-
exports.Drip=
|
|
1
|
+
'use strict';var crypto$1=require('crypto');var T=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});var x=class{_total=0;_flushed=false;_flushCount=0;_chargeFn;_options;constructor(e,t){this._chargeFn=e,this._options=t;}get total(){return this._total}get isFlushed(){return this._flushed}get flushCount(){return this._flushCount}async add(e){return e<=0?null:(this._total+=e,this._options.onAdd?.(e,this._total),this._options.flushThreshold!==void 0&&this._total>=this._options.flushThreshold?this.flush():null)}addSync(e){e<=0||(this._total+=e,this._options.onAdd?.(e,this._total));}async flush(){let e=this._total;if(this._total=0,e===0)return {success:true,quantity:0,charge:null,isReplay:false};let t=this._options.idempotencyKey?`${this._options.idempotencyKey}_flush_${this._flushCount}`:void 0,s=await this._chargeFn({customerId:this._options.customerId,meter:this._options.meter,quantity:e,idempotencyKey:t,metadata:this._options.metadata});this._flushed=true,this._flushCount++;let n={success:s.success,quantity:e,charge:s.charge,isReplay:s.isReplay};return this._options.onFlush?.(n),n}reset(){this._total=0;}};var R=class r extends Error{constructor(t,s,n){super(t);this.statusCode=s;this.code=n;this.name="DripError",Object.setPrototypeOf(this,r.prototype);}},E=class{apiKey;baseUrl;timeout;constructor(e){if(!e.apiKey)throw new Error("Drip API key is required");this.apiKey=e.apiKey,this.baseUrl=e.baseUrl||"https://api.drip.dev/v1",this.timeout=e.timeout||3e4;}async request(e,t={}){let s=new AbortController,n=setTimeout(()=>s.abort(),this.timeout);try{let i=await fetch(`${this.baseUrl}${e}`,{...t,signal:s.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...t.headers}});if(i.status===204)return {success:!0};let a=await i.json();if(!i.ok)throw new R(a.message||a.error||"Request failed",i.status,a.code);return a}catch(i){throw i instanceof R?i:i instanceof Error&&i.name==="AbortError"?new R("Request timed out",408,"TIMEOUT"):new R(i instanceof Error?i.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(n);}}async createCustomer(e){return this.request("/customers",{method:"POST",body:JSON.stringify(e)})}async getCustomer(e){return this.request(`/customers/${e}`)}async listCustomers(e){let t=new URLSearchParams;e?.limit&&t.set("limit",e.limit.toString()),e?.status&&t.set("status",e.status);let s=t.toString(),n=s?`/customers?${s}`:"/customers";return this.request(n)}async getBalance(e){return this.request(`/customers/${e}/balance`)}async charge(e){return this.request("/usage",{method:"POST",body:JSON.stringify({customerId:e.customerId,usageType:e.meter,quantity:e.quantity,idempotencyKey:e.idempotencyKey,metadata:e.metadata})})}async getCharge(e){return this.request(`/charges/${e}`)}async listCharges(e){let t=new URLSearchParams;e?.customerId&&t.set("customerId",e.customerId),e?.status&&t.set("status",e.status),e?.limit&&t.set("limit",e.limit.toString()),e?.offset&&t.set("offset",e.offset.toString());let s=t.toString(),n=s?`/charges?${s}`:"/charges";return this.request(n)}async getChargeStatus(e){return this.request(`/charges/${e}/status`)}async checkout(e){let t=await this.request("/checkout",{method:"POST",body:JSON.stringify({customer_id:e.customerId,external_customer_id:e.externalCustomerId,amount:e.amount,return_url:e.returnUrl,cancel_url:e.cancelUrl,metadata:e.metadata})});return {id:t.id,url:t.url,expiresAt:t.expires_at,amountUsd:t.amount_usd}}async createWebhook(e){return this.request("/webhooks",{method:"POST",body:JSON.stringify(e)})}async listWebhooks(){return this.request("/webhooks")}async getWebhook(e){return this.request(`/webhooks/${e}`)}async deleteWebhook(e){return this.request(`/webhooks/${e}`,{method:"DELETE"})}async testWebhook(e){return this.request(`/webhooks/${e}/test`,{method:"POST"})}async rotateWebhookSecret(e){return this.request(`/webhooks/${e}/rotate-secret`,{method:"POST"})}async createWorkflow(e){return this.request("/workflows",{method:"POST",body:JSON.stringify(e)})}async listWorkflows(){return this.request("/workflows")}async startRun(e){return this.request("/runs",{method:"POST",body:JSON.stringify(e)})}async endRun(e,t){return this.request(`/runs/${e}`,{method:"PATCH",body:JSON.stringify(t)})}async getRunTimeline(e){return this.request(`/runs/${e}`)}async emitEvent(e){return this.request("/events",{method:"POST",body:JSON.stringify(e)})}async emitEventsBatch(e){return this.request("/events/batch",{method:"POST",body:JSON.stringify({events:e})})}async listMeters(){let e=await this.request("/pricing-plans");return {data:e.data.map(t=>({id:t.id,name:t.name,meter:t.unitType,unitPriceUsd:t.unitPriceUsd,isActive:t.isActive})),count:e.count}}async recordRun(e){let t=Date.now(),s=e.workflow,n=e.workflow;if(!e.workflow.startsWith("wf_"))try{let h=(await this.listWorkflows()).data.find(d=>d.slug===e.workflow||d.id===e.workflow);if(h)s=h.id,n=h.name;else {let d=await this.createWorkflow({name:e.workflow.replace(/[_-]/g," ").replace(/\b\w/g,b=>b.toUpperCase()),slug:e.workflow,productSurface:"AGENT"});s=d.id,n=d.name;}}catch{s=e.workflow;}let i=await this.startRun({customerId:e.customerId,workflowId:s,externalRunId:e.externalRunId,correlationId:e.correlationId,metadata:e.metadata}),a=0,c=0;if(e.events.length>0){let g=e.events.map((d,b)=>({runId:i.id,eventType:d.eventType,quantity:d.quantity,units:d.units,description:d.description,costUnits:d.costUnits,metadata:d.metadata,idempotencyKey:e.externalRunId?`${e.externalRunId}:${d.eventType}:${b}`:void 0})),h=await this.emitEventsBatch(g);a=h.created,c=h.duplicates;}let m=await this.endRun(i.id,{status:e.status,errorMessage:e.errorMessage,errorCode:e.errorCode}),p=Date.now()-t,u=e.events.length>0?`${a} events recorded`:"no events",f=`${e.status==="COMPLETED"?"\u2713":e.status==="FAILED"?"\u2717":"\u25CB"} ${n}: ${u} (${m.durationMs??p}ms)`;return {run:{id:i.id,workflowId:s,workflowName:n,status:e.status,durationMs:m.durationMs},events:{created:a,duplicates:c},totalCostUnits:m.totalCostUnits,summary:f}}static generateIdempotencyKey(e){let t=[e.customerId,e.runId??"no_run",e.stepName,String(e.sequence??0)],s=0,n=t.join("|");for(let i=0;i<n.length;i++){let a=n.charCodeAt(i);s=(s<<5)-s+a,s=s&s;}return `drip_${Math.abs(s).toString(36)}_${e.stepName.slice(0,16)}`}static async verifyWebhookSignature(e,t,s){if(!e||!t||!s)return false;try{let n=new TextEncoder,i=n.encode(s),a=n.encode(e),c=await crypto.subtle.importKey("raw",i,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),m=await crypto.subtle.sign("HMAC",c,a),p=Array.from(new Uint8Array(m)).map(o=>o.toString(16).padStart(2,"0")).join("");if(t.length!==p.length)return !1;let u=0;for(let o=0;o<t.length;o++)u|=t.charCodeAt(o)^p.charCodeAt(o);return u===0}catch{return false}}static verifyWebhookSignatureSync(e,t,s){if(!e||!t||!s)return false;try{let n=T("crypto"),i=n.createHmac("sha256",s).update(e).digest("hex"),a=Buffer.from(t),c=Buffer.from(i);return a.length!==c.length?!1:n.timingSafeEqual(a,c)}catch{return false}}createStreamMeter(e){return new x(this.charge.bind(this),e)}};var l=class r extends Error{constructor(t,s,n,i){super(t);this.code=s;this.statusCode=n;this.details=i;this.name="DripMiddlewareError",Object.setPrototypeOf(this,r.prototype);}};var A=300,_=300,M=["x-payment-signature","x-payment-session-key","x-payment-smart-account","x-payment-timestamp","x-payment-amount","x-payment-recipient","x-payment-usage-id","x-payment-nonce"];function O(r){return r.toLowerCase()}function y(r,e){let t=O(e);if(r[t]!==void 0){let s=r[t];return Array.isArray(s)?s[0]:s}for(let[s,n]of Object.entries(r))if(s.toLowerCase()===t)return Array.isArray(n)?n[0]:n}function w(r){return M.every(e=>y(r,e)!==void 0)}function N(r){let e=y(r,"x-payment-signature"),t=y(r,"x-payment-session-key"),s=y(r,"x-payment-smart-account"),n=y(r,"x-payment-timestamp"),i=y(r,"x-payment-amount"),a=y(r,"x-payment-recipient"),c=y(r,"x-payment-usage-id"),m=y(r,"x-payment-nonce");if(!e||!t||!s||!n||!i||!a||!c||!m)return null;let p=parseInt(n,10);if(isNaN(p)||Math.floor(Date.now()/1e3)-p>_)return null;let o=(f,g)=>{if(!f.startsWith("0x"))return false;let h=f.slice(2);return h.length<g?false:/^[a-fA-F0-9]+$/.test(h)};return !o(e,130)||!o(t,64)||!o(s,40)?null:{signature:e,sessionKeyId:t,smartAccount:s,timestamp:p,amount:i,recipient:a,usageId:c,nonce:m}}function U(r){let e=Math.floor(Date.now()/1e3),t=e+(r.expiresInSec??A),s=`${e}-${crypto$1.randomBytes(16).toString("hex")}`,n=r.usageId;n.startsWith("0x")||(n=I(n));let i={"X-Payment-Required":"true","X-Payment-Amount":r.amount,"X-Payment-Recipient":r.recipient,"X-Payment-Usage-Id":n,"X-Payment-Description":r.description??"API usage charge","X-Payment-Expires":String(t),"X-Payment-Nonce":s,"X-Payment-Timestamp":String(e)},a={amount:r.amount,recipient:r.recipient,usageId:n,description:r.description??"API usage charge",expiresAt:t,nonce:s,timestamp:e};return {headers:i,paymentRequest:a}}function I(r){let e=5381,t=52711;for(let i=0;i<r.length;i++){let a=r.charCodeAt(i);e=(e<<5)+e^a,t=(t<<5)+t^a;}return `0x${Math.abs(e*31+t).toString(16).padStart(16,"0").slice(0,16).padEnd(64,"0")}`}async function W(r,e){let t=e.customerResolver??"header";if(typeof t=="function")return t(r);if(t==="header"){let s=y(r.headers,"x-drip-customer-id")??y(r.headers,"x-customer-id");if(!s)throw new l("Missing customer ID. Include X-Drip-Customer-Id header.","CUSTOMER_RESOLUTION_FAILED",400);return s}if(t==="query"){let s=r.query??{},n=s.drip_customer_id??s.customer_id,i=Array.isArray(n)?n[0]:n;if(!i)throw new l("Missing customer ID. Include drip_customer_id query parameter.","CUSTOMER_RESOLUTION_FAILED",400);return i}throw new l(`Invalid customer resolver: ${t}`,"CONFIGURATION_ERROR",500)}async function L(r,e){return typeof e.quantity=="function"?e.quantity(r):e.quantity}async function F(r,e,t){if(t.idempotencyKey)return t.idempotencyKey(r);let s=Date.now(),n=[r.method,r.url,e,s];return `drip_${I(n.join("|")).slice(2,18)}`}function P(r){let e=r.apiKey??process.env.DRIP_API_KEY;if(!e)throw new l("Missing Drip API key. Set DRIP_API_KEY environment variable or pass apiKey in config.","CONFIGURATION_ERROR",500);return new E({apiKey:e,baseUrl:r.baseUrl??process.env.DRIP_API_URL})}async function q(r,e){if(e.skipInDevelopment&&process.env.NODE_ENV==="development"){console.warn("[Drip] Skipping billing in development mode. Set skipInDevelopment: false or NODE_ENV to production to enable billing.");let t=P(e),s={success:true,usageEventId:"dev_usage_event",isReplay:false,charge:{id:"dev_charge",amountUsdc:"0.00",amountToken:"0",txHash:"0x0",status:"CONFIRMED"}};return {success:true,state:{customerId:"dev_customer",quantity:typeof e.quantity=="number"?e.quantity:1,idempotencyKey:"dev_idempotency",hasPaymentProof:false},charge:s,drip:t,isReplay:false}}try{let t=P(e),s=await W(r,e),n=await L(r,e),i=await F(r,s,e),a=w(r.headers),c=a?N(r.headers):void 0,m={customerId:s,quantity:n,idempotencyKey:i,hasPaymentProof:a,paymentProof:c??void 0},p=typeof e.metadata=="function"?e.metadata(r):e.metadata;try{let u=await t.charge({customerId:s,meter:e.meter,quantity:n,idempotencyKey:i,metadata:p});return e.onCharge&&await e.onCharge(u,r),{success:!0,state:m,charge:u,drip:t,isReplay:u.isReplay??!1}}catch(u){if(u instanceof R){if(u.statusCode===402){let o=process.env.DRIP_RECIPIENT_ADDRESS;if(!o)throw new l("DRIP_RECIPIENT_ADDRESS environment variable must be configured for x402 payment flow.","CONFIGURATION_ERROR",500);let f="0.01",g=u.message.match(/amount[:\s]+([0-9.]+)/i);g?f=g[1]:f=(n*1e-4).toFixed(6);let{headers:h,paymentRequest:d}=U({amount:f,recipient:o,usageId:i,description:`${e.meter} usage charge`});return {success:!1,error:new l("Insufficient balance. Payment required.","PAYMENT_REQUIRED",402),paymentRequired:{headers:h,paymentRequest:d}}}throw e.onError&&await e.onError(u,r),new l(u.message,"CHARGE_FAILED",u.statusCode,{code:u.code})}throw u}}catch(t){if(t instanceof l)return {success:false,error:t};let s=t instanceof Error?t.message:"Unknown error";return {success:false,error:new l(s,"INTERNAL_ERROR",500)}}}function D(r){let e={};for(let[t,s]of Object.entries(r))e[t.toLowerCase()]=Array.isArray(s)?s[0]:s;return e}function K(r,e,t){r.status(402).set(e).json({error:"Payment required",code:"PAYMENT_REQUIRED",paymentRequest:t,instructions:{step1:"Sign the payment request with your session key using EIP-712",step2:"Retry the request with X-Payment-* headers",documentation:"https://docs.drip.dev/x402"}});}function X(r,e,t,s,n){r.status(s).json({error:e,code:t,...n&&{details:n}});}function k(r){let e=r.attachToRequest??true;return async(t,s,n)=>{let i={method:t.method,url:t.originalUrl||t.url,headers:D(t.headers),query:t.query},a=typeof r.quantity=="function"?await r.quantity(t):r.quantity,c;if(typeof r.customerResolver=="function"){let g=r.customerResolver;c=async()=>g(t);}else c=r.customerResolver;let m;if(typeof r.idempotencyKey=="function"){let g=r.idempotencyKey;m=async()=>g(t);}let p=typeof r.metadata=="function"?r.metadata(t):r.metadata,u={meter:r.meter,quantity:a,apiKey:r.apiKey,baseUrl:r.baseUrl,customerResolver:c,idempotencyKey:m,metadata:p,skipInDevelopment:r.skipInDevelopment,onCharge:void 0,onError:void 0},o=await q(i,u);if(!o.success){if(r.errorHandler&&await r.errorHandler(o.error,t,s))return;if(o.paymentRequired){K(s,o.paymentRequired.headers,o.paymentRequired.paymentRequest);return}X(s,o.error.message,o.error.code,o.error.statusCode,o.error.details);return}r.onCharge&&await r.onCharge(o.charge,t);let f={drip:o.drip,customerId:o.state.customerId,charge:o.charge,isReplay:o.isReplay};e&&(t.drip=f),n();}}function $(r){return e=>k({...r,...e})}function H(r){return w(D(r.headers))}function v(r){return "drip"in r&&typeof r.drip=="object"}function j(r){if(!v(r))throw new Error("Drip context not found on request. Ensure dripMiddleware is applied before this route.");return r.drip}
|
|
2
|
+
exports.Drip=E;exports.DripError=R;exports.DripMiddlewareError=l;exports.createDripMiddleware=$;exports.dripMiddleware=k;exports.getDripContext=j;exports.hasDripContext=v;exports.hasPaymentProofHeaders=H;//# sourceMappingURL=express.cjs.map
|
|
3
3
|
//# sourceMappingURL=express.cjs.map
|