
const IMPLEMENT_SYSTEM_PROMPT = "You are the implementation agent. Make the requested change directly using the available tools. After editing, always run the test suite via the runTests tool before declaring the task done. If tests fail, read the failure output and fix it yourself in this same conversation — do not ask for a separate reviewer. When you are finished and tests pass, reply with a short plain-text summary of the diff and explicitly say \"tests passed\".";
/** Hard cap on the self-correction tool loop; the spin guard below usually exits far earlier. */
const MAX_TURNS = 12;
/** Longest tool result fed back to the model per call — keeps history from ballooning turn over turn. */
const MAX_TOOL_RESULT_CHARS = 6e3;
/** Two identical consecutive tool calls means the loop is spinning — tell the model to conclude. */
const SPIN_LIMIT = 2;
/**
* Owns implementation AND validation as one continuous session on one model.
* The same agent runs tests as a tool call and self-corrects in the same turn
* loop. Removes the biggest source of coordination overhead and context loss
* from the pipeline.
*
* Loop hardening:
* - Identical consecutive tool calls (same name + same input) are treated as
*   a spin, not progress — the model is told to conclude instead of burning
*   the remaining turns repeating itself.
* - Tool results are truncated before being fed back, so the conversation
*   stays small enough for the model to keep working instead of losing track
*   and repeating calls.
* - An empty tool result becomes an explicit "no text output" note so the
*   model never repeats a call because it couldn't see a result.
*/
async function runImplementation(task, findings, config, tools, token, toolInvocationToken) {
	const model = await pickModel(config.implement);
	const findingsBlock = findings.map((f) => `### ${f.area}\n${f.summary}`).join("\n\n");
	const messages = [vscode.LanguageModelChatMessage.User(IMPLEMENT_SYSTEM_PROMPT), vscode.LanguageModelChatMessage.User(`Task: ${task.description}\n` + (findings.length > 0 ? `\nResearch findings:\n${findingsBlock}\n` : "") + `\nWorkspace root: ${task.workspaceRoot}`)];
	let testsPassed = false;
	let ranTests = false;
	let turn = 0;
	let lastAssistantText = "";
	let allText = "";
	let lastToolOutput = "";
	let lastCallKey = "";
	let consecutiveIdentical = 0;
	while (turn < MAX_TURNS) {
		turn++;
		const response = await model.sendRequest(messages, { tools }, token);
		let assistantText = "";
		const textParts = [];
		const toolCalls = [];
		for await (const part of response.stream) if (part instanceof vscode.LanguageModelTextPart) {
			assistantText += part.value;
			textParts.push(part);
		} else if (part instanceof vscode.LanguageModelToolCallPart) toolCalls.push(part);
		if (assistantText.trim()) lastAssistantText = assistantText;
		allText += assistantText + "\n";
		if (textParts.length > 0 || toolCalls.length > 0) messages.push(vscode.LanguageModelChatMessage.Assistant([...textParts, ...toolCalls]));
		if (toolCalls.length === 0) {
			if (!ranTests) testsPassed = /tests? (?:pass|passed)/i.test(assistantText);
			return {
				diffSummary: assistantText,
				testsPassed,
				ranTests,
				turns: turn
			};
		}
		for (const call of toolCalls) {
			const callKey = call.name + "::" + safeStringify(call.input);
			if (callKey === lastCallKey) consecutiveIdentical++;
			else {
				lastCallKey = callKey;
				consecutiveIdentical = 0;
			}
			if (consecutiveIdentical >= SPIN_LIMIT) {
				messages.push(vscode.LanguageModelChatMessage.User([new vscode.LanguageModelToolResultPart(call.callId, [new vscode.LanguageModelTextPart(`You just called ${call.name} with identical input. That call and its result are already in your history. Stop looping: conclude with a final plain-text summary instead of another tool call.`)])]));
				continue;
			}
			let result;
			try {
				result = await vscode.lm.invokeTool(call.name, {
					input: call.input,
					toolInvocationToken
				}, token);
			} catch (err) {
				const detail = err instanceof Error ? err.message : String(err);
				messages.push(vscode.LanguageModelChatMessage.User([new vscode.LanguageModelToolResultPart(call.callId, [new vscode.LanguageModelTextPart(`Tool ${call.name} failed: ${detail} — adjust and retry if needed.`)])]));
				continue;
			}
			const text = resultToText(result);
			if (text.trim()) lastToolOutput = text.slice(0, 2e3);
			const parts = truncateToolResult(resultToTextParts(result));
			messages.push(vscode.LanguageModelChatMessage.User([new vscode.LanguageModelToolResultPart(call.callId, parts)]));
			if (call.name === "runTests") {
				ranTests = true;
				testsPassed = /pass/i.test(text) && !/fail/i.test(text);
			}
		}
	}
	return {
		diffSummary: lastAssistantText.trim() || allText.trim() || (lastToolOutput ? `Implementation hit the turn limit. Last tool output:\n\n${lastToolOutput}` : "Max turns reached without an explicit completion signal — check manually."),
		testsPassed,
		ranTests,
		turns: turn
	};
}
/** Feed back at most MAX_TOOL_RESULT_CHARS of text per tool result, so history stays small. */
function truncateToolResult(parts) {
	if (parts.length === 0) return [new vscode.LanguageModelTextPart("Tool returned no text output.")];
	let total = 0;
	const kept = [];
	for (const part of parts) {
		const room = MAX_TOOL_RESULT_CHARS - total;
		if (room <= 0) break;
		kept.push(room >= part.value.length ? part : new vscode.LanguageModelTextPart(part.value.slice(0, room) + "\n… [truncated]"));
		total += part.value.length;
	}
	return kept;
}
