From 1e89d844942c6c974d19d0d696ccc35dce6c8340 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 7 Sep 2026 13:42:56 -0700 Subject: [PATCH 1/4] feat(chat)!: remove provideChat(), ChatConfig, and CHAT_CONFIG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No component in @threadplane/chat ever injected CHAT_CONFIG, so provideChat({}) configured nothing: renderRegistry, avatarLabel, and assistantName were values only a consumer's own wrappers could read back. The provider, the token, the interface, and the dead ChatConfig duplicate in chat.types.ts are deleted outright. Every call site follows: 35 cockpit example app.config.ts files, both standalone example apps, the chat and langgraph READMEs, the @threadplane /render JSDoc that referenced it, the docs pages that described it, and the blog tutorials whose bootstrap snippets would otherwise no longer compile. The three pages that documented the API — chat/api/provide-chat, chat/api/chat-config, and chat/guides/configuration — are deleted, dropped from the docs nav, added to the retired-route pattern the public-copy contract enforces, and permanently redirected (with their /api/markdown mirrors) to chat installation so delivered links do not 404. Co-Authored-By: Claude Fable 5.1 --- ...ming-chat-ui-in-angular-with-langgraph.mdx | 4 +- ...stack-agentic-angular-apps-using-ag-ui.mdx | 6 +- ...n-the-loop-langgraph-agents-in-angular.mdx | 2 - ...an-in-the-loop-ag-ui-agents-in-angular.mdx | 2 - ...strands-agent-ui-in-angular-with-ag-ui.mdx | 2 - ...3-angular-chat-app-tutorial-with-ag-ui.mdx | 2 - ...-app-tutorial-with-langchain-langgraph.mdx | 2 - .../a2ui/getting-started/introduction.mdx | 1 - .../docs/ag-ui/guides/client-tools.mdx | 2 +- .../content/docs/ag-ui/guides/interrupts.mdx | 2 +- .../content/docs/ag-ui/guides/json-render.mdx | 1 - .../content/docs/ag-ui/guides/subagents.mdx | 2 +- .../content/docs/ag-ui/guides/tool-views.mdx | 2 +- .../docs/ag-ui/reference/event-mapping.mdx | 2 +- .../content/docs/chat/a2ui/overview.mdx | 1 - .../content/docs/chat/api/chat-config.mdx | 125 ------------- .../content/docs/chat/api/provide-chat.mdx | 173 ------------------ .../docs/chat/components/chat-debug.mdx | 3 +- .../docs/chat/components/chat-input.mdx | 2 +- .../chat/components/chat-interrupt-panel.mdx | 2 +- .../chat/components/chat-subagent-card.mdx | 2 +- .../docs/chat/components/chat-tool-calls.mdx | 2 +- .../docs/chat/components/chat-trace.mdx | 2 +- .../docs/chat/concepts/message-model.mdx | 2 +- .../chat/getting-started/installation.mdx | 15 +- .../docs/chat/getting-started/quickstart.mdx | 2 +- .../content/docs/chat/guides/client-tools.mdx | 2 +- .../docs/chat/guides/configuration.mdx | 137 -------------- .../docs/chat/guides/generative-ui.mdx | 1 - .../content/docs/chat/guides/theming.mdx | 2 +- .../docs/chat/guides/thread-routing.mdx | 2 +- .../docs/chat/guides/writing-an-adapter.mdx | 6 +- .../deep-agents/capabilities/filesystem.mdx | 2 +- .../docs/deep-agents/capabilities/memory.mdx | 2 +- .../deep-agents/capabilities/planning.mdx | 2 +- .../docs/deep-agents/capabilities/skills.mdx | 2 +- .../deep-agents/capabilities/subagents.mdx | 2 +- .../getting-started/introduction.mdx | 2 - .../docs/langgraph/guides/deployment.mdx | 3 +- .../langgraph/guides/durable-execution.mdx | 2 +- .../docs/langgraph/guides/interrupts.mdx | 2 +- .../content/docs/langgraph/guides/memory.mdx | 2 +- .../docs/langgraph/guides/persistence.mdx | 2 +- .../docs/langgraph/guides/streaming.mdx | 3 +- .../docs/langgraph/guides/subgraphs.mdx | 3 +- .../docs/runtimes/aws-strands/overview.mdx | 2 +- .../runtimes/getting-started/introduction.mdx | 2 - .../content/docs/runtimes/mastra/overview.mdx | 2 +- .../microsoft-agent-framework/overview.mdx | 2 +- apps/website/next.config.spec.ts | 41 +++++ apps/website/next.config.ts | 34 ++++ apps/website/src/app/chat/page.tsx | 5 +- .../src/components/docs/mdx/headings.spec.tsx | 6 +- apps/website/src/lib/docs-config.ts | 3 - apps/website/src/lib/public-copy-contract.ts | 11 +- .../ag-ui/a2ui/angular/src/app/app.config.ts | 2 - .../angular/src/app/app.config.ts | 2 - .../interrupts/angular/src/app/app.config.ts | 2 - .../json-render/angular/src/app/app.config.ts | 2 - .../streaming/angular/src/app/app.config.ts | 2 - .../subagents/angular/src/app/app.config.ts | 2 - .../tool-views/angular/src/app/app.config.ts | 2 - .../chat/a2ui/angular/src/app/app.config.ts | 2 - .../chat/debug/angular/src/app/app.config.ts | 2 - .../angular/src/app/app.config.ts | 2 - .../chat/input/angular/src/app/app.config.ts | 2 - .../interrupts/angular/src/app/app.config.ts | 2 - .../messages/angular/src/app/app.config.ts | 2 - .../subagents/angular/src/app/app.config.ts | 2 - .../theming/angular/src/app/app.config.ts | 2 - .../threads/angular/src/app/app.config.ts | 2 - .../timeline/angular/src/app/app.config.ts | 2 - .../tool-calls/angular/src/app/app.config.ts | 2 - .../filesystem/angular/src/app/app.config.ts | 2 - .../memory/angular/src/app/app.config.ts | 2 - .../planning/angular/src/app/app.config.ts | 2 - .../skills/angular/src/app/app.config.ts | 2 - .../subagents/angular/src/app/app.config.ts | 2 - .../angular/src/app/app.config.ts | 2 - .../angular/src/app/app.config.ts | 2 - .../angular/src/app/app.config.ts | 2 - .../interrupts/angular/src/app/app.config.ts | 2 - .../memory/angular/src/app/app.config.ts | 2 - .../persistence/angular/src/app/app.config.ts | 2 - .../streaming/angular/src/app/app.config.ts | 2 - .../subgraphs/angular/src/app/app.config.ts | 2 - .../time-travel/angular/src/app/app.config.ts | 2 - .../aws-strands/angular/src/app/app.config.ts | 2 - .../mastra/angular/src/app/app.config.ts | 2 - .../angular/src/app/app.config.ts | 2 - examples/ag-ui/angular/src/app/app.config.ts | 2 - examples/chat/angular/src/app/app.config.ts | 2 - libs/chat/CHANGELOG.md | 4 + libs/chat/README.md | 2 - libs/chat/src/lib/chat.types.ts | 6 - libs/chat/src/lib/client-tools/tools.ts | 2 +- libs/chat/src/lib/provide-chat.spec.ts | 40 ---- libs/chat/src/lib/provide-chat.ts | 61 ------ libs/chat/src/public-api.ts | 6 +- libs/langgraph/README.md | 2 +- .../render/src/lib/define-angular-registry.ts | 5 +- libs/render/src/lib/provide-render.ts | 6 +- libs/render/src/lib/provide-views.ts | 2 +- 103 files changed, 144 insertions(+), 716 deletions(-) delete mode 100644 apps/website/content/docs/chat/api/chat-config.mdx delete mode 100644 apps/website/content/docs/chat/api/provide-chat.mdx delete mode 100644 apps/website/content/docs/chat/guides/configuration.mdx delete mode 100644 libs/chat/src/lib/provide-chat.spec.ts delete mode 100644 libs/chat/src/lib/provide-chat.ts diff --git a/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx b/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx index 1ccd2d0f6..1c22ccbb4 100644 --- a/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx +++ b/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx @@ -95,17 +95,15 @@ yarn add @threadplane/chat @threadplane/langgraph marked // app.config.ts import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ provideAgent({ apiUrl: 'http://localhost:2024' }), - provideChat({ assistantName: 'Assistant' }), ], }; ``` -`provideAgent` is the transport. `provideChat` is the UI configuration. They are independent on purpose — you can use one without the other. +`provideAgent` is the transport, and it is the only provider the chat components need. The UI is configured entirely through component inputs, so the two layers stay independent on purpose. diff --git a/apps/website/content/blog/2026-05-21-build-fullstack-agentic-angular-apps-using-ag-ui.mdx b/apps/website/content/blog/2026-05-21-build-fullstack-agentic-angular-apps-using-ag-ui.mdx index cf25cdef0..bd58a65ee 100644 --- a/apps/website/content/blog/2026-05-21-build-fullstack-agentic-angular-apps-using-ag-ui.mdx +++ b/apps/website/content/blog/2026-05-21-build-fullstack-agentic-angular-apps-using-ag-ui.mdx @@ -115,21 +115,19 @@ It is a peer dep so you can swap it. // app.config.ts import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ provideAgent({ url: 'http://localhost:8000/agent' }), - provideChat({ assistantName: 'Astra' }), ], }; ``` That is the whole bootstrap. `provideAgent` is the AG-UI transport. It wraps the official `@ag-ui/client` `HttpAgent` and exposes the signal-shaped contract via DI. -`provideChat` is the chat UI's configuration. +It is the only provider the chat components need. -Notice they are independent. +Notice how little the two layers know about each other. `@threadplane/chat` does not know it is talking to an AG-UI backend. It just reads from the `Agent` contract. We will lean on that boundary later. diff --git a/apps/website/content/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular.mdx b/apps/website/content/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular.mdx index 8670f6186..d0cf923d8 100644 --- a/apps/website/content/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular.mdx +++ b/apps/website/content/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular.mdx @@ -117,12 +117,10 @@ That is why `request_approval` can branch on `decision["approved"]` and pick up ```ts // app.config.ts import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ provideAgent({ apiUrl: environment.langGraphApiUrl }), - provideChat({}), ], }; ``` diff --git a/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx b/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx index aede30758..094ff645a 100644 --- a/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx +++ b/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx @@ -205,12 +205,10 @@ Two details worth knowing: // app.config.ts — cockpit/ag-ui/interrupts/angular/src/app/app.config.ts import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ provideAgent({ url: '/agent' }), - provideChat({}), ], }; ``` diff --git a/apps/website/content/blog/2026-08-09-build-an-aws-strands-agent-ui-in-angular-with-ag-ui.mdx b/apps/website/content/blog/2026-08-09-build-an-aws-strands-agent-ui-in-angular-with-ag-ui.mdx index 722305668..918d625b9 100644 --- a/apps/website/content/blog/2026-08-09-build-an-aws-strands-agent-ui-in-angular-with-ag-ui.mdx +++ b/apps/website/content/blog/2026-08-09-build-an-aws-strands-agent-ui-in-angular-with-ag-ui.mdx @@ -230,14 +230,12 @@ Wire both packages into `app.config.ts`: ```ts import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ provideAgent({ url: 'http://localhost:8080/invocations', }), - provideChat({ assistantName: 'Strands Assistant' }), ], }; ``` diff --git a/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-ag-ui.mdx b/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-ag-ui.mdx index bc3599ba1..c337ccb90 100644 --- a/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-ag-ui.mdx +++ b/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-ag-ui.mdx @@ -167,14 +167,12 @@ npm install @threadplane/chat @threadplane/ag-ui @ag-ui/client @ag-ui/core marke The provider is one line, because AG-UI's connection surface is one URL: ```ts -import { provideChat } from '@threadplane/chat'; import { provideAgent } from '@threadplane/ag-ui'; export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), provideAgent({ url: 'http://localhost:8000/agent' }), - provideChat({ assistantName: 'Librarian' }), ], }; ``` diff --git a/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-langchain-langgraph.mdx b/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-langchain-langgraph.mdx index 6f438d60c..70fc0c8c2 100644 --- a/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-langchain-langgraph.mdx +++ b/apps/website/content/blog/2026-08-13-angular-chat-app-tutorial-with-langchain-langgraph.mdx @@ -173,7 +173,6 @@ import { signal, } from '@angular/core'; import { provideRouter } from '@angular/router'; -import { provideChat } from '@threadplane/chat'; import { LANGGRAPH_THREADS_CONFIG, provideAgent } from '@threadplane/langgraph'; import { routes } from './app.routes'; @@ -195,7 +194,6 @@ export const appConfig: ApplicationConfig = { onThreadId: (id) => ACTIVE_THREAD.set(id), }), { provide: LANGGRAPH_THREADS_CONFIG, useValue: { apiUrl: API_URL } }, - provideChat({ assistantName: 'Assistant' }), ], }; ``` diff --git a/apps/website/content/docs/a2ui/getting-started/introduction.mdx b/apps/website/content/docs/a2ui/getting-started/introduction.mdx index e8bb26efa..eb539add7 100644 --- a/apps/website/content/docs/a2ui/getting-started/introduction.mdx +++ b/apps/website/content/docs/a2ui/getting-started/introduction.mdx @@ -94,7 +94,6 @@ Nothing in this file is specific to A2UI; the surfaces travel as assistant messa -`provideChat({})` registers the chat composition defaults alongside it. ### Giving the chat composition a catalog diff --git a/apps/website/content/docs/ag-ui/guides/client-tools.mdx b/apps/website/content/docs/ag-ui/guides/client-tools.mdx index 78ff28671..a23e61d59 100644 --- a/apps/website/content/docs/ag-ui/guides/client-tools.mdx +++ b/apps/website/content/docs/ag-ui/guides/client-tools.mdx @@ -36,7 +36,7 @@ The server is a FastAPI application. `LangGraphAgent` wraps the compiled graph a ### Providing the agent -`provideAgent()` from `@threadplane/ag-ui` registers the agent at the application root, and `provideChat({})` registers the chat defaults. The example resolves its URL at runtime because the host that serves the demo decides which runtime is attached; your own application passes a `url` directly. +`provideAgent()` from `@threadplane/ag-ui` registers the agent at the application root, and it is the only provider the `` composition requires. The example resolves its URL at runtime because the host that serves the demo decides which runtime is attached; your own application passes a `url` directly. diff --git a/apps/website/content/docs/ag-ui/guides/interrupts.mdx b/apps/website/content/docs/ag-ui/guides/interrupts.mdx index 1e60f4269..9e42ebef8 100644 --- a/apps/website/content/docs/ag-ui/guides/interrupts.mdx +++ b/apps/website/content/docs/ag-ui/guides/interrupts.mdx @@ -72,7 +72,7 @@ That wrapper is what turns a LangGraph pause into the AG-UI event described belo ### The agent provider -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. +`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. diff --git a/apps/website/content/docs/ag-ui/guides/json-render.mdx b/apps/website/content/docs/ag-ui/guides/json-render.mdx index 6f188a2aa..2e253f855 100644 --- a/apps/website/content/docs/ag-ui/guides/json-render.mdx +++ b/apps/website/content/docs/ag-ui/guides/json-render.mdx @@ -90,7 +90,6 @@ Nothing in this file is specific to generative UI. -`provideChat({})` registers the chat composition defaults alongside it. ### The view registry and the shared store diff --git a/apps/website/content/docs/ag-ui/guides/subagents.mdx b/apps/website/content/docs/ag-ui/guides/subagents.mdx index fad6b96ea..cba5ef0c2 100644 --- a/apps/website/content/docs/ag-ui/guides/subagents.mdx +++ b/apps/website/content/docs/ag-ui/guides/subagents.mdx @@ -74,7 +74,7 @@ The AG-UI encoder serializes pydantic `ag_ui.core` event classes, so the subclas ### The agent provider -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. Nothing in either provider is subagent-specific. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. Your own application does not need the factory. +`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. Nothing about it is subagent-specific. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. Your own application does not need the factory. diff --git a/apps/website/content/docs/ag-ui/guides/tool-views.mdx b/apps/website/content/docs/ag-ui/guides/tool-views.mdx index 63a71959a..48033016a 100644 --- a/apps/website/content/docs/ag-ui/guides/tool-views.mdx +++ b/apps/website/content/docs/ag-ui/guides/tool-views.mdx @@ -42,7 +42,7 @@ The server is a FastAPI application. `add_langgraph_fastapi_endpoint` mounts the ### Providing the agent -`provideAgent()` from `@threadplane/ag-ui` registers the agent at the application root, and `provideChat({})` registers the chat defaults. The example resolves its URL at runtime because the host that serves the demo decides which runtime is attached; your own application passes a `url` directly. +`provideAgent()` from `@threadplane/ag-ui` registers the agent at the application root, and it is the only provider the `` composition requires. The example resolves its URL at runtime because the host that serves the demo decides which runtime is attached; your own application passes a `url` directly. diff --git a/apps/website/content/docs/ag-ui/reference/event-mapping.mdx b/apps/website/content/docs/ag-ui/reference/event-mapping.mdx index 1cfe9be31..b93961682 100644 --- a/apps/website/content/docs/ag-ui/reference/event-mapping.mdx +++ b/apps/website/content/docs/ag-ui/reference/event-mapping.mdx @@ -38,7 +38,7 @@ That wrapper is the piece that translates LangGraph's own stream into the protoc ### Providing the agent -`provideAgent()` from `@threadplane/ag-ui` registers the agent once for the whole application, and `provideChat({})` registers the chat configuration, here left at its defaults. Your own application passes `{ url: 'https://your-backend.example.com/agent' }` directly; this example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. +`provideAgent()` from `@threadplane/ag-ui` registers the agent once for the whole application, and it is the only provider the `` composition requires. Your own application passes `{ url: 'https://your-backend.example.com/agent' }` directly; this example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. diff --git a/apps/website/content/docs/chat/a2ui/overview.mdx b/apps/website/content/docs/chat/a2ui/overview.mdx index ee98a4fc7..f5823a507 100644 --- a/apps/website/content/docs/chat/a2ui/overview.mdx +++ b/apps/website/content/docs/chat/a2ui/overview.mdx @@ -86,7 +86,6 @@ The graph is served by the LangGraph API server, which supplies persistence, so -`provideChat({})` registers the chat composition defaults alongside it. ### Giving the chat composition a catalog diff --git a/apps/website/content/docs/chat/api/chat-config.mdx b/apps/website/content/docs/chat/api/chat-config.mdx deleted file mode 100644 index 0e8c63559..000000000 --- a/apps/website/content/docs/chat/api/chat-config.mdx +++ /dev/null @@ -1,125 +0,0 @@ ---- -description: The ChatConfig interface accepted by provideChat(), its three optional fields, and how to read the CHAT_CONFIG token from your own components. ---- - -# ChatConfig - -`ChatConfig` is the configuration interface accepted by `provideChat()`. The object is stored under `CHAT_CONFIG` for application code that wants shared chat defaults. - -No library component reads `CHAT_CONFIG` today. Every field below is a value your own components inject and apply; the shipped chat components ignore the token entirely. - -**Import:** - -```typescript -import type { ChatConfig } from '@threadplane/chat'; -import type { AngularRegistry } from '@threadplane/render'; -``` - -## Interface Definition - -```typescript -interface ChatConfig { - /** Shared render registry for consumers that read CHAT_CONFIG. */ - renderRegistry?: AngularRegistry; - /** Shared AI avatar label for consumers that read CHAT_CONFIG (default: "A"). */ - avatarLabel?: string; - /** Shared assistant display name for consumers that read CHAT_CONFIG (default: "Assistant"). */ - assistantName?: string; -} -``` - -## Properties - -### avatarLabel - -```typescript -avatarLabel?: string -``` - -A short string (typically one or two characters) for wrappers or components that choose to read `CHAT_CONFIG`. - -**Default:** `"A"` - -**Example:** - -```typescript -provideChat({ avatarLabel: 'AI' }); -``` - -There is no avatar-specific CSS token. Use the shared `--tplane-chat-*` tokens such as `--tplane-chat-surface`, `--tplane-chat-text`, and `--tplane-chat-text-muted` to align chat surfaces with your app theme. - -### assistantName - -```typescript -assistantName?: string -``` - -The display name for wrappers or components that choose to read `CHAT_CONFIG`. - -**Default:** `"Assistant"` - -**Example:** - -```typescript -provideChat({ assistantName: 'Code Copilot' }); -``` - -### renderRegistry - -```typescript -renderRegistry?: AngularRegistry -``` - -A shared render registry value for consumers that inject `CHAT_CONFIG`. `ChatComponent` does not read this value directly; pass a `ViewRegistry` to the `` input for built-in generative UI rendering. - -**Example:** - -```typescript -provideChat({ renderRegistry }); -``` - -## Accessing ChatConfig at Runtime - -Inject `CHAT_CONFIG` to read configuration values in your own components: - -```typescript -import { inject } from '@angular/core'; -import { CHAT_CONFIG } from '@threadplane/chat'; -import type { ChatConfig } from '@threadplane/chat'; - -@Component({ - selector: 'app-chat-header', - template: ` -

{{ assistantName }}

- `, -}) -export class ChatHeaderComponent { - private config = inject(CHAT_CONFIG, { optional: true }); - - get assistantName(): string { - return this.config?.assistantName ?? 'Assistant'; - } -} -``` - -## Gotcha: Inputs Still Win - -`provideChat()` is not a replacement for component inputs. For generative UI, configure the chat surface directly: - -```html - -``` - -Use `CHAT_CONFIG` when you are building your own wrappers or want route-level defaults that your code reads explicitly. - -## Type Location - -The canonical `ChatConfig` interface is defined alongside `provideChat()`: - -- `libs/chat/src/lib/provide-chat.ts` -- The canonical definition with JSDoc comments, alongside the `provideChat()` function and `CHAT_CONFIG` token - -The public API exports `ChatConfig` as a type-only export: - -```typescript -export type { ChatConfig } from './lib/provide-chat'; -``` diff --git a/apps/website/content/docs/chat/api/provide-chat.mdx b/apps/website/content/docs/chat/api/provide-chat.mdx deleted file mode 100644 index 69929ba64..000000000 --- a/apps/website/content/docs/chat/api/provide-chat.mdx +++ /dev/null @@ -1,173 +0,0 @@ ---- -description: How provideChat() registers the CHAT_CONFIG token, what ChatConfig carries, and why the values are for components you write yourself. ---- - -# provideChat() - -`provideChat` is the provider factory that registers `@threadplane/chat` configuration in Angular's dependency injection system. Call it in your `ApplicationConfig` or at the route level when you need a shared `CHAT_CONFIG` value. - -```typescript -import { provideChat } from '@threadplane/chat'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideChat({ - avatarLabel: 'AI', - assistantName: 'My Assistant', - }), - ], -}; -``` - -## Signature - -```typescript -function provideChat(config: ChatConfig): EnvironmentProviders -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| `config` | `ChatConfig` | Configuration object with optional render registry, avatar label, and assistant name | - -**Returns:** `EnvironmentProviders` -- created via `makeEnvironmentProviders()`, compatible with `bootstrapApplication`, `ApplicationConfig`, and route-level `providers`. - -## What It Does - -`provideChat()` registers a single provider: - -```typescript -{ provide: CHAT_CONFIG, useValue: config } -``` - -This makes the `ChatConfig` object available throughout the application via the `CHAT_CONFIG` injection token. `provideChat()` does not automatically wire generative UI into ``; pass `[views]`, `[store]`, and `[handlers]` directly to `ChatComponent`. - -## CHAT_CONFIG Injection Token - -```typescript -import { CHAT_CONFIG } from '@threadplane/chat'; - -const CHAT_CONFIG: InjectionToken; -``` - -The token is an `InjectionToken` that can be injected in any component, directive, or service: - -```typescript -import { inject } from '@angular/core'; -import { CHAT_CONFIG } from '@threadplane/chat'; - -@Component({ /* ... */ }) -export class MyComponent { - private chatConfig = inject(CHAT_CONFIG); -} -``` - - -Injecting `CHAT_CONFIG` without calling `provideChat()` will throw a `NullInjectorError`. Use `inject(CHAT_CONFIG, { optional: true })` if your component should work without global configuration. - - -## Configuration Options - -See the [ChatConfig API reference](/docs/chat/api/chat-config) for the full interface definition. - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `renderRegistry` | `AngularRegistry` | `undefined` | Stored on `CHAT_CONFIG` for consumers that want a shared render registry. `` uses the `[views]` input directly. | -| `avatarLabel` | `string` | `"A"` | Shared avatar label for consumers that inject `CHAT_CONFIG` | -| `assistantName` | `string` | `"Assistant"` | Shared assistant display name for consumers that inject `CHAT_CONFIG` | - -## Usage Patterns - -### Application-Wide Configuration - -```typescript -// app.config.ts -import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideAgent({ apiUrl: 'http://localhost:2024', assistantId: 'chat' }), - provideChat({ - avatarLabel: 'B', - assistantName: 'Bot', - }), - ], -}; -``` - -### Route-Level Configuration - -Provide different chat configurations for different parts of your application: - -```typescript -// app.routes.ts -export const routes: Routes = [ - { - path: 'support', - loadComponent: () => import('./support/support-chat.component'), - providers: [ - provideChat({ - assistantName: 'Support Agent', - avatarLabel: 'S', - }), - ], - }, - { - path: 'coding', - loadComponent: () => import('./coding/code-chat.component'), - providers: [ - provideChat({ - assistantName: 'Code Helper', - avatarLabel: 'C', - }), - ], - }, -]; -``` - -### Without provideChat() - -All chat components work without `provideChat()`. No library component reads `CHAT_CONFIG` today, so the `avatarLabel` and `assistantName` values are conventions for wrapper components you write yourself rather than settings the shipped components consume. Generative UI still requires the `[views]` input on `ChatComponent`. - -```typescript -// This works fine without provideChat() -import { injectAgent, provideAgent } from '@threadplane/langgraph'; - -@Component({ - imports: [ChatComponent], - providers: [ - provideAgent({ - apiUrl: 'http://localhost:2024', - assistantId: 'chat', - threadId: signal(null), - }), - ], - template: ``, -}) -export class SimpleChatComponent { - chatRef = injectAgent(); -} -``` - -## What's Next - - - - Full ChatConfig interface reference. - - - Set up view registries for dynamic UI components. - - - Configuration patterns and best practices. - - diff --git a/apps/website/content/docs/chat/components/chat-debug.mdx b/apps/website/content/docs/chat/components/chat-debug.mdx index bde27f0c9..ca357cbfd 100644 --- a/apps/website/content/docs/chat/components/chat-debug.mdx +++ b/apps/website/content/docs/chat/components/chat-debug.mdx @@ -44,7 +44,7 @@ The last line calls `compile()` with no checkpointer. The checkpoints the Timeli ### The agent provider -`provideAgent()` registers the agent once for the whole application. The example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them, and it sits alongside `provideChat({})`, which supplies the chat library's own providers. +`provideAgent()` registers the agent once for the whole application, and it is the only provider the chat compositions require. The example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. @@ -55,7 +55,6 @@ provideAgent({ apiUrl: 'https://your-deployment.langgraph.app', assistantId: 'debug', }), -provideChat({}), ``` ### Mounting the panel diff --git a/apps/website/content/docs/chat/components/chat-input.mdx b/apps/website/content/docs/chat/components/chat-input.mdx index a91ec21ef..0219698e6 100644 --- a/apps/website/content/docs/chat/components/chat-input.mdx +++ b/apps/website/content/docs/chat/components/chat-input.mdx @@ -26,7 +26,7 @@ The compiled graph is exported as `graph`, which is the symbol `langgraph.json` ### The application configuration -`provideAgent()` registers the agent for the whole application. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. It sits beside `provideChat({})`, which registers the chat library's own providers. +`provideAgent()` registers the agent for the whole application, and it is the only provider the chat components require. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. diff --git a/apps/website/content/docs/chat/components/chat-interrupt-panel.mdx b/apps/website/content/docs/chat/components/chat-interrupt-panel.mdx index 9c90a5df6..0ca85673b 100644 --- a/apps/website/content/docs/chat/components/chat-interrupt-panel.mdx +++ b/apps/website/content/docs/chat/components/chat-interrupt-panel.mdx @@ -42,7 +42,7 @@ The graph is an ordinary agent and tool loop. `book_flight` is registered next t ### The agent provider -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. +`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. diff --git a/apps/website/content/docs/chat/components/chat-subagent-card.mdx b/apps/website/content/docs/chat/components/chat-subagent-card.mdx index f2572d9da..5e30c77e7 100644 --- a/apps/website/content/docs/chat/components/chat-subagent-card.mdx +++ b/apps/website/content/docs/chat/components/chat-subagent-card.mdx @@ -62,7 +62,7 @@ The system prompt tells the orchestrator to dispatch research, then booking, the ### Telling the adapter that task means delegation -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the chat configuration at its defaults. The one subagent-specific line is `subagentToolNames`. The default is already `['task']`, so this example states explicitly what it would otherwise inherit. +`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. The one subagent-specific line is `subagentToolNames`. The default is already `['task']`, so this example states explicitly what it would otherwise inherit. The example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them; your own application passes `apiUrl` and `assistantId` directly. diff --git a/apps/website/content/docs/chat/components/chat-tool-calls.mdx b/apps/website/content/docs/chat/components/chat-tool-calls.mdx index b84a95074..ebd50483c 100644 --- a/apps/website/content/docs/chat/components/chat-tool-calls.mdx +++ b/apps/website/content/docs/chat/components/chat-tool-calls.mdx @@ -38,7 +38,7 @@ The names bound here are the names the component groups by and labels its cards ### The agent provider -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the chat library's configuration, here left at its defaults. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. Your own application does not need the factory. +`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. Your own application does not need the factory. diff --git a/apps/website/content/docs/chat/components/chat-trace.mdx b/apps/website/content/docs/chat/components/chat-trace.mdx index 16f891d48..78b5b50dd 100644 --- a/apps/website/content/docs/chat/components/chat-trace.mdx +++ b/apps/website/content/docs/chat/components/chat-trace.mdx @@ -36,7 +36,7 @@ The compiled graph is exported as `graph`, which is the symbol `langgraph.json` ### The agent and the chat configuration -`provideAgent()` registers the agent for the whole application. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. `provideChat({})` registers the chat library's own configuration at its defaults. +`provideAgent()` registers the agent for the whole application, and it is the only provider the chat compositions require. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. diff --git a/apps/website/content/docs/chat/concepts/message-model.mdx b/apps/website/content/docs/chat/concepts/message-model.mdx index 4edf14e5e..22feed25d 100644 --- a/apps/website/content/docs/chat/concepts/message-model.mdx +++ b/apps/website/content/docs/chat/concepts/message-model.mdx @@ -28,7 +28,7 @@ Every message the browser renders begins as an entry in that returned list. ### The application configuration -`provideAgent()` registers the agent for the whole application, keyed by a typed ref. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. It sits beside `provideChat({})`, which registers the chat library's own providers. +`provideAgent()` registers the agent for the whole application, keyed by a typed ref, and it is the only provider the chat components require. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. diff --git a/apps/website/content/docs/chat/getting-started/installation.mdx b/apps/website/content/docs/chat/getting-started/installation.mdx index 88f6976c7..425d21137 100644 --- a/apps/website/content/docs/chat/getting-started/installation.mdx +++ b/apps/website/content/docs/chat/getting-started/installation.mdx @@ -89,16 +89,9 @@ export const appConfig: ApplicationConfig = { }; ``` -That is the only provider `` needs. - - -`provideChat()` parks a `ChatConfig` object on the `CHAT_CONFIG` injection token -for your own components to read. No component in `@threadplane/chat` injects that -token, so calling it changes nothing about what `` renders — including -`assistantName` and `avatarLabel`, which no built-in component displays. Call it -only when your own wrappers inject `CHAT_CONFIG`. See -[Configuration](/docs/chat/guides/configuration). - +That is the only provider `` needs. There is no separate chat provider: +everything the built-in components render is driven by their inputs, and their +appearance by the `--tplane-chat-*` custom properties. ## 3. Render your first chat @@ -131,6 +124,6 @@ carries the conformance runners and fixtures, not `mockAgent()`. ## What's next -- [Configuration](/docs/chat/guides/configuration) +- [Theming](/docs/chat/guides/theming) - [ChatComponent](/docs/chat/components/chat) - [Choosing an adapter](/docs/choosing-an-adapter) diff --git a/apps/website/content/docs/chat/getting-started/quickstart.mdx b/apps/website/content/docs/chat/getting-started/quickstart.mdx index 9fc42b073..d4c409709 100644 --- a/apps/website/content/docs/chat/getting-started/quickstart.mdx +++ b/apps/website/content/docs/chat/getting-started/quickstart.mdx @@ -46,7 +46,7 @@ export const appConfig: ApplicationConfig = { }; ``` -`provideAgent()` is the only provider `` requires. `provideChat()` exists too, but it only parks values on the `CHAT_CONFIG` token for your own components to read — no built-in component injects it, so adding it changes nothing about what renders. +`provideAgent()` is the only provider `` requires. There is no separate chat provider: everything `` renders is driven by its inputs.
diff --git a/apps/website/content/docs/chat/guides/client-tools.mdx b/apps/website/content/docs/chat/guides/client-tools.mdx index b3f76802e..d4796fcfc 100644 --- a/apps/website/content/docs/chat/guides/client-tools.mdx +++ b/apps/website/content/docs/chat/guides/client-tools.mdx @@ -48,7 +48,7 @@ The AG-UI twin's sibling graph is a separate file that compiles with `MemorySave ### Providing the agent -`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and `provideChat({})` registers the chat defaults. The example resolves its `apiUrl` and `assistantId` at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them; your own application passes them directly. The `CLIENT_TOOLS_AGENT_REF` argument is a typed reference, covered under [typed agent state](#typed-agent-state) below. +`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and it is the only provider the `` composition requires. The example resolves its `apiUrl` and `assistantId` at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them; your own application passes them directly. The `CLIENT_TOOLS_AGENT_REF` argument is a typed reference, covered under [typed agent state](#typed-agent-state) below. diff --git a/apps/website/content/docs/chat/guides/configuration.mdx b/apps/website/content/docs/chat/guides/configuration.mdx deleted file mode 100644 index 5946d0c40..000000000 --- a/apps/website/content/docs/chat/guides/configuration.mdx +++ /dev/null @@ -1,137 +0,0 @@ ---- -description: What provideChat() and the CHAT_CONFIG token actually do — a carrier for your own components' shared defaults, not a way to configure the built-in chat UI. ---- - -# Configuration - -`@threadplane/chat` leans on Angular's dependency injection for shared configuration. The `provideChat()` function registers a `ChatConfig` object under the `CHAT_CONFIG` injection token. - - -No component in `@threadplane/chat` injects `CHAT_CONFIG`. `provideChat()` is a -carrier for your own components and wrappers: it stores values you can read back -with `inject(CHAT_CONFIG)`. It does not alter ``, ``, or any -other built-in component — including `assistantName` and `avatarLabel`, which no -built-in component displays. Everything `` renders is driven by its inputs. - - -## provideChat() - -Call `provideChat()` in your application's provider array when your application or wrappers need to inject shared chat configuration. - -```typescript -// app.config.ts -import { ApplicationConfig } from '@angular/core'; -import { provideChat } from '@threadplane/chat'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideChat({ - avatarLabel: 'AI', - assistantName: 'My Assistant', - }), - ], -}; -``` - -**Signature:** - -```typescript -function provideChat(config: ChatConfig): EnvironmentProviders -``` - -`provideChat()` returns `EnvironmentProviders` (via `makeEnvironmentProviders`), so it works with `bootstrapApplication`, `ApplicationConfig`, or route-level providers. - -## ChatConfig Interface - -```typescript -import type { AngularRegistry } from '@threadplane/render'; - -interface ChatConfig { - /** Default render registry for consumers that read CHAT_CONFIG. */ - renderRegistry?: AngularRegistry; - - /** Override the default AI avatar label (default: "A"). */ - avatarLabel?: string; - - /** Override the default assistant display name (default: "Assistant"). */ - assistantName?: string; - -} -``` - -### Options - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `renderRegistry` | `AngularRegistry` | `undefined` | Stored on `CHAT_CONFIG` for consumers that want a shared render registry. Pass `[views]` directly to `ChatComponent` for built-in generative UI rendering. | -| `avatarLabel` | `string` | `"A"` | Single character or short string for consumers that inject `CHAT_CONFIG`. | -| `assistantName` | `string` | `"Assistant"` | Display name for consumers that inject `CHAT_CONFIG`. | - -## CHAT_CONFIG Injection Token - -The `CHAT_CONFIG` token is an `InjectionToken` that you can inject directly in any component or service: - -```typescript -import { inject } from '@angular/core'; -import { CHAT_CONFIG } from '@threadplane/chat'; -import type { ChatConfig } from '@threadplane/chat'; - -@Component({ /* ... */ }) -export class MyComponent { - private config = inject(CHAT_CONFIG); - - get avatarText(): string { - return this.config.avatarLabel ?? 'A'; - } -} -``` - - -If `provideChat()` has not been called, injecting `CHAT_CONFIG` will throw. Use `inject(CHAT_CONFIG, { optional: true })` if your component needs to work with or without global configuration. - - -## Per-Route Configuration - -Because `provideChat()` returns `EnvironmentProviders`, you can provide different configurations at the route level: - -```typescript -// app.routes.ts -import { provideChat } from '@threadplane/chat'; - -export const routes: Routes = [ - { - path: 'support', - loadComponent: () => import('./support-chat.component'), - providers: [ - provideChat({ - assistantName: 'Support Bot', - avatarLabel: 'S', - }), - ], - }, - { - path: 'code', - loadComponent: () => import('./code-chat.component'), - providers: [ - provideChat({ - assistantName: 'Code Assistant', - avatarLabel: 'C', - renderRegistry: codeRegistry, - }), - ], - }, -]; -``` - -## Using the library without provideChat() - -Skipping `provideChat()` costs nothing. The built-in components never read -`CHAT_CONFIG`, so `ChatComponent`, `ChatInputComponent`, and the rest behave -identically with or without it. The defaults documented above (`"A"`, -`"Assistant"`) are values your own components can fall back to when they read the -token optionally — they are not applied by the library. - -Everything the built-in UI does is controlled through component inputs instead: -generative UI through `[views]`, `[store]`, and `[handlers]` on ``, and -appearance through the `--tplane-chat-*` custom properties covered in the -[Theming guide](/docs/chat/guides/theming). diff --git a/apps/website/content/docs/chat/guides/generative-ui.mdx b/apps/website/content/docs/chat/guides/generative-ui.mdx index 43a3a2d89..b8c42fe07 100644 --- a/apps/website/content/docs/chat/guides/generative-ui.mdx +++ b/apps/website/content/docs/chat/guides/generative-ui.mdx @@ -68,7 +68,6 @@ The last line calls `compile()` with no checkpointer, because this graph is serv -`provideChat({})` registers the chat composition defaults alongside it. ### The view registry and the shared store diff --git a/apps/website/content/docs/chat/guides/theming.mdx b/apps/website/content/docs/chat/guides/theming.mdx index 43864a945..7db0e5074 100644 --- a/apps/website/content/docs/chat/guides/theming.mdx +++ b/apps/website/content/docs/chat/guides/theming.mdx @@ -26,7 +26,7 @@ The compiled graph is exported as `graph`, which is the symbol `langgraph.json` ### The application configuration -`provideAgent()` registers the agent for the whole application, and this example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. It sits beside `provideChat({})`, which registers the chat library's own providers. There is no theme option on `provideChat`: theming is CSS, not configuration. +`provideAgent()` registers the agent for the whole application, and it is the only provider the chat components require. This example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. There is no theme provider at all: theming is CSS, not configuration. diff --git a/apps/website/content/docs/chat/guides/thread-routing.mdx b/apps/website/content/docs/chat/guides/thread-routing.mdx index 00ffa5c10..298ca6049 100644 --- a/apps/website/content/docs/chat/guides/thread-routing.mdx +++ b/apps/website/content/docs/chat/guides/thread-routing.mdx @@ -40,7 +40,7 @@ This graph calls `compile()` with no checkpointer, because the LangGraph API ser -`provideChat({})` is the only other root provider, because the agent itself is registered at the component instead. +Nothing else is registered at the root, because the agent itself is provided at the component instead. ### The active-thread signal diff --git a/apps/website/content/docs/chat/guides/writing-an-adapter.mdx b/apps/website/content/docs/chat/guides/writing-an-adapter.mdx index 03de66843..06ff35e71 100644 --- a/apps/website/content/docs/chat/guides/writing-an-adapter.mdx +++ b/apps/website/content/docs/chat/guides/writing-an-adapter.mdx @@ -352,10 +352,10 @@ While `@threadplane/chat` is pre-1.0, every patch release can carry a breaking c - All provideChat() options for global chat configuration. + The custom properties that control how the chat surface looks. ` composition reads, here left at its defaults. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. +`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. diff --git a/apps/website/content/docs/deep-agents/capabilities/memory.mdx b/apps/website/content/docs/deep-agents/capabilities/memory.mdx index 038ec4524..35db5416d 100644 --- a/apps/website/content/docs/deep-agents/capabilities/memory.mdx +++ b/apps/website/content/docs/deep-agents/capabilities/memory.mdx @@ -67,7 +67,7 @@ This is an application-side shim rather than a framework change: the key stays p ### Providing the agent -`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and `provideChat({})` registers the chat defaults. The example resolves its connection at runtime because the host that serves the demo decides which runtime is attached; your own application passes `apiUrl` and `assistantId` directly. +`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and it is the only provider the `` composition requires. The example resolves its connection at runtime because the host that serves the demo decides which runtime is attached; your own application passes `apiUrl` and `assistantId` directly. diff --git a/apps/website/content/docs/deep-agents/capabilities/planning.mdx b/apps/website/content/docs/deep-agents/capabilities/planning.mdx index 525c56fe6..6e8b8ba6b 100644 --- a/apps/website/content/docs/deep-agents/capabilities/planning.mdx +++ b/apps/website/content/docs/deep-agents/capabilities/planning.mdx @@ -49,7 +49,7 @@ The tool replaces the entire list, so two calls in the same turn would be ambigu ### Providing the agent -`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and `provideChat({})` registers the chat defaults with no overrides. The example resolves its connection at runtime because the host that serves the demo decides which runtime is attached; your own application passes `apiUrl` and `assistantId` directly. +`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and it is the only provider the `` composition requires. The example resolves its connection at runtime because the host that serves the demo decides which runtime is attached; your own application passes `apiUrl` and `assistantId` directly. diff --git a/apps/website/content/docs/deep-agents/capabilities/skills.mdx b/apps/website/content/docs/deep-agents/capabilities/skills.mdx index ff795c855..3c800a575 100644 --- a/apps/website/content/docs/deep-agents/capabilities/skills.mdx +++ b/apps/website/content/docs/deep-agents/capabilities/skills.mdx @@ -102,7 +102,7 @@ This is the same application-side shim the [memory capability](/docs/deep-agents ### Providing the agent -`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and `provideChat({})` registers the chat defaults. The example resolves its connection at runtime because the host that serves the demo decides which runtime is attached; your own application passes `apiUrl` and `assistantId` directly. +`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and it is the only provider the `` composition requires. The example resolves its connection at runtime because the host that serves the demo decides which runtime is attached; your own application passes `apiUrl` and `assistantId` directly. diff --git a/apps/website/content/docs/deep-agents/capabilities/subagents.mdx b/apps/website/content/docs/deep-agents/capabilities/subagents.mdx index 64f44c135..57f225ae2 100644 --- a/apps/website/content/docs/deep-agents/capabilities/subagents.mdx +++ b/apps/website/content/docs/deep-agents/capabilities/subagents.mdx @@ -56,7 +56,7 @@ Both halves matter, and the first one matters for a reason worth reading the nex ### Providing the agent -`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and `provideChat({})` registers the chat defaults with no overrides. `subagentToolNames: ['task']` is already the default, so the line changes nothing at runtime and is there to say which tool call means that a child started. The example resolves its connection at runtime because the host that serves the demo decides which runtime is attached; your own application passes `apiUrl` and `assistantId` directly. +`provideAgent()` from `@threadplane/langgraph` registers the agent at the application root, and it is the only provider the `` composition requires. `subagentToolNames: ['task']` is already the default, so the line changes nothing at runtime and is there to say which tool call means that a child started. The example resolves its connection at runtime because the host that serves the demo decides which runtime is attached; your own application passes `apiUrl` and `assistantId` directly. diff --git a/apps/website/content/docs/deep-agents/getting-started/introduction.mdx b/apps/website/content/docs/deep-agents/getting-started/introduction.mdx index 6059e1d28..94bc9880e 100644 --- a/apps/website/content/docs/deep-agents/getting-started/introduction.mdx +++ b/apps/website/content/docs/deep-agents/getting-started/introduction.mdx @@ -33,7 +33,6 @@ That means the setup is identical for all five capabilities, and it is the setup // app.config.ts import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -42,7 +41,6 @@ export const appConfig: ApplicationConfig = { // the key this graph is registered under in langgraph.json assistantId: 'agent', }), - provideChat({}), ], }; ``` diff --git a/apps/website/content/docs/langgraph/guides/deployment.mdx b/apps/website/content/docs/langgraph/guides/deployment.mdx index 8f6cff2b0..e16e0097d 100644 --- a/apps/website/content/docs/langgraph/guides/deployment.mdx +++ b/apps/website/content/docs/langgraph/guides/deployment.mdx @@ -30,7 +30,7 @@ The last line matters more than the graph does: `graph = build_deployment_runtim ### Pointing the app at a deployment -`provideAgent()` registers the agent once for the whole application, which is where every deployment-specific value belongs. The example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. It sits alongside `provideChat({})`, which supplies the chat composition's own providers. +`provideAgent()` registers the agent once for the whole application, which is where every deployment-specific value belongs. The example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. It is the only provider `` requires. @@ -41,7 +41,6 @@ provideAgent({ apiUrl: 'https://your-deployment.langgraph.app', assistantId: 'deployment-runtime', }), -provideChat({}), ``` `clientOptions` is the third value the example forwards. It tunes the LangGraph SDK client behind the default transport, and it accepts two keys: `apiKey`, and `maxRetries` for how many times a failed request is retried with exponential backoff before the error reaches your UI. diff --git a/apps/website/content/docs/langgraph/guides/durable-execution.mdx b/apps/website/content/docs/langgraph/guides/durable-execution.mdx index 493068b20..52d0ab86e 100644 --- a/apps/website/content/docs/langgraph/guides/durable-execution.mdx +++ b/apps/website/content/docs/langgraph/guides/durable-execution.mdx @@ -54,7 +54,7 @@ The graph is a straight line. Three nodes, three edges, and an entry point. ### The agent provider -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. +`provideAgent()` registers the agent once for the whole application, and it is the only provider `` requires. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. diff --git a/apps/website/content/docs/langgraph/guides/interrupts.mdx b/apps/website/content/docs/langgraph/guides/interrupts.mdx index f1773d427..939904fd5 100644 --- a/apps/website/content/docs/langgraph/guides/interrupts.mdx +++ b/apps/website/content/docs/langgraph/guides/interrupts.mdx @@ -64,7 +64,7 @@ A paused graph is a stored checkpoint. Serving through `langgraph dev` or LangGr ### The agent provider -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. +`provideAgent()` registers the agent once for the whole application, and it is the only provider `` requires. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. diff --git a/apps/website/content/docs/langgraph/guides/memory.mdx b/apps/website/content/docs/langgraph/guides/memory.mdx index 69668f956..317a38af1 100644 --- a/apps/website/content/docs/langgraph/guides/memory.mdx +++ b/apps/website/content/docs/langgraph/guides/memory.mdx @@ -60,7 +60,7 @@ The graph is a straight line: generate, then extract, then end. Putting extracti ### The agent provider -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. +`provideAgent()` registers the agent once for the whole application, and it is the only provider `` requires. The example passes a factory because it resolves its connection details at runtime from the host that serves the demo. diff --git a/apps/website/content/docs/langgraph/guides/persistence.mdx b/apps/website/content/docs/langgraph/guides/persistence.mdx index 7a02de1b4..00fd653d4 100644 --- a/apps/website/content/docs/langgraph/guides/persistence.mdx +++ b/apps/website/content/docs/langgraph/guides/persistence.mdx @@ -49,7 +49,7 @@ Most of the LangGraph examples register their agent in `app.config.ts`. This one -The `onThreadId` callback is per-instance state, so the agent is provided at the component instead. `provideChat({})` is all that stays at the application root. +The `onThreadId` callback is per-instance state, so the agent is provided at the component instead, and the application root carries no chat providers at all. ### The thread bookkeeping the sidebar reads diff --git a/apps/website/content/docs/langgraph/guides/streaming.mdx b/apps/website/content/docs/langgraph/guides/streaming.mdx index 3d23ea9f6..dfec29fe3 100644 --- a/apps/website/content/docs/langgraph/guides/streaming.mdx +++ b/apps/website/content/docs/langgraph/guides/streaming.mdx @@ -42,7 +42,7 @@ export interface StreamingState { export const STREAMING_AGENT = createAgentRef('streaming'); ``` -The example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. It sits alongside `provideChat({})`, which supplies the chat composition's own providers. +The example resolves its connection details at runtime from the host that serves the demo, which is why its factory reads them rather than hard-coding them. It is the only provider `` requires. @@ -53,7 +53,6 @@ provideAgent(STREAMING_AGENT, { apiUrl: 'https://your-deployment.langgraph.app', assistantId: 'streaming', }), -provideChat({}), ``` `assistantId` must match the graph name in `langgraph.json`. diff --git a/apps/website/content/docs/langgraph/guides/subgraphs.mdx b/apps/website/content/docs/langgraph/guides/subgraphs.mdx index e7c4c7d4c..80cde3eae 100644 --- a/apps/website/content/docs/langgraph/guides/subgraphs.mdx +++ b/apps/website/content/docs/langgraph/guides/subgraphs.mdx @@ -66,7 +66,7 @@ The Angular side declares the parent's state once and hands it to a ref that bot -The application config registers that ref with `provideAgent()` and adds `provideChat({})` for the chat composition. +The application config registers that ref with `provideAgent()`. The running example registers the provider through a factory that reads its connection from the host that serves the demo, which is how the same build runs locally and in production. Your own application passes the two connection values as literals, and keeps `transcriptNodeNames` exactly as the example sets it: @@ -76,7 +76,6 @@ provideAgent(SUBGRAPHS_AGENT, { assistantId: 'subgraphs', transcriptNodeNames: ['answer'], }), -provideChat({}), ``` `assistantId` must match the graph name in `langgraph.json`. `transcriptNodeNames` whitelists the *top-level* nodes whose message tokens belong in the chat transcript, which here means the `answer` node alone. The parent's other node runs a structured-output call of its own, and the whitelist is what keeps that traffic out of the chat. diff --git a/apps/website/content/docs/runtimes/aws-strands/overview.mdx b/apps/website/content/docs/runtimes/aws-strands/overview.mdx index c568f9631..7b16062b3 100644 --- a/apps/website/content/docs/runtimes/aws-strands/overview.mdx +++ b/apps/website/content/docs/runtimes/aws-strands/overview.mdx @@ -65,7 +65,7 @@ The backend is a FastAPI application. `add_strands_fastapi_endpoint` from the `a ### Providing the agent -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. Your own application does not need the factory. +`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. Your own application does not need the factory. diff --git a/apps/website/content/docs/runtimes/getting-started/introduction.mdx b/apps/website/content/docs/runtimes/getting-started/introduction.mdx index 0f08f8e13..3c1c8cc04 100644 --- a/apps/website/content/docs/runtimes/getting-started/introduction.mdx +++ b/apps/website/content/docs/runtimes/getting-started/introduction.mdx @@ -53,12 +53,10 @@ The Angular side does not change between these three runtimes. Each example bind // app.config.ts import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ provideAgent({ url: '/agent' }), - provideChat({}), ], }; ``` diff --git a/apps/website/content/docs/runtimes/mastra/overview.mdx b/apps/website/content/docs/runtimes/mastra/overview.mdx index 40a1a68d1..7b76e9f4d 100644 --- a/apps/website/content/docs/runtimes/mastra/overview.mdx +++ b/apps/website/content/docs/runtimes/mastra/overview.mdx @@ -81,7 +81,7 @@ The agent handed to the bridge is wrapped first, which is what makes the subagen ### The agent provider -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. Neither is Mastra-specific: this is the same config every AG-UI example uses. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. +`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. Nothing about it is Mastra-specific: this is the same config every AG-UI example uses. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. diff --git a/apps/website/content/docs/runtimes/microsoft-agent-framework/overview.mdx b/apps/website/content/docs/runtimes/microsoft-agent-framework/overview.mdx index fca702d95..8811bef29 100644 --- a/apps/website/content/docs/runtimes/microsoft-agent-framework/overview.mdx +++ b/apps/website/content/docs/runtimes/microsoft-agent-framework/overview.mdx @@ -73,7 +73,7 @@ The agent handed to the endpoint is the wrapped one, because the wrapper is the ### The agent provider -`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `` composition reads, here left at its defaults. Nothing in either provider is specific to this runtime. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. +`provideAgent()` registers the agent once for the whole application, and it is the only provider the `` composition requires. Nothing about it is specific to this runtime. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. diff --git a/apps/website/next.config.spec.ts b/apps/website/next.config.spec.ts index 735a96532..e743db509 100644 --- a/apps/website/next.config.spec.ts +++ b/apps/website/next.config.spec.ts @@ -55,3 +55,44 @@ describe('website next.config redirects', () => { } }); }); + +/** + * `provideChat()` / `ChatConfig` / `CHAT_CONFIG` were removed from + * `@threadplane/chat` — no shipped component ever injected the token, so the + * three pages that documented them described a no-op. Their URLs were indexed + * and linked, so each has to land on a page that still exists. + */ +describe('website next.config redirects for the removed chat configuration API', () => { + const retired = [ + '/docs/chat/api/provide-chat', + '/docs/chat/api/chat-config', + '/docs/chat/guides/configuration', + ]; + + it('permanently redirects each retired page to chat installation', async () => { + const redirects = await config.redirects!(); + + for (const source of retired) { + const rule = redirects.find( + (r: { source: string }) => r.source === source + ); + expect(rule, `missing redirect for ${source}`).toBeTruthy(); + expect(rule.destination).toBe('/docs/chat/getting-started/installation'); + expect(rule.permanent).toBe(true); + } + }); + + it('redirects the markdown mirror of each retired page', async () => { + const redirects = await config.redirects!(); + + for (const source of retired) { + const mirror = source.replace('/docs/', '/api/markdown/'); + const rule = redirects.find((r: { source: string }) => r.source === mirror); + expect(rule, `missing redirect for ${mirror}`).toBeTruthy(); + expect(rule.destination).toBe( + '/api/markdown/chat/getting-started/installation' + ); + expect(rule.permanent).toBe(true); + } + }); +}); diff --git a/apps/website/next.config.ts b/apps/website/next.config.ts index b40c8809e..2d802f934 100644 --- a/apps/website/next.config.ts +++ b/apps/website/next.config.ts @@ -50,6 +50,40 @@ export const nextConfig: WithNxOptions = { destination: '/privacy', permanent: true, }, + // `provideChat()`, `ChatConfig`, and `CHAT_CONFIG` were removed from + // `@threadplane/chat`: no component ever read the token, so the API and its + // configuration guide documented a no-op. The pages are gone; delivered + // links land on installation, which is where the real providers are. + { + source: '/docs/chat/api/provide-chat', + destination: '/docs/chat/getting-started/installation', + permanent: true, + }, + { + source: '/docs/chat/api/chat-config', + destination: '/docs/chat/getting-started/installation', + permanent: true, + }, + { + source: '/docs/chat/guides/configuration', + destination: '/docs/chat/getting-started/installation', + permanent: true, + }, + { + source: '/api/markdown/chat/api/provide-chat', + destination: '/api/markdown/chat/getting-started/installation', + permanent: true, + }, + { + source: '/api/markdown/chat/api/chat-config', + destination: '/api/markdown/chat/getting-started/installation', + permanent: true, + }, + { + source: '/api/markdown/chat/guides/configuration', + destination: '/api/markdown/chat/getting-started/installation', + permanent: true, + }, ], rewrites: async () => [ { diff --git a/apps/website/src/app/chat/page.tsx b/apps/website/src/app/chat/page.tsx index 54569793f..40cd2fd40 100644 --- a/apps/website/src/app/chat/page.tsx +++ b/apps/website/src/app/chat/page.tsx @@ -79,7 +79,10 @@ export default async function ChatPage() { { claim: 'The approval gate as a component', api: 'interrupt primitive' }, { claim: 'Composes against the streaming contract', api: 'Agent contract' }, ]} - cta={{ label: 'Headless API', href: '/docs/chat/api/provide-chat' }} + cta={{ + label: 'Headless API', + href: '/docs/chat/concepts/primitives-vs-compositions', + }} visualLeft visual={} /> diff --git a/apps/website/src/components/docs/mdx/headings.spec.tsx b/apps/website/src/components/docs/mdx/headings.spec.tsx index 965428be4..e81243c7f 100644 --- a/apps/website/src/components/docs/mdx/headings.spec.tsx +++ b/apps/website/src/components/docs/mdx/headings.spec.tsx @@ -53,13 +53,13 @@ describe('mdx heading components', () => { it('derives the label from nested nodes, not [object Object]', () => { const { container } = render( -

- Wire provideChat() first +

+ Wire provideAgent() first

, ); expect(container.querySelector('a.heading-anchor')?.getAttribute('aria-label')).toBe( - 'Link to Wire provideChat() first', + 'Link to Wire provideAgent() first', ); }); diff --git a/apps/website/src/lib/docs-config.ts b/apps/website/src/lib/docs-config.ts index a3f877ddd..0e2878ddd 100644 --- a/apps/website/src/lib/docs-config.ts +++ b/apps/website/src/lib/docs-config.ts @@ -297,7 +297,6 @@ export const docsConfig: DocsLibrary[] = [ section: 'guides', }, { title: 'Streaming', slug: 'streaming', section: 'guides' }, - { title: 'Configuration', slug: 'configuration', section: 'guides' }, { title: 'Error Handling', slug: 'error-handling', @@ -407,8 +406,6 @@ export const docsConfig: DocsLibrary[] = [ id: 'api', color: 'blue', pages: [ - { title: 'provideChat()', slug: 'provide-chat', section: 'api' }, - { title: 'ChatConfig', slug: 'chat-config', section: 'api' }, { title: 'mockAgent()', slug: 'mock-agent', section: 'api' }, { title: 'createContentClassifier()', diff --git a/apps/website/src/lib/public-copy-contract.ts b/apps/website/src/lib/public-copy-contract.ts index 50ccea97b..b03b2a507 100644 --- a/apps/website/src/lib/public-copy-contract.ts +++ b/apps/website/src/lib/public-copy-contract.ts @@ -62,8 +62,15 @@ export const RETIRED_POSITIONING: readonly string[] = [ 'thread plane for enterprise agents', ]; -/** Routes retired in favour of the canonical policy. */ -export const RETIRED_ROUTE_PATTERN = /\/docs\/telemetry|\/api\/markdown\/telemetry/u; +/** + * Routes retired from the public site. + * + * `/docs/telemetry` went in favour of the canonical policy. The three chat + * configuration pages went with `provideChat()` / `CHAT_CONFIG`, which the + * library no longer ships: `` is configured through its inputs. + */ +export const RETIRED_ROUTE_PATTERN = + /\/docs\/telemetry|\/api\/markdown\/telemetry|\/docs\/chat\/api\/provide-chat|\/docs\/chat\/api\/chat-config|\/docs\/chat\/guides\/configuration|\/api\/markdown\/chat\/api\/provide-chat|\/api\/markdown\/chat\/api\/chat-config|\/api\/markdown\/chat\/guides\/configuration/u; /** * Public routes that are intentionally absent from the sitemap. diff --git a/cockpit/ag-ui/a2ui/angular/src/app/app.config.ts b/cockpit/ag-ui/a2ui/angular/src/app/app.config.ts index 3c06f12ba..0d04381b6 100644 --- a/cockpit/ag-ui/a2ui/angular/src/app/app.config.ts +++ b/cockpit/ag-ui/a2ui/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -14,6 +13,5 @@ export const appConfig: ApplicationConfig = { url: connection.url, }; }), - provideChat({}), ], }; diff --git a/cockpit/ag-ui/client-tools/angular/src/app/app.config.ts b/cockpit/ag-ui/client-tools/angular/src/app/app.config.ts index 3c06f12ba..0d04381b6 100644 --- a/cockpit/ag-ui/client-tools/angular/src/app/app.config.ts +++ b/cockpit/ag-ui/client-tools/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -14,6 +13,5 @@ export const appConfig: ApplicationConfig = { url: connection.url, }; }), - provideChat({}), ], }; diff --git a/cockpit/ag-ui/interrupts/angular/src/app/app.config.ts b/cockpit/ag-ui/interrupts/angular/src/app/app.config.ts index 3c06f12ba..0d04381b6 100644 --- a/cockpit/ag-ui/interrupts/angular/src/app/app.config.ts +++ b/cockpit/ag-ui/interrupts/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -14,6 +13,5 @@ export const appConfig: ApplicationConfig = { url: connection.url, }; }), - provideChat({}), ], }; diff --git a/cockpit/ag-ui/json-render/angular/src/app/app.config.ts b/cockpit/ag-ui/json-render/angular/src/app/app.config.ts index 3c06f12ba..0d04381b6 100644 --- a/cockpit/ag-ui/json-render/angular/src/app/app.config.ts +++ b/cockpit/ag-ui/json-render/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -14,6 +13,5 @@ export const appConfig: ApplicationConfig = { url: connection.url, }; }), - provideChat({}), ], }; diff --git a/cockpit/ag-ui/streaming/angular/src/app/app.config.ts b/cockpit/ag-ui/streaming/angular/src/app/app.config.ts index 3c06f12ba..0d04381b6 100644 --- a/cockpit/ag-ui/streaming/angular/src/app/app.config.ts +++ b/cockpit/ag-ui/streaming/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -14,6 +13,5 @@ export const appConfig: ApplicationConfig = { url: connection.url, }; }), - provideChat({}), ], }; diff --git a/cockpit/ag-ui/subagents/angular/src/app/app.config.ts b/cockpit/ag-ui/subagents/angular/src/app/app.config.ts index 3c06f12ba..0d04381b6 100644 --- a/cockpit/ag-ui/subagents/angular/src/app/app.config.ts +++ b/cockpit/ag-ui/subagents/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -14,6 +13,5 @@ export const appConfig: ApplicationConfig = { url: connection.url, }; }), - provideChat({}), ], }; diff --git a/cockpit/ag-ui/tool-views/angular/src/app/app.config.ts b/cockpit/ag-ui/tool-views/angular/src/app/app.config.ts index 3c06f12ba..0d04381b6 100644 --- a/cockpit/ag-ui/tool-views/angular/src/app/app.config.ts +++ b/cockpit/ag-ui/tool-views/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -14,6 +13,5 @@ export const appConfig: ApplicationConfig = { url: connection.url, }; }), - provideChat({}), ], }; diff --git a/cockpit/chat/a2ui/angular/src/app/app.config.ts b/cockpit/chat/a2ui/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/chat/a2ui/angular/src/app/app.config.ts +++ b/cockpit/chat/a2ui/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/chat/debug/angular/src/app/app.config.ts b/cockpit/chat/debug/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/chat/debug/angular/src/app/app.config.ts +++ b/cockpit/chat/debug/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/chat/generative-ui/angular/src/app/app.config.ts b/cockpit/chat/generative-ui/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/chat/generative-ui/angular/src/app/app.config.ts +++ b/cockpit/chat/generative-ui/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/chat/input/angular/src/app/app.config.ts b/cockpit/chat/input/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/chat/input/angular/src/app/app.config.ts +++ b/cockpit/chat/input/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/chat/interrupts/angular/src/app/app.config.ts b/cockpit/chat/interrupts/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/chat/interrupts/angular/src/app/app.config.ts +++ b/cockpit/chat/interrupts/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/chat/messages/angular/src/app/app.config.ts b/cockpit/chat/messages/angular/src/app/app.config.ts index c95b9f9a6..a9cffa91f 100644 --- a/cockpit/chat/messages/angular/src/app/app.config.ts +++ b/cockpit/chat/messages/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; import { MESSAGES_AGENT } from './agent-ref'; export const appConfig: ApplicationConfig = { @@ -17,6 +16,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/chat/subagents/angular/src/app/app.config.ts b/cockpit/chat/subagents/angular/src/app/app.config.ts index 090022421..b1b4d4a81 100644 --- a/cockpit/chat/subagents/angular/src/app/app.config.ts +++ b/cockpit/chat/subagents/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -20,6 +19,5 @@ export const appConfig: ApplicationConfig = { subagentToolNames: ['task'], }; }), - provideChat({}), ], }; diff --git a/cockpit/chat/theming/angular/src/app/app.config.ts b/cockpit/chat/theming/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/chat/theming/angular/src/app/app.config.ts +++ b/cockpit/chat/theming/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/chat/threads/angular/src/app/app.config.ts b/cockpit/chat/threads/angular/src/app/app.config.ts index e94b00fcb..96b3e9557 100644 --- a/cockpit/chat/threads/angular/src/app/app.config.ts +++ b/cockpit/chat/threads/angular/src/app/app.config.ts @@ -1,5 +1,4 @@ import { ApplicationConfig } from '@angular/core'; -import { provideChat } from '@threadplane/chat'; import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { LANGGRAPH_CLIENT_OPTIONS, @@ -10,7 +9,6 @@ export const appConfig: ApplicationConfig = { providers: [ // The agent is provided at the component (ThreadsComponent) because its // threadId + onThreadId config is per-instance — see threads.component.ts. - provideChat({}), // The adapter expects metadata.title; the cap's generate_title // graph node writes there. No per-cap key override needed. { diff --git a/cockpit/chat/timeline/angular/src/app/app.config.ts b/cockpit/chat/timeline/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/chat/timeline/angular/src/app/app.config.ts +++ b/cockpit/chat/timeline/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/chat/tool-calls/angular/src/app/app.config.ts b/cockpit/chat/tool-calls/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/chat/tool-calls/angular/src/app/app.config.ts +++ b/cockpit/chat/tool-calls/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/deep-agents/filesystem/angular/src/app/app.config.ts b/cockpit/deep-agents/filesystem/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/deep-agents/filesystem/angular/src/app/app.config.ts +++ b/cockpit/deep-agents/filesystem/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/deep-agents/memory/angular/src/app/app.config.ts b/cockpit/deep-agents/memory/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/deep-agents/memory/angular/src/app/app.config.ts +++ b/cockpit/deep-agents/memory/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/deep-agents/planning/angular/src/app/app.config.ts b/cockpit/deep-agents/planning/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/deep-agents/planning/angular/src/app/app.config.ts +++ b/cockpit/deep-agents/planning/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/deep-agents/skills/angular/src/app/app.config.ts b/cockpit/deep-agents/skills/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/deep-agents/skills/angular/src/app/app.config.ts +++ b/cockpit/deep-agents/skills/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/deep-agents/subagents/angular/src/app/app.config.ts b/cockpit/deep-agents/subagents/angular/src/app/app.config.ts index a84d6c8bc..19bb8810c 100644 --- a/cockpit/deep-agents/subagents/angular/src/app/app.config.ts +++ b/cockpit/deep-agents/subagents/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -24,6 +23,5 @@ export const appConfig: ApplicationConfig = { subagentToolNames: ['task'], }; }), - provideChat({}), ], }; diff --git a/cockpit/langgraph/client-tools/angular/src/app/app.config.ts b/cockpit/langgraph/client-tools/angular/src/app/app.config.ts index 508df6908..6ed6f1f58 100644 --- a/cockpit/langgraph/client-tools/angular/src/app/app.config.ts +++ b/cockpit/langgraph/client-tools/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; import { CLIENT_TOOLS_AGENT_REF } from './agent-ref'; export const appConfig: ApplicationConfig = { @@ -17,6 +16,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/langgraph/deployment-runtime/angular/src/app/app.config.ts b/cockpit/langgraph/deployment-runtime/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/langgraph/deployment-runtime/angular/src/app/app.config.ts +++ b/cockpit/langgraph/deployment-runtime/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/langgraph/durable-execution/angular/src/app/app.config.ts b/cockpit/langgraph/durable-execution/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/langgraph/durable-execution/angular/src/app/app.config.ts +++ b/cockpit/langgraph/durable-execution/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/langgraph/interrupts/angular/src/app/app.config.ts b/cockpit/langgraph/interrupts/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/langgraph/interrupts/angular/src/app/app.config.ts +++ b/cockpit/langgraph/interrupts/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/langgraph/memory/angular/src/app/app.config.ts b/cockpit/langgraph/memory/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/langgraph/memory/angular/src/app/app.config.ts +++ b/cockpit/langgraph/memory/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/langgraph/persistence/angular/src/app/app.config.ts b/cockpit/langgraph/persistence/angular/src/app/app.config.ts index bbaafd83f..51757a424 100644 --- a/cockpit/langgraph/persistence/angular/src/app/app.config.ts +++ b/cockpit/langgraph/persistence/angular/src/app/app.config.ts @@ -1,10 +1,8 @@ import { ApplicationConfig } from '@angular/core'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ // The agent is provided at the component (PersistenceComponent) because // its onThreadId callback is per-instance — see persistence.component.ts. - provideChat({}), ], }; diff --git a/cockpit/langgraph/streaming/angular/src/app/app.config.ts b/cockpit/langgraph/streaming/angular/src/app/app.config.ts index a35eb5e46..c95dabf4e 100644 --- a/cockpit/langgraph/streaming/angular/src/app/app.config.ts +++ b/cockpit/langgraph/streaming/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; import { STREAMING_AGENT } from './agent-ref'; export const appConfig: ApplicationConfig = { @@ -17,6 +16,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/langgraph/subgraphs/angular/src/app/app.config.ts b/cockpit/langgraph/subgraphs/angular/src/app/app.config.ts index 9c18f1740..b65493c05 100644 --- a/cockpit/langgraph/subgraphs/angular/src/app/app.config.ts +++ b/cockpit/langgraph/subgraphs/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; import { SUBGRAPHS_AGENT } from './agent-ref'; export const appConfig: ApplicationConfig = { @@ -29,6 +28,5 @@ export const appConfig: ApplicationConfig = { transcriptNodeNames: ['answer'], }; }), - provideChat({}), ], }; diff --git a/cockpit/langgraph/time-travel/angular/src/app/app.config.ts b/cockpit/langgraph/time-travel/angular/src/app/app.config.ts index 3ed758075..9395cf923 100644 --- a/cockpit/langgraph/time-travel/angular/src/app/app.config.ts +++ b/cockpit/langgraph/time-travel/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,6 +15,5 @@ export const appConfig: ApplicationConfig = { clientOptions: connection.clientOptions, }; }), - provideChat({}), ], }; diff --git a/cockpit/runtimes/aws-strands/angular/src/app/app.config.ts b/cockpit/runtimes/aws-strands/angular/src/app/app.config.ts index 3c06f12ba..0d04381b6 100644 --- a/cockpit/runtimes/aws-strands/angular/src/app/app.config.ts +++ b/cockpit/runtimes/aws-strands/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -14,6 +13,5 @@ export const appConfig: ApplicationConfig = { url: connection.url, }; }), - provideChat({}), ], }; diff --git a/cockpit/runtimes/mastra/angular/src/app/app.config.ts b/cockpit/runtimes/mastra/angular/src/app/app.config.ts index 3c06f12ba..0d04381b6 100644 --- a/cockpit/runtimes/mastra/angular/src/app/app.config.ts +++ b/cockpit/runtimes/mastra/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -14,6 +13,5 @@ export const appConfig: ApplicationConfig = { url: connection.url, }; }), - provideChat({}), ], }; diff --git a/cockpit/runtimes/microsoft-agent-framework/angular/src/app/app.config.ts b/cockpit/runtimes/microsoft-agent-framework/angular/src/app/app.config.ts index 3c06f12ba..0d04381b6 100644 --- a/cockpit/runtimes/microsoft-agent-framework/angular/src/app/app.config.ts +++ b/cockpit/runtimes/microsoft-agent-framework/angular/src/app/app.config.ts @@ -1,7 +1,6 @@ import { injectCockpitRuntimeConnection } from '@threadplane/cockpit-telemetry'; import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@threadplane/ag-ui'; -import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { providers: [ @@ -14,6 +13,5 @@ export const appConfig: ApplicationConfig = { url: connection.url, }; }), - provideChat({}), ], }; diff --git a/examples/ag-ui/angular/src/app/app.config.ts b/examples/ag-ui/angular/src/app/app.config.ts index f3502ceee..bae35b3fb 100644 --- a/examples/ag-ui/angular/src/app/app.config.ts +++ b/examples/ag-ui/angular/src/app/app.config.ts @@ -7,7 +7,6 @@ import { } from '@angular/core'; import { provideRouter } from '@angular/router'; import { provideThreadplaneTelemetry } from '@threadplane/telemetry/browser'; -import { provideChat } from '@threadplane/chat'; import { provideAgent } from '@threadplane/ag-ui'; import { environment } from '../environments/environment'; import { routes } from './app.routes'; @@ -42,7 +41,6 @@ export const appConfig: ApplicationConfig = { // mode is opened; the map still waits for `loaded()` so its component never // constructs before the API is present. Skips cleanly with no key. provideEnvironmentInitializer(() => inject(GoogleMapsLoader).ensureLoaded()), - provideChat({}), // The frontend-owned itinerary is a single shared instance: the panel, // the App component, and the client-tool ask component all inject it, so // user edits and agent writes hit the same signals and render live. diff --git a/examples/chat/angular/src/app/app.config.ts b/examples/chat/angular/src/app/app.config.ts index 80524b9c4..305b4de09 100644 --- a/examples/chat/angular/src/app/app.config.ts +++ b/examples/chat/angular/src/app/app.config.ts @@ -8,7 +8,6 @@ import { import { provideRouter, withComponentInputBinding } from '@angular/router'; import { provideThreadplaneTelemetry } from '@threadplane/telemetry/browser'; import { LANGGRAPH_THREADS_CONFIG, LANGGRAPH_CLIENT_OPTIONS } from '@threadplane/langgraph'; -import { provideChat } from '@threadplane/chat'; import { e2eClientOptions } from './shell/e2e-overrides'; import { ItineraryStore } from './itinerary-store'; import { GoogleMapsLoader } from './google-maps-loader'; @@ -32,7 +31,6 @@ export const appConfig: ApplicationConfig = { // localStorage flag → fail fast. useFactory runs at injection time (post- // bootstrap), so the flag is readable. { provide: LANGGRAPH_CLIENT_OPTIONS, useFactory: () => e2eClientOptions() }, - provideChat({}), // App-wide singleton so DemoShell, the itinerary panel, and the map cockpit // all read/write ONE working copy of the itinerary. Provided at root (not at // the component) so routed children share the same instance. diff --git a/libs/chat/CHANGELOG.md b/libs/chat/CHANGELOG.md index 0c02723c5..0075d33d8 100644 --- a/libs/chat/CHANGELOG.md +++ b/libs/chat/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Removed + +- **`provideChat()`, `ChatConfig`, and `CHAT_CONFIG` are gone.** No component in the library ever injected the token, so calling `provideChat({})` configured nothing: `renderRegistry`, `avatarLabel`, and `assistantName` were values only your own wrappers could read back. Delete the call and the import; `provideAgent()` from your runtime adapter is the only provider the chat components require, and everything they render is driven by component inputs. If you were reading `CHAT_CONFIG` from your own components, define your own injection token for those values. + ### Changed - **`@angular/forms` peer dependency removed:** `chat-input` now binds its textarea with a direct `[value]`/`(input)` pair (fixes the composer keeping sent text under zoneless + OnPush). `@threadplane/chat` no longer requires `@angular/forms` — consumers may drop it unless they use it themselves. diff --git a/libs/chat/README.md b/libs/chat/README.md index 1d8ac02e0..9884f2888 100644 --- a/libs/chat/README.md +++ b/libs/chat/README.md @@ -53,13 +53,11 @@ katex ^0.16.0 || ^0.17.0 (optional) ```typescript // app.config.ts import { ApplicationConfig } from '@angular/core'; -import { provideChat } from '@threadplane/chat'; import { provideAgent } from '@threadplane/langgraph'; export const appConfig: ApplicationConfig = { providers: [ provideAgent({ apiUrl: '/api/langgraph', assistantId: 'agent' }), - provideChat({}), ], }; ``` diff --git a/libs/chat/src/lib/chat.types.ts b/libs/chat/src/lib/chat.types.ts index d2e0e52f7..fa7482964 100644 --- a/libs/chat/src/lib/chat.types.ts +++ b/libs/chat/src/lib/chat.types.ts @@ -1,7 +1 @@ -import type { AngularRegistry } from '@threadplane/render'; - -export interface ChatConfig { - registry?: AngularRegistry; -} - export type MessageTemplateType = 'human' | 'ai' | 'tool' | 'system' | 'function'; diff --git a/libs/chat/src/lib/client-tools/tools.ts b/libs/chat/src/lib/client-tools/tools.ts index 2dfc3e2ef..2200041b3 100644 --- a/libs/chat/src/lib/client-tools/tools.ts +++ b/libs/chat/src/lib/client-tools/tools.ts @@ -129,7 +129,7 @@ export function ask( * The overload is generic over the entire map (`const M`) so that each tool's * precise type ({@link FunctionToolDef}``, {@link ViewToolDef}``, or * {@link AskToolDef}``) and every literal key are preserved in the - * {@link ClientToolRegistry} passed to `provideChat`. This lets downstream + * {@link ClientToolRegistry} passed to the `clientTools` input. This lets downstream * consumers look up individual tools without losing generic information. * * @param map An object literal mapping tool names to tool definitions created diff --git a/libs/chat/src/lib/provide-chat.spec.ts b/libs/chat/src/lib/provide-chat.spec.ts deleted file mode 100644 index b67f960b1..000000000 --- a/libs/chat/src/lib/provide-chat.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { TestBed } from '@angular/core/testing'; -import { provideChat, CHAT_CONFIG } from './provide-chat'; -import type { ChatConfig } from './provide-chat'; - -describe('provideChat', () => { - it('registers CHAT_CONFIG token with the provided config', () => { - const config: ChatConfig = { renderRegistry: undefined }; - - TestBed.configureTestingModule({ - providers: [provideChat(config)], - }); - - const injected = TestBed.inject(CHAT_CONFIG); - expect(injected).toBe(config); - }); - - it('injects the exact config object reference', () => { - const config: ChatConfig = {}; - - TestBed.configureTestingModule({ - providers: [provideChat(config)], - }); - - expect(TestBed.inject(CHAT_CONFIG)).toStrictEqual({}); - }); - - it('returns environment providers (duck-type check)', () => { - const result = provideChat({}); - // makeEnvironmentProviders returns an object with ɵproviders - expect(result).toBeDefined(); - expect(typeof result).toBe('object'); - }); - - it('provides CHAT_CONFIG token', () => { - TestBed.configureTestingModule({ providers: [provideChat({})] }); - const config = TestBed.inject(CHAT_CONFIG); - expect(config).toBeDefined(); - }); -}); diff --git a/libs/chat/src/lib/provide-chat.ts b/libs/chat/src/lib/provide-chat.ts deleted file mode 100644 index e6e9c957c..000000000 --- a/libs/chat/src/lib/provide-chat.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { InjectionToken, makeEnvironmentProviders } from '@angular/core'; -import type { AngularRegistry } from '@threadplane/render'; - -/** - * Application-wide options for {@link provideChat}. Every field is optional; - * the values are exposed to all chat components in the tree via the - * `CHAT_CONFIG` injection token, so you set them once at bootstrap instead of - * threading props through every component. - */ -export interface ChatConfig { - /** Shared render registry for consumers that read CHAT_CONFIG. */ - renderRegistry?: AngularRegistry; - /** Shared AI avatar label for consumers that read CHAT_CONFIG (default: "A"). */ - avatarLabel?: string; - /** Shared assistant display name for consumers that read CHAT_CONFIG (default: "Assistant"). */ - assistantName?: string; -} - -export const CHAT_CONFIG = new InjectionToken('CHAT_CONFIG'); - -/** - * Bootstrap `@threadplane/chat` in an Angular application or standalone - * component tree. - * - * Call this once inside `bootstrapApplication` (or the `providers` array of a - * root `ApplicationConfig`). It registers the shared {@link ChatConfig} token - * so every chat component in the tree can read the render registry, avatar - * label, and assistant display name without explicit prop threading. - * - * @param config Options bag that controls the chat feature set: - * - `renderRegistry` — shared {@link AngularRegistry} wiring tool-view - * components to their names; pass the value returned by - * `defineAngularRegistry` from `\@threadplane/render`. - * - `avatarLabel` — short label shown in the AI avatar bubble (default `"A"`). - * - `assistantName` — display name shown above assistant messages - * (default `"Assistant"`). - * @returns An `EnvironmentProviders` value suitable for the `providers` array - * of `bootstrapApplication` or `ApplicationConfig`. - * @example - * ```ts - * // main.ts - * import { bootstrapApplication } from '@angular/platform-browser'; - * import { provideChat } from '@threadplane/chat'; - * import { defineAngularRegistry, provideRender } from '@threadplane/render'; - * import { DayCardComponent } from './day-card.component'; - * - * const registry = defineAngularRegistry({ day_card: DayCardComponent }); - * - * bootstrapApplication(AppComponent, { - * providers: [ - * provideChat({ renderRegistry: registry, avatarLabel: 'AI' }), - * provideRender({ registry }), - * ], - * }); - * ``` - */ -export function provideChat(config: ChatConfig) { - return makeEnvironmentProviders([ - { provide: CHAT_CONFIG, useValue: config }, - ]); -} diff --git a/libs/chat/src/public-api.ts b/libs/chat/src/public-api.ts index 758e9b5a5..b84e5c7c5 100644 --- a/libs/chat/src/public-api.ts +++ b/libs/chat/src/public-api.ts @@ -1,5 +1,4 @@ // Shared types -export type { ChatConfig } from './lib/provide-chat'; export type { MessageTemplateType } from './lib/chat.types'; // Agent contract (runtime-neutral) @@ -99,9 +98,6 @@ export type { CitationImageVisual, CitationTypeIconVisual, CitationMonogramVisual, } from './lib/agent/citation-display'; -// DI provider -export { provideChat, CHAT_CONFIG } from './lib/provide-chat'; - // Routing utilities export { injectThreadRouting } from './lib/routing/thread-routing'; export type { ThreadRoutingConfig } from './lib/routing/thread-routing'; @@ -286,7 +282,7 @@ export type { ClientToolExecutionRecord, ClientToolExecutionStore, } from './lib/client-tools/client-tool-execution-guard'; -// createClientToolsCoordinator: internal — provideChat wires it; not public. +// createClientToolsCoordinator: internal — the chat compositions wire it; not public. export { toClientToolSpecs } from './lib/client-tools/client-tools-coordinator'; export type { ClientToolsCoordinator } from './lib/client-tools/client-tools-coordinator'; diff --git a/libs/langgraph/README.md b/libs/langgraph/README.md index 06e1e82b2..16971f0db 100644 --- a/libs/langgraph/README.md +++ b/libs/langgraph/README.md @@ -174,7 +174,7 @@ import { extractCitations } from '@threadplane/langgraph'; const citations = extractCitations(message); ``` -`Citation` is a type from `@threadplane/chat`; `CitationsResolverService` and `provideChat` also live there. +`Citation` is a type from `@threadplane/chat`; `CitationsResolverService` also lives there. ## Testing diff --git a/libs/render/src/lib/define-angular-registry.ts b/libs/render/src/lib/define-angular-registry.ts index 659bc0d00..f47517181 100644 --- a/libs/render/src/lib/define-angular-registry.ts +++ b/libs/render/src/lib/define-angular-registry.ts @@ -20,9 +20,8 @@ function normalize(entry: Type | RenderViewEntry): NormalizedEntry { * Build an {@link AngularRegistry} from a plain object mapping tool-call names * to Angular components (or fully specified {@link RenderViewEntry} objects). * - * The returned registry is consumed by both `provideRender` (to drive - * dynamic component rendering) and `provideChat` (via `renderRegistry`) so - * that a single `defineAngularRegistry` call wires both layers. + * The returned registry is consumed by `provideRender` to drive dynamic + * component rendering. * * **Entry forms** * - Bare `Type` — the component is paired with the built-in diff --git a/libs/render/src/lib/provide-render.ts b/libs/render/src/lib/provide-render.ts index a1e43807e..389d07926 100644 --- a/libs/render/src/lib/provide-render.ts +++ b/libs/render/src/lib/provide-render.ts @@ -11,8 +11,8 @@ export const RENDER_CONFIG = new InjectionToken('RENDER_CONFIG'); * * Registers the shared {@link RenderConfig} token and the internal * `RenderLifecycleService` that coordinates mount/unmount events across - * dynamically rendered components. Call this once alongside `provideChat` in - * `bootstrapApplication` (or the root `ApplicationConfig`). + * dynamically rendered components. Call this once in `bootstrapApplication` + * (or the root `ApplicationConfig`). * * @param config Options bag that controls the render feature set: * - `registry` — component registry returned by {@link defineAngularRegistry}; @@ -27,7 +27,6 @@ export const RENDER_CONFIG = new InjectionToken('RENDER_CONFIG'); * // main.ts * import { bootstrapApplication } from '@angular/platform-browser'; * import { defineAngularRegistry, provideRender } from '@threadplane/render'; - * import { provideChat } from '@threadplane/chat'; * import { DayCardComponent } from './day-card.component'; * * const registry = defineAngularRegistry({ day_card: DayCardComponent }); @@ -35,7 +34,6 @@ export const RENDER_CONFIG = new InjectionToken('RENDER_CONFIG'); * bootstrapApplication(AppComponent, { * providers: [ * provideRender({ registry }), - * provideChat({ renderRegistry: registry }), * ], * }); * ``` diff --git a/libs/render/src/lib/provide-views.ts b/libs/render/src/lib/provide-views.ts index f812a5ff1..341a6a04f 100644 --- a/libs/render/src/lib/provide-views.ts +++ b/libs/render/src/lib/provide-views.ts @@ -10,7 +10,7 @@ export const VIEW_REGISTRY = new InjectionToken('VIEW_REGISTRY'); * * @param registry Map of spec element types to the components that render them, * typically built with `views()` / `withViews()`. - * @returns Environment providers to spread into `providers` / `provideChat`. + * @returns Environment providers to spread into the `providers` array. * @example * ```ts * bootstrapApplication(App, { From a1cc24ec77f12976767234bc50df826887e09013 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 7 Sep 2026 13:45:19 -0700 Subject: [PATCH 2/4] fix(chat): let an ancestor MARKDOWN_VIEW_REGISTRY override markdown views provided MARKDOWN_VIEW_REGISTRY on its own component injector from its own default, so a provider at the application root or on a route was always shadowed and never reached the markdown node components. The [viewRegistry] input was the only override that worked. Resolution is now most-specific-first: the [viewRegistry] input, then a registry found through skipSelf on an ancestor injector, then cacheplaneMarkdownViews. The resolved value is still provided on the component injector, so and the table-row view are unchanged, and needs to forward nothing for an app-wide override to reach the markdown it renders for assistant messages. The new spec fails on the old resolution in two of its four cases (ancestor provider, and the same through ) and passes on the other two, which pin the default and the input precedence. Restores an accurate "App-wide override" section in the markdown guide, which previously carried a Callout asserting the opposite, and corrects the CHANGELOG entry that made the same claim. Co-Authored-By: Claude Fable 5.1 --- .../content/docs/chat/guides/markdown.mdx | 54 +++++-- libs/chat/CHANGELOG.md | 6 +- .../lib/markdown/markdown-view-registry.ts | 10 +- .../streaming/streaming-markdown.component.ts | 19 ++- ...reaming-markdown.registry-override.spec.ts | 145 ++++++++++++++++++ 5 files changed, 214 insertions(+), 20 deletions(-) create mode 100644 libs/chat/src/lib/streaming/streaming-markdown.registry-override.spec.ts diff --git a/apps/website/content/docs/chat/guides/markdown.mdx b/apps/website/content/docs/chat/guides/markdown.mdx index 35f01e678..7a930b126 100644 --- a/apps/website/content/docs/chat/guides/markdown.mdx +++ b/apps/website/content/docs/chat/guides/markdown.mdx @@ -126,16 +126,14 @@ Use the exported `markdownDocument(content, delivery)` helper to derive it from It resolves each markdown node type against `MARKDOWN_VIEW_REGISTRY` — a chat-internal DI token exported from `@threadplane/chat` and consumed by the markdown node components. -Every `` instance provides `MARKDOWN_VIEW_REGISTRY` on its own -component injector, from `[viewRegistry]` when you pass one and from -`cacheplaneMarkdownViews` (the full 26-node registry) otherwise. - - -Because the component always provides the token itself, a -`MARKDOWN_VIEW_REGISTRY` provider in your root or route providers is shadowed and -never reaches the markdown node components. `[viewRegistry]` on -`` is the only override point. - +Every `` instance provides the resolved registry on its own +component injector, so the markdown node components below it read one value. +Resolution runs most-specific-first: + +1. The `[viewRegistry]` input on that ``. +2. A `MARKDOWN_VIEW_REGISTRY` provider on an ancestor injector -- your + application root or a route. +3. `cacheplaneMarkdownViews`, the full 26-node registry. ## Overriding Markdown Components @@ -177,10 +175,38 @@ export class CustomChatComponent { Use `overrideViews` when replacing an existing node type. Use `withViews` when adding a brand-new node type that `cacheplaneMarkdownViews` does not yet cover — `withViews` is additive-only and the base registry wins on conflicts. See the [render views API](/docs/render/api/views) for full signatures. -`` renders assistant markdown through its own ``, and -does not forward a `[viewRegistry]`. To ship a custom node renderer inside a -conversation, project your own `ai` message template into -`` and mount `` there. +### App-wide override + +Provide `MARKDOWN_VIEW_REGISTRY` once and every markdown surface below that +injector picks it up, including the `` that `` mounts +for assistant messages. `` forwards nothing, and needs to forward nothing. + +```typescript +// app.config.ts +import { ApplicationConfig } from '@angular/core'; +import { + MARKDOWN_VIEW_REGISTRY, + cacheplaneMarkdownViews, +} from '@threadplane/chat'; +import { overrideViews } from '@threadplane/render'; +import { MyCodeBlockComponent } from './my-code-block.component'; + +export const appConfig: ApplicationConfig = { + providers: [ + { + provide: MARKDOWN_VIEW_REGISTRY, + useValue: overrideViews(cacheplaneMarkdownViews, { + 'code-block': MyCodeBlockComponent, + }), + }, + ], +}; +``` + +The same provider works in a route's `providers` array when only one section of +the application should render markdown differently. A `[viewRegistry]` input on +an individual `` still wins over both, so a single surface can +opt out of the application-wide choice. ## Node-Type Reference diff --git a/libs/chat/CHANGELOG.md b/libs/chat/CHANGELOG.md index 0075d33d8..917689a39 100644 --- a/libs/chat/CHANGELOG.md +++ b/libs/chat/CHANGELOG.md @@ -6,9 +6,13 @@ - **`provideChat()`, `ChatConfig`, and `CHAT_CONFIG` are gone.** No component in the library ever injected the token, so calling `provideChat({})` configured nothing: `renderRegistry`, `avatarLabel`, and `assistantName` were values only your own wrappers could read back. Delete the call and the import; `provideAgent()` from your runtime adapter is the only provider the chat components require, and everything they render is driven by component inputs. If you were reading `CHAT_CONFIG` from your own components, define your own injection token for those values. +### Fixed + +- **An application-level `MARKDOWN_VIEW_REGISTRY` provider now takes effect.** `` provided the token on its own injector from its own default, which shadowed any provider at the application root or on a route. It now resolves most-specific-first — the `[viewRegistry]` input, then an ancestor injector, then `cacheplaneMarkdownViews` — so one root provider overrides markdown rendering across every chat surface, including inside ``, with nothing to forward. + ### Changed - **`@angular/forms` peer dependency removed:** `chat-input` now binds its textarea with a direct `[value]`/`(input)` pair (fixes the composer keeping sent text under zoneless + OnPush). `@threadplane/chat` no longer requires `@angular/forms` — consumers may drop it unless they use it themselves. - **json-render store isolation:** ``'s json-render message surfaces no longer fall back to the conversation-wide internal store — each surface self-seeds from its spec's `state` unless you pass an explicit `[store]`. Pass `[store]` (e.g. `signalStateStore({})`) when dashboards should receive backend agent state (STATE_SNAPSHOT) or share live values across surfaces; same-key dashboards in different messages are now isolated by default. Tool views (`chat-tool-views`) keep the previous shared-store behavior. -- **Public API trim:** `@threadplane/chat` no longer re-exports `provideViews` / `VIEW_REGISTRY` from `@threadplane/render`. Consumers using `` / `` directly should import from `@threadplane/render`. For chat's markdown view overrides, pass `overrideViews(cacheplaneMarkdownViews, { … })` from `@threadplane/render` to the `[viewRegistry]` input on ``. That input is the only override point: the component always provides `MARKDOWN_VIEW_REGISTRY` on its own injector, so an app-level or route-level provider for that token is shadowed and never reaches the markdown node components. The previously-documented `provideViews(withViews(…))` pattern never drove rendering either. +- **Public API trim:** `@threadplane/chat` no longer re-exports `provideViews` / `VIEW_REGISTRY` from `@threadplane/render`. Consumers using `` / `` directly should import from `@threadplane/render`. For chat's markdown view overrides, pass `overrideViews(cacheplaneMarkdownViews, { … })` from `@threadplane/render` to the `[viewRegistry]` input on ``, or provide the same value for `MARKDOWN_VIEW_REGISTRY` at the application root or on a route to override every markdown surface at once. The previously-documented `provideViews(withViews(…))` pattern never drove rendering. - **License:** `@threadplane/chat` is now MIT-licensed for commercial and noncommercial use. The package no longer accepts or checks activation tokens. diff --git a/libs/chat/src/lib/markdown/markdown-view-registry.ts b/libs/chat/src/lib/markdown/markdown-view-registry.ts index 413d2dfe0..d682097dd 100644 --- a/libs/chat/src/lib/markdown/markdown-view-registry.ts +++ b/libs/chat/src/lib/markdown/markdown-view-registry.ts @@ -7,10 +7,12 @@ import type { ViewRegistry } from '@threadplane/render'; * and . Maps MarkdownNode.type strings (e.g. "paragraph", * "heading") to Angular components that render that node type. * - * `` provides the runtime registry on its component-level - * injector — either the consumer-supplied [viewRegistry] input, or - * `cacheplaneMarkdownViews` (the default) — so descendant - * components resolve the right components for each node. + * `` provides the resolved registry on its component-level + * injector so descendant components resolve the right component + * for each node. It resolves most-specific-first: the `[viewRegistry]` input, + * then a registry provided by an ancestor injector (application root or route), + * then `cacheplaneMarkdownViews` (the default). Providing this token at the + * application root is therefore a supported app-wide override. */ export const MARKDOWN_VIEW_REGISTRY = new InjectionToken( 'MARKDOWN_VIEW_REGISTRY', diff --git a/libs/chat/src/lib/streaming/streaming-markdown.component.ts b/libs/chat/src/lib/streaming/streaming-markdown.component.ts index f6dcdd35c..5a12a0ec5 100644 --- a/libs/chat/src/lib/streaming/streaming-markdown.component.ts +++ b/libs/chat/src/lib/streaming/streaming-markdown.component.ts @@ -127,8 +127,25 @@ export class ChatStreamingMdComponent { readonly document = input.required(); readonly viewRegistry = input(undefined); + /** + * A registry provided further up the injector tree — at the application root + * or on a route. `skipSelf` is what makes an app-wide override possible: this + * component provides `MARKDOWN_VIEW_REGISTRY` on its own injector for its + * descendants, so without it the component would only ever find its own + * value and shadow the ancestor. + */ + private readonly ancestorRegistry = inject( + MARKDOWN_VIEW_REGISTRY, + { optional: true, skipSelf: true } + ); + + /** + * Most specific wins: the `[viewRegistry]` input, then a registry provided by + * an ancestor injector, then the built-in markdown views. + */ readonly resolvedRegistry = computed( - () => this.viewRegistry() ?? cacheplaneMarkdownViews + () => + this.viewRegistry() ?? this.ancestorRegistry ?? cacheplaneMarkdownViews ); private readonly resolver = inject(CitationsResolverService, { diff --git a/libs/chat/src/lib/streaming/streaming-markdown.registry-override.spec.ts b/libs/chat/src/lib/streaming/streaming-markdown.registry-override.spec.ts new file mode 100644 index 000000000..c0c459e6d --- /dev/null +++ b/libs/chat/src/lib/streaming/streaming-markdown.registry-override.spec.ts @@ -0,0 +1,145 @@ +// libs/chat/src/lib/streaming/streaming-markdown.registry-override.spec.ts +// +// WHAT THIS PINS. `` provides MARKDOWN_VIEW_REGISTRY on its +// own component injector so `` and the table-row view can +// resolve it. Providing it unconditionally from the component's own default +// made an application- or route-level provider for the same token unreachable: +// the component injector always won. Resolution now runs input first, then an +// ancestor injector, then the built-in default — so an app-wide override +// actually reaches the markdown node components. +import { Component, input, signal } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { overrideViews, type ViewRegistry } from '@threadplane/render'; +import { describe, expect, it } from 'vitest'; +import { cacheplaneMarkdownViews } from '../markdown/cacheplane-markdown-views'; +import { MARKDOWN_VIEW_REGISTRY } from '../markdown/markdown-view-registry'; +import { ChatComponent } from '../compositions/chat/chat.component'; +import { staticDelivery } from '../agent/message-delivery'; +import { mockAgent } from '../testing/mock-agent'; +import { + ChatStreamingMdComponent, + type StreamingMarkdownDocument, +} from './streaming-markdown.component'; + +@Component({ + standalone: true, + selector: 'test-loud-paragraph', + template: `

`, +}) +class LoudParagraphComponent { + /** The markdown node the registry binds onto every view component. */ + readonly node = input(); +} + +const customViews = (): ViewRegistry => + overrideViews(cacheplaneMarkdownViews, { + paragraph: LoudParagraphComponent, + }); + +const doc = (content: string): StreamingMarkdownDocument => ({ + generation: 'g1', + phase: 'complete', + content, +}); + +@Component({ + standalone: true, + imports: [ChatStreamingMdComponent], + template: ``, +}) +class PlainHost { + readonly document = signal(doc('Hello world')); +} + +@Component({ + standalone: true, + imports: [ChatStreamingMdComponent], + template: ``, +}) +class InputHost { + readonly document = signal(doc('Hello world')); + readonly registry = signal(undefined); +} + +@Component({ + standalone: true, + imports: [ChatComponent], + template: ``, +}) +class ChatHost { + readonly agent = mockAgent({ + messages: [ + { + id: 'm1', + role: 'assistant', + content: 'Hello world', + delivery: staticDelivery('m1'), + }, + ], + }); +} + +describe(' markdown view registry resolution', () => { + it('uses the built-in default when nothing overrides it', () => { + const fixture = TestBed.createComponent(PlainHost); + fixture.detectChanges(); + + expect( + fixture.nativeElement.querySelector('.loud-paragraph') + ).toBeNull(); + expect(fixture.nativeElement.querySelector('p')).toBeTruthy(); + }); + + it('uses a registry provided by an ancestor injector', () => { + TestBed.configureTestingModule({ + providers: [ + { provide: MARKDOWN_VIEW_REGISTRY, useValue: customViews() }, + ], + }); + + const fixture = TestBed.createComponent(PlainHost); + fixture.detectChanges(); + + expect( + fixture.nativeElement.querySelector('.loud-paragraph'), + 'an app-level MARKDOWN_VIEW_REGISTRY must reach the markdown nodes' + ).toBeTruthy(); + }); + + it('lets the [viewRegistry] input win over an ancestor provider', () => { + TestBed.configureTestingModule({ + providers: [ + { provide: MARKDOWN_VIEW_REGISTRY, useValue: customViews() }, + ], + }); + + const fixture = TestBed.createComponent(InputHost); + fixture.componentInstance.registry.set(cacheplaneMarkdownViews); + fixture.detectChanges(); + + expect( + fixture.nativeElement.querySelector('.loud-paragraph'), + 'the explicit input is the most specific override' + ).toBeNull(); + expect(fixture.nativeElement.querySelector('p')).toBeTruthy(); + }); + + it('reaches markdown rendered inside without forwarding anything', () => { + TestBed.configureTestingModule({ + providers: [ + { provide: MARKDOWN_VIEW_REGISTRY, useValue: customViews() }, + ], + }); + + const fixture = TestBed.createComponent(ChatHost); + fixture.detectChanges(); + + expect( + fixture.nativeElement.querySelector('.loud-paragraph'), + ' renders assistant markdown through ' + ).toBeTruthy(); + }); +}); From c63be785ddcbecc0aa0782737125f28041ec6106 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 7 Sep 2026 13:54:16 -0700 Subject: [PATCH 3/4] refactor(chat): drop the unreachable 'mixed' content type, widen messageContent, tokenize the popup launcher Three small cleanups: - ContentType no longer carries 'mixed'. createContentClassifier never emitted it, so the `|| currentType === 'mixed'` branch in the delta path was dead and any consumer switch on it had a case no input reaches. The docs said as much and told readers not to branch on it; the member is simply gone instead. - messageContent() takes { content: unknown } rather than LangChain's BaseMessage. Every caller in this library holds the runtime-neutral Message from agent.messages(), so ChatComponent.humanContent carried a cast and the docs told consumers to pass something other than the message they actually have. The function reads nothing but .content, so the structural parameter is the honest signature; the cast is gone. - --tplane-chat-launcher-offset-x / -y (both 1rem) position the launcher, which was pinned with hard-coded corner offsets. The popup window reads the horizontal one too, so it stays aligned when the launcher moves clear of a bottom bar or a consent banner. The new message-utils spec fails to type-check against the old BaseMessage signature (four errors under tsconfig.spec.json) and passes after. The new chat-popup styles spec fails four of its five cases before the change, and its window-alignment guard was mutation-checked by reverting that one declaration. Regenerates the chat and render api-docs. Co-Authored-By: Claude Fable 5.1 --- .../content/docs/chat/api/api-docs.json | 76 +++---------------- .../docs/chat/api/content-classifier.mdx | 5 +- .../chat/components/chat-message-list.mdx | 6 +- .../docs/chat/components/chat-popup.mdx | 17 ++++- .../docs/chat/concepts/message-model.mdx | 2 +- .../content/docs/chat/guides/streaming.mdx | 6 +- .../content/docs/chat/guides/theming.mdx | 2 + .../content/docs/render/api/api-docs.json | 6 +- libs/chat/CHANGELOG.md | 6 ++ .../chat-popup/chat-popup.component.ts | 9 ++- .../chat-popup/chat-popup.styles.spec.ts | 60 +++++++++++++++ .../lib/compositions/chat/chat.component.ts | 7 +- .../compositions/shared/message-utils.spec.ts | 56 ++++++++++++++ .../lib/compositions/shared/message-utils.ts | 25 +++--- .../src/lib/streaming/content-classifier.ts | 4 +- ...reaming-markdown.registry-override.spec.ts | 2 +- libs/chat/src/lib/styles/chat-tokens.ts | 4 + 17 files changed, 192 insertions(+), 101 deletions(-) create mode 100644 libs/chat/src/lib/compositions/chat-popup/chat-popup.styles.spec.ts create mode 100644 libs/chat/src/lib/compositions/shared/message-utils.spec.ts diff --git a/apps/website/content/docs/chat/api/api-docs.json b/apps/website/content/docs/chat/api/api-docs.json index 162182aa1..60d63bddf 100644 --- a/apps/website/content/docs/chat/api/api-docs.json +++ b/apps/website/content/docs/chat/api/api-docs.json @@ -1925,7 +1925,7 @@ }, { "name": "messageContent", - "type": "(message: BaseMessage<>) => string", + "type": "(message: object) => string", "description": "", "optional": false }, @@ -2072,12 +2072,12 @@ }, { "name": "humanContent", - "signature": "humanContent(message: unknown): string", + "signature": "humanContent(message: object): string", "description": "Renderable content for a human-role message bubble. Most human\nmessages are typed prompts and pass through `messageContent`\nunchanged. A2UI action messages (e.g. form submits, button clicks\non a rendered surface) flow through the same submit channel and\nland in the message stream as a HumanMessage whose content is a\nJSON-serialized `A2uiActionMessage`. Showing the raw JSON as if\nthe user typed it leaks the protocol; per the A2UI spec\nthose events resemble tool calls more than user utterances.\n\n`a2uiActionLabel` returns a short human-readable label for\nrecognized action shapes (\"Search flights\", \"Selected flight UA123\",\netc.) — or null for any non-action content, in which case we fall\nback to the original text.", "params": [ { "name": "message", - "type": "unknown", + "type": "object", "description": "", "optional": false } @@ -4002,7 +4002,7 @@ { "name": "resolvedRegistry", "type": "Signal | RenderViewEntry>>>", - "description": "", + "description": "Most specific wins: the `[viewRegistry]` input, then a registry provided by\nan ancestor injector, then the built-in markdown views.", "optional": false }, { @@ -6467,32 +6467,6 @@ ], "examples": [] }, - { - "name": "ChatConfig", - "kind": "interface", - "description": "Application-wide options for provideChat. Every field is optional;\nthe values are exposed to all chat components in the tree via the\n`CHAT_CONFIG` injection token, so you set them once at bootstrap instead of\nthreading props through every component.", - "properties": [ - { - "name": "assistantName", - "type": "string", - "description": "Shared assistant display name for consumers that read CHAT_CONFIG (default: \"Assistant\").", - "optional": true - }, - { - "name": "avatarLabel", - "type": "string", - "description": "Shared AI avatar label for consumers that read CHAT_CONFIG (default: \"A\").", - "optional": true - }, - { - "name": "renderRegistry", - "type": "AngularRegistry", - "description": "Shared render registry for consumers that read CHAT_CONFIG.", - "optional": true - } - ], - "examples": [] - }, { "name": "ChatLifecycle", "kind": "interface", @@ -8468,7 +8442,7 @@ "name": "ContentType", "kind": "type", "description": "", - "signature": "\"pending\" | \"markdown\" | \"json-render\" | \"a2ui\" | \"mixed\"", + "signature": "\"pending\" | \"markdown\" | \"json-render\" | \"a2ui\"", "examples": [] }, { @@ -8590,13 +8564,6 @@ "signature": "ViewRegistry", "examples": [] }, - { - "name": "CHAT_CONFIG", - "kind": "const", - "description": "", - "signature": "InjectionToken", - "examples": [] - }, { "name": "CHAT_LIFECYCLE", "kind": "const", @@ -8614,7 +8581,7 @@ { "name": "MARKDOWN_VIEW_REGISTRY", "kind": "const", - "description": "DI token for the markdown view registry consumed by \nand . Maps MarkdownNode.type strings (e.g. \"paragraph\",\n\"heading\") to Angular components that render that node type.\n\n`` provides the runtime registry on its component-level\ninjector — either the consumer-supplied [viewRegistry] input, or\n`cacheplaneMarkdownViews` (the default) — so descendant \ncomponents resolve the right components for each node.", + "description": "DI token for the markdown view registry consumed by \nand . Maps MarkdownNode.type strings (e.g. \"paragraph\",\n\"heading\") to Angular components that render that node type.\n\n`` provides the resolved registry on its component-level\ninjector so descendant components resolve the right component\nfor each node. It resolves most-specific-first: the `[viewRegistry]` input,\nthen a registry provided by an ancestor injector (application root or route),\nthen `cacheplaneMarkdownViews` (the default). Providing this token at the\napplication root is therefore a supported app-wide override.", "signature": "InjectionToken | RenderViewEntry>>>", "examples": [] }, @@ -9418,13 +9385,13 @@ { "name": "messageContent", "kind": "function", - "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", + "description": "Extracts a human-readable string from a message's content.\n\nMessage content is either a plain string or an array of typed blocks.\nReasoning-capable models (OpenAI gpt-5/o-series, Anthropic) emit complex\narrays: `{type:'text',text}`, `{type:'reasoning',...}`, tool-use blocks, etc.\nOnly the visible text portions are rendered and anything else is skipped.\nStringifying the whole array would dump raw JSON like `[{\"type\":\"text\",...}]`\ninto the chat bubble.\n\nThe parameter is structural on purpose. This function reads nothing but\n`.content`, and callers hold either the runtime-neutral `Message` from\n`agent.messages()` or a LangChain `BaseMessage` depending on where the\nmessage came from. Both satisfy `{ content: unknown }`, so neither has to\ncast.", + "signature": "messageContent(message: object): string", "params": [ { "name": "message", - "type": "BaseMessage<>", - "description": "", + "type": "object", + "description": "Any object carrying a `content` field.", "optional": false } ], @@ -9531,27 +9498,6 @@ }, "examples": [] }, - { - "name": "provideChat", - "kind": "function", - "description": "Bootstrap `@threadplane/chat` in an Angular application or standalone\ncomponent tree.\n\nCall this once inside `bootstrapApplication` (or the `providers` array of a\nroot `ApplicationConfig`). It registers the shared ChatConfig token\nso every chat component in the tree can read the render registry, avatar\nlabel, and assistant display name without explicit prop threading.", - "signature": "provideChat(config: ChatConfig): EnvironmentProviders", - "params": [ - { - "name": "config", - "type": "ChatConfig", - "description": "Options bag that controls the chat feature set:\n - `renderRegistry` — shared AngularRegistry wiring tool-view\n components to their names; pass the value returned by\n `defineAngularRegistry` from `\\@threadplane/render`.\n - `avatarLabel` — short label shown in the AI avatar bubble (default `\"A\"`).\n - `assistantName` — display name shown above assistant messages\n (default `\"Assistant\"`).", - "optional": false - } - ], - "returns": { - "type": "EnvironmentProviders", - "description": "" - }, - "examples": [ - "```ts\n// main.ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { provideChat } from '@threadplane/chat';\nimport { defineAngularRegistry, provideRender } from '@threadplane/render';\nimport { DayCardComponent } from './day-card.component';\n\nconst registry = defineAngularRegistry({ day_card: DayCardComponent });\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideChat({ renderRegistry: registry, avatarLabel: 'AI' }),\n provideRender({ registry }),\n ],\n});\n```" - ] - }, { "name": "renderMarkdown", "kind": "function", @@ -9771,7 +9717,7 @@ { "name": "tools", "kind": "function", - "description": "Collect named client tools into a frozen, name-keyed registry.\n\nThe overload is generic over the entire map (`const M`) so that each tool's\nprecise type (FunctionToolDef``, ViewToolDef``, or\nAskToolDef``) and every literal key are preserved in the\nClientToolRegistry passed to `provideChat`. This lets downstream\nconsumers look up individual tools without losing generic information.", + "description": "Collect named client tools into a frozen, name-keyed registry.\n\nThe overload is generic over the entire map (`const M`) so that each tool's\nprecise type (FunctionToolDef``, ViewToolDef``, or\nAskToolDef``) and every literal key are preserved in the\nClientToolRegistry passed to the `clientTools` input. This lets downstream\nconsumers look up individual tools without losing generic information.", "signature": "tools(map: M): Readonly", "params": [ { diff --git a/apps/website/content/docs/chat/api/content-classifier.mdx b/apps/website/content/docs/chat/api/content-classifier.mdx index 1ceb1dd0f..6c7e6c048 100644 --- a/apps/website/content/docs/chat/api/content-classifier.mdx +++ b/apps/website/content/docs/chat/api/content-classifier.mdx @@ -60,7 +60,7 @@ interface ContentClassifier { ## ContentType ```typescript -type ContentType = 'pending' | 'markdown' | 'json-render' | 'a2ui' | 'mixed'; +type ContentType = 'pending' | 'markdown' | 'json-render' | 'a2ui'; ``` | Value | Meaning | @@ -69,7 +69,8 @@ type ContentType = 'pending' | 'markdown' | 'json-render' | 'a2ui' | 'mixed'; | `markdown` | Plain text / markdown prose | | `json-render` | JSON spec detected (first non-whitespace is `{`) | | `a2ui` | A2UI payload detected via `---a2ui_JSON---` prefix, parsed as JSONL messages | -| `mixed` | Reserved. The current implementation never emits this value. | + +The union has no member for interleaved content: prose with inline JSON-render specs classifies as `markdown` and the markdown path renders the embedded specs in place. ### a2uiSurfaces diff --git a/apps/website/content/docs/chat/components/chat-message-list.mdx b/apps/website/content/docs/chat/components/chat-message-list.mdx index d139580f1..536f2883c 100644 --- a/apps/website/content/docs/chat/components/chat-message-list.mdx +++ b/apps/website/content/docs/chat/components/chat-message-list.mdx @@ -121,15 +121,13 @@ const type = getMessageType(message); // 'human' | 'ai' | 'tool' | 'system' | 'f Runtime-neutral messages have a `content` property that is either a `string` or a `ContentBlock[]`. The library exports a `messageContent()` utility that flattens either shape to a string: ```typescript -import type { BaseMessage } from '@langchain/core/messages'; - // If content is a string, returns it directly. // If content is a block array, concatenates the visible text blocks // (`text` and `output_text`) and skips reasoning, tool-use, and image blocks. -function messageContent(message: BaseMessage): string +function messageContent(message: { content: unknown }): string ``` -Note the parameter type: `messageContent()` consumes the LangChain `BaseMessage` shape, not the runtime-neutral `Message` handed to a `chatMessageTemplate`. Pass it a message from a LangChain-shaped source, not one straight out of `agent.messages()`. +The parameter is structural: the function reads nothing but `content`. Pass it the `Message` handed to a `chatMessageTemplate` straight out of `agent.messages()`, or a LangChain `BaseMessage` from a LangChain-shaped source. Both type-check, and neither needs a cast. For custom templates, access `message.content` directly and narrow the type in the component class. Angular template expressions have no `typeof` operator, so the check has to live in a method: diff --git a/apps/website/content/docs/chat/components/chat-popup.mdx b/apps/website/content/docs/chat/components/chat-popup.mdx index b532ed040..70a48dd32 100644 --- a/apps/website/content/docs/chat/components/chat-popup.mdx +++ b/apps/website/content/docs/chat/components/chat-popup.mdx @@ -126,13 +126,22 @@ Project content into the window header with the `[chatHeader]` slot: ## Styling -The popup uses the standard `--tplane-chat-*` token system. The launcher position is not tokenized: the host is `position: fixed; bottom: 1rem; right: 1rem`, so move it by overriding those properties on the host element from a global stylesheet: +The popup uses the standard `--tplane-chat-*` token system. Two tokens control where the launcher sits, both defaulting to `1rem`: + +| Token | Default | Controls | +|-------|---------|----------| +| `--tplane-chat-launcher-offset-x` | `1rem` | Distance from the right edge of the viewport | +| `--tplane-chat-launcher-offset-y` | `1rem` | Distance from the bottom edge of the viewport | + +Set them anywhere the popup inherits from, such as `:root`, to clear a bottom bar or a consent banner: ```css -chat-popup { - bottom: 1.5rem; - right: 1.5rem; +:root { + --tplane-chat-launcher-offset-x: 1.5rem; + --tplane-chat-launcher-offset-y: 5rem; } ``` +The popup window reads the same horizontal offset, so it stays aligned with the launcher when you move it. Below 640px the window goes full screen and ignores both. + See [Theming](/docs/chat/guides/theming) for the full token reference. diff --git a/apps/website/content/docs/chat/concepts/message-model.mdx b/apps/website/content/docs/chat/concepts/message-model.mdx index 22feed25d..fc2499d2d 100644 --- a/apps/website/content/docs/chat/concepts/message-model.mdx +++ b/apps/website/content/docs/chat/concepts/message-model.mdx @@ -164,7 +164,7 @@ type ContentBlock = Plain text is the common case, and it is the only case in the demo: the LangGraph adapter flattens LangChain content arrays down to their visible text before the message reaches a component, keeping the raw payload on `extra`. Adapters that can preserve more shape emit blocks instead. -Custom templates should therefore check the type before assuming they can interpolate `content` directly. `messageContent()` does this for you, taking a LangChain `BaseMessage` rather than the `Message` shape, and hand-written code can do the same: +Custom templates should therefore check the type before assuming they can interpolate `content` directly. `messageContent()` does this for you and accepts the `Message` shape directly, and hand-written code can do the same: ```ts function textOf(message: Message): string { diff --git a/apps/website/content/docs/chat/guides/streaming.mdx b/apps/website/content/docs/chat/guides/streaming.mdx index 16a033312..eee9a25f0 100644 --- a/apps/website/content/docs/chat/guides/streaming.mdx +++ b/apps/website/content/docs/chat/guides/streaming.mdx @@ -20,7 +20,7 @@ Each AI message is processed by a `ContentClassifier` that examines the content | Prose with inline JSON-render specs | `markdown` | Markdown path, with embedded specs rendered in place | | Any other text | `markdown` | Rendered as markdown prose | -Prose that interleaves inline JSON-render specs still classifies as `'markdown'` — the markdown path renders the embedded specs in place. The `ContentType` union also includes `'mixed'`, but `createContentClassifier` does not currently emit it, so treat it as reserved: do not branch on `classifier.type() === 'mixed'` expecting inline-spec content to land there. (`'a2ui'` is covered under [A2UI Content Detection](#a2ui-content-detection) below.) +Prose that interleaves inline JSON-render specs still classifies as `'markdown'` — the markdown path renders the embedded specs in place. There is no separate type for interleaved content. (`'a2ui'` is covered under [A2UI Content Detection](#a2ui-content-detection) below.) Each message gets its own classifier instance. Classification happens once per message — the type is determined by the first meaningful character and never changes. @@ -68,7 +68,7 @@ classifier.dispose(); | Signal | Type | Description | |--------|------|-------------| -| `type` | `Signal` | `'pending'`, `'markdown'`, `'json-render'`, `'a2ui'`, or `'mixed'` | +| `type` | `Signal` | `'pending'`, `'markdown'`, `'json-render'`, or `'a2ui'` | | `markdown` | `Signal` | Accumulated markdown prose (empty for pure JSON) | | `spec` | `Signal` | Materialized JSON-render spec with structural sharing | | `elementStates` | `Signal>` | Per-element tracking of which properties have been received | @@ -77,7 +77,7 @@ classifier.dispose(); ### ContentType ```typescript -type ContentType = 'pending' | 'markdown' | 'json-render' | 'a2ui' | 'mixed'; +type ContentType = 'pending' | 'markdown' | 'json-render' | 'a2ui'; ``` ## Using ParseTreeStore Directly diff --git a/apps/website/content/docs/chat/guides/theming.mdx b/apps/website/content/docs/chat/guides/theming.mdx index 7db0e5074..4458578c8 100644 --- a/apps/website/content/docs/chat/guides/theming.mdx +++ b/apps/website/content/docs/chat/guides/theming.mdx @@ -94,6 +94,8 @@ Three shadow tokens, `--tplane-chat-shadow-sm`, `--tplane-chat-shadow-md`, and ` | `--tplane-chat-radius-card` | `8px` | Tool call cards, citations, generative UI surfaces | | `--tplane-chat-radius-button` | `8px` | Buttons in panels and lists | | `--tplane-chat-radius-launcher` | `9999px` | Circular launcher button | +| `--tplane-chat-launcher-offset-x` | `1rem` | Distance from the viewport right edge to the `` launcher and window | +| `--tplane-chat-launcher-offset-y` | `1rem` | Distance from the viewport bottom edge to the `` launcher | | `--tplane-chat-max-width` | `48rem` | Message column and input width | | `--tplane-chat-edge-pad` | `16px` | Horizontal padding at the edges of the conversation | | `--tplane-chat-space-1` … `--tplane-chat-space-6`, `--tplane-chat-space-8` | `4px` … `24px`, `32px` | Internal spacing scale | diff --git a/apps/website/content/docs/render/api/api-docs.json b/apps/website/content/docs/render/api/api-docs.json index 0702947a5..35e8c0965 100644 --- a/apps/website/content/docs/render/api/api-docs.json +++ b/apps/website/content/docs/render/api/api-docs.json @@ -790,7 +790,7 @@ { "name": "defineAngularRegistry", "kind": "function", - "description": "Build an AngularRegistry from a plain object mapping tool-call names\nto Angular components (or fully specified RenderViewEntry objects).\n\nThe returned registry is consumed by both `provideRender` (to drive\ndynamic component rendering) and `provideChat` (via `renderRegistry`) so\nthat a single `defineAngularRegistry` call wires both layers.\n\n**Entry forms**\n- Bare `Type` — the component is paired with the built-in\n `DefaultFallbackComponent` while its props are still streaming.\n- `RenderViewEntry` object — lets you supply a custom `fallback` component,\n an optional Standard Schema (`schema`) used as a mount-readiness gate, and\n an optional `description` for model-facing tool registration.\n\n**Registry accessor**\nThe returned object exposes a single `getEntry(name: string)` accessor that\nreturns the fully-normalized NormalizedEntry (component + fallback +\noptional schema + optional description) or `undefined` when the name is not\nregistered. Use `names()` to enumerate all registered names.", + "description": "Build an AngularRegistry from a plain object mapping tool-call names\nto Angular components (or fully specified RenderViewEntry objects).\n\nThe returned registry is consumed by `provideRender` to drive dynamic\ncomponent rendering.\n\n**Entry forms**\n- Bare `Type` — the component is paired with the built-in\n `DefaultFallbackComponent` while its props are still streaming.\n- `RenderViewEntry` object — lets you supply a custom `fallback` component,\n an optional Standard Schema (`schema`) used as a mount-readiness gate, and\n an optional `description` for model-facing tool registration.\n\n**Registry accessor**\nThe returned object exposes a single `getEntry(name: string)` accessor that\nreturns the fully-normalized NormalizedEntry (component + fallback +\noptional schema + optional description) or `undefined` when the name is not\nregistered. Use `names()` to enumerate all registered names.", "signature": "defineAngularRegistry(componentMap: RegistryInput): AngularRegistry", "params": [ { @@ -852,7 +852,7 @@ { "name": "provideRender", "kind": "function", - "description": "Bootstrap `@threadplane/render` in an Angular application or standalone\ncomponent tree.\n\nRegisters the shared RenderConfig token and the internal\n`RenderLifecycleService` that coordinates mount/unmount events across\ndynamically rendered components. Call this once alongside `provideChat` in\n`bootstrapApplication` (or the root `ApplicationConfig`).", + "description": "Bootstrap `@threadplane/render` in an Angular application or standalone\ncomponent tree.\n\nRegisters the shared RenderConfig token and the internal\n`RenderLifecycleService` that coordinates mount/unmount events across\ndynamically rendered components. Call this once in `bootstrapApplication`\n(or the root `ApplicationConfig`).", "signature": "provideRender(config: RenderConfig): EnvironmentProviders", "params": [ { @@ -867,7 +867,7 @@ "description": "" }, "examples": [ - "```ts\n// main.ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { defineAngularRegistry, provideRender } from '@threadplane/render';\nimport { provideChat } from '@threadplane/chat';\nimport { DayCardComponent } from './day-card.component';\n\nconst registry = defineAngularRegistry({ day_card: DayCardComponent });\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideRender({ registry }),\n provideChat({ renderRegistry: registry }),\n ],\n});\n```" + "```ts\n// main.ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { defineAngularRegistry, provideRender } from '@threadplane/render';\nimport { DayCardComponent } from './day-card.component';\n\nconst registry = defineAngularRegistry({ day_card: DayCardComponent });\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideRender({ registry }),\n ],\n});\n```" ] }, { diff --git a/libs/chat/CHANGELOG.md b/libs/chat/CHANGELOG.md index 917689a39..c7a9af888 100644 --- a/libs/chat/CHANGELOG.md +++ b/libs/chat/CHANGELOG.md @@ -10,8 +10,14 @@ - **An application-level `MARKDOWN_VIEW_REGISTRY` provider now takes effect.** `` provided the token on its own injector from its own default, which shadowed any provider at the application root or on a route. It now resolves most-specific-first — the `[viewRegistry]` input, then an ancestor injector, then `cacheplaneMarkdownViews` — so one root provider overrides markdown rendering across every chat surface, including inside ``, with nothing to forward. +### Added + +- **`--tplane-chat-launcher-offset-x` / `--tplane-chat-launcher-offset-y`** (both `1rem`) position the `` launcher, replacing hard-coded corner offsets. The popup window reads the horizontal one too, so it stays aligned when you move the launcher clear of a bottom bar or a consent banner. + ### Changed +- **`messageContent()` takes any `{ content: unknown }`.** It was typed against LangChain's `BaseMessage` while every caller holds the runtime-neutral `Message` from `agent.messages()`, which forced a cast. The function only ever reads `.content`, so the parameter is now structural and both shapes type-check. +- **`ContentType` no longer includes `'mixed'`.** `createContentClassifier` never emitted it, so any consumer branching on it had dead code. Prose with inline JSON-render specs classifies as `'markdown'`, as it always did. - **`@angular/forms` peer dependency removed:** `chat-input` now binds its textarea with a direct `[value]`/`(input)` pair (fixes the composer keeping sent text under zoneless + OnPush). `@threadplane/chat` no longer requires `@angular/forms` — consumers may drop it unless they use it themselves. - **json-render store isolation:** ``'s json-render message surfaces no longer fall back to the conversation-wide internal store — each surface self-seeds from its spec's `state` unless you pass an explicit `[store]`. Pass `[store]` (e.g. `signalStateStore({})`) when dashboards should receive backend agent state (STATE_SNAPSHOT) or share live values across surfaces; same-key dashboards in different messages are now isolated by default. Tool views (`chat-tool-views`) keep the previous shared-store behavior. - **Public API trim:** `@threadplane/chat` no longer re-exports `provideViews` / `VIEW_REGISTRY` from `@threadplane/render`. Consumers using `` / `` directly should import from `@threadplane/render`. For chat's markdown view overrides, pass `overrideViews(cacheplaneMarkdownViews, { … })` from `@threadplane/render` to the `[viewRegistry]` input on ``, or provide the same value for `MARKDOWN_VIEW_REGISTRY` at the application root or on a route to override every markdown surface at once. The previously-documented `provideViews(withViews(…))` pattern never drove rendering. diff --git a/libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts b/libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts index 472402173..9e5c75d00 100644 --- a/libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts +++ b/libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts @@ -14,12 +14,17 @@ import { CHAT_HOST_TOKENS, ensureChatRootStyles } from '../../styles/chat-tokens imports: [ChatComponent, ChatLauncherButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, styles: [CHAT_HOST_TOKENS, ` - :host { position: fixed; bottom: 1rem; right: 1rem; z-index: var(--tplane-chat-z-overlay-content, 30); } + :host { + position: fixed; + bottom: var(--tplane-chat-launcher-offset-y); + right: var(--tplane-chat-launcher-offset-x); + z-index: var(--tplane-chat-z-overlay-content, 30); + } .chat-popup__launcher { position: relative; } .chat-popup__window { position: fixed; bottom: 5rem; - right: 1rem; + right: var(--tplane-chat-launcher-offset-x); width: 24rem; height: 600px; max-height: calc(100vh - 6rem); diff --git a/libs/chat/src/lib/compositions/chat-popup/chat-popup.styles.spec.ts b/libs/chat/src/lib/compositions/chat-popup/chat-popup.styles.spec.ts new file mode 100644 index 000000000..de569a916 --- /dev/null +++ b/libs/chat/src/lib/compositions/chat-popup/chat-popup.styles.spec.ts @@ -0,0 +1,60 @@ +// libs/chat/src/lib/compositions/chat-popup/chat-popup.styles.spec.ts +// +// WHAT THIS PINS. The popup launcher was pinned to the viewport corner with a +// hard-coded `bottom: 1rem; right: 1rem`, so an application with a bottom bar, +// a cookie banner, or its own floating control had no way to move it short of +// overriding the component's styles. Two custom properties make the offsets +// part of the theming surface, and the anchored window has to read the same +// horizontal offset or an override would leave it misaligned with the button. +import { describe, expect, it } from 'vitest'; +import { ChatPopupComponent } from './chat-popup.component'; +import { ROOT_TOKEN_STYLES } from '../../styles/chat-tokens'; + +/** The component's compiled style strings. */ +function popupStyles(): string { + const meta = ChatPopupComponent as unknown as { + ɵcmp: { styles: readonly string[] }; + }; + return meta.ɵcmp.styles.join('\n'); +} + +/** + * The declaration block whose selector contains `selector`. Compiled styles + * carry Angular's `[_ngcontent-…]` scoping attributes, so the selector text is + * matched loosely and only the braces are used to find the block's end. + */ +function ruleBody(styles: string, selector: string): string { + const at = styles.indexOf(selector); + expect(at, `no rule found for ${selector}`).toBeGreaterThanOrEqual(0); + const open = styles.indexOf('{', at); + return styles.slice(open + 1, styles.indexOf('}', open)); +} + +describe('chat-popup launcher offset tokens', () => { + it.each([ + '--tplane-chat-launcher-offset-x: 1rem;', + '--tplane-chat-launcher-offset-y: 1rem;', + ])('defines a default for %s on :root', (decl) => { + expect(ROOT_TOKEN_STYLES).toContain(decl); + }); + + it('positions the launcher host from the offset tokens', () => { + const styles = popupStyles(); + + expect(styles).toContain('bottom: var(--tplane-chat-launcher-offset-y)'); + expect(styles).toContain('right: var(--tplane-chat-launcher-offset-x)'); + }); + + it('anchors the popup window to the same horizontal offset', () => { + expect( + ruleBody(popupStyles(), '.chat-popup__window'), + 'the window must track the launcher when the offset is overridden' + ).toContain('right: var(--tplane-chat-launcher-offset-x)'); + }); + + it('leaves no hard-coded corner offsets on the launcher host', () => { + expect(ruleBody(popupStyles(), '[_nghost')).not.toMatch( + /(bottom|right):\s*1rem/ + ); + }); +}); diff --git a/libs/chat/src/lib/compositions/chat/chat.component.ts b/libs/chat/src/lib/compositions/chat/chat.component.ts index 1228dcb89..aee63817a 100644 --- a/libs/chat/src/lib/compositions/chat/chat.component.ts +++ b/libs/chat/src/lib/compositions/chat/chat.component.ts @@ -483,11 +483,8 @@ export class ChatComponent { * etc.) — or null for any non-action content, in which case we fall * back to the original text. */ - protected humanContent(message: unknown): string { - // Cast: `messageContent` is typed against LangChain's BaseMessage, but - // templates iterate the chat-lib's looser `Message` shape. Either type - // is fine at runtime (`extractText` only reads `.content`). - const raw = messageContent(message as Parameters[0]); + protected humanContent(message: { content: unknown }): string { + const raw = messageContent(message); return a2uiActionLabel(raw) ?? raw; } diff --git a/libs/chat/src/lib/compositions/shared/message-utils.spec.ts b/libs/chat/src/lib/compositions/shared/message-utils.spec.ts new file mode 100644 index 000000000..bdb79dc68 --- /dev/null +++ b/libs/chat/src/lib/compositions/shared/message-utils.spec.ts @@ -0,0 +1,56 @@ +// libs/chat/src/lib/compositions/shared/message-utils.spec.ts +// +// WHAT THIS PINS. `messageContent()` was typed against LangChain's +// `BaseMessage`, but every caller in this library holds the runtime-neutral +// `Message` from `agent.messages()`. The two shapes are interchangeable for +// this function — it only reads `.content` — so callers were forced to cast, +// and consumers writing a `chatMessageTemplate` were told in the docs to pass +// something other than the message they actually have. The parameter is now +// structural, so a `Message` type-checks directly. +import { describe, expect, it } from 'vitest'; +import { AIMessage, HumanMessage } from '@langchain/core/messages'; +import type { Message } from '../../agent/message'; +import { staticDelivery } from '../../agent/message-delivery'; +import { messageContent } from './message-utils'; + +describe('messageContent()', () => { + it('accepts the library Message shape without a cast', () => { + const message: Message = { + id: 'm1', + role: 'assistant', + content: 'hello world', + delivery: staticDelivery('m1'), + }; + + expect(messageContent(message)).toBe('hello world'); + }); + + it('extracts visible text from a library Message with complex content', () => { + const message: Message = { + id: 'm2', + role: 'assistant', + content: [ + { type: 'reasoning', text: 'thinking' }, + { type: 'text', text: 'Hello' }, + { type: 'text', text: ' world' }, + ] as unknown as Message['content'], + delivery: staticDelivery('m2'), + }; + + expect(messageContent(message)).toBe('Hello world'); + }); + + it('still accepts a LangChain BaseMessage', () => { + expect(messageContent(new HumanMessage('typed prompt'))).toBe( + 'typed prompt' + ); + expect( + messageContent(new AIMessage({ content: [{ type: 'text', text: 'hi' }] })) + ).toBe('hi'); + }); + + it('returns an empty string for content it cannot render', () => { + expect(messageContent({ content: 42 })).toBe(''); + expect(messageContent({ content: null })).toBe(''); + }); +}); diff --git a/libs/chat/src/lib/compositions/shared/message-utils.ts b/libs/chat/src/lib/compositions/shared/message-utils.ts index 371db7b73..4789b8ac0 100644 --- a/libs/chat/src/lib/compositions/shared/message-utils.ts +++ b/libs/chat/src/lib/compositions/shared/message-utils.ts @@ -1,16 +1,23 @@ -import type { BaseMessage } from '@langchain/core/messages'; - /** * Extracts a human-readable string from a message's content. * - * `BaseMessage.content` is `string | MessageContentComplex[]`. Reasoning- - * capable models (OpenAI gpt-5/o-series, Anthropic) emit complex arrays of - * typed blocks: `{type:'text',text}`, `{type:'reasoning',...}`, tool-use - * blocks, etc. We render only the visible text portions and skip anything - * else. Stringifying the whole array would dump raw JSON like - * `[{"type":"text",...}]` into the chat bubble. + * Message content is either a plain string or an array of typed blocks. + * Reasoning-capable models (OpenAI gpt-5/o-series, Anthropic) emit complex + * arrays: `{type:'text',text}`, `{type:'reasoning',...}`, tool-use blocks, etc. + * Only the visible text portions are rendered and anything else is skipped. + * Stringifying the whole array would dump raw JSON like `[{"type":"text",...}]` + * into the chat bubble. + * + * The parameter is structural on purpose. This function reads nothing but + * `.content`, and callers hold either the runtime-neutral `Message` from + * `agent.messages()` or a LangChain `BaseMessage` depending on where the + * message came from. Both satisfy `{ content: unknown }`, so neither has to + * cast. + * + * @param message Any object carrying a `content` field. + * @returns The concatenated visible text, or `''` when there is none. */ -export function messageContent(message: BaseMessage): string { +export function messageContent(message: { content: unknown }): string { return extractText(message.content); } diff --git a/libs/chat/src/lib/streaming/content-classifier.ts b/libs/chat/src/lib/streaming/content-classifier.ts index 0d580f45b..5d4c1a085 100644 --- a/libs/chat/src/lib/streaming/content-classifier.ts +++ b/libs/chat/src/lib/streaming/content-classifier.ts @@ -7,7 +7,7 @@ import type { A2uiSurface } from '@threadplane/a2ui'; import { createA2uiSurfaceStore, type A2uiSurfaceStore, type A2uiSurfaceState } from '../a2ui/surface-store'; import { isTraceEnabled, trace } from './trace'; -export type ContentType = 'pending' | 'markdown' | 'json-render' | 'a2ui' | 'mixed'; +export type ContentType = 'pending' | 'markdown' | 'json-render' | 'a2ui'; const A2UI_PREFIX = '---a2ui_JSON---'; @@ -211,7 +211,7 @@ export function createContentClassifier(): ContentClassifier { if (delta.length === 0) return; - if (currentType === 'markdown' || currentType === 'mixed') { + if (currentType === 'markdown') { markdownSignal.set(content); } else if (currentType === 'json-render') { if (store) { diff --git a/libs/chat/src/lib/streaming/streaming-markdown.registry-override.spec.ts b/libs/chat/src/lib/streaming/streaming-markdown.registry-override.spec.ts index c0c459e6d..732b88f12 100644 --- a/libs/chat/src/lib/streaming/streaming-markdown.registry-override.spec.ts +++ b/libs/chat/src/lib/streaming/streaming-markdown.registry-override.spec.ts @@ -23,7 +23,7 @@ import { @Component({ standalone: true, - selector: 'test-loud-paragraph', + selector: 'chat-test-loud-paragraph', template: `

`, }) class LoudParagraphComponent { diff --git a/libs/chat/src/lib/styles/chat-tokens.ts b/libs/chat/src/lib/styles/chat-tokens.ts index 97fe9d86a..a8099b0ca 100644 --- a/libs/chat/src/lib/styles/chat-tokens.ts +++ b/libs/chat/src/lib/styles/chat-tokens.ts @@ -137,6 +137,10 @@ const GEOMETRY_TOKENS = ` --tplane-chat-radius-card: 8px; --tplane-chat-radius-button: 8px; --tplane-chat-radius-launcher: 9999px; + /* Distance from the viewport corner to the launcher. The popup + window tracks the horizontal offset so both move together. */ + --tplane-chat-launcher-offset-x: 1rem; + --tplane-chat-launcher-offset-y: 1rem; --tplane-chat-max-width: 48rem; --tplane-chat-citation-radius: 6px; `; From 3c4e865daae20e5e9c32f9f54cb85a41e6065815 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 7 Sep 2026 20:53:25 -0700 Subject: [PATCH 4/4] fix(chat): drop the now-unused @langchain/core peer dependency Widening messageContent() to { content: unknown } removed the last LangChain import from the published package, so @nx/dependency-checks fails the declared peer. It stays a peer of @threadplane/langgraph, which is why the install command still lists it. Co-Authored-By: Claude Opus 5 --- .../content/docs/chat/getting-started/installation.mdx | 5 ++--- libs/chat/CHANGELOG.md | 3 +++ libs/chat/package.json | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/website/content/docs/chat/getting-started/installation.mdx b/apps/website/content/docs/chat/getting-started/installation.mdx index 425d21137..ada385505 100644 --- a/apps/website/content/docs/chat/getting-started/installation.mdx +++ b/apps/website/content/docs/chat/getting-started/installation.mdx @@ -58,15 +58,14 @@ every required peer for you, so the install command above is enough. | `@threadplane/render` | `0.0.66` | yes | | `@threadplane/a2ui` | `0.0.66` | yes | | `@json-render/core` | `^0.16.0` | yes | -| `@langchain/core` | `^1.1.33` | yes | | `rxjs` | `~7.8.0` | yes | | `marked` | `^15.0.0 \|\| ^16.0.0` | yes | | `zod` | `^3.25.0` | yes | | `katex` | `^0.16.0 \|\| ^0.17.0` | optional | Installing `@threadplane/chat` therefore also brings in `@threadplane/telemetry`, -`@threadplane/render`, `@threadplane/a2ui`, `@json-render/core`, -`@langchain/core`, `rxjs`, `marked`, and `zod`. +`@threadplane/render`, `@threadplane/a2ui`, `@json-render/core`, `rxjs`, +`marked`, and `zod`.
## 2. Configure the runtime diff --git a/libs/chat/CHANGELOG.md b/libs/chat/CHANGELOG.md index c7a9af888..414040435 100644 --- a/libs/chat/CHANGELOG.md +++ b/libs/chat/CHANGELOG.md @@ -4,6 +4,9 @@ ### Removed +- **`@langchain/core` is no longer a peer dependency.** `messageContent()` now takes + any `{ content: unknown }`, so nothing in the published package imports LangChain. + Keep installing it for `@threadplane/langgraph`, which still requires it. - **`provideChat()`, `ChatConfig`, and `CHAT_CONFIG` are gone.** No component in the library ever injected the token, so calling `provideChat({})` configured nothing: `renderRegistry`, `avatarLabel`, and `assistantName` were values only your own wrappers could read back. Delete the call and the import; `provideAgent()` from your runtime adapter is the only provider the chat components require, and everything they render is driven by component inputs. If you were reading `CHAT_CONFIG` from your own components, define your own injection token for those values. ### Fixed diff --git a/libs/chat/package.json b/libs/chat/package.json index cae6db522..5cb09ea9a 100644 --- a/libs/chat/package.json +++ b/libs/chat/package.json @@ -27,7 +27,6 @@ "@threadplane/render": "0.0.66", "@threadplane/a2ui": "0.0.66", "@json-render/core": "^0.16.0", - "@langchain/core": "^1.1.33", "rxjs": "~7.8.0", "marked": "^15.0.0 || ^16.0.0", "katex": "^0.16.0 || ^0.17.0"