295 lines · 11.4 KB
import { createTwoFilesPatch } from "diff";
import type { MergeConflict } from "../git/merge";
import type { ConflictHunk } from "../git/merge3";
import { acceptGenerated, assembleResolution, baseCandidates, blockContext, dedupe, redactSecrets } from "./candidates";
import type { Candidate, SecretVault } from "./candidates";
import { buildClefRequest, decideFile, MAX_HUNKS_PER_FILE } from "./decide";
import type { ClefRequest, ClefResponse, FileDecision, HunkInput, Thresholds } from "./decide";

export type ChatMessage = { role: "system" | "user"; content: string };
export type TokenUsage = { inputTokens: number; outputTokens: number };
export type GenerationInput = { messages: ChatMessage[]; hunk: ConflictHunk };

/** The two model calls the pipeline makes; production uses AI Gateway, tests and e2e use the mock. */
export interface MergeFixProvider {
  generate(input: GenerationInput): Promise<{ text: string; usage: TokenUsage }>;
  decide(request: ClefRequest): Promise<ClefResponse>;
}

export type FixLimits = {
  maxFiles: number;
  maxHunkBytes: number;
  maxModelCalls: number;
  aiCandidates: number;
  callTimeoutMs: number;
  /** Wall-clock budget for all model calls, kept below the Workflow plan step's timeout. */
  totalTimeoutMs: number;
};

export const DEFAULT_LIMITS: FixLimits = {
  maxFiles: 8,
  maxHunkBytes: 4096,
  maxModelCalls: 40,
  aiCandidates: 2,
  callTimeoutMs: 30_000,
  totalTimeoutMs: 7 * 60 * 1000,
};

export type HunkResult = { candidates: Candidate[]; selected: string | null; probabilities: Record<string, number> };
export type FileResult =
  | {