Middleware · Getting Started

Introduction

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.

There are two package surfaces:

RuntimePackageEntry point
LangGraph.js@threadplane/middleware@threadplane/middleware/langgraph
Python LangGraphthreadplane-middlewarethreadplane.middleware.langgraph

The TypeScript package currently publishes one runtime entry point:

import {
  bindClientTools,
  clientToolsChannel,
  clientToolsRouter,
} from '@threadplane/middleware/langgraph';

There is no root @threadplane/middleware JavaScript entry point. Import from @threadplane/middleware/langgraph.

#What it does

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.

The catalog is read from state.tools first. If that channel is absent or empty, both packages fall back to state.client_tools.

#Runtime flow

  1. The browser sends tool specs with the run request.
  2. Your LangGraph node calls bindClientTools() or bind_client_tools() inside the run, because the catalog can differ per request.
  3. The model emits a tool call for a browser-declared tool.
  4. The router routes client-only tool calls to END.
  5. The browser executes the local tool and resumes the graph with a ToolMessage.

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.

#TypeScript public surface

@threadplane/middleware/langgraph exports:

APIPurpose
clientToolsChannel()Adds the tools and client_tools state channels to a LangGraph annotation.
bindClientTools()Binds server tools plus client-declared tool stubs onto a model.
clientToolsRouter()Creates a conditional-edge router for server-tool vs client-tool routing.
clientToolSpecs()Converts state catalog entries into OpenAI function-tool specs.
clientToolNames()Returns the set of client-declared tool names for a run.
hasClientToolCall()Checks whether the last message calls a client tool.
hasServerToolCall()Checks whether the last message calls a server or unknown tool.
routeAfterAgent()Lower-level routing helper used by clientToolsRouter().
lastMessage()Reads the last message from state.

#Python public surface

threadplane.middleware.langgraph exports:

APIPurpose
bind_client_tools()Binds server tools plus client-declared tool stubs onto a model.
client_tool_specs()Converts state catalog entries into OpenAI function-tool specs.
client_tool_names()Returns the set of client-declared tool names for a run.
has_client_tool_call()Checks whether the last message calls a client tool.
has_server_tool_call()Checks whether the last message calls a server or unknown tool.
route_after_agent()Routing helper for conditional edges.
last_message()Reads the last message from state.

#When to use it

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.

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.

#Next steps