feat(core): add location-scoped config loading (#29625)

This commit is contained in:
Dax 2026-05-30 00:06:08 -04:00 committed by GitHub
commit 9583e08be4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
89 changed files with 3509 additions and 527 deletions

View file

@ -1,147 +1,104 @@
export * as AgentV2 from "./agent"
import { Context, Effect, HashMap, Layer, Option, Order, pipe, Schema, Array } from "effect"
import { produce, type Draft } from "immer"
import { Array, Context, Effect, Layer, Schema } from "effect"
import { castDraft, enableMapSet, type Draft } from "immer"
import { ModelV2 } from "./model"
import { PermissionV2 } from "./permission"
import { PluginV2 } from "./plugin"
import { ProviderV2 } from "./provider"
import { PositiveInt } from "./schema"
import { State } from "./state"
export const ID = Schema.String.pipe(Schema.brand("AgentV2.ID"))
export type ID = typeof ID.Type
export const Mode = Schema.Literals(["subagent", "primary", "all"]).annotate({ identifier: "AgentV2.Mode" })
export type Mode = typeof Mode.Type
export const Color = Schema.Union([
Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)),
Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
])
export const Info = Schema.Struct({
name: ID,
description: Schema.optional(Schema.String),
mode: Mode,
hidden: Schema.Boolean.pipe(Schema.optional),
color: Schema.String.pipe(Schema.optional),
permission: PermissionV2.Ruleset,
export class Info extends Schema.Class<Info>("AgentV2.Info")({
id: ID,
model: ModelV2.Ref.pipe(Schema.optional),
options: ProviderV2.Options,
system: Schema.String.pipe(Schema.optional),
options: ProviderV2.Options.pipe(Schema.optional),
steps: Schema.Int.pipe(Schema.optional),
}).annotate({ identifier: "AgentV2.Info" })
export type Info = typeof Info.Type
description: Schema.String.pipe(Schema.optional),
mode: Schema.Literals(["subagent", "primary", "all"]),
hidden: Schema.Boolean,
color: Color.pipe(Schema.optional),
steps: PositiveInt.pipe(Schema.optional),
permissions: PermissionV2.Ruleset,
}) {
static empty(id: ID) {
return new Info({
id,
options: {
headers: {},
body: {},
aisdk: {
provider: {},
request: {},
},
},
mode: "all",
hidden: false,
permissions: [],
})
}
}
export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("AgentV2.NotFound", {
agent: ID,
}) {}
type Data = {
agents: Map<ID, Info>
}
export class InvalidDefaultError extends Schema.TaggedErrorClass<InvalidDefaultError>()("AgentV2.InvalidDefault", {
agent: ID,
reason: Schema.Literals(["missing", "subagent", "hidden"]),
}) {}
export class NoDefaultError extends Schema.TaggedErrorClass<NoDefaultError>()("AgentV2.NoDefault", {}) {}
export type Editor = {
list: () => readonly Info[]
get: (id: ID) => Info | undefined
update: (id: ID, fn: (agent: Draft<Info>) => void) => void
remove: (id: ID) => void
}
export interface Interface {
readonly get: (agent: ID) => Effect.Effect<Info, NotFoundError>
readonly list: () => Effect.Effect<Info[]>
readonly update: (agent: ID, fn: (agent: Draft<Info>) => void) => Effect.Effect<void>
readonly remove: (agent: ID) => Effect.Effect<void>
readonly defaultInfo: () => Effect.Effect<Info, InvalidDefaultError | NoDefaultError>
readonly defaultAgent: () => Effect.Effect<ID, InvalidDefaultError | NoDefaultError>
readonly setDefault: (agent: ID) => Effect.Effect<void, NotFoundError>
readonly transform: State.Interface<Data, Editor>["transform"]
readonly update: State.Interface<Data, Editor>["update"]
readonly get: (id: ID) => Effect.Effect<Info | undefined>
readonly all: () => Effect.Effect<Info[]>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Agent") {}
enableMapSet()
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
let agents = HashMap.empty<ID, Info>()
let defaultAgent: ID | undefined
const result: Interface = {
get: Effect.fn("AgentV2.get")(function* (agent) {
const match = HashMap.get(agents, agent)
if (!match.valueOrUndefined) return yield* new NotFoundError({ agent })
return match.value
const state = State.create<Data, Editor>({
initial: () => ({ agents: new Map() }),
editor: (draft) => ({
list: () => Array.fromIterable(draft.agents.values()) as Info[],
get: (id) => draft.agents.get(id),
update: (id, fn) => {
const current = draft.agents.get(id) ?? castDraft(Info.empty(id))
if (!draft.agents.has(id)) draft.agents.set(id, current)
fn(current)
current.id = id
},
remove: (id) => {
draft.agents.delete(id)
},
}),
})
list: Effect.fn("AgentV2.list")(function* () {
return pipe(
HashMap.toValues(agents),
Array.sortWith((agent) => agent.name, Order.String),
)
return Service.of({
transform: state.transform,
update: state.update,
get: Effect.fn("AgentV2.get")(function* (id) {
return state.get().agents.get(id)
}),
update: Effect.fnUntraced(function* (agent, fn) {
const next = produce(
HashMap.get(agents, agent).pipe(
Option.getOrElse(
() =>
({
name: agent,
mode: "all",
permission: [],
options: {
headers: {},
body: {},
aisdk: {
provider: {},
request: {},
},
},
}) satisfies Info,
),
),
fn,
)
const updated = yield* plugin.trigger("agent.update", {}, { agent: next, cancel: false })
if (updated.cancel) return
agents = HashMap.set(agents, agent, { ...updated.agent, name: agent })
all: Effect.fn("AgentV2.all")(function* () {
return Array.fromIterable(state.get().agents.values())
}),
remove: Effect.fn("AgentV2.remove")(function* (agent) {
const existing = Option.getOrUndefined(HashMap.get(agents, agent))
if (!existing) return
if ((yield* plugin.trigger("agent.remove", { agent: existing }, { cancel: false })).cancel) return
agents = HashMap.remove(agents, agent)
if (defaultAgent === agent) defaultAgent = undefined
}),
defaultInfo: Effect.fn("AgentV2.defaultInfo")(function* () {
const updated = yield* plugin.trigger("agent.default", {}, { agent: defaultAgent })
const selected = updated.agent
if (selected) {
const agent = yield* result
.get(selected)
.pipe(
Effect.catchTag("AgentV2.NotFound", () =>
Effect.fail(new InvalidDefaultError({ agent: selected, reason: "missing" })),
),
)
if (agent.mode === "subagent") return yield* new InvalidDefaultError({ agent: selected, reason: "subagent" })
if (agent.hidden === true) return yield* new InvalidDefaultError({ agent: selected, reason: "hidden" })
return agent
}
const visible = pipe(
yield* result.list(),
Array.findFirst((agent) => agent.mode !== "subagent" && agent.hidden !== true),
)
if (Option.isSome(visible)) return visible.value
return yield* new NoDefaultError()
}),
defaultAgent: Effect.fn("AgentV2.defaultAgent")(function* () {
return (yield* result.defaultInfo()).name
}),
setDefault: Effect.fn("AgentV2.setDefault")(function* (agent) {
yield* result.get(agent)
defaultAgent = agent
}),
}
return Service.of(result)
})
}),
)
export const defaultLayer = layer.pipe(Layer.provide(PluginV2.defaultLayer))
export const defaultLayer = layer