@nevermined-io/payments 1.4.1 → 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.
Files changed (54) hide show
  1. package/README.md +121 -93
  2. package/dist/api/agents-api.d.ts.map +1 -1
  3. package/dist/api/agents-api.js +8 -7
  4. package/dist/api/agents-api.js.map +1 -1
  5. package/dist/api/plans-api.d.ts +5 -1
  6. package/dist/api/plans-api.d.ts.map +1 -1
  7. package/dist/api/plans-api.js +14 -9
  8. package/dist/api/plans-api.js.map +1 -1
  9. package/dist/api/requests-api.d.ts.map +1 -1
  10. package/dist/api/requests-api.js +4 -3
  11. package/dist/api/requests-api.js.map +1 -1
  12. package/dist/common/helper.d.ts +11 -0
  13. package/dist/common/helper.d.ts.map +1 -1
  14. package/dist/common/helper.js +32 -0
  15. package/dist/common/helper.js.map +1 -1
  16. package/dist/environments.d.ts +8 -0
  17. package/dist/environments.d.ts.map +1 -1
  18. package/dist/environments.js +10 -0
  19. package/dist/environments.js.map +1 -1
  20. package/dist/plans.d.ts +24 -0
  21. package/dist/plans.d.ts.map +1 -1
  22. package/dist/plans.js +24 -0
  23. package/dist/plans.js.map +1 -1
  24. package/dist/x402/delegation-api.d.ts +9 -0
  25. package/dist/x402/delegation-api.d.ts.map +1 -1
  26. package/dist/x402/delegation-api.js +4 -0
  27. package/dist/x402/delegation-api.js.map +1 -1
  28. package/dist/x402/express/middleware.d.ts.map +1 -1
  29. package/dist/x402/express/middleware.js +48 -25
  30. package/dist/x402/express/middleware.js.map +1 -1
  31. package/dist/x402/facilitator-api.d.ts.map +1 -1
  32. package/dist/x402/facilitator-api.js +10 -2
  33. package/dist/x402/facilitator-api.js.map +1 -1
  34. package/dist/x402/langchain/agent.d.ts +96 -0
  35. package/dist/x402/langchain/agent.d.ts.map +1 -0
  36. package/dist/x402/langchain/agent.js +121 -0
  37. package/dist/x402/langchain/agent.js.map +1 -0
  38. package/dist/x402/langchain/decorator.d.ts +43 -4
  39. package/dist/x402/langchain/decorator.d.ts.map +1 -1
  40. package/dist/x402/langchain/decorator.js +173 -6
  41. package/dist/x402/langchain/decorator.js.map +1 -1
  42. package/dist/x402/langchain/index.d.ts +2 -1
  43. package/dist/x402/langchain/index.d.ts.map +1 -1
  44. package/dist/x402/langchain/index.js +2 -1
  45. package/dist/x402/langchain/index.js.map +1 -1
  46. package/dist/x402/langsmith/index.d.ts +15 -0
  47. package/dist/x402/langsmith/index.d.ts.map +1 -0
  48. package/dist/x402/langsmith/index.js +15 -0
  49. package/dist/x402/langsmith/index.js.map +1 -0
  50. package/dist/x402/langsmith/spans.d.ts +163 -0
  51. package/dist/x402/langsmith/spans.d.ts.map +1 -0
  52. package/dist/x402/langsmith/spans.js +341 -0
  53. package/dist/x402/langsmith/spans.js.map +1 -0
  54. package/package.json +16 -2
@@ -0,0 +1,341 @@
1
+ /**
2
+ * LangSmith span helpers for Nevermined payment events (TypeScript).
3
+ *
4
+ * TS parity port of `payments_py/langsmith/spans.py`. Emits the cross-SDK
5
+ * `nvm:verify` / `nvm:settlement` spans defined by the
6
+ * **observability-spans-v1** contract
7
+ * (`docs/specs/observability-spans-v1.md` in nvm-monorepo), so a single
8
+ * LangSmith trace can be correlated across the TypeScript and Python SDKs
9
+ * (e.g. filtered on `nvm.tx_hash`).
10
+ *
11
+ * All helpers in this module silently no-op when:
12
+ * - the optional `langsmith` JS SDK is not installed; or
13
+ * - no LangSmith run tree is active in the current context (e.g.
14
+ * `LANGSMITH_TRACING` is unset, or the call is not inside a traced run).
15
+ *
16
+ * Failures inside this module never propagate out — observability is
17
+ * best-effort and must not interfere with the payment flow.
18
+ *
19
+ * `langsmith` is imported **lazily** (dynamic `import()`), so users who only
20
+ * use the `requiresPayment` wrapper without tracing are not forced to install
21
+ * it. Install it yourself (`pnpm add langsmith`) and set `LANGSMITH_TRACING=true`
22
+ * to surface these spans.
23
+ */
24
+ /** Span names — must match observability-spans-v1 exactly (case-sensitive). */
25
+ export const NVM_VERIFY_SPAN = 'nvm:verify';
26
+ export const NVM_SETTLEMENT_SPAN = 'nvm:settlement';
27
+ /**
28
+ * Marker appended to a redacted short token. The joining character is the
29
+ * single Unicode ellipsis `…` (U+2026), matching the Python implementation
30
+ * and the spec, NOT three ASCII dots. (`…` is one UTF-16 code unit, so this
31
+ * string has length 8.)
32
+ */
33
+ const SHORT_TOKEN_MARKER = '…(short)';
34
+ /**
35
+ * A redacted short token is exactly `<prefix><marker>` where `prefix` is either
36
+ * empty (raw length 4 or fewer) or the first 4 chars (raw length over 4) — so a genuine
37
+ * marker only ever has one of these two lengths. Recognising it by both suffix
38
+ * AND an exact length stops a raw ≤20-char value that merely *ends* in the
39
+ * marker (e.g. `"x…(short)"`) from slipping through verbatim.
40
+ */
41
+ const REDACTED_MARKER_LENGTHS = new Set([SHORT_TOKEN_MARKER.length, 4 + SHORT_TOKEN_MARKER.length]);
42
+ /** True if `token` is already a value produced by the short-token branch. */
43
+ function isRedactedMarker(token) {
44
+ return token.endsWith(SHORT_TOKEN_MARKER) && REDACTED_MARKER_LENGTHS.has(token.length);
45
+ }
46
+ let cachedLangsmith;
47
+ /** True when the dynamic-import error means the package is simply not installed. */
48
+ function isModuleNotFound(err) {
49
+ const code = err?.code;
50
+ return code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND';
51
+ }
52
+ async function loadLangsmith() {
53
+ if (cachedLangsmith !== undefined)
54
+ return cachedLangsmith;
55
+ try {
56
+ // `getCurrentRunTree` is NOT exported from the `langsmith` root entry point —
57
+ // it lives exclusively at the `langsmith/singletons/traceable` sub-path (true
58
+ // across every published 0.x). Importing the root and reading
59
+ // `mod.getCurrentRunTree` always yields `undefined`, which would disable
60
+ // every span in production while the mocked unit tests stay green. Import the
61
+ // sub-path directly. (See the un-mocked smoke test in
62
+ // `langsmith-real-sdk.test.ts`, which fails loudly if this ever moves again.)
63
+ const mod = (await import('langsmith/singletons/traceable'));
64
+ if (typeof mod?.getCurrentRunTree === 'function') {
65
+ cachedLangsmith = mod;
66
+ }
67
+ else {
68
+ // Installed but missing the API we rely on — a real, diagnosable problem,
69
+ // distinct from "not installed". Warn once (cached null prevents repeats).
70
+ cachedLangsmith = null;
71
+ console.warn('nvm-langsmith: `langsmith` is installed but `langsmith/singletons/traceable` ' +
72
+ 'does not export getCurrentRunTree; Nevermined spans are disabled.');
73
+ }
74
+ }
75
+ catch (err) {
76
+ cachedLangsmith = null;
77
+ // "Not installed" is the expected optional-peer path → stay silent. Any
78
+ // other failure (a broken install, a transitive load error) disables all
79
+ // spans with no other signal, so surface it once.
80
+ if (!isModuleNotFound(err)) {
81
+ console.warn('nvm-langsmith: failed to load `langsmith`; spans disabled', err);
82
+ }
83
+ }
84
+ return cachedLangsmith;
85
+ }
86
+ /**
87
+ * Return the current LangSmith `RunTree`, or `undefined` if none is active or
88
+ * `langsmith` is not installed. Safe to call unconditionally.
89
+ */
90
+ export async function activeRunTree() {
91
+ const ls = await loadLangsmith();
92
+ if (ls === null)
93
+ return undefined;
94
+ try {
95
+ // `permitAbsentRunTree: true` returns undefined instead of throwing when
96
+ // there is no active run, mirroring Python's `get_current_run_tree()` used
97
+ // behind a try/except.
98
+ return ls.getCurrentRunTree(true);
99
+ }
100
+ catch (err) {
101
+ console.debug('nvm-langsmith: getCurrentRunTree failed (ignored)', err);
102
+ return undefined;
103
+ }
104
+ }
105
+ /**
106
+ * Merge `metadata` into `runTree`, swallowing any error. No-op if `runTree` is
107
+ * absent or `metadata` is empty. Matches Python's `run_tree.add_metadata`.
108
+ *
109
+ * The merge is performed on THIS side of the boundary — we read the existing
110
+ * `extra.metadata`, spread the new keys over it, then assign — rather than
111
+ * relying on the langsmith `RunTree.metadata` setter to merge. In `langsmith@0.7`
112
+ * the setter does merge (`extra.metadata = { ...existing, ...new }`), but that is
113
+ * an implementation detail of `run_trees.js`, not a documented contract: a future
114
+ * release that flips it to plain assignment would silently drop the static
115
+ * `nvm.*` keys attached on the pre-verify pass once the post-verify pass runs.
116
+ * Reading+merging here keeps the double-pass accumulation correct regardless, and
117
+ * mirrors how {@link redactMetadataKeys} already reaches into `extra.metadata`.
118
+ */
119
+ export function addMetadata(runTree, metadata) {
120
+ if (!runTree || !metadata || Object.keys(metadata).length === 0)
121
+ return;
122
+ try {
123
+ const rt = runTree;
124
+ const existing = rt.extra?.metadata ?? {};
125
+ runTree.metadata = { ...existing, ...metadata };
126
+ }
127
+ catch (err) {
128
+ // observability hygiene must never disrupt the payment flow
129
+ console.debug('nvm-langsmith: addMetadata failed (ignored)', err);
130
+ }
131
+ }
132
+ /**
133
+ * Remove `keys` from `runTree`'s metadata in place.
134
+ *
135
+ * LangSmith inherits a parent run's metadata into child runs created via
136
+ * `createChild`, so call this on the PARENT run BEFORE opening any child span
137
+ * whose metadata should not carry the keys. The most common use is stripping
138
+ * the full `payment_token` from the parent tool span's metadata, since the raw
139
+ * access token grants access to the protected tool until it expires.
140
+ *
141
+ * No-op when `runTree` is absent or no keys are provided. Errors are swallowed
142
+ * so observability never disrupts the payment flow — but, unlike the other
143
+ * swallow paths, a failure here is **security-sensitive**: if the parent run
144
+ * tree still holds the raw `payment_token`, that credential rides into the
145
+ * parent tree and the inherited verify/settle child spans. So this path warns
146
+ * (not just debug) to keep the leak diagnosable.
147
+ */
148
+ export function redactMetadataKeys(runTree, ...keys) {
149
+ if (!runTree || keys.length === 0)
150
+ return;
151
+ try {
152
+ const extra = runTree.extra;
153
+ const metadata = extra?.metadata;
154
+ if (metadata && typeof metadata === 'object') {
155
+ for (const key of keys)
156
+ delete metadata[key];
157
+ }
158
+ }
159
+ catch (err) {
160
+ console.warn(`nvm-langsmith: failed to redact metadata keys [${keys.join(', ')}] from ` +
161
+ 'the parent run tree — a raw credential may remain in the trace', err);
162
+ }
163
+ }
164
+ /**
165
+ * Return a short, non-functional reference to a payment token for span
166
+ * metadata. Mirrors `payments_py/langsmith/spans.py::abbreviate_token` and the
167
+ * redaction contract in nvm-monorepo#1746 §4 (short-token hardening from
168
+ * nvm-monorepo#1747, tracked in payments-py PR #217).
169
+ *
170
+ * - `undefined`/empty input → `undefined` (and silent — no token at all is not
171
+ * a "wrong token" mistake).
172
+ * - Already-redacted input (a genuine `<prefix>…(short)` marker) → returned
173
+ * unchanged and silent, so the helper stays idempotent (the decorator path
174
+ * abbreviates the same token more than once). A raw value that merely *ends*
175
+ * in the marker is NOT treated as redacted (see {@link isRedactedMarker}).
176
+ * - A token of length ≤ 20 is almost always a misconfiguration (a plan id, an
177
+ * opaque handle, etc. passed where the JWT was expected). Because this helper
178
+ * exists to keep credentials out of a durable trace store, such tokens are
179
+ * **redacted, not exported**: at most the first 4 chars are revealed (to aid
180
+ * debugging the misconfig), followed by the `…(short)` marker — and for a
181
+ * token of 4 chars or fewer **nothing** is revealed (the whole value would
182
+ * otherwise be the "prefix"), so it collapses to just `…(short)`. A warning
183
+ * is emitted. The full short value never leaves this function.
184
+ * - Otherwise (a normal JWT, more than 20 chars) → `<first 16>…<last 4>`.
185
+ */
186
+ export function abbreviateToken(token) {
187
+ if (!token)
188
+ return undefined;
189
+ if (isRedactedMarker(token)) {
190
+ // Already redacted (re-applied on the decorator path); re-slicing would let
191
+ // the marker drift, so return unchanged and stay silent — the original
192
+ // short value already triggered the warning.
193
+ return token;
194
+ }
195
+ if (token.length <= 20) {
196
+ console.warn('abbreviateToken: token is 20 characters or fewer — was the right x402 ' +
197
+ 'access token passed? Short/non-JWT tokens are almost always a ' +
198
+ 'misconfiguration and are redacted (not exported).');
199
+ // Reveal at most 4 chars; for a ≤4-char token reveal nothing, since
200
+ // token.slice(0, 4) would be the entire value — defeating the redaction.
201
+ const prefix = token.length > 4 ? token.slice(0, 4) : '';
202
+ return `${prefix}${SHORT_TOKEN_MARKER}`;
203
+ }
204
+ return `${token.slice(0, 16)}…${token.slice(-4)}`;
205
+ }
206
+ /**
207
+ * Build the `nvm.*` metadata for a verify span. Drops absent values (a key is
208
+ * omitted, never set to null/undefined). Matches observability-spans-v1 §2.
209
+ *
210
+ * `token` is abbreviated/redacted via {@link abbreviateToken} before being
211
+ * surfaced as `nvm.payment_token` so the full credential never lands in
212
+ * metadata we control.
213
+ */
214
+ export function buildVerifyMetadata(input) {
215
+ const { planIds, scheme, network, agentId, verification, durationMs, token } = input;
216
+ const md = { 'nvm.plan_ids': [...planIds] };
217
+ if (scheme)
218
+ md['nvm.scheme'] = scheme;
219
+ if (network)
220
+ md['nvm.network'] = network;
221
+ if (agentId)
222
+ md['nvm.agent_id'] = agentId;
223
+ if (durationMs !== undefined)
224
+ md['nvm.verify.duration_ms'] = round2(durationMs);
225
+ const abbreviated = abbreviateToken(token);
226
+ if (abbreviated)
227
+ md['nvm.payment_token'] = abbreviated;
228
+ if (verification) {
229
+ if (verification.payer)
230
+ md['nvm.payer'] = verification.payer;
231
+ if (verification.network && !('nvm.network' in md))
232
+ md['nvm.network'] = verification.network;
233
+ if (verification.agentRequestId)
234
+ md['nvm.agent_request_id'] = verification.agentRequestId;
235
+ }
236
+ return md;
237
+ }
238
+ /**
239
+ * Build the `nvm.*` metadata for a settlement span. Drops absent values.
240
+ * Matches observability-spans-v1 §3.
241
+ *
242
+ * Note the types preserved exactly per the spec: `nvm.credits_redeemed`
243
+ * (←`creditsRedeemed`) and `nvm.balance.after` (←`remainingBalance`) are
244
+ * STRINGS — they are not coerced to numbers. `nvm.tx_hash` ← `transaction`.
245
+ */
246
+ export function buildSettleMetadata(input) {
247
+ const { settlement, planIds, agentId, durationMs, token } = input;
248
+ const md = { 'nvm.plan_ids': [...planIds] };
249
+ if (agentId)
250
+ md['nvm.agent_id'] = agentId;
251
+ if (durationMs !== undefined)
252
+ md['nvm.settle.duration_ms'] = round2(durationMs);
253
+ const abbreviated = abbreviateToken(token);
254
+ if (abbreviated)
255
+ md['nvm.payment_token'] = abbreviated;
256
+ if (settlement.creditsRedeemed != null)
257
+ md['nvm.credits_redeemed'] = settlement.creditsRedeemed;
258
+ if (settlement.remainingBalance != null)
259
+ md['nvm.balance.after'] = settlement.remainingBalance;
260
+ if (settlement.transaction)
261
+ md['nvm.tx_hash'] = settlement.transaction;
262
+ if (settlement.network)
263
+ md['nvm.network'] = settlement.network;
264
+ if (settlement.payer)
265
+ md['nvm.payer'] = settlement.payer;
266
+ return md;
267
+ }
268
+ /** Round to 2 decimals, matching Python's `round(value, 2)`. */
269
+ function round2(value) {
270
+ return Math.round(value * 100) / 100;
271
+ }
272
+ async function openNvmSpan(name, inputs) {
273
+ const parent = await activeRunTree();
274
+ if (!parent)
275
+ return inactiveSpan();
276
+ let child;
277
+ try {
278
+ child = parent.createChild({ name, run_type: 'tool', inputs });
279
+ }
280
+ catch (err) {
281
+ // span setup is pure observability — never let it disrupt the payment flow
282
+ console.debug(`nvm-langsmith: createChild(${name}) failed (ignored)`, err);
283
+ return inactiveSpan();
284
+ }
285
+ return {
286
+ runTree: child,
287
+ addMetadata(metadata) {
288
+ addMetadata(child, metadata);
289
+ },
290
+ async end(error) {
291
+ try {
292
+ await child.end(undefined, error === undefined ? undefined : error instanceof Error ? error.message : String(error));
293
+ await child.postRun();
294
+ }
295
+ catch (err) {
296
+ // best-effort flush
297
+ console.debug(`nvm-langsmith: ${name} span flush failed (ignored)`, err);
298
+ }
299
+ },
300
+ };
301
+ }
302
+ function inactiveSpan() {
303
+ return {
304
+ runTree: undefined,
305
+ addMetadata() {
306
+ /* no-op */
307
+ },
308
+ async end() {
309
+ /* no-op */
310
+ },
311
+ };
312
+ }
313
+ /**
314
+ * Open an `nvm:verify` child span around a verify call. Returns an
315
+ * {@link NvmSpan} whose `end()` must be called once the verify completes (or
316
+ * throws). Always safe — a no-op span is returned when tracing is inactive or
317
+ * `langsmith` is not installed.
318
+ */
319
+ export async function verifySpan(input) {
320
+ const { planIds, scheme, network, agentId } = input;
321
+ const inputs = { plan_ids: [...planIds] };
322
+ if (scheme)
323
+ inputs.scheme = scheme;
324
+ if (network)
325
+ inputs.network = network;
326
+ if (agentId)
327
+ inputs.agent_id = agentId;
328
+ return openNvmSpan(NVM_VERIFY_SPAN, inputs);
329
+ }
330
+ /**
331
+ * Open an `nvm:settlement` child span around a settle call. Same semantics as
332
+ * {@link verifySpan}.
333
+ */
334
+ export async function settlementSpan(input) {
335
+ const { planIds, agentId } = input;
336
+ const inputs = { plan_ids: [...planIds] };
337
+ if (agentId)
338
+ inputs.agent_id = agentId;
339
+ return openNvmSpan(NVM_SETTLEMENT_SPAN, inputs);
340
+ }
341
+ //# sourceMappingURL=spans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spans.js","sourceRoot":"","sources":["../../../src/x402/langsmith/spans.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LangSmith span helpers for Nevermined payment events (TypeScript).\n *\n * TS parity port of `payments_py/langsmith/spans.py`. Emits the cross-SDK\n * `nvm:verify` / `nvm:settlement` spans defined by the\n * **observability-spans-v1** contract\n * (`docs/specs/observability-spans-v1.md` in nvm-monorepo), so a single\n * LangSmith trace can be correlated across the TypeScript and Python SDKs\n * (e.g. filtered on `nvm.tx_hash`).\n *\n * All helpers in this module silently no-op when:\n * - the optional `langsmith` JS SDK is not installed; or\n * - no LangSmith run tree is active in the current context (e.g.\n * `LANGSMITH_TRACING` is unset, or the call is not inside a traced run).\n *\n * Failures inside this module never propagate out — observability is\n * best-effort and must not interfere with the payment flow.\n *\n * `langsmith` is imported **lazily** (dynamic `import()`), so users who only\n * use the `requiresPayment` wrapper without tracing are not forced to install\n * it. Install it yourself (`pnpm add langsmith`) and set `LANGSMITH_TRACING=true`\n * to surface these spans.\n */\n\nimport type { RunTree } from 'langsmith'\nimport type { VerifyPermissionsResult, SettlePermissionsResult } from '../facilitator-api.js'\n\n/** Plain JSON-ish metadata bag attached to a span / run tree. */\nexport type SpanMetadata = Record<string, unknown>\n\n/** Span names — must match observability-spans-v1 exactly (case-sensitive). */\nexport const NVM_VERIFY_SPAN = 'nvm:verify'\nexport const NVM_SETTLEMENT_SPAN = 'nvm:settlement'\n\n/**\n * Marker appended to a redacted short token. The joining character is the\n * single Unicode ellipsis `…` (U+2026), matching the Python implementation\n * and the spec, NOT three ASCII dots. (`…` is one UTF-16 code unit, so this\n * string has length 8.)\n */\nconst SHORT_TOKEN_MARKER = '…(short)'\n\n/**\n * A redacted short token is exactly `<prefix><marker>` where `prefix` is either\n * empty (raw length 4 or fewer) or the first 4 chars (raw length over 4) — so a genuine\n * marker only ever has one of these two lengths. Recognising it by both suffix\n * AND an exact length stops a raw ≤20-char value that merely *ends* in the\n * marker (e.g. `\"x…(short)\"`) from slipping through verbatim.\n */\nconst REDACTED_MARKER_LENGTHS = new Set([SHORT_TOKEN_MARKER.length, 4 + SHORT_TOKEN_MARKER.length])\n\n/** True if `token` is already a value produced by the short-token branch. */\nfunction isRedactedMarker(token: string): boolean {\n return token.endsWith(SHORT_TOKEN_MARKER) && REDACTED_MARKER_LENGTHS.has(token.length)\n}\n\n/**\n * Cached result of the lazy `langsmith` import.\n *\n * `undefined` = not yet attempted; `null` = attempted and unavailable (so we\n * never retry the dynamic import on every call); otherwise the resolved module\n * surface we use (`getCurrentRunTree`).\n */\ntype LangsmithModule = {\n getCurrentRunTree: (permitAbsentRunTree: boolean) => RunTree | undefined\n}\nlet cachedLangsmith: LangsmithModule | null | undefined\n\n/** True when the dynamic-import error means the package is simply not installed. */\nfunction isModuleNotFound(err: unknown): boolean {\n const code = (err as { code?: string } | null)?.code\n return code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND'\n}\n\nasync function loadLangsmith(): Promise<LangsmithModule | null> {\n if (cachedLangsmith !== undefined) return cachedLangsmith\n try {\n // `getCurrentRunTree` is NOT exported from the `langsmith` root entry point —\n // it lives exclusively at the `langsmith/singletons/traceable` sub-path (true\n // across every published 0.x). Importing the root and reading\n // `mod.getCurrentRunTree` always yields `undefined`, which would disable\n // every span in production while the mocked unit tests stay green. Import the\n // sub-path directly. (See the un-mocked smoke test in\n // `langsmith-real-sdk.test.ts`, which fails loudly if this ever moves again.)\n const mod = (await import('langsmith/singletons/traceable')) as unknown as LangsmithModule\n if (typeof mod?.getCurrentRunTree === 'function') {\n cachedLangsmith = mod\n } else {\n // Installed but missing the API we rely on — a real, diagnosable problem,\n // distinct from \"not installed\". Warn once (cached null prevents repeats).\n cachedLangsmith = null\n console.warn(\n 'nvm-langsmith: `langsmith` is installed but `langsmith/singletons/traceable` ' +\n 'does not export getCurrentRunTree; Nevermined spans are disabled.',\n )\n }\n } catch (err) {\n cachedLangsmith = null\n // \"Not installed\" is the expected optional-peer path → stay silent. Any\n // other failure (a broken install, a transitive load error) disables all\n // spans with no other signal, so surface it once.\n if (!isModuleNotFound(err)) {\n console.warn('nvm-langsmith: failed to load `langsmith`; spans disabled', err)\n }\n }\n return cachedLangsmith\n}\n\n/**\n * Return the current LangSmith `RunTree`, or `undefined` if none is active or\n * `langsmith` is not installed. Safe to call unconditionally.\n */\nexport async function activeRunTree(): Promise<RunTree | undefined> {\n const ls = await loadLangsmith()\n if (ls === null) return undefined\n try {\n // `permitAbsentRunTree: true` returns undefined instead of throwing when\n // there is no active run, mirroring Python's `get_current_run_tree()` used\n // behind a try/except.\n return ls.getCurrentRunTree(true)\n } catch (err) {\n console.debug('nvm-langsmith: getCurrentRunTree failed (ignored)', err)\n return undefined\n }\n}\n\n/**\n * Merge `metadata` into `runTree`, swallowing any error. No-op if `runTree` is\n * absent or `metadata` is empty. Matches Python's `run_tree.add_metadata`.\n *\n * The merge is performed on THIS side of the boundary — we read the existing\n * `extra.metadata`, spread the new keys over it, then assign — rather than\n * relying on the langsmith `RunTree.metadata` setter to merge. In `langsmith@0.7`\n * the setter does merge (`extra.metadata = { ...existing, ...new }`), but that is\n * an implementation detail of `run_trees.js`, not a documented contract: a future\n * release that flips it to plain assignment would silently drop the static\n * `nvm.*` keys attached on the pre-verify pass once the post-verify pass runs.\n * Reading+merging here keeps the double-pass accumulation correct regardless, and\n * mirrors how {@link redactMetadataKeys} already reaches into `extra.metadata`.\n */\nexport function addMetadata(runTree: RunTree | undefined, metadata: SpanMetadata): void {\n if (!runTree || !metadata || Object.keys(metadata).length === 0) return\n try {\n const rt = runTree as unknown as { extra?: Record<string, unknown> }\n const existing = (rt.extra?.metadata as Record<string, unknown> | undefined) ?? {}\n runTree.metadata = { ...existing, ...metadata }\n } catch (err) {\n // observability hygiene must never disrupt the payment flow\n console.debug('nvm-langsmith: addMetadata failed (ignored)', err)\n }\n}\n\n/**\n * Remove `keys` from `runTree`'s metadata in place.\n *\n * LangSmith inherits a parent run's metadata into child runs created via\n * `createChild`, so call this on the PARENT run BEFORE opening any child span\n * whose metadata should not carry the keys. The most common use is stripping\n * the full `payment_token` from the parent tool span's metadata, since the raw\n * access token grants access to the protected tool until it expires.\n *\n * No-op when `runTree` is absent or no keys are provided. Errors are swallowed\n * so observability never disrupts the payment flow — but, unlike the other\n * swallow paths, a failure here is **security-sensitive**: if the parent run\n * tree still holds the raw `payment_token`, that credential rides into the\n * parent tree and the inherited verify/settle child spans. So this path warns\n * (not just debug) to keep the leak diagnosable.\n */\nexport function redactMetadataKeys(runTree: RunTree | undefined, ...keys: string[]): void {\n if (!runTree || keys.length === 0) return\n try {\n const extra = (runTree as unknown as { extra?: Record<string, unknown> }).extra\n const metadata = extra?.metadata as Record<string, unknown> | undefined\n if (metadata && typeof metadata === 'object') {\n for (const key of keys) delete metadata[key]\n }\n } catch (err) {\n console.warn(\n `nvm-langsmith: failed to redact metadata keys [${keys.join(', ')}] from ` +\n 'the parent run tree — a raw credential may remain in the trace',\n err,\n )\n }\n}\n\n/**\n * Return a short, non-functional reference to a payment token for span\n * metadata. Mirrors `payments_py/langsmith/spans.py::abbreviate_token` and the\n * redaction contract in nvm-monorepo#1746 §4 (short-token hardening from\n * nvm-monorepo#1747, tracked in payments-py PR #217).\n *\n * - `undefined`/empty input → `undefined` (and silent — no token at all is not\n * a \"wrong token\" mistake).\n * - Already-redacted input (a genuine `<prefix>…(short)` marker) → returned\n * unchanged and silent, so the helper stays idempotent (the decorator path\n * abbreviates the same token more than once). A raw value that merely *ends*\n * in the marker is NOT treated as redacted (see {@link isRedactedMarker}).\n * - A token of length ≤ 20 is almost always a misconfiguration (a plan id, an\n * opaque handle, etc. passed where the JWT was expected). Because this helper\n * exists to keep credentials out of a durable trace store, such tokens are\n * **redacted, not exported**: at most the first 4 chars are revealed (to aid\n * debugging the misconfig), followed by the `…(short)` marker — and for a\n * token of 4 chars or fewer **nothing** is revealed (the whole value would\n * otherwise be the \"prefix\"), so it collapses to just `…(short)`. A warning\n * is emitted. The full short value never leaves this function.\n * - Otherwise (a normal JWT, more than 20 chars) → `<first 16>…<last 4>`.\n */\nexport function abbreviateToken(token: string | undefined | null): string | undefined {\n if (!token) return undefined\n if (isRedactedMarker(token)) {\n // Already redacted (re-applied on the decorator path); re-slicing would let\n // the marker drift, so return unchanged and stay silent — the original\n // short value already triggered the warning.\n return token\n }\n if (token.length <= 20) {\n console.warn(\n 'abbreviateToken: token is 20 characters or fewer — was the right x402 ' +\n 'access token passed? Short/non-JWT tokens are almost always a ' +\n 'misconfiguration and are redacted (not exported).',\n )\n // Reveal at most 4 chars; for a ≤4-char token reveal nothing, since\n // token.slice(0, 4) would be the entire value — defeating the redaction.\n const prefix = token.length > 4 ? token.slice(0, 4) : ''\n return `${prefix}${SHORT_TOKEN_MARKER}`\n }\n return `${token.slice(0, 16)}…${token.slice(-4)}`\n}\n\n/** Inputs accepted by {@link buildVerifyMetadata}. */\nexport interface VerifyMetadataInput {\n planIds: string[]\n scheme?: string\n network?: string\n agentId?: string\n verification?: VerifyPermissionsResult\n durationMs?: number\n token?: string\n}\n\n/**\n * Build the `nvm.*` metadata for a verify span. Drops absent values (a key is\n * omitted, never set to null/undefined). Matches observability-spans-v1 §2.\n *\n * `token` is abbreviated/redacted via {@link abbreviateToken} before being\n * surfaced as `nvm.payment_token` so the full credential never lands in\n * metadata we control.\n */\nexport function buildVerifyMetadata(input: VerifyMetadataInput): SpanMetadata {\n const { planIds, scheme, network, agentId, verification, durationMs, token } = input\n const md: SpanMetadata = { 'nvm.plan_ids': [...planIds] }\n if (scheme) md['nvm.scheme'] = scheme\n if (network) md['nvm.network'] = network\n if (agentId) md['nvm.agent_id'] = agentId\n if (durationMs !== undefined) md['nvm.verify.duration_ms'] = round2(durationMs)\n const abbreviated = abbreviateToken(token)\n if (abbreviated) md['nvm.payment_token'] = abbreviated\n if (verification) {\n if (verification.payer) md['nvm.payer'] = verification.payer\n if (verification.network && !('nvm.network' in md)) md['nvm.network'] = verification.network\n if (verification.agentRequestId) md['nvm.agent_request_id'] = verification.agentRequestId\n }\n return md\n}\n\n/** Inputs accepted by {@link buildSettleMetadata}. */\nexport interface SettleMetadataInput {\n settlement: SettlePermissionsResult\n planIds: string[]\n agentId?: string\n durationMs?: number\n token?: string\n}\n\n/**\n * Build the `nvm.*` metadata for a settlement span. Drops absent values.\n * Matches observability-spans-v1 §3.\n *\n * Note the types preserved exactly per the spec: `nvm.credits_redeemed`\n * (←`creditsRedeemed`) and `nvm.balance.after` (←`remainingBalance`) are\n * STRINGS — they are not coerced to numbers. `nvm.tx_hash` ← `transaction`.\n */\nexport function buildSettleMetadata(input: SettleMetadataInput): SpanMetadata {\n const { settlement, planIds, agentId, durationMs, token } = input\n const md: SpanMetadata = { 'nvm.plan_ids': [...planIds] }\n if (agentId) md['nvm.agent_id'] = agentId\n if (durationMs !== undefined) md['nvm.settle.duration_ms'] = round2(durationMs)\n const abbreviated = abbreviateToken(token)\n if (abbreviated) md['nvm.payment_token'] = abbreviated\n if (settlement.creditsRedeemed != null) md['nvm.credits_redeemed'] = settlement.creditsRedeemed\n if (settlement.remainingBalance != null) md['nvm.balance.after'] = settlement.remainingBalance\n if (settlement.transaction) md['nvm.tx_hash'] = settlement.transaction\n if (settlement.network) md['nvm.network'] = settlement.network\n if (settlement.payer) md['nvm.payer'] = settlement.payer\n return md\n}\n\n/** Round to 2 decimals, matching Python's `round(value, 2)`. */\nfunction round2(value: number): number {\n return Math.round(value * 100) / 100\n}\n\n/**\n * An opened Nevermined span. `end()` records `outputs`/`error` and flushes the\n * child run to LangSmith. Always safe to call (no-op when `runTree` is absent).\n */\nexport interface NvmSpan {\n /** The underlying child `RunTree`, or `undefined` when tracing is inactive. */\n readonly runTree: RunTree | undefined\n /** Attach `nvm.*` metadata to this span. */\n addMetadata(metadata: SpanMetadata): void\n /** Close the span, flushing it to LangSmith. Optionally record an error. */\n end(error?: unknown): Promise<void>\n}\n\nasync function openNvmSpan(name: string, inputs: SpanMetadata): Promise<NvmSpan> {\n const parent = await activeRunTree()\n if (!parent) return inactiveSpan()\n let child: RunTree\n try {\n child = parent.createChild({ name, run_type: 'tool', inputs })\n } catch (err) {\n // span setup is pure observability — never let it disrupt the payment flow\n console.debug(`nvm-langsmith: createChild(${name}) failed (ignored)`, err)\n return inactiveSpan()\n }\n return {\n runTree: child,\n addMetadata(metadata: SpanMetadata) {\n addMetadata(child, metadata)\n },\n async end(error?: unknown) {\n try {\n await child.end(\n undefined,\n error === undefined ? undefined : error instanceof Error ? error.message : String(error),\n )\n await child.postRun()\n } catch (err) {\n // best-effort flush\n console.debug(`nvm-langsmith: ${name} span flush failed (ignored)`, err)\n }\n },\n }\n}\n\nfunction inactiveSpan(): NvmSpan {\n return {\n runTree: undefined,\n addMetadata() {\n /* no-op */\n },\n async end() {\n /* no-op */\n },\n }\n}\n\n/** Inputs accepted by {@link verifySpan}. */\nexport interface VerifySpanInput {\n planIds: string[]\n scheme?: string\n network?: string\n agentId?: string\n}\n\n/**\n * Open an `nvm:verify` child span around a verify call. Returns an\n * {@link NvmSpan} whose `end()` must be called once the verify completes (or\n * throws). Always safe — a no-op span is returned when tracing is inactive or\n * `langsmith` is not installed.\n */\nexport async function verifySpan(input: VerifySpanInput): Promise<NvmSpan> {\n const { planIds, scheme, network, agentId } = input\n const inputs: SpanMetadata = { plan_ids: [...planIds] }\n if (scheme) inputs.scheme = scheme\n if (network) inputs.network = network\n if (agentId) inputs.agent_id = agentId\n return openNvmSpan(NVM_VERIFY_SPAN, inputs)\n}\n\n/** Inputs accepted by {@link settlementSpan}. */\nexport interface SettlementSpanInput {\n planIds: string[]\n agentId?: string\n}\n\n/**\n * Open an `nvm:settlement` child span around a settle call. Same semantics as\n * {@link verifySpan}.\n */\nexport async function settlementSpan(input: SettlementSpanInput): Promise<NvmSpan> {\n const { planIds, agentId } = input\n const inputs: SpanMetadata = { plan_ids: [...planIds] }\n if (agentId) inputs.agent_id = agentId\n return openNvmSpan(NVM_SETTLEMENT_SPAN, inputs)\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nevermined-io/payments",
3
- "version": "1.4.1",
3
+ "version": "1.6.0",
4
4
  "description": "Typescript SDK to interact with the Nevermined Payments Protocol",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -35,6 +35,10 @@
35
35
  "./langchain": {
36
36
  "types": "./dist/x402/langchain/index.d.ts",
37
37
  "import": "./dist/x402/langchain/index.js"
38
+ },
39
+ "./langsmith": {
40
+ "types": "./dist/x402/langsmith/index.d.ts",
41
+ "import": "./dist/x402/langsmith/index.js"
38
42
  }
39
43
  },
40
44
  "scripts": {
@@ -60,6 +64,7 @@
60
64
  "@babel/core": "^7.27.4",
61
65
  "@babel/preset-env": "^7.27.2",
62
66
  "@google-cloud/aiplatform": "^6.1.0",
67
+ "@langchain/langgraph": "1.2.0",
63
68
  "@modelcontextprotocol/sdk": "^1.24.3",
64
69
  "@types/express": "4.17.23",
65
70
  "@types/jest": "^29.5.13",
@@ -79,6 +84,7 @@
79
84
  "eslint-plugin-tsdoc": "^0.5.2",
80
85
  "jest": "^29.7.0",
81
86
  "langchain": "^0.3.37",
87
+ "langsmith": "^0.7.4",
82
88
  "openai": "^6.2.0",
83
89
  "prettier": "^3.2.5",
84
90
  "source-map-support": "^0.5.21",
@@ -92,9 +98,11 @@
92
98
  "typescript": "^5.3.3"
93
99
  },
94
100
  "peerDependencies": {
101
+ "@langchain/core": ">=0.3.0",
102
+ "@langchain/langgraph": ">=1.0.0 <2",
95
103
  "@modelcontextprotocol/sdk": ">=1.25.0",
96
104
  "express": ">=4.0.0",
97
- "@langchain/core": ">=0.3.0"
105
+ "langsmith": ">=0.7.0"
98
106
  },
99
107
  "peerDependenciesMeta": {
100
108
  "@modelcontextprotocol/sdk": {
@@ -105,6 +113,12 @@
105
113
  },
106
114
  "@langchain/core": {
107
115
  "optional": true
116
+ },
117
+ "@langchain/langgraph": {
118
+ "optional": true
119
+ },
120
+ "langsmith": {
121
+ "optional": true
108
122
  }
109
123
  },
110
124
  "dependencies": {