diff --git a/src/tools/background-task/council-result-format.ts b/src/tools/background-task/council-result-format.ts new file mode 100644 index 000000000..262793dd8 --- /dev/null +++ b/src/tools/background-task/council-result-format.ts @@ -0,0 +1,82 @@ +import type { BackgroundTask } from "../../features/background-agent" +import { COUNCIL_MEMBER_KEY_PREFIX } from "../../agents/builtin-agents/council-member-agents" +import type { BackgroundOutputClient } from "./clients" +import { extractMessages, getErrorMessage } from "./session-messages" + +const OPENING_TAG = "" +const CLOSING_TAG = "" + +export interface CouncilTaskResult { + has_response: boolean + response_complete: boolean + result: string | null + session_id: string | null +} + +export function isCouncilTask(task: BackgroundTask): boolean { + return task.agent?.startsWith(COUNCIL_MEMBER_KEY_PREFIX) ?? false +} + +function getTimeString(value: unknown): string { + return typeof value === "string" ? value : "" +} + +function extractCouncilResponse(fullText: string): CouncilTaskResult & { session_id: null } { + const lastOpenIdx = fullText.lastIndexOf(OPENING_TAG) + if (lastOpenIdx === -1) { + return { has_response: false, response_complete: false, result: null, session_id: null } + } + + const contentStart = lastOpenIdx + OPENING_TAG.length + const closingAfterLastOpen = fullText.indexOf(CLOSING_TAG, contentStart) + + if (closingAfterLastOpen === -1) { + const partial = fullText.slice(contentStart).trim() + return { has_response: true, response_complete: false, result: partial || null, session_id: null } + } + + const content = fullText.slice(contentStart, closingAfterLastOpen).trim() + return { has_response: true, response_complete: true, result: content, session_id: null } +} + +export async function formatCouncilTaskResult( + task: BackgroundTask, + client: BackgroundOutputClient, +): Promise { + if (!task.sessionID) { + return { has_response: false, response_complete: false, result: null, session_id: null } + } + + const messagesResult = await client.session.messages({ path: { id: task.sessionID } }) + + const errorMessage = getErrorMessage(messagesResult) + if (errorMessage) { + return { has_response: false, response_complete: false, result: null, session_id: task.sessionID } + } + + const messages = extractMessages(messagesResult) + if (!Array.isArray(messages) || messages.length === 0) { + return { has_response: false, response_complete: false, result: null, session_id: task.sessionID } + } + + const assistantMessages = messages.filter((m) => m.info?.role === "assistant") + const sorted = [...assistantMessages].sort((a, b) => { + const timeA = getTimeString(a.info?.time) + const timeB = getTimeString(b.info?.time) + return timeA.localeCompare(timeB) + }) + + const textParts: string[] = [] + for (const message of sorted) { + for (const part of message.parts ?? []) { + if ((part.type === "text" || part.type === "reasoning") && part.text) { + textParts.push(part.text) + } + } + } + + const fullText = textParts.join("\n\n") + const extracted = extractCouncilResponse(fullText) + + return { ...extracted, session_id: task.sessionID } +}