diff --git a/index.ts b/index.ts index fe0d48d..918d49c 100644 --- a/index.ts +++ b/index.ts @@ -955,19 +955,35 @@ async function chat(text: string, files: PromptFiles = []) { if (!chatRes.ok) { throw new Error(`opencode /chat returned ${chatRes.status} ${chatRes.statusText}: ${rawText.slice(0, 1000)}`) } - let chatData: { parts?: unknown[]; data?: { parts?: unknown[] } } + let chatData: any try { chatData = JSON.parse(rawText) } catch (e) { throw new Error(`opencode /chat returned non-JSON (status ${chatRes.status}, content-type ${chatRes.headers.get("content-type")}): ${rawText.slice(0, 1000)}`) } - // Find the last text part in the response + const info = chatData?.info ?? chatData?.data?.info const parts = chatData?.parts || chatData?.data?.parts || [] - const match = parts.findLast((p: any) => p.type === "text") as { text: string } | undefined - if (!match) throw new Error("Failed to parse the text response") - return match.text + // Surface a provider/model error instead of the generic parse failure. + if (info?.error) { + throw new Error(`opencode model error: ${JSON.stringify(info.error).slice(0, 2000)}`) + } + + // Prefer the last non-empty text part. + const textParts = parts.filter((p: any) => p.type === "text" && p.text?.trim()) + const last = textParts[textParts.length - 1] as { text: string } | undefined + if (last) return last.text + + // The agent may have done work (edited files) without a closing text message. + // Don't hard-fail in that case; report what happened instead. + console.error("No text part. Part types:", parts.map((p: any) => p.type).join(", ") || "none") + const ranTools = parts.some((p: any) => p.type === "tool") + if (ranTools) return "_(opencode completed the run but produced no summary message.)_" + + throw new Error( + `Failed to parse the text response (parts: ${parts.map((p: any) => p.type).join(", ") || "none"}; raw: ${rawText.slice(0, 1000)})`, + ) } // ─── Prompt building ────────────────────────────────────────────────────────