import type { AgentEvent, AgentTool } from '@earendil-works/pi-agent-core'; import type { AssistantMessage, Model } from '@earendil-works/pi-ai'; import { streamSimple } from '@earendil-works/pi-ai/compat'; import { type ContextFile, type McpServerUsage as AgentContextMcpServerUsage, type MarketplaceRef, type McpServerConfig, type Skill, type SkillUsage } from '@weareikko/agent-context'; import type { Config } from './config.js'; import { type ReviewIntent } from './intent.js'; import type { Logger } from './logger.js'; import { type ConnectMcpServersOptions, type McpConnection, type McpServerStatus } from './mcp.js'; import type { PriorThread } from './prior-threads.js'; import { type SkippedDiffFile } from './skipped-retrieval.js'; import { type ReviewAngle } from './triage.js'; import type { GitLabReviewSeverity, SizeSkippedFile, ThinkingLevel } from './types.js'; import { type McpResultEntry } from './verify.js'; export interface UsageBreakdown { input: number; output: number; cacheRead: number; cacheWrite: number; total: number; } /** * MR size signals derived from the reviewed diff: files dropped for exceeding * the char budget, and an optional "this MR is too big — decompose it" hint when * the reviewed changed-line count crosses the configured threshold. */ export interface ReviewSizeNotice { sizeSkippedFiles: SizeSkippedFile[]; decomposeHint?: { lines: number; threshold: number; }; /** * Diff coverage when files were dropped for the char budget: how many changed * lines were actually reviewed vs the total. Present only when something was * size-skipped, so a partial review reports its coverage instead of reading as * a confident full review. */ coverage?: { reviewedLines: number; totalLines: number; }; /** * True when the size-dropped diffs were staged on disk for on-demand retrieval * (retrieval mode). The summary callout then frames them as staged-and-readable * rather than lost, and defers the read/not-read account to the reviewer. */ retrieved?: boolean; } /** Token and cost usage attributed to a single pool member. */ export interface ModelUsage { model: string; tokens: UsageBreakdown; cost: UsageBreakdown; } export interface ReviewUsage { model: string; /** Reasoning effort the main agent ran at, surfaced alongside the model in the footer. */ thinkingLevel: ThinkingLevel; tokens: UsageBreakdown; cost: UsageBreakdown; /** * Per-pool-member usage breakdown for heterogeneous `full`-depth runs. Each * entry attributes the tokens/cost of the agents that ran on that pool member. * The top-level `model`/`tokens`/`cost` stay the main model and the totals (the * sum of all entries). Present only when more than one distinct model ran; the * single-model path leaves it undefined so output is byte-identical to before. */ byModel?: ModelUsage[]; /** Skills the reviewer loaded, with the origin each was loaded from. */ skills: SkillUsage[]; /** MCP servers connected for this review, with their exposed tools and call counts. */ mcp: McpServerUsage[]; /** * Size signals for surfacing in the MR summary. `sizeSkippedFiles` lists files * dropped for the char budget; `decomposeHint` is set when the reviewed diff is * past the configured line threshold. Both feed the prominent summary callout. */ sizeNotice: ReviewSizeNotice; } export type { SkillUsage }; /** One MCP server's outcome for a review, as reported in {@link ReviewUsage}. */ export interface McpServerUsage extends AgentContextMcpServerUsage { /** Names of the tools exposed to the reviewer (read-only only — see `isReadOnlyMcpTool`). */ exposedTools: string[]; } export interface AgentLike { subscribe(listener: (event: AgentEvent) => void | Promise): () => void; prompt(input: string): Promise; } export interface CreateAgentParams { systemPrompt: string; model: Model; tools: AgentTool[]; thinkingLevel: ThinkingLevel; getApiKey: () => Promise; } export type CreateAgent = (params: CreateAgentParams) => AgentLike; export interface RunReviewOptions { cwd?: string; diff: string; /** * Commit messages for all non-merge commits in the MR (merge-base…MR head), * in chronological order. When provided, a `` section is prepended * to the user prompt so the reviewer understands the intent behind each change. * Produced by `getMergeCommitLog` in `src/git.ts`. */ commitLog?: string; createAgent?: CreateAgent; timeoutMs?: number; logger?: Logger; /** * Prior developer replies to bot-posted review threads on the MR. * When provided, a `` section is appended to the user * prompt after `` so the reviewer can avoid re-raising already-acknowledged * concerns and can provide contextual follow-up. * Produced by `extractPriorThreads` in `src/prior-threads.ts`. */ priorThreads?: PriorThread[]; /** * Author-declared intent for the MR (title + description). When present and * non-empty, an `` block is prepended to the user prompt so the * reviewer can check the diff against the stated purpose and flag code/intent * mismatches. A missing or empty description degrades gracefully (no block). * Sourced from the GitLab MR via `getMergeRequest` in `src/gitlab.ts`. */ intent?: ReviewIntent; /** * Called with the agent after it is created, before the first prompt. * Use this to attach telemetry (e.g. `otelBridge.createAgentTelemetry(runId)`). * The returned function, if any, is called after the review completes. */ attachTelemetry?: (agent: AgentLike) => (() => void) | undefined; /** * Called with the latest accumulated usage snapshot as the review runs — * always at least once, and crucially even when the review then throws * (timeout, agent error, quota exhaustion). Callers use it to record the real * partial cost/tokens of a run that failed mid-flight; without it the per-turn * `gen_ai.client.cost` telemetry counts money already spent while the * review-level total silently drops it, so the two diverge on failed runs. */ onUsage?: (usage: ReviewUsage) => void; /** * `commits` input mode only: the last-reviewed commit (a ref/sha). When set, * the commit-exploration prompt tells the agent to review only the commits * after it (incremental review); when absent, the whole history is in scope. */ sinceRef?: string; /** * Supported library extension point: override how resolved MCP server configs * are connected. Defaults to `connectMcpServers`. A caller (or a test) can * substitute its own connection — e.g. an in-memory transport with a canned * tool list — without touching how MCP server configs are resolved from the * repo/marketplace/file sources. See `docs/mcp.md`. */ connectMcp?: (configs: readonly McpServerConfig[], options: ConnectMcpServersOptions) => Promise; /** * The enclosing review's diagnostic run id (from `createDiagnosticRunId()` in * `cli.ts`). Forwarded to `connectMcpServers` so MCP connect/list-tools/call * are traced on the same run as the rest of the review's diagnostics/OTel * data; omitted means no MCP diagnostics events are published. */ runId?: string; } interface ReviewContext { conventions: ContextFile[]; reviewRules: ContextFile[]; skills: Skill[]; } /** * Stream function for the reviewer agent. * * pi-ai >=0.82 requires an explicit stream function (earlier versions built one * internally from model + getApiKey); `streamSimple` is the drop-in. Critically, * 0.83 also moved base-URL placeholder substitution from a direct `process.env` * read to an explicit `env` on the stream options. Providers whose base URL * contains `{VAR}` placeholders keep them literal — and fail every request — * unless `env` is threaded through. Passing `env` is harmless for providers * whose base URL has none. * * `stream` is injectable so the env threading can be unit-tested without a live call. */ export declare function createReviewStreamFn(stream?: typeof streamSimple): typeof streamSimple; export interface LoadReviewContextOptions { /** Re-clone `git:` / `git+ssh:` skills, bypassing the on-disk clone cache. */ refreshGitSkills?: boolean; /** Registered Claude-plugin marketplaces referenced by name in skill specs. */ marketplaces?: readonly MarketplaceRef[]; } export declare function loadReviewContext(cwd: string, skillNames?: string[], warn?: (msg: string) => void, options?: LoadReviewContextOptions): Promise; export interface FilteredDiff { diff: string; /** Files dropped because they matched a noise pattern (lockfiles, generated, build output). */ noiseSkippedFiles: string[]; /** Files dropped because including them would exceed the char budget, with their diff size. */ sizeSkippedFiles: SizeSkippedFile[]; /** Number of added/removed lines in the reviewed (included) diff. */ reviewedChangedLines: number; /** Number of added/removed lines in files dropped for the char budget. */ skippedChangedLines: number; /** * The raw diff text of each size-dropped file, keyed by path. Lets the caller * write them to disk so an agentic reviewer can read the dropped diffs on * demand instead of losing them entirely (opt-in retrieval). */ sizeSkippedSections: Array<{ path: string; section: string; }>; /** * Every non-noise file section (path + raw diff), regardless of the char * budget. Disk input mode stages all of these so the reviewer reads them on * demand instead of receiving any diff inline. */ allSections: Array<{ path: string; section: string; changedLines: number; }>; } export declare function filterDiff(raw: string, maxChars?: number): FilteredDiff; export declare function buildJSONSystemPrompt(context: ReviewContext, minSeverity: GitLabReviewSeverity): string; /** * Build a Find system prompt specialised to one review angle. Used by `full` * depth, which runs one finder per angle. The base prompt (severity/confidence * tiers, output format, skills, conventions) is unchanged; an `` * section narrows the finder to its lane so the finders cover breadth rather * than all re-finding the same top issue. */ export declare function buildAngleSystemPrompt(context: ReviewContext, minSeverity: GitLabReviewSeverity, angle: ReviewAngle): string; export declare function buildUserPrompt(diff: string, skippedFiles?: string[], commitLog?: string, priorThreads?: PriorThread[], intent?: ReviewIntent, coverage?: { reviewedLines: number; totalLines: number; }, retrievableSkipped?: SkippedDiffFile[], omitInlineDiff?: boolean, commitExploration?: { sinceRef?: string; }, mcpServers?: McpServerStatus[]): string; export declare function extractLastAssistantText(messages: AssistantMessage[]): string; /** * One usable model in the review pool. `id` is the original `provider/modelId` * string — the stable identity used for fixed angle→model mapping, cross-family * verifier selection, and per-model usage keying. `getApiKey` resolves THIS * member's provider key, so a key for provider X is never sent to provider Y. */ export interface PoolMember { id: string; model: Model; getApiKey: () => Promise; } /** * Build the effective model pool from `config.model` plus `config.modelPool`, * resolving each member's own provider key and dropping (with a warning) any * member whose key is missing/empty. Order and duplicates from `config.modelPool` * are preserved by first occurrence. When the pool is empty or every member is * unusable, falls back to a single-member pool of `config.model` (already * validated to have a key) — reproducing single-model behaviour exactly. * * `config.apiKey` (which honours `--api-key`) is used for any member whose id * equals `config.model`, so an explicit override key still applies; other members * resolve their key via the provider-aware resolver. */ export declare function buildEffectivePool(config: Config, logger: Logger): PoolMember[]; /** Blended per-token cost (input + output) used to compare model tiers. */ export declare function blendedCost(model: Model): number; /** * Resolve `config.verifyModel` into a dedicated Verify-stage pool member. Returns * null when unset (Verify falls back to the pool's cross-family pick) or when the * model/key can't be resolved (warns and falls back, so a bad value never aborts * the run). Also warns when the verify model is a *cheaper* tier than the finder: * the Verify stage is a precision-judgment task, and a weak verifier drops real * findings (recall loss) — so cheap-find/strong-verify is the intended shape. */ export declare function resolveVerifyMember(config: Config, primary: PoolMember, logger: Logger): PoolMember | null; /** * The "consider decomposing this MR" hint: present when a threshold is set * (`> 0`) and the change exceeds it. `changedLines` is the count the caller * deems reviewed — the budget-fitted subset for inline mode, the whole change * for disk/commits mode. */ export declare function resolveDecomposeHint(threshold: number, reviewedLines: number): { lines: number; threshold: number; } | undefined; export declare function runReview(config: Config, options: RunReviewOptions): Promise; /** Per-agent capture handles. Tool-call ids are only unique within one agent. */ export interface McpResultScope { start(toolName: string, args: unknown, toolCallId: string): void; end(toolName: string, result: unknown, isError: boolean, toolCallId: string): void; } export interface McpResultCollector { /** Results captured so far, oldest first. Read by the Verify stage. */ entries: McpResultEntry[]; /** * Open a capture scope for one agent. Concurrent agents share the collector * but each gets its own pending-call namespace, so two agents that happen to * mint the same tool-call id cannot overwrite or delete each other's pending * argument summary. */ scope(): McpResultScope; } /** * Collect what the MCP tools returned during Find so the Verify stage can read * the same external evidence without calling any tool itself. Only `mcp__*` * tools are captured (repo/file reads are already reproducible from the diff), * and failed calls are skipped — an error string is not evidence. * * Identical calls are captured once: at full depth the angle finders share one * collector and each resolves the same identifiers from the same intent block, * so the same lookup arrives up to one time per angle. Keeping every copy would * spend the Verify render budget on duplicates and evict distinct lookups. */ export declare function createMcpResultCollector(): McpResultCollector; //# sourceMappingURL=gitlab-review.d.ts.map