refactor(server): canonicalize service API (#31049)
This commit is contained in:
parent
53ff1b57c9
commit
fe0c4f8c74
388 changed files with 7075 additions and 4064 deletions
|
|
@ -63,7 +63,7 @@ export type Editor = {
|
|||
|
||||
export interface Interface {
|
||||
readonly transform: State.Interface<Data, Editor>["transform"]
|
||||
readonly update: (update: State.Transform<Editor>) => Effect.Effect<void, never, Scope.Scope>
|
||||
readonly update: State.Interface<Data, Editor>["update"]
|
||||
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
||||
readonly default: () => Effect.Effect<Info | undefined>
|
||||
readonly resolve: (id?: ID | string) => Effect.Effect<Info | undefined>
|
||||
|
|
@ -113,10 +113,7 @@ export const layer = Layer.effect(
|
|||
|
||||
return Service.of({
|
||||
transform: state.transform,
|
||||
update: Effect.fn("AgentV2.update")(function* (update) {
|
||||
const transform = yield* state.transform()
|
||||
yield* transform(update)
|
||||
}),
|
||||
update: state.update,
|
||||
get: Effect.fn("AgentV2.get")(function* (id) {
|
||||
return state.get().agents.get(id)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ export const layer = Layer.effect(
|
|||
event.location?.directory === location.directory && event.location.workspaceID === location.workspaceID,
|
||||
),
|
||||
Stream.runForEach((event) =>
|
||||
state.update((catalog) => plugin.triggerFor(event.data.id, "catalog.transform", catalog, {}), "plugin.added"),
|
||||
state.mutate((catalog) => plugin.triggerFor(event.data.id, "catalog.transform", catalog, {}), "plugin.added"),
|
||||
),
|
||||
Effect.forkIn(scope, { startImmediately: true }),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export type Editor = {
|
|||
|
||||
export interface Interface {
|
||||
readonly transform: State.Interface<Data, Editor>["transform"]
|
||||
readonly update: State.Interface<Data, Editor>["update"]
|
||||
readonly get: (name: string) => Effect.Effect<Info | undefined>
|
||||
readonly list: () => Effect.Effect<Info[]>
|
||||
}
|
||||
|
|
@ -54,6 +55,7 @@ export const layer = Layer.effect(
|
|||
})
|
||||
|
||||
return Service.of({
|
||||
update: state.update,
|
||||
transform: state.transform,
|
||||
get: Effect.fn("CommandV2.get")(function* (name) {
|
||||
return state.get().commands.get(name)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Git } from "../git"
|
|||
import { Location } from "../location"
|
||||
import { ProjectV2 } from "../project"
|
||||
import { SessionV2 } from "../session"
|
||||
import { SessionExecution } from "../session/execution"
|
||||
import { SessionEvent } from "../session/event"
|
||||
import { SessionSchema } from "../session/schema"
|
||||
import { AbsolutePath, RelativePath } from "../schema"
|
||||
|
|
@ -124,5 +125,6 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
Layer.provide(ProjectV2.defaultLayer),
|
||||
Layer.provide(SessionExecution.noopLayer),
|
||||
Layer.provide(SessionV2.defaultLayer),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export const layer = Layer.effectDiscard(
|
|||
return files.filter((file): file is File => file !== undefined)
|
||||
})
|
||||
|
||||
yield* registry.contribute({
|
||||
yield* registry.register({
|
||||
key,
|
||||
load: observe().pipe(
|
||||
Effect.map((files) =>
|
||||
|
|
|
|||
|
|
@ -425,20 +425,12 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
const DefaultDatabase = Database.defaultLayer
|
||||
const DefaultEvents = EventV2.layer.pipe(Layer.provide(DefaultDatabase))
|
||||
const DefaultProjector = SessionProjector.layer.pipe(Layer.provide(DefaultEvents), Layer.provide(DefaultDatabase))
|
||||
const DefaultStore = SessionStore.layer.pipe(Layer.provide(DefaultDatabase))
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(
|
||||
Layer.mergeAll(
|
||||
DefaultDatabase,
|
||||
DefaultEvents,
|
||||
DefaultProjector,
|
||||
DefaultStore,
|
||||
SessionExecution.noopLayer,
|
||||
ProjectV2.defaultLayer,
|
||||
),
|
||||
),
|
||||
Layer.provide(SessionExecution.noopLayer),
|
||||
Layer.provide(SessionStore.defaultLayer),
|
||||
Layer.provide(SessionProjector.defaultLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(ProjectV2.defaultLayer),
|
||||
Layer.orDie,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -31,3 +31,5 @@ export const layer = Layer.effect(
|
|||
})
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(SessionStore.defaultLayer))
|
||||
|
|
|
|||
|
|
@ -58,3 +58,5 @@ export const layer = Layer.effect(
|
|||
})
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Effect, Scope, Semaphore } from "effect"
|
|||
import type { Draft, Objectish } from "immer"
|
||||
|
||||
/**
|
||||
* A replayable contribution applied to an editor during rebuild.
|
||||
* A replayable transform applied to an editor during rebuild.
|
||||
*
|
||||
* Transforms are intentionally synchronous and mutation-shaped: domain editors
|
||||
* hide the draft representation while preserving concise plugin/config code.
|
||||
|
|
@ -39,15 +39,17 @@ export interface Interface<State extends Objectish, Editor> {
|
|||
* registration order. Closing the owning Scope removes the slot and rebuilds.
|
||||
*/
|
||||
readonly transform: () => Effect.Effect<(transform: Transform<Editor>) => Effect.Effect<void>, never, Scope.Scope>
|
||||
/** Registers and applies a replayable transform in the current Scope. */
|
||||
readonly update: (update: Transform<Editor>) => Effect.Effect<void, never, Scope.Scope>
|
||||
/**
|
||||
* Mutates the current materialized state directly.
|
||||
* Mutates the current materialized state directly, once.
|
||||
*
|
||||
* This is not replayable contribution state: a later rebuild starts again
|
||||
* This is not replayable transform state: a later rebuild starts again
|
||||
* from `initial()` plus active transforms, so direct edits must be reserved
|
||||
* for current-state adjustments that are intentionally outside the transform
|
||||
* fold.
|
||||
*/
|
||||
readonly update: (update: (editor: Editor) => Effect.Effect<void>, reason?: string) => Effect.Effect<void>
|
||||
readonly mutate: (update: (editor: Editor) => Effect.Effect<void>, reason?: string) => Effect.Effect<void>
|
||||
}
|
||||
|
||||
export function create<State extends Objectish, Editor>(options: Options<State, Editor>): Interface<State, Editor> {
|
||||
|
|
@ -69,7 +71,7 @@ export function create<State extends Objectish, Editor>(options: Options<State,
|
|||
yield* commit(next)
|
||||
})
|
||||
|
||||
return {
|
||||
const result: Interface<State, Editor> = {
|
||||
get: () => state,
|
||||
transform: Effect.fn("State.transform")(function* () {
|
||||
const scope = yield* Scope.Scope
|
||||
|
|
@ -96,10 +98,15 @@ export function create<State extends Objectish, Editor>(options: Options<State,
|
|||
}),
|
||||
)
|
||||
}),
|
||||
update: Effect.fn("State.update")(function* (update, reason) {
|
||||
update: Effect.fn("State.update")(function* (update) {
|
||||
const transform = yield* result.transform()
|
||||
yield* transform(update)
|
||||
}),
|
||||
mutate: Effect.fn("State.mutate")(function* (update, reason) {
|
||||
const api = options.editor(state as Draft<State>)
|
||||
yield* update(api)
|
||||
if (options.finalize) yield* options.finalize(api, reason)
|
||||
}, semaphore.withPermit),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const builtIns = Layer.effectDiscard(
|
|||
}),
|
||||
])
|
||||
|
||||
yield* registry.contribute({ key: SystemContext.Key.make("core/builtins"), load: Effect.succeed(context) })
|
||||
yield* registry.register({ key: SystemContext.Key.make("core/builtins"), load: Effect.succeed(context) })
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ export * as SystemContextRegistry from "./registry"
|
|||
import { Context, Effect, Layer, Ref, Scope } from "effect"
|
||||
import { SystemContext } from "./index"
|
||||
|
||||
export interface Contribution {
|
||||
export interface Entry {
|
||||
readonly key: SystemContext.Key
|
||||
readonly load: Effect.Effect<SystemContext.SystemContext>
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
readonly contribute: (contribution: Contribution) => Effect.Effect<void, never, Scope.Scope>
|
||||
readonly register: (entry: Entry) => Effect.Effect<void, never, Scope.Scope>
|
||||
readonly load: () => Effect.Effect<SystemContext.SystemContext>
|
||||
}
|
||||
|
||||
|
|
@ -18,27 +18,27 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
|
|||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const contributions = yield* Ref.make<ReadonlyArray<Contribution>>([])
|
||||
const entries = yield* Ref.make<ReadonlyArray<Entry>>([])
|
||||
|
||||
return Service.of({
|
||||
contribute: Effect.fn("SystemContextRegistry.contribute")(function* (contribution) {
|
||||
register: Effect.fn("SystemContextRegistry.register")(function* (entry) {
|
||||
yield* Effect.acquireRelease(
|
||||
Ref.modify(contributions, (current) => {
|
||||
if (current.some((item) => item.key === contribution.key)) return [false, current]
|
||||
return [true, [...current, contribution]]
|
||||
Ref.modify(entries, (current) => {
|
||||
if (current.some((item) => item.key === entry.key)) return [false, current]
|
||||
return [true, [...current, entry]]
|
||||
}).pipe(
|
||||
Effect.flatMap((added) =>
|
||||
added ? Effect.void : Effect.die(`Duplicate system context contribution key: ${contribution.key}`),
|
||||
added ? Effect.void : Effect.die(`Duplicate system context entry key: ${entry.key}`),
|
||||
),
|
||||
Effect.as(contribution),
|
||||
Effect.as(entry),
|
||||
),
|
||||
(entry) => Ref.update(contributions, (current) => current.filter((item) => item !== entry)),
|
||||
(entry) => Ref.update(entries, (current) => current.filter((item) => item !== entry)),
|
||||
)
|
||||
}),
|
||||
load: Effect.fn("SystemContextRegistry.load")(function* () {
|
||||
const current = (yield* Ref.get(contributions)).toSorted((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
|
||||
const current = (yield* Ref.get(entries)).toSorted((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
|
||||
return SystemContext.combine(
|
||||
yield* Effect.forEach(current, (contribution) => contribution.load, { concurrency: "unbounded" }),
|
||||
yield* Effect.forEach(current, (entry) => entry.load, { concurrency: "unbounded" }),
|
||||
)
|
||||
}),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { WebSearchTool } from "./websearch"
|
|||
import { WriteTool } from "./write"
|
||||
|
||||
/**
|
||||
* Composes only the shipped Location-scoped built-in tool contributions.
|
||||
* Composes only the shipped Location-scoped built-in tool transforms.
|
||||
* Each tool retains its implementation and focused tests independently. Dynamic
|
||||
* MCP and plugin tools later use separate scoped canonical registrations, while
|
||||
* provider/model filtering belongs to a future materialization phase rather
|
||||
|
|
@ -25,7 +25,7 @@ import { WriteTool } from "./write"
|
|||
* TODO: Port the remaining launch-follow-up leaves deliberately: edit fuzzy
|
||||
* parity, task, LSP,
|
||||
* repo_clone, repo_overview, plan_exit, and Rune/code mode. Keep MCP and plugin
|
||||
* contributions separate from this static built-in list.
|
||||
* transforms separate from this static built-in list.
|
||||
*/
|
||||
export const locationLayer = Layer.mergeAll(
|
||||
ApplyPatchTool.layer,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue