provideAgent()
provideAgent() registers the agent configuration that injectAgent() resolves. Call it in bootstrapApplication, an ApplicationConfig, or a component's providers array to wire up the LangGraph API URL, assistant id, thread binding, and transport.
injectAgent() takes no configuration — it takes at most a typed AgentRef; everything else flows through provideAgent().
import { bootstrapApplication } from '@angular/platform-browser';
import {
provideAgent,
MockAgentTransport,
} from '@threadplane/langgraph';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideAgent({
apiUrl: 'http://localhost:2024',
assistantId: 'my-agent',
transport: environment.testMode
? new MockAgentTransport()
: undefined,
}),
],
});Configuration options
Every field on the config object is optional at the type level, but assistantId has to be present by the time the agent is constructed.
| Option | Type | Description |
|---|---|---|
apiUrl | string | LangGraph Platform API base URL. |
assistantId | string | LangGraph assistant id to bind to. The only effectively required field — constructing an agent without it throws. |
threadId | Signal<string | null> | string | null | Thread ID to connect to. Pass a Signal for reactive thread switching. |
onThreadId | (id: string) => void | Called when a new thread is auto-created by the transport. |
initialValues | Partial<T> | Initial state values before the first stream response arrives. |
throttle | number | false | Throttle signal updates in milliseconds. false to disable. (default 16) |
toMessage | (msg: unknown) => BaseMessage | Custom message deserializer for non-standard message formats. |
transport | AgentTransport | Optional transport instance. Defaults to FetchStreamTransport when omitted. |
clientOptions | LangGraphClientOptions | LangGraph SDK client tuning: maxRetries and defaultHeaders. There is no apiKey option; see Client tuning below. |
telemetry | AgentRuntimeTelemetrySink | false | Omit to enable automatic development-only collection. Set false to disable it. An app-owned sink replaces the automatic destination and receives the same runtime lifecycle events. |
subagentToolNames | string[] | Tool names that indicate a subagent invocation. |
transcriptNodeNames | string[] | LangGraph node names whose messages-tuple chunks should stream into the main chat transcript. Omit to accept all top-level chunks. |
Factory config
provideAgent() accepts a config object or a factory that returns one. The factory runs inside an Angular injection context, so it may call inject() to read services, route parameters, or component-scoped signals. It is resolved once, lazily — never at decoration time.
import { inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { provideAgent } from '@threadplane/langgraph';
providers: [
provideAgent(() => {
const route = inject(ActivatedRoute);
return {
assistantId: 'chat',
threadId: route.snapshot.paramMap.get('threadId'),
};
}),
];One agent, or several
One provideAgent({...}) call at the application root configures the whole application, and every injectAgent() resolves to that agent.
provideAgent({
apiUrl: 'https://api.example.com',
assistantId: 'support-agent',
});
// Elsewhere, inside an injection context:
const chat = injectAgent();Several agents may also coexist at one injector level. Each provideAgent(ref, …) call builds its own agent from its own config, so injectAgent(refA) and injectAgent(refB) return distinct instances. The ref-less injectAgent() resolves a single shared token, which can only point at one of them — the last ref-form call at that level wins, and resolving it in development mode logs a warning naming every competing ref. Always inject by ref when an injector provides more than one agent.
export const LIVE = createAgentRef<ChatState>('live');
export const REPLAY = createAgentRef<ChatState>('replay');
providers: [
provideAgent(LIVE, { assistantId: 'chat' }),
provideAgent(REPLAY, { assistantId: 'chat', transport: replayTransport }),
];
// injectAgent(LIVE) !== injectAgent(REPLAY)In development mode, resolving the ref-less token while several refs share the injector level logs a console.warn naming every competing ref and the one that won. It fires only on the ambiguous path: injectAgent(LIVE) and injectAgent(REPLAY) are unambiguous and stay silent. Production builds never log it.
The AGENT_LIFECYCLE token
Every form of provideAgent() also provides AGENT_LIFECYCLE, so instrumentation can read an agent's lifecycle signals without depending on the agent itself:
import { inject } from '@angular/core';
import { AGENT_LIFECYCLE } from '@threadplane/langgraph';
const lifecycle = inject(AGENT_LIFECYCLE); // === injectAgent().lifecycleThe token follows the same last-one-wins rule as the ref-less injectAgent(): with several refs at one injector level it resolves the last ref's lifecycle. Read injectAgent(ref).lifecycle instead when that is the case.
Transcript node filtering
LangGraph streams messages-tuple chunks for every LLM node in a run. If your graph has side-effect LLM nodes, such as a title generator or evaluator, set transcriptNodeNames so only your conversational node updates messages().
Child-graph streams are a separate concern and need no configuration: any namespaced event — a compiled child added with add_node (research:<uuid>) or a tool-dispatched subagent (tools:<id>) — belongs to its child stream in subagents() and never merges into the transcript. See Subgraphs.
provideAgent({
apiUrl: 'https://api.example.com',
assistantId: 'support-agent',
transcriptNodeNames: ['generate'],
});Test transports
transport is an object that implements AgentTransport, not an Angular class token. Create an instance before passing it to provideAgent().
const transport = new MockAgentTransport();
TestBed.configureTestingModule({
providers: [
provideAgent({
apiUrl: '',
assistantId: 'test-agent',
transport,
}),
],
});Swap out the live transport for MockAgentTransport in tests by changing only
the transport field on provideAgent(). injectAgent() call sites stay
unchanged across environments.
Client tuning (retry budget)
The LangGraph SDK retries connection failures — including the run stream — before throwing. With the defaults, a hard connection failure can take several seconds (multiple retries) to surface as an error, which delays your error UI. Tune the retry budget with clientOptions:
provideAgent({
apiUrl: 'https://api.example.com',
assistantId: 'support-agent',
clientOptions: { maxRetries: 0 }, // fail fast — surface connection errors immediately
});To configure it once for the whole app, provide the LANGGRAPH_CLIENT_OPTIONS token. Both the default FetchStreamTransport and the LangGraphThreadsAdapter read it, so the retry budget is set in one place:
import { LANGGRAPH_CLIENT_OPTIONS } from '@threadplane/langgraph';
bootstrapApplication(AppComponent, {
providers: [
provideAgent({ apiUrl: '…', assistantId: 'support-agent' }),
{ provide: LANGGRAPH_CLIENT_OPTIONS, useValue: { maxRetries: 0 } },
],
});Precedence (first defined wins, whole-object — no per-field merge):
- The per-agent
provideAgent({ clientOptions }) - The app-wide
LANGGRAPH_CLIENT_OPTIONStoken - The SDK default
Headers, and why there is no apiKey
clientOptions.defaultHeaders is attached to every request the SDK client makes, including the run stream. Its intended use is a per-user session token that a custom authenticate handler on your deployment validates:
provideAgent({
apiUrl: '/api/langgraph',
assistantId: 'support-agent',
clientOptions: { defaultHeaders: { Authorization: `Bearer ${session.token()}` } },
});LangGraphClientOptions deliberately has no apiKey member. A deployment credential passed from Angular is bundled into the browser build like any other constant, so the adapter offers no field for one, and it constructs the SDK client with an explicit null key so nothing is read from the environment either. Keep deployment keys on a same-origin proxy or gateway you own and point apiUrl at it; the deployment guide shows the shape.
If a dropped connection takes ~15s to show an error, the SDK is exhausting its
retry budget. Set maxRetries: 0 to fail fast, or a small number to keep a few
retries with quicker surfacing. The resulting failure is classified as a
retryable connection AgentError.
Typed state via AgentRef
By default injectAgent() returns an agent typed as Record<string, unknown>. To thread a concrete state shape through Angular DI without restating the generic at every call site, create a typed AgentRef with createAgentRef<TState>() and pass it as the first argument to both provideAgent() and injectAgent():
import { createAgentRef } from '@threadplane/chat';
import { provideAgent, injectAgent } from '@threadplane/langgraph';
interface TripState {
day: number;
places: string[];
}
// Module scope — one typed handle, shared by providers and components.
export const TRIP = createAgentRef<TripState>('trip');
// app.config.ts
provideAgent(TRIP, { assistantId: 'trip' });
// component
const agent = injectAgent(TRIP); // LangGraphAgent<TripState>
agent.state().places; // string[] — fully typed, no castThe optional debugName ('trip' above) is surfaced in Angular DI error messages. AgentRef is exported from @threadplane/chat, so the same typed handle works across adapters.