You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: apps/website/content/docs/licensing/api/api-docs.json
+3-3Lines changed: 3 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -185,7 +185,7 @@
185
185
{
186
186
"name": "emitNag",
187
187
"kind": "function",
188
-
"description": "",
188
+
"description": "Emit one non-blocking license warning for a package/status pair.\n\n`licensed` and `noncommercial` statuses are silent. Other statuses warn once\nper package and status so repeated initialization does not spam logs.",
"description": "Convert a verified token result into a license status.\n\n`verifyResult` may be undefined when no token was supplied. In that case a\ncaller-provided `isNoncommercial` hint returns `noncommercial`; otherwise the\nstatus is `missing`. Signature failures return `tampered`, and valid claims\nare evaluated against `exp` plus the optional grace window.",
"description": "Run the full package license check.\n\nThe helper verifies an optional token, evaluates the status, emits the\npackage nag warning when appropriate, and memoizes identical package/token\npairs so repeated provider initialization stays quiet.",
Copy file name to clipboardExpand all lines: apps/website/content/docs/licensing/getting-started/introduction.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -69,7 +69,7 @@ The higher-level check is built not to block app startup:
69
69
- warning output goes through `console.warn` unless a custom `warn` function is supplied;
70
70
- no network request is made by the licensing check.
71
71
72
-
The code returns statuses instead of throwing for normal license states. For me, that's the right default here: you get to decide what a missing or expired license means for your app instead of having the check make that call for you. The tradeoff is that nothing stops a consumer from ignoring the status entirely. `@threadplane/chat` treats it as a warning and visibility mechanism, not an app kill switch.
72
+
The code returns statuses instead of throwing for normal license states. That keeps policy decisions in the host package or application: a missing or expired license can be logged, surfaced, or enforced by the caller instead of being decided by initialization code. `@threadplane/chat` treats the status as a warning and visibility mechanism, not an app kill switch.
Copy file name to clipboardExpand all lines: apps/website/content/docs/middleware/api/client-tool-helpers.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,4 +1,4 @@
1
-
# bindClientTools()
1
+
# LangGraph.js client-tool helpers
2
2
3
3
`bindClientTools()` is the main helper from `@threadplane/middleware/langgraph`. It binds your server tools plus the current run's client-tool catalog onto a LangChain chat model.
4
4
@@ -10,7 +10,7 @@ import {
10
10
} from'@threadplane/middleware/langgraph';
11
11
```
12
12
13
-
There is no root JavaScript entry point for `@threadplane/middleware`; import from the `/langgraph` subpath.
13
+
There is no root JavaScript entry point for `@threadplane/middleware`; import from the `/langgraph` subpath. The Python package exposes equivalent snake_case helpers from `threadplane.middleware.langgraph`.
14
14
15
15
Related helpers in the same entry point include `clientToolsChannel()`, `clientToolsRouter()`, `clientToolSpecs()`, `clientToolNames()`, `hasClientToolCall()`, `hasServerToolCall()`, `routeAfterAgent()`, and `lastMessage()`.
Copy file name to clipboardExpand all lines: apps/website/content/docs/middleware/getting-started/introduction.mdx
+32-10Lines changed: 32 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,8 +1,15 @@
1
1
# Introduction
2
2
3
-
`@threadplane/middleware` is the backend companion for Threadplane client tools. It lets a browser declare tools, lets the model call those tools, and routes client-tool-only turns back to the browser for execution.
3
+
Threadplane publishes middleware for backend graphs that need browser-executed client tools. The browser declares tools, the model can call those tools, and the backend routes client-tool-only turns back to the browser for execution.
4
4
5
-
The package currently publishes one runtime entry point:
The TypeScript package currently publishes one runtime entry point:
6
13
7
14
```ts
8
15
import {
@@ -16,23 +23,23 @@ There is no root `@threadplane/middleware` JavaScript entry point. Import from `
16
23
17
24
## What it does
18
25
19
-
The LangGraph entry point reads a client tool catalog from graph state, converts it into OpenAI function-tool objects, binds those tool stubs onto your chat model, and routes client-tool calls to `END` so the browser can execute them.
26
+
The LangGraph entry points read a client tool catalog from graph state, convert it into OpenAI function-tool objects, bind those tool stubs onto your chat model, and route client-tool calls to `END` so the browser can execute them.
20
27
21
-
The catalog is read from `state.tools` first. If that channel is absent or empty, it falls back to `state.client_tools`.
28
+
The catalog is read from `state.tools` first. If that channel is absent or empty, both packages fall back to `state.client_tools`.
22
29
23
30
## Runtime flow
24
31
25
32
1. The browser sends tool specs with the run request.
26
-
2. Your LangGraph node calls `bindClientTools()` inside the run, because the catalog can differ per request.
33
+
2. Your LangGraph node calls `bindClientTools()`or `bind_client_tools()`inside the run, because the catalog can differ per request.
27
34
3. The model emits a tool call for a browser-declared tool.
28
-
4.`clientToolsRouter()` routes client-only tool calls to `END`.
35
+
4.The router routes client-only tool calls to `END`.
29
36
5. The browser executes the local tool and resumes the graph with a `ToolMessage`.
30
37
31
38
If a turn mixes server tool calls and client tool calls, server tools win the first route. The server tool node runs first, and the client call can surface on a later turn.
32
39
33
-
## Public surface
40
+
## TypeScript public surface
34
41
35
-
The entry point exports:
42
+
`@threadplane/middleware/langgraph` exports:
36
43
37
44
| API | Purpose |
38
45
|-----|---------|
@@ -46,14 +53,29 @@ The entry point exports:
46
53
|`routeAfterAgent()`| Lower-level routing helper used by `clientToolsRouter()`. |
47
54
|`lastMessage()`| Reads the last message from state. |
48
55
56
+
## Python public surface
57
+
58
+
`threadplane.middleware.langgraph` exports:
59
+
60
+
| API | Purpose |
61
+
|-----|---------|
62
+
|`bind_client_tools()`| Binds server tools plus client-declared tool stubs onto a model. |
63
+
|`client_tool_specs()`| Converts state catalog entries into OpenAI function-tool specs. |
64
+
|`client_tool_names()`| Returns the set of client-declared tool names for a run. |
65
+
|`has_client_tool_call()`| Checks whether the last message calls a client tool. |
66
+
|`has_server_tool_call()`| Checks whether the last message calls a server or unknown tool. |
67
+
|`route_after_agent()`| Routing helper for conditional edges. |
68
+
|`last_message()`| Reads the last message from state. |
69
+
49
70
## When to use it
50
71
51
-
Use middleware when you own a LangGraph.js backend and want browser-declared tools from `@threadplane/chat` to participate in model tool calling without executing browser-only code on the server.
72
+
Use middleware when you own a LangGraph backend and want browser-declared tools from `@threadplane/chat` to participate in model tool calling without executing browser-only code on the server.
52
73
53
74
If your backend already speaks AG-UI, use `@threadplane/ag-ui` instead. If your frontend talks directly to LangGraph and does not need browser-executed tools, `@threadplane/langgraph` can run without this middleware.
54
75
55
76
## Next steps
56
77
57
78
-[Quick Start](/docs/middleware/getting-started/quickstart) - install and wire the LangGraph helper.
58
79
-[LangGraph Client Tools](/docs/middleware/guides/langgraph-client-tools) - routing details and server-tool behavior.
59
-
-[Client Tool Helpers](/docs/middleware/api/client-tool-helpers) - generated API reference for the exported helpers.
80
+
-[Python LangGraph Middleware](/docs/middleware/guides/python-langgraph) - the Python package and snake_case helpers.
81
+
-[Client Tool Helpers](/docs/middleware/api/client-tool-helpers) - generated API reference for the TypeScript helpers.
The package exposes its JavaScript API from `@threadplane/middleware/langgraph`.
9
+
The package exposes its JavaScript API from `@threadplane/middleware/langgraph`. For Python LangGraph, install `threadplane-middleware` and follow the [Python guide](/docs/middleware/guides/python-langgraph).
10
10
11
11
## Add client-tool state channels
12
12
@@ -95,4 +95,5 @@ On the frontend, declare client tools with `@threadplane/chat` and send them thr
95
95
## Next steps
96
96
97
97
-[LangGraph Client Tools](/docs/middleware/guides/langgraph-client-tools) - mixed server/client routing and helper behavior.
98
+
-[Python LangGraph Middleware](/docs/middleware/guides/python-langgraph) - the equivalent Python package and helper names.
98
99
-[Client Tool Helpers](/docs/middleware/api/client-tool-helpers) - generated API details.
Copy file name to clipboardExpand all lines: apps/website/content/docs/middleware/guides/langgraph-client-tools.mdx
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,9 @@
1
-
# LangGraph Client Tools
1
+
# LangGraph.js Client Tools
2
2
3
3
Client tools are frontend-declared tools that the model can call but the browser executes. The backend only exposes tool schemas to the model and decides whether a turn should continue on the server or end so the client can run a local tool.
4
4
5
+
This page documents the TypeScript `@threadplane/middleware/langgraph` entry point. The Python package uses the same routing model with snake_case helpers; see [Python LangGraph Middleware](/docs/middleware/guides/python-langgraph).
The Python package is `threadplane-middleware`. It is the Python LangGraph twin of `@threadplane/middleware/langgraph`: it binds browser-declared client-tool stubs onto a chat model and routes client-tool-only turns to `END`.
4
+
5
+
## Install
6
+
7
+
```bash
8
+
pip install threadplane-middleware
9
+
```
10
+
11
+
The package depends on `langchain-core>=0.3.0` and `langgraph>=0.3.0`.
12
+
13
+
Install your model provider package separately, for example `langchain-openai` when using `ChatOpenAI`.
14
+
15
+
## Bind tools per run
16
+
17
+
Call `bind_client_tools()` inside your agent node. The browser sends the tool catalog with each run, so the model-visible tool list is request-scoped.
18
+
19
+
```python
20
+
from langchain_openai import ChatOpenAI
21
+
from threadplane.middleware.langgraph import bind_client_tools
The helper reads `state["tools"]` first and falls back to `state["client_tools"]`. It appends each client tool as an explicit OpenAI function-tool dict:
33
+
34
+
```python
35
+
{
36
+
"type": "function",
37
+
"function": {
38
+
"name": "get_weather",
39
+
"description": "Read local weather",
40
+
"parameters": {"type": "object"},
41
+
},
42
+
}
43
+
```
44
+
45
+
## Route after the agent
46
+
47
+
Use `route_after_agent()` from a LangGraph conditional edge. It returns the server tools node name when the last model message contains a server or unknown tool call. It returns `__end__` when the turn has only client tool calls or no tool calls.
48
+
49
+
```python
50
+
from langgraph.graph importEND, StateGraph
51
+
from langgraph.prebuilt import ToolNode
52
+
from threadplane.middleware.langgraph import route_after_agent
53
+
54
+
server_tool_names = [tool.name for tool inSERVER_TOOLS]
|`client_tool_specs(state)`| Convert the run catalog into OpenAI function-tool dicts. |
88
+
|`client_tool_names(state)`| Return the set of client-declared tool names. |
89
+
|`has_client_tool_call(state)`| Check whether the last message calls a known client tool. |
90
+
|`has_server_tool_call(state, server_tool_names)`| Check whether the last message calls a server or unknown tool. |
91
+
|`last_message(state)`| Return the last message from `state["messages"]`, or `None`. |
92
+
93
+
## Frontend contract
94
+
95
+
The middleware does not execute browser tools. The frontend still needs to send the catalog, observe the model tool call, execute the local function or UI interaction, and resume the graph with a `ToolMessage` containing the result.
0 commit comments