chore: generate

This commit is contained in:
opencode-agent[bot] 2026-04-29 13:36:05 +00:00
parent 6015084fa2
commit df147b65fd
25 changed files with 978 additions and 715 deletions

View file

@ -29,14 +29,18 @@ export const PtyApi = HttpApi.make("pty")
.add(
HttpApiGroup.make("pty")
.add(
HttpApiEndpoint.get("shells", PtyPaths.shells, { success: described(Schema.Array(ShellItem), "List of shells") }).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: described(Schema.Array(Pty.Info), "List of sessions") }).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",

View file

@ -11,11 +11,28 @@ export const TuiRequestPayload = Schema.Struct({
path: Schema.String,
body: Schema.Unknown,
})
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])
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,
])
export const TuiPaths = {
appendPrompt: `${root}/append-prompt`,
@ -48,42 +65,54 @@ export const TuiApi = HttpApi.make("tui")
description: "Append prompt to the TUI.",
}),
),
HttpApiEndpoint.post("openHelp", TuiPaths.openHelp, { success: described(Schema.Boolean, "Help dialog opened successfully") }).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: described(Schema.Boolean, "Session dialog opened successfully") }).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: described(Schema.Boolean, "Theme dialog opened successfully") }).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: described(Schema.Boolean, "Model dialog opened successfully") }).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: described(Schema.Boolean, "Prompt submitted successfully") }).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: described(Schema.Boolean, "Prompt cleared successfully") }).annotateMerge(
HttpApiEndpoint.post("clearPrompt", TuiPaths.clearPrompt, {
success: described(Schema.Boolean, "Prompt cleared successfully"),
}).annotateMerge(
OpenApi.annotations({
identifier: "tui.clearPrompt",
summary: "Clear TUI prompt",
@ -133,7 +162,9 @@ export const TuiApi = HttpApi.make("tui")
description: "Navigate the TUI to display the specified session.",
}),
),
HttpApiEndpoint.get("controlNext", TuiPaths.controlNext, { success: described(TuiRequestPayload, "Next TUI request") }).annotateMerge(
HttpApiEndpoint.get("controlNext", TuiPaths.controlNext, {
success: described(TuiRequestPayload, "Next TUI request"),
}).annotateMerge(
OpenApi.annotations({
identifier: "tui.control.next",
summary: "Get next TUI request",

View file

@ -9,9 +9,7 @@ import { described } from "./metadata"
const root = "/experimental/workspace"
export const CreatePayload = Schema.Struct(Struct.omit(Workspace.CreateInput.fields, ["projectID"]))
export const SessionRestorePayload = Schema.Struct(
Struct.omit(Workspace.SessionRestoreInput.fields, ["workspaceID"]),
)
export const SessionRestorePayload = Schema.Struct(Struct.omit(Workspace.SessionRestoreInput.fields, ["workspaceID"]))
export const SessionRestoreResponse = Schema.Struct({
total: NonNegativeInt,
})

View file

@ -7,28 +7,28 @@ import { InstanceHttpApi } from "../api"
import { markInstanceForDisposal } from "../lifecycle"
export const configHandlers = HttpApiBuilder.group(InstanceHttpApi, "config", (handlers) =>
Effect.gen(function* () {
const providerSvc = yield* Provider.Service
const configSvc = yield* Config.Service
Effect.gen(function* () {
const providerSvc = yield* Provider.Service
const configSvc = yield* Config.Service
const get = Effect.fn("ConfigHttpApi.get")(function* () {
return yield* configSvc.get()
})
const get = Effect.fn("ConfigHttpApi.get")(function* () {
return yield* configSvc.get()
})
const update = Effect.fn("ConfigHttpApi.update")(function* (ctx) {
yield* configSvc.update(ctx.payload, { dispose: false })
yield* markInstanceForDisposal(yield* InstanceState.context)
return ctx.payload
})
const update = Effect.fn("ConfigHttpApi.update")(function* (ctx) {
yield* configSvc.update(ctx.payload, { dispose: false })
yield* markInstanceForDisposal(yield* InstanceState.context)
return ctx.payload
})
const providers = Effect.fn("ConfigHttpApi.providers")(function* () {
const providers = yield* providerSvc.list()
return {
providers: Object.values(providers),
default: Provider.defaultModelIDs(providers),
}
})
const providers = Effect.fn("ConfigHttpApi.providers")(function* () {
const providers = yield* providerSvc.list()
return {
providers: Object.values(providers),
default: Provider.defaultModelIDs(providers),
}
})
return handlers.handle("get", get).handle("update", update).handle("providers", providers)
}),
return handlers.handle("get", get).handle("update", update).handle("providers", providers)
}),
)

View file

@ -7,28 +7,28 @@ import { RootHttpApi } from "../api"
import { LogInput } from "../groups/control"
export const controlHandlers = HttpApiBuilder.group(RootHttpApi, "control", (handlers) =>
Effect.gen(function* () {
const auth = yield* Auth.Service
Effect.gen(function* () {
const auth = yield* Auth.Service
const authSet = Effect.fn("ControlHttpApi.authSet")(function* (ctx: {
params: { providerID: ProviderID }
payload: Auth.Info
}) {
yield* auth.set(ctx.params.providerID, ctx.payload).pipe(Effect.orDie)
return true
})
const authSet = Effect.fn("ControlHttpApi.authSet")(function* (ctx: {
params: { providerID: ProviderID }
payload: Auth.Info
}) {
yield* auth.set(ctx.params.providerID, ctx.payload).pipe(Effect.orDie)
return true
})
const authRemove = Effect.fn("ControlHttpApi.authRemove")(function* (ctx: { params: { providerID: ProviderID } }) {
yield* auth.remove(ctx.params.providerID).pipe(Effect.orDie)
return true
})
const authRemove = Effect.fn("ControlHttpApi.authRemove")(function* (ctx: { params: { providerID: ProviderID } }) {
yield* auth.remove(ctx.params.providerID).pipe(Effect.orDie)
return true
})
const log = Effect.fn("ControlHttpApi.log")(function* (ctx: { payload: typeof LogInput.Type }) {
const logger = Log.create({ service: ctx.payload.service })
logger[ctx.payload.level](ctx.payload.message, ctx.payload.extra)
return true
})
const log = Effect.fn("ControlHttpApi.log")(function* (ctx: { payload: typeof LogInput.Type }) {
const logger = Log.create({ service: ctx.payload.service })
logger[ctx.payload.level](ctx.payload.message, ctx.payload.extra)
return true
})
return handlers.handle("authSet", authSet).handle("authRemove", authRemove).handle("log", log)
}),
return handlers.handle("authSet", authSet).handle("authRemove", authRemove).handle("log", log)
}),
)

View file

@ -15,141 +15,141 @@ import { InstanceHttpApi } from "../api"
import { ConsoleSwitchPayload, SessionListQuery, ToolListQuery } from "../groups/experimental"
export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "experimental", (handlers) =>
Effect.gen(function* () {
const account = yield* Account.Service
const agents = yield* Agent.Service
const config = yield* Config.Service
const mcp = yield* MCP.Service
const project = yield* Project.Service
const registry = yield* ToolRegistry.Service
const worktreeSvc = yield* Worktree.Service
Effect.gen(function* () {
const account = yield* Account.Service
const agents = yield* Agent.Service
const config = yield* Config.Service
const mcp = yield* MCP.Service
const project = yield* Project.Service
const registry = yield* ToolRegistry.Service
const worktreeSvc = yield* Worktree.Service
const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () {
const [state, groups] = yield* Effect.all(
[config.getConsoleState(), account.orgsByAccount().pipe(Effect.orDie)],
{
concurrency: "unbounded",
},
)
return {
consoleManagedProviders: state.consoleManagedProviders,
...(state.activeOrgName ? { activeOrgName: state.activeOrgName } : {}),
switchableOrgCount: groups.reduce((count, group) => count + group.orgs.length, 0),
}
const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () {
const [state, groups] = yield* Effect.all(
[config.getConsoleState(), account.orgsByAccount().pipe(Effect.orDie)],
{
concurrency: "unbounded",
},
)
return {
consoleManagedProviders: state.consoleManagedProviders,
...(state.activeOrgName ? { activeOrgName: state.activeOrgName } : {}),
switchableOrgCount: groups.reduce((count, group) => count + group.orgs.length, 0),
}
})
const listConsoleOrgs = Effect.fn("ExperimentalHttpApi.consoleOrgs")(function* () {
const [groups, active] = yield* Effect.all(
[account.orgsByAccount().pipe(Effect.orDie), account.active().pipe(Effect.orDie)],
{
concurrency: "unbounded",
},
)
const info = Option.getOrUndefined(active)
return {
orgs: groups.flatMap((group) =>
group.orgs.map((org) => ({
accountID: group.account.id,
accountEmail: group.account.email,
accountUrl: group.account.url,
orgID: org.id,
orgName: org.name,
active: !!info && info.id === group.account.id && info.active_org_id === org.id,
})),
),
}
})
const switchConsole = Effect.fn("ExperimentalHttpApi.consoleSwitch")(function* (ctx: {
payload: typeof ConsoleSwitchPayload.Type
}) {
yield* account
.use(ctx.payload.accountID, Option.some(ctx.payload.orgID))
.pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({}))))
return true
})
const tool = Effect.fn("ExperimentalHttpApi.tool")(function* (ctx: { query: typeof ToolListQuery.Type }) {
const list = yield* registry.tools({
providerID: ctx.query.provider,
modelID: ctx.query.model,
agent: yield* agents.get(yield* agents.defaultAgent()),
})
return list.map((item) => ({
id: item.id,
description: item.description,
parameters: EffectZod.toJsonSchema(item.parameters),
}))
})
const listConsoleOrgs = Effect.fn("ExperimentalHttpApi.consoleOrgs")(function* () {
const [groups, active] = yield* Effect.all(
[account.orgsByAccount().pipe(Effect.orDie), account.active().pipe(Effect.orDie)],
{
concurrency: "unbounded",
},
)
const info = Option.getOrUndefined(active)
return {
orgs: groups.flatMap((group) =>
group.orgs.map((org) => ({
accountID: group.account.id,
accountEmail: group.account.email,
accountUrl: group.account.url,
orgID: org.id,
orgName: org.name,
active: !!info && info.id === group.account.id && info.active_org_id === org.id,
})),
),
}
const toolIDs = Effect.fn("ExperimentalHttpApi.toolIDs")(function* () {
return yield* registry.ids()
})
const worktree = Effect.fn("ExperimentalHttpApi.worktree")(function* () {
const ctx = yield* InstanceState.context
return yield* project.sandboxes(ctx.project.id)
})
const worktreeCreate = Effect.fn("ExperimentalHttpApi.worktreeCreate")(function* (ctx: {
payload: Worktree.CreateInput | undefined
}) {
return yield* worktreeSvc.create(ctx.payload)
})
const worktreeRemove = Effect.fn("ExperimentalHttpApi.worktreeRemove")(function* (input: {
payload: Worktree.RemoveInput
}) {
const ctx = yield* InstanceState.context
yield* worktreeSvc.remove(input.payload)
yield* project.removeSandbox(ctx.project.id, input.payload.directory)
return true
})
const worktreeReset = Effect.fn("ExperimentalHttpApi.worktreeReset")(function* (ctx: {
payload: Worktree.ResetInput
}) {
yield* worktreeSvc.reset(ctx.payload)
return true
})
const session = Effect.fn("ExperimentalHttpApi.session")(function* (ctx: { query: typeof SessionListQuery.Type }) {
const limit = ctx.query.limit ?? 100
const sessions = Array.from(
Session.listGlobal({
directory: ctx.query.directory,
roots: ctx.query.roots,
start: ctx.query.start,
cursor: ctx.query.cursor,
search: ctx.query.search,
limit: limit + 1,
archived: ctx.query.archived,
}),
)
const list = sessions.length > limit ? sessions.slice(0, limit) : sessions
return HttpServerResponse.jsonUnsafe(list, {
headers:
sessions.length > limit && list.length > 0
? { "x-next-cursor": String(list[list.length - 1].time.updated) }
: undefined,
})
})
const switchConsole = Effect.fn("ExperimentalHttpApi.consoleSwitch")(function* (ctx: {
payload: typeof ConsoleSwitchPayload.Type
}) {
yield* account
.use(ctx.payload.accountID, Option.some(ctx.payload.orgID))
.pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({}))))
return true
})
const resource = Effect.fn("ExperimentalHttpApi.resource")(function* () {
return yield* mcp.resources()
})
const tool = Effect.fn("ExperimentalHttpApi.tool")(function* (ctx: { query: typeof ToolListQuery.Type }) {
const list = yield* registry.tools({
providerID: ctx.query.provider,
modelID: ctx.query.model,
agent: yield* agents.get(yield* agents.defaultAgent()),
})
return list.map((item) => ({
id: item.id,
description: item.description,
parameters: EffectZod.toJsonSchema(item.parameters),
}))
})
const toolIDs = Effect.fn("ExperimentalHttpApi.toolIDs")(function* () {
return yield* registry.ids()
})
const worktree = Effect.fn("ExperimentalHttpApi.worktree")(function* () {
const ctx = yield* InstanceState.context
return yield* project.sandboxes(ctx.project.id)
})
const worktreeCreate = Effect.fn("ExperimentalHttpApi.worktreeCreate")(function* (ctx: {
payload: Worktree.CreateInput | undefined
}) {
return yield* worktreeSvc.create(ctx.payload)
})
const worktreeRemove = Effect.fn("ExperimentalHttpApi.worktreeRemove")(function* (input: {
payload: Worktree.RemoveInput
}) {
const ctx = yield* InstanceState.context
yield* worktreeSvc.remove(input.payload)
yield* project.removeSandbox(ctx.project.id, input.payload.directory)
return true
})
const worktreeReset = Effect.fn("ExperimentalHttpApi.worktreeReset")(function* (ctx: {
payload: Worktree.ResetInput
}) {
yield* worktreeSvc.reset(ctx.payload)
return true
})
const session = Effect.fn("ExperimentalHttpApi.session")(function* (ctx: { query: typeof SessionListQuery.Type }) {
const limit = ctx.query.limit ?? 100
const sessions = Array.from(
Session.listGlobal({
directory: ctx.query.directory,
roots: ctx.query.roots,
start: ctx.query.start,
cursor: ctx.query.cursor,
search: ctx.query.search,
limit: limit + 1,
archived: ctx.query.archived,
}),
)
const list = sessions.length > limit ? sessions.slice(0, limit) : sessions
return HttpServerResponse.jsonUnsafe(list, {
headers:
sessions.length > limit && list.length > 0
? { "x-next-cursor": String(list[list.length - 1].time.updated) }
: undefined,
})
})
const resource = Effect.fn("ExperimentalHttpApi.resource")(function* () {
return yield* mcp.resources()
})
return handlers
.handle("console", getConsole)
.handle("consoleOrgs", listConsoleOrgs)
.handle("consoleSwitch", switchConsole)
.handle("tool", tool)
.handle("toolIDs", toolIDs)
.handle("worktree", worktree)
.handle("worktreeCreate", worktreeCreate)
.handle("worktreeRemove", worktreeRemove)
.handle("worktreeReset", worktreeReset)
.handle("session", session)
.handle("resource", resource)
}),
return handlers
.handle("console", getConsole)
.handle("consoleOrgs", listConsoleOrgs)
.handle("consoleSwitch", switchConsole)
.handle("tool", tool)
.handle("toolIDs", toolIDs)
.handle("worktree", worktree)
.handle("worktreeCreate", worktreeCreate)
.handle("worktreeRemove", worktreeRemove)
.handle("worktreeReset", worktreeReset)
.handle("session", session)
.handle("resource", resource)
}),
)

View file

@ -6,49 +6,49 @@ import { HttpApiBuilder } from "effect/unstable/httpapi"
import { InstanceHttpApi } from "../api"
export const fileHandlers = HttpApiBuilder.group(InstanceHttpApi, "file", (handlers) =>
Effect.gen(function* () {
const svc = yield* File.Service
const ripgrep = yield* Ripgrep.Service
Effect.gen(function* () {
const svc = yield* File.Service
const ripgrep = yield* Ripgrep.Service
const findText = Effect.fn("FileHttpApi.findText")(function* (ctx: { query: { pattern: string } }) {
return (yield* ripgrep
.search({ cwd: (yield* InstanceState.context).directory, pattern: ctx.query.pattern, limit: 10 })
.pipe(Effect.orDie)).items
const findText = Effect.fn("FileHttpApi.findText")(function* (ctx: { query: { pattern: string } }) {
return (yield* ripgrep
.search({ cwd: (yield* InstanceState.context).directory, pattern: ctx.query.pattern, limit: 10 })
.pipe(Effect.orDie)).items
})
const findFile = Effect.fn("FileHttpApi.findFile")(function* (ctx: {
query: { query: string; dirs?: "true" | "false"; type?: "file" | "directory"; limit?: number }
}) {
return yield* svc.search({
query: ctx.query.query,
limit: ctx.query.limit ?? 10,
dirs: ctx.query.dirs !== "false",
type: ctx.query.type,
})
})
const findFile = Effect.fn("FileHttpApi.findFile")(function* (ctx: {
query: { query: string; dirs?: "true" | "false"; type?: "file" | "directory"; limit?: number }
}) {
return yield* svc.search({
query: ctx.query.query,
limit: ctx.query.limit ?? 10,
dirs: ctx.query.dirs !== "false",
type: ctx.query.type,
})
})
const findSymbol = Effect.fn("FileHttpApi.findSymbol")(function* () {
return []
})
const findSymbol = Effect.fn("FileHttpApi.findSymbol")(function* () {
return []
})
const list = Effect.fn("FileHttpApi.list")(function* (ctx: { query: { path: string } }) {
return yield* svc.list(ctx.query.path)
})
const list = Effect.fn("FileHttpApi.list")(function* (ctx: { query: { path: string } }) {
return yield* svc.list(ctx.query.path)
})
const content = Effect.fn("FileHttpApi.content")(function* (ctx: { query: { path: string } }) {
return yield* svc.read(ctx.query.path)
})
const content = Effect.fn("FileHttpApi.content")(function* (ctx: { query: { path: string } }) {
return yield* svc.read(ctx.query.path)
})
const status = Effect.fn("FileHttpApi.status")(function* () {
return yield* svc.status()
})
const status = Effect.fn("FileHttpApi.status")(function* () {
return yield* svc.status()
})
return handlers
.handle("findText", findText)
.handle("findFile", findFile)
.handle("findSymbol", findSymbol)
.handle("list", list)
.handle("content", content)
.handle("status", status)
}),
return handlers
.handle("findText", findText)
.handle("findFile", findFile)
.handle("findSymbol", findSymbol)
.handle("list", list)
.handle("content", content)
.handle("status", status)
}),
)

View file

@ -65,92 +65,92 @@ function eventResponse() {
}
export const globalHandlers = HttpApiBuilder.group(RootHttpApi, "global", (handlers) =>
Effect.gen(function* () {
const config = yield* Config.Service
const installation = yield* Installation.Service
Effect.gen(function* () {
const config = yield* Config.Service
const installation = yield* Installation.Service
const health = Effect.fn("GlobalHttpApi.health")(function* () {
return { healthy: true as const, version: InstallationVersion }
const health = Effect.fn("GlobalHttpApi.health")(function* () {
return { healthy: true as const, version: InstallationVersion }
})
const event = Effect.fn("GlobalHttpApi.event")(function* () {
return eventResponse()
})
const configGet = Effect.fn("GlobalHttpApi.configGet")(function* () {
return yield* config.getGlobal()
})
const configUpdate = Effect.fn("GlobalHttpApi.configUpdate")(function* (ctx) {
return yield* config.updateGlobal(ctx.payload)
})
const dispose = Effect.fn("GlobalHttpApi.dispose")(function* () {
yield* Effect.promise(() => Instance.disposeAll())
GlobalBus.emit("event", {
directory: "global",
payload: { type: "global.disposed", properties: {} },
})
return true
})
const event = Effect.fn("GlobalHttpApi.event")(function* () {
return eventResponse()
})
const configGet = Effect.fn("GlobalHttpApi.configGet")(function* () {
return yield* config.getGlobal()
})
const configUpdate = Effect.fn("GlobalHttpApi.configUpdate")(function* (ctx) {
return yield* config.updateGlobal(ctx.payload)
})
const dispose = Effect.fn("GlobalHttpApi.dispose")(function* () {
yield* Effect.promise(() => Instance.disposeAll())
GlobalBus.emit("event", {
directory: "global",
payload: { type: "global.disposed", properties: {} },
})
return true
})
const upgrade = Effect.fn("GlobalHttpApi.upgrade")(function* (ctx: { payload: typeof GlobalUpgradeInput.Type }) {
const method = yield* installation.method()
if (method === "unknown") {
return {
status: 400,
body: { success: false as const, error: "Unknown installation method" },
}
const upgrade = Effect.fn("GlobalHttpApi.upgrade")(function* (ctx: { payload: typeof GlobalUpgradeInput.Type }) {
const method = yield* installation.method()
if (method === "unknown") {
return {
status: 400,
body: { success: false as const, error: "Unknown installation method" },
}
const target = ctx.payload.target || (yield* installation.latest(method))
const result = yield* installation.upgrade(method, target).pipe(
Effect.as({ status: 200, body: { success: true as const, version: target } }),
Effect.catch((err) =>
Effect.succeed({
status: 500,
body: {
success: false as const,
error: err instanceof Error ? err.message : String(err),
},
}),
),
)
if (!result.body.success) return result
GlobalBus.emit("event", {
directory: "global",
payload: {
type: Installation.Event.Updated.type,
properties: { version: target },
},
})
return result
}
const target = ctx.payload.target || (yield* installation.latest(method))
const result = yield* installation.upgrade(method, target).pipe(
Effect.as({ status: 200, body: { success: true as const, version: target } }),
Effect.catch((err) =>
Effect.succeed({
status: 500,
body: {
success: false as const,
error: err instanceof Error ? err.message : String(err),
},
}),
),
)
if (!result.body.success) return result
GlobalBus.emit("event", {
directory: "global",
payload: {
type: Installation.Event.Updated.type,
properties: { version: target },
},
})
return result
})
const upgradeRaw = Effect.fn("GlobalHttpApi.upgradeRaw")(function* (ctx: {
request: HttpServerRequest.HttpServerRequest
}) {
const body = yield* Effect.orDie(ctx.request.text)
const json = parseBody(body)
if (json === undefined) {
return HttpServerResponse.jsonUnsafe({ success: false, error: "Invalid request body" }, { status: 400 })
}
const payload = yield* Schema.decodeUnknownEffect(GlobalUpgradeInput)(json).pipe(
Effect.map((payload) => ({ valid: true as const, payload })),
Effect.catch(() => Effect.succeed({ valid: false as const })),
)
if (!payload.valid) {
return HttpServerResponse.jsonUnsafe({ success: false, error: "Invalid request body" }, { status: 400 })
}
const result = yield* upgrade({ payload: payload.payload })
return HttpServerResponse.jsonUnsafe(result.body, { status: result.status })
})
const upgradeRaw = Effect.fn("GlobalHttpApi.upgradeRaw")(function* (ctx: {
request: HttpServerRequest.HttpServerRequest
}) {
const body = yield* Effect.orDie(ctx.request.text)
const json = parseBody(body)
if (json === undefined) {
return HttpServerResponse.jsonUnsafe({ success: false, error: "Invalid request body" }, { status: 400 })
}
const payload = yield* Schema.decodeUnknownEffect(GlobalUpgradeInput)(json).pipe(
Effect.map((payload) => ({ valid: true as const, payload })),
Effect.catch(() => Effect.succeed({ valid: false as const })),
)
if (!payload.valid) {
return HttpServerResponse.jsonUnsafe({ success: false, error: "Invalid request body" }, { status: 400 })
}
const result = yield* upgrade({ payload: payload.payload })
return HttpServerResponse.jsonUnsafe(result.body, { status: result.status })
})
return handlers
.handle("health", health)
.handleRaw("event", event)
.handle("configGet", configGet)
.handle("configUpdate", configUpdate)
.handle("dispose", dispose)
.handleRaw("upgrade", upgradeRaw)
}),
return handlers
.handle("health", health)
.handleRaw("event", event)
.handle("configGet", configGet)
.handle("configUpdate", configUpdate)
.handle("dispose", dispose)
.handleRaw("upgrade", upgradeRaw)
}),
)

View file

@ -12,68 +12,68 @@ import { InstanceHttpApi } from "../api"
import { markInstanceForDisposal } from "../lifecycle"
export const instanceHandlers = HttpApiBuilder.group(InstanceHttpApi, "instance", (handlers) =>
Effect.gen(function* () {
const agent = yield* Agent.Service
const command = yield* Command.Service
const format = yield* Format.Service
const lsp = yield* LSP.Service
const skill = yield* Skill.Service
const vcs = yield* Vcs.Service
Effect.gen(function* () {
const agent = yield* Agent.Service
const command = yield* Command.Service
const format = yield* Format.Service
const lsp = yield* LSP.Service
const skill = yield* Skill.Service
const vcs = yield* Vcs.Service
const dispose = Effect.fn("InstanceHttpApi.dispose")(function* () {
yield* markInstanceForDisposal(yield* InstanceState.context)
return true
})
const dispose = Effect.fn("InstanceHttpApi.dispose")(function* () {
yield* markInstanceForDisposal(yield* InstanceState.context)
return true
})
const getPath = Effect.fn("InstanceHttpApi.path")(function* () {
const ctx = yield* InstanceState.context
return {
home: Global.Path.home,
state: Global.Path.state,
config: Global.Path.config,
worktree: ctx.worktree,
directory: ctx.directory,
}
})
const getPath = Effect.fn("InstanceHttpApi.path")(function* () {
const ctx = yield* InstanceState.context
return {
home: Global.Path.home,
state: Global.Path.state,
config: Global.Path.config,
worktree: ctx.worktree,
directory: ctx.directory,
}
})
const getVcs = Effect.fn("InstanceHttpApi.vcs")(function* () {
const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { concurrency: 2 })
return { branch, default_branch }
})
const getVcs = Effect.fn("InstanceHttpApi.vcs")(function* () {
const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { concurrency: 2 })
return { branch, default_branch }
})
const getVcsDiff = Effect.fn("InstanceHttpApi.vcsDiff")(function* (ctx: { query: { mode: Vcs.Mode } }) {
return yield* vcs.diff(ctx.query.mode)
})
const getVcsDiff = Effect.fn("InstanceHttpApi.vcsDiff")(function* (ctx: { query: { mode: Vcs.Mode } }) {
return yield* vcs.diff(ctx.query.mode)
})
const getCommand = Effect.fn("InstanceHttpApi.command")(function* () {
return yield* command.list()
})
const getCommand = Effect.fn("InstanceHttpApi.command")(function* () {
return yield* command.list()
})
const getAgent = Effect.fn("InstanceHttpApi.agent")(function* () {
return yield* agent.list()
})
const getAgent = Effect.fn("InstanceHttpApi.agent")(function* () {
return yield* agent.list()
})
const getSkill = Effect.fn("InstanceHttpApi.skill")(function* () {
return yield* skill.all()
})
const getSkill = Effect.fn("InstanceHttpApi.skill")(function* () {
return yield* skill.all()
})
const getLsp = Effect.fn("InstanceHttpApi.lsp")(function* () {
return yield* lsp.status()
})
const getLsp = Effect.fn("InstanceHttpApi.lsp")(function* () {
return yield* lsp.status()
})
const getFormatter = Effect.fn("InstanceHttpApi.formatter")(function* () {
return yield* format.status()
})
const getFormatter = Effect.fn("InstanceHttpApi.formatter")(function* () {
return yield* format.status()
})
return handlers
.handle("dispose", dispose)
.handle("path", getPath)
.handle("vcs", getVcs)
.handle("vcsDiff", getVcsDiff)
.handle("command", getCommand)
.handle("agent", getAgent)
.handle("skill", getSkill)
.handle("lsp", getLsp)
.handle("formatter", getFormatter)
}),
return handlers
.handle("dispose", dispose)
.handle("path", getPath)
.handle("vcs", getVcs)
.handle("vcsDiff", getVcsDiff)
.handle("command", getCommand)
.handle("agent", getAgent)
.handle("skill", getSkill)
.handle("lsp", getLsp)
.handle("formatter", getFormatter)
}),
)

View file

@ -5,64 +5,64 @@ import { InstanceHttpApi } from "../api"
import { AddPayload, AuthCallbackPayload, StatusMap, UnsupportedOAuthError } from "../groups/mcp"
export const mcpHandlers = HttpApiBuilder.group(InstanceHttpApi, "mcp", (handlers) =>
Effect.gen(function* () {
const mcp = yield* MCP.Service
Effect.gen(function* () {
const mcp = yield* MCP.Service
const status = Effect.fn("McpHttpApi.status")(function* () {
return yield* mcp.status()
})
const status = Effect.fn("McpHttpApi.status")(function* () {
return yield* mcp.status()
})
const add = Effect.fn("McpHttpApi.add")(function* (ctx: { payload: typeof AddPayload.Type }) {
const result = (yield* mcp.add(ctx.payload.name, ctx.payload.config)).status
return yield* Schema.decodeUnknownEffect(StatusMap)(
"status" in result ? { [ctx.payload.name]: result } : result,
).pipe(Effect.mapError(() => new HttpApiError.BadRequest({})))
})
const add = Effect.fn("McpHttpApi.add")(function* (ctx: { payload: typeof AddPayload.Type }) {
const result = (yield* mcp.add(ctx.payload.name, ctx.payload.config)).status
return yield* Schema.decodeUnknownEffect(StatusMap)(
"status" in result ? { [ctx.payload.name]: result } : result,
).pipe(Effect.mapError(() => new HttpApiError.BadRequest({})))
})
const authStart = Effect.fn("McpHttpApi.authStart")(function* (ctx: { params: { name: string } }) {
if (!(yield* mcp.supportsOAuth(ctx.params.name))) {
return yield* new UnsupportedOAuthError({ error: `MCP server ${ctx.params.name} does not support OAuth` })
}
return yield* mcp.startAuth(ctx.params.name)
})
const authStart = Effect.fn("McpHttpApi.authStart")(function* (ctx: { params: { name: string } }) {
if (!(yield* mcp.supportsOAuth(ctx.params.name))) {
return yield* new UnsupportedOAuthError({ error: `MCP server ${ctx.params.name} does not support OAuth` })
}
return yield* mcp.startAuth(ctx.params.name)
})
const authCallback = Effect.fn("McpHttpApi.authCallback")(function* (ctx: {
params: { name: string }
payload: typeof AuthCallbackPayload.Type
}) {
return yield* mcp.finishAuth(ctx.params.name, ctx.payload.code)
})
const authCallback = Effect.fn("McpHttpApi.authCallback")(function* (ctx: {
params: { name: string }
payload: typeof AuthCallbackPayload.Type
}) {
return yield* mcp.finishAuth(ctx.params.name, ctx.payload.code)
})
const authAuthenticate = Effect.fn("McpHttpApi.authAuthenticate")(function* (ctx: { params: { name: string } }) {
if (!(yield* mcp.supportsOAuth(ctx.params.name))) {
return yield* new UnsupportedOAuthError({ error: `MCP server ${ctx.params.name} does not support OAuth` })
}
return yield* mcp.authenticate(ctx.params.name)
})
const authAuthenticate = Effect.fn("McpHttpApi.authAuthenticate")(function* (ctx: { params: { name: string } }) {
if (!(yield* mcp.supportsOAuth(ctx.params.name))) {
return yield* new UnsupportedOAuthError({ error: `MCP server ${ctx.params.name} does not support OAuth` })
}
return yield* mcp.authenticate(ctx.params.name)
})
const authRemove = Effect.fn("McpHttpApi.authRemove")(function* (ctx: { params: { name: string } }) {
yield* mcp.removeAuth(ctx.params.name)
return { success: true as const }
})
const authRemove = Effect.fn("McpHttpApi.authRemove")(function* (ctx: { params: { name: string } }) {
yield* mcp.removeAuth(ctx.params.name)
return { success: true as const }
})
const connect = Effect.fn("McpHttpApi.connect")(function* (ctx: { params: { name: string } }) {
yield* mcp.connect(ctx.params.name)
return true
})
const connect = Effect.fn("McpHttpApi.connect")(function* (ctx: { params: { name: string } }) {
yield* mcp.connect(ctx.params.name)
return true
})
const disconnect = Effect.fn("McpHttpApi.disconnect")(function* (ctx: { params: { name: string } }) {
yield* mcp.disconnect(ctx.params.name)
return true
})
const disconnect = Effect.fn("McpHttpApi.disconnect")(function* (ctx: { params: { name: string } }) {
yield* mcp.disconnect(ctx.params.name)
return true
})
return handlers
.handle("status", status)
.handle("add", add)
.handle("authStart", authStart)
.handle("authCallback", authCallback)
.handle("authAuthenticate", authAuthenticate)
.handle("authRemove", authRemove)
.handle("connect", connect)
.handle("disconnect", disconnect)
}),
return handlers
.handle("status", status)
.handle("add", add)
.handle("authStart", authStart)
.handle("authCallback", authCallback)
.handle("authAuthenticate", authAuthenticate)
.handle("authRemove", authRemove)
.handle("connect", connect)
.handle("disconnect", disconnect)
}),
)

View file

@ -5,25 +5,25 @@ import { HttpApiBuilder } from "effect/unstable/httpapi"
import { InstanceHttpApi } from "../api"
export const permissionHandlers = HttpApiBuilder.group(InstanceHttpApi, "permission", (handlers) =>
Effect.gen(function* () {
const svc = yield* Permission.Service
Effect.gen(function* () {
const svc = yield* Permission.Service
const list = Effect.fn("PermissionHttpApi.list")(function* () {
return yield* svc.list()
const list = Effect.fn("PermissionHttpApi.list")(function* () {
return yield* svc.list()
})
const reply = Effect.fn("PermissionHttpApi.reply")(function* (ctx: {
params: { requestID: PermissionID }
payload: Permission.ReplyBody
}) {
yield* svc.reply({
requestID: ctx.params.requestID,
reply: ctx.payload.reply,
message: ctx.payload.message,
})
return true
})
const reply = Effect.fn("PermissionHttpApi.reply")(function* (ctx: {
params: { requestID: PermissionID }
payload: Permission.ReplyBody
}) {
yield* svc.reply({
requestID: ctx.params.requestID,
reply: ctx.payload.reply,
message: ctx.payload.message,
})
return true
})
return handlers.handle("list", list).handle("reply", reply)
}),
return handlers.handle("list", list).handle("reply", reply)
}),
)

View file

@ -9,38 +9,38 @@ import { InstanceHttpApi } from "../api"
import { markInstanceForReload } from "../lifecycle"
export const projectHandlers = HttpApiBuilder.group(InstanceHttpApi, "project", (handlers) =>
Effect.gen(function* () {
const svc = yield* Project.Service
Effect.gen(function* () {
const svc = yield* Project.Service
const list = Effect.fn("ProjectHttpApi.list")(function* () {
return yield* svc.list()
})
const list = Effect.fn("ProjectHttpApi.list")(function* () {
return yield* svc.list()
})
const current = Effect.fn("ProjectHttpApi.current")(function* () {
return (yield* InstanceState.context).project
})
const current = Effect.fn("ProjectHttpApi.current")(function* () {
return (yield* InstanceState.context).project
})
const initGit = Effect.fn("ProjectHttpApi.initGit")(function* () {
const ctx = yield* InstanceState.context
const next = yield* svc.initGit({ directory: ctx.directory, project: ctx.project })
if (next.id === ctx.project.id && next.vcs === ctx.project.vcs && next.worktree === ctx.project.worktree)
return next
yield* markInstanceForReload(ctx, {
directory: ctx.directory,
worktree: ctx.directory,
project: next,
init: () => AppRuntime.runPromise(InstanceBootstrap),
})
const initGit = Effect.fn("ProjectHttpApi.initGit")(function* () {
const ctx = yield* InstanceState.context
const next = yield* svc.initGit({ directory: ctx.directory, project: ctx.project })
if (next.id === ctx.project.id && next.vcs === ctx.project.vcs && next.worktree === ctx.project.worktree)
return next
yield* markInstanceForReload(ctx, {
directory: ctx.directory,
worktree: ctx.directory,
project: next,
init: () => AppRuntime.runPromise(InstanceBootstrap),
})
return next
})
const update = Effect.fn("ProjectHttpApi.update")(function* (ctx: {
params: { projectID: ProjectID }
payload: Project.UpdatePayload
}) {
return yield* svc.update({ ...ctx.payload, projectID: ctx.params.projectID })
})
const update = Effect.fn("ProjectHttpApi.update")(function* (ctx: {
params: { projectID: ProjectID }
payload: Project.UpdatePayload
}) {
return yield* svc.update({ ...ctx.payload, projectID: ctx.params.projectID })
})
return handlers.handle("list", list).handle("current", current).handle("initGit", initGit).handle("update", update)
}),
return handlers.handle("list", list).handle("current", current).handle("initGit", initGit).handle("update", update)
}),
)

View file

@ -10,80 +10,80 @@ import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi"
import { InstanceHttpApi } from "../api"
export const providerHandlers = HttpApiBuilder.group(InstanceHttpApi, "provider", (handlers) =>
Effect.gen(function* () {
const cfg = yield* Config.Service
const provider = yield* Provider.Service
const svc = yield* ProviderAuth.Service
Effect.gen(function* () {
const cfg = yield* Config.Service
const provider = yield* Provider.Service
const svc = yield* ProviderAuth.Service
const list = Effect.fn("ProviderHttpApi.list")(function* () {
const config = yield* cfg.get()
const all = yield* Effect.promise(() => ModelsDev.get())
const disabled = new Set(config.disabled_providers ?? [])
const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
const filtered: Record<string, (typeof all)[string]> = {}
for (const [key, value] of Object.entries(all)) {
if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) filtered[key] = value
}
const connected = yield* provider.list()
const providers = Object.assign(
mapValues(filtered, (item) => Provider.fromModelsDevProvider(item)),
connected,
)
return {
all: Object.values(providers),
default: Provider.defaultModelIDs(providers),
connected: Object.keys(connected),
}
})
const list = Effect.fn("ProviderHttpApi.list")(function* () {
const config = yield* cfg.get()
const all = yield* Effect.promise(() => ModelsDev.get())
const disabled = new Set(config.disabled_providers ?? [])
const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
const filtered: Record<string, (typeof all)[string]> = {}
for (const [key, value] of Object.entries(all)) {
if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) filtered[key] = value
}
const connected = yield* provider.list()
const providers = Object.assign(
mapValues(filtered, (item) => Provider.fromModelsDevProvider(item)),
connected,
)
return {
all: Object.values(providers),
default: Provider.defaultModelIDs(providers),
connected: Object.keys(connected),
}
})
const auth = Effect.fn("ProviderHttpApi.auth")(function* () {
return yield* svc.methods()
})
const auth = Effect.fn("ProviderHttpApi.auth")(function* () {
return yield* svc.methods()
})
const authorize = Effect.fn("ProviderHttpApi.authorize")(function* (ctx: {
params: { providerID: ProviderID }
payload: ProviderAuth.AuthorizeInput
}) {
return yield* svc
.authorize({
providerID: ctx.params.providerID,
method: ctx.payload.method,
inputs: ctx.payload.inputs,
})
.pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({}))))
})
const authorize = Effect.fn("ProviderHttpApi.authorize")(function* (ctx: {
params: { providerID: ProviderID }
payload: ProviderAuth.AuthorizeInput
}) {
return yield* svc
.authorize({
providerID: ctx.params.providerID,
method: ctx.payload.method,
inputs: ctx.payload.inputs,
})
.pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({}))))
})
const authorizeRaw = Effect.fn("ProviderHttpApi.authorizeRaw")(function* (ctx: {
params: { providerID: ProviderID }
request: HttpServerRequest.HttpServerRequest
}) {
const body = yield* Effect.orDie(ctx.request.text)
const payload = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(ProviderAuth.AuthorizeInput))(body).pipe(
Effect.mapError(() => new HttpApiError.BadRequest({})),
)
const result = yield* authorize({ params: ctx.params, payload })
if (result === undefined) return HttpServerResponse.empty({ status: 200 })
return HttpServerResponse.jsonUnsafe(result)
})
const authorizeRaw = Effect.fn("ProviderHttpApi.authorizeRaw")(function* (ctx: {
params: { providerID: ProviderID }
request: HttpServerRequest.HttpServerRequest
}) {
const body = yield* Effect.orDie(ctx.request.text)
const payload = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(ProviderAuth.AuthorizeInput))(body).pipe(
Effect.mapError(() => new HttpApiError.BadRequest({})),
)
const result = yield* authorize({ params: ctx.params, payload })
if (result === undefined) return HttpServerResponse.empty({ status: 200 })
return HttpServerResponse.jsonUnsafe(result)
})
const callback = Effect.fn("ProviderHttpApi.callback")(function* (ctx: {
params: { providerID: ProviderID }
payload: ProviderAuth.CallbackInput
}) {
yield* svc
.callback({
providerID: ctx.params.providerID,
method: ctx.payload.method,
code: ctx.payload.code,
})
.pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({}))))
return true
})
const callback = Effect.fn("ProviderHttpApi.callback")(function* (ctx: {
params: { providerID: ProviderID }
payload: ProviderAuth.CallbackInput
}) {
yield* svc
.callback({
providerID: ctx.params.providerID,
method: ctx.payload.method,
code: ctx.payload.code,
})
.pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({}))))
return true
})
return handlers
.handle("list", list)
.handle("auth", auth)
.handleRaw("authorize", authorizeRaw)
.handle("callback", callback)
}),
return handlers
.handle("list", list)
.handle("auth", auth)
.handleRaw("authorize", authorizeRaw)
.handle("callback", callback)
}),
)

View file

@ -88,7 +88,9 @@ export const ptyConnectRoute = HttpRouter.add(
},
send: (data: string | Uint8Array | ArrayBuffer) => {
if (closed) return
Effect.runFork(write(data instanceof ArrayBuffer ? new Uint8Array(data) : data).pipe(Effect.catch(() => Effect.void)))
Effect.runFork(
write(data instanceof ArrayBuffer ? new Uint8Array(data) : data).pipe(Effect.catch(() => Effect.void)),
)
},
close: (code?: number, reason?: string) => {
if (closed) return

View file

@ -5,29 +5,29 @@ import { HttpApiBuilder } from "effect/unstable/httpapi"
import { InstanceHttpApi } from "../api"
export const questionHandlers = HttpApiBuilder.group(InstanceHttpApi, "question", (handlers) =>
Effect.gen(function* () {
const svc = yield* Question.Service
Effect.gen(function* () {
const svc = yield* Question.Service
const list = Effect.fn("QuestionHttpApi.list")(function* () {
return yield* svc.list()
const list = Effect.fn("QuestionHttpApi.list")(function* () {
return yield* svc.list()
})
const reply = Effect.fn("QuestionHttpApi.reply")(function* (ctx: {
params: { requestID: QuestionID }
payload: Question.Reply
}) {
yield* svc.reply({
requestID: ctx.params.requestID,
answers: ctx.payload.answers,
})
return true
})
const reply = Effect.fn("QuestionHttpApi.reply")(function* (ctx: {
params: { requestID: QuestionID }
payload: Question.Reply
}) {
yield* svc.reply({
requestID: ctx.params.requestID,
answers: ctx.payload.answers,
})
return true
})
const reject = Effect.fn("QuestionHttpApi.reject")(function* (ctx: { params: { requestID: QuestionID } }) {
yield* svc.reject(ctx.params.requestID)
return true
})
const reject = Effect.fn("QuestionHttpApi.reject")(function* (ctx: { params: { requestID: QuestionID } }) {
yield* svc.reject(ctx.params.requestID)
return true
})
return handlers.handle("list", list).handle("reply", reply).handle("reject", reject)
}),
return handlers.handle("list", list).handle("reply", reply).handle("reject", reject)
}),
)

View file

@ -25,7 +25,20 @@ import * as Stream from "effect/Stream"
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { HttpApiBuilder, HttpApiError, HttpApiSchema } from "effect/unstable/httpapi"
import { InstanceHttpApi } from "../api"
import { CommandPayload, DiffQuery, ForkPayload, InitPayload, ListQuery, MessagesQuery, PermissionResponsePayload, PromptPayload, RevertPayload, ShellPayload, SummarizePayload, UpdatePayload } from "../groups/session"
import {
CommandPayload,
DiffQuery,
ForkPayload,
InitPayload,
ListQuery,
MessagesQuery,
PermissionResponsePayload,
PromptPayload,
RevertPayload,
ShellPayload,
SummarizePayload,
UpdatePayload,
} from "../groups/session"
const log = Log.create({ service: "server" })
@ -88,40 +101,42 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
params: { sessionID: SessionID }
query: typeof MessagesQuery.Type
}) {
return yield* mapNotFound(Effect.gen(function* () {
if (ctx.query.before && ctx.query.limit === undefined) return yield* new HttpApiError.BadRequest({})
if (ctx.query.before) {
const before = ctx.query.before
yield* Effect.try({
try: () => MessageV2.cursor.decode(before),
catch: () => new HttpApiError.BadRequest({}),
})
}
if (ctx.query.limit === undefined || ctx.query.limit === 0) {
return yield* mapNotFound(
Effect.gen(function* () {
if (ctx.query.before && ctx.query.limit === undefined) return yield* new HttpApiError.BadRequest({})
if (ctx.query.before) {
const before = ctx.query.before
yield* Effect.try({
try: () => MessageV2.cursor.decode(before),
catch: () => new HttpApiError.BadRequest({}),
})
}
if (ctx.query.limit === undefined || ctx.query.limit === 0) {
yield* session.get(ctx.params.sessionID)
return yield* session.messages({ sessionID: ctx.params.sessionID })
}
yield* session.get(ctx.params.sessionID)
return yield* session.messages({ sessionID: ctx.params.sessionID })
}
const page = MessageV2.page({
sessionID: ctx.params.sessionID,
limit: ctx.query.limit,
before: ctx.query.before,
})
if (!page.cursor) return page.items
yield* session.get(ctx.params.sessionID)
const page = MessageV2.page({
sessionID: ctx.params.sessionID,
limit: ctx.query.limit,
before: ctx.query.before,
})
if (!page.cursor) return page.items
const request = yield* HttpServerRequest.HttpServerRequest
const url = new URL(request.url, "http://localhost")
url.searchParams.set("limit", ctx.query.limit.toString())
url.searchParams.set("before", page.cursor)
return HttpServerResponse.jsonUnsafe(page.items, {
headers: {
"Access-Control-Expose-Headers": "Link, X-Next-Cursor",
Link: `<${url.toString()}>; rel="next"`,
"X-Next-Cursor": page.cursor,
},
})
}))
const request = yield* HttpServerRequest.HttpServerRequest
const url = new URL(request.url, "http://localhost")
url.searchParams.set("limit", ctx.query.limit.toString())
url.searchParams.set("before", page.cursor)
return HttpServerResponse.jsonUnsafe(page.items, {
headers: {
"Access-Control-Expose-Headers": "Link, X-Next-Cursor",
Link: `<${url.toString()}>; rel="next"`,
"X-Next-Cursor": page.cursor,
},
})
}),
)
})
const message = Effect.fn("SessionHttpApi.message")(function* (ctx: {

View file

@ -19,11 +19,12 @@ import * as Socket from "effect/unstable/socket/Socket"
type HandlerEffect = Effect.Effect<HttpServerResponse.HttpServerResponse, unhandled, never>
export class InstanceContextMiddleware extends HttpApiMiddleware.Service<InstanceContextMiddleware, {
requires: Session.Service
}>()(
"@opencode/ExperimentalHttpApiInstanceContext",
) {}
export class InstanceContextMiddleware extends HttpApiMiddleware.Service<
InstanceContextMiddleware,
{
requires: Session.Service
}
>()("@opencode/ExperimentalHttpApiInstanceContext") {}
function decode(input: string) {
try {
@ -53,9 +54,14 @@ function requestHeaders(request: HttpServerRequest.HttpServerRequest) {
return sourceRequest(request).headers
}
function writeSocket(write: (data: string | Uint8Array | Socket.CloseEvent) => Effect.Effect<void, unknown>, data: unknown) {
function writeSocket(
write: (data: string | Uint8Array | Socket.CloseEvent) => Effect.Effect<void, unknown>,
data: unknown,
) {
if (data instanceof Blob) {
void data.arrayBuffer().then((buffer) => Effect.runFork(write(new Uint8Array(buffer)).pipe(Effect.catch(() => Effect.void))))
void data
.arrayBuffer()
.then((buffer) => Effect.runFork(write(new Uint8Array(buffer)).pipe(Effect.catch(() => Effect.void))))
return
}
if (typeof data === "string" || data instanceof Uint8Array) {
@ -78,7 +84,8 @@ function proxyWebSocket(request: HttpServerRequest.HttpServerRequest, target: st
queue.length = 0
}
remote.onmessage = (event) => writeSocket(write, event.data)
remote.onerror = () => Effect.runFork(write(new Socket.CloseEvent(1011, "proxy error")).pipe(Effect.catch(() => Effect.void)))
remote.onerror = () =>
Effect.runFork(write(new Socket.CloseEvent(1011, "proxy error")).pipe(Effect.catch(() => Effect.void)))
remote.onclose = (event) =>
Effect.runFork(write(new Socket.CloseEvent(event.code, event.reason)).pipe(Effect.catch(() => Effect.void)))
@ -109,7 +116,9 @@ function proxyRemote(
const url = workspaceProxyURL(target.url, requestURL)
const source = sourceRequest(request)
if (source.headers.get("upgrade")?.toLowerCase() === "websocket") return proxyWebSocket(request, url)
return Effect.promise(() => ServerProxy.http(url, target.headers, source, workspace.id)).pipe(Effect.map(HttpServerResponse.raw))
return Effect.promise(() => ServerProxy.http(url, target.headers, source, workspace.id)).pipe(
Effect.map(HttpServerResponse.raw),
)
}
function requestContext() {
@ -118,14 +127,19 @@ function requestContext() {
)
}
function provideRequestContext(effect: HandlerEffect, request: HttpServerRequest.HttpServerRequest, sessionWorkspaceID?: WorkspaceID) {
function provideRequestContext(
effect: HandlerEffect,
request: HttpServerRequest.HttpServerRequest,
sessionWorkspaceID?: WorkspaceID,
) {
return Effect.gen(function* () {
const url = new URL(request.url, "http://localhost")
const headers = requestHeaders(request)
const envWorkspaceID = Flag.OPENCODE_WORKSPACE_ID ? WorkspaceID.make(Flag.OPENCODE_WORKSPACE_ID) : undefined
const workspaceParam = url.searchParams.get("workspace")
const workspaceID = sessionWorkspaceID ?? (workspaceParam ? WorkspaceID.make(workspaceParam) : undefined)
const workspace = workspaceID && !envWorkspaceID ? yield* Effect.promise(() => Workspace.get(workspaceID)) : undefined
const workspace =
workspaceID && !envWorkspaceID ? yield* Effect.promise(() => Workspace.get(workspaceID)) : undefined
if (workspaceID && !workspace && !envWorkspaceID) {
return HttpServerResponse.text(`Workspace not found: ${workspaceID}`, {
@ -134,7 +148,12 @@ function provideRequestContext(effect: HandlerEffect, request: HttpServerRequest
})
}
if (workspace && !isLocalWorkspaceRoute(request.method, url.pathname) && !url.pathname.startsWith("/console") && !envWorkspaceID) {
if (
workspace &&
!isLocalWorkspaceRoute(request.method, url.pathname) &&
!url.pathname.startsWith("/console") &&
!envWorkspaceID
) {
const adaptor = yield* Effect.promise(() => getAdaptor(workspace.projectID, workspace.type))
const target = yield* Effect.promise(() => Promise.resolve(adaptor.target(workspace)))
if (target.type === "remote") return yield* proxyRemote(request, workspace, target, url)
@ -186,6 +205,8 @@ export const instanceContextLayer = Layer.succeed(
InstanceContextMiddleware.of((effect) => provideInstanceContext(effect)),
)
export const instanceRouterLayer = HttpRouter.middleware()(Effect.succeed((effect) =>
requestContext().pipe(Effect.flatMap((request) => provideRequestContext(effect, request))),
)).layer
export const instanceRouterLayer = HttpRouter.middleware()(
Effect.succeed((effect) =>
requestContext().pipe(Effect.flatMap((request) => provideRequestContext(effect, request))),
),
).layer

View file

@ -14,7 +14,10 @@ export const markInstanceForDisposal = (ctx: InstanceContext) =>
export const markInstanceForReload = (ctx: InstanceContext, next: Parameters<typeof Instance.reload>[0]) =>
HttpEffect.appendPreResponseHandler((_request, response) =>
Effect.as(Effect.uninterruptible(Effect.promise(() => Instance.restore(ctx, () => Instance.reload(next)))), response),
Effect.as(
Effect.uninterruptible(Effect.promise(() => Instance.restore(ctx, () => Instance.reload(next)))),
response,
),
)
export const disposeMiddleware: HttpMiddleware.HttpMiddleware = (effect) =>

View file

@ -126,8 +126,13 @@ function matchLegacyOpenApi(input: Record<string, unknown>) {
// Workspace creation fields `branch` and `extra` are Schema.NullOr —
// genuinely nullable, not just optional. Re-add the null that the
// component-level strip above removed.
const ref = operation.requestBody.content?.["application/json"]?.schema?.$ref?.replace("#/components/schemas/", "")
const properties = ref ? spec.components?.schemas?.[ref]?.properties : operation.requestBody.content?.["application/json"]?.schema?.properties
const ref = operation.requestBody.content?.["application/json"]?.schema?.$ref?.replace(
"#/components/schemas/",
"",
)
const properties = ref
? spec.components?.schemas?.[ref]?.properties
: operation.requestBody.content?.["application/json"]?.schema?.properties
if (properties?.branch) properties.branch = { anyOf: [properties.branch, { type: "null" }] }
if (properties?.extra) properties.extra = { anyOf: [properties.extra, { type: "null" }] }
}
@ -150,7 +155,10 @@ function matchLegacyOpenApi(input: Record<string, unknown>) {
description: "Event stream",
content: {
"text/event-stream": {
schema: path === "/event" ? { $ref: "#/components/schemas/Event" } : { $ref: "#/components/schemas/GlobalEvent" },
schema:
path === "/event"
? { $ref: "#/components/schemas/Event" }
: { $ref: "#/components/schemas/GlobalEvent" },
},
},
}
@ -251,7 +259,8 @@ function applyLegacySchemaOverrides(spec: OpenApiSpec) {
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)
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
@ -486,12 +495,11 @@ function normalizeParameter(param: OpenApiParameter, route: string) {
param.schema = stripOptionalNull(param.schema)
}
export const PublicApi = OpenCodeHttpApi
.annotateMerge(
OpenApi.annotations({
title: "opencode",
version: "1.0.0",
description: "opencode api",
transform: matchLegacyOpenApi,
}),
)
export const PublicApi = OpenCodeHttpApi.annotateMerge(
OpenApi.annotations({
title: "opencode",
version: "1.0.0",
description: "opencode api",
transform: matchLegacyOpenApi,
}),
)

View file

@ -38,7 +38,9 @@ type ServerApp = {
request(input: string | URL | Request, init?: RequestInit): Response | Promise<Response>
}
const DefaultHono = lazy(() => withBackend({ backend: "hono", reason: "stable" }, createHono({}, { backend: "hono", reason: "stable" })))
const DefaultHono = lazy(() =>
withBackend({ backend: "hono", reason: "stable" }, createHono({}, { backend: "hono", reason: "stable" })),
)
const DefaultHttpApi = lazy(() => createDefaultHttpApi())
function select() {
@ -86,7 +88,10 @@ function createHttpApi() {
}
}
function createHono(opts: { cors?: string[] }, selection: ServerBackend.Selection = ServerBackend.force(select(), "hono")) {
function createHono(
opts: { cors?: string[] },
selection: ServerBackend.Selection = ServerBackend.force(select(), "hono"),
) {
const backendAttributes = ServerBackend.attributes(selection)
const app = new Hono()
.onError(ErrorMiddleware)

View file

@ -461,7 +461,9 @@ type AssistantError = z.infer<typeof AssistantErrorZod>
// Effect Schema for the same union — used by HttpApi OpenAPI generation.
const AssistantErrorSchema = Schema.Union([
AuthError.EffectSchema,
Schema.Struct({ name: Schema.Literal("UnknownError"), data: Schema.Struct({ message: Schema.String }) }).annotate({ identifier: "UnknownError" }),
Schema.Struct({ name: Schema.Literal("UnknownError"), data: Schema.Struct({ message: Schema.String }) }).annotate({
identifier: "UnknownError",
}),
OutputLengthError.EffectSchema,
AbortedError.EffectSchema,
StructuredOutputError.EffectSchema,

View file

@ -11,8 +11,6 @@ export const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0))
*/
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
/**
* Optional public JSON field that can hold explicit `undefined` on the type
* side but encodes it as an omitted key, matching legacy `JSON.stringify`.

View file

@ -126,9 +126,9 @@ function requestBodyKey(spec: OpenApiSpec, body: unknown) {
function requestBodySchemaKind(spec: OpenApiSpec, schema: OpenApiSchema | undefined) {
if (!schema) return ""
const resolved = (schema.$ref ? spec.components?.schemas?.[schema.$ref.replace("#/components/schemas/", "")] : schema) as
| OpenApiSchema
| undefined
const resolved = (
schema.$ref ? spec.components?.schemas?.[schema.$ref.replace("#/components/schemas/", "")] : schema
) as OpenApiSchema | undefined
if (resolved?.properties) return "object"
if (resolved?.anyOf ?? resolved?.oneOf ?? resolved?.allOf) return "object"
return resolved?.type ?? schema.type ?? "inline"

View file

@ -214,7 +214,11 @@ describe("workspace HttpApi", () => {
const workspace = await Instance.provide({
directory: tmp.path,
fn: async () => {
registerAdaptor(Instance.project.id, "remote-target", remoteAdaptor(path.join(tmp.path, ".remote"), "https://remote.test/base"))
registerAdaptor(
Instance.project.id,
"remote-target",
remoteAdaptor(path.join(tmp.path, ".remote"), "https://remote.test/base"),
)
return Workspace.create({
type: "remote-target",
branch: null,

View file

@ -1339,10 +1339,14 @@
"type": "object",
"properties": {
"rows": {
"type": "number"
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"cols": {
"type": "number"
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
}
},
"required": ["rows", "cols"]
@ -5595,10 +5599,14 @@
"required": ["text"]
},
"line_number": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"absolute_offset": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"submatches": {
"type": "array",
@ -5615,10 +5623,14 @@
"required": ["text"]
},
"start": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"end": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["match", "start", "end"]
@ -6901,7 +6913,9 @@
},
"duration": {
"description": "Duration in milliseconds",
"type": "number"
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
}
},
"required": ["message", "variant"]
@ -7621,13 +7635,19 @@
"type": "object",
"properties": {
"created": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"updated": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"initialized": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["created", "updated"]
@ -7913,10 +7933,14 @@
"type": "string"
},
"additions": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"deletions": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"status": {
"type": "string",
@ -8039,7 +8063,9 @@
"type": "string"
},
"retries": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["message", "retries"]
@ -8083,7 +8109,9 @@
"type": "string"
},
"statusCode": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"isRetryable": {
"type": "boolean"
@ -8420,13 +8448,17 @@
"const": "retry"
},
"attempt": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"message": {
"type": "string"
},
"next": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["type", "attempt", "message", "next"]
@ -8591,7 +8623,9 @@
},
"duration": {
"description": "Duration in milliseconds",
"type": "number"
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
}
},
"required": ["message", "variant"]
@ -8777,7 +8811,9 @@
"enum": ["running", "exited"]
},
"pid": {
"type": "number"
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
}
},
"required": ["id", "title", "command", "args", "cwd", "status", "pid"]
@ -8835,7 +8871,9 @@
"pattern": "^pty.*"
},
"exitCode": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["id", "exitCode"]
@ -9024,7 +9062,9 @@
"type": "object",
"properties": {
"created": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["created"]
@ -9102,10 +9142,14 @@
"type": "object",
"properties": {
"created": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"completed": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["created"]
@ -9173,25 +9217,37 @@
"type": "object",
"properties": {
"total": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"input": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"output": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"reasoning": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"cache": {
"type": "object",
"properties": {
"read": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"write": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["read", "write"]
@ -9311,10 +9367,14 @@
"type": "object",
"properties": {
"start": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"end": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["start"]
@ -9408,10 +9468,14 @@
"type": "object",
"properties": {
"start": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"end": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["start"]
@ -9427,12 +9491,12 @@
},
"start": {
"type": "integer",
"minimum": -9007199254740991,
"minimum": 0,
"maximum": 9007199254740991
},
"end": {
"type": "integer",
"minimum": -9007199254740991,
"minimum": 0,
"maximum": 9007199254740991
}
},
@ -9461,10 +9525,14 @@
"type": "object",
"properties": {
"line": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"character": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["line", "character"]
@ -9473,10 +9541,14 @@
"type": "object",
"properties": {
"line": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"character": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["line", "character"]
@ -9505,7 +9577,7 @@
},
"kind": {
"type": "integer",
"minimum": -9007199254740991,
"minimum": 0,
"maximum": 9007199254740991
}
},
@ -9625,7 +9697,9 @@
"type": "object",
"properties": {
"start": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["start"]
@ -9664,13 +9738,19 @@
"type": "object",
"properties": {
"start": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"end": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"compacted": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["start", "end"]
@ -9712,10 +9792,14 @@
"type": "object",
"properties": {
"start": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"end": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["start", "end"]
@ -9834,25 +9918,37 @@
"type": "object",
"properties": {
"total": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"input": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"output": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"reasoning": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"cache": {
"type": "object",
"properties": {
"read": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"write": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["read", "write"]
@ -9949,12 +10045,12 @@
},
"start": {
"type": "integer",
"minimum": -9007199254740991,
"minimum": 0,
"maximum": 9007199254740991
},
"end": {
"type": "integer",
"minimum": -9007199254740991,
"minimum": 0,
"maximum": 9007199254740991
}
},
@ -9983,7 +10079,9 @@
"const": "retry"
},
"attempt": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"error": {
"$ref": "#/components/schemas/APIError"
@ -9992,7 +10090,9 @@
"type": "object",
"properties": {
"created": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["created"]
@ -10090,7 +10190,9 @@
"$ref": "#/components/schemas/Part"
},
"time": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["sessionID", "part", "time"]
@ -10182,13 +10284,19 @@
"type": "object",
"properties": {
"additions": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"deletions": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"files": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"diffs": {
"type": "array",
@ -10218,16 +10326,24 @@
"type": "object",
"properties": {
"created": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"updated": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"compacting": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"archived": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["created", "updated"]
@ -10434,7 +10550,9 @@
"$ref": "#/components/schemas/Part"
},
"time": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["sessionID", "part", "time"]
@ -10631,13 +10749,19 @@
"type": "object",
"properties": {
"additions": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"deletions": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"files": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"diffs": {
"type": "array",
@ -10694,7 +10818,9 @@
"created": {
"anyOf": [
{
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
@ -10704,7 +10830,9 @@
"updated": {
"anyOf": [
{
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
@ -10714,7 +10842,9 @@
"compacting": {
"anyOf": [
{
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
@ -10724,7 +10854,9 @@
"archived": {
"anyOf": [
{
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
@ -11481,7 +11613,9 @@
},
"timeout": {
"description": "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",
"type": "number"
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
}
},
"required": ["type", "command"]
@ -11547,7 +11681,9 @@
},
"timeout": {
"description": "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",
"type": "number"
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
}
},
"required": ["type", "url"]
@ -12055,7 +12191,9 @@
"type": "string"
},
"expires": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"accountId": {
"type": "string"
@ -12458,7 +12596,9 @@
"type": "string"
},
"switchableOrgCount": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["consoleManagedProviders", "switchableOrgCount"]
@ -12579,13 +12719,19 @@
"type": "object",
"properties": {
"additions": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"deletions": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"files": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"diffs": {
"type": "array",
@ -12615,16 +12761,24 @@
"type": "object",
"properties": {
"created": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"updated": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"compacting": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"archived": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["created", "updated"]
@ -12710,10 +12864,14 @@
"type": "object",
"properties": {
"start": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"end": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": ["start"]
@ -12776,12 +12934,12 @@
},
"start": {
"type": "integer",
"minimum": -9007199254740991,
"minimum": 0,
"maximum": 9007199254740991
},
"end": {
"type": "integer",
"minimum": -9007199254740991,
"minimum": 0,
"maximum": 9007199254740991
}
},
@ -12956,7 +13114,9 @@
"type": "string"
},
"kind": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"location": {
"type": "object",
@ -13029,16 +13189,24 @@
"type": "object",
"properties": {
"oldStart": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"oldLines": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"newStart": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"newLines": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"lines": {
"type": "array",
@ -13074,12 +13242,12 @@
},
"added": {
"type": "integer",
"minimum": -9007199254740991,
"minimum": 0,
"maximum": 9007199254740991
},
"removed": {
"type": "integer",
"minimum": -9007199254740991,
"minimum": 0,
"maximum": 9007199254740991
},
"status": {
@ -13360,10 +13528,14 @@
"type": "string"
},
"additions": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"deletions": {
"type": "number"
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"status": {
"type": "string",