Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions ts/packages/chat-ui/README.AUTOGEN.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<!-- AUTOGEN:DOCS:START -->

<!-- AUTOGEN:DOCS:HASH:sha256=2f3b8b92de1a4ddb7a82177f4ed1a24b86c144254b7d27160d720f4f48026841 -->
<!-- AUTOGEN:DOCS:HASH:sha256=5c3d3c1229b4d627ddfd459e6e435a3d96f566965e161d4eaf961070f111d1c4 -->
<!-- AUTOGEN:DOCS:SOURCE: ./README.md (hand-written documentation; this file is the AI-generated companion) -->

# chat-ui — AI-generated documentation
Expand All @@ -12,17 +12,17 @@

## Overview

The `chat-ui` package provides a shared, framework-free chat user interface for TypeAgent applications. It is designed to ensure a consistent and interactive chat experience across various platforms, including the VS Code shell extension, the browser extension chat panel, and the Visual Studio extension webview. The package includes components for rendering user and agent messages, handling streaming updates, replaying chat history, managing connection status, and collecting user feedback.
The `chat-ui` package provides a shared, framework-free chat user interface for TypeAgent applications. It is used across multiple platforms, including the VS Code shell extension, the browser extension, and the Visual Studio extension webview, to deliver a consistent and interactive chat experience. The package includes components for rendering chat messages, handling streaming updates, replaying chat history, managing connection status, and collecting user feedback.

## What it does

The `chat-ui` package offers a set of tools and components to build and manage chat interfaces. Its primary features include:
The `chat-ui` package offers a comprehensive set of tools and components for building and managing chat interfaces. Key features include:

- **ChatPanel**: The core component for rendering the chat interface. It supports:
- **ChatPanel**: The central component for rendering the chat interface. It supports:

- Adding user and agent messages via `addAgentMessage`.
- Updating display metadata with `setDisplayInfo`.
- Replaying historical chat entries using `replayHistory`.
- Adding user and agent messages with `addAgentMessage`.
- Updating display metadata using `setDisplayInfo`.
- Replaying historical chat entries via `replayHistory`.
- Streaming updates for dynamic content display.

- **FeedbackWidget**: A component for collecting user feedback, including thumbs-up/thumbs-down ratings, comments, and contextual information.
Expand Down Expand Up @@ -120,6 +120,6 @@ External: `ansi_up`, `dompurify`, `markdown-it`

---

_Auto-generated against commit `8f591da77983db53fd4a3e0ca12b58d80aaa3628` on `2026-07-22T20:55:48.144Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter chat-ui docs:verify-links` to spot-check._
_Auto-generated against commit `274f0c51c3f1dca4e627c5311084db01d02fe1e9` on `2026-07-23T15:09:16.468Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter chat-ui docs:verify-links` to spot-check._

<!-- AUTOGEN:DOCS:END -->
44 changes: 44 additions & 0 deletions ts/packages/chat-ui/src/chatPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,7 @@ export class ChatPanel {
this.setupContextMenu();
this.setupProviderAffordances();
this.setupImageLightbox();
this.setupReasoningToolCall();
}

/**
Expand All @@ -977,6 +978,42 @@ export class ChatPanel {
});
}

/**
* Lazily syntax-highlight a logged tool-call block the first time it opens.
* The reasoning engine renders every tool call (single or folded) as a native
* <details class="reasoning-tool-call"> holding only that call's own JSON (an
* object for one call, an array for a folded run). The <details>/<summary>
* handles show/hide plus keyboard toggling on its own; we only highlight the
* <pre> once, when it first becomes visible, so it reads like the clickable
* action JSON view (same highlightJson tokens). The `toggle` event does not
* bubble, so we listen in the capture phase to keep a single delegated
* listener. Each block owns its JSON inline, independent of the enclosing
* action's JSON view. Shared by the Electron and VS Code shells.
*/
private setupReasoningToolCall() {
this.messageDiv.addEventListener(
"toggle",
(e) => {
const details = e.target as HTMLElement | null;
if (
!details?.classList?.contains("reasoning-tool-call") ||
!(details as HTMLDetailsElement).open
) {
return;
}
const pre = details.querySelector<HTMLElement>(
"pre.reasoning-tool-call-json",
);
if (!pre || pre.dataset.highlighted === "true") return;
// The <pre> starts as raw (escaped) JSON text; its textContent is
// the un-escaped JSON, which highlightJson re-escapes.
pre.innerHTML = sanitize(highlightJson(pre.textContent ?? ""));
pre.dataset.highlighted = "true";
},
true,
);
}

/**
* Open a full-window image viewer overlaying the chat. Supports
* wheel-zoom centered on the cursor, drag-to-pan, double-click toggle,
Expand Down Expand Up @@ -6394,6 +6431,13 @@ class AgentMessageContainer {
}
if (!text) return undefined;

// Content that is already a self-contained HTML block (the reasoning
// engine's <details> thinking and batched-tool blocks) is
// pre-structured: any JSON inside is intentional markup, not a trailing
// action payload to hoist into the details panel. Splitting it at a
// "{"/"[" line would tear the <details>/<pre> apart mid-element.
if (text.trimStart().startsWith("<")) return undefined;

// Strip ANSI to find the JSON boundary
const stripped = text.replace(/\x1b\[[0-9;]*m/g, "");
const lines = stripped.split("\n");
Expand Down
60 changes: 55 additions & 5 deletions ts/packages/chat-ui/styles/chat.css
Original file line number Diff line number Diff line change
Expand Up @@ -1805,21 +1805,26 @@ table.sc-kv-table.sc-card-fields {
`!important` because the host theme (e.g. vscode-shell's overlay)
applies `color: ... !important` to the parent bubble; without it
inheritance makes the spans render as the bubble's foreground color. */
.chat-message-details pre.chat-json .json-key {
.chat-message-details pre.chat-json .json-key,
.reasoning-tool-call pre.chat-json .json-key {
color: #9c27b0 !important;
font-weight: 600;
}
.chat-message-details pre.chat-json .json-string {
.chat-message-details pre.chat-json .json-string,
.reasoning-tool-call pre.chat-json .json-string {
color: #0a8043 !important;
}
.chat-message-details pre.chat-json .json-number {
.chat-message-details pre.chat-json .json-number,
.reasoning-tool-call pre.chat-json .json-number {
color: #1976d2 !important;
}
.chat-message-details pre.chat-json .json-bool {
.chat-message-details pre.chat-json .json-bool,
.reasoning-tool-call pre.chat-json .json-bool {
color: #b85c00 !important;
font-weight: 600;
}
.chat-message-details pre.chat-json .json-null {
.chat-message-details pre.chat-json .json-null,
.reasoning-tool-call pre.chat-json .json-null {
color: #888 !important;
font-style: italic;
}
Expand All @@ -1828,6 +1833,51 @@ table.sc-kv-table.sc-card-fields {
display: block;
}

/* Logged tool calls inside a reasoning step. Every tool call (single or a folded
run "Tool: <name> xN") renders as a native <details> whose <summary> is a
clickable line with the tool name as inline code; expanding it reveals only
that call's own JSON (same look as the clickable action JSON view), independent
of the enclosing action's JSON. The disclosure marker is hidden so it reads as
a plain line. */
.reasoning-tool-call {
margin: 2px 0;
}
.reasoning-tool-call-summary {
cursor: pointer;
list-style: none;
}
.reasoning-tool-call-summary::-webkit-details-marker {
display: none;
}
.reasoning-tool-call-summary strong {
font-weight: 600;
}
/* Tool name chip — light-gray background, yellow/gold text (matches how inline
code renders in the reasoning text and how a single tool call looks). */
.reasoning-tool-call-summary code {
background: rgba(127, 127, 127, 0.18);
color: #9a7d0a;
padding: 0 4px;
border-radius: 3px;
font-family: Consolas, "Courier New", monospace;
font-size: 0.95em;
}
.reasoning-tool-call-json {
margin: 4px 0 0 0;
padding: 8px;
background: rgba(127, 127, 127, 0.1);
border-radius: 6px;
white-space: pre-wrap;
word-break: break-word;
overflow-x: auto;
max-width: 100%;
max-height: 400px;
overflow-y: auto;
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
line-height: 1.4;
}

/* =========================================================================
Choice Panel
========================================================================= */
Expand Down
133 changes: 133 additions & 0 deletions ts/packages/chat-ui/test/chatPanel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,139 @@ describe("notifications (persistent, dismissable)", () => {
});
});

describe("reasoning tool calls (single + folded)", () => {
// Mirrors what the reasoning engine emits for a logged tool call: a native
// <details class="reasoning-tool-call"> with a <summary> (tool name as inline
// code) and a <pre> holding only that call's own JSON, collapsed until opened.
// Sent as a markdown display message (MarkdownIt passes the block-level HTML
// through verbatim). Folded runs carry a JSON array; single calls a lone object.
const foldedHtml =
'<details class="reasoning-tool-call">' +
'<summary class="reasoning-tool-call-summary"><strong>Tool:</strong> ' +
"<code>read_conversation</code> x2</summary>" +
'<pre class="chat-json reasoning-tool-call-json">[\n' +
' {\n "tool": "read_conversation",\n "arguments": {\n "offset": 0\n }\n },\n' +
' {\n "tool": "read_conversation",\n "arguments": {\n "offset": 6\n }\n }\n' +
"]</pre></details>";

const singleHtml =
'<details class="reasoning-tool-call">' +
'<summary class="reasoning-tool-call-summary"><strong>Tool:</strong> ' +
"<code>get_conversation_info</code></summary>" +
'<pre class="chat-json reasoning-tool-call-json">{\n' +
' "tool": "get_conversation_info",\n "arguments": {\n "limit": 1\n }\n' +
"}</pre></details>";

function addRun(panel: ChatPanel, html: string) {
panel.addUserMessage("run a tool", "req-1");
panel.addAgentMessage(
{ type: "markdown", content: html, kind: "info" },
"dispatcher.reasoningAction.copilot",
undefined,
"step",
"req-1",
);
}

it("renders a folded run's summary and collapsed JSON array", () => {
const { root, panel } = makePanel();
addRun(panel, foldedHtml);

const details = root.querySelector<HTMLDetailsElement>(
"details.reasoning-tool-call",
);
expect(details).not.toBeNull();
// Native <details> is collapsed until the user opens it.
expect(details!.open).toBe(false);
const summary = root.querySelector<HTMLElement>(
".reasoning-tool-call-summary",
);
expect(summary).not.toBeNull();
// Tool name is inline code, not split apart by the action-JSON splitter.
expect(summary!.querySelector("code")!.textContent).toBe(
"read_conversation",
);
expect(summary!.textContent).toContain("x2");
const pre = root.querySelector<HTMLElement>(
"pre.reasoning-tool-call-json",
);
expect(pre).not.toBeNull();
const parsed = JSON.parse(pre!.textContent ?? "");
expect(parsed).toHaveLength(2);
expect(parsed[1].arguments.offset).toBe(6);
});

it("renders a single tool call as its own collapsed block with object JSON", () => {
const { root, panel } = makePanel();
addRun(panel, singleHtml);

const details = root.querySelector<HTMLDetailsElement>(
"details.reasoning-tool-call",
)!;
expect(details.open).toBe(false);
const summary = root.querySelector<HTMLElement>(
".reasoning-tool-call-summary",
)!;
expect(summary.querySelector("code")!.textContent).toBe(
"get_conversation_info",
);
expect(summary.textContent).not.toContain("x");
const pre = root.querySelector<HTMLElement>(
"pre.reasoning-tool-call-json",
)!;
// Only the relevant JSON for this one call — a lone object.
expect(JSON.parse(pre.textContent ?? "")).toEqual({
tool: "get_conversation_info",
arguments: { limit: 1 },
});
});

it("keeps each call's JSON inline, not in the action-data details panel", () => {
const { root, panel } = makePanel();
addRun(panel, singleHtml);

const pre = root.querySelector<HTMLElement>(
"pre.reasoning-tool-call-json",
)!;
// The JSON lives in the message content, decoupled from the clickable
// action JSON view (.chat-message-details) of the reasoningAction bubble.
expect(pre.closest(".chat-message-content")).not.toBeNull();
expect(pre.closest(".chat-message-details")).toBeNull();
});

it("syntax-highlights the JSON once, the first time the block opens", () => {
const { root, panel } = makePanel();
addRun(panel, foldedHtml);

const details = root.querySelector<HTMLDetailsElement>(
"details.reasoning-tool-call",
)!;
const pre = root.querySelector<HTMLElement>(
"pre.reasoning-tool-call-json",
)!;
expect(pre.querySelector(".json-key")).toBeNull();

// Native <details> handles show/hide + keyboard on its own; our capture-
// phase `toggle` listener highlights the JSON once when the block first
// opens. Drive the toggle event directly since jsdom doesn't run the
// native summary-click -> open behavior.
details.open = true;
details.dispatchEvent(new Event("toggle"));
expect(pre.dataset.highlighted).toBe("true");
expect(pre.querySelector(".json-key")).not.toBeNull();
expect(pre.querySelector(".json-string")).not.toBeNull();

// Closing and re-opening does not re-highlight or duplicate the body.
const highlightedHtml = pre.innerHTML;
details.open = false;
details.dispatchEvent(new Event("toggle"));
expect(pre.innerHTML).toBe(highlightedHtml);
details.open = true;
details.dispatchEvent(new Event("toggle"));
expect(pre.innerHTML).toBe(highlightedHtml);
});
});

describe("question form wizard (paged)", () => {
const form: QuestionForm = {
message: "Q",
Expand Down
6 changes: 3 additions & 3 deletions ts/packages/dispatcher/dispatcher/README.AUTOGEN.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<!-- AUTOGEN:DOCS:START -->

<!-- AUTOGEN:DOCS:HASH:sha256=2936f0f60995ac4d33d834840fececd2e3f472e349a87ccad9ebdae0ddff2f4e -->
<!-- AUTOGEN:DOCS:HASH:sha256=1ecb293213fb971be98bf94d00394caa1670ef6ac8a2ca5439c3089635ecb65a -->
<!-- AUTOGEN:DOCS:SOURCE: ./README.md (hand-written documentation; this file is the AI-generated companion) -->

# agent-dispatcher — AI-generated documentation
Expand All @@ -12,7 +12,7 @@

## Overview

The TypeAgent Dispatcher is a TypeScript library that acts as the central component of the TypeAgent ecosystem. It processes user inputs, translates natural language into structured actions using large language models (LLMs), and coordinates interactions across various application agents. The Dispatcher is designed to integrate with multiple front ends, such as the TypeAgent Shell and CLI, and supports an extensible architecture for building personal agents with natural language interfaces.
The TypeAgent Dispatcher is a TypeScript library that serves as the core component of the TypeAgent ecosystem. It processes user inputs, translates natural language into structured actions using large language models (LLMs), and coordinates interactions across various application agents. The Dispatcher is designed to integrate with multiple front ends, such as the TypeAgent Shell and CLI, and supports an extensible architecture for building personal agents with natural language interfaces.

## What it does

Expand Down Expand Up @@ -236,6 +236,6 @@ _9 environment variables referenced from `./src/` (set in `ts/.env` or your shel

---

_Auto-generated against commit `ff9101e00228c5d0ff34c8dd3f108f46af46b0a6` on `2026-07-23T00:41:03.476Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-dispatcher docs:verify-links` to spot-check._
_Auto-generated against commit `274f0c51c3f1dca4e627c5311084db01d02fe1e9` on `2026-07-23T15:09:16.468Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-dispatcher docs:verify-links` to spot-check._

<!-- AUTOGEN:DOCS:END -->
Loading
Loading