Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153// Thin bridge between transport output and the footer API.//// Transports produce immutable StreamCommit[] rows and typed mutable-footer// updates. This module forwards both to the footer API, adding trace writes// along the way. It also// defaults status updates to phase "running" if the caller didn't set a// phase -- a convenience so transport code doesn't have to repeat that.import type { FooterApi, FooterEvent, FooterPatch, FooterSubagentState, StreamCommit } from "./types"
type Trace = { write(type: string, data?: unknown): void}
type OutputInput = { footer: FooterApi trace?: Trace}
type StreamOutput = { commits: StreamCommit[] updates?: Extract<FooterEvent, { type: "stream.patch" | "stream.view" | "stream.subagent" }>[]}
// Default to "running" phase when a status string arrives without an explicit phase.function patch(next: FooterPatch): FooterPatch { if (typeof next.status === "string" && next.phase === undefined) { return { phase: "running", ...next, } }
return next}
function summarize(value: unknown): unknown { if (typeof value === "string") { if (value.length <= 160) { return value }
return { type: "string", length: value.length, preview: `${value.slice(0, 160)}…`, } }
if (Array.isArray(value)) { return { type: "array", length: value.length, } }
if (!value || typeof value !== "object") { return value }
return { type: "object", keys: Object.keys(value), }}
function traceCommit(commit: StreamCommit) { return { ...commit, text: summarize(commit.text), textLength: commit.text.length, part: commit.part ? { id: commit.part.id, tool: commit.part.name, state: { status: commit.part.state.status, input: summarize(commit.part.state.input), metadata: "metadata" in commit.part.state ? summarize(commit.part.state.metadata) : undefined, content: "content" in commit.part.state ? summarize(commit.part.state.content) : undefined, error: "error" in commit.part.state ? summarize(commit.part.state.error) : undefined, }, time: commit.part.time, } : undefined, }}
function traceSubagentState(state: FooterSubagentState) { return { tabs: state.tabs, details: Object.fromEntries( Object.entries(state.details).map(([sessionID, detail]) => [ sessionID, { sessionID, commits: detail.commits.map(traceCommit), }, ]), ), permissions: state.permissions.map((item) => ({ id: item.id, sessionID: item.sessionID, action: item.action, resources: item.resources, source: item.source, tool: item.tool ? { id: item.tool.id, name: item.tool.name, status: item.tool.state.status, input: summarize(item.tool.state.input), } : undefined, metadata: item.metadata ? { keys: Object.keys(item.metadata), } : undefined, })), forms: state.forms.map((item) => ({ id: item.id, sessionID: item.sessionID, title: item.title, fields: item.fields.map((field) => ({ key: field.key, type: field.type })), location: item.location, })), }}
// Forwards transport output to the footer: commits go to scrollback, patches update the status bar.export function writeSessionOutput(input: OutputInput, out: StreamOutput): void { for (const commit of out.commits) { input.trace?.write("ui.commit", commit) input.footer.append(commit) }
for (const update of out.updates ?? []) { if (update.type === "stream.patch") { const next = { ...update, patch: patch(update.patch) } input.trace?.write("ui.patch", next.patch) input.footer.event(next) continue } if (update.type === "stream.subagent") { input.trace?.write("ui.subagent", traceSubagentState(update.state)) input.footer.event(update) continue } input.trace?.write("ui.patch", { view: update.view }) input.footer.event(update) }}