FakeAgent
FakeAgent is an in-process AG-UI test double that emits a canned streaming response without a real backend. Use it for offline development, CI, and component tests.
provideFakeAgent()
provideFakeAgent() is the DI-friendly entry point. It is a drop-in replacement for the ref-less provideAgent({ url }) when no backend is available: it registers the fake under the same shared token, so injectAgent() resolves it.
import { bootstrapApplication } from '@angular/platform-browser';
import { provideFakeAgent } from '@threadplane/ag-ui';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideFakeAgent(),
],
});Pass an AgUiFakeAgentConfig to customize the canned response:
provideFakeAgent({
tokens: ['Hello', ' world', '!'],
delayMs: 40,
})AgUiFakeAgentConfig
| Option | Type | Description |
|---|---|---|
tokens | string[] | Assistant reply streamed token-by-token. Defaults to a fixed placeholder message. |
reasoningTokens | string[] | Optional reasoning chunks emitted before the text reply. |
delayMs | number | Milliseconds between successive token emissions. Defaults to 60. |
script | FakeAgentScript | Raw AG-UI event branches that replace the canned reply. |
The first three options are the shared FakeAgentConfig that every adapter's provideFakeAgent() accepts. script is the AG-UI-specific addition.
FakeAgent class
Construct a FakeAgent directly when you need lower-level control, for example to pass it to toAgent() in a test harness.
import { FakeAgent } from '@threadplane/ag-ui';
import { toAgent } from '@threadplane/ag-ui';
const agent = toAgent(new FakeAgent({
tokens: ['Thinking', '...', ' done.'],
reasoningTokens: ['Step 1', ': evaluate'],
delayMs: 30,
}));FakeAgent extends AbstractAgent from @ag-ui/client. Its run() method returns an Observable<BaseEvent> that emits the full event sequence — RUN_STARTED, optional reasoning events, TEXT_MESSAGE_START / TEXT_MESSAGE_CONTENT tokens, TEXT_MESSAGE_END, RUN_FINISHED — then completes.
script
FakeAgentScript is an exported type. Both the constructor and provideFakeAgent() accept it:
type FakeAgentScript = readonly {
when: 'initial' | { toolMessageFor: string };
events: readonly BaseEvent[];
}[];Each branch supplies a raw AG-UI event sequence for tests that need an exact stream — tool calls, STATE_SNAPSHOT, CUSTOM events, anything the protocol defines. when: 'initial' matches a turn whose history carries no tool result; { toolMessageFor: id } matches the follow-up turn whose history carries a tool result for that tool call id, which is what a resolved client tool produces. The first matching branch wins, and FakeAgent wraps its events in RUN_STARTED and RUN_FINISHED for you. When no branch matches, the canned token reply is emitted instead.
Through DI, a script reaches the reducer exactly as wire events would, so toolCalls(), state(), customEvents(), and interrupt() all populate:
import { TestBed } from '@angular/core/testing';
import { EventType, type BaseEvent } from '@ag-ui/client';
import { provideFakeAgent, injectAgent } from '@threadplane/ag-ui';
it('reduces a scripted tool call', async () => {
TestBed.configureTestingModule({
providers: [
provideFakeAgent({
delayMs: 0,
script: [
{
when: 'initial',
events: [
{
type: EventType.TOOL_CALL_START,
toolCallId: 'tool-1',
toolCallName: 'get_weather',
} as BaseEvent,
{
type: EventType.TOOL_CALL_ARGS,
toolCallId: 'tool-1',
delta: '{"city":"SF"}',
} as BaseEvent,
{ type: EventType.TOOL_CALL_END, toolCallId: 'tool-1' } as BaseEvent,
],
},
],
}),
],
});
const agent = TestBed.runInInjectionContext(() => injectAgent());
await agent.submit({ message: 'weather?' });
expect(agent.toolCalls()[0]).toMatchObject({
name: 'get_weather',
args: { city: 'SF' },
});
});TestBed example
import { TestBed } from '@angular/core/testing';
import { provideFakeAgent, injectAgent } from '@threadplane/ag-ui';
import { Component } from '@angular/core';
@Component({ template: '' })
class ChatComponent {
readonly chat = injectAgent();
}
describe('ChatComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ChatComponent],
providers: [
provideFakeAgent({ tokens: ['Hello', ' from', ' fake'] }),
],
});
});
it('streams a canned reply', async () => {
const fixture = TestBed.createComponent(ChatComponent);
fixture.detectChanges();
await fixture.componentInstance.chat.submit({ message: 'Hi' });
fixture.detectChanges();
expect(fixture.componentInstance.chat.messages().at(-1)?.content)
.toBe('Hello from fake');
});
});FakeAgent and provideFakeAgent() are intended for development and tests
only. Do not use them in production builds.
See also: Fake Agent guide for practical offline-development patterns, and Testing guide for full component-testing recipes.