Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206export * as SessionContext from "./context.js"
import { Model } from "../model.js"import { Permission } from "../permission.js"import { Context, Effect, Layer } from "effect"import { Agent } from "../agent.js"import { CodeModeInstructions } from "../codemode/instructions.js"import { Database } from "../database/database.js"import { makeLocationNode } from "@opencode/util/effect/app-node"import { InstructionDiscovery } from "../instruction-discovery.js"import { Instructions } from "../instructions/index.js"import { InstructionBuiltIns } from "../instructions/builtins.js"import { Location } from "../location.js"import { McpInstructions } from "../mcp/instructions.js"import { McpTool } from "../tool/mcp.js"import { ReferenceInstructions } from "../reference/instructions.js"import { SkillInstructions } from "../skill/instructions.js"import { Tool } from "../tool.js"import { AgentNotFoundError } from "./error.js"import { SessionHistory } from "./history.js"import { SessionProviderContext } from "./provider-context.js"import { InstructionEntry } from "./instruction-entry.js"import { SessionMessage } from "./message.js"import { SessionModelRequest } from "./model-request.js"import { SessionRunnerModel } from "./runner/model.js"import { SessionSchema } from "./schema.js"import { SessionStore } from "./store.js"
export interface Selection { readonly session: SessionSchema.Info readonly agent: Agent.Selection & { readonly info: Agent.Info } readonly instructions: Instructions.List readonly tools: Tool.Snapshot}
export interface Loaded { readonly session: SessionSchema.Info readonly agent: Agent.Selection & { readonly info: Agent.Info } readonly model: SessionRunnerModel.Resolved readonly initial: string readonly messages: ReadonlyArray<SessionMessage.Info> readonly tools: Tool.Snapshot}
/** * Resolves model-request state in two phases: `select` fixes the Session, * agent, instruction sources, and tool snapshot; `load` adds the model and * active history for that selection. Auxiliary operations resolve only the * capabilities they need; request preparation stays separate from selection. */export interface Interface { /** Selects the Session, agent, instructions, and tools used by subsequent work. */ readonly select: (sessionID: SessionSchema.ID) => Effect.Effect<Selection, AgentNotFoundError> /** Resolves the model and active history for that selection. */ readonly load: (selection: Selection) => Effect.Effect<Loaded, SessionRunnerModel.Error> readonly resolveModel: ( session: SessionSchema.Info, ) => Effect.Effect<SessionRunnerModel.Resolved, SessionRunnerModel.Error> /** Selects auxiliary title capabilities without instruction or tool preflight. */ readonly selectTitle: (session: SessionSchema.Info) => Effect.Effect< | { readonly agent: Agent.Info readonly primary: SessionRunnerModel.Resolved | undefined readonly selected: SessionRunnerModel.Resolved } | undefined > readonly request: SessionModelRequest.Interface}
/** Location-scoped model-context loader for durable Session Steps. */export class Service extends Context.Service<Service, Interface>()("@opencode/SessionContext") {}
const layer = Layer.effect( Service, Effect.gen(function* () { const agents = yield* Agent.Service const builtins = yield* InstructionBuiltIns.Service const model = yield* Model.Service const db = (yield* Database.Service).db const discovery = yield* InstructionDiscovery.Service const entries = yield* InstructionEntry.Service const location = yield* Location.Service const mcpInstructions = yield* McpInstructions.Service const mcpTools = yield* McpTool.Service const models = yield* SessionRunnerModel.Service const request = yield* SessionModelRequest.Service const referenceInstructions = yield* ReferenceInstructions.Service const skillInstructions = yield* SkillInstructions.Service const store = yield* SessionStore.Service const registry = yield* Tool.Service
const resolveModel = (session: SessionSchema.Info) => models.resolve(session, model.available)
const selectTitle = Effect.fn("SessionContext.selectTitle")(function* (session: SessionSchema.Info) { const agent = yield* agents.get(Agent.ID.make("title")) if (!agent) return const primary = yield* resolveModel(session).pipe(Effect.orElseSucceed(() => undefined)) const info = yield* Effect.gen(function* () { if (agent.model) return yield* model.get(agent.model.providerID, agent.model.id) if (!primary) return return yield* model.small(primary.ref.providerID) }) const variant = agent.model?.variant ?? MINIMAL_REASONING_VARIANTS.find((id) => info?.variants.some((item) => item.id === id)) const preferred = info && (yield* resolveModel({ ...session, model: Model.Ref.make({ providerID: info.providerID, id: info.id, ...(variant ? { variant } : {}), }), }).pipe(Effect.orElseSucceed(() => undefined))) const selected = preferred ?? primary if (!selected) return return { agent, primary, selected } })
const select = Effect.fn("SessionContext.select")(function* (sessionID: SessionSchema.ID) { const session = yield* store.get(sessionID) if (!session) return yield* Effect.die(new Error(`Session not found: ${sessionID}`)) if (session.location.directory !== location.directory || session.location.workspaceID !== location.workspaceID) return yield* Effect.interrupt
yield* mcpTools.flush const agent = yield* agents.select(session.agent) if (!agent.info) return yield* new AgentNotFoundError({ sessionID: session.id, agent: session.agent ?? agent.id }) // Session permissions narrow discovery the same way they narrow the tool snapshot. const permissions = Permission.merge(agent.info.permissions, session.permissions ?? []) const loaded = yield* Effect.all( { tools: registry.snapshot(permissions), builtins: builtins.load(sessionID), discovery: discovery.load(), skills: skillInstructions.load(permissions), references: referenceInstructions.load(), mcp: mcpInstructions.load(permissions), entries: entries.load(sessionID), }, { concurrency: "unbounded" }, ) return { session, agent: { ...agent, info: agent.info }, instructions: Instructions.combine([ loaded.builtins, CodeModeInstructions.make(loaded.tools.codeModeCatalog), loaded.discovery, loaded.skills, loaded.references, loaded.mcp, loaded.entries, ]), tools: loaded.tools, } })
const load = Effect.fn("SessionContext.load")(function* (selection: Selection) { const model = yield* resolveModel(selection.session) const history = yield* SessionHistory.entriesForRunner( db, selection.session.id, selection.instructions, SessionProviderContext.provenance(model) ?? "local", ) return { session: selection.session, agent: selection.agent, model, initial: history.initial, messages: history.entries.map((entry) => entry.message), tools: selection.tools, } })
return Service.of({ select, load, resolveModel, selectTitle, request }) }),)
/** Variant IDs that minimize reasoning output, in preference order. */const MINIMAL_REASONING_VARIANTS = ["none", "minimal", "low"].map((id) => Model.VariantID.make(id))
export const node = makeLocationNode({ service: Service, layer, deps: [ Agent.node, Model.node, Database.node, InstructionBuiltIns.node, InstructionDiscovery.node, InstructionEntry.node, Location.node, McpInstructions.node, McpTool.node, ReferenceInstructions.node, SessionRunnerModel.node, SessionModelRequest.node, SessionStore.node, SkillInstructions.node, Tool.node, ],})