Просмотр исходного кода

feat: agent color cfg (#4226)

Co-authored-by: 0xrin <[email protected]>
Co-authored-by: GitHub Action <[email protected]>
Aiden Cline 3 месяцев назад
Родитель
Сommit
0b86adbe99

+ 3 - 1
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
 

+ 2 - 0
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]
         },

+ 5 - 0
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(),

+ 19 - 0
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`
+  }
+}

+ 66 - 0
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()
+})

+ 2 - 0
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

+ 5 - 0
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: {