diff --git a/packages/opencode/test/server/session-messages.test.ts b/packages/opencode/test/server/session-messages.test.ts index c47913bde5ab..1141aadf3fc3 100644 --- a/packages/opencode/test/server/session-messages.test.ts +++ b/packages/opencode/test/server/session-messages.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect } from "bun:test" import { SessionV1 } from "@opencode-ai/core/v1/session" import { LayerNode } from "@opencode-ai/core/effect/layer-node" -import { Effect, Layer } from "effect" +import { Effect, Layer, Schema } from "effect" import { HttpClientResponse } from "effect/unstable/http" import { Session as SessionNs } from "@/session/session" import { MessageV2 } from "../../src/session/message-v2" @@ -126,6 +126,53 @@ describe("session messages endpoint", () => { { git: true }, ) + it.instance( + "returns persisted output formats", + withoutWatcher( + Effect.gen(function* () { + const session = yield* sessionScoped + const service = yield* SessionNs.Service + const formats = [ + { type: "text" as const }, + { + type: "json_schema" as const, + schema: { type: "object", properties: { answer: { type: "string" } } }, + retryCount: 2, + }, + ] + + yield* Effect.forEach(formats, (format, index) => + Effect.gen(function* () { + const id = MessageID.ascending() + yield* service.updateMessage({ + id, + sessionID: session.id, + role: "user", + time: { created: Date.now() + index }, + agent: "test", + model, + format: Schema.decodeUnknownSync(SessionV1.Format)(format), + }) + yield* service.updatePart({ + id: PartID.ascending(), + sessionID: session.id, + messageID: id, + type: "text", + text: `m${index}`, + }) + }), + ) + + const res = yield* request(`/session/${session.id}/message`) + expect(res.status).toBe(200) + const body = yield* json(res) + expect(body.map((item) => item.info.role === "user" && item.info.format)).toEqual(formats) + }), + ), + { git: true }, + 60_000, + ) + it.instance( "rejects invalid cursors and missing sessions", withoutWatcher( diff --git a/packages/schema/src/v1/session.ts b/packages/schema/src/v1/session.ts index 75e9282f117c..bf645c4fe7c2 100644 --- a/packages/schema/src/v1/session.ts +++ b/packages/schema/src/v1/session.ts @@ -62,15 +62,17 @@ export const ContentFilterError = namedError("ContentFilterError", { message: Schema.String, }) -export class OutputFormatText extends Schema.Class("OutputFormatText")({ +export const OutputFormatText = Schema.Struct({ type: Schema.Literal("text"), -}) {} +}).annotate({ identifier: "OutputFormatText" }) +export type OutputFormatText = Schema.Schema.Type -export class OutputFormatJsonSchema extends Schema.Class("OutputFormatJsonSchema")({ +export const OutputFormatJsonSchema = Schema.Struct({ type: Schema.Literal("json_schema"), schema: Schema.Record(Schema.String, Schema.Any).annotate({ identifier: "JSONSchema" }), retryCount: NonNegativeInt.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))), -}) {} +}).annotate({ identifier: "OutputFormatJsonSchema" }) +export type OutputFormatJsonSchema = Schema.Schema.Type export const Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotate({ discriminator: "type",