Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/opencode/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Config } from "../config/config"
import { Bus } from "../bus"
import { Log } from "../util/log"
import { createOpencodeClient } from "@opencode-ai/sdk"
import { Server } from "../server/server"
import { BunProc } from "../bun"
import { Instance } from "../project/instance"
import { Flag } from "../flag/flag"
Expand All @@ -18,6 +17,7 @@ export namespace Plugin {
const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin]

const state = Instance.state(async () => {
const { Server } = await import("../server/server")
const client = createOpencodeClient({
baseUrl: "http://localhost:4096",
// @ts-ignore - fetch type incompatibility
Expand Down
26 changes: 26 additions & 0 deletions packages/opencode/src/server/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Hono } from "hono"
import { describeRoute, resolver } from "hono-openapi"
import { Agent } from "../agent/agent"

export const AgentRoute = new Hono().get(
"/",
describeRoute({
summary: "List agents",
description: "Get a list of all available AI agents in the OpenCode system.",
operationId: "app.agents",
responses: {
200: {
description: "List of agents",
content: {
"application/json": {
schema: resolver(Agent.Info.array()),
},
},
},
},
}),
async (c) => {
const modes = await Agent.list()
return c.json(modes)
},
)
38 changes: 38 additions & 0 deletions packages/opencode/src/server/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Hono } from "hono"
import { describeRoute, resolver, validator } from "hono-openapi"
import z from "zod"
import { errors } from "./error"
import { Auth } from "../auth"

export const AuthRoute = new Hono().put(
"/:providerID",
describeRoute({
summary: "Set auth credentials",
description: "Set authentication credentials",
operationId: "auth.set",
responses: {
200: {
description: "Successfully set authentication credentials",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
...errors(400),
},
}),
validator(
"param",
z.object({
providerID: z.string(),
}),
),
validator("json", Auth.Info),
async (c) => {
const providerID = c.req.valid("param").providerID
const info = c.req.valid("json")
await Auth.set(providerID, info)
return c.json(true)
},
)
26 changes: 26 additions & 0 deletions packages/opencode/src/server/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Hono } from "hono"
import { describeRoute, resolver } from "hono-openapi"
import { Command } from "../command"

export const CommandRoute = new Hono().get(
"/",
describeRoute({
summary: "List commands",
description: "Get a list of all available commands in the OpenCode system.",
operationId: "command.list",
responses: {
200: {
description: "List of commands",
content: {
"application/json": {
schema: resolver(Command.Info.array()),
},
},
},
},
}),
async (c) => {
const commands = await Command.list()
return c.json(commands)
},
)
90 changes: 90 additions & 0 deletions packages/opencode/src/server/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Hono } from "hono"
import { describeRoute, resolver, validator } from "hono-openapi"
import { mapValues } from "remeda"
import z from "zod"
import { errors } from "./error"
import { Config } from "../config/config"
import { Provider } from "../provider/provider"
import { Log } from "../util/log"

const log = Log.create({ service: "server.config" })

export const ConfigRoute = new Hono()
.get(
"/",
describeRoute({
summary: "Get configuration",
description: "Retrieve the current OpenCode configuration settings and preferences.",
operationId: "config.get",
responses: {
200: {
description: "Get config info",
content: {
"application/json": {
schema: resolver(Config.Info),
},
},
},
},
}),
async (c) => {
return c.json(await Config.get())
},
)

.patch(
"/",
describeRoute({
summary: "Update configuration",
description: "Update OpenCode configuration settings and preferences.",
operationId: "config.update",
responses: {
200: {
description: "Successfully updated config",
content: {
"application/json": {
schema: resolver(Config.Info),
},
},
},
...errors(400),
},
}),
validator("json", Config.Info),
async (c) => {
const config = c.req.valid("json")
await Config.update(config)
return c.json(config)
},
)
.get(
"/providers",
describeRoute({
summary: "List config providers",
description: "Get a list of all configured AI providers and their default models.",
operationId: "config.providers",
responses: {
200: {
description: "List of providers",
content: {
"application/json": {
schema: resolver(
z.object({
providers: Provider.Info.array(),
default: z.record(z.string(), z.string()),
}),
),
},
},
},
},
}),
async (c) => {
using _ = log.time("providers")
const providers = await Provider.list().then((x) => mapValues(x, (item) => item))
return c.json({
providers: Object.values(providers),
default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
})
},
)
196 changes: 196 additions & 0 deletions packages/opencode/src/server/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { Hono } from "hono"
import { describeRoute, resolver, validator } from "hono-openapi"
import z from "zod"
import { File } from "../file"
import { Ripgrep } from "../file/ripgrep"
import { LSP } from "../lsp"
import { Instance } from "../project/instance"

export const FileRoute = new Hono()
.get(
"/",
describeRoute({
summary: "List files",
description: "List files and directories in a specified path.",
operationId: "file.list",
responses: {
200: {
description: "Files and directories",
content: {
"application/json": {
schema: resolver(File.Node.array()),
},
},
},
},
}),
validator(
"query",
z.object({
path: z.string(),
}),
),
async (c) => {
const path = c.req.valid("query").path
const content = await File.list(path)
return c.json(content)
},
)
.get(
"/content",
describeRoute({
summary: "Read file",
description: "Read the content of a specified file.",
operationId: "file.read",
responses: {
200: {
description: "File content",
content: {
"application/json": {
schema: resolver(File.Content),
},
},
},
},
}),
validator(
"query",
z.object({
path: z.string(),
}),
),
async (c) => {
const path = c.req.valid("query").path
const content = await File.read(path)
return c.json(content)
},
)
.get(
"/status",
describeRoute({
summary: "Get file status",
description: "Get the git status of all files in the project.",
operationId: "file.status",
responses: {
200: {
description: "File status",
content: {
"application/json": {
schema: resolver(File.Info.array()),
},
},
},
},
}),
async (c) => {
const content = await File.status()
return c.json(content)
},
)

export const FindRoute = new Hono()
.get(
"/",
describeRoute({
summary: "Find text",
description: "Search for text patterns across files in the project using ripgrep.",
operationId: "find.text",
responses: {
200: {
description: "Matches",
content: {
"application/json": {
schema: resolver(Ripgrep.Match.shape.data.array()),
},
},
},
},
}),
validator(
"query",
z.object({
pattern: z.string(),
}),
),
async (c) => {
const pattern = c.req.valid("query").pattern
const result = await Ripgrep.search({
cwd: Instance.directory,
pattern,
limit: 10,
})
return c.json(result)
},
)
.get(
"/file",
describeRoute({
summary: "Find files",
description: "Search for files or directories by name or pattern in the project directory.",
operationId: "find.files",
responses: {
200: {
description: "File paths",
content: {
"application/json": {
schema: resolver(z.string().array()),
},
},
},
},
}),
validator(
"query",
z.object({
query: z.string(),
dirs: z.enum(["true", "false"]).optional(),
type: z.enum(["file", "directory"]).optional(),
limit: z.coerce.number().int().min(1).max(200).optional(),
}),
),
async (c) => {
const query = c.req.valid("query").query
const dirs = c.req.valid("query").dirs
const type = c.req.valid("query").type
const limit = c.req.valid("query").limit
const results = await File.search({
query,
limit: limit ?? 10,
dirs: dirs !== "false",
type,
})
return c.json(results)
},
)
.get(
"/symbol",
describeRoute({
summary: "Find symbols",
description: "Search for workspace symbols like functions, classes, and variables using LSP.",
operationId: "find.symbols",
responses: {
200: {
description: "Symbols",
content: {
"application/json": {
schema: resolver(LSP.Symbol.array()),
},
},
},
},
}),
validator(
"query",
z.object({
query: z.string(),
}),
),
async (c) => {
/*
const query = c.req.valid("query").query
const result = await LSP.workspaceSymbol(query)
return c.json(result)
*/
return c.json([])
},
)
Loading