feat(background-task): add council-specific result formatter with COUNCIL_MEMBER_RESPONSE tag extraction
This commit is contained in:
@@ -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 = "<COUNCIL_MEMBER_RESPONSE>"
|
||||
const CLOSING_TAG = "</COUNCIL_MEMBER_RESPONSE>"
|
||||
|
||||
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<CouncilTaskResult> {
|
||||
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 }
|
||||
}
|
||||
Reference in New Issue
Block a user