Skip to content

Commit 451650b

Browse files
authored
refactor(httpapi): preserve typed errors in session prompt handlers (#25181)
1 parent 1b76bec commit 451650b

2 files changed

Lines changed: 22 additions & 17 deletions

File tree

packages/opencode/src/effect/bridge.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Effect, Fiber } from "effect"
1+
import { Effect, Exit, Fiber } from "effect"
22
import { WorkspaceContext } from "@/control-plane/workspace-context"
33
import { Instance, type InstanceContext } from "@/project/instance"
44
import type { WorkspaceID } from "@/control-plane/schema"
@@ -9,6 +9,7 @@ import { attachWith } from "./run-service"
99
export interface Shape {
1010
readonly promise: <A, E, R>(effect: Effect.Effect<A, E, R>) => Promise<A>
1111
readonly fork: <A, E, R>(effect: Effect.Effect<A, E, R>) => Fiber.Fiber<A, E>
12+
readonly run: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E>
1213
}
1314

1415
function restore<R>(instance: InstanceContext | undefined, workspace: WorkspaceID | undefined, fn: () => R): R {
@@ -43,6 +44,14 @@ export function make(): Effect.Effect<Shape> {
4344
restore(instance, workspace, () => Effect.runPromise(wrap(effect))),
4445
fork: <A, E, R>(effect: Effect.Effect<A, E, R>) =>
4546
restore(instance, workspace, () => Effect.runFork(wrap(effect))),
47+
run: <A, E, R>(effect: Effect.Effect<A, E, R>) =>
48+
Effect.callback<A, E>((resume) => {
49+
restore(instance, workspace, () =>
50+
Effect.runPromiseExit(wrap(effect)).then((exit) =>
51+
resume(Exit.isSuccess(exit) ? Effect.succeed(exit.value) : Effect.failCause(exit.cause)),
52+
),
53+
)
54+
}),
4655
} satisfies Shape
4756
})
4857
}

packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ import { SessionSummary } from "@/session/summary"
1818
import { Todo } from "@/session/todo"
1919
import { MessageID, PartID, SessionID } from "@/session/schema"
2020
import { NotFoundError } from "@/storage/storage"
21-
import * as Log from "@opencode-ai/core/util/log"
2221
import { NamedError } from "@opencode-ai/core/util/error"
23-
import { Effect, Schema } from "effect"
22+
import { Cause, Effect, Schema } from "effect"
2423
import * as Stream from "effect/Stream"
2524
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
2625
import { HttpApiBuilder, HttpApiError, HttpApiSchema } from "effect/unstable/httpapi"
@@ -40,8 +39,6 @@ import {
4039
UpdatePayload,
4140
} from "../groups/session"
4241

43-
const log = Log.create({ service: "server" })
44-
4542
const mapNotFound = <A, E, R>(self: Effect.Effect<A, E, R>) =>
4643
self.pipe(
4744
Effect.catchIf(NotFoundError.isInstance, () => Effect.fail(new HttpApiError.NotFound({}))),
@@ -63,6 +60,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
6360
const statusSvc = yield* SessionStatus.Service
6461
const todoSvc = yield* Todo.Service
6562
const summary = yield* SessionSummary.Service
63+
const bus = yield* Bus.Service
6664

6765
const list = Effect.fn("SessionHttpApi.list")(function* (ctx: { query: typeof ListQuery.Type }) {
6866
const instance = yield* InstanceState.context
@@ -264,13 +262,11 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
264262
const bridge = yield* EffectBridge.make()
265263
return HttpServerResponse.stream(
266264
Stream.fromEffect(
267-
Effect.promise(() =>
268-
bridge.promise(
269-
promptSvc.prompt({
270-
...ctx.payload,
271-
sessionID: ctx.params.sessionID,
272-
}),
273-
),
265+
bridge.run(
266+
promptSvc.prompt({
267+
...ctx.payload,
268+
sessionID: ctx.params.sessionID,
269+
}),
274270
),
275271
).pipe(
276272
Stream.map((message) => JSON.stringify(message)),
@@ -288,12 +284,12 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
288284
yield* Effect.sync(() => {
289285
bridge.fork(
290286
promptSvc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID }).pipe(
291-
Effect.catchCause((error) =>
292-
Effect.sync(() => {
293-
log.error("prompt_async failed", { sessionID: ctx.params.sessionID, error })
294-
void Bus.publish(Session.Event.Error, {
287+
Effect.catchCause((cause) =>
288+
Effect.gen(function* () {
289+
yield* Effect.logError("prompt_async failed", { sessionID: ctx.params.sessionID, cause })
290+
yield* bus.publish(Session.Event.Error, {
295291
sessionID: ctx.params.sessionID,
296-
error: new NamedError.Unknown({ message: String(error) }).toObject(),
292+
error: new NamedError.Unknown({ message: Cause.pretty(cause) }).toObject(),
297293
})
298294
}),
299295
),

0 commit comments

Comments
 (0)