@memberjunction/ai-prompts 5.41.0 → 5.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,61 +3,8 @@ import { AIModelRunner } from './AIModelRunner.js';
3
3
  import { AIPromptRunResult } from '@memberjunction/ai-core-plus';
4
4
  import { IMetadataProvider } from '@memberjunction/core';
5
5
  import { MJAIModelEntityExtended, MJAIPromptEntityExtended, MJAIPromptRunEntityExtended } from "@memberjunction/ai-core-plus";
6
+ import { type IParallelExecutionCoordinator } from './ParallelExecution.js';
6
7
  import { TemplateMessageRole, AIPromptParams } from '@memberjunction/ai-core-plus';
7
- /**
8
- * Advanced AI Prompt execution engine with comprehensive template support, hierarchical template composition,
9
- * sophisticated model selection, parallelization, output validation, and execution tracking.
10
- *
11
- * ## Core Features
12
- * - **Template-based prompt generation** using MJ Templates system
13
- * - **Hierarchical template composition** with depth-first rendering and parallel template processing
14
- * - **Advanced model selection** strategies (Default, Specific, ByPower)
15
- * - **Parallel execution** with multiple models and execution groups
16
- * - **Structured output validation** and type conversion with retry logic
17
- * - **Comprehensive execution tracking** with agent run linking
18
- * - **Configuration-driven behavior** with caching and performance optimization
19
- * - **Real-time progress updates** and streaming response support
20
- *
21
- * ## Hierarchical Template Composition
22
- * When `childPrompts` array is provided in {@link AIPromptParams}:
23
- * 1. Renders child prompt templates in depth-first manner (children before parents)
24
- * 2. At each level, renders sibling templates in parallel for optimal performance
25
- * 3. Recursively handles grandchild templates (unlimited nesting depth)
26
- * 4. Substitutes rendered child templates into corresponding placeholders in parent template
27
- * 5. Executes the final composed prompt as a single operation
28
- *
29
- * This enables complex prompt composition patterns where templates can be built from reusable
30
- * sub-templates, creating sophisticated prompts through hierarchical template inheritance.
31
- *
32
- * ## Agent Integration
33
- * - Links executions to agent runs via `agentRunId` parameter
34
- * - Supports agent decision-making workflows with structured JSON responses
35
- * - Enables hierarchical template patterns in AI agents
36
- *
37
- * @example Basic Usage
38
- * ```typescript
39
- * const runner = new AIPromptRunner();
40
- * const params = new AIPromptParams();
41
- * params.prompt = aiPrompt;
42
- * params.data = { key: 'value' };
43
- * const result = await runner.ExecutePrompt(params);
44
- * ```
45
- *
46
- * @example Hierarchical Template Composition
47
- * ```typescript
48
- * const params = new AIPromptParams();
49
- * params.prompt = parentPrompt;
50
- * params.childPrompts = [
51
- * new ChildPromptParam(analysisPrompt, 'analysis'),
52
- * new ChildPromptParam(summaryPrompt, 'summary'),
53
- * new ChildPromptParam(complexChild, 'complex') // This can have its own child templates
54
- * ];
55
- * params.data = { userInput: 'complex data to process' };
56
- * const result = await runner.ExecutePrompt(params);
57
- * // Child templates render first, then parent template uses {{ analysis }}, {{ summary }}, {{ complex }}
58
- * // Final composed prompt is executed once
59
- * ```
60
- */
61
8
  /**
62
9
  * Represents a model-vendor pair candidate for execution
63
10
  */
@@ -99,7 +46,7 @@ export declare class AIPromptRunner {
99
46
  private _metadata;
100
47
  private _templateEngine;
101
48
  private _executionPlanner;
102
- private _parallelCoordinator;
49
+ private _parallelCoordinator?;
103
50
  private _jsonValidator;
104
51
  private _modelRunner;
105
52
  private _provider;
@@ -136,6 +83,21 @@ export declare class AIPromptRunner {
136
83
  get Provider(): IMetadataProvider;
137
84
  set Provider(value: IMetadataProvider | null);
138
85
  constructor();
86
+ /** ClassFactory key the parallel coordinator self-registers under (see {@link ParallelCoordinator}). */
87
+ private static readonly PARALLEL_COORDINATOR_KEY;
88
+ /**
89
+ * Lazily resolves the parallel execution coordinator.
90
+ *
91
+ * The coordinator is a SUBCLASS of AIPromptRunner — it inherits {@link executeModel} and the rest
92
+ * of the battle-tested execution path so there is a single source of truth for credential / driver
93
+ * / ChatParams / streaming resolution. That subclass relationship means the base cannot statically
94
+ * `new` it without a hard circular import, so we resolve it through the ClassFactory instead (the
95
+ * coordinator self-registers via `@RegisterClass(AIPromptRunner, PARALLEL_COORDINATOR_KEY)`).
96
+ * Created once per runner and reused; the runner's Provider override is propagated. Throws a clear
97
+ * error if the coordinator class was never loaded/registered — otherwise ClassFactory would silently
98
+ * fall back to a plain AIPromptRunner that lacks the parallel methods.
99
+ */
100
+ protected get ParallelCoordinator(): IParallelExecutionCoordinator;
139
101
  /**
140
102
  * Access the underlying AIModelRunner for embedding and other non-LLM model calls.
141
103
  * Use this when you need tracked embedding execution with AIPromptRun record creation.
@@ -438,14 +400,6 @@ export declare class AIPromptRunner {
438
400
  * TypeScript requires instantiating the class to get the getValidCandidates() method.
439
401
  */
440
402
  private createSelectionInfo;
441
- /**
442
- * Converts model selection info into ModelVendorCandidate array for retry logic.
443
- * Extracts only the valid candidates (those with available API keys) from the selection info.
444
- *
445
- * @param selectionInfo - Model selection information containing considered models
446
- * @returns Array of valid model-vendor candidates sorted by priority
447
- */
448
- private buildCandidatesFromSelectionInfo;
449
403
  /**
450
404
  * Enhanced version of selectModelWithAPIKey that tracks all considered models
451
405
  * for model selection reporting. Uses the hierarchical credential resolution
@@ -514,7 +468,7 @@ export declare class AIPromptRunner {
514
468
  * - updatePromptRunWithFailoverFailure: Records failed failover metadata
515
469
  * - createFailoverErrorResult: Creates standardized error response
516
470
  */
517
- 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): Promise<ChatResult>;
471
+ 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<string, boolean>): Promise<ChatResult>;
518
472
  /**
519
473
  * Builds failover candidates for a prompt based on available models and type restrictions
520
474
  */
@@ -536,9 +490,13 @@ export declare class AIPromptRunner {
536
490
  */
537
491
  private createFailoverErrorResult;
538
492
  /**
539
- * Executes the AI model with the rendered prompt
493
+ * Executes the AI model with the rendered prompt.
494
+ *
495
+ * `protected` so the parallel coordinator subclass reuses this exact code path — credential
496
+ * resolution, driver selection, ChatParams construction, prefill, media handling, and streaming
497
+ * all live here ONCE. Do not duplicate this logic elsewhere.
540
498
  */
541
- private executeModel;
499
+ 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<ChatResult>;
542
500
  /**
543
501
  * Walks every message in chatParams and rewrites media content blocks
544
502
  * (image_url / audio_url / video_url / file_url) into visible text markers
@@ -1 +1 @@
1
- {"version":3,"file":"AIPromptRunner.d.ts","sourceRoot":"","sources":["../src/AIPromptRunner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AIPromptRunner.d.ts","sourceRoot":"","sources":["../src/AIPromptRunner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,UAAU,EAAmB,WAAW,EAAqE,MAAM,oBAAoB,CAAC;AACtK,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAqB,iBAAiB,EAAwB,MAAM,8BAA8B,CAAC;AAC1G,OAAO,EAAmF,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG1I,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAK9H,OAAO,EAAyB,KAAK,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAIhG,OAAO,EACH,mBAAmB,EAEnB,cAAc,EACjB,MAAM,8BAA8B,CAAC;AAuGtC;;GAEG;AACH,UAAU,oBAAoB;IAC5B,KAAK,EAAE,uBAAuB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,GAAG,cAAc,GAAG,YAAY,GAAG,YAAY,GAAG,sBAAsB,CAAC;CAC5F;AAGD;;GAEG;AACH,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,0BAA0B,GAAG,eAAe,GAAG,WAAW,GAAG,MAAM,CAAC;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,iBAAiB,GAAG,sBAAsB,GAAG,kBAAkB,CAAC;IAChF,UAAU,CAAC,EAAE,KAAK,GAAG,aAAa,GAAG,eAAe,GAAG,kBAAkB,CAAC;CAC3E;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;CACjB;AAqBD,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,iBAAiB,CAAmB;IAC5C,OAAO,CAAC,oBAAoB,CAAC,CAAgC;IAC7D,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,SAAS,CAAkC;IAEnD;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB,CAA4D;IACxF,2GAA2G;IAC3G,OAAO,CAAC,sBAAsB,CAA0B;IAExD;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAA2D;IAEtG;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAgI;IAE5K;;;;OAIG;IACH,IAAW,QAAQ,IAAI,iBAAiB,CAEvC;IACD,IAAW,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,EAElD;;IAUD,wGAAwG;IACxG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAkC;IAElF;;;;;;;;;;;OAWG;IACH,SAAS,KAAK,mBAAmB,IAAI,6BAA6B,CAiBjE;IAED;;;OAGG;IACH,IAAW,WAAW,IAAI,aAAa,CAEtC;IAED;;;OAGG;IACH,OAAO,CAAC,aAAa;IAUrB;;;;;OAKG;IACH,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,GAAE,OAAe,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI;IAYjG;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE;QAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC/B,MAAM,CAAC,EAAE,wBAAwB,CAAC;QAClC,KAAK,CAAC,EAAE,uBAAuB,CAAC;QAChC,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;QAC5C,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,IAAI;IAmCR;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAI3B;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,6BAA6B;IAqE3C;;;OAGG;YACW,iCAAiC;IAiC/C;;OAEG;YACW,oBAAoB;IA4DlC;;;OAGG;YACW,qBAAqB;IA0BnC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAUnC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,uBAAuB;IAuD/B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACU,aAAa,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAqM9F;;;;;;;;OAQG;YACW,mBAAmB;IAgIjC;;;;;;;;OAQG;YACW,uBAAuB;IA6QrC;;OAEG;YACW,YAAY;IAkB1B;;;;;;;;OAQG;IACH,OAAO,CAAC,4BAA4B;IAoBpC;;;;;;;OAOG;YACW,0BAA0B;IA8LxC;;;;;;;OAOG;YACW,8BAA8B;IAmE5C;;;;OAIG;YACW,WAAW;IAoLzB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,0BAA0B;IAsBlC;;;OAGG;IACH,OAAO,CAAC,+BAA+B;IAoBvC;;;;;OAKG;IACH,OAAO,CAAC,kCAAkC;IAkD1C;;;;;;;;OAQG;IACH,OAAO,CAAC,oCAAoC;IA6C5C;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAmB9B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAY5B;;;OAGG;IACH,OAAO,CAAC,kCAAkC;IAiC1C;;;OAGG;IACH,OAAO,CAAC,iCAAiC;IAoBzC;;;;OAIG;IACH,OAAO,CAAC,mCAAmC;IA2B3C;;;OAGG;IACH,OAAO,CAAC,+BAA+B;IA4BvC;;OAEG;IACH,OAAO,CAAC,gCAAgC;IA6BxC;;OAEG;IACH,OAAO,CAAC,6BAA6B;IA2CrC;;;;OAIG;IACH,OAAO,CAAC,+BAA+B;IAiCvC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAoBnC;;;OAGG;IACH,OAAO,CAAC,kCAAkC;IAiE1C;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAgBlC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAe/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAqB7B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAwEhC;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;;;;;;;;OASG;YACW,4BAA4B;IAiI1C;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IAwBhC;;OAEG;IACH;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IAoBpC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;;;OAIG;IACU,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC;YAI5C,eAAe;IAmN7B;;OAEG;YACW,oBAAoB;IAsDlC;;;;;;;;;;;;;;OAcG;cACa,wBAAwB,CACtC,KAAK,EAAE,uBAAuB,EAC9B,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,wBAAwB,EAChC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,oBAAoB,CAAC,EAAE,WAAW,EAAE,EACpC,mBAAmB,GAAE,mBAA8B,EACnD,iBAAiB,CAAC,EAAE,WAAW,EAC/B,aAAa,CAAC,EAAE,oBAAoB,EAAE,EACtC,SAAS,CAAC,EAAE,2BAA2B,EACvC,iBAAiB,CAAC,EAAE,MAAM,EAC1B,aAAa,CAAC,EAAE,MAAM,EACtB,yBAAyB,CAAC,EAAE,OAAO,EACnC,gBAAgB,CAAC,EAAE,MAAM,EACzB,sBAAsB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5C,OAAO,CAAC,UAAU,CAAC;IA4LtB;;OAEG;cACa,uBAAuB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IA2B1G;;OAEG;IACH,SAAS,CAAC,0BAA0B,CAAC,MAAM,EAAE,uBAAuB,EAAE,GAAG,oBAAoB,EAAE;IAuC/F;;OAEG;IACH,SAAS,CAAC,kCAAkC,CAC1C,SAAS,EAAE,2BAA2B,EACtC,gBAAgB,EAAE,eAAe,EAAE,EACnC,YAAY,EAAE,uBAAuB,EACrC,eAAe,EAAE,MAAM,GAAG,IAAI,GAC7B,IAAI;IAoBP;;OAEG;IACH,OAAO,CAAC,kCAAkC;IAe1C;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAiCjC;;;;;;OAMG;cACa,YAAY,CAC1B,KAAK,EAAE,uBAAuB,EAC9B,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,wBAAwB,EAChC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,oBAAoB,CAAC,EAAE,WAAW,EAAE,EACpC,mBAAmB,GAAE,mBAA8B,EACnD,iBAAiB,CAAC,EAAE,WAAW,EAC/B,iBAAiB,CAAC,EAAE,MAAM,EAC1B,aAAa,CAAC,EAAE,MAAM,EACtB,yBAAyB,CAAC,EAAE,OAAO,EACnC,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,UAAU,CAAC;IAsMtB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,2BAA2B;IA0DnC;;;;;;OAMG;IACH,OAAO,CAAC,mCAAmC;IA0C3C;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAc9B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA+C9B,OAAO,CAAC,iBAAiB;IAqCzB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAoJ;IAEpM;;;;;;;;;;OAUG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAsB;IAEtE;;;;;;;;;;;;;;;OAeG;IACH;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,sBAAsB;IA0B9B;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IA0BlC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IA4C7B;;OAEG;YACW,4BAA4B;IA8L1C;;OAEG;IACH;;;OAGG;IACH,OAAO,CAAC,mBAAmB;YA8Bb,eAAe;IAO7B;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IA6C9B;;;OAGG;YACW,oBAAoB;IAmDlC;;;;;OAKG;YACW,oBAAoB;IAsFlC;;;OAGG;YACW,yBAAyB;IAoDvC;;OAEG;IACH,OAAO,CAAC,gCAAgC;IAuBxC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA2CjC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAkB3B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA+B9B;;;;;;;;;OASG;YACW,8BAA8B;IAmI5C;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IAiBzB;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAkB1B;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAcvB;;;;;;;;;;;OAWG;YACW,iBAAiB;IAsC/B;;;;;;;;OAQG;YACW,iBAAiB;IA8G/B;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAe9B;;OAEG;YACW,qBAAqB;IAkGnC;;OAEG;YACW,eAAe;IAoP7B;;;;;;;OAOG;IAIH;;;;;;;;;;OAUG;IACH,SAAS,CAAC,wBAAwB,CAAC,MAAM,EAAE,wBAAwB,GAAG,qBAAqB;IAU3F;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,qBAAqB,CAC7B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,qBAAqB,EAC7B,aAAa,EAAE,MAAM,GACpB,OAAO;IA6BV;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,sBAAsB,CAC9B,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,GACvB,MAAM;IAaT;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,wBAAwB,CAChC,YAAY,EAAE,uBAAuB,EACrC,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,QAAQ,EAAE,qBAAqB,CAAC,UAAU,CAAC,EAC3C,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,EACrD,aAAa,EAAE,oBAAoB,EAAE,EACrC,cAAc,EAAE,eAAe,EAAE,GAChC,oBAAoB,EAAE;IAgIzB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,kBAAkB,CAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,OAAO,GACjB,IAAI;CA6BR"}
@@ -6,7 +6,6 @@ import { CleanJSON, MJGlobal, JSONValidator, ValidationResult, ValidationErrorIn
6
6
  import { CredentialEngine } from '@memberjunction/credentials';
7
7
  import { TemplateEngineServer } from '@memberjunction/templates';
8
8
  import { ExecutionPlanner } from './ExecutionPlanner.js';
9
- import { ParallelExecutionCoordinator } from './ParallelExecutionCoordinator.js';
10
9
  import { AIEngine } from '@memberjunction/aiengine';
11
10
  import { AIEngineBase } from '@memberjunction/ai-engine-base';
12
11
  import { SystemPlaceholderManager } from '@memberjunction/ai-core-plus';
@@ -67,10 +66,36 @@ export class AIPromptRunner {
67
66
  this._metadata = this._provider ?? new Metadata();
68
67
  this._templateEngine = TemplateEngineServer.Instance;
69
68
  this._executionPlanner = new ExecutionPlanner();
70
- this._parallelCoordinator = new ParallelExecutionCoordinator();
71
69
  this._jsonValidator = new JSONValidator();
72
70
  this._modelRunner = new AIModelRunner();
73
71
  }
72
+ /** ClassFactory key the parallel coordinator self-registers under (see {@link ParallelCoordinator}). */
73
+ static { this.PARALLEL_COORDINATOR_KEY = 'ParallelExecutionCoordinator'; }
74
+ /**
75
+ * Lazily resolves the parallel execution coordinator.
76
+ *
77
+ * The coordinator is a SUBCLASS of AIPromptRunner — it inherits {@link executeModel} and the rest
78
+ * of the battle-tested execution path so there is a single source of truth for credential / driver
79
+ * / ChatParams / streaming resolution. That subclass relationship means the base cannot statically
80
+ * `new` it without a hard circular import, so we resolve it through the ClassFactory instead (the
81
+ * coordinator self-registers via `@RegisterClass(AIPromptRunner, PARALLEL_COORDINATOR_KEY)`).
82
+ * Created once per runner and reused; the runner's Provider override is propagated. Throws a clear
83
+ * error if the coordinator class was never loaded/registered — otherwise ClassFactory would silently
84
+ * fall back to a plain AIPromptRunner that lacks the parallel methods.
85
+ */
86
+ get ParallelCoordinator() {
87
+ if (!this._parallelCoordinator) {
88
+ const instance = MJGlobal.Instance.ClassFactory.CreateInstance(AIPromptRunner, AIPromptRunner.PARALLEL_COORDINATOR_KEY);
89
+ if (!instance || typeof instance.executeTasksInParallel !== 'function') {
90
+ throw new Error(`ParallelExecutionCoordinator is not registered with the ClassFactory. Ensure ` +
91
+ `'@memberjunction/ai-prompts' is fully loaded (it is exported from the package index and ` +
92
+ `picked up by the class-registration manifest).`);
93
+ }
94
+ instance.Provider = this.Provider;
95
+ this._parallelCoordinator = instance;
96
+ }
97
+ return this._parallelCoordinator;
98
+ }
74
99
  /**
75
100
  * Access the underlying AIModelRunner for embedding and other non-LLM model calls.
76
101
  * Use this when you need tracked embedding execution with AIPromptRun record creation.
@@ -457,25 +482,20 @@ export class AIPromptRunner {
457
482
  let renderedPromptText = '';
458
483
  // For hierarchical prompts, we need to create the parent prompt run first to get its ID
459
484
  let parentPromptRun;
460
- let selectedModel;
461
485
  let childTemplateRenderingResult;
462
- let modelSelectionInfo;
486
+ let selection;
463
487
  // Handle different prompt execution modes
464
488
  if (params.childPrompts && params.childPrompts.length > 0) {
465
489
  // Hierarchical template composition mode - render child templates first, then compose
466
- //this.logStatus(`🌳 Composing prompt with ${params.childPrompts.length} child templates in hierarchical mode`, true, params);
467
490
  // Determine which prompt to use for model selection
468
491
  let modelSelectionPrompt = prompt;
469
492
  if (params.modelSelectionPrompt) {
470
493
  modelSelectionPrompt = params.modelSelectionPrompt;
471
- //this.logStatus(`🎯 Using prompt "${modelSelectionPrompt.Name}" for model selection instead of parent prompt`, true, params);
472
494
  }
473
- // Select model using the appropriate prompt
474
- const modelResult = await this.selectModel(modelSelectionPrompt, params.override?.modelId, params.contextUser, params.configurationId, params.override?.vendorId, params);
475
- selectedModel = modelResult.model;
476
- modelSelectionInfo = modelResult.selectionInfo;
477
- if (!selectedModel) {
478
- throw new Error(this.buildNoModelFoundMessage(modelSelectionPrompt.Name, modelSelectionInfo));
495
+ // Select model using the appropriate prompt — capture the FULL result
496
+ selection = await this.selectModel(modelSelectionPrompt, params.override?.modelId, params.contextUser, params.configurationId, params.override?.vendorId, params);
497
+ if (!selection.model) {
498
+ throw new Error(this.buildNoModelFoundMessage(modelSelectionPrompt.Name, selection.selectionInfo));
479
499
  }
480
500
  // Check if we have a system prompt override
481
501
  if (params.systemPromptOverride) {
@@ -490,7 +510,7 @@ export class AIPromptRunner {
490
510
  renderedPromptText = await this.renderPromptWithChildTemplates(prompt, params, childTemplateRenderingResult.renderedTemplates);
491
511
  }
492
512
  // Create parent prompt run for the final composed prompt execution
493
- parentPromptRun = await this.createPromptRun(prompt, selectedModel, params, renderedPromptText, startTime, params.override?.vendorId, modelSelectionInfo);
513
+ parentPromptRun = await this.createPromptRun(prompt, selection.model, params, renderedPromptText, startTime, params.override?.vendorId, selection.selectionInfo);
494
514
  }
495
515
  else if (prompt.TemplateID && (!params.conversationMessages || params.templateMessageRole !== 'none')) {
496
516
  // Check if we have a system prompt override
@@ -520,30 +540,28 @@ export class AIPromptRunner {
520
540
  if (params.cancellationToken?.aborted) {
521
541
  throw new Error('Prompt execution was cancelled during template rendering');
522
542
  }
523
- // If no model was selected yet (no template case), select one now
524
- if (!selectedModel) {
543
+ // If no model was selected yet (non-hierarchical case), select one now — capture the FULL result
544
+ if (!selection?.model) {
525
545
  let modelSelectionPrompt = prompt;
526
546
  if (params.modelSelectionPrompt) {
527
547
  modelSelectionPrompt = params.modelSelectionPrompt;
528
548
  this.logStatus(`🎯 Using prompt "${modelSelectionPrompt.Name}" for model selection instead of main prompt`, true, params);
529
549
  }
530
- const modelResult = await this.selectModel(modelSelectionPrompt, params.override?.modelId, params.contextUser, params.configurationId, params.override?.vendorId, params);
531
- selectedModel = modelResult.model;
532
- modelSelectionInfo = modelResult.selectionInfo;
533
- if (!selectedModel) {
534
- throw new Error(this.buildNoModelFoundMessage(modelSelectionPrompt.Name, modelSelectionInfo));
550
+ selection = await this.selectModel(modelSelectionPrompt, params.override?.modelId, params.contextUser, params.configurationId, params.override?.vendorId, params);
551
+ if (!selection.model) {
552
+ throw new Error(this.buildNoModelFoundMessage(modelSelectionPrompt.Name, selection.selectionInfo));
535
553
  }
536
554
  }
537
555
  // Check if we need parallel execution based on ParallelizationMode
538
556
  const shouldUseParallelExecution = prompt.ParallelizationMode && prompt.ParallelizationMode !== 'None';
539
557
  let result;
540
558
  if (shouldUseParallelExecution) {
541
- // Use parallel execution path
542
- result = await this.executePromptInParallel(prompt, renderedPromptText, params, startTime, parentPromptRun, selectedModel, modelSelectionInfo);
559
+ // Use parallel execution path — pass full selection through
560
+ result = await this.executePromptInParallel(prompt, renderedPromptText, params, startTime, parentPromptRun, selection);
543
561
  }
544
562
  else {
545
- // Use traditional single execution path
546
- result = await this.executeSinglePrompt(prompt, renderedPromptText, params, startTime, parentPromptRun, selectedModel, modelSelectionInfo);
563
+ // Use traditional single execution path — pass full selection through
564
+ result = await this.executeSinglePrompt(prompt, renderedPromptText, params, startTime, parentPromptRun, selection);
547
565
  }
548
566
  // Note: With template composition, we only execute once so no rollup calculations needed
549
567
  // The final composed prompt is executed as a single operation
@@ -613,33 +631,22 @@ export class AIPromptRunner {
613
631
  * @param startTime - Execution start time
614
632
  * @returns Promise<AIPromptRunResult<T>> - The execution result
615
633
  */
616
- async executeSinglePrompt(prompt, renderedPromptText, params, startTime, existingPromptRun, existingModel, existingModelSelectionInfo) {
634
+ async executeSinglePrompt(prompt, renderedPromptText, params, startTime, existingPromptRun, existingSelection) {
617
635
  // Check for cancellation before model selection
618
636
  if (params.cancellationToken?.aborted) {
619
637
  throw new Error('Prompt execution was cancelled before model selection');
620
638
  }
621
- // Use existing model if provided (hierarchical case) or select one
622
- let selectedModel = existingModel;
623
- let modelSelectionInfo = existingModelSelectionInfo;
624
- let vendorDriverClass;
625
- let vendorApiName;
626
- let vendorSupportsEffortLevel;
627
- let modelEffortLevel;
628
- let allCandidates = [];
629
- if (modelSelectionInfo) {
630
- // we received model selection info, need to lookup vendor driver class and api name from there
631
- const vendorID = modelSelectionInfo.vendorSelected?.ID;
632
- const modelID = modelSelectionInfo.modelSelected.ID;
633
- const modelVendor = AIEngine.Instance.ModelVendorsByModelID.get(NormalizeUUID(modelID))
634
- ?.find(mv => UUIDsEqual(mv.VendorID, vendorID));
635
- if (modelVendor) {
636
- vendorDriverClass = modelVendor.DriverClass;
637
- vendorApiName = modelVendor.APIName;
638
- vendorSupportsEffortLevel = modelVendor.SupportsEffortLevel;
639
- }
640
- // Extract valid candidates from selection info for retry logic
641
- allCandidates = this.buildCandidatesFromSelectionInfo(modelSelectionInfo);
642
- }
639
+ // Use existing selection if provided (hierarchical case) or select now
640
+ let selectedModel = existingSelection?.model ?? undefined;
641
+ let modelSelectionInfo = existingSelection?.selectionInfo;
642
+ let vendorDriverClass = existingSelection?.vendorDriverClass;
643
+ let vendorApiName = existingSelection?.vendorApiName;
644
+ let vendorSupportsEffortLevel = existingSelection?.vendorSupportsEffortLevel;
645
+ let modelEffortLevel = existingSelection?.modelEffortLevel;
646
+ let allCandidates = existingSelection?.allCandidates ?? [];
647
+ // Credential probes already done during selection — reused by failover so it doesn't
648
+ // recompute hasCredentialsAvailable for the prefix it walks before the selected candidate.
649
+ let credentialAvailability = existingSelection?.credentialAvailability;
643
650
  if (!selectedModel) {
644
651
  // Determine which prompt to use for model selection
645
652
  let modelSelectionPrompt = prompt;
@@ -655,6 +662,7 @@ export class AIPromptRunner {
655
662
  modelEffortLevel = modelResult.modelEffortLevel;
656
663
  modelSelectionInfo = modelResult.selectionInfo;
657
664
  allCandidates = modelResult.allCandidates || [];
665
+ credentialAvailability = modelResult.credentialAvailability;
658
666
  if (!selectedModel) {
659
667
  throw new Error(this.buildNoModelFoundMessage(modelSelectionPrompt.Name, modelSelectionInfo));
660
668
  }
@@ -670,7 +678,8 @@ export class AIPromptRunner {
670
678
  throw new Error('Prompt execution was cancelled before model execution');
671
679
  }
672
680
  // Execute with retry logic for validation failures
673
- const { modelResult, parsedResult, validationAttempts, cumulativeTokens } = await this.executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, allCandidates, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel // Pass model-specific effort level
681
+ const { modelResult, parsedResult, validationAttempts, cumulativeTokens } = await this.executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, allCandidates, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel, // Pass model-specific effort level
682
+ credentialAvailability // Reuse credential probes from selection
674
683
  );
675
684
  // Calculate execution metrics
676
685
  const endTime = new Date();
@@ -730,7 +739,7 @@ export class AIPromptRunner {
730
739
  * @param startTime - Execution start time
731
740
  * @returns Promise<AIPromptRunResult<T>> - The aggregated execution result
732
741
  */
733
- async executePromptInParallel(prompt, renderedPromptText, params, startTime, existingPromptRun, existingModel, existingModelSelectionInfo) {
742
+ async executePromptInParallel(prompt, renderedPromptText, params, startTime, existingPromptRun, existingSelection) {
734
743
  // Check for cancellation before starting parallel execution
735
744
  if (params.cancellationToken?.aborted) {
736
745
  throw new Error('Parallel execution was cancelled before starting');
@@ -738,21 +747,21 @@ export class AIPromptRunner {
738
747
  // Load AI Engine to get models and prompt models
739
748
  await AIEngine.Instance.Config(false, params.contextUser);
740
749
  let executionTasks;
741
- // If a model is already selected (from hierarchical template composition),
750
+ // If a model is already selected (from hierarchical template composition),
742
751
  // create a single task with that model instead of using the planner
743
- if (existingModel) {
752
+ if (existingSelection?.model) {
744
753
  // Create a single execution task with the pre-selected model
745
754
  executionTasks = [{
746
755
  taskId: 'pre-selected',
747
- model: existingModel,
748
- vendorDriverClass: undefined, // Would need to look up vendor entity for this
749
- vendorApiName: existingModel.Vendor, // Vendor is already the name string
756
+ model: existingSelection.model,
757
+ vendorDriverClass: existingSelection.vendorDriverClass,
758
+ vendorApiName: existingSelection.vendorApiName,
750
759
  messages: params.conversationMessages || [],
751
760
  promptText: renderedPromptText,
752
761
  templateMessageRole: params.templateMessageRole || 'system',
753
762
  contextUser: params.contextUser
754
763
  }];
755
- this.logStatus(` Using pre-selected model "${existingModel.Name}" for parallel execution`, true, params);
764
+ this.logStatus(` Using pre-selected model "${existingSelection.model.Name}" for parallel execution`, true, params);
756
765
  }
757
766
  else {
758
767
  // Normal parallel execution path - let the planner decide
@@ -777,7 +786,7 @@ export class AIPromptRunner {
777
786
  throw new Error('Parallel execution was cancelled before task execution');
778
787
  }
779
788
  // Execute tasks in parallel
780
- const parallelResult = await this._parallelCoordinator.executeTasksInParallel(params, executionTasks, undefined, undefined, params.cancellationToken, undefined, params.agentRunId);
789
+ const parallelResult = await this.ParallelCoordinator.executeTasksInParallel(params, executionTasks, undefined, undefined, params.cancellationToken, undefined, params.agentRunId);
781
790
  if (!parallelResult.success) {
782
791
  throw new Error(`Parallel execution failed: ${parallelResult.errors.join(', ')}`);
783
792
  }
@@ -793,7 +802,7 @@ export class AIPromptRunner {
793
802
  method: 'PromptSelector',
794
803
  selectorPromptId: prompt.ResultSelectorPromptID,
795
804
  };
796
- const aiSelectedResult = await this._parallelCoordinator.selectBestResult(successfulResults, selectionConfig, undefined, params.cancellationToken);
805
+ const aiSelectedResult = await this.ParallelCoordinator.selectBestResult(successfulResults, selectionConfig, undefined, params.cancellationToken);
797
806
  if (aiSelectedResult) {
798
807
  selectedResult = aiSelectedResult;
799
808
  }
@@ -822,7 +831,7 @@ export class AIPromptRunner {
822
831
  }
823
832
  // Use existing prompt run if provided (hierarchical case) or create new one
824
833
  // Use the model selection info if provided (from hierarchical execution)
825
- const consolidatedPromptRun = existingPromptRun || await this.createPromptRun(prompt, selectedResult.task.model, params, renderedPromptText, startTime, params.override?.vendorId, existingModelSelectionInfo);
834
+ const consolidatedPromptRun = existingPromptRun || await this.createPromptRun(prompt, selectedResult.task.model, params, renderedPromptText, startTime, params.override?.vendorId, existingSelection?.selectionInfo);
826
835
  // Update with parallel execution metadata
827
836
  const endTime = new Date();
828
837
  consolidatedPromptRun.CompletedAt = endTime;
@@ -944,11 +953,11 @@ export class AIPromptRunner {
944
953
  modelInfo: {
945
954
  modelId: selectedResult.task.model.ID,
946
955
  modelName: selectedResult.task.model.Name,
947
- vendorId: existingModelSelectionInfo?.vendorSelected?.ID, // VendorID not directly available on AIModel
956
+ vendorId: existingSelection?.selectionInfo?.vendorSelected?.ID, // VendorID not directly available on AIModel
948
957
  vendorName: selectedResult.task.model.Vendor,
949
958
  },
950
959
  judgeMetadata: selectedResult.judgeMetadata,
951
- modelSelectionInfo: existingModelSelectionInfo, // Include model selection info if provided
960
+ modelSelectionInfo: existingSelection?.selectionInfo, // Include model selection info if provided
952
961
  };
953
962
  }
954
963
  /**
@@ -1275,7 +1284,7 @@ export class AIPromptRunner {
1275
1284
  // });
1276
1285
  // }
1277
1286
  // Select the first candidate with available credentials and track all attempts
1278
- const { selected, consideredModels } = await this.selectModelWithAPIKeyTracked(candidates, prompt.ID, params);
1287
+ const { selected, consideredModels, credentialAvailability } = await this.selectModelWithAPIKeyTracked(candidates, prompt.ID, params);
1279
1288
  // Merge considered models into our tracking
1280
1289
  modelsConsidered.push(...consideredModels);
1281
1290
  if (!selected) {
@@ -1287,6 +1296,7 @@ export class AIPromptRunner {
1287
1296
  vendorSupportsEffortLevel: undefined,
1288
1297
  modelEffortLevel: undefined,
1289
1298
  allCandidates: candidates,
1299
+ credentialAvailability,
1290
1300
  selectionInfo: this.createSelectionInfo({
1291
1301
  aiConfiguration: configuration,
1292
1302
  modelsConsidered,
@@ -1331,6 +1341,7 @@ export class AIPromptRunner {
1331
1341
  vendorSupportsEffortLevel: selected.supportsEffortLevel,
1332
1342
  modelEffortLevel: selected.effortLevel, // Pass through model-specific effort level
1333
1343
  allCandidates: candidates,
1344
+ credentialAvailability,
1334
1345
  selectionInfo: this.createSelectionInfo({
1335
1346
  aiConfiguration: configuration,
1336
1347
  modelsConsidered,
@@ -1879,33 +1890,6 @@ export class AIPromptRunner {
1879
1890
  Object.assign(info, data);
1880
1891
  return info;
1881
1892
  }
1882
- /**
1883
- * Converts model selection info into ModelVendorCandidate array for retry logic.
1884
- * Extracts only the valid candidates (those with available API keys) from the selection info.
1885
- *
1886
- * @param selectionInfo - Model selection information containing considered models
1887
- * @returns Array of valid model-vendor candidates sorted by priority
1888
- */
1889
- buildCandidatesFromSelectionInfo(selectionInfo) {
1890
- const validModels = selectionInfo.extractValidCandidates();
1891
- return validModels.map(considered => {
1892
- // Find matching model vendor for driver and API info
1893
- const modelVendor = considered.vendor
1894
- ? considered.model.ModelVendors.find(mv => UUIDsEqual(mv.VendorID, considered.vendor.ID))
1895
- : undefined;
1896
- return {
1897
- model: considered.model,
1898
- vendorId: considered.vendor?.ID,
1899
- vendorName: considered.vendor?.Name,
1900
- driverClass: modelVendor?.DriverClass || considered.model.DriverClass,
1901
- apiName: modelVendor?.APIName || considered.model.APIName,
1902
- supportsEffortLevel: modelVendor?.SupportsEffortLevel ?? considered.model.SupportsEffortLevel ?? false,
1903
- isPreferredVendor: false, // Can't determine from selection info alone
1904
- priority: considered.priority,
1905
- source: (selectionInfo.selectionStrategy === 'ByPower' ? 'power-rank' : 'model-type')
1906
- };
1907
- }).sort((a, b) => b.priority - a.priority); // Sort by priority descending
1908
- }
1909
1893
  /**
1910
1894
  * Enhanced version of selectModelWithAPIKey that tracks all considered models
1911
1895
  * for model selection reporting. Uses the hierarchical credential resolution
@@ -1997,7 +1981,7 @@ export class AIPromptRunner {
1997
1981
  maxErrorLength: params?.maxErrorLength
1998
1982
  });
1999
1983
  }
2000
- return { selected: selectedCandidate, consideredModels };
1984
+ return { selected: selectedCandidate, consideredModels, credentialAvailability: credentialCache };
2001
1985
  }
2002
1986
  /**
2003
1987
  * Builds a descriptive error message when no model could be selected for a prompt.
@@ -2110,7 +2094,7 @@ export class AIPromptRunner {
2110
2094
  promptRun.Status = 'Running';
2111
2095
  promptRun.Cancelled = false;
2112
2096
  promptRun.CacheHit = false;
2113
- promptRun.StreamingEnabled = false;
2097
+ promptRun.StreamingEnabled = !!params.onStreaming;
2114
2098
  promptRun.WasSelectedResult = false;
2115
2099
  // Set model selection tracking fields
2116
2100
  if (modelSelectionInfo) {
@@ -2353,7 +2337,7 @@ export class AIPromptRunner {
2353
2337
  * - updatePromptRunWithFailoverFailure: Records failed failover metadata
2354
2338
  * - createFailoverErrorResult: Creates standardized error response
2355
2339
  */
2356
- async executeModelWithFailover(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, allCandidates, promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel) {
2340
+ async executeModelWithFailover(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, allCandidates, promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel, credentialAvailability) {
2357
2341
  // Get failover configuration (used for errorScope filtering)
2358
2342
  const failoverConfig = this.getFailoverConfiguration(prompt);
2359
2343
  // If no candidates provided or failover disabled, execute normally with first model
@@ -2363,10 +2347,45 @@ export class AIPromptRunner {
2363
2347
  // Track failover attempts
2364
2348
  const failoverAttempts = [];
2365
2349
  let lastError = null;
2350
+ // Cache credential availability per driver:model:vendor for the duration of this failover
2351
+ // scan so we don't repeat env-var / binding lookups while walking the candidate list.
2352
+ //
2353
+ // PERF: seed it with the probes model SELECTION already performed (same key format). Selection
2354
+ // walks the priority list until it finds the first credentialed candidate, so this map holds
2355
+ // the prefix it rejected (known false) PLUS the selected candidate (known true) — which is
2356
+ // exactly the segment failover re-walks on the happy path. Reusing those results means the
2357
+ // common case (and any caller looping failover) does ZERO redundant hasCredentialsAvailable
2358
+ // calls. The not-evaluated tail is intentionally absent, so failover still lazily probes it
2359
+ // only if a real failure forces it to walk down there.
2360
+ const failoverCredentialCache = credentialAvailability
2361
+ ? new Map(credentialAvailability)
2362
+ : new Map();
2363
+ const candidateHasCredentials = (c) => {
2364
+ const key = `${c.driverClass}:${c.model.ID}:${c.vendorId || 'default'}`;
2365
+ let has = failoverCredentialCache.get(key);
2366
+ if (has === undefined) {
2367
+ has = this.hasCredentialsAvailable(c.driverClass, prompt.ID, c.model.ID, c.vendorId, params);
2368
+ failoverCredentialCache.set(key, has);
2369
+ }
2370
+ return has;
2371
+ };
2372
+ let skippedForCredentials = 0;
2366
2373
  // Iterate through all candidates in priority order with instant failover
2367
2374
  for (let i = 0; i < allCandidates.length; i++) {
2368
2375
  const candidate = allCandidates[i];
2369
2376
  const attemptStartTime = Date.now();
2377
+ // Skip candidates with no credentials configured. `allCandidates` is intentionally the
2378
+ // FULL priority-ordered list (see the DECISION note in selectModelWithAPIKeyTracked),
2379
+ // so it can include vendors that have no API key in this environment. Firing a live
2380
+ // request at one of those produces a misleading "401 invalid API key" — and because an
2381
+ // Authentication error is treated as fatal, it would halt failover before any
2382
+ // credentialed candidate is ever reached. Skipping here makes failover land on the
2383
+ // first candidate that can actually authenticate (mirroring model selection's own
2384
+ // highest-priority-with-credentials rule).
2385
+ if (!candidateHasCredentials(candidate)) {
2386
+ skippedForCredentials++;
2387
+ continue;
2388
+ }
2370
2389
  try {
2371
2390
  // Log the attempt if not the first one
2372
2391
  if (i > 0) {
@@ -2435,6 +2454,11 @@ export class AIPromptRunner {
2435
2454
  if (promptRun && failoverAttempts.length > 0) {
2436
2455
  this.updatePromptRunWithFailoverFailure(promptRun, failoverAttempts);
2437
2456
  }
2457
+ // If every candidate was skipped for missing credentials we never attempted a call and
2458
+ // have no underlying error to report — surface an actionable message instead of null.
2459
+ if (!lastError && failoverAttempts.length === 0 && skippedForCredentials > 0) {
2460
+ lastError = new Error(`No API credentials configured for any of the ${skippedForCredentials} candidate model-vendor combination(s) for prompt "${prompt.Name}".`);
2461
+ }
2438
2462
  return this.createFailoverErrorResult(lastError, failoverAttempts);
2439
2463
  }
2440
2464
  /**
@@ -2570,7 +2594,11 @@ export class AIPromptRunner {
2570
2594
  };
2571
2595
  }
2572
2596
  /**
2573
- * Executes the AI model with the rendered prompt
2597
+ * Executes the AI model with the rendered prompt.
2598
+ *
2599
+ * `protected` so the parallel coordinator subclass reuses this exact code path — credential
2600
+ * resolution, driver selection, ChatParams construction, prefill, media handling, and streaming
2601
+ * all live here ONCE. Do not duplicate this logic elsewhere.
2574
2602
  */
2575
2603
  async executeModel(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel) {
2576
2604
  // define these variables here to ensure they're available in the catch block
@@ -2712,6 +2740,17 @@ export class AIPromptRunner {
2712
2740
  this.stripUnsupportedMediaBlocks(llm, chatParams, model, verbose, params);
2713
2741
  // Apply assistant prefill (native or fallback) based on prompt config and provider support
2714
2742
  this.applyAssistantPrefill(chatParams, prompt, model, vendorId, llm);
2743
+ // Streaming: wire the prompt-level onStreaming callback into the LLM call. This is the SINGLE
2744
+ // place streaming is configured for prompt execution, so the single-model path and the parallel
2745
+ // path (which bridges its per-task callbacks into params.onStreaming) stream through identical
2746
+ // code — no second streaming implementation that can drift.
2747
+ if (params.onStreaming) {
2748
+ const onStreaming = params.onStreaming;
2749
+ chatParams.streaming = true;
2750
+ chatParams.streamingCallbacks = {
2751
+ OnContent: (chunk, isComplete) => onStreaming({ content: chunk, isComplete, modelName: model.Name }),
2752
+ };
2753
+ }
2715
2754
  // Execute the model with cancellation support
2716
2755
  if (cancellationToken) {
2717
2756
  // If cancellation token is provided, wrap the execution to handle cancellation
@@ -3091,7 +3130,7 @@ export class AIPromptRunner {
3091
3130
  /**
3092
3131
  * Executes the model with retry logic for validation failures
3093
3132
  */
3094
- async executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, allCandidates, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel) {
3133
+ async executeWithValidationRetries(selectedModel, renderedPromptText, prompt, params, promptRun, allCandidates, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel, credentialAvailability) {
3095
3134
  const validationAttempts = [];
3096
3135
  const maxRetries = Math.max(0, prompt.MaxRetries || 0);
3097
3136
  let lastError = null;
@@ -3111,7 +3150,8 @@ export class AIPromptRunner {
3111
3150
  }
3112
3151
  // Execute the AI model with failover support
3113
3152
  const modelResult = await this.executeModelWithFailover(selectedModel, renderedPromptText, prompt, params, promptRun.VendorID, params.conversationMessages, params.templateMessageRole || 'system', params.cancellationToken, allCandidates, // Pass the candidates from initial selection
3114
- promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel);
3153
+ promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel, modelEffortLevel, credentialAvailability // Reuse credential probes from selection
3154
+ );
3115
3155
  // Check for fatal errors - don't attempt validation/retry on these
3116
3156
  // Fatal errors (like ContextLengthExceeded when all models exhausted) cannot be resolved by retrying
3117
3157
  if (!modelResult.success && modelResult.errorInfo?.severity === 'Fatal') {
@@ -3983,6 +4023,12 @@ export class AIPromptRunner {
3983
4023
  */
3984
4024
  async updatePromptRun(promptRun, prompt, modelResult, parsedResult, endTime, executionTimeMS, validationAttempts, cumulativeTokens) {
3985
4025
  try {
4026
+ // Ensure the initial 'Running' INSERT (and its BaseEntity.finalizeSave post-save reload, which does
4027
+ // init() + SetMany(insertedRow)) has fully landed BEFORE we mutate the final state. Mutating while the
4028
+ // INSERT is still in flight lets the reload revert these values, and the chained UPDATE then persists
4029
+ // the stale 'Running' row (the same race fixed in the agent-run-step queue and action-execution-log).
4030
+ // The model call between create and update almost always covers this; a fast-failing prompt could not.
4031
+ await this._promptRunSaveChains.get(promptRun);
3986
4032
  promptRun.CompletedAt = endTime;
3987
4033
  promptRun.ExecutionTimeMS = executionTimeMS;
3988
4034
  // Determine what to save as the result