Files
neuron/packages/opencode/src/server/routes/instance/httpapi/instance.ts
T

209 lines
6.8 KiB
TypeScript

import { Agent } from "@/agent/agent"
import { Command } from "@/command"
import { Format } from "@/format"
import { Global } from "@opencode-ai/core/global"
import { LSP } from "@/lsp/lsp"
import { Vcs } from "@/project/vcs"
import { Skill } from "@/skill"
import * as InstanceState from "@/effect/instance-state"
import { Effect, Schema } from "effect"
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "./auth"
import { markInstanceForDisposal } from "./lifecycle"
const PathInfo = Schema.Struct({
home: Schema.String,
state: Schema.String,
config: Schema.String,
worktree: Schema.String,
directory: Schema.String,
}).annotate({ identifier: "Path" })
const VcsDiffQuery = Schema.Struct({
mode: Vcs.Mode,
})
export const InstancePaths = {
dispose: "/instance/dispose",
path: "/path",
vcs: "/vcs",
vcsDiff: "/vcs/diff",
command: "/command",
agent: "/agent",
skill: "/skill",
lsp: "/lsp",
formatter: "/formatter",
} as const
export const InstanceApi = HttpApi.make("instance")
.add(
HttpApiGroup.make("instance")
.add(
HttpApiEndpoint.post("dispose", InstancePaths.dispose, {
success: Schema.Boolean,
}).annotateMerge(
OpenApi.annotations({
identifier: "instance.dispose",
summary: "Dispose instance",
description: "Clean up and dispose the current OpenCode instance, releasing all resources.",
}),
),
HttpApiEndpoint.get("path", InstancePaths.path, {
success: PathInfo,
}).annotateMerge(
OpenApi.annotations({
identifier: "path.get",
summary: "Get paths",
description:
"Retrieve the current working directory and related path information for the OpenCode instance.",
}),
),
HttpApiEndpoint.get("vcs", InstancePaths.vcs, {
success: Vcs.Info,
}).annotateMerge(
OpenApi.annotations({
identifier: "vcs.get",
summary: "Get VCS info",
description:
"Retrieve version control system (VCS) information for the current project, such as git branch.",
}),
),
HttpApiEndpoint.get("vcsDiff", InstancePaths.vcsDiff, {
query: VcsDiffQuery,
success: Schema.Array(Vcs.FileDiff),
}).annotateMerge(
OpenApi.annotations({
identifier: "vcs.diff",
summary: "Get VCS diff",
description: "Retrieve the current git diff for the working tree or against the default branch.",
}),
),
HttpApiEndpoint.get("command", InstancePaths.command, {
success: Schema.Array(Command.Info),
}).annotateMerge(
OpenApi.annotations({
identifier: "command.list",
summary: "List commands",
description: "Get a list of all available commands in the OpenCode system.",
}),
),
HttpApiEndpoint.get("agent", InstancePaths.agent, {
success: Schema.Array(Agent.Info),
}).annotateMerge(
OpenApi.annotations({
identifier: "app.agents",
summary: "List agents",
description: "Get a list of all available AI agents in the OpenCode system.",
}),
),
HttpApiEndpoint.get("skill", InstancePaths.skill, {
success: Schema.Array(Skill.Info),
}).annotateMerge(
OpenApi.annotations({
identifier: "app.skills",
summary: "List skills",
description: "Get a list of all available skills in the OpenCode system.",
}),
),
HttpApiEndpoint.get("lsp", InstancePaths.lsp, {
success: Schema.Array(LSP.Status),
}).annotateMerge(
OpenApi.annotations({
identifier: "lsp.status",
summary: "Get LSP status",
description: "Get LSP server status",
}),
),
HttpApiEndpoint.get("formatter", InstancePaths.formatter, {
success: Schema.Array(Format.Status),
}).annotateMerge(
OpenApi.annotations({
identifier: "formatter.status",
summary: "Get formatter status",
description: "Get formatter status",
}),
),
)
.annotateMerge(
OpenApi.annotations({
title: "instance",
description: "Experimental HttpApi instance read routes.",
}),
)
.middleware(Authorization),
)
.annotateMerge(
OpenApi.annotations({
title: "opencode experimental HttpApi",
version: "0.0.1",
description: "Experimental HttpApi surface for selected instance routes.",
}),
)
export const instanceHandlers = HttpApiBuilder.group(InstanceApi, "instance", (handlers) =>
Effect.gen(function* () {
const agent = yield* Agent.Service
const command = yield* Command.Service
const format = yield* Format.Service
const lsp = yield* LSP.Service
const skill = yield* Skill.Service
const vcs = yield* Vcs.Service
const dispose = Effect.fn("InstanceHttpApi.dispose")(function* () {
yield* markInstanceForDisposal(yield* InstanceState.context)
return true
})
const getPath = Effect.fn("InstanceHttpApi.path")(function* () {
const ctx = yield* InstanceState.context
return {
home: Global.Path.home,
state: Global.Path.state,
config: Global.Path.config,
worktree: ctx.worktree,
directory: ctx.directory,
}
})
const getVcs = Effect.fn("InstanceHttpApi.vcs")(function* () {
const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { concurrency: 2 })
return { branch, default_branch }
})
const getVcsDiff = Effect.fn("InstanceHttpApi.vcsDiff")(function* (ctx: { query: { mode: Vcs.Mode } }) {
return yield* vcs.diff(ctx.query.mode)
})
const getCommand = Effect.fn("InstanceHttpApi.command")(function* () {
return yield* command.list()
})
const getAgent = Effect.fn("InstanceHttpApi.agent")(function* () {
return yield* agent.list()
})
const getSkill = Effect.fn("InstanceHttpApi.skill")(function* () {
return yield* skill.all()
})
const getLsp = Effect.fn("InstanceHttpApi.lsp")(function* () {
return yield* lsp.status()
})
const getFormatter = Effect.fn("InstanceHttpApi.formatter")(function* () {
return yield* format.status()
})
return handlers
.handle("dispose", dispose)
.handle("path", getPath)
.handle("vcs", getVcs)
.handle("vcsDiff", getVcsDiff)
.handle("command", getCommand)
.handle("agent", getAgent)
.handle("skill", getSkill)
.handle("lsp", getLsp)
.handle("formatter", getFormatter)
}),
)