|
|
@@ -1,17 +1,53 @@
|
|
|
-import path from "path"
|
|
|
import { Log } from "../util/log"
|
|
|
import { z } from "zod"
|
|
|
import { App } from "../app/app"
|
|
|
import { Provider } from "../provider/provider"
|
|
|
+import { Filesystem } from "../util/filesystem"
|
|
|
|
|
|
export namespace Config {
|
|
|
const log = Log.create({ service: "config" })
|
|
|
|
|
|
export const state = App.state("config", async (app) => {
|
|
|
- const result = await load(app.path.root)
|
|
|
+ let result: Info = {}
|
|
|
+ for (const file of ["opencode.jsonc", "opencode.json"]) {
|
|
|
+ const resolved = await Filesystem.findUp(
|
|
|
+ file,
|
|
|
+ app.path.cwd,
|
|
|
+ app.path.root,
|
|
|
+ )
|
|
|
+ if (!resolved) continue
|
|
|
+ try {
|
|
|
+ result = await import(resolved).then((mod) => Info.parse(mod.default))
|
|
|
+ log.info("found", { path: resolved })
|
|
|
+ break
|
|
|
+ } catch (e) {
|
|
|
+ if (e instanceof z.ZodError) {
|
|
|
+ for (const issue of e.issues) {
|
|
|
+ log.info(issue.message)
|
|
|
+ }
|
|
|
+ throw e
|
|
|
+ }
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.info("loaded", result)
|
|
|
return result
|
|
|
})
|
|
|
|
|
|
+ export const McpLocal = z.object({
|
|
|
+ type: z.literal("local"),
|
|
|
+ command: z.string().array(),
|
|
|
+ environment: z.record(z.string(), z.string()).optional(),
|
|
|
+ })
|
|
|
+
|
|
|
+ export const McpRemote = z.object({
|
|
|
+ type: z.literal("remote"),
|
|
|
+ url: z.string(),
|
|
|
+ })
|
|
|
+
|
|
|
+ export const Mcp = z.discriminatedUnion("type", [McpLocal, McpRemote])
|
|
|
+ export type Mcp = z.infer<typeof Mcp>
|
|
|
+
|
|
|
export const Info = z
|
|
|
.object({
|
|
|
provider: z.lazy(() => Provider.Info.array().optional()),
|
|
|
@@ -20,6 +56,7 @@ export namespace Config {
|
|
|
provider: z.record(z.string(), z.string().array()).optional(),
|
|
|
})
|
|
|
.optional(),
|
|
|
+ mcp: z.record(z.string(), Mcp).optional(),
|
|
|
})
|
|
|
.strict()
|
|
|
|
|
|
@@ -28,29 +65,4 @@ export namespace Config {
|
|
|
export function get() {
|
|
|
return state()
|
|
|
}
|
|
|
-
|
|
|
- async function load(directory: string) {
|
|
|
- let result: Info = {}
|
|
|
- for (const file of ["opencode.jsonc", "opencode.json"]) {
|
|
|
- const resolved = path.join(directory, file)
|
|
|
- log.info("searching", { path: resolved })
|
|
|
- try {
|
|
|
- result = await import(path.join(directory, file)).then((mod) =>
|
|
|
- Info.parse(mod.default),
|
|
|
- )
|
|
|
- log.info("found", { path: resolved })
|
|
|
- break
|
|
|
- } catch (e) {
|
|
|
- if (e instanceof z.ZodError) {
|
|
|
- for (const issue of e.issues) {
|
|
|
- log.info(issue.message)
|
|
|
- }
|
|
|
- throw e
|
|
|
- }
|
|
|
- continue
|
|
|
- }
|
|
|
- }
|
|
|
- log.info("loaded", result)
|
|
|
- return result
|
|
|
- }
|
|
|
}
|