From 11e743373da727b5e6802bc5663fc6d31c4193f2 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 22 Dec 2025 00:23:01 +0100 Subject: [PATCH] Fix syntax highlighting stderr spam with malformed markdown, fixes #274 --- package-lock.json | 40 ++++---- packages/agent/package.json | 6 +- packages/ai/package.json | 2 +- packages/coding-agent/CHANGELOG.md | 6 ++ packages/coding-agent/package.json | 8 +- .../src/modes/interactive/theme/theme.ts | 10 +- .../assistant-message-with-thinking-code.json | 33 +++++++ .../test/streaming-render-debug.ts | 97 +++++++++++++++++++ packages/mom/package.json | 8 +- packages/pods/package.json | 4 +- packages/proxy/package.json | 2 +- packages/tui/package.json | 2 +- packages/web-ui/example/package.json | 2 +- packages/web-ui/package.json | 6 +- 14 files changed, 183 insertions(+), 43 deletions(-) create mode 100644 packages/coding-agent/test/fixtures/assistant-message-with-thinking-code.json create mode 100644 packages/coding-agent/test/streaming-render-debug.ts diff --git a/package-lock.json b/package-lock.json index 277982ff..23aced47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6447,11 +6447,11 @@ }, "packages/agent": { "name": "@mariozechner/pi-agent-core", - "version": "0.25.3", + "version": "0.25.4", "license": "MIT", "dependencies": { - "@mariozechner/pi-ai": "^0.25.3", - "@mariozechner/pi-tui": "^0.25.3" + "@mariozechner/pi-ai": "^0.25.4", + "@mariozechner/pi-tui": "^0.25.4" }, "devDependencies": { "@types/node": "^24.3.0", @@ -6481,7 +6481,7 @@ }, "packages/ai": { "name": "@mariozechner/pi-ai", - "version": "0.25.3", + "version": "0.25.4", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "0.71.2", @@ -6523,12 +6523,12 @@ }, "packages/coding-agent": { "name": "@mariozechner/pi-coding-agent", - "version": "0.25.3", + "version": "0.25.4", "license": "MIT", "dependencies": { - "@mariozechner/pi-agent-core": "^0.25.3", - "@mariozechner/pi-ai": "^0.25.3", - "@mariozechner/pi-tui": "^0.25.3", + "@mariozechner/pi-agent-core": "^0.25.4", + "@mariozechner/pi-ai": "^0.25.4", + "@mariozechner/pi-tui": "^0.25.4", "chalk": "^5.5.0", "cli-highlight": "^2.1.11", "diff": "^8.0.2", @@ -6568,13 +6568,13 @@ }, "packages/mom": { "name": "@mariozechner/pi-mom", - "version": "0.25.3", + "version": "0.25.4", "license": "MIT", "dependencies": { "@anthropic-ai/sandbox-runtime": "^0.0.16", - "@mariozechner/pi-agent-core": "^0.25.3", - "@mariozechner/pi-ai": "^0.25.3", - "@mariozechner/pi-coding-agent": "^0.25.3", + "@mariozechner/pi-agent-core": "^0.25.4", + "@mariozechner/pi-ai": "^0.25.4", + "@mariozechner/pi-coding-agent": "^0.25.4", "@sinclair/typebox": "^0.34.0", "@slack/socket-mode": "^2.0.0", "@slack/web-api": "^7.0.0", @@ -6613,10 +6613,10 @@ }, "packages/pods": { "name": "@mariozechner/pi", - "version": "0.25.3", + "version": "0.25.4", "license": "MIT", "dependencies": { - "@mariozechner/pi-agent-core": "^0.25.3", + "@mariozechner/pi-agent-core": "^0.25.4", "chalk": "^5.5.0" }, "bin": { @@ -6629,7 +6629,7 @@ }, "packages/proxy": { "name": "@mariozechner/pi-proxy", - "version": "0.25.3", + "version": "0.25.4", "dependencies": { "@hono/node-server": "^1.14.0", "hono": "^4.6.16" @@ -6645,7 +6645,7 @@ }, "packages/tui": { "name": "@mariozechner/pi-tui", - "version": "0.25.3", + "version": "0.25.4", "license": "MIT", "dependencies": { "@types/mime-types": "^2.1.4", @@ -6689,12 +6689,12 @@ }, "packages/web-ui": { "name": "@mariozechner/pi-web-ui", - "version": "0.25.3", + "version": "0.25.4", "license": "MIT", "dependencies": { "@lmstudio/sdk": "^1.5.0", - "@mariozechner/pi-ai": "^0.25.3", - "@mariozechner/pi-tui": "^0.25.3", + "@mariozechner/pi-ai": "^0.25.4", + "@mariozechner/pi-tui": "^0.25.4", "docx-preview": "^0.3.7", "jszip": "^3.10.1", "lucide": "^0.544.0", @@ -6715,7 +6715,7 @@ }, "packages/web-ui/example": { "name": "pi-web-ui-example", - "version": "1.13.3", + "version": "1.13.4", "dependencies": { "@mariozechner/mini-lit": "^0.2.0", "@mariozechner/pi-ai": "file:../../ai", diff --git a/packages/agent/package.json b/packages/agent/package.json index 7cc1c6ad..fe701f13 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-agent-core", - "version": "0.25.3", + "version": "0.25.4", "description": "General-purpose agent with transport abstraction, state management, and attachment support", "type": "module", "main": "./dist/index.js", @@ -18,8 +18,8 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { - "@mariozechner/pi-ai": "^0.25.3", - "@mariozechner/pi-tui": "^0.25.3" + "@mariozechner/pi-ai": "^0.25.4", + "@mariozechner/pi-tui": "^0.25.4" }, "keywords": [ "ai", diff --git a/packages/ai/package.json b/packages/ai/package.json index 1094b4fa..db33addd 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-ai", - "version": "0.25.3", + "version": "0.25.4", "description": "Unified LLM API with automatic model discovery and provider configuration", "type": "module", "main": "./dist/index.js", diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 28288310..0ed6b38b 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## [0.25.4] - 2025-12-22 + +### Fixed + +- **Syntax highlighting stderr spam**: Fixed cli-highlight logging errors to stderr when markdown contains malformed code fences (e.g., missing newlines around closing backticks). Now validates language identifiers before highlighting and falls back silently to plain text. ([#274](https://github.com/badlogic/pi-mono/issues/274)) + ## [0.25.3] - 2025-12-21 ### Added diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index da455e0c..7ea5b66f 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-coding-agent", - "version": "0.25.3", + "version": "0.25.4", "description": "Coding agent CLI with read, bash, edit, write tools and session management", "type": "module", "piConfig": { @@ -39,9 +39,9 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { - "@mariozechner/pi-agent-core": "^0.25.3", - "@mariozechner/pi-ai": "^0.25.3", - "@mariozechner/pi-tui": "^0.25.3", + "@mariozechner/pi-agent-core": "^0.25.4", + "@mariozechner/pi-ai": "^0.25.4", + "@mariozechner/pi-tui": "^0.25.4", "chalk": "^5.5.0", "cli-highlight": "^2.1.11", "diff": "^8.0.2", diff --git a/packages/coding-agent/src/modes/interactive/theme/theme.ts b/packages/coding-agent/src/modes/interactive/theme/theme.ts index 3a6705ce..ad8d3fb1 100644 --- a/packages/coding-agent/src/modes/interactive/theme/theme.ts +++ b/packages/coding-agent/src/modes/interactive/theme/theme.ts @@ -4,7 +4,7 @@ import type { EditorTheme, MarkdownTheme, SelectListTheme } from "@mariozechner/ import { type Static, Type } from "@sinclair/typebox"; import { TypeCompiler } from "@sinclair/typebox/compiler"; import chalk from "chalk"; -import { highlight } from "cli-highlight"; +import { highlight, supportsLanguage } from "cli-highlight"; import { getCustomThemesDir, getThemesDir } from "../../../config.js"; // ============================================================================ @@ -673,8 +673,10 @@ function getCliHighlightTheme(t: Theme): CliHighlightTheme { * Returns array of highlighted lines. */ export function highlightCode(code: string, lang?: string): string[] { + // Validate language before highlighting to avoid stderr spam from cli-highlight + const validLang = lang && supportsLanguage(lang) ? lang : undefined; const opts = { - language: lang, + language: validLang, ignoreIllegals: true, theme: getCliHighlightTheme(theme), }; @@ -773,8 +775,10 @@ export function getMarkdownTheme(): MarkdownTheme { underline: (text: string) => theme.underline(text), strikethrough: (text: string) => chalk.strikethrough(text), highlightCode: (code: string, lang?: string): string[] => { + // Validate language before highlighting to avoid stderr spam from cli-highlight + const validLang = lang && supportsLanguage(lang) ? lang : undefined; const opts = { - language: lang, + language: validLang, ignoreIllegals: true, theme: getCliHighlightTheme(theme), }; diff --git a/packages/coding-agent/test/fixtures/assistant-message-with-thinking-code.json b/packages/coding-agent/test/fixtures/assistant-message-with-thinking-code.json new file mode 100644 index 00000000..1acb123a --- /dev/null +++ b/packages/coding-agent/test/fixtures/assistant-message-with-thinking-code.json @@ -0,0 +1,33 @@ +{ + "role": "assistant", + "content": [ + { + "type": "thinking", + "thinking": "So `findModel` just looks up a model by provider + id from the merged models list (built-in + models.json).\n\nFor the SDK, the flow would be:1. `model` provided → use it\n2. Else check settings for default → call `findModel(provider, id)` → check if available (has API key)\n3. Else first from `getAvailableModels()`\n\nThe `findModel` function returns `{ model, error }`. For SDK export, we might want a simpler signature.Options for SDK:\n\n**Option A: Keep current signature**```typescript\nfunction findModel(provider: string, modelId: string, agentDir?: string): { model: Model | null; error: string | null };\n```**Option B: Simpler - just returns model or null**\n```typescript\nfunction findModel(provider: string, modelId: string, agentDir?: string): Model | null;```\n\n**Option C: Throws on error**```typescript\nfunction findModel(provider: string, modelId: string, agentDir?: string): Model; // throws if not found\n```\n\nI think **Option B** is fine for SDK. Errors from loading models.json are edge cases - if the file is malformed, we can throw. For \"model not found\", just return null.\n\nActually, for the SDK, maybe we should rethink. The user of the SDK will likely:\n1. Know which model they want and construct it directly2. Or want to pick from available modelsFor case 1, they don't need findModel - they construct the Model object.\nFor case 2, they use `discoverAvailableModels()` and pick.`findModel` is useful for looking up by string ID (like from config or user input). But the signature could just be:```typescriptfunction findModel(provider: string, modelId: string, agentDir?: string): Model | null;\n```\n\nReturns the model if found (in built-ins or models.json), null if not found. Throws if models.json is malformed (exceptional case).\n\n\nThe SDK's model discovery functions provide a clean, straightforward way to handle model selection. I'll outline the key methods for finding and selecting models programmatically. The core functions allow discovering all models, filtering to available models with valid API keys, and finding a specific model by provider and ID. This gives developers flexible options for model management in their applications.\n\nIn the session creation process, if no model is explicitly specified, the system will first attempt to use the default provider and model configuration. If that fails, it will then retrieve the first available model from the discovered set. Should no models be found, an error will be raised to prevent silent failures.\n\nThe potential complexity arises from the need to support custom model configurations and ensure robust model selection across different scenarios.", + "thinkingSignature": "EqsVCkYIChgCKkBUqXXsLfuqzbiQxI5MbbS31Yw670soGNGQX63Lb/osACS2A5rrS8Gd939xNdXbHUGouQngzqIPs2KFk9HWmKajEgxqYOzDBE7h++0vFUUaDF8r9MacsHyJPwr3XiIwn3spAIQV8IxgAIFoxdYefFrCAeez7pnXqUqaK2QTTG3OjWpCIYzPwvEVs7ObbWVbKpIUy2X7MkKrZOdtlTGRUvmuEij6vCbXjPwj0zH+mjaefERbkL+aT84QCiStHqc7uuM5nZvntl4KZ76Mt1VrFoBXwi3val4fJDP9GhDj7tkD0Id22udIb+yHBuo8yBnyy2fWLMaeRTEn8vN2eUaqiuE7wvgvPF4tf6bn4mKjh/HEwpAzJ+rLsE/hmXA9eG/hub387iF4rnLP/rDJR4olzSQyb7bPpdQ5RLRIymkRJce4wRY0nFxPuZayiYooGwI7gqKPJz2mkTCdWZABn4n6PpqZB+caXCn63A3WvJtZacItZ6z3DAoi2I3jwsOC8BWQmHKBfCXd9wttQ+HuYYmduASJ3j/TNtdO1vZsiItknKneZXTPhmt0nuqphgWiDWnPFv1iOoJw++tLJO+u2hYOtM/3Nx6O+l9QWcQgkgnQjN29SRd7uiI14sTogJkWVrVaKJ6StXx+/mXrro7I++6PSBMnFJevIJ89MFVB8EiYs+x4pOuEJDaNekBU3Tm6+Eg4vL2SguijClR9yv+4bQsIHKtq6QLLABt1SuNRvO9HgUIOx6HDdn0PXeInhqJ/aILA4bRryf6lbRp0qNEcexAVrT8zbrMUkY2SzMX1kEo4IvmprCzmukHXQdal2AoxSdxPp2br12Lcz0njxzhWFd58f0gLRVHKf7gGzTWe6EGVfvve7/yquhVG1IWkDid54PcdqUEpIbeRZE4gklPQhEflfZ9ppnyeRDVmBq4N9Wmv+S19z8/sLRXMXBM2Lv31vVf7QXjZGmJxEWpKfXGPOmuChZsgZuMZSVoXSh9u+gr+M29Se6ArQ/L18/3p8grm8TwT2TKuaMeuIdki7Ja0jQQYPOqoIVHVXahtVto/4YVGcClx6eTbNtXDfKDKnWw7Eu+l+6wjF9nqEjTLQIxjpT6ABWhXw1ersAFIDgDDwRLUZFHZ8i1jQKvg3IxgWsqIyyMXjwm1gfwzeeOrNIkx8KwIGybeheHX1vZRsqaOAhARiziiBsl4PLD8ci6OLJgp1ZBke9QW8DFFwMZY6hNf4yYOb0/6K2g+qx9Z0OuHW7p2MRef97oLiDyx/WCNgv6DUW2FxHy2KjtcB50aeSLfccBCJOXkRlnym08nsBYa7H17REi2O30wkoOPnOYNqytE40EPYwqUPUdRF6WwN6LFEpbGGmQ5atrJ/upzz+MoBoeqeoF0fOrO3AaW27E7dvduDCrK2hF/TZZN5FHipNNHP/JY5NhWPBhCBumxJN9uf+nGqPcQwn3IL0eriz9ki0EUBdAYXY9kCxKYU3DhsbLsBn3YfhXLbLIT1Woy4RUqkWN7BXOC8aWi+uLVm0JUXVt/dr6ndnxdyqJdxc22Wz4EHFZZe+VtntNr1BF/6VsUoQSsSR1c0QvbxPE3iLhZ3R9RPmKduotJsQ6hb3aZrAgsMF5KWlmOKcouGQW1TNEwd8tI8Rxg91FdOuU0o98LddVlUFknfYr9gUn3/NorpUCKjDgZDyY4Oy7QeHWg9E6s6jeH1aYhHsO8mZiPGxQi4n5y0pSU8jFHEoIvlgQ+hN+7bsYRfUNMXfxsYuUZKiUqvCIiInu6W1dkxjS2GOmiQcCjB9XzOxF9gHXEkU2E4xHmSkbpBGrJjR/DHZ8gsosTPDg9VmFY2aYX/WLGYbjguzaKD8zS9LpQ3UZmbC0Jv9bZUGn3TdRRJj+xLY4fqWxEvplWNTJRTAPkHlQbawvgs8ziL9gBmfohPKHg+MA4bFCP2BPaaw/Xmw03TuDhaQ/Nb4e52N7heoN3DMd3NUQl/YFeb4kqzcF24GLhLi/Pbl2Y/JehWVgNyFeIvMkk7laFgydLqCMTWGl8VHiy3koUXOgPG/s/qERzIyYprLd/h5gcGt0aQMgl089UU69wUhT0xXkZjuUSMeCUKHLgjvhbn6gaMoMCrcqe+Ar0eZPGeW7OR9w8jhC/rE5Lh8zMpQ2uKo2Hwi/eFZul6Qq1ZSthx0kcsbqT8wW6Fyr8O42mxUmBVS8TUhvVSOccGVy5tBOXQpxQPgYbXNyUy3obUi9vhPzViEbt6KDIAW5bQwbuDSMHd+tf9nWd8H1nvEO2aWM6/v4+/qLSWqMcTXs3Rea2+GFMQkbRzj1pRN1MLzSjBP5pGLlYPQre5RHK3kImZ7ISMj7oQWfzNYLkswkD2Ay3nzk6v4JpjaFNFAaOhTHjtO0c4qA2elkvQ/5RrtD4g4/wlH+p048wIiuQhw4Iiu3rcFrclXUWny74ON5n56OY5uIXsPsmQQwCGUwtZFBVe5bP3nVgoHCBPI0SyEQXxgbd4q0o+HZyjkH9KdOL6LpxdxbrqbvONS6/EMMheWHxDAmibL5pFJh4z60o+aNejvMoZahKX04M5/KC1k7gwzAn/yIxC+VEPi/IijxKKlU0mEPE+q/HAHTe7S5CdrM5vWzgzNefKk0PjMW3/OnveH9mFoMHmIybWgrCZPlPzLyL3PPBW1Iv6q1g/NOzfxczx/ZbudD3UQOY0u84Acjcb938Y7uvUNHPLfSopleds0hGGgeUGy6aLdidmypcc3b8icF8k3KDozTN0v/3EqgLzb4PY6HML6dIwI6UYpeMvb110GWh1mXgl45v4afFwojhp0Ld92WnOrxEIMKv9/S6NCiUxR6KwAhp7ssPzdPvlTTtlmN01Xn95+Vo4GuZHvgyjcBnF9dIy+WJhwDRcgLrwV+wkZuGR71ACKTdHE3jW3QEuWlf4HuV+63c/OZj3B2rB2s2zadJVGDBn35dX434ZnJZudakoOGcK/0LZ2bhSN8qCkxs/2KJk7TMtBi6wsmQ7VGw74I1+c45iPjRcaO63UO/1rI7dZZZkD5lKje8BgwPBt+V+0DW6OHrfYC6qf+Nc5xrkE8Q6R4fbijYf3MkaDQ1FhJpGUiBIZ90tXPcs06utEq8kNT1kMGLdgoFlHRA3gQzJY7vU2kYVNs0sTp1pQL3JPSHtBdITHnITJLN4tVun0lrv4Ev4L4qkXT+tygwmmYvlR+JlnxkdMDd0DhNcv4lF9ltD1zQaTgfPjSbl3+lW/znRKgXTXqxDzHDIusv5u0lna2X7M7WWXoucp3eyHqTtZiP4DgsDgh9lNiwz4zu0Uqh7DNF5ZTWrSwEItMbQc0vs3Bryd7TirHAOK928rxRwl2Vo9YdDxEbVGV7OmSGdu3NOLDVp/oFvwsSUhFD/BwjgX9xSbLgLab8g3lEsbMGnA9h+aY0M1nSlmjjLavl5XvMvwd6VDRAbEMb+/FdI6Sp/z3/Yx4VmKeh7lB3rPPF6n9RdUijTKbBn6hVRyEZw30n5BMxRwGYxFxb5xhPhOLpmj6UNEQJUkileYUhEGN7o/GMLBBT7eOXujXJhq/ADhex7Wilj5rFy0VtIfuUkrNXpRxVLOdOTA3/eNxYn21B91jFM7UWCZc42BGZhkEvxFuIS+wK8u3TBvMv3RgB" + }, + { + "type": "text", + "text": "So the current flow in main.ts:\n\n**findInitialModelForSession:**\n1. CLI args (`--provider` + `--model`) → use if both provided\n2. Scoped models (`--models` flag) → use first (unless --continue/--resume)\n3. Settings (`defaultProvider` + `defaultModel`) → use if found\n4. First available model with valid API key\n5. Return null if none\n\n**initialThinking:**\n1. Start with `'off'`\n2. If scoped models (and not continuing) → use first scoped model's thinking level\n3. Else check `settings.defaultThinkingLevel`\n4. CLI `--thinking` overrides\n5. Clamp to model capabilities\n\n---\n\nFor SDK, we don't have CLI args or scoped models. Simplifies to:\n\n**model resolution (if omitted):**\n1. Check `settings.defaultProvider` + `settings.defaultModel` → find it, verify has API key\n2. Else first from `discoverAvailableModels()`\n3. Error if none\n\n**thinkingLevel resolution (if omitted):**\n1. Check `settings.defaultThinkingLevel`\n2. Else `'off'`\n3. Clamp to model capabilities\n\n---\n\nFor `findModel`, current signature returns `{ model, error }`. For SDK, simpler:\n\n```typescript\n// Returns model or null. Throws only if models.json is malformed.\nexport function findModel(provider: string, modelId: string, agentDir?: string): Model | null;\n```\n\nSo the model-related exports:\n\n```typescript\n// All models (built-in + models.json)\nexport function discoverModels(agentDir?: string): Model[];\n\n// Models with valid API keys\nexport function discoverAvailableModels(agentDir?: string): Promise[]>;\n\n// Lookup by provider/id\nexport function findModel(provider: string, modelId: string, agentDir?: string): Model | null;\n```\n\nDoes this address the wonkiness, or is there something else bothering you about it?" + } + ], + "api": "anthropic-messages", + "provider": "anthropic", + "model": "claude-opus-4-5", + "usage": { + "input": 8, + "output": 1211, + "cacheRead": 89775, + "cacheWrite": 611, + "totalTokens": 91605, + "cost": { + "input": 4e-5, + "output": 0.030275, + "cacheRead": 0.0448875, + "cacheWrite": 0.00381875, + "total": 0.07902124999999999 + } + }, + "stopReason": "stop", + "timestamp": 1766357372971 +} diff --git a/packages/coding-agent/test/streaming-render-debug.ts b/packages/coding-agent/test/streaming-render-debug.ts new file mode 100644 index 00000000..05f72e5c --- /dev/null +++ b/packages/coding-agent/test/streaming-render-debug.ts @@ -0,0 +1,97 @@ +/** + * Debug script to reproduce streaming rendering issues. + * Uses real fixture data that caused the bug. + * Run with: npx tsx test/streaming-render-debug.ts + */ + +import type { AssistantMessage } from "@mariozechner/pi-ai"; +import { ProcessTerminal, TUI } from "@mariozechner/pi-tui"; +import { readFileSync } from "fs"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; +import { AssistantMessageComponent } from "../src/modes/interactive/components/assistant-message.js"; +import { initTheme } from "../src/modes/interactive/theme/theme.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Initialize dark theme with full color support +process.env.COLORTERM = "truecolor"; +initTheme("dark"); + +// Load the real fixture that caused the bug +const fixtureMessage: AssistantMessage = JSON.parse( + readFileSync(join(__dirname, "fixtures/assistant-message-with-thinking-code.json"), "utf-8"), +); + +// Extract thinking and text content +const thinkingContent = fixtureMessage.content.find((c) => c.type === "thinking"); +const textContent = fixtureMessage.content.find((c) => c.type === "text"); + +if (!thinkingContent || thinkingContent.type !== "thinking") { + console.error("No thinking content in fixture"); + process.exit(1); +} + +const fullThinkingText = thinkingContent.thinking; +const fullTextContent = textContent && textContent.type === "text" ? textContent.text : ""; + +async function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function main() { + const terminal = new ProcessTerminal(); + const tui = new TUI(terminal); + + // Start with empty message + const message = { + role: "assistant", + content: [{ type: "thinking", thinking: "" }], + } as AssistantMessage; + + const component = new AssistantMessageComponent(message, false); + tui.addChild(component); + tui.start(); + + // Simulate streaming thinking content + let thinkingBuffer = ""; + const chunkSize = 10; // characters per "token" + + for (let i = 0; i < fullThinkingText.length; i += chunkSize) { + thinkingBuffer += fullThinkingText.slice(i, i + chunkSize); + + // Update message content + const updatedMessage = { + role: "assistant", + content: [{ type: "thinking", thinking: thinkingBuffer }], + } as AssistantMessage; + + component.updateContent(updatedMessage); + tui.requestRender(); + + await sleep(15); // Simulate token delay + } + + // Now add the text content + await sleep(500); + + const finalMessage = { + role: "assistant", + content: [ + { type: "thinking", thinking: fullThinkingText }, + { type: "text", text: fullTextContent }, + ], + } as AssistantMessage; + + component.updateContent(finalMessage); + tui.requestRender(); + + // Keep alive for a moment to see the result + await sleep(3000); + + tui.stop(); + process.exit(0); +} + +main().catch(console.error); diff --git a/packages/mom/package.json b/packages/mom/package.json index 4022b9bb..a957f606 100644 --- a/packages/mom/package.json +++ b/packages/mom/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-mom", - "version": "0.25.3", + "version": "0.25.4", "description": "Slack bot that delegates messages to the pi coding agent", "type": "module", "bin": { @@ -21,9 +21,9 @@ }, "dependencies": { "@anthropic-ai/sandbox-runtime": "^0.0.16", - "@mariozechner/pi-agent-core": "^0.25.3", - "@mariozechner/pi-ai": "^0.25.3", - "@mariozechner/pi-coding-agent": "^0.25.3", + "@mariozechner/pi-agent-core": "^0.25.4", + "@mariozechner/pi-ai": "^0.25.4", + "@mariozechner/pi-coding-agent": "^0.25.4", "@sinclair/typebox": "^0.34.0", "@slack/socket-mode": "^2.0.0", "@slack/web-api": "^7.0.0", diff --git a/packages/pods/package.json b/packages/pods/package.json index 73d5057b..bbd804f1 100644 --- a/packages/pods/package.json +++ b/packages/pods/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi", - "version": "0.25.3", + "version": "0.25.4", "description": "CLI tool for managing vLLM deployments on GPU pods", "type": "module", "bin": { @@ -34,7 +34,7 @@ "node": ">=20.0.0" }, "dependencies": { - "@mariozechner/pi-agent-core": "^0.25.3", + "@mariozechner/pi-agent-core": "^0.25.4", "chalk": "^5.5.0" }, "devDependencies": {} diff --git a/packages/proxy/package.json b/packages/proxy/package.json index b3bf8653..b4199249 100644 --- a/packages/proxy/package.json +++ b/packages/proxy/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-proxy", - "version": "0.25.3", + "version": "0.25.4", "type": "module", "description": "CORS and authentication proxy for pi-ai", "main": "dist/index.js", diff --git a/packages/tui/package.json b/packages/tui/package.json index bd491e36..8417d61a 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-tui", - "version": "0.25.3", + "version": "0.25.4", "description": "Terminal User Interface library with differential rendering for efficient text-based applications", "type": "module", "main": "dist/index.js", diff --git a/packages/web-ui/example/package.json b/packages/web-ui/example/package.json index 2c7d3197..8f26c96d 100644 --- a/packages/web-ui/example/package.json +++ b/packages/web-ui/example/package.json @@ -1,6 +1,6 @@ { "name": "pi-web-ui-example", - "version": "1.13.3", + "version": "1.13.4", "private": true, "type": "module", "scripts": { diff --git a/packages/web-ui/package.json b/packages/web-ui/package.json index e5ee805c..ecd29686 100644 --- a/packages/web-ui/package.json +++ b/packages/web-ui/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-web-ui", - "version": "0.25.3", + "version": "0.25.4", "description": "Reusable web UI components for AI chat interfaces powered by @mariozechner/pi-ai", "type": "module", "main": "dist/index.js", @@ -18,8 +18,8 @@ }, "dependencies": { "@lmstudio/sdk": "^1.5.0", - "@mariozechner/pi-ai": "^0.25.3", - "@mariozechner/pi-tui": "^0.25.3", + "@mariozechner/pi-ai": "^0.25.4", + "@mariozechner/pi-tui": "^0.25.4", "docx-preview": "^0.3.7", "jszip": "^3.10.1", "lucide": "^0.544.0",