Skip to content

Commit c317da8

Browse files
bloveclaude
andauthored
chat reasoning + tool-call templates (chat 0.0.19, langgraph 0.0.11, ag-ui 0.0.3) (#192)
* feat(chat): add Message.reasoning + Message.reasoningDurationMs Optional fields on the shared Message contract. Adapters populate them from provider-agnostic sources (LangGraph reasoning/thinking content blocks, AG-UI REASONING_MESSAGE_* events). UI primitives consume the fields without provider-specific knowledge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat): add formatDuration utility Renders millisecond durations as compact human-readable labels: <1s, Ns, Nm Ms. Powers the chat-reasoning 'Thought for Ns' pill. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat): chat-reasoning styles Pill-shaped header with chevron + animated pulse dot for the streaming state, expanded body with thin left border (matches the blockquote pattern). Muted text throughout so reasoning content recedes next to the response. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat): chat-reasoning primitive Renders model reasoning content as a compact pill. Three visual states (streaming with pulse + auto-expand, idle with 'Thought for Ns', idle with 'Show reasoning' fallback). User toggle wins over auto logic for the lifetime of the instance. Body re-uses chat-streaming-md so markdown in reasoning output renders consistently with the response. Adds @analogjs/vite-plugin-angular to the chat library's vite config (with pool: 'forks' to preserve existing test isolation) so that Angular signal inputs resolve correctly in vitest HostComponent specs. Also adds tsconfig.spec.json required by the Angular compiler plugin. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat): export ChatReasoningComponent + formatDuration Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(chat): chat-reasoning auto-resets on streaming re-engage + spec amendments Phase 2 review found three issues. Behavioral fix: re-add the constructor effect that resets the manual expanded-override to null when isStreaming transitions false→true (spec §3.3 bullet 3 — same component instance reused across follow-up turns auto-expands when the next reasoning phase begins). The previous "preserves user choice" test conflated two scenarios; replace with one test asserting bullet 2 (no force-collapse on true→false), one test asserting user collapse persists, and one test asserting auto-reset on false→true. Spec drift: amend §3.1 so the content input defaults to '' (matching the shipped pragmatic API; pairs cleanly with data-has-content="false" hide-when-empty styling). Drop the unused [chatReasoningLabel] slot from §3.1 — the [label] string input covers the common case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat): chatToolCallTemplate directive Per-tool-name template registry consumed by <chat-tool-calls>. A '*' wildcard registers a catch-all for any unmapped tool name. Also extends DebugElement.prototype.queryAll in test-setup to traverse DebugNode (comment) children so directive-on-ng-template specs can use the injector-predicate pattern under Angular 21. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat): export ChatToolCallTemplateDirective + context type Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(chat): test chatToolCallTemplate via viewChildren signal The original spec used DebugElement.queryAll to enumerate directive instances, which doesn't traverse comment nodes (where ng-template compiles). The previous workaround monkey-patched DebugElement.prototype.queryAll across the whole chat library — too broad. Use viewChildren(ChatToolCallTemplateDirective) on the host component instead; it picks up directives on ng-template nodes naturally and needs no test-infrastructure changes. Revert the monkey-patch in test-setup.ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat): chat-tool-calls grouping + per-tool template registry Sequential same-name tool calls auto-group into a collapsible strip with a sensible default summary ('Searched N sites'). Consumers can register per-tool-name templates via chatToolCallTemplate to fully replace the default card UX, plus a '*' wildcard for any unmapped name. [grouping]='none' opts out of the auto-collapse behavior; [groupSummary] lets callers override the default registry. Also widens ToolCallInfo to carry an optional status — Phase 5 will use this to drive the at-a-glance status pill on chat-tool-call-card. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat): chat-tool-call-card status pill + default-collapsed Tool-call cards now render a consistent status pill (spinner / checkmark / error glyph) regardless of state, and default to collapsed when complete. Running and errored cards stay expanded so progress and failures are visible without clicks. User toggle wins for the lifetime of the card. Adds defaultExpanded input to chat-trace; drops the unused 200ms auto-collapse-on-done timeout in favor of explicit defaults driven by consumers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(langgraph): extractReasoning + accumulateReasoning helpers Walks complex-content arrays for {type:'reasoning'}/{type:'thinking'} blocks (provider-agnostic between OpenAI Responses API and Anthropic). Same accumulator semantics as accumulateContent: superset takes priority for final-id swap, prefix-match keeps the longer side, otherwise pure-delta append. Returns string so downstream code never sees the raw block array. Exports _internalsForTesting for conformance tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(langgraph): bridge accumulates reasoning + tracks per-message timing mergeMessages now reads incoming reasoning content (from {type:'reasoning'|'thinking'} blocks or an explicit Message.reasoning field) and accumulates it into the merged slot alongside response text. A per-message reasoningTimingMap captures when reasoning chunks first arrive and when response text first overlaps; the manager exposes getReasoningDurationMs(id) so the agent.fn projection can populate Message.reasoningDurationMs. Map is cleared on thread switch and bridge teardown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(langgraph): toMessage populates Message.reasoning + reasoningDurationMs agent.fn's toMessage projection reads the bridge's accumulated reasoning string and asks the manager for the per-message duration. Both fields land as undefined when no reasoning was emitted, so existing consumers see no shape change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ag-ui): handle REASONING_MESSAGE_* events REASONING_MESSAGE_START creates (or finds) an assistant slot with an empty reasoning string and starts a per-message timing entry. REASONING_MESSAGE_CONTENT/CHUNK appends to it. REASONING_MESSAGE_END records the end timestamp and writes Message.reasoningDurationMs onto the slot. TEXT_MESSAGE_START is now idempotent so a follow-up text stream on the same messageId reuses the existing slot rather than splitting reasoning + response into separate messages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ag-ui): FakeAgent reasoningTokens option Optional reasoningTokens?: string[] constructor argument that, when provided, emits a REASONING_MESSAGE_START → CONTENT × N → END sequence before the existing text-message stream. provideFakeAgUiAgent plumbs the new option through. Lets demo apps and integration tests exercise the reasoning UI end-to-end without a real model. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat/testing): add provider-neutral reasoning fixture Defines a canonical abstract event sequence (reasoning start → three chunks → end → text start → three chunks → end) and an assertReasoningFixtureMessages() helper that both adapter conformance suites use to verify identical Message[] output. Keeps the populating logic for Message.reasoning + reasoningDurationMs honest across implementations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(ag-ui): reasoning-fixture conformance Translates the shared @ngaf/chat/testing fixture sequence into AG-UI wire format and asserts the reducer produces the expected Message[] shape (single assistant message with full reasoning, full content, and a non-negative reasoningDurationMs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(langgraph): reasoning-fixture conformance Translates the shared @ngaf/chat/testing fixture sequence into LangGraph AIMessageChunk shape (complex-content arrays with {type:'reasoning'} and {type:'text'} blocks) and asserts the bridge's mergeMessages + toMessage projection produces the same Message[] shape AG-UI does. One fixture, two adapters — keeps both honest. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(langgraph): wrap toMessage in arrow fn to avoid map index collision Array.prototype.map passes (value, index, array) to its callback. Passing toMessage directly caused TypeScript to infer that the optional second parameter (getReasoningDurationMs: (id: string) => number | undefined) could receive the numeric index, producing TS2345. Wrapping it in an arrow function makes the call explicit and type-safe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat): <chat> renders <chat-reasoning> + forwards chatToolCallTemplate When an assistant Message carries a non-empty reasoning string, the chat composition automatically renders <chat-reasoning> above the response markdown. The pill streams visibly while reasoning content is arriving (tail message + agent loading + no response text yet), then collapses to 'Thought for Ns' once response tokens begin. Consumers projecting <ng-template chatToolCallTemplate='search_web'> directly into <chat> have those templates forwarded into the inner <chat-tool-calls> via the same outer-content re-projection pattern used for [chatInputModelSelect] and [chatWelcomeSuggestions]. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(chat): chat-reasoning + tool-call template references + 0.0.19 changelog Six new/updated MDX pages covering Phase B2 surface: - New chat-reasoning.mdx — primitive reference (states, inputs, slot) - New chat-tool-call-template.mdx — directive reference + dispatch order - New chat-tool-calls.mdx — grouping inputs + default summaries + template extension - Updated chat-tool-call-card.mdx — status pill table + default-collapsed - Updated chat.mdx — reasoning subsection + tool-call template projection example - New getting-started/changelog.mdx — 0.0.19 entry Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump chat 0.0.19, langgraph 0.0.11, ag-ui 0.0.3 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(chat): satisfy @nx/dependency-checks for vite-only @analogjs plugin Add @analogjs/vite-plugin-angular to ignoredDependencies in the chat project's eslint config — it is used only in vite.config.mts for test setup and must not appear in the published package dependencies. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2a1c563 commit c317da8

43 files changed

Lines changed: 1920 additions & 213 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# ChatReasoningComponent
2+
3+
`ChatReasoningComponent` renders an assistant's reasoning content as a compact pill that expands to reveal the underlying text. The `<chat>` composition automatically renders this primitive above the assistant response when `Message.reasoning` is populated by the adapter — most consumers don't need to use it directly.
4+
5+
**Selector:** `chat-reasoning`
6+
7+
**Import:**
8+
9+
```typescript
10+
import { ChatReasoningComponent, formatDuration } from '@ngaf/chat';
11+
```
12+
13+
## Visual states
14+
15+
| State | Pill label | Behavior |
16+
|---|---|---|
17+
| `[isStreaming]="true"` | "Thinking…" with pulsing dot | Auto-expanded; body streams in |
18+
| Idle, `[durationMs]` set | "Thought for Ns" | Collapsed by default; click to expand |
19+
| Idle, no `[durationMs]` | "Show reasoning" | Collapsed by default; click to expand |
20+
21+
## Inputs
22+
23+
| Input | Type | Default | Description |
24+
|---|---|---|---|
25+
| `[content]` | `string` | `''` | The reasoning text to render |
26+
| `[isStreaming]` | `boolean` | `false` | True while the model is mid-reasoning |
27+
| `[durationMs]` | `number \| undefined` | `undefined` | Wall-clock duration of the reasoning phase |
28+
| `[label]` | `string \| undefined` | `undefined` | Override the auto-derived label |
29+
| `[defaultExpanded]` | `boolean` | `false` | Open the panel by default when idle |
30+
31+
## Standalone usage
32+
33+
```html
34+
<chat-reasoning
35+
[content]="reasoningText"
36+
[isStreaming]="isStillThinking"
37+
[durationMs]="thoughtFor"
38+
/>
39+
```
40+
41+
## formatDuration utility
42+
43+
Use `formatDuration(ms)` to render the duration string yourself (e.g. for a sidebar):
44+
45+
```typescript
46+
formatDuration(0) // "<1s"
47+
formatDuration(4_000) // "4s"
48+
formatDuration(72_000) // "1m 12s"
49+
```
50+
51+
## Behavior
52+
53+
- The component hides itself entirely (`display: none`) when `[content]` is empty.
54+
- `[isStreaming]="true"` force-expands the panel so streaming content is visible.
55+
- A user click on the pill toggles the panel; the user choice persists across `[isStreaming]` transitions for the lifetime of the instance.
56+
- When `isStreaming` re-engages on a follow-up turn (a new reasoning phase begins after a prior idle period), the panel resets to expanded.
57+
- The body re-uses `<chat-streaming-md>` so reasoning content gets the same markdown rendering pipeline as the response (lists, code blocks, headings render).
Lines changed: 25 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,57 @@
11
# ChatToolCallCardComponent
22

3-
`ChatToolCallCardComponent` is a composition that renders an expandable card for a single tool call. It displays the tool name in the header, shows a completion badge when done, and expands to reveal the tool's input arguments and output result.
3+
`ChatToolCallCardComponent` renders a single tool call as an expandable card with a status pill (running / complete / error), inputs, and output.
44

55
**Selector:** `chat-tool-call-card`
66

77
**Import:**
88

99
```typescript
10-
import { ChatToolCallCardComponent } from '@ngaf/chat';
11-
import type { ToolCallInfo } from '@ngaf/chat';
10+
import { ChatToolCallCardComponent, type ToolCallInfo } from '@ngaf/chat';
1211
```
1312

14-
## Basic Usage
13+
## Status pill
1514

16-
```html
17-
<chat-tool-call-card [toolCall]="myToolCall" />
18-
```
15+
| Status | Visual | aria-label |
16+
|---|---|---|
17+
| `running` | spinner (animated) | "Running" |
18+
| `complete` | check (success color) | "Completed" |
19+
| `error` | exclamation (error color) | "Failed" |
1920

20-
Where `myToolCall` is a `ToolCallInfo` object:
21+
## Default-collapsed behavior
2122

22-
```typescript
23-
const myToolCall: ToolCallInfo = {
24-
id: 'call_abc123',
25-
name: 'search_documents',
26-
args: { query: 'Angular signals tutorial' },
27-
result: { documents: ['doc1', 'doc2'] },
28-
};
29-
```
23+
| Status | Default state |
24+
|---|---|
25+
| `running` | Expanded |
26+
| `error` | Expanded |
27+
| `complete` | Collapsed (when `[defaultCollapsed]="true"`, the default) |
3028

31-
## API
29+
A user click on the header toggles open/closed. Once toggled, the user choice persists across status changes for the lifetime of the card.
3230

33-
### Inputs
31+
## Inputs
3432

3533
| Input | Type | Default | Description |
36-
|-------|------|---------|-------------|
37-
| `toolCall` | `ToolCallInfo` | **Required** | The tool call data to display |
34+
|---|---|---|---|
35+
| `[toolCall]` | `ToolCallInfo` | — (required) | `{id, name, args, status?, result?}` |
36+
| `[defaultCollapsed]` | `boolean` | `true` | Collapse on `complete`; pass `false` to keep cards always-expanded |
3837

39-
## ToolCallInfo Type
38+
## ToolCallInfo
4039

4140
```typescript
4241
interface ToolCallInfo {
4342
id: string;
4443
name: string;
4544
args: unknown;
45+
status?: 'pending' | 'running' | 'complete' | 'error';
4646
result?: unknown;
4747
}
4848
```
4949

50-
| Property | Type | Description |
51-
|----------|------|-------------|
52-
| `id` | `string` | Unique identifier for the tool call |
53-
| `name` | `string` | The tool function name (displayed in the header) |
54-
| `args` | `unknown` | The arguments passed to the tool (displayed as formatted JSON) |
55-
| `result` | `unknown \| undefined` | The tool's return value. When present, a green checkmark badge appears. |
56-
57-
## Card Behavior
58-
59-
### Collapsed State (Default)
60-
61-
The card header shows:
62-
- A gear icon on the left
63-
- The tool name in monospace font
64-
- A green checkmark with "done" text when `result` is defined
65-
- A chevron toggle on the right
66-
67-
### Expanded State
68-
69-
Clicking the header toggles expansion. The expanded area shows:
70-
- **Inputs** section: The `args` value formatted as indented JSON
71-
- **Output** section (when `result` is defined): The `result` value formatted as indented JSON
72-
73-
The component uses a `formatJson()` method that:
74-
- Returns strings directly
75-
- Serializes objects with `JSON.stringify(value, null, 2)`
76-
- Falls back to `String(value)` if serialization fails
77-
78-
## Using with ChatToolCallsComponent
79-
80-
The `ChatToolCallsComponent` primitive iterates over tool calls from a `LangGraphAgent`. Combine it with `ChatToolCallCardComponent` to render a list of tool call cards:
50+
## Basic usage
8151

8252
```html
83-
<chat-tool-calls [ref]="chatRef">
84-
<ng-template let-toolCall>
85-
<chat-tool-call-card [toolCall]="asToolCallInfo(toolCall)" />
86-
</ng-template>
87-
</chat-tool-calls>
88-
```
53+
<chat-tool-call-card [toolCall]="tc" />
8954

90-
```typescript
91-
import type { ToolCallWithResult } from '@langchain/langgraph-sdk';
92-
import type { ToolCallInfo } from '@ngaf/chat';
93-
94-
asToolCallInfo(tc: ToolCallWithResult): ToolCallInfo {
95-
return {
96-
id: tc.id ?? '',
97-
name: tc.name,
98-
args: tc.args,
99-
result: tc.result,
100-
};
101-
}
102-
```
103-
104-
## Using in Message Templates
105-
106-
Display tool calls inline with AI messages:
107-
108-
```html
109-
<chat-messages [ref]="chatRef">
110-
<ng-template chatMessageTemplate="ai" let-message>
111-
<div class="ai-message">{{ message.content }}</div>
112-
113-
<!-- Show tool calls attached to this AI message -->
114-
<chat-tool-calls [ref]="chatRef" [message]="message">
115-
<ng-template let-toolCall>
116-
<chat-tool-call-card [toolCall]="asToolCallInfo(toolCall)" />
117-
</ng-template>
118-
</chat-tool-calls>
119-
</ng-template>
120-
</chat-messages>
55+
<!-- Always-expanded -->
56+
<chat-tool-call-card [toolCall]="tc" [defaultCollapsed]="false" />
12157
```
122-
123-
## Styling
124-
125-
The card uses the following CSS custom properties:
126-
127-
| Variable | Applied To |
128-
|----------|-----------|
129-
| `--ngaf-chat-surface-alt` | Card background |
130-
| `--ngaf-chat-separator` | Card border, section dividers |
131-
| `--ngaf-chat-radius-card` | Card border radius |
132-
| `--ngaf-chat-text` | Tool name and JSON content |
133-
| `--ngaf-chat-text-muted` | Gear icon, chevron, section labels |
134-
| `--ngaf-chat-success` | Checkmark and "done" badge |
135-
136-
## ARIA
137-
138-
- The header button has `aria-expanded` reflecting the current state
139-
- The button has `aria-label="Toggle tool call details"`
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# ChatToolCallTemplateDirective
2+
3+
`ChatToolCallTemplateDirective` registers a per-tool-name template inside `<chat-tool-calls>`. The primitive collects all directive instances and dispatches each tool call to the template matching its `name`. A literal `"*"` registers a wildcard catch-all for any unmapped name.
4+
5+
**Selector:** `[chatToolCallTemplate]`
6+
7+
**Import:**
8+
9+
```typescript
10+
import { ChatToolCallTemplateDirective, type ChatToolCallTemplateContext } from '@ngaf/chat';
11+
```
12+
13+
## Template context
14+
15+
Each registered template receives:
16+
17+
| Variable | Type | Description |
18+
|---|---|---|
19+
| `let-call` (`$implicit`) | `ToolCall` | The full tool call: `{id, name, args, status, result?, error?}` |
20+
| `let-status="status"` | `ToolCallStatus` | `'pending' \| 'running' \| 'complete' \| 'error'` |
21+
22+
## Examples
23+
24+
### Custom search-result card
25+
26+
```html
27+
<chat-tool-calls [agent]="agent" [message]="msg">
28+
<ng-template chatToolCallTemplate="search_web" let-call let-status="status">
29+
<my-search-result-card
30+
[query]="call.args.query"
31+
[results]="call.result"
32+
[status]="status"
33+
/>
34+
</ng-template>
35+
</chat-tool-calls>
36+
```
37+
38+
### Wildcard catch-all
39+
40+
```html
41+
<chat-tool-calls [agent]="agent" [message]="msg">
42+
<ng-template chatToolCallTemplate="search_web" let-call let-status="status">
43+
<my-search-result-card [query]="call.args.query" [results]="call.result" />
44+
</ng-template>
45+
46+
<!-- Anything not search_web falls through to here -->
47+
<ng-template chatToolCallTemplate="*" let-call>
48+
<chat-tool-call-card [toolCall]="call" />
49+
</ng-template>
50+
</chat-tool-calls>
51+
```
52+
53+
### Project through `<chat>` directly
54+
55+
`<chat>` re-projects any `chatToolCallTemplate` directive inside it down to the inner `<chat-tool-calls>`:
56+
57+
```html
58+
<chat [agent]="agent">
59+
<ng-template chatToolCallTemplate="generate_image" let-call let-status="status">
60+
<my-image-card
61+
[prompt]="call.args.prompt"
62+
[imageUrl]="call.result"
63+
[status]="status"
64+
/>
65+
</ng-template>
66+
</chat>
67+
```
68+
69+
## Dispatch order
70+
71+
1. Per-tool template whose `name` exactly matches `tc.name`.
72+
2. Wildcard template with `name === "*"`.
73+
3. Default `<chat-tool-call-card>` (no template registered for either).
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# ChatToolCallsComponent
2+
3+
`ChatToolCallsComponent` renders all tool calls associated with an assistant message. By default sequential same-name calls auto-group into a labeled strip; consumers can register per-tool-name templates via the `chatToolCallTemplate` directive to fully replace the default card UX.
4+
5+
**Selector:** `chat-tool-calls`
6+
7+
**Import:**
8+
9+
```typescript
10+
import { ChatToolCallsComponent } from '@ngaf/chat';
11+
```
12+
13+
## Inputs
14+
15+
| Input | Type | Default | Description |
16+
|---|---|---|---|
17+
| `[agent]` | `Agent` | — (required) | Source of `agent.toolCalls()` |
18+
| `[message]` | `Message \| undefined` | `undefined` | Filter to calls referenced by this message's `tool_use` content blocks |
19+
| `[grouping]` | `'auto' \| 'none'` | `'auto'` | Auto-collapse adjacent same-name calls into a strip |
20+
| `[groupSummary]` | `(name: string, count: number) => string` | built-in registry | Override the default strip label |
21+
22+
## Default group summaries
23+
24+
| Tool name shape | Default label |
25+
|---|---|
26+
| `search_*` | "Searched N sites" |
27+
| `generate_*` | "Generated N items" |
28+
| `read_*` | "Read N files" |
29+
| `write_*` | "Wrote N files" |
30+
| `list_*` | "Listed N items" |
31+
| Anything else | "Called {name} N times" |
32+
33+
## Per-tool templates
34+
35+
Register a template per tool name (or `"*"` as a wildcard) — see [chat-tool-call-template](./chat-tool-call-template).
36+
37+
```html
38+
<chat-tool-calls [agent]="agent" [message]="msg">
39+
<ng-template chatToolCallTemplate="search_web" let-call let-status="status">
40+
<my-search-result-card [query]="call.args.query" [results]="call.result" />
41+
</ng-template>
42+
</chat-tool-calls>
43+
```
44+
45+
When a per-tool template is registered for a name, calls of that name skip grouping and are rendered each through the template (the consumer takes responsibility for visual density).
46+
47+
## Custom group summary
48+
49+
```html
50+
<chat-tool-calls
51+
[agent]="agent"
52+
[message]="msg"
53+
[groupSummary]="myGroupSummary"
54+
/>
55+
```
56+
57+
```typescript
58+
myGroupSummary = (name: string, count: number) =>
59+
name === 'fetch_user' ? `Fetched ${count} profiles` : `${name} × ${count}`;
60+
```
61+
62+
## Disabling grouping
63+
64+
```html
65+
<chat-tool-calls [agent]="agent" [message]="msg" [grouping]="'none'" />
66+
```
67+
68+
Each call renders independently regardless of name adjacency.

apps/website/content/docs/chat/components/chat.mdx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,27 @@ Under the hood, `ChatComponent` composes these primitives:
193193
- `ChatErrorComponent` for error display
194194
- `ChatInterruptComponent` for the interrupt banner
195195
- `ChatThreadListComponent` for the sidebar
196+
197+
## Reasoning
198+
199+
When a model emits reasoning content (gpt-5 / o-series with `reasoning` blocks, Anthropic with `thinking` blocks, or any AG-UI agent emitting `REASONING_MESSAGE_*` events), the adapter populates `Message.reasoning` and `Message.reasoningDurationMs`. The `<chat>` composition automatically renders [`<chat-reasoning>`](./chat-reasoning) above the assistant response. No configuration required.
200+
201+
While reasoning is streaming, the pill shows "Thinking…" with a pulse dot and the body auto-expands so the user sees content arrive in real time. Once response text begins, the pill collapses to "Thought for Ns" (e.g. "Thought for 4s").
202+
203+
## Tool-call templates
204+
205+
Project a `<ng-template chatToolCallTemplate="…">` directly into `<chat>` to replace the default card UX for a specific tool name. The composition forwards the template into the inner [`<chat-tool-calls>`](./chat-tool-calls).
206+
207+
```html
208+
<chat [agent]="agent">
209+
<ng-template chatToolCallTemplate="generate_image" let-call let-status="status">
210+
<my-image-card
211+
[prompt]="call.args.prompt"
212+
[imageUrl]="call.result"
213+
[status]="status"
214+
/>
215+
</ng-template>
216+
</chat>
217+
```
218+
219+
A `chatToolCallTemplate="*"` wildcard catches any unmapped tool name. See [chatToolCallTemplate](./chat-tool-call-template) for the directive reference.

0 commit comments

Comments
 (0)