feat(server): wrap remaining route handlers in request spans (#23169)

This commit is contained in:
Kit Langton
2026-04-17 16:52:40 -04:00
committed by GitHub
parent ed0f022502
commit 999d8651aa
13 changed files with 550 additions and 600 deletions
@@ -5,7 +5,6 @@ import { Config } from "@/config"
import { Provider } from "@/provider" import { Provider } from "@/provider"
import { errors } from "../../error" import { errors } from "../../error"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { AppRuntime } from "@/effect/app-runtime"
import { jsonRequest } from "./trace" import { jsonRequest } from "./trace"
export const ConfigRoutes = lazy(() => export const ConfigRoutes = lazy(() =>
@@ -52,11 +51,13 @@ export const ConfigRoutes = lazy(() =>
}, },
}), }),
validator("json", Config.Info), validator("json", Config.Info),
async (c) => { async (c) =>
const config = c.req.valid("json") jsonRequest("ConfigRoutes.update", c, function* () {
await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.update(config))) const config = c.req.valid("json")
return c.json(config) const cfg = yield* Config.Service
}, yield* cfg.update(config)
return config
}),
) )
.get( .get(
"/providers", "/providers",
@@ -12,11 +12,11 @@ import { Config } from "@/config"
import { ConsoleState } from "@/config/console-state" import { ConsoleState } from "@/config/console-state"
import { Account } from "@/account/account" import { Account } from "@/account/account"
import { AccountID, OrgID } from "@/account/schema" import { AccountID, OrgID } from "@/account/schema"
import { AppRuntime } from "@/effect/app-runtime"
import { errors } from "../../error" import { errors } from "../../error"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { Effect, Option } from "effect" import { Effect, Option } from "effect"
import { Agent } from "@/agent/agent" import { Agent } from "@/agent/agent"
import { jsonRequest, runRequest } from "./trace"
const ConsoleOrgOption = z.object({ const ConsoleOrgOption = z.object({
accountID: z.string(), accountID: z.string(),
@@ -55,22 +55,18 @@ export const ExperimentalRoutes = lazy(() =>
}, },
}, },
}), }),
async (c) => { async (c) =>
const result = await AppRuntime.runPromise( jsonRequest("ExperimentalRoutes.console.get", c, function* () {
Effect.gen(function* () { const config = yield* Config.Service
const config = yield* Config.Service const account = yield* Account.Service
const account = yield* Account.Service const [state, groups] = yield* Effect.all([config.getConsoleState(), account.orgsByAccount()], {
const [state, groups] = yield* Effect.all([config.getConsoleState(), account.orgsByAccount()], { concurrency: "unbounded",
concurrency: "unbounded", })
}) return {
return { ...state,
...state, switchableOrgCount: groups.reduce((count, group) => count + group.orgs.length, 0),
switchableOrgCount: groups.reduce((count, group) => count + group.orgs.length, 0), }
} }),
}),
)
return c.json(result)
},
) )
.get( .get(
"/console/orgs", "/console/orgs",
@@ -89,28 +85,25 @@ export const ExperimentalRoutes = lazy(() =>
}, },
}, },
}), }),
async (c) => { async (c) =>
const orgs = await AppRuntime.runPromise( jsonRequest("ExperimentalRoutes.console.listOrgs", c, function* () {
Effect.gen(function* () { const account = yield* Account.Service
const account = yield* Account.Service const [groups, active] = yield* Effect.all([account.orgsByAccount(), account.active()], {
const [groups, active] = yield* Effect.all([account.orgsByAccount(), account.active()], { concurrency: "unbounded",
concurrency: "unbounded", })
}) const info = Option.getOrUndefined(active)
const info = Option.getOrUndefined(active) const orgs = groups.flatMap((group) =>
return groups.flatMap((group) => group.orgs.map((org) => ({
group.orgs.map((org) => ({ accountID: group.account.id,
accountID: group.account.id, accountEmail: group.account.email,
accountEmail: group.account.email, accountUrl: group.account.url,
accountUrl: group.account.url, orgID: org.id,
orgID: org.id, orgName: org.name,
orgName: org.name, active: !!info && info.id === group.account.id && info.active_org_id === org.id,
active: !!info && info.id === group.account.id && info.active_org_id === org.id, })),
})), )
) return { orgs }
}), }),
)
return c.json({ orgs })
},
) )
.post( .post(
"/console/switch", "/console/switch",
@@ -130,16 +123,13 @@ export const ExperimentalRoutes = lazy(() =>
}, },
}), }),
validator("json", ConsoleSwitchBody), validator("json", ConsoleSwitchBody),
async (c) => { async (c) =>
const body = c.req.valid("json") jsonRequest("ExperimentalRoutes.console.switchOrg", c, function* () {
await AppRuntime.runPromise( const body = c.req.valid("json")
Effect.gen(function* () { const account = yield* Account.Service
const account = yield* Account.Service yield* account.use(AccountID.make(body.accountID), Option.some(OrgID.make(body.orgID)))
yield* account.use(AccountID.make(body.accountID), Option.some(OrgID.make(body.orgID))) return true
}), }),
)
return c.json(true)
},
) )
.get( .get(
"/tool/ids", "/tool/ids",
@@ -160,15 +150,11 @@ export const ExperimentalRoutes = lazy(() =>
...errors(400), ...errors(400),
}, },
}), }),
async (c) => { async (c) =>
const ids = await AppRuntime.runPromise( jsonRequest("ExperimentalRoutes.tool.ids", c, function* () {
Effect.gen(function* () { const registry = yield* ToolRegistry.Service
const registry = yield* ToolRegistry.Service return yield* registry.ids()
return yield* registry.ids() }),
}),
)
return c.json(ids)
},
) )
.get( .get(
"/tool", "/tool",
@@ -210,7 +196,9 @@ export const ExperimentalRoutes = lazy(() =>
), ),
async (c) => { async (c) => {
const { provider, model } = c.req.valid("query") const { provider, model } = c.req.valid("query")
const tools = await AppRuntime.runPromise( const tools = await runRequest(
"ExperimentalRoutes.tool.list",
c,
Effect.gen(function* () { Effect.gen(function* () {
const agents = yield* Agent.Service const agents = yield* Agent.Service
const registry = yield* ToolRegistry.Service const registry = yield* ToolRegistry.Service
@@ -249,11 +237,12 @@ export const ExperimentalRoutes = lazy(() =>
}, },
}), }),
validator("json", Worktree.CreateInput.optional()), validator("json", Worktree.CreateInput.optional()),
async (c) => { async (c) =>
const body = c.req.valid("json") jsonRequest("ExperimentalRoutes.worktree.create", c, function* () {
const worktree = await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.create(body))) const body = c.req.valid("json")
return c.json(worktree) const svc = yield* Worktree.Service
}, return yield* svc.create(body)
}),
) )
.get( .get(
"/worktree", "/worktree",
@@ -272,10 +261,11 @@ export const ExperimentalRoutes = lazy(() =>
}, },
}, },
}), }),
async (c) => { async (c) =>
const sandboxes = await AppRuntime.runPromise(Project.Service.use((svc) => svc.sandboxes(Instance.project.id))) jsonRequest("ExperimentalRoutes.worktree.list", c, function* () {
return c.json(sandboxes) const svc = yield* Project.Service
}, return yield* svc.sandboxes(Instance.project.id)
}),
) )
.delete( .delete(
"/worktree", "/worktree",
@@ -296,14 +286,15 @@ export const ExperimentalRoutes = lazy(() =>
}, },
}), }),
validator("json", Worktree.RemoveInput), validator("json", Worktree.RemoveInput),
async (c) => { async (c) =>
const body = c.req.valid("json") jsonRequest("ExperimentalRoutes.worktree.remove", c, function* () {
await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove(body))) const body = c.req.valid("json")
await AppRuntime.runPromise( const worktree = yield* Worktree.Service
Project.Service.use((svc) => svc.removeSandbox(Instance.project.id, body.directory)), const project = yield* Project.Service
) yield* worktree.remove(body)
return c.json(true) yield* project.removeSandbox(Instance.project.id, body.directory)
}, return true
}),
) )
.post( .post(
"/worktree/reset", "/worktree/reset",
@@ -324,11 +315,13 @@ export const ExperimentalRoutes = lazy(() =>
}, },
}), }),
validator("json", Worktree.ResetInput), validator("json", Worktree.ResetInput),
async (c) => { async (c) =>
const body = c.req.valid("json") jsonRequest("ExperimentalRoutes.worktree.reset", c, function* () {
await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.reset(body))) const body = c.req.valid("json")
return c.json(true) const svc = yield* Worktree.Service
}, yield* svc.reset(body)
return true
}),
) )
.get( .get(
"/session", "/session",
@@ -406,15 +399,10 @@ export const ExperimentalRoutes = lazy(() =>
}, },
}, },
}), }),
async (c) => { async (c) =>
return c.json( jsonRequest("ExperimentalRoutes.resource.list", c, function* () {
await AppRuntime.runPromise( const mcp = yield* MCP.Service
Effect.gen(function* () { return yield* mcp.resources()
const mcp = yield* MCP.Service }),
return yield* mcp.resources()
}),
),
)
},
), ),
) )
@@ -1,13 +1,12 @@
import { Hono } from "hono" import { Hono } from "hono"
import { describeRoute, validator, resolver } from "hono-openapi" import { describeRoute, validator, resolver } from "hono-openapi"
import { Effect } from "effect"
import z from "zod" import z from "zod"
import { AppRuntime } from "@/effect/app-runtime"
import { File } from "@/file" import { File } from "@/file"
import { Ripgrep } from "@/file/ripgrep" import { Ripgrep } from "@/file/ripgrep"
import { LSP } from "@/lsp" import { LSP } from "@/lsp"
import { Instance } from "@/project/instance" import { Instance } from "@/project/instance"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { jsonRequest } from "./trace"
export const FileRoutes = lazy(() => export const FileRoutes = lazy(() =>
new Hono() new Hono()
@@ -34,13 +33,13 @@ export const FileRoutes = lazy(() =>
pattern: z.string(), pattern: z.string(),
}), }),
), ),
async (c) => { async (c) =>
const pattern = c.req.valid("query").pattern jsonRequest("FileRoutes.findText", c, function* () {
const result = await AppRuntime.runPromise( const pattern = c.req.valid("query").pattern
Ripgrep.Service.use((svc) => svc.search({ cwd: Instance.directory, pattern, limit: 10 })), const svc = yield* Ripgrep.Service
) const result = yield* svc.search({ cwd: Instance.directory, pattern, limit: 10 })
return c.json(result.items) return result.items
}, }),
) )
.get( .get(
"/find/file", "/find/file",
@@ -68,25 +67,17 @@ export const FileRoutes = lazy(() =>
limit: z.coerce.number().int().min(1).max(200).optional(), limit: z.coerce.number().int().min(1).max(200).optional(),
}), }),
), ),
async (c) => { async (c) =>
const query = c.req.valid("query").query jsonRequest("FileRoutes.findFile", c, function* () {
const dirs = c.req.valid("query").dirs const query = c.req.valid("query")
const type = c.req.valid("query").type const svc = yield* File.Service
const limit = c.req.valid("query").limit return yield* svc.search({
const results = await AppRuntime.runPromise( query: query.query,
Effect.gen(function* () { limit: query.limit ?? 10,
return yield* File.Service.use((svc) => dirs: query.dirs !== "false",
svc.search({ type: query.type,
query, })
limit: limit ?? 10, }),
dirs: dirs !== "false",
type,
}),
)
}),
)
return c.json(results)
},
) )
.get( .get(
"/find/symbol", "/find/symbol",
@@ -138,15 +129,11 @@ export const FileRoutes = lazy(() =>
path: z.string(), path: z.string(),
}), }),
), ),
async (c) => { async (c) =>
const path = c.req.valid("query").path jsonRequest("FileRoutes.list", c, function* () {
const content = await AppRuntime.runPromise( const svc = yield* File.Service
Effect.gen(function* () { return yield* svc.list(c.req.valid("query").path)
return yield* File.Service.use((svc) => svc.list(path)) }),
}),
)
return c.json(content)
},
) )
.get( .get(
"/file/content", "/file/content",
@@ -171,15 +158,11 @@ export const FileRoutes = lazy(() =>
path: z.string(), path: z.string(),
}), }),
), ),
async (c) => { async (c) =>
const path = c.req.valid("query").path jsonRequest("FileRoutes.read", c, function* () {
const content = await AppRuntime.runPromise( const svc = yield* File.Service
Effect.gen(function* () { return yield* svc.read(c.req.valid("query").path)
return yield* File.Service.use((svc) => svc.read(path)) }),
}),
)
return c.json(content)
},
) )
.get( .get(
"/file/status", "/file/status",
@@ -198,13 +181,10 @@ export const FileRoutes = lazy(() =>
}, },
}, },
}), }),
async (c) => { async (c) =>
const content = await AppRuntime.runPromise( jsonRequest("FileRoutes.status", c, function* () {
Effect.gen(function* () { const svc = yield* File.Service
return yield* File.Service.use((svc) => svc.status()) return yield* svc.status()
}), }),
)
return c.json(content)
},
), ),
) )
@@ -26,8 +26,8 @@ import { ExperimentalRoutes } from "./experimental"
import { ProviderRoutes } from "./provider" import { ProviderRoutes } from "./provider"
import { EventRoutes } from "./event" import { EventRoutes } from "./event"
import { SyncRoutes } from "./sync" import { SyncRoutes } from "./sync"
import { AppRuntime } from "@/effect/app-runtime"
import { InstanceMiddleware } from "./middleware" import { InstanceMiddleware } from "./middleware"
import { jsonRequest } from "./trace"
export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
const app = new Hono() const app = new Hono()
@@ -141,19 +141,14 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
}, },
}, },
}), }),
async (c) => { async (c) =>
return c.json( jsonRequest("InstanceRoutes.vcs.get", c, function* () {
await AppRuntime.runPromise( const vcs = yield* Vcs.Service
Effect.gen(function* () { const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], {
const vcs = yield* Vcs.Service concurrency: 2,
const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { })
concurrency: 2, return { branch, default_branch }
}) }),
return { branch, default_branch }
}),
),
)
},
) )
.get( .get(
"/vcs/diff", "/vcs/diff",
@@ -178,16 +173,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
mode: Vcs.Mode, mode: Vcs.Mode,
}), }),
), ),
async (c) => { async (c) =>
return c.json( jsonRequest("InstanceRoutes.vcs.diff", c, function* () {
await AppRuntime.runPromise( const vcs = yield* Vcs.Service
Effect.gen(function* () { return yield* vcs.diff(c.req.valid("query").mode)
const vcs = yield* Vcs.Service }),
return yield* vcs.diff(c.req.valid("query").mode)
}),
),
)
},
) )
.get( .get(
"/command", "/command",
@@ -206,10 +196,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
}, },
}, },
}), }),
async (c) => { async (c) =>
const commands = await AppRuntime.runPromise(Command.Service.use((svc) => svc.list())) jsonRequest("InstanceRoutes.command.list", c, function* () {
return c.json(commands) const svc = yield* Command.Service
}, return yield* svc.list()
}),
) )
.get( .get(
"/agent", "/agent",
@@ -228,10 +219,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
}, },
}, },
}), }),
async (c) => { async (c) =>
const modes = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.list())) jsonRequest("InstanceRoutes.agent.list", c, function* () {
return c.json(modes) const svc = yield* Agent.Service
}, return yield* svc.list()
}),
) )
.get( .get(
"/skill", "/skill",
@@ -250,15 +242,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
}, },
}, },
}), }),
async (c) => { async (c) =>
const skills = await AppRuntime.runPromise( jsonRequest("InstanceRoutes.skill.list", c, function* () {
Effect.gen(function* () { const skill = yield* Skill.Service
const skill = yield* Skill.Service return yield* skill.all()
return yield* skill.all() }),
}),
)
return c.json(skills)
},
) )
.get( .get(
"/lsp", "/lsp",
@@ -277,10 +265,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
}, },
}, },
}), }),
async (c) => { async (c) =>
const items = await AppRuntime.runPromise(LSP.Service.use((lsp) => lsp.status())) jsonRequest("InstanceRoutes.lsp.status", c, function* () {
return c.json(items) const lsp = yield* LSP.Service
}, return yield* lsp.status()
}),
) )
.get( .get(
"/formatter", "/formatter",
@@ -299,8 +288,10 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
}, },
}, },
}), }),
async (c) => { async (c) =>
return c.json(await AppRuntime.runPromise(Format.Service.use((svc) => svc.status()))) jsonRequest("InstanceRoutes.formatter.status", c, function* () {
}, const svc = yield* Format.Service
return yield* svc.status()
}),
) )
} }
@@ -2,12 +2,11 @@ import { Hono } from "hono"
import { describeRoute, validator, resolver } from "hono-openapi" import { describeRoute, validator, resolver } from "hono-openapi"
import z from "zod" import z from "zod"
import { MCP } from "@/mcp" import { MCP } from "@/mcp"
import { Config } from "@/config"
import { ConfigMCP } from "@/config/mcp" import { ConfigMCP } from "@/config/mcp"
import { AppRuntime } from "@/effect/app-runtime"
import { errors } from "../../error" import { errors } from "../../error"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { Effect } from "effect" import { Effect } from "effect"
import { jsonRequest, runRequest } from "./trace"
export const McpRoutes = lazy(() => export const McpRoutes = lazy(() =>
new Hono() new Hono()
@@ -28,9 +27,11 @@ export const McpRoutes = lazy(() =>
}, },
}, },
}), }),
async (c) => { async (c) =>
return c.json(await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.status()))) jsonRequest("McpRoutes.status", c, function* () {
}, const mcp = yield* MCP.Service
return yield* mcp.status()
}),
) )
.post( .post(
"/", "/",
@@ -57,11 +58,13 @@ export const McpRoutes = lazy(() =>
config: ConfigMCP.Info.zod, config: ConfigMCP.Info.zod,
}), }),
), ),
async (c) => { async (c) =>
const { name, config } = c.req.valid("json") jsonRequest("McpRoutes.add", c, function* () {
const result = await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.add(name, config))) const { name, config } = c.req.valid("json")
return c.json(result.status) const mcp = yield* MCP.Service
}, const result = yield* mcp.add(name, config)
return result.status
}),
) )
.post( .post(
"/:name/auth", "/:name/auth",
@@ -87,7 +90,9 @@ export const McpRoutes = lazy(() =>
}), }),
async (c) => { async (c) => {
const name = c.req.param("name") const name = c.req.param("name")
const result = await AppRuntime.runPromise( const result = await runRequest(
"McpRoutes.auth.start",
c,
Effect.gen(function* () { Effect.gen(function* () {
const mcp = yield* MCP.Service const mcp = yield* MCP.Service
const supports = yield* mcp.supportsOAuth(name) const supports = yield* mcp.supportsOAuth(name)
@@ -129,12 +134,13 @@ export const McpRoutes = lazy(() =>
code: z.string().describe("Authorization code from OAuth callback"), code: z.string().describe("Authorization code from OAuth callback"),
}), }),
), ),
async (c) => { async (c) =>
const name = c.req.param("name") jsonRequest("McpRoutes.auth.callback", c, function* () {
const { code } = c.req.valid("json") const name = c.req.param("name")
const status = await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.finishAuth(name, code))) const { code } = c.req.valid("json")
return c.json(status) const mcp = yield* MCP.Service
}, return yield* mcp.finishAuth(name, code)
}),
) )
.post( .post(
"/:name/auth/authenticate", "/:name/auth/authenticate",
@@ -156,7 +162,9 @@ export const McpRoutes = lazy(() =>
}), }),
async (c) => { async (c) => {
const name = c.req.param("name") const name = c.req.param("name")
const result = await AppRuntime.runPromise( const result = await runRequest(
"McpRoutes.auth.authenticate",
c,
Effect.gen(function* () { Effect.gen(function* () {
const mcp = yield* MCP.Service const mcp = yield* MCP.Service
const supports = yield* mcp.supportsOAuth(name) const supports = yield* mcp.supportsOAuth(name)
@@ -191,11 +199,13 @@ export const McpRoutes = lazy(() =>
...errors(404), ...errors(404),
}, },
}), }),
async (c) => { async (c) =>
const name = c.req.param("name") jsonRequest("McpRoutes.auth.remove", c, function* () {
await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.removeAuth(name))) const name = c.req.param("name")
return c.json({ success: true as const }) const mcp = yield* MCP.Service
}, yield* mcp.removeAuth(name)
return { success: true as const }
}),
) )
.post( .post(
"/:name/connect", "/:name/connect",
@@ -214,11 +224,13 @@ export const McpRoutes = lazy(() =>
}, },
}), }),
validator("param", z.object({ name: z.string() })), validator("param", z.object({ name: z.string() })),
async (c) => { async (c) =>
const { name } = c.req.valid("param") jsonRequest("McpRoutes.connect", c, function* () {
await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.connect(name))) const { name } = c.req.valid("param")
return c.json(true) const mcp = yield* MCP.Service
}, yield* mcp.connect(name)
return true
}),
) )
.post( .post(
"/:name/disconnect", "/:name/disconnect",
@@ -237,10 +249,12 @@ export const McpRoutes = lazy(() =>
}, },
}), }),
validator("param", z.object({ name: z.string() })), validator("param", z.object({ name: z.string() })),
async (c) => { async (c) =>
const { name } = c.req.valid("param") jsonRequest("McpRoutes.disconnect", c, function* () {
await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.disconnect(name))) const { name } = c.req.valid("param")
return c.json(true) const mcp = yield* MCP.Service
}, yield* mcp.disconnect(name)
return true
}),
), ),
) )
@@ -1,11 +1,11 @@
import { Hono } from "hono" import { Hono } from "hono"
import { describeRoute, validator, resolver } from "hono-openapi" import { describeRoute, validator, resolver } from "hono-openapi"
import z from "zod" import z from "zod"
import { AppRuntime } from "@/effect/app-runtime"
import { Permission } from "@/permission" import { Permission } from "@/permission"
import { PermissionID } from "@/permission/schema" import { PermissionID } from "@/permission/schema"
import { errors } from "../../error" import { errors } from "../../error"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { jsonRequest } from "./trace"
export const PermissionRoutes = lazy(() => export const PermissionRoutes = lazy(() =>
new Hono() new Hono()
@@ -34,20 +34,18 @@ export const PermissionRoutes = lazy(() =>
}), }),
), ),
validator("json", z.object({ reply: Permission.Reply.zod, message: z.string().optional() })), validator("json", z.object({ reply: Permission.Reply.zod, message: z.string().optional() })),
async (c) => { async (c) =>
const params = c.req.valid("param") jsonRequest("PermissionRoutes.reply", c, function* () {
const json = c.req.valid("json") const params = c.req.valid("param")
await AppRuntime.runPromise( const json = c.req.valid("json")
Permission.Service.use((svc) => const svc = yield* Permission.Service
svc.reply({ yield* svc.reply({
requestID: params.requestID, requestID: params.requestID,
reply: json.reply, reply: json.reply,
message: json.message, message: json.message,
}), })
), return true
) }),
return c.json(true)
},
) )
.get( .get(
"/", "/",
@@ -66,9 +64,10 @@ export const PermissionRoutes = lazy(() =>
}, },
}, },
}), }),
async (c) => { async (c) =>
const permissions = await AppRuntime.runPromise(Permission.Service.use((svc) => svc.list())) jsonRequest("PermissionRoutes.list", c, function* () {
return c.json(permissions) const svc = yield* Permission.Service
}, return yield* svc.list()
}),
), ),
) )
@@ -9,6 +9,7 @@ import { errors } from "../../error"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { InstanceBootstrap } from "@/project/bootstrap" import { InstanceBootstrap } from "@/project/bootstrap"
import { AppRuntime } from "@/effect/app-runtime" import { AppRuntime } from "@/effect/app-runtime"
import { jsonRequest, runRequest } from "./trace"
export const ProjectRoutes = lazy(() => export const ProjectRoutes = lazy(() =>
new Hono() new Hono()
@@ -75,7 +76,9 @@ export const ProjectRoutes = lazy(() =>
async (c) => { async (c) => {
const dir = Instance.directory const dir = Instance.directory
const prev = Instance.project const prev = Instance.project
const next = await AppRuntime.runPromise( const next = await runRequest(
"ProjectRoutes.initGit",
c,
Project.Service.use((svc) => svc.initGit({ directory: dir, project: prev })), Project.Service.use((svc) => svc.initGit({ directory: dir, project: prev })),
) )
if (next.id === prev.id && next.vcs === prev.vcs && next.worktree === prev.worktree) return c.json(next) if (next.id === prev.id && next.vcs === prev.vcs && next.worktree === prev.worktree) return c.json(next)
@@ -108,11 +111,12 @@ export const ProjectRoutes = lazy(() =>
}), }),
validator("param", z.object({ projectID: ProjectID.zod })), validator("param", z.object({ projectID: ProjectID.zod })),
validator("json", Project.UpdateInput.omit({ projectID: true })), validator("json", Project.UpdateInput.omit({ projectID: true })),
async (c) => { async (c) =>
const projectID = c.req.valid("param").projectID jsonRequest("ProjectRoutes.update", c, function* () {
const body = c.req.valid("json") const projectID = c.req.valid("param").projectID
const project = await AppRuntime.runPromise(Project.Service.use((svc) => svc.update({ ...body, projectID }))) const body = c.req.valid("json")
return c.json(project) const svc = yield* Project.Service
}, return yield* svc.update({ ...body, projectID })
}),
), ),
) )
@@ -6,11 +6,11 @@ import { Provider } from "@/provider"
import { ModelsDev } from "@/provider" import { ModelsDev } from "@/provider"
import { ProviderAuth } from "@/provider" import { ProviderAuth } from "@/provider"
import { ProviderID } from "@/provider/schema" import { ProviderID } from "@/provider/schema"
import { AppRuntime } from "@/effect/app-runtime"
import { mapValues } from "remeda" import { mapValues } from "remeda"
import { errors } from "../../error" import { errors } from "../../error"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { Effect } from "effect" import { Effect } from "effect"
import { jsonRequest } from "./trace"
export const ProviderRoutes = lazy(() => export const ProviderRoutes = lazy(() =>
new Hono() new Hono()
@@ -31,39 +31,31 @@ export const ProviderRoutes = lazy(() =>
}, },
}, },
}), }),
async (c) => { async (c) =>
const result = await AppRuntime.runPromise( jsonRequest("ProviderRoutes.list", c, function* () {
Effect.gen(function* () { const svc = yield* Provider.Service
const svc = yield* Provider.Service const cfg = yield* Config.Service
const cfg = yield* Config.Service const config = yield* cfg.get()
const config = yield* cfg.get() const all = yield* Effect.promise(() => ModelsDev.get())
const all = yield* Effect.promise(() => ModelsDev.get()) const disabled = new Set(config.disabled_providers ?? [])
const disabled = new Set(config.disabled_providers ?? []) const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined const filtered: Record<string, (typeof all)[string]> = {}
const filtered: Record<string, (typeof all)[string]> = {} for (const [key, value] of Object.entries(all)) {
for (const [key, value] of Object.entries(all)) { if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) { filtered[key] = value
filtered[key] = value
}
} }
const connected = yield* svc.list() }
const providers = Object.assign( const connected = yield* svc.list()
mapValues(filtered, (x) => Provider.fromModelsDevProvider(x)), const providers = Object.assign(
connected, mapValues(filtered, (x) => Provider.fromModelsDevProvider(x)),
) connected,
return { )
all: Object.values(providers), return {
default: Provider.defaultModelIDs(providers), all: Object.values(providers),
connected: Object.keys(connected), default: Provider.defaultModelIDs(providers),
} connected: Object.keys(connected),
}), }
) }),
return c.json({
all: result.all,
default: result.default,
connected: result.connected,
})
},
) )
.get( .get(
"/auth", "/auth",
@@ -82,9 +74,11 @@ export const ProviderRoutes = lazy(() =>
}, },
}, },
}), }),
async (c) => { async (c) =>
return c.json(await AppRuntime.runPromise(ProviderAuth.Service.use((svc) => svc.methods()))) jsonRequest("ProviderRoutes.auth", c, function* () {
}, const svc = yield* ProviderAuth.Service
return yield* svc.methods()
}),
) )
.post( .post(
"/:providerID/oauth/authorize", "/:providerID/oauth/authorize",
@@ -111,20 +105,17 @@ export const ProviderRoutes = lazy(() =>
}), }),
), ),
validator("json", ProviderAuth.AuthorizeInput.zod), validator("json", ProviderAuth.AuthorizeInput.zod),
async (c) => { async (c) =>
const providerID = c.req.valid("param").providerID jsonRequest("ProviderRoutes.oauth.authorize", c, function* () {
const { method, inputs } = c.req.valid("json") const providerID = c.req.valid("param").providerID
const result = await AppRuntime.runPromise( const { method, inputs } = c.req.valid("json")
ProviderAuth.Service.use((svc) => const svc = yield* ProviderAuth.Service
svc.authorize({ return yield* svc.authorize({
providerID, providerID,
method, method,
inputs, inputs,
}), })
), }),
)
return c.json(result)
},
) )
.post( .post(
"/:providerID/oauth/callback", "/:providerID/oauth/callback",
@@ -151,19 +142,17 @@ export const ProviderRoutes = lazy(() =>
}), }),
), ),
validator("json", ProviderAuth.CallbackInput.zod), validator("json", ProviderAuth.CallbackInput.zod),
async (c) => { async (c) =>
const providerID = c.req.valid("param").providerID jsonRequest("ProviderRoutes.oauth.callback", c, function* () {
const { method, code } = c.req.valid("json") const providerID = c.req.valid("param").providerID
await AppRuntime.runPromise( const { method, code } = c.req.valid("json")
ProviderAuth.Service.use((svc) => const svc = yield* ProviderAuth.Service
svc.callback({ yield* svc.callback({
providerID, providerID,
method, method,
code, code,
}), })
), return true
) }),
return c.json(true)
},
), ),
) )
@@ -8,6 +8,7 @@ import { Pty } from "@/pty"
import { PtyID } from "@/pty/schema" import { PtyID } from "@/pty/schema"
import { NotFoundError } from "@/storage" import { NotFoundError } from "@/storage"
import { errors } from "../../error" import { errors } from "../../error"
import { jsonRequest, runRequest } from "./trace"
export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
return new Hono() return new Hono()
@@ -28,16 +29,11 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
}, },
}, },
}), }),
async (c) => { async (c) =>
return c.json( jsonRequest("PtyRoutes.list", c, function* () {
await AppRuntime.runPromise( const pty = yield* Pty.Service
Effect.gen(function* () { return yield* pty.list()
const pty = yield* Pty.Service }),
return yield* pty.list()
}),
),
)
},
) )
.post( .post(
"/", "/",
@@ -58,15 +54,11 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
}, },
}), }),
validator("json", Pty.CreateInput), validator("json", Pty.CreateInput),
async (c) => { async (c) =>
const info = await AppRuntime.runPromise( jsonRequest("PtyRoutes.create", c, function* () {
Effect.gen(function* () { const pty = yield* Pty.Service
const pty = yield* Pty.Service return yield* pty.create(c.req.valid("json"))
return yield* pty.create(c.req.valid("json")) }),
}),
)
return c.json(info)
},
) )
.get( .get(
"/:ptyID", "/:ptyID",
@@ -88,7 +80,9 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
}), }),
validator("param", z.object({ ptyID: PtyID.zod })), validator("param", z.object({ ptyID: PtyID.zod })),
async (c) => { async (c) => {
const info = await AppRuntime.runPromise( const info = await runRequest(
"PtyRoutes.get",
c,
Effect.gen(function* () { Effect.gen(function* () {
const pty = yield* Pty.Service const pty = yield* Pty.Service
return yield* pty.get(c.req.valid("param").ptyID) return yield* pty.get(c.req.valid("param").ptyID)
@@ -120,15 +114,11 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
}), }),
validator("param", z.object({ ptyID: PtyID.zod })), validator("param", z.object({ ptyID: PtyID.zod })),
validator("json", Pty.UpdateInput), validator("json", Pty.UpdateInput),
async (c) => { async (c) =>
const info = await AppRuntime.runPromise( jsonRequest("PtyRoutes.update", c, function* () {
Effect.gen(function* () { const pty = yield* Pty.Service
const pty = yield* Pty.Service return yield* pty.update(c.req.valid("param").ptyID, c.req.valid("json"))
return yield* pty.update(c.req.valid("param").ptyID, c.req.valid("json")) }),
}),
)
return c.json(info)
},
) )
.delete( .delete(
"/:ptyID", "/:ptyID",
@@ -149,15 +139,12 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
}, },
}), }),
validator("param", z.object({ ptyID: PtyID.zod })), validator("param", z.object({ ptyID: PtyID.zod })),
async (c) => { async (c) =>
await AppRuntime.runPromise( jsonRequest("PtyRoutes.remove", c, function* () {
Effect.gen(function* () { const pty = yield* Pty.Service
const pty = yield* Pty.Service yield* pty.remove(c.req.valid("param").ptyID)
yield* pty.remove(c.req.valid("param").ptyID) return true
}), }),
)
return c.json(true)
},
) )
.get( .get(
"/:ptyID/connect", "/:ptyID/connect",
@@ -194,7 +181,9 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
})() })()
let handler: Handler | undefined let handler: Handler | undefined
if ( if (
!(await AppRuntime.runPromise( !(await runRequest(
"PtyRoutes.connect",
c,
Effect.gen(function* () { Effect.gen(function* () {
const pty = yield* Pty.Service const pty = yield* Pty.Service
return yield* pty.get(id) return yield* pty.get(id)
@@ -232,7 +221,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
Effect.gen(function* () { Effect.gen(function* () {
const pty = yield* Pty.Service const pty = yield* Pty.Service
return yield* pty.connect(id, socket, cursor) return yield* pty.connect(id, socket, cursor)
}), }).pipe(Effect.withSpan("PtyRoutes.connect.open")),
) )
ready = true ready = true
for (const msg of pending) handler?.onMessage(msg) for (const msg of pending) handler?.onMessage(msg)
@@ -3,10 +3,10 @@ import { describeRoute, validator } from "hono-openapi"
import { resolver } from "hono-openapi" import { resolver } from "hono-openapi"
import { QuestionID } from "@/question/schema" import { QuestionID } from "@/question/schema"
import { Question } from "@/question" import { Question } from "@/question"
import { AppRuntime } from "@/effect/app-runtime"
import z from "zod" import z from "zod"
import { errors } from "../../error" import { errors } from "../../error"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { jsonRequest } from "./trace"
const Reply = z.object({ const Reply = z.object({
answers: Question.Answer.zod answers: Question.Answer.zod
@@ -33,10 +33,11 @@ export const QuestionRoutes = lazy(() =>
}, },
}, },
}), }),
async (c) => { async (c) =>
const questions = await AppRuntime.runPromise(Question.Service.use((svc) => svc.list())) jsonRequest("QuestionRoutes.list", c, function* () {
return c.json(questions) const svc = yield* Question.Service
}, return yield* svc.list()
}),
) )
.post( .post(
"/:requestID/reply", "/:requestID/reply",
@@ -63,19 +64,17 @@ export const QuestionRoutes = lazy(() =>
}), }),
), ),
validator("json", Reply), validator("json", Reply),
async (c) => { async (c) =>
const params = c.req.valid("param") jsonRequest("QuestionRoutes.reply", c, function* () {
const json = c.req.valid("json") const params = c.req.valid("param")
await AppRuntime.runPromise( const json = c.req.valid("json")
Question.Service.use((svc) => const svc = yield* Question.Service
svc.reply({ yield* svc.reply({
requestID: params.requestID, requestID: params.requestID,
answers: json.answers, answers: json.answers,
}), })
), return true
) }),
return c.json(true)
},
) )
.post( .post(
"/:requestID/reject", "/:requestID/reject",
@@ -101,10 +100,12 @@ export const QuestionRoutes = lazy(() =>
requestID: QuestionID.zod, requestID: QuestionID.zod,
}), }),
), ),
async (c) => { async (c) =>
const params = c.req.valid("param") jsonRequest("QuestionRoutes.reject", c, function* () {
await AppRuntime.runPromise(Question.Service.use((svc) => svc.reject(params.requestID))) const params = c.req.valid("param")
return c.json(true) const svc = yield* Question.Service
}, yield* svc.reject(params.requestID)
return true
}),
), ),
) )
@@ -14,7 +14,6 @@ import { SessionStatus } from "@/session/status"
import { SessionSummary } from "@/session/summary" import { SessionSummary } from "@/session/summary"
import { Todo } from "@/session/todo" import { Todo } from "@/session/todo"
import { Effect } from "effect" import { Effect } from "effect"
import { AppRuntime } from "@/effect/app-runtime"
import { Agent } from "@/agent/agent" import { Agent } from "@/agent/agent"
import { Snapshot } from "@/snapshot" import { Snapshot } from "@/snapshot"
import { Command } from "@/command" import { Command } from "@/command"
@@ -26,7 +25,7 @@ import { errors } from "../../error"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { Bus } from "@/bus" import { Bus } from "@/bus"
import { NamedError } from "@opencode-ai/shared/util/error" import { NamedError } from "@opencode-ai/shared/util/error"
import { jsonRequest } from "./trace" import { jsonRequest, runRequest } from "./trace"
const log = Log.create({ service: "server" }) const log = Log.create({ service: "server" })
@@ -218,11 +217,12 @@ export const SessionRoutes = lazy(() =>
}, },
}), }),
validator("json", Session.CreateInput), validator("json", Session.CreateInput),
async (c) => { async (c) =>
const body = c.req.valid("json") ?? {} jsonRequest("SessionRoutes.create", c, function* () {
const session = await AppRuntime.runPromise(SessionShare.Service.use((svc) => svc.create(body))) const body = c.req.valid("json") ?? {}
return c.json(session) const svc = yield* SessionShare.Service
}, return yield* svc.create(body)
}),
) )
.delete( .delete(
"/:sessionID", "/:sessionID",
@@ -248,11 +248,13 @@ export const SessionRoutes = lazy(() =>
sessionID: Session.RemoveInput, sessionID: Session.RemoveInput,
}), }),
), ),
async (c) => { async (c) =>
const sessionID = c.req.valid("param").sessionID jsonRequest("SessionRoutes.delete", c, function* () {
await AppRuntime.runPromise(Session.Service.use((svc) => svc.remove(sessionID))) const sessionID = c.req.valid("param").sessionID
return c.json(true) const svc = yield* Session.Service
}, yield* svc.remove(sessionID)
return true
}),
) )
.patch( .patch(
"/:sessionID", "/:sessionID",
@@ -290,32 +292,28 @@ export const SessionRoutes = lazy(() =>
.optional(), .optional(),
}), }),
), ),
async (c) => { async (c) =>
const sessionID = c.req.valid("param").sessionID jsonRequest("SessionRoutes.update", c, function* () {
const updates = c.req.valid("json") const sessionID = c.req.valid("param").sessionID
const session = await AppRuntime.runPromise( const updates = c.req.valid("json")
Effect.gen(function* () { const session = yield* Session.Service
const session = yield* Session.Service const current = yield* session.get(sessionID)
const current = yield* session.get(sessionID)
if (updates.title !== undefined) { if (updates.title !== undefined) {
yield* session.setTitle({ sessionID, title: updates.title }) yield* session.setTitle({ sessionID, title: updates.title })
} }
if (updates.permission !== undefined) { if (updates.permission !== undefined) {
yield* session.setPermission({ yield* session.setPermission({
sessionID, sessionID,
permission: Permission.merge(current.permission ?? [], updates.permission), permission: Permission.merge(current.permission ?? [], updates.permission),
}) })
} }
if (updates.time?.archived !== undefined) { if (updates.time?.archived !== undefined) {
yield* session.setArchived({ sessionID, time: updates.time.archived }) yield* session.setArchived({ sessionID, time: updates.time.archived })
} }
return yield* session.get(sessionID) return yield* session.get(sessionID)
}), }),
)
return c.json(session)
},
) )
// TODO(v2): remove this dedicated route and rely on the normal `/init` command flow. // TODO(v2): remove this dedicated route and rely on the normal `/init` command flow.
.post( .post(
@@ -351,22 +349,20 @@ export const SessionRoutes = lazy(() =>
messageID: MessageID.zod, messageID: MessageID.zod,
}), }),
), ),
async (c) => { async (c) =>
const sessionID = c.req.valid("param").sessionID jsonRequest("SessionRoutes.init", c, function* () {
const body = c.req.valid("json") const sessionID = c.req.valid("param").sessionID
await AppRuntime.runPromise( const body = c.req.valid("json")
SessionPrompt.Service.use((svc) => const svc = yield* SessionPrompt.Service
svc.command({ yield* svc.command({
sessionID, sessionID,
messageID: body.messageID, messageID: body.messageID,
model: body.providerID + "/" + body.modelID, model: body.providerID + "/" + body.modelID,
command: Command.Default.INIT, command: Command.Default.INIT,
arguments: "", arguments: "",
}), })
), return true
) }),
return c.json(true)
},
) )
.post( .post(
"/:sessionID/fork", "/:sessionID/fork",
@@ -392,12 +388,13 @@ export const SessionRoutes = lazy(() =>
}), }),
), ),
validator("json", Session.ForkInput.omit({ sessionID: true })), validator("json", Session.ForkInput.omit({ sessionID: true })),
async (c) => { async (c) =>
const sessionID = c.req.valid("param").sessionID jsonRequest("SessionRoutes.fork", c, function* () {
const body = c.req.valid("json") const sessionID = c.req.valid("param").sessionID
const result = await AppRuntime.runPromise(Session.Service.use((svc) => svc.fork({ ...body, sessionID }))) const body = c.req.valid("json")
return c.json(result) const svc = yield* Session.Service
}, return yield* svc.fork({ ...body, sessionID })
}),
) )
.post( .post(
"/:sessionID/abort", "/:sessionID/abort",
@@ -423,10 +420,12 @@ export const SessionRoutes = lazy(() =>
sessionID: SessionID.zod, sessionID: SessionID.zod,
}), }),
), ),
async (c) => { async (c) =>
await AppRuntime.runPromise(SessionPrompt.Service.use((svc) => svc.cancel(c.req.valid("param").sessionID))) jsonRequest("SessionRoutes.abort", c, function* () {
return c.json(true) const svc = yield* SessionPrompt.Service
}, yield* svc.cancel(c.req.valid("param").sessionID)
return true
}),
) )
.post( .post(
"/:sessionID/share", "/:sessionID/share",
@@ -452,18 +451,14 @@ export const SessionRoutes = lazy(() =>
sessionID: SessionID.zod, sessionID: SessionID.zod,
}), }),
), ),
async (c) => { async (c) =>
const sessionID = c.req.valid("param").sessionID jsonRequest("SessionRoutes.share", c, function* () {
const session = await AppRuntime.runPromise( const sessionID = c.req.valid("param").sessionID
Effect.gen(function* () { const share = yield* SessionShare.Service
const share = yield* SessionShare.Service const session = yield* Session.Service
const session = yield* Session.Service yield* share.share(sessionID)
yield* share.share(sessionID) return yield* session.get(sessionID)
return yield* session.get(sessionID) }),
}),
)
return c.json(session)
},
) )
.get( .get(
"/:sessionID/diff", "/:sessionID/diff",
@@ -494,19 +489,16 @@ export const SessionRoutes = lazy(() =>
messageID: SessionSummary.DiffInput.shape.messageID, messageID: SessionSummary.DiffInput.shape.messageID,
}), }),
), ),
async (c) => { async (c) =>
const query = c.req.valid("query") jsonRequest("SessionRoutes.diff", c, function* () {
const params = c.req.valid("param") const query = c.req.valid("query")
const result = await AppRuntime.runPromise( const params = c.req.valid("param")
SessionSummary.Service.use((summary) => const summary = yield* SessionSummary.Service
summary.diff({ return yield* summary.diff({
sessionID: params.sessionID, sessionID: params.sessionID,
messageID: query.messageID, messageID: query.messageID,
}), })
), }),
)
return c.json(result)
},
) )
.delete( .delete(
"/:sessionID/share", "/:sessionID/share",
@@ -532,18 +524,14 @@ export const SessionRoutes = lazy(() =>
sessionID: SessionID.zod, sessionID: SessionID.zod,
}), }),
), ),
async (c) => { async (c) =>
const sessionID = c.req.valid("param").sessionID jsonRequest("SessionRoutes.unshare", c, function* () {
const session = await AppRuntime.runPromise( const sessionID = c.req.valid("param").sessionID
Effect.gen(function* () { const share = yield* SessionShare.Service
const share = yield* SessionShare.Service const session = yield* Session.Service
const session = yield* Session.Service yield* share.unshare(sessionID)
yield* share.unshare(sessionID) return yield* session.get(sessionID)
return yield* session.get(sessionID) }),
}),
)
return c.json(session)
},
) )
.post( .post(
"/:sessionID/summarize", "/:sessionID/summarize",
@@ -577,43 +565,40 @@ export const SessionRoutes = lazy(() =>
auto: z.boolean().optional().default(false), auto: z.boolean().optional().default(false),
}), }),
), ),
async (c) => { async (c) =>
const sessionID = c.req.valid("param").sessionID jsonRequest("SessionRoutes.summarize", c, function* () {
const body = c.req.valid("json") const sessionID = c.req.valid("param").sessionID
await AppRuntime.runPromise( const body = c.req.valid("json")
Effect.gen(function* () { const session = yield* Session.Service
const session = yield* Session.Service const revert = yield* SessionRevert.Service
const revert = yield* SessionRevert.Service const compact = yield* SessionCompaction.Service
const compact = yield* SessionCompaction.Service const prompt = yield* SessionPrompt.Service
const prompt = yield* SessionPrompt.Service const agent = yield* Agent.Service
const agent = yield* Agent.Service
yield* revert.cleanup(yield* session.get(sessionID)) yield* revert.cleanup(yield* session.get(sessionID))
const msgs = yield* session.messages({ sessionID }) const msgs = yield* session.messages({ sessionID })
const defaultAgent = yield* agent.defaultAgent() const defaultAgent = yield* agent.defaultAgent()
let currentAgent = defaultAgent let currentAgent = defaultAgent
for (let i = msgs.length - 1; i >= 0; i--) { for (let i = msgs.length - 1; i >= 0; i--) {
const info = msgs[i].info const info = msgs[i].info
if (info.role === "user") { if (info.role === "user") {
currentAgent = info.agent || defaultAgent currentAgent = info.agent || defaultAgent
break break
}
} }
}
yield* compact.create({ yield* compact.create({
sessionID, sessionID,
agent: currentAgent, agent: currentAgent,
model: { model: {
providerID: body.providerID, providerID: body.providerID,
modelID: body.modelID, modelID: body.modelID,
}, },
auto: body.auto, auto: body.auto,
}) })
yield* prompt.loop({ sessionID }) yield* prompt.loop({ sessionID })
}), return true
) }),
return c.json(true)
},
) )
.get( .get(
"/:sessionID/message", "/:sessionID/message",
@@ -675,7 +660,9 @@ export const SessionRoutes = lazy(() =>
const query = c.req.valid("query") const query = c.req.valid("query")
const sessionID = c.req.valid("param").sessionID const sessionID = c.req.valid("param").sessionID
if (query.limit === undefined || query.limit === 0) { if (query.limit === undefined || query.limit === 0) {
const messages = await AppRuntime.runPromise( const messages = await runRequest(
"SessionRoutes.messages",
c,
Effect.gen(function* () { Effect.gen(function* () {
const session = yield* Session.Service const session = yield* Session.Service
yield* session.get(sessionID) yield* session.get(sessionID)
@@ -766,21 +753,18 @@ export const SessionRoutes = lazy(() =>
messageID: MessageID.zod, messageID: MessageID.zod,
}), }),
), ),
async (c) => { async (c) =>
const params = c.req.valid("param") jsonRequest("SessionRoutes.deleteMessage", c, function* () {
await AppRuntime.runPromise( const params = c.req.valid("param")
Effect.gen(function* () { const state = yield* SessionRunState.Service
const state = yield* SessionRunState.Service const session = yield* Session.Service
const session = yield* Session.Service yield* state.assertNotBusy(params.sessionID)
yield* state.assertNotBusy(params.sessionID) yield* session.removeMessage({
yield* session.removeMessage({ sessionID: params.sessionID,
sessionID: params.sessionID, messageID: params.messageID,
messageID: params.messageID, })
}) return true
}), }),
)
return c.json(true)
},
) )
.delete( .delete(
"/:sessionID/message/:messageID/part/:partID", "/:sessionID/message/:messageID/part/:partID",
@@ -807,19 +791,17 @@ export const SessionRoutes = lazy(() =>
partID: PartID.zod, partID: PartID.zod,
}), }),
), ),
async (c) => { async (c) =>
const params = c.req.valid("param") jsonRequest("SessionRoutes.deletePart", c, function* () {
await AppRuntime.runPromise( const params = c.req.valid("param")
Session.Service.use((svc) => const svc = yield* Session.Service
svc.removePart({ yield* svc.removePart({
sessionID: params.sessionID, sessionID: params.sessionID,
messageID: params.messageID, messageID: params.messageID,
partID: params.partID, partID: params.partID,
}), })
), return true
) }),
return c.json(true)
},
) )
.patch( .patch(
"/:sessionID/message/:messageID/part/:partID", "/:sessionID/message/:messageID/part/:partID",
@@ -855,8 +837,10 @@ export const SessionRoutes = lazy(() =>
`Part mismatch: body.id='${body.id}' vs partID='${params.partID}', body.messageID='${body.messageID}' vs messageID='${params.messageID}', body.sessionID='${body.sessionID}' vs sessionID='${params.sessionID}'`, `Part mismatch: body.id='${body.id}' vs partID='${params.partID}', body.messageID='${body.messageID}' vs messageID='${params.messageID}', body.sessionID='${body.sessionID}' vs sessionID='${params.sessionID}'`,
) )
} }
const part = await AppRuntime.runPromise(Session.Service.use((svc) => svc.updatePart(body))) return jsonRequest("SessionRoutes.updatePart", c, function* () {
return c.json(part) const svc = yield* Session.Service
return yield* svc.updatePart(body)
})
}, },
) )
.post( .post(
@@ -895,7 +879,9 @@ export const SessionRoutes = lazy(() =>
return stream(c, async (stream) => { return stream(c, async (stream) => {
const sessionID = c.req.valid("param").sessionID const sessionID = c.req.valid("param").sessionID
const body = c.req.valid("json") const body = c.req.valid("json")
const msg = await AppRuntime.runPromise( const msg = await runRequest(
"SessionRoutes.prompt",
c,
SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID })), SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID })),
) )
void stream.write(JSON.stringify(msg)) void stream.write(JSON.stringify(msg))
@@ -926,15 +912,17 @@ export const SessionRoutes = lazy(() =>
async (c) => { async (c) => {
const sessionID = c.req.valid("param").sessionID const sessionID = c.req.valid("param").sessionID
const body = c.req.valid("json") const body = c.req.valid("json")
void AppRuntime.runPromise(SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID }))).catch( void runRequest(
(err) => { "SessionRoutes.prompt_async",
log.error("prompt_async failed", { sessionID, error: err }) c,
void Bus.publish(Session.Event.Error, { SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID })),
sessionID, ).catch((err) => {
error: new NamedError.Unknown({ message: err instanceof Error ? err.message : String(err) }).toObject(), log.error("prompt_async failed", { sessionID, error: err })
}) void Bus.publish(Session.Event.Error, {
}, sessionID,
) error: new NamedError.Unknown({ message: err instanceof Error ? err.message : String(err) }).toObject(),
})
})
return c.body(null, 204) return c.body(null, 204)
}, },
@@ -969,12 +957,13 @@ export const SessionRoutes = lazy(() =>
}), }),
), ),
validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })), validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })),
async (c) => { async (c) =>
const sessionID = c.req.valid("param").sessionID jsonRequest("SessionRoutes.command", c, function* () {
const body = c.req.valid("json") const sessionID = c.req.valid("param").sessionID
const msg = await AppRuntime.runPromise(SessionPrompt.Service.use((svc) => svc.command({ ...body, sessionID }))) const body = c.req.valid("json")
return c.json(msg) const svc = yield* SessionPrompt.Service
}, return yield* svc.command({ ...body, sessionID })
}),
) )
.post( .post(
"/:sessionID/shell", "/:sessionID/shell",
@@ -1001,12 +990,13 @@ export const SessionRoutes = lazy(() =>
}), }),
), ),
validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })), validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })),
async (c) => { async (c) =>
const sessionID = c.req.valid("param").sessionID jsonRequest("SessionRoutes.shell", c, function* () {
const body = c.req.valid("json") const sessionID = c.req.valid("param").sessionID
const msg = await AppRuntime.runPromise(SessionPrompt.Service.use((svc) => svc.shell({ ...body, sessionID }))) const body = c.req.valid("json")
return c.json(msg) const svc = yield* SessionPrompt.Service
}, return yield* svc.shell({ ...body, sessionID })
}),
) )
.post( .post(
"/:sessionID/revert", "/:sessionID/revert",
@@ -1036,15 +1026,13 @@ export const SessionRoutes = lazy(() =>
async (c) => { async (c) => {
const sessionID = c.req.valid("param").sessionID const sessionID = c.req.valid("param").sessionID
log.info("revert", c.req.valid("json")) log.info("revert", c.req.valid("json"))
const session = await AppRuntime.runPromise( return jsonRequest("SessionRoutes.revert", c, function* () {
SessionRevert.Service.use((svc) => const svc = yield* SessionRevert.Service
svc.revert({ return yield* svc.revert({
sessionID, sessionID,
...c.req.valid("json"), ...c.req.valid("json"),
}), })
), })
)
return c.json(session)
}, },
) )
.post( .post(
@@ -1071,11 +1059,12 @@ export const SessionRoutes = lazy(() =>
sessionID: SessionID.zod, sessionID: SessionID.zod,
}), }),
), ),
async (c) => { async (c) =>
const sessionID = c.req.valid("param").sessionID jsonRequest("SessionRoutes.unrevert", c, function* () {
const session = await AppRuntime.runPromise(SessionRevert.Service.use((svc) => svc.unrevert({ sessionID }))) const sessionID = c.req.valid("param").sessionID
return c.json(session) const svc = yield* SessionRevert.Service
}, return yield* svc.unrevert({ sessionID })
}),
) )
.post( .post(
"/:sessionID/permissions/:permissionID", "/:sessionID/permissions/:permissionID",
@@ -1104,17 +1093,15 @@ export const SessionRoutes = lazy(() =>
}), }),
), ),
validator("json", z.object({ response: Permission.Reply.zod })), validator("json", z.object({ response: Permission.Reply.zod })),
async (c) => { async (c) =>
const params = c.req.valid("param") jsonRequest("SessionRoutes.permissionRespond", c, function* () {
await AppRuntime.runPromise( const params = c.req.valid("param")
Permission.Service.use((svc) => const svc = yield* Permission.Service
svc.reply({ yield* svc.reply({
requestID: params.permissionID, requestID: params.permissionID,
reply: c.req.valid("json").response, reply: c.req.valid("json").response,
}), })
), return true
) }),
return c.json(true)
},
), ),
) )
@@ -4,10 +4,10 @@ import z from "zod"
import { Bus } from "@/bus" import { Bus } from "@/bus"
import { Session } from "@/session" import { Session } from "@/session"
import { TuiEvent } from "@/cli/cmd/tui/event" import { TuiEvent } from "@/cli/cmd/tui/event"
import { AppRuntime } from "@/effect/app-runtime"
import { AsyncQueue } from "@/util/queue" import { AsyncQueue } from "@/util/queue"
import { errors } from "../../error" import { errors } from "../../error"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { runRequest } from "./trace"
const TuiRequest = z.object({ const TuiRequest = z.object({
path: z.string(), path: z.string(),
@@ -371,7 +371,11 @@ export const TuiRoutes = lazy(() =>
validator("json", TuiEvent.SessionSelect.properties), validator("json", TuiEvent.SessionSelect.properties),
async (c) => { async (c) => {
const { sessionID } = c.req.valid("json") const { sessionID } = c.req.valid("json")
await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(sessionID))) await runRequest(
"TuiRoutes.sessionSelect",
c,
Session.Service.use((svc) => svc.get(sessionID)),
)
await Bus.publish(TuiEvent.SessionSelect, { sessionID }) await Bus.publish(TuiEvent.SessionSelect, { sessionID })
return c.json(true) return c.json(true)
}, },
+4 -1
View File
@@ -10,6 +10,7 @@ import { Instance } from "@/project/instance"
import { Session } from "@/session" import { Session } from "@/session"
import { SessionID } from "@/session/schema" import { SessionID } from "@/session/schema"
import { AppRuntime } from "@/effect/app-runtime" import { AppRuntime } from "@/effect/app-runtime"
import { Effect } from "effect"
import { Log } from "@/util" import { Log } from "@/util"
import { ServerProxy } from "./proxy" import { ServerProxy } from "./proxy"
@@ -42,7 +43,9 @@ async function getSessionWorkspace(url: URL) {
const id = getSessionID(url) const id = getSessionID(url)
if (!id) return null if (!id) return null
const session = await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(id))).catch(() => undefined) const session = await AppRuntime.runPromise(
Session.Service.use((svc) => svc.get(id)).pipe(Effect.withSpan("WorkspaceRouter.lookup")),
).catch(() => undefined)
return session?.workspaceID return session?.workspaceID
} }