mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-23 00:43:14 +00:00
Merge c6bfa43e6b into e921f9d21e
This commit is contained in:
commit
761a2069ef
5 changed files with 90 additions and 8 deletions
|
|
@ -112,6 +112,39 @@ describe("convertToOpenAiMessages", () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("should strip null values from tool call arguments to prevent Jinja template errors", () => {
|
||||
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "followup-123",
|
||||
name: "ask_followup_question",
|
||||
input: {
|
||||
question: "Pick one",
|
||||
follow_up: [
|
||||
{ text: "Option A", mode: null },
|
||||
{ text: "Option B", mode: "code" },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const openAiMessages = convertToOpenAiMessages(anthropicMessages)
|
||||
const assistantMessage = openAiMessages[0] as OpenAI.Chat.ChatCompletionAssistantMessageParam
|
||||
const toolCall = assistantMessage.tool_calls![0] as any
|
||||
const args = JSON.parse(toolCall.function.arguments)
|
||||
|
||||
// null mode should be stripped (becomes undefined, omitted from JSON)
|
||||
expect(args.follow_up[0]).toEqual({ text: "Option A" })
|
||||
expect(args.follow_up[0].mode).toBeUndefined()
|
||||
// non-null mode should be preserved
|
||||
expect(args.follow_up[1]).toEqual({ text: "Option B", mode: "code" })
|
||||
})
|
||||
|
||||
it("should handle user messages with tool results (no normalization without normalizeToolCallId)", () => {
|
||||
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -467,8 +467,13 @@ export function convertToOpenAiMessages(
|
|||
type: "function",
|
||||
function: {
|
||||
name: toolMessage.name,
|
||||
// json string
|
||||
arguments: JSON.stringify(toolMessage.input),
|
||||
// Serialize as JSON, stripping null values to prevent Jinja template
|
||||
// errors on local models (e.g. "Cannot convert value of type
|
||||
// Optional<Any> to Jinja Value"). Null in tool args typically means
|
||||
// "not provided" and should be omitted instead.
|
||||
arguments: JSON.stringify(toolMessage.input, (_key, value) =>
|
||||
value === null ? undefined : value,
|
||||
),
|
||||
},
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Parameters:
|
|||
- follow_up: (required) A list of 2-4 suggested answers. Suggestions must be complete, actionable answers without placeholders. Optionally include mode to switch modes (code/architect/etc.)
|
||||
|
||||
Example: Asking for file path
|
||||
{ "question": "What is the path to the frontend-config.json file?", "follow_up": [{ "text": "./src/frontend-config.json", "mode": null }, { "text": "./config/frontend-config.json", "mode": null }, { "text": "./frontend-config.json", "mode": null }] }
|
||||
{ "question": "What is the path to the frontend-config.json file?", "follow_up": [{ "text": "./src/frontend-config.json" }, { "text": "./config/frontend-config.json" }, { "text": "./frontend-config.json" }] }
|
||||
|
||||
Example: Asking with mode switch
|
||||
{ "question": "Would you like me to implement this feature?", "follow_up": [{ "text": "Yes, implement it now", "mode": "code" }, { "text": "No, just plan it out", "mode": "architect" }] }`
|
||||
|
|
@ -25,7 +25,12 @@ export default {
|
|||
function: {
|
||||
name: "ask_followup_question",
|
||||
description: ASK_FOLLOWUP_QUESTION_DESCRIPTION,
|
||||
strict: true,
|
||||
// Note: strict mode is intentionally disabled for this tool.
|
||||
// With strict: true, OpenAI requires ALL properties to be in the 'required' array,
|
||||
// which forces the LLM to always provide explicit values (even null) for optional params.
|
||||
// Local models using Jinja chat templates cannot handle null values in tool call arguments,
|
||||
// causing "Cannot convert value of type Optional<Any> to Jinja Value" errors.
|
||||
// By disabling strict mode, the LLM can omit the optional `mode` parameter entirely.
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
|
|
@ -44,11 +49,11 @@ export default {
|
|||
description: FOLLOW_UP_TEXT_DESCRIPTION,
|
||||
},
|
||||
mode: {
|
||||
type: ["string", "null"],
|
||||
type: "string",
|
||||
description: FOLLOW_UP_MODE_DESCRIPTION,
|
||||
},
|
||||
},
|
||||
required: ["text", "mode"],
|
||||
required: ["text"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
minItems: 1,
|
||||
|
|
|
|||
|
|
@ -39,10 +39,18 @@ export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> {
|
|||
return
|
||||
}
|
||||
|
||||
// Transform follow_up suggestions to the format expected by task.ask
|
||||
// Transform follow_up suggestions to the format expected by task.ask.
|
||||
// Omit `mode` when it's null/undefined to avoid Jinja template errors
|
||||
// on local models that can't handle null values in tool call arguments.
|
||||
const follow_up_json = {
|
||||
question,
|
||||
suggest: follow_up.map((s) => ({ answer: s.text, mode: s.mode })),
|
||||
suggest: follow_up.map((s) => {
|
||||
const suggestion: { answer: string; mode?: string } = { answer: s.text }
|
||||
if (s.mode != null) {
|
||||
suggestion.mode = s.mode
|
||||
}
|
||||
return suggestion
|
||||
}),
|
||||
}
|
||||
|
||||
task.consecutiveMistakeCount = 0
|
||||
|
|
|
|||
|
|
@ -82,6 +82,37 @@ describe("askFollowupQuestionTool", () => {
|
|||
)
|
||||
})
|
||||
|
||||
it("should strip null mode values from suggestions to prevent Jinja template errors", async () => {
|
||||
const block: ToolUse = {
|
||||
type: "tool_use",
|
||||
name: "ask_followup_question",
|
||||
params: {
|
||||
question: "What would you like to do?",
|
||||
},
|
||||
nativeArgs: {
|
||||
question: "What would you like to do?",
|
||||
follow_up: [
|
||||
{ text: "Option A", mode: null as any },
|
||||
{ text: "Option B", mode: "code" },
|
||||
],
|
||||
},
|
||||
partial: false,
|
||||
}
|
||||
|
||||
await askFollowupQuestionTool.handle(mockCline, block as ToolUse<"ask_followup_question">, {
|
||||
askApproval: vi.fn(),
|
||||
handleError: vi.fn(),
|
||||
pushToolResult: mockPushToolResult,
|
||||
})
|
||||
|
||||
// mode: null should be stripped, mode: "code" should be preserved
|
||||
expect(mockCline.ask).toHaveBeenCalledWith(
|
||||
"followup",
|
||||
expect.stringContaining('"suggest":[{"answer":"Option A"},{"answer":"Option B","mode":"code"}]'),
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle mixed suggestions with and without mode attributes", async () => {
|
||||
const block: ToolUse = {
|
||||
type: "tool_use",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue