improve startup speed

This commit is contained in:
Dax Raad
2025-11-08 20:17:46 -05:00
parent 7af3380455
commit 4bb7ea9127
15 changed files with 203 additions and 192 deletions

View File

@@ -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=="],

View File

@@ -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",

View File

@@ -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<void>
}) {
export function tui(input: { url: string; args: Args; onExit?: () => Promise<void> }) {
// promise to prevent immediate exit
return new Promise<void>(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: {
<ErrorComponent error={error} reset={reset} onExit={onExit} />
)}
>
<ExitProvider onExit={onExit}>
<KVProvider>
<ToastProvider>
<RouteProvider data={routeData}>
<SDKProvider url={input.url}>
<SyncProvider>
<ThemeProvider mode={mode}>
<LocalProvider
initialModel={input.model}
initialAgent={input.agent}
initialPrompt={input.prompt}
>
<KeybindProvider>
<DialogProvider>
<CommandProvider>
<PromptHistoryProvider>
<App />
</PromptHistoryProvider>
</CommandProvider>
</DialogProvider>
</KeybindProvider>
</LocalProvider>
</ThemeProvider>
</SyncProvider>
</SDKProvider>
</RouteProvider>
</ToastProvider>
</KVProvider>
</ExitProvider>
<ArgsProvider {...input.args}>
<ExitProvider onExit={onExit}>
<KVProvider>
<ToastProvider>
<RouteProvider>
<SDKProvider url={input.url}>
<SyncProvider>
<ThemeProvider mode={mode}>
<LocalProvider>
<KeybindProvider>
<DialogProvider>
<CommandProvider>
<PromptHistoryProvider>
<App />
</PromptHistoryProvider>
</CommandProvider>
</DialogProvider>
</KeybindProvider>
</LocalProvider>
</ThemeProvider>
</SyncProvider>
</SDKProvider>
</RouteProvider>
</ToastProvider>
</KVProvider>
</ExitProvider>
</ArgsProvider>
</ErrorBoundary>
)
},
@@ -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",

View File

@@ -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: {},
})
},
})

View File

@@ -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")
})

View File

@@ -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
},
})

View File

@@ -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<string>()
onMount(() => {
if (props.initialPrompt) setInitialPrompt.emit(props.initialPrompt)
})
const result = {
model,
agent,
get setInitialPrompt() {
return setInitialPrompt
},
}
return result
},

View File

@@ -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<Route>(
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 {

View File

@@ -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([

View File

@@ -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() {
</Show>
)
let prompt: PromptRef
const args = useArgs()
onMount(() => {
if (once) return
if (args.prompt) {
prompt.set({ input: args.prompt, parts: [] })
once = true
}
})
return (
<box
flexGrow={1}
@@ -55,7 +69,7 @@ export function Home() {
<HelpRow keybind="agent_cycle">Switch agent</HelpRow>
</box>
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1}>
<Prompt hint={Hint} />
<Prompt ref={(r) => (prompt = r)} hint={Hint} />
</box>
<Toast />
</box>

View File

@@ -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<typeof rpc>(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<typeof rpc>(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)
},
})
},
})

View File

@@ -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()

View File

@@ -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<Context>("instance")
const cache = new Map<string, Context>()
const cache = new Map<string, Promise<Context>>()
export const Instance = {
async provide<R>(input: {
@@ -19,18 +20,22 @@ export const Instance = {
}): Promise<R> {
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()
})
}

View File

@@ -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()

View File

@@ -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: [],
})
},
)