import type OpenAI from "openai"; /** * Heuristic issues detected in a `messages` array before it's sent to the * provider. These are defensive checks — upstream code is supposed to * produce valid conversations, but historical bugs (and the many providers * reachable via OpenRouter) have surfaced invalid shapes that different * providers reject in different ways. Logging and healing here keeps a * single run from failing outright when the shape is still salvageable. */ export type MessageIssueKind = "empty_messages" | "trailing_assistant_with_content" | "trailing_assistant_empty" | "orphan_tool_result" | "dangling_tool_calls_unanswered" | "empty_assistant_mid" | "empty_user_content"; export interface MessageIssue { kind: MessageIssueKind; index: number; detail: string; healed: boolean; } export interface ValidateAndHealResult { messages: OpenAI.ChatCompletionMessageParam[]; issues: MessageIssue[]; } /** * Run sanity checks against the outgoing `messages` array and attempt to * heal any detected issues. Returns the (possibly modified) messages along * with a list of issues found — the caller should log these as errors. * * Heuristics: * - Empty array → flagged, not healable (provider will reject). * - Trailing assistant with content → append a synthetic user turn so * providers that don't support assistant prefill (Azure-hosted Claude * via OpenRouter) accept the request. * - Trailing assistant with no content / no tool_calls → drop it, then * re-check the new tail. * - Assistant with `tool_calls` not followed by matching tool results → * strip the unanswered tool_calls. If the message has no other content * either, drop it. Without this, the provider rejects the next turn. * - Tool message whose `tool_call_id` doesn't match any preceding * assistant `tool_calls` → drop the orphan. * - Mid-conversation assistant with no content and no tool_calls → drop. * - User message with entirely empty content → drop. */ export declare function validateAndHealMessages(input: OpenAI.ChatCompletionMessageParam[], /** * Tool-call ids that are * *legitimately* open — a suspended `ask` awaiting a human answer. This is the * UNIVERSAL strip site (the host runs it before every model call), * and the never-send-unpaired invariant means a correct array never carries a * suspended call here unpaired. So this set is a **fail-loud assertion**, NOT a * silent-keep: if an allowed id reaches the validator unpaired, resume failed to * thread its answer — surface the bug rather than strip it (which would mask it) * or send it (which the provider would 400 on). Bounded to ids in the set: * callers that pass nothing (every legacy/non-HITL path) get exactly today's * non-throwing heal-and-log behaviour. */ allowedOpenToolCallIds?: ReadonlySet): ValidateAndHealResult;