langgraph-middleware 1.0.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/LICENSE +21 -0
- package/README.md +278 -0
- package/dist/catch.d.ts +22 -0
- package/dist/catch.d.ts.map +1 -0
- package/dist/catch.js +67 -0
- package/dist/catch.js.map +1 -0
- package/dist/compile.d.ts +2 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +45 -0
- package/dist/compile.js.map +1 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/inject.d.ts +22 -0
- package/dist/inject.d.ts.map +1 -0
- package/dist/inject.js +34 -0
- package/dist/inject.js.map +1 -0
- package/dist/middleware.d.ts +23 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +81 -0
- package/dist/middleware.js.map +1 -0
- package/dist/observe.d.ts +19 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/observe.js +33 -0
- package/dist/observe.js.map +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +26 -0
- package/dist/utils.js.map +1 -0
- package/dist/withNodes.d.ts +10 -0
- package/dist/withNodes.d.ts.map +1 -0
- package/dist/withNodes.js +12 -0
- package/dist/withNodes.js.map +1 -0
- package/package.json +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jake Hon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# langgraph-middleware
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/langgraph-middleware)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
|
|
7
|
+
**English** | [中文](doc/README.zh.md)
|
|
8
|
+
|
|
9
|
+
**langgraph-middleware** brings middleware-style utilities to LangGraph's `StateGraph` — add **before/after hooks**, **reusable inject modules**, **error catching**, and **observability callbacks** to your graph nodes with a clean chainable API.
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
| Method | Description |
|
|
14
|
+
|--------|-------------|
|
|
15
|
+
| `.middleware(name, before?, after?)` | Wraps a node with **before** / **after** hooks, forming a subgraph <br> Cannot be applied to the same node more than once <br> `before` hook **must not** return a `Command` (use the main node or `after` hook for Command-based routing) |
|
|
16
|
+
| `.inject(...modules)` | Registers reusable structure modules executed before `compile()` |
|
|
17
|
+
| `.observe(callbacks, ...nodes?)` | Attaches LangChain `BaseCallbackHandler` instances for tracing/logging |
|
|
18
|
+
| `.catch(handler, ...nodes?)` | Wraps nodes with try/catch error recovery <br>⚠️ **Do NOT** use on nodes that call `interrupt()` — `GraphInterrupt` will be rejected |
|
|
19
|
+
| `.withNodes(...names)` | Type-level helper to inform TypeScript of dynamically-added node names |
|
|
20
|
+
| `.compileAsync(...)` | Async version of `compile()` — supports async inject modules |
|
|
21
|
+
|
|
22
|
+
- All methods support method chaining
|
|
23
|
+
- Compatible with checkpointing, interrupt, streaming, and retryPolicy
|
|
24
|
+
|
|
25
|
+
## 📦 Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install langgraph-middleware @langchain/langgraph @langchain/core
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Use Cases
|
|
32
|
+
|
|
33
|
+
This library is ideal for:
|
|
34
|
+
|
|
35
|
+
- Quickly adding logging, validation, or state transformation before/after specific nodes
|
|
36
|
+
- Reusing the same graph structure across multiple projects (`.inject()`)
|
|
37
|
+
- Attaching tracing / callback handlers to specific nodes (`.observe()`)
|
|
38
|
+
- Rapidly adding/removing nodes and edges without verbose `addNode`/`addEdge` chains (`.inject()`, `.middleware()`)
|
|
39
|
+
- Wrapping a node in a try/catch block for graceful error recovery (`.catch()`)
|
|
40
|
+
- You accept that before/after hooks are implemented as a small internal subgraph (`.middleware()`)
|
|
41
|
+
|
|
42
|
+
### Not Recommended
|
|
43
|
+
|
|
44
|
+
- Heavy reliance on `.catch()` for error handling — prefer explicit error-handling nodes for complex logic
|
|
45
|
+
|
|
46
|
+
## 🚀 Quick Start
|
|
47
|
+
|
|
48
|
+
### Middleware
|
|
49
|
+
|
|
50
|
+
`.middleware()` internally turns your node into a small subgraph (`before_{name}` → `{name}` → `after_{name}`).
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
|
|
54
|
+
|
|
55
|
+
const GraphState = Annotation.Root({
|
|
56
|
+
input: Annotation<string>(),
|
|
57
|
+
logs: Annotation<string[]>({
|
|
58
|
+
reducer: (curr, next) => [...(curr ?? []), ...(next ?? [])],
|
|
59
|
+
default: () => [],
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const graph = new StateGraph(GraphState)
|
|
64
|
+
.addNode("process", async (state) => {
|
|
65
|
+
return { result: `done: ${state.input}` };
|
|
66
|
+
})
|
|
67
|
+
.addEdge(START, "process")
|
|
68
|
+
.addEdge("process", END)
|
|
69
|
+
.middleware(
|
|
70
|
+
"process",
|
|
71
|
+
// before hook
|
|
72
|
+
(state) => {
|
|
73
|
+
console.log("before:", state.input);
|
|
74
|
+
return { logs: ["starting"] };
|
|
75
|
+
},
|
|
76
|
+
// after hook
|
|
77
|
+
(state) => {
|
|
78
|
+
console.log("after:", state.result);
|
|
79
|
+
return { logs: ["finished"] };
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
.compile();
|
|
83
|
+
|
|
84
|
+
const result = await graph.invoke({ input: "hello" });
|
|
85
|
+
console.log(result);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Inject Modules
|
|
89
|
+
|
|
90
|
+
Create reusable modules that modify any graph before compilation:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { StateGraph, Annotation, START, END, type StateGraphWithInject } from "@langchain/langgraph";
|
|
94
|
+
|
|
95
|
+
const MyAnnotation = Annotation.Root({ value: Annotation<string>() });
|
|
96
|
+
|
|
97
|
+
// The second generic parameter tells TypeScript which node names the module adds
|
|
98
|
+
const addLoggingModule = (
|
|
99
|
+
graph: StateGraphWithInject<StateGraph<typeof MyAnnotation>, "logger" | "prepare_logger">
|
|
100
|
+
) => {
|
|
101
|
+
return graph
|
|
102
|
+
.addNode("prepare_logger", (state) => state)
|
|
103
|
+
.addNode("logger", async (state) => {
|
|
104
|
+
console.log("State:", state);
|
|
105
|
+
return {};
|
|
106
|
+
})
|
|
107
|
+
.addEdge(START, "prepare_logger")
|
|
108
|
+
.addEdge("prepare_logger", "logger");
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const graph = new StateGraph(MyAnnotation)
|
|
112
|
+
.addNode("main", mainNode)
|
|
113
|
+
.addEdge("logger", "main")
|
|
114
|
+
.addEdge("main", END)
|
|
115
|
+
.inject(addLoggingModule)
|
|
116
|
+
.inject(state => state) // Inline usage
|
|
117
|
+
.inject(moduleA, moduleB, moduleC) // Multiple modules at once
|
|
118
|
+
.compile(); // Use compileAsync() if you injected async modules
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> **Important timing note:** `.middleware()`, `.catch()`, and `.observe()` scan nodes at **builder-chain time**. Nodes added via `.inject()` are registered at **compile time** (later). To apply middleware/catch/observe to inject-added nodes, call those methods *inside* the inject module itself. See `examples/inject.ts` for patterns.
|
|
122
|
+
|
|
123
|
+
### Error Handling
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const graph = new StateGraph(MyAnnotation)
|
|
127
|
+
.addNode("risky", riskyNode)
|
|
128
|
+
.addEdge(START, "risky")
|
|
129
|
+
.addEdge("risky", END)
|
|
130
|
+
.catch(
|
|
131
|
+
(error, state, meta) => {
|
|
132
|
+
console.error(`Node failed:`, error);
|
|
133
|
+
return { fallback: true, error: String(error) };
|
|
134
|
+
},
|
|
135
|
+
"risky", // Omit to wrap all existing nodes, or list specific ones: "node1", "node2", ...
|
|
136
|
+
)
|
|
137
|
+
.compile();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Observability
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
144
|
+
|
|
145
|
+
class MyTimer extends BaseCallbackHandler {
|
|
146
|
+
name = "MyTimer";
|
|
147
|
+
async handleChainStart(_chain, _inputs, runId, _parentRunId, _tags, _metadata, _runType, name) {
|
|
148
|
+
console.log(`⏱️ [${name}] started`);
|
|
149
|
+
}
|
|
150
|
+
async handleChainEnd(_outputs, runId) {
|
|
151
|
+
console.log(`✅ [${runId.slice(0, 8)}] completed`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const graph = new StateGraph(MyAnnotation)
|
|
156
|
+
.addNode("step1", step1)
|
|
157
|
+
.addNode("step2", step2)
|
|
158
|
+
.addEdge(START, "step1")
|
|
159
|
+
.addEdge("step1", "step2")
|
|
160
|
+
.addEdge("step2", END)
|
|
161
|
+
.observe([new MyTimer()]) // Omitting node names applies to all nodes
|
|
162
|
+
.compile();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## API Reference
|
|
166
|
+
|
|
167
|
+
### `.middleware(name, before?, after?, options?)`
|
|
168
|
+
|
|
169
|
+
Attaches before/after hooks around a node. Internally creates a subgraph.
|
|
170
|
+
|
|
171
|
+
**Restrictions:**
|
|
172
|
+
- Called **at most once** per node name (repeated calls throw)
|
|
173
|
+
- `before` function **must not** return a `Command` (throws at runtime)
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
.middleware(
|
|
177
|
+
"myNode",
|
|
178
|
+
(state) => { /* before */ return { logs: ["start"] }; },
|
|
179
|
+
(state) => { /* after */ return { logs: ["end"] }; }
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The `after` hook supports `{ func, options }` form for retryPolicy or other node options. The `before` hook also supports this form. An optional 4th argument forwards compile options to the internal subgraph.
|
|
184
|
+
|
|
185
|
+
### `.inject(...modules)`
|
|
186
|
+
|
|
187
|
+
Registers reusable modules executed in order just before `compile()`. Modules receive the `StateGraph` instance and should return it for chaining.
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
.inject((graph) => {
|
|
191
|
+
return graph
|
|
192
|
+
.addNode("logger", (s) => { console.log(s); return {}; })
|
|
193
|
+
.addEdge(START, "logger");
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Supports both sync and async modules. Use `.compileAsync()` when async modules are present — using `.compile()` with async modules throws.
|
|
198
|
+
|
|
199
|
+
### `.catch(handler, ...names?)`
|
|
200
|
+
|
|
201
|
+
Wraps targeted nodes in try/catch. The handler receives `(error, state, meta)` where `meta` is the `RunnableConfig` (includes `metadata`, `executionInfo`, etc.).
|
|
202
|
+
|
|
203
|
+
- Handler returns a value → the node is treated as successful (short-circuits any remaining retries)
|
|
204
|
+
- Handler throws → the error propagates to LangGraph's normal retryPolicy logic
|
|
205
|
+
|
|
206
|
+
**⚠️ Do NOT use on nodes that call `interrupt()`.** If a `GraphInterrupt` is caught, it is immediately re-thrown as a regular Error to prevent checkpoint corruption.
|
|
207
|
+
|
|
208
|
+
If no node names are provided, all *currently registered* nodes are wrapped.
|
|
209
|
+
|
|
210
|
+
### `.observe(callbacks, ...names?)`
|
|
211
|
+
|
|
212
|
+
Attaches LangChain `BaseCallbackHandler` instances to nodes. The `runName` in callback events is set to the node name for easy identification in traces.
|
|
213
|
+
|
|
214
|
+
If no node names are provided, all *currently registered* nodes are observed.
|
|
215
|
+
|
|
216
|
+
### `.withNodes(...names)`
|
|
217
|
+
|
|
218
|
+
A type-level-only helper. Does nothing at runtime — it tells TypeScript that certain node names exist on the builder, enabling type inference after conditional `addNode` calls or `.inject()` usage.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
const builder = new StateGraph(GraphState)
|
|
222
|
+
.addNode("node1", fn);
|
|
223
|
+
|
|
224
|
+
if (someCondition) {
|
|
225
|
+
builder.addNode("node2", fn).addNode("node3", fn);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
builder
|
|
229
|
+
.withNodes("node2", "node3") // TypeScript now knows about these
|
|
230
|
+
.addEdge("__start__", "node1")
|
|
231
|
+
.addEdge("node1", "node2")
|
|
232
|
+
.addEdge("node2", "node3");
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### `.compileAsync(...args)`
|
|
236
|
+
|
|
237
|
+
Async version of `compile()`. Awaits all registered inject modules (including async ones) before compilation.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const graph = await builder.compileAsync(checkpointerOptions);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Examples
|
|
244
|
+
|
|
245
|
+
See the `examples/` directory for runnable demos:
|
|
246
|
+
|
|
247
|
+
| File | Description |
|
|
248
|
+
|------|-------------|
|
|
249
|
+
| `middleware.ts` | Before/after hooks with traceable wrappers and retryPolicy |
|
|
250
|
+
| `middleware-with-checkpointer.ts` | Middleware + checkpoint persistence, xray inspection |
|
|
251
|
+
| `inject.ts` | Async inject modules, conditional edges, error handler injection |
|
|
252
|
+
| `catch.ts` | Error recovery with catch(), multiple target nodes |
|
|
253
|
+
| `catch-with-retry.ts` | catch() + retryPolicy: conditional re-throw pattern |
|
|
254
|
+
| `catch-interrupt-limitation.ts` | Documents the catch + interrupt() incompatibility |
|
|
255
|
+
| `observe.ts` | Callback-based timing/monitoring |
|
|
256
|
+
| `withNodes.ts` | Type-level helper for dynamically-added nodes |
|
|
257
|
+
|
|
258
|
+
Run any example with:
|
|
259
|
+
```bash
|
|
260
|
+
npx tsx examples/<name>.ts
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Run Tests
|
|
264
|
+
|
|
265
|
+
All features are verified with comprehensive tests:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
npm test
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
|
|
273
|
+
MIT © jakehkw
|
|
274
|
+
|
|
275
|
+
## Links
|
|
276
|
+
|
|
277
|
+
- GitHub: https://github.com/jakehkw/langgraph-middleware
|
|
278
|
+
- npm: https://www.npmjs.com/package/langgraph-middleware
|
package/dist/catch.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type StateGraph } from '@langchain/langgraph';
|
|
2
|
+
import type { StateOf } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Wraps one or more nodes with try/catch error handling.
|
|
5
|
+
*
|
|
6
|
+
* When a wrapped node throws, the error handler is invoked with:
|
|
7
|
+
* - `error` — the thrown value
|
|
8
|
+
* - `state` — the state at the time of failure
|
|
9
|
+
* - `meta` — the metadata for the node
|
|
10
|
+
*
|
|
11
|
+
* The handler's return value is used as the node's output, allowing graceful
|
|
12
|
+
* recovery or state mutation on failure.
|
|
13
|
+
*
|
|
14
|
+
* @typeParam T - The `StateGraph` subtype
|
|
15
|
+
* @param this - The `StateGraph` instance
|
|
16
|
+
* @param handler - Error handler receiving (error, state, meta)
|
|
17
|
+
* @param names - Optional list of node names to wrap; if omitted, all nodes
|
|
18
|
+
* @returns `this` for chaining
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export declare function catchError<T extends StateGraph<any>>(this: T, handler: (error: unknown, state: Partial<StateOf<T>>, meta: any) => Partial<StateOf<T>> | Promise<Partial<StateOf<T>>>, ...names: string[]): T;
|
|
22
|
+
//# sourceMappingURL=catch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catch.d.ts","sourceRoot":"","sources":["../src/catch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAE,OAAO,EAAe,MAAM,YAAY,CAAC;AAGvD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,EAClD,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,CACP,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAC1B,IAAI,EAAE,GAAG,KACN,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EACvD,GAAG,KAAK,EAAE,MAAM,EAAE,KAkDnB"}
|
package/dist/catch.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { GraphInterrupt } from '@langchain/langgraph';
|
|
2
|
+
import { RunnableLambda } from '@langchain/core/runnables';
|
|
3
|
+
import { getNodes } from './utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Wraps one or more nodes with try/catch error handling.
|
|
6
|
+
*
|
|
7
|
+
* When a wrapped node throws, the error handler is invoked with:
|
|
8
|
+
* - `error` — the thrown value
|
|
9
|
+
* - `state` — the state at the time of failure
|
|
10
|
+
* - `meta` — the metadata for the node
|
|
11
|
+
*
|
|
12
|
+
* The handler's return value is used as the node's output, allowing graceful
|
|
13
|
+
* recovery or state mutation on failure.
|
|
14
|
+
*
|
|
15
|
+
* @typeParam T - The `StateGraph` subtype
|
|
16
|
+
* @param this - The `StateGraph` instance
|
|
17
|
+
* @param handler - Error handler receiving (error, state, meta)
|
|
18
|
+
* @param names - Optional list of node names to wrap; if omitted, all nodes
|
|
19
|
+
* @returns `this` for chaining
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export function catchError(handler, ...names) {
|
|
23
|
+
const nodes = this.nodes;
|
|
24
|
+
const targets = getNodes(names, nodes);
|
|
25
|
+
for (const [nodeName, spec] of Object.entries(targets)) {
|
|
26
|
+
// Design:
|
|
27
|
+
// We always run the original runnable first.
|
|
28
|
+
// If it throws, we immediately give the error to the user's handler.
|
|
29
|
+
//
|
|
30
|
+
// - If handler returns a value → this attempt is treated as success.
|
|
31
|
+
// LangGraph will NOT retry further for this task (even if retries remained).
|
|
32
|
+
// - If handler throws → the error propagates to LangGraph, which will then
|
|
33
|
+
// apply its normal retryPolicy logic for the next attempt (if any remain).
|
|
34
|
+
//
|
|
35
|
+
// Trade-off:
|
|
36
|
+
// This means that for nodes with retryPolicy, using .catch will cause the
|
|
37
|
+
// handler to be invoked on the FIRST error (not only the last).
|
|
38
|
+
// Returning a value from the handler will short-circuit any remaining retries.
|
|
39
|
+
//
|
|
40
|
+
// This is the cleanest and most predictable behavior we can achieve at the
|
|
41
|
+
// current wrapper level while still giving the user powerful control.
|
|
42
|
+
const wrappedRunnable = RunnableLambda.from(async (state, config) => {
|
|
43
|
+
try {
|
|
44
|
+
return await spec.runnable.invoke(state, config);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (err instanceof GraphInterrupt) {
|
|
48
|
+
throw new Error(`[.catch] Caught a GraphInterrupt in node "${nodeName}".
|
|
49
|
+
This likely means you are using .catch() on a node that calls interrupt(), which is not supported.
|
|
50
|
+
The interrupt will be treated as a normal error and passed to your handler,
|
|
51
|
+
which can lead to unexpected behavior.
|
|
52
|
+
Please remove .catch() from this node or ensure it does not call interrupt().`);
|
|
53
|
+
}
|
|
54
|
+
const result = await Promise.resolve(handler(err, state, config));
|
|
55
|
+
// If the handler itself throws, it will propagate out of this RunnableLambda.
|
|
56
|
+
// LangGraph will then treat it according to its normal error/retry rules.
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
nodes[nodeName] = {
|
|
61
|
+
...spec,
|
|
62
|
+
runnable: wrappedRunnable,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=catch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catch.js","sourceRoot":"","sources":["../src/catch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAmB,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,UAAU,CAExB,OAIuD,EACvD,GAAG,KAAe;IAElB,MAAM,KAAK,GAAI,IAAY,CAAC,KAAoC,CAAC;IACjE,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAEvC,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,UAAU;QACV,6CAA6C;QAC7C,qEAAqE;QACrE,EAAE;QACF,qEAAqE;QACrE,+EAA+E;QAC/E,2EAA2E;QAC3E,6EAA6E;QAC7E,EAAE;QACF,aAAa;QACb,0EAA0E;QAC1E,gEAAgE;QAChE,+EAA+E;QAC/E,EAAE;QACF,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM,eAAe,GAAG,cAAc,CAAC,IAAI,CACzC,KAAK,EAAE,KAAiB,EAAE,MAAY,EAAE,EAAE;YACxC,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YACnD,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;oBAClC,MAAM,IAAI,KAAK,CAAC,6CAA6C,QAAQ;;;;4FAIW,CAAC,CAAC;gBACpF,CAAC;gBAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;gBAClE,8EAA8E;gBAC9E,0EAA0E;gBAC1E,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC,CACF,CAAC;QAEF,KAAK,CAAC,QAAQ,CAAC,GAAG;YAChB,GAAG,IAAI;YACP,QAAQ,EAAE,eAAe;SAC1B,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":""}
|
package/dist/compile.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { StateGraph } from '@langchain/langgraph';
|
|
2
|
+
import { getInjectModules } from './inject.js';
|
|
3
|
+
/** Reference to the original `StateGraph.prototype.compile` method. */
|
|
4
|
+
const originalCompile = StateGraph.prototype.compile;
|
|
5
|
+
/**
|
|
6
|
+
* Overrides `StateGraph.prototype.compile` to execute all registered inject
|
|
7
|
+
* modules before the original compilation logic runs.
|
|
8
|
+
*
|
|
9
|
+
* **Important:** Async inject modules are NOT supported here — use
|
|
10
|
+
* `.compileAsync()` for modules that return a `Promise`.
|
|
11
|
+
*
|
|
12
|
+
* @param args - Arguments forwarded to the original `compile()` method
|
|
13
|
+
* @returns A compiled state graph
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
StateGraph.prototype.compile = function (...args) {
|
|
17
|
+
const self = this;
|
|
18
|
+
const modules = getInjectModules(self);
|
|
19
|
+
for (const mod of modules) {
|
|
20
|
+
const result = mod(self);
|
|
21
|
+
if (result instanceof Promise) {
|
|
22
|
+
throw new Error('Async module detected in StateGraph.inject(). ' +
|
|
23
|
+
'Module must be synchronous when using .compile() — ' +
|
|
24
|
+
'if you need async setup, use .compileAsync() instead.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return originalCompile.apply(self, args);
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Implementation of `StateGraph.prototype.compileAsync`
|
|
31
|
+
* Execute all registered inject modules (including async ones) before compilation.
|
|
32
|
+
*
|
|
33
|
+
* @param args - Arguments forwarded to the original `compile()` method
|
|
34
|
+
* @returns A Promise resolving to a compiled state graph
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
StateGraph.prototype.compileAsync = async function (...args) {
|
|
38
|
+
const self = this;
|
|
39
|
+
const modules = getInjectModules(self);
|
|
40
|
+
for (const mod of modules) {
|
|
41
|
+
await Promise.resolve(mod(self));
|
|
42
|
+
}
|
|
43
|
+
return originalCompile.apply(self, args);
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=compile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.js","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,uEAAuE;AACvE,MAAM,eAAe,GAAG,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC;AAErD;;;;;;;;;;GAUG;AACF,UAAU,CAAC,SAAiB,CAAC,OAAO,GAAG,UAAU,GAAG,IAAe;IAClE,MAAM,IAAI,GAAG,IAAW,CAAC;IACzB,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAEvC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,gDAAgD;gBAC9C,qDAAqD;gBACrD,uDAAuD,CAC1D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,IAAW,CAAC,CAAC;AAClD,CAAC,CAAC;AAEF;;;;;;;GAOG;AACF,UAAU,CAAC,SAAiB,CAAC,YAAY,GAAG,KAAK,WAAW,GAAG,IAAe;IAC7E,MAAM,IAAI,GAAG,IAAW,CAAC;IACzB,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAEvC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,eAAe,CAAC,KAAK,CAAC,IAAW,EAAE,IAAW,CAAsC,CAAC;AAC9F,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* langgraph-middleware
|
|
3
|
+
* ===================
|
|
4
|
+
* Middleware utilities for LangGraph's `StateGraph`.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import type { START, CompiledStateGraph, StateDefinitionInit, ExtractStateType, ExtractUpdateType, StateDefinition, ToStateDefinition, AnyStateSchema } from '@langchain/langgraph';
|
|
9
|
+
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
10
|
+
import type { StateOf, NodeNamesOf, ExtractStateGraphModule, LastFunction, StateCallback, MiddleHook } from './types';
|
|
11
|
+
type ExtractStateDefinition<T> = T extends AnyStateSchema ? T : T extends StateDefinitionInit ? ToStateDefinition<T> : StateDefinition;
|
|
12
|
+
declare module '@langchain/langgraph' {
|
|
13
|
+
type StateGraphWithInject<T, NewNodes extends string | readonly string[] = typeof START> = T extends StateGraph<infer A, infer B, infer C, infer Node, infer D, infer E, infer F, infer G, infer H, infer I> ? StateGraph<A, B, C, Node | (NewNodes extends readonly string[] ? NewNodes[number] : NewNodes), D, E, F, G, H, I> : never;
|
|
14
|
+
interface StateGraph<SD extends StateDefinitionInit | unknown, S = ExtractStateType<SD>, U = ExtractUpdateType<SD, S>, N extends string = typeof START, I extends StateDefinitionInit = ExtractStateDefinition<SD>, O extends StateDefinitionInit = ExtractStateDefinition<SD>, C extends StateDefinitionInit = StateDefinition, NodeReturnType = unknown, InterruptType = unknown, WriterType = unknown> {
|
|
15
|
+
/**
|
|
16
|
+
* Implementation of `StateGraph.prototype.inject`.
|
|
17
|
+
*
|
|
18
|
+
* Registers one or more functions that will be executed in order before the graph is compiled.
|
|
19
|
+
* Supports method chaining.
|
|
20
|
+
* @param modules - One or more inject modules containing the graph instance in its param.
|
|
21
|
+
* @returns `this` for chaining
|
|
22
|
+
*/
|
|
23
|
+
inject<T extends ((p: this) => this)[]>(...modules: T): this;
|
|
24
|
+
inject<T extends any[], U extends ExtractStateGraphModule<LastFunction<T[number]>>, const Nodes extends NodeNamesOf<Parameters<U>[0]>>(...modules: T): StateGraphWithInject<StateGraph<SD, S, ExtractUpdateType<SD, S>, NodeNamesOf<this> | N, ExtractStateDefinition<SD>, ExtractStateDefinition<SD>, StateDefinition, unknown, unknown, unknown>, Nodes>;
|
|
25
|
+
/**
|
|
26
|
+
* Wraps one or more nodes with try/catch error handling.
|
|
27
|
+
*
|
|
28
|
+
* When a wrapped node throws, the error handler is invoked with:
|
|
29
|
+
* - `error` — the thrown value
|
|
30
|
+
* - `state` — the state at the time of failure
|
|
31
|
+
* - `meta` — the RunnableConfig (with metadata, executionInfo, etc.)
|
|
32
|
+
*
|
|
33
|
+
* The handler's return value is used as the node's output, allowing graceful
|
|
34
|
+
* recovery or state mutation on failure.
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ IMPORTANT RESTRICTION:
|
|
37
|
+
* You **cannot** use `.catch()` on nodes that may call `interrupt()`.
|
|
38
|
+
* If `GraphInterrupt` is caught, it is immediately re-thrown as a regular
|
|
39
|
+
* Error to prevent silent checkpoint corruption. The handler will NOT receive it.
|
|
40
|
+
*
|
|
41
|
+
* @param handler - Error handler receiving (error, state, meta)
|
|
42
|
+
* @param names - Optional list of node names to wrap; if omitted, all existing nodes
|
|
43
|
+
* @returns `this` for chaining
|
|
44
|
+
*/
|
|
45
|
+
catch(handler: (error: any, state: Partial<StateOf<this>>, meta: any) => Partial<StateOf<this>> | Promise<Partial<StateOf<this>>>, ...names: NodeNamesOf<this>[]): this;
|
|
46
|
+
/**
|
|
47
|
+
* Attaches LangChain callback handlers to one or more nodes for
|
|
48
|
+
* observability (logging, tracing, monitoring).
|
|
49
|
+
*
|
|
50
|
+
* Each targeted node is wrapped so that its execution is reported through
|
|
51
|
+
* the provided callbacks. The `runName` in callback events is set to the
|
|
52
|
+
* node name for easy identification.
|
|
53
|
+
*
|
|
54
|
+
* @param callbacks - Array of callback handlers to attach
|
|
55
|
+
* @param names - Optional list of node names to observe; if omitted, all nodes
|
|
56
|
+
* @returns `this` for chaining
|
|
57
|
+
*/
|
|
58
|
+
observe(callbacks: BaseCallbackHandler[], ...names: NodeNamesOf<this>[]): this;
|
|
59
|
+
/**
|
|
60
|
+
* Attach before/after hooks around a node.
|
|
61
|
+
*
|
|
62
|
+
* Restrictions (enforced):
|
|
63
|
+
* - You may call .middleware() **at most once** per node name on a given builder.
|
|
64
|
+
* Nested / repeated calls on the same node are rejected (prevents non-linear execution and
|
|
65
|
+
* exponential before-hook duplication that occurs with deep nesting).
|
|
66
|
+
* - The `before` function **must not** return a `Command`. Doing so will throw at runtime.
|
|
67
|
+
* Use the main node or the `after` hook for any Command-based routing.
|
|
68
|
+
*
|
|
69
|
+
* @param name - Name of a previously registered node
|
|
70
|
+
* @param before - Optional before hook (plain function or { func, options })
|
|
71
|
+
* @param after - Optional after hook (plain function or { func, options })
|
|
72
|
+
* @param options - Compile options for the internal subgraph (advanced)
|
|
73
|
+
* @returns `this` for chaining
|
|
74
|
+
*/
|
|
75
|
+
middleware<T = MiddleHook<StateCallback<StateOf<this>>, StateOf<this>>, U = T, V = T>(name: NodeNamesOf<this>, before?: U, after?: V, options?: Parameters<this['compile']>[0]): this;
|
|
76
|
+
/**
|
|
77
|
+
* Implementation of `StateGraph.prototype.compileAsync`
|
|
78
|
+
* Execute all registered inject modules (including async ones) before compilation.
|
|
79
|
+
*
|
|
80
|
+
* @param args - Arguments forwarded to the original `compile()` method
|
|
81
|
+
* @returns A Promise resolving to a compiled state graph
|
|
82
|
+
*/
|
|
83
|
+
compileAsync(...args: any[]): Promise<CompiledStateGraph<any, any, any>>;
|
|
84
|
+
/**
|
|
85
|
+
* A type-level only chain method to explicitly tell TypeScript
|
|
86
|
+
* that certain node names now exist on this builder.
|
|
87
|
+
*
|
|
88
|
+
* This is useful after .inject() or stepwise .addNode() calls,
|
|
89
|
+
* where the variable's static type hasn't automatically picked up
|
|
90
|
+
* the newly added nodes.
|
|
91
|
+
*
|
|
92
|
+
* Example:
|
|
93
|
+
* const b = new StateGraph(GraphState)
|
|
94
|
+
* .inject(addTimestampModule)
|
|
95
|
+
* .withNodes<'timestamp'>();
|
|
96
|
+
*
|
|
97
|
+
* After this, NodeNamesOf<typeof b> will include "timestamp".
|
|
98
|
+
*/
|
|
99
|
+
withNodes<Nodes extends string[]>(...names: Nodes): StateGraphWithInject<StateGraph<SD, S, ExtractUpdateType<SD, S>, NodeNamesOf<this>, ExtractStateDefinition<SD>, ExtractStateDefinition<SD>, StateDefinition, unknown, unknown, unknown>, Nodes>;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
import './compile.js';
|
|
103
|
+
export type { StateOf, NodeNamesOf } from './types.js';
|
|
104
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* langgraph-middleware
|
|
3
|
+
* ===================
|
|
4
|
+
* Middleware utilities for LangGraph's `StateGraph`.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
10
|
+
// Apply prototype augmentations (side effects)
|
|
11
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
12
|
+
import { StateGraph } from '@langchain/langgraph';
|
|
13
|
+
import './compile.js';
|
|
14
|
+
import { inject } from './inject.js';
|
|
15
|
+
import { middleware } from './middleware.js';
|
|
16
|
+
import { catchError } from './catch.js';
|
|
17
|
+
import { observe } from './observe.js';
|
|
18
|
+
import { withNodes } from './withNodes';
|
|
19
|
+
const proto = StateGraph.prototype;
|
|
20
|
+
if (!Object.prototype.hasOwnProperty.call(proto, 'inject')) {
|
|
21
|
+
proto.inject = inject;
|
|
22
|
+
}
|
|
23
|
+
if (!Object.prototype.hasOwnProperty.call(proto, 'middleware')) {
|
|
24
|
+
proto.middleware = middleware;
|
|
25
|
+
}
|
|
26
|
+
if (!Object.prototype.hasOwnProperty.call(proto, 'catch')) {
|
|
27
|
+
proto.catch = catchError;
|
|
28
|
+
}
|
|
29
|
+
if (!Object.prototype.hasOwnProperty.call(proto, 'observe')) {
|
|
30
|
+
proto.observe = observe;
|
|
31
|
+
}
|
|
32
|
+
if (!Object.prototype.hasOwnProperty.call(proto, 'withNodes')) {
|
|
33
|
+
proto.withNodes = withNodes;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAiJrE,0EAA0E;AAC1E,+CAA+C;AAC/C,0EAA0E;AAE1E,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,cAAc,CAAC;AACtB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,KAAK,GAAG,UAAU,CAAC,SAAgB,CAAC;AAE1C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;IAC3D,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;AACxB,CAAC;AACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,EAAE,CAAC;IAC/D,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC;AAChC,CAAC;AACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;IAC1D,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC;AAC3B,CAAC;AACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC5D,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;AAC1B,CAAC;AACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;IAC9D,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;AAC9B,CAAC"}
|
package/dist/inject.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { StateGraph } from '@langchain/langgraph';
|
|
2
|
+
type Graph = StateGraph<any>;
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves (or creates) the inject-module queue for a given `StateGraph`
|
|
5
|
+
* instance.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare function getInjectModules(graph: Graph): ((...p: any) => any)[];
|
|
9
|
+
/**
|
|
10
|
+
* Implementation of `StateGraph.prototype.inject`.
|
|
11
|
+
*
|
|
12
|
+
* Registers one or more functions that will be executed in order before the graph is compiled.
|
|
13
|
+
* Supports method chaining.
|
|
14
|
+
*
|
|
15
|
+
* @param this - The `StateGraph` instance
|
|
16
|
+
* @param modules - One or more inject modules
|
|
17
|
+
* @returns `this` for chaining
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function inject(this: Graph, ...modules: ((...p: any) => any)[]): Graph;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=inject.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inject.d.ts","sourceRoot":"","sources":["../src/inject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,KAAK,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;AAU5B;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,GACX,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,GAAG,CAAC,EAAE,CAOxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CACpB,IAAI,EAAE,KAAK,EACX,GAAG,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,GAAG,CAAC,EAAE,SAKnC"}
|
package/dist/inject.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// ── Per-graph inject module queue ─────────────────────────────────────
|
|
2
|
+
// We use a WeakMap keyed on each StateGraph instance so that modules are
|
|
3
|
+
// garbage-collected when the graph is no longer referenced.
|
|
4
|
+
const modulesMap = new WeakMap();
|
|
5
|
+
/**
|
|
6
|
+
* Retrieves (or creates) the inject-module queue for a given `StateGraph`
|
|
7
|
+
* instance.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export function getInjectModules(graph) {
|
|
11
|
+
let list = modulesMap.get(graph);
|
|
12
|
+
if (!list) {
|
|
13
|
+
list = [];
|
|
14
|
+
modulesMap.set(graph, list);
|
|
15
|
+
}
|
|
16
|
+
return list;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Implementation of `StateGraph.prototype.inject`.
|
|
20
|
+
*
|
|
21
|
+
* Registers one or more functions that will be executed in order before the graph is compiled.
|
|
22
|
+
* Supports method chaining.
|
|
23
|
+
*
|
|
24
|
+
* @param this - The `StateGraph` instance
|
|
25
|
+
* @param modules - One or more inject modules
|
|
26
|
+
* @returns `this` for chaining
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export function inject(...modules) {
|
|
30
|
+
const list = getInjectModules(this);
|
|
31
|
+
list.push(...modules);
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=inject.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inject.js","sourceRoot":"","sources":["../src/inject.ts"],"names":[],"mappings":"AAIA,yEAAyE;AACzE,yEAAyE;AACzE,4DAA4D;AAC5D,MAAM,UAAU,GAAG,IAAI,OAAO,EAG3B,CAAC;AAEJ;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAY;IAEZ,IAAI,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,EAAE,CAAC;QACV,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,MAAM,CAEpB,GAAG,OAA+B;IAElC,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;IACtB,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { StateGraph } from '@langchain/langgraph';
|
|
2
|
+
import type { MiddleHook, StateCallback, StateOf } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Attach before/after hooks around a node.
|
|
5
|
+
*
|
|
6
|
+
* Restrictions (enforced):
|
|
7
|
+
* - You may call .middleware() **at most once** per node name on a given builder.
|
|
8
|
+
* Nested / repeated calls on the same node are rejected (prevents non-linear execution and
|
|
9
|
+
* exponential before-hook duplication that occurs with deep nesting).
|
|
10
|
+
* - The `before` function **must not** return a `Command`. Doing so will throw at runtime.
|
|
11
|
+
* Use the main node or the `after` hook for any Command-based routing.
|
|
12
|
+
*
|
|
13
|
+
* @typeParam T - The `StateGraph` subtype
|
|
14
|
+
* @param this - The `StateGraph` instance
|
|
15
|
+
* @param name - Name of a previously registered node
|
|
16
|
+
* @param before - Hook executed before the original node (optional)
|
|
17
|
+
* @param after - Hook executed after the original node (optional)
|
|
18
|
+
* @param options - Compile options for the internal subgraph (advanced)
|
|
19
|
+
* @returns `this` for chaining
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export declare function middleware<T extends StateGraph<any>>(this: T, name: string, before?: MiddleHook<StateCallback<StateOf<T>>, StateOf<T>>, after?: MiddleHook<StateCallback<StateOf<T>>, StateOf<T>>, options?: Parameters<T['compile']>[0]): T;
|
|
23
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,UAAU,EAA4B,MAAM,sBAAsB,CAAC;AACxF,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAalE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,EAClD,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAC1D,KAAK,CAAC,EAAE,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EACzD,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,KAmEtC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { END, START, StateGraph, isCommand } from '@langchain/langgraph';
|
|
2
|
+
const MIDDLEWARE_WRAPPED = Symbol('middlewareWrapped');
|
|
3
|
+
const extract = (par) => {
|
|
4
|
+
if (typeof par === 'function') {
|
|
5
|
+
return { func: par, options: {} };
|
|
6
|
+
}
|
|
7
|
+
return { func: par.func, options: par.options ?? {} };
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Attach before/after hooks around a node.
|
|
11
|
+
*
|
|
12
|
+
* Restrictions (enforced):
|
|
13
|
+
* - You may call .middleware() **at most once** per node name on a given builder.
|
|
14
|
+
* Nested / repeated calls on the same node are rejected (prevents non-linear execution and
|
|
15
|
+
* exponential before-hook duplication that occurs with deep nesting).
|
|
16
|
+
* - The `before` function **must not** return a `Command`. Doing so will throw at runtime.
|
|
17
|
+
* Use the main node or the `after` hook for any Command-based routing.
|
|
18
|
+
*
|
|
19
|
+
* @typeParam T - The `StateGraph` subtype
|
|
20
|
+
* @param this - The `StateGraph` instance
|
|
21
|
+
* @param name - Name of a previously registered node
|
|
22
|
+
* @param before - Hook executed before the original node (optional)
|
|
23
|
+
* @param after - Hook executed after the original node (optional)
|
|
24
|
+
* @param options - Compile options for the internal subgraph (advanced)
|
|
25
|
+
* @returns `this` for chaining
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export function middleware(name, before, after, options) {
|
|
29
|
+
const nodes = this.nodes;
|
|
30
|
+
const nodeSpec = nodes[name];
|
|
31
|
+
if (!nodeSpec) {
|
|
32
|
+
throw new Error(`Node "${name}" does not exist on this graph. ` +
|
|
33
|
+
`Register it first with .addNode("${name}", ...)`);
|
|
34
|
+
}
|
|
35
|
+
// Block nested middleware on the same node (prevents exponential before-hook duplication)
|
|
36
|
+
if (nodeSpec[MIDDLEWARE_WRAPPED]) {
|
|
37
|
+
throw new Error(`Cannot apply .middleware() to node "${name}" — it has already been wrapped by a previous .middleware() call.\n` +
|
|
38
|
+
'Nested middleware on the same node is not supported.\n' +
|
|
39
|
+
'If you need multiple before/after stages, compose the logic inside a single .middleware() call or use explicit nodes + edges instead.');
|
|
40
|
+
}
|
|
41
|
+
const subGraph = new StateGraph(this.channels).addNode(name, nodeSpec.runnable);
|
|
42
|
+
if (before) {
|
|
43
|
+
const { func, options: nodeOptions } = extract(before);
|
|
44
|
+
// Guard: Before hooks must never return Command (leads to surprising non-native behavior)
|
|
45
|
+
const guardedBefore = async (state, config) => {
|
|
46
|
+
const result = await Promise.resolve(func(state, config));
|
|
47
|
+
if (isCommand(result)) {
|
|
48
|
+
throw new Error(`Before hooks passed to .middleware("${name}", beforeFn, ...) are not allowed to return Command objects.\n` +
|
|
49
|
+
'Returning Command from a before hook does not behave reliably (the original node may still execute).\n' +
|
|
50
|
+
'Please perform routing with Command from the main node function or the after hook instead.');
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
subGraph
|
|
55
|
+
.addNode('before_' + name, guardedBefore, nodeOptions)
|
|
56
|
+
.addEdge(START, 'before_' + name)
|
|
57
|
+
.addEdge('before_' + name, name);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
subGraph.addEdge(START, name);
|
|
61
|
+
}
|
|
62
|
+
if (after) {
|
|
63
|
+
const { func, options: nodeOptions } = extract(after);
|
|
64
|
+
subGraph
|
|
65
|
+
.addNode('after_' + name, func, nodeOptions)
|
|
66
|
+
.addEdge(name, 'after_' + name)
|
|
67
|
+
.addEdge('after_' + name, END);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
subGraph.addEdge(name, END);
|
|
71
|
+
}
|
|
72
|
+
delete nodes[name];
|
|
73
|
+
this.addNode(name, subGraph.compile(options));
|
|
74
|
+
// Mark the newly wrapped node so future .middleware() calls on it are rejected
|
|
75
|
+
const newSpec = nodes[name];
|
|
76
|
+
if (newSpec) {
|
|
77
|
+
newSpec[MIDDLEWARE_WRAPPED] = true;
|
|
78
|
+
}
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StateGraph } from '@langchain/langgraph';
|
|
2
|
+
import type { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
3
|
+
/**
|
|
4
|
+
* Attaches LangChain callback handlers to one or more nodes for
|
|
5
|
+
* observability (logging, tracing, monitoring).
|
|
6
|
+
*
|
|
7
|
+
* Each targeted node is wrapped so that its execution is reported through
|
|
8
|
+
* the provided callbacks. The `runName` in callback events is set to the
|
|
9
|
+
* node name for easy identification.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam T - The `StateGraph` subtype
|
|
12
|
+
* @param this - The `StateGraph` instance
|
|
13
|
+
* @param callbacks - Array of callback handlers to attach
|
|
14
|
+
* @param names - Optional list of node names to observe; if omitted, all nodes
|
|
15
|
+
* @returns `this` for chaining
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export declare function observe<T extends StateGraph<any>>(this: T, callbacks: BaseCallbackHandler[], ...names: string[]): T;
|
|
19
|
+
//# sourceMappingURL=observe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../src/observe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAY,MAAM,sBAAsB,CAAC;AAGjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAM1E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,EAC/C,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,mBAAmB,EAAE,EAChC,GAAG,KAAK,EAAE,MAAM,EAAE,KAqBnB"}
|
package/dist/observe.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { RunnableLambda } from '@langchain/core/runnables';
|
|
2
|
+
import { getNodes } from './utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* Attaches LangChain callback handlers to one or more nodes for
|
|
5
|
+
* observability (logging, tracing, monitoring).
|
|
6
|
+
*
|
|
7
|
+
* Each targeted node is wrapped so that its execution is reported through
|
|
8
|
+
* the provided callbacks. The `runName` in callback events is set to the
|
|
9
|
+
* node name for easy identification.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam T - The `StateGraph` subtype
|
|
12
|
+
* @param this - The `StateGraph` instance
|
|
13
|
+
* @param callbacks - Array of callback handlers to attach
|
|
14
|
+
* @param names - Optional list of node names to observe; if omitted, all nodes
|
|
15
|
+
* @returns `this` for chaining
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export function observe(callbacks, ...names) {
|
|
19
|
+
const nodes = this.nodes;
|
|
20
|
+
const targets = getNodes(names, nodes);
|
|
21
|
+
for (const [nodeName, spec] of Object.entries(targets)) {
|
|
22
|
+
const wrappedRunnable = RunnableLambda.from(async (state, config) => spec.runnable.invoke(state, config)).withConfig({
|
|
23
|
+
callbacks,
|
|
24
|
+
runName: nodeName,
|
|
25
|
+
});
|
|
26
|
+
nodes[nodeName] = {
|
|
27
|
+
...spec,
|
|
28
|
+
runnable: wrappedRunnable,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=observe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observe.js","sourceRoot":"","sources":["../src/observe.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,OAAO,CAErB,SAAgC,EAChC,GAAG,KAAe;IAElB,MAAM,KAAK,GAAI,IAAY,CAAC,KAAoC,CAAC;IACjE,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAEvC,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,MAAM,eAAe,GAAG,cAAc,CAAC,IAAI,CACzC,KAAK,EAAE,KAAiB,EAAE,MAAuB,EAAE,EAAE,CACnD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CACtC,CAAC,UAAU,CAAC;YACX,SAAS;YACT,OAAO,EAAE,QAAQ;SAClB,CAAC,CAAC;QAEH,KAAK,CAAC,QAAQ,CAAC,GAAG;YAChB,GAAG,IAAI;YACP,QAAQ,EAAE,eAAe;SAC1B,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { END, NodeSpec, START, StateGraph, StateGraphAddNodeOptions } from '@langchain/langgraph';
|
|
2
|
+
import type { TraceableFunction } from 'langsmith/traceable';
|
|
3
|
+
/**
|
|
4
|
+
* Extract all states from StateGraph
|
|
5
|
+
*/
|
|
6
|
+
export type StateOf<T> = T extends StateGraph<any, infer S, any, any, any, any, any, any, any, any> ? S : never;
|
|
7
|
+
/**
|
|
8
|
+
* Extract all node names from StateGraph (exclude START / END)
|
|
9
|
+
*/
|
|
10
|
+
export type NodeNamesOf<T> = T extends StateGraph<any, any, any, infer N, any, any, any, any, any, any> ? Exclude<N, typeof START | typeof END> : never;
|
|
11
|
+
/**
|
|
12
|
+
* Unwrap traceable-wrapped functions to their inner type.
|
|
13
|
+
* Extensible for other wrapper types as needed.
|
|
14
|
+
*/
|
|
15
|
+
type ExtractFrom<T> = T extends TraceableFunction<infer A> ? A : T;
|
|
16
|
+
type IsStateGraph<T> = T extends Promise<StateGraph<any, any, any, any>> | StateGraph<any, any, any, any> ? true : false;
|
|
17
|
+
export type ExtractStateGraphModule<T> = T extends IsStateGraph<T> ? (p: T) => T : T extends (...args: any) => infer R ? IsStateGraph<R> extends true ? T : never : never;
|
|
18
|
+
export type StateCallback<T> = (p: T) => Partial<T>;
|
|
19
|
+
export type MiddleHookFunc<T, U> = ExtractFrom<T> extends StateCallback<U> ? T : T extends (...args: any) => infer R ? MiddleHookFunc<R, U> : never;
|
|
20
|
+
export type MiddleHook<T, U> = MiddleHookFunc<T, U> | {
|
|
21
|
+
func: MiddleHookFunc<T, U>;
|
|
22
|
+
options?: StateGraphAddNodeOptions;
|
|
23
|
+
};
|
|
24
|
+
export type LastFunction<T> = T extends (...args: any) => infer R ? R extends Promise<infer P> ? P extends (...args: any) => any ? LastFunction<P> : ExtractFrom<T> : R extends AsyncIterable<infer Item> ? LastFunction<Item> : R extends (...args: any) => any ? LastFunction<R> : ExtractFrom<T> : never;
|
|
25
|
+
export type AnyNodeSpec = NodeSpec<any, any> & StateGraphAddNodeOptions;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AnyNodeSpec } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Filters the graph's node registry to only include the specified node names.
|
|
4
|
+
*
|
|
5
|
+
* If no names are provided, the entire node registry is returned as-is.
|
|
6
|
+
* This is used internally by `catch()` and `observe()` to optionally scope
|
|
7
|
+
* their effects to a subset of nodes.
|
|
8
|
+
*
|
|
9
|
+
* @param names - Specific node names to select, or an empty array for all nodes
|
|
10
|
+
* @param nodes - The full node registry from `StateGraph.nodes`
|
|
11
|
+
* @returns A filtered copy of the node registry
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare function getNodes(names: string[], nodes: Record<string, AnyNodeSpec>): Record<string, AnyNodeSpec>;
|
|
15
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,MAAM,EAAE,EACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACjC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAe7B"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters the graph's node registry to only include the specified node names.
|
|
3
|
+
*
|
|
4
|
+
* If no names are provided, the entire node registry is returned as-is.
|
|
5
|
+
* This is used internally by `catch()` and `observe()` to optionally scope
|
|
6
|
+
* their effects to a subset of nodes.
|
|
7
|
+
*
|
|
8
|
+
* @param names - Specific node names to select, or an empty array for all nodes
|
|
9
|
+
* @param nodes - The full node registry from `StateGraph.nodes`
|
|
10
|
+
* @returns A filtered copy of the node registry
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export function getNodes(names, nodes) {
|
|
14
|
+
if (!names || names.length === 0) {
|
|
15
|
+
return nodes;
|
|
16
|
+
}
|
|
17
|
+
const targetNames = new Set(names);
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const key of Object.keys(nodes)) {
|
|
20
|
+
if (targetNames.has(key)) {
|
|
21
|
+
result[key] = nodes[key];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,QAAQ,CACtB,KAAe,EACf,KAAkC;IAElC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,MAAM,GAAgC,EAAE,CAAC;IAE/C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAE,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime implementation for the new type-level helper method.
|
|
3
|
+
* It does nothing at runtime — its only purpose is to help TypeScript
|
|
4
|
+
* narrow the 4th generic (N) through the return type.
|
|
5
|
+
*
|
|
6
|
+
* @returns `this` for chaining
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function withNodes(this: any): any;
|
|
10
|
+
//# sourceMappingURL=withNodes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withNodes.d.ts","sourceRoot":"","sources":["../src/withNodes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,GAAG,OAElC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime implementation for the new type-level helper method.
|
|
3
|
+
* It does nothing at runtime — its only purpose is to help TypeScript
|
|
4
|
+
* narrow the 4th generic (N) through the return type.
|
|
5
|
+
*
|
|
6
|
+
* @returns `this` for chaining
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export function withNodes() {
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=withNodes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withNodes.js","sourceRoot":"","sources":["../src/withNodes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "langgraph-middleware",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Middleware utilities for LangGraph StateGraph — inject modules, before/after hooks, error catching, and callback observation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"lint:fix": "eslint . --fix",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest watch",
|
|
26
|
+
"test:coverage": "vitest run --coverage",
|
|
27
|
+
"prepublishOnly": "npm run build && npm run test"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/jakehkw/langgraph-middleware.git"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"langgraph",
|
|
35
|
+
"langchain",
|
|
36
|
+
"middleware",
|
|
37
|
+
"state-graph",
|
|
38
|
+
"hooks",
|
|
39
|
+
"inject",
|
|
40
|
+
"catch",
|
|
41
|
+
"observe",
|
|
42
|
+
"before-after",
|
|
43
|
+
"llm",
|
|
44
|
+
"ai"
|
|
45
|
+
],
|
|
46
|
+
"author": "jakehkw",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/jakehkw/langgraph-middleware/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/jakehkw/langgraph-middleware#readme",
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"langsmith": "^0.7.3",
|
|
54
|
+
"@langchain/core": "^1.0.0",
|
|
55
|
+
"@langchain/langgraph": "^1.0.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"@langchain/core": {
|
|
59
|
+
"optional": false
|
|
60
|
+
},
|
|
61
|
+
"@langchain/langgraph": {
|
|
62
|
+
"optional": false
|
|
63
|
+
},
|
|
64
|
+
"langsmith": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@langchain/core": "^1.1.47",
|
|
70
|
+
"@langchain/langgraph": "^1.3.2",
|
|
71
|
+
"@types/node": "25.9.1",
|
|
72
|
+
"eslint": "^10.4.0",
|
|
73
|
+
"globals": "^17.6.0",
|
|
74
|
+
"langsmith": "^0.7.3",
|
|
75
|
+
"typescript": "^6.0.3",
|
|
76
|
+
"typescript-eslint": "^8.59.4",
|
|
77
|
+
"vitest": "4.1.7"
|
|
78
|
+
},
|
|
79
|
+
"engines": {
|
|
80
|
+
"node": ">=18"
|
|
81
|
+
}
|
|
82
|
+
}
|