diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index b292cc7f8..7cbc72163 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -4,8 +4,7 @@ import { Provider } from "../provider/provider" import { generateObject, type ModelMessage } from "ai" import PROMPT_GENERATE from "./generate.txt" import { SystemPrompt } from "../session/system" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace Agent { export const Info = z @@ -25,8 +24,7 @@ export namespace Agent { ref: "Agent", }) export type Info = z.infer - const state = State.create( - () => Paths.directory, + const state = Instance.state( async () => { const cfg = await Config.get() const result: Record = { diff --git a/packages/opencode/src/app/app.ts b/packages/opencode/src/app/app.ts index e279cd70c..de7aff896 100644 --- a/packages/opencode/src/app/app.ts +++ b/packages/opencode/src/app/app.ts @@ -7,8 +7,7 @@ import path from "path" import os from "os" import { z } from "zod" import { Project } from "../project/project" -import { Paths } from "../project/path" -import { State } from "../project/state" +import { Instance } from "../project/instance" export namespace App { const log = Log.create({ service: "app" }) @@ -101,7 +100,7 @@ export namespace App { return ctx.provide(app, async () => { return Project.provide(project, async () => { - const result = await Paths.provide( + const result = await Instance.provide( { worktree: app.info.path.root, directory: app.info.path.cwd, @@ -119,7 +118,7 @@ export namespace App { } }, ) - await State.dispose(app.info.path.cwd) + await Instance.dispose() return result }) }) diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index bd97bd6ff..8c5af8bde 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -1,15 +1,12 @@ import { z, type ZodType } from "zod" import { Log } from "../util/log" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace Bus { const log = Log.create({ service: "bus" }) type Subscription = (event: any) => void - const state = State.create( - () => Paths.directory, - () => { + const state = Instance.state(() => { const subscriptions = new Map() return { diff --git a/packages/opencode/src/cli/cmd/debug/ripgrep.ts b/packages/opencode/src/cli/cmd/debug/ripgrep.ts index 40567cdbf..3e7dcd678 100644 --- a/packages/opencode/src/cli/cmd/debug/ripgrep.ts +++ b/packages/opencode/src/cli/cmd/debug/ripgrep.ts @@ -1,5 +1,5 @@ import { Ripgrep } from "../../../file/ripgrep" -import { Paths } from "../../../project/path" +import { Instance } from "../../../project/instance" import { bootstrap } from "../../bootstrap" import { cmd } from "../cmd" @@ -17,7 +17,7 @@ const TreeCommand = cmd({ }), async handler(args) { await bootstrap({ cwd: process.cwd() }, async () => { - console.log(await Ripgrep.tree({ cwd: Paths.directory, limit: args.limit })) + console.log(await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit })) }) }, }) @@ -41,7 +41,7 @@ const FilesCommand = cmd({ async handler(args) { await bootstrap({ cwd: process.cwd() }, async () => { const files = await Ripgrep.files({ - cwd: Paths.directory, + cwd: Instance.directory, query: args.query, glob: args.glob ? [args.glob] : undefined, limit: args.limit, diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index ca92830e0..666e8b31e 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -20,7 +20,7 @@ import { Provider } from "../../provider/provider" import { Bus } from "../../bus" import { MessageV2 } from "../../session/message-v2" import { Project } from "../../project/project" -import { Paths } from "../../project/path" +import { Instance } from "../../project/instance" type GitHubAuthor = { login: string @@ -197,7 +197,7 @@ export const GithubInstallCommand = cmd({ throw new UI.CancelledError() } const [owner, repo] = parsed[1].split("/") - return { owner, repo, root: Paths.worktree } + return { owner, repo, root: Instance.worktree } } async function promptProvider() { diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 3422eb31a..39042404c 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -13,19 +13,17 @@ import matter from "gray-matter" import { Flag } from "../flag/flag" import { Auth } from "../auth" import { type ParseError as JsoncParseError, parse as parseJsonc, printParseErrorCode } from "jsonc-parser" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace Config { const log = Log.create({ service: "config" }) - export const state = State.create( - () => Paths.directory, + export const state = Instance.state( async () => { const auth = await Auth.all() let result = await global() for (const file of ["opencode.jsonc", "opencode.json"]) { - const found = await Filesystem.findUp(file, Paths.directory, Paths.worktree) + const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree) for (const resolved of found.toReversed()) { result = mergeDeep(result, await loadFile(resolved)) } @@ -48,7 +46,7 @@ export namespace Config { result.agent = result.agent || {} const markdownAgents = [ ...(await Filesystem.globUp("agent/*.md", Global.Path.config, Global.Path.config)), - ...(await Filesystem.globUp(".opencode/agent/*.md", Paths.directory, Paths.worktree)), + ...(await Filesystem.globUp(".opencode/agent/*.md", Instance.directory, Instance.worktree)), ] for (const item of markdownAgents) { const content = await Bun.file(item).text() @@ -74,7 +72,7 @@ export namespace Config { result.mode = result.mode || {} const markdownModes = [ ...(await Filesystem.globUp("mode/*.md", Global.Path.config, Global.Path.config)), - ...(await Filesystem.globUp(".opencode/mode/*.md", Paths.directory, Paths.worktree)), + ...(await Filesystem.globUp(".opencode/mode/*.md", Instance.directory, Instance.worktree)), ] for (const item of markdownModes) { const content = await Bun.file(item).text() @@ -100,7 +98,7 @@ export namespace Config { result.plugin.push( ...[ ...(await Filesystem.globUp("plugin/*.ts", Global.Path.config, Global.Path.config)), - ...(await Filesystem.globUp(".opencode/plugin/*.ts", Paths.directory, Paths.worktree)), + ...(await Filesystem.globUp(".opencode/plugin/*.ts", Instance.directory, Instance.worktree)), ].map((x) => "file://" + x), ) diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index bc11c1ca0..0856a69b2 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -6,7 +6,7 @@ import path from "path" import * as git from "isomorphic-git" import fs from "fs" import { Log } from "../util/log" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" import { Project } from "../project/project" export namespace File { @@ -38,7 +38,7 @@ export namespace File { const project = Project.use() if (project.vcs !== "git") return [] - const diffOutput = await $`git diff --numstat HEAD`.cwd(Paths.directory).quiet().nothrow().text() + const diffOutput = await $`git diff --numstat HEAD`.cwd(Instance.directory).quiet().nothrow().text() const changedFiles: Info[] = [] @@ -56,7 +56,7 @@ export namespace File { } const untrackedOutput = await $`git ls-files --others --exclude-standard` - .cwd(Paths.directory) + .cwd(Instance.directory) .quiet() .nothrow() .text() @@ -65,7 +65,7 @@ export namespace File { const untrackedFiles = untrackedOutput.trim().split("\n") for (const filepath of untrackedFiles) { try { - const content = await Bun.file(path.join(Paths.worktree, filepath)).text() + const content = await Bun.file(path.join(Instance.worktree, filepath)).text() const lines = content.split("\n").length changedFiles.push({ path: filepath, @@ -81,7 +81,7 @@ export namespace File { // Get deleted files const deletedOutput = await $`git diff --name-only --diff-filter=D HEAD` - .cwd(Paths.directory) + .cwd(Instance.directory) .quiet() .nothrow() .text() @@ -100,27 +100,27 @@ export namespace File { return changedFiles.map((x) => ({ ...x, - path: path.relative(Paths.directory, path.join(Paths.worktree, x.path)), + path: path.relative(Instance.directory, path.join(Instance.worktree, x.path)), })) } export async function read(file: string) { using _ = log.time("read", { file }) const project = Project.use() - const full = path.join(Paths.directory, file) + const full = path.join(Instance.directory, file) const content = await Bun.file(full) .text() .catch(() => "") .then((x) => x.trim()) if (project.vcs === "git") { - const rel = path.relative(Paths.worktree, full) + const rel = path.relative(Instance.worktree, full) const diff = await git.status({ fs, - dir: Paths.worktree, + dir: Instance.worktree, filepath: rel, }) if (diff !== "unmodified") { - const original = await $`git show HEAD:${rel}`.cwd(Paths.worktree).quiet().nothrow().text() + const original = await $`git show HEAD:${rel}`.cwd(Instance.worktree).quiet().nothrow().text() const patch = createPatch(file, original, content, "old", "new", { context: Infinity, }) diff --git a/packages/opencode/src/file/time.ts b/packages/opencode/src/file/time.ts index cfc224452..ab973bd3d 100644 --- a/packages/opencode/src/file/time.ts +++ b/packages/opencode/src/file/time.ts @@ -1,11 +1,9 @@ -import { Paths } from "../project/path" -import { State } from "../project/state" +import { Instance } from "../project/instance" import { Log } from "../util/log" export namespace FileTime { const log = Log.create({ service: "file.time" }) - export const state = State.create( - () => Paths.directory, + export const state = Instance.state( () => { const read: { [sessionID: string]: { diff --git a/packages/opencode/src/file/watch.ts b/packages/opencode/src/file/watch.ts index b86e44b48..f9a943cd4 100644 --- a/packages/opencode/src/file/watch.ts +++ b/packages/opencode/src/file/watch.ts @@ -4,8 +4,7 @@ import fs from "fs" import { App } from "../app/app" import { Log } from "../util/log" import { Flag } from "../flag/flag" -import { Paths } from "../project/path" -import { State } from "../project/state" +import { Instance } from "../project/instance" export namespace FileWatcher { const log = Log.create({ service: "file.watcher" }) @@ -19,8 +18,7 @@ export namespace FileWatcher { }), ), } - const state = State.create( - () => Paths.directory, + const state = Instance.state( () => { const app = App.use() if (!app.info.git) return {} diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index f7023d4bc..12fea371d 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -1,5 +1,5 @@ import { BunProc } from "../bun" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" import { Filesystem } from "../util/filesystem" export interface Info { @@ -63,7 +63,7 @@ export const prettier: Info = { ".gql", ], async enabled() { - const items = await Filesystem.findUp("package.json", Paths.directory, Paths.worktree) + const items = await Filesystem.findUp("package.json", Instance.directory, Instance.worktree) for (const item of items) { const json = await Bun.file(item).json() if (json.dependencies?.prettier) return true @@ -108,7 +108,7 @@ export const biome: Info = { ".gql", ], async enabled() { - const items = await Filesystem.findUp("biome.json", Paths.directory, Paths.worktree) + const items = await Filesystem.findUp("biome.json", Instance.directory, Instance.worktree) return items.length > 0 }, } @@ -127,7 +127,7 @@ export const clang: Info = { command: ["clang-format", "-i", "$FILE"], extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"], async enabled() { - const items = await Filesystem.findUp(".clang-format", Paths.directory, Paths.worktree) + const items = await Filesystem.findUp(".clang-format", Instance.directory, Instance.worktree) return items.length > 0 }, } @@ -149,7 +149,7 @@ export const ruff: Info = { if (!Bun.which("ruff")) return false const configs = ["pyproject.toml", "ruff.toml", ".ruff.toml"] for (const config of configs) { - const found = await Filesystem.findUp(config, Paths.directory, Paths.worktree) + const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree) if (found.length > 0) { if (config === "pyproject.toml") { const content = await Bun.file(found[0]).text() @@ -161,7 +161,7 @@ export const ruff: Info = { } const deps = ["requirements.txt", "pyproject.toml", "Pipfile"] for (const dep of deps) { - const found = await Filesystem.findUp(dep, Paths.directory, Paths.worktree) + const found = await Filesystem.findUp(dep, Instance.directory, Instance.worktree) if (found.length > 0) { const content = await Bun.file(found[0]).text() if (content.includes("ruff")) return true diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 1fe8a4ccd..9cf2cf98f 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -6,14 +6,12 @@ import path from "path" import * as Formatter from "./formatter" import { Config } from "../config/config" import { mergeDeep } from "remeda" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace Format { const log = Log.create({ service: "format" }) - const state = State.create( - () => Paths.directory, + const state = Instance.state( async () => { const enabled: Record = {} const cfg = await Config.get() @@ -74,7 +72,7 @@ export namespace Format { log.info("running", { command: item.command }) const proc = Bun.spawn({ cmd: item.command.map((x) => x.replace("$FILE", file)), - cwd: Paths.directory, + cwd: Instance.directory, env: item.environment, stdout: "ignore", stderr: "ignore", diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index e17f98d41..1d0ce59f8 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -8,7 +8,7 @@ import z from "zod" import type { LSPServer } from "./server" import { NamedError } from "../util/error" import { withTimeout } from "../util/timeout" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace LSPClient { const log = Log.create({ service: "lsp.client" }) @@ -122,7 +122,7 @@ export namespace LSPClient { }, notify: { async open(input: { path: string }) { - input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Paths.directory, input.path) + input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path) const file = Bun.file(input.path) const text = await file.text() const version = files[input.path] @@ -154,7 +154,7 @@ export namespace LSPClient { return diagnostics }, async waitForDiagnostics(input: { path: string }) { - input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Paths.directory, input.path) + input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path) log.info("waiting for diagnostics", input) let unsub: () => void return await withTimeout( diff --git a/packages/opencode/src/lsp/index.ts b/packages/opencode/src/lsp/index.ts index e4e5aeb0c..1d8ab78be 100644 --- a/packages/opencode/src/lsp/index.ts +++ b/packages/opencode/src/lsp/index.ts @@ -5,8 +5,7 @@ import { LSPServer } from "./server" import { z } from "zod" import { Config } from "../config/config" import { spawn } from "child_process" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace LSP { const log = Log.create({ service: "lsp" }) @@ -54,8 +53,7 @@ export namespace LSP { }) export type DocumentSymbol = z.infer - const state = State.create( - () => Paths.directory, + const state = Instance.state( async () => { const clients: LSPClient.Info[] = [] const servers: Record = LSPServer @@ -68,7 +66,7 @@ export namespace LSP { } servers[name] = { ...existing, - root: existing?.root ?? (async () => Paths.directory), + root: existing?.root ?? (async () => Instance.directory), extensions: item.extensions ?? existing.extensions, spawn: async (root) => { return { diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index f781e1fe7..7d40ccef3 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -6,7 +6,7 @@ import { BunProc } from "../bun" import { $ } from "bun" import fs from "fs/promises" import { Filesystem } from "../util/filesystem" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace LSPServer { const log = Log.create({ service: "lsp.server" }) @@ -23,11 +23,11 @@ export namespace LSPServer { const files = Filesystem.up({ targets: patterns, start: path.dirname(file), - stop: Paths.worktree, + stop: Instance.worktree, }) const first = await files.next() await files.return() - if (!first.value) return Paths.worktree + if (!first.value) return Instance.worktree return path.dirname(first.value) } } @@ -45,7 +45,7 @@ export namespace LSPServer { root: NearestRoot(["tsconfig.json", "package.json", "jsconfig.json"]), extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"], async spawn(root) { - const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Paths.directory).catch(() => {}) + const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Instance.directory).catch(() => {}) if (!tsserver) return const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], { cwd: root, diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 15815f966..050743fa6 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -8,8 +8,7 @@ import { NamedError } from "../util/error" import { z } from "zod" import { Session } from "../session" import { Bus } from "../bus" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace MCP { const log = Log.create({ service: "mcp" }) @@ -21,8 +20,7 @@ export namespace MCP { }), ) - const state = State.create( - () => Paths.directory, + const state = Instance.state( async () => { const cfg = await Config.get() const clients: { diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index e9525160a..ed3c7e6db 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -3,8 +3,7 @@ import { Bus } from "../bus" import { Log } from "../util/log" import { Identifier } from "../id/id" import { Plugin } from "../plugin" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace Permission { const log = Log.create({ service: "permission" }) @@ -36,8 +35,7 @@ export namespace Permission { ), } - const state = State.create( - () => Paths.directory, + const state = Instance.state( () => { const pending: { [sessionID: string]: { diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 5c254a079..c4603d5fa 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -5,14 +5,12 @@ import { Log } from "../util/log" import { createOpencodeClient } from "@opencode-ai/sdk" import { Server } from "../server/server" import { BunProc } from "../bun" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace Plugin { const log = Log.create({ service: "plugin" }) - const state = State.create( - () => Paths.directory, + const state = Instance.state( async () => { const client = createOpencodeClient({ baseUrl: "http://localhost:4096", diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts new file mode 100644 index 000000000..3af1c9fde --- /dev/null +++ b/packages/opencode/src/project/instance.ts @@ -0,0 +1,20 @@ +import { Context } from "../util/context" +import { State } from "./state" + +const context = Context.create<{ directory: string; worktree: string }>("path") + +export const Instance = { + provide: context.provide, + get directory() { + return context.use().directory + }, + get worktree() { + return context.use().worktree + }, + state(init: () => S, dispose?: (state: Awaited) => Promise): () => S { + return State.create(() => Instance.directory, init, dispose) + }, + async dispose() { + await State.dispose(Instance.directory) + }, +} diff --git a/packages/opencode/src/project/path.ts b/packages/opencode/src/project/path.ts deleted file mode 100644 index acbed3158..000000000 --- a/packages/opencode/src/project/path.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Context } from "../util/context" - -const context = Context.create<{ directory: string; worktree: string }>("path") - -export const Paths = { - provide: context.provide, - get directory() { - return context.use().directory - }, - get worktree() { - return context.use().worktree - }, -} diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 00f577946..47f9b70ff 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -9,8 +9,7 @@ import { AuthCopilot } from "../auth/copilot" import { ModelsDev } from "./models" import { NamedError } from "../util/error" import { Auth } from "../auth" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace Provider { const log = Log.create({ service: "provider" }) @@ -217,152 +216,149 @@ export namespace Provider { }, } - const state = State.create( - () => Paths.directory, - async () => { - const config = await Config.get() - const database = await ModelsDev.get() + const state = Instance.state(async () => { + const config = await Config.get() + const database = await ModelsDev.get() - const providers: { - [providerID: string]: { - source: Source - info: ModelsDev.Provider - getModel?: (sdk: any, modelID: string) => Promise - options: Record + const providers: { + [providerID: string]: { + source: Source + info: ModelsDev.Provider + getModel?: (sdk: any, modelID: string) => Promise + options: Record + } + } = {} + const models = new Map() + const sdk = new Map() + + log.info("init") + + function mergeProvider( + id: string, + options: Record, + source: Source, + getModel?: (sdk: any, modelID: string) => Promise, + ) { + const provider = providers[id] + if (!provider) { + const info = database[id] + if (!info) return + if (info.api && !options["baseURL"]) options["baseURL"] = info.api + providers[id] = { + source, + info, + options, + getModel, } - } = {} - const models = new Map() - const sdk = new Map() + return + } + provider.options = mergeDeep(provider.options, options) + provider.source = source + provider.getModel = getModel ?? provider.getModel + } - log.info("init") + const configProviders = Object.entries(config.provider ?? {}) - function mergeProvider( - id: string, - options: Record, - source: Source, - getModel?: (sdk: any, modelID: string) => Promise, - ) { - const provider = providers[id] - if (!provider) { - const info = database[id] - if (!info) return - if (info.api && !options["baseURL"]) options["baseURL"] = info.api - providers[id] = { - source, - info, - options, - getModel, - } - return - } - provider.options = mergeDeep(provider.options, options) - provider.source = source - provider.getModel = getModel ?? provider.getModel + for (const [providerID, provider] of configProviders) { + const existing = database[providerID] + const parsed: ModelsDev.Provider = { + id: providerID, + npm: provider.npm ?? existing?.npm, + name: provider.name ?? existing?.name ?? providerID, + env: provider.env ?? existing?.env ?? [], + api: provider.api ?? existing?.api, + models: existing?.models ?? {}, } - const configProviders = Object.entries(config.provider ?? {}) - - for (const [providerID, provider] of configProviders) { - const existing = database[providerID] - const parsed: ModelsDev.Provider = { - id: providerID, - npm: provider.npm ?? existing?.npm, - name: provider.name ?? existing?.name ?? providerID, - env: provider.env ?? existing?.env ?? [], - api: provider.api ?? existing?.api, - models: existing?.models ?? {}, - } - - for (const [modelID, model] of Object.entries(provider.models ?? {})) { - const existing = parsed.models[modelID] - const parsedModel: ModelsDev.Model = { - id: modelID, - name: model.name ?? existing?.name ?? modelID, - release_date: model.release_date ?? existing?.release_date, - attachment: model.attachment ?? existing?.attachment ?? false, - reasoning: model.reasoning ?? existing?.reasoning ?? false, - temperature: model.temperature ?? existing?.temperature ?? false, - tool_call: model.tool_call ?? existing?.tool_call ?? true, - cost: - !model.cost && !existing?.cost - ? { - input: 0, - output: 0, - cache_read: 0, - cache_write: 0, - } - : { - cache_read: 0, - cache_write: 0, - ...existing?.cost, - ...model.cost, - }, - options: { - ...existing?.options, - ...model.options, + for (const [modelID, model] of Object.entries(provider.models ?? {})) { + const existing = parsed.models[modelID] + const parsedModel: ModelsDev.Model = { + id: modelID, + name: model.name ?? existing?.name ?? modelID, + release_date: model.release_date ?? existing?.release_date, + attachment: model.attachment ?? existing?.attachment ?? false, + reasoning: model.reasoning ?? existing?.reasoning ?? false, + temperature: model.temperature ?? existing?.temperature ?? false, + tool_call: model.tool_call ?? existing?.tool_call ?? true, + cost: + !model.cost && !existing?.cost + ? { + input: 0, + output: 0, + cache_read: 0, + cache_write: 0, + } + : { + cache_read: 0, + cache_write: 0, + ...existing?.cost, + ...model.cost, + }, + options: { + ...existing?.options, + ...model.options, + }, + limit: model.limit ?? + existing?.limit ?? { + context: 0, + output: 0, }, - limit: model.limit ?? - existing?.limit ?? { - context: 0, - output: 0, - }, - } - parsed.models[modelID] = parsedModel } - database[providerID] = parsed + parsed.models[modelID] = parsedModel } + database[providerID] = parsed + } - const disabled = await Config.get().then((cfg) => new Set(cfg.disabled_providers ?? [])) - // load env - for (const [providerID, provider] of Object.entries(database)) { - if (disabled.has(providerID)) continue - const apiKey = provider.env.map((item) => process.env[item]).at(0) - if (!apiKey) continue - mergeProvider( - providerID, - // only include apiKey if there's only one potential option - provider.env.length === 1 ? { apiKey } : {}, - "env", - ) - } + const disabled = await Config.get().then((cfg) => new Set(cfg.disabled_providers ?? [])) + // load env + for (const [providerID, provider] of Object.entries(database)) { + if (disabled.has(providerID)) continue + const apiKey = provider.env.map((item) => process.env[item]).at(0) + if (!apiKey) continue + mergeProvider( + providerID, + // only include apiKey if there's only one potential option + provider.env.length === 1 ? { apiKey } : {}, + "env", + ) + } - // load apikeys - for (const [providerID, provider] of Object.entries(await Auth.all())) { - if (disabled.has(providerID)) continue - if (provider.type === "api") { - mergeProvider(providerID, { apiKey: provider.key }, "api") - } + // load apikeys + for (const [providerID, provider] of Object.entries(await Auth.all())) { + if (disabled.has(providerID)) continue + if (provider.type === "api") { + mergeProvider(providerID, { apiKey: provider.key }, "api") } + } - // load custom - for (const [providerID, fn] of Object.entries(CUSTOM_LOADERS)) { - if (disabled.has(providerID)) continue - const result = await fn(database[providerID]) - if (result && (result.autoload || providers[providerID])) { - mergeProvider(providerID, result.options ?? {}, "custom", result.getModel) - } + // load custom + for (const [providerID, fn] of Object.entries(CUSTOM_LOADERS)) { + if (disabled.has(providerID)) continue + const result = await fn(database[providerID]) + if (result && (result.autoload || providers[providerID])) { + mergeProvider(providerID, result.options ?? {}, "custom", result.getModel) } + } - // load config - for (const [providerID, provider] of configProviders) { - mergeProvider(providerID, provider.options ?? {}, "config") - } + // load config + for (const [providerID, provider] of configProviders) { + mergeProvider(providerID, provider.options ?? {}, "config") + } - for (const [providerID, provider] of Object.entries(providers)) { - if (Object.keys(provider.info.models).length === 0) { - delete providers[providerID] - continue - } - log.info("found", { providerID }) + for (const [providerID, provider] of Object.entries(providers)) { + if (Object.keys(provider.info.models).length === 0) { + delete providers[providerID] + continue } + log.info("found", { providerID }) + } - return { - models, - providers, - sdk, - } - }, - ) + return { + models, + providers, + sdk, + } + }) export async function list() { return state().then((state) => state.providers) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index d6cd940f3..75ccc0e31 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -20,7 +20,7 @@ import { Mode } from "../session/mode" import { callTui, TuiRoute } from "./tui" import { Permission } from "../permission" import { lazy } from "../util/lazy" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" const ERRORS = { 400: { @@ -695,7 +695,7 @@ export namespace Server { async (c) => { const pattern = c.req.valid("query").pattern const result = await Ripgrep.search({ - cwd: Paths.directory, + cwd: Instance.directory, pattern, limit: 10, }) @@ -727,7 +727,7 @@ export namespace Server { async (c) => { const query = c.req.valid("query").query const result = await Ripgrep.files({ - cwd: Paths.directory, + cwd: Instance.directory, query, limit: 10, }) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 7875fc6ff..a554336eb 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -41,8 +41,8 @@ import { mergeDeep, pipe, splitWhen } from "remeda" import { ToolRegistry } from "../tool/registry" import { Plugin } from "../plugin" import { Project } from "../project/project" -import { State } from "../project/state" -import { Paths } from "../project/path" + +import { Instance } from "../project/instance" export namespace Session { const log = Log.create({ service: "session" }) @@ -65,7 +65,6 @@ export namespace Session { id: Identifier.schema("session"), projectID: z.string(), directory: z.string(), - worktree: z.string(), parentID: Identifier.schema("session").optional(), share: z .object({ @@ -130,8 +129,7 @@ export namespace Session { ), } - const state = State.create( - () => Paths.directory, + const state = Instance.state( () => { const pending = new Map() const autoCompacting = new Map() @@ -162,7 +160,7 @@ export namespace Session { export async function create(parentID?: string) { return createNext({ parentID, - directory: Paths.directory, + directory: Instance.directory, }) } @@ -172,7 +170,6 @@ export namespace Session { id: Identifier.descending("session", input.id), version: Installation.VERSION, projectID: project.id, - worktree: project.worktree, directory: input.directory, parentID: input.parentID, title: createDefaultTitle(!!input.parentID), @@ -711,8 +708,8 @@ export namespace Session { system, mode: inputMode, path: { - cwd: Paths.directory, - root: Paths.worktree, + cwd: Instance.directory, + root: Instance.worktree, }, cost: 0, tokens: { @@ -838,6 +835,7 @@ export namespace Session { }, params, ) + console.log(outputLimit) const stream = streamText({ onError(e) { log.error("streamText error", { @@ -866,8 +864,8 @@ export namespace Session { role: "assistant", system, path: { - cwd: Paths.directory, - root: Paths.worktree, + cwd: Instance.directory, + root: Instance.worktree, }, cost: 0, tokens: { @@ -1276,8 +1274,8 @@ export namespace Session { system, mode: "build", path: { - cwd: Paths.directory, - root: Paths.worktree, + cwd: Instance.directory, + root: Instance.worktree, }, summary: true, cost: 0, @@ -1400,7 +1398,7 @@ export namespace Session { { id: Identifier.ascending("part"), type: "text", - text: PROMPT_INITIALIZE.replace("${path}", Paths.worktree), + text: PROMPT_INITIALIZE.replace("${path}", Instance.worktree), }, ], }) diff --git a/packages/opencode/src/session/mode.ts b/packages/opencode/src/session/mode.ts index 5a3600466..b4ad50c4a 100644 --- a/packages/opencode/src/session/mode.ts +++ b/packages/opencode/src/session/mode.ts @@ -1,8 +1,7 @@ import { Config } from "../config/config" import z from "zod" import { Provider } from "../provider/provider" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace Mode { export const Info = z @@ -23,50 +22,47 @@ export namespace Mode { ref: "Mode", }) export type Info = z.infer - const state = State.create( - () => Paths.directory, - async () => { - const cfg = await Config.get() - const model = cfg.model ? Provider.parseModel(cfg.model) : undefined - const result: Record = { - build: { - model, - name: "build", + const state = Instance.state(async () => { + const cfg = await Config.get() + const model = cfg.model ? Provider.parseModel(cfg.model) : undefined + const result: Record = { + build: { + model, + name: "build", + tools: {}, + }, + plan: { + name: "plan", + model, + tools: { + write: false, + edit: false, + patch: false, + }, + }, + } + for (const [key, value] of Object.entries(cfg.mode ?? {})) { + if (value.disable) continue + let item = result[key] + if (!item) + item = result[key] = { + name: key, tools: {}, - }, - plan: { - name: "plan", - model, - tools: { - write: false, - edit: false, - patch: false, - }, - }, - } - for (const [key, value] of Object.entries(cfg.mode ?? {})) { - if (value.disable) continue - let item = result[key] - if (!item) - item = result[key] = { - name: key, - tools: {}, - } - item.name = key - if (value.model) item.model = Provider.parseModel(value.model) - if (value.prompt) item.prompt = value.prompt - if (value.temperature != undefined) item.temperature = value.temperature - if (value.top_p != undefined) item.topP = value.top_p - if (value.tools) - item.tools = { - ...value.tools, - ...item.tools, - } - } + } + item.name = key + if (value.model) item.model = Provider.parseModel(value.model) + if (value.prompt) item.prompt = value.prompt + if (value.temperature != undefined) item.temperature = value.temperature + if (value.top_p != undefined) item.topP = value.top_p + if (value.tools) + item.tools = { + ...value.tools, + ...item.tools, + } + } - return result - }, - ) + return result + }) export async function get(mode: string) { return state().then((x) => x[mode]) diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index 9578d5aea..68fa482b3 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -13,7 +13,7 @@ import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt" import PROMPT_SUMMARIZE from "./prompt/summarize.txt" import PROMPT_TITLE from "./prompt/title.txt" import { Project } from "../project/project" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace SystemPrompt { export function header(providerID: string) { @@ -33,7 +33,7 @@ export namespace SystemPrompt { [ `Here is some useful information about the environment you are running in:`, ``, - ` Working directory: ${Paths.directory}`, + ` Working directory: ${Instance.directory}`, ` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`, ` Platform: ${process.platform}`, ` Today's date: ${new Date().toDateString()}`, @@ -42,7 +42,7 @@ export namespace SystemPrompt { ` ${ project.vcs === "git" ? await Ripgrep.tree({ - cwd: Paths.directory, + cwd: Instance.directory, limit: 200, }) : "" @@ -63,7 +63,7 @@ export namespace SystemPrompt { const paths = new Set() for (const item of CUSTOM_FILES) { - const matches = await Filesystem.findUp(item, Paths.directory, Paths.worktree) + const matches = await Filesystem.findUp(item, Instance.directory, Instance.worktree) matches.forEach((path) => paths.add(path)) } @@ -72,7 +72,7 @@ export namespace SystemPrompt { if (config.instructions) { for (const instruction of config.instructions) { - const matches = await Filesystem.globUp(instruction, Paths.directory, Paths.worktree).catch(() => []) + const matches = await Filesystem.globUp(instruction, Instance.directory, Instance.worktree).catch(() => []) matches.forEach((path) => paths.add(path)) } } diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index a191a60fa..8cdeeca6c 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -6,7 +6,7 @@ import { Global } from "../global" import { z } from "zod" import { Config } from "../config/config" import { Project } from "../project/project" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export namespace Snapshot { const log = Log.create({ service: "snapshot" }) @@ -36,14 +36,14 @@ export namespace Snapshot { .env({ ...process.env, GIT_DIR: git, - GIT_WORK_TREE: Paths.worktree, + GIT_WORK_TREE: Instance.worktree, }) .quiet() .nothrow() log.info("initialized") } - await $`git --git-dir ${git} add .`.quiet().cwd(Paths.directory).nothrow() - const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(Paths.directory).nothrow().text() + await $`git --git-dir ${git} add .`.quiet().cwd(Instance.directory).nothrow() + const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(Instance.directory).nothrow().text() return hash.trim() } @@ -55,8 +55,8 @@ export namespace Snapshot { export async function patch(hash: string): Promise { const git = gitdir() - await $`git --git-dir ${git} add .`.quiet().cwd(Paths.directory).nothrow() - const files = await $`git --git-dir ${git} diff --name-only ${hash} -- .`.cwd(Paths.directory).text() + await $`git --git-dir ${git} add .`.quiet().cwd(Instance.directory).nothrow() + const files = await $`git --git-dir ${git} diff --name-only ${hash} -- .`.cwd(Instance.directory).text() return { hash, files: files @@ -64,7 +64,7 @@ export namespace Snapshot { .split("\n") .map((x) => x.trim()) .filter(Boolean) - .map((x) => path.join(Paths.directory, x)), + .map((x) => path.join(Instance.directory, x)), } } @@ -73,7 +73,7 @@ export namespace Snapshot { const git = gitdir() await $`git --git-dir=${git} read-tree ${snapshot} && git --git-dir=${git} checkout-index -a -f` .quiet() - .cwd(Paths.worktree) + .cwd(Instance.worktree) } export async function revert(patches: Patch[]) { @@ -85,7 +85,7 @@ export namespace Snapshot { log.info("reverting", { file, hash: item.hash }) const result = await $`git --git-dir=${git} checkout ${item.hash} -- ${file}` .quiet() - .cwd(Paths.worktree) + .cwd(Instance.worktree) .nothrow() if (result.exitCode !== 0) { log.info("file not found in history, deleting", { file }) @@ -98,7 +98,7 @@ export namespace Snapshot { export async function diff(hash: string) { const git = gitdir() - const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(Paths.worktree).text() + const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(Instance.worktree).text() return result.trim() } diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 1db68813d..bb5893d1e 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -10,7 +10,7 @@ import { lazy } from "../util/lazy" import { Log } from "../util/log" import { Wildcard } from "../util/wildcard" import { $ } from "bun" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" const MAX_OUTPUT_LENGTH = 30000 const DEFAULT_TIMEOUT = 1 * 60 * 1000 @@ -82,9 +82,9 @@ export const BashTool = Tool.define("bash", { .text() .then((x) => x.trim()) log.info("resolved path", { arg, resolved }) - if (resolved && !Filesystem.contains(Paths.directory, resolved)) { + if (resolved && !Filesystem.contains(Instance.directory, resolved)) { throw new Error( - `This command references paths outside of ${Paths.directory} so it is not allowed to be executed.`, + `This command references paths outside of ${Instance.directory} so it is not allowed to be executed.`, ) } } @@ -123,7 +123,7 @@ export const BashTool = Tool.define("bash", { } const process = exec(params.command, { - cwd: Paths.directory, + cwd: Instance.directory, signal: ctx.abort, maxBuffer: MAX_OUTPUT_LENGTH, timeout, diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 42c0fb5a2..2a51c0aca 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -15,7 +15,7 @@ import { Bus } from "../bus" import { FileTime } from "../file/time" import { Config } from "../config/config" import { Filesystem } from "../util/filesystem" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export const EditTool = Tool.define("edit", { description: DESCRIPTION, @@ -34,8 +34,8 @@ export const EditTool = Tool.define("edit", { throw new Error("oldString and newString must be different") } - const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Paths.directory, params.filePath) - if (!Filesystem.contains(Paths.directory, filePath)) { + const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath) + if (!Filesystem.contains(Instance.directory, filePath)) { throw new Error(`File ${filePath} is not in the current working directory`) } @@ -120,7 +120,7 @@ export const EditTool = Tool.define("edit", { diagnostics, diff, }, - title: `${path.relative(Paths.worktree, filePath)}`, + title: `${path.relative(Instance.worktree, filePath)}`, output, } }, diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts index eec734a4e..9534f0aff 100644 --- a/packages/opencode/src/tool/glob.ts +++ b/packages/opencode/src/tool/glob.ts @@ -3,7 +3,7 @@ import path from "path" import { Tool } from "./tool" import DESCRIPTION from "./glob.txt" import { Ripgrep } from "../file/ripgrep" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export const GlobTool = Tool.define("glob", { description: DESCRIPTION, @@ -17,8 +17,8 @@ export const GlobTool = Tool.define("glob", { ), }), async execute(params) { - let search = params.path ?? Paths.directory - search = path.isAbsolute(search) ? search : path.resolve(Paths.directory, search) + let search = params.path ?? Instance.directory + search = path.isAbsolute(search) ? search : path.resolve(Instance.directory, search) const limit = 100 const files = [] @@ -54,7 +54,7 @@ export const GlobTool = Tool.define("glob", { } return { - title: path.relative(Paths.worktree, search), + title: path.relative(Instance.worktree, search), metadata: { count: files.length, truncated, diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index 9d81a3c89..a8a42a825 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/packages/opencode/src/tool/grep.ts @@ -3,7 +3,7 @@ import { Tool } from "./tool" import { Ripgrep } from "../file/ripgrep" import DESCRIPTION from "./grep.txt" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export const GrepTool = Tool.define("grep", { description: DESCRIPTION, @@ -17,7 +17,7 @@ export const GrepTool = Tool.define("grep", { throw new Error("pattern is required") } - const searchPath = params.path || Paths.directory + const searchPath = params.path || Instance.directory const rgPath = await Ripgrep.filepath() const args = ["-n", params.pattern] diff --git a/packages/opencode/src/tool/ls.ts b/packages/opencode/src/tool/ls.ts index e1f072a0a..00dbcbd3f 100644 --- a/packages/opencode/src/tool/ls.ts +++ b/packages/opencode/src/tool/ls.ts @@ -2,7 +2,7 @@ import { z } from "zod" import { Tool } from "./tool" import * as path from "path" import DESCRIPTION from "./ls.txt" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export const IGNORE_PATTERNS = [ "node_modules/", @@ -40,7 +40,7 @@ export const ListTool = Tool.define("list", { ignore: z.array(z.string()).describe("List of glob patterns to ignore").optional(), }), async execute(params) { - const searchPath = path.resolve(Paths.directory, params.path || ".") + const searchPath = path.resolve(Instance.directory, params.path || ".") const glob = new Bun.Glob("**/*") const files = [] @@ -101,7 +101,7 @@ export const ListTool = Tool.define("list", { const output = `${searchPath}/\n` + renderDir(".", 0) return { - title: path.relative(Paths.worktree, searchPath), + title: path.relative(Instance.worktree, searchPath), metadata: { count: files.length, truncated: files.length >= LIMIT, diff --git a/packages/opencode/src/tool/lsp-diagnostics.ts b/packages/opencode/src/tool/lsp-diagnostics.ts index bc9556e45..b69e84851 100644 --- a/packages/opencode/src/tool/lsp-diagnostics.ts +++ b/packages/opencode/src/tool/lsp-diagnostics.ts @@ -3,7 +3,7 @@ import { Tool } from "./tool" import path from "path" import { LSP } from "../lsp" import DESCRIPTION from "./lsp-diagnostics.txt" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export const LspDiagnosticTool = Tool.define("lsp_diagnostics", { description: DESCRIPTION, @@ -11,12 +11,12 @@ export const LspDiagnosticTool = Tool.define("lsp_diagnostics", { path: z.string().describe("The path to the file to get diagnostics."), }), execute: async (args) => { - const normalized = path.isAbsolute(args.path) ? args.path : path.join(Paths.directory, args.path) + const normalized = path.isAbsolute(args.path) ? args.path : path.join(Instance.directory, args.path) await LSP.touchFile(normalized, true) const diagnostics = await LSP.diagnostics() const file = diagnostics[normalized] return { - title: path.relative(Paths.worktree, normalized), + title: path.relative(Instance.worktree, normalized), metadata: { diagnostics, }, diff --git a/packages/opencode/src/tool/lsp-hover.ts b/packages/opencode/src/tool/lsp-hover.ts index 9404b8b31..b33a4e804 100644 --- a/packages/opencode/src/tool/lsp-hover.ts +++ b/packages/opencode/src/tool/lsp-hover.ts @@ -3,7 +3,7 @@ import { Tool } from "./tool" import path from "path" import { LSP } from "../lsp" import DESCRIPTION from "./lsp-hover.txt" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export const LspHoverTool = Tool.define("lsp_hover", { description: DESCRIPTION, @@ -13,7 +13,7 @@ export const LspHoverTool = Tool.define("lsp_hover", { character: z.number().describe("The character number to get diagnostics."), }), execute: async (args) => { - const file = path.isAbsolute(args.file) ? args.file : path.join(Paths.directory, args.file) + const file = path.isAbsolute(args.file) ? args.file : path.join(Instance.directory, args.file) await LSP.touchFile(file, true) const result = await LSP.hover({ ...args, @@ -21,7 +21,7 @@ export const LspHoverTool = Tool.define("lsp_hover", { }) return { - title: path.relative(Paths.worktree, file) + ":" + args.line + ":" + args.character, + title: path.relative(Instance.worktree, file) + ":" + args.line + ":" + args.character, metadata: { result, }, diff --git a/packages/opencode/src/tool/multiedit.ts b/packages/opencode/src/tool/multiedit.ts index 61ffa06cd..8ae81ab96 100644 --- a/packages/opencode/src/tool/multiedit.ts +++ b/packages/opencode/src/tool/multiedit.ts @@ -3,7 +3,7 @@ import { Tool } from "./tool" import { EditTool } from "./edit" import DESCRIPTION from "./multiedit.txt" import path from "path" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export const MultiEditTool = Tool.define("multiedit", { description: DESCRIPTION, @@ -36,7 +36,7 @@ export const MultiEditTool = Tool.define("multiedit", { results.push(result) } return { - title: path.relative(Paths.worktree, params.filePath), + title: path.relative(Instance.worktree, params.filePath), metadata: { results: results.map((r) => r.metadata), }, diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index b1702f6cf..cebd166ab 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -6,7 +6,7 @@ import { LSP } from "../lsp" import { FileTime } from "../file/time" import DESCRIPTION from "./read.txt" import { Filesystem } from "../util/filesystem" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" const DEFAULT_READ_LIMIT = 2000 const MAX_LINE_LENGTH = 2000 @@ -23,7 +23,7 @@ export const ReadTool = Tool.define("read", { if (!path.isAbsolute(filepath)) { filepath = path.join(process.cwd(), filepath) } - if (!Filesystem.contains(Paths.directory, filepath)) { + if (!Filesystem.contains(Instance.directory, filepath)) { throw new Error(`File ${filepath} is not in the current working directory`) } @@ -76,7 +76,7 @@ export const ReadTool = Tool.define("read", { FileTime.read(ctx.sessionID, filepath) return { - title: path.relative(Paths.worktree, filepath), + title: path.relative(Instance.worktree, filepath), output, metadata: { preview, diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts index 686293ed6..99e71e45b 100644 --- a/packages/opencode/src/tool/todo.ts +++ b/packages/opencode/src/tool/todo.ts @@ -1,8 +1,7 @@ import { z } from "zod" import { Tool } from "./tool" import DESCRIPTION_WRITE from "./todowrite.txt" -import { State } from "../project/state" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" const TodoInfo = z.object({ content: z.string().describe("Brief description of the task"), @@ -12,8 +11,7 @@ const TodoInfo = z.object({ }) type TodoInfo = z.infer -const state = State.create( - () => Paths.directory, +const state = Instance.state( () => { const todos: { [sessionId: string]: TodoInfo[] diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 5ffaf6dcd..2d4a95eb4 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -9,7 +9,7 @@ import { File } from "../file" import { FileTime } from "../file/time" import { Config } from "../config/config" import { Filesystem } from "../util/filesystem" -import { Paths } from "../project/path" +import { Instance } from "../project/instance" export const WriteTool = Tool.define("write", { description: DESCRIPTION, @@ -18,8 +18,8 @@ export const WriteTool = Tool.define("write", { content: z.string().describe("The content to write to the file"), }), async execute(params, ctx) { - const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Paths.directory, params.filePath) - if (!Filesystem.contains(Paths.directory, filepath)) { + const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath) + if (!Filesystem.contains(Instance.directory, filepath)) { throw new Error(`File ${filepath} is not in the current working directory`) } @@ -61,7 +61,7 @@ export const WriteTool = Tool.define("write", { } return { - title: path.relative(Paths.worktree, filepath), + title: path.relative(Instance.worktree, filepath), metadata: { diagnostics, filepath, diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 25d962a57..261f2eb54 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -4,6 +4,9 @@ export type Event = | ({ type: "installation.updated" } & EventInstallationUpdated) + | ({ + type: "storage.write" + } & EventStorageWrite) | ({ type: "lsp.client.diagnostics" } & EventLspClientDiagnostics) @@ -19,9 +22,6 @@ export type Event = | ({ type: "message.part.removed" } & EventMessagePartRemoved) - | ({ - type: "storage.write" - } & EventStorageWrite) | ({ type: "file.edited" } & EventFileEdited) @@ -60,6 +60,14 @@ export type EventInstallationUpdated = { } } +export type EventStorageWrite = { + type: string + properties: { + key: Array + content?: unknown + } +} + export type EventLspClientDiagnostics = { type: string properties: { @@ -383,14 +391,6 @@ export type EventMessagePartRemoved = { } } -export type EventStorageWrite = { - type: string - properties: { - key: string - content?: unknown - } -} - export type EventFileEdited = { type: string properties: { @@ -444,6 +444,8 @@ export type EventSessionUpdated = { export type Session = { id: string + projectID: string + directory: string parentID?: string share?: { url: string @@ -675,22 +677,7 @@ export type Config = { } } experimental?: { - hook?: { - file_edited?: { - [key: string]: Array<{ - command: Array - environment?: { - [key: string]: string - } - }> - } - session_completed?: Array<{ - command: Array - environment?: { - [key: string]: string - } - }> - } + [key: string]: unknown } } diff --git a/specs/project.md b/specs/project.md index d6d1af082..dd51f0e7f 100644 --- a/specs/project.md +++ b/specs/project.md @@ -58,6 +58,8 @@ POST /log GET /provider?directory= -> Provider GET /config?directory= -> Config // think only tui uses this? -GET /agent?directory= -> Mode + +GET /project/:projectID/agent?directory= -> Agent +GET /project/:projectID/find/file?directory= -> File ```