import get from 'lodash-es/get.js'
import omit from 'lodash-es/omit.js'
import isarr from 'wsemi/src/isarr.mjs'
import isobj from 'wsemi/src/isobj.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import isearr from 'wsemi/src/isearr.mjs'
// resolveProviders.mjs — 把providers定義檔展開為可直接使用的條目
//
// 【為何需要】providers.mjs之條目以envVar間接引用金鑰(機密不落設定檔, 只寫變數名),
// 而dispatchAiFallback只認keys陣列——本函數負責envVar → keys之展開,
// 缺對應環境變數者停用該條目並列入skipped回報(不中斷、不throw),
// 訂閱登入態條目(claude/codex等無envVar)原樣通過。
//
// 【自選】opt.pick給id陣列即可只取用部分條目, 且依pick之順序回傳
// (順序即dispatchAiFallback之優先序); 查無之id列入missing回報。
/**
* 展開providers定義條目:envVar → keys,並可依id自選子集
*
* 特點:
* 條目之envVar依env來源(預設process.env)展開為keys陣列(變數值以逗號分隔多把),envVar欄位自輸出移除;
* 缺對應環境變數(或值為空)之條目停用並列入skipped,不中斷不throw;
* 無envVar之條目(訂閱登入態CLI)原樣通過;條目已自帶有效keys者以keys為準;
* opt.pick可依id自選子集並以pick順序回傳(順序即遞補優先序);
* 輸入陣列與條目皆不被改動(輸出為淺拷貝)
*
* @param {Array} providers 輸入providers條目物件陣列(如providers.mjs之預設匯出)
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Object} [opt.env=process.env] 輸入金鑰來源物件(變數名 → 逗號分隔之金鑰字串),預設process.env
* @param {Array} [opt.pick=null] 輸入自選id字串陣列,依此順序回傳對應條目,查無之id列入missing,預設null代表全取(依原順序)
* @returns {Object} 回傳物件,內含providers(可直接餵dispatchAiFallback之條目陣列)、table(id對條目之物件,可直接餵dispatchAiWkf)、skipped(缺環境變數而停用之{id,envVar}陣列)、missing(pick查無之id字串陣列)
* @example
*
* import resolveProviders from './src/resolveProviders.mjs'
* import providersAll from './src/providers.mjs'
*
* process.loadEnvFile('./.env') //金鑰放.env, 變數值以逗號分隔多把
*
* //全取: envVar展開為keys, 缺環境變數者列入skipped
* let { providers, table, skipped } = resolveProviders(providersAll)
* console.log(providers.length, skipped)
* // => 10 []
*
* //自選: 依pick順序回傳(順序即遞補優先序), 可直接餵dispatchAiFallback
* let r2 = resolveProviders(providersAll, { pick: ['agnes:agnes-2.5-flash', 'claude:sonnet'] })
* console.log(r2.providers.map((p) => p.id))
* // => [ 'agnes:agnes-2.5-flash', 'claude:sonnet' ]
*
* //table可直接餵dispatchAiWkf之providers定義表
* //let wkf = dispatchAiWkf({ providers: r2.table, defaults: { timeoutMs: 1200000 } })
*
*/
function resolveProviders(providers, opt = {}) {
//env, 無效回退process.env(瀏覽器環境無process則空物件)
let env = get(opt, 'env', null)
if (!isobj(env)) {
env = (typeof process !== 'undefined' && process && isobj(process.env)) ? process.env : {}
}
//providersRaw
let providersRaw = isarr(providers) ? providers.filter(isobj) : []
//pick, 依id自選並依pick順序回傳
let pick = get(opt, 'pick', null)
let missing = []
let selected = providersRaw
if (isearr(pick)) {
selected = []
for (let id of pick) {
let entry = providersRaw.find((p) => get(p, 'id', null) === id)
if (entry) {
selected.push(entry)
}
else {
missing.push(String(id))
}
}
}
//展開envVar → keys
let out = []
let skipped = []
for (let entry of selected) {
let envVar = get(entry, 'envVar', null)
//無envVar(訂閱登入態CLI)或已自帶有效keys, 原樣通過(僅移除envVar欄位)
if (!isestr(envVar) || isearr(get(entry, 'keys', null))) {
out.push(omit(entry, ['envVar']))
continue
}
//envVar → keys, 變數值以逗號分隔多把
let keys = String(get(env, envVar, '') || '').split(',').map((k) => k.trim()).filter(Boolean)
if (keys.length === 0) {
//缺環境變數: 停用該條目並回報, 不中斷(設計providers原則§四.4)
skipped.push({ id: get(entry, 'id', ''), envVar })
continue
}
out.push({ ...omit(entry, ['envVar']), keys })
}
//table, id → 條目, 可直接作dispatchAiWkf之providers定義表
let table = {}
for (let entry of out) {
table[entry.id] = entry
}
return { providers: out, table, skipped, missing }
}
export default resolveProviders