[PR #11609] feat(messages): add renderAsAssistant field for user messages #13865

Open
opened 2026-02-16 18:18:41 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/11609

State: open
Merged: No


Closes #11611

Summary

This PR adds a new renderAsAssistant field to user messages, allowing plugins to send user messages that render with assistant styling in the UI. This enables plugins to display content beautifully (like plans, summaries, or reports) before requesting user approval or action.

Motivation

Plugins that generate structured content (like plans, documentation, or reports) need to present this content to users in a visually distinct way before requesting approval. The plan-mode plugin is a primary use case: it needs to display implementation plans with assistant styling before showing approval buttons via the built-in plan_exit tool.

Without this feature, plugins can only send regular user messages, which don't have the same visual presentation as assistant messages.

Changes

Backend (Core)

  • packages/opencode/src/session/message-v2.ts

    • Added renderAsAssistant: z.boolean().optional() to UserMessage schema
    • Allows user messages to opt into assistant-style rendering
  • packages/opencode/src/session/prompt.ts

    • Added renderAsAssistant to PromptInput schema
    • Passed field through to user message creation
  • packages/sdk/js/src/v2/gen/types.gen.ts

    • Regenerated SDK types to include new field
  • packages/sdk/js/src/v2/gen/sdk.gen.ts

    • Regenerated SDK to support new field

Frontend (UI)

  • packages/ui/src/components/message-part.tsx

    • Modified Message component to check renderAsAssistant field
    • When renderAsAssistant: true, renders user messages with AssistantMessageDisplay component
    • Preserves normal user message styling when field is absent or false
  • packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

    • Updated TUI to respect renderAsAssistant field for consistent rendering

Usage Example

Plugin Code (plan-mode.ts)

const displayPlan: ToolFunction = async (args, context) => {
  const planContent = await context.fs.readTextFile(planFilePath);
  
  // Send user message with assistant styling
  await context.addUserMessage({
    content: `# Implementation Plan\n\n${planContent}`,
    renderAsAssistant: true  // ← This makes it render beautifully
  });
  
  return "Plan displayed to user with assistant styling.";
};

Workflow

  1. Plugin generates content (e.g., implementation plan)
  2. Plugin calls context.addUserMessage({ content: "...", renderAsAssistant: true })
  3. UI renders the message with assistant styling (indented, styled markdown)
  4. User sees beautifully formatted content
  5. Plugin or built-in tool can then request approval (e.g., via plan_exit)

Testing

Setup

Enable experimental plan mode to test the full workflow:

export OPENCODE_EXPERIMENTAL_PLAN_MODE=true

Test Steps

  1. Install plan-mode plugin at ~/.config/opencode/plugins/plan-mode.ts (see example above)
  2. Restart OpenCode to load the plugin
  3. Switch to plan agent: /agent plan
  4. Create a plan: Agent will use write_plan, edit_plan, read_plan tools
  5. Display plan: Agent calls display_plan (plugin tool)
    • Verify plan appears with assistant styling (not regular user message)
    • Verify plan content is beautifully formatted
  6. Request approval: Agent calls plan_exit (built-in tool)
    • Verify Yes/No buttons appear
    • Click "Yes" → verify switches to build agent

Manual Testing

Without the full plugin workflow, you can test the field directly:

// In any plugin or tool
await context.addUserMessage({
  content: "## Test Content\n\nThis should render with assistant styling.",
  renderAsAssistant: true
});

Expected: Message appears indented with assistant styling, not as a regular user message.

Backward Compatibility

  • Fully backward compatible - field is optional
  • Existing user messages (without renderAsAssistant) render normally
  • No breaking changes to message schema or UI components

Dependencies

This feature works standalone but is designed to integrate with:

  • OPENCODE_EXPERIMENTAL_PLAN_MODE=true flag (enables plan_exit built-in tool)
  • Plan-mode plugin (uses renderAsAssistant for plan display)

The plugin is not part of this PR but demonstrates the primary use case.

Notes

  • The renderAsAssistant field only affects UI rendering, not message semantics
  • Messages are still user messages in the conversation history
  • The field is primarily intended for plugin-generated content display
  • Built-in tools could also use this field if needed

Checklist

  • Backend schema changes (message-v2.ts, prompt.ts)
  • Frontend UI changes (message-part.tsx, TUI)
  • SDK regenerated (types.gen.ts, sdk.gen.ts)
  • Type checking passes
  • No breaking changes
  • Backward compatible
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/11609 **State:** open **Merged:** No --- Closes #11611 ## Summary This PR adds a new `renderAsAssistant` field to user messages, allowing plugins to send user messages that render with assistant styling in the UI. This enables plugins to display content beautifully (like plans, summaries, or reports) before requesting user approval or action. ## Motivation Plugins that generate structured content (like plans, documentation, or reports) need to present this content to users in a visually distinct way before requesting approval. The plan-mode plugin is a primary use case: it needs to display implementation plans with assistant styling before showing approval buttons via the built-in `plan_exit` tool. Without this feature, plugins can only send regular user messages, which don't have the same visual presentation as assistant messages. ## Changes ### Backend (Core) - **`packages/opencode/src/session/message-v2.ts`** - Added `renderAsAssistant: z.boolean().optional()` to `UserMessage` schema - Allows user messages to opt into assistant-style rendering - **`packages/opencode/src/session/prompt.ts`** - Added `renderAsAssistant` to `PromptInput` schema - Passed field through to user message creation - **`packages/sdk/js/src/v2/gen/types.gen.ts`** - Regenerated SDK types to include new field - **`packages/sdk/js/src/v2/gen/sdk.gen.ts`** - Regenerated SDK to support new field ### Frontend (UI) - **`packages/ui/src/components/message-part.tsx`** - Modified `Message` component to check `renderAsAssistant` field - When `renderAsAssistant: true`, renders user messages with `AssistantMessageDisplay` component - Preserves normal user message styling when field is absent or false - **`packages/opencode/src/cli/cmd/tui/routes/session/index.tsx`** - Updated TUI to respect `renderAsAssistant` field for consistent rendering ## Usage Example ### Plugin Code (plan-mode.ts) ```typescript const displayPlan: ToolFunction = async (args, context) => { const planContent = await context.fs.readTextFile(planFilePath); // Send user message with assistant styling await context.addUserMessage({ content: `# Implementation Plan\n\n${planContent}`, renderAsAssistant: true // ← This makes it render beautifully }); return "Plan displayed to user with assistant styling."; }; ``` ### Workflow 1. Plugin generates content (e.g., implementation plan) 2. Plugin calls `context.addUserMessage({ content: "...", renderAsAssistant: true })` 3. UI renders the message with assistant styling (indented, styled markdown) 4. User sees beautifully formatted content 5. Plugin or built-in tool can then request approval (e.g., via `plan_exit`) ## Testing ### Setup Enable experimental plan mode to test the full workflow: ```bash export OPENCODE_EXPERIMENTAL_PLAN_MODE=true ``` ### Test Steps 1. **Install plan-mode plugin** at `~/.config/opencode/plugins/plan-mode.ts` (see example above) 2. **Restart OpenCode** to load the plugin 3. **Switch to plan agent**: `/agent plan` 4. **Create a plan**: Agent will use `write_plan`, `edit_plan`, `read_plan` tools 5. **Display plan**: Agent calls `display_plan` (plugin tool) - ✅ Verify plan appears with **assistant styling** (not regular user message) - ✅ Verify plan content is beautifully formatted 6. **Request approval**: Agent calls `plan_exit` (built-in tool) - ✅ Verify Yes/No buttons appear - ✅ Click "Yes" → verify switches to build agent ### Manual Testing Without the full plugin workflow, you can test the field directly: ```typescript // In any plugin or tool await context.addUserMessage({ content: "## Test Content\n\nThis should render with assistant styling.", renderAsAssistant: true }); ``` Expected: Message appears indented with assistant styling, not as a regular user message. ## Backward Compatibility - ✅ **Fully backward compatible** - field is optional - ✅ Existing user messages (without `renderAsAssistant`) render normally - ✅ No breaking changes to message schema or UI components ## Dependencies This feature works standalone but is designed to integrate with: - `OPENCODE_EXPERIMENTAL_PLAN_MODE=true` flag (enables `plan_exit` built-in tool) - Plan-mode plugin (uses `renderAsAssistant` for plan display) The plugin is not part of this PR but demonstrates the primary use case. ## Notes - The `renderAsAssistant` field only affects UI rendering, not message semantics - Messages are still user messages in the conversation history - The field is primarily intended for plugin-generated content display - Built-in tools could also use this field if needed ## Checklist - [x] Backend schema changes (message-v2.ts, prompt.ts) - [x] Frontend UI changes (message-part.tsx, TUI) - [x] SDK regenerated (types.gen.ts, sdk.gen.ts) - [x] Type checking passes - [x] No breaking changes - [x] Backward compatible
yindo added the pull-request label 2026-02-16 18:18:41 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#13865