fix(session): tighten http error contracts (#27308)
This commit is contained in:
@@ -141,7 +141,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
params: { sessionID: SessionID },
|
params: { sessionID: SessionID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: described(Schema.Array(Session.Info), "List of children"),
|
success: described(Schema.Array(Session.Info), "List of children"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.children",
|
identifier: "session.children",
|
||||||
@@ -153,7 +153,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
params: { sessionID: SessionID },
|
params: { sessionID: SessionID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: described(Schema.Array(Todo.Info), "Todo list"),
|
success: described(Schema.Array(Todo.Info), "Todo list"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.todo",
|
identifier: "session.todo",
|
||||||
@@ -250,7 +250,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
params: { sessionID: SessionID },
|
params: { sessionID: SessionID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: described(Schema.Boolean, "Aborted session"),
|
success: described(Schema.Boolean, "Aborted session"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: HttpApiError.BadRequest,
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.abort",
|
identifier: "session.abort",
|
||||||
@@ -263,7 +263,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
payload: InitPayload,
|
payload: InitPayload,
|
||||||
success: described(Schema.Boolean, "200"),
|
success: described(Schema.Boolean, "200"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.init",
|
identifier: "session.init",
|
||||||
@@ -314,7 +314,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
payload: PromptPayload,
|
payload: PromptPayload,
|
||||||
success: described(MessageV2.WithParts, "Created message"),
|
success: described(MessageV2.WithParts, "Created message"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.prompt",
|
identifier: "session.prompt",
|
||||||
@@ -327,7 +327,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
payload: PromptPayload,
|
payload: PromptPayload,
|
||||||
success: described(HttpApiSchema.NoContent, "Prompt accepted"),
|
success: described(HttpApiSchema.NoContent, "Prompt accepted"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.prompt_async",
|
identifier: "session.prompt_async",
|
||||||
@@ -341,7 +341,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
payload: CommandPayload,
|
payload: CommandPayload,
|
||||||
success: described(MessageV2.WithParts, "Created message"),
|
success: described(MessageV2.WithParts, "Created message"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.command",
|
identifier: "session.command",
|
||||||
@@ -354,7 +354,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
payload: ShellPayload,
|
payload: ShellPayload,
|
||||||
success: described(MessageV2.WithParts, "Created message"),
|
success: described(MessageV2.WithParts, "Created message"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.shell",
|
identifier: "session.shell",
|
||||||
@@ -367,7 +367,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
payload: RevertPayload,
|
payload: RevertPayload,
|
||||||
success: described(Session.Info, "Updated session"),
|
success: described(Session.Info, "Updated session"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.revert",
|
identifier: "session.revert",
|
||||||
@@ -380,7 +380,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
params: { sessionID: SessionID },
|
params: { sessionID: SessionID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: described(Session.Info, "Updated session"),
|
success: described(Session.Info, "Updated session"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.unrevert",
|
identifier: "session.unrevert",
|
||||||
@@ -393,7 +393,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
payload: PermissionResponsePayload,
|
payload: PermissionResponsePayload,
|
||||||
success: described(Schema.Boolean, "Permission processed successfully"),
|
success: described(Schema.Boolean, "Permission processed successfully"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "permission.respond",
|
identifier: "permission.respond",
|
||||||
@@ -406,7 +406,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
params: { sessionID: SessionID, messageID: MessageID },
|
params: { sessionID: SessionID, messageID: MessageID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: described(Schema.Boolean, "Successfully deleted message"),
|
success: described(Schema.Boolean, "Successfully deleted message"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "session.deleteMessage",
|
identifier: "session.deleteMessage",
|
||||||
@@ -419,7 +419,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
params: { sessionID: SessionID, messageID: MessageID, partID: PartID },
|
params: { sessionID: SessionID, messageID: MessageID, partID: PartID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: described(Schema.Boolean, "Successfully deleted part"),
|
success: described(Schema.Boolean, "Successfully deleted part"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "part.delete",
|
identifier: "part.delete",
|
||||||
@@ -431,7 +431,7 @@ export const SessionApi = HttpApi.make("session")
|
|||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
payload: MessageV2.Part,
|
payload: MessageV2.Part,
|
||||||
success: described(MessageV2.Part, "Successfully updated part"),
|
success: described(MessageV2.Part, "Successfully updated part"),
|
||||||
error: [HttpApiError.BadRequest, HttpApiError.NotFound],
|
error: [HttpApiError.BadRequest, ApiNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "part.update",
|
identifier: "part.update",
|
||||||
|
|||||||
@@ -68,15 +68,21 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
return Object.fromEntries(yield* statusSvc.list())
|
return Object.fromEntries(yield* statusSvc.list())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const requireSession = Effect.fn("SessionHttpApi.requireSession")(function* (sessionID: SessionID) {
|
||||||
|
return yield* SessionError.mapStorageNotFound(session.get(sessionID))
|
||||||
|
})
|
||||||
|
|
||||||
const get = Effect.fn("SessionHttpApi.get")(function* (ctx: { params: { sessionID: SessionID } }) {
|
const get = Effect.fn("SessionHttpApi.get")(function* (ctx: { params: { sessionID: SessionID } }) {
|
||||||
return yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID))
|
return yield* requireSession(ctx.params.sessionID)
|
||||||
})
|
})
|
||||||
|
|
||||||
const children = Effect.fn("SessionHttpApi.children")(function* (ctx: { params: { sessionID: SessionID } }) {
|
const children = Effect.fn("SessionHttpApi.children")(function* (ctx: { params: { sessionID: SessionID } }) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
return yield* session.children(ctx.params.sessionID)
|
return yield* session.children(ctx.params.sessionID)
|
||||||
})
|
})
|
||||||
|
|
||||||
const todo = Effect.fn("SessionHttpApi.todo")(function* (ctx: { params: { sessionID: SessionID } }) {
|
const todo = Effect.fn("SessionHttpApi.todo")(function* (ctx: { params: { sessionID: SessionID } }) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
return yield* todoSvc.get(ctx.params.sessionID)
|
return yield* todoSvc.get(ctx.params.sessionID)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -99,7 +105,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
catch: () => new HttpApiError.BadRequest({}),
|
catch: () => new HttpApiError.BadRequest({}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID))
|
yield* requireSession(ctx.params.sessionID)
|
||||||
if (ctx.query.limit === undefined || ctx.query.limit === 0) {
|
if (ctx.query.limit === undefined || ctx.query.limit === 0) {
|
||||||
return yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID }))
|
return yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID }))
|
||||||
}
|
}
|
||||||
@@ -165,7 +171,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
payload: typeof UpdatePayload.Type
|
payload: typeof UpdatePayload.Type
|
||||||
}) {
|
}) {
|
||||||
const current = yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID))
|
const current = yield* requireSession(ctx.params.sessionID)
|
||||||
if (ctx.payload.title !== undefined) {
|
if (ctx.payload.title !== undefined) {
|
||||||
yield* session.setTitle({ sessionID: ctx.params.sessionID, title: ctx.payload.title })
|
yield* session.setTitle({ sessionID: ctx.params.sessionID, title: ctx.payload.title })
|
||||||
}
|
}
|
||||||
@@ -178,7 +184,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
if (ctx.payload.time?.archived !== undefined) {
|
if (ctx.payload.time?.archived !== undefined) {
|
||||||
yield* session.setArchived({ sessionID: ctx.params.sessionID, time: ctx.payload.time.archived })
|
yield* session.setArchived({ sessionID: ctx.params.sessionID, time: ctx.payload.time.archived })
|
||||||
}
|
}
|
||||||
return yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID))
|
return yield* requireSession(ctx.params.sessionID)
|
||||||
})
|
})
|
||||||
|
|
||||||
const fork = Effect.fn("SessionHttpApi.fork")(function* (ctx: {
|
const fork = Effect.fn("SessionHttpApi.fork")(function* (ctx: {
|
||||||
@@ -216,6 +222,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
payload: typeof InitPayload.Type
|
payload: typeof InitPayload.Type
|
||||||
}) {
|
}) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
yield* promptSvc
|
yield* promptSvc
|
||||||
.command({
|
.command({
|
||||||
sessionID: ctx.params.sessionID,
|
sessionID: ctx.params.sessionID,
|
||||||
@@ -234,22 +241,24 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
// ErrorMiddleware → NamedError.Unknown 500) instead of blanket-mapping
|
// ErrorMiddleware → NamedError.Unknown 500) instead of blanket-mapping
|
||||||
// every failure to a 400 BadRequest.
|
// every failure to a 400 BadRequest.
|
||||||
const share = Effect.fn("SessionHttpApi.share")(function* (ctx: { params: { sessionID: SessionID } }) {
|
const share = Effect.fn("SessionHttpApi.share")(function* (ctx: { params: { sessionID: SessionID } }) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
yield* shareSvc.share(ctx.params.sessionID).pipe(Effect.mapError(() => new HttpApiError.InternalServerError({})))
|
yield* shareSvc.share(ctx.params.sessionID).pipe(Effect.mapError(() => new HttpApiError.InternalServerError({})))
|
||||||
return yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID))
|
return yield* requireSession(ctx.params.sessionID)
|
||||||
})
|
})
|
||||||
|
|
||||||
const unshare = Effect.fn("SessionHttpApi.unshare")(function* (ctx: { params: { sessionID: SessionID } }) {
|
const unshare = Effect.fn("SessionHttpApi.unshare")(function* (ctx: { params: { sessionID: SessionID } }) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
yield* shareSvc
|
yield* shareSvc
|
||||||
.unshare(ctx.params.sessionID)
|
.unshare(ctx.params.sessionID)
|
||||||
.pipe(Effect.mapError(() => new HttpApiError.InternalServerError({})))
|
.pipe(Effect.mapError(() => new HttpApiError.InternalServerError({})))
|
||||||
return yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID))
|
return yield* requireSession(ctx.params.sessionID)
|
||||||
})
|
})
|
||||||
|
|
||||||
const summarize = Effect.fn("SessionHttpApi.summarize")(function* (ctx: {
|
const summarize = Effect.fn("SessionHttpApi.summarize")(function* (ctx: {
|
||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
payload: typeof SummarizePayload.Type
|
payload: typeof SummarizePayload.Type
|
||||||
}) {
|
}) {
|
||||||
yield* revertSvc.cleanup(yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID)))
|
yield* revertSvc.cleanup(yield* requireSession(ctx.params.sessionID))
|
||||||
const messages = yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID }))
|
const messages = yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID }))
|
||||||
const defaultAgent = yield* agentSvc.defaultAgent()
|
const defaultAgent = yield* agentSvc.defaultAgent()
|
||||||
const currentAgent = messages.findLast((message) => message.info.role === "user")?.info.agent ?? defaultAgent
|
const currentAgent = messages.findLast((message) => message.info.role === "user")?.info.agent ?? defaultAgent
|
||||||
@@ -271,6 +280,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
payload: typeof PromptPayload.Type
|
payload: typeof PromptPayload.Type
|
||||||
}) {
|
}) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
const message = yield* promptSvc
|
const message = yield* promptSvc
|
||||||
.prompt({
|
.prompt({
|
||||||
...ctx.payload,
|
...ctx.payload,
|
||||||
@@ -286,6 +296,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
payload: typeof PromptPayload.Type
|
payload: typeof PromptPayload.Type
|
||||||
}) {
|
}) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
yield* promptSvc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID }).pipe(
|
yield* promptSvc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID }).pipe(
|
||||||
Effect.catchCause((cause) =>
|
Effect.catchCause((cause) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -307,6 +318,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
payload: typeof CommandPayload.Type
|
payload: typeof CommandPayload.Type
|
||||||
}) {
|
}) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
return yield* promptSvc
|
return yield* promptSvc
|
||||||
.command({ ...ctx.payload, sessionID: ctx.params.sessionID })
|
.command({ ...ctx.payload, sessionID: ctx.params.sessionID })
|
||||||
.pipe(Effect.mapError(() => new HttpApiError.BadRequest({})))
|
.pipe(Effect.mapError(() => new HttpApiError.BadRequest({})))
|
||||||
@@ -316,6 +328,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
payload: typeof ShellPayload.Type
|
payload: typeof ShellPayload.Type
|
||||||
}) {
|
}) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
return yield* promptSvc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID })
|
return yield* promptSvc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID })
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -323,17 +336,20 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
payload: typeof RevertPayload.Type
|
payload: typeof RevertPayload.Type
|
||||||
}) {
|
}) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
return yield* revertSvc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload })
|
return yield* revertSvc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload })
|
||||||
})
|
})
|
||||||
|
|
||||||
const unrevert = Effect.fn("SessionHttpApi.unrevert")(function* (ctx: { params: { sessionID: SessionID } }) {
|
const unrevert = Effect.fn("SessionHttpApi.unrevert")(function* (ctx: { params: { sessionID: SessionID } }) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
return yield* revertSvc.unrevert({ sessionID: ctx.params.sessionID })
|
return yield* revertSvc.unrevert({ sessionID: ctx.params.sessionID })
|
||||||
})
|
})
|
||||||
|
|
||||||
const permissionRespond = Effect.fn("SessionHttpApi.permissionRespond")(function* (ctx: {
|
const permissionRespond = Effect.fn("SessionHttpApi.permissionRespond")(function* (ctx: {
|
||||||
params: { permissionID: PermissionID }
|
params: { sessionID: SessionID; permissionID: PermissionID }
|
||||||
payload: typeof PermissionResponsePayload.Type
|
payload: typeof PermissionResponsePayload.Type
|
||||||
}) {
|
}) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
yield* permissionSvc.reply({ requestID: ctx.params.permissionID, reply: ctx.payload.response })
|
yield* permissionSvc.reply({ requestID: ctx.params.permissionID, reply: ctx.payload.response })
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -341,6 +357,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
const deleteMessage = Effect.fn("SessionHttpApi.deleteMessage")(function* (ctx: {
|
const deleteMessage = Effect.fn("SessionHttpApi.deleteMessage")(function* (ctx: {
|
||||||
params: { sessionID: SessionID; messageID: MessageID }
|
params: { sessionID: SessionID; messageID: MessageID }
|
||||||
}) {
|
}) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
yield* runState.assertNotBusy(ctx.params.sessionID)
|
yield* runState.assertNotBusy(ctx.params.sessionID)
|
||||||
yield* session.removeMessage(ctx.params)
|
yield* session.removeMessage(ctx.params)
|
||||||
return true
|
return true
|
||||||
@@ -349,6 +366,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
const deletePart = Effect.fn("SessionHttpApi.deletePart")(function* (ctx: {
|
const deletePart = Effect.fn("SessionHttpApi.deletePart")(function* (ctx: {
|
||||||
params: { sessionID: SessionID; messageID: MessageID; partID: PartID }
|
params: { sessionID: SessionID; messageID: MessageID; partID: PartID }
|
||||||
}) {
|
}) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
yield* session.removePart(ctx.params)
|
yield* session.removePart(ctx.params)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -357,15 +375,14 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
params: { sessionID: SessionID; messageID: MessageID; partID: PartID }
|
params: { sessionID: SessionID; messageID: MessageID; partID: PartID }
|
||||||
payload: typeof MessageV2.Part.Type
|
payload: typeof MessageV2.Part.Type
|
||||||
}) {
|
}) {
|
||||||
|
yield* requireSession(ctx.params.sessionID)
|
||||||
const payload = ctx.payload as MessageV2.Part
|
const payload = ctx.payload as MessageV2.Part
|
||||||
if (
|
if (
|
||||||
payload.id !== ctx.params.partID ||
|
payload.id !== ctx.params.partID ||
|
||||||
payload.messageID !== ctx.params.messageID ||
|
payload.messageID !== ctx.params.messageID ||
|
||||||
payload.sessionID !== ctx.params.sessionID
|
payload.sessionID !== ctx.params.sessionID
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
return yield* new HttpApiError.BadRequest({})
|
||||||
`Part mismatch: body.id='${payload.id}' vs partID='${ctx.params.partID}', body.messageID='${payload.messageID}' vs messageID='${ctx.params.messageID}', body.sessionID='${payload.sessionID}' vs sessionID='${ctx.params.sessionID}'`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return yield* session.updatePart(payload)
|
return yield* session.updatePart(payload)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -212,6 +212,14 @@ describe("session HttpApi", () => {
|
|||||||
expect(get.status).toBe(404)
|
expect(get.status).toBe(404)
|
||||||
expect(yield* responseJson(get)).toEqual(missingSessionBody)
|
expect(yield* responseJson(get)).toEqual(missingSessionBody)
|
||||||
|
|
||||||
|
const children = yield* request(pathFor(SessionPaths.children, { sessionID: missingSession }), { headers })
|
||||||
|
expect(children.status).toBe(404)
|
||||||
|
expect(yield* responseJson(children)).toEqual(missingSessionBody)
|
||||||
|
|
||||||
|
const todo = yield* request(pathFor(SessionPaths.todo, { sessionID: missingSession }), { headers })
|
||||||
|
expect(todo.status).toBe(404)
|
||||||
|
expect(yield* responseJson(todo)).toEqual(missingSessionBody)
|
||||||
|
|
||||||
const messages = yield* request(pathFor(SessionPaths.messages, { sessionID: missingSession }), { headers })
|
const messages = yield* request(pathFor(SessionPaths.messages, { sessionID: missingSession }), { headers })
|
||||||
expect(messages.status).toBe(404)
|
expect(messages.status).toBe(404)
|
||||||
expect(yield* responseJson(messages)).toEqual(missingSessionBody)
|
expect(yield* responseJson(messages)).toEqual(missingSessionBody)
|
||||||
@@ -223,6 +231,21 @@ describe("session HttpApi", () => {
|
|||||||
expect(remove.status).toBe(404)
|
expect(remove.status).toBe(404)
|
||||||
expect(yield* responseJson(remove)).toEqual(missingSessionBody)
|
expect(yield* responseJson(remove)).toEqual(missingSessionBody)
|
||||||
|
|
||||||
|
const prompt = yield* request(pathFor(SessionPaths.prompt, { sessionID: missingSession }), {
|
||||||
|
headers: { ...headers, "content-type": "application/json" },
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ agent: "build", noReply: true, parts: [{ type: "text", text: "hello" }] }),
|
||||||
|
})
|
||||||
|
expect(prompt.status).toBe(404)
|
||||||
|
expect(yield* responseJson(prompt)).toEqual(missingSessionBody)
|
||||||
|
|
||||||
|
const abort = yield* request(pathFor(SessionPaths.abort, { sessionID: missingSession }), {
|
||||||
|
headers,
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
expect(abort.status).toBe(200)
|
||||||
|
expect(yield* responseJson(abort)).toBe(true)
|
||||||
|
|
||||||
const session = yield* createSession({ title: "missing message" })
|
const session = yield* createSession({ title: "missing message" })
|
||||||
const missingMessage = MessageID.ascending()
|
const missingMessage = MessageID.ascending()
|
||||||
const message = yield* request(
|
const message = yield* request(
|
||||||
@@ -530,6 +553,32 @@ describe("session HttpApi", () => {
|
|||||||
{ git: true, config: { formatter: false, lsp: false } },
|
{ git: true, config: { formatter: false, lsp: false } },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.instance(
|
||||||
|
"rejects part updates whose path and body ids disagree",
|
||||||
|
() =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const test = yield* TestInstance
|
||||||
|
const headers = { "x-opencode-directory": test.directory, "content-type": "application/json" }
|
||||||
|
const session = yield* createSession({ title: "part mismatch" })
|
||||||
|
const message = yield* createTextMessage(session.id, "first")
|
||||||
|
const response = yield* request(
|
||||||
|
pathFor(SessionPaths.updatePart, {
|
||||||
|
sessionID: session.id,
|
||||||
|
messageID: message.info.id,
|
||||||
|
partID: message.part.id,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify({ ...message.part, id: PartID.ascending() }),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toBe(400)
|
||||||
|
}),
|
||||||
|
{ git: true, config: { formatter: false, lsp: false } },
|
||||||
|
)
|
||||||
|
|
||||||
it.instance(
|
it.instance(
|
||||||
"serves remaining non-LLM session mutation routes",
|
"serves remaining non-LLM session mutation routes",
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -5442,7 +5442,7 @@ export type SessionChildrenErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -5476,7 +5476,7 @@ export type SessionTodoErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -5586,7 +5586,7 @@ export type SessionPromptErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -5624,7 +5624,7 @@ export type SessionDeleteMessageErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -5731,10 +5731,6 @@ export type SessionAbortErrors = {
|
|||||||
* Bad request
|
* Bad request
|
||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
|
||||||
* Not found
|
|
||||||
*/
|
|
||||||
404: NotFoundError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SessionAbortError = SessionAbortErrors[keyof SessionAbortErrors]
|
export type SessionAbortError = SessionAbortErrors[keyof SessionAbortErrors]
|
||||||
@@ -5770,7 +5766,7 @@ export type SessionInitErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -5925,7 +5921,7 @@ export type SessionPromptAsyncErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -5974,7 +5970,7 @@ export type SessionCommandErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -6019,7 +6015,7 @@ export type SessionShellErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -6059,7 +6055,7 @@ export type SessionRevertErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -6093,7 +6089,7 @@ export type SessionUnrevertErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -6130,7 +6126,7 @@ export type PermissionRespondErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -6166,7 +6162,7 @@ export type PartDeleteErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
@@ -6202,7 +6198,7 @@ export type PartUpdateErrors = {
|
|||||||
*/
|
*/
|
||||||
400: BadRequestError
|
400: BadRequestError
|
||||||
/**
|
/**
|
||||||
* Not found
|
* NotFoundError
|
||||||
*/
|
*/
|
||||||
404: NotFoundError
|
404: NotFoundError
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user