From d7ab5c4d7bd4d1d1ee5a49b0fe8f6793b890eed5 Mon Sep 17 00:00:00 2001 From: edxeth Date: Thu, 26 Feb 2026 21:39:04 +0100 Subject: [PATCH] refactor(schema): dedupe custom agent override with ref --- assets/oh-my-opencode.schema.json | 441 +++++++++++++++-------------- script/build-schema-document.ts | 38 ++- src/config/schema-document.test.ts | 10 +- 3 files changed, 267 insertions(+), 222 deletions(-) diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index df796fb78..b0cce0422 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -3155,223 +3155,7 @@ "pattern": "^(?!(?:[bB][uU][iI][lL][dD]|[pP][lL][aA][nN]|[sS][iI][sS][yY][pP][hH][uU][sS]|[hH][eE][pP][hH][aA][eE][sS][tT][uU][sS]|[sS][iI][sS][yY][pP][hH][uU][sS]-[jJ][uU][nN][iI][oO][rR]|[oO][pP][eE][nN][cC][oO][dD][eE]-[bB][uU][iI][lL][dD][eE][rR]|[pP][rR][oO][mM][eE][tT][hH][eE][uU][sS]|[mM][eE][tT][iI][sS]|[mM][oO][mM][uU][sS]|[oO][rR][aA][cC][lL][eE]|[lL][iI][bB][rR][aA][rR][iI][aA][nN]|[eE][xX][pP][lL][oO][rR][eE]|[mM][uU][lL][tT][iI][mM][oO][dD][aA][lL]-[lL][oO][oO][kK][eE][rR]|[aA][tT][lL][aA][sS])$).+" }, "additionalProperties": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "fallback_models": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "task": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - }, - "additionalProperties": false - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ], - "additionalProperties": false - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "ultrawork": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - } - }, - "additionalProperties": false - }, - "compaction": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false + "$ref": "#/$defs/agentOverrideConfig" } }, "categories": { @@ -4070,5 +3854,226 @@ } } }, - "additionalProperties": false + "additionalProperties": false, + "$defs": { + "agentOverrideConfig": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "fallback_models": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "variant": { + "type": "string" + }, + "category": { + "type": "string" + }, + "skills": { + "type": "array", + "items": { + "type": "string" + } + }, + "temperature": { + "type": "number", + "minimum": 0, + "maximum": 2 + }, + "top_p": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "prompt": { + "type": "string" + }, + "prompt_append": { + "type": "string" + }, + "tools": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "disable": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "subagent", + "primary", + "all" + ] + }, + "color": { + "type": "string", + "pattern": "^#[0-9A-Fa-f]{6}$" + }, + "permission": { + "type": "object", + "properties": { + "edit": { + "type": "string", + "enum": [ + "ask", + "allow", + "deny" + ] + }, + "bash": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ask", + "allow", + "deny" + ] + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "enum": [ + "ask", + "allow", + "deny" + ] + } + } + ] + }, + "webfetch": { + "type": "string", + "enum": [ + "ask", + "allow", + "deny" + ] + }, + "task": { + "type": "string", + "enum": [ + "ask", + "allow", + "deny" + ] + }, + "doom_loop": { + "type": "string", + "enum": [ + "ask", + "allow", + "deny" + ] + }, + "external_directory": { + "type": "string", + "enum": [ + "ask", + "allow", + "deny" + ] + } + }, + "additionalProperties": false + }, + "maxTokens": { + "type": "number" + }, + "thinking": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "enabled", + "disabled" + ] + }, + "budgetTokens": { + "type": "number" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "reasoningEffort": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "xhigh" + ] + }, + "textVerbosity": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + }, + "providerOptions": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "ultrawork": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "additionalProperties": false + }, + "compaction": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + } } \ No newline at end of file diff --git a/script/build-schema-document.ts b/script/build-schema-document.ts index 17681dcd9..9180c5af9 100644 --- a/script/build-schema-document.ts +++ b/script/build-schema-document.ts @@ -1,17 +1,53 @@ import * as z from "zod" import { OhMyOpenCodeConfigSchema } from "../src/config/schema" +function asRecord(value: unknown): Record | undefined { + return typeof value === "object" && value !== null ? (value as Record) : undefined +} + +function dedupeCustomAgentOverrideSchema(schema: Record): Record { + const rootProperties = asRecord(schema.properties) + const agentsSchema = asRecord(rootProperties?.agents) + const builtInAgentProps = asRecord(agentsSchema?.properties) + const customAgentsSchema = asRecord(rootProperties?.custom_agents) + const customAdditionalProperties = asRecord(customAgentsSchema?.additionalProperties) + + if (!builtInAgentProps || !customAgentsSchema || !customAdditionalProperties) { + return schema + } + + const referenceAgentSchema = asRecord( + builtInAgentProps.build + ?? builtInAgentProps.oracle + ?? builtInAgentProps.explore, + ) + + if (!referenceAgentSchema) { + return schema + } + + const defs = asRecord(schema.$defs) ?? {} + defs.agentOverrideConfig = referenceAgentSchema + schema.$defs = defs + + customAgentsSchema.additionalProperties = { $ref: "#/$defs/agentOverrideConfig" } + + return schema +} + export function createOhMyOpenCodeJsonSchema(): Record { const jsonSchema = z.toJSONSchema(OhMyOpenCodeConfigSchema, { target: "draft-7", unrepresentable: "any", }) - return { + const schema = { $schema: "http://json-schema.org/draft-07/schema#", $id: "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json", title: "Oh My OpenCode Configuration", description: "Configuration schema for oh-my-opencode plugin", ...jsonSchema, } + + return dedupeCustomAgentOverrideSchema(schema) } diff --git a/src/config/schema-document.test.ts b/src/config/schema-document.test.ts index bc6863f91..80bc6d078 100644 --- a/src/config/schema-document.test.ts +++ b/src/config/schema-document.test.ts @@ -16,7 +16,9 @@ describe("schema document generation", () => { const customAgentsSchema = asRecord(rootProperties?.custom_agents) const customPropertyNames = asRecord(customAgentsSchema?.propertyNames) const customAdditionalProperties = asRecord(customAgentsSchema?.additionalProperties) - const customAgentProperties = asRecord(customAdditionalProperties?.properties) + const defs = asRecord(schema.$defs) + const sharedAgentOverrideSchema = asRecord(defs?.agentOverrideConfig) + const sharedAgentProperties = asRecord(sharedAgentOverrideSchema?.properties) // then expect(agentsSchema).toBeDefined() @@ -26,8 +28,10 @@ describe("schema document generation", () => { expect(customPropertyNames?.pattern).toContain("[bB][uU][iI][lL][dD]") expect(customPropertyNames?.pattern).toContain("[pP][lL][aA][nN]") expect(customAdditionalProperties).toBeDefined() - expect(customAgentProperties?.model).toEqual({ type: "string" }) - expect(customAgentProperties?.temperature).toEqual( + expect(customAdditionalProperties?.$ref).toBe("#/$defs/agentOverrideConfig") + expect(sharedAgentOverrideSchema).toBeDefined() + expect(sharedAgentProperties?.model).toEqual({ type: "string" }) + expect(sharedAgentProperties?.temperature).toEqual( expect.objectContaining({ type: "number" }), ) })