feat(core): add embedded v2 session runtime and tool foundation (#30632)

This commit is contained in:
Kit Langton
2026-06-03 23:02:17 -04:00
committed by GitHub
parent c35267776a
commit 76ee87ead8
215 changed files with 31344 additions and 3278 deletions
@@ -5,6 +5,7 @@ import { ProviderGroup } from "./v2/provider"
import { SessionGroup } from "./v2/session"
import { PermissionGroup, PermissionSavedGroup, SessionPermissionGroup } from "./v2/permission"
import { FileSystemGroup } from "./v2/fs"
import { QuestionGroup, SessionQuestionGroup } from "./v2/question"
export const V2Api = HttpApi.make("v2")
.add(SessionGroup)
@@ -15,6 +16,8 @@ export const V2Api = HttpApi.make("v2")
.add(SessionPermissionGroup)
.add(PermissionSavedGroup)
.add(FileSystemGroup)
.add(QuestionGroup)
.add(SessionQuestionGroup)
.annotateMerge(
OpenApi.annotations({
title: "opencode experimental HttpApi",
@@ -6,6 +6,8 @@ import { PermissionV2 } from "@opencode-ai/core/permission"
import { ProjectReference } from "@opencode-ai/core/project-reference"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
import { QuestionV2 } from "@opencode-ai/core/question"
import { Effect, Layer, Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"
import { HttpApiMiddleware, OpenApi } from "effect/unstable/httpapi"
@@ -43,16 +45,18 @@ export class V2LocationMiddleware extends HttpApiMiddleware.Service<
| PermissionV2.Service
| ProjectReference.Service
| FileSystem.Service
| QuestionV2.Service
}
>()("@opencode/ExperimentalHttpApiV2Location") {}
function ref(request: HttpServerRequest.HttpServerRequest): Location.Ref {
const query = new URL(request.url, "http://localhost").searchParams
const workspaceID = query.get("location[workspace]") || request.headers["x-opencode-workspace"]
return {
directory: AbsolutePath.make(
query.get("location[directory]") || request.headers["x-opencode-directory"] || process.cwd(),
),
workspaceID: query.get("location[workspace]") || request.headers["x-opencode-workspace"],
workspaceID: workspaceID ? WorkspaceV2.ID.make(workspaceID) : undefined,
}
}
@@ -9,7 +9,7 @@ export const ModelGroup = HttpApiGroup.make("v2.model")
.add(
HttpApiEndpoint.get("models", "/api/model", {
query: LocationQuery,
success: Schema.Array(ModelV2.Info),
success: Schema.Array(ModelV2.PublicInfo),
error: ServiceUnavailableError,
})
.annotateMerge(locationQueryOpenApi)
@@ -9,7 +9,7 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
.add(
HttpApiEndpoint.get("providers", "/api/provider", {
query: LocationQuery,
success: Schema.Array(ProviderV2.Info),
success: Schema.Array(ProviderV2.PublicInfo),
error: ServiceUnavailableError,
})
.annotateMerge(locationQueryOpenApi)
@@ -25,7 +25,7 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
HttpApiEndpoint.get("provider", "/api/provider/:providerID", {
params: { providerID: ProviderV2.ID },
query: LocationQuery,
success: ProviderV2.Info,
success: ProviderV2.PublicInfo,
error: [ProviderNotFoundError, ServiceUnavailableError],
})
.annotateMerge(locationQueryOpenApi)
@@ -0,0 +1,59 @@
import { QuestionV2 } from "@opencode-ai/core/question"
import { SessionV2 } from "@opencode-ai/core/session"
import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
import { QuestionNotFoundError, SessionNotFoundError } from "../../errors"
import { V2Authorization } from "../../middleware/authorization"
import { LocationQuery, locationQueryOpenApi, V2LocationMiddleware } from "./location"
export const QuestionGroup = HttpApiGroup.make("v2.question")
.add(
HttpApiEndpoint.get("questionRequests", "/api/question/request", {
query: LocationQuery,
success: Schema.Array(QuestionV2.Request),
})
.annotateMerge(locationQueryOpenApi)
.annotateMerge(
OpenApi.annotations({
identifier: "v2.question.request.list",
summary: "List pending question requests",
description: "Retrieve pending question requests for a location.",
}),
),
)
.annotateMerge(OpenApi.annotations({ title: "v2 questions", description: "Experimental v2 question routes." }))
.middleware(V2LocationMiddleware)
.middleware(V2Authorization)
export const SessionQuestionGroup = HttpApiGroup.make("v2.session.question")
.add(
HttpApiEndpoint.post("questionRequestReply", "/api/session/:sessionID/question/request/:requestID/reply", {
params: { sessionID: SessionV2.ID, requestID: QuestionV2.ID },
payload: QuestionV2.Reply,
success: HttpApiSchema.NoContent,
error: [SessionNotFoundError, QuestionNotFoundError],
}).annotateMerge(
OpenApi.annotations({
identifier: "v2.session.question.reply",
summary: "Reply to pending question request",
description: "Answer a pending question request owned by a session.",
}),
),
)
.add(
HttpApiEndpoint.post("questionRequestReject", "/api/session/:sessionID/question/request/:requestID/reject", {
params: { sessionID: SessionV2.ID, requestID: QuestionV2.ID },
success: HttpApiSchema.NoContent,
error: [SessionNotFoundError, QuestionNotFoundError],
}).annotateMerge(
OpenApi.annotations({
identifier: "v2.session.question.reject",
summary: "Reject pending question request",
description: "Reject a pending question request owned by a session.",
}),
),
)
.annotateMerge(
OpenApi.annotations({ title: "v2 session questions", description: "Experimental v2 session question routes." }),
)
.middleware(V2Authorization)
@@ -1,5 +1,6 @@
import { SessionID } from "@/session/schema"
import { SessionMessage } from "@opencode-ai/core/session/message"
import { SessionInput } from "@opencode-ai/core/session/input"
import { Prompt } from "@opencode-ai/core/session/prompt"
import { SessionV2 } from "@opencode-ai/core/session"
import { ProjectV2 } from "@opencode-ai/core/project"
@@ -8,6 +9,7 @@ import { WorkspaceV2 } from "@opencode-ai/core/workspace"
import { Schema, Struct } from "effect"
import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
import {
ConflictError,
InvalidCursorError,
InvalidRequestError,
ServiceUnavailableError,
@@ -110,16 +112,18 @@ export const SessionGroup = HttpApiGroup.make("v2.session")
params: { sessionID: SessionID },
query: WorkspaceRoutingQuery,
payload: Schema.Struct({
id: SessionMessage.ID.pipe(Schema.optional),
prompt: Prompt,
delivery: SessionV2.Delivery.pipe(Schema.optional),
delivery: SessionInput.Delivery.pipe(Schema.optional),
resume: Schema.Boolean.pipe(Schema.optional),
}),
success: SessionMessage.Message,
error: [SessionNotFoundError, ServiceUnavailableError],
success: SessionMessage.User,
error: [ConflictError, SessionNotFoundError],
}).annotateMerge(
OpenApi.annotations({
identifier: "v2.session.prompt",
summary: "Send v2 message",
description: "Create a v2 session message and queue it for the agent loop.",
description: "Durably admit one v2 session input and schedule agent-loop execution unless resume is false.",
}),
),
)