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