summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAiden Cline <[email protected]>2025-11-11 16:32:44 -0800
committerGitHub <[email protected]>2025-11-11 18:32:44 -0600
commit0b86adbe997320c1a13e91899cace1ddd956a39a (patch)
tree0fc8dd5eaf882e960eb5936b303a6d00b91ef871
parent834a2c09d57b39faedb0b3416bc6fa216778637f (diff)
downloadopencode-0b86adbe997320c1a13e91899cace1ddd956a39a.tar.gz
opencode-0b86adbe997320c1a13e91899cace1ddd956a39a.zip
feat: agent color cfg (#4226)
Co-authored-by: 0xrin <[email protected]> Co-authored-by: GitHub Action <[email protected]>
-rw-r--r--packages/opencode/src/agent/agent.ts4
-rw-r--r--packages/opencode/src/cli/cmd/tui/context/local.tsx2
-rw-r--r--packages/opencode/src/config/config.ts5
-rw-r--r--packages/opencode/src/util/color.ts19
-rw-r--r--packages/opencode/test/config/agent-color.test.ts66
-rw-r--r--packages/sdk/go/agent.go2
-rw-r--r--packages/sdk/js/src/gen/types.gen.ts5
7 files changed, 102 insertions, 1 deletions
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index f1050ae72..867bc0fe9 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -16,6 +16,7 @@ export namespace Agent {
builtIn: z.boolean(),
topP: z.number().optional(),
temperature: z.number().optional(),
+ color: z.string().optional(),
permission: z.object({
edit: Config.Permission,
bash: z.record(z.string(), Config.Permission),
@@ -147,7 +148,7 @@ export namespace Agent {
tools: {},
builtIn: false,
}
- const { name, model, prompt, tools, description, temperature, top_p, mode, permission, ...extra } = value
+ const { name, model, prompt, tools, description, temperature, top_p, mode, permission, color, ...extra } = value
item.options = {
...item.options,
...extra,
@@ -167,6 +168,7 @@ export namespace Agent {
if (temperature != undefined) item.temperature = temperature
if (top_p != undefined) item.topP = top_p
if (mode) item.mode = mode
+ if (color) item.color = color
// just here for consistency & to prevent it from being added as an option
if (name) item.name = name
diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx
index fe81fd180..998739f12 100644
--- a/packages/opencode/src/cli/cmd/tui/context/local.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx
@@ -90,6 +90,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
})
},
color(name: string) {
+ const agent = agents().find((x) => x.name === name)
+ if (agent?.color) return agent.color
const index = agents().findIndex((x) => x.name === name)
return colors()[index % colors().length]
},
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index eaac1dd4f..8823c866e 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -355,6 +355,11 @@ export namespace Config {
disable: z.boolean().optional(),
description: z.string().optional().describe("Description of when to use the agent"),
mode: z.union([z.literal("subagent"), z.literal("primary"), z.literal("all")]).optional(),
+ color: z
+ .string()
+ .regex(/^#[0-9a-fA-F]{6}$/, "Invalid hex color format")
+ .optional()
+ .describe("Hex color code for the agent (e.g., #FF5733)"),
permission: z
.object({
edit: Permission.optional(),
diff --git a/packages/opencode/src/util/color.ts b/packages/opencode/src/util/color.ts
new file mode 100644
index 000000000..b96deaec4
--- /dev/null
+++ b/packages/opencode/src/util/color.ts
@@ -0,0 +1,19 @@
+export namespace Color {
+ export function isValidHex(hex?: string): hex is string {
+ if (!hex) return false
+ return /^#[0-9a-fA-F]{6}$/.test(hex)
+ }
+
+ export function hexToRgb(hex: string): { r: number; g: number; b: number } {
+ const r = parseInt(hex.slice(1, 3), 16)
+ const g = parseInt(hex.slice(3, 5), 16)
+ const b = parseInt(hex.slice(5, 7), 16)
+ return { r, g, b }
+ }
+
+ export function hexToAnsiBold(hex?: string): string | undefined {
+ if (!isValidHex(hex)) return undefined
+ const { r, g, b } = hexToRgb(hex)
+ return `\x1b[38;2;${r};${g};${b}m\x1b[1m`
+ }
+}
diff --git a/packages/opencode/test/config/agent-color.test.ts b/packages/opencode/test/config/agent-color.test.ts
new file mode 100644
index 000000000..a2c374296
--- /dev/null
+++ b/packages/opencode/test/config/agent-color.test.ts
@@ -0,0 +1,66 @@
+import { test, expect } from "bun:test"
+import path from "path"
+import { tmpdir } from "../fixture/fixture"
+import { Instance } from "../../src/project/instance"
+import { Config } from "../../src/config/config"
+import { Agent as AgentSvc } from "../../src/agent/agent"
+import { Color } from "../../src/util/color"
+
+test("agent color parsed from project config", async () => {
+ await using tmp = await tmpdir({
+ init: async (dir) => {
+ await Bun.write(
+ path.join(dir, "opencode.json"),
+ JSON.stringify({
+ $schema: "https://opencode.ai/config.json",
+ agent: {
+ build: { color: "#FFA500" },
+ },
+ }),
+ )
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const cfg = await Config.get()
+ expect(cfg.agent?.["build"]?.color).toBe("#FFA500")
+ },
+ })
+})
+
+test("Agent.get includes color from config", async () => {
+ await using tmp = await tmpdir({
+ init: async (dir) => {
+ await Bun.write(
+ path.join(dir, "opencode.json"),
+ JSON.stringify({
+ $schema: "https://opencode.ai/config.json",
+ agent: {
+ plan: { color: "#A855F7" },
+ },
+ }),
+ )
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const plan = await AgentSvc.get("plan")
+ expect(plan?.color).toBe("#A855F7")
+ },
+ })
+})
+
+test("Color.hexToAnsiBold converts valid hex to ANSI", () => {
+ const result = Color.hexToAnsiBold("#FFA500")
+ expect(result).toBe("\x1b[38;2;255;165;0m\x1b[1m")
+})
+
+test("Color.hexToAnsiBold returns undefined for invalid hex", () => {
+ expect(Color.hexToAnsiBold(undefined)).toBeUndefined()
+ expect(Color.hexToAnsiBold("")).toBeUndefined()
+ expect(Color.hexToAnsiBold("#FFF")).toBeUndefined()
+ expect(Color.hexToAnsiBold("FFA500")).toBeUndefined()
+ expect(Color.hexToAnsiBold("#GGGGGG")).toBeUndefined()
+})
diff --git a/packages/sdk/go/agent.go b/packages/sdk/go/agent.go
index 3413b5666..d1bb36e36 100644
--- a/packages/sdk/go/agent.go
+++ b/packages/sdk/go/agent.go
@@ -49,6 +49,7 @@ type Agent struct {
Options map[string]interface{} `json:"options,required"`
Permission AgentPermission `json:"permission,required"`
Tools map[string]bool `json:"tools,required"`
+ Color string `json:"color"`
Description string `json:"description"`
Model AgentModel `json:"model"`
Prompt string `json:"prompt"`
@@ -65,6 +66,7 @@ type agentJSON struct {
Options apijson.Field
Permission apijson.Field
Tools apijson.Field
+ Color apijson.Field
Description apijson.Field
Model apijson.Field
Prompt apijson.Field
diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts
index 19f7a3a77..3e64fc9a0 100644
--- a/packages/sdk/js/src/gen/types.gen.ts
+++ b/packages/sdk/js/src/gen/types.gen.ts
@@ -190,6 +190,10 @@ export type AgentConfig = {
*/
description?: string
mode?: "subagent" | "primary" | "all"
+ /**
+ * Hex color code for the agent (e.g., #FF5733)
+ */
+ color?: string
permission?: {
edit?: "ask" | "allow" | "deny"
bash?:
@@ -1043,6 +1047,7 @@ export type Agent = {
builtIn: boolean
topP?: number
temperature?: number
+ color?: string
permission: {
edit: "ask" | "allow" | "deny"
bash: {