| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- export * as OpenCode from "./opencode"
- import { Context, Effect, Layer } from "effect"
- import { Catalog } from "../catalog"
- import { Database } from "../database/database"
- import { EventV2 } from "../event"
- import { LocationServiceMap } from "../location-layer"
- import { PluginBoot } from "../plugin/boot"
- 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.Service
- }
- /** Intentional public native API for Effect applications embedding OpenCode. */
- export class Service extends Context.Service<Service, Interface>()("@opencode/public/OpenCode") {}
- class SessionModelValidation extends Context.Service<
- SessionModelValidation,
- {
- readonly validate: (
- input: Session.SwitchModelInput & { readonly location: Session.Info["location"] },
- ) => Effect.Effect<void, Session.ModelUnavailableError | Session.VariantUnavailableError>
- }
- >()("@opencode/public/OpenCode/SessionModelValidation") {}
- const LocationServicesLayer = LocationServiceMap.layer
- const SessionModelValidationLayer = Layer.effect(
- SessionModelValidation,
- Effect.gen(function* () {
- const locations = yield* LocationServiceMap
- return SessionModelValidation.of({
- validate: Effect.fn("OpenCode.sessions.validateModel")(function* (input) {
- yield* Effect.gen(function* () {
- yield* (yield* PluginBoot.Service).wait()
- const catalog = yield* Catalog.Service
- const model = (yield* catalog.model.available()).find(
- (model) => model.providerID === input.model.providerID && model.id === input.model.id,
- )
- if (!model)
- return yield* new Session.ModelUnavailableError({
- providerID: input.model.providerID,
- modelID: input.model.id,
- })
- if (
- input.model.variant !== undefined &&
- input.model.variant !== "default" &&
- !model.variants.some((variant) => variant.id === input.model.variant)
- )
- return yield* new Session.VariantUnavailableError({
- providerID: input.model.providerID,
- modelID: input.model.id,
- variant: input.model.variant,
- })
- }).pipe(Effect.provide(locations.get(input.location)))
- }),
- })
- }),
- )
- const SessionsLayer = Layer.merge(
- 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.orDie,
- ),
- SessionModelValidationLayer,
- ).pipe(Layer.provide(LocationServicesLayer))
- const ApplicationToolsLayer = ApplicationTools.layer
- // 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
- const validation = yield* SessionModelValidation
- return Service.of({
- tools: { attach: tools.attach },
- sessions: {
- create: (input) =>
- sessions.create({
- id: input.id,
- agent: input.agent,
- model: input.model,
- location: input.location,
- }),
- get: sessions.get,
- list: sessions.list,
- switchModel: Effect.fn("OpenCode.sessions.switchModel")(function* (input) {
- const session = yield* sessions.get(input.sessionID)
- yield* validation.validate({ ...input, location: session.location })
- yield* sessions.switchModel(input)
- }),
- 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(ApplicationToolsLayer, SessionsLayer)))
- // TODO: Add OpenCode.create(...) as the Promise facade over the same native API semantics.
|