chore: merge origin/dev into v2
Brings the latest dev (45 commits) into v2. Conflict resolutions: - core/src/session.ts: keep v2's Service (BusyError active-checks in revert.stage/clear/commit, rename method + Renamed event) and add dev's history method. v2's Renamed event flows through the merged SessionEvent.Definitions into the OpenCodeEvent union, so it encodes correctly on the SSE stream. - server/src/handlers/event.ts: take dev's OpenCodeEvent encoder, which supersedes v2's PublicEventManifest handler wiring. This also brings dev's live event stream (allBounded + heartbeat + keepalive headers) onto v2. - Removed the now-orphaned public-event-manifest.ts (only self-referenced after taking dev's handler). - Generated client + sdk types regenerated via codegen (not hand-merged); they pick up dev's effect SSE OpenAPI-naming patch (V2EventStream).
This commit is contained in:
commit
727f9f46a0
207 changed files with 9806 additions and 6648 deletions
|
|
@ -3,7 +3,7 @@ import { EventManifest } from "@opencode-ai/schema/event-manifest"
|
|||
import { Location } from "@opencode-ai/schema/location"
|
||||
import type { Definition } from "@opencode-ai/schema/event"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||
|
||||
const fields = {
|
||||
id: Event.ID,
|
||||
|
|
@ -12,15 +12,9 @@ const fields = {
|
|||
location: Schema.optional(Location.Ref),
|
||||
}
|
||||
|
||||
const schema = (definitions: ReadonlyArray<Definition>) =>
|
||||
const schema = <const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) =>
|
||||
Schema.Union([
|
||||
...definitions.map((definition) =>
|
||||
Schema.Struct({
|
||||
...fields,
|
||||
type: Schema.Literal(definition.type),
|
||||
data: definition.data,
|
||||
}).annotate({ identifier: `V2Event.${definition.type}` }),
|
||||
),
|
||||
...definitions,
|
||||
...(definitions.some((definition) => definition.type === "server.connected")
|
||||
? []
|
||||
: [
|
||||
|
|
@ -32,14 +26,14 @@ const schema = (definitions: ReadonlyArray<Definition>) =>
|
|||
]),
|
||||
]).annotate({ identifier: "V2Event" })
|
||||
|
||||
const make = (definitions: ReadonlyArray<Definition>) => {
|
||||
const make = <const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) => {
|
||||
const EventSchema = schema(definitions)
|
||||
return {
|
||||
schema: EventSchema,
|
||||
group: HttpApiGroup.make("server.event")
|
||||
.add(
|
||||
HttpApiEndpoint.get("event.subscribe", "/api/event", {
|
||||
success: EventSchema,
|
||||
success: HttpApiSchema.StreamSse({ data: EventSchema }),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.event.subscribe",
|
||||
|
|
@ -52,8 +46,11 @@ const make = (definitions: ReadonlyArray<Definition>) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const makeEventGroup = (definitions: ReadonlyArray<Definition>) => make(definitions).group
|
||||
export const makeEventGroup = <const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) =>
|
||||
make(definitions).group
|
||||
|
||||
const event = make(EventManifest.ServerDefinitions)
|
||||
export const EventGroup = event.group
|
||||
export type Event = typeof event.schema.Type
|
||||
export const OpenCodeEvent = event.schema
|
||||
export type OpenCodeEvent = typeof OpenCodeEvent.Type
|
||||
export type OpenCodeEventEncoded = typeof OpenCodeEvent.Encoded
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Session } from "@opencode-ai/schema/session"
|
|||
import { Project } from "@opencode-ai/schema/project"
|
||||
import { AbsolutePath, NonNegativeInt, PositiveInt, RelativePath, statics } from "@opencode-ai/schema/schema"
|
||||
import { Workspace } from "@opencode-ai/schema/workspace"
|
||||
import { Context, Encoding, Result, Schema, Struct } from "effect"
|
||||
import { Context, Effect, Encoding, Result, Schema, Struct } from "effect"
|
||||
import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||
import {
|
||||
ConflictError,
|
||||
|
|
@ -61,6 +61,7 @@ const SessionsCursorInput = Schema.Union([
|
|||
const SessionsCursorJson = Schema.fromJsonString(SessionsCursorInput)
|
||||
const encodeSessionsCursor = Schema.encodeSync(SessionsCursorJson)
|
||||
const decodeSessionsCursor = Schema.decodeUnknownEffect(SessionsCursorJson)
|
||||
const invalidCursor = "Invalid cursor" as const
|
||||
|
||||
export const SessionsCursor = Schema.String.pipe(
|
||||
Schema.brand("SessionsCursor"),
|
||||
|
|
@ -68,7 +69,13 @@ export const SessionsCursor = Schema.String.pipe(
|
|||
const make = schema.make.bind(schema)
|
||||
return {
|
||||
make: (input: typeof SessionsCursorInput.Type) => make(Encoding.encodeBase64Url(encodeSessionsCursor(input))),
|
||||
parse: (input: string) => decodeSessionsCursor(Result.getOrThrow(Encoding.decodeBase64UrlString(input))),
|
||||
parse: (input: string) =>
|
||||
Effect.suspend(() => {
|
||||
const result = Encoding.decodeBase64UrlString(input)
|
||||
return Result.isFailure(result)
|
||||
? Effect.fail(invalidCursor)
|
||||
: decodeSessionsCursor(result.success).pipe(Effect.mapError(() => invalidCursor))
|
||||
}),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
|
@ -78,6 +85,13 @@ const SessionActive = Schema.Struct({
|
|||
type: Schema.Literal("running"),
|
||||
}).annotate({ identifier: "SessionActive" })
|
||||
|
||||
const SessionHistoryLimit = PositiveInt.check(Schema.isLessThanOrEqualTo(100))
|
||||
|
||||
export const SessionHistoryQuery = Schema.Struct({
|
||||
limit: Schema.NumberFromString.pipe(Schema.decodeTo(SessionHistoryLimit), Schema.optional),
|
||||
after: Schema.NumberFromString.pipe(Schema.decodeTo(NonNegativeInt), Schema.optional),
|
||||
})
|
||||
|
||||
const SessionsQueryCursor = SessionsCursor.annotate({
|
||||
description: "Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response.",
|
||||
})
|
||||
|
|
@ -306,6 +320,26 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
|||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("session.history", "/api/session/:sessionID/history", {
|
||||
params: { sessionID: Session.ID },
|
||||
query: SessionHistoryQuery,
|
||||
success: Schema.Struct({
|
||||
data: Schema.Array(SessionEvent.Durable),
|
||||
hasMore: Schema.Boolean,
|
||||
}).annotate({ identifier: "SessionHistory" }),
|
||||
error: SessionNotFoundError,
|
||||
})
|
||||
.middleware(sessionLocationMiddleware)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.session.history",
|
||||
summary: "Get session history",
|
||||
description:
|
||||
"Read one finite page of public durable Session events after an exclusive aggregate sequence. Newly committed events may appear on later pages.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("session.events", "/api/session/:sessionID/event", {
|
||||
params: { sessionID: Session.ID },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { SessionsCursor } from "../src/groups/session"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { SessionHistoryQuery, SessionsCursor } from "../src/groups/session"
|
||||
import { Session } from "@opencode-ai/schema/session"
|
||||
|
||||
describe("SessionsCursor", () => {
|
||||
|
|
@ -16,3 +16,11 @@ describe("SessionsCursor", () => {
|
|||
expect(await Effect.runPromise(SessionsCursor.parse(cursor))).toEqual(input)
|
||||
})
|
||||
})
|
||||
|
||||
describe("SessionHistoryQuery", () => {
|
||||
test("decodes numeric paging inputs", async () => {
|
||||
const query = await Effect.runPromise(Schema.decodeUnknownEffect(SessionHistoryQuery)({ after: "3", limit: "10" }))
|
||||
|
||||
expect(query).toEqual({ after: 3, limit: 10 })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue