provideAgent()
provideAgent() builds an AG-UI agent from connection options and registers it in Angular's dependency injection container, where injectAgent() reads it back. Call it in bootstrapApplication, in an ApplicationConfig, or in a route's or component's providers array to wire up the endpoint URL, optional identifiers, custom headers, and telemetry.
The function has two overloads:
provideAgent(configOrFactory: AgentConfig | (() => AgentConfig)): Provider[];
provideAgent<T>(ref: AgentRef<T>, configOrFactory: AgentConfig | (() => AgentConfig)): Provider[];The ref-less form registers the agent under a single shared token that the no-argument injectAgent() resolves. The AgentRef form registers it under that ref's own token, which carries the state type to injectAgent(ref) and lets more than one agent coexist at one injector level.
Ref-less form
Most applications talk to one backend and need one agent. Pass the config on its own:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideAgent } from '@threadplane/ag-ui';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideAgent({
url: 'http://localhost:8000/my-agent',
}),
],
});Every injectAgent() call under that injector then resolves the same agent instance:
const agent = injectAgent(); // AgUiAgent<Record<string, unknown>>Configuration options
| Option | Type | Description |
|---|---|---|
url | string | HTTP endpoint for the AG-UI backend agent. Required. |
agentId | string | Agent identifier, when the endpoint serves more than one agent. |
threadId | string | Thread to connect to on start. Omit to begin a fresh conversation. |
interruptTransport | InterruptTransport | auto (default), protocol, legacy-command, or mastra-command. Native batches win in auto; select the Mastra command profile for the Mastra integration. |
persistence | AgUiInterruptPersistence | Application-owned store and optional authoritative reconciler. Requires a stable configured threadId and scoped namespace. |
headers | Record<string, string> | Extra HTTP headers sent with every request. |
telemetry | AgentRuntimeTelemetrySink | false | Omit for automatic development-only collection, pass false to disable it, or pass an app-owned sink to receive the runtime lifecycle events yourself. |
With persistence enabled, await agent.ready before displaying restored approval controls. The store must implement atomic compareAndSwap; browser state alone cannot restore a lost backend checkpoint. The provider disposes the adapter when its injector is destroyed, stopping local work without cancelling backend checkpoints.
Static versus factory config
Pass a plain AgentConfig object when the URL 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 environment tokens.
import { inject } from '@angular/core';
import { provideAgent } from '@threadplane/ag-ui';
import { SessionService } from './session.service';
// Factory form — reads the signed-in user's session token at runtime
provideAgent(() => {
const session = inject(SessionService);
return {
url: '/api/agent',
headers: { Authorization: `Bearer ${session.accessToken()}` },
};
});Both overloads accept either shape, so the factory form works with an AgentRef too.
Every value in headers ships in the browser bundle and travels with every request. A per-user session token belongs there. An API key or any other server credential does not, because the bundle is public. Keep those on a same-origin endpoint that you own and that adds them on the way to the agent server. The Deployment guide shows that endpoint.
Typed state with AgentRef
AG-UI shared state arrives on agent.state(). Declaring an AgentRef once flows that state shape from the provider to every injection site, so the generic does not have to be restated at each call:
import { createAgentRef } from '@threadplane/chat';
import { provideAgent, injectAgent } from '@threadplane/ag-ui';
interface TripState {
day: number;
places: string[];
}
export const TRIP = createAgentRef<TripState>('trip');
// app.config.ts
providers: [provideAgent(TRIP, { url: 'http://localhost:8000/trip' })];
// component
const agent = injectAgent(TRIP); // AgUiAgent<TripState>
const day = agent.state().day; // number, not unknowncreateAgentRef is exported from @threadplane/chat, which @threadplane/ag-ui already depends on.
Several agents at one injector level
Each provideAgent(ref, …) call builds its own agent under its own ref token, so two or more refs may sit side by side in a single providers array and return distinct agents:
export const TRIP = createAgentRef<TripState>('trip');
export const SUPPORT = createAgentRef<SupportState>('support');
providers: [
provideAgent(TRIP, { url: 'http://localhost:8000/trip' }),
provideAgent(SUPPORT, { url: 'http://localhost:8000/support' }),
];
// component
const trip = injectAgent(TRIP); // the trip agent
const support = injectAgent(SUPPORT); // the support agentThe ref form also aliases the shared token so that the no-argument injectAgent() keeps working. That token can only point at one agent, so when several refs are provided at the same injector level the last provideAgent(ref, …) call wins. In the example above, a bare injectAgent() returns the support agent. Always inject by ref when an injector provides more than one agent.
Development builds do not leave this silent. The first time such an injector builds one of its agents, the adapter emits a single console.warn naming every ref registered at that level and the one the ref-less injectAgent() resolves:
[@threadplane/ag-ui] provideAgent() was called with more than one AgentRef at the
same injector level (trip, support). The ref-less injectAgent() reads a single
shared token, so it resolves the last ref provided (support) and the others are
reachable only by ref. Inject by ref — injectAgent(ref) — when an injector
provides more than one agent.The warning is development-only (isDevMode()), fires once per injector, and never changes what DI hands back: each ref keeps its own agent. Refs provided at different injector levels — one in the application config, another in a component's providers — do not collide and do not warn.
With a single ref the alias is exact: one instance, one config evaluation, reachable both as injectAgent(TRIP) and as injectAgent().