Skip to content

Commit 3631bbd

Browse files
authored
Rename agent→langgraph, symmetric adapter API, Choosing-an-adapter docs (#556)
Squash-merge of the agent→langgraph rename + symmetric adapter API + Choosing-an-adapter docs. All rename-caused CI failures fixed. The sole remaining red — cockpit-langgraph-interrupts e2e (aimock 'No fixture matched') — is pre-existing and fails identically on main; unrelated to this change. Merged via --admin to bypass the branch-protection gate held by that pre-existing check.
1 parent 12a994f commit 3631bbd

170 files changed

Lines changed: 4106 additions & 1409 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
Threadplane is a production-ready agent UI framework for Angular. Use `@threadplane/chat` for chat surfaces, `@threadplane/langgraph` for LangGraph-backed agents, `@threadplane/ag-ui` for AG-UI event streams, and `@threadplane/render` for generative UI that stays inside your Angular design system.
2828

29-
When you are building on LangGraph, `agent()` is the Angular equivalent of LangGraph's React `useStream()` hook, projected into a runtime-neutral `Agent` contract consumed by `@threadplane/chat`. Drop it into any Angular 20+ component, point it at your LangGraph Platform endpoint, and get signal-driven access to messages, status, tool calls, interrupts, subagents, regenerate, and thread history.
29+
When you are building on LangGraph, `injectAgent()` is the Angular equivalent of LangGraph's React `useStream()` hook, projected into a runtime-neutral `Agent` contract consumed by `@threadplane/chat`. Configure it once with `provideAgent({...})`, inject it into any Angular 20+ component, and get signal-driven access to messages, status, tool calls, interrupts, subagents, regenerate, and thread history.
3030

3131
---
3232

@@ -43,9 +43,22 @@ npm install @threadplane/langgraph @threadplane/chat
4343
## 30-Second Example
4444

4545
```typescript
46+
// app.config.ts — wire the adapter once
47+
import { provideAgent } from '@threadplane/langgraph';
48+
49+
export const appConfig: ApplicationConfig = {
50+
providers: [
51+
provideAgent({
52+
apiUrl: 'https://your-langgraph-platform.com',
53+
assistantId: 'my-agent',
54+
}),
55+
],
56+
};
57+
58+
// support-chat.component.ts
4659
import { Component } from '@angular/core';
4760
import { ChatComponent as ThreadplaneChatComponent } from '@threadplane/chat';
48-
import { agent } from '@threadplane/langgraph';
61+
import { injectAgent } from '@threadplane/langgraph';
4962

5063
@Component({
5164
selector: 'app-support-chat',
@@ -61,10 +74,7 @@ import { agent } from '@threadplane/langgraph';
6174
`,
6275
})
6376
export class SupportChatComponent {
64-
chat = agent({
65-
apiUrl: 'https://your-langgraph-platform.com',
66-
assistantId: 'my-agent',
67-
});
77+
protected readonly chat = injectAgent();
6878

6979
send() {
7080
void this.chat.submit({ message: 'Hello' });
@@ -78,7 +88,7 @@ That's it. `chat.messages()` and `chat.status()` are Angular Signals. Bind them
7888

7989
## Feature Comparison
8090

81-
| Feature | `agent()` (Angular) | `useStream()` (React) |
91+
| Feature | `injectAgent()` (Angular) | `useStream()` (React) |
8292
|---|---|---|
8393
| Streaming state as reactive primitives | Angular Signals | React state |
8494
| Messages signal | `messages()` | `messages` |
@@ -113,17 +123,18 @@ That's it. `chat.messages()` and `chat.status()` are Angular Signals. Bind them
113123
/>
114124
</p>
115125

116-
`agent()` creates its internal `BehaviorSubject`s at injection-context time — once, at component construction. The `StreamManager` bridge (the only file that touches `@langchain/langgraph-sdk` internals) pushes stream events into those subjects. `toSignal()` converts each subject to an Angular Signal, also at construction time. Dynamic actions (`submit`, `stop`, `switchThread`) push into the existing subjects — no new subjects are ever created after construction. This architecture is required because `toSignal()` must be called in an injection context and cannot be called again later.
126+
`injectAgent()` resolves an agent whose internal `BehaviorSubject`s were created at injection-context time — once, when `provideAgent()`'s factory ran. The `StreamManager` bridge (the only file that touches `@langchain/langgraph-sdk` internals) pushes stream events into those subjects. `toSignal()` converts each subject to an Angular Signal, also at construction time. Dynamic actions (`submit`, `stop`, `switchThread`) push into the existing subjects — no new subjects are ever created after construction. This architecture is required because `toSignal()` must be called in an injection context and cannot be called again later.
117127

118128
---
119129

120130
## Documentation
121131

122-
- [Agent Quickstart](https://threadplane.ai/docs/agent/getting-started/quickstart)
123-
- [agent() API](https://threadplane.ai/docs/agent/api/agent)
132+
- [LangGraph Quickstart](https://threadplane.ai/docs/langgraph/getting-started/quickstart)
133+
- [injectAgent() API](https://threadplane.ai/docs/langgraph/api/inject-agent)
134+
- [Choosing an adapter (LangGraph vs AG-UI)](https://threadplane.ai/docs/choosing-an-adapter)
124135
- [Chat Introduction](https://threadplane.ai/docs/chat/getting-started/introduction)
125-
- [Human-in-the-Loop / Interrupts](https://threadplane.ai/docs/agent/guides/interrupts)
126-
- [Subgraph and Subagent Streaming](https://threadplane.ai/docs/agent/guides/subgraphs)
136+
- [Human-in-the-Loop / Interrupts](https://threadplane.ai/docs/langgraph/guides/interrupts)
137+
- [Subgraph and Subagent Streaming](https://threadplane.ai/docs/langgraph/guides/subgraphs)
127138

128139
---
129140

apps/website/content/blog/2026-05-21-build-fullstack-agentic-angular-apps-using-ag-ui.mdx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,18 @@ yarn add @threadplane/ag-ui @threadplane/chat marked
9696
```ts
9797
// app.config.ts
9898
import { ApplicationConfig } from '@angular/core';
99-
import { provideAgUiAgent } from '@threadplane/ag-ui';
99+
import { provideAgent } from '@threadplane/ag-ui';
100100
import { provideChat } from '@threadplane/chat';
101101

102102
export const appConfig: ApplicationConfig = {
103103
providers: [
104-
provideAgUiAgent({ url: 'http://localhost:8000/agent' }),
104+
provideAgent({ url: 'http://localhost:8000/agent' }),
105105
provideChat({ assistantName: 'Astra' }),
106106
],
107107
};
108108
```
109109

110-
That's the whole bootstrap. `provideAgUiAgent` is the AG-UI transport. It wraps the official `@ag-ui/client` `HttpAgent` and exposes the signal-shaped contract via DI. `provideChat` is the chat UI's configuration.
110+
That's the whole bootstrap. `provideAgent` is the AG-UI transport. It wraps the official `@ag-ui/client` `HttpAgent` and exposes the signal-shaped contract via DI. `provideChat` is the chat UI's configuration.
111111

112112
Notice they're independent. `@threadplane/chat` doesn't know it's talking to an AG-UI backend. It just reads from the `Agent` contract. We'll lean on that boundary later.
113113

@@ -116,7 +116,7 @@ Notice they're independent. `@threadplane/chat` doesn't know it's talking to an
116116
```ts
117117
// chat-page.component.ts
118118
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
119-
import { AG_UI_AGENT } from '@threadplane/ag-ui';
119+
import { injectAgent } from '@threadplane/ag-ui';
120120
import { ChatComponent } from '@threadplane/chat';
121121

122122
@Component({
@@ -131,7 +131,7 @@ import { ChatComponent } from '@threadplane/chat';
131131
`,
132132
})
133133
export class ChatPageComponent {
134-
protected readonly agent = inject(AG_UI_AGENT);
134+
protected readonly agent = injectAgent();
135135
}
136136
```
137137

@@ -275,10 +275,10 @@ Say you started with a Python LangGraph backend, shipped to production, and a qu
275275

276276
```ts
277277
// before
278-
provideAgUiAgent({ url: '/agents/langgraph' }),
278+
provideAgent({ url: '/agents/langgraph' }),
279279

280280
// after
281-
provideAgUiAgent({ url: '/agents/mastra' }),
281+
provideAgent({ url: '/agents/mastra' }),
282282
```
283283

284284
That's the diff.

apps/website/content/docs/ag-ui/api/api-docs.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@
365365
]
366366
},
367367
{
368-
"name": "AgUiAgentConfig",
368+
"name": "AgentConfig",
369369
"kind": "interface",
370370
"description": "Configuration for the AG-UI agent provider.\nHttpAgentConfig shape (from @ag-ui/client@0.0.52):\n - url: string (required) — endpoint for the HTTP agent\n - agentId: string (optional) — agent identifier\n - threadId: string (optional) — thread identifier\n - headers: Record<string, string> (optional) — custom HTTP headers",
371371
"properties": [
@@ -403,7 +403,7 @@
403403
"examples": []
404404
},
405405
{
406-
"name": "FakeAgUiAgentConfig",
406+
"name": "FakeAgentConfig",
407407
"kind": "interface",
408408
"description": "",
409409
"properties": [
@@ -468,10 +468,10 @@
468468
"examples": []
469469
},
470470
{
471-
"name": "injectAgUiAgent",
471+
"name": "injectAgent",
472472
"kind": "function",
473-
"description": "Injects the AG_UI_AGENT from Angular's dependency injection container.\nUse this in components or services that have been provided via provideAgUiAgent().",
474-
"signature": "injectAgUiAgent(): Agent",
473+
"description": "Injects the Agent from Angular's dependency injection container.\nUse this in components or services that have been provided via provideAgent().",
474+
"signature": "injectAgent(): Agent",
475475
"params": [],
476476
"returns": {
477477
"type": "Agent",
@@ -480,14 +480,14 @@
480480
"examples": []
481481
},
482482
{
483-
"name": "provideAgUiAgent",
483+
"name": "provideAgent",
484484
"kind": "function",
485-
"description": "Provides an Agent instance wired through HttpAgent and toAgent.\nConstructs an HttpAgent from config and wraps it in the runtime-neutral\nAgent contract via toAgent(). Returns a provider array suitable for\nbootstrapApplication or TestBed.configureTestingModule().",
486-
"signature": "provideAgUiAgent(config: AgUiAgentConfig): Provider[]",
485+
"description": "Provides an Agent instance wired through HttpAgent and toAgent.\nConstructs an HttpAgent from config and wraps it in the runtime-neutral\nAgent contract via toAgent(). Returns a provider array suitable for\nbootstrapApplication or TestBed.configureTestingModule().\n\n**Static vs factory config.** Pass a plain `AgentConfig` object when the\nconfig is known up front. Pass a `() => AgentConfig` factory when the config\ndepends on runtime/DI state — the factory runs inside an Angular injection\ncontext, so it may call `inject()` to read services or route params.",
486+
"signature": "provideAgent(configOrFactory: AgentConfig | object): Provider[]",
487487
"params": [
488488
{
489-
"name": "config",
490-
"type": "AgUiAgentConfig",
489+
"name": "configOrFactory",
490+
"type": "AgentConfig | object",
491491
"description": "",
492492
"optional": false
493493
}
@@ -499,14 +499,14 @@
499499
"examples": []
500500
},
501501
{
502-
"name": "provideFakeAgUiAgent",
502+
"name": "provideFakeAgent",
503503
"kind": "function",
504-
"description": "Registers an in-process FakeAgent under AG_UI_AGENT.\n\nUse for offline demos and development. Drop-in replacement for\nprovideAgUiAgent({ url }) when no real backend is available.",
505-
"signature": "provideFakeAgUiAgent(config: FakeAgUiAgentConfig): Provider[]",
504+
"description": "Registers an in-process FakeAgent under AGENT.\n\nUse for offline demos and development. Drop-in replacement for\nprovideAgent({ url }) when no real backend is available.",
505+
"signature": "provideFakeAgent(config: FakeAgentConfig): Provider[]",
506506
"params": [
507507
{
508508
"name": "config",
509-
"type": "FakeAgUiAgentConfig",
509+
"type": "FakeAgentConfig",
510510
"description": "",
511511
"optional": false
512512
}

apps/website/content/docs/ag-ui/concepts/architecture.mdx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,39 +42,39 @@ Most Angular apps should use DI instead:
4242

4343
```ts
4444
import { ApplicationConfig } from '@angular/core';
45-
import { provideAgUiAgent } from '@threadplane/ag-ui';
45+
import { provideAgent } from '@threadplane/ag-ui';
4646

4747
export const appConfig: ApplicationConfig = {
4848
providers: [
49-
provideAgUiAgent({ url: '/api/agent' }),
49+
provideAgent({ url: '/api/agent' }),
5050
],
5151
};
5252
```
5353

54-
`provideAgUiAgent()` creates an AG-UI `HttpAgent` and registers the wrapped `Agent` under `AG_UI_AGENT`.
54+
`provideAgent()` creates an AG-UI `HttpAgent` and registers the wrapped `Agent` under an internal DI token. Retrieve it with `injectAgent()`.
5555

5656
```ts
5757
import { Component } from '@angular/core';
5858
import { ChatComponent } from '@threadplane/chat';
59-
import { injectAgUiAgent } from '@threadplane/ag-ui';
59+
import { injectAgent } from '@threadplane/ag-ui';
6060

6161
@Component({
6262
standalone: true,
6363
imports: [ChatComponent],
6464
template: `<chat [agent]="agent" />`,
6565
})
6666
export class ChatPage {
67-
protected readonly agent = injectAgUiAgent();
67+
protected readonly agent = injectAgent();
6868
}
6969
```
7070

7171
You can also inject the token directly:
7272

7373
```ts
7474
import { inject } from '@angular/core';
75-
import { AG_UI_AGENT } from '@threadplane/ag-ui';
75+
import { injectAgent } from '@threadplane/ag-ui';
7676

77-
const agent = inject(AG_UI_AGENT);
77+
const agent = injectAgent();
7878
```
7979

8080
## Runtime data flow
@@ -95,10 +95,10 @@ This is optimistic on purpose. The user message appears immediately while the ba
9595

9696
## Provider choices
9797

98-
Use `provideAgUiAgent()` when you have a real AG-UI HTTP endpoint.
98+
Use `provideAgent()` when you have a real AG-UI HTTP endpoint.
9999

100100
```ts
101-
provideAgUiAgent({
101+
provideAgent({
102102
url: '/api/agent',
103103
agentId: 'support-agent',
104104
threadId: 'thread-123',
@@ -110,16 +110,16 @@ The config maps directly to the AG-UI `HttpAgent` options currently exposed by t
110110

111111
`threadId` here is a plain string consumed once at construction — the provider does not accept an Angular Signal, and the adapter does not observe changes to it at runtime. The AG-UI protocol carries events, not snapshots, and defines no server-side endpoint for "fetch the messages of thread X". To move a user between threads with their prior conversation restored, you have two options:
112112

113-
- **Recreate the provider.** Inject `provideAgUiAgent({ ..., threadId: newId })` from a fresh injector when the active thread changes. Any prior message history must come from your own host service — pre-populate `setMessages()` on the source before the adapter boots, or render a "loading…" surface while you fetch it.
114-
- **Use the LangGraph adapter instead.** `@threadplane/langgraph` accepts `threadId: Signal<string | null>` and hydrates messages from the latest checkpoint on every change. See its [Persistence guide](/docs/agent/guides/persistence). Use AG-UI when your runtime publishes events without checkpoint storage; use LangGraph when the server owns durable thread state.
113+
- **Recreate the provider.** Inject `provideAgent({ ..., threadId: newId })` from a fresh injector when the active thread changes. Any prior message history must come from your own host service — pre-populate `setMessages()` on the source before the adapter boots, or render a "loading…" surface while you fetch it.
114+
- **Use the LangGraph adapter instead.** `@threadplane/langgraph` accepts `threadId: Signal<string | null>` and hydrates messages from the latest checkpoint on every change. See its [Persistence guide](/docs/langgraph/guides/persistence). Use AG-UI when your runtime publishes events without checkpoint storage; use LangGraph when the server owns durable thread state.
115115

116-
Use `provideFakeAgUiAgent()` when you need the UI to run without a backend:
116+
Use `provideFakeAgent()` when you need the UI to run without a backend:
117117

118118
```ts
119-
import { provideFakeAgUiAgent } from '@threadplane/ag-ui';
119+
import { provideFakeAgent } from '@threadplane/ag-ui';
120120

121121
providers: [
122-
provideFakeAgUiAgent({
122+
provideFakeAgent({
123123
tokens: ['Offline', ' demo', ' response.'],
124124
delayMs: 40,
125125
}),

apps/website/content/docs/ag-ui/getting-started/installation.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,18 @@ In your app config:
3232

3333
```ts
3434
import { ApplicationConfig } from '@angular/core';
35-
import { provideAgUiAgent } from '@threadplane/ag-ui';
35+
import { provideAgent } from '@threadplane/ag-ui';
3636

3737
export const appConfig: ApplicationConfig = {
3838
providers: [
39-
provideAgUiAgent({
39+
provideAgent({
4040
url: 'http://localhost:3000/agent', // your AG-UI backend
4141
}),
4242
],
4343
};
4444
```
4545

46-
`provideAgUiAgent` accepts:
46+
`provideAgent` accepts:
4747

4848
| Option | Type | Description |
4949
|---|---|---|
@@ -57,7 +57,7 @@ export const appConfig: ApplicationConfig = {
5757
```ts
5858
import { Component, inject } from '@angular/core';
5959
import { ChatComponent } from '@threadplane/chat';
60-
import { AG_UI_AGENT } from '@threadplane/ag-ui';
60+
import { injectAgent } from '@threadplane/ag-ui';
6161

6262
@Component({
6363
selector: 'app-streaming',
@@ -66,7 +66,7 @@ import { AG_UI_AGENT } from '@threadplane/ag-ui';
6666
template: `<chat [agent]="agent" />`,
6767
})
6868
export class StreamingComponent {
69-
protected readonly agent = inject(AG_UI_AGENT);
69+
protected readonly agent = injectAgent();
7070
}
7171
```
7272

@@ -75,19 +75,19 @@ export class StreamingComponent {
7575
Use the `FakeAgent` for offline demos:
7676

7777
```ts
78-
import { provideFakeAgUiAgent } from '@threadplane/ag-ui';
78+
import { provideFakeAgent } from '@threadplane/ag-ui';
7979

8080
export const appConfig: ApplicationConfig = {
8181
providers: [
82-
provideFakeAgUiAgent({
82+
provideFakeAgent({
8383
tokens: ['Hello', ' from', ' a', ' fake', ' agent.'],
8484
delayMs: 60,
8585
}),
8686
],
8787
};
8888
```
8989

90-
`FakeAgent` extends `AbstractAgent` and emits a canned `RUN_STARTED -> TEXT_MESSAGE_START -> TEXT_MESSAGE_CONTENT x N -> TEXT_MESSAGE_END -> RUN_FINISHED` sequence. Drop-in replacement for `provideAgUiAgent({ url })` while you're prototyping.
90+
`FakeAgent` extends `AbstractAgent` and emits a canned `RUN_STARTED -> TEXT_MESSAGE_START -> TEXT_MESSAGE_CONTENT x N -> TEXT_MESSAGE_END -> RUN_FINISHED` sequence. Drop-in replacement for `provideAgent({ url })` while you're prototyping.
9191

9292
## Custom transport
9393

apps/website/content/docs/ag-ui/getting-started/introduction.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Introduction
22

3+
> **Picking an adapter?** This guide covers `@threadplane/ag-ui` — the AG-UI protocol adapter. If you're talking to LangGraph Platform directly via the LangGraph SDK, use [`@threadplane/langgraph`](/langgraph) instead. See [Choosing an adapter](/docs/choosing-an-adapter) for a side-by-side comparison.
4+
35
`@threadplane/ag-ui` is the runtime adapter that wraps an [AG-UI](https://github.com/ag-ui-protocol/ag-ui) `AbstractAgent` into the runtime-neutral `Agent` contract from `@threadplane/chat`. The chat UI primitives consume the Agent contract; the AG-UI adapter translates between the contract and the AG-UI event protocol.
46

57
<Callout type="info" title="What is AG-UI?">
@@ -25,7 +27,7 @@ AG-UI is the open agent-to-UI protocol from the CopilotKit ecosystem. It standar
2527
## What you get
2628

2729
- **`toAgent(source: AbstractAgent): Agent`** - wraps any `AbstractAgent` subclass (custom transports, mocks) into the runtime-neutral `Agent` contract.
28-
- **`provideAgUiAgent({ url })`** - DI convenience that instantiates `HttpAgent` under the hood for the common SSE/HTTP case.
30+
- **`provideAgent({ url })`** - DI convenience that instantiates `HttpAgent` under the hood for the common SSE/HTTP case.
2931
- **`FakeAgent`** - in-process `AbstractAgent` subclass that emits canned streaming events for offline demos and tests.
3032

3133
## What's covered

0 commit comments

Comments
 (0)