import type { Locale } from './i18n/types.js'; import type { LlmProvider, TokenCounter } from './types.js'; /** * Rewrites the rules cannot do, proposed one phrase at a time. * * The existing LLM pass (`refineWithLlm`) hands the model the whole prompt and * takes the whole answer back, which makes it all-or-nothing: when the result * fails a safety check the author gets *nothing*, and when it passes they get a * wholesale rewrite they have to read end to end to trust. Both halves of that * are worse than they need to be. * * This asks a different question. "Which exact phrases in this prompt say * something in more words than they need to?" — and the answer is a list of * `before → after` pairs, each one small enough to judge on sight: * * You should always make sure to → Always * It is important to note that → (removed) * * Nothing is applied unless the caller asks. Eight surviving suggestions out of * ten is a useful result; a wholesale rewrite that fails one check is not. * * ## What makes a suggestion survive * * The model is a source of proposals, not of truth, so every one is checked * against the prompt before it is shown: * * 1. **`before` must appear in the prompt, byte for byte.** A model that * paraphrases what it is quoting has invented a suggestion about text that * does not exist, and applying it would do nothing or, worse, match * something else. * 2. **It must not touch protected content.** Code, URLs, placeholders and XML * tags are copied verbatim by every other part of this project, and a * suggestion that edits one is refused rather than negotiated. * 3. **`after` must not introduce protected content.** A replacement that adds * a `{{placeholder}}` or a URL is proposing new semantics, not shorter * phrasing. * 4. **It must actually save tokens.** A rephrasing that costs the same is a * change of style, and this tool is not a style guide. * 5. **Overlapping suggestions are dropped, later ones first.** Applying two * edits that share characters produces text neither of them described. */ export interface RewriteSuggestion { /** The exact text in the prompt, as it appears there. */ before: string; /** What to put in its place. Empty means "delete this". */ after: string; /** Character offsets in the prompt, one per surviving occurrence. */ offsets: number[]; /** Tokens saved if every occurrence is applied. */ tokensSaved: number; } export interface SuggestResult { suggestions: RewriteSuggestion[]; /** Suggestions the model returned that did not survive, with the reason. */ rejected: Array<{ before: string; after: string; reason: RejectedReason; }>; provider: string; model: string; } export type RejectedReason = /** `before` is not in the prompt. The model paraphrased what it quoted. */ 'not-found' /** It would edit a code block, URL, placeholder or tag. */ | 'touches-protected' /** `after` introduces protected content that was not there. */ | 'introduces-protected' /** No shorter than what it replaces. */ | 'no-saving' /** It shares characters with a suggestion already accepted. */ | 'overlaps'; export declare const SUGGEST_SYSTEM_PROMPT = "You find phrases in a prompt that say something in more words than they need.\n\nReturn ONLY a JSON array. Each element is {\"before\": \"...\", \"after\": \"...\"}.\n\nRules:\n- \"before\" MUST be copied character for character from the prompt. Do not paraphrase it, do not fix its punctuation, do not change its capitalisation. If you cannot copy it exactly, leave it out.\n- \"after\" says the same thing in fewer words. Use \"\" to delete the phrase entirely.\n- Preserve meaning exactly. Never change what the prompt asks for, its constraints, its output format or its success criteria.\n- Never touch code, URLs, template placeholders ({{x}}, ${x}, {x}) or XML/HTML tags \u2014 do not include them in \"before\" at all.\n- Keep the original language of the prompt.\n- Prefer a few high-value rewrites to many trivial ones. Return [] if there is nothing worth changing.\n\nNo explanation, no code fences, no commentary. The array alone."; export interface SuggestOptions { tokenCounter?: TokenCounter; locale?: Locale; /** Cap on how many survive, highest saving first. Defaults to 20. */ max?: number; } /** * Asks the provider for rewrites and returns the ones that survive checking. * * A provider that returns something unparseable yields an empty result rather * than throwing: the deterministic rules have already run, and a malformed * answer from an optional pass should cost the caller nothing. */ export declare function suggestRewrites(prompt: string, provider: LlmProvider, options?: SuggestOptions): Promise; /** * Applies suggestions to the prompt. * * Right to left, so an earlier edit cannot move the offsets of a later one — * the bug that makes every naive implementation of this corrupt long prompts, * and one that shows up only when two suggestions are far enough apart that a * short test never notices. */ export declare function applyRewrites(prompt: string, suggestions: readonly RewriteSuggestion[]): string; /** A reason code turned into a sentence, for a report. */ export declare function rejectionText(reason: RejectedReason, locale: Locale): string; //# sourceMappingURL=suggest.d.ts.map