From e74c7f95547637900bf14b7e9a690574566f9f11 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 26 May 2026 14:36:45 +0700 Subject: [PATCH] Show OpenCode agent colors in modes --- .../src/server/agent/provider-manifest.ts | 2 +- .../agent/providers/opencode-agent.test.ts | 29 ++++++++++++ .../server/agent/providers/opencode-agent.ts | 46 ++++++++++++------- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/packages/server/src/server/agent/provider-manifest.ts b/packages/server/src/server/agent/provider-manifest.ts index 88f85fb29..2477037f0 100644 --- a/packages/server/src/server/agent/provider-manifest.ts +++ b/packages/server/src/server/agent/provider-manifest.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import type { AgentMode } from "./agent-sdk-types.js"; -export type AgentModeColorTier = "safe" | "moderate" | "dangerous" | "planning"; +export type AgentModeColorTier = "safe" | "moderate" | "dangerous" | "planning" | `#${string}`; export type AgentModeIcon = "ShieldCheck" | "ShieldAlert" | "ShieldOff" | "ShieldQuestionMark"; export interface AgentModeVisuals { diff --git a/packages/server/src/server/agent/providers/opencode-agent.test.ts b/packages/server/src/server/agent/providers/opencode-agent.test.ts index 82deedbc9..793719531 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.test.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.test.ts @@ -582,6 +582,35 @@ describe("OpenCode adapter context-window normalization", () => { false, ); }); + + test("carries only hex OpenCode agent colors as mode color tiers", () => { + expect( + __openCodeInternals.mapOpenCodeAgentToMode({ + name: "review", + description: "Review code", + color: "#ff6b6b", + }), + ).toMatchObject({ + id: "review", + label: "Review", + description: "Review code", + colorTier: "#ff6b6b", + }); + + expect( + __openCodeInternals.mapOpenCodeAgentToMode({ + name: "creative", + color: "accent", + }), + ).not.toHaveProperty("colorTier"); + + expect( + __openCodeInternals.mapOpenCodeAgentToMode({ + name: "debug", + color: "#fff", + }), + ).not.toHaveProperty("colorTier"); + }); }); describe("OpenCode adapter startTurn error handling", () => { diff --git a/packages/server/src/server/agent/providers/opencode-agent.ts b/packages/server/src/server/agent/providers/opencode-agent.ts index 6284b6f80..8e868c394 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.ts @@ -445,6 +445,31 @@ function isSelectableOpenCodeAgent(agent: { mode?: string; hidden?: boolean }): return (agent.mode === "primary" || agent.mode === "all") && agent.hidden !== true; } +const OPENCODE_AGENT_HEX_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/; + +function readOpenCodeAgentHexColor(agent: { color?: unknown }): string | undefined { + return typeof agent.color === "string" && OPENCODE_AGENT_HEX_COLOR_PATTERN.test(agent.color) + ? agent.color + : undefined; +} + +function mapOpenCodeAgentToMode(agent: { + name: string; + description?: unknown; + color?: unknown; +}): AgentMode { + const colorTier = readOpenCodeAgentHexColor(agent); + return { + id: agent.name, + label: agent.name.charAt(0).toUpperCase() + agent.name.slice(1), + description: + typeof agent.description === "string" && agent.description.trim().length > 0 + ? agent.description.trim() + : DEFAULT_MODES.find((mode) => mode.id === agent.name)?.description, + ...(colorTier ? { colorTier } : {}), + }; +} + function mergeOpenCodeModes(discoveredModes: AgentMode[]): AgentMode[] { const modesById = new Map(DEFAULT_MODES.map((mode) => [mode.id, mode])); for (const mode of discoveredModes) { @@ -1042,6 +1067,7 @@ export const __openCodeInternals = { resolveOpenCodeModelLookupKeyFromAssistantMessage, resolveOpenCodeSelectedModelContextWindow, isSelectableOpenCodeAgent, + mapOpenCodeAgentToMode, get OpenCodeAgentSession() { return OpenCodeAgentSession; }, @@ -1277,14 +1303,9 @@ export class OpenCodeAgentClient implements AgentClient { return DEFAULT_MODES; } - const discovered = response.data.filter(isSelectableOpenCodeAgent).map((agent) => ({ - id: agent.name, - label: agent.name.charAt(0).toUpperCase() + agent.name.slice(1), - description: - typeof agent.description === "string" && agent.description.trim().length > 0 - ? agent.description.trim() - : DEFAULT_MODES.find((mode) => mode.id === agent.name)?.description, - })); + const discovered = response.data + .filter(isSelectableOpenCodeAgent) + .map(mapOpenCodeAgentToMode); return mergeOpenCodeModes(discovered); } finally { @@ -3202,14 +3223,7 @@ class OpenCodeAgentSession implements AgentSession { }); const agents = response.error || !response.data ? [] : response.data; - const discoveredModes = agents.filter(isSelectableOpenCodeAgent).map((agent) => ({ - id: agent.name, - label: agent.name.charAt(0).toUpperCase() + agent.name.slice(1), - description: - typeof agent.description === "string" && agent.description.trim().length > 0 - ? agent.description.trim() - : DEFAULT_MODES.find((mode) => mode.id === agent.name)?.description, - })); + const discoveredModes = agents.filter(isSelectableOpenCodeAgent).map(mapOpenCodeAgentToMode); this.availableModesCache = mergeOpenCodeModes(discoveredModes); return this.availableModesCache;