Created
January 22, 2026 00:53
-
-
Save ShamanicArts/b2ae98c0f51ff2e6f7cebc93fa37978f to your computer and use it in GitHub Desktop.
Dialog API test plugin for opencode PR #9910
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Dialog Test Plugin | |
| * | |
| * Template plugin demonstrating various dialog patterns for plugin developers. | |
| * Each handler shows a different approach to using the dialog API. | |
| */ | |
| import type { Plugin } from "@opencode-ai/plugin" | |
| export const dialogTestPlugin: Plugin = async ({ client, dialog }) => { | |
| return { | |
| config: async (input) => { | |
| input.command ??= {} | |
| input.command["dialog-select"] = { | |
| template: | |
| "[DIALOG_SELECT_COMMAND]\n\nThe user wants to select a color from a dialog. Wait for their selection.\n$ARGUMENTS", | |
| description: "Test dialog with LLM response", | |
| } | |
| input.command["dialog-test"] = { | |
| template: "[DIALOG_TEST_COMMAND]", | |
| description: "Test dialog without LLM response", | |
| } | |
| input.command["dialog-drill"] = { | |
| template: "[DIALOG_DRILL_COMMAND]", | |
| description: "Test multi-level drill-down dialog", | |
| } | |
| input.command["dialog-inline-test"] = { | |
| template: "[DIALOG_INLINE_TEST_COMMAND]", | |
| description: "Test inline dialog without LLM response", | |
| } | |
| input.command["dialog-inline-select"] = { | |
| template: | |
| "[DIALOG_INLINE_SELECT_COMMAND]\n\nThe user wants to select a color from an inline dialog. Wait for their selection.\n$ARGUMENTS", | |
| description: "Test inline dialog with LLM response", | |
| } | |
| input.command["dialog-confirm"] = { | |
| template: "[DIALOG_CONFIRM_COMMAND]", | |
| description: "Test confirmation dialog", | |
| } | |
| input.command["dialog-alert"] = { | |
| template: "[DIALOG_ALERT_COMMAND]", | |
| description: "Test alert dialog", | |
| } | |
| input.command["dialog-prompt"] = { | |
| template: "[DIALOG_PROMPT_COMMAND]", | |
| description: "Test prompt dialog", | |
| } | |
| input.command["dialog-prompt-default"] = { | |
| template: "[DIALOG_PROMPT_DEFAULT_COMMAND]", | |
| description: "Test prompt dialog with default value", | |
| } | |
| }, | |
| "chat.message": async (input, output) => { | |
| const text = output.parts.find((p) => p.type === "text")?.text ?? "" | |
| // Pattern A: Modal dialog that modifies message, then sends to LLM | |
| // Use case: Let user make a choice, then have LLM respond to that choice | |
| if (text.includes("[DIALOG_SELECT_COMMAND]")) { | |
| const result = await dialog.show({ | |
| type: "select", | |
| mode: "modal", | |
| title: "Pick a Color", | |
| options: [ | |
| { value: "red", title: "Red", description: "A warm color" }, | |
| { value: "blue", title: "Blue", description: "A cool color" }, | |
| { value: "green", title: "Green", description: "Nature's color" }, | |
| ], | |
| }) | |
| if (result.dismissed) return | |
| // Test: If user selects "blue", switch to plan agent | |
| if (result.value === "blue") { | |
| output.message.agent = "plan" | |
| } | |
| // Modify message - LLM will process this | |
| const textPart = output.parts.find((p) => p.type === "text") | |
| if (textPart && "text" in textPart) { | |
| textPart.text = `User selected color: ${result.value}. Please acknowledge their choice.` | |
| } | |
| return | |
| } | |
| // Pattern B: Modal dialog that bypasses LLM entirely | |
| // Use case: Quick actions that don't need AI processing | |
| if (text.includes("[DIALOG_TEST_COMMAND]")) { | |
| const result = await dialog.show({ | |
| type: "select", | |
| mode: "modal", | |
| title: "Pick a Color (No LLM)", | |
| options: [ | |
| { value: "red", title: "Red" }, | |
| { value: "blue", title: "Blue" }, | |
| { value: "green", title: "Green" }, | |
| ], | |
| }) | |
| if (result.dismissed) throw new Error("__DIALOG_TEST_HANDLED__") | |
| // Inject response directly (bypasses LLM) | |
| await client.session.prompt({ | |
| path: { id: input.sessionID }, | |
| body: { | |
| noReply: true, | |
| agent: input.agent, | |
| model: input.model, | |
| parts: [ | |
| { | |
| type: "text", | |
| text: `You selected: ${result.value}`, | |
| ignored: true, | |
| }, | |
| ], | |
| }, | |
| }) | |
| // Throw to abort - LLM never runs | |
| throw new Error("__DIALOG_TEST_HANDLED__") | |
| } | |
| // Pattern C: Multi-level drill-down dialog | |
| // Use case: Hierarchical navigation (category -> item) | |
| if (text.includes("[DIALOG_DRILL_COMMAND]")) { | |
| const categoryResult = await dialog.show({ | |
| type: "select", | |
| mode: "modal", | |
| title: "Pick a Category", | |
| options: [ | |
| { value: "colors", title: "Colors" }, | |
| { value: "shapes", title: "Shapes" }, | |
| ], | |
| }) | |
| if (categoryResult.dismissed) throw new Error("__DIALOG_DRILL_HANDLED__") | |
| const category = categoryResult.value | |
| const options = | |
| category === "colors" | |
| ? [ | |
| { value: "red", title: "Red" }, | |
| { value: "blue", title: "Blue" }, | |
| { value: "green", title: "Green" }, | |
| ] | |
| : [ | |
| { value: "circle", title: "Circle" }, | |
| { value: "square", title: "Square" }, | |
| { value: "triangle", title: "Triangle" }, | |
| ] | |
| const itemResult = await dialog.show({ | |
| type: "select", | |
| mode: "modal", | |
| title: category === "colors" ? "Pick a Color" : "Pick a Shape", | |
| options, | |
| }) | |
| if (itemResult.dismissed) throw new Error("__DIALOG_DRILL_HANDLED__") | |
| await client.session.prompt({ | |
| path: { id: input.sessionID }, | |
| body: { | |
| noReply: true, | |
| agent: input.agent, | |
| model: input.model, | |
| parts: [{ type: "text", text: `You selected: ${itemResult.value} from ${category}`, ignored: true }], | |
| }, | |
| }) | |
| throw new Error("__DIALOG_DRILL_HANDLED__") | |
| } | |
| // Pattern D: Inline dialog (appears in session area, not modal overlay) | |
| // Use case: Non-blocking selection that keeps context visible | |
| if (text.includes("[DIALOG_INLINE_TEST_COMMAND]")) { | |
| const result = await dialog.show({ | |
| type: "select", | |
| mode: "inline", | |
| title: "Pick a Color (Inline, No LLM)", | |
| options: [ | |
| { value: "red", title: "Red" }, | |
| { value: "blue", title: "Blue" }, | |
| { value: "green", title: "Green" }, | |
| ], | |
| }) | |
| if (result.dismissed) throw new Error("__DIALOG_INLINE_TEST_HANDLED__") | |
| await client.session.prompt({ | |
| path: { id: input.sessionID }, | |
| body: { | |
| noReply: true, | |
| agent: input.agent, | |
| model: input.model, | |
| parts: [{ type: "text", text: `You selected (inline): ${result.value}`, ignored: true }], | |
| }, | |
| }) | |
| throw new Error("__DIALOG_INLINE_TEST_HANDLED__") | |
| } | |
| // Pattern E: Inline dialog that modifies message for LLM | |
| // Use case: Inline selection that feeds into AI response | |
| if (text.includes("[DIALOG_INLINE_SELECT_COMMAND]")) { | |
| const result = await dialog.show({ | |
| type: "select", | |
| mode: "inline", | |
| title: "Pick a Color (Inline)", | |
| options: [ | |
| { value: "red", title: "Red", description: "A warm color" }, | |
| { value: "blue", title: "Blue", description: "A cool color" }, | |
| { value: "green", title: "Green", description: "Nature's color" }, | |
| ], | |
| }) | |
| if (result.dismissed) throw new Error("__DIALOG_INLINE_SELECT_HANDLED__") | |
| const textPart = output.parts.find((p) => p.type === "text") | |
| if (textPart && "text" in textPart) { | |
| textPart.text = `User selected color (via inline dialog): ${result.value}. Please acknowledge their choice.` | |
| } | |
| return | |
| } | |
| // Pattern F: Confirmation dialog | |
| // Use case: Yes/No decisions | |
| if (text.includes("[DIALOG_CONFIRM_COMMAND]")) { | |
| await dialog.show({ | |
| type: "confirm", | |
| mode: "modal", | |
| title: "Confirm Action", | |
| message: "Are you sure you want to proceed with this action?", | |
| }) | |
| throw new Error("__DIALOG_CONFIRM_HANDLED__") | |
| } | |
| // Pattern G: Alert dialog | |
| // Use case: Informational messages that require acknowledgment | |
| if (text.includes("[DIALOG_ALERT_COMMAND]")) { | |
| await dialog.show({ | |
| type: "alert", | |
| mode: "modal", | |
| title: "Important Notice", | |
| message: "This is an important message that requires acknowledgment.", | |
| }) | |
| throw new Error("__DIALOG_ALERT_HANDLED__") | |
| } | |
| // Pattern H: Prompt dialog | |
| // Use case: Get text input from user | |
| if (text.includes("[DIALOG_PROMPT_COMMAND]")) { | |
| const result = await dialog.show({ | |
| type: "prompt", | |
| mode: "modal", | |
| title: "Enter Value", | |
| message: "Please enter a value below:", | |
| placeholder: "Type something...", | |
| }) | |
| if (result.dismissed) throw new Error("__DIALOG_PROMPT_HANDLED__") | |
| await client.session.prompt({ | |
| path: { id: input.sessionID }, | |
| body: { | |
| noReply: true, | |
| agent: input.agent, | |
| model: input.model, | |
| parts: [ | |
| { | |
| type: "text", | |
| text: `User entered: "${result.value}"`, | |
| ignored: true, | |
| }, | |
| ], | |
| }, | |
| }) | |
| throw new Error("__DIALOG_PROMPT_HANDLED__") | |
| } | |
| // Pattern I: Prompt dialog with default value | |
| // Use case: Get text input with pre-filled value | |
| if (text.includes("[DIALOG_PROMPT_DEFAULT_COMMAND]")) { | |
| const result = await dialog.show({ | |
| type: "prompt", | |
| mode: "modal", | |
| title: "Edit Value", | |
| message: "Edit default value below:", | |
| placeholder: "Type something...", | |
| defaultValue: "Default Text", | |
| }) | |
| if (result.dismissed) throw new Error("__DIALOG_PROMPT_DEFAULT_HANDLED__") | |
| await client.session.prompt({ | |
| path: { id: input.sessionID }, | |
| body: { | |
| noReply: true, | |
| agent: input.agent, | |
| model: input.model, | |
| parts: [ | |
| { | |
| type: "text", | |
| text: `User entered: "${result.value}"`, | |
| ignored: true, | |
| }, | |
| ], | |
| }, | |
| }) | |
| throw new Error("__DIALOG_PROMPT_DEFAULT_HANDLED__") | |
| } | |
| }, | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment