Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679import { useIsRouting, useLocation, useParams } from "@solidjs/router"import { batch, createEffect, createMemo, on, onCleanup, onMount, Show } from "solid-js"import { createStore } from "solid-js/store"import { makeEventListener } from "@solid-primitives/event-listener"import { Tooltip } from "@opencode/ui/tooltip"import { useLanguage } from "@/runtime/i18n/language"import { usePlatform } from "@/runtime/platform/platform"import { useGlobal } from "@/runtime/server/runtime"import { ServerConnection } from "@/runtime/server/registry"import { base64Encode } from "@opencode/util/encode"import { applyProviderMetricEvent, isProviderMetricEvent, projectedProviderMetrics, type ProviderMetrics, type ProviderMetricState,} from "./provider-metrics"
type Mem = Performance & { memory?: { usedJSHeapSize: number jsHeapSizeLimit: number }}
type Evt = PerformanceEntry & { interactionId?: number processingStart?: number}
type Shift = PerformanceEntry & { hadRecentInput: boolean value: number}
type Obs = PerformanceObserverInit & { durationThreshold?: number}
const span = 5000
const ms = (n?: number, d = 0) => { if (n === undefined || Number.isNaN(n)) return return `${n.toFixed(d)}ms`}
const time = (n?: number) => { if (n === undefined || Number.isNaN(n)) return return `${Math.round(n)}`}
const fixed = (n?: number, digits = 0) => { if (n === undefined || Number.isNaN(n)) return return n.toFixed(digits)}
const mb = (n?: number) => { if (n === undefined || Number.isNaN(n)) return const v = n / 1024 / 1024 return `${v >= 1024 ? v.toFixed(0) : v.toFixed(1)}MB`}
const duration = (n?: number) => { if (n === undefined || Number.isNaN(n)) return if (n < 1_000) return `${Math.round(n)}ms` return `${(n / 1_000).toFixed(n < 10_000 ? 1 : 0)}s`}
const bad = (n: number | undefined, limit: number, low = false) => { if (n === undefined || Number.isNaN(n)) return false return low ? n < limit : n > limit}
const session = (path: string) => path.includes("/session")
function Cell(props: { bad?: boolean dim?: boolean inline?: boolean label: string tip: string value: string span?: 2 | 3}) { const content = () => ( <div classList={{ "flex min-w-0 items-center": true, "min-h-[20px] w-fit justify-start px-1.5 py-0.5 text-left": !!props.inline, "justify-center text-center": !props.inline, "min-h-[42px] w-full flex-col rounded-[8px] px-0.5 py-1": !props.inline, "col-span-2": props.span === 2 && !props.inline, "col-span-3": props.span === 3 && !props.inline, }} > <div classList={{ "flex min-w-0": true, "-translate-y-px items-baseline gap-1.5": !!props.inline, "flex-col items-center": !props.inline, }} > <div dir="ltr" classList={{ "text-[10px] leading-none font-black uppercase tracking-[0.04em] opacity-70": true, }} > {props.label} </div> <div dir="ltr" classList={{ "uppercase font-bold tabular-nums": true, "text-[11px] leading-text-tight": !!props.inline, "text-[13px] leading-text-compact sm:text-[14px]": !props.inline, "text-text-on-critical-base": !!props.bad, "opacity-70": !!props.dim, }} > {props.value} </div> </div> </div> )
return ( <Tooltip appearance={props.inline ? "compact" : "standard"} value={props.tip} placement="top"> {content()} </Tooltip> )}
function ToggleCell(props: { active: boolean inline?: boolean label: string onClick: () => void tip: string value: string}) { const content = () => ( <button type="button" aria-label={`${props.label}: ${props.value}`} aria-pressed={props.active} classList={{ "flex min-w-0 items-center font-mono uppercase hover:bg-surface-raised-base focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-border-focus": true, "min-h-[20px] w-fit justify-start rounded px-1.5 py-0.5 text-left": !!props.inline, "min-h-[42px] w-full flex-col justify-center rounded-[8px] px-0.5 py-1 text-center": !props.inline, "bg-surface-raised-base text-text-strong": props.active, }} onClick={props.onClick} > <span classList={{ flex: true, "-translate-y-px items-baseline gap-1.5": !!props.inline, "flex-col items-center": !props.inline, }} > <span dir="ltr" class="text-[10px] leading-none font-black tracking-[0.04em] opacity-70"> {props.label} </span> <span dir="ltr" class="text-[11px] leading-none font-bold"> {props.value} </span> </span> </button> )
return ( <Tooltip appearance={props.inline ? "compact" : "standard"} value={props.tip} placement="top"> {content()} </Tooltip> )}
export function DebugBar(props: { diagnostics?: boolean; inline?: boolean } = {}) { const language = useLanguage() const platform = usePlatform() const global = useGlobal() const params = useParams<{ serverKey?: string; id?: string }>() const location = useLocation() const routing = useIsRouting() const [state, setState] = createStore({ cls: undefined as number | undefined, delay: undefined as number | undefined, fps: undefined as number | undefined, gap: undefined as number | undefined, focus: false, heap: { limit: undefined as number | undefined, used: undefined as number | undefined, }, inp: undefined as number | undefined, jank: undefined as number | undefined, long: { block: undefined as number | undefined, count: undefined as number | undefined, max: undefined as number | undefined, }, nav: { dur: undefined as number | undefined, pending: false, }, live: undefined as ProviderMetrics | undefined, })
const target = createMemo( () => { if (!params.serverKey || !params.id) return const connection = global.servers .list() .find((item) => base64Encode(ServerConnection.key(item)) === params.serverKey) if (!connection) return return { ctx: global.ensureServerCtx(connection), id: params.id } }, undefined, { equals: (a, b) => a?.ctx === b?.ctx && a?.id === b?.id }, ) // History comes from the already-loaded message projection; live requests refine it in place. const projected = createMemo(() => { const current = target() if (!current) return return projectedProviderMetrics(current.ctx.data.session.message.list(current.id)) }) const metrics = () => state.live ?? projected()
// Missed events during an outage are never replayed; the refreshed projection must win. createEffect( on( () => target()?.ctx.sdk.connection.status(), (status) => { if (status !== "connected") setState("live", undefined) }, { defer: true }, ), )
createEffect( on(target, (current) => { setState("live", undefined) if (!current) return const accumulator: ProviderMetricState = {} onCleanup( current.ctx.sdk.event.listen((event) => { if (!isProviderMetricEvent(event) || event.data.sessionID !== current.id) return applyProviderMetricEvent(accumulator, event) if (accumulator.latest) setState("live", accumulator.latest) }), ) }), )
const na = () => language.t("debugBar.na").toUpperCase() const heap = () => (state.heap.limit ? (state.heap.used ?? 0) / state.heap.limit : undefined) const heapv = () => { const value = heap() if (value === undefined) return na() return `${Math.round(value * 100)}%` } const longv = () => (state.long.count === undefined ? na() : `${time(state.long.block) ?? na()}/${state.long.count}`) const navv = () => (state.nav.pending ? "…" : (time(state.nav.dur) ?? na())) const toggleFocus = async () => { if (!platform.setForceFocus) return const enabled = !state.focus await platform.setForceFocus(enabled) setState("focus", enabled) }
onCleanup(() => { if (state.focus) void platform.setForceFocus?.(false).catch(() => undefined) })
let prev = "" let start = 0 let init = false let one = 0 let two = 0
createEffect(() => { if (!props.diagnostics) return const busy = routing() const next = `${location.pathname}${location.search}`
if (!init) { init = true prev = next return }
if (busy) { if (one !== 0) cancelAnimationFrame(one) if (two !== 0) cancelAnimationFrame(two) one = 0 two = 0 if (start !== 0) return start = performance.now() if (session(prev)) setState("nav", { dur: undefined, pending: true }) return }
if (start === 0) { prev = next return }
const at = start const from = prev start = 0 prev = next
if (!(session(from) || session(next))) return
if (one !== 0) cancelAnimationFrame(one) if (two !== 0) cancelAnimationFrame(two) one = requestAnimationFrame(() => { one = 0 two = requestAnimationFrame(() => { two = 0 setState("nav", { dur: performance.now() - at, pending: false }) }) }) })
onMount(() => { if (!props.diagnostics) return const obs: PerformanceObserver[] = [] const fps: Array<{ at: number; dur: number }> = [] const long: Array<{ at: number; dur: number }> = [] const seen = new Map<number | string, { at: number; delay: number; dur: number }>() let hasLong = false let poll: number | undefined let raf = 0 let last = 0 let snap = 0
const trim = (list: Array<{ at: number; dur: number }>, span: number, at: number) => { while (list[0] && at - list[0].at > span) list.shift() }
const syncFrame = (at: number) => { trim(fps, span, at) const total = fps.reduce((sum, entry) => sum + entry.dur, 0) const gap = fps.reduce((max, entry) => Math.max(max, entry.dur), 0) const jank = fps.filter((entry) => entry.dur > 32).length batch(() => { setState("fps", total > 0 ? (fps.length * 1000) / total : undefined) setState("gap", gap > 0 ? gap : undefined) setState("jank", jank) }) }
const syncLong = (at = performance.now()) => { if (!hasLong) return trim(long, span, at) const block = long.reduce((sum, entry) => sum + Math.max(0, entry.dur - 50), 0) const max = long.reduce((hi, entry) => Math.max(hi, entry.dur), 0) setState("long", { block, count: long.length, max }) }
const syncInp = (at = performance.now()) => { for (const [key, entry] of seen) { if (at - entry.at > span) seen.delete(key) } let delay = 0 let inp = 0 for (const entry of seen.values()) { delay = Math.max(delay, entry.delay) inp = Math.max(inp, entry.dur) } batch(() => { setState("delay", delay > 0 ? delay : undefined) setState("inp", inp > 0 ? inp : undefined) }) }
const syncHeap = () => { const mem = (performance as Mem).memory if (!mem) return setState("heap", { limit: mem.jsHeapSizeLimit, used: mem.usedJSHeapSize }) }
const reset = () => { fps.length = 0 long.length = 0 seen.clear() last = 0 snap = 0 batch(() => { setState("fps", undefined) setState("gap", undefined) setState("jank", undefined) setState("delay", undefined) setState("inp", undefined) if (hasLong) setState("long", { block: 0, count: 0, max: 0 }) }) }
const watch = (type: string, init: Obs, fn: (entries: PerformanceEntry[]) => void) => { if (typeof PerformanceObserver === "undefined") return false if (!(PerformanceObserver.supportedEntryTypes ?? []).includes(type)) return false const ob = new PerformanceObserver((list) => fn(list.getEntries())) try { ob.observe(init) obs.push(ob) return true } catch { ob.disconnect() return false } }
if ( watch("layout-shift", { buffered: true, type: "layout-shift" }, (entries) => { const add = entries.reduce((sum, entry) => { const item = entry as Shift if (item.hadRecentInput) return sum return sum + item.value }, 0) if (add === 0) return setState("cls", (value) => (value ?? 0) + add) }) ) { setState("cls", 0) }
if ( watch("longtask", { buffered: true, type: "longtask" }, (entries) => { const at = performance.now() long.push(...entries.map((entry) => ({ at: entry.startTime, dur: entry.duration }))) syncLong(at) }) ) { hasLong = true setState("long", { block: 0, count: 0, max: 0 }) }
watch("event", { buffered: true, durationThreshold: 16, type: "event" }, (entries) => { for (const raw of entries) { const entry = raw as Evt if (entry.duration < 16) continue const key = entry.interactionId && entry.interactionId > 0 ? entry.interactionId : `${entry.name}:${Math.round(entry.startTime)}` const prev = seen.get(key) const delay = Math.max(0, (entry.processingStart ?? entry.startTime) - entry.startTime) seen.set(key, { at: entry.startTime, delay: Math.max(prev?.delay ?? 0, delay), dur: Math.max(prev?.dur ?? 0, entry.duration), }) if (seen.size <= 200) continue const first = seen.keys().next().value if (first !== undefined) seen.delete(first) } syncInp() })
const loop = (at: number) => { if (document.visibilityState !== "visible") { raf = 0 return }
if (last === 0) { last = at raf = requestAnimationFrame(loop) return }
fps.push({ at, dur: at - last }) last = at
if (at - snap >= 250) { snap = at syncFrame(at) }
raf = requestAnimationFrame(loop) }
const stop = () => { if (raf !== 0) cancelAnimationFrame(raf) raf = 0 if (poll === undefined) return clearInterval(poll) poll = undefined }
const start = () => { if (document.visibilityState !== "visible") return if (poll === undefined) { poll = window.setInterval(() => { syncLong() syncInp() syncHeap() }, 1000) } if (raf !== 0) return raf = requestAnimationFrame(loop) }
const vis = () => { if (document.visibilityState !== "visible") { stop() return } reset() start() }
syncHeap() start() makeEventListener(document, "visibilitychange", vis)
onCleanup(() => { if (one !== 0) cancelAnimationFrame(one) if (two !== 0) cancelAnimationFrame(two) stop() for (const ob of obs) ob.disconnect() }) })
return ( <aside aria-label={language.t(props.diagnostics ? "debugBar.ariaLabel" : "debugBar.providerAriaLabel")} classList={{ "pointer-events-auto hidden overflow-hidden text-text-strong md:block": true, "mt-[-6px] w-full shrink-0 px-3 py-1": !!props.inline, "fixed bottom-3 right-3 z-50 w-[308px] max-w-[calc(100vw-1.5rem)] rounded-xl border border-border-base bg-surface-raised-stronger-non-alpha p-0.5 shadow-[var(--shadow-lg-border-base)] sm:bottom-4 sm:right-4 sm:w-[324px]": !props.inline, }} > <div classList={{ "font-mono": true, "gap-[9px]": !!props.inline, "gap-px": !props.inline, "flex w-full flex-nowrap items-center justify-start": !!props.inline, "grid-cols-4": !props.inline, grid: !props.inline, }} > <Cell label={language.t("debugBar.tps.label")} tip={language.t("debugBar.tps.tip")} value={fixed(metrics()?.tps, 1) ?? na()} dim={metrics()?.tps === undefined} inline={props.inline} /> <Cell label={language.t("debugBar.ttft.label")} tip={language.t("debugBar.ttft.tip")} value={duration(metrics()?.ttft) ?? na()} dim={metrics()?.ttft === undefined} inline={props.inline} /> <Cell label={language.t("debugBar.ttfa.label")} tip={language.t("debugBar.ttfa.tip")} value={duration(metrics()?.ttfa) ?? na()} dim={metrics()?.ttfa === undefined} inline={props.inline} /> <Cell label={language.t("debugBar.e2e.label")} tip={language.t("debugBar.e2e.tip")} value={duration(metrics()?.e2e) ?? na()} dim={metrics()?.e2e === undefined} inline={props.inline} /> <Show when={props.diagnostics}> <Cell label={language.t("debugBar.nav.label")} tip={language.t("debugBar.nav.tip")} value={navv()} bad={bad(state.nav.dur, 400)} dim={state.nav.dur === undefined && !state.nav.pending} inline={props.inline} /> <Cell label={language.t("debugBar.fps.label")} tip={language.t("debugBar.fps.tip")} value={state.fps === undefined ? na() : `${Math.round(state.fps)}`} bad={bad(state.fps, 50, true)} dim={state.fps === undefined} inline={props.inline} /> <Cell label={language.t("debugBar.frame.label")} tip={language.t("debugBar.frame.tip")} value={time(state.gap) ?? na()} bad={bad(state.gap, 50)} dim={state.gap === undefined} inline={props.inline} /> <Cell label={language.t("debugBar.jank.label")} tip={language.t("debugBar.jank.tip")} value={state.jank === undefined ? na() : `${state.jank}`} bad={bad(state.jank, 8)} dim={state.jank === undefined} inline={props.inline} /> <Cell label={language.t("debugBar.long.label")} tip={language.t("debugBar.long.tip", { max: ms(state.long.max) ?? na() })} value={longv()} bad={bad(state.long.block, 200)} dim={state.long.count === undefined} inline={props.inline} /> <Cell label={language.t("debugBar.delay.label")} tip={language.t("debugBar.delay.tip")} value={time(state.delay) ?? na()} bad={bad(state.delay, 100)} dim={state.delay === undefined} inline={props.inline} /> <Cell label={language.t("debugBar.inp.label")} tip={language.t("debugBar.inp.tip")} value={time(state.inp) ?? na()} bad={bad(state.inp, 200)} dim={state.inp === undefined} inline={props.inline} /> <Cell label={language.t("debugBar.cls.label")} tip={language.t("debugBar.cls.tip")} value={state.cls === undefined ? na() : state.cls.toFixed(2)} bad={bad(state.cls, 0.1)} dim={state.cls === undefined} inline={props.inline} /> <Cell label={language.t("debugBar.mem.label")} tip={ state.heap.used === undefined ? language.t("debugBar.mem.tipUnavailable") : language.t("debugBar.mem.tip", { used: mb(state.heap.used) ?? na(), limit: mb(state.heap.limit) ?? na(), }) } value={heapv()} bad={bad(heap(), 0.8)} dim={state.heap.used === undefined} inline={props.inline} span={platform.setForceFocus ? 2 : 3} /> <ToggleCell active={language.direction() === "rtl"} inline={props.inline} label={language.t("debugBar.direction.label")} tip={language.t("debugBar.direction.tip")} value={language.t(`debugBar.direction.${language.direction()}`)} onClick={() => language.setDirection(language.direction() === "rtl" ? "ltr" : "rtl")} /> <Show when={platform.setForceFocus}> <ToggleCell active={state.focus} inline={props.inline} label={language.t("debugBar.focus.label")} tip={language.t("debugBar.focus.tip")} value={language.t(state.focus ? "debugBar.focus.on" : "debugBar.focus.off")} onClick={() => void toggleFocus()} /> </Show> </Show> </div> </aside> )}