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
52 changes: 31 additions & 21 deletions apps/website/content/docs/chat/components/chat-subagent-card.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# ChatSubagentCardComponent

`ChatSubagentCardComponent` is a composition that renders an expandable card for a subagent stream. It displays the subagent's tool call ID, current status (with a color-coded badge), and expands to show the message count and latest message content.
`ChatSubagentCardComponent` is a composition that renders an expandable card for a subagent stream. It displays the subagent's name (or tool call ID), current status (with a color-coded badge) and message count, and expands to show the subagent's **full transcript** — every message rendered as streaming markdown, alongside any reasoning and the subagent's own tool-call cards.

The card is built on the [`<chat-trace>`](/docs/chat/components/chat-trace) primitive, so it auto-expands while the subagent is `running` and collapses once it reaches `complete` (a user toggle always wins).

**Selector:** `chat-subagent-card`

Expand Down Expand Up @@ -34,27 +36,33 @@ The `Subagent` type comes from `@threadplane/chat`. It provides reactive state f
| `name` | `string \| undefined` | Optional human-readable name. The card's "Subagent" label uses it when present |
| `status()` | `Signal<'pending' \| 'running' \| 'complete' \| 'error'>` | Current execution status |
| `messages()` | `Signal<Message[]>` | Messages produced by the subagent |
| `toolCalls()` | `Signal<ToolCall[]> \| undefined` | The subagent's own tool calls (name/args/result), referenced by each message's `toolCallIds`. Optional — adapters that don't surface subagent tool calls omit it, and the card defaults to `[]` |
| `state()` | `Signal<Record<string, unknown>>` | Arbitrary subagent state exposed by the runtime |

## Card Behavior

### Collapsed State (Default)
### Header

The card header (the `<chat-trace>` toggle button) shows:
- A chevron that reflects the expanded state
- The subagent `name` if present, otherwise the literal `"Subagent"`, with the `toolCallId` in monospace
- A color-coded status pill
- The message count (e.g., "3 message(s)")

The card header shows:
- An agent icon on the left
- "Subagent" label with the `toolCallId` in monospace
- A color-coded status badge
- A chevron toggle on the right
### Auto-expand and collapse

### Expanded State
Expansion is driven by `<chat-trace>`: the card auto-expands while `status()` is `running` and collapses when it settles to `complete`. Clicking the header toggles it manually, and a manual toggle overrides the automatic behavior.

Clicking the header toggles expansion. The expanded area shows:
- Message count (e.g., "3 message(s)")
- The content of the latest message (either as plain text or serialized JSON)
### Transcript

### Status Badge Colors
When expanded, the card renders the subagent's **entire message list** (not just the latest). For each message it shows, in order:
- Any `reasoning` text, as a muted italic line
- The message `content`, rendered through `<chat-streaming-md>` (streaming markdown)
- A [`<chat-tool-call-card>`](/docs/chat/components/chat-tool-call-card) for each tool call referenced by the message's `toolCallIds`

The status badge uses different chat theme variables based on the current status:
### Status Pill Colors

The status pill is styled via a `data-status` attribute and CSS selectors (the exported `statusColor()` helper is retained for backward compatibility). Colors map to chat theme variables:

| Status | Background | Text Color |
|--------|-----------|------------|
Expand Down Expand Up @@ -122,14 +130,16 @@ The card uses the following CSS custom properties:

| Variable | Applied To |
|----------|-----------|
| `--ngaf-chat-surface-alt` | Card background |
| `--ngaf-chat-surface` | Latest message content background |
| `--ngaf-chat-separator` | Card border, section dividers |
| `--ngaf-chat-radius-card` | Card border radius |
| `--ngaf-chat-text` | Subagent label, message content |
| `--ngaf-chat-text-muted` | Agent icon, tool call ID, chevron, message count |
| `--ngaf-chat-text` | Subagent name, message content |
| `--ngaf-chat-text-muted` | Tool call ID, message count, reasoning line |
| `--ngaf-chat-font-mono` | Tool call ID |
| `--ngaf-chat-separator` | Divider between successive transcript messages |
| `--ngaf-chat-warning-bg` / `--ngaf-chat-warning-text` | `running` status pill |
| `--ngaf-chat-success` | `complete` status pill |
| `--ngaf-chat-error-bg` / `--ngaf-chat-error-text` | `error` status pill |

The outer card chrome (background, border, radius) comes from the wrapping [`<chat-trace>`](/docs/chat/components/chat-trace).

## ARIA

- The header button has `aria-expanded` reflecting the current state
- The button has `aria-label="Toggle subagent details"`
- The header button (from `<chat-trace>`) exposes `aria-expanded` reflecting the current state
22 changes: 21 additions & 1 deletion apps/website/content/docs/chat/getting-started/changelog.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
# Changelog

<Callout type="info" title="Highlight releases only">
This changelog tracks selected highlight releases, not every patch. The published package is at `0.0.49` — see the entry below for the notable additions since `0.0.19`.
This changelog tracks selected highlight releases, not every patch. The published package is at `0.0.52` — see the entry below for the notable additions since `0.0.19`.
</Callout>

## 0.0.52

### Classified errors and Retry

- New structured `AgentError` on the `Agent` contract: `agent.error()` now returns an `AgentError` (or `undefined`) carrying a machine-readable `kind` (`connection` | `auth` | `server` | `interrupted` | `aborted`), a `retryable` flag, an optional HTTP `status`, and the original `cause`. Both adapters normalize raw failures through `toAgentError()`.
- New `agent.retry()` action re-runs the last request and clears `error`. The built-in `<chat-error>` primitive (auto-rendered by `<chat>`) shows cause-specific copy and a **Retry** button whenever `error.retryable` is true.
- New exports from `@threadplane/chat`: `AgentError`, `AgentErrorKind`, `toAgentError`, `isAbortError`, `AGENT_ERROR_MESSAGES`, plus `ChatErrorComponent` and `extractErrorMessage`. See [Error Handling](/docs/chat/guides/error-handling).

### Thread routing

- New `injectThreadRouting()` helper binds an app-owned active-thread signal to the Angular Router — restoring the thread id from the URL on load, stamping changes back into the URL, and treating a bare URL as the welcome state, with no `localStorage`. See [Thread Routing](/docs/chat/guides/thread-routing).

### Subagent cards

- `<chat-subagent-card>` now renders the subagent's **full transcript** — every message as streaming markdown, with reasoning and the subagent's own tool-call cards — instead of only the latest message. Cards are inline and persistent, auto-expand while running, and collapse on completion. See [ChatSubagentCard](/docs/chat/components/chat-subagent-card).

### TypeScript DX

- Strict-safe, fully typed authoring surface for client tools, `view`, and `ask`, plus typed agent dependency injection. New type helpers exported from `@threadplane/chat`: `ToolArgs`, `ViewProps`, `AgentRef` / `createAgentRef`, and the `StandardSchemaV1` inference aliases.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StandardSchemaV1 inference aliases are listed here but are not mentioned in the client-tools.mdx API reference table. If these are public exports from @threadplane/chat, they should appear in the import block and table in the Client Tools guide — otherwise consumers who look there won't know to import them.


## 0.0.49

### Citations
Expand Down
170 changes: 170 additions & 0 deletions apps/website/content/docs/chat/guides/client-tools.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Client Tools

Client tools are tools you declare **in the browser** that the model calls and the browser executes — no server-side implementation. There are three kinds:

| Helper | Kind | What it does |
|---|---|---|
| `action()` | function | Runs an async handler in the browser; its resolved return value becomes the tool result sent back to the model. |
| `view()` | render-only component | The model fills the component's props from the schema; the card renders inline and the call is auto-acknowledged once it mounts. |
| `ask()` | interactive component | The model fills the component's props; the value the component emits back becomes the tool result (human-in-the-loop). |

Tools are arguments-typed by a [Standard Schema](https://standardschema.dev) (e.g. a Zod object). The catalog is shipped to the model by the adapter; the backend graph binds the client stubs and ends its turn so the browser executes them.

<Callout type="info" title="Adapter-neutral">
The same declarations work with `@threadplane/langgraph` and `@threadplane/ag-ui` — only the `provideAgent`/`injectAgent` imports change.
</Callout>

## Declaring a registry

`tools({...})` collects named tools into a frozen registry. Pass it to `<chat>` via `[clientTools]`:

```typescript
import { Component } from '@angular/core';
import { ChatComponent, tools, action, view, ask } from '@threadplane/chat';
import { injectAgent } from '@threadplane/langgraph';
import { z } from 'zod/v4';
import { WeatherCardComponent } from './weather-card.component';
import { ConfirmBookingComponent } from './confirm-booking.component';

const clientTools = tools({
get_weather: action(
'Look up the current weather for a location.',
z.object({ location: z.string() }),
async ({ location }) => ({ location, temperatureF: 68, conditions: 'Sunny' }),
),
weather_card: view(
'Display a weather card for a location.',
z.object({ location: z.string(), temperatureF: z.number(), conditions: z.string() }),
WeatherCardComponent,
),
confirm_booking: ask(
'Ask the user to confirm a booking before finalizing it.',
z.object({ summary: z.string() }),
ConfirmBookingComponent,
),
});

@Component({
selector: 'app-client-tools',
standalone: true,
imports: [ChatComponent],
template: `<chat [agent]="agent" [clientTools]="clientTools" />`,
})
export class ClientToolsComponent {
protected readonly agent = injectAgent();
protected readonly clientTools = clientTools;
}
```

The object keys (`get_weather`, `weather_card`, `confirm_booking`) are the tool names the model sees. `tools()` preserves each tool's precise generic type, so downstream lookups stay typed.

## Typed component props with `ViewProps`

For `view()` and `ask()`, the component's signal inputs are checked against the schema output at compile time — every field the schema produces must be a declared `input()` with an assignable type (the component may declare extra inputs the schema doesn't fill). Derive the input types directly from the schema with `ViewProps<typeof schema>` so the two never drift:

```typescript
import { Component, input } from '@angular/core';
import type { ViewProps } from '@threadplane/chat';
import { z } from 'zod/v4';

export const weatherCardSchema = z.object({
location: z.string(),
temperatureF: z.number(),
conditions: z.string(),
});

// { location: string; temperatureF: number; conditions: string }
type Inputs = ViewProps<typeof weatherCardSchema>;

@Component({
selector: 'app-weather-card',
standalone: true,
template: `<div>{{ location() }}: {{ temperatureF() }}°F, {{ conditions() }}</div>`,
})
export class WeatherCardComponent {
location = input.required<string>();
temperatureF = input.required<number>();
conditions = input.required<string>();
}
```

Under `strict: true`, the typed `view`/`ask` overloads report a compile error at the `view(...)`/`ask(...)` call site if the component's inputs diverge from the schema — mismatches become build errors, not silent runtime failures.

## Typed handler args with `ToolArgs`

For `action()`, the handler argument type is inferred from the schema automatically. When you want to name that type — e.g. to write the handler separately — use `ToolArgs<typeof schema>` (an alias of the schema's inferred output):

```typescript
import { action, type ToolArgs } from '@threadplane/chat';
import { z } from 'zod/v4';

const moveSchema = z.object({ fromDay: z.number(), toDay: z.number() });

async function moveStop(args: ToolArgs<typeof moveSchema>) {
// args is { fromDay: number; toDay: number }
return reorder(args.fromDay, args.toDay);
}

const move = action('Move a stop to another day.', moveSchema, moveStop);
```

## Typed agent state

Tool handlers and components often read agent state. Pair the registry with a typed `AgentRef` so `agent.state()` / `agent.value()` carry your state shape instead of `Record<string, unknown>` — see [Typed state via AgentRef](/docs/langgraph/api/provide-agent#typed-state-via-agentref):

```typescript
import { createAgentRef } from '@threadplane/chat';
import { injectAgent } from '@threadplane/langgraph';

interface ClientToolsState { messages: unknown[]; client_tools: unknown[]; }
export const CLIENT_TOOLS = createAgentRef<ClientToolsState>('client-tools');

// component
protected readonly agent = injectAgent(CLIENT_TOOLS); // LangGraphAgent<ClientToolsState>
```

## API reference

```typescript
import {
tools, action, view, ask,
type ViewProps, type ToolArgs,
type ClientToolDef, type ClientToolRegistry,
} from '@threadplane/chat';
```

| Export | Purpose |
|---|---|
| `action(description, schema, handler)` | Declare a function tool (handler return → result) |
| `view(description, schema, component)` | Declare a render-only component tool (auto-acknowledged) |
| `ask(description, schema, component)` | Declare an interactive component tool (emitted value → result) |
| `tools(map)` | Freeze a name-keyed registry for `[clientTools]` |
| `ViewProps<S>` | Component input prop bag inferred from a schema |
| `ToolArgs<S>` | Handler argument type inferred from a schema |
| `ClientToolDef` / `ClientToolRegistry` | The tool-definition union and frozen-registry types |

## What's next

<CardGroup cols={3}>
<Card
title="Generative UI"
icon="layout"
href="/docs/chat/guides/generative-ui"
>
Render agent-emitted UI specs with `[views]`, distinct from model-called tools.
</Card>
<Card
title="provideAgent()"
icon="code"
href="/docs/langgraph/api/provide-agent"
>
Typed agent state via `AgentRef` and client-tuning options.
</Card>
<Card
title="Error Handling"
icon="alert-triangle"
href="/docs/chat/guides/error-handling"
>
Classified errors and Retry when a tool call or run fails.
</Card>
</CardGroup>
Loading
Loading