| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- import { Permission } from "@/permission"
- import { PermissionID } from "@/permission/schema"
- import { ModelID, ProviderID } from "@/provider/schema"
- import { Session } from "@/session/session"
- import { MessageV2 } from "@/session/message-v2"
- import { SessionPrompt } from "@/session/prompt"
- import { SessionRevert } from "@/session/revert"
- 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 { Schema, Struct } from "effect"
- import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
- import { Authorization } from "../middleware/authorization"
- import { InstanceContextMiddleware } from "../middleware/instance-context"
- import {
- WorkspaceRoutingMiddleware,
- WorkspaceRoutingQuery,
- WorkspaceRoutingQueryFields,
- } from "../middleware/workspace-routing"
- import { ApiNotFoundError, PermissionNotFoundError, SessionBusyError } from "../errors"
- import { described } from "./metadata"
- import { QueryBoolean } from "./query"
- const root = "/session"
- export const ListQuery = Schema.Struct({
- ...WorkspaceRoutingQueryFields,
- 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),
- })
- export const DiffQuery = Schema.Struct({
- ...WorkspaceRoutingQueryFields,
- ...Struct.omit(SessionSummary.DiffInput.fields, ["sessionID"]),
- })
- export const MessagesQuery = Schema.Struct({
- ...WorkspaceRoutingQueryFields,
- limit: Schema.optional(Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))),
- before: Schema.optional(Schema.String),
- })
- export const StatusMap = Schema.Record(Schema.String, SessionStatus.Info)
- export const UpdatePayload = Schema.Struct({
- title: Schema.optional(Schema.String),
- permission: Schema.optional(Permission.Ruleset),
- time: Schema.optional(
- Schema.Struct({
- archived: Schema.optional(Session.ArchivedTimestamp),
- }),
- ),
- })
- export const ForkPayload = Schema.Struct(Struct.omit(Session.ForkInput.fields, ["sessionID"]))
- export const InitPayload = Schema.Struct({
- modelID: ModelID,
- providerID: ProviderID,
- messageID: MessageID,
- })
- export const SummarizePayload = Schema.Struct({
- providerID: ProviderID,
- modelID: ModelID,
- auto: Schema.optional(Schema.Boolean),
- })
- export const PromptPayload = Schema.Struct(Struct.omit(SessionPrompt.PromptInput.fields, ["sessionID"]))
- export const CommandPayload = Schema.Struct(Struct.omit(SessionPrompt.CommandInput.fields, ["sessionID"]))
- export const ShellPayload = Schema.Struct(Struct.omit(SessionPrompt.ShellInput.fields, ["sessionID"]))
- export const RevertPayload = Schema.Struct(Struct.omit(SessionRevert.RevertInput.fields, ["sessionID"]))
- export const PermissionResponsePayload = Schema.Struct({
- response: Permission.Reply,
- })
- 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: described(Schema.Array(Session.Info), "List of sessions"),
- }).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, {
- query: WorkspaceRoutingQuery,
- success: described(StatusMap, "Get session status"),
- error: HttpApiError.BadRequest,
- }).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 },
- query: WorkspaceRoutingQuery,
- success: described(Session.Info, "Get session"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- success: described(Schema.Array(Session.Info), "List of children"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- success: described(Schema.Array(Todo.Info), "Todo list"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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: described(Schema.Array(Snapshot.FileDiff), "Successfully retrieved diff"),
- }).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: described(Schema.Array(MessageV2.WithParts), "List of messages"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- success: described(MessageV2.WithParts, "Message"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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, {
- query: WorkspaceRoutingQuery,
- payload: [HttpApiSchema.NoContent, Session.CreateInput],
- success: described(Session.Info, "Successfully created session"),
- 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 },
- query: WorkspaceRoutingQuery,
- success: described(Schema.Boolean, "Successfully deleted session"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: UpdatePayload,
- success: described(Session.Info, "Successfully updated session"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: Schema.optional(ForkPayload),
- success: described(Session.Info, "200"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- success: described(Schema.Boolean, "Aborted session"),
- error: HttpApiError.BadRequest,
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: InitPayload,
- success: described(Schema.Boolean, "200"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- success: described(Session.Info, "Successfully shared session"),
- error: [HttpApiError.InternalServerError, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- success: described(Session.Info, "Successfully unshared session"),
- error: [HttpApiError.InternalServerError, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: SummarizePayload,
- success: described(Schema.Boolean, "Summarized session"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: PromptPayload,
- success: described(MessageV2.WithParts, "Created message"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: PromptPayload,
- success: described(HttpApiSchema.NoContent, "Prompt accepted"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: CommandPayload,
- success: described(MessageV2.WithParts, "Created message"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: ShellPayload,
- success: described(MessageV2.WithParts, "Created message"),
- error: [HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError],
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: RevertPayload,
- success: described(Session.Info, "Updated session"),
- error: [HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError],
- }).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 },
- query: WorkspaceRoutingQuery,
- success: described(Session.Info, "Updated session"),
- error: [HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError],
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: PermissionResponsePayload,
- success: described(Schema.Boolean, "Permission processed successfully"),
- error: [HttpApiError.BadRequest, ApiNotFoundError, PermissionNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- success: described(Schema.Boolean, "Successfully deleted message"),
- error: [HttpApiError.BadRequest, ApiNotFoundError, SessionBusyError],
- }).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 },
- query: WorkspaceRoutingQuery,
- success: described(Schema.Boolean, "Successfully deleted part"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).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 },
- query: WorkspaceRoutingQuery,
- payload: MessageV2.Part,
- success: described(MessageV2.Part, "Successfully updated part"),
- error: [HttpApiError.BadRequest, ApiNotFoundError],
- }).annotateMerge(
- OpenApi.annotations({
- identifier: "part.update",
- description: "Update a part in a message.",
- }),
- ),
- )
- .annotateMerge(
- OpenApi.annotations({
- title: "session",
- description: "Experimental HttpApi session routes.",
- }),
- )
- .middleware(InstanceContextMiddleware)
- .middleware(WorkspaceRoutingMiddleware)
- .middleware(Authorization),
- )
- .annotateMerge(
- OpenApi.annotations({
- title: "opencode experimental HttpApi",
- version: "0.0.1",
- description: "Experimental HttpApi surface for selected instance routes.",
- }),
- )
|