From c7fd8431b8fd3d172509b895eef7e1cb2188730c Mon Sep 17 00:00:00 2001 From: kirillk Date: Mon, 3 Aug 2026 14:48:17 -0400 Subject: [PATCH] feat(cli): expose command metadata in command-file endpoints --- .changeset/commands-settings-jetbrains.md | 2 +- .../kilocode/backend/cli/KiloCliDataParser.kt | 12 ++++++++ .../backend/rpc/KiloWorkspaceDtoMapper.kt | 4 +++ .../backend/workspace/KiloWorkspaceState.kt | 4 +++ .../backend/cli/KiloCliDataParserTest.kt | 6 +++- .../rpc/KiloAgentBehaviorRpcApiImplTest.kt | 6 +++- .../kotlin/ai/kilocode/rpc/dto/CommandDto.kt | 4 +++ .../ai/kilocode/rpc/dto/CommandFileDto.kt | 4 +++ .../opencode/src/kilocode/command-files.ts | 12 ++++++++ .../test/kilocode/command-files.test.ts | 28 +++++++++++++++++-- packages/sdk/js/src/v2/gen/types.gen.ts | 4 +++ packages/sdk/openapi.json | 12 ++++++++ 12 files changed, 93 insertions(+), 5 deletions(-) diff --git a/.changeset/commands-settings-jetbrains.md b/.changeset/commands-settings-jetbrains.md index fc7b8272360..0c451ea9f32 100644 --- a/.changeset/commands-settings-jetbrains.md +++ b/.changeset/commands-settings-jetbrains.md @@ -2,4 +2,4 @@ "@kilocode/cli": minor --- -Add kilocode command-file endpoints so clients can list editable command/workflow files and remove them. +Add kilocode command-file endpoints so clients can list editable command/workflow files, inspect model and reasoning variant metadata, and remove them. diff --git a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt index eec74b0c1cb..a1d16e49db9 100644 --- a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt +++ b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt @@ -631,8 +631,12 @@ object KiloCliDataParser { CommandInfo( name = obj.str("name") ?: "", description = obj.str("description"), + agent = obj.str("agent"), + model = obj.str("model"), + variant = obj.str("variant"), source = obj.str("source"), hints = obj["hints"]?.jsonArray?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList(), + subtask = obj.flagOrNull("subtask"), ) } @@ -663,9 +667,13 @@ object KiloCliDataParser { CommandDto( name = name, description = obj.str("description"), + agent = obj.str("agent"), + model = obj.str("model"), + variant = obj.str("variant"), source = obj.str("source"), hints = obj["hints"].arr()?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList(), template = obj.str("template"), + subtask = obj.flagOrNull("subtask"), ) } @@ -677,11 +685,15 @@ object KiloCliDataParser { CommandFileDto( name = name, description = obj.str("description"), + agent = obj.str("agent"), + model = obj.str("model"), + variant = obj.str("variant"), source = obj.str("source"), builtin = obj.bool("builtin"), location = location, editable = obj.bool("editable"), content = obj.str("content"), + subtask = obj.flagOrNull("subtask"), hints = obj["hints"].arr()?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList(), ) } diff --git a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceDtoMapper.kt b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceDtoMapper.kt index 981ea1ad28b..ad89fb9be0f 100644 --- a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceDtoMapper.kt +++ b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceDtoMapper.kt @@ -56,8 +56,12 @@ internal object KiloWorkspaceDtoMapper { fun command(c: CommandInfo) = CommandDto( name = c.name, description = c.description, + agent = c.agent, + model = c.model, + variant = c.variant, source = c.source, hints = c.hints, + subtask = c.subtask, ) fun skill(s: SkillInfo) = SkillDto( diff --git a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloWorkspaceState.kt b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloWorkspaceState.kt index 37c58d853d6..3d926aa84da 100644 --- a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloWorkspaceState.kt +++ b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloWorkspaceState.kt @@ -135,8 +135,12 @@ data class AgentInfo( data class CommandInfo( val name: String, val description: String?, + val agent: String?, + val model: String?, + val variant: String?, val source: String?, val hints: List, + val subtask: Boolean?, ) data class SkillInfo( diff --git a/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt b/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt index 93514a7a750..25f339f9a99 100644 --- a/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt +++ b/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt @@ -1817,7 +1817,7 @@ class KiloCliDataParserTest { @Test fun `parseCommands - maps name, description, source, and hints`() { val raw = """[ - {"name":"init","description":"guided AGENTS.md setup","template":"static body","hints":["${'$'}ARGUMENTS"],"source":"command"}, + {"name":"init","description":"guided AGENTS.md setup","agent":"reviewer","model":"anthropic/claude-sonnet-4-6","variant":"high","template":"static body","hints":["${'$'}ARGUMENTS"],"source":"command","subtask":true}, {"name":"mcp-tool","template":"","hints":["${'$'}1","${'$'}2"],"source":"mcp"} ]""" @@ -1826,8 +1826,12 @@ class KiloCliDataParserTest { assertEquals(2, result.size) assertEquals("init", result[0].name) assertEquals("guided AGENTS.md setup", result[0].description) + assertEquals("reviewer", result[0].agent) + assertEquals("anthropic/claude-sonnet-4-6", result[0].model) + assertEquals("high", result[0].variant) assertEquals("command", result[0].source) assertEquals(listOf("\$ARGUMENTS"), result[0].hints) + assertEquals(true, result[0].subtask) assertEquals("mcp", result[1].source) assertEquals(listOf("\$1", "\$2"), result[1].hints) } diff --git a/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloAgentBehaviorRpcApiImplTest.kt b/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloAgentBehaviorRpcApiImplTest.kt index 514fa0d7e28..b979bc81d3b 100644 --- a/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloAgentBehaviorRpcApiImplTest.kt +++ b/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloAgentBehaviorRpcApiImplTest.kt @@ -125,7 +125,7 @@ class KiloAgentBehaviorRpcApiImplTest { val file = Files.createDirectories(dir.resolve("command")).resolve("review.md") Files.writeString(file, "---\ndescription: Review code\n---\n\nReview $" + "ARGUMENTS") mock.commandFiles = """[ - {"name":"review","description":"Review code","source":"command","builtin":false,"location":"$file","editable":true,"content":"Review"}, + {"name":"review","description":"Review code","agent":"reviewer","model":"anthropic/claude-sonnet-4-6","variant":"high","source":"command","builtin":false,"location":"$file","editable":true,"content":"Review","subtask":true}, {"name":"init","source":"command","builtin":true,"location":"builtin","editable":false,"content":"Init"} ]""".trimIndent() val rpc = rpc() @@ -133,6 +133,10 @@ class KiloAgentBehaviorRpcApiImplTest { val commands = rpc.commandFiles("/test project") assertEquals(listOf("review", "init"), commands.map { it.name }) assertEquals(true, commands.single { it.name == "review" }.editable) + assertEquals("reviewer", commands.single { it.name == "review" }.agent) + assertEquals("anthropic/claude-sonnet-4-6", commands.single { it.name == "review" }.model) + assertEquals("high", commands.single { it.name == "review" }.variant) + assertEquals(true, commands.single { it.name == "review" }.subtask) assertEquals(false, commands.single { it.name == "init" }.editable) assertTrue(rpc.removeCommand("/test project", file.toString())) diff --git a/packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/CommandDto.kt b/packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/CommandDto.kt index 4d0e6540aac..f14eab4aaa5 100644 --- a/packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/CommandDto.kt +++ b/packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/CommandDto.kt @@ -6,7 +6,11 @@ import kotlinx.serialization.Serializable data class CommandDto( val name: String, val description: String? = null, + val agent: String? = null, + val model: String? = null, + val variant: String? = null, val source: String? = null, val hints: List = emptyList(), val template: String? = null, + val subtask: Boolean? = null, ) diff --git a/packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/CommandFileDto.kt b/packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/CommandFileDto.kt index 77c4a678117..9c4267dcf03 100644 --- a/packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/CommandFileDto.kt +++ b/packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/CommandFileDto.kt @@ -6,10 +6,14 @@ import kotlinx.serialization.Serializable data class CommandFileDto( val name: String, val description: String? = null, + val agent: String? = null, + val model: String? = null, + val variant: String? = null, val source: String? = null, val builtin: Boolean = false, val location: String, val editable: Boolean = false, val content: String? = null, + val subtask: Boolean? = null, val hints: List = emptyList(), ) diff --git a/packages/opencode/src/kilocode/command-files.ts b/packages/opencode/src/kilocode/command-files.ts index b1748018057..2841994af09 100644 --- a/packages/opencode/src/kilocode/command-files.ts +++ b/packages/opencode/src/kilocode/command-files.ts @@ -9,11 +9,15 @@ import { WorkflowsMigrator } from "@/kilocode/workflows-migrator" export const Info = Schema.Struct({ name: Schema.String, description: Schema.optional(Schema.String), + agent: Schema.optional(Schema.String), + model: Schema.optional(Schema.String), + variant: Schema.optional(Schema.String), source: Schema.optional(Schema.String), builtin: Schema.Boolean, location: Schema.String, editable: Schema.Boolean, content: Schema.optional(Schema.String), + subtask: Schema.optional(Schema.Boolean), hints: Schema.Array(Schema.String), }).annotate({ identifier: "CommandFile" }) @@ -85,22 +89,30 @@ export async function discover(input: { commands: readonly Command.Info[]; direc return { name: cmd.name, description: description(cmd, file), + agent: cmd.agent, + model: cmd.model, + variant: cmd.variant, source: cmd.source, builtin: false, location: file.location, editable: true, content: file.content, + subtask: cmd.subtask, hints: cmd.hints, } } return { name: cmd.name, description: description(cmd), + agent: cmd.agent, + model: cmd.model, + variant: cmd.variant, source: cmd.source, builtin: true, location: "builtin", editable: false, content: literal(cmd), + subtask: cmd.subtask, hints: cmd.hints, } }) diff --git a/packages/opencode/test/kilocode/command-files.test.ts b/packages/opencode/test/kilocode/command-files.test.ts index 354bf749625..8e38eedf3bf 100644 --- a/packages/opencode/test/kilocode/command-files.test.ts +++ b/packages/opencode/test/kilocode/command-files.test.ts @@ -22,8 +22,12 @@ function cmd(input: Partial & Pick): Command return { name: input.name, description: input.description, + agent: input.agent, + model: input.model, + variant: input.variant, source: input.source, template: input.template ?? "body", + subtask: input.subtask, hints: input.hints ?? [], } } @@ -38,11 +42,31 @@ describe("CommandFiles", () => { const items = await CommandFiles.discover({ directory: dir, directories: [path.join(dir, ".kilo")], - commands: [cmd({ name: "review", source: "command", hints: ["$ARGUMENTS"] }), cmd({ name: "init", source: "command" })], + commands: [ + cmd({ + name: "review", + source: "command", + agent: "reviewer", + model: "anthropic/claude-sonnet-4-6", + variant: "high", + subtask: true, + hints: ["$ARGUMENTS"], + }), + cmd({ name: "init", source: "command" }), + ], }) expect(items.map((item) => item.name)).toEqual(["review", "init"]) - expect(items[0]).toMatchObject({ name: "review", editable: true, builtin: false, location: file }) + expect(items[0]).toMatchObject({ + name: "review", + editable: true, + builtin: false, + location: file, + agent: "reviewer", + model: "anthropic/claude-sonnet-4-6", + variant: "high", + subtask: true, + }) expect(items[0].content).toContain("Review $ARGUMENTS") expect(items[1]).toMatchObject({ name: "init", editable: false, builtin: true, location: "builtin" }) }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 64eb6aa5c4c..459fa4d8dee 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -3237,11 +3237,15 @@ export type AgentRequirementResult = { export type CommandFile = { name: string description?: string + agent?: string + model?: string + variant?: string source?: string builtin: boolean location: string editable: boolean content?: string + subtask?: boolean hints: Array } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index b67a8255660..11853b6aac6 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -34497,6 +34497,15 @@ "description": { "type": "string" }, + "agent": { + "type": "string" + }, + "model": { + "type": "string" + }, + "variant": { + "type": "string" + }, "source": { "type": "string" }, @@ -34512,6 +34521,9 @@ "content": { "type": "string" }, + "subtask": { + "type": "boolean" + }, "hints": { "type": "array", "items": {