refactor: move DELETE /skill to dedicated route file

Extract the inline DELETE /skill endpoint from server.ts into a
dedicated packages/opencode/src/server/routes/skill.ts file, reducing
kilo-specific changes in the shared server.ts and minimizing future
merge conflicts with upstream.

The route is mounted at /skill and uses the same lazy() pattern as
other kilo-specific route files (telemetry, enhance-prompt, etc.).
This commit is contained in:
Mark IJbema
2026-03-16 13:04:07 +01:00
parent 5618b810fb
commit 07d16dff23
6 changed files with 365 additions and 348 deletions
+1 -1
View File
@@ -1139,7 +1139,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
if (!this.client) return
try {
const dir = this.getWorkspaceDirectory()
await this.client.app.removeSkill({ location, directory: dir }, { throwOnError: true })
await this.client.skill.remove({ location, directory: dir }, { throwOnError: true })
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to remove skill:", error)
}
@@ -0,0 +1,40 @@
// kilocode_change - new file
import { Hono } from "hono"
import { describeRoute, validator, resolver } from "hono-openapi"
import z from "zod"
import { Skill } from "../../skill/skill"
import { lazy } from "../../util/lazy"
import { errors } from "../error"
export const SkillRoutes = lazy(() =>
new Hono().delete(
"/",
describeRoute({
summary: "Remove a skill",
description: "Remove a skill by deleting its directory from disk and clearing it from cache.",
operationId: "skill.remove",
responses: {
200: {
description: "Skill removed",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
...errors(400),
},
}),
validator(
"query",
z.object({
location: z.string(),
}),
),
async (c) => {
const { location } = c.req.valid("query")
await Skill.remove(location)
return c.json(true)
},
),
)
+2 -32
View File
@@ -47,6 +47,7 @@ import { HTTPException } from "hono/http-exception"
import { errors } from "./error"
import { CommitMessageRoutes } from "./routes/commit-message"
import { EnhancePromptRoutes } from "./routes/enhance-prompt" // kilocode_change
import { SkillRoutes } from "./routes/skill" // kilocode_change
import { QuestionRoutes } from "./routes/question"
import { PermissionRoutes } from "./routes/permission"
import { GlobalRoutes } from "./routes/global"
@@ -268,6 +269,7 @@ export namespace Server {
.route("/telemetry", TelemetryRoutes()) // kilocode_change
.route("/commit-message", CommitMessageRoutes()) // kilocode_change
.route("/enhance-prompt", EnhancePromptRoutes()) // kilocode_change
.route("/skill", SkillRoutes()) // kilocode_change
// kilocode_change start - Kilo Gateway routes
.route(
"/kilo",
@@ -500,38 +502,6 @@ export namespace Server {
return c.json(skills)
},
)
// kilocode_change start
.delete(
"/skill",
describeRoute({
summary: "Remove a skill",
description: "Remove a skill by deleting its directory from disk and clearing it from cache.",
operationId: "app.removeSkill",
responses: {
200: {
description: "Skill removed",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
...errors(400),
},
}),
validator(
"query",
z.object({
location: z.string(),
}),
),
async (c) => {
const { location } = c.req.valid("query")
await Skill.remove(location)
return c.json(true)
},
)
// kilocode_change end
.get(
"/lsp",
describeRoute({
+153 -146
View File
@@ -7,8 +7,6 @@ import type {
AppAgentsResponses,
AppLogErrors,
AppLogResponses,
AppRemoveSkillErrors,
AppRemoveSkillResponses,
AppSkillsResponses,
Auth as Auth3,
AuthRemoveErrors,
@@ -170,6 +168,8 @@ import type {
SessionUnshareResponses,
SessionUpdateErrors,
SessionUpdateResponses,
SkillRemoveErrors,
SkillRemoveResponses,
SubtaskPartInput,
TelemetryCaptureErrors,
TelemetryCaptureResponses,
@@ -2930,6 +2930,147 @@ export class EnhancePrompt extends HeyApiClient {
}
}
export class Skill extends HeyApiClient {
/**
* Remove a skill
*
* Remove a skill by deleting its directory from disk and clearing it from cache.
*/
public remove<ThrowOnError extends boolean = false>(
parameters: {
directory?: string
workspace?: string
location: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
{ in: "query", key: "location" },
],
},
],
)
return (options?.client ?? this.client).delete<SkillRemoveResponses, SkillRemoveErrors, ThrowOnError>({
url: "/skill",
...options,
...params,
})
}
}
export class App extends HeyApiClient {
/**
* List skills
*
* Get a list of all available skills in the OpenCode system.
*/
public skills<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
workspace?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
],
},
],
)
return (options?.client ?? this.client).get<AppSkillsResponses, unknown, ThrowOnError>({
url: "/skill",
...options,
...params,
})
}
/**
* Write log
*
* Write a log entry to the server logs with specified level and metadata.
*/
public log<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
workspace?: string
service?: string
level?: "debug" | "info" | "error" | "warn"
message?: string
extra?: {
[key: string]: unknown
}
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
{ in: "body", key: "service" },
{ in: "body", key: "level" },
{ in: "body", key: "message" },
{ in: "body", key: "extra" },
],
},
],
)
return (options?.client ?? this.client).post<AppLogResponses, AppLogErrors, ThrowOnError>({
url: "/log",
...options,
...params,
headers: {
"Content-Type": "application/json",
...options?.headers,
...params.headers,
},
})
}
/**
* List agents
*
* Get a list of all available AI agents in the OpenCode system.
*/
public agents<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
workspace?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
],
},
],
)
return (options?.client ?? this.client).get<AppAgentsResponses, unknown, ThrowOnError>({
url: "/agent",
...options,
...params,
})
}
}
export class Organization extends HeyApiClient {
/**
* Update Kilo Gateway organization
@@ -4256,145 +4397,6 @@ export class Command extends HeyApiClient {
}
}
export class App extends HeyApiClient {
/**
* Write log
*
* Write a log entry to the server logs with specified level and metadata.
*/
public log<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
workspace?: string
service?: string
level?: "debug" | "info" | "error" | "warn"
message?: string
extra?: {
[key: string]: unknown
}
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
{ in: "body", key: "service" },
{ in: "body", key: "level" },
{ in: "body", key: "message" },
{ in: "body", key: "extra" },
],
},
],
)
return (options?.client ?? this.client).post<AppLogResponses, AppLogErrors, ThrowOnError>({
url: "/log",
...options,
...params,
headers: {
"Content-Type": "application/json",
...options?.headers,
...params.headers,
},
})
}
/**
* List agents
*
* Get a list of all available AI agents in the OpenCode system.
*/
public agents<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
workspace?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
],
},
],
)
return (options?.client ?? this.client).get<AppAgentsResponses, unknown, ThrowOnError>({
url: "/agent",
...options,
...params,
})
}
/**
* Remove a skill
*
* Remove a skill by deleting its directory from disk and clearing it from cache.
*/
public removeSkill<ThrowOnError extends boolean = false>(
parameters: {
directory?: string
workspace?: string
location: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
{ in: "query", key: "location" },
],
},
],
)
return (options?.client ?? this.client).delete<AppRemoveSkillResponses, AppRemoveSkillErrors, ThrowOnError>({
url: "/skill",
...options,
...params,
})
}
/**
* List skills
*
* Get a list of all available skills in the OpenCode system.
*/
public skills<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
workspace?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
],
},
],
)
return (options?.client ?? this.client).get<AppSkillsResponses, unknown, ThrowOnError>({
url: "/skill",
...options,
...params,
})
}
}
export class Lsp extends HeyApiClient {
/**
* Get LSP status
@@ -4579,6 +4581,16 @@ export class KiloClient extends HeyApiClient {
return (this._enhancePrompt ??= new EnhancePrompt({ client: this.client }))
}
private _skill?: Skill
get skill(): Skill {
return (this._skill ??= new Skill({ client: this.client }))
}
private _app?: App
get app(): App {
return (this._app ??= new App({ client: this.client }))
}
private _kilo?: Kilo
get kilo(): Kilo {
return (this._kilo ??= new Kilo({ client: this.client }))
@@ -4624,11 +4636,6 @@ export class KiloClient extends HeyApiClient {
return (this._command ??= new Command({ client: this.client }))
}
private _app?: App
get app(): App {
return (this._app ??= new App({ client: this.client }))
}
private _lsp?: Lsp
get lsp(): Lsp {
return (this._lsp ??= new Lsp({ client: this.client }))
+53 -53
View File
@@ -4402,6 +4402,59 @@ export type EnhancePromptEnhanceResponses = {
export type EnhancePromptEnhanceResponse = EnhancePromptEnhanceResponses[keyof EnhancePromptEnhanceResponses]
export type SkillRemoveData = {
body?: never
path?: never
query: {
directory?: string
workspace?: string
location: string
}
url: "/skill"
}
export type SkillRemoveErrors = {
/**
* Bad request
*/
400: BadRequestError
}
export type SkillRemoveError = SkillRemoveErrors[keyof SkillRemoveErrors]
export type SkillRemoveResponses = {
/**
* Skill removed
*/
200: boolean
}
export type SkillRemoveResponse = SkillRemoveResponses[keyof SkillRemoveResponses]
export type AppSkillsData = {
body?: never
path?: never
query?: {
directory?: string
workspace?: string
}
url: "/skill"
}
export type AppSkillsResponses = {
/**
* List of skills
*/
200: Array<{
name: string
description: string
location: string
content: string
}>
}
export type AppSkillsResponse = AppSkillsResponses[keyof AppSkillsResponses]
export type KiloProfileData = {
body?: never
path?: never
@@ -5483,59 +5536,6 @@ export type AppAgentsResponses = {
export type AppAgentsResponse = AppAgentsResponses[keyof AppAgentsResponses]
export type AppRemoveSkillData = {
body?: never
path?: never
query: {
directory?: string
workspace?: string
location: string
}
url: "/skill"
}
export type AppRemoveSkillErrors = {
/**
* Bad request
*/
400: BadRequestError
}
export type AppRemoveSkillError = AppRemoveSkillErrors[keyof AppRemoveSkillErrors]
export type AppRemoveSkillResponses = {
/**
* Skill removed
*/
200: boolean
}
export type AppRemoveSkillResponse = AppRemoveSkillResponses[keyof AppRemoveSkillResponses]
export type AppSkillsData = {
body?: never
path?: never
query?: {
directory?: string
workspace?: string
}
url: "/skill"
}
export type AppSkillsResponses = {
/**
* List of skills
*/
200: Array<{
name: string
description: string
location: string
content: string
}>
}
export type AppSkillsResponse = AppSkillsResponses[keyof AppSkillsResponses]
export type LspStatusData = {
body?: never
path?: never
+116 -116
View File
@@ -5372,6 +5372,122 @@
]
}
},
"/skill": {
"delete": {
"operationId": "skill.remove",
"parameters": [
{
"in": "query",
"name": "directory",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "workspace",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "location",
"schema": {
"type": "string"
},
"required": true
}
],
"summary": "Remove a skill",
"description": "Remove a skill by deleting its directory from disk and clearing it from cache.",
"responses": {
"200": {
"description": "Skill removed",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
},
"400": {
"description": "Bad request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BadRequestError"
}
}
}
}
},
"x-codeSamples": [
{
"lang": "js",
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.skill.remove({\n ...\n})"
}
]
},
"get": {
"operationId": "app.skills",
"parameters": [
{
"in": "query",
"name": "directory",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "workspace",
"schema": {
"type": "string"
}
}
],
"summary": "List skills",
"description": "Get a list of all available skills in the OpenCode system.",
"responses": {
"200": {
"description": "List of skills",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"location": {
"type": "string"
},
"content": {
"type": "string"
}
},
"required": ["name", "description", "location", "content"]
}
}
}
}
}
},
"x-codeSamples": [
{
"lang": "js",
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.app.skills({\n ...\n})"
}
]
}
},
"/kilo/profile": {
"get": {
"operationId": "kilo.profile",
@@ -7878,122 +7994,6 @@
]
}
},
"/skill": {
"get": {
"operationId": "app.skills",
"parameters": [
{
"in": "query",
"name": "directory",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "workspace",
"schema": {
"type": "string"
}
}
],
"summary": "List skills",
"description": "Get a list of all available skills in the OpenCode system.",
"responses": {
"200": {
"description": "List of skills",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"location": {
"type": "string"
},
"content": {
"type": "string"
}
},
"required": ["name", "description", "location", "content"]
}
}
}
}
}
},
"x-codeSamples": [
{
"lang": "js",
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.app.skills({\n ...\n})"
}
]
},
"delete": {
"operationId": "app.removeSkill",
"parameters": [
{
"in": "query",
"name": "directory",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "workspace",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "location",
"schema": {
"type": "string"
},
"required": true
}
],
"summary": "Remove a skill",
"description": "Remove a skill by deleting its directory from disk and clearing it from cache.",
"responses": {
"200": {
"description": "Skill removed",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
},
"400": {
"description": "Bad request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BadRequestError"
}
}
}
}
},
"x-codeSamples": [
{
"lang": "js",
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.app.removeSkill({\n ...\n})"
}
]
}
},
"/lsp": {
"get": {
"operationId": "lsp.status",