@threadplane/langgraph
v0.0.52
Published
LangGraph adapter for @threadplane/chat — Angular bindings for LangGraph Platform.
Maintainers
Readme
@threadplane/langgraph
Adapter that wraps a LangGraph agent into the runtime-neutral Agent contract from @threadplane/chat. The Angular equivalent of LangGraph's React useStream() hook — signal-driven access to messages, status, tool calls, interrupts, subagents, branch history, and thread persistence.
Talking to a non-LangGraph backend? See
@threadplane/ag-ui— same API shape, AG-UI protocol underneath.
What it does
provideAgent()— wire the LangGraph adapter into Angular DI. Provided at the root injector or at any component subtree (multi-thread UIs work via Angular's hierarchical DI).injectAgent()— retrieve the configuredLangGraphAgentin any component. Returns aLangGraphAgentwhose entire state surface (messages,status,isLoading,error,interrupt,toolCalls,subagents,queue,branch,history, and more) is exposed as Angular Signals. No subscriptions, noasyncpipe, no zone.js required.- Human-in-the-loop —
interrupt()delivers a runtime-neutral interrupt value;langGraphInterrupts()exposes the raw LangGraph interrupt list when you need it. - Subagent streaming —
subagents()+getSubagent(toolCallId),getSubagentsByType(type),getSubagentsByMessage(msg), andactiveSubagents()surface streaming subgraph state without extra bookkeeping. - Time-travel and thread persistence —
branch()/history()/experimentalBranchTree()enable checkpoint navigation;LangGraphThreadsAdapterprovides SDK-backed thread CRUD so you never have to hand-roll thread management.
Install
npm install @threadplane/langgraph @threadplane/chatPeer dependencies:
@threadplane/chat *
@angular/core ^20.0.0 || ^21.0.0
@langchain/core ^1.1.33
@langchain/langgraph-sdk ^1.7.4
rxjs ~7.8.0Quick start
Configure the LangGraph endpoint once in app.config.ts:
// app.config.ts
import { provideAgent } from '@threadplane/langgraph';
export const appConfig: ApplicationConfig = {
providers: [
provideAgent({
apiUrl: 'https://your-langgraph-platform.com',
assistantId: 'my-agent',
}),
],
};Then call injectAgent() in any component and pass the result to <chat />:
// chat.component.ts
import { Component } from '@angular/core';
import { injectAgent } from '@threadplane/langgraph';
import { ChatComponent } from '@threadplane/chat';
@Component({
imports: [ChatComponent],
template: `<chat [agent]="chat" />`,
})
export class ChatComponentHost {
protected readonly chat = injectAgent();
}
injectAgent()must be called within an Angular injection context — a component field initializer or constructor. Calling it inngOnInitor any async context throwsNG0203: inject() must be called from an injection context.
Need a different agent for a specific component subtree (e.g., a sidebar showing a separate conversation)? Re-provide
provideAgent({...})in that component'sproviders: []array — Angular's hierarchical DI takes care of the rest.
Capabilities
Messages, status, and errors
| Signal | Type | Description |
|---|---|---|
| messages() | Message[] | Accumulated chat messages from the stream |
| status() | 'idle' \| 'running' \| 'error' | Runtime-neutral run status |
| isLoading() | boolean | true while a run is streaming |
| error() | unknown \| null | Last error, if any |
Human-in-the-loop (interrupts)
const pending = chat.interrupt(); // runtime-neutral interrupt value
const raw = chat.langGraphInterrupts(); // raw LangGraph Interrupt[]Resume by calling chat.submit(response).
Tool calls
toolCalls() is a Signal of all tool call entries observed in the current run, updated incrementally as the stream progresses.
Subagents
chat.subagents() // Signal<Map<string, Subagent>> of all subagents
chat.activeSubagents() // currently streaming subagents (SubagentStreamRef[])
chat.getSubagent(toolCallId) // look up by tool call ID
chat.getSubagentsByType(type) // filter by subagent type
chat.getSubagentsByMessage(msg) // filter by parent messageQueue
queue() exposes pending run entries when the agent is configured with a multitask strategy that queues concurrent submissions.
Branch, history, and time-travel
chat.branch() // current branch identifier Signal
chat.setBranch(b) // switch to a checkpoint branch
chat.history() // runtime-neutral history entries
chat.langGraphHistory() // raw LangGraph ThreadState[]
chat.experimentalBranchTree() // full branching tree for time-travel UIActions
chat.submit(input, opts?) // send a new message
chat.stop() // cancel the active run
chat.regenerate(assistantMessageIndex) // re-run from a prior assistant turn
chat.reload() // re-run the last submission
chat.switchThread(threadId) // load a different thread
chat.joinStream(runId, lastEventId?) // reconnect to an in-flight runThread persistence
LangGraphThreadsAdapter is a drop-in, SDK-backed thread store. Provide it alongside the agent config:
import { provideAgent, LangGraphThreadsAdapter, LANGGRAPH_THREADS_CONFIG } from '@threadplane/langgraph';
export const appConfig: ApplicationConfig = {
providers: [
provideAgent({ apiUrl: 'https://your-langgraph-platform.com' }),
{ provide: LANGGRAPH_THREADS_CONFIG, useValue: { apiUrl: 'https://your-langgraph-platform.com' } },
LangGraphThreadsAdapter,
],
};Pair it with the lifecycle helpers to keep your thread list fresh:
import { refreshOnRunEnd, refreshOnTransition } from '@threadplane/langgraph';
refreshOnRunEnd(chat, () => threadsAdapter.loadThreads());Citations
extractCitations(msg) reads citation metadata from a LangGraph message's additional_kwargs, returning Citation[] | undefined (undefined when no citation metadata is present). It checks additional_kwargs.citations first, falling back to additional_kwargs.sources.
import { extractCitations } from '@threadplane/langgraph';
const citations = extractCitations(message);Citation is a type from @threadplane/chat; CitationsResolverService and provideChat also live there.
Testing
// Fake backend — streams canned tokens, no server:
import { provideFakeAgent } from '@threadplane/langgraph';
providers: [provideFakeAgent({ tokens: ['Hello', ' world'] })];For component/unit tests, use the writable-signal mock mockLangGraphAgent()
(it extends the neutral mockAgent from @threadplane/chat). See
Choosing an adapter → Testing.
Need to hand-script exact wire events (tool calls, interrupts, multi-batch
lifecycles)? MockAgentTransport is the advanced escape hatch — swap the
transport, never mock injectAgent() itself.
Reliability
Runtime-neutral contract. LangGraphAgent implements the Agent contract from @threadplane/chat. Components that depend only on that contract are portable across adapters (@threadplane/ag-ui, future adapters) without modification.
Release policy. Patch-only 0.0.x releases — every change, including breaking ones, increments the patch version until the library reaches 1.0.0.
CI. The "Library — lint / test / build" job runs lint, tests, and build on every pull request.
Documentation
- Quickstart
injectAgent()API referenceprovideAgent()API reference- Human-in-the-loop / interrupts
- Thread persistence
- Testing with
MockAgentTransport - Choosing an adapter (LangGraph vs AG-UI)
License
MIT. See LICENSE.
