mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-21 09:44:21 +01:00
feat: agent color cfg (#4226)
Co-authored-by: 0xrin <0xrin1@protonmail.com> Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
@@ -16,6 +16,7 @@ export namespace Agent {
|
|||||||
builtIn: z.boolean(),
|
builtIn: z.boolean(),
|
||||||
topP: z.number().optional(),
|
topP: z.number().optional(),
|
||||||
temperature: z.number().optional(),
|
temperature: z.number().optional(),
|
||||||
|
color: z.string().optional(),
|
||||||
permission: z.object({
|
permission: z.object({
|
||||||
edit: Config.Permission,
|
edit: Config.Permission,
|
||||||
bash: z.record(z.string(), Config.Permission),
|
bash: z.record(z.string(), Config.Permission),
|
||||||
@@ -147,7 +148,7 @@ export namespace Agent {
|
|||||||
tools: {},
|
tools: {},
|
||||||
builtIn: false,
|
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 = {
|
||||||
...item.options,
|
...item.options,
|
||||||
...extra,
|
...extra,
|
||||||
@@ -167,6 +168,7 @@ export namespace Agent {
|
|||||||
if (temperature != undefined) item.temperature = temperature
|
if (temperature != undefined) item.temperature = temperature
|
||||||
if (top_p != undefined) item.topP = top_p
|
if (top_p != undefined) item.topP = top_p
|
||||||
if (mode) item.mode = mode
|
if (mode) item.mode = mode
|
||||||
|
if (color) item.color = color
|
||||||
// just here for consistency & to prevent it from being added as an option
|
// just here for consistency & to prevent it from being added as an option
|
||||||
if (name) item.name = name
|
if (name) item.name = name
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
color(name: string) {
|
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)
|
const index = agents().findIndex((x) => x.name === name)
|
||||||
return colors()[index % colors().length]
|
return colors()[index % colors().length]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -355,6 +355,11 @@ export namespace Config {
|
|||||||
disable: z.boolean().optional(),
|
disable: z.boolean().optional(),
|
||||||
description: z.string().optional().describe("Description of when to use the agent"),
|
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(),
|
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
|
permission: z
|
||||||
.object({
|
.object({
|
||||||
edit: Permission.optional(),
|
edit: Permission.optional(),
|
||||||
|
|||||||
19
packages/opencode/src/util/color.ts
Normal file
19
packages/opencode/src/util/color.ts
Normal file
@@ -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
packages/opencode/test/config/agent-color.test.ts
Normal file
66
packages/opencode/test/config/agent-color.test.ts
Normal file
@@ -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()
|
||||||
|
})
|
||||||
@@ -49,6 +49,7 @@ type Agent struct {
|
|||||||
Options map[string]interface{} `json:"options,required"`
|
Options map[string]interface{} `json:"options,required"`
|
||||||
Permission AgentPermission `json:"permission,required"`
|
Permission AgentPermission `json:"permission,required"`
|
||||||
Tools map[string]bool `json:"tools,required"`
|
Tools map[string]bool `json:"tools,required"`
|
||||||
|
Color string `json:"color"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Model AgentModel `json:"model"`
|
Model AgentModel `json:"model"`
|
||||||
Prompt string `json:"prompt"`
|
Prompt string `json:"prompt"`
|
||||||
@@ -65,6 +66,7 @@ type agentJSON struct {
|
|||||||
Options apijson.Field
|
Options apijson.Field
|
||||||
Permission apijson.Field
|
Permission apijson.Field
|
||||||
Tools apijson.Field
|
Tools apijson.Field
|
||||||
|
Color apijson.Field
|
||||||
Description apijson.Field
|
Description apijson.Field
|
||||||
Model apijson.Field
|
Model apijson.Field
|
||||||
Prompt apijson.Field
|
Prompt apijson.Field
|
||||||
|
|||||||
@@ -190,6 +190,10 @@ export type AgentConfig = {
|
|||||||
*/
|
*/
|
||||||
description?: string
|
description?: string
|
||||||
mode?: "subagent" | "primary" | "all"
|
mode?: "subagent" | "primary" | "all"
|
||||||
|
/**
|
||||||
|
* Hex color code for the agent (e.g., #FF5733)
|
||||||
|
*/
|
||||||
|
color?: string
|
||||||
permission?: {
|
permission?: {
|
||||||
edit?: "ask" | "allow" | "deny"
|
edit?: "ask" | "allow" | "deny"
|
||||||
bash?:
|
bash?:
|
||||||
@@ -1043,6 +1047,7 @@ export type Agent = {
|
|||||||
builtIn: boolean
|
builtIn: boolean
|
||||||
topP?: number
|
topP?: number
|
||||||
temperature?: number
|
temperature?: number
|
||||||
|
color?: string
|
||||||
permission: {
|
permission: {
|
||||||
edit: "ask" | "allow" | "deny"
|
edit: "ask" | "allow" | "deny"
|
||||||
bash: {
|
bash: {
|
||||||
|
|||||||
Reference in New Issue
Block a user