From 8b9b9ad31ee715301613f7254424590f0cc8805b Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sun, 12 Apr 2026 12:02:39 -0500 Subject: [PATCH] fix: ensure images read by agent dont count against quota (#22168) --- .../src/plugin/github-copilot/copilot.ts | 22 ++++++++++++++++--- packages/opencode/src/session/message-v2.ts | 4 +++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/plugin/github-copilot/copilot.ts b/packages/opencode/src/plugin/github-copilot/copilot.ts index 5f61f013d9..ac685f74da 100644 --- a/packages/opencode/src/plugin/github-copilot/copilot.ts +++ b/packages/opencode/src/plugin/github-copilot/copilot.ts @@ -5,6 +5,7 @@ import { iife } from "@/util/iife" import { Log } from "../../util/log" import { setTimeout as sleep } from "node:timers/promises" import { CopilotModels } from "./models" +import { MessageV2 } from "@/session/message-v2" const log = Log.create({ service: "plugin.copilot" }) @@ -27,6 +28,21 @@ function base(enterpriseUrl?: string) { return enterpriseUrl ? `https://copilot-api.${normalizeDomain(enterpriseUrl)}` : "https://api.githubcopilot.com" } +// Check if a message is a synthetic user msg used to attach an image from a tool call +function imgMsg(msg: any): boolean { + if (msg?.role !== "user") return false + + // Handle the 3 api formats + + const content = msg.content + if (typeof content === "string") return content === MessageV2.SYNTHETIC_ATTACHMENT_PROMPT + if (!Array.isArray(content)) return false + return content.some( + (part: any) => + (part?.type === "text" || part?.type === "input_text") && part.text === MessageV2.SYNTHETIC_ATTACHMENT_PROMPT, + ) +} + function fix(model: Model, url: string): Model { return { ...model, @@ -90,7 +106,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { (msg: any) => Array.isArray(msg.content) && msg.content.some((part: any) => part.type === "image_url"), ), - isAgent: last?.role !== "user", + isAgent: last?.role !== "user" || imgMsg(last), } } @@ -102,7 +118,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { (item: any) => Array.isArray(item?.content) && item.content.some((part: any) => part.type === "input_image"), ), - isAgent: last?.role !== "user", + isAgent: last?.role !== "user" || imgMsg(last), } } @@ -124,7 +140,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { part.content.some((nested: any) => nested?.type === "image")), ), ), - isAgent: !(last?.role === "user" && hasNonToolCalls), + isAgent: !(last?.role === "user" && hasNonToolCalls) || imgMsg(last), } } } catch {} diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index aa42e1c1dc..4c18d1f7e0 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -25,6 +25,8 @@ interface FetchDecompressionError extends Error { } export namespace MessageV2 { + export const SYNTHETIC_ATTACHMENT_PROMPT = "Attached image(s) from tool result:" + export function isMedia(mime: string) { return mime.startsWith("image/") || mime === "application/pdf" } @@ -808,7 +810,7 @@ export namespace MessageV2 { parts: [ { type: "text" as const, - text: "Attached image(s) from tool result:", + text: SYNTHETIC_ATTACHMENT_PROMPT, }, ...media.map((attachment) => ({ type: "file" as const,