175 lines
4.5 KiB
TypeScript
175 lines
4.5 KiB
TypeScript
// Thin bridge between reducer output and the footer API.
|
|
//
|
|
// The reducers produce StreamCommit[] and an optional FooterOutput (patch +
|
|
// view + subagent state). This module forwards them to footer.append() and
|
|
// footer.event() respectively, 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 reducer code doesn't have to repeat that.
|
|
import type { FooterApi, FooterOutput, FooterPatch, FooterSubagentState, StreamCommit } from "./types"
|
|
|
|
type Trace = {
|
|
write(type: string, data?: unknown): void
|
|
}
|
|
|
|
type OutputInput = {
|
|
footer: FooterApi
|
|
trace?: Trace
|
|
}
|
|
|
|
type StreamOutput = {
|
|
commits: StreamCommit[]
|
|
footer?: FooterOutput
|
|
}
|
|
|
|
// 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,
|
|
sessionID: commit.part.sessionID,
|
|
messageID: commit.part.messageID,
|
|
callID: commit.part.callID,
|
|
tool: commit.part.tool,
|
|
state: {
|
|
status: commit.part.state.status,
|
|
title: "title" in commit.part.state ? summarize(commit.part.state.title) : undefined,
|
|
error: "error" in commit.part.state ? summarize(commit.part.state.error) : undefined,
|
|
time: "time" in commit.part.state ? summarize(commit.part.state.time) : undefined,
|
|
input: summarize(commit.part.state.input),
|
|
metadata: "metadata" in commit.part.state ? summarize(commit.part.state.metadata) : undefined,
|
|
},
|
|
}
|
|
: undefined,
|
|
}
|
|
}
|
|
|
|
export 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,
|
|
permission: item.permission,
|
|
patterns: item.patterns,
|
|
tool: item.tool,
|
|
metadata: item.metadata
|
|
? {
|
|
keys: Object.keys(item.metadata),
|
|
input: summarize(item.metadata.input),
|
|
}
|
|
: undefined,
|
|
})),
|
|
questions: state.questions.map((item) => ({
|
|
id: item.id,
|
|
sessionID: item.sessionID,
|
|
questions: item.questions.map((question) => ({
|
|
header: question.header,
|
|
question: question.question,
|
|
options: question.options.length,
|
|
multiple: question.multiple,
|
|
})),
|
|
})),
|
|
}
|
|
}
|
|
|
|
export function traceFooterOutput(footer?: FooterOutput) {
|
|
if (!footer?.subagent) {
|
|
return footer
|
|
}
|
|
|
|
return {
|
|
...footer,
|
|
subagent: traceSubagentState(footer.subagent),
|
|
}
|
|
}
|
|
|
|
// Forwards reducer 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)
|
|
}
|
|
|
|
if (out.footer?.patch) {
|
|
const next = patch(out.footer.patch)
|
|
input.trace?.write("ui.patch", next)
|
|
input.footer.event({
|
|
type: "stream.patch",
|
|
patch: next,
|
|
})
|
|
}
|
|
|
|
if (out.footer?.subagent) {
|
|
input.trace?.write("ui.subagent", traceSubagentState(out.footer.subagent))
|
|
input.footer.event({
|
|
type: "stream.subagent",
|
|
state: out.footer.subagent,
|
|
})
|
|
}
|
|
|
|
if (!out.footer?.view) {
|
|
return
|
|
}
|
|
|
|
input.trace?.write("ui.patch", {
|
|
view: out.footer.view,
|
|
})
|
|
input.footer.event({
|
|
type: "stream.view",
|
|
view: out.footer.view,
|
|
})
|
|
}
|