From 1f324d01892f6c4d7407435d3e506bbe48c7d71e Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 19 Jun 2026 07:52:54 -0700 Subject: [PATCH 1/5] =?UTF-8?q?docs(dx):=20TypeScript=20DX=20audit=20?= =?UTF-8?q?=E2=80=94=20methodology=20+=20graded=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit of the dev-facing @threadplane/* public surface (chat/ag-ui/langgraph/ render, 302 exports). Method: TypeDoc extraction for JSDoc/signature grading + real tsserver quick-info probing for hover/inference. Findings: JSDoc is the dominant gap (0/66 fns have @example, 60/66 undocumented params, 21 missing summaries); view/ask don't infer component inputs from schema; 8 internal consts leak into the public surface; hover readability is otherwise clean. Includes a triage table for fix sequencing. Co-Authored-By: Claude Fable 5 --- .../specs/2026-06-19-ts-dx-audit-design.md | 33 +++++++++ .../specs/2026-06-19-ts-dx-audit-findings.md | 72 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-19-ts-dx-audit-design.md create mode 100644 docs/superpowers/specs/2026-06-19-ts-dx-audit-findings.md diff --git a/docs/superpowers/specs/2026-06-19-ts-dx-audit-design.md b/docs/superpowers/specs/2026-06-19-ts-dx-audit-design.md new file mode 100644 index 00000000..e756a7eb --- /dev/null +++ b/docs/superpowers/specs/2026-06-19-ts-dx-audit-design.md @@ -0,0 +1,33 @@ +# TypeScript DX Audit — Methodology & Rubric + +**Status:** Approved (brainstorm 2026-06-19) + +**Goal:** Audit the developer-facing `@threadplane/*` public TypeScript surface for **intellisense accuracy and readability** — hover/quick-info, generic inference, inline JSDoc guidance, and misuse-error legibility — producing a **graded findings report** that we triage into fixes. Carries forward the DX bar from prior framework work. + +## Scope + +- **Libraries (dev-facing core only):** `@threadplane/chat`, `@threadplane/ag-ui`, `@threadplane/langgraph`, `@threadplane/render`. The other 14 published libs (cockpit-*, db, middleware, licensing, design-tokens, telemetry, a2ui) are internal/infra and out of scope. +- **Breadth:** every public export (302 symbols) graded; deeper detail on the authoring surface (functions, providers, injectables, tokens, option/return types). +- **Deliverable:** findings report → triage. Fixes are a *separate* effort decided after the report. + +## Method (Approach B — TS-API extraction + rubric grading) + +1. **Mechanical extraction.** Reuse the existing TypeDoc walk (`apps/website/scripts/generate-api-docs.ts`, already emits `apps/website/content/docs//api/api-docs.json`) for per-export name, kind, signature, JSDoc summary, `@param` descriptions, `@returns`, `@example`. This grades the JSDoc + signature-shape dimensions across all 302 symbols. +2. **Real-hover verification.** For the type dimensions TypeDoc can't judge faithfully, probe the **actual TypeScript `quickInfo`** via the language service (`ts.createLanguageService().getQuickInfoAtPosition` against a synthetic usage file). This is what the IDE shows on hover — it catches leaked internal generics and confirms/refutes inference, and guards against TypeDoc rendering artifacts (e.g. TypeDoc renders `StandardSchemaV1<>` where the real hover is the clean `StandardSchemaV1`). +3. **Source confirmation.** For inference + misuse-error findings, read the definition site. + +## Rubric — 5 dimensions, graded ✅ good / 🟡 minor / 🔴 needs-work per symbol + +1. **Hover readability** — clean quick-info; no leaked internal generics (`__type`, deep conditional types, workspace-internal names). +2. **Generic inference** — generics flow as a developer expects (e.g. `action` infers handler args from the schema ✅; `view`/`ask` do *not* infer component inputs 🔴). +3. **JSDoc** — summary present + accurate; `@param`/`@returns`/`@example` where they help. +4. **Misuse-error legibility** — wrong/missing args yield a readable TS error, not conditional-type noise. +5. **`.d.ts` leak / surface hygiene** — emitted declarations don't expose workspace-internal types; internal-only consts aren't in the public surface. + +## Output + +`2026-06-19-ts-dx-audit-findings.md`: exec summary (counts + top themes) → per-lib graded summary → detailed findings by theme with concrete recommendation + example → a final **triage table** (impact × effort) for picking fixes. + +## Reusability + +The extraction harness (TypeDoc gap-grading + the tsserver hover probe) can be promoted to a **CI guard** later so the DX bar holds — out of scope for the audit itself, noted as a follow-up. diff --git a/docs/superpowers/specs/2026-06-19-ts-dx-audit-findings.md b/docs/superpowers/specs/2026-06-19-ts-dx-audit-findings.md new file mode 100644 index 00000000..1cb41a48 --- /dev/null +++ b/docs/superpowers/specs/2026-06-19-ts-dx-audit-findings.md @@ -0,0 +1,72 @@ +# TypeScript DX Audit — Findings + +**Date:** 2026-06-19 · **Scope:** `@threadplane/chat`, `ag-ui`, `langgraph`, `render` (302 public exports) +**Method:** TypeDoc extraction (`api-docs.json`) for JSDoc/signature grading across all exports + real `tsserver` quick-info probing for hover/inference on the authoring surface + source confirmation. See `2026-06-19-ts-dx-audit-design.md`. + +## Executive summary + +The public **type shapes and hovers are in good condition** — the IDE quick-info for the headline authoring APIs is clean and readable, and generic inference works where it's been wired (`action`). The gap is almost entirely **inline guidance (JSDoc)** and a few **surface-hygiene + inference** items. + +| Theme | Severity | One-line | +|---|---|---| +| **JSDoc: zero `@example` coverage** | 🔴 high | 0 of 66 public functions across the 4 libs carry an `@example`. | +| **JSDoc: undocumented params** | 🔴 high | 60 of 66 functions have ≥1 `@param` with no description. | +| **JSDoc: missing summaries** | 🟡 med | 21 functions + many option interfaces have no summary at all — incl. `provideChat`, `provideRender`, `provideViews`, `mockAgent`. | +| **Inference: `view`/`ask` don't infer component inputs** | 🟡 med | `schema: StandardSchemaV1` + `component: Type` — no schema→component link, unlike `action`. | +| **Surface hygiene: internal consts exported** | 🟡 med | 8 chat consts (`CHAT_MARKDOWN_STYLES`, 7 `ICON_*`) leak into the public API as raw string literals. | +| **Hover readability** | ✅ good | Headline symbols render clean quick-info; no `__type`/internal-generic leaks found. | + +## Per-library grades + +| Lib | exports | fns | no summary (fn) | undoc params (fn) | no `@example` (fn) | option ifaces w/o summary | hover | +|---|---|---|---|---|---|---|---| +| chat | 212 | 42 | 15 | 39 | 42 | 27/42 | ✅ | +| ag-ui | 10 | 5 | 1 | 4 | 5 | 1/4 | ✅ | +| langgraph | 43 | 9 | 1 | 8 | 7 | 4/20 | ✅ | +| render | 37 | 10 | 4 | 9 | 10 | 11/13 | ✅ | +| **total** | **302** | **66** | **21** | **60** | **66** | — | — | + +## Findings + +### F1 — Zero `@example` on the public function surface (🔴 high) +**Every** exported function across all four libs (66/66) lacks an `@example`. For an app-developer-facing framework, the hover example is the highest-leverage piece of guidance — it's what lets a dev use `view`/`action`/`provideChat`/`provideViews` without leaving the editor. Today the hover shows the signature + (sometimes) a one-line summary, never a usage snippet. +**Recommendation:** add a concise `@example` to the authoring surface first — `tools/action/view/ask`, `provideChat`, `provideAgent` (both adapters), `injectAgent`, `injectRenderHost`, `provideRender`, `provideViews`, `clientToolsChannel`, `injectThreadRouting`, `createAgentRef`, `mockAgent`. ~15 symbols cover the 80% path. + +### F2 — Undocumented `@param`s (🔴 high) +60/66 functions have at least one parameter with no description. Example — `view`/`ask`/`action` have a summary but document none of `description` / `schema` / `component` / `handler`, so hovering a parameter shows only its type. `provideChat(config: ChatConfig)` doesn't describe `config`, and `ChatConfig`'s own members are largely undocumented (see F3). +**Recommendation:** `@param` lines on the authoring surface; for provider configs, prefer documenting the **option interface members** (richer hover at the call site) over the `@param` on the config bag. + +### F3 — Missing summaries on entrypoints + option interfaces (🟡 med) +21 functions have no summary, including high-traffic ones: `provideChat`, `provideRender`, `provideViews`, `defineAngularRegistry`, `mockAgent`, `signalStateStore`, `getInterrupt`, and the message type-guards (`isUserMessage`/`isAssistantMessage`/`isSystemMessage`/`isToolMessage`/`isTyping`). Option/config interfaces are widely undocumented (chat 27/42, render 11/13) — these are exactly the objects a developer fills in, so undocumented members = blind authoring. +**Recommendation:** one-line summaries on the 21 functions; member docs on the public config interfaces (`ChatConfig`, render provider options, `AgentConfig`). + +### F4 — `view`/`ask` don't infer component input types from the schema (🟡 med) +`action(desc, schema, handler)` correctly infers the handler's args from the schema (`StandardSchemaInferOutput`). But `view(desc, schema, component: Type)` and `ask(...)` are **not** generic — the component is `Type`, so there's no compile-time link between the schema the model fills and the component's declared inputs. A developer can pass a component whose `@Input()`s don't match the schema with no type error. +**Recommendation (triage candidate, may touch signatures + type-tests):** make `view`/`ask` generic over the schema and constrain `component` to a `Type<>` whose inputs are assignable from `StandardSchemaInferOutput`. Verify with type-tests. This is the one genuinely *type-level* improvement; everything else is docs/hygiene. + +### F5 — Internal-only consts in the public surface (🟡 med) +8 chat exports are raw string-literal constants that read as internal implementation: `CHAT_MARKDOWN_STYLES` (a 6.8 KB CSS string) and `ICON_AGENT`/`ICON_CHECK`/`ICON_CHEVRON_DOWN`/`ICON_CHEVRON_UP`/`ICON_SEND`/`ICON_TOOL`/`ICON_WARNING` (inline SVG strings). They bloat the export list, produce noisy hovers, and imply a support contract. +**Recommendation:** mark `@internal` (and drop from `public-api.ts`) unless there's a deliberate theming/customization contract — if there is, document it; if not, remove from the surface. + +### F6 — Hover readability is good (✅ — positive finding) +Real `tsserver` quick-info for the headline symbols is clean and faithful, e.g.: +- `view` → `function view(description: string, schema: StandardSchemaV1, component: Type): ClientToolDef` +- `action` → `function action(description: string, schema: S, handler: (args: StandardSchemaInferOutput) => unknown | Promise): FunctionToolDef` + +No `__type`/anonymous-object or internal-generic leaks were found on the authoring surface. (Note: TypeDoc's `api-docs.json` renders some generics as `StandardSchemaV1<>` — a TypeDoc artifact, **not** what the IDE shows; do not treat as a finding.) + +## Triage table (impact × effort) + +| # | Fix | Impact | Effort | Breaking | Recommendation | +|---|---|---|---|---|---| +| F1 | `@example` on ~15 authoring symbols | high | low | no | **Do first** | +| F2/F3 | `@param` + summaries on authoring fns + config interface members | high | med | no | **Do first** (same PR as F1) | +| F5 | `@internal`/remove `ICON_*` + `CHAT_MARKDOWN_STYLES` | med | low | maybe (if consumed) | Do — verify no external consumer first | +| F4 | generic `view`/`ask` inferring component inputs | med | med-high | possibly | Triage — highest type-level value; needs type-tests + a design check on the component-input constraint | +| — | promote the extraction harness to a CI JSDoc-coverage guard | med | med | no | Optional follow-up — keeps the bar from regressing | + +## Suggested sequencing +1. **PR 1 (docs sweep, non-breaking):** F1 + F2 + F3 on the authoring surface and public config interfaces. Pure additive JSDoc; regenerate `api-docs.json`. +2. **PR 2 (hygiene):** F5 — `@internal`/remove leaked consts after a consumer check. +3. **PR 3 (types, its own design):** F4 — generic `view`/`ask`, type-tested. +4. **Optional:** CI guard for JSDoc coverage on the dev-facing public surface. From ed760f7e235e2e4e41070894868f880fa7e36fe6 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 19 Jun 2026 16:17:01 -0700 Subject: [PATCH 2/5] =?UTF-8?q?docs(dx):=20JSDoc=20sweep=20=E2=80=94=20und?= =?UTF-8?q?ocumented=20dev-facing=20helpers=20(audit=20PR=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-baselined the TS DX audit against post-#685 main (#685 already documented tools/action/view/ask + made view/ask generic). Close the remaining missing-summary holes on the dev-facing surface: - chat: isUserMessage/isAssistantMessage/isToolMessage/isSystemMessage, getInterrupt, isTyping, mockAgent — summaries + @param + @example - render: provideViews, signalStateStore — summaries + @param + @example Regenerated chat/render api-docs.json; updated the findings doc with a post-#685 re-baseline note. Comment-only; chat + render build green. Co-Authored-By: Claude Fable 5 --- .../content/docs/chat/api/api-docs.json | 56 ++++++++++++------- .../content/docs/render/api/api-docs.json | 16 ++++-- .../specs/2026-06-19-ts-dx-audit-findings.md | 2 + libs/chat/src/lib/agent/message.ts | 40 +++++++++++++ .../chat-interrupt.component.ts | 12 ++++ .../chat-typing-indicator.component.ts | 12 ++++ libs/chat/src/lib/testing/mock-agent.ts | 15 +++++ libs/render/src/lib/provide-views.ts | 15 +++++ libs/render/src/lib/signal-state-store.ts | 13 +++++ 9 files changed, 154 insertions(+), 27 deletions(-) diff --git a/apps/website/content/docs/chat/api/api-docs.json b/apps/website/content/docs/chat/api/api-docs.json index 5cbf08b4..565115e9 100644 --- a/apps/website/content/docs/chat/api/api-docs.json +++ b/apps/website/content/docs/chat/api/api-docs.json @@ -7793,13 +7793,13 @@ { "name": "getInterrupt", "kind": "function", - "description": "", + "description": "Read the agent's current human-in-the-loop interrupt, if any.", "signature": "getInterrupt(agent: Agent<>): AgentInterrupt | undefined", "params": [ { "name": "agent", "type": "Agent<>", - "description": "", + "description": "The agent to inspect.", "optional": false } ], @@ -7807,7 +7807,9 @@ "type": "AgentInterrupt | undefined", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst interrupt = getInterrupt(agent);\nif (interrupt) agent.resume('approved');\n```" + ] }, { "name": "getMessageType", @@ -7871,13 +7873,13 @@ { "name": "isAssistantMessage", "kind": "function", - "description": "", + "description": "Type guard narrowing a Message to `role: 'assistant'`.", "signature": "isAssistantMessage(m: Message): m is Message & { role: \"assistant\" }", "params": [ { "name": "m", "type": "Message", - "description": "", + "description": "The message to test.", "optional": false } ], @@ -7885,18 +7887,20 @@ "type": "m is Message & { role: \"assistant\" }", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst reply = agent.messages().findLast(isAssistantMessage);\n```" + ] }, { "name": "isSystemMessage", "kind": "function", - "description": "", + "description": "Type guard narrowing a Message to `role: 'system'`.", "signature": "isSystemMessage(m: Message): m is Message & { role: \"system\" }", "params": [ { "name": "m", "type": "Message", - "description": "", + "description": "The message to test.", "optional": false } ], @@ -7904,18 +7908,20 @@ "type": "m is Message & { role: \"system\" }", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst visible = agent.messages().filter((m) => !isSystemMessage(m));\n```" + ] }, { "name": "isToolMessage", "kind": "function", - "description": "", + "description": "Type guard narrowing a Message to `role: 'tool'` (a tool result turn).", "signature": "isToolMessage(m: Message): m is Message & { role: \"tool\" }", "params": [ { "name": "m", "type": "Message", - "description": "", + "description": "The message to test.", "optional": false } ], @@ -7923,18 +7929,20 @@ "type": "m is Message & { role: \"tool\" }", "description": "" }, - "examples": [] + "examples": [ + "```ts\nif (isToolMessage(m)) console.log(m.toolCallId);\n```" + ] }, { "name": "isTyping", "kind": "function", - "description": "", + "description": "Whether the agent should show a \"typing\" indicator — it is loading and has\nnot yet started streaming the assistant's reply.", "signature": "isTyping(agent: Agent<>): boolean", "params": [ { "name": "agent", "type": "Agent<>", - "description": "", + "description": "The agent to inspect.", "optional": false } ], @@ -7942,18 +7950,20 @@ "type": "boolean", "description": "" }, - "examples": [] + "examples": [ + "```ts\n\\@if (isTyping(agent)) { }\n```" + ] }, { "name": "isUserMessage", "kind": "function", - "description": "", + "description": "Type guard narrowing a Message to `role: 'user'`.", "signature": "isUserMessage(m: Message): m is Message & { role: \"user\" }", "params": [ { "name": "m", "type": "Message", - "description": "", + "description": "The message to test.", "optional": false } ], @@ -7961,7 +7971,9 @@ "type": "m is Message & { role: \"user\" }", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst userTurns = agent.messages().filter(isUserMessage);\n```" + ] }, { "name": "messageContent", @@ -7985,13 +7997,13 @@ { "name": "mockAgent", "kind": "function", - "description": "", + "description": "Build an in-memory Agent for tests and stories — no transport, no\nnetwork. Every field is a writable signal so a test can drive UI states\n(loading, error, interrupts, tool calls, subagents) deterministically.", "signature": "mockAgent(opts: MockAgentOptions): MockAgent<>", "params": [ { "name": "opts", "type": "MockAgentOptions", - "description": "", + "description": "Initial values for the mock's signals; all optional.", "optional": true } ], @@ -7999,7 +8011,9 @@ "type": "MockAgent<>", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst agent = mockAgent({\n messages: [{ id: '1', role: 'assistant', content: 'Hi' }],\n isLoading: true,\n});\n```" + ] }, { "name": "normalizeEnvelopeArgs", diff --git a/apps/website/content/docs/render/api/api-docs.json b/apps/website/content/docs/render/api/api-docs.json index 951646bf..154507bc 100644 --- a/apps/website/content/docs/render/api/api-docs.json +++ b/apps/website/content/docs/render/api/api-docs.json @@ -781,13 +781,13 @@ { "name": "provideViews", "kind": "function", - "description": "", + "description": "Register a ViewRegistry for the render engine so generative-UI specs\ncan resolve element `type`s to Angular components. Provide at the application\n(or a feature route's) environment injector.", "signature": "provideViews(registry: ViewRegistry): EnvironmentProviders", "params": [ { "name": "registry", "type": "ViewRegistry", - "description": "", + "description": "Map of spec element types to the components that render them,\n typically built with `views()` / `withViews()`.", "optional": false } ], @@ -795,18 +795,20 @@ "type": "EnvironmentProviders", "description": "" }, - "examples": [] + "examples": [ + "```ts\nbootstrapApplication(App, {\n providers: [provideViews(views({ metric: MetricCardComponent }))],\n});\n```" + ] }, { "name": "signalStateStore", "kind": "function", - "description": "", + "description": "Create a signal-backed StateStore for a generative-UI surface —\nholds the bound state that spec `$bindState` paths read and interactive\nelements write, with path-addressable get/set and change subscriptions.", "signature": "signalStateStore(initialState: StateModel): StateStore", "params": [ { "name": "initialState", "type": "StateModel", - "description": "", + "description": "Optional starting state object.", "optional": true } ], @@ -814,7 +816,9 @@ "type": "StateStore", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst store = signalStateStore({ count: 0 });\nstore.set('count', 1);\n```" + ] }, { "name": "toRenderRegistry", diff --git a/docs/superpowers/specs/2026-06-19-ts-dx-audit-findings.md b/docs/superpowers/specs/2026-06-19-ts-dx-audit-findings.md index 1cb41a48..67fe9ddf 100644 --- a/docs/superpowers/specs/2026-06-19-ts-dx-audit-findings.md +++ b/docs/superpowers/specs/2026-06-19-ts-dx-audit-findings.md @@ -3,6 +3,8 @@ **Date:** 2026-06-19 · **Scope:** `@threadplane/chat`, `ag-ui`, `langgraph`, `render` (302 public exports) **Method:** TypeDoc extraction (`api-docs.json`) for JSDoc/signature grading across all exports + real `tsserver` quick-info probing for hover/inference on the authoring surface + source confirmation. See `2026-06-19-ts-dx-audit-design.md`. +> **Re-baselined 2026-06-19 (post #685).** The original grades were extracted from `api-docs.json` generated *before* PR #685 ("TypeScript DX pass") merged. #685 already implemented the headline items: `view`/`ask` are now generic with `AcceptComponent` so component inputs are checked against the schema (**F4 done**), and `tools`/`action`/`view`/`ask` carry full `@param`/`@example`/`@returns` JSDoc (**F1/F2 done for client-tools**); `provideChat` and `AgentError`/`toAgentError` were documented too. The remaining gap is narrower — undocumented secondary functions (message type-guards, `getInterrupt`, `mockAgent`, `provideViews`, `signalStateStore`, `bridgeCitationsState`, `extractCitations`) and `@example`-less wiring APIs (`provideAgent`/`injectAgent` on both adapters, `injectRenderHost`). PR 1 on this branch (`docs/ts-dx-audit`) closes the missing-summary holes first. + ## Executive summary The public **type shapes and hovers are in good condition** — the IDE quick-info for the headline authoring APIs is clean and readable, and generic inference works where it's been wired (`action`). The gap is almost entirely **inline guidance (JSDoc)** and a few **surface-hygiene + inference** items. diff --git a/libs/chat/src/lib/agent/message.ts b/libs/chat/src/lib/agent/message.ts index f898fffd..78542e0a 100644 --- a/libs/chat/src/lib/agent/message.ts +++ b/libs/chat/src/lib/agent/message.ts @@ -44,18 +44,58 @@ export interface Message { toolCallIds?: string[]; } +/** + * Type guard narrowing a {@link Message} to `role: 'user'`. + * + * @param m The message to test. + * @returns `true` (and narrows `m`) when the message was sent by the user. + * @example + * ```ts + * const userTurns = agent.messages().filter(isUserMessage); + * ``` + */ export function isUserMessage(m: Message): m is Message & { role: 'user' } { return m.role === 'user'; } +/** + * Type guard narrowing a {@link Message} to `role: 'assistant'`. + * + * @param m The message to test. + * @returns `true` (and narrows `m`) when the message came from the assistant. + * @example + * ```ts + * const reply = agent.messages().findLast(isAssistantMessage); + * ``` + */ export function isAssistantMessage(m: Message): m is Message & { role: 'assistant' } { return m.role === 'assistant'; } +/** + * Type guard narrowing a {@link Message} to `role: 'tool'` (a tool result turn). + * + * @param m The message to test. + * @returns `true` (and narrows `m`) when the message is a tool result. + * @example + * ```ts + * if (isToolMessage(m)) console.log(m.toolCallId); + * ``` + */ export function isToolMessage(m: Message): m is Message & { role: 'tool' } { return m.role === 'tool'; } +/** + * Type guard narrowing a {@link Message} to `role: 'system'`. + * + * @param m The message to test. + * @returns `true` (and narrows `m`) when the message is a system message. + * @example + * ```ts + * const visible = agent.messages().filter((m) => !isSystemMessage(m)); + * ``` + */ export function isSystemMessage(m: Message): m is Message & { role: 'system' } { return m.role === 'system'; } diff --git a/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts b/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts index f080f573..1c63fa5c 100644 --- a/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts +++ b/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts @@ -14,6 +14,18 @@ import type { AgentInterrupt } from '../../agent/agent-interrupt'; import { CHAT_HOST_TOKENS } from '../../styles/chat-tokens'; import { CHAT_INTERRUPT_STYLES } from '../../styles/chat-interrupt.styles'; +/** + * Read the agent's current human-in-the-loop interrupt, if any. + * + * @param agent The agent to inspect. + * @returns The pending {@link AgentInterrupt}, or `undefined` when the agent is + * not currently waiting on an interrupt. + * @example + * ```ts + * const interrupt = getInterrupt(agent); + * if (interrupt) agent.resume('approved'); + * ``` + */ export function getInterrupt(agent: Agent): AgentInterrupt | undefined { return agent.interrupt?.(); } diff --git a/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts b/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts index bc43ffe0..892bdd9c 100644 --- a/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts +++ b/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts @@ -5,6 +5,18 @@ import type { Agent } from '../../agent'; import { CHAT_HOST_TOKENS } from '../../styles/chat-tokens'; import { CHAT_TYPING_INDICATOR_STYLES } from '../../styles/chat-typing-indicator.styles'; +/** + * Whether the agent should show a "typing" indicator — it is loading and has + * not yet started streaming the assistant's reply. + * + * @param agent The agent to inspect. + * @returns `true` while the agent is awaiting a response but no assistant text + * has streamed yet; `false` once tokens arrive or the agent is idle. + * @example + * ```ts + * \@if (isTyping(agent)) { } + * ``` + */ export function isTyping(agent: Agent): boolean { if (!agent.isLoading()) return false; const msgs = agent.messages(); diff --git a/libs/chat/src/lib/testing/mock-agent.ts b/libs/chat/src/lib/testing/mock-agent.ts index 84d684a0..5bd97f59 100644 --- a/libs/chat/src/lib/testing/mock-agent.ts +++ b/libs/chat/src/lib/testing/mock-agent.ts @@ -63,6 +63,21 @@ export interface MockAgentOptions { events$?: Observable; } +/** + * Build an in-memory {@link Agent} for tests and stories — no transport, no + * network. Every field is a writable signal so a test can drive UI states + * (loading, error, interrupts, tool calls, subagents) deterministically. + * + * @param opts Initial values for the mock's signals; all optional. + * @returns A {@link MockAgent} satisfying the full `Agent` contract. + * @example + * ```ts + * const agent = mockAgent({ + * messages: [{ id: '1', role: 'assistant', content: 'Hi' }], + * isLoading: true, + * }); + * ``` + */ export function mockAgent(opts: MockAgentOptions = {}): MockAgent { const messages = signal(opts.messages ?? []); const status = signal(opts.status ?? 'idle'); diff --git a/libs/render/src/lib/provide-views.ts b/libs/render/src/lib/provide-views.ts index 3e4ea409..8398da51 100644 --- a/libs/render/src/lib/provide-views.ts +++ b/libs/render/src/lib/provide-views.ts @@ -4,6 +4,21 @@ import type { ViewRegistry } from './views'; export const VIEW_REGISTRY = new InjectionToken('VIEW_REGISTRY'); +/** + * Register a {@link ViewRegistry} for the render engine so generative-UI specs + * can resolve element `type`s to Angular components. Provide at the application + * (or a feature route's) environment injector. + * + * @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`. + * @example + * ```ts + * bootstrapApplication(App, { + * providers: [provideViews(views({ metric: MetricCardComponent }))], + * }); + * ``` + */ export function provideViews(registry: ViewRegistry) { return makeEnvironmentProviders([ { provide: VIEW_REGISTRY, useValue: registry }, diff --git a/libs/render/src/lib/signal-state-store.ts b/libs/render/src/lib/signal-state-store.ts index ed1f9210..9baff6ed 100644 --- a/libs/render/src/lib/signal-state-store.ts +++ b/libs/render/src/lib/signal-state-store.ts @@ -34,6 +34,19 @@ function setByPath(obj: unknown, segments: string[], value: unknown): unknown { return record; } +/** + * Create a signal-backed {@link StateStore} for a generative-UI surface — + * holds the bound state that spec `$bindState` paths read and interactive + * elements write, with path-addressable get/set and change subscriptions. + * + * @param initialState Optional starting state object. + * @returns A {@link StateStore} bridging Angular signals to the render engine. + * @example + * ```ts + * const store = signalStateStore({ count: 0 }); + * store.set('count', 1); + * ``` + */ export function signalStateStore(initialState: StateModel = {}): StateStore { const state = signal(initialState); const listeners = new Set<() => void>(); From 4206b72fb7cada98f9fd4767fcccfa00ea0c883e Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 19 Jun 2026 16:33:40 -0700 Subject: [PATCH 3/5] refactor(chat)!: prune internal-only exports + document public factories (audit F5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surface hygiene — tighten the public API to what's intentional: - Remove from @threadplane/chat public surface (0 external consumers, internal relative-imported): CHAT_MARKDOWN_STYLES, ICON_* (7 inline-SVG consts), surfaceToSpec, createClientToolsCoordinator. (toClientToolSpecs kept.) - Document genuinely-public factories that lacked summaries: a2uiBasicCatalog, createA2uiSurfaceStore, createContentClassifier, createParseTreeStore (chat), and the advanced citation APIs bridgeCitationsState (ag-ui) + extractCitations (langgraph) — kept per their "advanced consumers" intent. chat api-docs 227→217 entries. chat/ag-ui/langgraph/render build, chat type-tests, examples/chat, and website all green. Pre-1.0 breaking-OK removal. Co-Authored-By: Claude Fable 5 --- .../content/docs/ag-ui/api/api-docs.json | 10 +- .../content/docs/chat/api/api-docs.json | 120 +++--------------- .../content/docs/langgraph/api/api-docs.json | 8 +- apps/website/next-env.d.ts | 2 +- libs/ag-ui/src/lib/bridge-citations-state.ts | 15 +++ libs/chat/src/lib/a2ui/catalog/index.ts | 12 ++ libs/chat/src/lib/a2ui/surface-store.ts | 13 ++ .../src/lib/streaming/content-classifier.ts | 13 ++ .../src/lib/streaming/parse-tree-store.ts | 15 +++ libs/chat/src/public-api.ts | 15 +-- .../src/lib/internals/extract-citations.ts | 13 ++ 11 files changed, 117 insertions(+), 119 deletions(-) diff --git a/apps/website/content/docs/ag-ui/api/api-docs.json b/apps/website/content/docs/ag-ui/api/api-docs.json index 7388984f..516376f9 100644 --- a/apps/website/content/docs/ag-ui/api/api-docs.json +++ b/apps/website/content/docs/ag-ui/api/api-docs.json @@ -537,19 +537,19 @@ { "name": "bridgeCitationsState", "kind": "function", - "description": "", + "description": "Attach per-message Citations carried in an AG-UI thread's `state`\n(`state.citations`, keyed by message id) onto the corresponding assistant\nMessages — for advanced consumers wiring citations from STATE events\ninto the neutral chat contract.", "signature": "bridgeCitationsState(thread: ThreadStateLike, messages: Message[]): Message[]", "params": [ { "name": "thread", "type": "ThreadStateLike", - "description": "", + "description": "The thread-state container holding `state.citations`.", "optional": false }, { "name": "messages", "type": "Message[]", - "description": "", + "description": "The messages to enrich.", "optional": false } ], @@ -557,7 +557,9 @@ "type": "Message[]", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst enriched = bridgeCitationsState(threadState, agent.messages());\n```" + ] }, { "name": "injectAgent", diff --git a/apps/website/content/docs/chat/api/api-docs.json b/apps/website/content/docs/chat/api/api-docs.json index 565115e9..b24ef8ef 100644 --- a/apps/website/content/docs/chat/api/api-docs.json +++ b/apps/website/content/docs/chat/api/api-docs.json @@ -7390,62 +7390,6 @@ "signature": "InjectionToken", "examples": [] }, - { - "name": "CHAT_MARKDOWN_STYLES", - "kind": "const", - "description": "", - "signature": "\"\\n chat-streaming-md { display: block; color: var(--ngaf-chat-text); line-height: var(--ngaf-chat-line-height); }\\n\\n /* Headings */\\n chat-streaming-md h1, chat-streaming-md h2, chat-streaming-md h3, chat-streaming-md h4, chat-streaming-md h5, chat-streaming-md h6 {\\n font-weight: 600;\\n line-height: 1.25;\\n margin: 1.25rem 0 0.75rem;\\n }\\n chat-streaming-md h1:first-child, chat-streaming-md h2:first-child, chat-streaming-md h3:first-child,\\n chat-streaming-md h4:first-child, chat-streaming-md h5:first-child, chat-streaming-md h6:first-child { margin-top: 0; }\\n chat-streaming-md h1 { font-size: 1.5em; font-weight: 700; }\\n chat-streaming-md h2 { font-size: 1.25em; }\\n chat-streaming-md h3 { font-size: 1.1em; }\\n chat-streaming-md h4 { font-size: 1em; }\\n chat-streaming-md h5, chat-streaming-md h6 { font-size: 0.95em; color: var(--ngaf-chat-text-muted); }\\n\\n /* Paragraphs and inline emphasis */\\n chat-streaming-md p { margin: 0 0 0.75rem; line-height: 1.6; font-size: var(--ngaf-chat-font-size); }\\n chat-streaming-md p:last-child { margin-bottom: 0; }\\n chat-streaming-md strong, chat-streaming-md b { font-weight: 700; }\\n chat-streaming-md em, chat-streaming-md i { font-style: italic; }\\n chat-streaming-md del, chat-streaming-md s { text-decoration: line-through; color: var(--ngaf-chat-text-muted); }\\n chat-streaming-md mark { background: var(--ngaf-chat-surface-alt); padding: 0 2px; border-radius: 2px; }\\n chat-streaming-md sub { font-size: 0.75em; vertical-align: sub; }\\n chat-streaming-md sup { font-size: 0.75em; vertical-align: super; }\\n\\n /* Links */\\n chat-streaming-md a { color: var(--ngaf-chat-primary); text-decoration: underline; text-underline-offset: 2px; }\\n chat-streaming-md a:hover { text-decoration-thickness: 2px; }\\n\\n /* Lists (CommonMark + GFM task lists) */\\n chat-streaming-md ul, chat-streaming-md ol { margin: 0 0 0.75rem; padding-left: 1.5rem; }\\n chat-streaming-md ul { list-style: disc outside; }\\n chat-streaming-md ol { list-style: decimal outside; }\\n chat-streaming-md ul ul { list-style: circle outside; }\\n chat-streaming-md ul ul ul { list-style: square outside; }\\n chat-streaming-md li { margin: 0.2rem 0; }\\n chat-streaming-md li::marker { color: var(--ngaf-chat-text-muted); }\\n chat-streaming-md li > p { margin: 0 0 0.25rem; }\\n chat-streaming-md li > ul, chat-streaming-md li > ol { margin: 0.25rem 0 0; }\\n /* GFM task lists: marked emits
  • ... */\\n chat-streaming-md li:has(> input[type=\\\"checkbox\\\"]) { list-style: none; margin-left: -1.25rem; }\\n chat-streaming-md li > input[type=\\\"checkbox\\\"] { margin-right: 0.5rem; vertical-align: middle; }\\n\\n /* Code (inline + fenced) */\\n chat-streaming-md code {\\n background: var(--ngaf-chat-surface-alt);\\n color: var(--ngaf-chat-text);\\n padding: 1px 5px;\\n border-radius: 4px;\\n font-family: var(--ngaf-chat-font-mono);\\n font-size: 0.9em;\\n }\\n chat-streaming-md pre {\\n background: var(--ngaf-chat-surface-alt);\\n color: var(--ngaf-chat-text);\\n padding: 12px 14px;\\n border-radius: var(--ngaf-chat-radius-card);\\n overflow-x: auto;\\n font-family: var(--ngaf-chat-font-mono);\\n font-size: var(--ngaf-chat-font-size-sm);\\n line-height: 1.5;\\n margin: 0 0 0.75rem;\\n }\\n chat-streaming-md pre code { background: transparent; padding: 0; border-radius: 0; font-size: inherit; }\\n\\n /* Blockquote */\\n chat-streaming-md blockquote {\\n border-left: 3px solid var(--ngaf-chat-separator);\\n padding: 0.25rem 0 0.25rem 12px;\\n margin: 0 0 0.75rem;\\n color: var(--ngaf-chat-text-muted);\\n }\\n chat-streaming-md blockquote > :last-child { margin-bottom: 0; }\\n\\n /* Horizontal rule */\\n chat-streaming-md hr {\\n border: none;\\n border-top: 1px solid var(--ngaf-chat-separator);\\n margin: 1rem 0;\\n }\\n\\n /* Tables (GFM) */\\n chat-streaming-md table {\\n border-collapse: collapse;\\n margin: 0 0 0.75rem;\\n width: 100%;\\n font-size: 0.95em;\\n }\\n chat-streaming-md thead { background: var(--ngaf-chat-surface-alt); }\\n chat-streaming-md th, chat-streaming-md td {\\n border: 1px solid var(--ngaf-chat-separator);\\n padding: 6px 10px;\\n text-align: left;\\n vertical-align: top;\\n }\\n chat-streaming-md th { font-weight: 600; }\\n /* Component-rendered table: chat-md-table becomes a horizontally-scrollable\\n wrapper for the inner ; row/cell elements stay layout-transparent\\n so the browser's table layout takes over. Without this overflow wrapper,\\n wide tables push their parent container past the viewport horizontally. */\\n chat-streaming-md chat-md-table {\\n display: block;\\n overflow-x: auto;\\n max-width: 100%;\\n margin: 0 0 0.75rem;\\n }\\n chat-streaming-md chat-md-table-row { display: contents; }\\n chat-streaming-md chat-md-table-cell { display: contents; }\\n chat-streaming-md chat-md-table > table { margin: 0; }\\n /* Task-list items: checkbox + first paragraph render inline; subsequent\\n blocks (sub-lists, multi-paragraph items) flow normally below. */\\n chat-streaming-md li.chat-md-list-item--task {\\n list-style: none;\\n margin-left: -1.25rem;\\n display: flex;\\n flex-wrap: wrap;\\n align-items: baseline;\\n gap: 0.5rem;\\n }\\n chat-streaming-md li.chat-md-list-item--task > input[type=\\\"checkbox\\\"] {\\n margin: 0;\\n flex: 0 0 auto;\\n transform: translateY(2px);\\n }\\n /* The chat-md-children wrapper around list-item content takes remaining width */\\n chat-streaming-md li.chat-md-list-item--task > chat-md-children {\\n flex: 1 1 auto;\\n min-width: 0;\\n }\\n /* Tight task items: only the FIRST paragraph aligns inline with the\\n checkbox (margin collapsed). Subsequent paragraphs/blocks keep their\\n normal vertical spacing so multi-block items render readably. */\\n chat-streaming-md li.chat-md-list-item--task > chat-md-children > chat-md-paragraph:first-child > p {\\n margin: 0;\\n }\\n\\n /* Media */\\n chat-streaming-md img { max-width: 100%; height: auto; border-radius: 6px; }\\n /* Broken-image fallback: muted pill showing alt text + icon. Triggered\\n when fires (error). Caught by live browser smoke — prior impl\\n showed only the browser's broken-image icon with no readable alt. */\\n chat-streaming-md .chat-md-image--broken {\\n display: inline-flex;\\n align-items: center;\\n gap: 0.4rem;\\n padding: 0.25rem 0.5rem;\\n background: var(--ngaf-chat-surface-alt);\\n border: 1px dashed var(--ngaf-chat-separator);\\n border-radius: 6px;\\n font-size: 0.9em;\\n color: var(--ngaf-chat-text-muted, currentColor);\\n opacity: 0.85;\\n }\\n chat-streaming-md .chat-md-image__icon { font-size: 1em; line-height: 1; }\\n chat-streaming-md .chat-md-image__alt { font-style: italic; }\\n\"", - "examples": [] - }, - { - "name": "ICON_AGENT", - "kind": "const", - "description": "Robot/agent icon (replacement). 14x14.", - "signature": "\"\"", - "examples": [] - }, - { - "name": "ICON_CHECK", - "kind": "const", - "description": "Check mark replacement. 12x12.", - "signature": "\"\"", - "examples": [] - }, - { - "name": "ICON_CHEVRON_DOWN", - "kind": "const", - "description": "Chevron down (▼ replacement). 12x12, stroke-based.", - "signature": "\"\"", - "examples": [] - }, - { - "name": "ICON_CHEVRON_UP", - "kind": "const", - "description": "Chevron up (▲ replacement). 12x12, stroke-based.", - "signature": "\"\"", - "examples": [] - }, - { - "name": "ICON_SEND", - "kind": "const", - "description": "Send arrow (for chat input). 16x16.", - "signature": "\"\"", - "examples": [] - }, - { - "name": "ICON_TOOL", - "kind": "const", - "description": "Gear icon (⚙ replacement). 14x14.", - "signature": "\"\"", - "examples": [] - }, - { - "name": "ICON_WARNING", - "kind": "const", - "description": "Warning triangle (⚠ replacement). 18x18.", - "signature": "\"\"", - "examples": [] - }, { "name": "IS_HEADER_ROW", "kind": "const", @@ -7463,14 +7407,16 @@ { "name": "a2uiBasicCatalog", "kind": "function", - "description": "", + "description": "Build the built-in A2UI component catalog — a ViewRegistry mapping\nthe standard A2UI element types (Card, Button, TextField, Image, AudioPlayer,\nVideo, …) to their Angular renderers. Spread it into `provideViews` (with any\nof your own views) so an agent's A2UI surface specs render.", "signature": "a2uiBasicCatalog(): ViewRegistry", "params": [], "returns": { "type": "ViewRegistry", "description": "" }, - "examples": [] + "examples": [ + "```ts\nproviders: [provideViews({ ...a2uiBasicCatalog(), MyWidget: MyWidgetComponent })]\n```" + ] }, { "name": "action", @@ -7566,14 +7512,16 @@ { "name": "createA2uiSurfaceStore", "kind": "function", - "description": "", + "description": "Create an A2uiSurfaceStore — the per-conversation store that buffers\nstreamed A2UI surface updates, tracks each surface's data model + lifecycle\nstate, and exposes them as signals for rendering. One store backs a chat\nthread's A2UI surfaces.", "signature": "createA2uiSurfaceStore(): A2uiSurfaceStore", "params": [], "returns": { "type": "A2uiSurfaceStore", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst store = createA2uiSurfaceStore();\nconst surfaces = store.surfaces; // Signal>\n```" + ] }, { "name": "createAgentRef", @@ -7596,47 +7544,30 @@ "```ts\ninterface TripState { day: number; places: string[]; }\nexport const TRIP = createAgentRef('trip');\n// app.config.ts: provideAgent(TRIP, { assistantId: 'trip' })\n// component: const agent = injectAgent(TRIP); // LangGraphAgent\n```" ] }, - { - "name": "createClientToolsCoordinator", - "kind": "function", - "description": "", - "signature": "createClientToolsCoordinator(registry: ClientToolRegistry): ClientToolsCoordinator", - "params": [ - { - "name": "registry", - "type": "ClientToolRegistry", - "description": "", - "optional": false - } - ], - "returns": { - "type": "ClientToolsCoordinator", - "description": "" - }, - "examples": [] - }, { "name": "createContentClassifier", "kind": "function", - "description": "", + "description": "Create a ContentClassifier — the streaming accumulator that inspects\nan assistant message's content as it arrives and classifies it (markdown vs a\ngenerative-UI/A2UI spec), exposing the parsed result and per-element state as\nsignals so the renderer can switch modes mid-stream.", "signature": "createContentClassifier(): ContentClassifier", "params": [], "returns": { "type": "ContentClassifier", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst cc = createContentClassifier();\neffect(() => console.log(cc.type())); // 'pending' | 'markdown' | 'spec'\n```" + ] }, { "name": "createParseTreeStore", "kind": "function", - "description": "", + "description": "Create a ParseTreeStore — feeds streamed JSON chunks through a\npartial-JSON parser and exposes the progressively-materialized spec and\nper-element accumulation state as signals, so a generative-UI surface can\nrender while the spec is still arriving.", "signature": "createParseTreeStore(parser: PartialJsonParser): ParseTreeStore", "params": [ { "name": "parser", "type": "PartialJsonParser", - "description": "", + "description": "The partial-JSON parser used to incrementally materialize chunks.", "optional": false } ], @@ -7644,7 +7575,9 @@ "type": "ParseTreeStore", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst store = createParseTreeStore(parser);\nstore.push('{\"type\":\"Car');\nstore.spec(); // best-effort Spec | null\n```" + ] }, { "name": "createPartialArgsBridge", @@ -8168,25 +8101,6 @@ }, "examples": [] }, - { - "name": "surfaceToSpec", - "kind": "function", - "description": "", - "signature": "surfaceToSpec(surface: A2uiSurface): Spec | null", - "params": [ - { - "name": "surface", - "type": "A2uiSurface", - "description": "", - "optional": false - } - ], - "returns": { - "type": "Spec | null", - "description": "" - }, - "examples": [] - }, { "name": "toAgentError", "kind": "function", diff --git a/apps/website/content/docs/langgraph/api/api-docs.json b/apps/website/content/docs/langgraph/api/api-docs.json index 945b87b8..cae1532c 100644 --- a/apps/website/content/docs/langgraph/api/api-docs.json +++ b/apps/website/content/docs/langgraph/api/api-docs.json @@ -2425,13 +2425,13 @@ { "name": "extractCitations", "kind": "function", - "description": "", + "description": "Normalize Citations out of a message's `additional_kwargs` (reading\n`citations` or `sources`). Exposed for advanced consumers building custom\nadapters or bridging non-LangGraph message shapes into the neutral\n`Citation[]` form.", "signature": "extractCitations(msg: KwargsLike): Citation[] | undefined", "params": [ { "name": "msg", "type": "KwargsLike", - "description": "", + "description": "Any object with an `additional_kwargs` bag (e.g. a LangChain message).", "optional": false } ], @@ -2439,7 +2439,9 @@ "type": "Citation[] | undefined", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst citations = extractCitations(lcMessage);\n```" + ] }, { "name": "injectAgent", diff --git a/apps/website/next-env.d.ts b/apps/website/next-env.d.ts index 9edff1c7..fdbfe525 100644 --- a/apps/website/next-env.d.ts +++ b/apps/website/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./../../dist/apps/website/.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/libs/ag-ui/src/lib/bridge-citations-state.ts b/libs/ag-ui/src/lib/bridge-citations-state.ts index 2def9237..11c7b4b4 100644 --- a/libs/ag-ui/src/lib/bridge-citations-state.ts +++ b/libs/ag-ui/src/lib/bridge-citations-state.ts @@ -6,6 +6,21 @@ interface ThreadStateLike { state?: Record; } +/** + * Attach per-message {@link Citation}s carried in an AG-UI thread's `state` + * (`state.citations`, keyed by message id) onto the corresponding assistant + * {@link Message}s — for advanced consumers wiring citations from STATE events + * into the neutral chat contract. + * + * @param thread The thread-state container holding `state.citations`. + * @param messages The messages to enrich. + * @returns A new array where matching messages gain their `citations`; messages + * without citations are returned unchanged. + * @example + * ```ts + * const enriched = bridgeCitationsState(threadState, agent.messages()); + * ``` + */ export function bridgeCitationsState(thread: ThreadStateLike, messages: Message[]): Message[] { const citationsByMsg = (thread.state as { citations?: unknown })?.citations; if (!citationsByMsg || typeof citationsByMsg !== 'object') return messages; diff --git a/libs/chat/src/lib/a2ui/catalog/index.ts b/libs/chat/src/lib/a2ui/catalog/index.ts index 99a95f1a..ad5dab02 100644 --- a/libs/chat/src/lib/a2ui/catalog/index.ts +++ b/libs/chat/src/lib/a2ui/catalog/index.ts @@ -19,6 +19,18 @@ import { A2uiTextComponent } from './text.component'; import { A2uiTextFieldComponent } from './text-field.component'; import { A2uiVideoComponent } from './video.component'; +/** + * Build the built-in A2UI component catalog — a {@link ViewRegistry} mapping + * the standard A2UI element types (Card, Button, TextField, Image, AudioPlayer, + * Video, …) to their Angular renderers. Spread it into `provideViews` (with any + * of your own views) so an agent's A2UI surface specs render. + * + * @returns A {@link ViewRegistry} of the standard A2UI components. + * @example + * ```ts + * providers: [provideViews({ ...a2uiBasicCatalog(), MyWidget: MyWidgetComponent })] + * ``` + */ export function a2uiBasicCatalog(): ViewRegistry { return views({ AudioPlayer: A2uiAudioPlayerComponent, diff --git a/libs/chat/src/lib/a2ui/surface-store.ts b/libs/chat/src/lib/a2ui/surface-store.ts index d9d76c68..d103b8c2 100644 --- a/libs/chat/src/lib/a2ui/surface-store.ts +++ b/libs/chat/src/lib/a2ui/surface-store.ts @@ -106,6 +106,19 @@ function resolveProps(value: unknown, dataModel: Record): unkno return value; } +/** + * Create an {@link A2uiSurfaceStore} — the per-conversation store that buffers + * streamed A2UI surface updates, tracks each surface's data model + lifecycle + * state, and exposes them as signals for rendering. One store backs a chat + * thread's A2UI surfaces. + * + * @returns A fresh, empty {@link A2uiSurfaceStore}. + * @example + * ```ts + * const store = createA2uiSurfaceStore(); + * const surfaces = store.surfaces; // Signal> + * ``` + */ export function createA2uiSurfaceStore(): A2uiSurfaceStore { const surfacesSignal = signal>(new Map()); const surfaceStatesSignal = signal>(new Map()); diff --git a/libs/chat/src/lib/streaming/content-classifier.ts b/libs/chat/src/lib/streaming/content-classifier.ts index 88978819..a669503c 100644 --- a/libs/chat/src/lib/streaming/content-classifier.ts +++ b/libs/chat/src/lib/streaming/content-classifier.ts @@ -25,6 +25,19 @@ export interface ContentClassifier { dispose(): void; } +/** + * Create a {@link ContentClassifier} — the streaming accumulator that inspects + * an assistant message's content as it arrives and classifies it (markdown vs a + * generative-UI/A2UI spec), exposing the parsed result and per-element state as + * signals so the renderer can switch modes mid-stream. + * + * @returns A fresh {@link ContentClassifier}; call `dispose()` when done. + * @example + * ```ts + * const cc = createContentClassifier(); + * effect(() => console.log(cc.type())); // 'pending' | 'markdown' | 'spec' + * ``` + */ export function createContentClassifier(): ContentClassifier { const typeSignal = signal('pending'); const markdownSignal = signal(''); diff --git a/libs/chat/src/lib/streaming/parse-tree-store.ts b/libs/chat/src/lib/streaming/parse-tree-store.ts index ef669d66..3c05cca4 100644 --- a/libs/chat/src/lib/streaming/parse-tree-store.ts +++ b/libs/chat/src/lib/streaming/parse-tree-store.ts @@ -17,6 +17,21 @@ export interface ParseTreeStore { readonly elementStates: Signal>; } +/** + * Create a {@link ParseTreeStore} — feeds streamed JSON chunks through a + * partial-JSON parser and exposes the progressively-materialized spec and + * per-element accumulation state as signals, so a generative-UI surface can + * render while the spec is still arriving. + * + * @param parser The partial-JSON parser used to incrementally materialize chunks. + * @returns A {@link ParseTreeStore}; call `push(chunk)` as bytes stream in. + * @example + * ```ts + * const store = createParseTreeStore(parser); + * store.push('{"type":"Car'); + * store.spec(); // best-effort Spec | null + * ``` + */ export function createParseTreeStore(parser: PartialJsonParser): ParseTreeStore { const specSignal = signal(null); const elementStatesSignal = signal>(new Map()); diff --git a/libs/chat/src/public-api.ts b/libs/chat/src/public-api.ts index f1c88a1f..183083d1 100644 --- a/libs/chat/src/public-api.ts +++ b/libs/chat/src/public-api.ts @@ -151,15 +151,13 @@ export { MarkdownTableRowComponent } from './lib/markdown/views/markdow export { MarkdownTableCellComponent } from './lib/markdown/views/markdown-table-cell.component'; export { IS_HEADER_ROW } from './lib/markdown/markdown-table-row.token'; -// Shared styles & utilities -export { CHAT_MARKDOWN_STYLES } from './lib/styles/chat-markdown.styles'; +// Shared utilities +// (Internal styling constants CHAT_MARKDOWN_STYLES and ICON_* are intentionally +// NOT exported — they are implementation details consumed via relative imports +// inside the lib; theming is done through CSS custom properties, not these.) export { renderMarkdown } from './lib/streaming/markdown-render'; export { messageContent } from './lib/compositions/shared/message-utils'; export { formatDuration } from './lib/utils/format-duration'; -export { - ICON_CHEVRON_DOWN, ICON_CHEVRON_UP, ICON_TOOL, - ICON_WARNING, ICON_AGENT, ICON_CHECK, ICON_SEND, -} from './lib/styles/chat-icons'; // Views (re-exported from @threadplane/render for convenience) export { views, withViews, withoutViews, toRenderRegistry } from '@threadplane/render'; @@ -181,7 +179,7 @@ export { createPartialArgsBridge } from './lib/a2ui/partial-args-bridge'; export type { PartialArgsBridge } from './lib/a2ui/partial-args-bridge'; export { normalizeEnvelopeArgs } from './lib/a2ui/envelope-normalizer'; export { A2uiSurfaceComponent } from './lib/a2ui/surface.component'; -export { surfaceToSpec } from './lib/a2ui/surface-to-spec'; +// surfaceToSpec: internal A2UI plumbing — consumed via relative imports, not public. export { buildA2uiActionMessage } from './lib/a2ui/build-action-message'; export { a2uiBasicCatalog } from './lib/a2ui/catalog/index'; export { emitBinding } from './lib/a2ui/catalog/emit-binding'; @@ -227,7 +225,8 @@ export type { ClientToolSpec } from './lib/client-tools/to-json-schema'; export type { ClientToolsCapability, ClientToolResult } from './lib/client-tools/client-tools-capability'; export { validateArgs, executeFunctionTool } from './lib/client-tools/execute'; export { startClientToolExecutor } from './lib/client-tools/client-tool-executor'; -export { createClientToolsCoordinator, toClientToolSpecs } from './lib/client-tools/client-tools-coordinator'; +// createClientToolsCoordinator: internal — provideChat wires it; not public. +export { toClientToolSpecs } from './lib/client-tools/client-tools-coordinator'; export type { ClientToolsCoordinator } from './lib/client-tools/client-tools-coordinator'; // Test utilities (no vitest dep — safe to ship in the main runtime bundle) diff --git a/libs/langgraph/src/lib/internals/extract-citations.ts b/libs/langgraph/src/lib/internals/extract-citations.ts index fd6330da..95a84e9c 100644 --- a/libs/langgraph/src/lib/internals/extract-citations.ts +++ b/libs/langgraph/src/lib/internals/extract-citations.ts @@ -6,6 +6,19 @@ interface KwargsLike { additional_kwargs?: Record | undefined; } +/** + * Normalize {@link Citation}s out of a message's `additional_kwargs` (reading + * `citations` or `sources`). Exposed for advanced consumers building custom + * adapters or bridging non-LangGraph message shapes into the neutral + * `Citation[]` form. + * + * @param msg Any object with an `additional_kwargs` bag (e.g. a LangChain message). + * @returns The normalized citations, or `undefined` when none are present. + * @example + * ```ts + * const citations = extractCitations(lcMessage); + * ``` + */ export function extractCitations(msg: KwargsLike): Citation[] | undefined { const raw = msg.additional_kwargs?.['citations'] ?? msg.additional_kwargs?.['sources']; if (!Array.isArray(raw) || raw.length === 0) return undefined; From a6a37f527f8528fdc7f92b7d6477cfc29e227424 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 19 Jun 2026 16:42:56 -0700 Subject: [PATCH 4/5] docs(dx): formalize inline ts examples as @example tags (provideAgent/injectAgent) Convert the inline ```ts usage blocks in the ag-ui + langgraph provideAgent / injectAgent JSDoc into formal @example tags so IDEs and the api-docs site render them as examples (api-docs now reports examples=1/1 ag-ui, 2/1 langgraph). Prose descriptions retained; ag-ui + langgraph build green. Co-Authored-By: Claude Fable 5 --- apps/website/content/docs/ag-ui/api/api-docs.json | 12 ++++++++---- .../content/docs/langgraph/api/api-docs.json | 13 +++++++++---- libs/ag-ui/src/lib/provide-agent.ts | 9 +++++---- libs/langgraph/src/lib/agent.provider.ts | 13 +++++++------ libs/langgraph/src/lib/inject-agent.ts | 6 +++--- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/apps/website/content/docs/ag-ui/api/api-docs.json b/apps/website/content/docs/ag-ui/api/api-docs.json index 516376f9..b07ce852 100644 --- a/apps/website/content/docs/ag-ui/api/api-docs.json +++ b/apps/website/content/docs/ag-ui/api/api-docs.json @@ -564,19 +564,21 @@ { "name": "injectAgent", "kind": "function", - "description": "Injects the AG-UI agent from Angular's dependency injection container.\nUse this in components or services provided via `provideAgent()` (or\n`provideFakeAgent()`).\n\nReturns an `AgUiAgent` — the runtime-neutral `Agent` contract plus the\nAG-UI-specific `customEvents` signal — so `customEvents` is reachable\ndirectly, without casting.\n\n**Typed state via AgentRef.** Pass the same ref that was supplied to\n`provideAgent(ref, …)` to carry the state type through DI without repeating\nthe generic at every call site:\n\n```ts\nconst agent = injectAgent(TRIP); // AgUiAgent\n```\n\nThe no-arg form defaults to `AgUiAgent>`.", + "description": "Injects the AG-UI agent from Angular's dependency injection container.\nUse this in components or services provided via `provideAgent()` (or\n`provideFakeAgent()`).\n\nReturns an `AgUiAgent` — the runtime-neutral `Agent` contract plus the\nAG-UI-specific `customEvents` signal — so `customEvents` is reachable\ndirectly, without casting.\n\n**Typed state via AgentRef.** Pass the same ref that was supplied to\n`provideAgent(ref, …)` to carry the state type through DI without repeating\nthe generic at every call site. The no-arg form defaults to\n`AgUiAgent>`.", "signature": "injectAgent(): AgUiAgent<>", "params": [], "returns": { "type": "AgUiAgent<>", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst agent = injectAgent(TRIP); // AgUiAgent\n```" + ] }, { "name": "provideAgent", "kind": "function", - "description": "Provides an Agent instance wired through HttpAgent and toAgent.\nConstructs an HttpAgent from config and wraps it in the runtime-neutral\nAgent contract via toAgent(). Returns a provider array suitable for\nbootstrapApplication or TestBed.configureTestingModule().\n\n**Static vs factory config.** Pass a plain `AgentConfig` object when the\nconfig is known up front. Pass a `() => AgentConfig` factory when the config\ndepends on runtime/DI state — the factory runs inside an Angular injection\ncontext, so it may call `inject()` to read services or route params.\n\n**Typed state via AgentRef.** Pass a typed ref as the first argument to flow\nthe state shape from `provideAgent` to `injectAgent` without repeating the\ngeneric at every call site:\n\n```ts\ninterface TripState { day: number; places: string[]; }\nexport const TRIP = createAgentRef('trip');\n// app.config.ts:\nproviders: [provideAgent(TRIP, { url: 'http://localhost:8000/agent' })]\n// component:\nconst agent = injectAgent(TRIP); // AgUiAgent\n```", + "description": "Provides an Agent instance wired through HttpAgent and toAgent.\nConstructs an HttpAgent from config and wraps it in the runtime-neutral\nAgent contract via toAgent(). Returns a provider array suitable for\nbootstrapApplication or TestBed.configureTestingModule().\n\n**Static vs factory config.** Pass a plain `AgentConfig` object when the\nconfig is known up front. Pass a `() => AgentConfig` factory when the config\ndepends on runtime/DI state — the factory runs inside an Angular injection\ncontext, so it may call `inject()` to read services or route params.\n\n**Typed state via AgentRef.** Pass a typed ref as the first argument to flow\nthe state shape from `provideAgent` to `injectAgent` without repeating the\ngeneric at every call site.", "signature": "provideAgent(ref: AgentRef, configOrFactory: AgentConfig | () => AgentConfig): Provider[]", "params": [ { @@ -596,7 +598,9 @@ "type": "Provider[]", "description": "" }, - "examples": [] + "examples": [ + "```ts\ninterface TripState { day: number; places: string[]; }\nexport const TRIP = createAgentRef('trip');\n// app.config.ts:\nproviders: [provideAgent(TRIP, { url: 'http://localhost:8000/agent' })]\n// component:\nconst agent = injectAgent(TRIP); // AgUiAgent\n```" + ] }, { "name": "provideFakeAgent", diff --git a/apps/website/content/docs/langgraph/api/api-docs.json b/apps/website/content/docs/langgraph/api/api-docs.json index cae1532c..21fefce7 100644 --- a/apps/website/content/docs/langgraph/api/api-docs.json +++ b/apps/website/content/docs/langgraph/api/api-docs.json @@ -2446,14 +2446,16 @@ { "name": "injectAgent", "kind": "function", - "description": "Retrieve the LangGraph-backed Agent from the current Angular injection context.\n\nMirrors `@threadplane/ag-ui`'s `injectAgent()` so consumer code is identical\nregardless of which adapter is wired in `app.config.ts`. The agent is a\nsingleton scoped to the injector that called `provideAgent()` — re-provide\nin a child component's `providers: []` to scope a different agent to that\nsubtree (Angular's hierarchical DI handles the rest).\n\n**Typed state via AgentRef.** Pass the same ref that was supplied to\n`provideAgent(ref, …)` to carry the state type through DI without repeating\nthe generic at every call site:\n\n```ts\nconst agent = injectAgent(TRIP); // LangGraphAgent\n```\n\nThe no-arg form defaults to `LangGraphAgent>`.", + "description": "Retrieve the LangGraph-backed Agent from the current Angular injection context.\n\nMirrors `@threadplane/ag-ui`'s `injectAgent()` so consumer code is identical\nregardless of which adapter is wired in `app.config.ts`. The agent is a\nsingleton scoped to the injector that called `provideAgent()` — re-provide\nin a child component's `providers: []` to scope a different agent to that\nsubtree (Angular's hierarchical DI handles the rest).\n\n**Typed state via AgentRef.** Pass the same ref that was supplied to\n`provideAgent(ref, …)` to carry the state type through DI without repeating\nthe generic at every call site. The no-arg form defaults to\n`LangGraphAgent>`.", "signature": "injectAgent(): LangGraphAgent>", "params": [], "returns": { "type": "LangGraphAgent>", "description": "" }, - "examples": [] + "examples": [ + "```ts\nconst agent = injectAgent(TRIP); // LangGraphAgent\n```" + ] }, { "name": "mockLangGraphAgent", @@ -2477,7 +2479,7 @@ { "name": "provideAgent", "kind": "function", - "description": "Wire the LangGraph adapter into Angular's dependency injection.\n\nRegisters a singleton `LangGraphAgent` constructed from `config`. Retrieve it\nin any component with `injectAgent()`. Provide this at the application root\n(`app.config.ts`) for an app-wide agent.\n\nTo use a different agent in a component subtree, re-provide\n`provideAgent({...})` in that component's `providers: []` array —\nAngular's hierarchical DI scopes the singleton accordingly.\n\n**Static vs factory config.** Pass a plain `AgentConfig` object when the\nconfig is known up front. Pass a `() => AgentConfig` factory when the config\ndepends on runtime/DI state — the factory runs inside an Angular injection\ncontext, so it may call `inject()` to read services, route params, or\ncomponent-scoped signals:\n\n```ts\nproviders: [\n provideAgent(() => {\n const route = inject(ActivatedRoute);\n return { assistantId: 'chat', threadId: toSignal(route.paramMap) };\n }),\n];\n```\n\n**Typed state via AgentRef.** Pass a typed ref as the first argument to flow\nthe state shape from `provideAgent` to `injectAgent` without repeating the\ngeneric at every call site:\n\n```ts\nexport const TRIP = createAgentRef('trip');\n// app.config.ts:\nproviders: [provideAgent(TRIP, { assistantId: 'trip-graph' })]\n// component:\nconst agent = injectAgent(TRIP); // LangGraphAgent\n```", + "description": "Wire the LangGraph adapter into Angular's dependency injection.\n\nRegisters a singleton `LangGraphAgent` constructed from `config`. Retrieve it\nin any component with `injectAgent()`. Provide this at the application root\n(`app.config.ts`) for an app-wide agent.\n\nTo use a different agent in a component subtree, re-provide\n`provideAgent({...})` in that component's `providers: []` array —\nAngular's hierarchical DI scopes the singleton accordingly.\n\n**Static vs factory config.** Pass a plain `AgentConfig` object when the\nconfig is known up front. Pass a `() => AgentConfig` factory when the config\ndepends on runtime/DI state — the factory runs inside an Angular injection\ncontext, so it may call `inject()` to read services, route params, or\ncomponent-scoped signals.\n\n**Typed state via AgentRef.** Pass a typed ref as the first argument to flow\nthe state shape from `provideAgent` to `injectAgent` without repeating the\ngeneric at every call site.", "signature": "provideAgent(ref: AgentRef, configOrFactory: AgentConfig | () => AgentConfig): Provider[]", "params": [ { @@ -2497,7 +2499,10 @@ "type": "Provider[]", "description": "" }, - "examples": [] + "examples": [ + "```ts\nproviders: [\n provideAgent(() => {\n const route = inject(ActivatedRoute);\n return { assistantId: 'chat', threadId: toSignal(route.paramMap) };\n }),\n];\n```", + "```ts\nexport const TRIP = createAgentRef('trip');\n// app.config.ts:\nproviders: [provideAgent(TRIP, { assistantId: 'trip-graph' })]\n// component:\nconst agent = injectAgent(TRIP); // LangGraphAgent\n```" + ] }, { "name": "provideFakeAgent", diff --git a/libs/ag-ui/src/lib/provide-agent.ts b/libs/ag-ui/src/lib/provide-agent.ts index f9ffcd5e..fdf6654f 100644 --- a/libs/ag-ui/src/lib/provide-agent.ts +++ b/libs/ag-ui/src/lib/provide-agent.ts @@ -60,8 +60,9 @@ function isAgentRef(x: unknown): x is AgentRef { * * **Typed state via AgentRef.** Pass a typed ref as the first argument to flow * the state shape from `provideAgent` to `injectAgent` without repeating the - * generic at every call site: + * generic at every call site. * + * @example Typed state via AgentRef * ```ts * interface TripState { day: number; places: string[]; } * export const TRIP = createAgentRef('trip'); @@ -102,13 +103,13 @@ export function provideAgent>( * * **Typed state via AgentRef.** Pass the same ref that was supplied to * `provideAgent(ref, …)` to carry the state type through DI without repeating - * the generic at every call site: + * the generic at every call site. The no-arg form defaults to + * `AgUiAgent>`. * + * @example Typed state via AgentRef * ```ts * const agent = injectAgent(TRIP); // AgUiAgent * ``` - * - * The no-arg form defaults to `AgUiAgent>`. */ export function injectAgent(): AgUiAgent; export function injectAgent(ref: AgentRef): AgUiAgent; diff --git a/libs/langgraph/src/lib/agent.provider.ts b/libs/langgraph/src/lib/agent.provider.ts index c0b763c6..f85c4c84 100644 --- a/libs/langgraph/src/lib/agent.provider.ts +++ b/libs/langgraph/src/lib/agent.provider.ts @@ -106,8 +106,13 @@ function isAgentRef(x: unknown): x is AgentRef { * config is known up front. Pass a `() => AgentConfig` factory when the config * depends on runtime/DI state — the factory runs inside an Angular injection * context, so it may call `inject()` to read services, route params, or - * component-scoped signals: + * component-scoped signals. * + * **Typed state via AgentRef.** Pass a typed ref as the first argument to flow + * the state shape from `provideAgent` to `injectAgent` without repeating the + * generic at every call site. + * + * @example Factory config reading route params * ```ts * providers: [ * provideAgent(() => { @@ -116,11 +121,7 @@ function isAgentRef(x: unknown): x is AgentRef { * }), * ]; * ``` - * - * **Typed state via AgentRef.** Pass a typed ref as the first argument to flow - * the state shape from `provideAgent` to `injectAgent` without repeating the - * generic at every call site: - * + * @example Typed state via AgentRef * ```ts * export const TRIP = createAgentRef('trip'); * // app.config.ts: diff --git a/libs/langgraph/src/lib/inject-agent.ts b/libs/langgraph/src/lib/inject-agent.ts index 8cd83d1a..3e665770 100644 --- a/libs/langgraph/src/lib/inject-agent.ts +++ b/libs/langgraph/src/lib/inject-agent.ts @@ -16,13 +16,13 @@ import type { LangGraphAgent } from './agent.types'; * * **Typed state via AgentRef.** Pass the same ref that was supplied to * `provideAgent(ref, …)` to carry the state type through DI without repeating - * the generic at every call site: + * the generic at every call site. The no-arg form defaults to + * `LangGraphAgent>`. * + * @example Typed state via AgentRef * ```ts * const agent = injectAgent(TRIP); // LangGraphAgent * ``` - * - * The no-arg form defaults to `LangGraphAgent>`. */ export function injectAgent(): LangGraphAgent>; export function injectAgent( From a2a3e6de51f4bfcb54471ea2dc25a29618d9457e Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 19 Jun 2026 16:50:52 -0700 Subject: [PATCH 5/5] docs(dx): document config-interface members (ChatConfig/AgentConfig/RenderConfig) The objects app developers fill in when wiring the framework now carry both an interface-level summary and per-member JSDoc: - chat ChatConfig: interface summary (members were already documented) - ag-ui AgentConfig: url/agentId/threadId/headers member docs + clean summary - render RenderConfig: interface summary + registry/store/functions/handlers docs (langgraph AgentConfig was already fully documented.) chat/ag-ui/render build green; api-docs regenerated. Co-Authored-By: Claude Fable 5 --- apps/website/content/docs/ag-ui/api/api-docs.json | 10 +++++----- apps/website/content/docs/chat/api/api-docs.json | 2 +- apps/website/content/docs/render/api/api-docs.json | 10 +++++----- libs/ag-ui/src/lib/provide-agent.ts | 13 +++++++------ libs/chat/src/lib/provide-chat.ts | 6 ++++++ libs/render/src/lib/render.types.ts | 9 +++++++++ 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/apps/website/content/docs/ag-ui/api/api-docs.json b/apps/website/content/docs/ag-ui/api/api-docs.json index b07ce852..996834ac 100644 --- a/apps/website/content/docs/ag-ui/api/api-docs.json +++ b/apps/website/content/docs/ag-ui/api/api-docs.json @@ -367,18 +367,18 @@ { "name": "AgentConfig", "kind": "interface", - "description": "Configuration for the AG-UI agent provider.\nHttpAgentConfig shape (from @ag-ui/client@0.0.52):\n - url: string (required) — endpoint for the HTTP agent\n - agentId: string (optional) — agent identifier\n - threadId: string (optional) — thread identifier\n - headers: Record (optional) — custom HTTP headers", + "description": "Connection options for the AG-UI agent provider, passed to provideAgent.\nMirrors the underlying `HttpAgent` config (`@ag-ui/client`) plus an optional\ntelemetry sink.", "properties": [ { "name": "agentId", "type": "string", - "description": "", + "description": "Agent identifier, when the endpoint serves more than one agent.", "optional": true }, { "name": "headers", "type": "Record", - "description": "", + "description": "Extra HTTP headers sent with every request (e.g. auth tokens).", "optional": true }, { @@ -390,13 +390,13 @@ { "name": "threadId", "type": "string", - "description": "", + "description": "Thread to connect to on start; omit to begin a fresh conversation.", "optional": true }, { "name": "url", "type": "string", - "description": "", + "description": "Endpoint URL of the AG-UI HTTP agent (e.g. `'http://localhost:8000/agent'`). Required.", "optional": false } ], diff --git a/apps/website/content/docs/chat/api/api-docs.json b/apps/website/content/docs/chat/api/api-docs.json index b24ef8ef..0a67824a 100644 --- a/apps/website/content/docs/chat/api/api-docs.json +++ b/apps/website/content/docs/chat/api/api-docs.json @@ -5887,7 +5887,7 @@ { "name": "ChatConfig", "kind": "interface", - "description": "", + "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": "__licenseEnvHint", diff --git a/apps/website/content/docs/render/api/api-docs.json b/apps/website/content/docs/render/api/api-docs.json index 154507bc..72b9f87f 100644 --- a/apps/website/content/docs/render/api/api-docs.json +++ b/apps/website/content/docs/render/api/api-docs.json @@ -251,30 +251,30 @@ { "name": "RenderConfig", "kind": "interface", - "description": "", + "description": "Options for provideRender — the registry, state, computed functions,\nand action handlers the render engine uses to turn a spec into live Angular\ncomponents. Every field is optional.", "properties": [ { "name": "functions", "type": "Record", - "description": "", + "description": "Named computed functions a spec can reference (e.g. via `$compute`) to derive values from state.", "optional": true }, { "name": "handlers", "type": "Record) => unknown>", - "description": "", + "description": "Named action handlers invoked when interactive elements fire (e.g. a Button's `onClick`).", "optional": true }, { "name": "registry", "type": "AngularRegistry", - "description": "", + "description": "Component registry mapping spec element `type`s to Angular components.", "optional": true }, { "name": "store", "type": "StateStore", - "description": "", + "description": "Backing state store that `$bindState` paths read from and interactive elements write to.", "optional": true } ], diff --git a/libs/ag-ui/src/lib/provide-agent.ts b/libs/ag-ui/src/lib/provide-agent.ts index fdf6654f..8fb7a4ac 100644 --- a/libs/ag-ui/src/lib/provide-agent.ts +++ b/libs/ag-ui/src/lib/provide-agent.ts @@ -5,17 +5,18 @@ import type { AgentRef, AgentRuntimeTelemetrySink } from '@threadplane/chat'; import { toAgent, type AgUiAgent } from './to-agent'; /** - * Configuration for the AG-UI agent provider. - * HttpAgentConfig shape (from @ag-ui/client@0.0.52): - * - url: string (required) — endpoint for the HTTP agent - * - agentId: string (optional) — agent identifier - * - threadId: string (optional) — thread identifier - * - headers: Record (optional) — custom HTTP headers + * Connection options for the AG-UI agent provider, passed to {@link provideAgent}. + * Mirrors the underlying `HttpAgent` config (`@ag-ui/client`) plus an optional + * telemetry sink. */ export interface AgentConfig { + /** Endpoint URL of the AG-UI HTTP agent (e.g. `'http://localhost:8000/agent'`). Required. */ url: string; + /** Agent identifier, when the endpoint serves more than one agent. */ agentId?: string; + /** Thread to connect to on start; omit to begin a fresh conversation. */ threadId?: string; + /** Extra HTTP headers sent with every request (e.g. auth tokens). */ headers?: Record; /** Optional app-owned telemetry sink. No telemetry is emitted unless this is provided. */ telemetry?: AgentRuntimeTelemetrySink | false; diff --git a/libs/chat/src/lib/provide-chat.ts b/libs/chat/src/lib/provide-chat.ts index 228fc39b..92a080a7 100644 --- a/libs/chat/src/lib/provide-chat.ts +++ b/libs/chat/src/lib/provide-chat.ts @@ -9,6 +9,12 @@ import type { AngularRegistry } from '@threadplane/render'; const PACKAGE_NAME = '@threadplane/chat'; +/** + * 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; diff --git a/libs/render/src/lib/render.types.ts b/libs/render/src/lib/render.types.ts index e4ee847b..7b60d1fb 100644 --- a/libs/render/src/lib/render.types.ts +++ b/libs/render/src/lib/render.types.ts @@ -58,9 +58,18 @@ export interface AngularRegistry { names(): string[]; } +/** + * Options for {@link provideRender} — the registry, state, computed functions, + * and action handlers the render engine uses to turn a spec into live Angular + * components. Every field is optional. + */ export interface RenderConfig { + /** Component registry mapping spec element `type`s to Angular components. */ registry?: AngularRegistry; + /** Backing state store that `$bindState` paths read from and interactive elements write to. */ store?: StateStore; + /** Named computed functions a spec can reference (e.g. via `$compute`) to derive values from state. */ functions?: Record; + /** Named action handlers invoked when interactive elements fire (e.g. a Button's `onClick`). */ handlers?: Record) => unknown | Promise>; }