summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/opencode/src/app/app.ts55
-rw-r--r--packages/opencode/src/lsp/client.ts2
-rw-r--r--packages/opencode/src/server/server.ts23
-rw-r--r--packages/opencode/src/session/context.ts29
-rw-r--r--packages/opencode/src/session/session.ts7
-rw-r--r--packages/opencode/src/storage/storage.ts2
-rw-r--r--packages/opencode/src/tool/glob.ts2
-rw-r--r--packages/opencode/src/tool/grep.ts2
-rw-r--r--packages/opencode/src/tool/ls.ts2
-rw-r--r--packages/opencode/src/tool/lsp-diagnostics.ts2
-rw-r--r--packages/opencode/src/tool/lsp-hover.ts3
-rw-r--r--packages/opencode/test/tool/tool.test.ts21
12 files changed, 114 insertions, 36 deletions
diff --git a/packages/opencode/src/app/app.ts b/packages/opencode/src/app/app.ts
index e6243da86..b9ad2e0c8 100644
--- a/packages/opencode/src/app/app.ts
+++ b/packages/opencode/src/app/app.ts
@@ -3,21 +3,44 @@ import { Context } from "../util/context"
import { Filesystem } from "../util/filesystem"
import { Global } from "../global"
import path from "path"
+import { z } from "zod"
export namespace App {
const log = Log.create({ service: "app" })
- export type Info = Awaited<ReturnType<typeof create>>
+ export const Info = z
+ .object({
+ time: z.object({
+ initialized: z.number().optional(),
+ }),
+ path: z.object({
+ data: z.string(),
+ root: z.string(),
+ cwd: z.string(),
+ }),
+ })
+ .openapi({
+ ref: "App.Info",
+ })
+ export type Info = z.infer<typeof Info>
- const ctx = Context.create<Info>("app")
+ const ctx = Context.create<Awaited<ReturnType<typeof create>>>("app")
async function create(input: { cwd: string; version: string }) {
- let root = await Filesystem.findUp(".git", input.cwd).then((x) =>
- x ? path.dirname(x) : input.cwd,
+ const git = await Filesystem.findUp(".git", input.cwd).then((x) =>
+ x ? path.dirname(x) : undefined,
)
- const data = path.join(Global.data(), root)
+ const data = path.join(Global.data(), git ?? "global")
await Bun.write(path.join(data, "version"), input.version)
+ const stateFile = Bun.file(path.join(data, "state"))
+ const state = ((await stateFile.exists()) ? stateFile.json() : {}) as {
+ initialized: number
+ version: string
+ }
+ state.version = input.version
+ if (!git) state.initialized = Date.now()
+ await stateFile.write(JSON.stringify(state))
const services = new Map<
any,
@@ -29,14 +52,20 @@ export namespace App {
await Log.file(path.join(data, "log"))
- const result = Object.freeze({
- services,
+ const info: Info = {
+ time: {
+ initialized: state.initialized,
+ },
path: {
data,
- root,
+ root: git ?? input.cwd,
cwd: input.cwd,
},
- })
+ }
+ const result = {
+ services,
+ info,
+ }
return result
}
@@ -52,7 +81,7 @@ export namespace App {
if (!services.has(key)) {
log.info("registering service", { name: key })
services.set(key, {
- state: init(app),
+ state: init(app.info),
shutdown: shutdown,
})
}
@@ -60,8 +89,8 @@ export namespace App {
}
}
- export async function use() {
- return ctx.use()
+ export function info() {
+ return ctx.use().info
}
export async function provide<T extends (app: Info) => any>(
@@ -71,7 +100,7 @@ export namespace App {
const app = await create(input)
return ctx.provide(app, async () => {
- const result = await cb(app)
+ const result = await cb(app.info)
for (const [key, entry] of app.services.entries()) {
log.info("shutdown", { name: key })
await entry.shutdown?.(await entry.state)
diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts
index 3f94bea6d..52dbc22a1 100644
--- a/packages/opencode/src/lsp/client.ts
+++ b/packages/opencode/src/lsp/client.ts
@@ -32,7 +32,7 @@ export namespace LSPClient {
export async function create(input: { cmd: string[]; serverID: string }) {
log.info("starting client", input)
- const app = await App.use()
+ const app = App.info()
const [command, ...args] = input.cmd
const server = spawn(command, args, {
stdio: ["pipe", "pipe", "pipe"],
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index ff7072e75..a4285f33a 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -15,7 +15,7 @@ export namespace Server {
const log = Log.create({ service: "server" })
const PORT = 16713
- export type App = ReturnType<typeof app>
+ export type Routes = ReturnType<typeof app>
function app() {
const app = new Hono()
@@ -75,6 +75,25 @@ export namespace Server {
},
)
.post(
+ "/app_info",
+ describeRoute({
+ description: "Get app info",
+ responses: {
+ 200: {
+ description: "200",
+ content: {
+ "application/json": {
+ schema: resolver(App.Info),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => {
+ return c.json(App.info())
+ },
+ )
+ .post(
"/path_get",
describeRoute({
description: "Get paths",
@@ -97,7 +116,7 @@ export namespace Server {
},
}),
async (c) => {
- const app = await App.use()
+ const app = App.info()
return c.json({
root: app.path.root,
data: app.path.data,
diff --git a/packages/opencode/src/session/context.ts b/packages/opencode/src/session/context.ts
new file mode 100644
index 000000000..2bd1acd5d
--- /dev/null
+++ b/packages/opencode/src/session/context.ts
@@ -0,0 +1,29 @@
+import { App } from "../app/app"
+import path from "path"
+
+export namespace SessionContext {
+ const FILES = [
+ "AGENTS.md",
+ "CLAUDE.md",
+ "CONTEXT.md", // deprecated
+ ]
+ export async function find() {
+ const { cwd, root } = App.info().path
+ let current = cwd
+ const found = []
+ while (true) {
+ for (const item of FILES) {
+ const file = Bun.file(path.join(current, item))
+ if (await file.exists()) {
+ found.push(file.text())
+ }
+ }
+
+ if (current === root) break
+ const parent = path.dirname(current)
+ if (parent === current) break
+ current = parent
+ }
+ return Promise.all(found).then((parts) => parts.join("\n\n"))
+ }
+}
diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts
index d9ae3b6e6..06ee88b01 100644
--- a/packages/opencode/src/session/session.ts
+++ b/packages/opencode/src/session/session.ts
@@ -23,6 +23,7 @@ import { Share } from "../share/share"
import { Message } from "./message"
import { Bus } from "../bus"
import { Provider } from "../provider/provider"
+import { SessionContext } from "./context"
export namespace Session {
const log = Log.create({ service: "session" })
@@ -201,7 +202,6 @@ export namespace Session {
(msg) => msg.role === "system" || msg.id >= lastSummary.id,
)
- const app = await App.use()
if (msgs.length === 0) {
const system: Message.Info = {
id: Identifier.ascending("message"),
@@ -220,9 +220,8 @@ export namespace Session {
tool: {},
},
}
- const contextFile = Bun.file(path.join(app.path.root, "CONTEXT.md"))
- if (await contextFile.exists()) {
- const context = await contextFile.text()
+ const context = await SessionContext.find()
+ if (context) {
system.parts.push({
type: "text",
text: context,
diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts
index 4addf5846..76823e74c 100644
--- a/packages/opencode/src/storage/storage.ts
+++ b/packages/opencode/src/storage/storage.ts
@@ -18,7 +18,7 @@ export namespace Storage {
}
const state = App.state("storage", async () => {
- const app = await App.use()
+ const app = App.info()
const storageDir = path.join(app.path.data, "storage")
await fs.mkdir(storageDir, { recursive: true })
const storage = new FileStorage(new LocalStorageAdapter(storageDir))
diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts
index 31b60a263..8b7aa3efe 100644
--- a/packages/opencode/src/tool/glob.ts
+++ b/packages/opencode/src/tool/glob.ts
@@ -50,7 +50,7 @@ export const GlobTool = Tool.define({
.optional(),
}),
async execute(params) {
- const app = await App.use()
+ const app = App.info()
const search = params.path || app.path.cwd
const limit = 100
const glob = new Bun.Glob(params.pattern)
diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts
index 32a40bce9..2700c16e1 100644
--- a/packages/opencode/src/tool/grep.ts
+++ b/packages/opencode/src/tool/grep.ts
@@ -286,7 +286,7 @@ export const GrepTool = Tool.define({
throw new Error("pattern is required")
}
- const app = await App.use()
+ const app = App.info()
const searchPath = params.path || app.path.cwd
// If literalText is true, escape the pattern
diff --git a/packages/opencode/src/tool/ls.ts b/packages/opencode/src/tool/ls.ts
index 821f7e5e9..bdf982dcf 100644
--- a/packages/opencode/src/tool/ls.ts
+++ b/packages/opencode/src/tool/ls.ts
@@ -25,7 +25,7 @@ export const ListTool = Tool.define({
ignore: z.array(z.string()).optional(),
}),
async execute(params) {
- const app = await App.use()
+ const app = App.info()
const searchPath = path.resolve(app.path.cwd, params.path || ".")
const glob = new Bun.Glob("**/*")
diff --git a/packages/opencode/src/tool/lsp-diagnostics.ts b/packages/opencode/src/tool/lsp-diagnostics.ts
index d10542310..4bce09516 100644
--- a/packages/opencode/src/tool/lsp-diagnostics.ts
+++ b/packages/opencode/src/tool/lsp-diagnostics.ts
@@ -34,7 +34,7 @@ TIPS:
path: z.string().describe("The path to the file to get diagnostics."),
}),
execute: async (args) => {
- const app = await App.use()
+ const app = App.info()
const normalized = path.isAbsolute(args.path)
? args.path
: path.join(app.path.cwd, args.path)
diff --git a/packages/opencode/src/tool/lsp-hover.ts b/packages/opencode/src/tool/lsp-hover.ts
index 5ca654a5e..9c2c4327d 100644
--- a/packages/opencode/src/tool/lsp-hover.ts
+++ b/packages/opencode/src/tool/lsp-hover.ts
@@ -17,8 +17,7 @@ export const LspHoverTool = Tool.define({
character: z.number().describe("The character number to get diagnostics."),
}),
execute: async (args) => {
- console.log(args)
- const app = await App.use()
+ const app = App.info()
const file = path.isAbsolute(args.file)
? args.file
: path.join(app.path.cwd, args.file)
diff --git a/packages/opencode/test/tool/tool.test.ts b/packages/opencode/test/tool/tool.test.ts
index da333d154..749d4ae62 100644
--- a/packages/opencode/test/tool/tool.test.ts
+++ b/packages/opencode/test/tool/tool.test.ts
@@ -6,17 +6,19 @@ import { ListTool } from "../../src/tool/ls"
describe("tool.glob", () => {
test("truncate", async () => {
await App.provide({ cwd: process.cwd(), version: "test" }, async () => {
- let result = await GlobTool.execute({
- pattern: "./node_modules/**/*",
- })
+ let result = await GlobTool.execute(
+ { pattern: "./node_modules/**/*" },
+ { sessionID: "test" },
+ )
expect(result.metadata.truncated).toBe(true)
})
})
test("basic", async () => {
await App.provide({ cwd: process.cwd(), version: "test" }, async () => {
- let result = await GlobTool.execute({
- pattern: "*.json",
- })
+ let result = await GlobTool.execute(
+ { pattern: "*.json" },
+ { sessionID: "test" },
+ )
expect(result.metadata).toMatchObject({
truncated: false,
count: 2,
@@ -30,9 +32,10 @@ describe("tool.ls", () => {
const result = await App.provide(
{ cwd: process.cwd(), version: "test" },
async () => {
- return await ListTool.execute({
- path: "./example",
- })
+ return await ListTool.execute(
+ { path: "./example" },
+ { sessionID: "test" },
+ )
},
)
expect(result.output).toMatchSnapshot()