fix(httpapi): preserve SDK schema parity
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { Config } from "@/config/config"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const root = "/config"
|
||||
|
||||
@@ -11,7 +12,7 @@ export const ConfigApi = HttpApi.make("config")
|
||||
HttpApiGroup.make("config")
|
||||
.add(
|
||||
HttpApiEndpoint.get("get", root, {
|
||||
success: Config.Info,
|
||||
success: described(Config.Info, "Get config info"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "config.get",
|
||||
@@ -21,7 +22,8 @@ export const ConfigApi = HttpApi.make("config")
|
||||
),
|
||||
HttpApiEndpoint.patch("update", root, {
|
||||
payload: Config.Info,
|
||||
success: Config.Info,
|
||||
success: described(Config.Info, "Successfully updated config"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "config.update",
|
||||
@@ -30,7 +32,7 @@ export const ConfigApi = HttpApi.make("config")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("providers", `${root}/providers`, {
|
||||
success: Provider.ConfigProvidersResult,
|
||||
success: described(Provider.ConfigProvidersResult, "List of providers"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "config.providers",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Auth } from "@/auth"
|
||||
import { ProviderID } from "@/provider/schema"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const AuthParams = Schema.Struct({
|
||||
providerID: ProviderID,
|
||||
@@ -24,7 +25,7 @@ export const LogInput = Schema.Struct({
|
||||
extra: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)).annotate({
|
||||
description: "Additional metadata for the log entry",
|
||||
}),
|
||||
}).annotate({ identifier: "AppLogInput" })
|
||||
})
|
||||
|
||||
export const ControlPaths = {
|
||||
auth: "/auth/:providerID",
|
||||
@@ -37,7 +38,8 @@ export const ControlApi = HttpApi.make("control").add(
|
||||
HttpApiEndpoint.put("authSet", ControlPaths.auth, {
|
||||
params: AuthParams,
|
||||
payload: Auth.Info,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Successfully set authentication credentials"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "auth.set",
|
||||
@@ -47,7 +49,8 @@ export const ControlApi = HttpApi.make("control").add(
|
||||
),
|
||||
HttpApiEndpoint.delete("authRemove", ControlPaths.auth, {
|
||||
params: AuthParams,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Successfully removed authentication credentials"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "auth.remove",
|
||||
@@ -58,7 +61,8 @@ export const ControlApi = HttpApi.make("control").add(
|
||||
HttpApiEndpoint.post("log", ControlPaths.log, {
|
||||
query: LogQuery,
|
||||
payload: LogInput,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Log entry written successfully"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "app.log",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Schema, SchemaGetter } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const ConsoleStateResponse = Schema.Struct({
|
||||
consoleManagedProviders: Schema.mutable(Schema.Array(Schema.String)),
|
||||
@@ -22,22 +23,22 @@ const ConsoleOrgOption = Schema.Struct({
|
||||
orgID: Schema.String,
|
||||
orgName: Schema.String,
|
||||
active: Schema.Boolean,
|
||||
}).annotate({ identifier: "ConsoleOrgOption" })
|
||||
})
|
||||
|
||||
const ConsoleOrgList = Schema.Struct({
|
||||
orgs: Schema.Array(ConsoleOrgOption),
|
||||
}).annotate({ identifier: "ConsoleOrgList" })
|
||||
})
|
||||
|
||||
export const ConsoleSwitchPayload = Schema.Struct({
|
||||
accountID: AccountID,
|
||||
orgID: OrgID,
|
||||
}).annotate({ identifier: "ConsoleSwitchInput" })
|
||||
})
|
||||
|
||||
const ToolIDs = Schema.Array(Schema.String).annotate({ identifier: "ToolIDs" })
|
||||
const ToolListItem = Schema.Struct({
|
||||
id: Schema.String,
|
||||
description: Schema.String,
|
||||
parameters: Schema.Record(Schema.String, Schema.Any),
|
||||
parameters: Schema.Unknown,
|
||||
}).annotate({ identifier: "ToolListItem" })
|
||||
const ToolList = Schema.Array(ToolListItem).annotate({ identifier: "ToolList" })
|
||||
export const ToolListQuery = Schema.Struct({
|
||||
@@ -51,7 +52,7 @@ const QueryBoolean = Schema.Literals(["true", "false"]).pipe(
|
||||
encode: SchemaGetter.transform((value) => (value ? "true" : "false")),
|
||||
}),
|
||||
)
|
||||
const WorktreeList = Schema.Array(Schema.String).annotate({ identifier: "WorktreeList" })
|
||||
const WorktreeList = Schema.Array(Schema.String)
|
||||
export const SessionListQuery = Schema.Struct({
|
||||
directory: Schema.optional(Schema.String),
|
||||
roots: Schema.optional(QueryBoolean),
|
||||
@@ -79,7 +80,7 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
HttpApiGroup.make("experimental")
|
||||
.add(
|
||||
HttpApiEndpoint.get("console", ExperimentalPaths.console, {
|
||||
success: ConsoleStateResponse,
|
||||
success: described(ConsoleStateResponse, "Active Console provider metadata"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.console.get",
|
||||
@@ -88,7 +89,7 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("consoleOrgs", ExperimentalPaths.consoleOrgs, {
|
||||
success: ConsoleOrgList,
|
||||
success: described(ConsoleOrgList, "Switchable Console orgs"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.console.listOrgs",
|
||||
@@ -98,7 +99,7 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
),
|
||||
HttpApiEndpoint.post("consoleSwitch", ExperimentalPaths.consoleSwitch, {
|
||||
payload: ConsoleSwitchPayload,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Switch success"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -109,7 +110,8 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
),
|
||||
HttpApiEndpoint.get("tool", ExperimentalPaths.tool, {
|
||||
query: ToolListQuery,
|
||||
success: ToolList,
|
||||
success: described(ToolList, "Tools"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tool.list",
|
||||
@@ -119,7 +121,8 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("toolIDs", ExperimentalPaths.toolIDs, {
|
||||
success: ToolIDs,
|
||||
success: described(ToolIDs, "Tool IDs"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tool.ids",
|
||||
@@ -129,7 +132,7 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("worktree", ExperimentalPaths.worktree, {
|
||||
success: WorktreeList,
|
||||
success: described(WorktreeList, "List of worktree directories"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "worktree.list",
|
||||
@@ -139,7 +142,8 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
),
|
||||
HttpApiEndpoint.post("worktreeCreate", ExperimentalPaths.worktree, {
|
||||
payload: Schema.optional(Worktree.CreateInput),
|
||||
success: Worktree.Info,
|
||||
success: described(Worktree.Info, "Worktree created"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "worktree.create",
|
||||
@@ -149,7 +153,8 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
),
|
||||
HttpApiEndpoint.delete("worktreeRemove", ExperimentalPaths.worktree, {
|
||||
payload: Worktree.RemoveInput,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Worktree removed"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "worktree.remove",
|
||||
@@ -159,7 +164,8 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
),
|
||||
HttpApiEndpoint.post("worktreeReset", ExperimentalPaths.worktreeReset, {
|
||||
payload: Worktree.ResetInput,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Worktree reset"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "worktree.reset",
|
||||
@@ -169,7 +175,7 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
),
|
||||
HttpApiEndpoint.get("session", ExperimentalPaths.session, {
|
||||
query: SessionListQuery,
|
||||
success: Schema.Array(Session.GlobalInfo),
|
||||
success: described(Schema.Array(Session.GlobalInfo), "List of sessions"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.session.list",
|
||||
@@ -179,7 +185,7 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("resource", ExperimentalPaths.resource, {
|
||||
success: Schema.Record(Schema.String, MCP.Resource),
|
||||
success: described(Schema.Record(Schema.String, MCP.Resource), "MCP resources"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.resource.list",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
export const FileQuery = Schema.Struct({
|
||||
path: Schema.String,
|
||||
@@ -42,7 +43,7 @@ export const FileApi = HttpApi.make("file")
|
||||
.add(
|
||||
HttpApiEndpoint.get("findText", FilePaths.findText, {
|
||||
query: FindTextQuery,
|
||||
success: Schema.Array(Ripgrep.SearchMatch),
|
||||
success: described(Schema.Array(Ripgrep.SearchMatch), "Matches"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "find.text",
|
||||
@@ -52,7 +53,7 @@ export const FileApi = HttpApi.make("file")
|
||||
),
|
||||
HttpApiEndpoint.get("findFile", FilePaths.findFile, {
|
||||
query: FindFileQuery,
|
||||
success: Schema.Array(Schema.String),
|
||||
success: described(Schema.Array(Schema.String), "File paths"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "find.files",
|
||||
@@ -62,7 +63,7 @@ export const FileApi = HttpApi.make("file")
|
||||
),
|
||||
HttpApiEndpoint.get("findSymbol", FilePaths.findSymbol, {
|
||||
query: FindSymbolQuery,
|
||||
success: Schema.Array(LSP.Symbol),
|
||||
success: described(Schema.Array(LSP.Symbol), "Symbols"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "find.symbols",
|
||||
@@ -72,7 +73,7 @@ export const FileApi = HttpApi.make("file")
|
||||
),
|
||||
HttpApiEndpoint.get("list", FilePaths.list, {
|
||||
query: FileQuery,
|
||||
success: Schema.Array(File.Node),
|
||||
success: described(Schema.Array(File.Node), "Files and directories"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "file.list",
|
||||
@@ -82,7 +83,7 @@ export const FileApi = HttpApi.make("file")
|
||||
),
|
||||
HttpApiEndpoint.get("content", FilePaths.content, {
|
||||
query: FileQuery,
|
||||
success: File.Content,
|
||||
success: described(File.Content, "File content"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "file.read",
|
||||
@@ -91,7 +92,7 @@ export const FileApi = HttpApi.make("file")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("status", FilePaths.status, {
|
||||
success: Schema.Array(File.Info),
|
||||
success: described(Schema.Array(File.Info), "File status"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "file.status",
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import { Config } from "@/config/config"
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { SyncEvent } from "@/sync"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const GlobalHealth = Schema.Struct({
|
||||
healthy: Schema.Literal(true),
|
||||
version: Schema.String,
|
||||
}).annotate({ identifier: "GlobalHealth" })
|
||||
})
|
||||
|
||||
const GlobalEventSchema = Schema.Struct({
|
||||
directory: Schema.String,
|
||||
project: Schema.optional(Schema.String),
|
||||
workspace: Schema.optional(Schema.String),
|
||||
payload: Schema.Unknown,
|
||||
payload: Schema.Union([...BusEvent.effectPayloads(), ...SyncEvent.effectPayloads()]),
|
||||
}).annotate({ identifier: "GlobalEvent" })
|
||||
|
||||
export const GlobalUpgradeInput = Schema.Struct({
|
||||
target: Schema.optional(Schema.String),
|
||||
}).annotate({ identifier: "GlobalUpgradeInput" })
|
||||
})
|
||||
|
||||
const GlobalUpgradeResult = Schema.Union([
|
||||
Schema.Struct({
|
||||
@@ -27,7 +30,7 @@ const GlobalUpgradeResult = Schema.Union([
|
||||
success: Schema.Literal(false),
|
||||
error: Schema.String,
|
||||
}),
|
||||
]).annotate({ identifier: "GlobalUpgradeResult" })
|
||||
])
|
||||
|
||||
export const GlobalPaths = {
|
||||
health: "/global/health",
|
||||
@@ -41,7 +44,7 @@ export const GlobalApi = HttpApi.make("global").add(
|
||||
HttpApiGroup.make("global")
|
||||
.add(
|
||||
HttpApiEndpoint.get("health", GlobalPaths.health, {
|
||||
success: GlobalHealth,
|
||||
success: described(GlobalHealth, "Health information"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "global.health",
|
||||
@@ -59,7 +62,7 @@ export const GlobalApi = HttpApi.make("global").add(
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("configGet", GlobalPaths.config, {
|
||||
success: Config.Info,
|
||||
success: described(Config.Info, "Get global config info"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "global.config.get",
|
||||
@@ -69,7 +72,8 @@ export const GlobalApi = HttpApi.make("global").add(
|
||||
),
|
||||
HttpApiEndpoint.patch("configUpdate", GlobalPaths.config, {
|
||||
payload: Config.Info,
|
||||
success: Config.Info,
|
||||
success: described(Config.Info, "Successfully updated global config"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "global.config.update",
|
||||
@@ -78,7 +82,7 @@ export const GlobalApi = HttpApi.make("global").add(
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("dispose", GlobalPaths.dispose, {
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Global disposed"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "global.dispose",
|
||||
@@ -88,7 +92,8 @@ export const GlobalApi = HttpApi.make("global").add(
|
||||
),
|
||||
HttpApiEndpoint.post("upgrade", GlobalPaths.upgrade, {
|
||||
payload: GlobalUpgradeInput,
|
||||
success: GlobalUpgradeResult,
|
||||
success: described(GlobalUpgradeResult, "Upgrade result"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "global.upgrade",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const PathInfo = Schema.Struct({
|
||||
home: Schema.String,
|
||||
@@ -38,7 +39,7 @@ export const InstanceApi = HttpApi.make("instance")
|
||||
HttpApiGroup.make("instance")
|
||||
.add(
|
||||
HttpApiEndpoint.post("dispose", InstancePaths.dispose, {
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Instance disposed"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "instance.dispose",
|
||||
@@ -57,7 +58,7 @@ export const InstanceApi = HttpApi.make("instance")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("vcs", InstancePaths.vcs, {
|
||||
success: Vcs.Info,
|
||||
success: described(Vcs.Info, "VCS info"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "vcs.get",
|
||||
@@ -68,7 +69,7 @@ export const InstanceApi = HttpApi.make("instance")
|
||||
),
|
||||
HttpApiEndpoint.get("vcsDiff", InstancePaths.vcsDiff, {
|
||||
query: VcsDiffQuery,
|
||||
success: Schema.Array(Vcs.FileDiff),
|
||||
success: described(Schema.Array(Vcs.FileDiff), "VCS diff"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "vcs.diff",
|
||||
@@ -77,7 +78,7 @@ export const InstanceApi = HttpApi.make("instance")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("command", InstancePaths.command, {
|
||||
success: Schema.Array(Command.Info),
|
||||
success: described(Schema.Array(Command.Info), "List of commands"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "command.list",
|
||||
@@ -86,7 +87,7 @@ export const InstanceApi = HttpApi.make("instance")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("agent", InstancePaths.agent, {
|
||||
success: Schema.Array(Agent.Info),
|
||||
success: described(Schema.Array(Agent.Info), "List of agents"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "app.agents",
|
||||
@@ -95,7 +96,7 @@ export const InstanceApi = HttpApi.make("instance")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("skill", InstancePaths.skill, {
|
||||
success: Schema.Array(Skill.Info),
|
||||
success: described(Schema.Array(Skill.Info), "List of skills"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "app.skills",
|
||||
@@ -104,7 +105,7 @@ export const InstanceApi = HttpApi.make("instance")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("lsp", InstancePaths.lsp, {
|
||||
success: Schema.Array(LSP.Status),
|
||||
success: described(Schema.Array(LSP.Status), "LSP server status"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "lsp.status",
|
||||
@@ -113,7 +114,7 @@ export const InstanceApi = HttpApi.make("instance")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("formatter", InstancePaths.formatter, {
|
||||
success: Schema.Array(Format.Status),
|
||||
success: described(Schema.Array(Format.Status), "Formatter status"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "formatter.status",
|
||||
|
||||
@@ -4,23 +4,23 @@ import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
export const AddPayload = Schema.Struct({
|
||||
name: Schema.String,
|
||||
config: ConfigMCP.Info,
|
||||
}).annotate({ identifier: "McpAddInput" })
|
||||
})
|
||||
|
||||
export const StatusMap = Schema.Record(Schema.String, MCP.Status)
|
||||
export const AuthStartResponse = Schema.Struct({
|
||||
authorizationUrl: Schema.String,
|
||||
oauthState: Schema.String,
|
||||
}).annotate({ identifier: "McpAuthStartResponse" })
|
||||
})
|
||||
export const AuthCallbackPayload = Schema.Struct({
|
||||
code: Schema.String,
|
||||
}).annotate({ identifier: "McpAuthCallbackInput" })
|
||||
})
|
||||
export const AuthRemoveResponse = Schema.Struct({
|
||||
success: Schema.Literal(true),
|
||||
}).annotate({ identifier: "McpAuthRemoveResponse" })
|
||||
})
|
||||
export class UnsupportedOAuthError extends Schema.ErrorClass<UnsupportedOAuthError>("McpUnsupportedOAuthError")(
|
||||
{ error: Schema.String },
|
||||
{ httpApiStatus: 400 },
|
||||
@@ -40,7 +40,7 @@ export const McpApi = HttpApi.make("mcp")
|
||||
HttpApiGroup.make("mcp")
|
||||
.add(
|
||||
HttpApiEndpoint.get("status", McpPaths.status, {
|
||||
success: Schema.Record(Schema.String, MCP.Status),
|
||||
success: described(Schema.Record(Schema.String, MCP.Status), "MCP server status"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "mcp.status",
|
||||
@@ -50,7 +50,7 @@ export const McpApi = HttpApi.make("mcp")
|
||||
),
|
||||
HttpApiEndpoint.post("add", McpPaths.status, {
|
||||
payload: AddPayload,
|
||||
success: StatusMap,
|
||||
success: described(StatusMap, "MCP server added successfully"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -61,8 +61,8 @@ export const McpApi = HttpApi.make("mcp")
|
||||
),
|
||||
HttpApiEndpoint.post("authStart", McpPaths.auth, {
|
||||
params: { name: Schema.String },
|
||||
success: AuthStartResponse,
|
||||
error: UnsupportedOAuthError,
|
||||
success: described(AuthStartResponse, "OAuth flow started"),
|
||||
error: [UnsupportedOAuthError, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "mcp.auth.start",
|
||||
@@ -73,7 +73,8 @@ export const McpApi = HttpApi.make("mcp")
|
||||
HttpApiEndpoint.post("authCallback", McpPaths.authCallback, {
|
||||
params: { name: Schema.String },
|
||||
payload: AuthCallbackPayload,
|
||||
success: MCP.Status,
|
||||
success: described(MCP.Status, "OAuth authentication completed"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "mcp.auth.callback",
|
||||
@@ -84,8 +85,8 @@ export const McpApi = HttpApi.make("mcp")
|
||||
),
|
||||
HttpApiEndpoint.post("authAuthenticate", McpPaths.authAuthenticate, {
|
||||
params: { name: Schema.String },
|
||||
success: MCP.Status,
|
||||
error: UnsupportedOAuthError,
|
||||
success: described(MCP.Status, "OAuth authentication completed"),
|
||||
error: [UnsupportedOAuthError, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "mcp.auth.authenticate",
|
||||
@@ -95,7 +96,8 @@ export const McpApi = HttpApi.make("mcp")
|
||||
),
|
||||
HttpApiEndpoint.delete("authRemove", McpPaths.auth, {
|
||||
params: { name: Schema.String },
|
||||
success: AuthRemoveResponse,
|
||||
success: described(AuthRemoveResponse, "OAuth credentials removed"),
|
||||
error: HttpApiError.NotFound,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "mcp.auth.remove",
|
||||
@@ -105,7 +107,7 @@ export const McpApi = HttpApi.make("mcp")
|
||||
),
|
||||
HttpApiEndpoint.post("connect", McpPaths.connect, {
|
||||
params: { name: Schema.String },
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "MCP server connected successfully"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "mcp.connect",
|
||||
@@ -114,7 +116,7 @@ export const McpApi = HttpApi.make("mcp")
|
||||
),
|
||||
HttpApiEndpoint.post("disconnect", McpPaths.disconnect, {
|
||||
params: { name: Schema.String },
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "MCP server disconnected successfully"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "mcp.disconnect",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Schema } from "effect"
|
||||
import { OpenApi } from "effect/unstable/httpapi"
|
||||
|
||||
export function described<S extends Schema.Top>(schema: S, description: string): S {
|
||||
return schema.annotate({ description }) as S
|
||||
}
|
||||
|
||||
export function responseDescription(description: string) {
|
||||
return OpenApi.annotations({
|
||||
transform: (operation) => {
|
||||
const response = operation.responses?.["200"]
|
||||
if (response && typeof response === "object" && "description" in response) {
|
||||
response.description = description
|
||||
}
|
||||
return operation
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,18 +1,23 @@
|
||||
import { Permission } from "@/permission"
|
||||
import { PermissionID } from "@/permission/schema"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const root = "/permission"
|
||||
const ReplyPayload = Schema.Struct({
|
||||
reply: Permission.Reply,
|
||||
message: Schema.optional(Schema.String),
|
||||
})
|
||||
|
||||
export const PermissionApi = HttpApi.make("permission")
|
||||
.add(
|
||||
HttpApiGroup.make("permission")
|
||||
.add(
|
||||
HttpApiEndpoint.get("list", root, {
|
||||
success: Schema.Array(Permission.Request),
|
||||
success: described(Schema.Array(Permission.Request), "List of pending permissions"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "permission.list",
|
||||
@@ -22,8 +27,9 @@ export const PermissionApi = HttpApi.make("permission")
|
||||
),
|
||||
HttpApiEndpoint.post("reply", `${root}/:requestID/reply`, {
|
||||
params: { requestID: PermissionID },
|
||||
payload: Permission.ReplyBody,
|
||||
success: Schema.Boolean,
|
||||
payload: ReplyPayload,
|
||||
success: described(Schema.Boolean, "Permission processed successfully"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "permission.reply",
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { Project } from "@/project/project"
|
||||
import { ProjectID } from "@/project/schema"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const root = "/project"
|
||||
const UpdatePayload = Schema.Struct({
|
||||
name: Schema.optional(Schema.String),
|
||||
icon: Schema.optional(Project.Info.fields.icon),
|
||||
commands: Schema.optional(Project.Info.fields.commands),
|
||||
})
|
||||
|
||||
export const ProjectApi = HttpApi.make("project")
|
||||
.add(
|
||||
HttpApiGroup.make("project")
|
||||
.add(
|
||||
HttpApiEndpoint.get("list", root, {
|
||||
success: Schema.Array(Project.Info),
|
||||
success: described(Schema.Array(Project.Info), "List of projects"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "project.list",
|
||||
@@ -21,7 +27,7 @@ export const ProjectApi = HttpApi.make("project")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("current", `${root}/current`, {
|
||||
success: Project.Info,
|
||||
success: described(Project.Info, "Current project information"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "project.current",
|
||||
@@ -30,7 +36,7 @@ export const ProjectApi = HttpApi.make("project")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("initGit", `${root}/git/init`, {
|
||||
success: Project.Info,
|
||||
success: described(Project.Info, "Project information after git initialization"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "project.initGit",
|
||||
@@ -40,8 +46,9 @@ export const ProjectApi = HttpApi.make("project")
|
||||
),
|
||||
HttpApiEndpoint.patch("update", `${root}/:projectID`, {
|
||||
params: { projectID: ProjectID },
|
||||
payload: Project.UpdatePayload,
|
||||
success: Project.Info,
|
||||
payload: UpdatePayload,
|
||||
success: described(Project.Info, "Updated project information"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "project.update",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const root = "/provider"
|
||||
|
||||
@@ -13,7 +14,7 @@ export const ProviderApi = HttpApi.make("provider")
|
||||
HttpApiGroup.make("provider")
|
||||
.add(
|
||||
HttpApiEndpoint.get("list", root, {
|
||||
success: Provider.ListResult,
|
||||
success: described(Provider.ListResult, "List of providers"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "provider.list",
|
||||
@@ -22,7 +23,7 @@ export const ProviderApi = HttpApi.make("provider")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("auth", `${root}/auth`, {
|
||||
success: ProviderAuth.Methods,
|
||||
success: described(ProviderAuth.Methods, "Provider auth methods"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "provider.auth",
|
||||
@@ -33,7 +34,7 @@ export const ProviderApi = HttpApi.make("provider")
|
||||
HttpApiEndpoint.post("authorize", `${root}/:providerID/oauth/authorize`, {
|
||||
params: { providerID: ProviderID },
|
||||
payload: ProviderAuth.AuthorizeInput,
|
||||
success: Schema.UndefinedOr(ProviderAuth.Authorization),
|
||||
success: described(Schema.UndefinedOr(ProviderAuth.Authorization), "Authorization URL and method"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -45,7 +46,7 @@ export const ProviderApi = HttpApi.make("provider")
|
||||
HttpApiEndpoint.post("callback", `${root}/:providerID/oauth/callback`, {
|
||||
params: { providerID: ProviderID },
|
||||
payload: ProviderAuth.CallbackInput,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "OAuth callback processed successfully"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const root = "/pty"
|
||||
export const Params = Schema.Struct({ ptyID: PtyID })
|
||||
@@ -28,21 +29,25 @@ export const PtyApi = HttpApi.make("pty")
|
||||
.add(
|
||||
HttpApiGroup.make("pty")
|
||||
.add(
|
||||
HttpApiEndpoint.get("shells", PtyPaths.shells, { success: Schema.Array(ShellItem) }).annotateMerge(
|
||||
HttpApiEndpoint.get("shells", PtyPaths.shells, { success: described(Schema.Array(ShellItem), "List of shells") }).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "pty.shells",
|
||||
summary: "List available shells",
|
||||
description: "Get a list of available shells on the system.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("list", PtyPaths.list, { success: Schema.Array(Pty.Info) }).annotateMerge(
|
||||
HttpApiEndpoint.get("list", PtyPaths.list, { success: described(Schema.Array(Pty.Info), "List of sessions") }).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "pty.list",
|
||||
summary: "List PTY sessions",
|
||||
description: "Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("create", PtyPaths.create, { payload: Pty.CreateInput, success: Pty.Info }).annotateMerge(
|
||||
HttpApiEndpoint.post("create", PtyPaths.create, {
|
||||
payload: Pty.CreateInput,
|
||||
success: described(Pty.Info, "Created session"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "pty.create",
|
||||
summary: "Create PTY session",
|
||||
@@ -51,7 +56,7 @@ export const PtyApi = HttpApi.make("pty")
|
||||
),
|
||||
HttpApiEndpoint.get("get", PtyPaths.get, {
|
||||
params: { ptyID: PtyID },
|
||||
success: Pty.Info,
|
||||
success: described(Pty.Info, "Session info"),
|
||||
error: HttpApiError.NotFound,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -63,8 +68,8 @@ export const PtyApi = HttpApi.make("pty")
|
||||
HttpApiEndpoint.put("update", PtyPaths.update, {
|
||||
params: { ptyID: PtyID },
|
||||
payload: Pty.UpdateInput,
|
||||
success: Pty.Info,
|
||||
error: HttpApiError.NotFound,
|
||||
success: described(Pty.Info, "Updated session"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "pty.update",
|
||||
@@ -74,7 +79,8 @@ export const PtyApi = HttpApi.make("pty")
|
||||
),
|
||||
HttpApiEndpoint.delete("remove", PtyPaths.remove, {
|
||||
params: { ptyID: PtyID },
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Session removed"),
|
||||
error: HttpApiError.NotFound,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "pty.remove",
|
||||
@@ -98,7 +104,11 @@ export const PtyApi = HttpApi.make("pty")
|
||||
export const PtyConnectApi = HttpApi.make("pty-connect").add(
|
||||
HttpApiGroup.make("pty-connect")
|
||||
.add(
|
||||
HttpApiEndpoint.get("connect", PtyPaths.connect, { params: Params, success: Schema.Boolean }).annotateMerge(
|
||||
HttpApiEndpoint.get("connect", PtyPaths.connect, {
|
||||
params: Params,
|
||||
success: described(Schema.Boolean, "Connected session"),
|
||||
error: HttpApiError.NotFound,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "pty.connect",
|
||||
summary: "Connect to PTY session",
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { Question } from "@/question"
|
||||
import { QuestionID } from "@/question/schema"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const root = "/question"
|
||||
const ReplyPayload = Schema.Struct({
|
||||
answers: Schema.Array(Question.Answer).annotate({
|
||||
description: "User answers in order of questions (each answer is an array of selected labels)",
|
||||
}),
|
||||
})
|
||||
|
||||
export const QuestionApi = HttpApi.make("question")
|
||||
.add(
|
||||
HttpApiGroup.make("question")
|
||||
.add(
|
||||
HttpApiEndpoint.get("list", root, {
|
||||
success: Schema.Array(Question.Request),
|
||||
success: described(Schema.Array(Question.Request), "List of pending questions"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "question.list",
|
||||
@@ -22,8 +28,9 @@ export const QuestionApi = HttpApi.make("question")
|
||||
),
|
||||
HttpApiEndpoint.post("reply", `${root}/:requestID/reply`, {
|
||||
params: { requestID: QuestionID },
|
||||
payload: Question.Reply,
|
||||
success: Schema.Boolean,
|
||||
payload: ReplyPayload,
|
||||
success: described(Schema.Boolean, "Question answered successfully"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "question.reply",
|
||||
@@ -33,7 +40,8 @@ export const QuestionApi = HttpApi.make("question")
|
||||
),
|
||||
HttpApiEndpoint.post("reject", `${root}/:requestID/reject`, {
|
||||
params: { requestID: QuestionID },
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Question rejected successfully"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "question.reject",
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Schema, SchemaGetter, Struct } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const root = "/session"
|
||||
const QueryBoolean = Schema.Literals(["true", "false"]).pipe(
|
||||
@@ -46,35 +47,25 @@ export const UpdatePayload = Schema.Struct({
|
||||
archived: Schema.optional(NonNegativeInt),
|
||||
}),
|
||||
),
|
||||
}).annotate({ identifier: "SessionUpdateInput" })
|
||||
export const ForkPayload = Schema.Struct(Struct.omit(Session.ForkInput.fields, ["sessionID"])).annotate({
|
||||
identifier: "SessionForkInput",
|
||||
})
|
||||
export const ForkPayload = Schema.Struct(Struct.omit(Session.ForkInput.fields, ["sessionID"]))
|
||||
export const InitPayload = Schema.Struct({
|
||||
modelID: ModelID,
|
||||
providerID: ProviderID,
|
||||
messageID: MessageID,
|
||||
}).annotate({ identifier: "SessionInitInput" })
|
||||
})
|
||||
export const SummarizePayload = Schema.Struct({
|
||||
providerID: ProviderID,
|
||||
modelID: ModelID,
|
||||
auto: Schema.optional(Schema.Boolean),
|
||||
}).annotate({ identifier: "SessionSummarizeInput" })
|
||||
export const PromptPayload = Schema.Struct(Struct.omit(SessionPrompt.PromptInput.fields, ["sessionID"])).annotate({
|
||||
identifier: "SessionPromptInput",
|
||||
})
|
||||
export const CommandPayload = Schema.Struct(Struct.omit(SessionPrompt.CommandInput.fields, ["sessionID"])).annotate({
|
||||
identifier: "SessionCommandInput",
|
||||
})
|
||||
export const ShellPayload = Schema.Struct(Struct.omit(SessionPrompt.ShellInput.fields, ["sessionID"])).annotate({
|
||||
identifier: "SessionShellInput",
|
||||
})
|
||||
export const RevertPayload = Schema.Struct(Struct.omit(SessionRevert.RevertInput.fields, ["sessionID"])).annotate({
|
||||
identifier: "SessionRevertInput",
|
||||
})
|
||||
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,
|
||||
}).annotate({ identifier: "SessionPermissionResponseInput" })
|
||||
})
|
||||
|
||||
export const SessionPaths = {
|
||||
list: root,
|
||||
@@ -111,7 +102,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
.add(
|
||||
HttpApiEndpoint.get("list", SessionPaths.list, {
|
||||
query: ListQuery,
|
||||
success: Schema.Array(Session.Info),
|
||||
success: described(Schema.Array(Session.Info), "List of sessions"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.list",
|
||||
@@ -120,7 +111,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("status", SessionPaths.status, {
|
||||
success: StatusMap,
|
||||
success: described(StatusMap, "Get session status"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.status",
|
||||
@@ -130,8 +122,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.get("get", SessionPaths.get, {
|
||||
params: { sessionID: SessionID },
|
||||
success: Session.Info,
|
||||
error: HttpApiError.NotFound,
|
||||
success: described(Session.Info, "Get session"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.get",
|
||||
@@ -141,7 +133,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.get("children", SessionPaths.children, {
|
||||
params: { sessionID: SessionID },
|
||||
success: Schema.Array(Session.Info),
|
||||
success: described(Schema.Array(Session.Info), "List of children"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.children",
|
||||
@@ -151,7 +144,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.get("todo", SessionPaths.todo, {
|
||||
params: { sessionID: SessionID },
|
||||
success: Schema.Array(Todo.Info),
|
||||
success: described(Schema.Array(Todo.Info), "Todo list"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.todo",
|
||||
@@ -162,7 +156,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.get("diff", SessionPaths.diff, {
|
||||
params: { sessionID: SessionID },
|
||||
query: DiffQuery,
|
||||
success: Schema.Array(Snapshot.FileDiff),
|
||||
success: described(Schema.Array(Snapshot.FileDiff), "Successfully retrieved diff"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.diff",
|
||||
@@ -173,7 +167,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.get("messages", SessionPaths.messages, {
|
||||
params: { sessionID: SessionID },
|
||||
query: MessagesQuery,
|
||||
success: Schema.Array(MessageV2.WithParts),
|
||||
success: described(Schema.Array(MessageV2.WithParts), "List of messages"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -184,8 +178,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.get("message", SessionPaths.message, {
|
||||
params: { sessionID: SessionID, messageID: MessageID },
|
||||
success: MessageV2.WithParts,
|
||||
error: HttpApiError.NotFound,
|
||||
success: described(MessageV2.WithParts, "Message"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.message",
|
||||
@@ -195,7 +189,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.post("create", SessionPaths.create, {
|
||||
payload: [HttpApiSchema.NoContent, Session.CreateInput],
|
||||
success: Session.Info,
|
||||
success: described(Session.Info, "Successfully created session"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -206,7 +200,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.delete("remove", SessionPaths.remove, {
|
||||
params: { sessionID: SessionID },
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Successfully deleted session"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.delete",
|
||||
@@ -217,7 +212,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.patch("update", SessionPaths.update, {
|
||||
params: { sessionID: SessionID },
|
||||
payload: UpdatePayload,
|
||||
success: Session.Info,
|
||||
success: described(Session.Info, "Successfully updated session"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.update",
|
||||
@@ -228,7 +224,7 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.post("fork", SessionPaths.fork, {
|
||||
params: { sessionID: SessionID },
|
||||
payload: ForkPayload,
|
||||
success: Session.Info,
|
||||
success: described(Session.Info, "200"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.fork",
|
||||
@@ -238,7 +234,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.post("abort", SessionPaths.abort, {
|
||||
params: { sessionID: SessionID },
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Aborted session"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.abort",
|
||||
@@ -249,7 +246,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.post("init", SessionPaths.init, {
|
||||
params: { sessionID: SessionID },
|
||||
payload: InitPayload,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "200"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.init",
|
||||
@@ -260,7 +258,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.post("share", SessionPaths.share, {
|
||||
params: { sessionID: SessionID },
|
||||
success: Session.Info,
|
||||
success: described(Session.Info, "Successfully shared session"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.share",
|
||||
@@ -270,7 +269,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.delete("unshare", SessionPaths.share, {
|
||||
params: { sessionID: SessionID },
|
||||
success: Session.Info,
|
||||
success: described(Session.Info, "Successfully unshared session"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.unshare",
|
||||
@@ -281,7 +281,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.post("summarize", SessionPaths.summarize, {
|
||||
params: { sessionID: SessionID },
|
||||
payload: SummarizePayload,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Summarized session"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.summarize",
|
||||
@@ -292,7 +293,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.post("prompt", SessionPaths.prompt, {
|
||||
params: { sessionID: SessionID },
|
||||
payload: PromptPayload,
|
||||
success: MessageV2.WithParts,
|
||||
success: described(MessageV2.WithParts, "Created message"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.prompt",
|
||||
@@ -303,7 +305,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.post("promptAsync", SessionPaths.promptAsync, {
|
||||
params: { sessionID: SessionID },
|
||||
payload: PromptPayload,
|
||||
success: HttpApiSchema.NoContent,
|
||||
success: described(HttpApiSchema.NoContent, "Prompt accepted"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.prompt_async",
|
||||
@@ -315,7 +318,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.post("command", SessionPaths.command, {
|
||||
params: { sessionID: SessionID },
|
||||
payload: CommandPayload,
|
||||
success: MessageV2.WithParts,
|
||||
success: described(MessageV2.WithParts, "Created message"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.command",
|
||||
@@ -326,7 +330,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.post("shell", SessionPaths.shell, {
|
||||
params: { sessionID: SessionID },
|
||||
payload: ShellPayload,
|
||||
success: MessageV2.WithParts,
|
||||
success: described(MessageV2.WithParts, "Created message"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.shell",
|
||||
@@ -337,7 +342,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.post("revert", SessionPaths.revert, {
|
||||
params: { sessionID: SessionID },
|
||||
payload: RevertPayload,
|
||||
success: Session.Info,
|
||||
success: described(Session.Info, "Updated session"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.revert",
|
||||
@@ -348,7 +354,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.post("unrevert", SessionPaths.unrevert, {
|
||||
params: { sessionID: SessionID },
|
||||
success: Session.Info,
|
||||
success: described(Session.Info, "Updated session"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.unrevert",
|
||||
@@ -359,7 +366,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.post("permissionRespond", SessionPaths.permissions, {
|
||||
params: { sessionID: SessionID, permissionID: PermissionID },
|
||||
payload: PermissionResponsePayload,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Permission processed successfully"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "permission.respond",
|
||||
@@ -370,7 +378,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.delete("deleteMessage", SessionPaths.deleteMessage, {
|
||||
params: { sessionID: SessionID, messageID: MessageID },
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Successfully deleted message"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.deleteMessage",
|
||||
@@ -381,7 +390,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
),
|
||||
HttpApiEndpoint.delete("deletePart", SessionPaths.deletePart, {
|
||||
params: { sessionID: SessionID, messageID: MessageID, partID: PartID },
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Successfully deleted part"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "part.delete",
|
||||
@@ -391,7 +401,8 @@ export const SessionApi = HttpApi.make("session")
|
||||
HttpApiEndpoint.patch("updatePart", SessionPaths.updatePart, {
|
||||
params: { sessionID: SessionID, messageID: MessageID, partID: PartID },
|
||||
payload: MessageV2.Part,
|
||||
success: MessageV2.Part,
|
||||
success: described(MessageV2.Part, "Successfully updated part"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "part.update",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const root = "/sync"
|
||||
export const ReplayEvent = Schema.Struct({
|
||||
@@ -11,14 +12,14 @@ export const ReplayEvent = Schema.Struct({
|
||||
seq: NonNegativeInt,
|
||||
type: Schema.String,
|
||||
data: Schema.Record(Schema.String, Schema.Unknown),
|
||||
}).annotate({ identifier: "SyncReplayEvent" })
|
||||
})
|
||||
export const ReplayPayload = Schema.Struct({
|
||||
directory: Schema.String,
|
||||
events: Schema.NonEmptyArray(ReplayEvent),
|
||||
}).annotate({ identifier: "SyncReplayInput" })
|
||||
})
|
||||
export const ReplayResponse = Schema.Struct({
|
||||
sessionID: Schema.String,
|
||||
}).annotate({ identifier: "SyncReplayResponse" })
|
||||
})
|
||||
export const HistoryPayload = Schema.Record(Schema.String, NonNegativeInt)
|
||||
export const HistoryEvent = Schema.Struct({
|
||||
id: Schema.String,
|
||||
@@ -26,7 +27,7 @@ export const HistoryEvent = Schema.Struct({
|
||||
seq: NonNegativeInt,
|
||||
type: Schema.String,
|
||||
data: Schema.Record(Schema.String, Schema.Unknown),
|
||||
}).annotate({ identifier: "SyncHistoryEvent" })
|
||||
})
|
||||
|
||||
export const SyncPaths = {
|
||||
start: `${root}/start`,
|
||||
@@ -39,7 +40,7 @@ export const SyncApi = HttpApi.make("sync")
|
||||
HttpApiGroup.make("sync")
|
||||
.add(
|
||||
HttpApiEndpoint.post("start", SyncPaths.start, {
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Workspace sync started"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "sync.start",
|
||||
@@ -49,7 +50,7 @@ export const SyncApi = HttpApi.make("sync")
|
||||
),
|
||||
HttpApiEndpoint.post("replay", SyncPaths.replay, {
|
||||
payload: ReplayPayload,
|
||||
success: ReplayResponse,
|
||||
success: described(ReplayResponse, "Replayed sync events"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -60,7 +61,7 @@ export const SyncApi = HttpApi.make("sync")
|
||||
),
|
||||
HttpApiEndpoint.post("history", SyncPaths.history, {
|
||||
payload: HistoryPayload,
|
||||
success: Schema.Array(HistoryEvent),
|
||||
success: described(Schema.Array(HistoryEvent), "Sync events"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -87,4 +88,3 @@ export const SyncApi = HttpApi.make("sync")
|
||||
description: "Experimental HttpApi surface for selected instance routes.",
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -3,18 +3,19 @@ import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const root = "/tui"
|
||||
export const CommandPayload = Schema.Struct({ command: Schema.String }).annotate({ identifier: "TuiCommandInput" })
|
||||
export const CommandPayload = Schema.Struct({ command: Schema.String })
|
||||
export const TuiRequestPayload = Schema.Struct({
|
||||
path: Schema.String,
|
||||
body: Schema.Unknown,
|
||||
}).annotate({ identifier: "TuiRequest" })
|
||||
})
|
||||
const EventTuiPromptAppend = Schema.Struct({ type: Schema.Literal(TuiEvent.PromptAppend.type), properties: TuiEvent.PromptAppend.properties }).annotate({ identifier: "EventTuiPromptAppend" })
|
||||
const EventTuiCommandExecute = Schema.Struct({ type: Schema.Literal(TuiEvent.CommandExecute.type), properties: TuiEvent.CommandExecute.properties }).annotate({ identifier: "EventTuiCommandExecute" })
|
||||
const EventTuiToastShow = Schema.Struct({ type: Schema.Literal(TuiEvent.ToastShow.type), properties: TuiEvent.ToastShow.properties }).annotate({ identifier: "EventTuiToastShow" })
|
||||
const EventTuiSessionSelect = Schema.Struct({ type: Schema.Literal(TuiEvent.SessionSelect.type), properties: TuiEvent.SessionSelect.properties }).annotate({ identifier: "EventTuiSessionSelect" })
|
||||
export const TuiPublishPayload = Schema.Union([EventTuiPromptAppend, EventTuiCommandExecute, EventTuiToastShow, EventTuiSessionSelect]).annotate({ identifier: "TuiEventInput" })
|
||||
export const TuiPublishPayload = Schema.Union([EventTuiPromptAppend, EventTuiCommandExecute, EventTuiToastShow, EventTuiSessionSelect])
|
||||
|
||||
export const TuiPaths = {
|
||||
appendPrompt: `${root}/append-prompt`,
|
||||
@@ -38,7 +39,7 @@ export const TuiApi = HttpApi.make("tui")
|
||||
.add(
|
||||
HttpApiEndpoint.post("appendPrompt", TuiPaths.appendPrompt, {
|
||||
payload: TuiEvent.PromptAppend.properties,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Prompt processed successfully"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -47,42 +48,42 @@ export const TuiApi = HttpApi.make("tui")
|
||||
description: "Append prompt to the TUI.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("openHelp", TuiPaths.openHelp, { success: Schema.Boolean }).annotateMerge(
|
||||
HttpApiEndpoint.post("openHelp", TuiPaths.openHelp, { success: described(Schema.Boolean, "Help dialog opened successfully") }).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tui.openHelp",
|
||||
summary: "Open help dialog",
|
||||
description: "Open the help dialog in the TUI to display user assistance information.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("openSessions", TuiPaths.openSessions, { success: Schema.Boolean }).annotateMerge(
|
||||
HttpApiEndpoint.post("openSessions", TuiPaths.openSessions, { success: described(Schema.Boolean, "Session dialog opened successfully") }).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tui.openSessions",
|
||||
summary: "Open sessions dialog",
|
||||
description: "Open the session dialog.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("openThemes", TuiPaths.openThemes, { success: Schema.Boolean }).annotateMerge(
|
||||
HttpApiEndpoint.post("openThemes", TuiPaths.openThemes, { success: described(Schema.Boolean, "Theme dialog opened successfully") }).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tui.openThemes",
|
||||
summary: "Open themes dialog",
|
||||
description: "Open the theme dialog.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("openModels", TuiPaths.openModels, { success: Schema.Boolean }).annotateMerge(
|
||||
HttpApiEndpoint.post("openModels", TuiPaths.openModels, { success: described(Schema.Boolean, "Model dialog opened successfully") }).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tui.openModels",
|
||||
summary: "Open models dialog",
|
||||
description: "Open the model dialog.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("submitPrompt", TuiPaths.submitPrompt, { success: Schema.Boolean }).annotateMerge(
|
||||
HttpApiEndpoint.post("submitPrompt", TuiPaths.submitPrompt, { success: described(Schema.Boolean, "Prompt submitted successfully") }).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tui.submitPrompt",
|
||||
summary: "Submit TUI prompt",
|
||||
description: "Submit the prompt.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("clearPrompt", TuiPaths.clearPrompt, { success: Schema.Boolean }).annotateMerge(
|
||||
HttpApiEndpoint.post("clearPrompt", TuiPaths.clearPrompt, { success: described(Schema.Boolean, "Prompt cleared successfully") }).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tui.clearPrompt",
|
||||
summary: "Clear TUI prompt",
|
||||
@@ -91,7 +92,7 @@ export const TuiApi = HttpApi.make("tui")
|
||||
),
|
||||
HttpApiEndpoint.post("executeCommand", TuiPaths.executeCommand, {
|
||||
payload: CommandPayload,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Command executed successfully"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -102,7 +103,7 @@ export const TuiApi = HttpApi.make("tui")
|
||||
),
|
||||
HttpApiEndpoint.post("showToast", TuiPaths.showToast, {
|
||||
payload: TuiEvent.ToastShow.properties,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Toast notification shown successfully"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tui.showToast",
|
||||
@@ -112,7 +113,7 @@ export const TuiApi = HttpApi.make("tui")
|
||||
),
|
||||
HttpApiEndpoint.post("publish", TuiPaths.publish, {
|
||||
payload: TuiPublishPayload,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Event published successfully"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -123,7 +124,7 @@ export const TuiApi = HttpApi.make("tui")
|
||||
),
|
||||
HttpApiEndpoint.post("selectSession", TuiPaths.selectSession, {
|
||||
payload: TuiEvent.SessionSelect.properties,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Session selected successfully"),
|
||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
@@ -132,7 +133,7 @@ export const TuiApi = HttpApi.make("tui")
|
||||
description: "Navigate the TUI to display the specified session.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("controlNext", TuiPaths.controlNext, { success: TuiRequestPayload }).annotateMerge(
|
||||
HttpApiEndpoint.get("controlNext", TuiPaths.controlNext, { success: described(TuiRequestPayload, "Next TUI request") }).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tui.control.next",
|
||||
summary: "Get next TUI request",
|
||||
@@ -141,7 +142,7 @@ export const TuiApi = HttpApi.make("tui")
|
||||
),
|
||||
HttpApiEndpoint.post("controlResponse", TuiPaths.controlResponse, {
|
||||
payload: Schema.Unknown,
|
||||
success: Schema.Boolean,
|
||||
success: described(Schema.Boolean, "Response submitted successfully"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "tui.control.response",
|
||||
@@ -161,4 +162,3 @@ export const TuiApi = HttpApi.make("tui")
|
||||
description: "Experimental HttpApi surface for selected instance routes.",
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -2,22 +2,19 @@ import { Workspace } from "@/control-plane/workspace"
|
||||
import { WorkspaceAdaptorEntry } from "@/control-plane/types"
|
||||
import { NonNegativeInt } from "@/util/schema"
|
||||
import { Schema, Struct } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../auth"
|
||||
import { InstanceContextMiddleware } from "../instance-context"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const root = "/experimental/workspace"
|
||||
export const CreatePayload = Schema.Struct(Struct.omit(Workspace.CreateInput.fields, ["projectID"])).annotate({
|
||||
identifier: "WorkspaceCreateInput",
|
||||
})
|
||||
export const CreatePayload = Schema.Struct(Struct.omit(Workspace.CreateInput.fields, ["projectID"]))
|
||||
export const SessionRestorePayload = Schema.Struct(
|
||||
Struct.omit(Workspace.SessionRestoreInput.fields, ["workspaceID"]),
|
||||
).annotate({
|
||||
identifier: "WorkspaceSessionRestoreInput",
|
||||
})
|
||||
)
|
||||
export const SessionRestoreResponse = Schema.Struct({
|
||||
total: NonNegativeInt,
|
||||
}).annotate({ identifier: "WorkspaceSessionRestoreResponse" })
|
||||
})
|
||||
|
||||
export const WorkspacePaths = {
|
||||
adaptors: `${root}/adaptor`,
|
||||
@@ -32,7 +29,7 @@ export const WorkspaceApi = HttpApi.make("workspace")
|
||||
HttpApiGroup.make("workspace")
|
||||
.add(
|
||||
HttpApiEndpoint.get("adaptors", WorkspacePaths.adaptors, {
|
||||
success: Schema.Array(WorkspaceAdaptorEntry),
|
||||
success: described(Schema.Array(WorkspaceAdaptorEntry), "Workspace adaptors"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.workspace.adaptor.list",
|
||||
@@ -40,7 +37,9 @@ export const WorkspaceApi = HttpApi.make("workspace")
|
||||
description: "List all available workspace adaptors for the current project.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("list", WorkspacePaths.list, { success: Schema.Array(Workspace.Info) }).annotateMerge(
|
||||
HttpApiEndpoint.get("list", WorkspacePaths.list, {
|
||||
success: described(Schema.Array(Workspace.Info), "Workspaces"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.workspace.list",
|
||||
summary: "List workspaces",
|
||||
@@ -49,7 +48,8 @@ export const WorkspaceApi = HttpApi.make("workspace")
|
||||
),
|
||||
HttpApiEndpoint.post("create", WorkspacePaths.list, {
|
||||
payload: CreatePayload,
|
||||
success: Workspace.Info,
|
||||
success: described(Workspace.Info, "Workspace created"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.workspace.create",
|
||||
@@ -58,7 +58,7 @@ export const WorkspaceApi = HttpApi.make("workspace")
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("status", WorkspacePaths.status, {
|
||||
success: Schema.Array(Workspace.ConnectionStatus),
|
||||
success: described(Schema.Array(Workspace.ConnectionStatus), "Workspace status"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.workspace.status",
|
||||
@@ -68,7 +68,8 @@ export const WorkspaceApi = HttpApi.make("workspace")
|
||||
),
|
||||
HttpApiEndpoint.delete("remove", WorkspacePaths.remove, {
|
||||
params: { id: Workspace.Info.fields.id },
|
||||
success: Schema.UndefinedOr(Workspace.Info),
|
||||
success: described(Schema.UndefinedOr(Workspace.Info), "Workspace removed"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.workspace.remove",
|
||||
@@ -79,7 +80,8 @@ export const WorkspaceApi = HttpApi.make("workspace")
|
||||
HttpApiEndpoint.post("sessionRestore", WorkspacePaths.sessionRestore, {
|
||||
params: { id: Workspace.Info.fields.id },
|
||||
payload: SessionRestorePayload,
|
||||
success: SessionRestoreResponse,
|
||||
success: described(SessionRestoreResponse, "Session replay started"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.workspace.sessionRestore",
|
||||
|
||||
@@ -33,6 +33,7 @@ type OpenApiSchema = {
|
||||
additionalProperties?: OpenApiSchema | boolean
|
||||
allOf?: OpenApiSchema[]
|
||||
anyOf?: OpenApiSchema[]
|
||||
description?: string
|
||||
enum?: Array<string | boolean>
|
||||
items?: OpenApiSchema
|
||||
maximum?: number
|
||||
@@ -76,185 +77,11 @@ const QueryParameterSchemas = {
|
||||
"GET /session/{sessionID}/message limit": { type: "integer", minimum: 0, maximum: Number.MAX_SAFE_INTEGER },
|
||||
} satisfies Record<string, OpenApiSchema>
|
||||
|
||||
// Mapping of "METHOD /path" to the correct 200 response description from Hono spec
|
||||
const ResponseDescriptions = {
|
||||
"GET /global/health": "Health information",
|
||||
"GET /global/config": "Get global config info",
|
||||
"PATCH /global/config": "Successfully updated global config",
|
||||
"POST /global/dispose": "Global disposed",
|
||||
"POST /global/upgrade": "Upgrade result",
|
||||
"PUT /auth/{providerID}": "Successfully set authentication credentials",
|
||||
"DELETE /auth/{providerID}": "Successfully removed authentication credentials",
|
||||
"POST /log": "Log entry written successfully",
|
||||
"GET /experimental/workspace/adaptor": "Workspace adaptors",
|
||||
"POST /experimental/workspace": "Workspace created",
|
||||
"GET /experimental/workspace": "Workspaces",
|
||||
"GET /experimental/workspace/status": "Workspace status",
|
||||
"DELETE /experimental/workspace/{id}": "Workspace removed",
|
||||
"POST /experimental/workspace/{id}/session-restore": "Session replay started",
|
||||
"GET /project": "List of projects",
|
||||
"GET /project/current": "Current project information",
|
||||
"POST /project/git/init": "Project information after git initialization",
|
||||
"PATCH /project/{projectID}": "Updated project information",
|
||||
"GET /pty/shells": "List of shells",
|
||||
"GET /pty": "List of sessions",
|
||||
"POST /pty": "Created session",
|
||||
"GET /pty/{ptyID}": "Session info",
|
||||
"PUT /pty/{ptyID}": "Updated session",
|
||||
"DELETE /pty/{ptyID}": "Session removed",
|
||||
"GET /pty/{ptyID}/connect": "Connected session",
|
||||
"GET /config": "Get config info",
|
||||
"PATCH /config": "Successfully updated config",
|
||||
"GET /config/providers": "List of providers",
|
||||
"GET /experimental/console": "Active Console provider metadata",
|
||||
"GET /experimental/console/orgs": "Switchable Console orgs",
|
||||
"POST /experimental/console/switch": "Switch success",
|
||||
"GET /experimental/tool/ids": "Tool IDs",
|
||||
"GET /experimental/tool": "Tools",
|
||||
"POST /experimental/worktree": "Worktree created",
|
||||
"GET /experimental/worktree": "List of worktree directories",
|
||||
"DELETE /experimental/worktree": "Worktree removed",
|
||||
"POST /experimental/worktree/reset": "Worktree reset",
|
||||
"GET /experimental/session": "List of sessions",
|
||||
"GET /experimental/resource": "MCP resources",
|
||||
"GET /session": "List of sessions",
|
||||
"POST /session": "Successfully created session",
|
||||
"GET /session/status": "Get session status",
|
||||
"GET /session/{sessionID}": "Get session",
|
||||
"DELETE /session/{sessionID}": "Successfully deleted session",
|
||||
"PATCH /session/{sessionID}": "Successfully updated session",
|
||||
"GET /session/{sessionID}/children": "List of children",
|
||||
"GET /session/{sessionID}/todo": "Todo list",
|
||||
"POST /session/{sessionID}/init": "200",
|
||||
"POST /session/{sessionID}/fork": "200",
|
||||
"POST /session/{sessionID}/abort": "Aborted session",
|
||||
"POST /session/{sessionID}/share": "Successfully shared session",
|
||||
"DELETE /session/{sessionID}/share": "Successfully unshared session",
|
||||
"GET /session/{sessionID}/diff": "Successfully retrieved diff",
|
||||
"POST /session/{sessionID}/summarize": "Summarized session",
|
||||
"GET /session/{sessionID}/message": "List of messages",
|
||||
"POST /session/{sessionID}/message": "Created message",
|
||||
"GET /session/{sessionID}/message/{messageID}": "Message",
|
||||
"DELETE /session/{sessionID}/message/{messageID}": "Successfully deleted message",
|
||||
"DELETE /session/{sessionID}/message/{messageID}/part/{partID}": "Successfully deleted part",
|
||||
"PATCH /session/{sessionID}/message/{messageID}/part/{partID}": "Successfully updated part",
|
||||
"POST /session/{sessionID}/command": "Created message",
|
||||
"POST /session/{sessionID}/shell": "Created message",
|
||||
"POST /session/{sessionID}/revert": "Updated session",
|
||||
"POST /session/{sessionID}/unrevert": "Updated session",
|
||||
"POST /session/{sessionID}/permissions/{permissionID}": "Permission processed successfully",
|
||||
"POST /permission/{requestID}/reply": "Permission processed successfully",
|
||||
"GET /permission": "List of pending permissions",
|
||||
"GET /question": "List of pending questions",
|
||||
"POST /question/{requestID}/reply": "Question answered successfully",
|
||||
"POST /question/{requestID}/reject": "Question rejected successfully",
|
||||
"GET /provider": "List of providers",
|
||||
"GET /provider/auth": "Provider auth methods",
|
||||
"POST /provider/{providerID}/oauth/authorize": "Authorization URL and method",
|
||||
"POST /provider/{providerID}/oauth/callback": "OAuth callback processed successfully",
|
||||
"POST /sync/start": "Workspace sync started",
|
||||
"POST /sync/replay": "Replayed sync events",
|
||||
"POST /sync/history": "Sync events",
|
||||
"GET /find": "Matches",
|
||||
"GET /find/file": "File paths",
|
||||
"GET /find/symbol": "Symbols",
|
||||
"GET /file": "Files and directories",
|
||||
"GET /file/content": "File content",
|
||||
"GET /file/status": "File status",
|
||||
"GET /mcp": "MCP server status",
|
||||
"POST /mcp": "MCP server added successfully",
|
||||
"POST /mcp/{name}/auth": "OAuth flow started",
|
||||
"DELETE /mcp/{name}/auth": "OAuth credentials removed",
|
||||
"POST /mcp/{name}/auth/callback": "OAuth authentication completed",
|
||||
"POST /mcp/{name}/auth/authenticate": "OAuth authentication completed",
|
||||
"POST /mcp/{name}/connect": "MCP server connected successfully",
|
||||
"POST /mcp/{name}/disconnect": "MCP server disconnected successfully",
|
||||
"POST /tui/append-prompt": "Prompt processed successfully",
|
||||
"POST /tui/open-help": "Help dialog opened successfully",
|
||||
"POST /tui/open-sessions": "Session dialog opened successfully",
|
||||
"POST /tui/open-themes": "Theme dialog opened successfully",
|
||||
"POST /tui/open-models": "Model dialog opened successfully",
|
||||
"POST /tui/submit-prompt": "Prompt submitted successfully",
|
||||
"POST /tui/clear-prompt": "Prompt cleared successfully",
|
||||
"POST /tui/execute-command": "Command executed successfully",
|
||||
"POST /tui/show-toast": "Toast notification shown successfully",
|
||||
"POST /tui/publish": "Event published successfully",
|
||||
"POST /tui/select-session": "Session selected successfully",
|
||||
"GET /tui/control/next": "Next TUI request",
|
||||
"POST /tui/control/response": "Response submitted successfully",
|
||||
"POST /instance/dispose": "Instance disposed",
|
||||
"GET /vcs": "VCS info",
|
||||
"GET /vcs/diff": "VCS diff",
|
||||
"GET /command": "List of commands",
|
||||
"GET /agent": "List of agents",
|
||||
"GET /skill": "List of skills",
|
||||
"GET /lsp": "LSP server status",
|
||||
"GET /formatter": "Formatter status",
|
||||
} as const satisfies Record<string, string>
|
||||
|
||||
const LegacyErrorResponses = {
|
||||
"PUT /auth/{providerID}": [400],
|
||||
"DELETE /auth/{providerID}": [400],
|
||||
"POST /log": [400],
|
||||
"PATCH /global/config": [400],
|
||||
"POST /global/upgrade": [400],
|
||||
"POST /experimental/workspace": [400],
|
||||
"POST /experimental/console/switch": [400],
|
||||
"DELETE /experimental/workspace/{id}": [400],
|
||||
"POST /experimental/workspace/{id}/session-restore": [400],
|
||||
"GET /experimental/tool/ids": [400],
|
||||
"GET /experimental/tool": [400],
|
||||
"POST /experimental/worktree": [400],
|
||||
"DELETE /experimental/worktree": [400],
|
||||
"POST /experimental/worktree/reset": [400],
|
||||
"PATCH /config": [400],
|
||||
"PATCH /project/{projectID}": [400, 404],
|
||||
"POST /pty": [400],
|
||||
"GET /pty/{ptyID}": [404],
|
||||
"PUT /pty/{ptyID}": [400],
|
||||
"DELETE /pty/{ptyID}": [404],
|
||||
"GET /pty/{ptyID}/connect": [404],
|
||||
"POST /session": [400],
|
||||
"GET /session/status": [400],
|
||||
"GET /session/{sessionID}": [400, 404],
|
||||
"DELETE /session/{sessionID}": [400, 404],
|
||||
"PATCH /session/{sessionID}": [400, 404],
|
||||
"GET /session/{sessionID}/children": [400, 404],
|
||||
"GET /session/{sessionID}/todo": [400, 404],
|
||||
"POST /session/{sessionID}/init": [400, 404],
|
||||
"POST /session/{sessionID}/abort": [400, 404],
|
||||
"POST /session/{sessionID}/share": [400, 404],
|
||||
"DELETE /session/{sessionID}/share": [400, 404],
|
||||
"POST /session/{sessionID}/summarize": [400, 404],
|
||||
"GET /session/{sessionID}/message": [400, 404],
|
||||
"POST /session/{sessionID}/message": [400, 404],
|
||||
"GET /session/{sessionID}/message/{messageID}": [400, 404],
|
||||
"DELETE /session/{sessionID}/message/{messageID}": [400, 404],
|
||||
"DELETE /session/{sessionID}/message/{messageID}/part/{partID}": [400, 404],
|
||||
"PATCH /session/{sessionID}/message/{messageID}/part/{partID}": [400, 404],
|
||||
"POST /session/{sessionID}/prompt_async": [400, 404],
|
||||
"POST /session/{sessionID}/command": [400, 404],
|
||||
"POST /session/{sessionID}/shell": [400, 404],
|
||||
"POST /session/{sessionID}/revert": [400, 404],
|
||||
"POST /session/{sessionID}/unrevert": [400, 404],
|
||||
"POST /session/{sessionID}/permissions/{permissionID}": [400, 404],
|
||||
"POST /permission/{requestID}/reply": [400, 404],
|
||||
"POST /question/{requestID}/reply": [400, 404],
|
||||
"POST /question/{requestID}/reject": [400, 404],
|
||||
"POST /provider/{providerID}/oauth/authorize": [400],
|
||||
"POST /provider/{providerID}/oauth/callback": [400],
|
||||
"POST /sync/replay": [400],
|
||||
"POST /sync/history": [400],
|
||||
"POST /mcp": [400],
|
||||
"POST /mcp/{name}/auth": [404],
|
||||
"POST /mcp/{name}/auth/callback": [400, 404],
|
||||
"POST /mcp/{name}/auth/authenticate": [404],
|
||||
"DELETE /mcp/{name}/auth": [404],
|
||||
"POST /tui/append-prompt": [400],
|
||||
"POST /tui/execute-command": [400],
|
||||
"POST /tui/publish": [400],
|
||||
"POST /tui/select-session": [400, 404],
|
||||
} as const satisfies Record<string, ReadonlyArray<400 | 404>>
|
||||
const LegacyComponentDescriptions = {
|
||||
LogLevel: "Log level",
|
||||
ServerConfig: "Server configuration for opencode serve and web commands",
|
||||
LayoutConfig: "@deprecated Always uses stretch layout.",
|
||||
} satisfies Record<string, string>
|
||||
|
||||
function matchLegacyOpenApi(input: Record<string, unknown>) {
|
||||
const spec = input as OpenApiSpec
|
||||
@@ -272,10 +99,16 @@ function matchLegacyOpenApi(input: Record<string, unknown>) {
|
||||
for (const [name, schema] of Object.entries(spec.components?.schemas ?? {})) {
|
||||
spec.components!.schemas![name] = stripOptionalNull(structuredClone(schema))
|
||||
}
|
||||
normalizeComponentNames(spec)
|
||||
collapseDuplicateComponents(spec)
|
||||
applyLegacySchemaOverrides(spec)
|
||||
normalizeComponentDescriptions(spec)
|
||||
addLegacyErrorSchemas(spec)
|
||||
delete spec.components?.schemas?.Unauthorized
|
||||
delete spec.components?.schemas?.EffectHttpApiErrorBadRequest
|
||||
delete spec.components?.schemas?.EffectHttpApiErrorNotFound
|
||||
delete spec.components?.schemas?.effect_HttpApiError_BadRequest
|
||||
delete spec.components?.schemas?.effect_HttpApiError_NotFound
|
||||
delete spec.components?.securitySchemes
|
||||
|
||||
for (const [path, item] of Object.entries(spec.paths ?? {})) {
|
||||
@@ -287,6 +120,8 @@ function matchLegacyOpenApi(input: Record<string, unknown>) {
|
||||
// Hono's generated OpenAPI never marked request bodies as required. Keep
|
||||
// that SDK surface stable during the HttpApi migration.
|
||||
delete operation.requestBody.required
|
||||
const body = operation.requestBody.content?.["application/json"]
|
||||
if (body?.schema) body.schema = stripOptionalNull(structuredClone(body.schema))
|
||||
if (path === "/experimental/workspace" && method === "post") {
|
||||
// Workspace creation fields `branch` and `extra` are Schema.NullOr —
|
||||
// genuinely nullable, not just optional. Re-add the null that the
|
||||
@@ -297,11 +132,17 @@ function matchLegacyOpenApi(input: Record<string, unknown>) {
|
||||
if (properties?.extra) properties.extra = { anyOf: [properties.extra, { type: "null" }] }
|
||||
}
|
||||
}
|
||||
for (const response of Object.values(operation.responses ?? {})) {
|
||||
for (const content of Object.values(response.content ?? {})) {
|
||||
if (content.schema) content.schema = stripOptionalNull(structuredClone(content.schema))
|
||||
}
|
||||
}
|
||||
// Hono applied auth as runtime middleware outside OpenAPI metadata, so the
|
||||
// legacy SDK did not expose auth schemes or generated 401 error unions.
|
||||
delete operation.security
|
||||
delete operation.responses?.["401"]
|
||||
normalizeLegacyErrorResponses(operation)
|
||||
normalizeLegacyOperation(operation, path, method)
|
||||
if ((path === "/event" || path === "/global/event") && method === "get") {
|
||||
// HttpApi has no first-class SSE response schema, and these handlers are
|
||||
// raw/streaming routes. Document the actual wire protocol explicitly.
|
||||
@@ -314,15 +155,6 @@ function matchLegacyOpenApi(input: Record<string, unknown>) {
|
||||
},
|
||||
}
|
||||
}
|
||||
// Apply response descriptions from the Hono spec to match legacy behavior
|
||||
const routeKey = `${method.toUpperCase()} ${path}` as keyof typeof ResponseDescriptions
|
||||
const responseDescription = ResponseDescriptions[routeKey]
|
||||
applyLegacyErrorResponses(operation, routeKey)
|
||||
inlineLegacyResponseSchemas(operation, routeKey)
|
||||
if (responseDescription && operation.responses?.["200"]) {
|
||||
const response200 = operation.responses["200"] as { description?: string }
|
||||
response200.description = responseDescription
|
||||
}
|
||||
if (!isInstanceRoute) continue
|
||||
operation.parameters = [
|
||||
...InstanceQueryParameters,
|
||||
@@ -369,57 +201,156 @@ function addLegacyErrorSchemas(spec: OpenApiSpec) {
|
||||
}
|
||||
}
|
||||
|
||||
function collapseDuplicateComponents(spec: OpenApiSpec) {
|
||||
const schemas = spec.components?.schemas
|
||||
if (!schemas) return
|
||||
for (const name of Object.keys(schemas)) {
|
||||
const base = name.replace(/\d+$/, "")
|
||||
if (base === name || !schemas[base]) continue
|
||||
if (stableSchema(schemas[name], schemas) !== stableSchema(schemas[base], schemas)) continue
|
||||
rewriteRefs(spec, name, base)
|
||||
delete schemas[name]
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeComponentNames(spec: OpenApiSpec) {
|
||||
const schemas = spec.components?.schemas
|
||||
if (!schemas) return
|
||||
for (const name of Object.keys(schemas)) {
|
||||
const next = componentTypeName(name)
|
||||
if (next === name) continue
|
||||
if (schemas[next]) {
|
||||
if (stableSchema(schemas[name], schemas) === stableSchema(schemas[next], schemas)) {
|
||||
rewriteRefs(spec, name, next)
|
||||
delete schemas[name]
|
||||
}
|
||||
continue
|
||||
}
|
||||
schemas[next] = schemas[name]
|
||||
rewriteRefs(spec, name, next)
|
||||
delete schemas[name]
|
||||
}
|
||||
}
|
||||
|
||||
function componentTypeName(name: string) {
|
||||
if (!name.includes(".")) return name
|
||||
return name
|
||||
.split(".")
|
||||
.filter((part) => !/^\d+$/.test(part))
|
||||
.map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
|
||||
.join("")
|
||||
}
|
||||
|
||||
function applyLegacySchemaOverrides(spec: OpenApiSpec) {
|
||||
const schemas = spec.components?.schemas
|
||||
if (!schemas) return
|
||||
if (schemas.AgentConfig) schemas.AgentConfig.additionalProperties = {}
|
||||
if (schemas.Command?.properties?.template) schemas.Command.properties.template = { type: "string" }
|
||||
if (schemas.Workspace?.properties) {
|
||||
schemas.Workspace.properties.branch = nullable(schemas.Workspace.properties.branch)
|
||||
schemas.Workspace.properties.directory = nullable(schemas.Workspace.properties.directory)
|
||||
schemas.Workspace.properties.extra = nullable(schemas.Workspace.properties.extra)
|
||||
}
|
||||
if (schemas.GlobalSession?.properties?.project) schemas.GlobalSession.properties.project = nullable(schemas.GlobalSession.properties.project)
|
||||
const providerOptions = schemas.ProviderConfig?.properties?.options
|
||||
if (providerOptions) providerOptions.additionalProperties = {}
|
||||
const model = schemas.ProviderConfig?.properties?.models?.additionalProperties
|
||||
const variants = typeof model === "object" ? model.properties?.variants?.additionalProperties : undefined
|
||||
if (variants && typeof variants === "object") variants.additionalProperties = {}
|
||||
const syncInfo = schemas.SyncEventSessionUpdated?.properties?.data?.properties?.info
|
||||
if (syncInfo?.properties) makePropertiesNullable(syncInfo.properties)
|
||||
}
|
||||
|
||||
function normalizeComponentDescriptions(spec: OpenApiSpec) {
|
||||
for (const [name, schema] of Object.entries(spec.components?.schemas ?? {})) {
|
||||
const description = LegacyComponentDescriptions[name as keyof typeof LegacyComponentDescriptions]
|
||||
if (description) {
|
||||
schema.description = description
|
||||
continue
|
||||
}
|
||||
delete schema.description
|
||||
}
|
||||
}
|
||||
|
||||
function makePropertiesNullable(properties: Record<string, OpenApiSchema>) {
|
||||
for (const [key, value] of Object.entries(properties)) {
|
||||
if (key === "share" && value.properties?.url) {
|
||||
value.properties.url = nullable(value.properties.url)
|
||||
continue
|
||||
}
|
||||
if (key === "time" && value.properties) {
|
||||
makePropertiesNullable(value.properties)
|
||||
continue
|
||||
}
|
||||
properties[key] = nullable(value)
|
||||
}
|
||||
}
|
||||
|
||||
function nullable(schema: OpenApiSchema): OpenApiSchema {
|
||||
if (flattenOptions(schema.anyOf ?? schema.oneOf)?.some((item) => item.type === "null")) return schema
|
||||
return { anyOf: [schema, { type: "null" }] }
|
||||
}
|
||||
|
||||
function stableSchema(input: unknown, schemas: Record<string, OpenApiSchema>): string {
|
||||
return JSON.stringify(canonicalizeSchema(input, schemas))
|
||||
}
|
||||
|
||||
function canonicalizeSchema(input: unknown, schemas: Record<string, OpenApiSchema>): unknown {
|
||||
if (Array.isArray(input)) return input.map((item) => canonicalizeSchema(item, schemas))
|
||||
if (!input || typeof input !== "object") return input
|
||||
const schema = input as OpenApiSchema
|
||||
if (schema.$ref) return { $ref: canonicalRef(schema.$ref, schemas) }
|
||||
return Object.fromEntries(
|
||||
Object.entries(input)
|
||||
.filter(([key]) => key !== "description")
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([key, value]) => [key, canonicalizeSchema(value, schemas)]),
|
||||
)
|
||||
}
|
||||
|
||||
function canonicalRef(ref: string, schemas: Record<string, OpenApiSchema>) {
|
||||
const name = ref.replace("#/components/schemas/", "")
|
||||
const base = name.replace(/\d+$/, "")
|
||||
if (base !== name && schemas[base]) return `#/components/schemas/${base}`
|
||||
return ref
|
||||
}
|
||||
|
||||
function rewriteRefs(input: unknown, from: string, to: string): void {
|
||||
if (Array.isArray(input)) {
|
||||
for (const item of input) rewriteRefs(item, from, to)
|
||||
return
|
||||
}
|
||||
if (!input || typeof input !== "object") return
|
||||
const schema = input as OpenApiSchema
|
||||
if (schema.$ref === `#/components/schemas/${from}`) schema.$ref = `#/components/schemas/${to}`
|
||||
for (const value of Object.values(input)) rewriteRefs(value, from, to)
|
||||
}
|
||||
|
||||
function normalizeLegacyErrorResponses(operation: OpenApiOperation) {
|
||||
if (operation.responses?.["400"] && isRefResponse(operation.responses["400"], "EffectHttpApiErrorBadRequest")) {
|
||||
if (operation.responses?.["400"] && isBuiltInErrorResponse(operation.responses["400"], "BadRequest")) {
|
||||
operation.responses["400"] = legacyErrorResponse("Bad request", "BadRequestError")
|
||||
}
|
||||
if (operation.responses?.["404"] && isRefResponse(operation.responses["404"], "EffectHttpApiErrorNotFound")) {
|
||||
if (operation.responses?.["404"] && isBuiltInErrorResponse(operation.responses["404"], "NotFound")) {
|
||||
operation.responses["404"] = legacyErrorResponse("Not found", "NotFoundError")
|
||||
}
|
||||
}
|
||||
|
||||
function applyLegacyErrorResponses(operation: OpenApiOperation, route: string) {
|
||||
const responses: ReadonlyArray<400 | 404> | undefined = LegacyErrorResponses[route as keyof typeof LegacyErrorResponses]
|
||||
if (!responses) return
|
||||
operation.responses ??= {}
|
||||
if (responses.includes(400)) operation.responses["400"] = legacyErrorResponse("Bad request", "BadRequestError")
|
||||
else if (operation.responses["400"] && isErrorResponse(operation.responses["400"], "BadRequest")) delete operation.responses["400"]
|
||||
if (responses.includes(404)) operation.responses["404"] = legacyErrorResponse("Not found", "NotFoundError")
|
||||
else if (operation.responses["404"] && isErrorResponse(operation.responses["404"], "NotFound")) delete operation.responses["404"]
|
||||
}
|
||||
|
||||
function inlineLegacyResponseSchemas(operation: OpenApiOperation, route: string) {
|
||||
const response = operation.responses?.["200"]
|
||||
const schema = response?.content?.["application/json"]?.schema
|
||||
if (!schema) return
|
||||
if (route === "POST /mcp/{name}/auth") {
|
||||
response.content!["application/json"]!.schema = {
|
||||
type: "object",
|
||||
required: ["authorizationUrl"],
|
||||
properties: {
|
||||
authorizationUrl: { type: "string" },
|
||||
function normalizeLegacyOperation(operation: OpenApiOperation, path: string, method: string) {
|
||||
if (path === "/experimental/console/switch" && method === "post") delete operation.responses?.["400"]
|
||||
if (path === "/pty/{ptyID}" && method === "put") delete operation.responses?.["404"]
|
||||
if ((path !== "/session/{sessionID}/message" && path !== "/session/{sessionID}/command") || method !== "post") return
|
||||
const response = operation.responses?.["200"]?.content?.["application/json"]
|
||||
if (!response) return
|
||||
response.schema = {
|
||||
type: "object",
|
||||
required: ["info", "parts"],
|
||||
properties: {
|
||||
info: { $ref: "#/components/schemas/AssistantMessage" },
|
||||
parts: {
|
||||
type: "array",
|
||||
items: { $ref: "#/components/schemas/Part" },
|
||||
},
|
||||
}
|
||||
return
|
||||
}
|
||||
if (route === "DELETE /mcp/{name}/auth") {
|
||||
response.content!["application/json"]!.schema = {
|
||||
type: "object",
|
||||
required: ["success"],
|
||||
properties: {
|
||||
success: { type: "boolean", enum: [true] },
|
||||
},
|
||||
}
|
||||
return
|
||||
}
|
||||
if (route === "POST /sync/replay") {
|
||||
response.content!["application/json"]!.schema = {
|
||||
type: "object",
|
||||
required: ["sessionID"],
|
||||
properties: {
|
||||
sessionID: { type: "string" },
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,8 +358,8 @@ function isRefResponse(response: OpenApiResponse, name: string) {
|
||||
return response.content?.["application/json"]?.schema?.$ref === `#/components/schemas/${name}`
|
||||
}
|
||||
|
||||
function isErrorResponse(response: OpenApiResponse, name: "BadRequest" | "NotFound") {
|
||||
return isRefResponse(response, `EffectHttpApiError${name}`) || isRefResponse(response, `${name}Error`)
|
||||
function isBuiltInErrorResponse(response: OpenApiResponse, name: "BadRequest" | "NotFound") {
|
||||
return response.description === name || isRefResponse(response, `EffectHttpApiError${name}`)
|
||||
}
|
||||
|
||||
function legacyErrorResponse(description: string, name: "BadRequestError" | "NotFoundError"): OpenApiResponse {
|
||||
@@ -488,6 +419,7 @@ function fixSelfReferencingComponents(spec: OpenApiSpec) {
|
||||
|
||||
/** Strip `{type:"null"}` arms that Effect's `Schema.optional` adds to OpenAPI unions. */
|
||||
function stripOptionalNull(schema: OpenApiSchema): OpenApiSchema {
|
||||
if (isEmptyObjectUnion(schema)) return { type: "object", properties: {} }
|
||||
const options = flattenOptions(schema.anyOf ?? schema.oneOf)
|
||||
if (options) {
|
||||
const withoutNull = options.filter((item) => item.type !== "null")
|
||||
@@ -496,8 +428,13 @@ function stripOptionalNull(schema: OpenApiSchema): OpenApiSchema {
|
||||
if (schema.oneOf) schema.oneOf = withoutNull.map(stripOptionalNull)
|
||||
}
|
||||
if (schema.allOf) {
|
||||
if (schema.type) delete schema.allOf
|
||||
else schema.allOf = schema.allOf.map(stripOptionalNull)
|
||||
const allOf = schema.allOf.map(stripOptionalNull)
|
||||
if (schema.type) {
|
||||
delete schema.allOf
|
||||
for (const item of allOf) Object.assign(schema, item)
|
||||
} else {
|
||||
schema.allOf = allOf
|
||||
}
|
||||
}
|
||||
if (schema.prefixItems && schema.items) delete schema.prefixItems
|
||||
if (schema.items) schema.items = stripOptionalNull(schema.items)
|
||||
@@ -512,6 +449,19 @@ function stripOptionalNull(schema: OpenApiSchema): OpenApiSchema {
|
||||
return schema
|
||||
}
|
||||
|
||||
function isEmptyObjectUnion(schema: OpenApiSchema) {
|
||||
const options = schema.anyOf ?? schema.oneOf
|
||||
return options?.length === 2 && options.some(isBareObjectSchema) && options.some(isBareArraySchema)
|
||||
}
|
||||
|
||||
function isBareObjectSchema(schema: OpenApiSchema) {
|
||||
return schema.type === "object" && !schema.properties && !schema.additionalProperties
|
||||
}
|
||||
|
||||
function isBareArraySchema(schema: OpenApiSchema) {
|
||||
return schema.type === "array" && !schema.items && !schema.prefixItems
|
||||
}
|
||||
|
||||
function flattenOptions(options: OpenApiSchema[] | undefined): OpenApiSchema[] | undefined {
|
||||
return options?.flatMap((item) => flattenOptions(item.anyOf ?? item.oneOf) ?? [item])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user