diff --git a/lib/messages/inject.ts b/lib/messages/inject.ts index d19aec9..76c72dd 100644 --- a/lib/messages/inject.ts +++ b/lib/messages/inject.ts @@ -7,6 +7,8 @@ import { extractParameterKey, buildToolIdList, createSyntheticAssistantMessageWithToolPart, + isIgnoredUserMessage, + hasReasoningInCurrentAssistantTurn, } from "./utils" import { getFilePathFromParameters, isProtectedFilePath } from "../protected-file-patterns" import { getLastUserMessage } from "../shared-utils" @@ -142,11 +144,22 @@ export const insertPruneToolContext = ( const modelID = userInfo.model.modelID const isGitHubCopilot = providerID === "github-copilot" || providerID === "github-copilot-enterprise" + + // TODO: This can probably be improved further to only trigger for the appropriate thinking settings + // This setting is also potentially only necessary for claude subscription, API seems to not need this + // validation. See more here: https://platform.claude.com/docs/en/build-with-claude/extended-thinking const isAnthropic = modelID.includes("claude") - if (isGitHubCopilot || isAnthropic) { + if (isGitHubCopilot) { const lastMessage = messages[messages.length - 1] - if (lastMessage?.info?.role === "user") { + if (lastMessage?.info?.role === "user" && !isIgnoredUserMessage(lastMessage)) { + return + } + } + + // Anthropic extended thinking models require a thinking block at the start of its turn + if (isAnthropic) { + if (!hasReasoningInCurrentAssistantTurn(messages)) { return } } diff --git a/lib/messages/utils.ts b/lib/messages/utils.ts index 99f02b9..26fc29a 100644 --- a/lib/messages/utils.ts +++ b/lib/messages/utils.ts @@ -193,3 +193,37 @@ export function buildToolIdList( } return toolIds } + +export const isIgnoredUserMessage = (message: WithParts): boolean => { + if (!message.parts || message.parts.length === 0) { + return true + } + + for (const part of message.parts) { + if (!(part as any).ignored) { + return false + } + } + + return true +} + +export const hasReasoningInCurrentAssistantTurn = (messages: WithParts[]): boolean => { + for (let i = messages.length - 1; i >= 0; i--) { + const message = messages[i] + if (message.info?.role === "user") { + if (isIgnoredUserMessage(message)) { + continue + } + return false + } + if (message.info?.role === "assistant" && message.parts) { + for (const part of message.parts) { + if (part.type === "reasoning") { + return true + } + } + } + } + return false +} diff --git a/lib/shared-utils.ts b/lib/shared-utils.ts index ce3be56..902ea40 100644 --- a/lib/shared-utils.ts +++ b/lib/shared-utils.ts @@ -1,4 +1,5 @@ import { SessionState, WithParts } from "./state" +import { isIgnoredUserMessage } from "./messages/utils" export const isMessageCompacted = (state: SessionState, msg: WithParts): boolean => { return msg.info.time.created < state.lastCompaction @@ -7,7 +8,7 @@ export const isMessageCompacted = (state: SessionState, msg: WithParts): boolean export const getLastUserMessage = (messages: WithParts[]): WithParts | null => { for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i] - if (msg.info.role === "user") { + if (msg.info.role === "user" && !isIgnoredUserMessage(msg)) { return msg } } diff --git a/package-lock.json b/package-lock.json index d06ba6d..86ef92a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tarquinen/opencode-dcp", - "version": "1.1.6", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tarquinen/opencode-dcp", - "version": "1.1.6", + "version": "1.2.1", "license": "MIT", "dependencies": { "@opencode-ai/sdk": "^1.1.3", diff --git a/package.json b/package.json index f9f95a9..6ddae7d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@tarquinen/opencode-dcp", - "version": "1.2.0", + "version": "1.2.1", "type": "module", "description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context", "main": "./dist/index.js",