@memberjunction/ai-prompts 2.118.0 → 2.120.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.
@@ -770,222 +770,284 @@ class AIPromptRunner {
770
770
  }
771
771
  }
772
772
  buildModelVendorCandidates(prompt, explicitModelId, configurationId, preferredVendorId, verbose) {
773
+ if (explicitModelId) {
774
+ return this.buildCandidatesForExplicitModel(explicitModelId, prompt, preferredVendorId);
775
+ }
776
+ if (prompt.SelectionStrategy === 'Specific') {
777
+ return this.buildCandidatesForSpecificStrategy(prompt, configurationId, verbose);
778
+ }
779
+ return this.buildCandidatesForGeneralSelection(prompt, configurationId, preferredVendorId, verbose);
780
+ }
781
+ buildCandidatesForExplicitModel(explicitModelId, prompt, preferredVendorId) {
782
+ const model = aiengine_1.AIEngine.Instance.Models.find(m => m.ID === explicitModelId);
783
+ if (!model || !model.IsActive) {
784
+ return [];
785
+ }
786
+ if (prompt.AIModelTypeID && model.AIModelTypeID !== prompt.AIModelTypeID) {
787
+ return [];
788
+ }
789
+ const candidates = this.createCandidatesForModel(model, 20000, 'explicit', preferredVendorId);
790
+ candidates.sort((a, b) => b.priority - a.priority);
791
+ return candidates;
792
+ }
793
+ buildCandidatesForSpecificStrategy(prompt, configurationId, verbose) {
794
+ const allPromptModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID && (pm.Status === 'Active' || pm.Status === 'Preview'));
795
+ const promptModels = this.filterPromptModelsByConfiguration(allPromptModels, configurationId);
796
+ const sortedPromptModels = this.sortPromptModelsForSpecificStrategy(promptModels, configurationId);
797
+ const candidates = this.buildCandidatesFromPromptModels(sortedPromptModels);
798
+ if (candidates.length === 0) {
799
+ const configInfo = configurationId ? ` with configuration "${configurationId}"` : '';
800
+ throw new Error(`SelectionStrategy is 'Specific' but no valid AIPromptModel candidates found for prompt "${prompt.Name}"${configInfo}. ` +
801
+ `Please configure AIPromptModel records for this prompt.`);
802
+ }
803
+ if (verbose) {
804
+ (0, core_1.LogStatus)(`Using SelectionStrategy='Specific' with ${sortedPromptModels.length} AIPromptModel entries, generated ${candidates.length} candidates`);
805
+ }
806
+ return candidates;
807
+ }
808
+ buildCandidatesForGeneralSelection(prompt, configurationId, preferredVendorId, verbose) {
773
809
  const preferredVendorName = preferredVendorId ?
774
810
  aiengine_1.AIEngine.Instance.Vendors.find(v => v.ID === preferredVendorId)?.Name : undefined;
775
- const createCandidatesForModel = (model, basePriority, source, promptModelPriority) => {
776
- const modelCandidates = [];
777
- const modelVendors = aiengine_1.AIEngine.Instance.ModelVendors
778
- .filter(mv => mv.ModelID === model.ID && mv.Status === 'Active' && this.isInferenceProvider(mv))
779
- .sort((a, b) => b.Priority - a.Priority);
780
- if (preferredVendorId) {
781
- const preferredVendor = modelVendors.find(mv => mv.VendorID === preferredVendorId);
782
- if (preferredVendor) {
783
- modelCandidates.push({
784
- model,
785
- vendorId: preferredVendor.VendorID,
786
- vendorName: preferredVendor.Vendor,
787
- driverClass: preferredVendor.DriverClass || model.DriverClass,
788
- apiName: preferredVendor.APIName || model.APIName,
789
- supportsEffortLevel: preferredVendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
790
- isPreferredVendor: true,
791
- priority: basePriority + 1000,
792
- source
793
- });
794
- }
795
- }
796
- for (const vendor of modelVendors) {
797
- if (vendor.VendorID !== preferredVendorId) {
798
- modelCandidates.push({
799
- model,
800
- vendorId: vendor.VendorID,
801
- vendorName: vendor.Vendor,
802
- driverClass: vendor.DriverClass || model.DriverClass,
803
- apiName: vendor.APIName || model.APIName,
804
- supportsEffortLevel: vendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
805
- isPreferredVendor: false,
806
- priority: basePriority + (vendor.Priority || 0),
807
- source
808
- });
809
- }
810
- }
811
- if (modelCandidates.length === 0 && model.DriverClass) {
812
- modelCandidates.push({
813
- model,
814
- driverClass: model.DriverClass,
815
- apiName: model.APIName,
816
- supportsEffortLevel: model.SupportsEffortLevel ?? false,
817
- isPreferredVendor: false,
818
- priority: basePriority,
819
- source
820
- });
821
- }
822
- if (promptModelPriority !== undefined) {
823
- modelCandidates.forEach(c => c.priority += promptModelPriority * 10);
824
- }
825
- return modelCandidates;
826
- };
827
- const createSpecificCandidatesForModel = (model, promptModel, preferredVendorId) => {
828
- const modelCandidates = [];
829
- const basePriority = 10000 + (promptModel.Priority || 0) * 100;
830
- const modelVendors = aiengine_1.AIEngine.Instance.ModelVendors
831
- .filter(mv => mv.ModelID === model.ID && mv.Status === 'Active' && this.isInferenceProvider(mv));
832
- const pmPreferredVendorId = promptModel.VendorID || preferredVendorId;
833
- if (pmPreferredVendorId) {
834
- const preferredVendor = modelVendors.find(mv => mv.VendorID === pmPreferredVendorId);
835
- if (preferredVendor) {
836
- modelCandidates.push({
837
- model,
838
- vendorId: preferredVendor.VendorID,
839
- vendorName: preferredVendor.Vendor,
840
- driverClass: preferredVendor.DriverClass || model.DriverClass,
841
- apiName: preferredVendor.APIName || model.APIName,
842
- supportsEffortLevel: preferredVendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
843
- isPreferredVendor: true,
844
- priority: basePriority + 1000,
845
- source: 'prompt-model'
846
- });
847
- }
811
+ const promptModels = this.getPromptModelsForConfiguration(prompt, configurationId);
812
+ const candidates = [];
813
+ if (promptModels.length > 0) {
814
+ this.addPromptSpecificCandidates(candidates, promptModels, preferredVendorId);
815
+ if (configurationId) {
816
+ this.addConfigurationFallbackCandidates(candidates, prompt, configurationId, preferredVendorId, verbose);
848
817
  }
849
- for (const vendor of modelVendors) {
850
- if (vendor.VendorID !== pmPreferredVendorId) {
851
- modelCandidates.push({
852
- model,
853
- vendorId: vendor.VendorID,
854
- vendorName: vendor.Vendor,
855
- driverClass: vendor.DriverClass || model.DriverClass,
856
- apiName: vendor.APIName || model.APIName,
857
- supportsEffortLevel: vendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
858
- isPreferredVendor: false,
859
- priority: basePriority + (vendor.Priority || 0) * 10,
860
- source: 'prompt-model'
861
- });
818
+ }
819
+ else {
820
+ this.addStrategyBasedCandidates(candidates, prompt, preferredVendorName);
821
+ }
822
+ candidates.sort((a, b) => b.priority - a.priority);
823
+ return candidates;
824
+ }
825
+ filterPromptModelsByConfiguration(allPromptModels, configurationId) {
826
+ if (configurationId) {
827
+ return allPromptModels.filter(pm => pm.ConfigurationID === configurationId || pm.ConfigurationID === null);
828
+ }
829
+ else {
830
+ return allPromptModels.filter(pm => pm.ConfigurationID === null);
831
+ }
832
+ }
833
+ sortPromptModelsForSpecificStrategy(promptModels, configurationId) {
834
+ return promptModels.sort((a, b) => {
835
+ const aIsConfigMatch = configurationId && a.ConfigurationID === configurationId ? 1 : 0;
836
+ const bIsConfigMatch = configurationId && b.ConfigurationID === configurationId ? 1 : 0;
837
+ if (aIsConfigMatch !== bIsConfigMatch) {
838
+ return bIsConfigMatch - aIsConfigMatch;
839
+ }
840
+ return (b.Priority || 0) - (a.Priority || 0);
841
+ });
842
+ }
843
+ buildCandidatesFromPromptModels(promptModels) {
844
+ const candidates = [];
845
+ for (const pm of promptModels) {
846
+ const model = aiengine_1.AIEngine.Instance.Models.find(m => m.ID === pm.ModelID);
847
+ if (!model || !model.IsActive)
848
+ continue;
849
+ if (pm.VendorID) {
850
+ const candidate = this.createCandidateForSpecificVendor(model, pm);
851
+ if (candidate) {
852
+ candidates.push(candidate);
862
853
  }
863
854
  }
864
- if (modelCandidates.length === 0 && model.DriverClass) {
865
- modelCandidates.push({
866
- model,
867
- driverClass: model.DriverClass,
868
- apiName: model.APIName,
869
- supportsEffortLevel: model.SupportsEffortLevel ?? false,
870
- isPreferredVendor: false,
871
- priority: basePriority,
872
- source: 'prompt-model'
873
- });
855
+ else {
856
+ const vendorCandidates = this.createCandidatesForAllVendors(model);
857
+ candidates.push(...vendorCandidates);
874
858
  }
875
- return modelCandidates;
859
+ }
860
+ return candidates;
861
+ }
862
+ createCandidateForSpecificVendor(model, promptModel) {
863
+ const modelVendor = aiengine_1.AIEngine.Instance.ModelVendors.find(mv => mv.ModelID === promptModel.ModelID &&
864
+ mv.VendorID === promptModel.VendorID &&
865
+ mv.Status === 'Active' &&
866
+ this.isInferenceProvider(mv));
867
+ if (!modelVendor)
868
+ return null;
869
+ return {
870
+ model,
871
+ vendorId: modelVendor.VendorID,
872
+ vendorName: modelVendor.Vendor,
873
+ driverClass: modelVendor.DriverClass || model.DriverClass,
874
+ apiName: modelVendor.APIName || model.APIName,
875
+ supportsEffortLevel: modelVendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
876
+ isPreferredVendor: false,
877
+ priority: 0,
878
+ source: 'prompt-model'
876
879
  };
877
- if (explicitModelId) {
878
- const model = aiengine_1.AIEngine.Instance.Models.find(m => m.ID === explicitModelId);
879
- if (model && model.IsActive) {
880
- if (!prompt.AIModelTypeID || model.AIModelTypeID === prompt.AIModelTypeID) {
881
- const candidates = createCandidatesForModel(model, 20000, 'explicit');
882
- candidates.sort((a, b) => b.priority - a.priority);
883
- return candidates;
884
- }
885
- }
886
- return [];
880
+ }
881
+ createCandidatesForAllVendors(model) {
882
+ const vendors = aiengine_1.AIEngine.Instance.ModelVendors
883
+ .filter(mv => mv.ModelID === model.ID &&
884
+ mv.Status === 'Active' &&
885
+ this.isInferenceProvider(mv))
886
+ .sort((a, b) => (b.Priority || 0) - (a.Priority || 0));
887
+ const candidates = [];
888
+ for (const vendor of vendors) {
889
+ candidates.push({
890
+ model,
891
+ vendorId: vendor.VendorID,
892
+ vendorName: vendor.Vendor,
893
+ driverClass: vendor.DriverClass || model.DriverClass,
894
+ apiName: vendor.APIName || model.APIName,
895
+ supportsEffortLevel: vendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
896
+ isPreferredVendor: false,
897
+ priority: 0,
898
+ source: 'prompt-model'
899
+ });
900
+ }
901
+ if (candidates.length === 0 && model.DriverClass) {
902
+ candidates.push({
903
+ model,
904
+ driverClass: model.DriverClass,
905
+ apiName: model.APIName,
906
+ supportsEffortLevel: model.SupportsEffortLevel ?? false,
907
+ isPreferredVendor: false,
908
+ priority: 0,
909
+ source: 'prompt-model'
910
+ });
887
911
  }
888
- let promptModels = [];
912
+ return candidates;
913
+ }
914
+ getPromptModelsForConfiguration(prompt, configurationId) {
889
915
  if (configurationId) {
890
- promptModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
916
+ const promptModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
891
917
  (pm.Status === 'Active' || pm.Status === 'Preview') &&
892
918
  pm.ConfigurationID === configurationId);
893
919
  if (promptModels.length === 0) {
894
920
  (0, core_1.LogStatus)(`No models found for configuration "${configurationId}", falling back to default models`);
895
- promptModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
921
+ return aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
896
922
  (pm.Status === 'Active' || pm.Status === 'Preview') &&
897
923
  !pm.ConfigurationID);
898
924
  }
925
+ return promptModels;
899
926
  }
900
927
  else {
901
- promptModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
928
+ return aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
902
929
  (pm.Status === 'Active' || pm.Status === 'Preview') &&
903
930
  !pm.ConfigurationID);
904
931
  }
905
- if (prompt.SelectionStrategy === 'Specific' && promptModels.length > 0) {
906
- const candidates = [];
907
- promptModels.sort((a, b) => (b.Priority || 0) - (a.Priority || 0));
908
- for (const pm of promptModels) {
909
- const model = aiengine_1.AIEngine.Instance.Models.find(m => m.ID === pm.ModelID);
910
- if (model && model.IsActive) {
911
- const modelCandidates = createSpecificCandidatesForModel(model, pm, preferredVendorId);
912
- candidates.push(...modelCandidates);
913
- }
914
- }
915
- if (candidates.length > 0) {
916
- candidates.sort((a, b) => b.priority - a.priority);
917
- if (verbose) {
918
- (0, core_1.LogStatus)(`Using SelectionStrategy='Specific' with ${promptModels.length} AIPromptModel entries, generated ${candidates.length} candidates`);
919
- }
920
- return candidates;
921
- }
922
- if (verbose) {
923
- (0, core_1.LogStatus)(`SelectionStrategy='Specific' specified but no usable candidates from AIPromptModel entries, falling back to general selection`);
932
+ }
933
+ addPromptSpecificCandidates(candidates, promptModels, preferredVendorId) {
934
+ for (const pm of promptModels) {
935
+ const model = aiengine_1.AIEngine.Instance.Models.find(m => m.ID === pm.ModelID);
936
+ if (model && model.IsActive) {
937
+ const modelCandidates = this.createCandidatesForModel(model, 5000, 'prompt-model', preferredVendorId, pm.Priority);
938
+ candidates.push(...modelCandidates);
924
939
  }
925
940
  }
926
- const candidates = [];
927
- if (promptModels.length > 0 && prompt.SelectionStrategy !== 'Specific') {
928
- for (const pm of promptModels) {
929
- const model = aiengine_1.AIEngine.Instance.Models.find(m => m.ID === pm.ModelID);
930
- if (model && model.IsActive) {
931
- const modelCandidates = createCandidatesForModel(model, 5000, 'prompt-model', pm.Priority);
932
- candidates.push(...modelCandidates);
933
- }
934
- }
935
- if (configurationId) {
936
- const nullConfigModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
937
- (pm.Status === 'Active' || pm.Status === 'Preview') &&
938
- !pm.ConfigurationID);
939
- if (nullConfigModels.length > 0 && verbose) {
940
- (0, core_1.LogStatus)(`Adding ${nullConfigModels.length} NULL configuration models as fallback candidates`);
941
- }
942
- for (const pm of nullConfigModels) {
943
- const model = aiengine_1.AIEngine.Instance.Models.find(m => m.ID === pm.ModelID);
944
- if (model && model.IsActive) {
945
- const modelCandidates = createCandidatesForModel(model, 2000, 'prompt-model', pm.Priority);
946
- candidates.push(...modelCandidates);
947
- }
948
- }
941
+ }
942
+ addConfigurationFallbackCandidates(candidates, prompt, configurationId, preferredVendorId, verbose) {
943
+ const nullConfigModels = aiengine_1.AIEngine.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
944
+ (pm.Status === 'Active' || pm.Status === 'Preview') &&
945
+ !pm.ConfigurationID);
946
+ if (nullConfigModels.length > 0 && verbose) {
947
+ (0, core_1.LogStatus)(`Adding ${nullConfigModels.length} NULL configuration models as fallback candidates`);
948
+ }
949
+ for (const pm of nullConfigModels) {
950
+ const model = aiengine_1.AIEngine.Instance.Models.find(m => m.ID === pm.ModelID);
951
+ if (model && model.IsActive) {
952
+ const modelCandidates = this.createCandidatesForModel(model, 2000, 'prompt-model', preferredVendorId, pm.Priority);
953
+ candidates.push(...modelCandidates);
949
954
  }
950
955
  }
956
+ }
957
+ addStrategyBasedCandidates(candidates, prompt, preferredVendorName) {
958
+ let modelPool = this.getModelPoolForStrategy(prompt, preferredVendorName);
959
+ modelPool = this.sortModelPoolByStrategy(modelPool, prompt);
960
+ modelPool.forEach((model, index) => {
961
+ const basePriority = 1000 - index * 10;
962
+ const source = prompt.SelectionStrategy === 'ByPower' ? 'power-rank' : 'model-type';
963
+ candidates.push(...this.createCandidatesForModel(model, basePriority, source));
964
+ });
965
+ }
966
+ getModelPoolForStrategy(prompt, preferredVendorName) {
967
+ return aiengine_1.AIEngine.Instance.Models.filter(m => m.IsActive &&
968
+ (!prompt.AIModelTypeID || m.AIModelTypeID === prompt.AIModelTypeID) &&
969
+ (!preferredVendorName ||
970
+ aiengine_1.AIEngine.Instance.ModelVendors.some(mv => mv.ModelID === m.ID &&
971
+ mv.Status === 'Active' &&
972
+ mv.Vendor === preferredVendorName &&
973
+ this.isInferenceProvider(mv))));
974
+ }
975
+ sortModelPoolByStrategy(modelPool, prompt) {
976
+ if (prompt.SelectionStrategy === 'ByPower') {
977
+ return this.sortByPowerPreference(modelPool, prompt.PowerPreference);
978
+ }
951
979
  else {
952
- let modelPool = aiengine_1.AIEngine.Instance.Models.filter(m => m.IsActive &&
953
- (!prompt.AIModelTypeID || m.AIModelTypeID === prompt.AIModelTypeID) &&
954
- (!preferredVendorName ||
955
- aiengine_1.AIEngine.Instance.ModelVendors.some(mv => mv.ModelID === m.ID &&
956
- mv.Status === 'Active' &&
957
- mv.Vendor === preferredVendorName &&
958
- this.isInferenceProvider(mv))));
959
- switch (prompt.SelectionStrategy) {
960
- case 'ByPower':
961
- switch (prompt.PowerPreference) {
962
- case 'Highest':
963
- modelPool.sort((a, b) => b.PowerRank - a.PowerRank);
964
- break;
965
- case 'Lowest':
966
- modelPool.sort((a, b) => a.PowerRank - b.PowerRank);
967
- break;
968
- case 'Balanced':
969
- const avgPower = modelPool.reduce((sum, m) => sum + m.PowerRank, 0) / modelPool.length;
970
- modelPool.sort((a, b) => Math.abs(a.PowerRank - avgPower) - Math.abs(b.PowerRank - avgPower));
971
- break;
972
- }
973
- break;
974
- case 'Specific':
975
- case 'Default':
976
- default:
977
- const minPowerRank = prompt.MinPowerRank || 0;
978
- modelPool = modelPool.filter(m => m.PowerRank >= minPowerRank);
979
- modelPool.sort((a, b) => b.PowerRank - a.PowerRank);
980
- break;
980
+ const minPowerRank = prompt.MinPowerRank || 0;
981
+ return modelPool
982
+ .filter(m => m.PowerRank >= minPowerRank)
983
+ .sort((a, b) => b.PowerRank - a.PowerRank);
984
+ }
985
+ }
986
+ sortByPowerPreference(modelPool, powerPreference) {
987
+ const pool = [...modelPool];
988
+ switch (powerPreference) {
989
+ case 'Highest':
990
+ return pool.sort((a, b) => b.PowerRank - a.PowerRank);
991
+ case 'Lowest':
992
+ return pool.sort((a, b) => a.PowerRank - b.PowerRank);
993
+ case 'Balanced':
994
+ const avgPower = pool.reduce((sum, m) => sum + m.PowerRank, 0) / pool.length;
995
+ return pool.sort((a, b) => Math.abs(a.PowerRank - avgPower) - Math.abs(b.PowerRank - avgPower));
996
+ default:
997
+ return pool.sort((a, b) => b.PowerRank - a.PowerRank);
998
+ }
999
+ }
1000
+ createCandidatesForModel(model, basePriority, source, preferredVendorId, promptModelPriority) {
1001
+ const modelCandidates = [];
1002
+ const modelVendors = aiengine_1.AIEngine.Instance.ModelVendors
1003
+ .filter(mv => mv.ModelID === model.ID && mv.Status === 'Active' && this.isInferenceProvider(mv))
1004
+ .sort((a, b) => b.Priority - a.Priority);
1005
+ if (preferredVendorId) {
1006
+ const preferredVendor = modelVendors.find(mv => mv.VendorID === preferredVendorId);
1007
+ if (preferredVendor) {
1008
+ modelCandidates.push({
1009
+ model,
1010
+ vendorId: preferredVendor.VendorID,
1011
+ vendorName: preferredVendor.Vendor,
1012
+ driverClass: preferredVendor.DriverClass || model.DriverClass,
1013
+ apiName: preferredVendor.APIName || model.APIName,
1014
+ supportsEffortLevel: preferredVendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
1015
+ isPreferredVendor: true,
1016
+ priority: basePriority + 1000,
1017
+ source
1018
+ });
981
1019
  }
982
- modelPool.forEach((model, index) => {
983
- const basePriority = 1000 - index * 10;
984
- candidates.push(...createCandidatesForModel(model, basePriority, prompt.SelectionStrategy === 'ByPower' ? 'power-rank' : 'model-type'));
1020
+ }
1021
+ for (const vendor of modelVendors) {
1022
+ if (vendor.VendorID !== preferredVendorId) {
1023
+ modelCandidates.push({
1024
+ model,
1025
+ vendorId: vendor.VendorID,
1026
+ vendorName: vendor.Vendor,
1027
+ driverClass: vendor.DriverClass || model.DriverClass,
1028
+ apiName: vendor.APIName || model.APIName,
1029
+ supportsEffortLevel: vendor.SupportsEffortLevel ?? model.SupportsEffortLevel ?? false,
1030
+ isPreferredVendor: false,
1031
+ priority: basePriority + (vendor.Priority || 0),
1032
+ source
1033
+ });
1034
+ }
1035
+ }
1036
+ if (modelCandidates.length === 0 && model.DriverClass) {
1037
+ modelCandidates.push({
1038
+ model,
1039
+ driverClass: model.DriverClass,
1040
+ apiName: model.APIName,
1041
+ supportsEffortLevel: model.SupportsEffortLevel ?? false,
1042
+ isPreferredVendor: false,
1043
+ priority: basePriority,
1044
+ source
985
1045
  });
986
1046
  }
987
- candidates.sort((a, b) => b.priority - a.priority);
988
- return candidates;
1047
+ if (promptModelPriority !== undefined) {
1048
+ modelCandidates.forEach(c => c.priority += promptModelPriority * 10);
1049
+ }
1050
+ return modelCandidates;
989
1051
  }
990
1052
  createSelectionInfo(data) {
991
1053
  const info = new ai_core_plus_1.AIModelSelectionInfo();
@@ -1282,39 +1344,33 @@ class AIPromptRunner {
1282
1344
  }
1283
1345
  async executeModelWithFailover(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole = 'system', cancellationToken, allCandidates, promptRun, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel) {
1284
1346
  const failoverConfig = this.getFailoverConfiguration(prompt);
1285
- if (failoverConfig.strategy === 'None') {
1347
+ if (!allCandidates || allCandidates.length === 0 || failoverConfig.strategy === 'None') {
1286
1348
  return this.executeModel(model, renderedPrompt, prompt, params, vendorId, conversationMessages, templateMessageRole, cancellationToken, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel);
1287
1349
  }
1288
1350
  const failoverAttempts = [];
1289
1351
  let lastError = null;
1290
- let currentModel = model;
1291
- let currentVendorId = vendorId;
1292
- let attemptNumber = 0;
1293
- if (!allCandidates || allCandidates.length === 0) {
1294
- allCandidates = await this.buildFailoverCandidates(prompt);
1295
- }
1296
- while (attemptNumber <= failoverConfig.maxAttempts) {
1297
- attemptNumber++;
1352
+ for (let i = 0; i < allCandidates.length; i++) {
1353
+ const candidate = allCandidates[i];
1298
1354
  const attemptStartTime = Date.now();
1299
1355
  try {
1300
- if (attemptNumber > 1) {
1301
- const vendorName = currentVendorId
1302
- ? aiengine_1.AIEngine.Instance.Vendors.find(v => v.ID === currentVendorId)?.Name || 'Unknown'
1303
- : 'default';
1356
+ if (i > 0) {
1357
+ const vendorName = candidate.vendorName || 'default';
1304
1358
  (0, core_1.LogStatusEx)({
1305
- message: `🔄 Failover attempt ${attemptNumber} with model ${currentModel.Name} (vendor: ${currentVendorId || 'default'})`,
1359
+ message: `🔄 Trying candidate ${i + 1}/${allCandidates.length}: ${candidate.model.Name} via ${vendorName}`,
1306
1360
  category: 'AI',
1307
1361
  additionalArgs: [{
1308
1362
  promptId: prompt.ID,
1309
- modelId: currentModel.ID,
1310
- vendorId: currentVendorId,
1311
- attemptNumber
1363
+ modelId: candidate.model.ID,
1364
+ model: candidate.model.Name,
1365
+ vendorId: candidate.vendorId,
1366
+ vendor: candidate.vendorName,
1367
+ attemptNumber: i + 1
1312
1368
  }]
1313
1369
  });
1314
1370
  }
1315
- const result = await this.executeModel(currentModel, renderedPrompt, prompt, params, currentVendorId, conversationMessages, templateMessageRole, cancellationToken, vendorDriverClass, vendorApiName, vendorSupportsEffortLevel);
1371
+ const result = await this.executeModel(candidate.model, renderedPrompt, prompt, params, candidate.vendorId || null, conversationMessages, templateMessageRole, cancellationToken, candidate.driverClass, candidate.apiName, candidate.supportsEffortLevel);
1316
1372
  if (failoverAttempts.length > 0 && promptRun) {
1317
- this.updatePromptRunWithFailoverSuccess(promptRun, failoverAttempts, currentModel, currentVendorId);
1373
+ this.updatePromptRunWithFailoverSuccess(promptRun, failoverAttempts, candidate.model, candidate.vendorId || null);
1318
1374
  }
1319
1375
  return result;
1320
1376
  }
@@ -1323,34 +1379,39 @@ class AIPromptRunner {
1323
1379
  lastError = error;
1324
1380
  const errorAnalysis = ai_1.ErrorAnalyzer.analyzeError(lastError);
1325
1381
  const failoverAttempt = {
1326
- attemptNumber,
1327
- modelId: currentModel.ID,
1328
- vendorId: currentVendorId,
1382
+ attemptNumber: i + 1,
1383
+ modelId: candidate.model.ID,
1384
+ vendorId: candidate.vendorId,
1329
1385
  error: lastError,
1330
1386
  errorType: errorAnalysis.errorType,
1331
1387
  duration: attemptDuration,
1332
1388
  timestamp: new Date()
1333
1389
  };
1334
1390
  failoverAttempts.push(failoverAttempt);
1335
- allCandidates = this.filterAuthenticationFailedVendor(errorAnalysis.errorType, currentVendorId, allCandidates);
1336
- const shouldContinueRateLimit = await this.handleRateLimitRetry(errorAnalysis, currentModel, currentVendorId, failoverAttempts, prompt, attemptNumber, failoverConfig.maxAttempts, failoverAttempt);
1337
- if (shouldContinueRateLimit) {
1338
- continue;
1391
+ if (errorAnalysis.errorType === 'Authentication') {
1392
+ allCandidates = this.filterAuthenticationFailedVendor(errorAnalysis.errorType, candidate.vendorId, allCandidates);
1339
1393
  }
1340
- const shouldFailover = this.shouldAttemptFailover(lastError, failoverConfig, attemptNumber);
1341
- if (!shouldFailover) {
1394
+ const isLastCandidate = i === allCandidates.length - 1;
1395
+ if (errorAnalysis.severity === 'Fatal') {
1342
1396
  this.logFailoverAttempt(prompt.ID, failoverAttempt, false);
1343
1397
  break;
1344
1398
  }
1345
- const transitionResult = await this.transitionToNextCandidate(currentModel, currentVendorId, failoverConfig, allCandidates, failoverAttempts, prompt.ID, failoverAttempt, attemptNumber);
1346
- if (!transitionResult) {
1399
+ if (errorAnalysis.errorType === 'InvalidRequest') {
1400
+ this.logFailoverAttempt(prompt.ID, failoverAttempt, false);
1401
+ break;
1402
+ }
1403
+ if (failoverConfig.errorScope && failoverConfig.errorScope !== 'All') {
1404
+ const matchesScope = this.errorMatchesScope(errorAnalysis.errorType, failoverConfig.errorScope);
1405
+ if (!matchesScope) {
1406
+ this.logFailoverAttempt(prompt.ID, failoverAttempt, false);
1407
+ break;
1408
+ }
1409
+ }
1410
+ if (isLastCandidate) {
1411
+ this.logFailoverAttempt(prompt.ID, failoverAttempt, false);
1347
1412
  break;
1348
1413
  }
1349
- currentModel = transitionResult.model;
1350
- currentVendorId = transitionResult.vendorId;
1351
- vendorDriverClass = transitionResult.driverClass;
1352
- vendorApiName = transitionResult.apiName;
1353
- vendorSupportsEffortLevel = transitionResult.supportsEffortLevel;
1414
+ this.logFailoverAttempt(prompt.ID, failoverAttempt, true);
1354
1415
  }
1355
1416
  }
1356
1417
  if (promptRun && failoverAttempts.length > 0) {
@@ -2420,6 +2481,19 @@ class AIPromptRunner {
2420
2481
  return true;
2421
2482
  }
2422
2483
  }
2484
+ errorMatchesScope(errorType, scope) {
2485
+ switch (scope) {
2486
+ case 'NetworkOnly':
2487
+ return errorType === 'NetworkError';
2488
+ case 'RateLimitOnly':
2489
+ return errorType === 'RateLimit';
2490
+ case 'ServiceErrorOnly':
2491
+ return errorType === 'ServiceUnavailable' || errorType === 'InternalServerError';
2492
+ case 'All':
2493
+ default:
2494
+ return true;
2495
+ }
2496
+ }
2423
2497
  calculateFailoverDelay(attemptNumber, baseDelaySeconds) {
2424
2498
  const exponentialDelay = baseDelaySeconds * Math.pow(2, attemptNumber - 1);
2425
2499
  const jitter = exponentialDelay * 0.25 * Math.random();