feat(sdk): add HttpApi clients and embedded host (#33445)

This commit is contained in:
Kit Langton 2026-06-25 05:08:54 +02:00 committed by GitHub
commit cdd67cf30f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 4629 additions and 316 deletions

View file

@ -1,6 +0,0 @@
export * as Agent from "./agent"
import { AgentV2 } from "../agent"
export const ID = AgentV2.ID
export type ID = AgentV2.ID

View file

@ -1,9 +0,0 @@
/** Intentional supported native API. Other core subpaths remain internal implementation surfaces. */
export { Agent } from "./agent"
export { Model } from "./model"
export { OpenCode } from "./opencode"
export { Session } from "./session"
export { Tool } from "./tool"
export { Location } from "./location"
export { Prompt } from "../session/prompt"
export { AbsolutePath } from "../schema"

View file

@ -1,6 +0,0 @@
export * as Location from "./location"
import { Location } from "../location"
export const Ref = Location.Ref
export type Ref = Location.Ref

View file

@ -1,9 +0,0 @@
export * as Model from "./model"
import { ModelV2 } from "../model"
export const ID = ModelV2.ID
export type ID = ModelV2.ID
export const Ref = ModelV2.Ref
export type Ref = ModelV2.Ref

View file

@ -1,76 +0,0 @@
export * as OpenCode from "./opencode"
import { Context, Effect, Layer } from "effect"
import { Database } from "../database/database"
import { EventV2 } from "../event"
import { LocationServiceMap } from "../location-layer"
import { ProjectV2 } from "../project"
import { SessionV2 } from "../session"
import * as SessionExecutionLocal from "../session/execution/local"
import { SessionProjector } from "../session/projector"
import { SessionStore } from "../session/store"
import { ApplicationTools } from "../tool/application-tools"
import { Session } from "./session"
import { Tool } from "./tool"
export interface Interface {
readonly sessions: Session.Interface
readonly tools: Tool.Interface
}
/** Intentional public native API for Effect applications embedding OpenCode. */
export class Service extends Context.Service<Service, Interface>()("@opencode/public/OpenCode") {}
const SessionsLayer = SessionV2.layer.pipe(
Layer.provide(SessionProjector.layer),
Layer.provide(SessionExecutionLocal.layer),
Layer.provide(SessionStore.layer),
Layer.provide(EventV2.layer),
Layer.provide(Database.defaultLayer),
Layer.provide(ProjectV2.defaultLayer),
Layer.provide(LocationServiceMap.layer.pipe(Layer.provide(ApplicationTools.layer))),
Layer.orDie,
)
// TODO: Accept explicit storage so tests and embeddings can select disposable or application-owned persistence.
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const sessions = yield* SessionV2.Service
const tools = yield* ApplicationTools.Service
return Service.of({
tools: { register: tools.register },
sessions: {
create: (input) =>
sessions.create({
id: input.id,
agent: input.agent,
model: input.model,
location: input.location,
}),
get: sessions.get,
list: sessions.list,
switchModel: sessions.switchModel,
interrupt: sessions.interrupt,
prompt: (input) =>
sessions.prompt({
id: input.id,
sessionID: input.sessionID,
prompt: input.prompt,
delivery: input.delivery,
}),
messages: (input) =>
sessions.messages({
sessionID: input.sessionID,
limit: input.limit,
order: input.order,
cursor: input.cursor,
}),
message: (input) => sessions.message({ sessionID: input.sessionID, messageID: input.messageID }),
context: sessions.context,
events: (input) => sessions.events({ sessionID: input.sessionID, after: input.after }),
},
})
}),
).pipe(Layer.provide(Layer.merge(ApplicationTools.layer, SessionsLayer)))
// TODO: Add OpenCode.create(...) as the Promise facade over the same native API semantics.

View file

@ -1,96 +0,0 @@
export * as Session from "./session"
import { Effect, Stream } from "effect"
import { SessionV2 } from "../session"
import { MessageDecodeError } from "../session/error"
import { SessionEvent } from "../session/event"
import { SessionInput } from "../session/input"
import { SessionMessage } from "../session/message"
import { Prompt } from "../session/prompt"
import { Agent } from "./agent"
import { Location } from "./location"
import { Model } from "./model"
export const ID = SessionV2.ID
export type ID = SessionV2.ID
export const Info = SessionV2.Info
export type Info = SessionV2.Info
export const MessageID = SessionMessage.ID
export type MessageID = SessionMessage.ID
export const Message = SessionMessage.Message
export type Message = SessionMessage.Message
export const Admission = SessionInput.Admitted
export type Admission = SessionInput.Admitted
export const Delivery = SessionInput.Delivery
export type Delivery = SessionInput.Delivery
export const ListInput = SessionV2.ListInput
export type ListInput = SessionV2.ListInput
export type Event = SessionEvent.DurableEvent
export const NotFoundError = SessionV2.NotFoundError
export type NotFoundError = SessionV2.NotFoundError
export const PromptConflictError = SessionV2.PromptConflictError
export type PromptConflictError = SessionV2.PromptConflictError
export { MessageDecodeError }
export interface CreateInput {
readonly id?: ID
readonly agent?: Agent.ID
readonly model?: Model.Ref
readonly location: Location.Ref
}
export interface PromptInput {
readonly id?: MessageID
readonly sessionID: ID
readonly prompt: Prompt
readonly delivery?: Delivery
}
export interface SwitchModelInput {
readonly sessionID: ID
readonly model: Model.Ref
}
export interface MessagesInput {
readonly sessionID: ID
readonly limit?: number
readonly order?: "asc" | "desc"
readonly cursor?: {
readonly id: MessageID
readonly direction: "previous" | "next"
}
}
export interface MessageInput {
readonly sessionID: ID
readonly messageID: MessageID
}
export interface EventsInput {
readonly sessionID: ID
readonly after?: number
}
export interface Interface {
readonly create: (input: CreateInput) => Effect.Effect<Info>
readonly get: (sessionID: ID) => Effect.Effect<Info, NotFoundError>
readonly list: (input?: ListInput) => Effect.Effect<Info[]>
readonly prompt: (input: PromptInput) => Effect.Effect<Admission, NotFoundError | PromptConflictError>
readonly switchModel: (input: SwitchModelInput) => Effect.Effect<void, NotFoundError>
/** Interrupt the active V2 execution chain for one Session on this process. Interrupting an idle or missing Session is a no-op. */
readonly interrupt: (sessionID: ID) => Effect.Effect<void>
readonly messages: (input: MessagesInput) => Effect.Effect<Message[], NotFoundError | MessageDecodeError>
readonly message: (input: MessageInput) => Effect.Effect<Message | undefined>
readonly context: (sessionID: ID) => Effect.Effect<Message[], NotFoundError | MessageDecodeError>
readonly events: (input: EventsInput) => Stream.Stream<Event, NotFoundError>
}

View file

@ -1,17 +0,0 @@
export * as Tool from "./tool"
import { Effect, Scope } from "effect"
import type { AnyTool, RegistrationError } from "../tool/tool"
export { Failure, RegistrationError, make } from "../tool/tool"
export type { AnyTool, Content, Context, Definition } from "../tool/tool"
export interface Interface {
/**
* Register same-process tools on this OpenCode instance for the current Scope.
* Location tools with the same name take precedence where they are installed.
* Closing the Scope removes the tools immediately, so calls that have not
* started settling may fail because the tool is no longer available.
*/
readonly register: (tools: Readonly<Record<string, AnyTool>>) => Effect.Effect<void, RegistrationError, Scope.Scope>
}