[PR #8497] fix: handle dangling tool_use blocks for LiteLLM proxy compatibility #12767

Open
opened 2026-02-16 18:17:39 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/8497

State: open
Merged: No


Problem

When using OpenCode with LiteLLM proxies (e.g., for Vertex AI, Bedrock), session compaction fails with:

invalid_request_error: messages.2: `tool_use` ids were found without `tool_result` blocks immediately after

or:

litellm.UnsupportedParamsError: Anthropic doesn't support tool calling without `tools=` param specified

This affects users who route Anthropic/Claude API calls through corporate LiteLLM proxies and cannot modify proxy settings (e.g., modify_params=True).

Root Cause Analysis

There are two distinct issues causing these errors:

Issue 1: Missing tools parameter during compaction

When compaction runs, it calls processor.process() with tools: {} (compaction.ts:149):

const result = await processor.process({
  // ...
  tools: {},  // <-- Empty tools object
  messages: [
    ...MessageV2.toModelMessage(input.messages),  // <-- Contains tool_use/tool_result from history
    // ...
  ],
})

LiteLLM validates that if message history contains tool_use/tool_result blocks, the tools parameter must be present. While Anthropic's native API now handles this gracefully, LiteLLM enforces stricter validation.

Issue 2: Dangling tool_use blocks without tool_result

When a session is interrupted or aborted, tool calls may be left in pending or running state. The toModelMessage() function in message-v2.ts currently skips these incomplete tool calls:

if (part.state.status === "completed")
  assistantMessage.parts.push({ /* tool-result */ })
if (part.state.status === "error")
  assistantMessage.parts.push({ /* tool-result with error */ })
// pending/running are NOT handled - creates dangling tool_use!

This creates tool_use blocks in the assistant message without corresponding tool_result blocks, which Anthropic/Claude APIs strictly reject.

Solution

Fix 1: Add dummy _noop tool when message history contains tool calls

In llm.ts, detect when messages contain tool calls but no tools are provided, and inject a placeholder tool:

if (Object.keys(tools).length === 0 && hasToolCalls(input.messages)) {
  tools["_noop"] = tool({
    description: "Internal placeholder tool - not for use",
    inputSchema: jsonSchema({ type: "object", properties: {} }),
    execute: async () => ({ output: "", title: "", metadata: {} }),
  })
}

The _noop tool is excluded from activeTools so it won't be suggested or called by the LLM.

Fix 2: Convert pending/running tool calls to error results

In message-v2.ts, handle pending and running tool states by generating error tool_result blocks:

if (part.state.status === "pending" || part.state.status === "running")
  assistantMessage.parts.push({
    type: ("tool-" + part.tool) as `tool-${string}`,
    state: "output-error",
    toolCallId: part.callID,
    input: part.state.input,
    errorText: "[Tool execution was interrupted]",
    callProviderMetadata: part.metadata,
  })

This ensures every tool_use has a corresponding tool_result, satisfying Anthropic's API requirements.

Files Changed

File Change
packages/opencode/src/session/llm.ts Add hasToolCalls() helper and dummy _noop tool injection
packages/opencode/src/session/message-v2.ts Handle pending/running tool states → error results
packages/opencode/test/session/message-v2.test.ts Add test for pending/running tool conversion

Testing

  • Added unit test verifying pending/running tool calls are converted to error results
  • Manual testing needed with actual LiteLLM proxy setup

Related

Fixes #8246
Fixes #2915
Related to #3243 (stale PR with merge conflicts addressing similar ordering issue)

Prior Art

  • Commit 0af450575 attempted a fix but was later simplified away
  • LiteLLM has a modify_params=True workaround, but many corporate proxy deployments cannot change this setting
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/8497 **State:** open **Merged:** No --- ## Problem When using OpenCode with LiteLLM proxies (e.g., for Vertex AI, Bedrock), session compaction fails with: ``` invalid_request_error: messages.2: `tool_use` ids were found without `tool_result` blocks immediately after ``` or: ``` litellm.UnsupportedParamsError: Anthropic doesn't support tool calling without `tools=` param specified ``` This affects users who route Anthropic/Claude API calls through corporate LiteLLM proxies and cannot modify proxy settings (e.g., `modify_params=True`). ## Root Cause Analysis There are **two distinct issues** causing these errors: ### Issue 1: Missing `tools` parameter during compaction When compaction runs, it calls `processor.process()` with `tools: {}` ([compaction.ts:149](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/compaction.ts#L149)): ```typescript const result = await processor.process({ // ... tools: {}, // <-- Empty tools object messages: [ ...MessageV2.toModelMessage(input.messages), // <-- Contains tool_use/tool_result from history // ... ], }) ``` LiteLLM validates that if message history contains `tool_use`/`tool_result` blocks, the `tools` parameter must be present. While Anthropic's native API now handles this gracefully, LiteLLM enforces stricter validation. ### Issue 2: Dangling `tool_use` blocks without `tool_result` When a session is interrupted or aborted, tool calls may be left in `pending` or `running` state. The `toModelMessage()` function in `message-v2.ts` currently skips these incomplete tool calls: ```typescript if (part.state.status === "completed") assistantMessage.parts.push({ /* tool-result */ }) if (part.state.status === "error") assistantMessage.parts.push({ /* tool-result with error */ }) // pending/running are NOT handled - creates dangling tool_use! ``` This creates `tool_use` blocks in the assistant message without corresponding `tool_result` blocks, which Anthropic/Claude APIs strictly reject. ## Solution ### Fix 1: Add dummy `_noop` tool when message history contains tool calls In `llm.ts`, detect when messages contain tool calls but no tools are provided, and inject a placeholder tool: ```typescript if (Object.keys(tools).length === 0 && hasToolCalls(input.messages)) { tools["_noop"] = tool({ description: "Internal placeholder tool - not for use", inputSchema: jsonSchema({ type: "object", properties: {} }), execute: async () => ({ output: "", title: "", metadata: {} }), }) } ``` The `_noop` tool is excluded from `activeTools` so it won't be suggested or called by the LLM. ### Fix 2: Convert pending/running tool calls to error results In `message-v2.ts`, handle `pending` and `running` tool states by generating error `tool_result` blocks: ```typescript if (part.state.status === "pending" || part.state.status === "running") assistantMessage.parts.push({ type: ("tool-" + part.tool) as `tool-${string}`, state: "output-error", toolCallId: part.callID, input: part.state.input, errorText: "[Tool execution was interrupted]", callProviderMetadata: part.metadata, }) ``` This ensures every `tool_use` has a corresponding `tool_result`, satisfying Anthropic's API requirements. ## Files Changed | File | Change | |------|--------| | `packages/opencode/src/session/llm.ts` | Add `hasToolCalls()` helper and dummy `_noop` tool injection | | `packages/opencode/src/session/message-v2.ts` | Handle pending/running tool states → error results | | `packages/opencode/test/session/message-v2.test.ts` | Add test for pending/running tool conversion | ## Testing - Added unit test verifying pending/running tool calls are converted to error results - Manual testing needed with actual LiteLLM proxy setup ## Related Fixes #8246 Fixes #2915 Related to #3243 (stale PR with merge conflicts addressing similar ordering issue) ## Prior Art - Commit [0af450575](https://github.com/anomalyco/opencode/commit/0af450575647fc906f017b0065fe3aca227c369f) attempted a fix but was later simplified away - LiteLLM has a `modify_params=True` workaround, but many corporate proxy deployments cannot change this setting
yindo added the pull-request label 2026-02-16 18:17:39 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#12767