From 1c903cd26ee963c5dff196610e2fc3b548dd0d4a Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Thu, 15 Jan 2026 05:28:38 +1000 Subject: [PATCH] feat(chat): use Streamdown for markdown rendering in chat messages - Add streamdown package for AI-optimized markdown streaming - Update dashboard and public agent chat pages to render messages with Streamdown - Enables proper markdown formatting (code blocks, lists, etc.) in chat responses --- apps/web/package.json | 1 + .../app/agents/[id]/chat/[chatId]/page.tsx | 13 +- .../agents/[id]/chat/[chatId]/page.tsx | 9 +- pnpm-lock.yaml | 3 + sprites_skill.md | 231 ++++++++++++++++++ 5 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 sprites_skill.md diff --git a/apps/web/package.json b/apps/web/package.json index 7005c18..bd3648a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -57,6 +57,7 @@ "remark-gfm": "^4.0.1", "resend": "^6.7.0", "sonner": "^2.0.7", + "streamdown": "^1.6.11", "zod": "^4.3.5" }, "devDependencies": { diff --git a/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx b/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx index ca5c669..ff09d23 100644 --- a/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx +++ b/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx @@ -8,6 +8,7 @@ import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso'; +import { Streamdown } from 'streamdown'; import { AppHeader } from '~/components/AppHeader'; import { LikeButton } from '~/components/LikeButton'; @@ -807,7 +808,9 @@ export default function PublicAgentChatPage(): React.ReactElement { {streamingContent && (
-

{streamingContent}

+
+ {streamingContent} +
@@ -884,7 +887,9 @@ export default function PublicAgentChatPage(): React.ReactElement { {message.role === 'USER' && (
-

{message.content}

+
+ {message.content} +
)} @@ -896,7 +901,9 @@ export default function PublicAgentChatPage(): React.ReactElement { {message.content && (
-

{message.content}

+
+ {message.content} +
{/* Token usage for debugging */} {(message.inputTokens || message.outputTokens) && (
diff --git a/apps/web/src/app/dashboard/agents/[id]/chat/[chatId]/page.tsx b/apps/web/src/app/dashboard/agents/[id]/chat/[chatId]/page.tsx index fabee03..6af8ff6 100644 --- a/apps/web/src/app/dashboard/agents/[id]/chat/[chatId]/page.tsx +++ b/apps/web/src/app/dashboard/agents/[id]/chat/[chatId]/page.tsx @@ -6,6 +6,7 @@ import { Icon } from '@tpmjs/ui/Icon/Icon'; import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; import { useCallback, useEffect, useRef, useState } from 'react'; +import { Streamdown } from 'streamdown'; import { DashboardLayout } from '~/components/dashboard/DashboardLayout'; interface Agent { @@ -678,7 +679,9 @@ export default function AgentChatPage(): React.ReactElement { : 'bg-surface-secondary' }`} > -

{message.content}

+
+ {message.content} +
)} @@ -707,7 +710,9 @@ export default function AgentChatPage(): React.ReactElement { {streamingContent && (
-

{streamingContent}

+
+ {streamingContent} +
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db0bdf2..2f81e7e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -338,6 +338,9 @@ importers: sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + streamdown: + specifier: ^1.6.11 + version: 1.6.11(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.3) zod: specifier: ^4.3.5 version: 4.3.5 diff --git a/sprites_skill.md b/sprites_skill.md new file mode 100644 index 0000000..b391cd8 --- /dev/null +++ b/sprites_skill.md @@ -0,0 +1,231 @@ +# Sprites Tools Development & Testing Guide + +This document covers how to build, publish, flush caches, and test the TPMJS sprites tools. + +## Credentials + +### TPMJS API Key +``` + +``` + +### SPRITES_TOKEN (set in collection env vars) +``` + +``` + +### CRON_SECRET (for triggering syncs) +``` + +``` + +## MCP Endpoint + +The sprites collection MCP endpoint: +``` +https://tpmjs.com/api/mcp/ajax/sprites/http +``` + +## Available Sprites Tools + +| Tool | Package | Description | +|------|---------|-------------| +| `sprites-list` | `@tpmjs/tools-sprites-list` | List all sprites with status | +| `sprites-get` | `@tpmjs/tools-sprites-get` | Get a specific sprite's details | +| `sprites-create` | `@tpmjs/tools-sprites-create` | Create a new sprite | +| `sprites-delete` | `@tpmjs/tools-sprites-delete` | Delete a sprite | +| `sprites-exec` | `@tpmjs/tools-sprites-exec` | Execute commands in a sprite | +| `sprites-sessions` | `@tpmjs/tools-sprites-sessions` | List active sessions | +| `sprites-policy-get` | `@tpmjs/tools-sprites-policy-get` | Get sprite policy | +| `sprites-checkpoint-list` | `@tpmjs/tools-sprites-checkpoint-list` | List checkpoints | +| `sprites-checkpoint-create` | `@tpmjs/tools-sprites-checkpoint-create` | Create a checkpoint | +| `sprites-checkpoint-restore` | `@tpmjs/tools-sprites-checkpoint-restore` | Restore from checkpoint | + +## Building a Sprites Tool + +1. Navigate to the tool directory: +```bash +cd packages/tools/official/sprites-exec +``` + +2. Build: +```bash +pnpm build +``` + +3. Type-check: +```bash +pnpm type-check +``` + +## Publishing a Sprites Tool + +1. Bump version in `package.json`: +```json +"version": "0.1.5" +``` + +2. Build and publish: +```bash +cd packages/tools/official/sprites-exec +pnpm build +npm publish +``` + +3. Commit the version bump: +```bash +git add package.json +git commit -m "chore: bump sprites-exec to 0.1.5" +git push +``` + +## Syncing to Database + +After publishing, sync the package to the TPMJS database: + +### Changes Feed Sync (fast, picks up recent npm changes) +```bash +curl -s -X POST "https://tpmjs.com/api/sync/changes" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" +``` + +### Keyword Sync (comprehensive, searches for all tpmjs packages) +```bash +curl -s -X POST "https://tpmjs.com/api/sync/keyword" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" +``` + +### Check version in database +```bash +curl -s "https://tpmjs.com/api/tools?q=sprites-exec" | jq '.data[] | select(.package.npmPackageName == "@tpmjs/tools-sprites-exec") | {name: .name, version: .package.npmVersion}' +``` + +## Flushing Executor Caches + +**CRITICAL**: The executor caches npm packages. After publishing a new version, you MUST flush the cache. + +### Flush Primary Executor (executor.tpmjs.com) +```bash +curl -s -X POST "https://executor.tpmjs.com/cache/clear" | jq . +``` + +### Flush Railway Executor (backup) +```bash +curl -s -X POST "https://endearing-commitment-production.up.railway.app/cache/clear" | jq . +``` + +### Check Cache Stats +```bash +curl -s "https://executor.tpmjs.com/cache/stats" | jq . +``` + +## Testing via MCP Endpoint + +### List All Tools +```bash +curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq '.result.tools[] | {name: .name}' +``` + +### Test sprites-list +```bash +curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-list--spritesListTool","arguments":{}}}' | jq '.result' +``` + +### Test sprites-exec (simple command) +```bash +curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-exec--spritesExecTool","arguments":{"name":"hello-script-sprite","cmd":"echo hello"}}}' | jq '.result' +``` + +### Test sprites-exec (shell operators) +```bash +curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-exec--spritesExecTool","arguments":{"name":"hello-script-sprite","cmd":"echo foo && echo bar"}}}' | jq '.result' +``` + +### Test sprites-get +```bash +curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-get--spritesGetTool","arguments":{"name":"hello-script-sprite"}}}' | jq '.result' +``` + +### Test sprites-create +```bash +curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-create--spritesCreateTool","arguments":{"name":"test-sprite-123"}}}' | jq '.result' +``` + +### Test sprites-delete +```bash +curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-delete--spritesDeleteTool","arguments":{"name":"test-sprite-123"}}}' | jq '.result' +``` + +## Testing via Agent Conversation + +```bash +curl -s -X POST "https://tpmjs.com/api/ajax/agents/sprites/conversation/test-conv?format=json" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"message":"List all sprites"}' +``` + +## Complete Workflow: Publish + Sync + Flush + Test + +```bash +# 1. Build and publish +cd packages/tools/official/sprites-exec +pnpm build && npm publish + +# 2. Trigger sync +curl -s -X POST "https://tpmjs.com/api/sync/changes" \ + -H "Authorization: Bearer " + +# 3. Flush caches +curl -s -X POST "https://executor.tpmjs.com/cache/clear" +curl -s -X POST "https://endearing-commitment-production.up.railway.app/cache/clear" + +# 4. Test +curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-exec--spritesExecTool","arguments":{"name":"hello-script-sprite","cmd":"echo test"}}}' | jq . +``` + +## Known Issues & Fixes + +### Issue: "Failed to parse response from Sprites API" +**Cause**: Sprites API returns JSON error `{"error":"..."}` with HTTP 200 instead of proper error codes. +**Fix**: Added JSON error detection in sprites-exec v0.1.4. + +### Issue: "Invalid sprite status: cold" +**Cause**: Sprites API returns "cold" status for idle sprites, which wasn't recognized. +**Fix**: Added "cold" to valid statuses in sprites-list v0.1.3. + +### Issue: "The update command takes no arguments" +**Cause**: Commands with shell operators (&&, ||, |, etc.) were being parsed incorrectly. +**Fix**: Added shell operator detection and `sh -c` wrapping in sprites-exec v0.1.5. + +### Issue: Old version still being used after publish +**Cause**: Deno HTTP import cache + executor module cache. +**Fix**: +1. Pass explicit version from database (deployed to web app) +2. Flush executor caches after publishing