Skip to content

Conversation

@elithrar
Copy link
Contributor

@elithrar elithrar commented Dec 25, 2025

This PR fixes the Failed to parse the text response error in opencode github run when the LLM responds with only tool calls or reasoning parts (no text). This is rare (about ~5/100 runs?) but frustrating as the entire invocation is discarded.

The new extractResponseText() function handles response parts with fallback logic:

  1. Text parts - returns text directly (existing behavior)
  2. Reasoning parts - returns reasoning with [Reasoning] prefix (for thinking models like o1, Claude extended thinking)
  3. Tool-only responses - returns a summary of completed tool calls with their titles

specifically:

  • Tool-heavy responses (e.g., todowrite only) caused findLast to return undefined, throwing an error
  • Thinking models may produce only reasoning parts with no text output
  • The error message now includes part types for debugging: Part types found: [tool, step-start]

repro

This is easy to repro directly by just prompting opencode via the GitHub Action:

/bonk lets reproduce this: put a todo list together based on our plan here. Provide NO other output of any kind. No text. ONLY use the todo tool.

before (fails):

Sending message to opencode...
|  Todo     {"todos":[{"id":"1","content":"Fork/clone sst/opencode repository","status":"pending","priority":"high"},{"id":"2","content":"Navigate to packages/opencode/src/cli/cmd/github.ts and locate lines 858-869","status":"pending","priority":"high"},{"id":"3","content":"Implement Priority 1: Add fallback to reasoning parts for thinking models","status":"pending","priority":"high"},{"id":"4","content":"Implement Priority 2: Handle tool-only responses with graceful summary","status":"pending","priority":"high"},{"id":"5","content":"Implement Priority 3: Improve error message with part types for debugging","status":"pending","priority":"high"},{"id":"6","content":"Handle edge cases: empty parts array, aborted requests, step-start only states","status":"pending","priority":"medium"},{"id":"7","content":"Test the fix with tool-heavy responses and reasoning-only models","status":"pending","priority":"medium"},{"id":"8","content":"Submit PR to sst/opencode with the comprehensive fix","status":"pending","priority":"high"}]}

855 |             `${result.info.error.name}: ${"message" in result.info.error ? result.info.error.message : ""}`,
856 |           )
857 |         }
858 | 
859 |         const match = result.parts.findLast((p) => p.type === "text")
860 |         if (!match) throw new Error("Failed to parse the text response")
                                ^
error: Failed to parse the text response
      at chat (src/cli/cmd/github.ts:860:27)

or:

|  Todo     {"todos":[{"id":"1","content":"Analyze OpenCode github.ts response parsing issue","status":"completed","priority":"high"},{"id":"2","content":"Document reproduction case: reasoning-only or tool-only responses fail","status":"completed","priority":"high"},{"id":"3","content":"Propose fix: add fallbacks for reasoning parts, tool-only responses, and better error messages","status":"completed","priority":"high"}]}
855 |             `${result.info.error.name}: ${"message" in result.info.error ? result.info.error.message : ""}`,
856 |           )
Creating comment...
857 |         }
858 | 
859 |         const match = result.parts.findLast((p) => p.type === "text")
860 |         if (!match) throw new Error("Failed to parse the text response")
                                ^
error: Failed to parse the text response
      at chat (src/cli/cmd/github.ts:860:27)

after (succeeds):

Completed 1 tool call(s):
- todowrite: 8 todos

tests:

  • extractResponseText tested for text parts, reasoning fallback, tool-only responses
  • Tests verify the original bug scenario (tool-only with no text) no longer throws
  • Edge cases: empty parts, running tools (ignored), mixed part types

related:

@elithrar elithrar force-pushed the fix-non-text-response-parts branch from 0f60493 to 0396a9e Compare December 25, 2025 23:34
@elithrar
Copy link
Contributor Author

This is good to go. Ran into this a bunch of times over the last week and figured I'd get a patch up.

@rekram1-node
Copy link
Collaborator

/review

@github-actions
Copy link
Contributor

lgtm

@rekram1-node
Copy link
Collaborator

rekram1-node commented Dec 27, 2025

wondering if we should just send another prompt to the agent and tell it to summarize the convo or something to keep it "cleaner"?

If the last message isn't a text part?

@elithrar
Copy link
Contributor Author

@rekram1-node not a bad idea: let me stub that out and test locally.

@elithrar elithrar force-pushed the fix-non-text-response-parts branch from 56b66da to 4fda449 Compare December 27, 2025 18:32
@elithrar
Copy link
Contributor Author

elithrar commented Dec 27, 2025

Here's a take on attempting to re-summarize @rekram1-node - e.g. we make one (1) attempt to summarize the output when there are no TextPart parts, and only fail if that ALSO fails (which is acceptable - models be models).


  1. extractResponseText now returns null for tool-only or reasoning-only responses (instead of generating a programmatic summary)

  2. chat() makes a follow-up summary call when extractResponseText returns null:

    • Uses tools: { "*": false } to disable all tools and force a text response
    • Prompt: "Summarize the actions (tool calls & reasoning) you did for the user in 1-2 sentences."
    • Standard error handling if the summary call fails
  3. Updated tests to expect null for tool-only/reasoning-only cases (removed programmatic summary tests)

  4. Edge case note: If the summary call itself returns only reasoning parts (e.g., certain thinking models may still produce extended thinking), this will currently fail with "Failed to get summary from agent". This can be addressed in a follow-up if needed.

const text = extractResponseText(result.parts)
if (text) return text

// No text part (tool-only or reasoning-only) - ask agent to summarize
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

providerID,
modelID,
},
tools: { "*": false }, // Disable all tools to force text response
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note - we explicitly disable tools here as we only want to summarize. This feels right, but not sure if it's surprising to users.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants