Buckets:
| import { SessionV2 } from "@opencode-ai/core/session" | |
| import { DateTime, Effect } from "effect" | |
| import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi" | |
| import { Api } from "../api" | |
| import { SessionsCursor } from "../groups/session" | |
| import { | |
| ConflictError, | |
| InvalidCursorError, | |
| ServiceUnavailableError, | |
| SessionNotFoundError, | |
| UnknownError, | |
| } from "../errors" | |
| import { AbsolutePath } from "@opencode-ai/core/schema" | |
| const DefaultSessionsLimit = 50 | |
| export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) => | |
| Effect.gen(function* () { | |
| const session = yield* SessionV2.Service | |
| return handlers | |
| .handle( | |
| "session.list", | |
| Effect.fn(function* (ctx) { | |
| const query = | |
| ctx.query.cursor !== undefined | |
| ? yield* SessionsCursor.parse(ctx.query.cursor).pipe( | |
| Effect.mapError(() => new InvalidCursorError({ message: "Invalid cursor" })), | |
| ) | |
| : ctx.query | |
| const sessions = yield* session.list({ | |
| ...query, | |
| workspaceID: query.workspace, | |
| limit: ctx.query.limit ?? DefaultSessionsLimit, | |
| }) | |
| const first = sessions[0] | |
| const last = sessions.at(-1) | |
| return { | |
| data: sessions, | |
| cursor: { | |
| previous: first | |
| ? SessionsCursor.make({ | |
| ...query, | |
| anchor: { | |
| id: first.id, | |
| time: DateTime.toEpochMillis(first.time.created), | |
| direction: "previous", | |
| }, | |
| }) | |
| : undefined, | |
| next: last | |
| ? SessionsCursor.make({ | |
| ...query, | |
| anchor: { | |
| id: last.id, | |
| time: DateTime.toEpochMillis(last.time.created), | |
| direction: "next", | |
| }, | |
| }) | |
| : undefined, | |
| }, | |
| } | |
| }), | |
| ) | |
| .handle( | |
| "session.create", | |
| Effect.fn(function* (ctx) { | |
| return { | |
| data: yield* session.create({ | |
| id: ctx.payload.id, | |
| agent: ctx.payload.agent, | |
| model: ctx.payload.model, | |
| location: ctx.payload.location ?? { directory: AbsolutePath.make(process.cwd()) }, | |
| }), | |
| } | |
| }), | |
| ) | |
| .handle( | |
| "session.get", | |
| Effect.fn(function* (ctx) { | |
| return { | |
| data: yield* session.get(ctx.params.sessionID).pipe( | |
| Effect.catchTag( | |
| "Session.NotFoundError", | |
| (error) => | |
| new SessionNotFoundError({ | |
| sessionID: error.sessionID, | |
| message: `Session not found: ${error.sessionID}`, | |
| }), | |
| ), | |
| ), | |
| } | |
| }), | |
| ) | |
| .handle( | |
| "session.prompt", | |
| Effect.fn(function* (ctx) { | |
| return { | |
| data: yield* session | |
| .prompt({ | |
| sessionID: ctx.params.sessionID, | |
| id: ctx.payload.id, | |
| prompt: ctx.payload.prompt, | |
| delivery: ctx.payload.delivery, | |
| resume: ctx.payload.resume, | |
| }) | |
| .pipe( | |
| Effect.catchTag("Session.NotFoundError", (error) => | |
| Effect.fail( | |
| new SessionNotFoundError({ | |
| sessionID: error.sessionID, | |
| message: `Session not found: ${error.sessionID}`, | |
| }), | |
| ), | |
| ), | |
| Effect.catchTag("Session.PromptConflictError", (error) => | |
| Effect.fail( | |
| new ConflictError({ | |
| message: `Prompt message ID conflicts with an existing durable record: ${error.messageID}`, | |
| resource: error.messageID, | |
| }), | |
| ), | |
| ), | |
| ), | |
| } | |
| }), | |
| ) | |
| .handle( | |
| "session.compact", | |
| Effect.fn(function* (ctx) { | |
| yield* session.compact({ sessionID: ctx.params.sessionID }).pipe( | |
| Effect.catchTag("Session.NotFoundError", (error) => | |
| Effect.fail( | |
| new SessionNotFoundError({ | |
| sessionID: error.sessionID, | |
| message: `Session not found: ${error.sessionID}`, | |
| }), | |
| ), | |
| ), | |
| Effect.catchTag("Session.OperationUnavailableError", (error) => | |
| Effect.fail( | |
| new ServiceUnavailableError({ | |
| message: `Session ${error.operation} is not available yet`, | |
| service: `session.${error.operation}`, | |
| }), | |
| ), | |
| ), | |
| ) | |
| return HttpApiSchema.NoContent.make() | |
| }), | |
| ) | |
| .handle( | |
| "session.wait", | |
| Effect.fn(function* (ctx) { | |
| yield* session.wait(ctx.params.sessionID).pipe( | |
| Effect.catchTag("Session.NotFoundError", (error) => | |
| Effect.fail( | |
| new SessionNotFoundError({ | |
| sessionID: error.sessionID, | |
| message: `Session not found: ${error.sessionID}`, | |
| }), | |
| ), | |
| ), | |
| Effect.catchTag("Session.OperationUnavailableError", (error) => | |
| Effect.fail( | |
| new ServiceUnavailableError({ | |
| message: `Session ${error.operation} is not available yet`, | |
| service: `session.${error.operation}`, | |
| }), | |
| ), | |
| ), | |
| ) | |
| return HttpApiSchema.NoContent.make() | |
| }), | |
| ) | |
| .handle( | |
| "session.context", | |
| Effect.fn(function* (ctx) { | |
| return { | |
| data: yield* session.context(ctx.params.sessionID).pipe( | |
| Effect.catchTag("Session.NotFoundError", (error) => | |
| Effect.fail( | |
| new SessionNotFoundError({ | |
| sessionID: error.sessionID, | |
| message: `Session not found: ${error.sessionID}`, | |
| }), | |
| ), | |
| ), | |
| Effect.catchTag("Session.MessageDecodeError", (error) => { | |
| const ref = `err_${crypto.randomUUID().slice(0, 8)}` | |
| return Effect.logError("failed to decode session message").pipe( | |
| Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }), | |
| Effect.andThen( | |
| Effect.fail( | |
| new UnknownError({ | |
| message: "Unexpected server error. Check server logs for details.", | |
| ref, | |
| }), | |
| ), | |
| ), | |
| ) | |
| }), | |
| ), | |
| } | |
| }), | |
| ) | |
| }), | |
| ) | |
Xet Storage Details
- Size:
- 7.07 kB
- Xet hash:
- e8cb789ed354773d38640c1558a4421d4e99477844f5386c6d2a5e051e8a597d
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.