diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd67fbc87..40568a2b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,8 @@ jobs: - run: npm ci - run: npx nx lint website - run: npm run generate-api-docs - # nx build website triggers demo:build first (dependsOn in project.json) + - name: Verify generated API docs are committed + run: git diff --exit-code -- apps/website/content/docs/*/api/api-docs.json - run: npx nx build website cockpit: diff --git a/README.md b/README.md index 9d96556ec..d622f51f0 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@

Angular Agent Framework — The Angular Agent Framework for LangChain

- The Angular Agent Framework for LangChain + Agent UI primitives and LangGraph streaming adapters for Angular

@@ -27,14 +27,14 @@ --- -`agent()` is the Angular equivalent of LangGraph's React `useStream()` hook, built on Angular Signals and the Angular Resource API. It gives enterprise Angular teams production-grade streaming primitives for LangChain. Drop it into any Angular 20+ component, point it at your LangGraph Platform endpoint, and get reactive, signal-driven access to streaming state, messages, tool calls, interrupts, and thread history. +`agent()` is the Angular equivalent of LangGraph's React `useStream()` hook, projected into a runtime-neutral `Agent` contract consumed by `@ngaf/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. --- ## Install ```bash -npm install @ngaf/langgraph +npm install @ngaf/langgraph @ngaf/chat ``` **Peer dependencies:** `@angular/core ^20.0.0 || ^21.0.0`, `@langchain/core ^1.1.0`, `@langchain/langgraph-sdk ^1.7.0`, `rxjs ~7.8.0` @@ -45,17 +45,14 @@ npm install @ngaf/langgraph ```typescript import { Component } from '@angular/core'; +import { ChatComponent as NgafChatComponent } from '@ngaf/chat'; import { agent } from '@ngaf/langgraph'; -import type { BaseMessage } from '@langchain/core/messages'; @Component({ - selector: 'app-chat', + selector: 'app-support-chat', + imports: [NgafChatComponent], template: ` -

+ @if (chat.isLoading()) { Streaming… @@ -64,20 +61,19 @@ import type { BaseMessage } from '@langchain/core/messages'; `, }) -export class ChatComponent { - chat = agent<{ messages: BaseMessage[] }>({ +export class SupportChatComponent { + chat = agent({ apiUrl: 'https://your-langgraph-platform.com', assistantId: 'my-agent', - messagesKey: 'messages', }); send() { - this.chat.submit({ messages: [{ role: 'human', content: 'Hello' }] }); + void this.chat.submit({ message: 'Hello' }); } } ``` -That's it. `chat.messages()` is an Angular Signal. Bind it directly in your template — no subscriptions, no `async` pipe, no zone.js required. +That's it. `chat.messages()` and `chat.status()` are Angular Signals. Bind them directly in your template — no subscriptions, no `async` pipe, no zone.js required. --- @@ -89,7 +85,7 @@ That's it. `chat.messages()` is an Angular Signal. Bind it directly in your temp | Messages signal | `messages()` | `messages` | | Loading state | `isLoading()` | `isLoading` | | Error state | `error()` | — | -| Resource status (idle/loading/resolved/error) | `status()` — full `ResourceStatus` | partial | +| Runtime-neutral status | `status()` — `'idle' \| 'running' \| 'error'` | partial | | Interrupt / human-in-the-loop | `interrupt()` / `interrupts()` | `interrupt` / `interrupts` | | Tool call progress | `toolProgress()` | `toolProgress` | | Tool calls with results | `toolCalls()` | `toolCalls` | @@ -99,8 +95,9 @@ That's it. `chat.messages()` is an Angular Signal. Bind it directly in your temp | Reactive thread switching | `Signal` input | prop | | Submit | `submit(values, opts?)` | `submit(values, opts?)` | | Stop | `stop()` | `stop()` | +| Regenerate response | `regenerate(assistantMessageIndex)` | — | | Reload last submission | `reload()` | — | -| Custom transport (for testing) | `MockStreamTransport` | mock fetch | +| Custom transport (for testing) | `MockAgentTransport` | mock fetch | | Angular `ResourceRef` compatibility | Full duck-type parity | N/A | | Angular 20+ Signals API | Native | N/A | | SSR / Server Components | Client-side only | React Server Components (React) | @@ -123,11 +120,11 @@ That's it. `chat.messages()` is an Angular Signal. Bind it directly in your temp ## Documentation -- [Getting Started](https://cacheplane.ai/docs/getting-started) -- [API Reference](https://cacheplane.ai/api-reference) -- [Testing with MockStreamTransport](https://cacheplane.ai/docs/testing) -- [Human-in-the-Loop / Interrupts](https://cacheplane.ai/docs/interrupts) -- [Subagent Streaming](https://cacheplane.ai/docs/subagents) +- [Agent Quickstart](https://cacheplane.ai/docs/agent/getting-started/quickstart) +- [agent() API](https://cacheplane.ai/docs/agent/api/agent) +- [Chat Introduction](https://cacheplane.ai/docs/chat/getting-started/introduction) +- [Human-in-the-Loop / Interrupts](https://cacheplane.ai/docs/agent/guides/interrupts) +- [Subgraph and Subagent Streaming](https://cacheplane.ai/docs/agent/guides/subgraphs) --- diff --git a/apps/website/content/docs/ag-ui/api/api-docs.json b/apps/website/content/docs/ag-ui/api/api-docs.json index 2e17267c0..114894dba 100644 --- a/apps/website/content/docs/ag-ui/api/api-docs.json +++ b/apps/website/content/docs/ag-ui/api/api-docs.json @@ -407,6 +407,12 @@ "description": "Milliseconds between successive token emissions.", "optional": true }, + { + "name": "reasoningTokens", + "type": "string[]", + "description": "Optional reasoning chunks emitted before the text reply.", + "optional": true + }, { "name": "tokens", "type": "string[]", @@ -416,6 +422,31 @@ ], "examples": [] }, + { + "name": "bridgeCitationsState", + "kind": "function", + "description": "", + "signature": "bridgeCitationsState(thread: ThreadStateLike, messages: Message[]): Message[]", + "params": [ + { + "name": "thread", + "type": "ThreadStateLike", + "description": "", + "optional": false + }, + { + "name": "messages", + "type": "Message[]", + "description": "", + "optional": false + } + ], + "returns": { + "type": "Message[]", + "description": "" + }, + "examples": [] + }, { "name": "injectAgUiAgent", "kind": "function", diff --git a/apps/website/content/docs/agent/api/agent.mdx b/apps/website/content/docs/agent/api/agent.mdx index e388e3341..4bf7a64c5 100644 --- a/apps/website/content/docs/agent/api/agent.mdx +++ b/apps/website/content/docs/agent/api/agent.mdx @@ -1,46 +1,88 @@ # agent() -`agent` is the core primitive of the library. It creates a reactive resource that opens a server-sent event stream, tracks loading and error states, and exposes the latest emitted value — all within Angular's signal-based reactivity model. +`agent()` is the LangGraph adapter for Angular. It connects to a LangGraph Platform assistant, consumes the LangGraph SDK event stream, and projects the result into the runtime-neutral `Agent` contract used by `@ngaf/chat`. -When the `url` signal changes, the resource tears down the previous connection and opens a fresh one automatically. You never write subscription management or cleanup logic yourself. +Call it in an Angular injection context, usually as a component field initializer. The returned object exposes Angular Signals for UI state and async methods for user actions. ```ts import { agent } from '@ngaf/langgraph'; -// Inside a component or service with injection context -const repo = agent({ - url: () => `/api/repos/${this.repoId()}`, - transport: inject(FetchStreamTransport), +readonly chat = agent({ + apiUrl: 'http://localhost:2024', + assistantId: 'my-agent', + threadId: this.threadId, + onThreadId: (id) => localStorage.setItem('threadId', id), }); -// Use in template -// repo.value() — latest emitted value (or undefined) -// repo.status() — 'idle' | 'loading' | 'streaming' | 'error' -// repo.error() — the thrown error, when status is 'error' -// repo.interrupt() — call to cancel the stream immediately +await this.chat.submit({ message: 'Hello' }); ``` -## Key signals +## Runtime-neutral surface -| Signal | Type | Description | +These fields are stable across runtime adapters and are what chat components consume. + +| Field | Type | Description | |--------|------|-------------| -| `value()` | `T \| undefined` | The latest value emitted by the stream. Starts as `undefined` and updates with each SSE event. | -| `status()` | `'idle' \| 'loading' \| 'streaming' \| 'error'` | Lifecycle state of the current connection. | -| `error()` | `unknown` | The error thrown when `status()` is `'error'`. `undefined` otherwise. | -| `interrupt()` | `() => void` | Closes the active stream without an error — useful for user-initiated cancellation. | +| `messages()` | `Message[]` | Runtime-neutral chat messages with `role`, `content`, optional `toolCallId`, citations, reasoning, and raw extras. | +| `status()` | `'idle' \| 'running' \| 'error'` | UI lifecycle status. LangGraph loading and reloading both map to `running`. | +| `isLoading()` | `boolean` | Convenience signal for active streaming. | +| `error()` | `unknown` | Latest runtime error, when present. | +| `toolCalls()` | `ToolCall[]` | Tool calls projected into the chat contract. | +| `state()` | `Record` | Latest state values projected as a plain object. | +| `submit(input, opts?)` | `Promise` | Submit a user message, resume payload, and/or state patch. | +| `stop()` | `Promise` | Stop the active run. | +| `regenerate(index)` | `Promise` | Remove the assistant message at `index` and all following messages, then rerun from the preceding user message. | +| `interrupt()` | `AgentInterrupt \| undefined` | Optional current interrupt, when the backend pauses for human input. | +| `subagents()` | `Map` | Optional subagent streams keyed by tool-call id. | + +## LangGraph-specific surface + +`agent()` also returns LangGraph-specific signals and helpers for apps that need the raw platform model: + +- `value()` and `hasValue()` for raw state values. +- `langGraphMessages()`, `langGraphToolCalls()`, and `langGraphHistory()` for raw SDK-shaped data. +- `history()`, `branch()`, `setBranch()`, and `experimentalBranchTree()` for checkpoint and time-travel UIs. +- `queue()`, `joinStream()`, and `switchThread()` for persisted threads and queued runs. +- `getSubagent()`, `getSubagentsByType()`, and `getSubagentsByMessage()` for subgraph/subagent inspection. + +## Submit and resume + +Use the runtime-neutral submit shape for normal chat input: + +```ts +await chat.submit({ message: 'Explain streaming in Angular' }); +``` + +Resume an interrupt with `resume`; combine it with `state` when the resume action also needs to update graph state: -## When to use +```ts +await chat.submit({ + resume: { approved: true }, + state: { reviewer: 'Ada' }, +}); +``` + +LangGraph run options are still accepted as the second argument: + +```ts +await chat.submit( + { message: 'Queue this run' }, + { multitaskStrategy: 'enqueue' }, +); +``` + +## Regenerate semantics -Use `agent` whenever your UI needs to react to a live data stream from the server: +`regenerate(assistantMessageIndex)` has replace semantics. It keeps the user message before the selected assistant message, removes the selected assistant message and every later message, synchronizes that rollback with LangGraph state when possible, then reruns with no new user message appended. -- **AI / LLM responses** — stream tokens into a chat bubble as they arrive -- **Live feeds** — stock tickers, activity logs, or progress updates -- **Long-running jobs** — subscribe to backend task progress over SSE +```ts +await chat.regenerate(3); +``` -For plain HTTP requests that return a single value and complete, Angular's built-in `resource()` or `httpResource()` is a better fit. +The method throws when the selected index is not an assistant message, when no preceding user message exists, or while another response is already loading. - `agent` must be called during construction, inside an injection + `agent()` must be called during construction, inside an injection context (e.g. a component constructor, field initializer, or a function passed to `runInInjectionContext`). Calling it outside an injection context will throw. @@ -68,7 +110,7 @@ For plain HTTP requests that return a single value and complete, Angular's built icon="bolt" href="/docs/agent/concepts/angular-signals" > - Understand how angular integrates with Angular's reactivity model. + Understand how `agent()` integrates with Angular's reactivity model. diff --git a/apps/website/content/docs/agent/api/api-docs.json b/apps/website/content/docs/agent/api/api-docs.json index f026a599e..7974a5992 100644 --- a/apps/website/content/docs/agent/api/api-docs.json +++ b/apps/website/content/docs/agent/api/api-docs.json @@ -170,6 +170,31 @@ "optional": true } ] + }, + { + "name": "updateState", + "signature": "updateState(threadId: string, values: Record, _signal: AbortSignal)", + "description": "Update server-side thread state, e.g. to remove messages for regenerate rollback.", + "params": [ + { + "name": "threadId", + "type": "string", + "description": "", + "optional": false + }, + { + "name": "values", + "type": "Record", + "description": "", + "optional": false + }, + { + "name": "_signal", + "type": "AbortSignal", + "description": "", + "optional": false + } + ] } ] }, @@ -529,7 +554,7 @@ { "name": "AgentOptions", "kind": "interface", - "description": "Options for creating a streaming resource via agent.", + "description": "Options for creating a LangGraph-backed agent via agent.", "properties": [ { "name": "apiUrl", @@ -555,12 +580,6 @@ "description": "Initial state values before the first stream response arrives.", "optional": true }, - { - "name": "messagesKey", - "type": "string", - "description": "Key in the state object that contains the messages array. Defaults to `'messages'`.", - "optional": true - }, { "name": "onThreadId", "type": "object", @@ -704,6 +723,12 @@ "type": "unknown", "description": "", "optional": false + }, + { + "name": "updateState", + "type": "unknown", + "description": "", + "optional": true } ], "examples": [] @@ -909,6 +934,12 @@ "description": "Pending server-side runs created via `multitaskStrategy: 'enqueue'`.", "optional": false }, + { + "name": "regenerate", + "type": "object", + "description": "Discards the assistant message at the given index AND all messages after\nit, then re-runs the agent against the trimmed conversation tail. The\npreceding user message (at index - 1) is preserved and re-submitted as\nthe agent's input. No new user message is added to the history.\n\nThrows if the message at `index` is not 'assistant' role, or if the\nagent is currently loading another response.", + "optional": false + }, { "name": "reload", "type": "object", @@ -1267,6 +1298,12 @@ "description": "Pending server-side runs created via `multitaskStrategy: 'enqueue'`.", "optional": false }, + { + "name": "regenerate", + "type": "object", + "description": "Discards the assistant message at the given index AND all messages after\nit, then re-runs the agent against the trimmed conversation tail. The\npreceding user message (at index - 1) is preserved and re-submitted as\nthe agent's input. No new user message is added to the history.\n\nThrows if the message at `index` is not 'assistant' role, or if the\nagent is currently loading another response.", + "optional": false + }, { "name": "reload", "type": "object", @@ -1609,13 +1646,13 @@ { "name": "agent", "kind": "function", - "description": "Creates a streaming resource connected to a LangGraph agent.\n\nMust be called within an Angular injection context (component constructor,\nfield initializer, or `runInInjectionContext`). Returns a unified\nLangGraphAgent whose properties are Angular Signals that update\nin real-time as the agent streams.", + "description": "Creates a LangGraph-backed Angular agent.\n\nMust be called within an Angular injection context (component constructor,\nfield initializer, or `runInInjectionContext`). Returns a unified\nLangGraphAgent whose properties are Angular Signals that update\nin real time as LangGraph streams messages, values, tool calls, interrupts,\nsubagent state, and checkpoint history.", "signature": "agent(options: AgentOptions>): LangGraphAgent>", "params": [ { "name": "options", "type": "AgentOptions>", - "description": "Configuration for the streaming resource", + "description": "Configuration for the LangGraph agent", "optional": false } ], @@ -1627,6 +1664,25 @@ "```typescript\n// In a component field initializer\nconst chat = agent<{ messages: BaseMessage[] }>({\n assistantId: 'chat_agent',\n apiUrl: 'http://localhost:2024',\n threadId: signal(this.savedThreadId),\n onThreadId: (id) => localStorage.setItem('threadId', id),\n});\n\n// Access signals in template\n// chat.messages(), chat.status(), chat.error()\n```" ] }, + { + "name": "extractCitations", + "kind": "function", + "description": "", + "signature": "extractCitations(msg: KwargsLike): Citation[] | undefined", + "params": [ + { + "name": "msg", + "type": "KwargsLike", + "description": "", + "optional": false + } + ], + "returns": { + "type": "Citation[] | undefined", + "description": "" + }, + "examples": [] + }, { "name": "mockLangGraphAgent", "kind": "function", diff --git a/apps/website/content/docs/agent/api/fetch-stream-transport.mdx b/apps/website/content/docs/agent/api/fetch-stream-transport.mdx index be5639995..52c418f60 100644 --- a/apps/website/content/docs/agent/api/fetch-stream-transport.mdx +++ b/apps/website/content/docs/agent/api/fetch-stream-transport.mdx @@ -1,39 +1,46 @@ # FetchStreamTransport -`FetchStreamTransport` is the production-ready transport that opens a real server-sent event connection using the browser's `fetch` API and reads a `ReadableStream` response body. It is the default transport you register with `provideAgent` in production builds. +`FetchStreamTransport` is the production transport for LangGraph Platform. It wraps the LangGraph SDK client, creates threads when needed, starts streaming runs, joins queued runs, cancels runs, loads history, and updates thread state for operations such as regenerate rollback. ## When you interact with it directly -In most apps you will never import or inject `FetchStreamTransport` by name — you register it once in `provideAgent` and forget about it. The two cases where you reach for it explicitly are: +In most apps you never instantiate `FetchStreamTransport` directly. When no custom transport is configured, `agent()` creates one from the resolved `apiUrl` and `onThreadId` callback. -1. **Per-resource override** — you want one resource to use a different transport than the global default while everything else stays on `FetchStreamTransport`. -2. **Outside the DI tree** — you are constructing a resource in a context where global providers are not available and you need to supply the transport manually. +You reach for it explicitly when you want to share a transport instance, inspect behavior in tests, or implement a custom transport by matching the same `AgentTransport` interface. ```ts -import { inject } from '@angular/core'; import { agent, FetchStreamTransport } from '@ngaf/langgraph'; -// Override transport for a single resource -const events = agent({ - url: () => '/api/events', - transport: inject(FetchStreamTransport), +const transport = new FetchStreamTransport( + 'http://localhost:2024', + (threadId) => localStorage.setItem('threadId', threadId), +); + +const chat = agent({ + apiUrl: 'http://localhost:2024', + assistantId: 'support-agent', + transport, }); ``` ## How it works -`FetchStreamTransport` makes a `fetch` call to the given URL and expects the server to respond with `Content-Type: text/event-stream`. It then reads the `ReadableStream` body line-by-line, parses SSE `data:` fields, and emits each parsed JSON value into the resource signal. +`FetchStreamTransport` creates a LangGraph SDK `Client` for the configured API URL. On `stream()`, it creates a thread when `threadId` is missing, calls `client.runs.stream(...)`, normalizes SDK events into `StreamEvent` objects, and yields them to the stream manager. The transport handles: -- **Backpressure** — reads chunks at the pace the browser delivers them -- **Cancellation** — aborts the underlying `fetch` when `interrupt()` is called or the resource is destroyed -- **Error propagation** — network errors and non-2xx responses surface through `resource.error()` +- **Thread creation** — calls `onThreadId` when the SDK creates a new thread. +- **Streaming modes** — defaults to `values`, `messages-tuple`, `updates`, and `custom`. +- **Cancellation** — forwards `AbortSignal` to LangGraph run APIs. +- **Queued runs** — supports create, cancel, and join flows when the backend supports them. +- **History and state update** — loads checkpoint history and calls `threads.updateState()` for regenerate rollback. +- **Error propagation** — SDK and network errors surface through `agent.error()`. - `FetchStreamTransport` implements the `StreamTransport` interface. You can - create custom transports (e.g. WebSocket-backed) by implementing the same - interface and providing them in place of this class. + `FetchStreamTransport` implements `AgentTransport`. Custom transports must + implement `stream(assistantId, threadId, payload, signal, options?)`; optional + methods such as `joinStream`, `createQueuedRun`, `cancelRun`, `getHistory`, + and `updateState` enable advanced LangGraph features. ## What's Next diff --git a/apps/website/content/docs/agent/api/mock-stream-transport.mdx b/apps/website/content/docs/agent/api/mock-stream-transport.mdx index 69ae82238..078fb8da3 100644 --- a/apps/website/content/docs/agent/api/mock-stream-transport.mdx +++ b/apps/website/content/docs/agent/api/mock-stream-transport.mdx @@ -1,13 +1,13 @@ # MockAgentTransport -`MockAgentTransport` is a test-friendly transport that replaces real network calls with an in-memory event emitter. Use it in unit and component tests to push values on demand and assert against your component's reactive state without a running server. +`MockAgentTransport` is a deterministic `AgentTransport` implementation for tests. It records stream calls, lets tests emit LangGraph stream events manually, and avoids a running LangGraph server. ## Complete test example -The pattern below covers the full lifecycle: configure the transport in `TestBed`, create the component, emit values, and assert signal state. +The pattern below covers the full lifecycle: provide a transport instance, create the component, emit stream events, and assert signal state. ```ts -import { Component, inject } from '@angular/core'; +import { Component } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { provideAgent, @@ -16,44 +16,59 @@ import { } from '@ngaf/langgraph'; @Component({ template: '' }) -class RepoComponent { - readonly repo = agent<{ name: string }>({ - url: () => '/api/repos/42', +class ChatComponent { + readonly chat = agent({ + apiUrl: '', + assistantId: 'test-agent', + threadId: 'thread-1', }); } -describe('RepoComponent', () => { +describe('ChatComponent', () => { let transport: MockAgentTransport; beforeEach(() => { + transport = new MockAgentTransport(); TestBed.configureTestingModule({ - imports: [RepoComponent], - providers: [provideAgent({ transport: MockAgentTransport })], + imports: [ChatComponent], + providers: [provideAgent({ apiUrl: '', transport })], }); - transport = TestBed.inject(MockAgentTransport); }); - it('reflects the streamed value', () => { - const fixture = TestBed.createComponent(RepoComponent); + it('reflects streamed messages', async () => { + const fixture = TestBed.createComponent(ChatComponent); fixture.detectChanges(); - // Push a value into the stream — synchronous, no fakeAsync needed - transport.emit('/api/repos/42', { name: 'my-repo' }); + const stream = fixture.componentInstance.chat.submit({ message: 'Hello' }); + transport.emit([ + { + type: 'values', + values: { + messages: [{ type: 'ai', id: 'a1', content: 'Hi there' }], + }, + }, + ]); + transport.close(); + await stream; fixture.detectChanges(); - expect(fixture.componentInstance.repo.value()).toEqual({ name: 'my-repo' }); - expect(fixture.componentInstance.repo.status()).toBe('streaming'); + expect(fixture.componentInstance.chat.messages()).toEqual([ + expect.objectContaining({ role: 'assistant', content: 'Hi there' }), + ]); + expect(fixture.componentInstance.chat.status()).toBe('idle'); }); - it('surfaces errors through the error signal', () => { - const fixture = TestBed.createComponent(RepoComponent); + it('surfaces errors through the error signal', async () => { + const fixture = TestBed.createComponent(ChatComponent); fixture.detectChanges(); - transport.error('/api/repos/42', new Error('not found')); + const stream = fixture.componentInstance.chat.submit({ message: 'Hello' }); + transport.emitError(new Error('not found')); + await expect(stream).rejects.toThrow('not found'); fixture.detectChanges(); - expect(fixture.componentInstance.repo.status()).toBe('error'); - expect(fixture.componentInstance.repo.error()).toBeInstanceOf(Error); + expect(fixture.componentInstance.chat.status()).toBe('error'); + expect(fixture.componentInstance.chat.error()).toBeInstanceOf(Error); }); }); ``` @@ -62,14 +77,18 @@ describe('RepoComponent', () => { | Method | Description | |--------|-------------| -| `emit(url, value)` | Pushes a single value into the stream at the given URL path. | -| `error(url, err)` | Triggers an error on the stream at the given URL path. | -| `complete(url)` | Closes the stream cleanly, as if the server sent the final event. | +| `constructor(script?)` | Optionally seeds scripted event batches for manual stepping. | +| `nextBatch()` | Returns the next scripted event batch. | +| `emit(events)` | Pushes one or more `StreamEvent` objects into the active stream. | +| `emitError(err)` | Causes the active stream to reject with `err`. | +| `close()` | Closes the active stream after queued events drain. | +| `isStreaming()` | Returns whether a stream is currently active. | + +The transport also records calls in `streams`, `createdQueuedRuns`, `cancelledRuns`, `joinedRuns`, and `historyCalls`, which is useful for asserting payloads and options. - Because `MockAgentTransport` is synchronous by default, you can emit values - and assert state changes in the same test tick — no `fakeAsync` or `tick` - required. + `MockAgentTransport` emits only when your test calls `emit()`, `emitError()`, + or `close()`. This makes stream state and payload assertions deterministic. ## What's Next @@ -92,7 +111,7 @@ describe('RepoComponent', () => { Full reference for the primitive you are testing against. diff --git a/apps/website/content/docs/agent/api/provide-agent.mdx b/apps/website/content/docs/agent/api/provide-agent.mdx index c632fc6ad..78bba7b34 100644 --- a/apps/website/content/docs/agent/api/provide-agent.mdx +++ b/apps/website/content/docs/agent/api/provide-agent.mdx @@ -1,21 +1,25 @@ # provideAgent() -`provideAgent` is the provider factory that registers `angular` in Angular's dependency injection system. Call it once inside `bootstrapApplication` (or an `ApplicationConfig`) to configure the transport and any global defaults used by every `agent` in your app. +`provideAgent()` registers global defaults for every `agent()` call in an Angular application. Call it once in `bootstrapApplication` or an `ApplicationConfig` when multiple agents share the same LangGraph API URL, transport, or license token. -This is the single configuration point for the entire library. Rather than configuring each resource individually, you declare your transport here and every `agent` call throughout the app inherits it automatically. +Per-call options still win over provider defaults, so you can configure the common case globally and override individual agents when needed. ```ts import { bootstrapApplication } from '@angular/platform-browser'; import { provideAgent, - FetchStreamTransport, + MockAgentTransport, } from '@ngaf/langgraph'; import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent, { providers: [ provideAgent({ - transport: FetchStreamTransport, + apiUrl: 'http://localhost:2024', + license: environment.ngafLicense, + transport: environment.testMode + ? new MockAgentTransport() + : undefined, }), ], }); @@ -25,24 +29,48 @@ bootstrapApplication(AppComponent, { | Option | Type | Description | |--------|------|-------------| -| `transport` | `Type` | The transport class to inject when resources request `StreamTransport`. Required. | +| `apiUrl` | `string` | Default LangGraph Platform API base URL. Individual `agent()` calls may override it. | +| `transport` | `AgentTransport` | Optional transport instance. Defaults to `FetchStreamTransport` when omitted. | +| `license` | `string` | Optional signed license token. Development and noncommercial use can omit it. | -## Swapping transports by environment +## Defaults and overrides -Because `provideAgent` accepts a class token, you can vary the transport based on your environment without touching any component code: +Provider defaults are merged with call-site options. The call site takes precedence. ```ts -// main.ts — production -provideAgent({ transport: FetchStreamTransport }) +provideAgent({ apiUrl: 'https://api.example.com' }); -// main.spec.ts / TestBed — tests -provideAgent({ transport: MockAgentTransport }) +const productionAgent = agent({ + assistantId: 'support-agent', +}); + +const localAgent = agent({ + apiUrl: 'http://localhost:2024', + assistantId: 'dev-agent', +}); +``` + +## Test transports + +`transport` is an object that implements `AgentTransport`, not an Angular class token. Create an instance before passing it to `provideAgent()` or to an individual `agent()` call. + +```ts +const transport = new MockAgentTransport(); + +TestBed.configureTestingModule({ + providers: [ + provideAgent({ + apiUrl: '', + transport, + }), + ], +}); ``` - - Swap `FetchStreamTransport` for `MockAgentTransport` (or any custom class - implementing the `StreamTransport` interface) to change the transport for all - resources at once — useful for testing or SSR. + + You do not need `provideAgent()` for one-off tests. Passing + `transport: new MockAgentTransport()` directly to `agent()` is often simpler + and keeps the test fixture explicit. ## What's Next @@ -65,7 +93,7 @@ provideAgent({ transport: MockAgentTransport }) Full reference for the core primitive you configure here. diff --git a/apps/website/content/docs/chat/api/api-docs.json b/apps/website/content/docs/chat/api/api-docs.json index 767973347..3119b1690 100644 --- a/apps/website/content/docs/chat/api/api-docs.json +++ b/apps/website/content/docs/chat/api/api-docs.json @@ -826,6 +826,72 @@ ], "methods": [] }, + { + "name": "ChatCitationCardTemplateDirective", + "kind": "class", + "description": "ContentChild template directive for custom citation card rendering.\nUsage: ...", + "params": [], + "examples": [], + "properties": [ + { + "name": "tpl", + "type": "TemplateRef", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "ChatCitationsCardComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "citation", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "ChatCitationsComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "cardTpl", + "type": "ChatCitationCardTemplateDirective<> | null", + "description": "", + "optional": false + }, + { + "name": "citations", + "type": "Signal", + "description": "Combined citation list:\n 1. Message.citations (provider-populated, takes precedence by id)\n 2. Markdown sidecar defs (Pandoc-formatted [^id]: lines), merged in\n for any id not already present.\n\nSorted by index ascending. This guarantees the sources panel surfaces\ncitations whether they come from message metadata, content syntax, or\nboth — matching the same precedence as inline-marker resolution.", + "optional": false + }, + { + "name": "heading", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "message", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, { "name": "ChatComponent", "kind": "class", @@ -857,6 +923,36 @@ "description": "", "optional": false }, + { + "name": "messageCopy", + "type": "OutputEmitterRef", + "description": "Emitted when the user copies an assistant message.", + "optional": false + }, + { + "name": "modelOptions", + "type": "InputSignal", + "description": "High-level model-picker API. When `modelOptions` is non-empty, the chat\ncomposition renders a `` inside the input pill (in BOTH\nwelcome and conversation modes), wired to the two-way `selectedModel`\nmodel. Consumers who want full control should leave `modelOptions`\nempty and project a `` themselves.", + "optional": false + }, + { + "name": "modelPickerPlaceholder", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "rate", + "type": "OutputEmitterRef", + "description": "Emitted when the user rates an assistant message.", + "optional": false + }, + { + "name": "regenerate", + "type": "OutputEmitterRef", + "description": "Emitted when the user clicks the regenerate button on an assistant message.", + "optional": false + }, { "name": "renderEvent", "type": "OutputEmitterRef", @@ -866,18 +962,24 @@ { "name": "renderRegistry", "type": "Signal", - "description": "Convert ViewRegistry → AngularRegistry for ChatGenerativeUiComponent.", + "description": "", "optional": false }, { "name": "resolvedStore", "type": "Signal", - "description": "Resolved store: use the explicitly provided store input, or fall back to\nan internal store when `views` are provided (generative-ui use case).", + "description": "", "optional": false }, { - "name": "sidebarOpen", - "type": "WritableSignal", + "name": "selectedModel", + "type": "ModelSignal", + "description": "", + "optional": false + }, + { + "name": "showWelcome", + "type": "Signal", "description": "", "optional": false }, @@ -904,12 +1006,18 @@ "type": "InputSignal>> | undefined>", "description": "", "optional": false + }, + { + "name": "welcomeDisabled", + "type": "InputSignal", + "description": "", + "optional": false } ], "methods": [ { "name": "classifyMessage", - "signature": "classifyMessage(content: string, index: number)", + "signature": "classifyMessage(content: string, message: object)", "description": "", "params": [ { @@ -919,8 +1027,8 @@ "optional": false }, { - "name": "index", - "type": "number", + "name": "message", + "type": "object", "description": "", "optional": false } @@ -932,6 +1040,25 @@ "description": "", "params": [] }, + { + "name": "isReasoningStreaming", + "signature": "isReasoningStreaming(message: Message, index: number)", + "description": "True while a message's reasoning is mid-stream — i.e. it's the latest\nmessage, the agent is loading, the message has reasoning content, and\nno response text has arrived yet. Once the response text begins, the\nreasoning pill collapses (per its internal logic).", + "params": [ + { + "name": "message", + "type": "Message", + "description": "", + "optional": false + }, + { + "name": "index", + "type": "number", + "description": "", + "optional": false + } + ] + }, { "name": "onA2uiAction", "signature": "onA2uiAction(message: A2uiActionMessage)", @@ -970,6 +1097,57 @@ } ] }, + { + "name": "onCopy", + "signature": "onCopy(message: unknown, content: string)", + "description": "", + "params": [ + { + "name": "message", + "type": "unknown", + "description": "", + "optional": false + }, + { + "name": "content", + "type": "string", + "description": "", + "optional": false + } + ] + }, + { + "name": "onRate", + "signature": "onRate(message: unknown, value: \"up\" | \"down\")", + "description": "", + "params": [ + { + "name": "message", + "type": "unknown", + "description": "", + "optional": false + }, + { + "name": "value", + "type": "\"up\" | \"down\"", + "description": "", + "optional": false + } + ] + }, + { + "name": "onRegenerate", + "signature": "onRegenerate(messageIndex: number)", + "description": "Regenerate the assistant response at the given message index.", + "params": [ + { + "name": "messageIndex", + "type": "number", + "description": "", + "optional": false + } + ] + }, { "name": "onSpecEvent", "signature": "onSpecEvent(event: RenderEvent, messageIndex: number)", @@ -988,6 +1166,19 @@ "optional": false } ] + }, + { + "name": "prevRole", + "signature": "prevRole(index: number)", + "description": "", + "params": [ + { + "name": "index", + "type": "number", + "description": "", + "optional": false + } + ] } ] }, @@ -1103,6 +1294,52 @@ ], "methods": [] }, + { + "name": "ChatGenerativeUiComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "events", + "type": "OutputEmitterRef", + "description": "", + "optional": false + }, + { + "name": "handlers", + "type": "InputSignal | undefined>", + "description": "", + "optional": false + }, + { + "name": "loading", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "registry", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "spec", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "store", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, { "name": "ChatInputComponent", "kind": "class", @@ -1116,6 +1353,24 @@ "description": "", "optional": false }, + { + "name": "canStop", + "type": "Signal", + "description": "The stop button only appears when the consumer opted in AND we're loading.", + "optional": false + }, + { + "name": "canSubmit", + "type": "Signal", + "description": "Submit is allowed only when not loading and there's non-whitespace text.", + "optional": false + }, + { + "name": "composing", + "type": "WritableSignal", + "description": "True while an IME composition (CJK input, accent, autocorrect) is active.", + "optional": false + }, { "name": "focused", "type": "WritableSignal", @@ -1123,7 +1378,7 @@ "optional": false }, { - "name": "isDisabled", + "name": "isLoading", "type": "Signal", "description": "", "optional": false @@ -1140,6 +1395,18 @@ "description": "", "optional": false }, + { + "name": "showStopButton", + "type": "InputSignal", + "description": "When true (default), shows a stop button while the agent is streaming.", + "optional": false + }, + { + "name": "stopped", + "type": "OutputEmitterRef", + "description": "", + "optional": false + }, { "name": "submitOnEnter", "type": "InputSignal", @@ -1154,6 +1421,12 @@ } ], "methods": [ + { + "name": "focusTextarea", + "signature": "focusTextarea()", + "description": "", + "params": [] + }, { "name": "onKeydown", "signature": "onKeydown(event: KeyboardEvent)", @@ -1167,6 +1440,12 @@ } ] }, + { + "name": "onStop", + "signature": "onStop()", + "description": "Abort the current streaming response (if the adapter supports it).", + "params": [] + }, { "name": "onSubmit", "signature": "onSubmit()", @@ -1201,7 +1480,21 @@ "optional": false } ], - "methods": [] + "methods": [ + { + "name": "defaultText", + "signature": "defaultText(i: AgentInterrupt)", + "description": "", + "params": [ + { + "name": "i", + "type": "AgentInterrupt", + "description": "", + "optional": false + } + ] + } + ] }, { "name": "ChatInterruptPanelComponent", @@ -1238,46 +1531,79 @@ "methods": [] }, { - "name": "ChatMessagesComponent", + "name": "ChatLauncherButtonComponent", "kind": "class", "description": "", "params": [], "examples": [], + "properties": [], + "methods": [] + }, + { + "name": "ChatMessageActionsComponent", + "kind": "class", + "description": "Default action buttons that appear under each assistant message:\nregenerate, copy-to-clipboard, thumbs up, thumbs down.\n\nHidden by default, fades in on `:hover`/`:focus-within` of the parent\n`chat-message`, and stays visible on the current/last assistant message\nand on mobile.", + "params": [], + "examples": [], "properties": [ { - "name": "agent", - "type": "InputSignal", - "description": "", + "name": "content", + "type": "InputSignal", + "description": "Plain text content to copy. Required for the copy button to function.", "optional": false }, { - "name": "getMessageType", - "type": "object", - "description": "", + "name": "contentCopied", + "type": "OutputEmitterRef", + "description": "Emitted with the copied content after a successful clipboard write.", "optional": false }, { - "name": "messages", - "type": "Signal", + "name": "copied", + "type": "WritableSignal", "description": "", "optional": false }, { - "name": "messageTemplates", - "type": "Signal", - "description": "", + "name": "disabled", + "type": "InputSignal", + "description": "When true, the regenerate button is disabled (e.g. while the agent is streaming).", + "optional": false + }, + { + "name": "rate", + "type": "OutputEmitterRef<\"up\" | \"down\">", + "description": "Emitted with 'up' or 'down' when the user rates the response.", + "optional": false + }, + { + "name": "rating", + "type": "WritableSignal<\"up\" | \"down\" | null>", + "description": "", + "optional": false + }, + { + "name": "regenerate", + "type": "OutputEmitterRef", + "description": "Emitted when the user clicks regenerate. Wire this to `agent.regenerate(index)`.", "optional": false } ], "methods": [ { - "name": "findTemplate", - "signature": "findTemplate(type: MessageTemplateType)", + "name": "onCopy", + "signature": "onCopy()", + "description": "", + "params": [] + }, + { + "name": "onRate", + "signature": "onRate(value: \"up\" | \"down\")", "description": "", "params": [ { - "name": "type", - "type": "MessageTemplateType", + "name": "value", + "type": "\"up\" | \"down\"", "description": "", "optional": false } @@ -1286,61 +1612,57 @@ ] }, { - "name": "ChatSubagentCardComponent", + "name": "ChatMessageComponent", "kind": "class", "description": "", "params": [], "examples": [], "properties": [ { - "name": "expanded", - "type": "WritableSignal", + "name": "bodyClass", + "type": "Signal<\"chat-message__bubble\" | \"chat-message__assistant-body\" | \"chat-message__plain\">", "description": "", "optional": false }, { - "name": "latestMessageContent", - "type": "Signal", + "name": "current", + "type": "InputSignal", "description": "", "optional": false }, { - "name": "statusColor", + "name": "currentStr", "type": "Signal", "description": "", "optional": false }, { - "name": "subagent", - "type": "InputSignal", + "name": "message", + "type": "InputSignal", "description": "", "optional": false - } - ], - "methods": [] - }, - { - "name": "ChatSubagentsComponent", - "kind": "class", - "description": "", - "params": [], - "examples": [], - "properties": [ + }, { - "name": "activeSubagents", - "type": "Signal", + "name": "prevRole", + "type": "InputSignal", "description": "", "optional": false }, { - "name": "agent", - "type": "InputSignal", + "name": "role", + "type": "InputSignal", "description": "", "optional": false }, { - "name": "templateRef", - "type": "Signal | undefined>", + "name": "streaming", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "streamingStr", + "type": "Signal", "description": "", "optional": false } @@ -1348,46 +1670,46 @@ "methods": [] }, { - "name": "ChatThreadListComponent", + "name": "ChatMessageListComponent", "kind": "class", "description": "", "params": [], "examples": [], "properties": [ { - "name": "activeThreadId", - "type": "InputSignal", + "name": "agent", + "type": "InputSignal", "description": "", "optional": false }, { - "name": "templateRef", - "type": "Signal | undefined>", + "name": "getMessageType", + "type": "object", "description": "", "optional": false }, { - "name": "threads", - "type": "InputSignal", + "name": "messages", + "type": "Signal", "description": "", "optional": false }, { - "name": "threadSelected", - "type": "OutputEmitterRef", + "name": "messageTemplates", + "type": "Signal", "description": "", "optional": false } ], "methods": [ { - "name": "selectThread", - "signature": "selectThread(threadId: string)", + "name": "findTemplate", + "signature": "findTemplate(type: MessageTemplateType)", "description": "", "params": [ { - "name": "threadId", - "type": "string", + "name": "type", + "type": "MessageTemplateType", "description": "", "optional": false } @@ -1396,7 +1718,7 @@ ] }, { - "name": "ChatTimelineComponent", + "name": "ChatPopupComponent", "kind": "class", "description": "", "params": [], @@ -1404,182 +1726,1301 @@ "properties": [ { "name": "agent", - "type": "InputSignal", + "type": "InputSignal", "description": "", "optional": false }, { - "name": "checkpointSelected", - "type": "OutputEmitterRef", + "name": "closeOnEscape", + "type": "InputSignal", + "description": "Close the popup on Escape (default true).", + "optional": false + }, + { + "name": "open", + "type": "ModelSignal", "description": "", "optional": false }, { - "name": "history", - "type": "Signal", + "name": "shortcut", + "type": "InputSignal", + "description": "Keyboard shortcut (single key) that toggles the popup with cmd (mac)\nor ctrl (other). Set to `null` to disable. Default: 'k' — matches the\nwidely-used cmd/ctrl+K convention.", + "optional": false + } + ], + "methods": [ + { + "name": "closeWindow", + "signature": "closeWindow()", + "description": "", + "params": [] + }, + { + "name": "openWindow", + "signature": "openWindow()", + "description": "", + "params": [] + }, + { + "name": "toggle", + "signature": "toggle()", + "description": "", + "params": [] + } + ] + }, + { + "name": "ChatReasoningComponent", + "kind": "class", + "description": "Renders an assistant's reasoning content as a compact pill that\nexpands to reveal the underlying text. Three visual states:\n\n- Streaming: pill shows \"Thinking…\" with a pulsing dot; auto-expanded\n so the user sees reasoning stream in real time.\n- Idle, with durationMs known: pill shows \"Thought for {duration}\";\n collapsed by default, expand on click.\n- Idle, no duration: pill shows \"Show reasoning\"; collapsed by default.\n\nThe body re-uses chat-streaming-md so reasoning content gets the same\nmarkdown rendering pipeline as the visible response (lists, code,\nstep labels often appear in reasoning output).\n\nInternal state: a tristate \"expanded\" — null means follow auto state-\ndriven logic (force-expand on isStreaming, otherwise honor\ndefaultExpanded), boolean is a manual user choice that wins for the\nlifetime of the instance.", + "params": [], + "examples": [], + "properties": [ + { + "name": "content", + "type": "InputSignal", "description": "", "optional": false }, { - "name": "templateRef", - "type": "Signal | undefined>", + "name": "defaultExpanded", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "durationMs", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "expanded", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "expandedStr", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "hasContent", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "isStreaming", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "label", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "resolvedLabel", + "type": "Signal", "description": "", "optional": false } ], "methods": [ { - "name": "selectCheckpoint", - "signature": "selectCheckpoint(cp: AgentCheckpoint)", + "name": "toggle", + "signature": "toggle()", "description": "", - "params": [ - { - "name": "cp", - "type": "AgentCheckpoint", - "description": "", - "optional": false - } - ] + "params": [] } ] }, { - "name": "ChatTimelineSliderComponent", + "name": "ChatSelectComponent", "kind": "class", - "description": "", + "description": "Generic single-select dropdown. Designed to slot into the chat input pill\n(via [chatInputModelSelect]) but usable anywhere.\n\nInputs:\n options — array of { value, label, disabled? }; required\n value — currently selected value (two-way via model())\n placeholder — trigger label when no option matches; default 'Select'\n disabled — disables the trigger; default false\n menuLabel — aria-label for the popover; defaults to placeholder", "params": [], "examples": [], "properties": [ { - "name": "agent", - "type": "InputSignal", + "name": "currentLabel", + "type": "Signal", "description": "", "optional": false }, { - "name": "forkRequested", - "type": "OutputEmitterRef", + "name": "disabled", + "type": "InputSignal", "description": "", "optional": false }, { - "name": "history", - "type": "Signal", + "name": "menuLabel", + "type": "InputSignal", "description": "", "optional": false }, { - "name": "replayRequested", - "type": "OutputEmitterRef", + "name": "open", + "type": "WritableSignal", "description": "", "optional": false }, { - "name": "selectedIndex", - "type": "WritableSignal", + "name": "options", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "placeholder", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "value", + "type": "ModelSignal", "description": "", "optional": false } ], "methods": [ { - "name": "fork", - "signature": "fork(cp: AgentCheckpoint, index: number)", + "name": "onMenuKeydown", + "signature": "onMenuKeydown(e: KeyboardEvent)", "description": "", "params": [ { - "name": "cp", - "type": "AgentCheckpoint", + "name": "e", + "type": "KeyboardEvent", "description": "", "optional": false - }, + } + ] + }, + { + "name": "onTriggerKeydown", + "signature": "onTriggerKeydown(e: KeyboardEvent)", + "description": "", + "params": [ { - "name": "index", - "type": "number", + "name": "e", + "type": "KeyboardEvent", + "description": "", + "optional": false + } + ] + }, + { + "name": "selectOption", + "signature": "selectOption(opt: ChatSelectOption)", + "description": "", + "params": [ + { + "name": "opt", + "type": "ChatSelectOption", "description": "", "optional": false } ] }, { - "name": "replay", - "signature": "replay(cp: AgentCheckpoint)", + "name": "toggle", + "signature": "toggle()", + "description": "", + "params": [] + } + ] + }, + { + "name": "ChatSidebarComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "agent", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "open", + "type": "ModelSignal", + "description": "", + "optional": false + }, + { + "name": "pushContent", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [ + { + "name": "closeWindow", + "signature": "closeWindow()", + "description": "", + "params": [] + }, + { + "name": "openWindow", + "signature": "openWindow()", + "description": "", + "params": [] + }, + { + "name": "toggle", + "signature": "toggle()", + "description": "", + "params": [] + } + ] + }, + { + "name": "ChatStreamingMdComponent", + "kind": "class", + "description": "Renders streaming markdown by walking a @cacheplane/partial-markdown AST\nthrough @ngaf/render's view registry.\n\nReactivity model: the live `parser.root` keeps a stable JS reference\nacross pushes (partial-markdown's identity guarantee). To make Angular\nsignals propagate downstream when the underlying tree changes, we surface\na materialized snapshot via `materialize()`. The snapshot shares\nstructurally — unchanged subtrees keep the SAME reference, and any\ndescendant change yields a NEW root reference. This lets Angular's\n`Object.is` equality check both detect changes (root reference differs)\nand short-circuit unchanged subtrees (per-node references stable).\n\nOverride per-node-type renderers via the `[viewRegistry]` input or by\nsupplying a different `MARKDOWN_VIEW_REGISTRY` provider in the injector\ntree.", + "params": [], + "examples": [], + "properties": [ + { + "name": "content", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "resolvedRegistry", + "type": "Signal>>>", + "description": "", + "optional": false + }, + { + "name": "root", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "streaming", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "viewRegistry", + "type": "InputSignal>> | undefined>", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "ChatSubagentCardComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "latestMessageContent", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "state", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "subagent", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "ChatSubagentsComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "activeSubagents", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "agent", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "templateRef", + "type": "Signal | undefined>", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "ChatSuggestionsComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "selected", + "type": "OutputEmitterRef", + "description": "", + "optional": false + }, + { + "name": "suggestions", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "ChatThreadListComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "activeThreadId", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "newThreadRequested", + "type": "OutputEmitterRef", + "description": "", + "optional": false + }, + { + "name": "showNewThreadButton", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "templateRef", + "type": "Signal | undefined>", + "description": "", + "optional": false + }, + { + "name": "threads", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "threadSelected", + "type": "OutputEmitterRef", + "description": "", + "optional": false + } + ], + "methods": [ + { + "name": "selectThread", + "signature": "selectThread(threadId: string)", + "description": "", + "params": [ + { + "name": "threadId", + "type": "string", + "description": "", + "optional": false + } + ] + }, + { + "name": "threadLabel", + "signature": "threadLabel(thread: Thread)", + "description": "", + "params": [ + { + "name": "thread", + "type": "Thread", + "description": "", + "optional": false + } + ] + } + ] + }, + { + "name": "ChatTimelineComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "agent", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "checkpointSelected", + "type": "OutputEmitterRef", + "description": "", + "optional": false + }, + { + "name": "history", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "templateRef", + "type": "Signal | undefined>", + "description": "", + "optional": false + } + ], + "methods": [ + { + "name": "selectCheckpoint", + "signature": "selectCheckpoint(cp: AgentCheckpoint)", + "description": "", + "params": [ + { + "name": "cp", + "type": "AgentCheckpoint", + "description": "", + "optional": false + } + ] + } + ] + }, + { + "name": "ChatTimelineSliderComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "agent", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "forkRequested", + "type": "OutputEmitterRef", + "description": "", + "optional": false + }, + { + "name": "history", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "replayRequested", + "type": "OutputEmitterRef", + "description": "", + "optional": false + }, + { + "name": "selectedIndex", + "type": "WritableSignal", + "description": "", + "optional": false + } + ], + "methods": [ + { + "name": "fork", + "signature": "fork(cp: AgentCheckpoint, index: number)", + "description": "", + "params": [ + { + "name": "cp", + "type": "AgentCheckpoint", + "description": "", + "optional": false + }, + { + "name": "index", + "type": "number", + "description": "", + "optional": false + } + ] + }, + { + "name": "replay", + "signature": "replay(cp: AgentCheckpoint)", + "description": "", + "params": [ + { + "name": "cp", + "type": "AgentCheckpoint", + "description": "", + "optional": false + } + ] + } + ] + }, + { + "name": "ChatToolCallCardComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "ariaLabel", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "autoExpanded", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "defaultCollapsed", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "state", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "status", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "toolCall", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [ + { + "name": "formatJson", + "signature": "formatJson(value: unknown)", + "description": "", + "params": [ + { + "name": "value", + "type": "unknown", + "description": "", + "optional": false + } + ] + } + ] + }, + { + "name": "ChatToolCallsComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "agent", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "expandedGroups", + "type": "Signal>", + "description": "", + "optional": false + }, + { + "name": "grouping", + "type": "InputSignal<\"auto\" | \"none\">", + "description": "", + "optional": false + }, + { + "name": "groups", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "groupSummary", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "message", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "templates", + "type": "Signal", + "description": "Per-tool-name + wildcard templates registered as content children.", + "optional": false + }, + { + "name": "toolCalls", + "type": "Signal", + "description": "", + "optional": false + } + ], + "methods": [ + { + "name": "summarize", + "signature": "summarize(name: string, count: number)", + "description": "", + "params": [ + { + "name": "name", + "type": "string", + "description": "", + "optional": false + }, + { + "name": "count", + "type": "number", + "description": "", + "optional": false + } + ] + }, + { + "name": "toggleGroup", + "signature": "toggleGroup(index: number)", + "description": "", + "params": [ + { + "name": "index", + "type": "number", + "description": "", + "optional": false + } + ] + }, + { + "name": "toToolCallInfo", + "signature": "toToolCallInfo(tc: ToolCall)", + "description": "", + "params": [ + { + "name": "tc", + "type": "ToolCall", + "description": "", + "optional": false + } + ] + } + ] + }, + { + "name": "ChatToolCallTemplateDirective", + "kind": "class", + "description": "Registers a per-tool-name template inside . The\nprimitive collects all directive instances via contentChildren() and\ndispatches incoming calls by their `name` field. A literal \"*\" name\nregisters a wildcard catch-all that handles any tool name without a\nspecific template registered.\n\nUsage:\n\n \n \n \n \n \n \n \n ", + "params": [], + "examples": [], + "properties": [ + { + "name": "name", + "type": "InputSignal", + "description": "The tool name this template handles, or \"*\" for the wildcard catch-all.", + "optional": false + }, + { + "name": "templateRef", + "type": "TemplateRef", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "ChatTraceComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "defaultExpanded", + "type": "InputSignal", + "description": "When state is not 'running' or 'error', honors this input as the default expansion.", + "optional": false + }, + { + "name": "expanded", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "expandedStr", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "state", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [ + { + "name": "toggle", + "signature": "toggle()", + "description": "", + "params": [] + } + ] + }, + { + "name": "ChatTypingIndicatorComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "agent", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "visible", + "type": "Signal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "ChatWelcomeComponent", + "kind": "class", + "description": "Empty-state owner. Renders a centered greeting + slot-projected input +\noptional vertical suggestion rows. Mounted only when the parent chat has\nno messages and welcome is not disabled.\n\nSlots:\n [chatWelcomeTitle] — replaces the default

\"How can I help?\"\n [chatWelcomeInput] — projects the chat input into the center column\n [chatWelcomeSuggestions] — projects suggestion rows below the input\n\nHost CSS variables (override on :host or any ancestor):\n --ngaf-chat-welcome-max-width default 36rem\n --ngaf-chat-welcome-gap default 1.25rem\n --ngaf-chat-welcome-padding default 24px", + "params": [], + "examples": [], + "properties": [], + "methods": [] + }, + { + "name": "ChatWelcomeSuggestionComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "label", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "selected", + "type": "OutputEmitterRef", + "description": "", + "optional": false + }, + { + "name": "value", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "ChatWindowComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [], + "methods": [] + }, + { + "name": "CitationsResolverService", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "markdownDefs", + "type": "WritableSignal>", + "description": "", + "optional": false + }, + { + "name": "message", + "type": "WritableSignal", + "description": "", + "optional": false + } + ], + "methods": [ + { + "name": "lookup", + "signature": "lookup(refId: string)", + "description": "", + "params": [ + { + "name": "refId", + "type": "string", + "description": "", + "optional": false + } + ] + } + ] + }, + { + "name": "MarkdownAutolinkComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownBlockquoteComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownChildrenComponent", + "kind": "class", + "description": "Recursively dispatches a parent node's children through the markdown view\nregistry. Each child's `type` is looked up in the registry; the resolved\ncomponent is rendered with `[node]` bound to that child.\n\nIdentity-preserving: `track $any(child)` keys on the JS reference of the\nchild node. Because @cacheplane/partial-markdown preserves node identity\nacross pushes, unchanged subtrees never re-render.", + "params": [], + "examples": [], + "properties": [ + { + "name": "children", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "parent", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [ + { + "name": "resolve", + "signature": "resolve(child: MarkdownNode)", + "description": "", + "params": [ + { + "name": "child", + "type": "MarkdownNode", + "description": "", + "optional": false + } + ] + } + ] + }, + { + "name": "MarkdownCitationReferenceComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + }, + { + "name": "resolved", + "type": "Signal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownCodeBlockComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "languageClass", + "type": "Signal", + "description": "", + "optional": false + }, + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownDocumentComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownEmphasisComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownHardBreakComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownHeadingComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownImageComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "failed", + "type": "WritableSignal", + "description": "", + "optional": false + }, + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownInlineCodeComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownLinkComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownListComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownListItemComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownParagraphComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownSoftBreakComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownStrikethroughComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", + "description": "", + "optional": false + } + ], + "methods": [] + }, + { + "name": "MarkdownStrongComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ + { + "name": "node", + "type": "InputSignal", "description": "", - "params": [ - { - "name": "cp", - "type": "AgentCheckpoint", - "description": "", - "optional": false - } - ] + "optional": false } - ] + ], + "methods": [] }, { - "name": "ChatToolCallCardComponent", + "name": "MarkdownTableCellComponent", "kind": "class", "description": "", "params": [], "examples": [], "properties": [ { - "name": "expanded", - "type": "WritableSignal", + "name": "alignment", + "type": "Signal", "description": "", "optional": false }, { - "name": "toolCall", - "type": "InputSignal", + "name": "isHeader", + "type": "Signal", "description": "", "optional": false - } - ], - "methods": [ + }, { - "name": "formatJson", - "signature": "formatJson(value: unknown)", + "name": "node", + "type": "InputSignal", "description": "", - "params": [ - { - "name": "value", - "type": "unknown", - "description": "", - "optional": false - } - ] + "optional": false } - ] + ], + "methods": [] }, { - "name": "ChatToolCallsComponent", + "name": "MarkdownTableComponent", "kind": "class", "description": "", "params": [], "examples": [], "properties": [ { - "name": "agent", - "type": "InputSignal", + "name": "bodyRows", + "type": "Signal", "description": "", "optional": false }, { - "name": "message", - "type": "InputSignal", + "name": "headerRow", + "type": "Signal", "description": "", "optional": false }, { - "name": "templateRef", - "type": "Signal | undefined>", + "name": "node", + "type": "InputSignal", "description": "", "optional": false - }, + } + ], + "methods": [] + }, + { + "name": "MarkdownTableRowComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ { - "name": "toolCalls", - "type": "Signal", + "name": "node", + "type": "InputSignal", "description": "", "optional": false } @@ -1587,21 +3028,31 @@ "methods": [] }, { - "name": "ChatTypingIndicatorComponent", + "name": "MarkdownTextComponent", "kind": "class", "description": "", "params": [], "examples": [], "properties": [ { - "name": "agent", - "type": "InputSignal", + "name": "node", + "type": "InputSignal", "description": "", "optional": false - }, + } + ], + "methods": [] + }, + { + "name": "MarkdownThematicBreakComponent", + "kind": "class", + "description": "", + "params": [], + "examples": [], + "properties": [ { - "name": "visible", - "type": "Signal", + "name": "node", + "type": "InputSignal", "description": "", "optional": false } @@ -1925,6 +3376,12 @@ "description": "", "optional": false }, + { + "name": "regenerate", + "type": "object", + "description": "Discards the assistant message at the given index AND all messages after\nit, then re-runs the agent against the trimmed conversation tail. The\npreceding user message (at index - 1) is preserved and re-submitted as\nthe agent's input. No new user message is added to the history.\n\nThrows if the message at `index` is not 'assistant' role, or if the\nagent is currently loading another response.", + "optional": false + }, { "name": "state", "type": "Signal>", @@ -2143,6 +3600,12 @@ "description": "", "optional": false }, + { + "name": "regenerate", + "type": "object", + "description": "Discards the assistant message at the given index AND all messages after\nit, then re-runs the agent against the trimmed conversation tail. The\npreceding user message (at index - 1) is preserved and re-submitted as\nthe agent's input. No new user message is added to the history.\n\nThrows if the message at `index` is not 'assistant' role, or if the\nagent is currently loading another response.", + "optional": false + }, { "name": "state", "type": "Signal>", @@ -2252,6 +3715,96 @@ ], "examples": [] }, + { + "name": "ChatSelectOption", + "kind": "interface", + "description": "", + "properties": [ + { + "name": "disabled", + "type": "boolean", + "description": "", + "optional": true + }, + { + "name": "label", + "type": "string", + "description": "", + "optional": false + }, + { + "name": "value", + "type": "string", + "description": "", + "optional": false + } + ], + "examples": [] + }, + { + "name": "ChatToolCallTemplateContext", + "kind": "interface", + "description": "Template-context surface available to a per-tool template. The first\nargument is the ToolCall itself (let-call); status is exposed as a\nnamed context property (let-status=\"status\").", + "properties": [ + { + "name": "$implicit", + "type": "ToolCall", + "description": "", + "optional": false + }, + { + "name": "status", + "type": "ToolCallStatus", + "description": "", + "optional": false + } + ], + "examples": [] + }, + { + "name": "Citation", + "kind": "interface", + "description": "Provider-agnostic citation entry. Populated by adapters from message\nmetadata (LangGraph additional_kwargs.citations, ag-ui STATE_DELTA at\n/citations/{messageId}). Pandoc-formatted [^id]: ... defs in message\ncontent remain in the markdown AST sidecar and are merged via\nCitationsResolverService at render time.", + "properties": [ + { + "name": "extra", + "type": "Record", + "description": "Provider-specific extras (retrieval score, source type, etc.).", + "optional": true + }, + { + "name": "id", + "type": "string", + "description": "Stable id used to match `[^id]` markers in Pandoc-formatted content.", + "optional": false + }, + { + "name": "index", + "type": "number", + "description": "1-based display order. Stable per-message.", + "optional": false + }, + { + "name": "snippet", + "type": "string", + "description": "", + "optional": true + }, + { + "name": "title", + "type": "string", + "description": "", + "optional": true + }, + { + "name": "url", + "type": "string", + "description": "", + "optional": true + } + ], + "examples": [] + }, { "name": "ContentClassifier", "kind": "interface", @@ -2351,6 +3904,12 @@ "kind": "interface", "description": "", "properties": [ + { + "name": "citations", + "type": "Citation[]", + "description": "Provider-agnostic citation list. Populated by adapters.", + "optional": true + }, { "name": "content", "type": "string | ContentBlock[]", @@ -2375,6 +3934,18 @@ "description": "Optional display/author name.", "optional": true }, + { + "name": "reasoning", + "type": "string", + "description": "Reasoning text emitted by the model before/alongside the visible\nresponse. Populated by adapters from {type:'reasoning'} or\n{type:'thinking'} content blocks (LangGraph) or REASONING_MESSAGE_*\nevents (AG-UI). Always a plain string — provider-specific shape\n(encrypted blocks, multi-step summaries) is absorbed by the adapter\nand not surfaced here.", + "optional": true + }, + { + "name": "reasoningDurationMs", + "type": "number", + "description": "Wall-clock duration of the reasoning phase in milliseconds.\nPopulated by the adapter when both start (first reasoning chunk) and\nend (first response-text chunk, or final canonical message) are\nknown. Undefined when reasoning timing isn't available.", + "optional": true + }, { "name": "role", "type": "Role", @@ -2431,6 +4002,12 @@ "description": "", "optional": false }, + { + "name": "regenerate", + "type": "object", + "description": "Discards the assistant message at the given index AND all messages after\nit, then re-runs the agent against the trimmed conversation tail. The\npreceding user message (at index - 1) is preserved and re-submitted as\nthe agent's input. No new user message is added to the history.\n\nThrows if the message at `index` is not 'assistant' role, or if the\nagent is currently loading another response.", + "optional": false + }, { "name": "state", "type": "WritableSignal>", @@ -2576,6 +4153,26 @@ ], "examples": [] }, + { + "name": "ResolvedCitation", + "kind": "interface", + "description": "", + "properties": [ + { + "name": "citation", + "type": "Citation", + "description": "", + "optional": false + }, + { + "name": "source", + "type": "\"message\" | \"markdown\"", + "description": "", + "optional": false + } + ], + "examples": [] + }, { "name": "Subagent", "kind": "interface", @@ -2686,6 +4283,12 @@ "type": "unknown", "description": "", "optional": true + }, + { + "name": "status", + "type": "ToolCallStatus", + "description": "Optional — present when the parent provides it. Drives the pill + default-collapsed logic.", + "optional": true } ], "examples": [] @@ -2704,6 +4307,13 @@ "signature": "\"idle\" | \"running\" | \"error\"", "examples": [] }, + { + "name": "ChatMessageRole", + "kind": "type", + "description": "", + "signature": "\"user\" | \"assistant\" | \"system\" | \"tool\"", + "examples": [] + }, { "name": "ContentBlock", "kind": "type", @@ -2788,6 +4398,13 @@ "signature": "\"pending\" | \"running\" | \"complete\" | \"error\"", "examples": [] }, + { + "name": "TraceState", + "kind": "type", + "description": "", + "signature": "\"pending\" | \"running\" | \"done\" | \"error\"", + "examples": [] + }, { "name": "ViewRegistry", "kind": "type", @@ -2931,10 +4548,29 @@ }, "examples": [] }, + { + "name": "formatDuration", + "kind": "function", + "description": "Render a millisecond duration as a human-readable label suitable for\nthe chat-reasoning \"Thought for Ns\" pill.\n\n- <1 s → \"<1s\"\n- 1–59 s → \"Ns\" (e.g. \"4s\")\n- ≥60 s → \"Nm Ms\" (e.g. \"1m 12s\", \"60m 0s\")\n\nNegative or non-finite inputs collapse to \"<1s\" so a corrupted timing\nmap never produces noisy output.", + "signature": "formatDuration(ms: number): string", + "params": [ + { + "name": "ms", + "type": "number", + "description": "", + "optional": false + } + ], + "returns": { + "type": "string", + "description": "" + }, + "examples": [] + }, { "name": "getInterrupt", "kind": "function", - "description": "Retrieves the current interrupt value from an Agent, or undefined when\nthe runtime does not expose interrupts.\nExported for unit testing without DOM rendering.", + "description": "", "signature": "getInterrupt(agent: Agent): AgentInterrupt | undefined", "params": [ { @@ -3105,7 +4741,7 @@ { "name": "messageContent", "kind": "function", - "description": "Extracts a human-readable string from a message's content.\nHandles string content directly; serializes structured (array) content to JSON.", + "description": "Extracts a human-readable string from a message's content.\n\n`BaseMessage.content` is `string | MessageContentComplex[]`. Reasoning-\ncapable models (OpenAI gpt-5/o-series, Anthropic) emit complex arrays of\ntyped blocks: `{type:'text',text}`, `{type:'reasoning',...}`, tool-use\nblocks, etc. We render only the visible text portions and skip anything\nelse. Stringifying the whole array would dump raw JSON like\n`[{\"type\":\"text\",...}]` into the chat bubble.", "signature": "messageContent(message: BaseMessage<>): string", "params": [ { @@ -3204,51 +4840,20 @@ "examples": [] }, { - "name": "runAgentConformance", + "name": "statusColor", "kind": "function", - "description": "Runs a suite of contract conformance assertions against a factory that\nproduces a fresh Agent. Adapter packages should call this in their\nown test suites to verify the contract is satisfied.", - "signature": "runAgentConformance(label: string, factory: object): void", + "description": "Returns a CSS style string for a subagent's status badge.\nKept exported for backward compatibility with existing consumers; the\npreferred way to style status visually is via the `data-status` attribute\n+ CSS selectors (see component styles below).", + "signature": "statusColor(status: SubagentStatus): string", "params": [ { - "name": "label", - "type": "string", - "description": "", - "optional": false - }, - { - "name": "factory", - "type": "object", - "description": "", - "optional": false - } - ], - "returns": { - "type": "void", - "description": "" - }, - "examples": [] - }, - { - "name": "runAgentWithHistoryConformance", - "kind": "function", - "description": "Conformance suite for AgentWithHistory implementations.\n\nRuns the base Agent conformance suite, then verifies the history\nsignal is present and returns an array of AgentCheckpoint-shaped entries.", - "signature": "runAgentWithHistoryConformance(label: string, factory: object): void", - "params": [ - { - "name": "label", - "type": "string", - "description": "", - "optional": false - }, - { - "name": "factory", - "type": "object", + "name": "status", + "type": "SubagentStatus", "description": "", "optional": false } ], "returns": { - "type": "void", + "type": "string", "description": "" }, "examples": [] @@ -3256,7 +4861,7 @@ { "name": "submitMessage", "kind": "function", - "description": "", + "description": "Submits a trimmed message to the agent.\nReturns the trimmed string on success, or `null` if the input was empty.", "signature": "submitMessage(agent: Agent, text: string): string | null", "params": [ { diff --git a/libs/chat/src/lib/primitives/chat-message-actions/chat-message-actions.component.ts b/libs/chat/src/lib/primitives/chat-message-actions/chat-message-actions.component.ts index 92a6e92bc..1251699bf 100644 --- a/libs/chat/src/lib/primitives/chat-message-actions/chat-message-actions.component.ts +++ b/libs/chat/src/lib/primitives/chat-message-actions/chat-message-actions.component.ts @@ -91,7 +91,7 @@ export class ChatMessageActionsComponent { /** When true, the regenerate button is disabled (e.g. while the agent is streaming). */ readonly disabled = input(false); - /** Emitted when the user clicks regenerate. Wire this to `agent.reload()`. */ + /** Emitted when the user clicks regenerate. Wire this to `agent.regenerate(index)`. */ readonly regenerate = output(); /** Emitted with 'up' or 'down' when the user rates the response. */ readonly rate = output<'up' | 'down'>(); diff --git a/libs/langgraph/src/lib/agent.fn.ts b/libs/langgraph/src/lib/agent.fn.ts index ced82682d..b173cb447 100644 --- a/libs/langgraph/src/lib/agent.fn.ts +++ b/libs/langgraph/src/lib/agent.fn.ts @@ -63,16 +63,17 @@ import { buildBranchTree } from './internals/branch-tree'; import { extractCitations } from './internals/extract-citations'; /** - * Creates a streaming resource connected to a LangGraph agent. + * Creates a LangGraph-backed Angular agent. * * Must be called within an Angular injection context (component constructor, * field initializer, or `runInInjectionContext`). Returns a unified * {@link LangGraphAgent} whose properties are Angular Signals that update - * in real-time as the agent streams. + * in real time as LangGraph streams messages, values, tool calls, interrupts, + * subagent state, and checkpoint history. * * @typeParam T - The state shape returned by the agent (e.g., `{ messages: BaseMessage[] }`) * @typeParam Bag - Optional bag template for typed interrupts and submit payloads - * @param options - Configuration for the streaming resource + * @param options - Configuration for the LangGraph agent * @returns A {@link LangGraphAgent} with reactive signals and action methods * * @example @@ -328,7 +329,7 @@ export function agent< langGraphHistory: historySig, experimentalBranchTree, - // ── Other AgentRef fields preserved ────────────────────────────────── + // ── Other LangGraph-specific fields ────────────────────────────────── value: value as Signal, hasValue: hasValueSig, reload: () => manager.resubmitLast(), diff --git a/libs/langgraph/src/lib/agent.types.ts b/libs/langgraph/src/lib/agent.types.ts index 47e106d39..b8544d59f 100644 --- a/libs/langgraph/src/lib/agent.types.ts +++ b/libs/langgraph/src/lib/agent.types.ts @@ -222,8 +222,11 @@ export interface AgentTransport { // ── Options ────────────────────────────────────────────────────────────────── -/** Options for creating a streaming resource via {@link agent}. */ -export interface AgentOptions { +/** Options for creating a LangGraph-backed agent via {@link agent}. */ +// The second generic is retained for source compatibility with existing typed +// AgentOptions references even though the options shape no longer depends on it. +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export interface AgentOptions { /** Base URL of the LangGraph Platform API. */ apiUrl: string; /** Agent or graph identifier on the LangGraph platform. */ @@ -234,8 +237,6 @@ export interface AgentOptions { onThreadId?: (id: string) => void; /** Initial state values before the first stream response arrives. */ initialValues?: Partial; - /** Key in the state object that contains the messages array. Defaults to `'messages'`. */ - messagesKey?: string; /** Throttle signal updates in milliseconds. `false` to disable. */ throttle?: number | false; /** Custom message deserializer for non-standard message formats. */ @@ -276,7 +277,7 @@ export interface SubagentStreamRef { */ export interface LangGraphAgent extends AgentWithHistory { - // ── Raw LangGraph signals (preserve full AgentRef public surface) ───────── + // ── Raw LangGraph signals ──────────────────────────────────────────────── /** Raw LangChain BaseMessage list. Use `messages` for chat rendering. */ langGraphMessages: Signal; @@ -299,7 +300,7 @@ export interface LangGraphAgent Promise; - // ── AgentRef fields preserved on the unified surface ───────────────────── + // ── LangGraph-specific fields preserved on the unified surface ─────────── /** Current agent state values (raw, typed per the type parameter T). */ value: Signal;