fix(httpapi): expose v2 session not found (#28511)
This commit is contained in:
@@ -2,7 +2,7 @@ import { SessionID } from "@/session/schema"
|
|||||||
import { SessionMessage } from "@opencode-ai/core/session-message"
|
import { SessionMessage } from "@opencode-ai/core/session-message"
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||||
import { InvalidCursorError } from "../../errors"
|
import { InvalidCursorError, SessionNotFoundError } from "../../errors"
|
||||||
import { V2Authorization } from "../../middleware/authorization"
|
import { V2Authorization } from "../../middleware/authorization"
|
||||||
import { WorkspaceRoutingQueryFields } from "../../middleware/workspace-routing"
|
import { WorkspaceRoutingQueryFields } from "../../middleware/workspace-routing"
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ export const MessageGroup = HttpApiGroup.make("v2.message")
|
|||||||
next: Schema.String.pipe(Schema.optional),
|
next: Schema.String.pipe(Schema.optional),
|
||||||
}),
|
}),
|
||||||
}).annotate({ identifier: "V2SessionMessagesResponse" }),
|
}).annotate({ identifier: "V2SessionMessagesResponse" }),
|
||||||
error: InvalidCursorError,
|
error: [InvalidCursorError, SessionNotFoundError],
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.session.messages",
|
identifier: "v2.session.messages",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Prompt } from "@opencode-ai/core/session-prompt"
|
|||||||
import { SessionV2 } from "@/v2/session"
|
import { SessionV2 } from "@/v2/session"
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||||
import { InvalidCursorError, InvalidRequestError } from "../../errors"
|
import { InvalidCursorError, InvalidRequestError, SessionNotFoundError } from "../../errors"
|
||||||
import { V2Authorization } from "../../middleware/authorization"
|
import { V2Authorization } from "../../middleware/authorization"
|
||||||
import { WorkspaceRoutingQuery, WorkspaceRoutingQueryFields } from "../../middleware/workspace-routing"
|
import { WorkspaceRoutingQuery, WorkspaceRoutingQueryFields } from "../../middleware/workspace-routing"
|
||||||
import { QueryBoolean } from "../query"
|
import { QueryBoolean } from "../query"
|
||||||
@@ -61,6 +61,7 @@ export const SessionGroup = HttpApiGroup.make("v2.session")
|
|||||||
delivery: SessionV2.Delivery.pipe(Schema.optional),
|
delivery: SessionV2.Delivery.pipe(Schema.optional),
|
||||||
}),
|
}),
|
||||||
success: SessionMessage.Message,
|
success: SessionMessage.Message,
|
||||||
|
error: SessionNotFoundError,
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.session.prompt",
|
identifier: "v2.session.prompt",
|
||||||
@@ -74,6 +75,7 @@ export const SessionGroup = HttpApiGroup.make("v2.session")
|
|||||||
params: { sessionID: SessionID },
|
params: { sessionID: SessionID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: HttpApiSchema.NoContent,
|
success: HttpApiSchema.NoContent,
|
||||||
|
error: SessionNotFoundError,
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.session.compact",
|
identifier: "v2.session.compact",
|
||||||
@@ -87,6 +89,7 @@ export const SessionGroup = HttpApiGroup.make("v2.session")
|
|||||||
params: { sessionID: SessionID },
|
params: { sessionID: SessionID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: HttpApiSchema.NoContent,
|
success: HttpApiSchema.NoContent,
|
||||||
|
error: SessionNotFoundError,
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.session.wait",
|
identifier: "v2.session.wait",
|
||||||
@@ -100,6 +103,7 @@ export const SessionGroup = HttpApiGroup.make("v2.session")
|
|||||||
params: { sessionID: SessionID },
|
params: { sessionID: SessionID },
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: Schema.Array(SessionMessage.Message),
|
success: Schema.Array(SessionMessage.Message),
|
||||||
|
error: SessionNotFoundError,
|
||||||
}).annotateMerge(
|
}).annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.session.context",
|
identifier: "v2.session.context",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Effect, Schema } from "effect"
|
|||||||
import * as DateTime from "effect/DateTime"
|
import * as DateTime from "effect/DateTime"
|
||||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||||
import { InstanceHttpApi } from "../../api"
|
import { InstanceHttpApi } from "../../api"
|
||||||
import { InvalidCursorError } from "../../errors"
|
import { InvalidCursorError, SessionNotFoundError } from "../../errors"
|
||||||
|
|
||||||
const DefaultMessagesLimit = 50
|
const DefaultMessagesLimit = 50
|
||||||
|
|
||||||
@@ -47,7 +47,16 @@ export const messageHandlers = HttpApiBuilder.group(InstanceHttpApi, "v2.message
|
|||||||
limit: ctx.query.limit ?? DefaultMessagesLimit,
|
limit: ctx.query.limit ?? DefaultMessagesLimit,
|
||||||
order,
|
order,
|
||||||
cursor: decoded ? { id: decoded.id, time: decoded.time, direction: decoded.direction } : undefined,
|
cursor: decoded ? { id: decoded.id, time: decoded.time, direction: decoded.direction } : undefined,
|
||||||
})
|
}).pipe(
|
||||||
|
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||||
|
Effect.fail(
|
||||||
|
new SessionNotFoundError({
|
||||||
|
sessionID: error.sessionID,
|
||||||
|
message: `Session not found: ${error.sessionID}`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
const first = messages[0]
|
const first = messages[0]
|
||||||
const last = messages.at(-1)
|
const last = messages.at(-1)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { SessionV2 } from "@/v2/session"
|
|||||||
import { DateTime, Effect, Option, Schema } from "effect"
|
import { DateTime, Effect, Option, Schema } from "effect"
|
||||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||||
import { InstanceHttpApi } from "../../api"
|
import { InstanceHttpApi } from "../../api"
|
||||||
import { InvalidCursorError, InvalidRequestError } from "../../errors"
|
import { InvalidCursorError, InvalidRequestError, SessionNotFoundError } from "../../errors"
|
||||||
|
|
||||||
const DefaultSessionsLimit = 50
|
const DefaultSessionsLimit = 50
|
||||||
|
|
||||||
@@ -137,27 +137,69 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "v2.session
|
|||||||
sessionID: ctx.params.sessionID,
|
sessionID: ctx.params.sessionID,
|
||||||
prompt: ctx.payload.prompt,
|
prompt: ctx.payload.prompt,
|
||||||
delivery: ctx.payload.delivery ?? SessionV2.DefaultDelivery,
|
delivery: ctx.payload.delivery ?? SessionV2.DefaultDelivery,
|
||||||
})
|
}).pipe(
|
||||||
|
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||||
|
Effect.fail(
|
||||||
|
new SessionNotFoundError({
|
||||||
|
sessionID: error.sessionID,
|
||||||
|
message: `Session not found: ${error.sessionID}`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.handle(
|
.handle(
|
||||||
"compact",
|
"compact",
|
||||||
Effect.fn(function* (ctx) {
|
Effect.fn(function* (ctx) {
|
||||||
yield* session.compact(ctx.params.sessionID)
|
yield* session
|
||||||
|
.compact(ctx.params.sessionID)
|
||||||
|
.pipe(
|
||||||
|
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||||
|
Effect.fail(
|
||||||
|
new SessionNotFoundError({
|
||||||
|
sessionID: error.sessionID,
|
||||||
|
message: `Session not found: ${error.sessionID}`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
return HttpApiSchema.NoContent.make()
|
return HttpApiSchema.NoContent.make()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.handle(
|
.handle(
|
||||||
"wait",
|
"wait",
|
||||||
Effect.fn(function* (ctx) {
|
Effect.fn(function* (ctx) {
|
||||||
yield* session.wait(ctx.params.sessionID)
|
yield* session
|
||||||
|
.wait(ctx.params.sessionID)
|
||||||
|
.pipe(
|
||||||
|
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||||
|
Effect.fail(
|
||||||
|
new SessionNotFoundError({
|
||||||
|
sessionID: error.sessionID,
|
||||||
|
message: `Session not found: ${error.sessionID}`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
return HttpApiSchema.NoContent.make()
|
return HttpApiSchema.NoContent.make()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.handle(
|
.handle(
|
||||||
"context",
|
"context",
|
||||||
Effect.fn(function* (ctx) {
|
Effect.fn(function* (ctx) {
|
||||||
return yield* session.context(ctx.params.sessionID)
|
return yield* session
|
||||||
|
.context(ctx.params.sessionID)
|
||||||
|
.pipe(
|
||||||
|
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||||
|
Effect.fail(
|
||||||
|
new SessionNotFoundError({
|
||||||
|
sessionID: error.sessionID,
|
||||||
|
message: `Session not found: ${error.sessionID}`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -97,14 +97,14 @@ export interface Interface {
|
|||||||
time: number
|
time: number
|
||||||
direction: "previous" | "next"
|
direction: "previous" | "next"
|
||||||
}
|
}
|
||||||
}) => Effect.Effect<SessionMessage.Message[], never>
|
}) => Effect.Effect<SessionMessage.Message[], NotFoundError>
|
||||||
readonly context: (sessionID: SessionID) => Effect.Effect<SessionMessage.Message[], never>
|
readonly context: (sessionID: SessionID) => Effect.Effect<SessionMessage.Message[], NotFoundError>
|
||||||
readonly prompt: (input: {
|
readonly prompt: (input: {
|
||||||
id?: EventV2.ID
|
id?: EventV2.ID
|
||||||
sessionID: SessionID
|
sessionID: SessionID
|
||||||
prompt: Prompt
|
prompt: Prompt
|
||||||
delivery?: Delivery
|
delivery?: Delivery
|
||||||
}) => Effect.Effect<SessionMessage.User, never>
|
}) => Effect.Effect<SessionMessage.User, NotFoundError>
|
||||||
readonly shell: (input: { id?: EventV2.ID; sessionID: SessionID; command: string }) => Effect.Effect<void, never>
|
readonly shell: (input: { id?: EventV2.ID; sessionID: SessionID; command: string }) => Effect.Effect<void, never>
|
||||||
readonly skill: (input: { id?: EventV2.ID; sessionID: SessionID; skill: string }) => Effect.Effect<void, never>
|
readonly skill: (input: { id?: EventV2.ID; sessionID: SessionID; skill: string }) => Effect.Effect<void, never>
|
||||||
readonly subagent: (input: {
|
readonly subagent: (input: {
|
||||||
@@ -116,8 +116,8 @@ export interface Interface {
|
|||||||
}) => Effect.Effect<void, NotFoundError>
|
}) => Effect.Effect<void, NotFoundError>
|
||||||
readonly switchAgent: (input: { sessionID: SessionID; agent: string }) => Effect.Effect<void, never>
|
readonly switchAgent: (input: { sessionID: SessionID; agent: string }) => Effect.Effect<void, never>
|
||||||
readonly switchModel: (input: { sessionID: SessionID; model: ModelV2.Ref }) => Effect.Effect<void, never>
|
readonly switchModel: (input: { sessionID: SessionID; model: ModelV2.Ref }) => Effect.Effect<void, never>
|
||||||
readonly compact: (sessionID: SessionID) => Effect.Effect<void, never>
|
readonly compact: (sessionID: SessionID) => Effect.Effect<void, NotFoundError>
|
||||||
readonly wait: (sessionID: SessionID) => Effect.Effect<void, never>
|
readonly wait: (sessionID: SessionID) => Effect.Effect<void, NotFoundError>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Session") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Session") {}
|
||||||
@@ -216,6 +216,7 @@ export const layer = Layer.effect(
|
|||||||
return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row))
|
return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row))
|
||||||
}),
|
}),
|
||||||
messages: Effect.fn("V2Session.messages")(function* (input) {
|
messages: Effect.fn("V2Session.messages")(function* (input) {
|
||||||
|
yield* result.get(input.sessionID)
|
||||||
const direction = input.cursor?.direction ?? "next"
|
const direction = input.cursor?.direction ?? "next"
|
||||||
let order = input.order ?? "desc"
|
let order = input.order ?? "desc"
|
||||||
// Query the adjacent rows in reverse, then flip them back into the requested order below.
|
// Query the adjacent rows in reverse, then flip them back into the requested order below.
|
||||||
@@ -257,6 +258,7 @@ export const layer = Layer.effect(
|
|||||||
return rows.map((row) => decode(row))
|
return rows.map((row) => decode(row))
|
||||||
}),
|
}),
|
||||||
context: Effect.fn("V2Session.context")(function* (sessionID) {
|
context: Effect.fn("V2Session.context")(function* (sessionID) {
|
||||||
|
yield* result.get(sessionID)
|
||||||
const rows = Database.use((db) => {
|
const rows = Database.use((db) => {
|
||||||
const compaction = db
|
const compaction = db
|
||||||
.select()
|
.select()
|
||||||
@@ -288,7 +290,8 @@ export const layer = Layer.effect(
|
|||||||
})
|
})
|
||||||
return rows.map((row) => decode(row))
|
return rows.map((row) => decode(row))
|
||||||
}),
|
}),
|
||||||
prompt: Effect.fn("V2Session.prompt")(function* (_input) {
|
prompt: Effect.fn("V2Session.prompt")(function* (input) {
|
||||||
|
yield* result.get(input.sessionID)
|
||||||
return {} as any
|
return {} as any
|
||||||
}),
|
}),
|
||||||
shell: Effect.fn("V2Session.shell")(function* (_input) {}),
|
shell: Effect.fn("V2Session.shell")(function* (_input) {}),
|
||||||
@@ -328,8 +331,12 @@ export const layer = Layer.effect(
|
|||||||
if (!text) return
|
if (!text) return
|
||||||
}).pipe(Effect.forkChild())
|
}).pipe(Effect.forkChild())
|
||||||
}),
|
}),
|
||||||
compact: Effect.fn("V2Session.compact")(function* (_sessionID) {}),
|
compact: Effect.fn("V2Session.compact")(function* (sessionID) {
|
||||||
wait: Effect.fn("V2Session.wait")(function* (_sessionID) {}),
|
yield* result.get(sessionID)
|
||||||
|
}),
|
||||||
|
wait: Effect.fn("V2Session.wait")(function* (sessionID) {
|
||||||
|
yield* result.get(sessionID)
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -675,22 +675,14 @@ const scenarios: Scenario[] = [
|
|||||||
path: route("/api/session/{sessionID}/context", { sessionID: "ses_httpapi_missing" }),
|
path: route("/api/session/{sessionID}/context", { sessionID: "ses_httpapi_missing" }),
|
||||||
headers: ctx.headers(),
|
headers: ctx.headers(),
|
||||||
}))
|
}))
|
||||||
.json(200, array, "none"),
|
.json(404, object, "status"),
|
||||||
http.protected
|
http.protected
|
||||||
.get("/api/session/{sessionID}/message", "v2.session.messages")
|
.get("/api/session/{sessionID}/message", "v2.session.messages")
|
||||||
.at((ctx) => ({
|
.at((ctx) => ({
|
||||||
path: route("/api/session/{sessionID}/message", { sessionID: "ses_httpapi_missing" }),
|
path: route("/api/session/{sessionID}/message", { sessionID: "ses_httpapi_missing" }),
|
||||||
headers: ctx.headers(),
|
headers: ctx.headers(),
|
||||||
}))
|
}))
|
||||||
.json(
|
.json(404, object, "status"),
|
||||||
200,
|
|
||||||
(body) => {
|
|
||||||
object(body)
|
|
||||||
array(body.items)
|
|
||||||
object(body.cursor)
|
|
||||||
},
|
|
||||||
"none",
|
|
||||||
),
|
|
||||||
http.protected
|
http.protected
|
||||||
.get("/api/session/{sessionID}/message", "v2.session.messages.params")
|
.get("/api/session/{sessionID}/message", "v2.session.messages.params")
|
||||||
.at((ctx) => ({
|
.at((ctx) => ({
|
||||||
@@ -700,15 +692,7 @@ const scenarios: Scenario[] = [
|
|||||||
})}`,
|
})}`,
|
||||||
headers: ctx.headers(),
|
headers: ctx.headers(),
|
||||||
}))
|
}))
|
||||||
.json(
|
.json(404, object, "status"),
|
||||||
200,
|
|
||||||
(body) => {
|
|
||||||
object(body)
|
|
||||||
array(body.items)
|
|
||||||
object(body.cursor)
|
|
||||||
},
|
|
||||||
"none",
|
|
||||||
),
|
|
||||||
http.protected
|
http.protected
|
||||||
.get("/api/session/{sessionID}/message", "v2.session.messages.cursor")
|
.get("/api/session/{sessionID}/message", "v2.session.messages.cursor")
|
||||||
.at((ctx) => ({
|
.at((ctx) => ({
|
||||||
@@ -719,15 +703,7 @@ const scenarios: Scenario[] = [
|
|||||||
})}`,
|
})}`,
|
||||||
headers: ctx.headers(),
|
headers: ctx.headers(),
|
||||||
}))
|
}))
|
||||||
.json(
|
.json(404, object, "status"),
|
||||||
200,
|
|
||||||
(body) => {
|
|
||||||
object(body)
|
|
||||||
array(body.items)
|
|
||||||
object(body.cursor)
|
|
||||||
},
|
|
||||||
"none",
|
|
||||||
),
|
|
||||||
http.protected
|
http.protected
|
||||||
.get("/api/session/{sessionID}/message", "v2.session.messages.cursor.invalid")
|
.get("/api/session/{sessionID}/message", "v2.session.messages.cursor.invalid")
|
||||||
.at((ctx) => ({
|
.at((ctx) => ({
|
||||||
@@ -752,14 +728,14 @@ const scenarios: Scenario[] = [
|
|||||||
path: route("/api/session/{sessionID}/compact", { sessionID: "ses_httpapi_missing" }),
|
path: route("/api/session/{sessionID}/compact", { sessionID: "ses_httpapi_missing" }),
|
||||||
headers: ctx.headers(),
|
headers: ctx.headers(),
|
||||||
}))
|
}))
|
||||||
.status(204, undefined, "none"),
|
.status(404, undefined, "status"),
|
||||||
http.protected
|
http.protected
|
||||||
.post("/api/session/{sessionID}/wait", "v2.session.wait")
|
.post("/api/session/{sessionID}/wait", "v2.session.wait")
|
||||||
.at((ctx) => ({
|
.at((ctx) => ({
|
||||||
path: route("/api/session/{sessionID}/wait", { sessionID: "ses_httpapi_missing" }),
|
path: route("/api/session/{sessionID}/wait", { sessionID: "ses_httpapi_missing" }),
|
||||||
headers: ctx.headers(),
|
headers: ctx.headers(),
|
||||||
}))
|
}))
|
||||||
.status(204, undefined, "none"),
|
.status(404, undefined, "status"),
|
||||||
http.protected
|
http.protected
|
||||||
.get("/session", "session.list")
|
.get("/session", "session.list")
|
||||||
.seeded((ctx) => ctx.session({ title: "List me" }))
|
.seeded((ctx) => ctx.session({ title: "List me" }))
|
||||||
|
|||||||
@@ -99,4 +99,20 @@ describe("PublicApi OpenAPI v2 errors", () => {
|
|||||||
"ServiceUnavailableError",
|
"ServiceUnavailableError",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("documents v2 session not-found errors", () => {
|
||||||
|
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
|
||||||
|
|
||||||
|
for (const route of [
|
||||||
|
["post", "/api/session/{sessionID}/prompt"],
|
||||||
|
["post", "/api/session/{sessionID}/compact"],
|
||||||
|
["post", "/api/session/{sessionID}/wait"],
|
||||||
|
["get", "/api/session/{sessionID}/context"],
|
||||||
|
["get", "/api/session/{sessionID}/message"],
|
||||||
|
] as const) {
|
||||||
|
expect(componentName(responseRef(spec.paths[route[1]]?.[route[0]]?.responses?.["404"]) ?? "")).toBe(
|
||||||
|
"SessionNotFoundError",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -402,6 +402,46 @@ describe("session HttpApi", () => {
|
|||||||
{ git: true, config: { formatter: false, lsp: false } },
|
{ git: true, config: { formatter: false, lsp: false } },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.instance(
|
||||||
|
"returns v2 public not found errors for missing sessions",
|
||||||
|
() =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const test = yield* TestInstance
|
||||||
|
const headers = { "x-opencode-directory": test.directory }
|
||||||
|
const missing = SessionID.descending()
|
||||||
|
const expected = {
|
||||||
|
_tag: "SessionNotFoundError",
|
||||||
|
sessionID: missing,
|
||||||
|
message: `Session not found: ${missing}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = yield* request(`/api/session/${missing}/message`, { headers })
|
||||||
|
expect(messages.status).toBe(404)
|
||||||
|
expect(yield* responseJson(messages)).toEqual(expected)
|
||||||
|
|
||||||
|
const context = yield* request(`/api/session/${missing}/context`, { headers })
|
||||||
|
expect(context.status).toBe(404)
|
||||||
|
expect(yield* responseJson(context)).toEqual(expected)
|
||||||
|
|
||||||
|
const compact = yield* request(`/api/session/${missing}/compact`, { method: "POST", headers })
|
||||||
|
expect(compact.status).toBe(404)
|
||||||
|
expect(yield* responseJson(compact)).toEqual(expected)
|
||||||
|
|
||||||
|
const wait = yield* request(`/api/session/${missing}/wait`, { method: "POST", headers })
|
||||||
|
expect(wait.status).toBe(404)
|
||||||
|
expect(yield* responseJson(wait)).toEqual(expected)
|
||||||
|
|
||||||
|
const prompt = yield* request(`/api/session/${missing}/prompt`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { ...headers, "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ prompt: { text: "hello" } }),
|
||||||
|
})
|
||||||
|
expect(prompt.status).toBe(404)
|
||||||
|
expect(yield* responseJson(prompt)).toEqual(expected)
|
||||||
|
}),
|
||||||
|
{ git: true, config: { formatter: false, lsp: false } },
|
||||||
|
)
|
||||||
|
|
||||||
it.instance(
|
it.instance(
|
||||||
"serves sessions with migrated summary diffs missing file details",
|
"serves sessions with migrated summary diffs missing file details",
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user