feat(session): expose transient generation API (#37433)

This commit is contained in:
Kit Langton
2026-07-17 09:25:50 -04:00
committed by GitHub
parent 42e2dde739
commit 3db4458e6c
15 changed files with 157 additions and 39 deletions
+7
View File
@@ -0,0 +1,7 @@
---
"@opencode-ai/client": patch
"@opencode-ai/plugin": patch
"@opencode-ai/protocol": patch
---
Expose transient, read-only session generation through the HTTP API, generated clients, and V2 plugin session context.
+27 -18
View File
@@ -269,32 +269,40 @@ export type SessionInstructionsEntryRemoveOperation<E = never> = (
input: Endpoint5_24Input, input: Endpoint5_24Input,
) => Effect.Effect<Endpoint5_24Output, E> ) => Effect.Effect<Endpoint5_24Output, E>
type Endpoint5_25Request = Parameters<RawClient["server.session"]["session.log"]>[0] type Endpoint5_25Request = Parameters<RawClient["server.session"]["session.generate"]>[0]
export type Endpoint5_25Input = { export type Endpoint5_25Input = {
readonly sessionID: Endpoint5_25Request["params"]["sessionID"] readonly sessionID: Endpoint5_25Request["params"]["sessionID"]
readonly after?: Endpoint5_25Request["query"]["after"] readonly prompt: Endpoint5_25Request["payload"]["prompt"]
readonly follow?: Endpoint5_25Request["query"]["follow"]
} }
export type Endpoint5_25Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>> export type Endpoint5_25Output = EffectValue<ReturnType<RawClient["server.session"]["session.generate"]>>["data"]
export type SessionLogOperation<E = never> = (input: Endpoint5_25Input) => Stream.Stream<Endpoint5_25Output, E> export type SessionGenerateOperation<E = never> = (input: Endpoint5_25Input) => Effect.Effect<Endpoint5_25Output, E>
type Endpoint5_26Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0] type Endpoint5_26Request = Parameters<RawClient["server.session"]["session.log"]>[0]
export type Endpoint5_26Input = { readonly sessionID: Endpoint5_26Request["params"]["sessionID"] } export type Endpoint5_26Input = {
export type Endpoint5_26Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>> readonly sessionID: Endpoint5_26Request["params"]["sessionID"]
export type SessionInterruptOperation<E = never> = (input: Endpoint5_26Input) => Effect.Effect<Endpoint5_26Output, E> readonly after?: Endpoint5_26Request["query"]["after"]
readonly follow?: Endpoint5_26Request["query"]["follow"]
}
export type Endpoint5_26Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>>
export type SessionLogOperation<E = never> = (input: Endpoint5_26Input) => Stream.Stream<Endpoint5_26Output, E>
type Endpoint5_27Request = Parameters<RawClient["server.session"]["session.background"]>[0] type Endpoint5_27Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
export type Endpoint5_27Input = { readonly sessionID: Endpoint5_27Request["params"]["sessionID"] } export type Endpoint5_27Input = { readonly sessionID: Endpoint5_27Request["params"]["sessionID"] }
export type Endpoint5_27Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>> export type Endpoint5_27Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>>
export type SessionBackgroundOperation<E = never> = (input: Endpoint5_27Input) => Effect.Effect<Endpoint5_27Output, E> export type SessionInterruptOperation<E = never> = (input: Endpoint5_27Input) => Effect.Effect<Endpoint5_27Output, E>
type Endpoint5_28Request = Parameters<RawClient["server.session"]["session.message"]>[0] type Endpoint5_28Request = Parameters<RawClient["server.session"]["session.background"]>[0]
export type Endpoint5_28Input = { export type Endpoint5_28Input = { readonly sessionID: Endpoint5_28Request["params"]["sessionID"] }
readonly sessionID: Endpoint5_28Request["params"]["sessionID"] export type Endpoint5_28Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>>
readonly messageID: Endpoint5_28Request["params"]["messageID"] export type SessionBackgroundOperation<E = never> = (input: Endpoint5_28Input) => Effect.Effect<Endpoint5_28Output, E>
type Endpoint5_29Request = Parameters<RawClient["server.session"]["session.message"]>[0]
export type Endpoint5_29Input = {
readonly sessionID: Endpoint5_29Request["params"]["sessionID"]
readonly messageID: Endpoint5_29Request["params"]["messageID"]
} }
export type Endpoint5_28Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"] export type Endpoint5_29Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"]
export type SessionMessageOperation<E = never> = (input: Endpoint5_28Input) => Effect.Effect<Endpoint5_28Output, E> export type SessionMessageOperation<E = never> = (input: Endpoint5_29Input) => Effect.Effect<Endpoint5_29Output, E>
export interface SessionApi<E = never> { export interface SessionApi<E = never> {
readonly list: SessionListOperation<E> readonly list: SessionListOperation<E>
@@ -328,6 +336,7 @@ export interface SessionApi<E = never> {
readonly remove: SessionInstructionsEntryRemoveOperation<E> readonly remove: SessionInstructionsEntryRemoveOperation<E>
} }
} }
readonly generate: SessionGenerateOperation<E>
readonly log: SessionLogOperation<E> readonly log: SessionLogOperation<E>
readonly interrupt: SessionInterruptOperation<E> readonly interrupt: SessionInterruptOperation<E>
readonly background: SessionBackgroundOperation<E> readonly background: SessionBackgroundOperation<E>
+30 -18
View File
@@ -361,13 +361,24 @@ const Endpoint5_24 = (raw: RawClient["server.session"]) => (input: Endpoint5_24I
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
) )
type Endpoint5_25Request = Parameters<RawClient["server.session"]["session.log"]>[0] type Endpoint5_25Request = Parameters<RawClient["server.session"]["session.generate"]>[0]
type Endpoint5_25Input = { type Endpoint5_25Input = {
readonly sessionID: Endpoint5_25Request["params"]["sessionID"] readonly sessionID: Endpoint5_25Request["params"]["sessionID"]
readonly after?: Endpoint5_25Request["query"]["after"] readonly prompt: Endpoint5_25Request["payload"]["prompt"]
readonly follow?: Endpoint5_25Request["query"]["follow"]
} }
const Endpoint5_25 = (raw: RawClient["server.session"]) => (input: Endpoint5_25Input) => const Endpoint5_25 = (raw: RawClient["server.session"]) => (input: Endpoint5_25Input) =>
raw["session.generate"]({ params: { sessionID: input["sessionID"] }, payload: { prompt: input["prompt"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint5_26Request = Parameters<RawClient["server.session"]["session.log"]>[0]
type Endpoint5_26Input = {
readonly sessionID: Endpoint5_26Request["params"]["sessionID"]
readonly after?: Endpoint5_26Request["query"]["after"]
readonly follow?: Endpoint5_26Request["query"]["follow"]
}
const Endpoint5_26 = (raw: RawClient["server.session"]) => (input: Endpoint5_26Input) =>
Stream.unwrap( Stream.unwrap(
raw["session.log"]({ raw["session.log"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
@@ -378,22 +389,22 @@ const Endpoint5_25 = (raw: RawClient["server.session"]) => (input: Endpoint5_25I
), ),
) )
type Endpoint5_26Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0] type Endpoint5_27Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
type Endpoint5_26Input = { readonly sessionID: Endpoint5_26Request["params"]["sessionID"] }
const Endpoint5_26 = (raw: RawClient["server.session"]) => (input: Endpoint5_26Input) =>
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint5_27Request = Parameters<RawClient["server.session"]["session.background"]>[0]
type Endpoint5_27Input = { readonly sessionID: Endpoint5_27Request["params"]["sessionID"] } type Endpoint5_27Input = { readonly sessionID: Endpoint5_27Request["params"]["sessionID"] }
const Endpoint5_27 = (raw: RawClient["server.session"]) => (input: Endpoint5_27Input) => const Endpoint5_27 = (raw: RawClient["server.session"]) => (input: Endpoint5_27Input) =>
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint5_28Request = Parameters<RawClient["server.session"]["session.background"]>[0]
type Endpoint5_28Input = { readonly sessionID: Endpoint5_28Request["params"]["sessionID"] }
const Endpoint5_28 = (raw: RawClient["server.session"]) => (input: Endpoint5_28Input) =>
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint5_28Request = Parameters<RawClient["server.session"]["session.message"]>[0] type Endpoint5_29Request = Parameters<RawClient["server.session"]["session.message"]>[0]
type Endpoint5_28Input = { type Endpoint5_29Input = {
readonly sessionID: Endpoint5_28Request["params"]["sessionID"] readonly sessionID: Endpoint5_29Request["params"]["sessionID"]
readonly messageID: Endpoint5_28Request["params"]["messageID"] readonly messageID: Endpoint5_29Request["params"]["messageID"]
} }
const Endpoint5_28 = (raw: RawClient["server.session"]) => (input: Endpoint5_28Input) => const Endpoint5_29 = (raw: RawClient["server.session"]) => (input: Endpoint5_29Input) =>
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe( raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
Effect.map((value) => value.data), Effect.map((value) => value.data),
@@ -421,10 +432,11 @@ const adaptGroup5 = (raw: RawClient["server.session"]) => ({
context: Endpoint5_20(raw), context: Endpoint5_20(raw),
pending: { list: Endpoint5_21(raw) }, pending: { list: Endpoint5_21(raw) },
instructions: { entry: { list: Endpoint5_22(raw), put: Endpoint5_23(raw), remove: Endpoint5_24(raw) } }, instructions: { entry: { list: Endpoint5_22(raw), put: Endpoint5_23(raw), remove: Endpoint5_24(raw) } },
log: Endpoint5_25(raw), generate: Endpoint5_25(raw),
interrupt: Endpoint5_26(raw), log: Endpoint5_26(raw),
background: Endpoint5_27(raw), interrupt: Endpoint5_27(raw),
message: Endpoint5_28(raw), background: Endpoint5_28(raw),
message: Endpoint5_29(raw),
}) })
type Endpoint6_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0] type Endpoint6_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
@@ -58,6 +58,8 @@ import type {
SessionInstructionsEntryPutOutput, SessionInstructionsEntryPutOutput,
SessionInstructionsEntryRemoveInput, SessionInstructionsEntryRemoveInput,
SessionInstructionsEntryRemoveOutput, SessionInstructionsEntryRemoveOutput,
SessionGenerateInput,
SessionGenerateOutput,
SessionLogInput, SessionLogInput,
SessionLogOutput, SessionLogOutput,
SessionInterruptInput, SessionInterruptInput,
@@ -743,6 +745,18 @@ export function make(options: ClientOptions) {
), ),
}, },
}, },
generate: (input: SessionGenerateInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionGenerateOutput }>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/generate`,
body: { prompt: input["prompt"] },
successStatus: 200,
declaredStatuses: [404, 503, 400, 401],
empty: false,
},
requestOptions,
).then((value) => value.data),
log: (input: SessionLogInput, requestOptions?: RequestOptions): AsyncIterable<SessionLogOutput> => log: (input: SessionLogInput, requestOptions?: RequestOptions): AsyncIterable<SessionLogOutput> =>
sse<SessionLogOutput>( sse<SessionLogOutput>(
{ {
@@ -134,6 +134,8 @@ export type SessionMessageCompactionCompleted = {
export type InstructionEntryKey = string export type InstructionEntryKey = string
export type SessionGenerateResponse = { data: { text: string } }
export type SessionPendingSyntheticData1 = { text: string; description?: string; metadata?: { [x: string]: any } } export type SessionPendingSyntheticData1 = { text: string; description?: string; metadata?: { [x: string]: any } }
export type ShellInfo = { export type ShellInfo = {
@@ -3187,6 +3189,13 @@ export type SessionInstructionsEntryRemoveInput = {
export type SessionInstructionsEntryRemoveOutput = void export type SessionInstructionsEntryRemoveOutput = void
export type SessionGenerateInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly prompt: { readonly prompt: string }["prompt"]
}
export type SessionGenerateOutput = SessionGenerateResponse["data"]
export type SessionLogInput = { export type SessionLogInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"] readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly after?: { readonly after?: number | undefined; readonly follow?: boolean | undefined }["after"] readonly after?: { readonly after?: number | undefined; readonly follow?: boolean | undefined }["after"]
+4
View File
@@ -379,6 +379,7 @@ test("session methods use the public HTTP contract", async () => {
}) })
} }
if (url.includes("/prompt")) return Response.json(admission) if (url.includes("/prompt")) return Response.json(admission)
if (url.includes("/generate")) return Response.json({ data: { text: "A transient answer" } })
if (url.includes("/synthetic")) return Response.json(syntheticAdmission) if (url.includes("/synthetic")) return Response.json(syntheticAdmission)
if (url.endsWith("/compact")) return Response.json(compactionAdmission) if (url.endsWith("/compact")) return Response.json(compactionAdmission)
if (url.includes("/context")) return Response.json({ data: [] }) if (url.includes("/context")) return Response.json({ data: [] })
@@ -403,6 +404,7 @@ test("session methods use the public HTTP contract", async () => {
text: "Hello", text: "Hello",
resume: false, resume: false,
}) })
const generated = await client.session.generate({ sessionID: "ses_test", prompt: "Summarize this session" })
const synthetic = await client.session.synthetic({ const synthetic = await client.session.synthetic({
sessionID: "ses_test", sessionID: "ses_test",
text: "Completed", text: "Completed",
@@ -421,6 +423,7 @@ test("session methods use the public HTTP contract", async () => {
expect(active).toEqual({ ses_test: { type: "running" } }) expect(active).toEqual({ ses_test: { type: "running" } })
expect(created.id).toBe("ses_test") expect(created.id).toBe("ses_test")
expect(admitted.id).toBe("msg_test") expect(admitted.id).toBe("msg_test")
expect(generated.text).toBe("A transient answer")
expect(synthetic).toMatchObject({ type: "synthetic", data: { text: "Completed" }, delivery: "queue" }) expect(synthetic).toMatchObject({ type: "synthetic", data: { text: "Completed" }, delivery: "queue" })
expect(context).toEqual([]) expect(context).toEqual([])
expect(log).toEqual([modelSwitchedEvent, synced]) expect(log).toEqual([modelSwitchedEvent, synced])
@@ -432,6 +435,7 @@ test("session methods use the public HTTP contract", async () => {
["POST", "http://localhost:3000/api/session/ses_test/agent"], ["POST", "http://localhost:3000/api/session/ses_test/agent"],
["POST", "http://localhost:3000/api/session/ses_test/model"], ["POST", "http://localhost:3000/api/session/ses_test/model"],
["POST", "http://localhost:3000/api/session/ses_test/prompt"], ["POST", "http://localhost:3000/api/session/ses_test/prompt"],
["POST", "http://localhost:3000/api/session/ses_test/generate"],
["POST", "http://localhost:3000/api/session/ses_test/synthetic"], ["POST", "http://localhost:3000/api/session/ses_test/synthetic"],
["POST", "http://localhost:3000/api/session/ses_test/compact"], ["POST", "http://localhost:3000/api/session/ses_test/compact"],
["POST", "http://localhost:3000/api/session/ses_test/wait"], ["POST", "http://localhost:3000/api/session/ses_test/wait"],
+1
View File
@@ -424,6 +424,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
}), }),
get: (input) => runtime.session.get(input.sessionID), get: (input) => runtime.session.get(input.sessionID),
prompt: runtime.session.prompt, prompt: runtime.session.prompt,
generate: (input) => runtime.session.generate(input).pipe(Effect.map((text) => ({ text }))),
command: runtime.session.command, command: runtime.session.command,
synthetic: runtime.session.synthetic, synthetic: runtime.session.synthetic,
interrupt: (input) => runtime.session.interrupt(input.sessionID), interrupt: (input) => runtime.session.interrupt(input.sessionID),
+2
View File
@@ -231,6 +231,8 @@ export function fromPromise(plugin: Plugin) {
resume: input.resume ?? undefined, resume: input.resume ?? undefined,
}), }),
), ),
generate: (input) =>
run(host.session.generate({ sessionID: Session.ID.make(input.sessionID), prompt: input.prompt })),
command: (input) => command: (input) =>
run( run(
host.session.command({ host.session.command({
+2 -1
View File
@@ -11,7 +11,7 @@ import { SessionV2 } from "../session"
export interface Interface { export interface Interface {
readonly session: Pick< readonly session: Pick<
SessionV2.Interface, SessionV2.Interface,
"get" | "create" | "messages" | "prompt" | "command" | "resume" | "interrupt" | "synthetic" "get" | "create" | "messages" | "prompt" | "generate" | "command" | "resume" | "interrupt" | "synthetic"
> >
readonly job: Pick<Job.Interface, "start" | "wait" | "block" | "background" | "cancel"> readonly job: Pick<Job.Interface, "start" | "wait" | "block" | "background" | "cancel">
readonly location: { readonly location: {
@@ -50,6 +50,7 @@ export const layerWithCell = (cell: Cell) =>
create: (input) => require(cell, (runtime) => runtime.session.create(input)), create: (input) => require(cell, (runtime) => runtime.session.create(input)),
messages: (input) => require(cell, (runtime) => runtime.session.messages(input)), messages: (input) => require(cell, (runtime) => runtime.session.messages(input)),
prompt: (input) => require(cell, (runtime) => runtime.session.prompt(input)), prompt: (input) => require(cell, (runtime) => runtime.session.prompt(input)),
generate: (input) => require(cell, (runtime) => runtime.session.generate(input)),
command: (input) => require(cell, (runtime) => runtime.session.command(input)), command: (input) => require(cell, (runtime) => runtime.session.command(input)),
resume: (sessionID) => require(cell, (runtime) => runtime.session.resume(sessionID)), resume: (sessionID) => require(cell, (runtime) => runtime.session.resume(sessionID)),
interrupt: (sessionID) => require(cell, (runtime) => runtime.session.interrupt(sessionID)), interrupt: (sessionID) => require(cell, (runtime) => runtime.session.interrupt(sessionID)),
+1
View File
@@ -96,6 +96,7 @@ export function host(overrides: Overrides = {}): PluginContext {
create: overrides.session?.create ?? (() => Effect.die("unused session.create")), create: overrides.session?.create ?? (() => Effect.die("unused session.create")),
get: overrides.session?.get ?? (() => Effect.die("unused session.get")), get: overrides.session?.get ?? (() => Effect.die("unused session.get")),
prompt: overrides.session?.prompt ?? (() => Effect.die("unused session.prompt")), prompt: overrides.session?.prompt ?? (() => Effect.die("unused session.prompt")),
generate: overrides.session?.generate ?? (() => Effect.die("unused session.generate")),
command: overrides.session?.command ?? (() => Effect.die("unused session.command")), command: overrides.session?.command ?? (() => Effect.die("unused session.command")),
synthetic: overrides.session?.synthetic ?? (() => Effect.die("unused session.synthetic")), synthetic: overrides.session?.synthetic ?? (() => Effect.die("unused session.synthetic")),
interrupt: overrides.session?.interrupt ?? (() => Effect.die("unused session.interrupt")), interrupt: overrides.session?.interrupt ?? (() => Effect.die("unused session.interrupt")),
+21
View File
@@ -24,6 +24,27 @@ import { host as testHost } from "./host"
const it = testEffect(PluginTestLayer) const it = testEffect(PluginTestLayer)
describe("fromPromise", () => { describe("fromPromise", () => {
it.effect("forwards transient session generation", () =>
Effect.gen(function* () {
const host = testHost({
session: {
generate: (input) => Effect.succeed({ text: `${input.sessionID}: ${input.prompt}` }),
},
})
yield* PluginPromise.fromPromise(
Plugin.define({
id: "promise-session-generate",
setup: async (ctx) => {
expect(await ctx.session.generate({ sessionID: "ses_generate", prompt: "Summarize" })).toEqual({
text: "ses_generate: Summarize",
})
},
}),
).effect(host)
}),
)
it.effect("forwards synthetic session input", () => it.effect("forwards synthetic session input", () =>
Effect.gen(function* () { Effect.gen(function* () {
const input = { const input = {
+1 -1
View File
@@ -21,7 +21,7 @@ export interface SessionHooks {
export type SessionDomain = Pick< export type SessionDomain = Pick<
SessionApi<unknown>, SessionApi<unknown>,
"create" | "get" | "prompt" | "command" | "synthetic" | "interrupt" "create" | "get" | "prompt" | "generate" | "command" | "synthetic" | "interrupt"
> & { > & {
readonly hook: Hooks<SessionHooks> readonly hook: Hooks<SessionHooks>
} }
+4 -1
View File
@@ -19,6 +19,9 @@ export interface SessionHooks {
readonly context: SessionContext readonly context: SessionContext
} }
export type SessionDomain = Pick<SessionApi, "create" | "get" | "prompt" | "command" | "synthetic" | "interrupt"> & { export type SessionDomain = Pick<
SessionApi,
"create" | "get" | "prompt" | "generate" | "command" | "synthetic" | "interrupt"
> & {
readonly hook: Hooks<SessionHooks> readonly hook: Hooks<SessionHooks>
} }
+18
View File
@@ -543,6 +543,24 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
}), }),
), ),
) )
.add(
HttpApiEndpoint.post("session.generate", "/api/session/:sessionID/generate", {
params: { sessionID: Session.ID },
payload: Schema.Struct({ prompt: Schema.String }),
success: Schema.Struct({
data: Schema.Struct({ text: Schema.String }),
}).annotate({ identifier: "SessionGenerateResponse" }),
error: [SessionNotFoundError, ServiceUnavailableError],
})
.middleware(sessionLocationMiddleware)
.annotateMerge(
OpenApi.annotations({
identifier: "v2.session.generate",
summary: "Generate text from session context",
description: "Generate transient text from the current session context without mutating session history.",
}),
),
)
.add( .add(
HttpApiEndpoint.get("session.log", "/api/experimental/session/:sessionID/log", { HttpApiEndpoint.get("session.log", "/api/experimental/session/:sessionID/log", {
params: { sessionID: Session.ID }, params: { sessionID: Session.ID },
+16
View File
@@ -627,6 +627,22 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
return HttpApiSchema.NoContent.make() return HttpApiSchema.NoContent.make()
}), }),
) )
.handle(
"session.generate",
Effect.fn(function* (ctx) {
const text = yield* session.generate({ sessionID: ctx.params.sessionID, prompt: ctx.payload.prompt }).pipe(
Effect.mapError((error) =>
error._tag === "Session.NotFoundError"
? new SessionNotFoundError({
sessionID: error.sessionID,
message: `Session not found: ${error.sessionID}`,
})
: new ServiceUnavailableError({ message: error.message, service: "session generation" }),
),
)
return { data: { text } }
}),
)
.handle( .handle(
"session.log", "session.log",
Effect.fn((ctx) => Effect.fn((ctx) =>