Page actions

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

OptionTypeDescription
tokensstring[]Assistant reply streamed token-by-token. Defaults to a fixed placeholder message.
reasoningTokensstring[]Optional reasoning chunks emitted before the text reply.
delayMsnumberMilliseconds between successive token emissions. Defaults to 60.
scriptFakeAgentScriptRaw 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');
  });
});
Note: Not for production

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.

What's Next

FakeAgentclass

In-process AG-UI agent that emits a canned streaming response. Use for offline demos and tests where a real backend isn't available. Echoes a fixed assistant reply token-by-token with realistic timing. NOT for production use.

Parameters

ParameterTypeDescription
opts?object

Properties

ParameterTypeDescription
agentId?string
descriptionstring
isRunningboolean
messagesobject | object | object | object | object | object | object[]
pendingInterruptsobject[]Interrupts emitted by the most recent run that have not yet been resolved. Populated when RUN_FINISHED arrives with outcome.type === "interrupt". Cleared when a subsequent run completes successfully.
stateany
subscribersAgentSubscriber[]
threadIdstring

Methods

abortRun(): void
addMessage(message: object | object | object | object | object | object | object): void
ParameterTypeDescription
messageobject | object | object | object | object | object | object
addMessages(messages: object | object | object | object | object | object | object[]): void
ParameterTypeDescription
messagesobject | object | object | object | object | object | object[]
apply(input: object, events$: Observable<objectOutputType<object, ZodTypeAny, "passthrough">>, subscribers: AgentSubscriber[]): Observable<AgentStateMutation>
ParameterTypeDescription
inputobject
events$Observable<objectOutputType<object, ZodTypeAny, "passthrough">>
subscribersAgentSubscriber[]
clone(): any
connect(input: object): Observable<objectOutputType<object, ZodTypeAny, "passthrough">>
ParameterTypeDescription
inputobject
connectAgent(parameters: RunAgentParameters<>, subscriber: AgentSubscriber): Promise<RunAgentResult>
ParameterTypeDescription
parameters?RunAgentParameters<>
subscriber?AgentSubscriber
detachActiveRun(): Promise<void>
getCapabilities(): Promise<object>

Returns the agent's current capabilities. Optional — subclasses implement this to advertise what they support.

legacy_to_be_removed_runAgentBridged(config: RunAgentParameters<>): Observable<object | object | object | object | object | object | object | object | object | object>
ParameterTypeDescription
config?RunAgentParameters<>
onError(input: object, error: Error, subscribers: AgentSubscriber[]): Observable<AgentStateMutation>
ParameterTypeDescription
inputobject
errorError
subscribersAgentSubscriber[]
onFinalize(input: object, subscribers: AgentSubscriber[]): Promise<void>
ParameterTypeDescription
inputobject
subscribersAgentSubscriber[]
onInitialize(input: object, subscribers: AgentSubscriber[]): Promise<void>
ParameterTypeDescription
inputobject
subscribersAgentSubscriber[]
prepareRunAgentInput(parameters: RunAgentParameters<>): object
ParameterTypeDescription
parameters?RunAgentParameters<>
processApplyEvents(input: object, events$: Observable<AgentStateMutation>, subscribers: AgentSubscriber[]): Observable<AgentStateMutation>
ParameterTypeDescription
inputobject
events$Observable<AgentStateMutation>
subscribersAgentSubscriber[]
run(input: object): Observable<objectOutputType<object, ZodTypeAny, "passthrough">>
ParameterTypeDescription
inputobject
runAgent(parameters: RunAgentParameters<>, subscriber: AgentSubscriber): Promise<RunAgentResult>
ParameterTypeDescription
parameters?RunAgentParameters<>
subscriber?AgentSubscriber
setMessages(messages: object | object | object | object | object | object | object[]): void
ParameterTypeDescription
messagesobject | object | object | object | object | object | object[]
setState(state: any): void
ParameterTypeDescription
stateany
subscribe(subscriber: AgentSubscriber): object
ParameterTypeDescription
subscriberAgentSubscriber
use(...middlewares: Middleware<> | MiddlewareFunction[]): this
ParameterTypeDescription
...middlewaresMiddleware<> | MiddlewareFunction[]

Looking for something specific?