Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions apps/web-roo-code/src/app/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,7 @@ export default function PricingPage() {
<li>To pay for Cloud Agents running time (${PRICE_CREDITS}/hour)</li>
<li>
To pay for AI model inference costs (
<a
href="/provider"
target="_blank"
rel="noopener noreferrer"
className="underline">
<a href="/provider" target="_blank" rel="noopener noreferrer" className="underline">
varies by model
</a>
)
Expand Down
32 changes: 16 additions & 16 deletions locales/zh-TW/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions packages/types/src/followup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { z } from "zod"
export interface FollowUpData {
/** The question being asked by the LLM */
question?: string
/** Array of questions being asked by the LLM */
questions?: FollowUpQuestion[]
/** Array of suggested answers that the user can select */
suggest?: Array<SuggestionItem>
}
Expand All @@ -22,6 +24,12 @@ export interface SuggestionItem {
mode?: string
}

/**
* Type definition for a follow-up question
* Can be a simple string or an object with text and options
*/
export type FollowUpQuestion = string | { text: string; options?: string[] }

/**
* Zod schema for SuggestionItem
*/
Expand All @@ -30,11 +38,23 @@ export const suggestionItemSchema = z.object({
mode: z.string().optional(),
})

/**
* Zod schema for FollowUpQuestion
*/
export const followUpQuestionSchema = z.union([
z.string(),
z.object({
text: z.string(),
options: z.array(z.string()).optional(),
}),
])

/**
* Zod schema for FollowUpData
*/
export const followUpDataSchema = z.object({
question: z.string().optional(),
questions: z.array(followUpQuestionSchema).optional(),
suggest: z.array(suggestionItemSchema).optional(),
})

Expand Down
7 changes: 7 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,12 @@ export const globalSettingsSchema = z.object({
lastTaskExportPath: z.string().optional(),
lastImageSavePath: z.string().optional(),

/**
* Whether to show multiple questions one by one or all at once.
* @default false (all at once)
*/
showQuestionsOneByOne: z.boolean().optional(),

/**
* Path to worktree to auto-open after switching workspaces.
* Used by the worktree feature to open the Roo Code sidebar in a new window.
Expand Down Expand Up @@ -392,6 +398,7 @@ export const EVALS_SETTINGS: RooCodeSettings = {
mode: "code", // "architect",

customModes: [],
showQuestionsOneByOne: false,
}

export const EVALS_TIMEOUT = 5 * 60 * 1_000
1 change: 1 addition & 0 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ export type ExtensionState = Pick<
| "enterBehavior"
| "includeCurrentTime"
| "includeCurrentCost"
| "showQuestionsOneByOne"
| "maxGitStatusFiles"
| "requestDelaySeconds"
| "showWorktreesInHomeScreen"
Expand Down
8 changes: 4 additions & 4 deletions src/core/assistant-message/NativeToolCallParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,9 +475,9 @@ export class NativeToolCallParser {
break

case "ask_followup_question":
if (partialArgs.question !== undefined || partialArgs.follow_up !== undefined) {
if (partialArgs.questions !== undefined || partialArgs.follow_up !== undefined) {
nativeArgs = {
question: partialArgs.question,
questions: Array.isArray(partialArgs.questions) ? partialArgs.questions : undefined,
follow_up: Array.isArray(partialArgs.follow_up) ? partialArgs.follow_up : undefined,
}
}
Expand Down Expand Up @@ -818,9 +818,9 @@ export class NativeToolCallParser {
break

case "ask_followup_question":
if (args.question !== undefined && args.follow_up !== undefined) {
if (args.questions !== undefined && args.follow_up !== undefined) {
nativeArgs = {
question: args.question,
questions: Array.isArray(args.questions) ? args.questions : undefined,
follow_up: args.follow_up,
} as NativeArgsFor<TName>
}
Expand Down
46 changes: 38 additions & 8 deletions src/core/prompts/tools/native-tools/ask_followup_question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@ import type OpenAI from "openai"
const ASK_FOLLOWUP_QUESTION_DESCRIPTION = `Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.

Parameters:
- question: (required) A clear, specific question addressing the information needed
- questions: (required) A list of questions to ask. Each question can be a simple string or an object with "text" and "options" for multiple choice.
- 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 }] }
{ "questions": ["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 }] }

Example: Asking with multiple questions and choices
{
"questions": [
{ "text": "Which framework are you using?", "options": ["React", "Vue", "Svelte", "Other"] },
"What is your project name?",
{ "text": "Include telemetry?", "options": ["Yes", "No"] }
],
"follow_up": [{ "text": "I've answered the questions", "mode": null }]
}

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" }] }`
{ "questions": ["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" }] }`

const QUESTION_PARAMETER_DESCRIPTION = `Clear, specific question that captures the missing information you need`
const QUESTIONS_PARAMETER_DESCRIPTION = `List of questions to ask. Each question can be a string or an object with "text" and "options" for multiple choice.`

const FOLLOW_UP_PARAMETER_DESCRIPTION = `Required list of 2-4 suggested responses; each suggestion must be a complete, actionable answer and may include a mode switch`

Expand All @@ -29,9 +39,29 @@ export default {
parameters: {
type: "object",
properties: {
question: {
type: "string",
description: QUESTION_PARAMETER_DESCRIPTION,
questions: {
type: "array",
items: {
anyOf: [
{
type: "string",
},
{
type: "object",
properties: {
text: { type: "string" },
options: {
type: "array",
items: { type: "string" },
},
},
required: ["text", "options"],
additionalProperties: false,
},
],
},
description: QUESTIONS_PARAMETER_DESCRIPTION,
minItems: 1,
},
follow_up: {
type: "array",
Expand All @@ -55,7 +85,7 @@ export default {
maxItems: 4,
},
},
required: ["question", "follow_up"],
required: ["questions", "follow_up"],
additionalProperties: false,
},
},
Expand Down
31 changes: 21 additions & 10 deletions src/core/tools/AskFollowupQuestionTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,40 @@ interface Suggestion {
mode?: string
}

interface Question {
text: string
options?: string[]
}

interface AskFollowupQuestionParams {
question: string
questions: Array<string | Question>
follow_up: Suggestion[]
}

export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> {
readonly name = "ask_followup_question" as const

async execute(params: AskFollowupQuestionParams, task: Task, callbacks: ToolCallbacks): Promise<void> {
const { question, follow_up } = params
const { questions, follow_up } = params
const { handleError, pushToolResult } = callbacks

try {
if (!question) {
if (!questions || questions.length === 0) {
task.consecutiveMistakeCount++
task.recordToolError("ask_followup_question")
task.didToolFailInCurrentTurn = true
pushToolResult(await task.sayAndCreateMissingParamError("ask_followup_question", "question"))
pushToolResult(await task.sayAndCreateMissingParamError("ask_followup_question", "questions"))
return
}

// Transform follow_up suggestions to the format expected by task.ask
const follow_up_json = {
question,
const followup_json = {
questions,
suggest: follow_up.map((s) => ({ answer: s.text, mode: s.mode })),
}

task.consecutiveMistakeCount = 0
const { text, images } = await task.ask("followup", JSON.stringify(follow_up_json), false)
const { text, images } = await task.ask("followup", JSON.stringify(followup_json), false)
await task.say("user_feedback", text ?? "", images)
pushToolResult(formatResponse.toolResult(`<user_message>\n${text}\n</user_message>`, images))
} catch (error) {
Expand All @@ -46,11 +51,17 @@ export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> {
}

override async handlePartial(task: Task, block: ToolUse<"ask_followup_question">): Promise<void> {
const question: string | undefined = block.nativeArgs?.question ?? block.params.question
// Get question from params or nativeArgs
const questions = block.nativeArgs?.questions ?? []
const firstQuestion = questions[0]
const multiQuestionText = typeof firstQuestion === "string" ? firstQuestion : firstQuestion?.text
const singleQuestionText = (block.nativeArgs as any)?.question ?? block.params.question

const questionText = multiQuestionText ?? singleQuestionText

// During partial streaming, only show the question to avoid displaying raw JSON
// The full JSON with suggestions will be sent when the tool call is complete (!block.partial)
await task.ask("followup", question ?? "", block.partial).catch(() => {})
// The full JSON with suggestions will be sent when the tool call is complete
await task.ask("followup", questionText ?? "", block.partial).catch(() => {})
}
}

Expand Down
Loading
Loading