diff --git a/bun.lock b/bun.lock index 9bed6812..5e9dea3a 100644 --- a/bun.lock +++ b/bun.lock @@ -185,8 +185,8 @@ "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@opentui/core": "0.1.39", - "@opentui/solid": "0.1.39", + "@opentui/core": "0.0.0-20251108-0c7899b1", + "@opentui/solid": "0.0.0-20251108-0c7899b1", "@parcel/watcher": "2.5.1", "@pierre/precision-diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -962,21 +962,21 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentui/core": ["@opentui/core@0.1.39", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.39", "@opentui/core-darwin-x64": "0.1.39", "@opentui/core-linux-arm64": "0.1.39", "@opentui/core-linux-x64": "0.1.39", "@opentui/core-win32-arm64": "0.1.39", "@opentui/core-win32-x64": "0.1.39", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-5gPyg3X/8Nr80RfNEJFiMM8Tj01VFfvFwEMCMQrDiOhmSfFXSH2grF/KPl2bnd2Qa13maXWFEl6W3aATObnrnQ=="], + "@opentui/core": ["@opentui/core@0.0.0-20251108-0c7899b1", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-darwin-x64": "0.0.0-20251108-0c7899b1", "@opentui/core-linux-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-linux-x64": "0.0.0-20251108-0c7899b1", "@opentui/core-win32-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-win32-x64": "0.0.0-20251108-0c7899b1", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-uJ7wbVw2v5NnL6g3v72SjPLUwMl2wqOejUEo8t4NeBA8nsboSxggqkrqOYf6OOmCADoAqyFDY7akZMsz6HMZtg=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.39", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tDUdNdzGeylkDWTiDIy/CalM/9nIeDwMZGN0Q6FLqABnAplwBhdIH2w/gInAcMaTyagm7Qk88p398Wbnxa9uyg=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.0.0-20251108-0c7899b1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DS9CmFmZZjwe6PIhz6zhZAsDx11DtyMFDxn8V3On2b8G892aBG6rHYtBBnsM28/1GGEJBTeDQ/jUXPVd6FNJ/g=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.39", "", { "os": "darwin", "cpu": "x64" }, "sha512-dWXXNUpdi3ndd+6WotQezsO7g54MLSc/6DmYcl0p7fZrQFct8fX0c9ny/S0xAusNHgBGVS5j5FWE75Mx79301Q=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.0.0-20251108-0c7899b1", "", { "os": "darwin", "cpu": "x64" }, "sha512-K4XwdmT6FTShn7EG8AKliPzO5H59R0XUlZi9+kfRVW59IIJtna5wxbu69SkA28dFoWj5i4yDumwoBI+tI7T6vg=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.39", "", { "os": "linux", "cpu": "arm64" }, "sha512-ookQbxLjsg51iwGb6/KTxCfiVRtE9lSE2OVFLLYork8iVzxg81jX29Uoxe1knZ8FjOJ0+VqTzex2IqQH6mjJlw=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.0.0-20251108-0c7899b1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3JUmxZeSvxV5yU7NEXSecy5Z1/LcVUMy1oWyusZgp96X0CTYAXMrolZt9IJDGO5raeO7JId1UaJmWW0r4DR8TA=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.39", "", { "os": "linux", "cpu": "x64" }, "sha512-CeXVNa3hB7gTYKYoZAuMtxWMIXn2rPhmXLkHKpEvXvDRjODFDk8wN1AIVnT5tfncXbWNa5z35BhmqewpGkl4oQ=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.0.0-20251108-0c7899b1", "", { "os": "linux", "cpu": "x64" }, "sha512-i/AQWGyanpPRpk9NK7Ze1tn+d5bqzM9wZFKNB3rd9d2Vbt/ROgBJItG6igz8vzKPKgnlHK4Gw9b5iG5sbjpd+Q=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.39", "", { "os": "win32", "cpu": "arm64" }, "sha512-eeBrVOHz7B+JNZ+w7GH6QxXhXQVBxI6jHmw3B05czG905Je62P0skZNHxiol2BZRawDljo1J/nXQdO5XPeAk2A=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.0.0-20251108-0c7899b1", "", { "os": "win32", "cpu": "arm64" }, "sha512-C7JLWuNN3w2txiVx3demwNwogVi4DQB5ZNHy2b09++kd2m449/RwGPyLcKpuoTzU4s/usYOeY4TxKIAd8cKedQ=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.39", "", { "os": "win32", "cpu": "x64" }, "sha512-lLXeQUBg6Wlenauwd+xaBD+0HT4YIcONeZUTHA+Gyd/rqVhxId97rhhzFikp3bBTvNJlYAscJI3yIF2JvRiFNQ=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.0.0-20251108-0c7899b1", "", { "os": "win32", "cpu": "x64" }, "sha512-mpOryp37YaHlTsN70LhiSn9hJJBktbyhlH/eB3N2K7H1ANYQVrekgBJ3rDxlH1GDVtRz6vLS3IDlyK75qNX4pg=="], - "@opentui/solid": ["@opentui/solid@0.1.39", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.39", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-J34JpWh3HdiDbZajo06WUpd+9CLE/RotVjpVlBE4xtWs9tVMVSUrEZqjI7enoRS/IcCZaeNy3HEREuNA8ng7dw=="], + "@opentui/solid": ["@opentui/solid@0.0.0-20251108-0c7899b1", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.0.0-20251108-0c7899b1", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-tcsYnFGH/KBlQNG0IyZE2bisnm5NwN/w7theuWga3L1zoXqZqA5dQHutAVg4zkq5l/YKULeDI4jBlvz0lzH88A=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index c6b03e65..b1268a10 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -54,8 +54,8 @@ "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@opentui/core": "0.1.39", - "@opentui/solid": "0.1.39", + "@opentui/core": "0.0.0-20251108-0c7899b1", + "@opentui/solid": "0.0.0-20251108-0c7899b1", "@parcel/watcher": "2.5.1", "@pierre/precision-diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 9d30ed6d..3dfbd376 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -1,13 +1,22 @@ import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" import { Clipboard } from "@tui/util/clipboard" import { TextAttributes } from "@opentui/core" -import { RouteProvider, useRoute, type Route } from "@tui/context/route" -import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal } from "solid-js" +import { RouteProvider, useRoute } from "@tui/context/route" +import { + Switch, + Match, + createEffect, + untrack, + ErrorBoundary, + createSignal, + onMount, + batch, +} from "solid-js" import { Installation } from "@/installation" import { Global } from "@/global" import { DialogProvider, useDialog } from "@tui/ui/dialog" import { SDKProvider, useSDK } from "@tui/context/sdk" -import { SyncProvider } from "@tui/context/sync" +import { SyncProvider, useSync } from "@tui/context/sync" import { LocalProvider, useLocal } from "@tui/context/local" import { DialogModel } from "@tui/component/dialog-model" import { DialogStatus } from "@tui/component/dialog-status" @@ -27,6 +36,8 @@ import { ExitProvider, useExit } from "./context/exit" import { Session as SessionApi } from "@/session" import { TuiEvent } from "./event" import { KVProvider, useKV } from "./context/kv" +import { Provider } from "@/provider/provider" +import { ArgsProvider, useArgs, type Args } from "./context/args" async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { // can't set raw mode if not a TTY @@ -88,25 +99,10 @@ async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { }) } -export function tui(input: { - url: string - sessionID?: string - model?: string - agent?: string - prompt?: string - onExit?: () => Promise -}) { +export function tui(input: { url: string; args: Args; onExit?: () => Promise }) { // promise to prevent immediate exit return new Promise(async (resolve) => { const mode = await getTerminalBackgroundColor() - - const routeData: Route | undefined = input.sessionID - ? { - type: "session", - sessionID: input.sessionID, - } - : undefined - const onExit = async () => { await input.onExit?.() resolve() @@ -120,35 +116,33 @@ export function tui(input: { )} > - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + ) }, @@ -174,12 +168,45 @@ function App() { const { event } = useSDK() const toast = useToast() const { theme, mode, setMode } = useTheme() + const sync = useSync() const exit = useExit() createEffect(() => { console.log(JSON.stringify(route.data)) }) + const args = useArgs() + onMount(() => { + batch(() => { + if (args.agent) local.agent.set(args.agent) + if (args.model) { + const { providerID, modelID } = Provider.parseModel(args.model) + if (!providerID || !modelID) + return toast.show({ + variant: "warning", + message: `Invalid model format: ${args.model}`, + duration: 3000, + }) + local.model.set({ providerID, modelID }, { recent: true }) + } + if (args.continue) { + const match = sync.data.session.at(-1)?.id + if (match) { + route.navigate({ + type: "session", + sessionID: match, + }) + } + } + if (args.sessionID) { + route.navigate({ + type: "session", + sessionID: args.sessionID, + }) + } + }) + }) + command.register(() => [ { title: "Switch session", diff --git a/packages/opencode/src/cli/cmd/tui/attach.ts b/packages/opencode/src/cli/cmd/tui/attach.ts index 38f1b671..7da6507e 100644 --- a/packages/opencode/src/cli/cmd/tui/attach.ts +++ b/packages/opencode/src/cli/cmd/tui/attach.ts @@ -17,6 +17,9 @@ export const AttachCommand = cmd({ }), handler: async (args) => { if (args.dir) process.chdir(args.dir) - await tui(args) + await tui({ + url: args.url, + args: {}, + }) }, }) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index b9e40659..ade7190f 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -200,16 +200,6 @@ export function Prompt(props: PromptProps) { input.focus() }) - local.setInitialPrompt.listen((initialPrompt) => { - batch(() => { - setStore("prompt", { - input: initialPrompt, - parts: [], - }) - input.insertText(initialPrompt) - }) - }) - onMount(() => { promptPartTypeId = input.extmarks.registerType("prompt-part") }) diff --git a/packages/opencode/src/cli/cmd/tui/context/args.tsx b/packages/opencode/src/cli/cmd/tui/context/args.tsx new file mode 100644 index 00000000..055c7af4 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/context/args.tsx @@ -0,0 +1,16 @@ +import { createSimpleContext } from "./helper" + +export interface Args { + model?: string + agent?: string + prompt?: string + continue?: boolean + sessionID?: string +} + +export const { use: useArgs, provider: ArgsProvider } = createSimpleContext({ + name: "Args", + init: (props: Args) => { + return props + }, +}) diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index ef26ee65..6f91d6a3 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -1,5 +1,5 @@ import { createStore } from "solid-js/store" -import { batch, createEffect, createMemo, createSignal, onMount } from "solid-js" +import { batch, createEffect, createMemo } from "solid-js" import { useSync } from "@tui/context/sync" import { useTheme } from "@tui/context/theme" import { uniqueBy } from "remeda" @@ -8,12 +8,12 @@ import { Global } from "@/global" import { iife } from "@/util/iife" import { createSimpleContext } from "./helper" import { useToast } from "../ui/toast" -import { createEventBus } from "@solid-primitives/event-bus" import { Provider } from "@/provider/provider" +import { useArgs } from "./args" export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ name: "Local", - init: (props: { initialModel?: string; initialAgent?: string; initialPrompt?: string }) => { + init: () => { const sync = useSync() const toast = useToast() @@ -32,25 +32,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } } - // Set initial model if provided - onMount(() => { - batch(() => { - if (props.initialAgent) { - agent.set(props.initialAgent) - } - if (props.initialModel) { - const { providerID, modelID } = Provider.parseModel(props.initialModel) - if (!providerID || !modelID) - return toast.show({ - variant: "warning", - message: `Invalid model format: ${props.initialModel}`, - duration: 3000, - }) - model.set({ providerID, modelID }, { recent: true }) - } - }) - }) - // Automatically update model when agent changes createEffect(() => { const value = agent.current() @@ -149,9 +130,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ setModelStore("ready", true) }) + const args = useArgs() const fallbackModel = createMemo(() => { - if (props.initialModel) { - const { providerID, modelID } = Provider.parseModel(props.initialModel) + if (args.model) { + const { providerID, modelID } = Provider.parseModel(args.model) if (isModelValid({ providerID, modelID })) { return { providerID, @@ -251,18 +233,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } }) - const setInitialPrompt = createEventBus() - - onMount(() => { - if (props.initialPrompt) setInitialPrompt.emit(props.initialPrompt) - }) - const result = { model, agent, - get setInitialPrompt() { - return setInitialPrompt - }, } return result }, diff --git a/packages/opencode/src/cli/cmd/tui/context/route.tsx b/packages/opencode/src/cli/cmd/tui/context/route.tsx index dd8ede15..b906de99 100644 --- a/packages/opencode/src/cli/cmd/tui/context/route.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/route.tsx @@ -14,14 +14,13 @@ export type Route = HomeRoute | SessionRoute export const { use: useRoute, provider: RouteProvider } = createSimpleContext({ name: "Route", - init: (props: { data?: Route }) => { + init: () => { const [store, setStore] = createStore( - props.data ?? - (process.env["OPENCODE_ROUTE"] - ? JSON.parse(process.env["OPENCODE_ROUTE"]) - : { - type: "home", - }), + process.env["OPENCODE_ROUTE"] + ? JSON.parse(process.env["OPENCODE_ROUTE"]) + : { + type: "home", + }, ) return { diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 60758aeb..8f6d118e 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -225,12 +225,16 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ } }) + const now = Date.now() // blocking Promise.all([ sdk.client.config.providers().then((x) => setStore("provider", x.data!.providers)), sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])), sdk.client.config.get().then((x) => setStore("config", x.data!)), - ]).then(() => setStore("ready", true)) + ]).then(() => { + console.log("loaded in " + (Date.now() - now)) + setStore("ready", true) + }) // non-blocking Promise.all([ diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index a01bfa6a..57aff585 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -1,5 +1,5 @@ -import { Prompt } from "@tui/component/prompt" -import { createMemo, Match, Show, Switch, type ParentProps } from "solid-js" +import { Prompt, type PromptRef } from "@tui/component/prompt" +import { createMemo, Match, onMount, Show, Switch, type ParentProps } from "solid-js" import { useTheme } from "@tui/context/theme" import { useKeybind } from "../context/keybind" import type { KeybindsConfig } from "@opencode-ai/sdk" @@ -7,6 +7,10 @@ import { Logo } from "../component/logo" import { Locale } from "@/util/locale" import { useSync } from "../context/sync" import { Toast } from "../ui/toast" +import { useArgs } from "../context/args" + +// TODO: what is the best way to do this? +let once = false export function Home() { const sync = useSync() @@ -38,6 +42,16 @@ export function Home() { ) + let prompt: PromptRef + const args = useArgs() + onMount(() => { + if (once) return + if (args.prompt) { + prompt.set({ input: args.prompt, parts: [] }) + once = true + } + }) + return ( Switch agent - + (prompt = r)} hint={Hint} /> diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index c530c888..90c31e6f 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -2,10 +2,9 @@ import { cmd } from "@/cli/cmd/cmd" import { tui } from "./app" import { Rpc } from "@/util/rpc" import { type rpc } from "./worker" -import { Session } from "@/session" -import { bootstrap } from "@/cli/bootstrap" import path from "path" import { UI } from "@/cli/ui" +import { iife } from "@/util/iife" declare global { const OPENCODE_WORKER_PATH: string @@ -32,8 +31,8 @@ export const TuiThreadCommand = cmd({ }) .option("session", { alias: ["s"], - describe: "session id to continue", type: "string", + describe: "session id to continue", }) .option("prompt", { alias: ["p"], @@ -55,12 +54,6 @@ export const TuiThreadCommand = cmd({ default: "127.0.0.1", }), handler: async (args) => { - const prompt = await (async () => { - const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined - if (!args.prompt) return piped - return piped ? piped + "\n" + args.prompt : args.prompt - })() - // Resolve relative paths against PWD to preserve behavior when using --cwd flag const baseCwd = process.env.PWD ?? process.cwd() const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd() @@ -76,56 +69,41 @@ export const TuiThreadCommand = cmd({ return } - await bootstrap(cwd, async () => { - const sessionID = await (async () => { - if (args.continue) { - const it = Session.list() - try { - for await (const s of it) { - if (s.parentID === undefined) { - return s.id - } - } - return - } finally { - await it.return() - } - } - if (args.session) { - return args.session - } - return undefined - })() - - const worker = new Worker(workerPath, { - env: Object.fromEntries( - Object.entries(process.env).filter( - (entry): entry is [string, string] => entry[1] !== undefined, - ), + const worker = new Worker(workerPath, { + env: Object.fromEntries( + Object.entries(process.env).filter( + (entry): entry is [string, string] => entry[1] !== undefined, ), - }) - worker.onerror = console.error - const client = Rpc.client(worker) - process.on("uncaughtException", (e) => { - console.error(e) - }) - process.on("unhandledRejection", (e) => { - console.error(e) - }) - const server = await client.call("server", { - port: args.port, - hostname: args.hostname, - }) - await tui({ - url: server.url, - sessionID, - model: args.model, + ), + }) + worker.onerror = console.error + const client = Rpc.client(worker) + process.on("uncaughtException", (e) => { + console.error(e) + }) + process.on("unhandledRejection", (e) => { + console.error(e) + }) + const server = await client.call("server", { + port: args.port, + hostname: args.hostname, + }) + const prompt = await iife(async () => { + const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined + if (!args.prompt) return piped + return piped ? piped + "\n" + args.prompt : args.prompt + }) + await tui({ + url: server.url, + args: { + continue: args.continue, + sessionID: args.session, agent: args.agent, prompt, - onExit: async () => { - await client.call("shutdown", undefined) - }, - }) + }, + onExit: async () => { + await client.call("shutdown", undefined) + }, }) }, }) diff --git a/packages/opencode/src/project/bootstrap.ts b/packages/opencode/src/project/bootstrap.ts index 27451d22..e801d3f7 100644 --- a/packages/opencode/src/project/bootstrap.ts +++ b/packages/opencode/src/project/bootstrap.ts @@ -9,9 +9,11 @@ import { Project } from "./project" import { Bus } from "../bus" import { Command } from "../command" import { Instance } from "./instance" +import { Log } from "@/util/log" export async function InstanceBootstrap() { if (Flag.OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP) return + Log.Default.info("bootstrapping", { directory: Instance.directory }) await Plugin.init() Share.init() Format.init() diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts index ce9a57d1..3809758e 100644 --- a/packages/opencode/src/project/instance.ts +++ b/packages/opencode/src/project/instance.ts @@ -2,6 +2,7 @@ import { Log } from "@/util/log" import { Context } from "../util/context" import { Project } from "./project" import { State } from "./state" +import { iife } from "@/util/iife" interface Context { directory: string @@ -9,7 +10,7 @@ interface Context { project: Project.Info } const context = Context.create("instance") -const cache = new Map() +const cache = new Map>() export const Instance = { async provide(input: { @@ -19,18 +20,22 @@ export const Instance = { }): Promise { let existing = cache.get(input.directory) if (!existing) { - const project = await Project.fromDirectory(input.directory) - existing = { - directory: input.directory, - worktree: project.worktree, - project, - } + existing = iife(async () => { + const project = await Project.fromDirectory(input.directory) + const ctx = { + directory: input.directory, + worktree: project.worktree, + project, + } + await context.provide(ctx, async () => { + await input.init?.() + }) + return ctx + }) + cache.set(input.directory, existing) } - return context.provide(existing, async () => { - if (!cache.has(input.directory)) { - cache.set(input.directory, existing) - await input.init?.() - } + const ctx = await existing + return context.provide(ctx, async () => { return input.fn() }) }, @@ -52,7 +57,7 @@ export const Instance = { }, async disposeAll() { for (const [_key, value] of cache) { - await context.provide(value, async () => { + await context.provide(await value, async () => { await Instance.dispose() }) } diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 2ed072e5..f5b4da15 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -231,6 +231,7 @@ export namespace Provider { } const state = Instance.state(async () => { + using _ = log.time("state") const config = await Config.get() const database = await ModelsDev.get() diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index bfe804ae..460a99ff 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -114,12 +114,13 @@ export namespace Server { path: c.req.path, }) } - const start = Date.now() + const timer = log.time("request", { + method: c.req.method, + path: c.req.path, + }) await next() if (!skipLogging) { - log.info("response", { - duration: Date.now() - start, - }) + timer.stop() } }) .use(async (c, next) => { @@ -1083,13 +1084,11 @@ export namespace Server { }, }), async (c) => { + using _ = log.time("providers") const providers = await Provider.list().then((x) => mapValues(x, (item) => item.info)) return c.json({ providers: Object.values(providers), - default: mapValues( - providers, - (item) => Provider.sort(Object.values(item.models))[0].id, - ), + default: [], }) }, )