feat(acp-next): add directory snapshot service (#29241)

This commit is contained in:
Shoubhit Dash 2026-05-25 21:55:52 +05:30 committed by GitHub
parent d18eab5b85
commit b2d76434ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 378 additions and 0 deletions

View file

@ -0,0 +1,193 @@
import { Agent } from "@/agent/agent"
import { Command } from "@/command"
import { InstanceRef } from "@/effect/instance-ref"
import { InstanceStore } from "@/project/instance-store"
import { ModelID, ProviderID } from "@/provider/schema"
import { Provider } from "@/provider/provider"
import { Context, Effect, Layer, SynchronizedRef } from "effect"
export type ModelOption = {
readonly providerID: ProviderID
readonly providerName: string
readonly modelID: ModelID
readonly modelName: string
}
export type ModeOption = {
readonly id: string
readonly name: string
readonly description?: string
}
export type ModelVariants = NonNullable<Provider.Model["variants"]>
export type DefaultModel = {
readonly providerID: ProviderID
readonly modelID: ModelID
}
export type Snapshot = {
readonly directory: string
readonly providers: Record<ProviderID, Provider.Info>
readonly modelOptions: readonly ModelOption[]
readonly variantsByModel: Readonly<Record<string, ModelVariants>>
readonly availableModes: readonly ModeOption[]
readonly defaultModeID: string
readonly availableCommands: readonly Command.Info[]
readonly defaultModel?: DefaultModel
}
export interface LoaderInterface {
readonly load: (directory: string) => Effect.Effect<Snapshot>
}
export interface Interface {
readonly get: (directory: string) => Effect.Effect<Snapshot>
readonly refresh: (directory: string) => Effect.Effect<Snapshot>
readonly variants: (snapshot: Snapshot, model: DefaultModel) => ModelVariants | undefined
}
export class Loader extends Context.Service<Loader, LoaderInterface>()("@opencode/ACPNextDirectoryLoader") {}
export class Service extends Context.Service<Service, Interface>()("@opencode/ACPNextDirectory") {}
export const modelKey = (model: DefaultModel) => `${model.providerID}/${model.modelID}`
export const variants = (snapshot: Snapshot, model: DefaultModel) => snapshot.variantsByModel[modelKey(model)]
export const build = (input: {
readonly directory: string
readonly providers: Record<ProviderID, Provider.Info>
readonly modes: readonly ModeOption[]
readonly defaultModeID: string
readonly commands: readonly Command.Info[]
readonly defaultModel?: DefaultModel
}): Snapshot => {
const modelOptions = Provider.sort(
Object.values(input.providers).flatMap((provider) =>
Object.values(provider.models).map((model) => ({
id: model.id,
providerID: provider.id,
providerName: provider.name,
modelID: model.id,
modelName: model.name,
})),
),
).map((model) => ({
providerID: model.providerID,
providerName: model.providerName,
modelID: model.modelID,
modelName: model.modelName,
}))
return {
directory: input.directory,
providers: input.providers,
modelOptions,
variantsByModel: Object.fromEntries(
Object.values(input.providers).flatMap((provider) =>
Object.values(provider.models).flatMap((model) =>
model.variants ? [[modelKey({ providerID: provider.id, modelID: model.id }), model.variants]] : [],
),
),
),
availableModes: input.modes,
defaultModeID: input.modes.some((mode) => mode.id === input.defaultModeID)
? input.defaultModeID
: (input.modes[0]?.id ?? input.defaultModeID),
availableCommands: input.commands,
...(input.defaultModel ? { defaultModel: input.defaultModel } : {}),
}
}
export const loaderLayer = Layer.effect(
Loader,
Effect.gen(function* () {
const store = yield* InstanceStore.Service
const provider = yield* Provider.Service
const agent = yield* Agent.Service
const command = yield* Command.Service
return Loader.of({
load: Effect.fn("ACPNextDirectoryLoader.load")(function* (directory) {
const ctx = yield* store.load({ directory })
return yield* Effect.gen(function* () {
const providers = yield* provider.list()
const [agents, defaultAgent, commands, defaultModel] = yield* Effect.all(
[
agent.list(),
agent.defaultInfo(),
command.list(),
provider.defaultModel().pipe(Effect.option),
],
{ concurrency: "unbounded" },
)
return build({
directory,
providers,
modes: agents
.filter((item) => item.mode !== "subagent" && item.hidden !== true)
.map((item) => ({
id: item.name,
name: item.name,
...(item.description ? { description: item.description } : {}),
})),
defaultModeID: defaultAgent.name,
commands: commands.toSorted((a, b) => a.name.localeCompare(b.name)),
...(defaultModel._tag === "Some" ? { defaultModel: defaultModel.value } : {}),
})
}).pipe(Effect.provideService(InstanceRef, ctx))
}),
})
}),
)
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const loader = yield* Loader
const snapshots = yield* SynchronizedRef.make(new Map<string, Effect.Effect<Snapshot>>())
const cached = Effect.fnUntraced(function* (directory: string) {
return yield* SynchronizedRef.modifyEffect(
snapshots,
Effect.fnUntraced(function* (items) {
const current = items.get(directory)
if (current) return [current, items] as const
const next = yield* Effect.cached(loader.load(directory))
return [next, new Map(items).set(directory, next)] as const
}),
)
})
const get = Effect.fn("ACPNextDirectory.get")(function* (directory: string) {
return yield* (yield* cached(directory))
})
const refresh = Effect.fn("ACPNextDirectory.refresh")(function* (directory: string) {
return yield* SynchronizedRef.modifyEffect(
snapshots,
Effect.fnUntraced(function* (items) {
const next = yield* Effect.cached(loader.load(directory))
return [next, new Map(items).set(directory, next)] as const
}),
).pipe(Effect.flatten)
})
return Service.of({
get,
refresh,
variants,
})
}),
)
export const defaultLayer = layer.pipe(
Layer.provide(loaderLayer),
Layer.provide(Provider.defaultLayer),
Layer.provide(Agent.defaultLayer),
Layer.provide(Command.defaultLayer),
Layer.provide(InstanceStore.defaultLayer),
)
export * as Directory from "./directory"

View file

@ -0,0 +1,185 @@
import { describe, expect } from "bun:test"
import { Directory } from "@/acp-next/directory"
import { Command } from "@/command"
import { ModelID, ProviderID } from "@/provider/schema"
import { Provider } from "@/provider/provider"
import { Effect, Layer } from "effect"
import { it } from "../lib/effect"
const command = (name: string): Command.Info => ({
name,
source: "command",
template: `run ${name}`,
hints: [],
})
const model = (providerID: ProviderID, id: string, variants?: Directory.ModelVariants): Provider.Model => ({
id: ModelID.make(id),
providerID,
api: {
id,
url: "https://example.com",
npm: "@ai-sdk/openai-compatible",
},
name: id,
family: "test",
capabilities: {
temperature: true,
reasoning: Boolean(variants),
attachment: false,
toolcall: true,
input: { text: true, audio: false, image: false, video: false, pdf: false },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
cost: {
input: 0,
output: 0,
cache: { read: 0, write: 0 },
},
limit: {
context: 128000,
output: 4096,
},
status: "active",
options: {},
headers: {},
release_date: "2026-01-01",
...(variants ? { variants } : {}),
})
const snapshot = (directory: string) => {
const providerID = ProviderID.make(`provider-${directory}`)
const modelID = ModelID.make(`model-${directory}`)
const providers = {
[providerID]: {
id: providerID,
name: `Provider ${directory}`,
source: "config",
env: [],
options: {},
models: {
[modelID]: model(providerID, modelID, {
low: { reasoningEffort: "low" },
high: { reasoningEffort: "high" },
}),
[ModelID.make(`plain-${directory}`)]: model(providerID, `plain-${directory}`),
},
},
} satisfies Record<ProviderID, Provider.Info>
return Directory.build({
directory,
providers,
modes: [
{ id: "build", name: `build-${directory}` },
{ id: "plan", name: `plan-${directory}`, description: "plan first" },
],
defaultModeID: "build",
commands: [command(`init-${directory}`), command(`review-${directory}`)],
defaultModel: { providerID, modelID },
})
}
const fakeLayer = (calls: string[]) =>
Directory.layer.pipe(
Layer.provide(
Layer.succeed(
Directory.Loader,
Directory.Loader.of({
load: (directory) =>
Effect.sync(() => {
calls.push(directory)
return snapshot(directory)
}),
}),
),
),
)
describe("ACP next directory snapshot", () => {
it.effect("two concurrent callers share one load", () => {
const calls: string[] = []
return Effect.gen(function* () {
const directory = yield* Directory.Service
const [first, second] = yield* Effect.all([directory.get("alpha"), directory.get("alpha")], {
concurrency: "unbounded",
})
expect(calls).toEqual(["alpha"])
expect(first).toBe(second)
}).pipe(Effect.provide(fakeLayer(calls)))
})
it.effect("warm calls use cached data", () => {
const calls: string[] = []
return Effect.gen(function* () {
const directory = yield* Directory.Service
const first = yield* directory.get("alpha")
const second = yield* directory.get("alpha")
expect(calls).toEqual(["alpha"])
expect(first).toBe(second)
}).pipe(Effect.provide(fakeLayer(calls)))
})
it.effect("different directories get different snapshots", () => {
const calls: string[] = []
return Effect.gen(function* () {
const directory = yield* Directory.Service
const [alpha, beta] = yield* Effect.all([directory.get("alpha"), directory.get("beta")], {
concurrency: "unbounded",
})
expect(calls.toSorted()).toEqual(["alpha", "beta"])
expect(alpha.directory).toBe("alpha")
expect(beta.directory).toBe("beta")
expect(alpha.defaultModel?.providerID).not.toBe(beta.defaultModel?.providerID)
}).pipe(Effect.provide(fakeLayer(calls)))
})
it.effect("model variant lookup works", () =>
Effect.gen(function* () {
const directory = yield* Directory.Service
const alpha = yield* directory.get("alpha")
const model = alpha.defaultModel!
expect(directory.variants(alpha, model)).toEqual({
low: { reasoningEffort: "low" },
high: { reasoningEffort: "high" },
})
expect(directory.variants(alpha, { ...model, modelID: ModelID.make("missing") })).toBeUndefined()
}).pipe(Effect.provide(fakeLayer([]))),
)
it.effect("commands and modes are included", () =>
Effect.gen(function* () {
const directory = yield* Directory.Service
const alpha = yield* directory.get("alpha")
expect(alpha.availableCommands.map((item) => item.name)).toEqual(["init-alpha", "review-alpha"])
expect(alpha.availableModes).toEqual([
{ id: "build", name: "build-alpha" },
{ id: "plan", name: "plan-alpha", description: "plan first" },
])
expect(alpha.defaultModeID).toBe("build")
}).pipe(Effect.provide(fakeLayer([]))),
)
it.effect("falls back when the default mode is not available", () =>
Effect.sync(() => {
expect(
Directory.build({
directory: "alpha",
providers: {},
modes: [
{ id: "build", name: "Build" },
{ id: "plan", name: "Plan" },
],
defaultModeID: "hidden",
commands: [],
}).defaultModeID,
).toBe("build")
}),
)
})