chore: merge dev into v2 (#34317)

Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Affan Ali <93028901+affanali2k3@users.noreply.github.com>
Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com>
Co-authored-by: Frank <frank@anoma.ly>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: James Long <longster@gmail.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Jay V <air@live.ca>
Co-authored-by: Dax Raad <d@ironbay.co>
Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com>
Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: Ben Guthrie <benjee.012@gmail.com>
Co-authored-by: Dax <mail@thdxr.com>
Co-authored-by: Filip <34747899+neriousy@users.noreply.github.com>
This commit is contained in:
Kit Langton 2026-06-28 11:30:38 -04:00 committed by GitHub
commit 41283933ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
208 changed files with 9815 additions and 6651 deletions

View file

@ -1,10 +1,4 @@
import { SessionV2 } from "@opencode-ai/core/session"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
import { Layer } from "effect"
import { layer as locationLayer } from "./location"
import { sessionLocationLayer } from "./middleware/session-location"
import { MessageHandler } from "./handlers/message"
import { ModelHandler } from "./handlers/model"
import { ProviderHandler } from "./handlers/provider"
@ -19,18 +13,11 @@ import { HealthHandler } from "./handlers/health"
import { PtyHandler } from "./handlers/pty"
import { QuestionHandler } from "./handlers/question"
import { ReferenceHandler } from "./handlers/reference"
import * as SessionExecutionLocal from "@opencode-ai/core/session/execution/local"
import { LocationHandler } from "./handlers/location"
import { IntegrationHandler } from "./handlers/integration"
import { CredentialHandler } from "./handlers/credential"
import { Credential } from "@opencode-ai/core/credential"
import { ProjectCopyHandler } from "./handlers/project-copy"
const sessionLayer = SessionV2.defaultLayer.pipe(
Layer.provide(SessionExecutionLocal.defaultLayer),
Layer.provide(LocationServiceMap.layer),
)
export const handlers = Layer.mergeAll(
HealthHandler,
LocationHandler,
@ -50,12 +37,4 @@ export const handlers = Layer.mergeAll(
QuestionHandler,
ReferenceHandler,
ProjectCopyHandler,
).pipe(
Layer.provide(sessionLocationLayer),
Layer.provide(locationLayer),
Layer.provide(sessionLayer),
Layer.provide(PermissionSaved.defaultLayer),
Layer.provide(PtyTicket.defaultLayer),
Layer.provide(LocationServiceMap.layer),
Layer.provide(Credential.defaultLayer),
)

View file

@ -1,25 +1,19 @@
import { EventV2 } from "@opencode-ai/core/event"
import { PublicEventManifest } from "@opencode-ai/core/public-event-manifest"
import { OpenCodeEvent } from "@opencode-ai/protocol/groups/event"
import { Effect, Schema, Stream } from "effect"
import { HttpServerResponse } from "effect/unstable/http"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import * as Sse from "effect/unstable/encoding/Sse"
import { Api } from "../api"
const subscriberCapacity = 256
function eventData(data: unknown): Sse.Event {
const event = data as EventV2.Payload
const definition = PublicEventManifest.Latest.get(event.type)
const encoded = definition
? {
...event,
data: Schema.encodeUnknownSync(definition.data as Schema.Codec<unknown, unknown, never, never>)(event.data),
}
: event
return {
_tag: "Event",
event: "message",
id: undefined,
data: JSON.stringify(encoded),
data: JSON.stringify(Schema.encodeUnknownSync(OpenCodeEvent)(data)),
}
}
@ -33,13 +27,16 @@ export const EventHandler = HttpApiBuilder.group(Api, "server.event", (handlers)
type: "server.connected",
data: {},
}
const output = Stream.unwrap(
Effect.gen(function* () {
// Acquiring the bounded stream installs its listener before readiness is observable.
const live = yield* EventV2.allBounded(events, subscriberCapacity)
return Stream.make(connected).pipe(Stream.concat(live))
}),
).pipe(Stream.map(eventData), Stream.pipeThroughChannel(Sse.encode()))
const heartbeat = Stream.tick("15 seconds").pipe(Stream.map(() => ": heartbeat\n\n"))
return HttpServerResponse.stream(
Stream.make(connected).pipe(
Stream.concat(events.all()),
Stream.map(eventData),
Stream.pipeThroughChannel(Sse.encode()),
Stream.encodeText,
),
output.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }), Stream.encodeText),
{
contentType: "text/event-stream",
headers: {

View file

@ -15,6 +15,7 @@ import {
import { AbsolutePath } from "@opencode-ai/core/schema"
const DefaultSessionsLimit = 50
const DefaultSessionHistoryLimit = 50
export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) =>
Effect.gen(function* () {
@ -376,6 +377,31 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
}
}),
)
.handle(
"session.history",
Effect.fn(function* (ctx) {
return yield* session
.history({
sessionID: ctx.params.sessionID,
after: ctx.query.after,
limit: ctx.query.limit ?? DefaultSessionHistoryLimit,
})
.pipe(
Effect.map((page) => ({
data: page.events,
hasMore: page.hasMore,
})),
Effect.catchTag(
"Session.NotFoundError",
(error) =>
new SessionNotFoundError({
sessionID: error.sessionID,
message: `Session not found: ${error.sessionID}`,
}),
),
)
}),
)
.handle(
"session.events",
Effect.fn((ctx) =>

View file

@ -1,12 +1,12 @@
import { Location } from "@opencode-ai/core/location"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { LocationServiceMap } from "@opencode-ai/core/location-services"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
import { Effect, Layer } from "effect"
import { HttpServerRequest } from "effect/unstable/http"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
export type LocationServices = Layer.Success<ReturnType<(typeof LocationServiceMap)["get"]>>
export type LocationServices = Layer.Success<ReturnType<(typeof LocationServiceMap.Service)["get"]>>
export class LocationMiddleware extends HttpApiMiddleware.Service<LocationMiddleware, { provides: LocationServices }>()(
"@opencode/HttpApiLocation",
@ -49,7 +49,7 @@ function decode(input: string) {
export const layer = Layer.effect(
LocationMiddleware,
Effect.gen(function* () {
const locations = yield* LocationServiceMap
const locations = yield* LocationServiceMap.Service
return LocationMiddleware.of((effect) =>
Effect.gen(function* () {
const request = yield* HttpServerRequest.HttpServerRequest

View file

@ -1,5 +1,5 @@
import { Database } from "@opencode-ai/core/database/database"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { LocationServiceMap } from "@opencode-ai/core/location-services"
import { Location } from "@opencode-ai/core/location"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { SessionV2 } from "@opencode-ai/core/session"
@ -25,7 +25,7 @@ export const sessionLocationLayer = Layer.effect(
SessionLocationMiddleware,
Effect.gen(function* () {
const { db } = yield* Database.Service
const locations = yield* LocationServiceMap
const locations = yield* LocationServiceMap.Service
return SessionLocationMiddleware.of((effect) =>
Effect.gen(function* () {

View file

@ -1,6 +1,7 @@
export * as PtyEnvironment from "./pty-environment"
import { Context, Effect, Layer } from "effect"
import { makeGlobalNode } from "@opencode-ai/core/effect/app-node"
export interface Interface {
readonly get: (input: { directory: string; cwd: string }) => Effect.Effect<Record<string, string>>
@ -8,9 +9,11 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/ServerPtyEnvironment") {}
export const defaultLayer = Layer.succeed(
export const layer = Layer.succeed(
Service,
Service.of({
get: () => Effect.succeed({}),
}),
)
export const node = makeGlobalNode({ service: Service, layer, deps: [] })

View file

@ -1,7 +1,17 @@
import { Database } from "@opencode-ai/core/database/database"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { EventV2 } from "@opencode-ai/core/event"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"
import { Credential } from "@opencode-ai/core/credential"
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
import { SessionV2 } from "@opencode-ai/core/session"
import { SessionExecution } from "@opencode-ai/core/session/execution"
import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
import { HttpRouter, HttpServer } from "effect/unstable/http"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Layer, Option } from "effect"
import { Api } from "./api"
@ -10,6 +20,21 @@ import { handlers } from "./handlers"
import { authorizationLayer } from "./middleware/authorization"
import { schemaErrorLayer } from "./middleware/schema-error"
import { PtyEnvironment } from "./pty-environment"
import { layer as locationLayer } from "./location"
import { sessionLocationLayer } from "./middleware/session-location"
const applicationServices = LayerNode.group([
Database.node,
EventV2.node,
httpClient,
ToolOutputStore.cleanupNode,
SessionV2.node,
PermissionSaved.node,
PtyTicket.node,
Credential.node,
PtyEnvironment.node,
LocationServiceMap.node,
])
export function createRoutes(password?: string) {
return makeRoutes(
@ -24,16 +49,18 @@ export function createEmbeddedRoutes() {
}
function makeRoutes<AuthError, AuthServices>(auth: Layer.Layer<ServerAuth.Config, AuthError, AuthServices>) {
const serviceLayer = AppNodeBuilder.build(
LayerNode.bind(applicationServices, SessionExecution.node, SessionExecutionLocal.node),
)
return HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(
Layer.provide(handlers),
Layer.provide(PtyEnvironment.defaultLayer),
Layer.provide(sessionLocationLayer),
Layer.provide(locationLayer),
Layer.provide(authorizationLayer),
Layer.provide(schemaErrorLayer),
Layer.provide(auth),
Layer.provide(LocationServiceMap.layer),
Layer.provide(Database.defaultLayer),
Layer.provide(EventV2.defaultLayer),
Layer.provide(FetchHttpClient.layer),
Layer.provide(serviceLayer),
)
}