Files
kilocode/packages/opencode/src/server/routes/instance/httpapi/groups/experimental.ts
T
2026-07-22 15:00:34 +02:00

336 lines
14 KiB
TypeScript

import { AccountID, OrgID } from "@/account/schema"
import { Snapshot } from "@/snapshot" // kilocode_change
import { MCP } from "@/mcp"
import { Session } from "@/session/session"
import { WorktreeDiff } from "@/kilocode/review/worktree-diff" // kilocode_change
import { SessionID } from "@/session/schema"
import { Worktree } from "@/worktree"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { Schema } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../middleware/authorization"
import { InstanceContextMiddleware } from "../middleware/instance-context"
import {
WorkspaceRoutingMiddleware,
WorkspaceRoutingQuery,
WorkspaceRoutingQueryFields,
} from "../middleware/workspace-routing"
import { described } from "./metadata"
import { QueryBoolean } from "./query"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
const ConsoleStateResponse = Schema.Struct({
consoleManagedProviders: Schema.mutable(Schema.Array(Schema.String)),
activeOrgName: Schema.optionalKey(Schema.String),
switchableOrgCount: NonNegativeInt,
}).annotate({ identifier: "ConsoleState" })
const CapabilitiesResponse = Schema.Struct({
backgroundSubagents: Schema.Boolean,
}).annotate({ identifier: "ExperimentalCapabilities" })
const ConsoleOrgOption = Schema.Struct({
accountID: Schema.String,
accountEmail: Schema.String,
accountUrl: Schema.String,
orgID: Schema.String,
orgName: Schema.String,
active: Schema.Boolean,
})
const ConsoleOrgList = Schema.Struct({
orgs: Schema.Array(ConsoleOrgOption),
})
export const ConsoleSwitchPayload = Schema.Struct({
accountID: AccountID,
orgID: OrgID,
})
const ToolIDs = Schema.Array(Schema.String).annotate({ identifier: "ToolIDs" })
const ToolListItem = Schema.Struct({
id: Schema.String,
description: Schema.String,
parameters: Schema.Unknown,
}).annotate({ identifier: "ToolListItem" })
const ToolList = Schema.Array(ToolListItem).annotate({ identifier: "ToolList" })
export const ToolListQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
provider: ProviderV2.ID,
model: ModelV2.ID,
})
// kilocode_change start
const WorktreeList = Schema.Array(
Schema.Struct({ directory: Schema.String, managed: Schema.Boolean }).annotate({ identifier: "WorktreeListItem" }),
)
// kilocode_change end
const WorktreeErrorName = Schema.Union([
Schema.Literal("WorktreeNotGitError"),
Schema.Literal("WorktreeNameGenerationFailedError"),
Schema.Literal("WorktreeCreateFailedError"),
Schema.Literal("WorktreeStartCommandFailedError"),
Schema.Literal("WorktreeRemoveFailedError"),
Schema.Literal("WorktreeResetFailedError"),
Schema.Literal("WorktreeListFailedError"),
])
export class WorktreeApiError extends Schema.ErrorClass<WorktreeApiError>("WorktreeError")(
{
name: WorktreeErrorName,
data: Schema.Struct({ message: Schema.String }),
},
{ httpApiStatus: 400 },
) {}
export const SessionListQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
// kilocode_change start
projectID: Schema.optional(Schema.String),
worktrees: Schema.optional(QueryBoolean),
current: Schema.optional(QueryBoolean),
// kilocode_change end
roots: Schema.optional(QueryBoolean),
start: Schema.optional(Schema.NumberFromString),
cursor: Schema.optional(Schema.NumberFromString),
search: Schema.optional(Schema.String),
limit: Schema.optional(Schema.NumberFromString),
archived: Schema.optional(QueryBoolean),
})
// kilocode_change start
export const WorktreeDiffQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
base: Schema.optional(Schema.String),
})
export const WorktreeDiffFileQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
base: Schema.optional(Schema.String),
file: Schema.String,
})
// kilocode_change end
export const ExperimentalPaths = {
capabilities: "/experimental/capabilities",
console: "/experimental/console",
consoleOrgs: "/experimental/console/orgs",
consoleSwitch: "/experimental/console/switch",
tool: "/experimental/tool",
toolIDs: "/experimental/tool/ids",
worktree: "/experimental/worktree",
worktreeDiff: "/experimental/worktree/diff", // kilocode_change
worktreeDiffFile: "/experimental/worktree/diff/file", // kilocode_change
worktreeDiffSummary: "/experimental/worktree/diff/summary", // kilocode_change
worktreeReset: "/experimental/worktree/reset",
session: "/experimental/session",
sessionBackground: "/experimental/session/:sessionID/background",
resource: "/experimental/resource",
} as const
export const ExperimentalApi = HttpApi.make("experimental")
.add(
HttpApiGroup.make("experimental")
.add(
HttpApiEndpoint.get("capabilities", ExperimentalPaths.capabilities, {
query: WorkspaceRoutingQuery,
success: described(CapabilitiesResponse, "Experimental capabilities"),
}).annotateMerge(
OpenApi.annotations({
identifier: "experimental.capabilities.get",
summary: "Get experimental capabilities",
description: "Get experimental features enabled on the OpenCode server.",
}),
),
HttpApiEndpoint.get("console", ExperimentalPaths.console, {
query: WorkspaceRoutingQuery,
success: described(ConsoleStateResponse, "Active Console provider metadata"),
error: HttpApiError.InternalServerError,
}).annotateMerge(
OpenApi.annotations({
identifier: "experimental.console.get",
summary: "Get active Console provider metadata",
description: "Get the active Console org name and the set of provider IDs managed by that Console org.",
}),
),
HttpApiEndpoint.get("consoleOrgs", ExperimentalPaths.consoleOrgs, {
query: WorkspaceRoutingQuery,
success: described(ConsoleOrgList, "Switchable Console orgs"),
error: HttpApiError.InternalServerError,
}).annotateMerge(
OpenApi.annotations({
identifier: "experimental.console.listOrgs",
summary: "List switchable Console orgs",
description: "Get the available Console orgs across logged-in accounts, including the current active org.",
}),
),
HttpApiEndpoint.post("consoleSwitch", ExperimentalPaths.consoleSwitch, {
query: WorkspaceRoutingQuery,
payload: ConsoleSwitchPayload,
success: described(Schema.Boolean, "Switch success"),
error: HttpApiError.BadRequest,
}).annotateMerge(
OpenApi.annotations({
identifier: "experimental.console.switchOrg",
summary: "Switch active Console org",
description: "Persist a new active Console account/org selection for the current local Kilo state.", // kilocode_change
}),
),
HttpApiEndpoint.get("tool", ExperimentalPaths.tool, {
query: ToolListQuery,
success: described(ToolList, "Tools"),
error: HttpApiError.BadRequest,
}).annotateMerge(
OpenApi.annotations({
identifier: "tool.list",
summary: "List tools",
description:
"Get a list of available tools with their JSON schema parameters for a specific provider and model combination.",
}),
),
HttpApiEndpoint.get("toolIDs", ExperimentalPaths.toolIDs, {
query: WorkspaceRoutingQuery,
success: described(ToolIDs, "Tool IDs"),
error: HttpApiError.BadRequest,
}).annotateMerge(
OpenApi.annotations({
identifier: "tool.ids",
summary: "List tool IDs",
description:
"Get a list of all available tool IDs, including both built-in tools and dynamically registered tools.",
}),
),
HttpApiEndpoint.get("worktree", ExperimentalPaths.worktree, {
query: WorkspaceRoutingQuery,
success: described(WorktreeList, "List of worktrees"), // kilocode_change
error: WorktreeApiError,
}).annotateMerge(
OpenApi.annotations({
identifier: "worktree.list",
summary: "List worktrees",
description: "List all git worktrees for the current project and whether Kilo manages them.", // kilocode_change
}),
),
HttpApiEndpoint.post("worktreeCreate", ExperimentalPaths.worktree, {
disableCodecs: true,
query: WorkspaceRoutingQuery,
payload: [HttpApiSchema.NoContent, Worktree.CreateInput],
success: described(Worktree.Info, "Worktree created"),
error: WorktreeApiError,
}).annotateMerge(
OpenApi.annotations({
identifier: "worktree.create",
summary: "Create worktree",
description: "Create a new git worktree for the current project and run any configured startup scripts.",
}),
),
HttpApiEndpoint.delete("worktreeRemove", ExperimentalPaths.worktree, {
query: WorkspaceRoutingQuery,
payload: Worktree.RemoveInput,
success: described(Schema.Boolean, "Worktree removed"),
error: WorktreeApiError,
}).annotateMerge(
OpenApi.annotations({
identifier: "worktree.remove",
summary: "Remove worktree",
description: "Remove a git worktree and delete its branch.",
}),
),
HttpApiEndpoint.post("worktreeReset", ExperimentalPaths.worktreeReset, {
query: WorkspaceRoutingQuery,
payload: Worktree.ResetInput,
success: described(Schema.Boolean, "Worktree reset"),
error: WorktreeApiError,
}).annotateMerge(
OpenApi.annotations({
identifier: "worktree.reset",
summary: "Reset worktree",
description: "Reset a worktree branch to the primary default branch.",
}),
),
// kilocode_change start - worktree diff endpoints for agent manager
HttpApiEndpoint.get("worktreeDiff", ExperimentalPaths.worktreeDiff, {
query: WorktreeDiffQuery,
success: described(Schema.Array(Snapshot.FileDiff), "File diffs"),
error: HttpApiError.BadRequest,
}).annotateMerge(
OpenApi.annotations({
identifier: "worktree.diff",
summary: "Get worktree diff",
description: "Get file diffs for a worktree compared to its base branch. Includes uncommitted changes.",
}),
),
HttpApiEndpoint.get("worktreeDiffSummary", ExperimentalPaths.worktreeDiffSummary, {
query: WorktreeDiffQuery,
success: described(Schema.Array(WorktreeDiff.Item), "Diff summary items"),
error: HttpApiError.BadRequest,
}).annotateMerge(
OpenApi.annotations({
identifier: "worktree.diffSummary",
summary: "Get worktree diff summary",
description: "Get lightweight file diff metadata for a worktree compared to its base branch.",
}),
),
HttpApiEndpoint.get("worktreeDiffFile", ExperimentalPaths.worktreeDiffFile, {
query: WorktreeDiffFileQuery,
success: described(Schema.NullOr(WorktreeDiff.Item), "Diff detail item"),
error: HttpApiError.BadRequest,
}).annotateMerge(
OpenApi.annotations({
identifier: "worktree.diffFile",
summary: "Get worktree diff detail",
description: "Get full diff contents for one worktree file compared to its base branch.",
}),
),
// kilocode_change end
HttpApiEndpoint.get("session", ExperimentalPaths.session, {
query: SessionListQuery,
success: described(Schema.Array(Session.GlobalInfo), "List of sessions"),
}).annotateMerge(
OpenApi.annotations({
identifier: "experimental.session.list",
summary: "List sessions",
description:
"Get a list of all Kilo sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.", // kilocode_change
}),
),
HttpApiEndpoint.post("sessionBackground", ExperimentalPaths.sessionBackground, {
params: { sessionID: SessionID },
query: WorkspaceRoutingQuery,
success: described(Schema.Boolean, "Backgrounded subagents"),
error: HttpApiError.BadRequest,
}).annotateMerge(
OpenApi.annotations({
identifier: "experimental.session.background",
summary: "Background subagents",
description:
"Detach any synchronous subagents currently blocking the session and continue them in the background.",
}),
),
HttpApiEndpoint.get("resource", ExperimentalPaths.resource, {
query: WorkspaceRoutingQuery,
success: described(Schema.Record(Schema.String, MCP.Resource), "MCP resources"),
}).annotateMerge(
OpenApi.annotations({
identifier: "experimental.resource.list",
summary: "Get MCP resources",
description: "Get all available MCP resources from connected servers. Optionally filter by name.",
}),
),
)
.annotateMerge(
OpenApi.annotations({
title: "experimental",
description: "Experimental HttpApi read-only routes.",
}),
)
.middleware(InstanceContextMiddleware)
.middleware(WorkspaceRoutingMiddleware)
.middleware(Authorization),
)
.annotateMerge(
OpenApi.annotations({
title: "opencode experimental HttpApi",
version: "0.0.1",
description: "Experimental HttpApi surface for selected instance routes.",
}),
)