|
|
@@ -6,7 +6,6 @@ import { Command } from "@/command"
|
|
|
import { Permission } from "@/permission"
|
|
|
import { PermissionID } from "@/permission/schema"
|
|
|
import { Instance } from "@/project/instance"
|
|
|
-import { ModelID, ProviderID } from "@/provider/schema"
|
|
|
import { SessionShare } from "@/share/session"
|
|
|
import { Session } from "@/session/session"
|
|
|
import { SessionCompaction } from "@/session/compaction"
|
|
|
@@ -18,422 +17,27 @@ import { SessionStatus } from "@/session/status"
|
|
|
import { SessionSummary } from "@/session/summary"
|
|
|
import { Todo } from "@/session/todo"
|
|
|
import { MessageID, PartID, SessionID } from "@/session/schema"
|
|
|
-import { Snapshot } from "@/snapshot"
|
|
|
+import { NotFoundError } from "@/storage/storage"
|
|
|
import * as Log from "@opencode-ai/core/util/log"
|
|
|
import { NamedError } from "@opencode-ai/core/util/error"
|
|
|
-import { Effect, Schema, SchemaGetter, Struct } from "effect"
|
|
|
+import { Effect, Schema } from "effect"
|
|
|
import * as Stream from "effect/Stream"
|
|
|
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
|
|
-import {
|
|
|
- HttpApi,
|
|
|
- HttpApiBuilder,
|
|
|
- HttpApiEndpoint,
|
|
|
- HttpApiError,
|
|
|
- HttpApiGroup,
|
|
|
- HttpApiSchema,
|
|
|
- OpenApi,
|
|
|
-} from "effect/unstable/httpapi"
|
|
|
-import { Authorization } from "./auth"
|
|
|
+import { HttpApiBuilder, HttpApiError, HttpApiSchema } from "effect/unstable/httpapi"
|
|
|
+import { InstanceHttpApi } from "../api"
|
|
|
+import { CommandPayload, DiffQuery, ForkPayload, InitPayload, ListQuery, MessagesQuery, PermissionResponsePayload, PromptPayload, RevertPayload, ShellPayload, SummarizePayload, UpdatePayload } from "../groups/session"
|
|
|
|
|
|
const log = Log.create({ service: "server" })
|
|
|
-const root = "/session"
|
|
|
-const QueryBoolean = Schema.Literals(["true", "false"]).pipe(
|
|
|
- Schema.decodeTo(Schema.Boolean, {
|
|
|
- decode: SchemaGetter.transform((value) => value === "true"),
|
|
|
- encode: SchemaGetter.transform((value) => (value ? "true" : "false")),
|
|
|
- }),
|
|
|
-)
|
|
|
-const ListQuery = Schema.Struct({
|
|
|
- directory: Schema.optional(Schema.String),
|
|
|
- scope: Schema.optional(Schema.Literals(["project"])),
|
|
|
- path: Schema.optional(Schema.String),
|
|
|
- roots: Schema.optional(QueryBoolean),
|
|
|
- start: Schema.optional(Schema.NumberFromString),
|
|
|
- search: Schema.optional(Schema.String),
|
|
|
- limit: Schema.optional(Schema.NumberFromString),
|
|
|
-})
|
|
|
-const DiffQuery = Schema.Struct(Struct.omit(SessionSummary.DiffInput.fields, ["sessionID"]))
|
|
|
-const MessagesQuery = Schema.Struct({
|
|
|
- limit: Schema.optional(Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))),
|
|
|
- before: Schema.optional(Schema.String),
|
|
|
-})
|
|
|
-const StatusMap = Schema.Record(Schema.String, SessionStatus.Info)
|
|
|
-const UpdatePayload = Schema.Struct({
|
|
|
- title: Schema.optional(Schema.String),
|
|
|
- permission: Schema.optional(Permission.Ruleset),
|
|
|
- time: Schema.optional(
|
|
|
- Schema.Struct({
|
|
|
- archived: Schema.optional(Schema.Number),
|
|
|
- }),
|
|
|
- ),
|
|
|
-}).annotate({ identifier: "SessionUpdateInput" })
|
|
|
-const ForkPayload = Schema.Struct(Struct.omit(Session.ForkInput.fields, ["sessionID"])).annotate({
|
|
|
- identifier: "SessionForkInput",
|
|
|
-})
|
|
|
-const InitPayload = Schema.Struct({
|
|
|
- modelID: ModelID,
|
|
|
- providerID: ProviderID,
|
|
|
- messageID: MessageID,
|
|
|
-}).annotate({ identifier: "SessionInitInput" })
|
|
|
-const SummarizePayload = Schema.Struct({
|
|
|
- providerID: ProviderID,
|
|
|
- modelID: ModelID,
|
|
|
- auto: Schema.optional(Schema.Boolean),
|
|
|
-}).annotate({ identifier: "SessionSummarizeInput" })
|
|
|
-const PromptPayload = Schema.Struct(Struct.omit(SessionPrompt.PromptInput.fields, ["sessionID"])).annotate({
|
|
|
- identifier: "SessionPromptInput",
|
|
|
-})
|
|
|
-const CommandPayload = Schema.Struct(Struct.omit(SessionPrompt.CommandInput.fields, ["sessionID"])).annotate({
|
|
|
- identifier: "SessionCommandInput",
|
|
|
-})
|
|
|
-const ShellPayload = Schema.Struct(Struct.omit(SessionPrompt.ShellInput.fields, ["sessionID"])).annotate({
|
|
|
- identifier: "SessionShellInput",
|
|
|
-})
|
|
|
-const RevertPayload = Schema.Struct(Struct.omit(SessionRevert.RevertInput.fields, ["sessionID"])).annotate({
|
|
|
- identifier: "SessionRevertInput",
|
|
|
-})
|
|
|
-const PermissionResponsePayload = Schema.Struct({
|
|
|
- response: Permission.Reply,
|
|
|
-}).annotate({ identifier: "SessionPermissionResponseInput" })
|
|
|
-
|
|
|
-export const SessionPaths = {
|
|
|
- list: root,
|
|
|
- status: `${root}/status`,
|
|
|
- get: `${root}/:sessionID`,
|
|
|
- children: `${root}/:sessionID/children`,
|
|
|
- todo: `${root}/:sessionID/todo`,
|
|
|
- diff: `${root}/:sessionID/diff`,
|
|
|
- messages: `${root}/:sessionID/message`,
|
|
|
- message: `${root}/:sessionID/message/:messageID`,
|
|
|
- create: root,
|
|
|
- remove: `${root}/:sessionID`,
|
|
|
- update: `${root}/:sessionID`,
|
|
|
- fork: `${root}/:sessionID/fork`,
|
|
|
- abort: `${root}/:sessionID/abort`,
|
|
|
- share: `${root}/:sessionID/share`,
|
|
|
- init: `${root}/:sessionID/init`,
|
|
|
- summarize: `${root}/:sessionID/summarize`,
|
|
|
- prompt: `${root}/:sessionID/message`,
|
|
|
- promptAsync: `${root}/:sessionID/prompt_async`,
|
|
|
- command: `${root}/:sessionID/command`,
|
|
|
- shell: `${root}/:sessionID/shell`,
|
|
|
- revert: `${root}/:sessionID/revert`,
|
|
|
- unrevert: `${root}/:sessionID/unrevert`,
|
|
|
- permissions: `${root}/:sessionID/permissions/:permissionID`,
|
|
|
- deleteMessage: `${root}/:sessionID/message/:messageID`,
|
|
|
- deletePart: `${root}/:sessionID/message/:messageID/part/:partID`,
|
|
|
- updatePart: `${root}/:sessionID/message/:messageID/part/:partID`,
|
|
|
-} as const
|
|
|
-
|
|
|
-export const SessionApi = HttpApi.make("session")
|
|
|
- .add(
|
|
|
- HttpApiGroup.make("session")
|
|
|
- .add(
|
|
|
- HttpApiEndpoint.get("list", SessionPaths.list, {
|
|
|
- query: ListQuery,
|
|
|
- success: Schema.Array(Session.Info),
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.list",
|
|
|
- summary: "List sessions",
|
|
|
- description: "Get a list of all OpenCode sessions, sorted by most recently updated.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.get("status", SessionPaths.status, {
|
|
|
- success: StatusMap,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.status",
|
|
|
- summary: "Get session status",
|
|
|
- description: "Retrieve the current status of all sessions, including active, idle, and completed states.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.get("get", SessionPaths.get, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- success: Session.Info,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.get",
|
|
|
- summary: "Get session",
|
|
|
- description: "Retrieve detailed information about a specific OpenCode session.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.get("children", SessionPaths.children, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- success: Schema.Array(Session.Info),
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.children",
|
|
|
- summary: "Get session children",
|
|
|
- description: "Retrieve all child sessions that were forked from the specified parent session.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.get("todo", SessionPaths.todo, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- success: Schema.Array(Todo.Info),
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.todo",
|
|
|
- summary: "Get session todos",
|
|
|
- description: "Retrieve the todo list associated with a specific session, showing tasks and action items.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.get("diff", SessionPaths.diff, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- query: DiffQuery,
|
|
|
- success: Schema.Array(Snapshot.FileDiff),
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.diff",
|
|
|
- summary: "Get message diff",
|
|
|
- description: "Get the file changes (diff) that resulted from a specific user message in the session.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.get("messages", SessionPaths.messages, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- query: MessagesQuery,
|
|
|
- success: Schema.Array(MessageV2.WithParts),
|
|
|
- error: HttpApiError.BadRequest,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.messages",
|
|
|
- summary: "Get session messages",
|
|
|
- description: "Retrieve all messages in a session, including user prompts and AI responses.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.get("message", SessionPaths.message, {
|
|
|
- params: { sessionID: SessionID, messageID: MessageID },
|
|
|
- success: MessageV2.WithParts,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.message",
|
|
|
- summary: "Get message",
|
|
|
- description: "Retrieve a specific message from a session by its message ID.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("create", SessionPaths.create, {
|
|
|
- payload: [HttpApiSchema.NoContent, Session.CreateInput],
|
|
|
- success: Session.Info,
|
|
|
- error: HttpApiError.BadRequest,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.create",
|
|
|
- summary: "Create session",
|
|
|
- description: "Create a new OpenCode session for interacting with AI assistants and managing conversations.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.delete("remove", SessionPaths.remove, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- success: Schema.Boolean,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.delete",
|
|
|
- summary: "Delete session",
|
|
|
- description: "Delete a session and permanently remove all associated data, including messages and history.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.patch("update", SessionPaths.update, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- payload: UpdatePayload,
|
|
|
- success: Session.Info,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.update",
|
|
|
- summary: "Update session",
|
|
|
- description: "Update properties of an existing session, such as title or other metadata.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("fork", SessionPaths.fork, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- payload: ForkPayload,
|
|
|
- success: Session.Info,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.fork",
|
|
|
- summary: "Fork session",
|
|
|
- description: "Create a new session by forking an existing session at a specific message point.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("abort", SessionPaths.abort, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- success: Schema.Boolean,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.abort",
|
|
|
- summary: "Abort session",
|
|
|
- description: "Abort an active session and stop any ongoing AI processing or command execution.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("init", SessionPaths.init, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- payload: InitPayload,
|
|
|
- success: Schema.Boolean,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.init",
|
|
|
- summary: "Initialize session",
|
|
|
- description:
|
|
|
- "Analyze the current application and create an AGENTS.md file with project-specific agent configurations.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("share", SessionPaths.share, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- success: Session.Info,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.share",
|
|
|
- summary: "Share session",
|
|
|
- description: "Create a shareable link for a session, allowing others to view the conversation.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.delete("unshare", SessionPaths.share, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- success: Session.Info,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.unshare",
|
|
|
- summary: "Unshare session",
|
|
|
- description: "Remove the shareable link for a session, making it private again.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("summarize", SessionPaths.summarize, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- payload: SummarizePayload,
|
|
|
- success: Schema.Boolean,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.summarize",
|
|
|
- summary: "Summarize session",
|
|
|
- description: "Generate a concise summary of the session using AI compaction to preserve key information.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("prompt", SessionPaths.prompt, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- payload: PromptPayload,
|
|
|
- success: MessageV2.WithParts,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.prompt",
|
|
|
- summary: "Send message",
|
|
|
- description: "Create and send a new message to a session, streaming the AI response.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("promptAsync", SessionPaths.promptAsync, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- payload: PromptPayload,
|
|
|
- success: HttpApiSchema.NoContent,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.prompt_async",
|
|
|
- summary: "Send async message",
|
|
|
- description:
|
|
|
- "Create and send a new message to a session asynchronously, starting the session if needed and returning immediately.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("command", SessionPaths.command, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- payload: CommandPayload,
|
|
|
- success: MessageV2.WithParts,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.command",
|
|
|
- summary: "Send command",
|
|
|
- description: "Send a new command to a session for execution by the AI assistant.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("shell", SessionPaths.shell, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- payload: ShellPayload,
|
|
|
- success: MessageV2.WithParts,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.shell",
|
|
|
- summary: "Run shell command",
|
|
|
- description: "Execute a shell command within the session context and return the AI's response.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("revert", SessionPaths.revert, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- payload: RevertPayload,
|
|
|
- success: Session.Info,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.revert",
|
|
|
- summary: "Revert message",
|
|
|
- description:
|
|
|
- "Revert a specific message in a session, undoing its effects and restoring the previous state.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("unrevert", SessionPaths.unrevert, {
|
|
|
- params: { sessionID: SessionID },
|
|
|
- success: Session.Info,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.unrevert",
|
|
|
- summary: "Restore reverted messages",
|
|
|
- description: "Restore all previously reverted messages in a session.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.post("permissionRespond", SessionPaths.permissions, {
|
|
|
- params: { sessionID: SessionID, permissionID: PermissionID },
|
|
|
- payload: PermissionResponsePayload,
|
|
|
- success: Schema.Boolean,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "permission.respond",
|
|
|
- summary: "Respond to permission",
|
|
|
- description: "Approve or deny a permission request from the AI assistant.",
|
|
|
- deprecated: true,
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.delete("deleteMessage", SessionPaths.deleteMessage, {
|
|
|
- params: { sessionID: SessionID, messageID: MessageID },
|
|
|
- success: Schema.Boolean,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "session.deleteMessage",
|
|
|
- summary: "Delete message",
|
|
|
- description:
|
|
|
- "Permanently delete a specific message and all of its parts from a session without reverting file changes.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.delete("deletePart", SessionPaths.deletePart, {
|
|
|
- params: { sessionID: SessionID, messageID: MessageID, partID: PartID },
|
|
|
- success: Schema.Boolean,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "part.delete",
|
|
|
- description: "Delete a part from a message.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- HttpApiEndpoint.patch("updatePart", SessionPaths.updatePart, {
|
|
|
- params: { sessionID: SessionID, messageID: MessageID, partID: PartID },
|
|
|
- payload: MessageV2.Part,
|
|
|
- success: MessageV2.Part,
|
|
|
- }).annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- identifier: "part.update",
|
|
|
- description: "Update a part in a message.",
|
|
|
- }),
|
|
|
- ),
|
|
|
- )
|
|
|
- .annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- title: "session",
|
|
|
- description: "Experimental HttpApi session routes.",
|
|
|
- }),
|
|
|
- )
|
|
|
- .middleware(Authorization),
|
|
|
- )
|
|
|
- .annotateMerge(
|
|
|
- OpenApi.annotations({
|
|
|
- title: "opencode experimental HttpApi",
|
|
|
- version: "0.0.1",
|
|
|
- description: "Experimental HttpApi surface for selected instance routes.",
|
|
|
- }),
|
|
|
+
|
|
|
+const mapNotFound = <A, E, R>(self: Effect.Effect<A, E, R>) =>
|
|
|
+ self.pipe(
|
|
|
+ Effect.catchIf(NotFoundError.isInstance, () => Effect.fail(new HttpApiError.NotFound({}))),
|
|
|
+ Effect.catchDefect((error) =>
|
|
|
+ NotFoundError.isInstance(error) ? Effect.fail(new HttpApiError.NotFound({})) : Effect.die(error),
|
|
|
+ ),
|
|
|
)
|
|
|
|
|
|
-export const sessionHandlers = HttpApiBuilder.group(SessionApi, "session", (handlers) =>
|
|
|
+export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", (handlers) =>
|
|
|
Effect.gen(function* () {
|
|
|
const session = yield* Session.Service
|
|
|
const statusSvc = yield* SessionStatus.Service
|
|
|
@@ -462,7 +66,7 @@ export const sessionHandlers = HttpApiBuilder.group(SessionApi, "session", (hand
|
|
|
})
|
|
|
|
|
|
const get = Effect.fn("SessionHttpApi.get")(function* (ctx: { params: { sessionID: SessionID } }) {
|
|
|
- return yield* session.get(ctx.params.sessionID)
|
|
|
+ return yield* mapNotFound(session.get(ctx.params.sessionID))
|
|
|
})
|
|
|
|
|
|
const children = Effect.fn("SessionHttpApi.children")(function* (ctx: { params: { sessionID: SessionID } }) {
|
|
|
@@ -484,44 +88,47 @@ export const sessionHandlers = HttpApiBuilder.group(SessionApi, "session", (hand
|
|
|
params: { sessionID: SessionID }
|
|
|
query: typeof MessagesQuery.Type
|
|
|
}) {
|
|
|
- if (ctx.query.before && ctx.query.limit === undefined) return yield* new HttpApiError.BadRequest({})
|
|
|
- if (ctx.query.before) {
|
|
|
- const before = ctx.query.before
|
|
|
- yield* Effect.try({
|
|
|
- try: () => MessageV2.cursor.decode(before),
|
|
|
- catch: () => new HttpApiError.BadRequest({}),
|
|
|
- })
|
|
|
- }
|
|
|
- if (ctx.query.limit === undefined || ctx.query.limit === 0) {
|
|
|
- yield* session.get(ctx.params.sessionID)
|
|
|
- return yield* session.messages({ sessionID: ctx.params.sessionID })
|
|
|
- }
|
|
|
+ return yield* mapNotFound(Effect.gen(function* () {
|
|
|
+ if (ctx.query.before && ctx.query.limit === undefined) return yield* new HttpApiError.BadRequest({})
|
|
|
+ if (ctx.query.before) {
|
|
|
+ const before = ctx.query.before
|
|
|
+ yield* Effect.try({
|
|
|
+ try: () => MessageV2.cursor.decode(before),
|
|
|
+ catch: () => new HttpApiError.BadRequest({}),
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if (ctx.query.limit === undefined || ctx.query.limit === 0) {
|
|
|
+ yield* session.get(ctx.params.sessionID)
|
|
|
+ return yield* session.messages({ sessionID: ctx.params.sessionID })
|
|
|
+ }
|
|
|
|
|
|
- const page = MessageV2.page({
|
|
|
- sessionID: ctx.params.sessionID,
|
|
|
- limit: ctx.query.limit,
|
|
|
- before: ctx.query.before,
|
|
|
- })
|
|
|
- if (!page.cursor) return page.items
|
|
|
-
|
|
|
- const request = yield* HttpServerRequest.HttpServerRequest
|
|
|
- const url = new URL(request.url, "http://localhost")
|
|
|
- url.searchParams.set("limit", ctx.query.limit.toString())
|
|
|
- url.searchParams.set("before", page.cursor)
|
|
|
- return HttpServerResponse.jsonUnsafe(page.items, {
|
|
|
- headers: {
|
|
|
- "Access-Control-Expose-Headers": "Link, X-Next-Cursor",
|
|
|
- Link: `<${url.toString()}>; rel="next"`,
|
|
|
- "X-Next-Cursor": page.cursor,
|
|
|
- },
|
|
|
- })
|
|
|
+ yield* session.get(ctx.params.sessionID)
|
|
|
+ const page = MessageV2.page({
|
|
|
+ sessionID: ctx.params.sessionID,
|
|
|
+ limit: ctx.query.limit,
|
|
|
+ before: ctx.query.before,
|
|
|
+ })
|
|
|
+ if (!page.cursor) return page.items
|
|
|
+
|
|
|
+ const request = yield* HttpServerRequest.HttpServerRequest
|
|
|
+ const url = new URL(request.url, "http://localhost")
|
|
|
+ url.searchParams.set("limit", ctx.query.limit.toString())
|
|
|
+ url.searchParams.set("before", page.cursor)
|
|
|
+ return HttpServerResponse.jsonUnsafe(page.items, {
|
|
|
+ headers: {
|
|
|
+ "Access-Control-Expose-Headers": "Link, X-Next-Cursor",
|
|
|
+ Link: `<${url.toString()}>; rel="next"`,
|
|
|
+ "X-Next-Cursor": page.cursor,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ }))
|
|
|
})
|
|
|
|
|
|
const message = Effect.fn("SessionHttpApi.message")(function* (ctx: {
|
|
|
params: { sessionID: SessionID; messageID: MessageID }
|
|
|
}) {
|
|
|
- return yield* Effect.sync(() =>
|
|
|
- MessageV2.get({ sessionID: ctx.params.sessionID, messageID: ctx.params.messageID }),
|
|
|
+ return yield* mapNotFound(
|
|
|
+ Effect.sync(() => MessageV2.get({ sessionID: ctx.params.sessionID, messageID: ctx.params.messageID })),
|
|
|
)
|
|
|
})
|
|
|
|