mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
refactor: rename State drafts to editors (#46876)
This commit is contained in:
+10
-10
@@ -34,7 +34,7 @@ type Data = {
|
||||
default?: ID
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
list: () => readonly Info[]
|
||||
get: (id: ID) => Info | undefined
|
||||
default: (id: ID | undefined) => void
|
||||
@@ -42,7 +42,7 @@ export type Draft = {
|
||||
remove: (id: ID) => void
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
||||
readonly resolve: (id?: ID | string) => Effect.Effect<Info | undefined>
|
||||
readonly select: (id?: ID | string) => Effect.Effect<Selection>
|
||||
@@ -62,29 +62,29 @@ const layer = Layer.effect(
|
||||
{ action: "external_directory", resource: path.join(global.tmp, "*"), effect: "allow" },
|
||||
{ action: "external_directory", resource: path.join(global.config, "*"), effect: "allow" },
|
||||
]
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "agent",
|
||||
initial: () => ({ agents: new Map() }),
|
||||
draft: (draft) => ({
|
||||
list: () => Array.fromIterable(draft.agents.values()) as Info[],
|
||||
get: (id) => draft.agents.get(id),
|
||||
editor: (editor) => ({
|
||||
list: () => Array.fromIterable(editor.agents.values()) as Info[],
|
||||
get: (id) => editor.agents.get(id),
|
||||
default: (id) => {
|
||||
draft.default = id
|
||||
editor.default = id
|
||||
},
|
||||
update: (id, fn) => {
|
||||
const defaults = Info.default(id)
|
||||
const current =
|
||||
draft.agents.get(id) ??
|
||||
editor.agents.get(id) ??
|
||||
({
|
||||
...defaults,
|
||||
permissions: [...defaults.permissions, ...permissions],
|
||||
} as Types.DeepMutable<Info>)
|
||||
if (!draft.agents.has(id)) draft.agents.set(id, current)
|
||||
if (!editor.agents.has(id)) editor.agents.set(id, current)
|
||||
fn(current)
|
||||
current.id = id
|
||||
},
|
||||
remove: (id) => {
|
||||
draft.agents.delete(id)
|
||||
editor.agents.delete(id)
|
||||
},
|
||||
}),
|
||||
notify: () => bus.publish(Agent.Event.Updated, {}).pipe(Effect.asVoid),
|
||||
|
||||
@@ -23,7 +23,7 @@ type Data = {
|
||||
defaultModel?: DefaultModel
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
provider: {
|
||||
list: () => readonly ProviderRecord[]
|
||||
get: (providerID: Provider.ID) => ProviderRecord | undefined
|
||||
@@ -41,7 +41,7 @@ export type Draft = {
|
||||
}
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly provider: {
|
||||
readonly get: (providerID: Provider.ID) => Effect.Effect<Provider.Info | undefined>
|
||||
readonly all: () => Effect.Effect<Provider.Info[]>
|
||||
@@ -82,39 +82,39 @@ const layer = Layer.effect(
|
||||
} satisfies Model.Info
|
||||
}
|
||||
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "catalog",
|
||||
initial: () => ({ providers: new Map() }),
|
||||
draft: (draft) => {
|
||||
const result: Draft = {
|
||||
editor: (editor) => {
|
||||
const result: Editor = {
|
||||
provider: {
|
||||
list: () => Array.fromIterable(draft.providers.values()) as ProviderRecord[],
|
||||
get: (providerID) => draft.providers.get(providerID),
|
||||
list: () => Array.fromIterable(editor.providers.values()) as ProviderRecord[],
|
||||
get: (providerID) => editor.providers.get(providerID),
|
||||
update: (providerID, fn) => {
|
||||
let current = draft.providers.get(providerID)
|
||||
let current = editor.providers.get(providerID)
|
||||
if (!current) {
|
||||
current = {
|
||||
provider: Provider.Info.empty(providerID) as Provider.MutableInfo,
|
||||
models: new Map<Model.ID, Model.MutableInfo>(),
|
||||
}
|
||||
draft.providers.set(providerID, current)
|
||||
editor.providers.set(providerID, current)
|
||||
}
|
||||
fn(current.provider)
|
||||
},
|
||||
remove: (providerID) => {
|
||||
draft.providers.delete(providerID)
|
||||
editor.providers.delete(providerID)
|
||||
},
|
||||
},
|
||||
model: {
|
||||
get: (providerID, modelID) => draft.providers.get(providerID)?.models.get(modelID),
|
||||
get: (providerID, modelID) => editor.providers.get(providerID)?.models.get(modelID),
|
||||
update: (providerID, modelID, fn) => {
|
||||
let record = draft.providers.get(providerID)
|
||||
let record = editor.providers.get(providerID)
|
||||
if (!record) {
|
||||
record = {
|
||||
provider: Provider.Info.empty(providerID) as Provider.MutableInfo,
|
||||
models: new Map<Model.ID, Model.MutableInfo>(),
|
||||
}
|
||||
draft.providers.set(providerID, record)
|
||||
editor.providers.set(providerID, record)
|
||||
}
|
||||
const model = record.models.get(modelID) ?? (Model.Info.default(providerID, modelID) as Model.MutableInfo)
|
||||
if (!record.models.has(modelID)) record.models.set(modelID, model)
|
||||
@@ -123,12 +123,12 @@ const layer = Layer.effect(
|
||||
model.providerID = providerID
|
||||
},
|
||||
remove: (providerID, modelID) => {
|
||||
draft.providers.get(providerID)?.models.delete(modelID)
|
||||
editor.providers.get(providerID)?.models.delete(modelID)
|
||||
},
|
||||
default: {
|
||||
get: () => draft.defaultModel,
|
||||
get: () => editor.defaultModel,
|
||||
set: (providerID, modelID) => {
|
||||
draft.defaultModel = { providerID, modelID }
|
||||
editor.defaultModel = { providerID, modelID }
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface Definition {
|
||||
readonly execute: (input: Invocation) => Effect.Effect<void, unknown>
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
add: (definition: Definition) => void
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export class ExecutionError extends Schema.TaggedError<ExecutionError>()("Comman
|
||||
message: Schema.String,
|
||||
}) {}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly get: (name: string) => Effect.Effect<Info | undefined>
|
||||
readonly list: () => Effect.Effect<Info[]>
|
||||
readonly execute: (input: {
|
||||
@@ -54,11 +54,11 @@ export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const bus = yield* Bus.Service
|
||||
const state = State.create<Map<string, Definition>, Draft>({
|
||||
const state = State.create<Map<string, Definition>, Editor>({
|
||||
name: "command",
|
||||
initial: () => new Map(),
|
||||
draft: (draft) => ({
|
||||
add: (definition) => draft.set(definition.name, definition),
|
||||
editor: (editor) => ({
|
||||
add: (definition) => editor.set(definition.name, definition),
|
||||
}),
|
||||
notify: () => bus.publish(Command.Event.Updated, {}).pipe(Effect.asVoid),
|
||||
})
|
||||
|
||||
@@ -83,27 +83,27 @@ export const Plugin = define({
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
)
|
||||
loaded.documents = yield* load()
|
||||
yield* ctx.agent.transform((draft) => {
|
||||
yield* ctx.agent.transform((editor) => {
|
||||
const permissions = expandPermissions(
|
||||
loaded.documents.flatMap((document) => document.info.permissions ?? []),
|
||||
global.home,
|
||||
)
|
||||
const configuredDefault = Config.latest(loaded.documents, "default_agent")
|
||||
if (configuredDefault !== undefined) draft.default(Agent.ID.make(configuredDefault))
|
||||
for (const current of draft.list()) {
|
||||
draft.update(current.id, (agent) => agent.permissions.push(...permissions))
|
||||
if (configuredDefault !== undefined) editor.default(Agent.ID.make(configuredDefault))
|
||||
for (const current of editor.list()) {
|
||||
editor.update(current.id, (agent) => agent.permissions.push(...permissions))
|
||||
}
|
||||
|
||||
for (const document of loaded.documents) {
|
||||
for (const [id, item] of Object.entries(document.info.agents ?? {})) {
|
||||
const agentID = Agent.ID.make(id)
|
||||
if (item.disabled) {
|
||||
draft.remove(agentID)
|
||||
editor.remove(agentID)
|
||||
continue
|
||||
}
|
||||
|
||||
const exists = draft.get(agentID) !== undefined
|
||||
draft.update(agentID, (agent) => {
|
||||
const exists = editor.get(agentID) !== undefined
|
||||
editor.update(agentID, (agent) => {
|
||||
if (!exists) agent.permissions.push(...permissions)
|
||||
if (item.model !== undefined)
|
||||
agent.model = {
|
||||
|
||||
@@ -64,10 +64,10 @@ export const Plugin = define({
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
)
|
||||
loaded.documents = yield* load()
|
||||
yield* ctx.command.transform((draft) => {
|
||||
yield* ctx.command.transform((editor) => {
|
||||
for (const document of loaded.documents) {
|
||||
for (const [name, command] of Object.entries(document.commands ?? {})) {
|
||||
draft.add({
|
||||
editor.add({
|
||||
name,
|
||||
description: command.description,
|
||||
execute: (input) =>
|
||||
|
||||
@@ -12,10 +12,10 @@ export const Plugin = define({
|
||||
const config = yield* Config.Service
|
||||
const compaction = yield* SessionCompaction.Service
|
||||
const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, compaction.reload())
|
||||
yield* compaction.transform((draft) => {
|
||||
yield* compaction.transform((editor) => {
|
||||
for (const entry of loaded.entries) {
|
||||
if (entry.type !== "document" || !entry.info.compaction) continue
|
||||
draft.configure({
|
||||
editor.configure({
|
||||
...(entry.info.compaction.auto === undefined ? {} : { auto: entry.info.compaction.auto }),
|
||||
...(entry.info.compaction.buffer === undefined ? {} : { buffer: entry.info.compaction.buffer }),
|
||||
...(entry.info.compaction.keep?.tokens === undefined ? {} : { tokens: entry.info.compaction.keep.tokens }),
|
||||
|
||||
@@ -24,7 +24,7 @@ export const Plugin = define({
|
||||
const processes = yield* AppProcess.Service
|
||||
const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, formatter.reload())
|
||||
|
||||
yield* formatter.transform((draft) => {
|
||||
yield* formatter.transform((editor) => {
|
||||
const configured = Config.latest(loaded.entries, "formatter")
|
||||
if (!configured) return
|
||||
const builtIns = make({
|
||||
@@ -35,12 +35,12 @@ export const Plugin = define({
|
||||
processes,
|
||||
bin: global.bin,
|
||||
})
|
||||
builtIns.forEach(draft.set)
|
||||
builtIns.forEach(editor.set)
|
||||
if (configured === true) return
|
||||
|
||||
for (const [name, entry] of Object.entries(configured)) {
|
||||
if (entry.disabled) {
|
||||
draft.remove(name)
|
||||
editor.remove(name)
|
||||
continue
|
||||
}
|
||||
const builtIn = builtIns.find((formatter) => formatter.name === name)
|
||||
@@ -51,7 +51,7 @@ export const Plugin = define({
|
||||
enabled:
|
||||
builtIn && !entry.command ? builtIn.enabled : Effect.succeed(entry.command ? [...entry.command] : false),
|
||||
}
|
||||
draft.set(current)
|
||||
editor.set(current)
|
||||
}
|
||||
})
|
||||
}),
|
||||
|
||||
@@ -12,12 +12,12 @@ export const Plugin = define({
|
||||
const config = yield* Config.Service
|
||||
const image = yield* Image.Service
|
||||
const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, image.reload())
|
||||
yield* image.transform((draft) => {
|
||||
yield* image.transform((editor) => {
|
||||
for (const entry of loaded.entries) {
|
||||
if (entry.type !== "document") continue
|
||||
const configured = entry.info.media?.image
|
||||
if (!configured) continue
|
||||
draft.configure({
|
||||
editor.configure({
|
||||
...(configured.auto_resize === undefined ? {} : { autoResize: configured.auto_resize }),
|
||||
...(configured.max_width === undefined ? {} : { maxWidth: configured.max_width }),
|
||||
...(configured.max_height === undefined ? {} : { maxHeight: configured.max_height }),
|
||||
|
||||
@@ -112,17 +112,17 @@ export const Plugin = define({
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
)
|
||||
yield* refresh()
|
||||
yield* discovery.transform((draft) => {
|
||||
yield* discovery.transform((editor) => {
|
||||
if (loaded.current.type === "unavailable") {
|
||||
draft.unavailable()
|
||||
editor.unavailable()
|
||||
return
|
||||
}
|
||||
for (const file of loaded.current.files) draft.add(file)
|
||||
for (const file of loaded.current.files) editor.add(file)
|
||||
})
|
||||
}).pipe(
|
||||
Effect.catchCause((cause) =>
|
||||
Effect.logWarning("failed to activate instruction source", { cause }).pipe(
|
||||
Effect.andThen(discovery.transform((draft) => draft.unavailable())),
|
||||
Effect.andThen(discovery.transform((editor) => editor.unavailable())),
|
||||
Effect.asVoid,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -12,10 +12,10 @@ export const Plugin = define({
|
||||
const config = yield* Config.Service
|
||||
const policy = yield* LocationWatcherPolicy.Service
|
||||
const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, policy.reload())
|
||||
yield* policy.transform((draft) => {
|
||||
yield* policy.transform((editor) => {
|
||||
for (const entry of loaded.entries) {
|
||||
if (entry.type !== "document" || !entry.info.watcher?.ignore) continue
|
||||
draft.add(entry.info.watcher.ignore)
|
||||
editor.add(entry.info.watcher.ignore)
|
||||
}
|
||||
})
|
||||
}),
|
||||
|
||||
@@ -36,7 +36,7 @@ export const register = Effect.fn("ConfigMCPPlugin.register")(function* (
|
||||
|
||||
// Subscribe before the initial load so updates racing it trigger a rebuild.
|
||||
loaded.entries = yield* config.entries()
|
||||
yield* mcp.transform((draft) => {
|
||||
yield* mcp.transform((editor) => {
|
||||
const documents = loaded.entries.filter((entry): entry is Document => entry.type === "document")
|
||||
// Global timeout defaults merge in config order; each server can override them.
|
||||
const timeout = Object.assign(
|
||||
@@ -50,8 +50,8 @@ export const register = Effect.fn("ConfigMCPPlugin.register")(function* (
|
||||
}
|
||||
}
|
||||
for (const [name, server] of servers) {
|
||||
if (draft.get(name)) continue
|
||||
draft.set(name, { ...server, timeout: { ...timeout, ...server.timeout } })
|
||||
if (editor.get(name)) continue
|
||||
editor.set(name, { ...server, timeout: { ...timeout, ...server.timeout } })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,14 +19,14 @@ export const Plugin = define({
|
||||
const location = yield* Location.Service
|
||||
const global = yield* Global.Service
|
||||
const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, ctx.reference.reload())
|
||||
yield* ctx.reference.transform((draft) => {
|
||||
yield* ctx.reference.transform((editor) => {
|
||||
for (const doc of loaded.entries.filter((entry): entry is Document => entry.type === "document")) {
|
||||
const directory = doc.path ? path.dirname(doc.path) : location.directory
|
||||
for (const [name, entry] of Object.entries(doc.info.references ?? {})) {
|
||||
if (!validAlias(name)) continue
|
||||
const description = typeof entry === "string" ? undefined : entry.description
|
||||
const hidden = typeof entry === "string" ? undefined : entry.hidden
|
||||
draft.add(
|
||||
editor.add(
|
||||
name,
|
||||
local(entry)
|
||||
? Reference.LocalSource.make({
|
||||
|
||||
@@ -12,9 +12,9 @@ export const Plugin = define({
|
||||
const config = yield* Config.Service
|
||||
const shell = yield* ShellSelect.Service
|
||||
const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, shell.reload())
|
||||
yield* shell.transform((draft) => {
|
||||
yield* shell.transform((editor) => {
|
||||
const configured = Config.latest(loaded.entries, "shell")
|
||||
if (configured) draft.configure(configured)
|
||||
if (configured) editor.configure(configured)
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -180,8 +180,8 @@ export const Plugin = define({
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
)
|
||||
yield* refresh()
|
||||
yield* ctx.skill.transform((draft) => {
|
||||
for (const skill of loaded.skills) draft.add(skill)
|
||||
yield* ctx.skill.transform((editor) => {
|
||||
for (const skill of loaded.skills) editor.add(skill)
|
||||
})
|
||||
yield* ctx.event.subscribe().pipe(
|
||||
Stream.filter((event) => event.type === "config.updated"),
|
||||
|
||||
@@ -12,10 +12,10 @@ export const Plugin = define({
|
||||
const config = yield* Config.Service
|
||||
const snapshot = yield* Snapshot.Service
|
||||
const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, snapshot.reload())
|
||||
yield* snapshot.transform((draft) => {
|
||||
yield* snapshot.transform((editor) => {
|
||||
const configured = Config.latest(loaded.entries, "snapshots")
|
||||
if (configured === undefined) return
|
||||
draft.configure(configured)
|
||||
editor.configure(configured)
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -12,10 +12,10 @@ export const Plugin = define({
|
||||
const config = yield* Config.Service
|
||||
const output = yield* ToolOutput.Service
|
||||
const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, output.reload())
|
||||
yield* output.transform((draft) => {
|
||||
yield* output.transform((editor) => {
|
||||
const configured = Config.latest(loaded.entries, "tool_output")
|
||||
if (!configured) return
|
||||
draft.configure({
|
||||
editor.configure({
|
||||
...(configured.max_lines === undefined ? {} : { maxLines: configured.max_lines }),
|
||||
...(configured.max_bytes === undefined ? {} : { maxBytes: configured.max_bytes }),
|
||||
})
|
||||
|
||||
@@ -8,12 +8,12 @@ type Data = {
|
||||
ignore: string[]
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
add: (ignore: readonly string[]) => void
|
||||
list: () => readonly string[]
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly current: () => readonly string[]
|
||||
readonly observe: (
|
||||
listener: (ignore: readonly string[]) => Effect.Effect<void>,
|
||||
@@ -26,12 +26,12 @@ const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const listeners = new Set<(ignore: readonly string[]) => Effect.Effect<void>>()
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "location-watcher-policy",
|
||||
initial: () => ({ ignore: [] }),
|
||||
draft: (draft) => ({
|
||||
add: (ignore) => draft.ignore.push(...ignore),
|
||||
list: () => draft.ignore,
|
||||
editor: (editor) => ({
|
||||
add: (ignore) => editor.ignore.push(...ignore),
|
||||
list: () => editor.ignore,
|
||||
}),
|
||||
// Read per listener: a reentrant transform inside an earlier listener must reach later ones.
|
||||
notify: () => Effect.forEach(listeners, (listener) => listener(current()), { discard: true }),
|
||||
|
||||
@@ -13,12 +13,12 @@ type Data = {
|
||||
formatters: Info[]
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
set: (formatter: Info) => void
|
||||
remove: (name: string) => void
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly file: (filepath: string) => Effect.Effect<boolean>
|
||||
}
|
||||
|
||||
@@ -30,17 +30,17 @@ const layer = Layer.effect(
|
||||
const location = yield* Location.Service
|
||||
const processes = yield* AppProcess.Service
|
||||
const commands = new WeakMap<Info, string[] | false>()
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "formatter",
|
||||
initial: () => ({ formatters: [] }),
|
||||
draft: (draft) => ({
|
||||
editor: (editor) => ({
|
||||
set: (formatter) => {
|
||||
const index = draft.formatters.findIndex((item) => item.name === formatter.name)
|
||||
if (index === -1) draft.formatters.push(formatter)
|
||||
else draft.formatters[index] = formatter
|
||||
const index = editor.formatters.findIndex((item) => item.name === formatter.name)
|
||||
if (index === -1) editor.formatters.push(formatter)
|
||||
else editor.formatters[index] = formatter
|
||||
},
|
||||
remove: (name) => {
|
||||
draft.formatters = draft.formatters.filter((formatter) => formatter.name !== name)
|
||||
editor.formatters = editor.formatters.filter((formatter) => formatter.name !== name)
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -39,11 +39,11 @@ export type Limits = {
|
||||
maxBase64Bytes: number
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
configure: (limits: Partial<Limits>) => void
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly normalize: (
|
||||
resource: string,
|
||||
content: FileSystem.Content & { readonly encoding: "base64" },
|
||||
@@ -58,7 +58,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Im
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const state = State.create<Limits, Draft>({
|
||||
const state = State.create<Limits, Editor>({
|
||||
name: "image",
|
||||
initial: () => ({
|
||||
autoResize: true,
|
||||
@@ -66,12 +66,12 @@ const layer = Layer.effect(
|
||||
maxHeight: 2_000,
|
||||
maxBase64Bytes: 5 * 1024 * 1024,
|
||||
}),
|
||||
draft: (draft) => ({
|
||||
editor: (editor) => ({
|
||||
configure: (limits) => {
|
||||
if (limits.autoResize !== undefined) draft.autoResize = limits.autoResize
|
||||
if (limits.maxWidth !== undefined) draft.maxWidth = limits.maxWidth
|
||||
if (limits.maxHeight !== undefined) draft.maxHeight = limits.maxHeight
|
||||
if (limits.maxBase64Bytes !== undefined) draft.maxBase64Bytes = limits.maxBase64Bytes
|
||||
if (limits.autoResize !== undefined) editor.autoResize = limits.autoResize
|
||||
if (limits.maxWidth !== undefined) editor.maxWidth = limits.maxWidth
|
||||
if (limits.maxHeight !== undefined) editor.maxHeight = limits.maxHeight
|
||||
if (limits.maxBase64Bytes !== undefined) editor.maxBase64Bytes = limits.maxBase64Bytes
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ export type Data = {
|
||||
available: boolean
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
list: () => readonly Types.DeepMutable<File>[]
|
||||
// Map insertion order is render order: config adds global then nearest-to-farthest project files;
|
||||
// sibling contributors interleave by transform registration order.
|
||||
@@ -35,7 +35,7 @@ export type Draft = {
|
||||
unavailable: () => void
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
// Discovery policy lives here because internal plugins have no per-composition options channel.
|
||||
// Move it into plugin config once plugins can consume their own options.
|
||||
readonly project: boolean
|
||||
@@ -57,21 +57,21 @@ export const layer = (options?: Options) =>
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const bus = yield* Bus.Service
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "instruction-discovery",
|
||||
initial: () => ({ files: new Map(), available: true }),
|
||||
draft: (draft) => ({
|
||||
list: () => Array.from(draft.files.values()),
|
||||
add: (file) => draft.files.set(file.path, new File(file) as Types.DeepMutable<File>),
|
||||
editor: (editor) => ({
|
||||
list: () => Array.from(editor.files.values()),
|
||||
add: (file) => editor.files.set(file.path, new File(file) as Types.DeepMutable<File>),
|
||||
update: (path, update) => {
|
||||
const current = draft.files.get(AbsolutePath.make(path))
|
||||
const current = editor.files.get(AbsolutePath.make(path))
|
||||
if (!current) return
|
||||
update(current)
|
||||
current.path = AbsolutePath.make(path)
|
||||
},
|
||||
remove: (path) => draft.files.delete(AbsolutePath.make(path)),
|
||||
remove: (path) => editor.files.delete(AbsolutePath.make(path)),
|
||||
unavailable: () => {
|
||||
draft.available = false
|
||||
editor.available = false
|
||||
},
|
||||
}),
|
||||
notify: () => bus.publish(Event.Updated, {}).pipe(Effect.asVoid),
|
||||
|
||||
@@ -130,7 +130,7 @@ type Data = {
|
||||
integrations: Map<ID, Entry>
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
list: () => readonly Ref[]
|
||||
get: (id: ID) => Ref | undefined
|
||||
update: (id: ID, update: (integration: Types.DeepMutable<Ref>) => void) => void
|
||||
@@ -142,7 +142,7 @@ export type Draft = {
|
||||
}
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
/** Registers a scoped transform over the integration registry. */
|
||||
/** Returns one integration with its methods and current connections. */
|
||||
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
||||
@@ -266,27 +266,27 @@ const layer = Layer.effect(
|
||||
const scope = yield* Scope.Scope
|
||||
const attempts = SynchronizedRef.makeUnsafe(new Map<AttemptID, AttemptEntry>())
|
||||
const commandAttempts = SynchronizedRef.makeUnsafe(new Map<AttemptID, CommandAttemptEntry>())
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "integration",
|
||||
initial: () => ({ integrations: new Map<ID, Entry>() }),
|
||||
draft: (draft) => ({
|
||||
list: () => Array.from(draft.integrations.values(), (entry) => entry.ref) as Ref[],
|
||||
get: (id) => draft.integrations.get(id)?.ref as Ref | undefined,
|
||||
editor: (editor) => ({
|
||||
list: () => Array.from(editor.integrations.values(), (entry) => entry.ref) as Ref[],
|
||||
get: (id) => editor.integrations.get(id)?.ref as Ref | undefined,
|
||||
update: (id, update) => {
|
||||
const current = draft.integrations.get(id) ?? {
|
||||
const current = editor.integrations.get(id) ?? {
|
||||
ref: { id, name: id },
|
||||
methods: [],
|
||||
implementations: new Map(),
|
||||
}
|
||||
if (!draft.integrations.has(id)) draft.integrations.set(id, current)
|
||||
if (!editor.integrations.has(id)) editor.integrations.set(id, current)
|
||||
update(current.ref)
|
||||
current.ref.id = id
|
||||
},
|
||||
remove: (id) => draft.integrations.delete(id),
|
||||
remove: (id) => editor.integrations.delete(id),
|
||||
method: {
|
||||
list: (integrationID) => (draft.integrations.get(integrationID)?.methods as Method[] | undefined) ?? [],
|
||||
list: (integrationID) => (editor.integrations.get(integrationID)?.methods as Method[] | undefined) ?? [],
|
||||
update: (implementation) => {
|
||||
const current = draft.integrations.get(implementation.integrationID) ?? {
|
||||
const current = editor.integrations.get(implementation.integrationID) ?? {
|
||||
ref: {
|
||||
id: implementation.integrationID,
|
||||
name: implementation.integrationID,
|
||||
@@ -294,8 +294,8 @@ const layer = Layer.effect(
|
||||
methods: [],
|
||||
implementations: new Map<MethodID, Types.DeepMutable<OAuthImplementation>>(),
|
||||
}
|
||||
if (!draft.integrations.has(implementation.integrationID)) {
|
||||
draft.integrations.set(implementation.integrationID, current)
|
||||
if (!editor.integrations.has(implementation.integrationID)) {
|
||||
editor.integrations.set(implementation.integrationID, current)
|
||||
}
|
||||
const index = current.methods.findIndex((method) => {
|
||||
if (method.type !== implementation.method.type) return false
|
||||
@@ -315,7 +315,7 @@ const layer = Layer.effect(
|
||||
}
|
||||
},
|
||||
remove: (integrationID, method) => {
|
||||
const current = draft.integrations.get(integrationID)
|
||||
const current = editor.integrations.get(integrationID)
|
||||
if (!current) return
|
||||
const index = current.methods.findIndex((candidate) => {
|
||||
if (candidate.type !== method.type) return false
|
||||
|
||||
@@ -133,7 +133,7 @@ type Data = {
|
||||
removed: Set<ServerName>
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
list: () => readonly [ServerName, Types.DeepMutable<Mcp.ServerConfig>][]
|
||||
get: (server: ServerName | string) => Types.DeepMutable<Mcp.ServerConfig> | undefined
|
||||
set: (server: ServerName | string, config: Mcp.ServerConfig) => void
|
||||
@@ -143,7 +143,7 @@ export type Draft = {
|
||||
|
||||
const cloneConfig = (config: Mcp.ServerConfig) => structuredClone(config) as Types.DeepMutable<Mcp.ServerConfig>
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly servers: () => Effect.Effect<ServerInfo[]>
|
||||
readonly add: (server: ServerName | string, config: Mcp.ServerConfig) => Effect.Effect<void>
|
||||
readonly connect: (server: ServerName | string) => Effect.Effect<void, NotFoundError>
|
||||
@@ -227,12 +227,12 @@ export const layer = (options?: Options) =>
|
||||
const scope = yield* Scope.fork(root)
|
||||
entry.registration = { dispose: Scope.close(scope, Exit.void) }
|
||||
yield* integration
|
||||
.transform((draft) => {
|
||||
draft.update(integrationID, (ref) => {
|
||||
.transform((editor) => {
|
||||
editor.update(integrationID, (ref) => {
|
||||
ref.name = name
|
||||
ref.metadata = { source: "mcp" }
|
||||
})
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID,
|
||||
method: { id: methodID, type: "oauth", label: name },
|
||||
authorize: () =>
|
||||
@@ -678,7 +678,7 @@ export const layer = (options?: Options) =>
|
||||
Stream.runForEach((event) => Effect.sync(() => fork(reconnect(event.data.integrationID)))),
|
||||
),
|
||||
)
|
||||
const state: State.Interface<Data, Draft> = State.create<Data, Draft>({
|
||||
const state: State.Interface<Data, Editor> = State.create<Data, Editor>({
|
||||
name: "mcp",
|
||||
initial: () => ({
|
||||
servers: new Map(
|
||||
@@ -688,20 +688,20 @@ export const layer = (options?: Options) =>
|
||||
),
|
||||
removed: new Set(Array.from(overrides).flatMap(([name, config]) => (config === false ? [name] : []))),
|
||||
}),
|
||||
draft: (draft) => ({
|
||||
list: () => Array.from(draft.servers),
|
||||
get: (server) => draft.servers.get(ServerName.make(server)),
|
||||
editor: (editor) => ({
|
||||
list: () => Array.from(editor.servers),
|
||||
get: (server) => editor.servers.get(ServerName.make(server)),
|
||||
set: (server, serverConfig) => {
|
||||
const name = ServerName.make(server)
|
||||
if (draft.removed.has(name)) return
|
||||
draft.servers.set(name, cloneConfig(serverConfig))
|
||||
if (editor.removed.has(name)) return
|
||||
editor.servers.set(name, cloneConfig(serverConfig))
|
||||
},
|
||||
update: (server, update) => {
|
||||
const current = draft.servers.get(ServerName.make(server))
|
||||
const current = editor.servers.get(ServerName.make(server))
|
||||
if (!current) return
|
||||
update(current)
|
||||
},
|
||||
remove: (server) => draft.servers.delete(ServerName.make(server)),
|
||||
remove: (server) => editor.servers.delete(ServerName.make(server)),
|
||||
}),
|
||||
notify: () => State.reconcile(root, fork, () => reconcileLock.withPermit(reconcile())),
|
||||
})
|
||||
|
||||
@@ -83,15 +83,15 @@ Rules:
|
||||
export const Plugin = define({
|
||||
id: "opencode.agent",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.agent.transform((draft) => {
|
||||
draft.update(Agent.defaultID, (item) => {
|
||||
yield* ctx.agent.transform((editor) => {
|
||||
editor.update(Agent.defaultID, (item) => {
|
||||
item.name = Agent.Name.make("Build")
|
||||
item.description = "The default agent. Executes tools based on configured permissions."
|
||||
item.mode = "primary"
|
||||
item.permissions.push({ action: "question", resource: "*", effect: "allow" })
|
||||
})
|
||||
|
||||
draft.update(Agent.ID.make("general"), (item) => {
|
||||
editor.update(Agent.ID.make("general"), (item) => {
|
||||
item.name = Agent.Name.make("General")
|
||||
item.description =
|
||||
"General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel."
|
||||
@@ -102,7 +102,7 @@ export const Plugin = define({
|
||||
)
|
||||
})
|
||||
|
||||
draft.update(Agent.ID.make("explore"), (item) => {
|
||||
editor.update(Agent.ID.make("explore"), (item) => {
|
||||
const externalDirectories = item.permissions.filter(
|
||||
(rule) => rule.action === "external_directory" && rule.effect === "allow",
|
||||
)
|
||||
@@ -130,13 +130,13 @@ export const Plugin = define({
|
||||
)
|
||||
})
|
||||
|
||||
draft.update(Agent.ID.make("compaction"), (item) => {
|
||||
editor.update(Agent.ID.make("compaction"), (item) => {
|
||||
item.name = Agent.Name.make("Compaction")
|
||||
item.mode = "primary"
|
||||
item.hidden = true
|
||||
})
|
||||
|
||||
draft.update(Agent.ID.make("title"), (item) => {
|
||||
editor.update(Agent.ID.make("title"), (item) => {
|
||||
item.name = Agent.Name.make("Title")
|
||||
item.mode = "primary"
|
||||
item.hidden = true
|
||||
@@ -144,7 +144,7 @@ export const Plugin = define({
|
||||
item.permissions.push({ action: "*", resource: "*", effect: "deny" })
|
||||
})
|
||||
|
||||
draft.update(Agent.ID.make("summary"), (item) => {
|
||||
editor.update(Agent.ID.make("summary"), (item) => {
|
||||
item.name = Agent.Name.make("Summary")
|
||||
item.mode = "primary"
|
||||
item.hidden = true
|
||||
|
||||
@@ -25,8 +25,8 @@ export const Plugin = define({
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
)
|
||||
loaded.prompts = yield* mcp.prompts()
|
||||
yield* ctx.command.transform((draft) => {
|
||||
draft.add({
|
||||
yield* ctx.command.transform((editor) => {
|
||||
editor.add({
|
||||
name: "init",
|
||||
description: "guided AGENTS.md setup",
|
||||
execute: (input) =>
|
||||
@@ -39,7 +39,7 @@ export const Plugin = define({
|
||||
})
|
||||
.pipe(Effect.asVoid),
|
||||
})
|
||||
draft.add({
|
||||
editor.add({
|
||||
name: "review",
|
||||
description: "review changes [commit|branch|pr], defaults to uncommitted",
|
||||
execute: (input) =>
|
||||
@@ -53,7 +53,7 @@ export const Plugin = define({
|
||||
.pipe(Effect.asVoid),
|
||||
})
|
||||
for (const prompt of loaded.prompts) {
|
||||
draft.add({
|
||||
editor.add({
|
||||
name: mcpCommandName(prompt.server, prompt.name),
|
||||
description: prompt.description,
|
||||
execute: (input) =>
|
||||
|
||||
@@ -136,13 +136,13 @@ export const make = Effect.fn("PluginHost.make")(function* (
|
||||
},
|
||||
reload: agents.reload,
|
||||
transform: (callback) =>
|
||||
agents.transform((draft) => {
|
||||
agents.transform((editor) => {
|
||||
callback({
|
||||
list: () => mutable(draft.list()),
|
||||
get: (id) => mutable(draft.get(Agent.ID.make(id))),
|
||||
default: (id) => draft.default(id === undefined ? undefined : Agent.ID.make(id)),
|
||||
update: (id, update) => draft.update(Agent.ID.make(id), update),
|
||||
remove: (id) => draft.remove(Agent.ID.make(id)),
|
||||
list: () => mutable(editor.list()),
|
||||
get: (id) => mutable(editor.get(Agent.ID.make(id))),
|
||||
default: (id) => editor.default(id === undefined ? undefined : Agent.ID.make(id)),
|
||||
update: (id, update) => editor.update(Agent.ID.make(id), update),
|
||||
remove: (id) => editor.remove(Agent.ID.make(id)),
|
||||
})
|
||||
}),
|
||||
},
|
||||
@@ -196,24 +196,25 @@ export const make = Effect.fn("PluginHost.make")(function* (
|
||||
},
|
||||
reload: catalog.reload,
|
||||
transform: (callback) =>
|
||||
catalog.transform((draft) => {
|
||||
catalog.transform((editor) => {
|
||||
callback({
|
||||
provider: {
|
||||
list: () => mutable(draft.provider.list()),
|
||||
get: (id) => mutable(draft.provider.get(Provider.ID.make(id))),
|
||||
update: (id, update) => draft.provider.update(Provider.ID.make(id), update),
|
||||
remove: (id) => draft.provider.remove(Provider.ID.make(id)),
|
||||
list: () => mutable(editor.provider.list()),
|
||||
get: (id) => mutable(editor.provider.get(Provider.ID.make(id))),
|
||||
update: (id, update) => editor.provider.update(Provider.ID.make(id), update),
|
||||
remove: (id) => editor.provider.remove(Provider.ID.make(id)),
|
||||
},
|
||||
model: {
|
||||
get: (providerID, modelID) =>
|
||||
mutable(draft.model.get(Provider.ID.make(providerID), Model.ID.make(modelID))),
|
||||
mutable(editor.model.get(Provider.ID.make(providerID), Model.ID.make(modelID))),
|
||||
update: (providerID, modelID, update) =>
|
||||
draft.model.update(Provider.ID.make(providerID), Model.ID.make(modelID), update),
|
||||
remove: (providerID, modelID) => draft.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)),
|
||||
editor.model.update(Provider.ID.make(providerID), Model.ID.make(modelID), update),
|
||||
remove: (providerID, modelID) =>
|
||||
editor.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)),
|
||||
default: {
|
||||
get: draft.model.default.get,
|
||||
get: editor.model.default.get,
|
||||
set: (providerID, modelID) =>
|
||||
draft.model.default.set(Provider.ID.make(providerID), Model.ID.make(modelID)),
|
||||
editor.model.default.set(Provider.ID.make(providerID), Model.ID.make(modelID)),
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -315,17 +316,17 @@ export const make = Effect.fn("PluginHost.make")(function* (
|
||||
),
|
||||
},
|
||||
transform: (callback) =>
|
||||
integration.transform((draft) => {
|
||||
integration.transform((editor) => {
|
||||
callback({
|
||||
list: () => mutable(draft.list()),
|
||||
get: (id) => mutable(draft.get(Integration.ID.make(id))),
|
||||
update: (id, update) => draft.update(Integration.ID.make(id), update),
|
||||
remove: (id) => draft.remove(Integration.ID.make(id)),
|
||||
list: () => mutable(editor.list()),
|
||||
get: (id) => mutable(editor.get(Integration.ID.make(id))),
|
||||
update: (id, update) => editor.update(Integration.ID.make(id), update),
|
||||
remove: (id) => editor.remove(Integration.ID.make(id)),
|
||||
method: {
|
||||
list: (id) => draft.method.list(Integration.ID.make(id)),
|
||||
update: (input) => draft.method.update(methodImplementation(input)),
|
||||
list: (id) => editor.method.list(Integration.ID.make(id)),
|
||||
update: (input) => editor.method.update(methodImplementation(input)),
|
||||
remove: (id, method) =>
|
||||
draft.method.remove(Integration.ID.make(id), Schema.decodeUnknownSync(Integration.Method)(method)),
|
||||
editor.method.remove(Integration.ID.make(id), Schema.decodeUnknownSync(Integration.Method)(method)),
|
||||
},
|
||||
})
|
||||
}),
|
||||
@@ -350,13 +351,13 @@ export const make = Effect.fn("PluginHost.make")(function* (
|
||||
},
|
||||
reload: mcp.reload,
|
||||
transform: (callback) =>
|
||||
mcp.transform((draft) => {
|
||||
mcp.transform((editor) => {
|
||||
callback({
|
||||
list: () => draft.list().map(([name, config]) => [name, mutable(config)]),
|
||||
get: (name) => mutable(draft.get(name)),
|
||||
set: (name, config) => draft.set(name, Schema.decodeUnknownSync(ServerConfig)(config)),
|
||||
update: draft.update,
|
||||
remove: draft.remove,
|
||||
list: () => editor.list().map(([name, config]) => [name, mutable(config)]),
|
||||
get: (name) => mutable(editor.get(name)),
|
||||
set: (name, config) => editor.set(name, Schema.decodeUnknownSync(ServerConfig)(config)),
|
||||
update: editor.update,
|
||||
remove: editor.remove,
|
||||
})
|
||||
}),
|
||||
},
|
||||
@@ -391,11 +392,11 @@ export const make = Effect.fn("PluginHost.make")(function* (
|
||||
list: () => response(reference.list()),
|
||||
reload: reference.reload,
|
||||
transform: (callback) =>
|
||||
reference.transform((draft) => {
|
||||
reference.transform((editor) => {
|
||||
callback({
|
||||
add: (name, source) => draft.add(name, Schema.decodeUnknownSync(Reference.Source)(source)),
|
||||
remove: draft.remove,
|
||||
list: draft.list,
|
||||
add: (name, source) => editor.add(name, Schema.decodeUnknownSync(Reference.Source)(source)),
|
||||
remove: editor.remove,
|
||||
list: editor.list,
|
||||
})
|
||||
}),
|
||||
},
|
||||
@@ -403,12 +404,12 @@ export const make = Effect.fn("PluginHost.make")(function* (
|
||||
list: () => response(skill.list()),
|
||||
reload: skill.reload,
|
||||
transform: (callback) =>
|
||||
skill.transform((draft) => {
|
||||
skill.transform((editor) => {
|
||||
callback({
|
||||
list: () => mutable(draft.list()),
|
||||
add: (value) => draft.add(Schema.decodeUnknownSync(Skill.Info)(value)),
|
||||
update: draft.update,
|
||||
remove: draft.remove,
|
||||
list: () => mutable(editor.list()),
|
||||
add: (value) => editor.add(Schema.decodeUnknownSync(Skill.Info)(value)),
|
||||
update: editor.update,
|
||||
remove: editor.remove,
|
||||
})
|
||||
}),
|
||||
},
|
||||
@@ -441,18 +442,18 @@ export const make = Effect.fn("PluginHost.make")(function* (
|
||||
),
|
||||
reload: websearch.reload,
|
||||
transform: (callback) =>
|
||||
websearch.transform((draft) => {
|
||||
websearch.transform((editor) => {
|
||||
callback({
|
||||
add: (definition) =>
|
||||
draft.add({
|
||||
editor.add({
|
||||
id: WebSearch.ID.make(definition.id),
|
||||
name: definition.name,
|
||||
execute: definition.execute,
|
||||
}),
|
||||
default: {
|
||||
get: draft.default.get,
|
||||
get: editor.default.get,
|
||||
set: (selection) =>
|
||||
draft.default.set(
|
||||
editor.default.set(
|
||||
selection === false || selection === "random" ? selection : WebSearch.ID.make(selection),
|
||||
),
|
||||
},
|
||||
|
||||
@@ -9,8 +9,8 @@ const urls = [/^https:\/\/executor\.sh\/[^/]+\/mcp$/]
|
||||
export const Plugin = define({
|
||||
id: "opencode.mcp.codemode.exclusion",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.mcp.transform((draft) => {
|
||||
for (const [, server] of draft.list()) {
|
||||
yield* ctx.mcp.transform((editor) => {
|
||||
for (const [, server] of editor.list()) {
|
||||
if (server.codemode !== undefined) continue
|
||||
if (server.type === "local") {
|
||||
if (server.command[0] === "executor" && server.command[1] === "mcp") server.codemode = false
|
||||
|
||||
@@ -30,8 +30,8 @@ export const Plugin = define({
|
||||
const global = yield* Global.Service
|
||||
const directory = path.join(global.home, ".opencode", "plan")
|
||||
const enterReminder = enter(directory)
|
||||
yield* ctx.agent.transform((draft) => {
|
||||
draft.update(plan, (item) => {
|
||||
yield* ctx.agent.transform((editor) => {
|
||||
editor.update(plan, (item) => {
|
||||
item.name = Agent.Name.make("Plan")
|
||||
item.description = "Read-only agent for exploring the codebase and planning work before implementation."
|
||||
item.mode = "primary"
|
||||
|
||||
@@ -80,13 +80,13 @@ export const AzurePlugin = define({
|
||||
])
|
||||
})
|
||||
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.method.update({
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.method.update({
|
||||
integrationID: Provider.ID.azure,
|
||||
method: { type: "key", label: "API key", form: form() },
|
||||
})
|
||||
if (!available) return
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: Provider.ID.azure,
|
||||
method: {
|
||||
id: methodID,
|
||||
|
||||
@@ -39,8 +39,8 @@ export const CloudflareAIGatewayPlugin = define({
|
||||
if (gatewayId) return Form.Fields.make([accountIdForm])
|
||||
return Form.Fields.make([accountIdForm, gatewayIdForm])
|
||||
})
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.method.update({
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.method.update({
|
||||
integrationID: providerID,
|
||||
method: {
|
||||
type: "key",
|
||||
|
||||
@@ -25,8 +25,8 @@ export const CloudflareWorkersAIPlugin = define({
|
||||
},
|
||||
])
|
||||
})
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.method.update({
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.method.update({
|
||||
integrationID: providerID,
|
||||
method: {
|
||||
type: "key",
|
||||
|
||||
@@ -190,9 +190,9 @@ export const GithubCopilotPlugin = define({
|
||||
)
|
||||
})
|
||||
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.method.remove("github-copilot", { type: "key" })
|
||||
draft.method.update(oauth(ctx.app))
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.method.remove("github-copilot", { type: "key" })
|
||||
editor.method.update(oauth(ctx.app))
|
||||
})
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
const item = evt.provider.get(Provider.ID.githubCopilot)
|
||||
|
||||
@@ -245,9 +245,9 @@ export const OpenAIPlugin = define({
|
||||
: undefined
|
||||
})
|
||||
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.method.update(browser(ctx.app))
|
||||
draft.method.update(headless(ctx.app))
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.method.update(browser(ctx.app))
|
||||
editor.method.update(headless(ctx.app))
|
||||
})
|
||||
yield* load()
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
|
||||
@@ -103,12 +103,12 @@ export const OpencodePlugin = define<HttpClient.HttpClient | Bus.Service | Scope
|
||||
: undefined
|
||||
})
|
||||
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.update("opencode", (integration) => {
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.update("opencode", (integration) => {
|
||||
integration.name = "OpenCode"
|
||||
})
|
||||
draft.method.update(oauth(http))
|
||||
draft.method.update({ integrationID: "opencode", method: { type: "key", label: "API key (service account)" } })
|
||||
editor.method.update(oauth(http))
|
||||
editor.method.update({ integrationID: "opencode", method: { type: "key", label: "API key (service account)" } })
|
||||
})
|
||||
|
||||
yield* load()
|
||||
|
||||
@@ -88,12 +88,12 @@ export const XAIPlugin = define({
|
||||
{ discard: true },
|
||||
)
|
||||
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.update("xai", (integration) => {
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.update("xai", (integration) => {
|
||||
integration.name = "xAI"
|
||||
})
|
||||
draft.method.update(device(ctx.app))
|
||||
draft.method.update({ integrationID: "xai", method: { type: "key", label: "Manually enter API Key" } })
|
||||
editor.method.update(device(ctx.app))
|
||||
editor.method.update({ integrationID: "xai", method: { type: "key", label: "Manually enter API Key" } })
|
||||
})
|
||||
yield* ctx.catalog.transform((catalog) => {
|
||||
const provider = catalog.provider.get(providerID)
|
||||
|
||||
@@ -24,8 +24,8 @@ export const Plugin = define({
|
||||
id: "opencode.skill",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
const reportContent = yield* reportContentWithDiagnostics(ctx.app)
|
||||
yield* ctx.skill.transform((draft) => {
|
||||
draft.add(
|
||||
yield* ctx.skill.transform((editor) => {
|
||||
editor.add(
|
||||
Skill.Info.make({
|
||||
id: Skill.ID.make("opencode"),
|
||||
name: Skill.Name.make("OpenCode"),
|
||||
@@ -34,7 +34,7 @@ export const Plugin = define({
|
||||
content: OpencodeContent,
|
||||
}),
|
||||
)
|
||||
draft.add(
|
||||
editor.add(
|
||||
Skill.Info.make({
|
||||
id: Skill.ID.make("report"),
|
||||
name: Skill.Name.make("Report"),
|
||||
|
||||
@@ -30,8 +30,8 @@ export const Plugin = define({
|
||||
worktree: location.project.directory,
|
||||
})
|
||||
|
||||
yield* ctx.vcs.transform((draft) => {
|
||||
draft.add({
|
||||
yield* ctx.vcs.transform((editor) => {
|
||||
editor.add({
|
||||
id: "git",
|
||||
name: "Git",
|
||||
info: () => adapter.info(),
|
||||
|
||||
@@ -35,8 +35,8 @@ export const Plugin = define({
|
||||
worktree: location.project.directory,
|
||||
})
|
||||
|
||||
yield* ctx.vcs.transform((draft) => {
|
||||
draft.add({
|
||||
yield* ctx.vcs.transform((editor) => {
|
||||
editor.add({
|
||||
id: "hg",
|
||||
name: "Mercurial",
|
||||
info: () => adapter.info(),
|
||||
|
||||
@@ -26,19 +26,19 @@ export const Plugin = define<HttpClient.HttpClient | Scope.Scope>({
|
||||
id: "opencode.websearch.exa",
|
||||
effect: Effect.fn("WebSearchExa.Plugin")(function* (ctx) {
|
||||
const http = yield* HttpClient.HttpClient
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.update("exa", (integration) => (integration.name = "Exa"))
|
||||
draft.method.update({
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.update("exa", (integration) => (integration.name = "Exa"))
|
||||
editor.method.update({
|
||||
integrationID: "exa",
|
||||
method: { type: "key" },
|
||||
})
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: "exa",
|
||||
method: { type: "env", names: ["EXA_API_KEY"] },
|
||||
})
|
||||
})
|
||||
yield* ctx.websearch.transform((draft) => {
|
||||
draft.add({
|
||||
yield* ctx.websearch.transform((editor) => {
|
||||
editor.add({
|
||||
id: "exa",
|
||||
name: "Exa",
|
||||
execute: (input) =>
|
||||
|
||||
@@ -37,19 +37,19 @@ export const Plugin = define<HttpClient.HttpClient | Scope.Scope>({
|
||||
id: "opencode.websearch.firecrawl",
|
||||
effect: Effect.fn("WebSearchFirecrawl.Plugin")(function* (ctx) {
|
||||
const http = yield* HttpClient.HttpClient
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.update("firecrawl", (integration) => (integration.name = "Firecrawl"))
|
||||
draft.method.update({
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.update("firecrawl", (integration) => (integration.name = "Firecrawl"))
|
||||
editor.method.update({
|
||||
integrationID: "firecrawl",
|
||||
method: { type: "key" },
|
||||
})
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: "firecrawl",
|
||||
method: { type: "env", names: ["FIRECRAWL_API_KEY"] },
|
||||
})
|
||||
})
|
||||
yield* ctx.websearch.transform((draft) => {
|
||||
draft.add({
|
||||
yield* ctx.websearch.transform((editor) => {
|
||||
editor.add({
|
||||
id: "firecrawl",
|
||||
name: "Firecrawl",
|
||||
execute: (input) =>
|
||||
|
||||
@@ -52,19 +52,19 @@ export const Plugin = define<HttpClient.HttpClient | Scope.Scope>({
|
||||
id: "opencode.websearch.parallel",
|
||||
effect: Effect.fn("WebSearchParallel.Plugin")(function* (ctx) {
|
||||
const http = yield* HttpClient.HttpClient
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.update("parallel", (integration) => (integration.name = "Parallel"))
|
||||
draft.method.update({
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.update("parallel", (integration) => (integration.name = "Parallel"))
|
||||
editor.method.update({
|
||||
integrationID: "parallel",
|
||||
method: { type: "key" },
|
||||
})
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: "parallel",
|
||||
method: { type: "env", names: ["PARALLEL_API_KEY"] },
|
||||
})
|
||||
})
|
||||
yield* ctx.websearch.transform((draft) => {
|
||||
draft.add({
|
||||
yield* ctx.websearch.transform((editor) => {
|
||||
editor.add({
|
||||
id: "parallel",
|
||||
name: "Parallel",
|
||||
execute: (input) =>
|
||||
|
||||
@@ -28,19 +28,19 @@ export const Plugin = define<HttpClient.HttpClient | Scope.Scope>({
|
||||
id: "opencode.websearch.tavily",
|
||||
effect: Effect.fn("WebSearchTavily.Plugin")(function* (ctx) {
|
||||
const http = yield* HttpClient.HttpClient
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
draft.update("tavily", (integration) => (integration.name = "Tavily"))
|
||||
draft.method.update({
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
editor.update("tavily", (integration) => (integration.name = "Tavily"))
|
||||
editor.method.update({
|
||||
integrationID: "tavily",
|
||||
method: { type: "key" },
|
||||
})
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: "tavily",
|
||||
method: { type: "env", names: ["TAVILY_API_KEY"] },
|
||||
})
|
||||
})
|
||||
yield* ctx.websearch.transform((draft) => {
|
||||
draft.add({
|
||||
yield* ctx.websearch.transform((editor) => {
|
||||
editor.add({
|
||||
id: "tavily",
|
||||
name: "Tavily",
|
||||
execute: (input) =>
|
||||
|
||||
@@ -28,13 +28,13 @@ type Data = {
|
||||
sources: Map<string, Source>
|
||||
}
|
||||
|
||||
type Draft = {
|
||||
type Editor = {
|
||||
add(name: string, source: Source): void
|
||||
remove(name: string): void
|
||||
list(): readonly [string, Source][]
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly list: () => Effect.Effect<Info[]>
|
||||
}
|
||||
|
||||
@@ -91,13 +91,13 @@ const layer = Layer.effect(
|
||||
{ concurrency: 4, discard: true },
|
||||
)
|
||||
})
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "reference",
|
||||
initial: () => ({ sources: new Map() }),
|
||||
draft: (draft) => ({
|
||||
add: (name, source) => draft.sources.set(name, source),
|
||||
remove: (name) => draft.sources.delete(name),
|
||||
list: () => Array.from(draft.sources),
|
||||
editor: (editor) => ({
|
||||
add: (name, source) => editor.sources.set(name, source),
|
||||
remove: (name) => editor.sources.delete(name),
|
||||
list: () => Array.from(editor.sources),
|
||||
}),
|
||||
notify: () =>
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -75,7 +75,7 @@ export type Settings = {
|
||||
tokens: number
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
configure: (settings: Partial<Settings>) => void
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ export type Outcome =
|
||||
| Pick<SessionMessage.CompactionCompleted, "status">
|
||||
| Pick<SessionMessage.CompactionFailed, "status" | "error">
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly enabled: () => boolean
|
||||
readonly required: (input: RequiredInput) => boolean
|
||||
readonly compact: (input: AutoInput) => Effect.Effect<Outcome>
|
||||
@@ -326,14 +326,14 @@ export const layer = Layer.effect(
|
||||
const bus = yield* Bus.Service
|
||||
const llm = yield* LLMClient.Service
|
||||
|
||||
const state = State.create<Settings, Draft>({
|
||||
const state = State.create<Settings, Editor>({
|
||||
name: "session-compaction",
|
||||
initial: () => ({ auto: true, buffer: DEFAULT_BUFFER, tokens: DEFAULT_KEEP_TOKENS }),
|
||||
draft: (draft) => ({
|
||||
editor: (editor) => ({
|
||||
configure: (settings) => {
|
||||
if (settings.auto !== undefined) draft.auto = settings.auto
|
||||
if (settings.buffer !== undefined) draft.buffer = settings.buffer
|
||||
if (settings.tokens !== undefined) draft.tokens = settings.tokens
|
||||
if (settings.auto !== undefined) editor.auto = settings.auto
|
||||
if (settings.buffer !== undefined) editor.buffer = settings.buffer
|
||||
if (settings.tokens !== undefined) editor.tokens = settings.tokens
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -37,7 +37,7 @@ type Data = {
|
||||
shell?: string
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
configure: (shell: string) => void
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export type ResolveInput = {
|
||||
priority: "config" | "compat"
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly resolve: (input: ResolveInput) => Effect.Effect<string>
|
||||
}
|
||||
|
||||
@@ -201,12 +201,12 @@ const layer = (options?: Options) =>
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const global = yield* Global.Service
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "shell-select",
|
||||
initial: () => ({}),
|
||||
draft: (draft) => ({
|
||||
editor: (editor) => ({
|
||||
configure: (shell) => {
|
||||
draft.shell = shell
|
||||
editor.shell = shell
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -72,14 +72,14 @@ export type Data = {
|
||||
skills: Map<ID, Types.DeepMutable<Info>>
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
list: () => readonly Types.DeepMutable<Info>[]
|
||||
add: (skill: Info) => void
|
||||
update: (id: string, update: (skill: Types.DeepMutable<Info>) => void) => void
|
||||
remove: (id: string) => void
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
||||
readonly list: () => Effect.Effect<Info[]>
|
||||
}
|
||||
@@ -91,22 +91,22 @@ const layer = Layer.effect(
|
||||
Effect.gen(function* () {
|
||||
const bus = yield* Bus.Service
|
||||
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "skill",
|
||||
initial: () => ({ skills: new Map() }),
|
||||
draft: (draft) => ({
|
||||
list: () => Array.from(draft.skills.values()),
|
||||
editor: (editor) => ({
|
||||
list: () => Array.from(editor.skills.values()),
|
||||
add: (skill) => {
|
||||
draft.skills.set(skill.id, { ...skill } as Types.DeepMutable<Info>)
|
||||
editor.skills.set(skill.id, { ...skill } as Types.DeepMutable<Info>)
|
||||
},
|
||||
update: (id, update) => {
|
||||
const current = draft.skills.get(ID.make(id))
|
||||
const current = editor.skills.get(ID.make(id))
|
||||
if (!current) return
|
||||
update(current)
|
||||
current.id = ID.make(id)
|
||||
},
|
||||
remove: (id) => {
|
||||
draft.skills.delete(ID.make(id))
|
||||
editor.skills.delete(ID.make(id))
|
||||
},
|
||||
}),
|
||||
notify: () => bus.publish(Skill.Event.Updated, {}).pipe(Effect.asVoid),
|
||||
|
||||
@@ -36,11 +36,11 @@ export interface RestoreInput {
|
||||
readonly files: ReadonlyMap<RelativePath, ID>
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
configure: (enabled: boolean) => void
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
/**
|
||||
* Capture the current Location-scoped filesystem state as a content-addressed
|
||||
* tree. Returns `undefined` when snapshots are disabled, unsupported, or the
|
||||
@@ -77,12 +77,12 @@ const layer = Layer.effect(
|
||||
const global = yield* Global.Service
|
||||
const location = yield* Location.Service
|
||||
const lifetime = yield* Scope.Scope
|
||||
const state = State.create<{ enabled: boolean }, Draft>({
|
||||
const state = State.create<{ enabled: boolean }, Editor>({
|
||||
name: "snapshot",
|
||||
initial: () => ({ enabled: true }),
|
||||
draft: (draft) => ({
|
||||
editor: (editor) => ({
|
||||
configure: (enabled) => {
|
||||
draft.enabled = enabled
|
||||
editor.enabled = enabled
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
+15
-15
@@ -5,11 +5,11 @@ import { Cause, Context, Effect, Exit, Fiber, Scope } from "effect"
|
||||
/**
|
||||
* A synchronous, replayable edit to the current domain state.
|
||||
*
|
||||
* Domain drafts expose readable and writable state while preserving concise
|
||||
* Domain editors expose readable and writable state while preserving concise
|
||||
* plugin/config code. Transforms synchronously rebuild derived state.
|
||||
*/
|
||||
type TransformCallback<DraftApi> = (draft: DraftApi) => void
|
||||
export type MakeDraft<State, DraftApi> = (state: State) => DraftApi
|
||||
type TransformCallback<Editor> = (editor: Editor) => void
|
||||
export type MakeEditor<State, Editor> = (state: State) => Editor
|
||||
|
||||
export interface Registration {
|
||||
readonly dispose: Effect.Effect<void>
|
||||
@@ -19,15 +19,15 @@ export interface Registration {
|
||||
* Registers a scoped transform. Reads rebuild by applying every registered transform in order.
|
||||
* Closing the owning Scope removes the transform and invalidates the current value.
|
||||
*/
|
||||
export type Transform<DraftApi> = (
|
||||
transform: TransformCallback<DraftApi>,
|
||||
export type Transform<Editor> = (
|
||||
transform: TransformCallback<Editor>,
|
||||
) => Effect.Effect<Registration, never, Scope.Scope>
|
||||
|
||||
/** Invalidates the current value after captured inputs change and notifies like a registration would. */
|
||||
export type Reload = () => Effect.Effect<void>
|
||||
|
||||
export interface Transformable<DraftApi> {
|
||||
readonly transform: Transform<DraftApi>
|
||||
export interface Transformable<Editor> {
|
||||
readonly transform: Transform<Editor>
|
||||
readonly reload: Reload
|
||||
}
|
||||
|
||||
@@ -88,12 +88,12 @@ export const inherit = Effect.fnUntraced(function* () {
|
||||
return <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.provideService(effect, CurrentBatch, batch)
|
||||
})
|
||||
|
||||
export interface Options<State, DraftApi> {
|
||||
export interface Options<State, Editor> {
|
||||
readonly name?: string
|
||||
/** Creates the empty base value for every rebuild. */
|
||||
readonly initial: () => State
|
||||
/** Wraps mutable state in a domain-specific draft API. */
|
||||
readonly draft: MakeDraft<State, DraftApi>
|
||||
/** Wraps mutable state in a domain-specific editor API. */
|
||||
readonly editor: MakeEditor<State, Editor>
|
||||
/**
|
||||
* Observes the freshly rebuilt value outside the read path. Every registration, disposal, or
|
||||
* reload notifies once it is applied; a batch coalesces them into one notification at its end.
|
||||
@@ -102,7 +102,7 @@ export interface Options<State, DraftApi> {
|
||||
readonly notify?: (state: State) => Effect.Effect<void>
|
||||
}
|
||||
|
||||
export interface Interface<State, DraftApi> extends Transformable<DraftApi> {
|
||||
export interface Interface<State, Editor> extends Transformable<Editor> {
|
||||
/**
|
||||
* Rebuilds synchronously when transforms changed since the last read. Each rebuild produces a new
|
||||
* value and never touches earlier ones, so callers may retain what they read.
|
||||
@@ -110,17 +110,17 @@ export interface Interface<State, DraftApi> extends Transformable<DraftApi> {
|
||||
readonly get: () => State
|
||||
}
|
||||
|
||||
export function create<State, DraftApi>(options: Options<State, DraftApi>): Interface<State, DraftApi> {
|
||||
export function create<State, Editor>(options: Options<State, Editor>): Interface<State, Editor> {
|
||||
let state = options.initial()
|
||||
const transforms: { run: TransformCallback<DraftApi> }[] = []
|
||||
const transforms: { run: TransformCallback<Editor> }[] = []
|
||||
let dirty = false
|
||||
let closed = false
|
||||
|
||||
const get = () => {
|
||||
if (closed || !dirty) return state
|
||||
const next = options.initial()
|
||||
const draft = options.draft(next)
|
||||
for (const transform of transforms) transform.run(draft)
|
||||
const editor = options.editor(next)
|
||||
for (const transform of transforms) transform.run(editor)
|
||||
// Only a complete fold becomes visible; a throwing callback leaves the previous value and stays dirty.
|
||||
state = next
|
||||
dirty = false
|
||||
|
||||
@@ -22,11 +22,11 @@ type Limits = {
|
||||
maxBytes: number
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
configure: (limits: Partial<Limits>) => void
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly truncate: (result: Result) => Effect.Effect<Result>
|
||||
readonly cleanup: () => Effect.Effect<void>
|
||||
}
|
||||
@@ -51,13 +51,13 @@ const layer = Layer.effect(
|
||||
const fs = yield* FSUtil.Service
|
||||
const global = yield* Global.Service
|
||||
const directory = path.join(global.data, DIRECTORY)
|
||||
const state = State.create<Limits, Draft>({
|
||||
const state = State.create<Limits, Editor>({
|
||||
name: "tool-output",
|
||||
initial: () => ({ maxLines: MAX_LINES, maxBytes: MAX_BYTES }),
|
||||
draft: (draft) => ({
|
||||
editor: (editor) => ({
|
||||
configure: (limits) => {
|
||||
if (limits.maxLines !== undefined) draft.maxLines = limits.maxLines
|
||||
if (limits.maxBytes !== undefined) draft.maxBytes = limits.maxBytes
|
||||
if (limits.maxLines !== undefined) editor.maxLines = limits.maxLines
|
||||
if (limits.maxBytes !== undefined) editor.maxBytes = limits.maxBytes
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
+14
-14
@@ -23,7 +23,7 @@ export class RegistrationError extends Schema.TaggedError<RegistrationError>()("
|
||||
message: Schema.String,
|
||||
}) {}
|
||||
|
||||
export interface Draft {
|
||||
export interface Editor {
|
||||
readonly list: () => readonly (Tool.Info & { readonly id: string })[]
|
||||
readonly get: (id: string) => (Tool.Info & { readonly id: string }) | undefined
|
||||
readonly namespace: (namespace: Tool.Namespace) => void
|
||||
@@ -38,7 +38,7 @@ type Data = {
|
||||
errors: { kind: "tool" | "namespace"; name: string; namespace?: string; error: RegistrationError }[]
|
||||
}
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly snapshot: (permissions?: Permission.Ruleset) => Effect.Effect<Snapshot>
|
||||
}
|
||||
|
||||
@@ -149,35 +149,35 @@ const layer = Layer.effect(
|
||||
}
|
||||
})
|
||||
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "tool",
|
||||
initial: () => ({
|
||||
tools: new Map(),
|
||||
namespaces: new Map(),
|
||||
errors: [],
|
||||
}),
|
||||
draft: (draft) => ({
|
||||
list: () => Array.from(draft.tools.values()),
|
||||
get: (id) => draft.tools.get(id),
|
||||
editor: (editor) => ({
|
||||
list: () => Array.from(editor.tools.values()),
|
||||
get: (id) => editor.tools.get(id),
|
||||
namespace: (namespace) => {
|
||||
const error = namespaceError(namespace.name)
|
||||
if (error) {
|
||||
draft.errors.push({ kind: "namespace", name: namespace.name, namespace: namespace.name, error })
|
||||
editor.errors.push({ kind: "namespace", name: namespace.name, namespace: namespace.name, error })
|
||||
return
|
||||
}
|
||||
draft.namespaces.set(namespace.name, { ...namespace })
|
||||
editor.namespaces.set(namespace.name, { ...namespace })
|
||||
},
|
||||
add: (tool) => {
|
||||
const error = registrationError(tool)
|
||||
if (error) {
|
||||
draft.errors.push({ kind: "tool", name: tool.name, namespace: tool.options?.namespace, error })
|
||||
editor.errors.push({ kind: "tool", name: tool.name, namespace: tool.options?.namespace, error })
|
||||
return
|
||||
}
|
||||
const id = effectiveName(tool)
|
||||
draft.tools.set(id, { ...tool, id, options: tool.options && { ...tool.options } })
|
||||
editor.tools.set(id, { ...tool, id, options: tool.options && { ...tool.options } })
|
||||
},
|
||||
update: (id, update) => {
|
||||
const current = draft.tools.get(id)
|
||||
const current = editor.tools.get(id)
|
||||
if (!current) return
|
||||
const tool = { ...current, options: current.options && { ...current.options } }
|
||||
update(tool)
|
||||
@@ -187,13 +187,13 @@ const layer = Layer.effect(
|
||||
tool.options = { ...tool.options, namespace: current.options?.namespace }
|
||||
const error = registrationError(tool)
|
||||
if (error) {
|
||||
draft.errors.push({ kind: "tool", name: tool.name, namespace: tool.options?.namespace, error })
|
||||
editor.errors.push({ kind: "tool", name: tool.name, namespace: tool.options?.namespace, error })
|
||||
return
|
||||
}
|
||||
draft.tools.set(id, tool)
|
||||
editor.tools.set(id, tool)
|
||||
},
|
||||
remove: (id) => {
|
||||
draft.tools.delete(id)
|
||||
editor.tools.delete(id)
|
||||
},
|
||||
}),
|
||||
notify: (value) =>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## Representations
|
||||
|
||||
- Plugin authors get schema-derived input types at the `ToolDraft.add` boundary through `Tool`.
|
||||
- Plugin authors get schema-derived input types at the `ToolEditor.add` boundary through `Tool`.
|
||||
- The heterogeneous Core registry deliberately erases registered definitions to `Tool.Info`. Use `any` at this internal boundary; do not replace it with `unknown`, JSON-value plumbing, casts, or compiled wrapper types solely to preserve type safety after registration.
|
||||
- Executors return model content and metadata alongside declared machine output. Shipped built-ins and plugin tools use the same runtime shape after registration.
|
||||
- `src/tool.ts` stores canonical Location registrations, derives LLM definitions, executes tools, and normalizes model content and images.
|
||||
@@ -30,11 +30,11 @@ Leaves own resolution, permission, and side-effect ordering. Translate only expe
|
||||
|
||||
## Registration
|
||||
|
||||
Built-ins, plugins, and MCP install tools through `Tool.Service.transform`, adding complete tool objects to the draft. A tool may provide a namespace, which flattens direct model names to `<namespace>_<tool>`, and defaults into CodeMode (`codemode` defaults true; `codemode: false` keeps the tool on the provider's native tool list).
|
||||
Built-ins, plugins, and MCP install tools through `Tool.Service.transform`, adding complete tool objects to the editor. A tool may provide a namespace, which flattens direct model names to `<namespace>_<tool>`, and defaults into CodeMode (`codemode` defaults true; `codemode: false` keeps the tool on the provider's native tool list).
|
||||
|
||||
Namespace descriptions are registered once through `draft.namespace(...)`. Tool options continue to reference the namespace by string name; an unregistered namespace remains valid and simply has no namespace description.
|
||||
Namespace descriptions are registered once through `editor.namespace(...)`. Tool options continue to reference the namespace by string name; an unregistered namespace remains valid and simply has no namespace description.
|
||||
|
||||
The service uses shared `State` to replay synchronous transforms in registration order against a fresh draft. `Tool.Service.reload()` rebuilds from captured source data without changing registration precedence. Registrations are scoped and return a real, idempotent `dispose` Effect:
|
||||
The service uses shared `State` to replay synchronous transforms in registration order against a fresh editor. `Tool.Service.reload()` rebuilds from captured source data without changing registration precedence. Registrations are scoped and return a real, idempotent `dispose` Effect:
|
||||
|
||||
- The latest valid active registration for the same effective name wins.
|
||||
- `update` and `remove` target effective names and do nothing for missing tools. Updates preserve the name and namespace; invalid updates leave the previous definition intact. Creating a tool requires `add`.
|
||||
|
||||
@@ -38,10 +38,10 @@ export const layer = Layer.effect(
|
||||
.withPermit(
|
||||
Effect.gen(function* () {
|
||||
discovered = yield* mcp.tools()
|
||||
yield* tools.transform((draft) => {
|
||||
yield* tools.transform((editor) => {
|
||||
for (const tool of discovered) {
|
||||
const schema = (tool.inputSchema ?? {}) as JsonSchema.JsonSchema
|
||||
draft.add({
|
||||
editor.add({
|
||||
name: tool.name,
|
||||
options: { namespace: namespace(tool.server), codemode: tool.codemode !== false },
|
||||
description: tool.description ?? "",
|
||||
|
||||
@@ -117,8 +117,8 @@ export const Plugin = {
|
||||
const permission = yield* Permission.Service
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false, permission: "edit" },
|
||||
description:
|
||||
|
||||
@@ -52,8 +52,8 @@ export const Plugin = {
|
||||
const permission = yield* Permission.Service
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false },
|
||||
description: 'Search file paths using a glob pattern (examples: "**/*.ts", "src/**/*.tsx").',
|
||||
|
||||
@@ -71,8 +71,8 @@ export const Plugin = {
|
||||
const permission = yield* Permission.Service
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false },
|
||||
description:
|
||||
|
||||
@@ -74,8 +74,8 @@ export const Plugin = {
|
||||
const permission = yield* Permission.Service
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false, permission: "edit" },
|
||||
description: DESCRIPTION,
|
||||
|
||||
@@ -52,8 +52,8 @@ export const Plugin = {
|
||||
const permission = yield* Permission.Service
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false },
|
||||
description,
|
||||
|
||||
@@ -38,8 +38,8 @@ export const Plugin = {
|
||||
const location = yield* Location.Service
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false },
|
||||
description:
|
||||
|
||||
@@ -196,8 +196,8 @@ export const Plugin = {
|
||||
)
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false },
|
||||
description: description(),
|
||||
|
||||
@@ -36,8 +36,8 @@ export const Plugin = {
|
||||
const skills = yield* Skill.Service
|
||||
const permission = yield* Permission.Service
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false },
|
||||
description,
|
||||
|
||||
@@ -98,8 +98,8 @@ export const Plugin = {
|
||||
})
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false },
|
||||
description,
|
||||
|
||||
@@ -107,8 +107,8 @@ export const Plugin = {
|
||||
const permission = yield* Permission.Service
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false },
|
||||
description,
|
||||
|
||||
@@ -32,8 +32,8 @@ export const Plugin = {
|
||||
const websearch = yield* WebSearch.Service
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false },
|
||||
description,
|
||||
|
||||
@@ -53,8 +53,8 @@ export const Plugin = {
|
||||
const permission = yield* Permission.Service
|
||||
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name,
|
||||
options: { codemode: false, permission: "edit" },
|
||||
description:
|
||||
|
||||
@@ -2,7 +2,7 @@ export * as Vcs from "./vcs.js"
|
||||
|
||||
import path from "path"
|
||||
import { Cause, Context, Effect, FiberSet, Layer, Schema, Semaphore, Stream } from "effect"
|
||||
import type { VcsDefinition, VcsDraft } from "@opencode-ai/plugin/effect/vcs"
|
||||
import type { VcsDefinition, VcsEditor } from "@opencode-ai/plugin/effect/vcs"
|
||||
import { FileDiff } from "@opencode-ai/schema/file-diff"
|
||||
import { FileSystem } from "@opencode-ai/schema/filesystem"
|
||||
import { Base, BranchList, FileStatus, Info, Mode } from "@opencode-ai/schema/vcs"
|
||||
@@ -38,7 +38,7 @@ export interface Adapter {
|
||||
readonly diff: (mode: Mode, options?: DiffOptions) => Effect.Effect<FileDiff.Info[], DiffError>
|
||||
}
|
||||
|
||||
export interface Interface extends Adapter, State.Transformable<VcsDraft> {
|
||||
export interface Interface extends Adapter, State.Transformable<VcsEditor> {
|
||||
readonly base: () => Effect.Effect<Base | null, DiffError>
|
||||
}
|
||||
|
||||
@@ -71,14 +71,14 @@ const layer = Layer.effect(
|
||||
const decodeBranches = Schema.decodeUnknownEffect(BranchList)
|
||||
const decodeStatus = Schema.decodeUnknownEffect(Schema.Array(FileStatus))
|
||||
const decodeDiff = Schema.decodeUnknownEffect(Schema.Array(FileDiff.Info))
|
||||
const state: State.Interface<Data, VcsDraft> = State.create<Data, VcsDraft>({
|
||||
const state: State.Interface<Data, VcsEditor> = State.create<Data, VcsEditor>({
|
||||
name: "vcs",
|
||||
initial: () => ({ providers: new Map() }),
|
||||
draft: (draft) => ({
|
||||
add: (provider) => draft.providers.set(provider.id, provider),
|
||||
editor: (editor) => ({
|
||||
add: (provider) => editor.providers.set(provider.id, provider),
|
||||
default: {
|
||||
get: () => draft.selection,
|
||||
set: (selection) => (draft.selection = selection),
|
||||
get: () => editor.selection,
|
||||
set: (selection) => (editor.selection = selection),
|
||||
},
|
||||
}),
|
||||
notify: () => State.reconcile(root, fork, () => refresh()),
|
||||
|
||||
@@ -51,7 +51,7 @@ export class RequestError extends Schema.TaggedError<RequestError>()("WebSearch.
|
||||
|
||||
export type Error = ProviderRequiredError | ProviderNotFoundError | DisabledError | RequestError
|
||||
|
||||
export interface Interface extends State.Transformable<Draft> {
|
||||
export interface Interface extends State.Transformable<Editor> {
|
||||
readonly providers: () => Effect.Effect<readonly Provider[]>
|
||||
readonly default: () => Effect.Effect<Provider | undefined, DisabledError>
|
||||
readonly select: (selection: Selection) => Effect.Effect<void>
|
||||
@@ -65,7 +65,7 @@ type Data = {
|
||||
selection?: Selection
|
||||
}
|
||||
|
||||
export type Draft = {
|
||||
export type Editor = {
|
||||
add: (provider: ProviderImplementation) => void
|
||||
default: {
|
||||
get: () => Selection | undefined
|
||||
@@ -79,13 +79,13 @@ const layer = Layer.effect(
|
||||
const bus = yield* Bus.Service
|
||||
const kv = yield* KV.Service
|
||||
const decodeResults = Schema.decodeUnknownEffect(Schema.Array(Result))
|
||||
const state = State.create<Data, Draft>({
|
||||
const state = State.create<Data, Editor>({
|
||||
initial: () => ({ providers: new Map() }),
|
||||
draft: (draft) => ({
|
||||
add: (provider) => draft.providers.set(provider.id, provider),
|
||||
editor: (editor) => ({
|
||||
add: (provider) => editor.providers.set(provider.id, provider),
|
||||
default: {
|
||||
get: () => draft.selection,
|
||||
set: (selection) => (draft.selection = selection),
|
||||
get: () => editor.selection,
|
||||
set: (selection) => (editor.selection = selection),
|
||||
},
|
||||
}),
|
||||
notify: () => bus.publish(WebSearch.Event.Updated, {}).pipe(Effect.asVoid),
|
||||
|
||||
@@ -11,13 +11,13 @@ export const Plugin = define({
|
||||
const bus = yield* Bus.Service
|
||||
const wellknown = yield* WellKnown.Service
|
||||
yield* wellknown.entries().pipe(Effect.orDie)
|
||||
yield* ctx.integration.transform((draft) => {
|
||||
yield* ctx.integration.transform((editor) => {
|
||||
wellknown.snapshot().forEach((entry) => {
|
||||
if (!entry.manifest.auth) return
|
||||
draft.update(entry.integrationID, (integration) => {
|
||||
editor.update(entry.integrationID, (integration) => {
|
||||
integration.name = new URL(entry.origin).hostname
|
||||
})
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: entry.integrationID,
|
||||
method: {
|
||||
id: "login",
|
||||
|
||||
@@ -38,8 +38,8 @@ describe("Catalog", () => {
|
||||
const catalog = yield* Catalog.Service
|
||||
const providerID = Provider.ID.make("resolve-fixture")
|
||||
const modelID = Model.ID.make("fixture-model")
|
||||
yield* catalog.transform((draft) =>
|
||||
draft.model.update(providerID, modelID, (model) => {
|
||||
yield* catalog.transform((editor) =>
|
||||
editor.model.update(providerID, modelID, (model) => {
|
||||
model.package = path === "aisdk" ? Provider.aisdk("@ai-sdk/fixture") : "@opencode-ai/ai/providers/openai"
|
||||
model.settings = {
|
||||
apiKey: path === "empty-key" ? "" : "fixture-key",
|
||||
@@ -62,8 +62,8 @@ describe("Catalog", () => {
|
||||
},
|
||||
)
|
||||
|
||||
yield* catalog.transform((draft) =>
|
||||
draft.model.update(providerID, modelID, (model) => {
|
||||
yield* catalog.transform((editor) =>
|
||||
editor.model.update(providerID, modelID, (model) => {
|
||||
model.limit.context = 100_000
|
||||
model.capabilities.tools = false
|
||||
model.variants.push({ id: Model.VariantID.make("other") })
|
||||
@@ -323,7 +323,7 @@ describe("Catalog", () => {
|
||||
const providerID = Provider.ID.make("test")
|
||||
const old = Model.ID.make("old")
|
||||
const newest = Model.ID.make("new")
|
||||
const models = (catalog: Catalog.Draft) => {
|
||||
const models = (catalog: Catalog.Editor) => {
|
||||
catalog.provider.update(providerID, () => {})
|
||||
catalog.model.update(providerID, old, (model) => {
|
||||
model.time.released = 1000
|
||||
|
||||
@@ -10,9 +10,9 @@ describe("CodeMode", () => {
|
||||
it.effect("owns registrations, execute, and catalog materialization", () =>
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tool.Service
|
||||
yield* tools.transform((draft) => {
|
||||
draft.namespace({ name: "empty", description: "No tools registered yet" })
|
||||
draft.add({
|
||||
yield* tools.transform((editor) => {
|
||||
editor.namespace({ name: "empty", description: "No tools registered yet" })
|
||||
editor.add({
|
||||
name: "echo",
|
||||
description: "Echo text",
|
||||
input: Schema.Struct({ text: Schema.String }),
|
||||
|
||||
@@ -94,10 +94,10 @@ describe("CodeModeInstructions", () => {
|
||||
const tools = yield* Tool.Service
|
||||
const initialized = yield* Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
yield* tools.transform((draft) => {
|
||||
draft.namespace({ name: "tools", description: "Project utilities" })
|
||||
draft.add({ ...zeta, options: { namespace: "tools" } })
|
||||
draft.add({ ...alpha, options: { namespace: "tools" } })
|
||||
yield* tools.transform((editor) => {
|
||||
editor.namespace({ name: "tools", description: "Project utilities" })
|
||||
editor.add({ ...zeta, options: { namespace: "tools" } })
|
||||
editor.add({ ...alpha, options: { namespace: "tools" } })
|
||||
})
|
||||
const snapshot = yield* tools.snapshot()
|
||||
return yield* readInitial(CodeModeInstructions.make(snapshot.codeModeCatalog))
|
||||
@@ -105,10 +105,10 @@ describe("CodeModeInstructions", () => {
|
||||
)
|
||||
const reordered = yield* Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
yield* tools.transform((draft) => {
|
||||
draft.namespace({ name: "tools", description: "Project utilities" })
|
||||
draft.add({ ...alpha, options: { namespace: "tools" } })
|
||||
draft.add({ ...zeta, options: { namespace: "tools" } })
|
||||
yield* tools.transform((editor) => {
|
||||
editor.namespace({ name: "tools", description: "Project utilities" })
|
||||
editor.add({ ...alpha, options: { namespace: "tools" } })
|
||||
editor.add({ ...zeta, options: { namespace: "tools" } })
|
||||
})
|
||||
const snapshot = yield* tools.snapshot()
|
||||
return yield* readUpdate(CodeModeInstructions.make(snapshot.codeModeCatalog), initialized)
|
||||
|
||||
@@ -12,8 +12,8 @@ describe("Command", () => {
|
||||
Effect.gen(function* () {
|
||||
const command = yield* Command.Service
|
||||
const calls: Command.Invocation[] = []
|
||||
yield* command.transform((draft) => {
|
||||
draft.add({
|
||||
yield* command.transform((editor) => {
|
||||
editor.add({
|
||||
name: "goal",
|
||||
description: "Manage the session goal",
|
||||
execute: (input) => Effect.sync(() => calls.push(input)),
|
||||
@@ -36,9 +36,9 @@ describe("Command", () => {
|
||||
it.effect("replaces commands with later definitions", () =>
|
||||
Effect.gen(function* () {
|
||||
const command = yield* Command.Service
|
||||
yield* command.transform((draft) => {
|
||||
draft.add({ name: "goal", description: "First", execute: () => Effect.void })
|
||||
draft.add({ name: "goal", description: "Second", execute: () => Effect.void })
|
||||
yield* command.transform((editor) => {
|
||||
editor.add({ name: "goal", description: "First", execute: () => Effect.void })
|
||||
editor.add({ name: "goal", description: "Second", execute: () => Effect.void })
|
||||
})
|
||||
|
||||
expect(yield* command.list()).toEqual([Command.Info.make({ name: "goal", description: "Second" })])
|
||||
@@ -48,8 +48,8 @@ describe("Command", () => {
|
||||
it.effect("returns callback error messages without stack traces", () =>
|
||||
Effect.gen(function* () {
|
||||
const command = yield* Command.Service
|
||||
yield* command.transform((draft) => {
|
||||
draft.add({
|
||||
yield* command.transform((editor) => {
|
||||
editor.add({
|
||||
name: "fail",
|
||||
execute: () => Effect.fail(new Error("command failed")),
|
||||
})
|
||||
|
||||
@@ -92,11 +92,11 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
const providerID = Provider.ID.make("custom")
|
||||
const inheritedID = Model.ID.make("inherited")
|
||||
const overriddenID = Model.ID.make("overridden")
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.model.update(providerID, inheritedID, (model) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.model.update(providerID, inheritedID, (model) => {
|
||||
model.capabilities = { tools: false, input: ["text"], output: ["text"] }
|
||||
})
|
||||
draft.model.update(providerID, overriddenID, (model) => {
|
||||
editor.model.update(providerID, overriddenID, (model) => {
|
||||
model.capabilities = { tools: false, input: ["text"], output: ["text"] }
|
||||
})
|
||||
})
|
||||
@@ -273,8 +273,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
const catalog = yield* Catalog.Service
|
||||
const providerID = Provider.ID.make("custom")
|
||||
const modelID = Model.ID.make("chat")
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.model.update(providerID, modelID, (model) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.model.update(providerID, modelID, (model) => {
|
||||
model.package = "aisdk:@ai-sdk/anthropic"
|
||||
model.settings = { baseURL: "https://catalog.example/v1" }
|
||||
model.capabilities = { tools: false, input: ["audio"], output: ["audio"] }
|
||||
@@ -480,11 +480,11 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
}),
|
||||
]
|
||||
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.provider.update(Provider.ID.anthropic, (provider) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.provider.update(Provider.ID.anthropic, (provider) => {
|
||||
provider.package = "aisdk:@ai-sdk/anthropic"
|
||||
})
|
||||
draft.model.update(Provider.ID.anthropic, modelID, (model) => {
|
||||
editor.model.update(Provider.ID.anthropic, modelID, (model) => {
|
||||
model.variants = [{ id: Model.VariantID.make("fast"), settings: { effort: "high" } }]
|
||||
})
|
||||
})
|
||||
|
||||
@@ -39,8 +39,8 @@ describe("config plugin reloads", () => {
|
||||
const plugins = yield* Plugin.Service
|
||||
const references = yield* Reference.Service
|
||||
const host = yield* PluginHost.make(plugins)
|
||||
yield* references.transform((draft) =>
|
||||
draft.add(
|
||||
yield* references.transform((editor) =>
|
||||
editor.add(
|
||||
"external",
|
||||
Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/references/external") }),
|
||||
),
|
||||
|
||||
@@ -20,8 +20,8 @@ describe("ConfigWebSearchPlugin.Plugin", () => {
|
||||
const bus = yield* Bus.Service
|
||||
const config = yield* Config.Test
|
||||
const plugins = yield* Plugin.Service
|
||||
yield* websearch.transform((draft) =>
|
||||
draft.add({ id: WebSearch.ID.make("test"), name: "Test", execute: () => Effect.succeed([]) }),
|
||||
yield* websearch.transform((editor) =>
|
||||
editor.add({ id: WebSearch.ID.make("test"), name: "Test", execute: () => Effect.succeed([]) }),
|
||||
)
|
||||
yield* ConfigWebSearchPlugin.Plugin.effect(yield* PluginHost.make(plugins))
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ describe("LocationWatcherPolicy", () => {
|
||||
|
||||
yield* State.batch(
|
||||
Effect.gen(function* () {
|
||||
yield* policy.transform((draft) => draft.add(["node_modules"]))
|
||||
yield* policy.transform((editor) => editor.add(["node_modules"]))
|
||||
expect(policy.current()).toEqual(["node_modules"])
|
||||
const overlay = yield* policy.transform((draft) => draft.add([".git"]))
|
||||
const overlay = yield* policy.transform((editor) => editor.add([".git"]))
|
||||
expect(policy.current()).toEqual(["node_modules", ".git"])
|
||||
expect(observed).toEqual([])
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("LocationWatcherPolicy", () => {
|
||||
Effect.gen(function* () {
|
||||
if (reentered) return
|
||||
reentered = true
|
||||
yield* policy.transform((draft) => draft.add([".git"])).pipe(Scope.provide(scope))
|
||||
yield* policy.transform((editor) => editor.add([".git"])).pipe(Scope.provide(scope))
|
||||
expect(policy.current()).toEqual(["node_modules", ".git"])
|
||||
}),
|
||||
)
|
||||
@@ -58,7 +58,7 @@ describe("LocationWatcherPolicy", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
yield* policy.transform((draft) => draft.add(["node_modules"]))
|
||||
yield* policy.transform((editor) => editor.add(["node_modules"]))
|
||||
|
||||
expect(policy.current()).toEqual(["node_modules", ".git"])
|
||||
expect(observed).toEqual([
|
||||
|
||||
@@ -306,7 +306,7 @@ describe("LocationWatcher subscriptions", () => {
|
||||
layer: Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const policy = yield* LocationWatcherPolicy.Service
|
||||
yield* policy.transform((draft) => draft.add([".git"]))
|
||||
yield* policy.transform((editor) => editor.add([".git"]))
|
||||
}),
|
||||
),
|
||||
deps: [LocationWatcherPolicy.node],
|
||||
|
||||
@@ -198,9 +198,9 @@ describe("Formatter", () => {
|
||||
withFormatter(false, (formatter, directory) =>
|
||||
Effect.gen(function* () {
|
||||
const command = { suffix: "A" }
|
||||
yield* formatter.transform((draft) => {
|
||||
yield* formatter.transform((editor) => {
|
||||
const suffix = command.suffix
|
||||
draft.set({
|
||||
editor.set({
|
||||
name: "reload",
|
||||
extensions: [".reload"],
|
||||
enabled: Effect.succeed([
|
||||
@@ -230,7 +230,7 @@ describe("Formatter", () => {
|
||||
const resolving = yield* Deferred.make<void>()
|
||||
const release = yield* Deferred.make<void>()
|
||||
const command = { suffix: "A" }
|
||||
yield* formatter.transform((draft) => {
|
||||
yield* formatter.transform((editor) => {
|
||||
const suffix = command.suffix
|
||||
const resolved = [
|
||||
process.execPath,
|
||||
@@ -238,7 +238,7 @@ describe("Formatter", () => {
|
||||
`const fs = require('fs'); const file = process.argv.at(-1); fs.appendFileSync(file, '${suffix}')`,
|
||||
"$FILE",
|
||||
]
|
||||
draft.set({
|
||||
editor.set({
|
||||
name: "reload-race",
|
||||
extensions: [".race"],
|
||||
enabled:
|
||||
|
||||
@@ -79,15 +79,15 @@ describe("InstructionDiscovery", () => {
|
||||
it.effect("stores ordered values with last-write-wins precedence", () =>
|
||||
Effect.gen(function* () {
|
||||
const discovery = yield* InstructionDiscovery.Service
|
||||
yield* discovery.transform((draft) => {
|
||||
draft.add(file("/repo/AGENTS.md", "first"))
|
||||
draft.add(file("/repo/packages/AGENTS.md", "package"))
|
||||
draft.add(file("/repo/AGENTS.md", "last"))
|
||||
draft.update("/repo/packages/AGENTS.md", (current) => {
|
||||
yield* discovery.transform((editor) => {
|
||||
editor.add(file("/repo/AGENTS.md", "first"))
|
||||
editor.add(file("/repo/packages/AGENTS.md", "package"))
|
||||
editor.add(file("/repo/AGENTS.md", "last"))
|
||||
editor.update("/repo/packages/AGENTS.md", (current) => {
|
||||
current.content = "updated"
|
||||
current.path = AbsolutePath.make("/ignored")
|
||||
})
|
||||
draft.remove("/missing")
|
||||
editor.remove("/missing")
|
||||
})
|
||||
|
||||
expect(yield* discovery.list()).toEqual([
|
||||
@@ -100,7 +100,7 @@ describe("InstructionDiscovery", () => {
|
||||
it.effect("preserves admitted values while the source is unavailable", () =>
|
||||
Effect.gen(function* () {
|
||||
const discovery = yield* InstructionDiscovery.Service
|
||||
yield* discovery.transform((draft) => draft.unavailable())
|
||||
yield* discovery.transform((editor) => editor.unavailable())
|
||||
expect(
|
||||
(yield* readUpdate(
|
||||
yield* discovery.load(),
|
||||
@@ -113,16 +113,16 @@ describe("InstructionDiscovery", () => {
|
||||
it.effect("renders granular instruction updates", () =>
|
||||
Effect.gen(function* () {
|
||||
const discovery = yield* InstructionDiscovery.Service
|
||||
yield* discovery.transform((draft) => {
|
||||
draft.add(file("/global/AGENTS.md", "global"))
|
||||
draft.add(
|
||||
yield* discovery.transform((editor) => {
|
||||
editor.add(file("/global/AGENTS.md", "global"))
|
||||
editor.add(
|
||||
file("/repo/AGENTS.md", ["old", ...Array.from({ length: 20 }, (_, index) => `keep ${index}`)].join("\n")),
|
||||
)
|
||||
})
|
||||
const initial = yield* readInitial(yield* discovery.load())
|
||||
|
||||
yield* discovery.transform((draft) => {
|
||||
draft.update("/repo/AGENTS.md", (current) => {
|
||||
yield* discovery.transform((editor) => {
|
||||
editor.update("/repo/AGENTS.md", (current) => {
|
||||
current.content = ["new", ...Array.from({ length: 20 }, (_, index) => `keep ${index}`)].join("\n")
|
||||
})
|
||||
})
|
||||
@@ -134,9 +134,9 @@ describe("InstructionDiscovery", () => {
|
||||
const rewritten = state({
|
||||
"core/instructions": [{ path: "/repo/AGENTS.md", content: "old one\nold two\nold three\nold four" }],
|
||||
})
|
||||
yield* discovery.transform((draft) => {
|
||||
draft.remove("/global/AGENTS.md")
|
||||
draft.update("/repo/AGENTS.md", (current) => {
|
||||
yield* discovery.transform((editor) => {
|
||||
editor.remove("/global/AGENTS.md")
|
||||
editor.update("/repo/AGENTS.md", (current) => {
|
||||
current.content = "new"
|
||||
})
|
||||
})
|
||||
@@ -144,8 +144,8 @@ describe("InstructionDiscovery", () => {
|
||||
"The instructions changed:\nInstructions from: /repo/AGENTS.md\nnew",
|
||||
)
|
||||
|
||||
yield* discovery.transform((draft) => {
|
||||
draft.add(file("/repo/packages/AGENTS.md", "package"))
|
||||
yield* discovery.transform((editor) => {
|
||||
editor.add(file("/repo/packages/AGENTS.md", "package"))
|
||||
})
|
||||
const structural = (yield* readUpdate(yield* discovery.load(), initial)).text
|
||||
expect(structural).toContain("The instructions from /global/AGENTS.md no longer apply.")
|
||||
|
||||
@@ -1297,8 +1297,8 @@ testEffect(Layer.empty).live(
|
||||
const original = JSON.stringify(entries)
|
||||
yield* Effect.gen(function* () {
|
||||
const service = yield* Mcp.Service
|
||||
const check = yield* service.transform((draft) => {
|
||||
expect(draft.get("resources")).toEqual({
|
||||
const check = yield* service.transform((editor) => {
|
||||
expect(editor.get("resources")).toEqual({
|
||||
type: "local",
|
||||
command: ["later"],
|
||||
disabled: true,
|
||||
@@ -1314,8 +1314,8 @@ testEffect(Layer.empty).live(
|
||||
} satisfies ConfigMCP.Local
|
||||
yield* service.add("resources", runtime)
|
||||
yield* service.reload()
|
||||
yield* service.transform((draft) => {
|
||||
expect(draft.get("resources")).toEqual(runtime)
|
||||
yield* service.transform((editor) => {
|
||||
expect(editor.get("resources")).toEqual(runtime)
|
||||
})
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
@@ -1336,8 +1336,8 @@ testEffect(resourceMcpLayer(new ConfigMCP.Local({ type: "local", command: ["unus
|
||||
|
||||
yield* Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
yield* service.transform((draft) => {
|
||||
draft.set("dynamic", {
|
||||
yield* service.transform((editor) => {
|
||||
editor.set("dynamic", {
|
||||
type: "local",
|
||||
command: [process.execPath, path.join(import.meta.dir, "fixture/mcp-output-schema.ts")],
|
||||
})
|
||||
@@ -1347,12 +1347,12 @@ testEffect(resourceMcpLayer(new ConfigMCP.Local({ type: "local", command: ["unus
|
||||
)
|
||||
expect(yield* service.tools()).toHaveLength(2)
|
||||
|
||||
yield* service.transform((draft) => draft.update("dynamic", (server) => (server.codemode = false)))
|
||||
yield* service.transform((editor) => editor.update("dynamic", (server) => (server.codemode = false)))
|
||||
expect((yield* service.tools()).map((tool) => tool.codemode)).toEqual([false, false])
|
||||
|
||||
const settings = { disabled: true }
|
||||
yield* service.transform((draft) => {
|
||||
draft.update("dynamic", (server) => {
|
||||
yield* service.transform((editor) => {
|
||||
editor.update("dynamic", (server) => {
|
||||
server.disabled = settings.disabled
|
||||
})
|
||||
})
|
||||
@@ -1366,7 +1366,7 @@ testEffect(resourceMcpLayer(new ConfigMCP.Local({ type: "local", command: ["unus
|
||||
)
|
||||
expect(yield* service.tools()).toHaveLength(2)
|
||||
|
||||
const removed = yield* service.transform((draft) => draft.remove("dynamic"))
|
||||
const removed = yield* service.transform((editor) => editor.remove("dynamic"))
|
||||
expect((yield* service.servers()).some((server) => server.name === "dynamic")).toBe(false)
|
||||
expect(yield* service.tools()).toEqual([])
|
||||
|
||||
@@ -1396,14 +1396,14 @@ test("restores runtime MCP config when a transform is disposed", async () => {
|
||||
disabled: true,
|
||||
})
|
||||
yield* service.add("dynamic", config)
|
||||
const transformed = yield* service.transform((draft) =>
|
||||
draft.update("dynamic", (server) => {
|
||||
const transformed = yield* service.transform((editor) =>
|
||||
editor.update("dynamic", (server) => {
|
||||
if (server.type === "remote") server.headers = { Authorization: "transformed" }
|
||||
}),
|
||||
)
|
||||
let observed: string | undefined
|
||||
yield* service.transform((draft) => {
|
||||
const server = draft.get("dynamic")
|
||||
yield* service.transform((editor) => {
|
||||
const server = editor.get("dynamic")
|
||||
observed = server?.type === "remote" ? server.headers?.Authorization : undefined
|
||||
})
|
||||
|
||||
@@ -1432,8 +1432,8 @@ test("isolates nested configured MCP mutations and reconciles them", async () =>
|
||||
Effect.gen(function* () {
|
||||
const service = yield* Mcp.Service
|
||||
expect(published.filter((type) => type === McpEvent.StatusChanged.type)).toHaveLength(1)
|
||||
yield* service.transform((draft) =>
|
||||
draft.update("resources", (server) => {
|
||||
yield* service.transform((editor) =>
|
||||
editor.update("resources", (server) => {
|
||||
if (server.type === "remote" && server.headers) server.headers.Authorization = "transformed"
|
||||
}),
|
||||
)
|
||||
@@ -1452,16 +1452,16 @@ testEffect(Layer.empty).live("batches MCP transforms without connecting intermed
|
||||
const service = yield* Mcp.Service
|
||||
const registrations = yield* State.batch(
|
||||
Effect.gen(function* () {
|
||||
const added = yield* service.transform((draft) =>
|
||||
draft.set("dynamic", {
|
||||
const added = yield* service.transform((editor) =>
|
||||
editor.set("dynamic", {
|
||||
type: "remote",
|
||||
url: server.url,
|
||||
oauth: false,
|
||||
}),
|
||||
)
|
||||
expect((yield* service.servers()).some((server) => server.name === "dynamic")).toBe(false)
|
||||
const disabled = yield* service.transform((draft) =>
|
||||
draft.update("dynamic", (config) => (config.disabled = true)),
|
||||
const disabled = yield* service.transform((editor) =>
|
||||
editor.update("dynamic", (config) => (config.disabled = true)),
|
||||
)
|
||||
return [added, disabled]
|
||||
}),
|
||||
@@ -1587,13 +1587,13 @@ testEffect(Layer.empty).live("keeps MCP config snapshots stable during an in-fli
|
||||
yield* Effect.gen(function* () {
|
||||
const service = yield* Mcp.Service
|
||||
const replacing = yield* service
|
||||
.transform((draft) => draft.update("resources", (config) => (config.disabled = false)))
|
||||
.transform((editor) => editor.update("resources", (config) => (config.disabled = false)))
|
||||
.pipe(Effect.forkScoped({ startImmediately: true }))
|
||||
yield* Deferred.await(started)
|
||||
|
||||
const restoring = yield* State.batch(
|
||||
Effect.gen(function* () {
|
||||
yield* service.transform((draft) => draft.update("resources", (config) => (config.disabled = true)))
|
||||
yield* service.transform((editor) => editor.update("resources", (config) => (config.disabled = true)))
|
||||
yield* Deferred.succeed(accepted, undefined)
|
||||
}),
|
||||
).pipe(Effect.forkScoped({ startImmediately: true }))
|
||||
@@ -1657,9 +1657,9 @@ shutdownIt.effect("discards in-flight and queued MCP notifications after its lay
|
||||
)
|
||||
const source = { url: "https://example.com/initial", added: false }
|
||||
yield* service
|
||||
.transform((draft) => {
|
||||
draft.set("fixture", { type: "remote", url: source.url, oauth: false, disabled: true })
|
||||
if (source.added) draft.set("queued", { type: "local", command: ["unused"], disabled: true })
|
||||
.transform((editor) => {
|
||||
editor.set("fixture", { type: "remote", url: source.url, oauth: false, disabled: true })
|
||||
if (source.added) editor.set("queued", { type: "local", command: ["unused"], disabled: true })
|
||||
})
|
||||
.pipe(Scope.provide(root))
|
||||
|
||||
@@ -1758,8 +1758,8 @@ testEffect(Layer.empty).live("isolates invalid MCP tools and preserves plugin tr
|
||||
"other_lookup",
|
||||
"execute",
|
||||
])
|
||||
const override = yield* registry.transform((draft) => {
|
||||
draft.add({
|
||||
const override = yield* registry.transform((editor) => {
|
||||
editor.add({
|
||||
name: "search",
|
||||
options: { namespace: "demo", codemode: false },
|
||||
description: "Override search",
|
||||
@@ -1768,11 +1768,11 @@ testEffect(Layer.empty).live("isolates invalid MCP tools and preserves plugin tr
|
||||
execute: () => Effect.succeed({ output: "override" }),
|
||||
})
|
||||
})
|
||||
const mutation = yield* registry.transform((draft) => {
|
||||
draft.update("other_lookup", (tool) => {
|
||||
const mutation = yield* registry.transform((editor) => {
|
||||
editor.update("other_lookup", (tool) => {
|
||||
tool.description += " updated"
|
||||
})
|
||||
draft.remove("repaired_lookup")
|
||||
editor.remove("repaired_lookup")
|
||||
})
|
||||
|
||||
yield* Ref.set(catalog, [tool("demo", "y".repeat(65)), ...healthy, tool("demo", "added"), namespace])
|
||||
|
||||
@@ -116,7 +116,7 @@ it.effect("unloading a plugin removes its commands and runs cleanup", () =>
|
||||
cleaned = true
|
||||
}),
|
||||
)
|
||||
yield* ctx.command.transform((draft) => draft.add({ name: "greet", execute: () => Effect.void }))
|
||||
yield* ctx.command.transform((editor) => editor.add({ name: "greet", execute: () => Effect.void }))
|
||||
}),
|
||||
},
|
||||
])
|
||||
@@ -141,7 +141,7 @@ it.effect("reports a failed plugin without blocking a healthy plugin", () =>
|
||||
revision: "1",
|
||||
effect: (ctx) =>
|
||||
ctx.command
|
||||
.transform((draft) => draft.add({ name: "greet", execute: () => Effect.void }))
|
||||
.transform((editor) => editor.add({ name: "greet", execute: () => Effect.void }))
|
||||
.pipe(Effect.asVoid),
|
||||
},
|
||||
])
|
||||
@@ -166,8 +166,8 @@ it.effect("reloading a plugin replaces its command implementation", () =>
|
||||
revision,
|
||||
effect: (ctx) =>
|
||||
ctx.command
|
||||
.transform((draft) =>
|
||||
draft.add({
|
||||
.transform((editor) =>
|
||||
editor.add({
|
||||
name: "greet",
|
||||
execute: () =>
|
||||
Effect.sync(() => {
|
||||
@@ -225,8 +225,8 @@ it.effect("refreshes expired OAuth credentials through the context during activa
|
||||
revision: "1",
|
||||
effect: (ctx) =>
|
||||
Effect.gen(function* () {
|
||||
yield* ctx.integration.transform((draft) =>
|
||||
draft.method.update({
|
||||
yield* ctx.integration.transform((editor) =>
|
||||
editor.method.update({
|
||||
integrationID,
|
||||
method: { id: methodID, type: "oauth", label: "Fixture" },
|
||||
authorize: () => Effect.die("unexpected authorization"),
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Plugin } from "@opencode-ai/plugin"
|
||||
export default Plugin.define({
|
||||
id: "greeting",
|
||||
async setup(ctx) {
|
||||
await ctx.command.transform((draft) =>
|
||||
draft.add({
|
||||
await ctx.command.transform((editor) =>
|
||||
editor.add({
|
||||
name: "greet",
|
||||
description: ctx.options.description,
|
||||
execute: async () => {},
|
||||
|
||||
@@ -193,21 +193,21 @@ export function agentHost(agent: Agent.Interface): Plugin.Context["agent"] {
|
||||
list: () => Effect.die("unused agent.list"),
|
||||
reload: agent.reload,
|
||||
transform: (callback) =>
|
||||
agent.transform((draft) =>
|
||||
agent.transform((editor) =>
|
||||
callback({
|
||||
list: () => draft.list().map(agentInfo),
|
||||
list: () => editor.list().map(agentInfo),
|
||||
get: (id) => {
|
||||
const value = draft.get(Agent.ID.make(id))
|
||||
const value = editor.get(Agent.ID.make(id))
|
||||
return value && agentInfo(value)
|
||||
},
|
||||
default: (id) => draft.default(id === undefined ? undefined : Agent.ID.make(id)),
|
||||
default: (id) => editor.default(id === undefined ? undefined : Agent.ID.make(id)),
|
||||
update: (id, update) =>
|
||||
draft.update(Agent.ID.make(id), (value) => {
|
||||
editor.update(Agent.ID.make(id), (value) => {
|
||||
const current = agentInfo(value)
|
||||
update(current)
|
||||
Object.assign(value, current, { id: Agent.ID.make(current.id) })
|
||||
}),
|
||||
remove: (id) => draft.remove(Agent.ID.make(id)),
|
||||
remove: (id) => editor.remove(Agent.ID.make(id)),
|
||||
}),
|
||||
),
|
||||
}
|
||||
@@ -238,16 +238,16 @@ export function catalogHost(catalog: Catalog.Interface): Plugin.Context["catalog
|
||||
},
|
||||
reload: catalog.reload,
|
||||
transform: (callback) =>
|
||||
catalog.transform((draft) =>
|
||||
catalog.transform((editor) =>
|
||||
callback({
|
||||
provider: {
|
||||
list: () =>
|
||||
draft.provider.list().map((value) => ({
|
||||
editor.provider.list().map((value) => ({
|
||||
provider: providerInfo(value.provider),
|
||||
models: new Map(Array.from(value.models, ([id, model]) => [id, modelInfo(model)])),
|
||||
})),
|
||||
get: (id) => {
|
||||
const value = draft.provider.get(Provider.ID.make(id))
|
||||
const value = editor.provider.get(Provider.ID.make(id))
|
||||
return (
|
||||
value && {
|
||||
provider: providerInfo(value.provider),
|
||||
@@ -256,20 +256,20 @@ export function catalogHost(catalog: Catalog.Interface): Plugin.Context["catalog
|
||||
)
|
||||
},
|
||||
update: (id, update) =>
|
||||
draft.provider.update(Provider.ID.make(id), (value) => {
|
||||
editor.provider.update(Provider.ID.make(id), (value) => {
|
||||
const current = providerInfo(value)
|
||||
update(current)
|
||||
Object.assign(value, current, { id: Provider.ID.make(current.id) })
|
||||
}),
|
||||
remove: (id) => draft.provider.remove(Provider.ID.make(id)),
|
||||
remove: (id) => editor.provider.remove(Provider.ID.make(id)),
|
||||
},
|
||||
model: {
|
||||
get: (providerID, modelID) => {
|
||||
const value = draft.model.get(Provider.ID.make(providerID), Model.ID.make(modelID))
|
||||
const value = editor.model.get(Provider.ID.make(providerID), Model.ID.make(modelID))
|
||||
return value && modelInfo(value)
|
||||
},
|
||||
update: (providerID, modelID, update) =>
|
||||
draft.model.update(Provider.ID.make(providerID), Model.ID.make(modelID), (value) => {
|
||||
editor.model.update(Provider.ID.make(providerID), Model.ID.make(modelID), (value) => {
|
||||
const current = modelInfo(value)
|
||||
update(current)
|
||||
Object.assign(value, current, {
|
||||
@@ -282,14 +282,14 @@ export function catalogHost(catalog: Catalog.Interface): Plugin.Context["catalog
|
||||
})),
|
||||
})
|
||||
}),
|
||||
remove: (providerID, modelID) => draft.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)),
|
||||
remove: (providerID, modelID) => editor.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)),
|
||||
default: {
|
||||
get: () => {
|
||||
const value = draft.model.default.get()
|
||||
const value = editor.model.default.get()
|
||||
return value && { providerID: value.providerID, modelID: value.modelID }
|
||||
},
|
||||
set: (providerID, modelID) =>
|
||||
draft.model.default.set(Provider.ID.make(providerID), Model.ID.make(modelID)),
|
||||
editor.model.default.set(Provider.ID.make(providerID), Model.ID.make(modelID)),
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -324,22 +324,22 @@ export function integrationHost(integration: Integration.Interface): Plugin.Cont
|
||||
),
|
||||
},
|
||||
transform: (callback) =>
|
||||
integration.transform((draft) =>
|
||||
integration.transform((editor) =>
|
||||
callback({
|
||||
list: () => draft.list().map((value) => ({ id: value.id, name: value.name })),
|
||||
list: () => editor.list().map((value) => ({ id: value.id, name: value.name })),
|
||||
get: (id) => {
|
||||
const value = draft.get(Integration.ID.make(id))
|
||||
const value = editor.get(Integration.ID.make(id))
|
||||
return value && { id: value.id, name: value.name }
|
||||
},
|
||||
update: (id, update) => draft.update(Integration.ID.make(id), update),
|
||||
remove: (id) => draft.remove(Integration.ID.make(id)),
|
||||
update: (id, update) => editor.update(Integration.ID.make(id), update),
|
||||
remove: (id) => editor.remove(Integration.ID.make(id)),
|
||||
method: {
|
||||
list: (id) => draft.method.list(Integration.ID.make(id)),
|
||||
list: (id) => editor.method.list(Integration.ID.make(id)),
|
||||
update: (input) => {
|
||||
if ("authorize" in input) {
|
||||
const methodID = Integration.MethodID.make(input.method.id)
|
||||
const refresh = input.refresh
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: Integration.ID.make(input.integrationID),
|
||||
method: { ...input.method, id: methodID },
|
||||
authorize: (answer) =>
|
||||
@@ -390,14 +390,14 @@ export function integrationHost(integration: Integration.Interface): Plugin.Cont
|
||||
return
|
||||
}
|
||||
if (input.method.type === "env") {
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: Integration.ID.make(input.integrationID),
|
||||
method: input.method,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (input.method.type === "command") {
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: Integration.ID.make(input.integrationID),
|
||||
method: {
|
||||
...input.method,
|
||||
@@ -406,12 +406,12 @@ export function integrationHost(integration: Integration.Interface): Plugin.Cont
|
||||
})
|
||||
return
|
||||
}
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: Integration.ID.make(input.integrationID),
|
||||
method: input.method,
|
||||
})
|
||||
},
|
||||
remove: (id, item) => draft.method.remove(Integration.ID.make(id), internalMethod(item)),
|
||||
remove: (id, item) => editor.method.remove(Integration.ID.make(id), internalMethod(item)),
|
||||
},
|
||||
}),
|
||||
),
|
||||
@@ -435,18 +435,18 @@ export function webSearchHost(websearch: WebSearch.Interface): Plugin.Context["w
|
||||
.pipe(Effect.map((data) => ({ location, data }))),
|
||||
reload: websearch.reload,
|
||||
transform: (callback) =>
|
||||
websearch.transform((draft) => {
|
||||
websearch.transform((editor) => {
|
||||
callback({
|
||||
add: (definition) =>
|
||||
draft.add({
|
||||
editor.add({
|
||||
id: WebSearch.ID.make(definition.id),
|
||||
name: definition.name,
|
||||
execute: definition.execute,
|
||||
}),
|
||||
default: {
|
||||
get: draft.default.get,
|
||||
get: editor.default.get,
|
||||
set: (selection) =>
|
||||
draft.default.set(
|
||||
editor.default.set(
|
||||
selection === false || selection === "random" ? selection : WebSearch.ID.make(selection),
|
||||
),
|
||||
},
|
||||
|
||||
@@ -76,8 +76,8 @@ describe("ModelsDevPlugin", () => {
|
||||
|
||||
const scope = yield* Scope.make()
|
||||
yield* catalog
|
||||
.transform((draft) =>
|
||||
draft.model.update(providerID, modelID, (model) => {
|
||||
.transform((editor) =>
|
||||
editor.model.update(providerID, modelID, (model) => {
|
||||
model.variants ??= []
|
||||
model.variants.push({ id: Model.VariantID.make("configured") })
|
||||
}),
|
||||
|
||||
@@ -244,12 +244,12 @@ describe("AzurePlugin", () => {
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.provider.update(Provider.ID.azure, (provider) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.provider.update(Provider.ID.azure, (provider) => {
|
||||
provider.package = Provider.aisdk("@ai-sdk/azure")
|
||||
})
|
||||
draft.model.update(Provider.ID.azure, Model.ID.make("gpt-5-mini"), () => {})
|
||||
draft.model.update(Provider.ID.azure, Model.ID.make("gpt-5-nano"), () => {})
|
||||
editor.model.update(Provider.ID.azure, Model.ID.make("gpt-5-mini"), () => {})
|
||||
editor.model.update(Provider.ID.azure, Model.ID.make("gpt-5-nano"), () => {})
|
||||
})
|
||||
yield* azureCredential
|
||||
yield* addPlugin()
|
||||
@@ -462,24 +462,24 @@ describe("AzurePlugin", () => {
|
||||
gateway: Model.ID.make("gateway"),
|
||||
nonAzure: Model.ID.make("non-azure"),
|
||||
}
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.provider.update(Provider.ID.azure, (provider) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.provider.update(Provider.ID.azure, (provider) => {
|
||||
provider.package = Provider.aisdk("@ai-sdk/azure")
|
||||
})
|
||||
draft.model.update(Provider.ID.azure, models.responses, () => {})
|
||||
draft.model.update(Provider.ID.azure, models.chat, (model) => {
|
||||
editor.model.update(Provider.ID.azure, models.responses, () => {})
|
||||
editor.model.update(Provider.ID.azure, models.chat, (model) => {
|
||||
model.settings = { useCompletionUrls: true }
|
||||
})
|
||||
draft.model.update(Provider.ID.azure, models.preview, (model) => {
|
||||
editor.model.update(Provider.ID.azure, models.preview, (model) => {
|
||||
model.settings = { apiVersion: "2025-04-01-preview" }
|
||||
})
|
||||
draft.model.update(Provider.ID.azure, models.deploymentURL, (model) => {
|
||||
editor.model.update(Provider.ID.azure, models.deploymentURL, (model) => {
|
||||
model.settings = { useDeploymentBasedUrls: true }
|
||||
})
|
||||
draft.model.update(Provider.ID.azure, models.gateway, (model) => {
|
||||
editor.model.update(Provider.ID.azure, models.gateway, (model) => {
|
||||
model.settings = { baseURL: "https://gateway.example/azure" }
|
||||
})
|
||||
draft.model.update(Provider.ID.azure, models.nonAzure, (model) => {
|
||||
editor.model.update(Provider.ID.azure, models.nonAzure, (model) => {
|
||||
model.package = Provider.aisdk("@ai-sdk/anthropic")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -56,12 +56,12 @@ describe("GithubCopilotPlugin", () => {
|
||||
it.effect("removes the generic key method", () =>
|
||||
Effect.gen(function* () {
|
||||
const integrations = yield* Integration.Service
|
||||
yield* integrations.transform((draft) => {
|
||||
draft.method.update({
|
||||
yield* integrations.transform((editor) => {
|
||||
editor.method.update({
|
||||
integrationID: Integration.ID.make("github-copilot"),
|
||||
method: { type: "key" },
|
||||
})
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: Integration.ID.make("github-copilot"),
|
||||
method: { type: "env", names: ["GITHUB_TOKEN"] },
|
||||
})
|
||||
|
||||
@@ -289,22 +289,22 @@ describe("LMStudioPlugin", () => {
|
||||
const catalog = yield* Catalog.Service
|
||||
const integrations = yield* Integration.Service
|
||||
const providerID = Provider.ID.make("lmstudio")
|
||||
yield* integrations.transform((draft) => {
|
||||
draft.update(Integration.ID.make("lmstudio"), (integration) => {
|
||||
yield* integrations.transform((editor) => {
|
||||
editor.update(Integration.ID.make("lmstudio"), (integration) => {
|
||||
integration.name = "LMStudio"
|
||||
})
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: Integration.ID.make("lmstudio"),
|
||||
method: { type: "env", names: ["LMSTUDIO_API_KEY"] },
|
||||
})
|
||||
})
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.provider.update(providerID, (provider) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.provider.update(providerID, (provider) => {
|
||||
provider.name = "LMStudio"
|
||||
provider.package = "aisdk:@ai-sdk/openai-compatible"
|
||||
provider.integrationID = Integration.ID.make("lmstudio")
|
||||
})
|
||||
draft.model.update(providerID, Model.ID.make("static-model"), () => {})
|
||||
editor.model.update(providerID, Model.ID.make("static-model"), () => {})
|
||||
})
|
||||
|
||||
expect((yield* catalog.provider.available()).map((provider) => provider.id)).not.toContain(providerID)
|
||||
@@ -319,11 +319,11 @@ describe("LMStudioPlugin", () => {
|
||||
expect(yield* catalog.model.get(providerID, Model.ID.make("static-model"))).toBeUndefined()
|
||||
expect((yield* catalog.provider.available()).map((provider) => provider.id)).toContain(providerID)
|
||||
|
||||
yield* integrations.transform((draft) => {
|
||||
draft.update(Integration.ID.make("lmstudio"), (integration) => {
|
||||
yield* integrations.transform((editor) => {
|
||||
editor.update(Integration.ID.make("lmstudio"), (integration) => {
|
||||
integration.name = "Configured LM Studio"
|
||||
})
|
||||
draft.method.update({ integrationID: Integration.ID.make("lmstudio"), method: { type: "key" } })
|
||||
editor.method.update({ integrationID: Integration.ID.make("lmstudio"), method: { type: "key" } })
|
||||
})
|
||||
expect((yield* catalog.provider.available()).map((provider) => provider.id)).toContain(providerID)
|
||||
|
||||
|
||||
@@ -188,22 +188,22 @@ describe("OllamaPlugin", () => {
|
||||
const catalog = yield* Catalog.Service
|
||||
const integrations = yield* Integration.Service
|
||||
const providerID = Provider.ID.make("ollama")
|
||||
yield* integrations.transform((draft) => {
|
||||
draft.update(Integration.ID.make("ollama"), (integration) => {
|
||||
yield* integrations.transform((editor) => {
|
||||
editor.update(Integration.ID.make("ollama"), (integration) => {
|
||||
integration.name = "Ollama"
|
||||
})
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: Integration.ID.make("ollama"),
|
||||
method: { type: "env", names: ["OLLAMA_API_KEY"] },
|
||||
})
|
||||
})
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.provider.update(providerID, (provider) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.provider.update(providerID, (provider) => {
|
||||
provider.name = "Ollama"
|
||||
provider.package = "aisdk:@ai-sdk/openai-compatible"
|
||||
provider.integrationID = Integration.ID.make("ollama")
|
||||
})
|
||||
draft.model.update(providerID, Model.ID.make("static-model"), () => {})
|
||||
editor.model.update(providerID, Model.ID.make("static-model"), () => {})
|
||||
})
|
||||
|
||||
yield* addPlugin(server.url.origin, "5 millis")
|
||||
|
||||
@@ -267,12 +267,12 @@ describe("OpencodePlugin", () => {
|
||||
const credentials = yield* Credential.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
const integrations = yield* Integration.Service
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.provider.update(Provider.ID.openai, (provider) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.provider.update(Provider.ID.openai, (provider) => {
|
||||
provider.package = Provider.aisdk("@ai-sdk/openai")
|
||||
provider.integrationID = Integration.ID.make("openai")
|
||||
})
|
||||
draft.model.update(Provider.ID.openai, Model.ID.make("api-model"), (model) => {
|
||||
editor.model.update(Provider.ID.openai, Model.ID.make("api-model"), (model) => {
|
||||
model.package = Provider.aisdk("@ai-sdk/openai")
|
||||
model.settings = { baseURL: "https://upstream.example/v1" }
|
||||
model.variants = [
|
||||
@@ -284,7 +284,7 @@ describe("OpencodePlugin", () => {
|
||||
},
|
||||
]
|
||||
})
|
||||
draft.model.update(Provider.ID.make("remote"), Model.ID.make("stale"), () => {})
|
||||
editor.model.update(Provider.ID.make("remote"), Model.ID.make("stale"), () => {})
|
||||
})
|
||||
const initial = yield* credentials.create({
|
||||
integrationID: Integration.ID.make("opencode"),
|
||||
|
||||
@@ -172,23 +172,23 @@ describe("VLLMPlugin", () => {
|
||||
const catalog = yield* Catalog.Service
|
||||
const integrations = yield* Integration.Service
|
||||
const providerID = Provider.ID.make("vllm")
|
||||
yield* integrations.transform((draft) => {
|
||||
draft.update(Integration.ID.make("vllm"), (integration) => {
|
||||
yield* integrations.transform((editor) => {
|
||||
editor.update(Integration.ID.make("vllm"), (integration) => {
|
||||
integration.name = "vLLM"
|
||||
})
|
||||
draft.method.update({
|
||||
editor.method.update({
|
||||
integrationID: Integration.ID.make("vllm"),
|
||||
method: { type: "env", names: ["VLLM_API_KEY"] },
|
||||
})
|
||||
})
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.provider.update(providerID, (provider) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.provider.update(providerID, (provider) => {
|
||||
provider.name = "vLLM"
|
||||
provider.package = "aisdk:@ai-sdk/openai-compatible"
|
||||
provider.integrationID = Integration.ID.make("vllm")
|
||||
provider.activation = "auto"
|
||||
})
|
||||
draft.model.update(providerID, Model.ID.make("static-model"), () => {})
|
||||
editor.model.update(providerID, Model.ID.make("static-model"), () => {})
|
||||
})
|
||||
|
||||
yield* addPlugin(server.url.origin, "5 millis")
|
||||
|
||||
@@ -73,11 +73,11 @@ describe("XAIPlugin", () => {
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const providerID = Provider.ID.make("xai")
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.provider.update(providerID, (provider) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.provider.update(providerID, (provider) => {
|
||||
provider.package = Provider.aisdk("@ai-sdk/xai")
|
||||
})
|
||||
draft.model.update(providerID, Model.ID.make("grok-4.6"), () => {})
|
||||
editor.model.update(providerID, Model.ID.make("grok-4.6"), () => {})
|
||||
})
|
||||
|
||||
yield* addPlugin()
|
||||
|
||||
@@ -24,7 +24,7 @@ const greeter = (command: string) =>
|
||||
define({
|
||||
id,
|
||||
effect: (ctx) =>
|
||||
ctx.command.transform((draft) => draft.add({ name: command, execute: () => Effect.void })).pipe(Effect.asVoid),
|
||||
ctx.command.transform((editor) => editor.add({ name: command, execute: () => Effect.void })).pipe(Effect.asVoid),
|
||||
})
|
||||
|
||||
const instances = Layer.effect(
|
||||
|
||||
@@ -21,7 +21,7 @@ const fallback = SessionSystemPrompt.make([])
|
||||
const makeHost = Effect.gen(function* () {
|
||||
const agents = yield* Agent.Service
|
||||
const plugins = yield* Plugin.Service
|
||||
yield* agents.transform((draft) => draft.update(Agent.ID.make("build"), () => {}))
|
||||
yield* agents.transform((editor) => editor.update(Agent.ID.make("build"), () => {}))
|
||||
return yield* PluginHost.make(plugins)
|
||||
})
|
||||
|
||||
@@ -66,9 +66,9 @@ describe("SystemPromptPlugin", () => {
|
||||
const catalog = yield* Catalog.Service
|
||||
const hooks = yield* PluginHooks.Service
|
||||
const pluginHost = yield* makeHost
|
||||
yield* catalog.transform((draft) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
for (const id of ["gpt-5", "gpt-4.1", "gpt-5-codex"])
|
||||
draft.model.update(Provider.ID.make("test"), Model.ID.make(id), () => {})
|
||||
editor.model.update(Provider.ID.make("test"), Model.ID.make(id), () => {})
|
||||
})
|
||||
yield* Effect.forEach(SystemPromptPlugin.Plugins, (plugin) => plugin.effect(pluginHost), {
|
||||
discard: true,
|
||||
@@ -108,8 +108,8 @@ describe("SystemPromptPlugin", () => {
|
||||
const catalog = yield* Catalog.Service
|
||||
const hooks = yield* PluginHooks.Service
|
||||
const pluginHost = yield* makeHost
|
||||
yield* catalog.transform((draft) =>
|
||||
draft.model.update(Provider.ID.make("test"), Model.ID.make("gpt-5"), () => {}),
|
||||
yield* catalog.transform((editor) =>
|
||||
editor.model.update(Provider.ID.make("test"), Model.ID.make("gpt-5"), () => {}),
|
||||
)
|
||||
yield* SystemPromptPlugin.OpenAIPlugin.effect(pluginHost)
|
||||
const event = context("gpt-5")
|
||||
@@ -154,8 +154,8 @@ describe("SystemPromptPlugin", () => {
|
||||
Effect.gen(function* () {
|
||||
const agents = yield* Agent.Service
|
||||
const hooks = yield* PluginHooks.Service
|
||||
yield* agents.transform((draft) =>
|
||||
draft.update(Agent.ID.make("build"), (agent) => {
|
||||
yield* agents.transform((editor) =>
|
||||
editor.update(Agent.ID.make("build"), (agent) => {
|
||||
agent.system = "Custom agent prompt"
|
||||
}),
|
||||
)
|
||||
@@ -177,7 +177,7 @@ describe("SystemPromptPlugin", () => {
|
||||
const hooks = yield* PluginHooks.Service
|
||||
const pluginHost = yield* makeHost
|
||||
yield* SystemPromptPlugin.OpenAIPlugin.effect(pluginHost)
|
||||
yield* agents.transform((draft) => draft.remove(Agent.ID.make("build")))
|
||||
yield* agents.transform((editor) => editor.remove(Agent.ID.make("build")))
|
||||
const event = context("gpt-5")
|
||||
|
||||
yield* hooks.trigger("session", "context", event)
|
||||
@@ -207,14 +207,14 @@ describe("SystemPromptPlugin", () => {
|
||||
const catalog = yield* Catalog.Service
|
||||
const hooks = yield* PluginHooks.Service
|
||||
const pluginHost = yield* makeHost
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.model.update(Provider.ID.make("test"), Model.ID.make("openai-alias"), (model) => {
|
||||
yield* catalog.transform((editor) => {
|
||||
editor.model.update(Provider.ID.make("test"), Model.ID.make("openai-alias"), (model) => {
|
||||
model.modelID = Model.ID.make("gpt-5")
|
||||
})
|
||||
draft.model.update(Provider.ID.make("test"), Model.ID.make("gpt-5-alias"), (model) => {
|
||||
editor.model.update(Provider.ID.make("test"), Model.ID.make("gpt-5-alias"), (model) => {
|
||||
model.modelID = Model.ID.make("custom-model")
|
||||
})
|
||||
draft.model.update(Provider.ID.make("test"), Model.ID.make("codex-family-alias"), (model) => {
|
||||
editor.model.update(Provider.ID.make("test"), Model.ID.make("codex-family-alias"), (model) => {
|
||||
model.modelID = Model.ID.make("custom-deployment")
|
||||
model.family = Model.Family.make("gpt-codex")
|
||||
})
|
||||
|
||||
@@ -34,8 +34,8 @@ describe("built-in web search providers", () => {
|
||||
Effect.gen(function* () {
|
||||
const integrations = yield* Integration.Service
|
||||
const websearch = yield* WebSearch.Service
|
||||
const registration = yield* webSearchHost(websearch).transform((draft) => {
|
||||
draft.add({
|
||||
const registration = yield* webSearchHost(websearch).transform((editor) => {
|
||||
editor.add({
|
||||
id: "test-websearch",
|
||||
name: "Test Web Search",
|
||||
execute: (input) => Effect.succeed([{ url: "https://example.com", content: input.query, time: {} }]),
|
||||
|
||||
@@ -212,7 +212,7 @@ describe("pty create defaults", () => {
|
||||
if (!configuredShell) return
|
||||
const pty = yield* Pty.Service
|
||||
const shell = yield* ShellSelect.Service
|
||||
yield* shell.transform((draft) => draft.configure(configuredShell))
|
||||
yield* shell.transform((editor) => editor.configure(configuredShell))
|
||||
const info = yield* Effect.acquireRelease(pty.create({ title: "configured" }), (created) =>
|
||||
pty.remove(created.id).pipe(Effect.ignore),
|
||||
)
|
||||
|
||||
@@ -61,13 +61,13 @@ describe("Reference", () => {
|
||||
|
||||
yield* State.batch(
|
||||
Effect.gen(function* () {
|
||||
yield* references.transform((draft) =>
|
||||
draft.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/docs") })),
|
||||
yield* references.transform((editor) =>
|
||||
editor.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/docs") })),
|
||||
)
|
||||
expect((yield* references.list()).map((info) => info.name)).toEqual(["docs"])
|
||||
|
||||
yield* references.transform((draft) => {
|
||||
draft.add(
|
||||
yield* references.transform((editor) => {
|
||||
editor.add(
|
||||
"sdk",
|
||||
Reference.GitSource.make({
|
||||
type: "git",
|
||||
@@ -77,12 +77,12 @@ describe("Reference", () => {
|
||||
hidden: true,
|
||||
}),
|
||||
)
|
||||
draft.add("invalid", Reference.GitSource.make({ type: "git", repository: "invalid" }))
|
||||
draft.add(
|
||||
editor.add("invalid", Reference.GitSource.make({ type: "git", repository: "invalid" }))
|
||||
editor.add(
|
||||
"invalid-branch",
|
||||
Reference.GitSource.make({ type: "git", repository: "owner/repo", branch: "../escape" }),
|
||||
)
|
||||
draft.add("file", Reference.GitSource.make({ type: "git", repository: "file:///docs" }))
|
||||
editor.add("file", Reference.GitSource.make({ type: "git", repository: "file:///docs" }))
|
||||
})
|
||||
const infos = yield* references.list()
|
||||
expect(infos.map((info) => info.name)).toEqual(["docs", "sdk"])
|
||||
@@ -122,8 +122,8 @@ describe("Reference", () => {
|
||||
if (event.type !== Reference.Event.Updated.type || reentered) return
|
||||
reentered = true
|
||||
yield* references
|
||||
.transform((draft) =>
|
||||
draft.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/new") })),
|
||||
.transform((editor) =>
|
||||
editor.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/new") })),
|
||||
)
|
||||
.pipe(Scope.provide(scope))
|
||||
}),
|
||||
@@ -139,8 +139,8 @@ describe("Reference", () => {
|
||||
)
|
||||
yield* Effect.addFinalizer(() => first.pipe(Effect.andThen(second)))
|
||||
|
||||
yield* references.transform((draft) =>
|
||||
draft.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/old") })),
|
||||
yield* references.transform((editor) =>
|
||||
editor.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/old") })),
|
||||
)
|
||||
|
||||
expect((yield* references.list()).map((info) => info.path)).toEqual([AbsolutePath.make("/new")])
|
||||
|
||||
@@ -200,8 +200,8 @@ const setup = Effect.gen(function* () {
|
||||
const instructionBuiltIns = yield* InstructionBuiltIns.Service
|
||||
const context = yield* SessionContext.Service
|
||||
const store = yield* SessionStore.Service
|
||||
yield* agents.transform((draft) =>
|
||||
draft.update(Agent.ID.make("build"), (agent) => {
|
||||
yield* agents.transform((editor) =>
|
||||
editor.update(Agent.ID.make("build"), (agent) => {
|
||||
agent.mode = "primary"
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -182,8 +182,8 @@ describe("SessionRunnerLLM recorded", () => {
|
||||
const agents = yield* Agent.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
const hooks = yield* PluginHooks.Service
|
||||
yield* agents.transform((draft) =>
|
||||
draft.update(Agent.ID.make("build"), (agent) => {
|
||||
yield* agents.transform((editor) =>
|
||||
editor.update(Agent.ID.make("build"), (agent) => {
|
||||
agent.mode = "primary"
|
||||
agent.permissions.push({ action: "execute", resource: "*", effect: "deny" })
|
||||
}),
|
||||
|
||||
@@ -257,8 +257,8 @@ const permissionFail = {
|
||||
}
|
||||
const permission = permissionLayer()
|
||||
const transformTools = (registry: Tool.Interface, tools: Readonly<Record<string, ToolInfo>>, options?: Tool.Options) =>
|
||||
registry.transform((draft) =>
|
||||
Object.entries(tools).forEach(([name, tool]) => draft.add({ ...tool, name, options: options ?? tool.options })),
|
||||
registry.transform((editor) =>
|
||||
Object.entries(tools).forEach(([name, tool]) => editor.add({ ...tool, name, options: options ?? tool.options })),
|
||||
)
|
||||
const layer = Layer.unwrap(
|
||||
Effect.map(RunnerState, (state) => {
|
||||
@@ -521,8 +521,8 @@ const setup = Effect.gen(function* () {
|
||||
yield* Effect.forEach(SystemPromptPlugin.Plugins, (plugin) => plugin.effect(pluginHost), {
|
||||
discard: true,
|
||||
})
|
||||
yield* agents.transform((draft) =>
|
||||
draft.update(Agent.ID.make("build"), (agent) => {
|
||||
yield* agents.transform((editor) =>
|
||||
editor.update(Agent.ID.make("build"), (agent) => {
|
||||
agent.mode = "primary"
|
||||
}),
|
||||
)
|
||||
@@ -906,8 +906,8 @@ const prepareTitleGeneration = Effect.gen(function* () {
|
||||
const agents = yield* Agent.Service
|
||||
const { db } = yield* Database.Service
|
||||
yield* db.update(SessionTable).set({ title: null }).where(eq(SessionTable.id, sessionID)).run().pipe(Effect.orDie)
|
||||
yield* agents.transform((draft) =>
|
||||
draft.update(Agent.ID.make("title"), (agent) => {
|
||||
yield* agents.transform((editor) =>
|
||||
editor.update(Agent.ID.make("title"), (agent) => {
|
||||
agent.mode = "primary"
|
||||
agent.hidden = true
|
||||
agent.system = "Generate a title."
|
||||
@@ -1700,8 +1700,8 @@ describe("SessionRunnerLLM", () => {
|
||||
|
||||
scenario("updates selected-agent skill instructions after an agent switch", function* (s) {
|
||||
const agents = yield* Agent.Service
|
||||
yield* agents.transform((draft) =>
|
||||
draft.update(Agent.ID.make("reviewer"), (agent) => {
|
||||
yield* agents.transform((editor) =>
|
||||
editor.update(Agent.ID.make("reviewer"), (agent) => {
|
||||
agent.mode = "primary"
|
||||
}),
|
||||
)
|
||||
@@ -2113,8 +2113,8 @@ describe("SessionRunnerLLM", () => {
|
||||
provider: route === AnthropicMessages.route ? "anthropic" : "openai",
|
||||
route,
|
||||
})
|
||||
yield* agents.transform((draft) =>
|
||||
draft.update(agentID, (agent) => {
|
||||
yield* agents.transform((editor) =>
|
||||
editor.update(agentID, (agent) => {
|
||||
agent.system = "Review the project carefully."
|
||||
}),
|
||||
)
|
||||
@@ -2484,7 +2484,7 @@ describe("SessionRunnerLLM", () => {
|
||||
scenario("does not recover provider context overflow when automatic compaction is disabled", function* (s) {
|
||||
yield* setupOverflowRecovery(s)
|
||||
const compaction = yield* SessionCompaction.Service
|
||||
yield* compaction.transform((draft) => draft.configure({ auto: false }))
|
||||
yield* compaction.transform((editor) => editor.configure({ auto: false }))
|
||||
yield* s.llm.push(
|
||||
[LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" })],
|
||||
TestLLM.text("Must not compact", "text-unexpected-summary"),
|
||||
|
||||
@@ -23,11 +23,11 @@ describe("Skill", () => {
|
||||
it.effect("registers values with last-write-wins precedence", () =>
|
||||
Effect.gen(function* () {
|
||||
const skill = yield* Skill.Service
|
||||
yield* skill.transform((draft) => {
|
||||
draft.add(info("review", "First"))
|
||||
draft.add(info("deploy", "Deploy"))
|
||||
draft.add(info("review", "Second"))
|
||||
expect(draft.list().map((item) => item.id)).toEqual([Skill.ID.make("review"), Skill.ID.make("deploy")])
|
||||
yield* skill.transform((editor) => {
|
||||
editor.add(info("review", "First"))
|
||||
editor.add(info("deploy", "Deploy"))
|
||||
editor.add(info("review", "Second"))
|
||||
expect(editor.list().map((item) => item.id)).toEqual([Skill.ID.make("review"), Skill.ID.make("deploy")])
|
||||
})
|
||||
|
||||
expect(yield* skill.list()).toEqual([info("review", "Second"), info("deploy", "Deploy")])
|
||||
@@ -39,17 +39,17 @@ describe("Skill", () => {
|
||||
it.effect("updates and removes registered values", () =>
|
||||
Effect.gen(function* () {
|
||||
const skill = yield* Skill.Service
|
||||
yield* skill.transform((draft) => {
|
||||
draft.add(info("review", "Initial"))
|
||||
draft.update("review", (value) => {
|
||||
yield* skill.transform((editor) => {
|
||||
editor.add(info("review", "Initial"))
|
||||
editor.update("review", (value) => {
|
||||
value.description = "Updated"
|
||||
value.id = Skill.ID.make("ignored")
|
||||
})
|
||||
draft.update("missing", () => {
|
||||
editor.update("missing", () => {
|
||||
throw new Error("unreachable")
|
||||
})
|
||||
draft.add(info("deploy", "Deploy"))
|
||||
draft.remove("deploy")
|
||||
editor.add(info("deploy", "Deploy"))
|
||||
editor.remove("deploy")
|
||||
})
|
||||
|
||||
expect(yield* skill.list()).toEqual([info("review", "Updated")])
|
||||
@@ -60,9 +60,9 @@ describe("Skill", () => {
|
||||
Effect.gen(function* () {
|
||||
const skill = yield* Skill.Service
|
||||
const original = info("review", "Initial")
|
||||
yield* skill.transform((draft) => draft.add(original))
|
||||
const updated = yield* skill.transform((draft) =>
|
||||
draft.update("review", (value) => {
|
||||
yield* skill.transform((editor) => editor.add(original))
|
||||
const updated = yield* skill.transform((editor) =>
|
||||
editor.update("review", (value) => {
|
||||
value.description = "Updated"
|
||||
}),
|
||||
)
|
||||
@@ -85,7 +85,7 @@ describe("Skill", () => {
|
||||
)
|
||||
yield* Effect.yieldNow
|
||||
|
||||
yield* skill.transform((draft) => draft.add(info("review", "Visible")))
|
||||
yield* skill.transform((editor) => editor.add(info("review", "Visible")))
|
||||
expect(yield* Deferred.await(updated).pipe(Effect.timeout("1 second"))).toEqual([info("review", "Visible")])
|
||||
yield* Fiber.interrupt(fiber)
|
||||
}),
|
||||
@@ -94,8 +94,8 @@ describe("Skill", () => {
|
||||
it.effect("filters values by agent permissions", () =>
|
||||
Effect.gen(function* () {
|
||||
const agents = yield* Agent.Service
|
||||
yield* agents.transform((draft) =>
|
||||
draft.update(Agent.ID.make("reviewer"), (agent) => {
|
||||
yield* agents.transform((editor) =>
|
||||
editor.update(Agent.ID.make("reviewer"), (agent) => {
|
||||
agent.permissions.push({ action: "skill", resource: "deploy", effect: "deny" })
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -166,7 +166,7 @@ describe("Snapshot", () => {
|
||||
|
||||
yield* Effect.gen(function* () {
|
||||
const snapshot = yield* Snapshot.Service
|
||||
const registration = yield* snapshot.transform((draft) => draft.configure(false))
|
||||
const registration = yield* snapshot.transform((editor) => editor.configure(false))
|
||||
expect(yield* snapshot.capture()).toBeUndefined()
|
||||
|
||||
yield* registration.dispose
|
||||
|
||||
@@ -47,14 +47,14 @@ describe("State replay properties", () => {
|
||||
const states = sources.map((_, index) =>
|
||||
State.create({
|
||||
initial: (): Value => ({ value: sources[index], order: [] }),
|
||||
draft: (draft) => draft,
|
||||
editor: (editor) => editor,
|
||||
notify: () => Effect.sync(() => void notifications[index]++),
|
||||
}),
|
||||
)
|
||||
const callbacks = operations.map((operation, index) => (draft: Value) => {
|
||||
const callbacks = operations.map((operation, index) => (editor: Value) => {
|
||||
calls++
|
||||
draft.value = apply(draft.value, operation)
|
||||
draft.order.push(index)
|
||||
editor.value = apply(editor.value, operation)
|
||||
editor.order.push(index)
|
||||
})
|
||||
const registrations: { handle: State.Registration; callback: number; active: boolean }[][] = [[], []]
|
||||
const expected = (index: number): Value => {
|
||||
@@ -138,7 +138,7 @@ describe("State replay properties", () => {
|
||||
let calls = 0
|
||||
const state = State.create({
|
||||
initial: () => ({ value: 1, order: new Array<number>() }),
|
||||
draft: (draft) => draft,
|
||||
editor: (editor) => editor,
|
||||
})
|
||||
const registrations: State.Registration[] = []
|
||||
const retained: { value: Value; snapshot: Value }[] = []
|
||||
@@ -155,10 +155,10 @@ describe("State replay properties", () => {
|
||||
Effect.gen(function* () {
|
||||
const before = calls
|
||||
const added = yield* Effect.forEach(chunk.append, (operation) =>
|
||||
state.transform((draft) => {
|
||||
state.transform((editor) => {
|
||||
calls++
|
||||
draft.value = apply(draft.value, operation)
|
||||
draft.order.push(draft.order.length)
|
||||
editor.value = apply(editor.value, operation)
|
||||
editor.order.push(editor.order.length)
|
||||
}),
|
||||
)
|
||||
registrations.push(...added)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user