Skip to content

Commit 76bfe4f

Browse files
authored
Refresh support package references
2 parents 19ec0ba + 4b8576c commit 76bfe4f

17 files changed

Lines changed: 227 additions & 51 deletions

File tree

apps/website/content/docs/licensing/api/api-docs.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@
185185
{
186186
"name": "emitNag",
187187
"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.",
189189
"signature": "emitNag(result: Pick<EvaluateResult, \"status\">, options: EmitNagOptions): void",
190190
"params": [
191191
{
@@ -210,7 +210,7 @@
210210
{
211211
"name": "evaluateLicense",
212212
"kind": "function",
213-
"description": "",
213+
"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.",
214214
"signature": "evaluateLicense(verifyResult: VerifyResult | undefined, options: EvaluateOptions): EvaluateResult",
215215
"params": [
216216
{
@@ -247,7 +247,7 @@
247247
{
248248
"name": "runLicenseCheck",
249249
"kind": "function",
250-
"description": "",
250+
"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.",
251251
"signature": "runLicenseCheck(options: RunLicenseCheckOptions): Promise<LicenseStatus>",
252252
"params": [
253253
{

apps/website/content/docs/licensing/getting-started/introduction.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ The higher-level check is built not to block app startup:
6969
- warning output goes through `console.warn` unless a custom `warn` function is supplied;
7070
- no network request is made by the licensing check.
7171

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.
7373

7474
## Next steps
7575

apps/website/content/docs/middleware/api/client-tool-helpers.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# bindClientTools()
1+
# LangGraph.js client-tool helpers
22

33
`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.
44

@@ -10,7 +10,7 @@ import {
1010
} from '@threadplane/middleware/langgraph';
1111
```
1212

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`.
1414

1515
Related helpers in the same entry point include `clientToolsChannel()`, `clientToolsRouter()`, `clientToolSpecs()`, `clientToolNames()`, `hasClientToolCall()`, `hasServerToolCall()`, `routeAfterAgent()`, and `lastMessage()`.
1616

apps/website/content/docs/middleware/getting-started/introduction.mdx

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
# Introduction
22

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.
44

5-
The package currently publishes one runtime entry point:
5+
There are two package surfaces:
6+
7+
| Runtime | Package | Entry point |
8+
|---------|---------|-------------|
9+
| LangGraph.js | `@threadplane/middleware` | `@threadplane/middleware/langgraph` |
10+
| Python LangGraph | `threadplane-middleware` | `threadplane.middleware.langgraph` |
11+
12+
The TypeScript package currently publishes one runtime entry point:
613

714
```ts
815
import {
@@ -16,23 +23,23 @@ There is no root `@threadplane/middleware` JavaScript entry point. Import from `
1623

1724
## What it does
1825

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.
2027

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`.
2229

2330
## Runtime flow
2431

2532
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.
2734
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`.
2936
5. The browser executes the local tool and resumes the graph with a `ToolMessage`.
3037

3138
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.
3239

33-
## Public surface
40+
## TypeScript public surface
3441

35-
The entry point exports:
42+
`@threadplane/middleware/langgraph` exports:
3643

3744
| API | Purpose |
3845
|-----|---------|
@@ -46,14 +53,29 @@ The entry point exports:
4653
| `routeAfterAgent()` | Lower-level routing helper used by `clientToolsRouter()`. |
4754
| `lastMessage()` | Reads the last message from state. |
4855

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+
4970
## When to use it
5071

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.
5273

5374
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.
5475

5576
## Next steps
5677

5778
- [Quick Start](/docs/middleware/getting-started/quickstart) - install and wire the LangGraph helper.
5879
- [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.

apps/website/content/docs/middleware/getting-started/quickstart.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# Quick Start
22

3-
Install the middleware package and its LangGraph peer dependencies:
3+
Install the TypeScript middleware package and its LangGraph.js peer dependencies:
44

55
```bash
66
npm install @threadplane/middleware @langchain/core @langchain/langgraph
77
```
88

9-
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).
1010

1111
## Add client-tool state channels
1212

@@ -95,4 +95,5 @@ On the frontend, declare client tools with `@threadplane/chat` and send them thr
9595
## Next steps
9696

9797
- [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.
9899
- [Client Tool Helpers](/docs/middleware/api/client-tool-helpers) - generated API details.

apps/website/content/docs/middleware/guides/langgraph-client-tools.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
# LangGraph Client Tools
1+
# LangGraph.js Client Tools
22

33
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.
44

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).
6+
57
## State shape
68

79
The middleware reads this state slice:
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Python LangGraph Middleware
2+
3+
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
22+
23+
SERVER_TOOLS = [search_tool]
24+
base_llm = ChatOpenAI(model="gpt-4o-mini")
25+
26+
def agent_node(state):
27+
llm = bind_client_tools(base_llm, SERVER_TOOLS, state)
28+
response = llm.invoke(state["messages"])
29+
return {"messages": [response]}
30+
```
31+
32+
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 import END, 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 in SERVER_TOOLS]
55+
56+
def router(state):
57+
return route_after_agent(state, server_tool_names)
58+
59+
graph = StateGraph(...)
60+
graph.add_node("agent", agent_node)
61+
graph.add_node("tools", ToolNode(SERVER_TOOLS))
62+
graph.add_conditional_edges("agent", router, {"tools": "tools", "__end__": END})
63+
```
64+
65+
You can override the returned route labels:
66+
67+
```python
68+
route_after_agent(state, server_tool_names, tools_node="server_tools", end="done")
69+
```
70+
71+
## Helper surface
72+
73+
```python
74+
from threadplane.middleware.langgraph import (
75+
bind_client_tools,
76+
client_tool_specs,
77+
client_tool_names,
78+
has_client_tool_call,
79+
has_server_tool_call,
80+
last_message,
81+
route_after_agent,
82+
)
83+
```
84+
85+
| Helper | Purpose |
86+
|--------|---------|
87+
| `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

Comments
 (0)