npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@threadplane/middleware

v0.0.2

Published

Backend middleware for the Threadplane client-tools capability. The /langgraph entrypoint targets LangGraph.js.

Downloads

275

Readme

@threadplane/middleware

Backend middleware for the Threadplane client-tools capability — frontend-declared tools the model calls and the browser executes.

The @threadplane/middleware/langgraph entrypoint is the LangGraph.js twin of the Python threadplane-middleware package: it binds client-declared tool stubs onto your model and routes client-tool-only turns to END so the browser executes them.

How it works

When a browser client sends a tool catalog ({ name, description, parameters } objects) along with a run request, the graph exposes those tools to the model and routes their calls back to the browser instead of executing them server-side. The browser executes the call and re-runs the graph with a ToolMessage carrying the result.

The catalog is read from state.tools, falling back to state.client_tools if tools is absent.

Installation

npm install @threadplane/middleware
# peer deps:
npm install @langchain/core @langchain/langgraph

Usage

import { Annotation, MessagesAnnotation, StateGraph, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import {
  bindClientTools,
  clientToolsChannel,
  clientToolsRouter,
} from '@threadplane/middleware/langgraph';

// Declare the client-tools state channels (tools + client_tools) in one line.
const State = Annotation.Root({ ...MessagesAnnotation.spec, ...clientToolsChannel() });

const SERVER_TOOLS: unknown[] = []; // your server-owned tools (if any)
const baseLlm = new ChatOpenAI({ model: 'gpt-4o-mini' });

async function agent(state: typeof State.State) {
  // Call bindClientTools per-run inside the node — the client catalog arrives
  // in state and may differ between runs.
  const llm = bindClientTools(baseLlm, SERVER_TOOLS, state);
  const response = await llm.invoke(state.messages);
  return { messages: [response] };
}

const graph = new StateGraph(State)
  .addNode('agent', agent)
  .addEdge('__start__', 'agent')
  // clientToolsRouter binds the server tool names once; pass [] when there are none.
  .addConditionalEdges('agent', clientToolsRouter([]), ['tools', END])
  .compile();

What happens with a client tool call

  1. The model emits a tool call whose name matches a client-declared tool.
  2. clientToolsRouter (via routeAfterAgent) returns "__end__" — the run ends.
  3. The browser receives the partial output, executes the tool locally, and re-runs the graph with a ToolMessage containing the result.
  4. The model continues from there as if it had called a server tool.

A turn that mixes a server tool call and a client tool call routes to the server destination first (the server tool runs; the client call surfaces on a later turn).

Lower-level helpers

import {
  clientToolSpecs,    // → OpenAI function-tool objects for model.bindTools
  clientToolNames,    // → Set<string> of client tool names
  hasClientToolCall,  // → boolean
  hasServerToolCall,  // → boolean (takes serverToolNames)
  lastMessage,        // → the last message from state.messages
  routeAfterAgent,    // → routing string (takes serverToolNames)
} from '@threadplane/middleware/langgraph';

Peer dependencies

@langchain/core and @langchain/langgraph. The package has no runtime dependencies of its own.