mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-30 09:32:11 +00:00
refactor(core): consolidate runner capability reads (#45448)
This commit is contained in:
parent
1e7c60adce
commit
10786cb60c
10 changed files with 137 additions and 104 deletions
|
|
@ -8,9 +8,10 @@ import { Bus } from "../bus.js"
|
|||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { llmClient } from "../effect/app-node-platform.js"
|
||||
import { SessionEvent } from "./event.js"
|
||||
import type { SessionContext } from "./context.js"
|
||||
import type { SessionMessage } from "./message.js"
|
||||
import { SessionModelRequest } from "./model-request.js"
|
||||
import { SessionRunnerModel } from "./runner/model.js"
|
||||
import type { SessionModelRequest } from "./model-request.js"
|
||||
import type { SessionRunnerModel } from "./runner/model.js"
|
||||
import { SessionSchema } from "./schema.js"
|
||||
import { toSessionError } from "./to-session-error.js"
|
||||
import { Token } from "../util/token.js"
|
||||
|
|
@ -69,14 +70,13 @@ type Dependencies = {
|
|||
readonly llm: {
|
||||
readonly stream: (request: LLMRequest, options?: StreamOptions) => Stream.Stream<LLMEvent, AIError>
|
||||
}
|
||||
readonly models: SessionRunnerModel.Interface
|
||||
readonly modelRequests: SessionModelRequest.Interface
|
||||
}
|
||||
|
||||
export type AutoInput = {
|
||||
readonly session: SessionSchema.Info
|
||||
readonly messages: readonly SessionMessage.Info[]
|
||||
readonly resolved: SessionRunnerModel.Resolved
|
||||
readonly prepare: SessionModelRequest.Interface["prepare"]
|
||||
}
|
||||
|
||||
type RequiredInput = Pick<AutoInput, "messages" | "resolved">
|
||||
|
|
@ -86,6 +86,9 @@ export type ManualInput = {
|
|||
readonly messages: readonly SessionMessage.Info[]
|
||||
readonly inputID: SessionMessage.ID
|
||||
readonly started?: boolean
|
||||
/** Invoked after content planning, not when the caller captures the operation. */
|
||||
readonly resolveModel: SessionContext.Interface["resolveModel"]
|
||||
readonly prepare: SessionModelRequest.Interface["prepare"]
|
||||
}
|
||||
|
||||
type Plan = {
|
||||
|
|
@ -96,6 +99,7 @@ type Plan = {
|
|||
readonly recent: string
|
||||
readonly inputID?: SessionMessage.ID
|
||||
readonly started?: boolean
|
||||
readonly prepare: SessionModelRequest.Interface["prepare"]
|
||||
}
|
||||
|
||||
export type Outcome =
|
||||
|
|
@ -278,7 +282,7 @@ const make = (dependencies: Dependencies) => {
|
|||
})
|
||||
: Effect.void,
|
||||
)
|
||||
const prepared = yield* dependencies.modelRequests.prepare({
|
||||
const prepared = yield* plan.prepare({
|
||||
scope: { session: plan.session, agentID: Agent.ID.make("compaction"), model: plan.resolved },
|
||||
transcript: { system: [], messages: [Message.user(plan.prompt)] },
|
||||
contextHooks: false,
|
||||
|
|
@ -348,6 +352,7 @@ const make = (dependencies: Dependencies) => {
|
|||
return yield* execute({
|
||||
session: input.session,
|
||||
resolved: input.resolved,
|
||||
prepare: input.prepare,
|
||||
reason: "auto",
|
||||
...content,
|
||||
})
|
||||
|
|
@ -387,7 +392,7 @@ const make = (dependencies: Dependencies) => {
|
|||
error: { type: "compaction.unavailable", message: "Nothing to compact yet" },
|
||||
inputID: input.inputID,
|
||||
})
|
||||
const resolved = yield* dependencies.models.resolve(input.session).pipe(
|
||||
const resolved = yield* input.resolveModel(input.session).pipe(
|
||||
Effect.catch((cause) =>
|
||||
failed({
|
||||
sessionID: input.session.id,
|
||||
|
|
@ -401,6 +406,7 @@ const make = (dependencies: Dependencies) => {
|
|||
return yield* execute({
|
||||
session: input.session,
|
||||
resolved,
|
||||
prepare: input.prepare,
|
||||
reason: "manual",
|
||||
inputID: input.inputID,
|
||||
started: input.started,
|
||||
|
|
@ -422,14 +428,12 @@ export const layer = Layer.effect(
|
|||
Effect.gen(function* () {
|
||||
const bus = yield* Bus.Service
|
||||
const llm = yield* LLMClient.Service
|
||||
const models = yield* SessionRunnerModel.Service
|
||||
const modelRequests = yield* SessionModelRequest.Service
|
||||
return make({ bus, llm, models, modelRequests })
|
||||
return make({ bus, llm })
|
||||
}),
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer,
|
||||
deps: [Bus.node, llmClient, SessionRunnerModel.node, SessionModelRequest.node],
|
||||
deps: [Bus.node, llmClient],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export * as SessionContext from "./context.js"
|
|||
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { Agent } from "../agent.js"
|
||||
import { Catalog } from "../catalog.js"
|
||||
import { CodeModeInstructions } from "../codemode/instructions.js"
|
||||
import { Database } from "../database/database.js"
|
||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
|
|
@ -11,6 +12,7 @@ import { InstructionBuiltIns } from "../instructions/builtins.js"
|
|||
import { Location } from "../location.js"
|
||||
import { McpInstructions } from "../mcp/instructions.js"
|
||||
import { McpTool } from "../tool/mcp.js"
|
||||
import { Model } from "../model.js"
|
||||
import { PluginSupervisor } from "../plugin/supervisor.js"
|
||||
import { ReferenceInstructions } from "../reference/instructions.js"
|
||||
import { SkillInstructions } from "../skill/instructions.js"
|
||||
|
|
@ -19,6 +21,7 @@ import { AgentNotFoundError } from "./error.js"
|
|||
import { SessionHistory } from "./history.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"
|
||||
|
|
@ -42,14 +45,27 @@ export interface Loaded {
|
|||
/**
|
||||
* 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. This module does not build or execute the
|
||||
* model request.
|
||||
* 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 prepare: SessionModelRequest.Interface["prepare"]
|
||||
}
|
||||
|
||||
/** Location-scoped model-context loader for durable Session Steps. */
|
||||
|
|
@ -60,6 +76,7 @@ const layer = Layer.effect(
|
|||
Effect.gen(function* () {
|
||||
const agents = yield* Agent.Service
|
||||
const builtins = yield* InstructionBuiltIns.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
const db = (yield* Database.Service).db
|
||||
const discovery = yield* InstructionDiscovery.Service
|
||||
const entries = yield* InstructionEntry.Service
|
||||
|
|
@ -67,12 +84,41 @@ const layer = Layer.effect(
|
|||
const mcpInstructions = yield* McpInstructions.Service
|
||||
const mcpTools = yield* McpTool.Service
|
||||
const models = yield* SessionRunnerModel.Service
|
||||
const modelRequests = yield* SessionModelRequest.Service
|
||||
const plugins = yield* PluginSupervisor.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, catalog.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* catalog.model.get(agent.model.providerID, agent.model.id)
|
||||
if (!primary) return
|
||||
return yield* catalog.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}`))
|
||||
|
|
@ -112,7 +158,7 @@ const layer = Layer.effect(
|
|||
})
|
||||
|
||||
const load = Effect.fn("SessionContext.load")(function* (selection: Selection) {
|
||||
const model = yield* models.resolve(selection.session)
|
||||
const model = yield* resolveModel(selection.session)
|
||||
const history = yield* SessionHistory.entriesForRunner(db, selection.session.id, selection.instructions)
|
||||
return {
|
||||
session: selection.session,
|
||||
|
|
@ -124,15 +170,19 @@ const layer = Layer.effect(
|
|||
}
|
||||
})
|
||||
|
||||
return Service.of({ select, load })
|
||||
return Service.of({ select, load, resolveModel, selectTitle, prepare: modelRequests.prepare })
|
||||
}),
|
||||
)
|
||||
|
||||
/** 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,
|
||||
Catalog.node,
|
||||
Database.node,
|
||||
InstructionBuiltIns.node,
|
||||
InstructionDiscovery.node,
|
||||
|
|
@ -143,6 +193,7 @@ export const node = makeLocationNode({
|
|||
PluginSupervisor.node,
|
||||
ReferenceInstructions.node,
|
||||
SessionRunnerModel.node,
|
||||
SessionModelRequest.node,
|
||||
SessionStore.node,
|
||||
SkillInstructions.node,
|
||||
Tool.node,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { SessionContext } from "./context.js"
|
|||
import { SessionGenerate } from "./generate.js"
|
||||
import { SessionHistory } from "./history.js"
|
||||
import { SessionModelRequest } from "./model-request.js"
|
||||
import { SessionRunnerModel } from "./runner/model.js"
|
||||
|
||||
export const layer = Layer.effect(
|
||||
SessionGenerate.Service,
|
||||
|
|
@ -17,13 +16,11 @@ export const layer = Layer.effect(
|
|||
const context = yield* SessionContext.Service
|
||||
const database = yield* Database.Service
|
||||
const llm = yield* LLMClient.Service
|
||||
const models = yield* SessionRunnerModel.Service
|
||||
const modelRequests = yield* SessionModelRequest.Service
|
||||
|
||||
return SessionGenerate.Service.of({
|
||||
generate: Effect.fn("SessionGenerate.generate")(function* (input) {
|
||||
const selection = yield* context.select(input.sessionID)
|
||||
const model = yield* models.resolve(selection.session)
|
||||
const model = yield* context.resolveModel(selection.session)
|
||||
const history = yield* SessionHistory.preview(database.db, selection.session.id, selection.instructions)
|
||||
const transcript = SessionModelRequest.baseTranscript({
|
||||
agent: selection.agent.info,
|
||||
|
|
@ -32,7 +29,7 @@ export const layer = Layer.effect(
|
|||
initial: history.initial,
|
||||
messages: history.messages,
|
||||
})
|
||||
const prepared = yield* modelRequests.prepare({
|
||||
const prepared = yield* context.prepare({
|
||||
scope: { session: selection.session, agentID: selection.agent.id, model, tools: selection.tools },
|
||||
transcript: {
|
||||
system: transcript.system,
|
||||
|
|
@ -59,5 +56,5 @@ export const layer = Layer.effect(
|
|||
export const node = makeLocationNode({
|
||||
service: SessionGenerate.Service,
|
||||
layer,
|
||||
deps: [SessionContext.node, Database.node, SessionModelRequest.node, SessionRunnerModel.node, llmClient],
|
||||
deps: [SessionContext.node, Database.node, llmClient],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ const layer = Layer.effect(
|
|||
const bus = yield* Bus.Service
|
||||
const store = yield* SessionStore.Service
|
||||
const context = yield* SessionContext.Service
|
||||
const modelRequests = yield* SessionModelRequest.Service
|
||||
const modelTransport = yield* SessionModelTransport.Service
|
||||
const db = (yield* Database.Service).db
|
||||
const compaction = yield* SessionCompaction.Service
|
||||
|
|
@ -142,6 +141,8 @@ const layer = Layer.effect(
|
|||
Effect.gen(function* () {
|
||||
return yield* compaction.compactManual({
|
||||
session,
|
||||
resolveModel: context.resolveModel,
|
||||
prepare: context.prepare,
|
||||
messages: yield* store.context(sessionID),
|
||||
inputID: pending.id,
|
||||
started: true,
|
||||
|
|
@ -215,7 +216,12 @@ const layer = Layer.effect(
|
|||
// Reuse boundary preparation once; retries refresh context without delivering more input.
|
||||
const loaded = initial ?? (yield* prepareContext(sessionID).pipe(Effect.flatMap(context.load)))
|
||||
initial = undefined
|
||||
const compactionInput = { session: loaded.session, messages: loaded.messages, resolved: loaded.model }
|
||||
const compactionInput = {
|
||||
session: loaded.session,
|
||||
messages: loaded.messages,
|
||||
resolved: loaded.model,
|
||||
prepare: context.prepare,
|
||||
}
|
||||
if (compaction.required(compactionInput)) {
|
||||
const compacted = yield* compaction.compact(compactionInput)
|
||||
if (compacted.status !== "completed") return yield* new StepFailedError({ error: compacted.error })
|
||||
|
|
@ -230,7 +236,7 @@ const layer = Layer.effect(
|
|||
initial: loaded.initial,
|
||||
messages: loaded.messages,
|
||||
})
|
||||
const prepared = yield* modelRequests.prepare({
|
||||
const prepared = yield* context.prepare({
|
||||
scope: { session: loaded.session, agentID: loaded.agent.id, model: loaded.model, tools: loaded.tools },
|
||||
transcript: {
|
||||
system: transcript.system,
|
||||
|
|
@ -319,7 +325,6 @@ export const node = makeLocationNode({
|
|||
Bus.node,
|
||||
llmClient,
|
||||
SessionContext.node,
|
||||
SessionModelRequest.node,
|
||||
SessionModelTransport.node,
|
||||
SessionStore.node,
|
||||
SessionCompaction.node,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ export * as SessionRunnerModel from "./model.js"
|
|||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { LanguageModel } from "@opencode-ai/ai"
|
||||
import { Context, Effect, Layer, Schema } from "effect"
|
||||
import { Catalog } from "../../catalog.js"
|
||||
import { ModelResolver } from "../../model-resolver.js"
|
||||
import { Capabilities, ID, Info, Ref, VariantID } from "../../model.js"
|
||||
import { Provider } from "../../provider.js"
|
||||
|
|
@ -41,7 +40,11 @@ export type Error = ModelNotSelectedError | ModelUnavailableError | ModelResolve
|
|||
export type Resolved = ModelResolver.Resolved
|
||||
|
||||
export interface Interface {
|
||||
readonly resolve: (session: SessionSchema.Info) => Effect.Effect<Resolved, Error>
|
||||
/** Availability is sampled lazily for each explicitly selected model resolution. */
|
||||
readonly resolve: (
|
||||
session: SessionSchema.Info,
|
||||
available: () => Effect.Effect<ReadonlyArray<Info>>,
|
||||
) => Effect.Effect<Resolved, Error>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionRunnerModel") {}
|
||||
|
|
@ -70,17 +73,16 @@ export const resolved = (
|
|||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const resolver = yield* ModelResolver.Service
|
||||
return Service.of({
|
||||
resolve: Effect.fn("SessionRunnerModel.resolve")(function* (session) {
|
||||
resolve: Effect.fn("SessionRunnerModel.resolve")(function* (session, available) {
|
||||
// Location plugins populate and filter the catalog asynchronously during layer startup.
|
||||
if (!session.model) {
|
||||
const resolved = yield* resolver.resolve()
|
||||
if (resolved) return resolved
|
||||
return yield* new ModelNotSelectedError({ sessionID: session.id })
|
||||
}
|
||||
const selected = (yield* catalog.model.available()).find(
|
||||
const selected = (yield* available()).find(
|
||||
(model) => model.providerID === session.model?.providerID && model.id === session.model.id,
|
||||
)
|
||||
if (!selected)
|
||||
|
|
@ -94,4 +96,4 @@ const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [Catalog.node, ModelResolver.node] })
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [ModelResolver.node] })
|
||||
|
|
|
|||
|
|
@ -4,18 +4,16 @@ import { isDeepStrictEqual } from "node:util"
|
|||
import { LLMClient, AIError, LLMEvent, Message, SystemPart, type LLMRequest } from "@opencode-ai/ai"
|
||||
import type { StreamOptions } from "@opencode-ai/ai/route"
|
||||
import { Context, DateTime, Effect, Layer, Stream } from "effect"
|
||||
import { Agent } from "../agent.js"
|
||||
import { Catalog } from "../catalog.js"
|
||||
import type { Agent } from "../agent.js"
|
||||
import { Database } from "../database/database.js"
|
||||
import { Bus } from "../bus.js"
|
||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { isExactRootFallback } from "@opencode-ai/util/session-title-fallback"
|
||||
import { llmClient } from "../effect/app-node-platform.js"
|
||||
import { Model } from "../model.js"
|
||||
import { SessionContext } from "./context.js"
|
||||
import { SessionEvent } from "./event.js"
|
||||
import { SessionHistory } from "./history.js"
|
||||
import { SessionModelRequest } from "./model-request.js"
|
||||
import { SessionRunnerModel } from "./runner/model.js"
|
||||
import type { SessionRunnerModel } from "./runner/model.js"
|
||||
import { SessionSchema } from "./schema.js"
|
||||
import { SessionUsage } from "./usage.js"
|
||||
import { SessionStore } from "./store.js"
|
||||
|
|
@ -30,10 +28,7 @@ type Dependencies = {
|
|||
readonly llm: {
|
||||
readonly stream: (request: LLMRequest, options?: StreamOptions) => Stream.Stream<LLMEvent, AIError>
|
||||
}
|
||||
readonly agents: Agent.Interface
|
||||
readonly catalog: Catalog.Interface
|
||||
readonly models: SessionRunnerModel.Interface
|
||||
readonly modelRequests: SessionModelRequest.Interface
|
||||
readonly context: SessionContext.Interface
|
||||
readonly store: SessionStore.Interface
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +67,7 @@ const attempt = Effect.fn("SessionTitle.attempt")(function* (
|
|||
})
|
||||
: Effect.void,
|
||||
)
|
||||
const prepared = yield* dependencies.modelRequests.prepare({
|
||||
const prepared = yield* dependencies.context.prepare({
|
||||
scope: { session: input.session, agentID: input.agent.id, model: input.model },
|
||||
transcript: {
|
||||
system: input.agent.system ? [SystemPart.make(input.agent.system)] : [],
|
||||
|
|
@ -106,9 +101,6 @@ const attempt = Effect.fn("SessionTitle.attempt")(function* (
|
|||
.find((line) => line.length > 0)
|
||||
})
|
||||
|
||||
/** Variant IDs that minimize reasoning output, in preference order. */
|
||||
const MINIMAL_REASONING_VARIANTS = ["none", "minimal", "low"].map((id) => Model.VariantID.make(id))
|
||||
|
||||
const make = (dependencies: Dependencies) => {
|
||||
const generate = Effect.fn("SessionTitle.generate")(function* (
|
||||
db: Database.Interface["db"],
|
||||
|
|
@ -140,34 +132,12 @@ const make = (dependencies: Dependencies) => {
|
|||
Effect.orElseSucceed(() => firstUser.text),
|
||||
)
|
||||
: firstUser.text
|
||||
const agent = yield* dependencies.agents.get(Agent.ID.make("title"))
|
||||
if (!agent) return
|
||||
const primary = yield* dependencies.models.resolve(session).pipe(Effect.orElseSucceed(() => undefined))
|
||||
const info = yield* Effect.gen(function* () {
|
||||
if (agent.model) return yield* dependencies.catalog.model.get(agent.model.providerID, agent.model.id)
|
||||
if (!primary) return
|
||||
return yield* dependencies.catalog.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* dependencies.models
|
||||
.resolve({
|
||||
...session,
|
||||
model: Model.Ref.make({
|
||||
providerID: info.providerID,
|
||||
id: info.id,
|
||||
...(variant ? { variant } : {}),
|
||||
}),
|
||||
})
|
||||
.pipe(Effect.orElseSucceed(() => undefined)))
|
||||
const selected = preferred ?? primary
|
||||
if (!selected) return
|
||||
const selection = yield* dependencies.context.selectTitle(session)
|
||||
if (!selection) return
|
||||
const title =
|
||||
(yield* attempt(dependencies, { session, agent, text, model: selected })) ??
|
||||
(primary && !isDeepStrictEqual(selected.ref, primary.ref)
|
||||
? yield* attempt(dependencies, { session, agent, text, model: primary })
|
||||
(yield* attempt(dependencies, { session, agent: selection.agent, text, model: selection.selected })) ??
|
||||
(selection.primary && !isDeepStrictEqual(selection.selected.ref, selection.primary.ref)
|
||||
? yield* attempt(dependencies, { session, agent: selection.agent, text, model: selection.primary })
|
||||
: undefined)
|
||||
if (!title) return
|
||||
const expectedSequence = (yield* Bus.latestSequence(db, sessionID)) + 1
|
||||
|
|
@ -192,13 +162,10 @@ export const layer = Layer.effect(
|
|||
Effect.gen(function* () {
|
||||
const bus = yield* Bus.Service
|
||||
const llm = yield* LLMClient.Service
|
||||
const agents = yield* Agent.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
const models = yield* SessionRunnerModel.Service
|
||||
const modelRequests = yield* SessionModelRequest.Service
|
||||
const context = yield* SessionContext.Service
|
||||
const store = yield* SessionStore.Service
|
||||
const database = yield* Database.Service
|
||||
const title = make({ bus, llm, agents, catalog, models, modelRequests, store })
|
||||
const title = make({ bus, llm, context, store })
|
||||
return Service.of({
|
||||
generate: (sessionID) => title.generate(database.db, sessionID),
|
||||
})
|
||||
|
|
@ -208,14 +175,5 @@ export const layer = Layer.effect(
|
|||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer,
|
||||
deps: [
|
||||
Bus.node,
|
||||
llmClient,
|
||||
Agent.node,
|
||||
Catalog.node,
|
||||
SessionRunnerModel.node,
|
||||
SessionModelRequest.node,
|
||||
SessionStore.node,
|
||||
Database.node,
|
||||
],
|
||||
deps: [Bus.node, llmClient, SessionContext.node, SessionStore.node, Database.node],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { llmClient } from "@opencode-ai/core/effect/app-node-platform"
|
|||
import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||
import { SessionModelRequest } from "@opencode-ai/core/session/model-request"
|
||||
import { SessionRunnerModel } from "@opencode-ai/core/session/runner/model"
|
||||
import { Session } from "@opencode-ai/core/session"
|
||||
import { Agent } from "@opencode-ai/core/agent"
|
||||
|
|
@ -38,19 +39,13 @@ const config = Config.testLayer()
|
|||
const it = testEffect(
|
||||
Layer.merge(
|
||||
config,
|
||||
AppNodeBuilder.build(LayerNode.group([SessionCompaction.node, Config.node, Bus.node]), [
|
||||
AppNodeBuilder.build(LayerNode.group([SessionCompaction.node, SessionModelRequest.node, Config.node, Bus.node]), [
|
||||
[
|
||||
llmClient,
|
||||
Layer.mock(LLMClient.Service)({
|
||||
stream: () => Stream.make(LLMEvent.textDelta({ id: "summary", text: "summary" })),
|
||||
}),
|
||||
],
|
||||
[
|
||||
SessionRunnerModel.node,
|
||||
Layer.mock(SessionRunnerModel.Service)({
|
||||
resolve: () => Effect.succeed(resolved),
|
||||
}),
|
||||
],
|
||||
[Config.node, config],
|
||||
]),
|
||||
),
|
||||
|
|
@ -59,6 +54,7 @@ describe("ConfigCompactionPlugin.Plugin", () => {
|
|||
it.live("merges settings and reloads changed config", () =>
|
||||
Effect.gen(function* () {
|
||||
const compaction = yield* SessionCompaction.Service
|
||||
const modelRequests = yield* SessionModelRequest.Service
|
||||
const config = yield* Config.Test
|
||||
const bus = yield* Bus.Service
|
||||
yield* config.setEntries([
|
||||
|
|
@ -85,6 +81,8 @@ describe("ConfigCompactionPlugin.Plugin", () => {
|
|||
expect(
|
||||
yield* compaction.compactManual({
|
||||
session,
|
||||
resolveModel: () => Effect.succeed(resolved),
|
||||
prepare: modelRequests.prepare,
|
||||
messages: [
|
||||
{
|
||||
id: SessionMessage.ID.create(),
|
||||
|
|
|
|||
|
|
@ -797,8 +797,10 @@ describe("LocationServiceMap", () => {
|
|||
}),
|
||||
),
|
||||
)
|
||||
const failure = yield* SessionRunnerModel.Service.use((models) =>
|
||||
models.resolve(
|
||||
const failure = yield* Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const models = yield* SessionRunnerModel.Service
|
||||
return yield* models.resolve(
|
||||
Session.Info.make({
|
||||
id: Session.ID.make("ses_unavailable_model"),
|
||||
projectID: Project.ID.global,
|
||||
|
|
@ -812,8 +814,9 @@ describe("LocationServiceMap", () => {
|
|||
time: { created: DateTime.makeUnsafe(0), updated: DateTime.makeUnsafe(0) },
|
||||
location,
|
||||
}),
|
||||
),
|
||||
).pipe(Effect.provide(LocationServiceMap.Service.get(location)), Effect.flip)
|
||||
catalog.model.available,
|
||||
)
|
||||
}).pipe(Effect.provide(LocationServiceMap.Service.get(location)), Effect.flip)
|
||||
|
||||
expect(failure).toMatchObject({
|
||||
_tag: "SessionRunnerModel.ModelUnavailableError",
|
||||
|
|
@ -837,8 +840,10 @@ describe("LocationServiceMap", () => {
|
|||
["azure-cognitive-services", "azure"],
|
||||
["google-vertex-anthropic", "google-vertex"],
|
||||
] as const) {
|
||||
const failure = yield* SessionRunnerModel.Service.use((models) =>
|
||||
models.resolve(
|
||||
const failure = yield* Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const models = yield* SessionRunnerModel.Service
|
||||
return yield* models.resolve(
|
||||
Session.Info.make({
|
||||
id: Session.ID.make(`ses_removed_${providerID}`),
|
||||
projectID: Project.ID.global,
|
||||
|
|
@ -852,8 +857,9 @@ describe("LocationServiceMap", () => {
|
|||
time: { created: DateTime.makeUnsafe(0), updated: DateTime.makeUnsafe(0) },
|
||||
location,
|
||||
}),
|
||||
),
|
||||
).pipe(Effect.provide(LocationServiceMap.Service.get(location)), Effect.flip)
|
||||
catalog.model.available,
|
||||
)
|
||||
}).pipe(Effect.provide(LocationServiceMap.Service.get(location)), Effect.flip)
|
||||
|
||||
expect(failure).toMatchObject({
|
||||
_tag: "SessionRunnerModel.ModelUnavailableError",
|
||||
|
|
@ -905,6 +911,7 @@ describe("LocationServiceMap", () => {
|
|||
time: { created: DateTime.makeUnsafe(0), updated: DateTime.makeUnsafe(0) },
|
||||
location,
|
||||
}),
|
||||
catalog.model.available,
|
||||
)
|
||||
}).pipe(Effect.provide(LocationServiceMap.Service.get(location)))
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { EventTable } from "@opencode-ai/core/event/sql"
|
|||
import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||
import { SessionModelRequest } from "@opencode-ai/core/session/model-request"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
import { SessionRunnerModel } from "@opencode-ai/core/session/runner/model"
|
||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||
|
|
@ -73,9 +74,6 @@ const resolved = SessionRunnerModel.resolved(model, {
|
|||
cost,
|
||||
limit: { context: 200_000, output: 32_000 },
|
||||
})
|
||||
const models = Layer.mock(SessionRunnerModel.Service)({
|
||||
resolve: () => Effect.succeed(resolved),
|
||||
})
|
||||
const it = testEffect(
|
||||
AppNodeBuilder.build(
|
||||
LayerNode.group([
|
||||
|
|
@ -85,11 +83,11 @@ const it = testEffect(
|
|||
SessionStore.node,
|
||||
PluginHooks.node,
|
||||
SessionCompaction.node,
|
||||
SessionModelRequest.node,
|
||||
]),
|
||||
[
|
||||
[Bus.node, Bus.configured({ persist: true })],
|
||||
[llmClient, client],
|
||||
[SessionRunnerModel.node, models],
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
@ -242,6 +240,7 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
|
|||
time: { created: DateTime.makeUnsafe(0) },
|
||||
}
|
||||
const session = yield* insertSession(sessionID, { parent_id: parentID })
|
||||
const modelRequests = yield* SessionModelRequest.Service
|
||||
|
||||
const delta = yield* bus
|
||||
.subscribe(SessionEvent.Compaction.Delta)
|
||||
|
|
@ -250,6 +249,8 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
|
|||
expect(
|
||||
yield* compaction.compactManual({
|
||||
session,
|
||||
resolveModel: () => Effect.succeed(resolved),
|
||||
prepare: modelRequests.prepare,
|
||||
messages: [userMessage],
|
||||
inputID: SessionMessage.ID.make("msg_manual_compaction"),
|
||||
}),
|
||||
|
|
@ -303,9 +304,12 @@ it.effect("forked session compaction reuses the fork root prompt cache key", ()
|
|||
fork_session_id: rootID,
|
||||
fork_boundary: { type: "before", messageID: SessionMessage.ID.create() },
|
||||
})
|
||||
const modelRequests = yield* SessionModelRequest.Service
|
||||
expect(
|
||||
yield* compaction.compactManual({
|
||||
session,
|
||||
resolveModel: () => Effect.succeed(resolved),
|
||||
prepare: modelRequests.prepare,
|
||||
messages: [
|
||||
{
|
||||
id: SessionMessage.ID.create(),
|
||||
|
|
@ -336,9 +340,12 @@ it.effect("keeps session context hooks away from compaction requests", () =>
|
|||
}),
|
||||
)
|
||||
const session = yield* insertSession(Session.ID.make("ses_hook_compaction"))
|
||||
const modelRequests = yield* SessionModelRequest.Service
|
||||
expect(
|
||||
yield* compaction.compactManual({
|
||||
session,
|
||||
resolveModel: () => Effect.succeed(resolved),
|
||||
prepare: modelRequests.prepare,
|
||||
messages: [
|
||||
{
|
||||
id: SessionMessage.ID.create(),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import { SessionTable } from "@opencode-ai/core/session/sql"
|
|||
import { SessionStore } from "@opencode-ai/core/session/store"
|
||||
import { SessionTitle } from "@opencode-ai/core/session/title"
|
||||
import { PluginHooks } from "@opencode-ai/core/plugin/hooks"
|
||||
import { PluginSupervisor } from "@opencode-ai/core/plugin/supervisor"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { Session } from "@opencode-ai/core/session"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
|
|
@ -127,6 +129,8 @@ const it = testEffect(
|
|||
[llmClient, client],
|
||||
[Catalog.node, catalog],
|
||||
[SessionRunnerModel.node, models],
|
||||
[Location.node, Location.boundNode({ directory: AbsolutePath.make("/project") })],
|
||||
[PluginSupervisor.node, Layer.mock(PluginSupervisor.Service, { flush: Effect.void })],
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue