Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions apps/website/content/AGENTS.md.template
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ Production-ready chat, durable threads, interrupts, subagents, planning, memory,
npm install @threadplane/chat @threadplane/langgraph

## Key requirement
`agent()` MUST be called within an Angular injection context (component constructor or field initializer). Calling it in ngOnInit or any async context throws "NG0203: inject() must be called from an injection context".
`injectAgent()` MUST be called within an Angular injection context (component constructor or field initializer). Calling it in ngOnInit or any async context throws "NG0203: inject() must be called from an injection context".

## Basic usage
```typescript
// app.config.ts
import type { ApplicationConfig } from '@angular/core';
import { provideAgent } from '@threadplane/langgraph';
export const appConfig: ApplicationConfig = {
providers: [provideAgent({ apiUrl: 'http://localhost:2024' })]
providers: [provideAgent({ apiUrl: 'http://localhost:2024', assistantId: 'chat_agent' })]
};

// chat.component.ts
import { Component } from '@angular/core';
import { agent } from '@threadplane/langgraph';
import { injectAgent } from '@threadplane/langgraph';
import { ChatComponent as ThreadplaneChatComponent } from '@threadplane/chat';

@Component({
Expand All @@ -29,15 +29,15 @@ import { ChatComponent as ThreadplaneChatComponent } from '@threadplane/chat';
`,
})
export class ChatComponent {
chat = agent({ apiUrl: 'http://localhost:2024', assistantId: 'chat_agent' });
chat = injectAgent();
}
```

## Key patterns
- Thread persistence: `threadId: signal(localStorage.getItem('t'))` + `onThreadId: (id) => localStorage.setItem('t', id)`
- Global config: `provideAgent({ apiUrl })` in app.config.ts
- Per-call override: pass `apiUrl` directly to `agent()`
- Testing: use `MockAgentTransport` — never mock `agent()` itself
- Thread persistence: configure `provideAgent({ assistantId, threadId: signal(localStorage.getItem('t')), onThreadId })`
- Global config: `provideAgent({ apiUrl, assistantId })` in app.config.ts
- Scoped config: re-provide `provideAgent({ apiUrl, assistantId })` in a component `providers` array for a subtree
- Testing: use `MockAgentTransport` — never mock `injectAgent()` itself

## Version check
If this file is stale, fetch the latest: https://threadplane.ai/llms-full.txt
16 changes: 8 additions & 8 deletions apps/website/content/CLAUDE.md.template
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ Production-ready chat, durable threads, interrupts, subagents, planning, memory,
npm install @threadplane/chat @threadplane/langgraph

## Key requirement
`agent()` MUST be called within an Angular injection context (component constructor or field initializer). Calling it in ngOnInit or any async context throws "NG0203: inject() must be called from an injection context".
`injectAgent()` MUST be called within an Angular injection context (component constructor or field initializer). Calling it in ngOnInit or any async context throws "NG0203: inject() must be called from an injection context".

## Basic usage
```typescript
// app.config.ts
import type { ApplicationConfig } from '@angular/core';
import { provideAgent } from '@threadplane/langgraph';
export const appConfig: ApplicationConfig = {
providers: [provideAgent({ apiUrl: 'http://localhost:2024' })]
providers: [provideAgent({ apiUrl: 'http://localhost:2024', assistantId: 'chat_agent' })]
};

// chat.component.ts
import { Component } from '@angular/core';
import { agent } from '@threadplane/langgraph';
import { injectAgent } from '@threadplane/langgraph';
import { ChatComponent as ThreadplaneChatComponent } from '@threadplane/chat';

@Component({
Expand All @@ -29,15 +29,15 @@ import { ChatComponent as ThreadplaneChatComponent } from '@threadplane/chat';
`,
})
export class ChatComponent {
chat = agent({ apiUrl: 'http://localhost:2024', assistantId: 'chat_agent' });
chat = injectAgent();
}
```

## Key patterns
- Thread persistence: `threadId: signal(localStorage.getItem('t'))` + `onThreadId: (id) => localStorage.setItem('t', id)`
- Global config: `provideAgent({ apiUrl })` in app.config.ts
- Per-call override: pass `apiUrl` directly to `agent()`
- Testing: use `MockAgentTransport` — never mock `agent()` itself
- Thread persistence: configure `provideAgent({ assistantId, threadId: signal(localStorage.getItem('t')), onThreadId })`
- Global config: `provideAgent({ apiUrl, assistantId })` in app.config.ts
- Scoped config: re-provide `provideAgent({ apiUrl, assistantId })` in a component `providers` array for a subtree
- Testing: use `MockAgentTransport` — never mock `injectAgent()` itself

## Version check
If this file is stale, fetch the latest: https://threadplane.ai/llms-full.txt
6 changes: 3 additions & 3 deletions apps/website/content/docs/ag-ui/concepts/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -189,15 +189,15 @@ The AG-UI adapter currently covers:
- Shared state from `STATE_SNAPSHOT` and `STATE_DELTA`.
- Message replacement from `MESSAGES_SNAPSHOT`.
- Custom events from `CUSTOM`.
- Interrupts from `CUSTOM` events named `on_interrupt`.
- Subagent/activity progress from `ACTIVITY_SNAPSHOT` and `ACTIVITY_DELTA`.
- Citations stored under `state.citations`.

These features are intentionally out of scope for the AG-UI adapter today:

- Interrupt workflows.
- Subagents.
- **History and time-travel.** AG-UI is an event-stream protocol — it doesn't define a server-side "fetch state of thread X" endpoint, so the adapter can't hydrate prior messages on a `threadId` change the way a checkpoint-aware runtime can. The [Provider choices](#provider-choices) section above describes the two patterns AG-UI consumers use to work around this.

If those are central to your product, use the LangGraph adapter for that surface or build a custom adapter against the `@threadplane/chat` `Agent` contract. The [Writing an Adapter guide](/docs/chat/guides/writing-an-adapter#hydrating-from-a-server-stored-thread) walks through the thread-loading design choice in detail.
If server-side history or time-travel is central to your product, use the LangGraph adapter for that surface or build a custom adapter against the `@threadplane/chat` `Agent` contract. The [Writing an Adapter guide](/docs/chat/guides/writing-an-adapter#hydrating-from-a-server-stored-thread) walks through the thread-loading design choice in detail.

## Next steps

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ Here's what the first release handles:
- `toolCalls` (streaming tool calls via `TOOL_CALL_*` events)
- `state` (snapshots and JSON-Patch deltas)
- `events$` (custom events; discriminates `state_update`)
- Interrupts (from `CUSTOM` events named `on_interrupt`)
- Subagent/activity progress (from `ACTIVITY_*` events)

Out of scope for now (use `@threadplane/langgraph` if you need these):
- Interrupts
- Subagents
Out of scope for now (use `@threadplane/langgraph` if you need LangGraph Platform-specific APIs):
- History / time-travel

## Next steps
Expand Down
17 changes: 9 additions & 8 deletions apps/website/content/docs/ag-ui/guides/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,21 +77,22 @@ agent.error();

## Tool call args are empty

`TOOL_CALL_ARGS` parses the event `delta` as JSON.
`TOOL_CALL_ARGS` appends each event `delta` to the tool call's accumulated argument buffer. When the accumulated buffer is valid JSON, the reducer stores the parsed object as the current args.

This works:
This can arrive as one complete payload:

```ts
{ type: 'TOOL_CALL_ARGS', toolCallId: 't1', delta: '{"query":"Angular"}' }
```

This becomes `{}`:
Or as fragments:

```ts
{ type: 'TOOL_CALL_ARGS', toolCallId: 't1', delta: '{"query":' }
{ type: 'TOOL_CALL_ARGS', toolCallId: 't1', delta: '"Angular"}' }
```

The current reducer replaces args with each parsed payload. It does not assemble partial JSON fragments across multiple `TOOL_CALL_ARGS` events. Emit complete JSON for each args event.
While the buffer is incomplete, the previously parsed args remain in place. If the final buffer still cannot be parsed at `TOOL_CALL_END`, the adapter leaves the last-good parsed args instead of throwing.

## Citations do not show up

Expand Down Expand Up @@ -132,13 +133,13 @@ Cancellation depends on the AG-UI source implementation. `HttpAgent` supports ab

Pass the index of the assistant message you want to replace, not the user message.

## Interrupts, subagents, or history do not work
## History or time-travel does not work

Those flows are not implemented by the AG-UI adapter today.
History and time-travel are not implemented by the AG-UI adapter today.

Current scope is messages, status/loading/error, tool calls, state/custom events, reasoning messages, message snapshots, and citations from state.
Current scope is messages, status/loading/error, tool calls, state/custom events, reasoning messages, message snapshots, interrupts from `CUSTOM` `on_interrupt` events, subagent/activity progress from `ACTIVITY_*` events, and citations from state.

Use `@threadplane/langgraph` for the richer LangGraph-specific surface, or write a custom adapter against the `@threadplane/chat` `Agent` contract if you need AG-UI plus product-specific behavior.
Use `@threadplane/langgraph` if you need LangGraph Platform thread history APIs, or write a custom adapter against the `@threadplane/chat` `Agent` contract if you need AG-UI plus product-specific behavior.

## Isolate with FakeAgent

Expand Down
12 changes: 9 additions & 3 deletions apps/website/content/docs/ag-ui/reference/event-mapping.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This page is the compatibility map. If your backend emits these events with the
| `REASONING_MESSAGE_CHUNK` | `messages` | Treated the same as `REASONING_MESSAGE_CONTENT`. |
| `REASONING_MESSAGE_END` | `messages` | Adds `reasoningDurationMs` when timing is available. |
| `TOOL_CALL_START` | `toolCalls`, `messages` | Adds a running tool call; also links the call to its parent assistant message (via `parentMessageId`), creating a message slot if needed. |
| `TOOL_CALL_ARGS` | `toolCalls` | Parses `delta` as JSON and replaces args on the matching tool call. |
| `TOOL_CALL_ARGS` | `toolCalls` | Accumulates partial JSON fragments, keeps the last-good parsed args, and finalizes parsing on `TOOL_CALL_END`. |
| `TOOL_CALL_RESULT` | `toolCalls` | Stores the tool result on the matching tool call. |
| `TOOL_CALL_END` | `toolCalls` | Marks the matching tool call complete. |
| `STATE_SNAPSHOT` | `state`, `messages` | Replaces state and merges citations from `state.citations`. |
Expand Down Expand Up @@ -113,7 +113,9 @@ The resulting tool call is:
}
```

`TOOL_CALL_ARGS` expects the `delta` value to be parseable JSON. If parsing fails, args become `{}`. The current reducer replaces args with each parsed payload; it does not merge partial JSON fragments.
`TOOL_CALL_ARGS` accepts partial JSON fragments. The reducer appends each `delta` to an internal buffer for the tool call, parses whenever the buffer becomes valid JSON, and keeps the last-good args while more fragments arrive. On `TOOL_CALL_END`, it performs one final parse before marking the call complete.

If the accumulated buffer never parses, args stay at the last-good value or `{}`.

## State

Expand Down Expand Up @@ -171,6 +173,10 @@ If `message` is omitted, no user message is appended, but `runAgent()` still run

`agent.stop()` calls `source.abortRun()`. Cancellation depends on the AG-UI source implementation.

## Activity and subagents

`ACTIVITY_SNAPSHOT` and `ACTIVITY_DELTA` events project into the AG-UI-specific `subagents()` signal. Use that signal for progress cards and nested task views backed by AG-UI activity streams.

## Unsupported protocol areas

Beyond the `on_interrupt` -> `interrupt` mapping documented above (and its `submit({ resume })` reply), the adapter implements no richer interrupt UI flow — no subagents, history, or time-travel. Unknown protocol events are ignored rather than treated as errors.
The adapter supports `CUSTOM` `on_interrupt` events for the runtime-neutral `interrupt()` signal, and it supports ACTIVITY-backed subagent progress. It does not implement history or time-travel. Unknown protocol events are ignored rather than treated as errors.
27 changes: 14 additions & 13 deletions apps/website/content/docs/chat/a2ui/catalog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Renders children in a scrollable vertical list (max height 24rem).
| `spec` | `Spec` | Injected automatically by the render engine |

<Callout type="info" title="Template children">
For data-driven lists, use the `A2uiChildTemplate` form instead of static `childKeys`. Set `children` to `{"path": "/items", "componentId": "item-template"}` and the surface component will expand the template once per array item. See the [Surface Component](/docs/chat/a2ui/surface-component) page for details.
For data-driven lists, use the `A2uiChildTemplate` form instead of static `childKeys`. Set `children` to `{"template": {"componentId": "item-template", "dataBinding": "/items"}}` and the surface component will expand the template once per array item. See the [Surface Component](/docs/chat/a2ui/surface-component) page for details.
</Callout>

## Interactive Components
Expand All @@ -155,8 +155,8 @@ Renders a button that dispatches an action when clicked.
| `childKeys` | `string[]` | Child component IDs whose rendered output is the button's content (e.g., a `Text` label) |
| `primary` | `boolean` | Renders the primary visual style. Defaults to `true` |
| `disabled` | `boolean` | Disables the button when `true` |
| `action` | `A2uiAction` | Action to execute on click (event or function call) |
| `validationResult` | `A2uiValidationResult` | Pre-computed validation result button is disabled if `valid` is `false` |
| `action` | `A2uiAction` | Agent-bound action to emit on click |
| `validationResult` | `A2uiValidationResult` | Pre-computed validation result. The button is disabled if `valid` is `false` |
| `emit` | injected | Event emitter provided by the render engine |

<Callout type="info" title="child vs childKeys">
Expand All @@ -166,18 +166,19 @@ The Angular `A2uiButtonComponent` input is `childKeys` (an array) — that's the
**Action types:**

```json
// Emit a named event with resolved context (sent back to the agent as v1 action)
{"action": {"event": {"name": "submit", "context": {"email": {"path": "/email"}, "formId": "contact"}}}}

// Execute a local function (e.g., open a URL) — agent never sees this
{"action": {"functionCall": {"call": "openUrl", "args": {"url": "https://example.com"}}}}
// Emit a named action with resolved context (sent back to the agent as v1 action)
{
"action": {
"name": "submit",
"context": [
{ "key": "email", "value": { "path": "/email" } },
{ "key": "formId", "value": { "literalString": "contact" } }
]
}
}
```

**Validation checks** reference built-in validators by name. If any check fails, the button is automatically disabled:

```json
{"checks": [{"call": "required", "args": {"value": {"path": "/email"}}, "message": "Email is required"}]}
```
Validation is data-driven. Send a resolved `validationResult` on the component when the backend has already evaluated validity.

### TextField

Expand Down
16 changes: 7 additions & 9 deletions apps/website/content/docs/chat/a2ui/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ assistant text starts with ---a2ui_JSON---
-> A2uiSurfaceComponent renders progressive state through your catalog
```

This is why A2UI sits between chat and render. Chat owns message streaming. A2UI owns the protocol shapes. The normal chat path uses progressive surface state and `a2uiSlot` so components can appear as their required data arrives.

There is also a compatibility path: when `A2uiSurfaceComponent` receives a `surface` without a `state`, it calls `surfaceToSpec()` and renders through `@threadplane/render`. That fallback is where render-spec handlers, render events, and json-render state bindings apply.
This is why A2UI sits between chat and render. Chat owns message streaming. A2UI owns the protocol shapes. `A2uiSurfaceComponent` turns the accumulated surface state into a render spec and delegates to `@threadplane/render`, so handlers, render events, and json-render state bindings use the same path for both the preferred `state` input and the legacy `surface` input.

## Message Envelopes

Expand Down Expand Up @@ -145,7 +143,7 @@ Components use keyed union definitions:

This is different from a flat `component: "Text"` shape. The current source expects the keyed union form.

This stream demonstrates the protocol boundary, not a guarantee that every built-in catalog component is fully wired in the progressive chat renderer. The current chat path renders surface state first and pushes resolved `A2uiComponentView.props` directly into Angular components. The richer projection work - unwrapping component definitions, mapping children, creating render state bindings, and turning actions into handlers - lives in the render-spec compatibility path.
This stream demonstrates the protocol boundary. The chat path accumulates the surface state as JSONL arrives, then `A2uiSurfaceComponent` converts the current surface into a render spec: it unwraps keyed component definitions, maps children, creates render state bindings, and turns actions into handlers.

## Data Model

Expand All @@ -163,7 +161,7 @@ A2UI component props can point at the surface data model.
}
```

In the render-spec compatibility path, `surfaceToSpec()` converts path references into json-render state bindings. Catalog input components can use `emitBinding()` to write back through the render event pipeline.
`surfaceToSpec()` converts path references into json-render state bindings. Catalog input components can use `emitBinding()` to write back through the render event pipeline.

```ts
import { emitBinding } from '@threadplane/chat';
Expand All @@ -189,7 +187,7 @@ Buttons carry an `A2uiAction`:
}
```

In the render-spec compatibility path, `surfaceToSpec()` turns this into a render `click` binding that calls the built-in `a2ui:event` handler. `A2uiSurfaceComponent` then emits an `A2uiActionMessage`.
`surfaceToSpec()` turns this into a render `click` binding that calls the built-in `a2ui:event` handler. `A2uiSurfaceComponent` then emits an `A2uiActionMessage`.

```json
{
Expand All @@ -208,11 +206,11 @@ In the render-spec compatibility path, `surfaceToSpec()` turns this into a rende

If the internal surface object has `sendDataModel: true`, the emitted message also includes `metadata.a2uiClientDataModel` with the current surface data model snapshot. Streamed protocol surfaces created by the current surface store do not set that flag.

In the progressive chat path, `A2uiSurfaceComponent` renders the surface state first. Catalog components receive resolved props as Angular inputs. The current `a2uiSlot` implementation does not wire render-spec handlers, child spec context, or render events into those mounted components. Treat agent-bound action messages as a render-spec compatibility behavior unless your catalog explicitly handles its own event wiring.
Catalog components receive resolved props as Angular inputs from the render engine. Bind `(action)` when you want agent-bound events, and bind `(events)` when you want the lower-level render stream.

## Local Handlers

In the render-spec compatibility path, `A2uiSurfaceComponent` also registers an `a2ui:localAction` handler. Consumer handlers take priority, and the built-in fallback currently supports `openUrl`.
`A2uiSurfaceComponent` also registers an `a2ui:localAction` handler. Consumer handlers take priority, and the built-in fallback currently supports `openUrl`.

Use local handlers for client-owned behavior. Use A2UI actions for agent-bound events.

Expand Down Expand Up @@ -244,7 +242,7 @@ Both paths render structured UI, but they optimize for different jobs.
| State | Surface data model | Spec state |
| Best fit | Incremental agent-owned surfaces; protocol-level A2UI streams | One-shot rendered content |
| Detection | `---a2ui_JSON---` prefix | JSON object content |
| Rendering | Progressive surface state in chat; render-spec fallback when no state is supplied | json-render spec directly |
| Rendering | Surface state converted to a render spec | json-render spec directly |

Use A2UI when the agent needs to keep updating a surface and you are working at the protocol boundary. Use json-render when the agent needs a stable, directly rendered structured result. For production interaction that depends on component handlers, state bindings, and child projection, verify the exact A2UI rendering path you are using.

Expand Down
Loading
Loading