@nevermined-io/payments 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -93
- package/dist/x402/langchain/agent.d.ts +96 -0
- package/dist/x402/langchain/agent.d.ts.map +1 -0
- package/dist/x402/langchain/agent.js +121 -0
- package/dist/x402/langchain/agent.js.map +1 -0
- package/dist/x402/langchain/decorator.d.ts +43 -4
- package/dist/x402/langchain/decorator.d.ts.map +1 -1
- package/dist/x402/langchain/decorator.js +173 -6
- package/dist/x402/langchain/decorator.js.map +1 -1
- package/dist/x402/langchain/index.d.ts +2 -1
- package/dist/x402/langchain/index.d.ts.map +1 -1
- package/dist/x402/langchain/index.js +2 -1
- package/dist/x402/langchain/index.js.map +1 -1
- package/dist/x402/langsmith/index.d.ts +15 -0
- package/dist/x402/langsmith/index.d.ts.map +1 -0
- package/dist/x402/langsmith/index.js +15 -0
- package/dist/x402/langsmith/index.js.map +1 -0
- package/dist/x402/langsmith/spans.d.ts +163 -0
- package/dist/x402/langsmith/spans.d.ts.map +1 -0
- package/dist/x402/langsmith/spans.js +341 -0
- package/dist/x402/langsmith/spans.js.map +1 -0
- package/package.json +16 -2
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* The `credits` option accepts two forms:
|
|
16
16
|
* - **Static number**: `credits: 1` — always charges 1 credit
|
|
17
|
-
* - **Function**: `credits: (ctx) => Math.max(1, ctx.result.length / 100)` — dynamic
|
|
17
|
+
* - **Function**: `credits: (ctx) => Math.max(1, Math.floor(ctx.result.length / 100))` — dynamic
|
|
18
18
|
*
|
|
19
19
|
* When `credits` is a function, it receives `{ args, result }` after tool execution.
|
|
20
20
|
*
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
45
|
import type { Payments } from '../../payments.js';
|
|
46
|
-
import { type X402PaymentRequired } from '../facilitator-api.js';
|
|
46
|
+
import { type X402PaymentRequired, type SettlePermissionsResult } from '../facilitator-api.js';
|
|
47
47
|
/**
|
|
48
48
|
* Context passed to a dynamic credits function after tool execution.
|
|
49
49
|
*/
|
|
@@ -66,7 +66,18 @@ export interface RequiresPaymentOptions {
|
|
|
66
66
|
payments: Payments;
|
|
67
67
|
/** Single plan ID to accept */
|
|
68
68
|
planId: string;
|
|
69
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Number of credits to charge, or a function for dynamic pricing (default: 1).
|
|
71
|
+
*
|
|
72
|
+
* This value is sent as `maxAmount` to the facilitator. The amount actually
|
|
73
|
+
* redeemed depends on the plan's server-side credit configuration:
|
|
74
|
+
*
|
|
75
|
+
* - **Fixed plans** (`plan.credits.minAmount === plan.credits.maxAmount`)
|
|
76
|
+
* always burn `plan.credits.maxAmount` — this value is then effectively a
|
|
77
|
+
* no-op (see nevermined-io/nvm-monorepo#1568).
|
|
78
|
+
* - **Range plans** clamp this value into
|
|
79
|
+
* `[plan.credits.minAmount, plan.credits.maxAmount]`.
|
|
80
|
+
*/
|
|
70
81
|
credits?: number | CreditsCallable;
|
|
71
82
|
/** Optional agent identifier */
|
|
72
83
|
agentId?: string;
|
|
@@ -88,7 +99,17 @@ export declare class PaymentRequiredError extends Error {
|
|
|
88
99
|
* Payment context stored in `config.configurable.payment_context` after verification.
|
|
89
100
|
*/
|
|
90
101
|
export interface PaymentContext {
|
|
91
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* Abbreviated, non-functional reference to the x402 access token — the SAME
|
|
104
|
+
* redacted form surfaced as `nvm.payment_token` (`<first 16>…<last 4>`, or a
|
|
105
|
+
* `…(short)` marker for a too-short token). The **full token is deliberately
|
|
106
|
+
* not persisted here**: this object is written into
|
|
107
|
+
* `config.configurable.payment_context`, and tracing frameworks (e.g.
|
|
108
|
+
* LangChain) can capture `config.configurable` into span metadata, so storing
|
|
109
|
+
* the raw credential would let it ride into any traced run opened during or
|
|
110
|
+
* after the tool body. Settlement uses the token read from
|
|
111
|
+
* `config.configurable.payment_token`, never this field.
|
|
112
|
+
*/
|
|
92
113
|
token: string;
|
|
93
114
|
/** The payment required object */
|
|
94
115
|
paymentRequired: X402PaymentRequired;
|
|
@@ -101,6 +122,24 @@ export interface PaymentContext {
|
|
|
101
122
|
/** Agent request context for observability */
|
|
102
123
|
agentRequest?: unknown;
|
|
103
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Return the most recent settlement receipt produced by {@link requiresPayment}.
|
|
127
|
+
*
|
|
128
|
+
* Use this after invoking a LangChain/LangGraph runnable whose tool is wrapped
|
|
129
|
+
* with {@link requiresPayment} to recover the settlement receipt
|
|
130
|
+
* (`creditsRedeemed`, `remainingBalance`, `transaction`, `network`, `payer`)
|
|
131
|
+
* without threading it back through the runnable config (which LangGraph copies
|
|
132
|
+
* per node, so the in-place write is invisible to the outer scope).
|
|
133
|
+
*
|
|
134
|
+
* Returns `undefined` if no settlement has happened yet in this process, or if
|
|
135
|
+
* the most recent invocation raised before reaching the settle phase.
|
|
136
|
+
*
|
|
137
|
+
* @remarks
|
|
138
|
+
* This accessor reads from a module-level slot. In multi-tenant processes (e.g.
|
|
139
|
+
* a server handling concurrent settlements), the value reflects whichever
|
|
140
|
+
* invocation settled most recently — there is no per-call isolation.
|
|
141
|
+
*/
|
|
142
|
+
export declare function lastSettlement(): SettlePermissionsResult | undefined;
|
|
104
143
|
/**
|
|
105
144
|
* Wraps a LangChain.js tool implementation with x402 payment verification and settlement.
|
|
106
145
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorator.d.ts","sourceRoot":"","sources":["../../../src/x402/langchain/decorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAEL,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"decorator.d.ts","sourceRoot":"","sources":["../../../src/x402/langchain/decorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAEL,KAAK,mBAAmB,EAExB,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAA;AAY9B;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,MAAM,CAAA;AAE7D;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,wDAAwD;IACxD,QAAQ,EAAE,QAAQ,CAAA;IAClB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe,CAAA;IAClC,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,yEAAyE;IACzE,eAAe,EAAE,mBAAmB,GAAG,SAAS,CAAA;gBAEpC,OAAO,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,mBAAmB;CAKnE;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;OAUG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,kCAAkC;IAClC,eAAe,EAAE,mBAAmB,CAAA;IACpC,kCAAkC;IAClC,eAAe,EAAE,MAAM,CAAA;IACvB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,CAAA;IACjB,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAeD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,IAAI,uBAAuB,GAAG,SAAS,CAEpE;AAwCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,eAAe,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAC5E,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EACjE,OAAO,EAAE,sBAAsB,GAC9B,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAoNrD"}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* The `credits` option accepts two forms:
|
|
16
16
|
* - **Static number**: `credits: 1` — always charges 1 credit
|
|
17
|
-
* - **Function**: `credits: (ctx) => Math.max(1, ctx.result.length / 100)` — dynamic
|
|
17
|
+
* - **Function**: `credits: (ctx) => Math.max(1, Math.floor(ctx.result.length / 100))` — dynamic
|
|
18
18
|
*
|
|
19
19
|
* When `credits` is a function, it receives `{ args, result }` after tool execution.
|
|
20
20
|
*
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
45
|
import { buildPaymentRequired, } from '../facilitator-api.js';
|
|
46
|
+
import { abbreviateToken, activeRunTree, addMetadata, buildSettleMetadata, buildVerifyMetadata, redactMetadataKeys, settlementSpan, verifySpan, } from '../langsmith/spans.js';
|
|
46
47
|
/**
|
|
47
48
|
* Thrown when payment verification fails or no token is provided.
|
|
48
49
|
*
|
|
@@ -56,6 +57,38 @@ export class PaymentRequiredError extends Error {
|
|
|
56
57
|
this.paymentRequired = paymentRequired;
|
|
57
58
|
}
|
|
58
59
|
}
|
|
60
|
+
// Module-level holder for the most recent settlement receipt. LangGraph copies
|
|
61
|
+
// `RunnableConfig.configurable` per node, so the in-place write to
|
|
62
|
+
// `config.configurable.payment_settlement` is not visible to the buyer's outer
|
|
63
|
+
// scope. A module-level slot is the simplest reliable signal. It is
|
|
64
|
+
// intentionally single-tenant — if the same process runs multiple concurrent
|
|
65
|
+
// settlements, the last writer wins. This race is not limited to multi-tenant
|
|
66
|
+
// servers: a single `createPaidReactAgent` run can also hit it, because a ReAct
|
|
67
|
+
// agent may execute several paid tools in parallel within one LLM turn via
|
|
68
|
+
// `ToolNode`, and this single slot only retains the last writer. For
|
|
69
|
+
// multi-tenant or parallel-tool use cases, surface the receipt via a callback
|
|
70
|
+
// or via observability (see Sprint 1 of the LangChain epic).
|
|
71
|
+
let lastSettlementReceipt;
|
|
72
|
+
/**
|
|
73
|
+
* Return the most recent settlement receipt produced by {@link requiresPayment}.
|
|
74
|
+
*
|
|
75
|
+
* Use this after invoking a LangChain/LangGraph runnable whose tool is wrapped
|
|
76
|
+
* with {@link requiresPayment} to recover the settlement receipt
|
|
77
|
+
* (`creditsRedeemed`, `remainingBalance`, `transaction`, `network`, `payer`)
|
|
78
|
+
* without threading it back through the runnable config (which LangGraph copies
|
|
79
|
+
* per node, so the in-place write is invisible to the outer scope).
|
|
80
|
+
*
|
|
81
|
+
* Returns `undefined` if no settlement has happened yet in this process, or if
|
|
82
|
+
* the most recent invocation raised before reaching the settle phase.
|
|
83
|
+
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* This accessor reads from a module-level slot. In multi-tenant processes (e.g.
|
|
86
|
+
* a server handling concurrent settlements), the value reflects whichever
|
|
87
|
+
* invocation settled most recently — there is no per-call isolation.
|
|
88
|
+
*/
|
|
89
|
+
export function lastSettlement() {
|
|
90
|
+
return lastSettlementReceipt;
|
|
91
|
+
}
|
|
59
92
|
/**
|
|
60
93
|
* Extract the payment token from a LangChain RunnableConfig.
|
|
61
94
|
*
|
|
@@ -82,6 +115,17 @@ function storeInConfigurable(config, key, value) {
|
|
|
82
115
|
return;
|
|
83
116
|
configurable[key] = value;
|
|
84
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Remove a key from config.configurable if present (no-op otherwise).
|
|
120
|
+
*/
|
|
121
|
+
function removeFromConfigurable(config, key) {
|
|
122
|
+
if (config == null || typeof config !== 'object')
|
|
123
|
+
return;
|
|
124
|
+
const configurable = config.configurable;
|
|
125
|
+
if (configurable == null || typeof configurable !== 'object')
|
|
126
|
+
return;
|
|
127
|
+
delete configurable[key];
|
|
128
|
+
}
|
|
85
129
|
/**
|
|
86
130
|
* Wraps a LangChain.js tool implementation with x402 payment verification and settlement.
|
|
87
131
|
*
|
|
@@ -126,17 +170,82 @@ function storeInConfigurable(config, key, value) {
|
|
|
126
170
|
export function requiresPayment(fn, options) {
|
|
127
171
|
const { payments, planId, credits = 1, agentId, network } = options;
|
|
128
172
|
return async (args, config) => {
|
|
173
|
+
// Reset the module-level slot at the START of every invocation, before
|
|
174
|
+
// verify. Any failure that does not reach the settle-success write (a
|
|
175
|
+
// verify failure / PaymentRequiredError, or a swallowed settle failure)
|
|
176
|
+
// then leaves lastSettlement() returning `undefined` rather than a stale
|
|
177
|
+
// receipt from a previous invocation — matching the JSDoc contract on
|
|
178
|
+
// lastSettlement().
|
|
179
|
+
lastSettlementReceipt = undefined;
|
|
129
180
|
// Build payment required object
|
|
130
181
|
const paymentRequired = buildPaymentRequired(planId, {
|
|
131
182
|
endpoint: fn.name || 'tool',
|
|
132
183
|
agentId,
|
|
133
184
|
network,
|
|
134
185
|
});
|
|
186
|
+
// The scheme/network the verify span advertises come from the resolved
|
|
187
|
+
// X402 scheme so the span metadata matches what the buyer paid against.
|
|
188
|
+
const accepted = paymentRequired.accepts[0];
|
|
189
|
+
const planIds = paymentRequired.accepts
|
|
190
|
+
.map((a) => a.planId)
|
|
191
|
+
.filter((p) => Boolean(p));
|
|
192
|
+
const resolvedScheme = accepted?.scheme;
|
|
193
|
+
const resolvedNetwork = network ?? accepted?.network;
|
|
194
|
+
// LangChain auto-captures every key in config.configurable into the parent
|
|
195
|
+
// tool span's metadata, and child spans inherit it at construction time.
|
|
196
|
+
// Strip the full x402 access token from the parent BEFORE opening the verify
|
|
197
|
+
// span so neither the parent nor the child carries the raw credential — only
|
|
198
|
+
// the abbreviated nvm.payment_token remains for correlation.
|
|
199
|
+
const parentRunTree = await activeRunTree();
|
|
200
|
+
redactMetadataKeys(parentRunTree, 'payment_token');
|
|
201
|
+
const verifyStarted = Date.now();
|
|
202
|
+
// Open the verify span BEFORE the token-presence check so failed probes
|
|
203
|
+
// (no payment_token in config) still produce a clearly-named span with the
|
|
204
|
+
// static nvm.* attrs attached to both the span and the parent tool span.
|
|
205
|
+
const vspan = await verifySpan({
|
|
206
|
+
planIds,
|
|
207
|
+
scheme: resolvedScheme,
|
|
208
|
+
network: resolvedNetwork,
|
|
209
|
+
agentId,
|
|
210
|
+
});
|
|
211
|
+
// Pre-verify metadata is best-effort and static-only.
|
|
212
|
+
const preVerifyMd = buildVerifyMetadata({
|
|
213
|
+
planIds,
|
|
214
|
+
scheme: resolvedScheme,
|
|
215
|
+
network: resolvedNetwork,
|
|
216
|
+
agentId,
|
|
217
|
+
});
|
|
218
|
+
vspan.addMetadata(preVerifyMd);
|
|
219
|
+
addMetadata(parentRunTree, preVerifyMd);
|
|
135
220
|
// Extract token from config.configurable.payment_token
|
|
136
221
|
const token = extractPaymentToken(config);
|
|
137
222
|
if (!token) {
|
|
138
|
-
|
|
223
|
+
await vspan.end(new Error('missing payment_token in config.configurable'));
|
|
224
|
+
throw new PaymentRequiredError('Payment required: missing payment_token in config.configurable', paymentRequired);
|
|
139
225
|
}
|
|
226
|
+
// Drop the raw token from configurable now that we hold it in `token`.
|
|
227
|
+
// LangChain's `ensureConfig` re-promotes every configurable scalar into a
|
|
228
|
+
// runnable's `metadata` on EACH invocation, so any traced runnable the tool
|
|
229
|
+
// body spawns (an LLM call, a sub-chain) sharing this config would otherwise
|
|
230
|
+
// re-leak the full credential into its own span metadata — and a child run
|
|
231
|
+
// opened *during* `fn()` would capture it before the settle-side
|
|
232
|
+
// `redactMetadataKeys` below could run. Removing the key here is the
|
|
233
|
+
// proactive complement to that reactive redaction. This MUST run after
|
|
234
|
+
// extraction (deleting earlier would make `extractPaymentToken` return null)
|
|
235
|
+
// and before `fn()` — guaranteed, since `fn()` is called further down.
|
|
236
|
+
// Settlement uses the local `token`, never configurable, so removal is
|
|
237
|
+
// non-functional. On the `tool()`/LangGraph path LangChain hands the wrapper
|
|
238
|
+
// a per-invocation config copy, so this does not mutate a config the caller
|
|
239
|
+
// reuses across sibling tools.
|
|
240
|
+
removeFromConfigurable(config, 'payment_token');
|
|
241
|
+
// Abbreviate/redact the token ONCE here (mirrors Python's
|
|
242
|
+
// attach_metadata_safely pre-abbreviation) and pass the result into the
|
|
243
|
+
// metadata builders. abbreviateToken is idempotent, so the builders leave
|
|
244
|
+
// it unchanged — this means the short-token warning fires at most once per
|
|
245
|
+
// call (not once per verify AND once per settle), and the raw token never
|
|
246
|
+
// reaches the builders' frame locals (defense against exception enrichers
|
|
247
|
+
// that capture locals).
|
|
248
|
+
const abbreviatedToken = abbreviateToken(token);
|
|
140
249
|
// Resolve pre-execution credits (static only; callable deferred to post-execution)
|
|
141
250
|
const creditsToVerify = typeof credits === 'number' ? credits : 1;
|
|
142
251
|
// Verify permissions
|
|
@@ -149,14 +258,38 @@ export function requiresPayment(fn, options) {
|
|
|
149
258
|
});
|
|
150
259
|
}
|
|
151
260
|
catch (error) {
|
|
261
|
+
await vspan.end(error);
|
|
152
262
|
throw new PaymentRequiredError(`Payment verification failed: ${error instanceof Error ? error.message : String(error)}`, paymentRequired);
|
|
153
263
|
}
|
|
264
|
+
// Augment span metadata with verification results + timing (both span and
|
|
265
|
+
// parent), then close the verify span. Best-effort: a metadata failure must
|
|
266
|
+
// not mask the PaymentRequiredError that may follow.
|
|
267
|
+
const verifyMd = buildVerifyMetadata({
|
|
268
|
+
planIds,
|
|
269
|
+
scheme: resolvedScheme,
|
|
270
|
+
network: resolvedNetwork,
|
|
271
|
+
agentId,
|
|
272
|
+
verification,
|
|
273
|
+
durationMs: Date.now() - verifyStarted,
|
|
274
|
+
token: abbreviatedToken,
|
|
275
|
+
});
|
|
276
|
+
vspan.addMetadata(verifyMd);
|
|
277
|
+
addMetadata(parentRunTree, verifyMd);
|
|
154
278
|
if (!verification.isValid) {
|
|
279
|
+
await vspan.end(new Error(verification.invalidReason || 'verification failed'));
|
|
155
280
|
throw new PaymentRequiredError(`Payment verification failed: ${verification.invalidReason || 'Insufficient credits or invalid token'}`, paymentRequired);
|
|
156
281
|
}
|
|
157
|
-
|
|
282
|
+
await vspan.end();
|
|
283
|
+
// Store payment context. The `token` field carries the ABBREVIATED
|
|
284
|
+
// reference, never the raw credential: `payment_context` is written into
|
|
285
|
+
// `config.configurable`, which LangChain can capture into span metadata, so
|
|
286
|
+
// persisting the full token here would reopen the very leak the parent-tree
|
|
287
|
+
// `redactMetadataKeys('payment_token')` calls close — and a child run opened
|
|
288
|
+
// *during* the tool body would capture it before any post-hoc redaction
|
|
289
|
+
// could run. `abbreviatedToken` is always defined past the `!token` guard
|
|
290
|
+
// above; `?? ''` is a belt-and-suspenders that can never fall back to raw.
|
|
158
291
|
const paymentContext = {
|
|
159
|
-
token,
|
|
292
|
+
token: abbreviatedToken ?? '',
|
|
160
293
|
paymentRequired,
|
|
161
294
|
creditsToSettle: creditsToVerify,
|
|
162
295
|
verified: true,
|
|
@@ -170,18 +303,52 @@ export function requiresPayment(fn, options) {
|
|
|
170
303
|
const finalCredits = typeof credits === 'function'
|
|
171
304
|
? credits({ args: args, result })
|
|
172
305
|
: credits;
|
|
173
|
-
// Settle credits
|
|
306
|
+
// Settle credits, wrapped in an nvm:settlement span (mirrors Python's
|
|
307
|
+
// settlement_span around settle_permissions).
|
|
308
|
+
//
|
|
309
|
+
// Re-fetch the active run tree: `fn()` may have opened nested traced runs, so
|
|
310
|
+
// `activeRunTree()` can now return a different RunTree than the verify-side
|
|
311
|
+
// redaction at the top of this function scrubbed. We already removed
|
|
312
|
+
// `payment_token` from `config.configurable` before `fn()` ran, so newly
|
|
313
|
+
// opened child runs can no longer re-promote the raw credential — this
|
|
314
|
+
// re-redaction is now defense-in-depth, covering any RunTree whose metadata
|
|
315
|
+
// was populated before that removal took effect. Redact BEFORE opening the
|
|
316
|
+
// settlement span so the credential never rides into the settle child span or
|
|
317
|
+
// the (possibly new) parent tree.
|
|
318
|
+
const settleParentRunTree = await activeRunTree();
|
|
319
|
+
redactMetadataKeys(settleParentRunTree, 'payment_token');
|
|
320
|
+
const settleStarted = Date.now();
|
|
321
|
+
const sspan = await settlementSpan({ planIds, agentId });
|
|
174
322
|
try {
|
|
175
323
|
const settlement = await payments.facilitator.settlePermissions({
|
|
176
324
|
paymentRequired,
|
|
177
325
|
x402AccessToken: token,
|
|
178
|
-
|
|
326
|
+
// A dynamic `credits` callable can return a float (e.g. length/100).
|
|
327
|
+
// `BigInt(1.5)` throws RangeError, which the surrounding try/catch
|
|
328
|
+
// swallows — credits are never burned and the caller is never told.
|
|
329
|
+
// Floor to keep the settle on the money path.
|
|
330
|
+
maxAmount: BigInt(Math.floor(finalCredits)),
|
|
179
331
|
agentRequestId: paymentContext.agentRequestId,
|
|
180
332
|
});
|
|
181
333
|
storeInConfigurable(config, 'payment_settlement', settlement);
|
|
334
|
+
// Also publish to the module-level slot so lastSettlement() can recover
|
|
335
|
+
// the receipt — LangGraph copies config.configurable per node, hiding the
|
|
336
|
+
// line above from the buyer's outer scope.
|
|
337
|
+
lastSettlementReceipt = settlement;
|
|
338
|
+
const settleMd = buildSettleMetadata({
|
|
339
|
+
settlement,
|
|
340
|
+
planIds,
|
|
341
|
+
agentId,
|
|
342
|
+
durationMs: Date.now() - settleStarted,
|
|
343
|
+
token: abbreviatedToken,
|
|
344
|
+
});
|
|
345
|
+
sspan.addMetadata(settleMd);
|
|
346
|
+
addMetadata(settleParentRunTree, settleMd);
|
|
347
|
+
await sspan.end();
|
|
182
348
|
}
|
|
183
349
|
catch (settleError) {
|
|
184
350
|
console.error('Payment settlement failed:', settleError);
|
|
351
|
+
await sspan.end(settleError);
|
|
185
352
|
// Still return result even if settlement fails
|
|
186
353
|
}
|
|
187
354
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorator.js","sourceRoot":"","sources":["../../../src/x402/langchain/decorator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LangChain tool wrapper for Nevermined payment protection using the x402 protocol.\n *\n * Wraps a LangChain.js tool implementation function to:\n *\n * 1. Extract the x402 payment token from `config.configurable.payment_token`\n * 2. Verify the subscriber has sufficient credits\n * 3. Execute the wrapped tool function\n * 4. Settle (burn) credits after successful execution\n *\n * Payment errors throw `PaymentRequiredError` so LangChain agents can catch and\n * surface them to the user. The error carries the full `X402PaymentRequired`\n * object for programmatic token acquisition.\n *\n * The `credits` option accepts two forms:\n * - **Static number**: `credits: 1` — always charges 1 credit\n * - **Function**: `credits: (ctx) => Math.max(1, ctx.result.length / 100)` — dynamic\n *\n * When `credits` is a function, it receives `{ args, result }` after tool execution.\n *\n * @example\n * ```typescript\n * import { tool } from '@langchain/core/tools'\n * import { z } from 'zod'\n * import { Payments } from '@nevermined-io/payments'\n * import { requiresPayment } from '@nevermined-io/payments/langchain'\n *\n * const payments = Payments.getInstance({ nvmApiKey: '...', environment: 'testing' })\n *\n * const searchData = tool(\n * requiresPayment(\n * (args) => `Results for ${args.query}`,\n * { payments, planId: PLAN_ID, credits: 1 }\n * ),\n * { name: 'search_data', description: 'Search for data', schema: z.object({ query: z.string() }) }\n * )\n *\n * // Invoke with payment token\n * const result = await searchData.invoke(\n * { query: 'AI trends' },\n * { configurable: { payment_token: accessToken } }\n * )\n * ```\n */\n\nimport type { Payments } from '../../payments.js'\nimport {\n buildPaymentRequired,\n type X402PaymentRequired,\n type VerifyPermissionsResult,\n} from '../facilitator-api.js'\n\n/**\n * Context passed to a dynamic credits function after tool execution.\n */\nexport interface CreditsContext {\n /** The tool's input arguments */\n args: Record<string, unknown>\n /** The tool's return value */\n result: unknown\n}\n\n/**\n * Credits can be a static number or a function that receives\n * `{ args, result }` and returns the number of credits to charge.\n */\nexport type CreditsCallable = (ctx: CreditsContext) => number\n\n/**\n * Options for the `requiresPayment` wrapper.\n */\nexport interface RequiresPaymentOptions {\n /** The Payments instance (with payments.facilitator) */\n payments: Payments\n /** Single plan ID to accept */\n planId: string\n /** Number of credits to charge, or a function for dynamic pricing (default: 1) */\n credits?: number | CreditsCallable\n /** Optional agent identifier */\n agentId?: string\n /** Blockchain network in CAIP-2 format (default: 'eip155:84532' for Base Sepolia) */\n network?: string\n}\n\n/**\n * Thrown when payment verification fails or no token is provided.\n *\n * Carries the `X402PaymentRequired` object so callers can inspect\n * accepted plans and acquire the correct payment token.\n */\nexport class PaymentRequiredError extends Error {\n /** The x402 PaymentRequired object for programmatic token acquisition */\n paymentRequired: X402PaymentRequired | undefined\n\n constructor(message: string, paymentRequired?: X402PaymentRequired) {\n super(message)\n this.name = 'PaymentRequiredError'\n this.paymentRequired = paymentRequired\n }\n}\n\n/**\n * Payment context stored in `config.configurable.payment_context` after verification.\n */\nexport interface PaymentContext {\n /** The x402 access token */\n token: string\n /** The payment required object */\n paymentRequired: X402PaymentRequired\n /** Number of credits to settle */\n creditsToSettle: number\n /** Whether verification was successful */\n verified: boolean\n /** Agent request ID for observability tracking */\n agentRequestId?: string\n /** Agent request context for observability */\n agentRequest?: unknown\n}\n\n/**\n * Extract the payment token from a LangChain RunnableConfig.\n *\n * In LangChain.js, the config is the optional second parameter to tool functions:\n * `(args, config?) => ...` where config is a `RunnableConfig`.\n */\nfunction extractPaymentToken(config: unknown): string | null {\n if (config == null || typeof config !== 'object') return null\n\n const configurable = (config as Record<string, unknown>).configurable\n if (configurable == null || typeof configurable !== 'object') return null\n\n const token = (configurable as Record<string, unknown>).payment_token\n return typeof token === 'string' ? token : null\n}\n\n/**\n * Store a value in config.configurable if available.\n */\nfunction storeInConfigurable(config: unknown, key: string, value: unknown): void {\n if (config == null || typeof config !== 'object') return\n\n const configurable = (config as Record<string, unknown>).configurable\n if (configurable == null || typeof configurable !== 'object') return\n\n ;(configurable as Record<string, unknown>)[key] = value\n}\n\n/**\n * Wraps a LangChain.js tool implementation with x402 payment verification and settlement.\n *\n * This is a higher-order function that takes the tool's implementation function and\n * payment options, returning a new function with the same signature that:\n *\n * 1. Extracts the payment token from `config.configurable.payment_token`\n * 2. Verifies the subscriber has sufficient credits\n * 3. Calls the original tool function\n * 4. Settles (burns) credits\n * 5. Stores `payment_context` and `payment_settlement` in `config.configurable`\n *\n * @param fn - The tool implementation function: `(args, config?) => result`\n * @param options - Payment configuration\n * @returns Wrapped function with the same signature\n *\n * @example Static credits\n * ```typescript\n * const searchData = tool(\n * requiresPayment(\n * (args) => `Results for ${args.query}`,\n * { payments, planId: PLAN_ID, credits: 1 }\n * ),\n * { name: 'search_data', description: 'Search', schema: z.object({ query: z.string() }) }\n * )\n * ```\n *\n * @example Dynamic credits\n * ```typescript\n * const summarize = tool(\n * requiresPayment(\n * (args) => `Summary of ${args.text}`,\n * {\n * payments, planId: PLAN_ID,\n * credits: (ctx) => Math.max(1, Math.floor(String(ctx.result).length / 100)),\n * }\n * ),\n * { name: 'summarize', description: 'Summarize text', schema: z.object({ text: z.string() }) }\n * )\n * ```\n */\nexport function requiresPayment<TArgs extends Record<string, unknown>, TResult>(\n fn: (args: TArgs, config?: unknown) => TResult | Promise<TResult>,\n options: RequiresPaymentOptions,\n): (args: TArgs, config?: unknown) => Promise<TResult> {\n const { payments, planId, credits = 1, agentId, network } = options\n\n return async (args: TArgs, config?: unknown): Promise<TResult> => {\n // Build payment required object\n const paymentRequired = buildPaymentRequired(planId, {\n endpoint: fn.name || 'tool',\n agentId,\n network,\n })\n\n // Extract token from config.configurable.payment_token\n const token = extractPaymentToken(config)\n if (!token) {\n throw new PaymentRequiredError(\n \"Payment required: missing payment_token in config.configurable\",\n paymentRequired,\n )\n }\n\n // Resolve pre-execution credits (static only; callable deferred to post-execution)\n const creditsToVerify = typeof credits === 'number' ? credits : 1\n\n // Verify permissions\n let verification: VerifyPermissionsResult\n try {\n verification = await payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToVerify),\n })\n } catch (error) {\n throw new PaymentRequiredError(\n `Payment verification failed: ${error instanceof Error ? error.message : String(error)}`,\n paymentRequired,\n )\n }\n\n if (!verification.isValid) {\n throw new PaymentRequiredError(\n `Payment verification failed: ${verification.invalidReason || 'Insufficient credits or invalid token'}`,\n paymentRequired,\n )\n }\n\n // Store payment context\n const paymentContext: PaymentContext = {\n token,\n paymentRequired,\n creditsToSettle: creditsToVerify,\n verified: true,\n agentRequestId: verification.agentRequest?.agentRequestId || verification.agentRequestId,\n agentRequest: verification.agentRequest,\n }\n storeInConfigurable(config, 'payment_context', paymentContext)\n\n // Execute the tool function\n const result = await fn(args, config)\n\n // Resolve final credits (may be dynamic based on result)\n const finalCredits =\n typeof credits === 'function'\n ? credits({ args: args as Record<string, unknown>, result })\n : credits\n\n // Settle credits\n try {\n const settlement = await payments.facilitator.settlePermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(finalCredits),\n agentRequestId: paymentContext.agentRequestId,\n })\n storeInConfigurable(config, 'payment_settlement', settlement)\n } catch (settleError) {\n console.error('Payment settlement failed:', settleError)\n // Still return result even if settlement fails\n }\n\n return result\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"decorator.js","sourceRoot":"","sources":["../../../src/x402/langchain/decorator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LangChain tool wrapper for Nevermined payment protection using the x402 protocol.\n *\n * Wraps a LangChain.js tool implementation function to:\n *\n * 1. Extract the x402 payment token from `config.configurable.payment_token`\n * 2. Verify the subscriber has sufficient credits\n * 3. Execute the wrapped tool function\n * 4. Settle (burn) credits after successful execution\n *\n * Payment errors throw `PaymentRequiredError` so LangChain agents can catch and\n * surface them to the user. The error carries the full `X402PaymentRequired`\n * object for programmatic token acquisition.\n *\n * The `credits` option accepts two forms:\n * - **Static number**: `credits: 1` — always charges 1 credit\n * - **Function**: `credits: (ctx) => Math.max(1, Math.floor(ctx.result.length / 100))` — dynamic\n *\n * When `credits` is a function, it receives `{ args, result }` after tool execution.\n *\n * @example\n * ```typescript\n * import { tool } from '@langchain/core/tools'\n * import { z } from 'zod'\n * import { Payments } from '@nevermined-io/payments'\n * import { requiresPayment } from '@nevermined-io/payments/langchain'\n *\n * const payments = Payments.getInstance({ nvmApiKey: '...', environment: 'testing' })\n *\n * const searchData = tool(\n * requiresPayment(\n * (args) => `Results for ${args.query}`,\n * { payments, planId: PLAN_ID, credits: 1 }\n * ),\n * { name: 'search_data', description: 'Search for data', schema: z.object({ query: z.string() }) }\n * )\n *\n * // Invoke with payment token\n * const result = await searchData.invoke(\n * { query: 'AI trends' },\n * { configurable: { payment_token: accessToken } }\n * )\n * ```\n */\n\nimport type { Payments } from '../../payments.js'\nimport {\n buildPaymentRequired,\n type X402PaymentRequired,\n type VerifyPermissionsResult,\n type SettlePermissionsResult,\n} from '../facilitator-api.js'\nimport {\n abbreviateToken,\n activeRunTree,\n addMetadata,\n buildSettleMetadata,\n buildVerifyMetadata,\n redactMetadataKeys,\n settlementSpan,\n verifySpan,\n} from '../langsmith/spans.js'\n\n/**\n * Context passed to a dynamic credits function after tool execution.\n */\nexport interface CreditsContext {\n /** The tool's input arguments */\n args: Record<string, unknown>\n /** The tool's return value */\n result: unknown\n}\n\n/**\n * Credits can be a static number or a function that receives\n * `{ args, result }` and returns the number of credits to charge.\n */\nexport type CreditsCallable = (ctx: CreditsContext) => number\n\n/**\n * Options for the `requiresPayment` wrapper.\n */\nexport interface RequiresPaymentOptions {\n /** The Payments instance (with payments.facilitator) */\n payments: Payments\n /** Single plan ID to accept */\n planId: string\n /**\n * Number of credits to charge, or a function for dynamic pricing (default: 1).\n *\n * This value is sent as `maxAmount` to the facilitator. The amount actually\n * redeemed depends on the plan's server-side credit configuration:\n *\n * - **Fixed plans** (`plan.credits.minAmount === plan.credits.maxAmount`)\n * always burn `plan.credits.maxAmount` — this value is then effectively a\n * no-op (see nevermined-io/nvm-monorepo#1568).\n * - **Range plans** clamp this value into\n * `[plan.credits.minAmount, plan.credits.maxAmount]`.\n */\n credits?: number | CreditsCallable\n /** Optional agent identifier */\n agentId?: string\n /** Blockchain network in CAIP-2 format (default: 'eip155:84532' for Base Sepolia) */\n network?: string\n}\n\n/**\n * Thrown when payment verification fails or no token is provided.\n *\n * Carries the `X402PaymentRequired` object so callers can inspect\n * accepted plans and acquire the correct payment token.\n */\nexport class PaymentRequiredError extends Error {\n /** The x402 PaymentRequired object for programmatic token acquisition */\n paymentRequired: X402PaymentRequired | undefined\n\n constructor(message: string, paymentRequired?: X402PaymentRequired) {\n super(message)\n this.name = 'PaymentRequiredError'\n this.paymentRequired = paymentRequired\n }\n}\n\n/**\n * Payment context stored in `config.configurable.payment_context` after verification.\n */\nexport interface PaymentContext {\n /**\n * Abbreviated, non-functional reference to the x402 access token — the SAME\n * redacted form surfaced as `nvm.payment_token` (`<first 16>…<last 4>`, or a\n * `…(short)` marker for a too-short token). The **full token is deliberately\n * not persisted here**: this object is written into\n * `config.configurable.payment_context`, and tracing frameworks (e.g.\n * LangChain) can capture `config.configurable` into span metadata, so storing\n * the raw credential would let it ride into any traced run opened during or\n * after the tool body. Settlement uses the token read from\n * `config.configurable.payment_token`, never this field.\n */\n token: string\n /** The payment required object */\n paymentRequired: X402PaymentRequired\n /** Number of credits to settle */\n creditsToSettle: number\n /** Whether verification was successful */\n verified: boolean\n /** Agent request ID for observability tracking */\n agentRequestId?: string\n /** Agent request context for observability */\n agentRequest?: unknown\n}\n\n// Module-level holder for the most recent settlement receipt. LangGraph copies\n// `RunnableConfig.configurable` per node, so the in-place write to\n// `config.configurable.payment_settlement` is not visible to the buyer's outer\n// scope. A module-level slot is the simplest reliable signal. It is\n// intentionally single-tenant — if the same process runs multiple concurrent\n// settlements, the last writer wins. This race is not limited to multi-tenant\n// servers: a single `createPaidReactAgent` run can also hit it, because a ReAct\n// agent may execute several paid tools in parallel within one LLM turn via\n// `ToolNode`, and this single slot only retains the last writer. For\n// multi-tenant or parallel-tool use cases, surface the receipt via a callback\n// or via observability (see Sprint 1 of the LangChain epic).\nlet lastSettlementReceipt: SettlePermissionsResult | undefined\n\n/**\n * Return the most recent settlement receipt produced by {@link requiresPayment}.\n *\n * Use this after invoking a LangChain/LangGraph runnable whose tool is wrapped\n * with {@link requiresPayment} to recover the settlement receipt\n * (`creditsRedeemed`, `remainingBalance`, `transaction`, `network`, `payer`)\n * without threading it back through the runnable config (which LangGraph copies\n * per node, so the in-place write is invisible to the outer scope).\n *\n * Returns `undefined` if no settlement has happened yet in this process, or if\n * the most recent invocation raised before reaching the settle phase.\n *\n * @remarks\n * This accessor reads from a module-level slot. In multi-tenant processes (e.g.\n * a server handling concurrent settlements), the value reflects whichever\n * invocation settled most recently — there is no per-call isolation.\n */\nexport function lastSettlement(): SettlePermissionsResult | undefined {\n return lastSettlementReceipt\n}\n\n/**\n * Extract the payment token from a LangChain RunnableConfig.\n *\n * In LangChain.js, the config is the optional second parameter to tool functions:\n * `(args, config?) => ...` where config is a `RunnableConfig`.\n */\nfunction extractPaymentToken(config: unknown): string | null {\n if (config == null || typeof config !== 'object') return null\n\n const configurable = (config as Record<string, unknown>).configurable\n if (configurable == null || typeof configurable !== 'object') return null\n\n const token = (configurable as Record<string, unknown>).payment_token\n return typeof token === 'string' ? token : null\n}\n\n/**\n * Store a value in config.configurable if available.\n */\nfunction storeInConfigurable(config: unknown, key: string, value: unknown): void {\n if (config == null || typeof config !== 'object') return\n\n const configurable = (config as Record<string, unknown>).configurable\n if (configurable == null || typeof configurable !== 'object') return\n ;(configurable as Record<string, unknown>)[key] = value\n}\n\n/**\n * Remove a key from config.configurable if present (no-op otherwise).\n */\nfunction removeFromConfigurable(config: unknown, key: string): void {\n if (config == null || typeof config !== 'object') return\n\n const configurable = (config as Record<string, unknown>).configurable\n if (configurable == null || typeof configurable !== 'object') return\n delete (configurable as Record<string, unknown>)[key]\n}\n\n/**\n * Wraps a LangChain.js tool implementation with x402 payment verification and settlement.\n *\n * This is a higher-order function that takes the tool's implementation function and\n * payment options, returning a new function with the same signature that:\n *\n * 1. Extracts the payment token from `config.configurable.payment_token`\n * 2. Verifies the subscriber has sufficient credits\n * 3. Calls the original tool function\n * 4. Settles (burns) credits\n * 5. Stores `payment_context` and `payment_settlement` in `config.configurable`\n *\n * @param fn - The tool implementation function: `(args, config?) => result`\n * @param options - Payment configuration\n * @returns Wrapped function with the same signature\n *\n * @example Static credits\n * ```typescript\n * const searchData = tool(\n * requiresPayment(\n * (args) => `Results for ${args.query}`,\n * { payments, planId: PLAN_ID, credits: 1 }\n * ),\n * { name: 'search_data', description: 'Search', schema: z.object({ query: z.string() }) }\n * )\n * ```\n *\n * @example Dynamic credits\n * ```typescript\n * const summarize = tool(\n * requiresPayment(\n * (args) => `Summary of ${args.text}`,\n * {\n * payments, planId: PLAN_ID,\n * credits: (ctx) => Math.max(1, Math.floor(String(ctx.result).length / 100)),\n * }\n * ),\n * { name: 'summarize', description: 'Summarize text', schema: z.object({ text: z.string() }) }\n * )\n * ```\n */\nexport function requiresPayment<TArgs extends Record<string, unknown>, TResult>(\n fn: (args: TArgs, config?: unknown) => TResult | Promise<TResult>,\n options: RequiresPaymentOptions,\n): (args: TArgs, config?: unknown) => Promise<TResult> {\n const { payments, planId, credits = 1, agentId, network } = options\n\n return async (args: TArgs, config?: unknown): Promise<TResult> => {\n // Reset the module-level slot at the START of every invocation, before\n // verify. Any failure that does not reach the settle-success write (a\n // verify failure / PaymentRequiredError, or a swallowed settle failure)\n // then leaves lastSettlement() returning `undefined` rather than a stale\n // receipt from a previous invocation — matching the JSDoc contract on\n // lastSettlement().\n lastSettlementReceipt = undefined\n\n // Build payment required object\n const paymentRequired = buildPaymentRequired(planId, {\n endpoint: fn.name || 'tool',\n agentId,\n network,\n })\n // The scheme/network the verify span advertises come from the resolved\n // X402 scheme so the span metadata matches what the buyer paid against.\n const accepted = paymentRequired.accepts[0]\n const planIds = paymentRequired.accepts\n .map((a) => a.planId)\n .filter((p): p is string => Boolean(p))\n const resolvedScheme = accepted?.scheme\n const resolvedNetwork = network ?? accepted?.network\n\n // LangChain auto-captures every key in config.configurable into the parent\n // tool span's metadata, and child spans inherit it at construction time.\n // Strip the full x402 access token from the parent BEFORE opening the verify\n // span so neither the parent nor the child carries the raw credential — only\n // the abbreviated nvm.payment_token remains for correlation.\n const parentRunTree = await activeRunTree()\n redactMetadataKeys(parentRunTree, 'payment_token')\n\n const verifyStarted = Date.now()\n // Open the verify span BEFORE the token-presence check so failed probes\n // (no payment_token in config) still produce a clearly-named span with the\n // static nvm.* attrs attached to both the span and the parent tool span.\n const vspan = await verifySpan({\n planIds,\n scheme: resolvedScheme,\n network: resolvedNetwork,\n agentId,\n })\n // Pre-verify metadata is best-effort and static-only.\n const preVerifyMd = buildVerifyMetadata({\n planIds,\n scheme: resolvedScheme,\n network: resolvedNetwork,\n agentId,\n })\n vspan.addMetadata(preVerifyMd)\n addMetadata(parentRunTree, preVerifyMd)\n\n // Extract token from config.configurable.payment_token\n const token = extractPaymentToken(config)\n if (!token) {\n await vspan.end(new Error('missing payment_token in config.configurable'))\n throw new PaymentRequiredError(\n 'Payment required: missing payment_token in config.configurable',\n paymentRequired,\n )\n }\n\n // Drop the raw token from configurable now that we hold it in `token`.\n // LangChain's `ensureConfig` re-promotes every configurable scalar into a\n // runnable's `metadata` on EACH invocation, so any traced runnable the tool\n // body spawns (an LLM call, a sub-chain) sharing this config would otherwise\n // re-leak the full credential into its own span metadata — and a child run\n // opened *during* `fn()` would capture it before the settle-side\n // `redactMetadataKeys` below could run. Removing the key here is the\n // proactive complement to that reactive redaction. This MUST run after\n // extraction (deleting earlier would make `extractPaymentToken` return null)\n // and before `fn()` — guaranteed, since `fn()` is called further down.\n // Settlement uses the local `token`, never configurable, so removal is\n // non-functional. On the `tool()`/LangGraph path LangChain hands the wrapper\n // a per-invocation config copy, so this does not mutate a config the caller\n // reuses across sibling tools.\n removeFromConfigurable(config, 'payment_token')\n\n // Abbreviate/redact the token ONCE here (mirrors Python's\n // attach_metadata_safely pre-abbreviation) and pass the result into the\n // metadata builders. abbreviateToken is idempotent, so the builders leave\n // it unchanged — this means the short-token warning fires at most once per\n // call (not once per verify AND once per settle), and the raw token never\n // reaches the builders' frame locals (defense against exception enrichers\n // that capture locals).\n const abbreviatedToken = abbreviateToken(token)\n\n // Resolve pre-execution credits (static only; callable deferred to post-execution)\n const creditsToVerify = typeof credits === 'number' ? credits : 1\n\n // Verify permissions\n let verification: VerifyPermissionsResult\n try {\n verification = await payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToVerify),\n })\n } catch (error) {\n await vspan.end(error)\n throw new PaymentRequiredError(\n `Payment verification failed: ${error instanceof Error ? error.message : String(error)}`,\n paymentRequired,\n )\n }\n\n // Augment span metadata with verification results + timing (both span and\n // parent), then close the verify span. Best-effort: a metadata failure must\n // not mask the PaymentRequiredError that may follow.\n const verifyMd = buildVerifyMetadata({\n planIds,\n scheme: resolvedScheme,\n network: resolvedNetwork,\n agentId,\n verification,\n durationMs: Date.now() - verifyStarted,\n token: abbreviatedToken,\n })\n vspan.addMetadata(verifyMd)\n addMetadata(parentRunTree, verifyMd)\n\n if (!verification.isValid) {\n await vspan.end(new Error(verification.invalidReason || 'verification failed'))\n throw new PaymentRequiredError(\n `Payment verification failed: ${verification.invalidReason || 'Insufficient credits or invalid token'}`,\n paymentRequired,\n )\n }\n\n await vspan.end()\n\n // Store payment context. The `token` field carries the ABBREVIATED\n // reference, never the raw credential: `payment_context` is written into\n // `config.configurable`, which LangChain can capture into span metadata, so\n // persisting the full token here would reopen the very leak the parent-tree\n // `redactMetadataKeys('payment_token')` calls close — and a child run opened\n // *during* the tool body would capture it before any post-hoc redaction\n // could run. `abbreviatedToken` is always defined past the `!token` guard\n // above; `?? ''` is a belt-and-suspenders that can never fall back to raw.\n const paymentContext: PaymentContext = {\n token: abbreviatedToken ?? '',\n paymentRequired,\n creditsToSettle: creditsToVerify,\n verified: true,\n agentRequestId: verification.agentRequest?.agentRequestId || verification.agentRequestId,\n agentRequest: verification.agentRequest,\n }\n storeInConfigurable(config, 'payment_context', paymentContext)\n\n // Execute the tool function\n const result = await fn(args, config)\n\n // Resolve final credits (may be dynamic based on result)\n const finalCredits =\n typeof credits === 'function'\n ? credits({ args: args as Record<string, unknown>, result })\n : credits\n\n // Settle credits, wrapped in an nvm:settlement span (mirrors Python's\n // settlement_span around settle_permissions).\n //\n // Re-fetch the active run tree: `fn()` may have opened nested traced runs, so\n // `activeRunTree()` can now return a different RunTree than the verify-side\n // redaction at the top of this function scrubbed. We already removed\n // `payment_token` from `config.configurable` before `fn()` ran, so newly\n // opened child runs can no longer re-promote the raw credential — this\n // re-redaction is now defense-in-depth, covering any RunTree whose metadata\n // was populated before that removal took effect. Redact BEFORE opening the\n // settlement span so the credential never rides into the settle child span or\n // the (possibly new) parent tree.\n const settleParentRunTree = await activeRunTree()\n redactMetadataKeys(settleParentRunTree, 'payment_token')\n const settleStarted = Date.now()\n const sspan = await settlementSpan({ planIds, agentId })\n try {\n const settlement = await payments.facilitator.settlePermissions({\n paymentRequired,\n x402AccessToken: token,\n // A dynamic `credits` callable can return a float (e.g. length/100).\n // `BigInt(1.5)` throws RangeError, which the surrounding try/catch\n // swallows — credits are never burned and the caller is never told.\n // Floor to keep the settle on the money path.\n maxAmount: BigInt(Math.floor(finalCredits)),\n agentRequestId: paymentContext.agentRequestId,\n })\n storeInConfigurable(config, 'payment_settlement', settlement)\n // Also publish to the module-level slot so lastSettlement() can recover\n // the receipt — LangGraph copies config.configurable per node, hiding the\n // line above from the buyer's outer scope.\n lastSettlementReceipt = settlement\n\n const settleMd = buildSettleMetadata({\n settlement,\n planIds,\n agentId,\n durationMs: Date.now() - settleStarted,\n token: abbreviatedToken,\n })\n sspan.addMetadata(settleMd)\n addMetadata(settleParentRunTree, settleMd)\n await sspan.end()\n } catch (settleError) {\n console.error('Payment settlement failed:', settleError)\n await sspan.end(settleError)\n // Still return result even if settlement fails\n }\n\n return result\n }\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LangChain integration for Nevermined payment protection using the x402 protocol.
|
|
3
3
|
*/
|
|
4
|
-
export { requiresPayment, PaymentRequiredError, type RequiresPaymentOptions, type CreditsCallable, type CreditsContext, type PaymentContext, } from './decorator.js';
|
|
4
|
+
export { requiresPayment, lastSettlement, PaymentRequiredError, type RequiresPaymentOptions, type CreditsCallable, type CreditsContext, type PaymentContext, } from './decorator.js';
|
|
5
|
+
export { createPaidReactAgent, type CreatePaidReactAgentOptions, } from './agent.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/x402/langchain/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/x402/langchain/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,GACjC,MAAM,YAAY,CAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LangChain integration for Nevermined payment protection using the x402 protocol.
|
|
3
3
|
*/
|
|
4
|
-
export { requiresPayment, PaymentRequiredError, } from './decorator.js';
|
|
4
|
+
export { requiresPayment, lastSettlement, PaymentRequiredError, } from './decorator.js';
|
|
5
|
+
export { createPaidReactAgent, } from './agent.js';
|
|
5
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/x402/langchain/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,eAAe,EACf,oBAAoB,GAKrB,MAAM,gBAAgB,CAAA","sourcesContent":["/**\n * LangChain integration for Nevermined payment protection using the x402 protocol.\n */\n\nexport {\n requiresPayment,\n PaymentRequiredError,\n type RequiresPaymentOptions,\n type CreditsCallable,\n type CreditsContext,\n type PaymentContext,\n} from './decorator.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/x402/langchain/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,eAAe,EACf,cAAc,EACd,oBAAoB,GAKrB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,oBAAoB,GAErB,MAAM,YAAY,CAAA","sourcesContent":["/**\n * LangChain integration for Nevermined payment protection using the x402 protocol.\n */\n\nexport {\n requiresPayment,\n lastSettlement,\n PaymentRequiredError,\n type RequiresPaymentOptions,\n type CreditsCallable,\n type CreditsContext,\n type PaymentContext,\n} from './decorator.js'\nexport {\n createPaidReactAgent,\n type CreatePaidReactAgentOptions,\n} from './agent.js'\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangSmith observability bridge for Nevermined payments (TypeScript).
|
|
3
|
+
*
|
|
4
|
+
* Surfaces the cross-SDK `nvm:verify` / `nvm:settlement` spans defined by the
|
|
5
|
+
* observability-spans-v1 contract, matching the Python SDK attribute-for-
|
|
6
|
+
* attribute so a single LangSmith trace can be correlated across both SDKs.
|
|
7
|
+
*
|
|
8
|
+
* These helpers are wired into the `requiresPayment` decorator automatically;
|
|
9
|
+
* they are also exported here for manual use in non-LangChain code paths.
|
|
10
|
+
*
|
|
11
|
+
* Requires the optional `langsmith` peer dependency and `LANGSMITH_TRACING=true`;
|
|
12
|
+
* every helper no-ops when tracing is inactive or `langsmith` is not installed.
|
|
13
|
+
*/
|
|
14
|
+
export { abbreviateToken, activeRunTree, addMetadata, buildSettleMetadata, buildVerifyMetadata, redactMetadataKeys, settlementSpan, verifySpan, NVM_VERIFY_SPAN, NVM_SETTLEMENT_SPAN, type NvmSpan, type SpanMetadata, type VerifyMetadataInput, type SettleMetadataInput, type VerifySpanInput, type SettlementSpanInput, } from './spans.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/x402/langsmith/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,eAAe,EACf,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,eAAe,EACf,mBAAmB,EACnB,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,mBAAmB,GACzB,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangSmith observability bridge for Nevermined payments (TypeScript).
|
|
3
|
+
*
|
|
4
|
+
* Surfaces the cross-SDK `nvm:verify` / `nvm:settlement` spans defined by the
|
|
5
|
+
* observability-spans-v1 contract, matching the Python SDK attribute-for-
|
|
6
|
+
* attribute so a single LangSmith trace can be correlated across both SDKs.
|
|
7
|
+
*
|
|
8
|
+
* These helpers are wired into the `requiresPayment` decorator automatically;
|
|
9
|
+
* they are also exported here for manual use in non-LangChain code paths.
|
|
10
|
+
*
|
|
11
|
+
* Requires the optional `langsmith` peer dependency and `LANGSMITH_TRACING=true`;
|
|
12
|
+
* every helper no-ops when tracing is inactive or `langsmith` is not installed.
|
|
13
|
+
*/
|
|
14
|
+
export { abbreviateToken, activeRunTree, addMetadata, buildSettleMetadata, buildVerifyMetadata, redactMetadataKeys, settlementSpan, verifySpan, NVM_VERIFY_SPAN, NVM_SETTLEMENT_SPAN, } from './spans.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/x402/langsmith/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,eAAe,EACf,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,eAAe,EACf,mBAAmB,GAOpB,MAAM,YAAY,CAAA","sourcesContent":["/**\n * LangSmith observability bridge for Nevermined payments (TypeScript).\n *\n * Surfaces the cross-SDK `nvm:verify` / `nvm:settlement` spans defined by the\n * observability-spans-v1 contract, matching the Python SDK attribute-for-\n * attribute so a single LangSmith trace can be correlated across both SDKs.\n *\n * These helpers are wired into the `requiresPayment` decorator automatically;\n * they are also exported here for manual use in non-LangChain code paths.\n *\n * Requires the optional `langsmith` peer dependency and `LANGSMITH_TRACING=true`;\n * every helper no-ops when tracing is inactive or `langsmith` is not installed.\n */\n\nexport {\n abbreviateToken,\n activeRunTree,\n addMetadata,\n buildSettleMetadata,\n buildVerifyMetadata,\n redactMetadataKeys,\n settlementSpan,\n verifySpan,\n NVM_VERIFY_SPAN,\n NVM_SETTLEMENT_SPAN,\n type NvmSpan,\n type SpanMetadata,\n type VerifyMetadataInput,\n type SettleMetadataInput,\n type VerifySpanInput,\n type SettlementSpanInput,\n} from './spans.js'\n"]}
|