| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- // 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,
- })
- }
|