[PR #8721] fix: Prevent excessive Copilot premium request consumption #12840

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

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

State: open
Merged: No


Summary

Fixes #8030

Builds on #8393 — Adds synthetic message detection to prevent excessive premium requests.

The official plugin (#8393) uses last?.role !== "user" which fails for synthetic user messages created by OpenCode (compaction, tool attachments, subtasks). This PR detects those synthetic messages and correctly marks them as agent-initiated.

How VSCode Copilot Chat Handles This

From microsoft/vscode-copilot-chat @ main:

// toolCallingLoop.ts:469
userInitiatedRequest: iterationNumber === 0 && !isContinuation && !this.options.request.subAgentInvocationId

VSCode uses explicit flags to mark tool loops, continuations, and subagents as agent. OpenCode doesn't have these flags, so we infer the same intent by checking message history.

Detection Logic

Scenario X-Initiator Charges Premium?
First user message user Yes
After assistant/tool message exists agent No
Synthetic user message (compaction, tool result) agent No

Rules:

  1. If ANY assistant/tool message exists → agent (matches VSCode's tool loop behavior)
  2. If LAST user message is synthetic → agent
  3. Otherwise → user

Synthetic patterns detected:

  • "What did we do so far?" (compaction)
  • "Tool X returned an attachment:" (tool results)
  • "The following tool was executed by the user" (subtasks)

Why This Approach

OpenCode creates synthetic user messages for internal operations that VSCode handles differently. Since we can't use VSCode's explicit flags (iterationNumber, isContinuation, subAgentInvocationId), we infer agent status from:

  1. Presence of assistant/tool messages in history
  2. Pattern matching on synthetic user message content

This aligns with third-party Copilot clients (litellm, copilot-api, crush) that use the same "Sticky" inference approach.

Changes

copilot.ts:

  • Added isSynthetic() and hasSyntheticContent() for pattern matching
  • Added detectAgent() with history-based inference
  • Unified detectVision() for both Completions and Responses API

copilot.test.ts (new):

  • 16 tests covering synthetic detection and agent detection

Verification

Check Result
Prettier Pass
Type check Pass
Copilot tests (local) 16 pass
Copilot tests (Docker) 16 pass
Rebased on latest dev (includes #8393)

Related

  • #8067 — Original discussion on Copilot premium request consumption (closed, discussion ongoing)
  • #8030 — Report of excessive premium requests
  • #8700 — Synthetic user messages burning premium requests
  • #8766 — Follow-up: migrate to metadata-based detection (long-term)
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/8721 **State:** open **Merged:** No --- ## Summary Fixes #8030 **Builds on #8393** — Adds synthetic message detection to prevent excessive premium requests. The official plugin (#8393) uses `last?.role !== "user"` which fails for synthetic user messages created by OpenCode (compaction, tool attachments, subtasks). This PR detects those synthetic messages and correctly marks them as agent-initiated. ## How VSCode Copilot Chat Handles This From [`microsoft/vscode-copilot-chat`](https://github.com/microsoft/vscode-copilot-chat) @ `main`: ```typescript // toolCallingLoop.ts:469 userInitiatedRequest: iterationNumber === 0 && !isContinuation && !this.options.request.subAgentInvocationId ``` VSCode uses **explicit flags** to mark tool loops, continuations, and subagents as `agent`. OpenCode doesn't have these flags, so we **infer** the same intent by checking message history. ## Detection Logic | Scenario | X-Initiator | Charges Premium? | |----------|-------------|------------------| | First user message | `user` | ✅ Yes | | After assistant/tool message exists | `agent` | ❌ No | | Synthetic user message (compaction, tool result) | `agent` | ❌ No | **Rules:** 1. If ANY assistant/tool message exists → `agent` (matches VSCode's tool loop behavior) 2. If LAST user message is synthetic → `agent` 3. Otherwise → `user` **Synthetic patterns detected:** - `"What did we do so far?"` (compaction) - `"Tool X returned an attachment:"` (tool results) - `"The following tool was executed by the user"` (subtasks) ## Why This Approach OpenCode creates synthetic `user` messages for internal operations that VSCode handles differently. Since we can't use VSCode's explicit flags (`iterationNumber`, `isContinuation`, `subAgentInvocationId`), we infer agent status from: 1. Presence of assistant/tool messages in history 2. Pattern matching on synthetic user message content This aligns with third-party Copilot clients (litellm, copilot-api, crush) that use the same "Sticky" inference approach. ## Changes **`copilot.ts`**: - Added `isSynthetic()` and `hasSyntheticContent()` for pattern matching - Added `detectAgent()` with history-based inference - Unified `detectVision()` for both Completions and Responses API **`copilot.test.ts`** (new): - 16 tests covering synthetic detection and agent detection ## Verification | Check | Result | |-------|--------| | Prettier | ✅ Pass | | Type check | ✅ Pass | | Copilot tests (local) | ✅ 16 pass | | Copilot tests (Docker) | ✅ 16 pass | | Rebased on latest dev | ✅ (includes #8393) | ## Related - #8067 — Original discussion on Copilot premium request consumption (closed, discussion ongoing) - #8030 — Report of excessive premium requests - #8700 — Synthetic user messages burning premium requests - #8766 — Follow-up: migrate to metadata-based detection (long-term)
yindo added the pull-request label 2026-02-16 18:17:44 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#12840