89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
import type { OpencodeClient } from "./types"
|
|
import type { SessionMessage } from "./executor-types"
|
|
import { normalizeSDKResponse } from "../../shared"
|
|
|
|
export async function fetchSyncResult(
|
|
client: OpencodeClient,
|
|
sessionID: string,
|
|
anchorMessageCount?: number,
|
|
options?: { strictAbortRecovery?: boolean }
|
|
): Promise<{ ok: true; textContent: string } | { ok: false; error: string }> {
|
|
const messagesResult = await client.session.messages({
|
|
path: { id: sessionID },
|
|
})
|
|
|
|
if ((messagesResult as { error?: unknown }).error) {
|
|
return { ok: false, error: `Error fetching result: ${(messagesResult as { error: unknown }).error}\n\nSession ID: ${sessionID}` }
|
|
}
|
|
|
|
const messages = normalizeSDKResponse(messagesResult, [] as SessionMessage[], {
|
|
preferResponseOnMissingData: true,
|
|
})
|
|
|
|
const messagesAfterAnchor = anchorMessageCount !== undefined ? messages.slice(anchorMessageCount) : messages
|
|
|
|
if (anchorMessageCount !== undefined && messagesAfterAnchor.length === 0) {
|
|
return {
|
|
ok: false,
|
|
error: `Session completed but no new response was generated. The model may have failed silently.\n\nSession ID: ${sessionID}`,
|
|
}
|
|
}
|
|
|
|
const assistantMessages = messagesAfterAnchor
|
|
.filter((m) => m.info?.role === "assistant")
|
|
.sort((a, b) => (b.info?.time?.created ?? 0) - (a.info?.time?.created ?? 0))
|
|
const lastMessage = assistantMessages[0]
|
|
|
|
if (anchorMessageCount !== undefined && !lastMessage) {
|
|
return {
|
|
ok: false,
|
|
error: `Session completed but no new response was generated. The model may have failed silently.\n\nSession ID: ${sessionID}`,
|
|
}
|
|
}
|
|
|
|
if (!lastMessage) {
|
|
return { ok: false, error: `No assistant response found.\n\nSession ID: ${sessionID}` }
|
|
}
|
|
|
|
if (options?.strictAbortRecovery) {
|
|
if (lastMessage.info && "error" in lastMessage.info) {
|
|
return {
|
|
ok: false,
|
|
error: `Latest assistant message is an error; refusing abort recovery.\n\nSession ID: ${sessionID}`,
|
|
}
|
|
}
|
|
|
|
const lastTextParts = lastMessage.parts?.filter((p) => p.type === "text" || p.type === "reasoning") ?? []
|
|
const lastContent = lastTextParts.map((p) => p.text ?? "").filter(Boolean).join("\n")
|
|
if (!lastContent) {
|
|
return {
|
|
ok: false,
|
|
error: `No assistant text output found in latest response.\n\nSession ID: ${sessionID}`,
|
|
}
|
|
}
|
|
|
|
return { ok: true, textContent: lastContent }
|
|
}
|
|
|
|
// Search assistant messages (newest first) for one with text/reasoning content.
|
|
// The last assistant message may only contain tool calls with no text.
|
|
let textContent = ""
|
|
for (const msg of assistantMessages) {
|
|
const textParts = msg.parts?.filter((p) => p.type === "text" || p.type === "reasoning") ?? []
|
|
const content = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n")
|
|
if (content) {
|
|
textContent = content
|
|
break
|
|
}
|
|
}
|
|
|
|
if (!textContent) {
|
|
return {
|
|
ok: false,
|
|
error: `No assistant text output found in completed response.\n\nSession ID: ${sessionID}`,
|
|
}
|
|
}
|
|
|
|
return { ok: true, textContent }
|
|
}
|