import { ChatResult, ChatMessage } from '@memberjunction/ai'; import { AIModelRunner } from './AIModelRunner.js'; import { AIPromptRunResult } from '@memberjunction/ai-core-plus'; import { IMetadataProvider } from '@memberjunction/core'; import { MJAIModelEntityExtended, MJAIPromptEntityExtended, MJAIPromptRunEntityExtended } from "@memberjunction/ai-core-plus"; import { type IParallelExecutionCoordinator } from './ParallelExecution.js'; import { TemplateMessageRole, AIPromptParams } from '@memberjunction/ai-core-plus'; /** * The composed bound applied to a single model call: the caller's cancellation token (if any) merged * with the prompt's configured `AIPrompt.TimeoutMS` (if any). * * Produced by `AIPromptRunner.createExecutionBound` and consumed by the bounded ChatCompletion race. * `Dispose()` MUST be called when the call settles so the timeout timer and abort listener are * released. */ export interface ExecutionBound { /** Merged abort signal; `undefined` when there is neither a caller token nor a prompt timeout. */ Signal?: AbortSignal; /** The prompt-configured timeout in ms, when one applies. */ TimeoutMS?: number; /** True once the TIMEOUT (not the caller's token) fired — used to build the right error. */ TimedOut: () => boolean; /** Releases the timer and the caller-token listener. Safe to call multiple times. */ Dispose: () => void; } /** * Represents a model-vendor pair candidate for execution */ interface ModelVendorCandidate { model: MJAIModelEntityExtended; vendorId?: string; vendorName?: string; driverClass: string; apiName?: string; supportsEffortLevel?: boolean; effortLevel?: number; isPreferredVendor: boolean; priority: number; source: 'explicit' | 'prompt-model' | 'model-type' | 'power-rank' | 'power-match-fallback'; } /** * Configuration for failover behavior when primary model fails */ interface FailoverConfiguration { strategy: 'SameModelDifferentVendor' | 'NextBestModel' | 'PowerRank' | 'None'; maxAttempts: number; delaySeconds: number; modelStrategy?: 'PreferSameModel' | 'PreferDifferentModel' | 'RequireSameModel'; errorScope?: 'All' | 'NetworkOnly' | 'RateLimitOnly' | 'ServiceErrorOnly'; } /** * Tracks information about a failover attempt */ interface FailoverAttempt { attemptNumber: number; modelId: string; vendorId?: string; error: Error; errorType: string; duration: number; timestamp: Date; } export declare class AIPromptRunner { private _metadata; private _templateEngine; private _executionPlanner; private _parallelCoordinator?; private _jsonValidator; private _modelRunner; private _provider; /** * Fire-and-forget AIPromptRun persistence. Prompt-run logging never blocks the execution path on a * DB round-trip; the shared {@link BaseEntitySaveQueue} sequences saves for the SAME entity (the * initial 'Running' INSERT always completes before the finalize UPDATE, and the finalize mutation * runs INSIDE the post-INSERT task so a slow INSERT can never clobber the finalized row). Failures * stay in this runner's structured log stream via the queue's `onError` hook. */ private _promptRunQueue; /** * Process-wide cache of parsed `OutputExample` JSON, keyed by the raw example string. * A prompt's OutputExample is a static string reused across every run and every validation * retry, so re-parsing it each time is pure waste. Keyed by content (not prompt ID) so two * prompts sharing an identical example share one parsed entry and an edited example never * serves a stale parse. Stores `{ parsed }` on success or `{ error }` on failure so we cache * the failure too rather than re-throwing-and-reparsing bad JSON every attempt. */ private static readonly _outputExampleCache; /** * Marker used in `AIModelSelectionInfo.modelsConsidered[].unavailableReason` for candidates * that were intentionally NOT credential-checked because a higher-priority candidate had * already been selected. See the DECISION note in {@link selectModelWithAPIKeyTracked}. */ private static readonly NOT_EVALUATED_REASON; /** * Optional metadata provider override. Callers should set * `instance.Provider = providerToUse` before invoking run methods * in multi-provider contexts. Falls back to the global default provider when unset. */ get Provider(): IMetadataProvider; set Provider(value: IMetadataProvider | null); constructor(); /** ClassFactory key the parallel coordinator self-registers under (see {@link ParallelCoordinator}). */ private static readonly PARALLEL_COORDINATOR_KEY; /** * Lazily resolves the parallel execution coordinator. * * The coordinator is a SUBCLASS of AIPromptRunner — it inherits {@link executeModel} and the rest * of the battle-tested execution path so there is a single source of truth for credential / driver * / ChatParams / streaming resolution. That subclass relationship means the base cannot statically * `new` it without a hard circular import, so we resolve it through the ClassFactory instead (the * coordinator self-registers via `@RegisterClass(AIPromptRunner, PARALLEL_COORDINATOR_KEY)`). * Created once per runner and reused; the runner's Provider override is propagated. Throws a clear * error if the coordinator class was never loaded/registered — otherwise ClassFactory would silently * fall back to a plain AIPromptRunner that lacks the parallel methods. */ protected get ParallelCoordinator(): IParallelExecutionCoordinator; /** * Access the underlying AIModelRunner for embedding and other non-LLM model calls. * Use this when you need tracked embedding execution with AIPromptRun record creation. */ get ModelRunner(): AIModelRunner; /** * Performs robust validation of an API key * @returns true if the API key is valid (not null, undefined, or empty/whitespace) */ private isValidAPIKey; /** * Internal logging helper that wraps LogStatusEx with verbose control * @param message The message to log * @param verboseOnly Whether this is a verbose-only message * @param params Optional prompt parameters for custom verbose check */ protected logStatus(message: string, verboseOnly?: boolean, params?: AIPromptParams): void; /** * Helper method for enhanced error logging with metadata */ protected logError(error: Error | string, options?: { category?: string; metadata?: Record; prompt?: MJAIPromptEntityExtended; model?: MJAIModelEntityExtended; severity?: 'warning' | 'error' | 'critical'; maxErrorLength?: number; }): void; /** * Checks if a model vendor is configured as an inference provider. * Delegates to the memoized {@link AIEngine.IsInferenceProvider} helper so the * "Inference Provider" vendor-type lookup happens once per engine load rather than on * every candidate in every selection pass. * @param modelVendor The model vendor to check * @returns true if the vendor is an inference provider */ private isInferenceProvider; /** * Resolves credentials for AI model execution using a hierarchical resolution system. * * Resolution priority (highest to lowest): * 1. Per-request override: params.credentialId * 2. Prompt-Model specific: AIPromptModel.CredentialID * 3. Model-Vendor specific: AIModelVendor.CredentialID * 4. Vendor default: AIVendor.CredentialID * 5. Legacy: params.apiKeys[] array * 6. Legacy: AI_VENDOR_API_KEY__ environment variables * * IMPORTANT: When ANY credential ID is found (priorities 1-4), the system uses * the Credentials path and ignores legacy methods (priorities 5-6). * * @param driverClass - The driver class name (e.g., 'OpenAILLM') * @param promptId - The prompt ID for looking up AIPromptModel credentials * @param modelId - The model ID for looking up AIPromptModel and AIModelVendor credentials * @param vendorId - The vendor ID for looking up AIModelVendor and AIVendor credentials * @param params - The prompt execution parameters containing contextUser and optional credentialId * @returns The API key/configuration string to pass to the LLM constructor */ private resolveCredentialForExecution; /** * Attempts to resolve credentials from bindings with priority-based failover. * Tries each binding in priority order until one succeeds. */ private tryCredentialBindingsWithFailover; /** * Attempts to resolve a single credential, returning null on failure for failover support. */ private tryResolveCredential; /** * Resolves a credential by its explicit ID (used for per-request override). * This does not support failover since it's an explicit choice. */ private resolveCredentialById; /** * Finds a default credential matching a specific credential type. */ private findDefaultCredentialByType; /** * Checks if credentials are available for a given model-vendor combination. * This is a pre-flight check used during model selection to determine which * candidates have valid authentication configured. * * Checks the credential hierarchy: * 1. Per-request override: params.credentialId * 2. PromptModel bindings: AICredentialBinding WHERE BindingType='PromptModel' * 3. ModelVendor bindings: AICredentialBinding WHERE BindingType='ModelVendor' * 4. Vendor bindings: AICredentialBinding WHERE BindingType='Vendor' * 5. Type-based default: Credential.IsDefault=1 matching AIVendor.CredentialTypeID * 6. Legacy: params.apiKeys[] array * 7. Legacy: AI_VENDOR_API_KEY__ environment variables * * @param driverClass - The driver class name (e.g., 'OpenAILLM') * @param promptId - The prompt ID for looking up AIPromptModel bindings * @param modelId - The model ID for looking up AIPromptModel and AIModelVendor bindings * @param vendorId - The vendor ID for looking up AIModelVendor and AIVendor bindings * @param params - The prompt execution parameters * @returns true if credentials are available, false otherwise */ private hasCredentialsAvailable; /** * Executes an AI prompt with full support for templates, model selection, and validation. * * @param params Parameters for prompt execution * @returns Promise> The execution result with tracking information * * @example * ```typescript * // Execute with specific result type * interface AnalysisResult { * sentiment: string; * score: number; * keywords: string[]; * } * * const result = await promptRunner.ExecutePrompt({ * prompt: sentimentPrompt, * data: { text: "Customer feedback text" } * }); * * if (result.success && result.result) { * // result.result is typed as AnalysisResult * console.log(`Sentiment: ${result.result.sentiment}, Score: ${result.result.score}`); * } * ``` */ ExecutePrompt(params: AIPromptParams): Promise>; /** * Executes a single prompt (non-parallel) using traditional model selection. * * @param prompt - The AI prompt to execute * @param renderedPromptText - The rendered prompt text * @param params - Original execution parameters * @param startTime - Execution start time * @returns Promise> - The execution result */ private executeSinglePrompt; /** * Executes a prompt using parallel execution with multiple models/tasks. * * @param prompt - The AI prompt to execute * @param renderedPromptText - The rendered prompt text * @param params - Original execution parameters * @param startTime - Execution start time * @returns Promise> - The aggregated execution result */ private executePromptInParallel; /** * Loads a template entity by ID */ private loadTemplate; /** * If a nunjucks render error message contains `[Line N, Column M]`, * return that line of the template source (1-based) with a caret pointer * for the column. Returns null when we can't parse the location, the * template text is missing, or the line is out of range. Surfaces the * actual offending source in child-template failure messages so the * server log + prompt run + agent step record point at the exact spot * instead of just "render failed". */ private extractTemplateSourceExcerpt; /** * Renders child prompt templates in a depth-first manner, composing them into a final template. * * @param childPrompts - Array of child prompts to render templates for * @param params - Original execution parameters for context * @param cancellationToken - Cancellation token for aborting rendering * @returns Promise with rendered templates map */ private renderChildPromptTemplates; /** * Renders a prompt template with child prompt templates merged into the data context. * * @param prompt - The AI prompt to render * @param params - Original execution parameters * @param childTemplates - Map of placeholder names to rendered child prompt templates * @returns Promise - The rendered prompt text with child templates embedded */ private renderPromptWithChildTemplates; /** * Selects the appropriate AI model based on prompt configuration and parameters. * Uses the unified buildModelVendorCandidates method to create an ordered list of candidates, * then selects the first one with an available API key. */ private selectModel; /** * Builds a unified, ordered list of model-vendor candidates based on all selection criteria. * Uses a 3-phase approach to properly handle SelectionStrategy='Specific' with AIPromptModel priorities. * * Phase 1: Handle explicit model ID (highest priority) * Phase 2: Check if SelectionStrategy='Specific' with AIPromptModel entries - use ONLY those with AIPromptModel priorities * Phase 3: Use general selection strategy (fallback) - blended priorities from legacy behavior * * @param prompt - The AI prompt with selection criteria * @param explicitModelId - Explicitly specified model ID (highest priority) * @param configurationId - Configuration ID for filtering * @param preferredVendorId - Preferred vendor ID * @returns Ordered array of model-vendor candidates (highest priority first) */ private buildModelVendorCandidates; /** * PHASE 1: Build candidates for explicitly specified model ID. * Returns candidates for the single model if it's active and compatible. */ private buildCandidatesForExplicitModel; /** * PHASE 2: Build candidates for 'Specific' selection strategy. * Uses AIPromptModel configuration with clean ranking: * 1. Config-matching models first (by priority DESC) * 2. Then universal (null config) models (by priority DESC) */ private buildCandidatesForSpecificStrategy; /** * Appends fallback candidates from the global model pool, sorted by proximity to the * average power rank of the originally configured models. This ensures that when * specific models lack credentials, the fallback uses models of similar capability * rather than defaulting to the most or least powerful available model. * * Fallback candidates are given lower priority than any specific candidate so * configured models are always preferred when their credentials are available. */ private appendPowerMatchedFallbackCandidates; /** * Computes the target power rank from configured AIPromptModel records. * Uses the weighted average (by priority) of the configured models' power ranks, * so higher-priority models have more influence on the target. * Falls back to simple average if priorities are all zero. */ private computeTargetPowerRank; /** * Sorts models by proximity to a target power rank (closest first). * When two models are equidistant, the higher-powered one is preferred. */ private sortByPowerProximity; /** * PHASE 3: Build candidates for general selection strategies ('Default' or 'ByPower'). * Uses configuration-aware fallback hierarchy with legacy blended priority calculation. */ private buildCandidatesForGeneralSelection; /** * Helper: Filter prompt models by configuration matching rules. * Supports configuration inheritance - includes models from the entire inheritance chain. */ private filterPromptModelsByConfiguration; /** * Helper: Sort prompt models for 'Specific' strategy. * Respects configuration inheritance chain - child configs first, then parents, then null-config. * Within each config level, sorts by priority DESC. */ private sortPromptModelsForSpecificStrategy; /** * Helper: Build candidates from sorted AIPromptModel records. * Expands VendorID=null to all vendors for that model. */ private buildCandidatesFromPromptModels; /** * Helper: Create candidate for specific vendor from AIPromptModel. */ private createCandidateForSpecificVendor; /** * Helper: Create candidates for all vendors of a model, sorted by vendor priority. */ private createCandidatesForAllVendors; /** * Helper: Get prompt models for configuration with inheritance chain fallback. * Walks the configuration inheritance chain looking for prompt models. * Returns models from the first config in the chain that has any, or falls back to null-config. */ private getPromptModelsForConfiguration; /** * Helper: Add prompt-specific candidates with blended priorities (legacy behavior). */ private addPromptSpecificCandidates; /** * Helper: Add configuration fallback candidates from the inheritance chain. * Adds models from parent configs (with decreasing priority) and null-config models as final fallback. */ private addConfigurationFallbackCandidates; /** * Helper: Add strategy-based candidates when no prompt models exist. */ private addStrategyBasedCandidates; /** * Helper: Get model pool filtered for strategy. */ private getModelPoolForStrategy; /** * Helper: Sort model pool by selection strategy. */ private sortModelPoolByStrategy; /** * Helper: Sort models by power preference. */ private sortByPowerPreference; /** * Helper: Create candidates for a model with AIModelVendor priorities (legacy behavior). */ private createCandidatesForModel; /** * Creates a properly typed AIModelSelectionInfo instance. * TypeScript requires instantiating the class to get the getValidCandidates() method. */ private createSelectionInfo; /** * Enhanced version of selectModelWithAPIKey that tracks all considered models * for model selection reporting. Uses the hierarchical credential resolution * system to check for available credentials. * * @param candidates - Ordered array of model-vendor candidates * @param promptId - The prompt ID for credential resolution * @param params - Optional prompt parameters for verbose logging and credential override * @returns Object containing selected candidate and all considered models */ private selectModelWithAPIKeyTracked; /** * Builds a descriptive error message when no model could be selected for a prompt. * Includes details about which models were considered and why they were unavailable * so the error message is actionable for end users (e.g., missing API credentials). */ private buildNoModelFoundMessage; /** * Creates an AIPromptRun entity for execution tracking */ /** * Resolves the scalar inference parameters for a run: each value is the per-request override * from `additionalParameters` when supplied, otherwise the prompt's configured default. This * is the single source of truth for parameter precedence so {@link executeModel} (ChatParams) * and {@link createPromptRun} (the persisted record) stay in lockstep. Stop sequences and * assistant prefill are intentionally excluded — their representations differ per target. */ private resolveScalarInferenceParams; /** * Awaits all in-flight prompt-run saves queued by this runner instance. The normal execution path * does NOT call this — prompt-run persistence is intentionally fire-and-forget. Exposed for tests * and for callers that need the AIPromptRun rows durably written before proceeding. */ WaitForPendingPromptRunSaves(): Promise; private createPromptRun; /** * Renders the prompt template with provided data */ private renderPromptTemplate; /** * Executes the AI model with failover support * * @remarks * This method wraps the core executeModel functionality with intelligent failover * capabilities. It will attempt to execute with different models/vendors according * to the configured failover strategy when errors occur. * * The method calls several smaller, focused helper methods: * - buildFailoverCandidates: Creates candidate models based on type restrictions * - createCandidatesFromModels: Converts models to vendor-specific candidates * - updatePromptRunWithFailoverSuccess: Records successful failover metadata * - updatePromptRunWithFailoverFailure: Records failed failover metadata * - createFailoverErrorResult: Creates standardized error response */ protected executeModelWithFailover(model: MJAIModelEntityExtended, renderedPrompt: string, prompt: MJAIPromptEntityExtended, params: AIPromptParams, vendorId: string | null, conversationMessages?: ChatMessage[], templateMessageRole?: TemplateMessageRole, cancellationToken?: AbortSignal, allCandidates?: ModelVendorCandidate[], promptRun?: MJAIPromptRunEntityExtended, vendorDriverClass?: string, vendorApiName?: string, vendorSupportsEffortLevel?: boolean, modelEffortLevel?: number, credentialAvailability?: Map): Promise; /** * Builds failover candidates for a prompt based on available models and type restrictions */ protected buildFailoverCandidates(prompt: MJAIPromptEntityExtended): Promise; /** * Creates model-vendor candidates from a list of models */ protected createCandidatesFromModels(models: MJAIModelEntityExtended[]): ModelVendorCandidate[]; /** * Updates prompt run with successful failover tracking data */ protected updatePromptRunWithFailoverSuccess(promptRun: MJAIPromptRunEntityExtended, failoverAttempts: FailoverAttempt[], currentModel: MJAIModelEntityExtended, currentVendorId: string | null): void; /** * Updates prompt run with failover failure tracking data */ private updatePromptRunWithFailoverFailure; /** * Creates an error result for failed failover attempts */ private createFailoverErrorResult; /** * Executes the AI model with the rendered prompt. * * `protected` so the parallel coordinator subclass reuses this exact code path — credential * resolution, driver selection, ChatParams construction, prefill, media handling, and streaming * all live here ONCE. Do not duplicate this logic elsewhere. */ protected executeModel(model: MJAIModelEntityExtended, renderedPrompt: string, prompt: MJAIPromptEntityExtended, params: AIPromptParams, vendorId: string | null, conversationMessages?: ChatMessage[], templateMessageRole?: TemplateMessageRole, cancellationToken?: AbortSignal, vendorDriverClass?: string, vendorApiName?: string, vendorSupportsEffortLevel?: boolean, modelEffortLevel?: number): Promise; /** * Engine-level default model-call timeout, in milliseconds, applied when the caller supplies no * `AIPromptParams.timeoutMS`. `undefined` (the default) means NO implicit bound — a prompt run * with neither a timeout nor a cancellation token stays unbounded, exactly as before, so this * change is behavior-preserving for existing callers. * * Subclasses (or a host application's runner subclass) can override this to impose a global * safety ceiling on every prompt call. */ protected get DefaultPromptTimeoutMS(): number | undefined; /** * Resolves the per-model-call timeout: the caller's `AIPromptParams.timeoutMS`, else the runner's * {@link DefaultPromptTimeoutMS}. Non-positive / non-numeric values mean "no timeout". * * The bound is applied PER MODEL CALL (not per prompt execution), which mirrors the parallel * path's existing `taskTimeoutMS` semantics: each failover candidate / validation retry gets a * fresh budget rather than sharing one wall-clock window. * * NOTE (issue #3064): there is deliberately NO prompt-entity source here yet — the `AIPrompt` * table has no `TimeoutMS` column today. Once a migration adds one and CodeGen regenerates the * entity, this becomes `prompt.TimeoutMS ?? params.timeoutMS ?? this.DefaultPromptTimeoutMS` * and every bound below starts honoring the per-prompt configuration with no other change. */ protected getEffectiveTimeoutMS(params: AIPromptParams): number | undefined; /** * Composes the caller-supplied cancellation token with the resolved model-call timeout into a * single {@link AbortSignal} that bounds one model call. NEITHER bound is discarded: * * - caller token only → the caller's signal is used directly (behavior unchanged) * - timeout only → an internal controller aborts after the timeout elapses * - both → an internal controller relays the caller's abort AND fires on timeout; * whichever happens first wins * - neither → `Signal` is undefined and the call runs unbounded (legacy behavior) * * Implemented with an AbortController + relay listener rather than `AbortSignal.any()` so it works * on Node 18 (where `AbortSignal.any` does not exist — it landed in Node 20.3). */ protected createExecutionBound(prompt: MJAIPromptEntityExtended, params: AIPromptParams, cancellationToken?: AbortSignal): ExecutionBound; /** * Runs the model call, racing it against the composed execution bound so a hung provider surfaces * as a rejected promise the caller's failover/retry logic can act on. * * A timeout rejects with a typed {@link AIPromptTimeoutError} (classified by ErrorAnalyzer as a * retriable NetworkError); a caller cancellation rejects with the same * `'Chat completion was cancelled'` error the previous implementation produced, so cancellation * semantics are unchanged. * * NOTE: `Promise.race` ignores the losing `ChatCompletion()` promise's eventual result, but the * underlying request IS torn down — the composed signal is on `ChatParams.cancellationToken`, and * all 19 drivers now forward it to their SDK/HTTP layer, so aborting the signal (which is exactly * what makes the model call lose the race on a timeout or cancellation) aborts the socket rather * than leaving it open until the provider closes it. */ private runChatCompletionBounded; /** * Builds the rejection error for an aborted model call — a typed {@link AIPromptTimeoutError} when * the prompt's TimeoutMS fired, otherwise the legacy cancellation error. */ private buildAbortError; /** * Walks every message in chatParams and rewrites media content blocks * (image_url / audio_url / video_url / file_url) into visible text markers * when the selected driver doesn't support that MIME modality. Keeps text * blocks intact. The replacement message tells the agent the file exists * and the model can't view it — much better UX than silent failure (model * receives image_url, ignores it, and the agent asks the user to "upload * the image" the user already uploaded). * * No-op when the driver's GetFileCapabilities() returns non-null AND the * block's MIME matches the supported list. The driver baseclass returns * null by default; only providers that declare vision/file support override * it (currently: OpenAI). For everyone else, every media block falls back * to text — exactly what you want when running a text-only model. */ private stripUnsupportedMediaBlocks; /** * Walks the rendered system-prompt text for the `## Available Artifacts` * section emitted by ArtifactToolManager.ToManifestString(). For each * artifact entry whose MIME hint indicates a media type the driver cannot * process, appends a one-line warning so the model doesn't pretend to view * an artifact it can only see as base64. */ private annotateManifestForUnsupportedMedia; /** * Returns true when the driver explicitly declares support for this MIME * (exact or subtype-wildcard match). Returns false when capabilities are * null (driver supports no files) or the MIME isn't on the supported list. */ private driverSupportsModality; /** * Checks each nativeFileInput against the resolved driver's FileCapabilities * and injects qualifying files as content blocks in the last user message. */ private injectNativeFileInputs; private buildMessageArray; /** * Default fallback instruction text used when no PrefillFallbackText is configured * at any level of the AIModelType → AIModel → AIModelVendor cascade. */ private static readonly DEFAULT_PREFILL_FALLBACK; /** * Regex used to trim only horizontal whitespace (spaces and tabs) from the start and end * of each stop sequence token after comma-splitting. * * We intentionally do NOT use String.trim() here because stop sequences can legitimately * begin or end with newline characters. For example, the sequence "\n```" is designed to * match only a closing code fence (preceded by a newline), distinguishing it from an * opening "```json" fence that does not start with a newline. Using trim() would strip * that leading "\n", turning "\n```" into "```" and causing the stop to fire on the * opening fence instead — producing an empty response for non-native prefill providers. */ private static readonly STOP_SEQUENCE_TRIM_REGEX; /** * Resolves whether the current model/vendor supports native assistant prefill. * * Resolution order: * 1. Start with llm.SupportsPrefill (code-level default from BaseLLM subclass) * 2. AIModel.SupportsPrefill overrides if non-null * 3. AIModelVendor.SupportsPrefill overrides if non-null * * AIModelType.SupportsPrefill is NOT used because it is NOT NULL DEFAULT 0, * so there is no way to distinguish "explicitly disabled" from "never configured." * The code-level default (llm.SupportsPrefill) serves as the type-level default instead. * * - `null` at AIModel/AIModelVendor means "inherit" (defer to code default) * - `true` means "force enable" (overrides code default) * - `false` means "force disable" (overrides code default, even if the driver says yes) */ /** * Resolves the effective response format for a run. A scope-level override — `additionalParameters. * responseFormat`, set by `ApplyScopedPromptConfig` from a `ScopedPromptConfig` row — takes precedence * over the prompt's own `ResponseFormat`. `'Any'` (or absent) from either source resolves to * `undefined`, i.e. stay silent so the provider uses its own default. */ private resolveEffectiveResponseFormat; /** * Decides whether a prompt's StopSequences should be sent to the model. * * StopSequences are commonly paired with AssistantPrefill to fence JSON output: * prefill the assistant turn with "```json" and stop on the closing "\n```". * That pairing is ONLY safe when native prefill is applied — prefill guarantees the * response BEGINS at the fence, so the only "\n```" in the output is the closing one. * * Without native prefill, a model that adds any preamble before the fence * (e.g. Gemini emitting `Here is the JSON requested:\n```json\n{...}`) manufactures a * "\n```" at the OPENING fence, so the stop fires immediately and truncates the response * to just the preamble (an empty/invalid result). To avoid that, when a prompt uses * AssistantPrefill but the resolved model/vendor does NOT support native prefill, we skip * the stop sequences entirely and let the full response through — downstream JSON * extraction strips the fence/preamble. * * Prompts that declare StopSequences WITHOUT AssistantPrefill are treated as independent * (not part of the prefill/fence optimization) and are always applied. */ private shouldApplyStopSequences; private resolveSupportsPrefill; /** * Resolves the prefill fallback instruction text using the cascade: * AIModelType → AIModel → AIModelVendor (most specific non-null wins). * Falls back to DEFAULT_PREFILL_FALLBACK if none are configured. */ private resolvePrefillFallbackText; /** * Applies assistant prefill to ChatParams based on prompt configuration and provider support. * Handles the full prefill resolution logic including fallback to system instructions. */ private applyAssistantPrefill; /** * Executes the model with retry logic for validation failures */ private executeWithValidationRetries; /** * Applies retry delay based on the prompt's retry strategy */ /** * Calculates retry delay for rate limit and other retriable errors. * Uses the prompt's RetryStrategy and can respect suggested delays from provider. */ private calculateRetryDelay; private applyRetryDelay; /** * Filters out all candidates from a vendor when a vendor-level error occurs. * Vendor-level errors affect all models from that vendor: * - Authentication: Invalid API key * - VendorValidationError: API schema/validation requirements */ private filterVendorCandidates; /** * Handles rate limit errors by retrying the same model/vendor with backoff. * Returns true if the caller should continue (retry), false if should proceed to failover. */ private handleRateLimitRetry; /** * Processes a failover error (either from catch block or from failed ChatResult). * Handles vendor filtering, rate limit retries, fatal error detection, and failover logic. * * @returns Decision object indicating whether to retry same model, continue to next candidate, or stop */ private processFailoverError; /** * Transitions to the next failover candidate. * Returns the next candidate info or null if no candidates are available. */ private transitionToNextCandidate; /** * Provides a human-readable description of the validation decision */ private getValidationDecisionDescription; /** * Generates a JSON schema from an example object for validation */ private generateSchemaFromExample; /** * Detects if a key/value pair looks like a placeholder or example value */ private isLikelyPlaceholder; /** * Detects if an entire object looks like it contains only placeholder/example data */ private isObjectLikelyPlaceholder; /** * Generates schema for a specific value type */ private generateSchemaForValue; /** * Enhanced parsing and validation with detailed error reporting and JSON repair capabilities. * * @param modelResult - The raw result from the AI model * @param prompt - The AI prompt entity containing configuration * @param skipValidation - Whether to skip validation * @param cleanValidationSyntax - Whether to clean validation syntax from results * @param params - Optional prompt parameters containing additional configuration like attemptJSONRepair * @returns Parsed result with optional validation results and errors */ private parseAndValidateResultEnhanced; /** * Parses a string output value. * * @param rawOutput - The raw output from the model * @returns The parsed string value */ private parseStringOutput; /** * Parses a number output value with validation. * * @param rawOutput - The raw output from the model * @param skipValidation - Whether to skip validation * @param validationErrors - Array to collect validation errors * @returns The parsed number value * @throws Error if the value cannot be parsed as a number and validation is enabled */ private parseNumberOutput; /** * Parses a boolean output value with flexible input handling. * * @param rawOutput - The raw output from the model * @param skipValidation - Whether to skip validation * @param validationErrors - Array to collect validation errors * @returns The parsed boolean value * @throws Error if the value cannot be parsed as a boolean and validation is enabled */ private parseBooleanOutput; /** * Parses a date output value with validation. * * @param rawOutput - The raw output from the model * @param skipValidation - Whether to skip validation * @param validationErrors - Array to collect validation errors * @returns The parsed Date value * @throws Error if the value cannot be parsed as a date and validation is enabled */ private parseDateOutput; /** * Parses an object (JSON) output value with optional repair capabilities. * * @param rawOutput - The raw output from the model * @param prompt - The AI prompt entity containing configuration * @param skipValidation - Whether to skip validation * @param cleanValidationSyntax - Whether to clean validation syntax * @param validationErrors - Array to collect validation errors * @param params - Optional prompt parameters containing attemptJSONRepair flag * @returns The parsed object value * @throws Error if the value cannot be parsed as JSON and validation is enabled */ private parseObjectOutput; /** * Attempts to repair malformed JSON using a two-step process. * * @param rawOutput - The malformed JSON string * @param originalError - The original parsing error * @param params - Prompt parameters containing contextUser * @returns The repaired and parsed JSON object * @throws Error if JSON repair fails */ private attemptJSONRepair; /** * Returns the parsed form of a prompt's `OutputExample` JSON, memoized by content. * Parsing happens at most once per distinct example string for the life of the process; * parse failures are cached too (so malformed examples aren't re-parsed every attempt). */ private getParsedOutputExample; /** * Validates parsed result against JSON schema derived from OutputExample */ private validateAgainstSchema; /** * Updates the AIPromptRun entity with execution results */ private updatePromptRun; /** * Populates a prompt-run's finalized fields (result, tokens, cost, timing, rollups) from the model * result. Runs INSIDE the post-INSERT save task — see {@link updatePromptRun}. Errors here are * logged (non-fatal): the AIPromptRun is observability, not part of the prompt's success contract. */ private applyFinalizedPromptRunFields; /** * Estimates the number of tokens in a rendered prompt and conversation messages. * This is a rough estimation based on character count and typical token ratios. * * @param renderedPrompt - The rendered prompt text * @param conversationMessages - Optional conversation messages * @returns Estimated token count */ /** * Retrieves failover configuration from the prompt entity. * * @param prompt - The AI prompt entity containing failover settings * @returns FailoverConfiguration object with strategy and settings * * @remarks * This method extracts failover configuration from the prompt entity and provides * default values when configuration is not specified. Override this method to * implement custom failover configuration logic. */ protected getFailoverConfiguration(prompt: MJAIPromptEntityExtended): FailoverConfiguration; /** * Determines whether a failover attempt should be made based on the error and configuration. * * @param error - The error that occurred during execution * @param config - The failover configuration * @param attemptNumber - The current attempt number (1-based) * @returns True if failover should be attempted, false otherwise * * @remarks * This method uses the ErrorAnalyzer to classify errors and determine if they are * eligible for failover based on the configured error scope. Override this method * to implement custom failover decision logic. */ protected shouldAttemptFailover(error: Error, config: FailoverConfiguration, attemptNumber: number): boolean; /** * Checks if an error type matches the configured error scope * * @param errorType - The error type from ErrorAnalyzer * @param scope - The configured error scope * @returns True if the error matches the scope */ private errorMatchesScope; /** * Calculates the delay before the next failover attempt. * * @param attemptNumber - The current attempt number (1-based) * @param baseDelaySeconds - The base delay in seconds from configuration * @param previousError - The error from the previous attempt * @returns Delay in milliseconds before the next attempt * * @remarks * Implements exponential backoff with jitter by default. The delay increases * exponentially with each attempt and includes random jitter to prevent * thundering herd problems. Override this method to implement custom delay logic. */ protected calculateFailoverDelay(attemptNumber: number, baseDelaySeconds: number): number; /** * Selects candidate models for failover based on the strategy and current failure. * * @param currentModel - The model that just failed * @param currentVendorId - The vendor ID that just failed * @param strategy - The failover strategy to use * @param modelStrategy - The model selection preference * @param allCandidates - All available model-vendor candidates * @param attemptHistory - History of previous failover attempts * @returns Array of candidates sorted by priority (highest first) * * @remarks * This method implements different strategies for selecting failover candidates: * - SameModelDifferentVendor: Try the same model with different vendors * - NextBestModel: Try different models in order of preference * - PowerRank: Use the global power ranking of models * * Override this method to implement custom candidate selection logic. */ protected selectFailoverCandidates(currentModel: MJAIModelEntityExtended, currentVendorId: string | undefined, strategy: FailoverConfiguration['strategy'], modelStrategy: FailoverConfiguration['modelStrategy'], allCandidates: ModelVendorCandidate[], attemptHistory: FailoverAttempt[]): ModelVendorCandidate[]; /** * Logs a failover attempt for tracking and debugging. * * @param promptId - The ID of the prompt being executed * @param attempt - The failover attempt details * @param willRetry - Whether another attempt will be made * * @remarks * This method logs detailed information about each failover attempt to help with * debugging and monitoring. Override this method to implement custom logging or * integrate with external monitoring systems. */ protected logFailoverAttempt(promptId: string, attempt: FailoverAttempt, willRetry: boolean): void; } export {}; //# sourceMappingURL=AIPromptRunner.d.ts.map