@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,96 @@
1
+ /**
2
+ * LangGraph ReAct agent helper that surfaces `PaymentRequiredError` intact.
3
+ *
4
+ * By default `createReactAgent` from `@langchain/langgraph/prebuilt` constructs a
5
+ * `ToolNode` with `handleToolErrors: true` — tool exceptions are caught and
6
+ * rendered into a `ToolMessage` for the LLM. That is convenient for
7
+ * prompt-engineered recovery, but it stringifies the exception and loses the
8
+ * `X402PaymentRequired` payload attached to {@link PaymentRequiredError}.
9
+ * Without that payload the caller cannot run the x402 discovery flow (probe →
10
+ * read scheme/network/plan id → acquire token → retry).
11
+ *
12
+ * {@link createPaidReactAgent} builds the same agent but with a `ToolNode`
13
+ * configured to **re-raise** exceptions (`handleToolErrors: false`), so
14
+ * `PaymentRequiredError` propagates all the way back to `agent.invoke()`'s
15
+ * caller with `.paymentRequired` populated.
16
+ *
17
+ * `@langchain/langgraph` is imported **lazily** inside the function so the
18
+ * existing `peerDependencies` story is unchanged — users who only use the
19
+ * `requiresPayment` wrapper do not need LangGraph installed. Install it
20
+ * yourself (`pnpm add @langchain/langgraph`) to use this helper.
21
+ *
22
+ * Unlike Python's synchronous `create_paid_react_agent`, this helper is
23
+ * **`async`** — that is a deliberate consequence of the lazy `import()` that
24
+ * keeps `@langchain/langgraph` an optional peer, not a parity break. `await` it.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { tool } from '@langchain/core/tools'
29
+ * import { ChatOpenAI } from '@langchain/openai'
30
+ * import { z } from 'zod'
31
+ * import {
32
+ * PaymentRequiredError,
33
+ * createPaidReactAgent,
34
+ * requiresPayment,
35
+ * lastSettlement,
36
+ * } from '@nevermined-io/payments/langchain'
37
+ *
38
+ * const getMarketInsight = tool(
39
+ * requiresPayment(
40
+ * (args) => `Market insight for ${args.topic} ...`,
41
+ * { payments, planId: PLAN_ID, credits: 1 },
42
+ * ),
43
+ * { name: 'get_market_insight', description: 'Paid market insight', schema: z.object({ topic: z.string() }) },
44
+ * )
45
+ *
46
+ * const agent = await createPaidReactAgent(
47
+ * new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0 }),
48
+ * [getMarketInsight],
49
+ * { prompt: '...' },
50
+ * )
51
+ *
52
+ * // Discovery: invoke without a token to learn what to pay for.
53
+ * try {
54
+ * await agent.invoke({ messages: [...] }, { configurable: {} })
55
+ * } catch (err) {
56
+ * if (err instanceof PaymentRequiredError) {
57
+ * const accept = err.paymentRequired?.accepts[0]
58
+ * // ... acquire token using accept.planId / accept.scheme / accept.network ...
59
+ * }
60
+ * }
61
+ *
62
+ * const result = await agent.invoke(
63
+ * { messages: [...] },
64
+ * { configurable: { payment_token: token } },
65
+ * )
66
+ * const receipt = lastSettlement()
67
+ * ```
68
+ */
69
+ /**
70
+ * Options forwarded to the underlying `createReactAgent` call.
71
+ *
72
+ * Everything except `tools` (which this helper builds from the `tools`
73
+ * argument as a `ToolNode` with `handleToolErrors: false`) and `llm` (which
74
+ * this helper maps from the `model` argument) is passed through verbatim —
75
+ * `prompt`, `stateSchema`, `checkpointer`, `responseFormat`, etc.
76
+ */
77
+ export type CreatePaidReactAgentOptions = Record<string, unknown>;
78
+ /**
79
+ * Build a LangGraph ReAct agent that lets `PaymentRequiredError` propagate.
80
+ *
81
+ * Wraps `createReactAgent` from `@langchain/langgraph/prebuilt` with a
82
+ * `ToolNode(tools, { handleToolErrors: false })`. The signature mirrors the
83
+ * Python `create_paid_react_agent(model, tools, **kwargs)` helper: `model` is
84
+ * mapped to the JS `llm` parameter and any extra `options` are forwarded.
85
+ *
86
+ * @param model - The chat model (mapped to `createReactAgent`'s `llm` argument).
87
+ * @param tools - The LangChain tools, typically functions wrapped with
88
+ * `requiresPayment` and registered via `tool(...)`.
89
+ * @param options - Forwarded verbatim to `createReactAgent` (`prompt`,
90
+ * `stateSchema`, `checkpointer`, …).
91
+ * @returns The compiled ReAct agent graph, ready to be invoked with
92
+ * `agent.invoke(...)`.
93
+ * @throws If `@langchain/langgraph` is not installed.
94
+ */
95
+ export declare function createPaidReactAgent(model: unknown, tools: readonly unknown[], options?: CreatePaidReactAgentOptions): Promise<unknown>;
96
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../src/x402/langchain/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,2BAA2B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEjE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,OAAO,CAAC,CAyClB"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * LangGraph ReAct agent helper that surfaces `PaymentRequiredError` intact.
3
+ *
4
+ * By default `createReactAgent` from `@langchain/langgraph/prebuilt` constructs a
5
+ * `ToolNode` with `handleToolErrors: true` — tool exceptions are caught and
6
+ * rendered into a `ToolMessage` for the LLM. That is convenient for
7
+ * prompt-engineered recovery, but it stringifies the exception and loses the
8
+ * `X402PaymentRequired` payload attached to {@link PaymentRequiredError}.
9
+ * Without that payload the caller cannot run the x402 discovery flow (probe →
10
+ * read scheme/network/plan id → acquire token → retry).
11
+ *
12
+ * {@link createPaidReactAgent} builds the same agent but with a `ToolNode`
13
+ * configured to **re-raise** exceptions (`handleToolErrors: false`), so
14
+ * `PaymentRequiredError` propagates all the way back to `agent.invoke()`'s
15
+ * caller with `.paymentRequired` populated.
16
+ *
17
+ * `@langchain/langgraph` is imported **lazily** inside the function so the
18
+ * existing `peerDependencies` story is unchanged — users who only use the
19
+ * `requiresPayment` wrapper do not need LangGraph installed. Install it
20
+ * yourself (`pnpm add @langchain/langgraph`) to use this helper.
21
+ *
22
+ * Unlike Python's synchronous `create_paid_react_agent`, this helper is
23
+ * **`async`** — that is a deliberate consequence of the lazy `import()` that
24
+ * keeps `@langchain/langgraph` an optional peer, not a parity break. `await` it.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { tool } from '@langchain/core/tools'
29
+ * import { ChatOpenAI } from '@langchain/openai'
30
+ * import { z } from 'zod'
31
+ * import {
32
+ * PaymentRequiredError,
33
+ * createPaidReactAgent,
34
+ * requiresPayment,
35
+ * lastSettlement,
36
+ * } from '@nevermined-io/payments/langchain'
37
+ *
38
+ * const getMarketInsight = tool(
39
+ * requiresPayment(
40
+ * (args) => `Market insight for ${args.topic} ...`,
41
+ * { payments, planId: PLAN_ID, credits: 1 },
42
+ * ),
43
+ * { name: 'get_market_insight', description: 'Paid market insight', schema: z.object({ topic: z.string() }) },
44
+ * )
45
+ *
46
+ * const agent = await createPaidReactAgent(
47
+ * new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0 }),
48
+ * [getMarketInsight],
49
+ * { prompt: '...' },
50
+ * )
51
+ *
52
+ * // Discovery: invoke without a token to learn what to pay for.
53
+ * try {
54
+ * await agent.invoke({ messages: [...] }, { configurable: {} })
55
+ * } catch (err) {
56
+ * if (err instanceof PaymentRequiredError) {
57
+ * const accept = err.paymentRequired?.accepts[0]
58
+ * // ... acquire token using accept.planId / accept.scheme / accept.network ...
59
+ * }
60
+ * }
61
+ *
62
+ * const result = await agent.invoke(
63
+ * { messages: [...] },
64
+ * { configurable: { payment_token: token } },
65
+ * )
66
+ * const receipt = lastSettlement()
67
+ * ```
68
+ */
69
+ /**
70
+ * Build a LangGraph ReAct agent that lets `PaymentRequiredError` propagate.
71
+ *
72
+ * Wraps `createReactAgent` from `@langchain/langgraph/prebuilt` with a
73
+ * `ToolNode(tools, { handleToolErrors: false })`. The signature mirrors the
74
+ * Python `create_paid_react_agent(model, tools, **kwargs)` helper: `model` is
75
+ * mapped to the JS `llm` parameter and any extra `options` are forwarded.
76
+ *
77
+ * @param model - The chat model (mapped to `createReactAgent`'s `llm` argument).
78
+ * @param tools - The LangChain tools, typically functions wrapped with
79
+ * `requiresPayment` and registered via `tool(...)`.
80
+ * @param options - Forwarded verbatim to `createReactAgent` (`prompt`,
81
+ * `stateSchema`, `checkpointer`, …).
82
+ * @returns The compiled ReAct agent graph, ready to be invoked with
83
+ * `agent.invoke(...)`.
84
+ * @throws If `@langchain/langgraph` is not installed.
85
+ */
86
+ export async function createPaidReactAgent(model, tools, options = {}) {
87
+ // `llm` and `tools` are owned by this helper — `tools` carries the
88
+ // handleToolErrors:false ToolNode that makes PaymentRequiredError propagate,
89
+ // and overriding either would silently defeat the whole point of the helper
90
+ // (a caller-supplied `tools` re-enables the default handleToolErrors:true and
91
+ // the X402 payload is stringified away). Reject them up front, mirroring how
92
+ // Python's positional `create_paid_react_agent(model, tools, **kwargs)` raises
93
+ // a TypeError if `llm`/`tools` are passed again via kwargs.
94
+ if ('llm' in options || 'tools' in options) {
95
+ throw new Error('createPaidReactAgent: `llm` and `tools` are set from the `model` and ' +
96
+ '`tools` arguments and must not be passed in `options` (they would ' +
97
+ 'override the handleToolErrors:false ToolNode and break x402 discovery).');
98
+ }
99
+ let prebuilt;
100
+ try {
101
+ prebuilt = await import('@langchain/langgraph/prebuilt');
102
+ }
103
+ catch (err) {
104
+ throw new Error('createPaidReactAgent requires @langchain/langgraph. ' +
105
+ `Install it with \`pnpm add @langchain/langgraph\`. (${err instanceof Error ? err.message : String(err)})`);
106
+ }
107
+ const { ToolNode, createReactAgent } = prebuilt;
108
+ // handleToolErrors: false re-raises tool exceptions instead of stringifying
109
+ // them into a ToolMessage, so PaymentRequiredError reaches agent.invoke()'s
110
+ // caller with its X402PaymentRequired payload intact.
111
+ const toolNode = new ToolNode(tools, { handleToolErrors: false });
112
+ // `createReactAgent` is the current prebuilt entry point in
113
+ // @langchain/langgraph@1.2.0. It is marked @deprecated in favour of
114
+ // `createAgent`, but that replacement lives in the separate `langchain`
115
+ // package (out of scope for this SDK's optional langgraph peer), so the
116
+ // prebuilt `createReactAgent` is the deliberate, only in-package choice here.
117
+ // Spread `...options` FIRST so the protected `llm`/`tools` keys (set last)
118
+ // always win, even though the guard above already forbids them in `options`.
119
+ return createReactAgent({ ...options, llm: model, tools: toolNode });
120
+ }
121
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/x402/langchain/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAYH;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAc,EACd,KAAyB,EACzB,UAAuC,EAAE;IAEzC,mEAAmE;IACnE,6EAA6E;IAC7E,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,+EAA+E;IAC/E,4DAA4D;IAC5D,IAAI,KAAK,IAAI,OAAO,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CACb,uEAAuE;YACrE,oEAAoE;YACpE,yEAAyE,CAC5E,CAAA;IACH,CAAC;IAED,IAAI,QAAwD,CAAA;IAC5D,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAA;IAC1D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,sDAAsD;YACpD,uDACE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CACjD,GAAG,CACN,CAAA;IACH,CAAC;IACD,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,QAAQ,CAAA;IAE/C,4EAA4E;IAC5E,4EAA4E;IAC5E,sDAAsD;IACtD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,KAAc,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAA;IAC1E,4DAA4D;IAC5D,oEAAoE;IACpE,wEAAwE;IACxE,wEAAwE;IACxE,8EAA8E;IAC9E,2EAA2E;IAC3E,6EAA6E;IAC7E,OAAO,gBAAgB,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,KAAc,EAAE,KAAK,EAAE,QAAQ,EAAW,CAAC,CAAA;AACxF,CAAC","sourcesContent":["/**\n * LangGraph ReAct agent helper that surfaces `PaymentRequiredError` intact.\n *\n * By default `createReactAgent` from `@langchain/langgraph/prebuilt` constructs a\n * `ToolNode` with `handleToolErrors: true` — tool exceptions are caught and\n * rendered into a `ToolMessage` for the LLM. That is convenient for\n * prompt-engineered recovery, but it stringifies the exception and loses the\n * `X402PaymentRequired` payload attached to {@link PaymentRequiredError}.\n * Without that payload the caller cannot run the x402 discovery flow (probe →\n * read scheme/network/plan id → acquire token → retry).\n *\n * {@link createPaidReactAgent} builds the same agent but with a `ToolNode`\n * configured to **re-raise** exceptions (`handleToolErrors: false`), so\n * `PaymentRequiredError` propagates all the way back to `agent.invoke()`'s\n * caller with `.paymentRequired` populated.\n *\n * `@langchain/langgraph` is imported **lazily** inside the function so the\n * existing `peerDependencies` story is unchanged — users who only use the\n * `requiresPayment` wrapper do not need LangGraph installed. Install it\n * yourself (`pnpm add @langchain/langgraph`) to use this helper.\n *\n * Unlike Python's synchronous `create_paid_react_agent`, this helper is\n * **`async`** — that is a deliberate consequence of the lazy `import()` that\n * keeps `@langchain/langgraph` an optional peer, not a parity break. `await` it.\n *\n * @example\n * ```typescript\n * import { tool } from '@langchain/core/tools'\n * import { ChatOpenAI } from '@langchain/openai'\n * import { z } from 'zod'\n * import {\n * PaymentRequiredError,\n * createPaidReactAgent,\n * requiresPayment,\n * lastSettlement,\n * } from '@nevermined-io/payments/langchain'\n *\n * const getMarketInsight = tool(\n * requiresPayment(\n * (args) => `Market insight for ${args.topic} ...`,\n * { payments, planId: PLAN_ID, credits: 1 },\n * ),\n * { name: 'get_market_insight', description: 'Paid market insight', schema: z.object({ topic: z.string() }) },\n * )\n *\n * const agent = await createPaidReactAgent(\n * new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0 }),\n * [getMarketInsight],\n * { prompt: '...' },\n * )\n *\n * // Discovery: invoke without a token to learn what to pay for.\n * try {\n * await agent.invoke({ messages: [...] }, { configurable: {} })\n * } catch (err) {\n * if (err instanceof PaymentRequiredError) {\n * const accept = err.paymentRequired?.accepts[0]\n * // ... acquire token using accept.planId / accept.scheme / accept.network ...\n * }\n * }\n *\n * const result = await agent.invoke(\n * { messages: [...] },\n * { configurable: { payment_token: token } },\n * )\n * const receipt = lastSettlement()\n * ```\n */\n\n/**\n * Options forwarded to the underlying `createReactAgent` call.\n *\n * Everything except `tools` (which this helper builds from the `tools`\n * argument as a `ToolNode` with `handleToolErrors: false`) and `llm` (which\n * this helper maps from the `model` argument) is passed through verbatim —\n * `prompt`, `stateSchema`, `checkpointer`, `responseFormat`, etc.\n */\nexport type CreatePaidReactAgentOptions = Record<string, unknown>\n\n/**\n * Build a LangGraph ReAct agent that lets `PaymentRequiredError` propagate.\n *\n * Wraps `createReactAgent` from `@langchain/langgraph/prebuilt` with a\n * `ToolNode(tools, { handleToolErrors: false })`. The signature mirrors the\n * Python `create_paid_react_agent(model, tools, **kwargs)` helper: `model` is\n * mapped to the JS `llm` parameter and any extra `options` are forwarded.\n *\n * @param model - The chat model (mapped to `createReactAgent`'s `llm` argument).\n * @param tools - The LangChain tools, typically functions wrapped with\n * `requiresPayment` and registered via `tool(...)`.\n * @param options - Forwarded verbatim to `createReactAgent` (`prompt`,\n * `stateSchema`, `checkpointer`, …).\n * @returns The compiled ReAct agent graph, ready to be invoked with\n * `agent.invoke(...)`.\n * @throws If `@langchain/langgraph` is not installed.\n */\nexport async function createPaidReactAgent(\n model: unknown,\n tools: readonly unknown[],\n options: CreatePaidReactAgentOptions = {},\n): Promise<unknown> {\n // `llm` and `tools` are owned by this helper — `tools` carries the\n // handleToolErrors:false ToolNode that makes PaymentRequiredError propagate,\n // and overriding either would silently defeat the whole point of the helper\n // (a caller-supplied `tools` re-enables the default handleToolErrors:true and\n // the X402 payload is stringified away). Reject them up front, mirroring how\n // Python's positional `create_paid_react_agent(model, tools, **kwargs)` raises\n // a TypeError if `llm`/`tools` are passed again via kwargs.\n if ('llm' in options || 'tools' in options) {\n throw new Error(\n 'createPaidReactAgent: `llm` and `tools` are set from the `model` and ' +\n '`tools` arguments and must not be passed in `options` (they would ' +\n 'override the handleToolErrors:false ToolNode and break x402 discovery).',\n )\n }\n\n let prebuilt: typeof import('@langchain/langgraph/prebuilt')\n try {\n prebuilt = await import('@langchain/langgraph/prebuilt')\n } catch (err) {\n throw new Error(\n 'createPaidReactAgent requires @langchain/langgraph. ' +\n `Install it with \\`pnpm add @langchain/langgraph\\`. (${\n err instanceof Error ? err.message : String(err)\n })`,\n )\n }\n const { ToolNode, createReactAgent } = prebuilt\n\n // handleToolErrors: false re-raises tool exceptions instead of stringifying\n // them into a ToolMessage, so PaymentRequiredError reaches agent.invoke()'s\n // caller with its X402PaymentRequired payload intact.\n const toolNode = new ToolNode(tools as never, { handleToolErrors: false })\n // `createReactAgent` is the current prebuilt entry point in\n // @langchain/langgraph@1.2.0. It is marked @deprecated in favour of\n // `createAgent`, but that replacement lives in the separate `langchain`\n // package (out of scope for this SDK's optional langgraph peer), so the\n // prebuilt `createReactAgent` is the deliberate, only in-package choice here.\n // Spread `...options` FIRST so the protected `llm`/`tools` keys (set last)\n // always win, even though the guard above already forbids them in `options`.\n return createReactAgent({ ...options, llm: model as never, tools: toolNode } as never)\n}\n"]}
@@ -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
- /** Number of credits to charge, or a function for dynamic pricing (default: 1) */
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
- /** The x402 access token */
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,EAEzB,MAAM,uBAAuB,CAAA;AAE9B;;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,kFAAkF;IAClF,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,4BAA4B;IAC5B,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;AA8BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CAiFrD"}
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
- throw new PaymentRequiredError("Payment required: missing payment_token in config.configurable", paymentRequired);
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
- // Store payment context
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
- maxAmount: BigInt(finalCredits),
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;