diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..e4ff0536 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: dansmolsky diff --git a/lib/config.ts b/lib/config.ts index beabaa3f..f24e9680 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -450,7 +450,7 @@ const defaultConfig: PluginConfig = { strategies: { deduplication: { enabled: true, - protectedTools: [...DEFAULT_PROTECTED_TOOLS], + protectedTools: [], }, supersedeWrites: { enabled: false, @@ -458,7 +458,7 @@ const defaultConfig: PluginConfig = { purgeErrors: { enabled: true, turns: 4, - protectedTools: [...DEFAULT_PROTECTED_TOOLS], + protectedTools: [], }, }, } diff --git a/lib/messages/inject.ts b/lib/messages/inject.ts index 5920566a..491ecd6c 100644 --- a/lib/messages/inject.ts +++ b/lib/messages/inject.ts @@ -8,6 +8,8 @@ import { buildToolIdList, createSyntheticAssistantMessage, createSyntheticUserMessage, + createSyntheticToolPart, + isDeepSeekOrKimi, isIgnoredUserMessage, } from "./utils" import { getFilePathFromParameters, isProtectedFilePath } from "../protected-file-patterns" @@ -142,15 +144,28 @@ export const insertPruneToolContext = ( const userInfo = lastUserMessage.info as UserMessage const variant = state.variant ?? userInfo.variant - const lastMessage = messages[messages.length - 1] - const isLastMessageUser = - lastMessage?.info?.role === "user" && !isIgnoredUserMessage(lastMessage) + let lastNonIgnoredMessage: WithParts | undefined + for (let i = messages.length - 1; i >= 0; i--) { + const msg = messages[i] + if (!(msg.info.role === "user" && isIgnoredUserMessage(msg))) { + lastNonIgnoredMessage = msg + break + } + } - if (isLastMessageUser) { + if (!lastNonIgnoredMessage || lastNonIgnoredMessage.info.role === "user") { messages.push(createSyntheticUserMessage(lastUserMessage, prunableToolsContent, variant)) } else { - messages.push( - createSyntheticAssistantMessage(lastUserMessage, prunableToolsContent, variant), - ) + const providerID = userInfo.model?.providerID || "" + const modelID = userInfo.model?.modelID || "" + + if (isDeepSeekOrKimi(providerID, modelID)) { + const toolPart = createSyntheticToolPart(lastNonIgnoredMessage, prunableToolsContent) + lastNonIgnoredMessage.parts.push(toolPart) + } else { + messages.push( + createSyntheticAssistantMessage(lastUserMessage, prunableToolsContent, variant), + ) + } } } diff --git a/lib/messages/utils.ts b/lib/messages/utils.ts index 48ae0e6c..406b6f42 100644 --- a/lib/messages/utils.ts +++ b/lib/messages/utils.ts @@ -7,9 +7,15 @@ const SYNTHETIC_MESSAGE_ID = "msg_01234567890123456789012345" const SYNTHETIC_PART_ID = "prt_01234567890123456789012345" const SYNTHETIC_CALL_ID = "call_01234567890123456789012345" -const isGeminiModel = (modelID: string): boolean => { +export const isDeepSeekOrKimi = (providerID: string, modelID: string): boolean => { + const lowerProviderID = providerID.toLowerCase() const lowerModelID = modelID.toLowerCase() - return lowerModelID.includes("gemini") + return ( + lowerProviderID.includes("deepseek") || + lowerProviderID.includes("kimi") || + lowerModelID.includes("deepseek") || + lowerModelID.includes("kimi") + ) } export const createSyntheticUserMessage = ( @@ -50,54 +56,59 @@ export const createSyntheticAssistantMessage = ( const userInfo = baseMessage.info as UserMessage const now = Date.now() - const baseInfo = { - id: SYNTHETIC_MESSAGE_ID, - sessionID: userInfo.sessionID, - role: "assistant" as const, - agent: userInfo.agent || "code", - parentID: userInfo.id, - modelID: userInfo.model.modelID, - providerID: userInfo.model.providerID, - mode: "default", - path: { - cwd: "/", - root: "/", - }, - time: { created: now, completed: now }, - cost: 0, - tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - ...(variant !== undefined && { variant }), - } - - // For Gemini models, add thoughtSignature bypass to avoid validation errors - const toolPartMetadata = isGeminiModel(userInfo.model.modelID) - ? { google: { thoughtSignature: "skip_thought_signature_validator" } } - : undefined - return { - info: baseInfo, + info: { + id: SYNTHETIC_MESSAGE_ID, + sessionID: userInfo.sessionID, + role: "assistant" as const, + agent: userInfo.agent || "code", + parentID: userInfo.id, + modelID: userInfo.model.modelID, + providerID: userInfo.model.providerID, + mode: "default", + path: { + cwd: "/", + root: "/", + }, + time: { created: now, completed: now }, + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + ...(variant !== undefined && { variant }), + }, parts: [ { id: SYNTHETIC_PART_ID, sessionID: userInfo.sessionID, messageID: SYNTHETIC_MESSAGE_ID, - type: "tool", - callID: SYNTHETIC_CALL_ID, - tool: "context_info", - state: { - status: "completed", - input: {}, - output: content, - title: "Context Info", - metadata: {}, - time: { start: now, end: now }, - }, - ...(toolPartMetadata && { metadata: toolPartMetadata }), + type: "text", + text: content, }, ], } } +export const createSyntheticToolPart = (baseMessage: WithParts, content: string) => { + const userInfo = baseMessage.info as UserMessage + const now = Date.now() + + return { + id: SYNTHETIC_PART_ID, + sessionID: userInfo.sessionID, + messageID: baseMessage.info.id, + type: "tool" as const, + callID: SYNTHETIC_CALL_ID, + tool: "context_info", + state: { + status: "completed" as const, + input: {}, + output: content, + title: "Context Info", + metadata: {}, + time: { start: now, end: now }, + }, + } +} + /** * Extracts a human-readable key from tool metadata for display purposes. */ diff --git a/lib/ui/utils.ts b/lib/ui/utils.ts index b1e00ed9..9134a5cf 100644 --- a/lib/ui/utils.ts +++ b/lib/ui/utils.ts @@ -60,9 +60,6 @@ function shortenSinglePath(path: string, workingDirectory?: string): string { return path } -/** - * Formats a list of pruned items in the style: "→ tool: parameter" - */ export function formatPrunedItemsList( pruneToolIds: string[], toolMetadata: Map, @@ -95,9 +92,6 @@ export function formatPrunedItemsList( return lines } -/** - * Formats a PruningResult into a human-readable string for the prune tool output. - */ export function formatPruningResultForTool( prunedIds: string[], toolMetadata: Map,