Page actions

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.

OptionTypeDescription
apiUrlstringLangGraph Platform API base URL.
assistantIdstringLangGraph assistant id to bind to. The only effectively required field — constructing an agent without it throws.
threadIdSignal<string | null> | string | nullThread ID to connect to. Pass a Signal for reactive thread switching.
onThreadId(id: string) => voidCalled when a new thread is auto-created by the transport.
initialValuesPartial<T>Initial state values before the first stream response arrives.
throttlenumber | falseThrottle signal updates in milliseconds. false to disable. (default 16)
toMessage(msg: unknown) => BaseMessageCustom message deserializer for non-standard message formats.
transportAgentTransportOptional transport instance. Defaults to FetchStreamTransport when omitted.
clientOptionsLangGraphClientOptionsLangGraph SDK client tuning: maxRetries and defaultHeaders. There is no apiKey option; see Client tuning below.
telemetryAgentRuntimeTelemetrySink | falseOmit 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.
subagentToolNamesstring[]Tool names that indicate a subagent invocation.
transcriptNodeNamesstring[]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)
Warning: Dev-mode warning on an ambiguous ref-less inject

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().lifecycle

The 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,
    }),
  ],
});
Note: Test transports

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):

  1. The per-agent provideAgent({ clientOptions })
  2. The app-wide LANGGRAPH_CLIENT_OPTIONS token
  3. 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.

Note: Diagnosing slow error surfacing

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 cast

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

What's Next

provideAgentfunction

Wire the LangGraph adapter into Angular's dependency injection. Registers a singleton `LangGraphAgent` constructed from `config`. Retrieve it in any component with `injectAgent()`. Provide this at the application root (`app.config.ts`) for an app-wide agent. To use a different agent in a component subtree, re-provide `provideAgent({...})` in that component's `providers: []` array — Angular's hierarchical DI scopes the singleton accordingly. **Static vs factory config.** Pass a plain `AgentConfig` object when the config is known up front. Pass a `() => AgentConfig` factory when the config depends on runtime/DI state — the factory runs inside an Angular injection context, so it may call `inject()` to read services, route params, or component-scoped signals. **Typed state via AgentRef.** Pass a typed ref as the first argument to flow the state shape from `provideAgent` to `injectAgent` without repeating the generic at every call site. **Several agents at one injector level.** Each `provideAgent(ref, …)` call builds its own agent from its own config, so two (or more) refs may be provided side by side in a single `providers` array and `injectAgent(refA)` / `injectAgent(refB)` return distinct agents. The ref-less `injectAgent()` resolves a single shared token, which can only point at one of them: when more than one ref is provided at the same level the **last** call wins, and resolving it in dev mode logs a `console.warn` naming every competing ref. The `AGENT_LIFECYCLE` token follows the same rule. Always inject by ref when an injector provides more than one agent. **Lifecycle token.** Every form also provides `AGENT_LIFECYCLE`, so `inject(AGENT_LIFECYCLE)` returns the same object as `injectAgent().lifecycle` without reaching for the agent itself.

provideAgent(ref: AgentRef<T>, configOrFactory: AgentConfig<T, BagTemplate> | () => AgentConfig<T>): Provider[]

Parameters

ParameterTypeDescription
refAgentRef<T>
configOrFactoryAgentConfig<T, BagTemplate> | () => AgentConfig<T>

Returns

Provider[]

Examples

export const LIVE = createAgentRef<ChatState>('live');
export const REPLAY = createAgentRef<ChatState>('replay');
providers: [
  provideAgent(LIVE, { assistantId: 'chat' }),
  provideAgent(REPLAY, { assistantId: 'chat', transport: replayTransport }),
];
// component: injectAgent(LIVE) !== injectAgent(REPLAY)
providers: [
  provideAgent(() => {
    const route = inject(ActivatedRoute);
    return { assistantId: 'chat', threadId: toSignal(route.paramMap) };
  }),
];
export const TRIP = createAgentRef<TripState>('trip');
// app.config.ts:
providers: [provideAgent(TRIP, { assistantId: 'trip-graph' })]
// component:
const agent = injectAgent(TRIP); // LangGraphAgent<TripState>

Looking for something specific?