Compare commits

...
Author SHA1 Message Date
Kit Langton cbf9d08cca refactor(core): inline single-caller modules 2026-10-01 19:57:26 -07:00
9 changed files with 100 additions and 122 deletions

No files matched your search

+27 -3
View File
@@ -11,7 +11,6 @@ import { fileURLToPath, pathToFileURL } from "url"
import { Config } from "../../config.js"
import { Watcher } from "../../filesystem/watcher.js"
import { Location } from "../../location.js"
import { PluginSourceDirectory } from "../../plugin/source-directory.js"
export type Operation =
| {
@@ -119,6 +118,8 @@ function parse(input: ConfigPlugin.Plugin): Operation {
return { type: "remove", target: input.slice(1) }
}
const sourceDirectories = ["plugin", "plugins"] as const
const scan = Effect.fn("ConfigPluginSource.scan")(function* (
fs: FSUtil.Interface,
location: Location.Interface,
@@ -127,7 +128,7 @@ const scan = Effect.fn("ConfigPluginSource.scan")(function* (
const discovered = yield* Effect.forEach(
entries.filter((entry): entry is Directory => entry.type === "directory"),
(entry) =>
PluginSourceDirectory.discover(fs, entry.path).pipe(
discover(fs, entry.path).pipe(
Effect.map((targets) => targets.map((target): Operation => ({ type: "add", target, options: {} }))),
),
).pipe(Effect.map((items) => items.flat()))
@@ -188,10 +189,33 @@ const scan = Effect.fn("ConfigPluginSource.scan")(function* (
).pipe(Effect.map((operations) => operations.flat()))
})
const discover = Effect.fn("ConfigPluginSource.discover")(function* (fs: FSUtil.Interface, directory: string) {
const children = (yield* Effect.forEach(sourceDirectories, (source) =>
fs.readDirectoryEntries(path.join(directory, source)).pipe(
Effect.orElseSucceed(() => []),
Effect.map((entries) => entries.map((entry) => ({ ...entry, target: path.join(directory, source, entry.name) }))),
),
))
.flat()
.sort((a, b) => (a.target < b.target ? -1 : a.target > b.target ? 1 : 0))
const targets = yield* Effect.forEach(children, (entry) =>
Effect.gen(function* () {
const source = entry.target.endsWith(".ts") || entry.target.endsWith(".js")
if (entry.type === "file" && source) return Option.some(entry.target)
if (entry.type === "directory") return Option.some(entry.target)
if (entry.type !== "symlink") return Option.none<string>()
if (source && (yield* fs.isFile(entry.target))) return Option.some(entry.target)
if (yield* fs.isDir(entry.target)) return Option.some(entry.target)
return Option.none<string>()
}),
)
return targets.flatMap(Option.toArray)
})
function isPluginSource(entries: readonly Entry[], file: string) {
return entries.some(
(entry) =>
entry.type === "directory" &&
PluginSourceDirectory.names.some((directory) => FSUtil.contains(path.join(entry.path, directory), file)),
sourceDirectories.some((directory) => FSUtil.contains(path.join(entry.path, directory), file)),
)
}
+10 -2
View File
@@ -94,7 +94,11 @@ import { ModelsDevPlugin } from "./models-dev.js"
import { McpCodeModeDefaultsPlugin } from "./mcp-codemode-defaults.js"
import { ProviderPlugins } from "./provider.js"
import { OpencodePlugin } from "./provider/opencode.js"
import { WebSearchPlugins } from "./websearch/index.js"
import { WebSearchExa } from "./websearch/exa.js"
import { WebSearchFirecrawl } from "./websearch/firecrawl.js"
import { WebSearchParallel } from "./websearch/parallel.js"
import { WebSearchTavily } from "./websearch/tavily.js"
import { WebSearchTinyFish } from "./websearch/tinyfish.js"
import { SkillPlugin } from "./skill.js"
import { VcsHgPlugin } from "./vcs/hg.js"
import { ToolInputRepairPlugin } from "./tool-input-repair.js"
@@ -224,7 +228,11 @@ const pre = [
VcsHgPlugin.Plugin,
ModelsDevPlugin,
...ProviderPlugins,
...WebSearchPlugins,
WebSearchExa.Plugin,
WebSearchFirecrawl.Plugin,
WebSearchParallel.Plugin,
WebSearchTavily.Plugin,
WebSearchTinyFish.Plugin,
PatchTool.Plugin,
// Render model prompts after the patch plugin selects the available editing tools.
...OptimizePlugin.Plugins,
@@ -1,33 +0,0 @@
export * as PluginSourceDirectory from "./source-directory.js"
import { FSUtil } from "@opencode/util/fs-util"
import { Effect, Option } from "effect"
import path from "path"
export const names = ["plugin", "plugins"] as const
export const discover = Effect.fn("PluginSourceDirectory.discover")(function* (
fs: FSUtil.Interface,
directory: string,
) {
const children = (yield* Effect.forEach(names, (source) =>
fs.readDirectoryEntries(path.join(directory, source)).pipe(
Effect.orElseSucceed(() => []),
Effect.map((entries) => entries.map((entry) => ({ ...entry, target: path.join(directory, source, entry.name) }))),
),
))
.flat()
.sort((a, b) => (a.target < b.target ? -1 : a.target > b.target ? 1 : 0))
const targets = yield* Effect.forEach(children, (entry) =>
Effect.gen(function* () {
const source = entry.target.endsWith(".ts") || entry.target.endsWith(".js")
if (entry.type === "file" && source) return Option.some(entry.target)
if (entry.type === "directory") return Option.some(entry.target)
if (entry.type !== "symlink") return Option.none<string>()
if (source && (yield* fs.isFile(entry.target))) return Option.some(entry.target)
if (yield* fs.isDir(entry.target)) return Option.some(entry.target)
return Option.none<string>()
}),
)
return targets.flatMap(Option.toArray)
})
@@ -1,13 +0,0 @@
import { WebSearchExa } from "./exa.js"
import { WebSearchFirecrawl } from "./firecrawl.js"
import { WebSearchParallel } from "./parallel.js"
import { WebSearchTavily } from "./tavily.js"
import { WebSearchTinyFish } from "./tinyfish.js"
export const WebSearchPlugins = [
WebSearchExa.Plugin,
WebSearchFirecrawl.Plugin,
WebSearchParallel.Plugin,
WebSearchTavily.Plugin,
WebSearchTinyFish.Plugin,
] as const
-64
View File
@@ -1,64 +0,0 @@
import { DateTime, Schema } from "effect"
import { Agent } from "@opencode/schema/agent"
import { Location } from "@opencode/schema/location"
import { Model } from "@opencode/schema/model"
import { Project } from "@opencode/schema/project"
import { Provider } from "@opencode/schema/provider"
import { AbsolutePath, RelativePath } from "../schema.js"
import { Workspace } from "@opencode/schema/workspace"
import { SessionSchema } from "./schema.js"
import type { SessionTable } from "./sql.js"
import { PersistedRevert } from "@opencode/schema/session-revert"
import { Money } from "@opencode/schema/money"
const decodeRevert = Schema.decodeUnknownSync(PersistedRevert)
export function fromRow(row: typeof SessionTable.$inferSelect): SessionSchema.Info {
return SessionSchema.Info.make({
id: SessionSchema.ID.make(row.id),
projectID: Project.ID.make(row.project_id),
title: row.title ?? undefined,
parentID: row.parent_id ? SessionSchema.ID.make(row.parent_id) : undefined,
fork:
row.fork_session_id && row.fork_boundary
? {
sessionID: SessionSchema.ID.make(row.fork_session_id),
boundary: row.fork_boundary,
}
: undefined,
agent: row.agent ? Agent.ID.make(row.agent) : undefined,
model: row.model
? {
id: Model.ID.make(row.model.id),
providerID: Provider.ID.make(row.model.providerID),
variant: Model.VariantID.make(row.model.variant ?? "default"),
}
: undefined,
cost: Money.USD.make(row.cost),
tokens: {
input: row.tokens_input,
output: row.tokens_output,
reasoning: row.tokens_reasoning,
cache: {
read: row.tokens_cache_read,
write: row.tokens_cache_write,
},
},
location: Location.Ref.make({
directory: AbsolutePath.make(row.directory),
workspaceID: row.workspace_id ? Workspace.ID.make(row.workspace_id) : undefined,
}),
subpath: row.path ? RelativePath.make(row.path) : undefined,
metadata: row.metadata ?? undefined,
permissions: row.permission ?? undefined,
revert: row.revert ? decodeRevert(row.revert) : undefined,
outcome: row.idle_outcome ?? undefined,
time: {
created: DateTime.makeUnsafe(row.time_created),
updated: DateTime.makeUnsafe(row.time_updated),
idle: row.time_idle === null ? undefined : DateTime.makeUnsafe(row.time_idle),
viewed: row.time_viewed === null ? undefined : DateTime.makeUnsafe(row.time_viewed),
archived: row.time_archived ? DateTime.makeUnsafe(row.time_archived) : undefined,
},
})
}
+1 -1
View File
@@ -30,7 +30,7 @@ import { SessionRunnerRetry } from "./retry.js"
import { SessionStep } from "./step.js"
import { ToolOutput } from "../../tool-output.js"
import { Plugin } from "../../plugin.js"
import { MAX_STEPS_PROMPT } from "./max-steps.js"
import MAX_STEPS_PROMPT from "./prompt/max-steps.txt"
const CONTINUE_AFTER_INCOMPLETE_STREAM =
"The previous response was interrupted. Continue from where you left off without repeating completed content."
@@ -1,4 +1,4 @@
export const MAX_STEPS_PROMPT = `CRITICAL - MAXIMUM STEPS REACHED
CRITICAL - MAXIMUM STEPS REACHED
The maximum number of steps allowed for this task has been reached. Tools are disabled until next user input. Respond with text only.
@@ -13,4 +13,4 @@ Response must include:
- List of any remaining tasks that were not completed
- Recommendations for what should be done next
Any attempt to use tools is a critical violation. Respond with text ONLY.`
Any attempt to use tools is a critical violation. Respond with text ONLY.
+59 -2
View File
@@ -1,7 +1,13 @@
export * as SessionStore from "./store.js"
import { and, asc, desc, eq, gt, isNotNull, isNull, like, lt, notInArray, or, sql, type SQL } from "drizzle-orm"
import { Context, Effect, Layer, Schema } from "effect"
import { Context, DateTime, Effect, Layer, Schema } from "effect"
import { Agent } from "@opencode/schema/agent"
import { Location } from "@opencode/schema/location"
import { Model } from "@opencode/schema/model"
import { Money } from "@opencode/schema/money"
import { Provider } from "@opencode/schema/provider"
import { PersistedRevert } from "@opencode/schema/session-revert"
import { Project } from "@opencode/schema/project"
import { Workspace } from "@opencode/schema/workspace"
import { AbsolutePath, PositiveInt, RelativePath } from "@opencode/schema/schema"
@@ -12,7 +18,6 @@ import { MessageDecodeError } from "./error.js"
import { SessionMessage } from "./message.js"
import { Session } from "@opencode/schema/session"
import { SessionMessageTable, SessionTable } from "./sql.js"
import { fromRow } from "./info.js"
const ListInputBase = {
workspaceID: Workspace.ID.pipe(Schema.optional),
@@ -254,3 +259,55 @@ const layer = Layer.effect(
)
export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] })
const decodeRevert = Schema.decodeUnknownSync(PersistedRevert)
export function fromRow(row: typeof SessionTable.$inferSelect): Session.Info {
return Session.Info.make({
id: Session.ID.make(row.id),
projectID: Project.ID.make(row.project_id),
title: row.title ?? undefined,
parentID: row.parent_id ? Session.ID.make(row.parent_id) : undefined,
fork:
row.fork_session_id && row.fork_boundary
? {
sessionID: Session.ID.make(row.fork_session_id),
boundary: row.fork_boundary,
}
: undefined,
agent: row.agent ? Agent.ID.make(row.agent) : undefined,
model: row.model
? {
id: Model.ID.make(row.model.id),
providerID: Provider.ID.make(row.model.providerID),
variant: Model.VariantID.make(row.model.variant ?? "default"),
}
: undefined,
cost: Money.USD.make(row.cost),
tokens: {
input: row.tokens_input,
output: row.tokens_output,
reasoning: row.tokens_reasoning,
cache: {
read: row.tokens_cache_read,
write: row.tokens_cache_write,
},
},
location: Location.Ref.make({
directory: AbsolutePath.make(row.directory),
workspaceID: row.workspace_id ? Workspace.ID.make(row.workspace_id) : undefined,
}),
subpath: row.path ? RelativePath.make(row.path) : undefined,
metadata: row.metadata ?? undefined,
permissions: row.permission ?? undefined,
revert: row.revert ? decodeRevert(row.revert) : undefined,
outcome: row.idle_outcome ?? undefined,
time: {
created: DateTime.makeUnsafe(row.time_created),
updated: DateTime.makeUnsafe(row.time_updated),
idle: row.time_idle === null ? undefined : DateTime.makeUnsafe(row.time_idle),
viewed: row.time_viewed === null ? undefined : DateTime.makeUnsafe(row.time_viewed),
archived: row.time_archived ? DateTime.makeUnsafe(row.time_archived) : undefined,
},
})
}
+1 -2
View File
@@ -19,7 +19,6 @@ import { SessionMessage } from "@opencode/core/session/message"
import { Money } from "@opencode/schema/money"
import { SessionProjector } from "@opencode/core/session/projector"
import { SessionExecution } from "@opencode/core/session/execution"
import { fromRow } from "@opencode/core/session/info"
import { SessionInbox } from "@opencode/core/session/inbox"
import { SessionStore } from "@opencode/core/session/store"
import { Shell } from "@opencode/schema/shell"
@@ -115,7 +114,7 @@ describe("SessionProjector", () => {
yield* db.run(sql`update session_v2 set revert = ${legacy} where id = ${sessionID}`)
const stored = yield* db.select().from(SessionTable).where(eq(SessionTable.id, sessionID)).get()
if (!stored) return yield* Effect.die("Session row missing")
const storedRevert = fromRow(stored).revert
const storedRevert = SessionStore.fromRow(stored).revert
expect(String(storedRevert?.messageID)).toBe("msg_boundary")
expect(String(storedRevert?.snapshot)).toBe("tree")
expect(storedRevert?.files).toEqual([