Skip to content

Commit bdc5f78

Browse files
authored
Merge pull request #264 from Opencode-DCP/dev
merge dev into master
2 parents 922ef17 + bea6a42 commit bdc5f78

File tree

2 files changed

+52
-62
lines changed

2 files changed

+52
-62
lines changed

lib/messages/inject.ts

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
buildToolIdList,
99
createSyntheticAssistantMessageWithToolPart,
1010
isIgnoredUserMessage,
11-
hasReasoningInCurrentAssistantTurn,
1211
} from "./utils"
1312
import { getFilePathFromParameters, isProtectedFilePath } from "../protected-file-patterns"
1413
import { getLastUserMessage } from "../shared-utils"
@@ -139,31 +138,16 @@ export const insertPruneToolContext = (
139138
return
140139
}
141140

142-
const userInfo = lastUserMessage.info as UserMessage
143-
const providerID = userInfo.model.providerID
144-
const modelID = userInfo.model.modelID
145-
const isGitHubCopilot =
146-
providerID === "github-copilot" || providerID === "github-copilot-enterprise"
147-
148-
// TODO: This can probably be improved further to only trigger for the appropriate thinking settings
149-
// This setting is also potentially only necessary for claude subscription, API seems to not need this
150-
// validation. See more here: https://platform.claude.com/docs/en/build-with-claude/extended-thinking
151-
const isAnthropic = modelID.includes("claude")
152-
153-
if (isGitHubCopilot) {
154-
const lastMessage = messages[messages.length - 1]
155-
if (lastMessage?.info?.role === "user" && !isIgnoredUserMessage(lastMessage)) {
156-
return
157-
}
158-
}
159-
160-
// Anthropic extended thinking models require a thinking block at the start of its turn
161-
if (isAnthropic) {
162-
if (!hasReasoningInCurrentAssistantTurn(messages)) {
163-
return
164-
}
141+
// Never inject immediately following a user message - wait until assistant has started its turn
142+
// This avoids interfering with model reasoning/thinking phases
143+
// TODO: This can be skipped if there is a good way to check if the model has reasoning,
144+
// can't find a good way to do this yet
145+
const lastMessage = messages[messages.length - 1]
146+
if (lastMessage?.info?.role === "user" && !isIgnoredUserMessage(lastMessage)) {
147+
return
165148
}
166149

150+
const userInfo = lastUserMessage.info as UserMessage
167151
const variant = state.variant ?? userInfo.variant
168152
messages.push(
169153
createSyntheticAssistantMessageWithToolPart(lastUserMessage, prunableToolsContent, variant),

lib/messages/utils.ts

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,58 @@ const SYNTHETIC_MESSAGE_ID = "msg_01234567890123456789012345"
77
const SYNTHETIC_PART_ID = "prt_01234567890123456789012345"
88
const SYNTHETIC_CALL_ID = "call_01234567890123456789012345"
99

10+
const isGeminiModel = (modelID: string): boolean => {
11+
const lowerModelID = modelID.toLowerCase()
12+
return lowerModelID.includes("gemini")
13+
}
14+
1015
export const createSyntheticAssistantMessageWithToolPart = (
1116
baseMessage: WithParts,
1217
content: string,
1318
variant?: string,
1419
): WithParts => {
1520
const userInfo = baseMessage.info as UserMessage
1621
const now = Date.now()
17-
return {
18-
info: {
19-
id: SYNTHETIC_MESSAGE_ID,
20-
sessionID: userInfo.sessionID,
21-
role: "assistant",
22-
agent: userInfo.agent || "code",
23-
parentID: userInfo.id,
24-
modelID: userInfo.model.modelID,
25-
providerID: userInfo.model.providerID,
26-
mode: "default",
27-
path: {
28-
cwd: "/",
29-
root: "/",
30-
},
31-
time: { created: now, completed: now },
32-
cost: 0,
33-
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
34-
...(variant !== undefined && { variant }),
22+
23+
const baseInfo = {
24+
id: SYNTHETIC_MESSAGE_ID,
25+
sessionID: userInfo.sessionID,
26+
role: "assistant" as const,
27+
agent: userInfo.agent || "code",
28+
parentID: userInfo.id,
29+
modelID: userInfo.model.modelID,
30+
providerID: userInfo.model.providerID,
31+
mode: "default",
32+
path: {
33+
cwd: "/",
34+
root: "/",
3535
},
36+
time: { created: now, completed: now },
37+
cost: 0,
38+
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
39+
...(variant !== undefined && { variant }),
40+
}
41+
42+
// For Gemini models, inject as text to avoid thought signature requirements
43+
// Gemini 3+ has strict validation requiring thoughtSignature on functionCall parts
44+
if (isGeminiModel(userInfo.model.modelID)) {
45+
return {
46+
info: baseInfo,
47+
parts: [
48+
{
49+
id: SYNTHETIC_PART_ID,
50+
sessionID: userInfo.sessionID,
51+
messageID: SYNTHETIC_MESSAGE_ID,
52+
type: "text",
53+
text: content,
54+
},
55+
],
56+
}
57+
}
58+
59+
// For other models, use tool part for cleaner context
60+
return {
61+
info: baseInfo,
3662
parts: [
3763
{
3864
id: SYNTHETIC_PART_ID,
@@ -207,23 +233,3 @@ export const isIgnoredUserMessage = (message: WithParts): boolean => {
207233

208234
return true
209235
}
210-
211-
export const hasReasoningInCurrentAssistantTurn = (messages: WithParts[]): boolean => {
212-
for (let i = messages.length - 1; i >= 0; i--) {
213-
const message = messages[i]
214-
if (message.info?.role === "user") {
215-
if (isIgnoredUserMessage(message)) {
216-
continue
217-
}
218-
return false
219-
}
220-
if (message.info?.role === "assistant" && message.parts) {
221-
for (const part of message.parts) {
222-
if (part.type === "reasoning") {
223-
return true
224-
}
225-
}
226-
}
227-
}
228-
return false
229-
}

0 commit comments

Comments
 (0)