
const RESEARCH_SYSTEM_PROMPT = "You are a read-only research agent. You can read files and search the workspace, but you cannot and must not suggest edits. Given a focus area, report only: relevant files, relevant symbols/functions, and any constraints or gotchas. Keep it to a short summary, not a file dump.";
/** Cap on parallel research areas produced by decomposition. */
const MAX_RESEARCH_AREAS = 3;
const DECOMPOSE_PROMPT = `You are a task decomposition agent. Split the task below into at most ${MAX_RESEARCH_AREAS} independent, non-overlapping research focus areas. Reply with a numbered list only, one area per line, nothing else.`;
/**
* Runs one research call per area, concurrently, round-robining across the
* configured research models so parallel calls don't all hit one rate limit.
* Findings are condensed summaries, never raw transcripts, so the
* implementation stage's context stays small. Decomposition and a whole-task
* scan start together so the fan-out actually engages — no sequential prefix.
*/
async function runResearch(task, config, tools, token, progress) {
	if (config.research.length === 0) throw new Error("AgentRoleConfig.research must list at least one model.");
	const readOnlyTools = selectReadOnlyTools(tools);
	const options = readOnlyTools.length > 0 ? { tools: readOnlyTools } : {};
	const decompose = deriveResearchAreas(task, config, token);
	const overviewCall = researchOneArea(task, task.description, 0, config, options, token, progress);
	const narrowCalls = (await decompose).filter((area) => area !== task.description).map((area, i) => researchOneArea(task, area, i + 1, config, options, token, progress));
	const [overview, ...narrow] = await Promise.all([overviewCall, ...narrowCalls]);
	return [overview, ...narrow];
}
async function researchOneArea(task, area, index, config, options, token, progress) {
	const modelRef = config.research[index % config.research.length];
	progress?.report({ message: `Researching (${modelRef.id ?? modelRef.family}): ${area.slice(0, 60)}` });
	const model = await pickModel(modelRef);
	const messages = [vscode.LanguageModelChatMessage.User(RESEARCH_SYSTEM_PROMPT), vscode.LanguageModelChatMessage.User(`Task: ${task.description}\nFocus area: ${area}\nWorkspace root: ${task.workspaceRoot}`)];
	const response = await model.sendRequest(messages, options, token);
	let text = "";
	for await (const chunk of response.text) text += chunk;
	return {
		area,
		summary: text,
		relevantFiles: extractFilePaths(text)
	};
}
/**
* Splits the task into independent focus areas for parallel research via one
* cheap call on the first research model. Parsing is intentionally loose:
* any failure or empty result falls back to a single whole-task area.
*/
async function deriveResearchAreas(task, config, token) {
	const model = await pickModel(config.research[0]);
	const messages = [vscode.LanguageModelChatMessage.User(DECOMPOSE_PROMPT), vscode.LanguageModelChatMessage.User(task.description)];
	try {
		const response = await model.sendRequest(messages, {}, token);
		let text = "";
		for await (const chunk of response.text) text += chunk;
		const areas = text.split(/\r?\n/).map((line) => line.replace(/^\s*(?:[-*]|\d+[.)]?)\s*/, "").trim()).filter((line) => line.length > 0).slice(0, MAX_RESEARCH_AREAS);
		return areas.length > 0 ? areas : [task.description];
	} catch {
		return [task.description];
	}
}
/** Loose extraction of file-ish paths from research output. */
function extractFilePaths(text) {
	const matches = text.match(/[\w./-]+\.\w+/g) ?? [];
	return [...new Set(matches)];
}
