@khanglvm/llm-router 2.3.0 → 2.3.2
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.
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/cli/router-module.js +32 -5
- package/src/node/coding-tool-config.js +138 -25
- package/src/node/large-request-log.js +54 -0
- package/src/node/litellm-context-catalog.js +13 -1
- package/src/node/local-server.js +10 -0
- package/src/node/ollama-client.js +195 -0
- package/src/node/ollama-hardware.js +94 -0
- package/src/node/ollama-install.js +230 -0
- package/src/node/provider-probe.js +69 -5
- package/src/node/web-console-client.js +36 -36
- package/src/node/web-console-server.js +478 -8
- package/src/node/web-console-styles.generated.js +1 -1
- package/src/node/web-console-ui/amp-utils.js +272 -0
- package/src/node/web-console-ui/api-client.js +128 -0
- package/src/node/web-console-ui/capability-utils.js +36 -0
- package/src/node/web-console-ui/config-editor-utils.js +20 -5
- package/src/node/web-console-ui/constants.js +140 -0
- package/src/node/web-console-ui/context-window-utils.js +262 -0
- package/src/node/web-console-ui/hooks/use-reorder-layout-animation.js +65 -0
- package/src/node/web-console-ui/provider-presets.js +211 -0
- package/src/node/web-console-ui/quick-start-utils.js +790 -0
- package/src/node/web-console-ui/utils.js +353 -0
- package/src/node/web-console-ui/web-search-utils.js +460 -0
- package/src/runtime/config.js +96 -9
- package/src/runtime/handler/fallback.js +71 -0
- package/src/runtime/handler/field-filter.js +39 -0
- package/src/runtime/handler/large-request-log.js +211 -0
- package/src/runtime/handler/provider-call.js +185 -15
- package/src/runtime/handler/reasoning-effort.js +11 -1
- package/src/runtime/handler/tool-name-sanitizer.js +258 -0
- package/src/runtime/handler.js +16 -3
- package/src/shared/coding-tool-bindings.js +3 -0
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import { QUICK_START_FALLBACK_USER_AGENT, QUICK_START_PROVIDER_ID_PATTERN, QUICK_START_ALIAS_ID_PATTERN, QUICK_START_CONNECTION_CATEGORIES, QUICK_START_DEFAULT_ENDPOINT_BY_PROTOCOL } from "./constants.js";
|
|
2
|
+
import { CODEX_SUBSCRIPTION_MODELS, CLAUDE_CODE_SUBSCRIPTION_MODELS } from "../../runtime/subscription-constants.js";
|
|
3
|
+
import { DEFAULT_MODEL_ALIAS_ID } from "../../runtime/config.js";
|
|
4
|
+
import { buildRateLimitBucketsFromDraftRows, validateRateLimitDraftRows, RATE_LIMIT_ALL_MODELS_SELECTOR } from "./rate-limit-utils.js";
|
|
5
|
+
import { safeClone, looksLikeEnvVarName, slugifyProviderId, createMasterKey, createRateLimitDraftRows, createRateLimitDraftRow, mergeChipValuesAndDraft, resolveRateLimitDraftRows } from "./utils.js";
|
|
6
|
+
import { findPresetByKey, findPresetByHost, PROVIDER_PRESET_BY_KEY, PROVIDER_PRESET_FREE_TIER_RPM_BY_HOST, presetModelCache } from "./provider-presets.js";
|
|
7
|
+
|
|
8
|
+
// ── Header / endpoint utils ──
|
|
9
|
+
|
|
10
|
+
export function getQuickStartDefaultHeaderRows(defaultProviderUserAgent = QUICK_START_FALLBACK_USER_AGENT) {
|
|
11
|
+
return [{ name: "User-Agent", value: String(defaultProviderUserAgent || QUICK_START_FALLBACK_USER_AGENT) }];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function normalizeQuickStartHeaderRows(rows = []) {
|
|
15
|
+
return (Array.isArray(rows) ? rows : []).map((row) => ({
|
|
16
|
+
name: String(row?.name || ""),
|
|
17
|
+
value: row?.value === undefined || row?.value === null ? "" : String(row.value)
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function headerObjectToRows(headers, defaultProviderUserAgent = QUICK_START_FALLBACK_USER_AGENT) {
|
|
22
|
+
if (!headers || typeof headers !== "object" || Array.isArray(headers)) {
|
|
23
|
+
return getQuickStartDefaultHeaderRows(defaultProviderUserAgent);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const rows = Object.entries(headers)
|
|
27
|
+
.map(([name, value]) => ({
|
|
28
|
+
name: String(name || ""),
|
|
29
|
+
value: value === undefined || value === null ? "" : String(value)
|
|
30
|
+
}))
|
|
31
|
+
.filter((row) => row.name.trim());
|
|
32
|
+
|
|
33
|
+
return rows.length > 0 ? rows : getQuickStartDefaultHeaderRows(defaultProviderUserAgent);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function headerRowsToObject(rows = []) {
|
|
37
|
+
const output = {};
|
|
38
|
+
for (const row of normalizeQuickStartHeaderRows(rows)) {
|
|
39
|
+
const name = String(row.name || "").trim();
|
|
40
|
+
if (!name) continue;
|
|
41
|
+
const isUserAgent = name.toLowerCase() === "user-agent";
|
|
42
|
+
const value = String(row.value || "").trim();
|
|
43
|
+
if (!value && !isUserAgent) continue;
|
|
44
|
+
output[name] = value;
|
|
45
|
+
}
|
|
46
|
+
return output;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildQuickStartApiSignature(quickStart = {}) {
|
|
50
|
+
return JSON.stringify({
|
|
51
|
+
connectionType: String(quickStart?.connectionType || ""),
|
|
52
|
+
endpoints: Array.isArray(quickStart?.endpoints) ? quickStart.endpoints.map((entry) => String(entry || "").trim()).filter(Boolean) : [],
|
|
53
|
+
apiKeyEnv: String(quickStart?.apiKeyEnv || "").trim(),
|
|
54
|
+
headers: headerRowsToObject(quickStart?.headerRows || [])
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isProviderReference(value, providerId) {
|
|
59
|
+
return String(value || "").trim().startsWith(`${providerId}/`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function pickFallbackDefaultModel(config) {
|
|
63
|
+
const aliasIds = Object.keys(config?.modelAliases || {});
|
|
64
|
+
if (aliasIds.includes(DEFAULT_MODEL_ALIAS_ID)) return DEFAULT_MODEL_ALIAS_ID;
|
|
65
|
+
if (aliasIds.length > 0) return aliasIds[0];
|
|
66
|
+
const provider = (config?.providers || []).find((entry) => (entry?.models || []).length > 0);
|
|
67
|
+
return provider?.models?.[0]?.id ? `${provider.id}/${provider.models[0].id}` : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Quick start state / config ──
|
|
71
|
+
|
|
72
|
+
export function removeProviderFromConfig(config, providerId) {
|
|
73
|
+
const next = safeClone(config && typeof config === "object" ? config : {});
|
|
74
|
+
next.providers = (Array.isArray(next.providers) ? next.providers : []).filter((provider) => provider?.id !== providerId);
|
|
75
|
+
|
|
76
|
+
const remainingModelRefs = new Set(
|
|
77
|
+
(next.providers || []).flatMap((provider) =>
|
|
78
|
+
(provider.models || []).map((model) => `${provider.id}/${model.id}`)
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const aliases = next.modelAliases && typeof next.modelAliases === "object" ? next.modelAliases : {};
|
|
83
|
+
const nextAliases = {};
|
|
84
|
+
for (const [aliasId, alias] of Object.entries(aliases)) {
|
|
85
|
+
const targets = Array.isArray(alias?.targets)
|
|
86
|
+
? alias.targets.filter((target) => !isProviderReference(target?.ref, providerId))
|
|
87
|
+
: [];
|
|
88
|
+
const fallbackTargets = Array.isArray(alias?.fallbackTargets)
|
|
89
|
+
? alias.fallbackTargets.filter((target) => !isProviderReference(target?.ref, providerId))
|
|
90
|
+
: [];
|
|
91
|
+
|
|
92
|
+
nextAliases[aliasId] = {
|
|
93
|
+
...alias,
|
|
94
|
+
...(Array.isArray(alias?.targets) ? { targets } : {}),
|
|
95
|
+
...(Array.isArray(alias?.fallbackTargets) ? { fallbackTargets } : {})
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
next.modelAliases = nextAliases;
|
|
99
|
+
|
|
100
|
+
if (next.defaultModel && !next.modelAliases?.[next.defaultModel] && !remainingModelRefs.has(next.defaultModel)) {
|
|
101
|
+
next.defaultModel = DEFAULT_MODEL_ALIAS_ID;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (next?.amp?.defaultRoute && !next.modelAliases?.[next.amp.defaultRoute] && !remainingModelRefs.has(next.amp.defaultRoute)) {
|
|
105
|
+
next.amp = {
|
|
106
|
+
...next.amp,
|
|
107
|
+
defaultRoute: next.defaultModel || pickFallbackDefaultModel(next)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return next;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function collectQuickStartEndpoints(provider = {}) {
|
|
115
|
+
const fromMetadata = Array.isArray(provider?.metadata?.endpointCandidates) ? provider.metadata.endpointCandidates : [];
|
|
116
|
+
const fromConfig = [provider?.baseUrl, ...Object.values(provider?.baseUrlByFormat || {})];
|
|
117
|
+
return Array.from(new Set((fromMetadata.length > 0 ? fromMetadata : fromConfig)
|
|
118
|
+
.map((entry) => String(entry || "").trim())
|
|
119
|
+
.filter(Boolean)));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function getStoredProviderCredentialPayload(provider = {}) {
|
|
123
|
+
const apiKeyEnv = String(provider?.apiKeyEnv || "").trim();
|
|
124
|
+
if (apiKeyEnv) return { apiKeyEnv };
|
|
125
|
+
|
|
126
|
+
const apiKey = String(provider?.apiKey || provider?.credential || "").trim();
|
|
127
|
+
return apiKey ? { apiKey } : {};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function getDraftProviderCredentialPayload(draftProvider = {}, provider = {}) {
|
|
131
|
+
if (draftProvider && Object.prototype.hasOwnProperty.call(draftProvider, "credentialInput")) {
|
|
132
|
+
const credentialInput = String(draftProvider?.credentialInput || "").trim();
|
|
133
|
+
if (!credentialInput) return {};
|
|
134
|
+
return looksLikeEnvVarName(credentialInput)
|
|
135
|
+
? { apiKeyEnv: credentialInput }
|
|
136
|
+
: { apiKey: credentialInput };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return getStoredProviderCredentialPayload(provider);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function inferQuickStartConnectionType(provider = {}) {
|
|
143
|
+
if (provider?.type === "subscription") {
|
|
144
|
+
return "subscription";
|
|
145
|
+
}
|
|
146
|
+
return "api";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function inferQuickStartPresetKey(provider = {}) {
|
|
150
|
+
if (provider?.type === "subscription") {
|
|
151
|
+
return provider?.subscriptionType === "claude-code" ? "oauth-claude" : "oauth-gpt";
|
|
152
|
+
}
|
|
153
|
+
const endpoints = collectQuickStartEndpoints(provider);
|
|
154
|
+
for (const ep of endpoints) {
|
|
155
|
+
try {
|
|
156
|
+
const host = new URL(String(ep || "")).hostname;
|
|
157
|
+
const preset = findPresetByHost(host);
|
|
158
|
+
if (preset) return preset.key;
|
|
159
|
+
} catch { /* ignore */ }
|
|
160
|
+
}
|
|
161
|
+
return "custom";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function createProviderInlineDraftState(provider = {}) {
|
|
165
|
+
const endpoints = collectQuickStartEndpoints(provider);
|
|
166
|
+
const connectionType = inferQuickStartConnectionType(provider);
|
|
167
|
+
const presetKey = inferQuickStartPresetKey(provider);
|
|
168
|
+
const rateLimitDefaults = getQuickStartRateLimitDefaults(presetKey);
|
|
169
|
+
return {
|
|
170
|
+
id: String(provider?.id || "").trim(),
|
|
171
|
+
name: String(provider?.name || provider?.id || "").trim(),
|
|
172
|
+
credentialInput: connectionType === "api"
|
|
173
|
+
? String(provider?.apiKeyEnv || provider?.apiKey || provider?.credential || "").trim()
|
|
174
|
+
: "",
|
|
175
|
+
endpoints: connectionType === "api" ? endpoints : [],
|
|
176
|
+
endpointDraft: "",
|
|
177
|
+
rateLimitRows: connectionType === "api"
|
|
178
|
+
? createRateLimitDraftRows(provider?.rateLimits, {
|
|
179
|
+
keyPrefix: `provider-${provider?.id || "new"}-rate-limit`,
|
|
180
|
+
defaults: rateLimitDefaults,
|
|
181
|
+
includeDefault: true
|
|
182
|
+
})
|
|
183
|
+
: []
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function getQuickStartConnectionLabel(presetKey) {
|
|
188
|
+
const preset = findPresetByKey(presetKey);
|
|
189
|
+
return preset.label || "API Key";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function detectPresetHostFromEndpoints(endpoints) {
|
|
193
|
+
for (const ep of (endpoints || [])) {
|
|
194
|
+
try {
|
|
195
|
+
const host = new URL(String(ep || "")).hostname;
|
|
196
|
+
if (PROVIDER_PRESET_FREE_TIER_RPM_BY_HOST[host]) return host;
|
|
197
|
+
} catch { /* ignore */ }
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function buildPresetFreeTierRateLimitRows(presetHost, modelIds) {
|
|
203
|
+
const limits = PROVIDER_PRESET_FREE_TIER_RPM_BY_HOST[presetHost];
|
|
204
|
+
if (!limits || !Array.isArray(modelIds) || modelIds.length === 0) return null;
|
|
205
|
+
const defaultLimit = limits._default || 30;
|
|
206
|
+
return modelIds.map((modelId, index) => createRateLimitDraftRow({
|
|
207
|
+
models: [String(modelId)],
|
|
208
|
+
requests: limits[modelId] || defaultLimit,
|
|
209
|
+
window: { size: 1, unit: "minute" }
|
|
210
|
+
}, { keyPrefix: `preset-rl`, index }));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function pickFreeTierProbeModels(modelIds) {
|
|
214
|
+
const tiers = new Map();
|
|
215
|
+
for (const id of modelIds) {
|
|
216
|
+
const lower = id.toLowerCase();
|
|
217
|
+
const tier = lower.includes("flash-lite") ? "flash-lite"
|
|
218
|
+
: lower.includes("flash") ? "flash"
|
|
219
|
+
: lower.includes("pro") ? "pro"
|
|
220
|
+
: lower;
|
|
221
|
+
if (!tiers.has(tier)) tiers.set(tier, id);
|
|
222
|
+
}
|
|
223
|
+
return [...tiers.values()];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function getQuickStartSuggestedModelIds(presetKey, protocol = "openai") {
|
|
227
|
+
const cached = presetModelCache.get(presetKey);
|
|
228
|
+
if (cached?.length) return [...cached];
|
|
229
|
+
const preset = findPresetByKey(presetKey);
|
|
230
|
+
const models = preset.defaultModels;
|
|
231
|
+
if (models && typeof models === "object" && !Array.isArray(models)) {
|
|
232
|
+
return [...(models[protocol] || models.openai || [])];
|
|
233
|
+
}
|
|
234
|
+
return Array.isArray(models) ? [...models] : [];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function getQuickStartRateLimitDefaults(presetKey) {
|
|
238
|
+
const preset = findPresetByKey(presetKey);
|
|
239
|
+
return preset.rateLimitDefaults || PROVIDER_PRESET_BY_KEY.custom.rateLimitDefaults;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function deduplicateProviderId(baseId, baseName, existingProviders = []) {
|
|
243
|
+
const ids = new Set((existingProviders || []).map((p) => p?.id).filter(Boolean));
|
|
244
|
+
if (!ids.has(baseId)) return { providerId: baseId, providerName: baseName };
|
|
245
|
+
let n = 2;
|
|
246
|
+
while (ids.has(`${baseId}-${n}`)) n++;
|
|
247
|
+
return { providerId: `${baseId}-${n}`, providerName: `${baseName} ${n}` };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function getQuickStartConnectionDefaults(presetKey, protocol = "openai") {
|
|
251
|
+
const preset = findPresetByKey(presetKey);
|
|
252
|
+
const rateLimitDefaults = getQuickStartRateLimitDefaults(presetKey);
|
|
253
|
+
const isApi = preset.category === "api";
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
providerName: preset.providerName,
|
|
257
|
+
providerId: preset.providerId,
|
|
258
|
+
endpoints: isApi && preset.endpoint ? [preset.endpoint] : [],
|
|
259
|
+
apiKeyEnv: isApi ? (preset.apiKeyEnv || "") : "",
|
|
260
|
+
subscriptionProfile: isApi ? "" : "",
|
|
261
|
+
modelIds: presetKey === "custom" ? [] : getQuickStartSuggestedModelIds(presetKey, protocol),
|
|
262
|
+
rateLimitRows: isApi
|
|
263
|
+
? createRateLimitDraftRows([], {
|
|
264
|
+
keyPrefix: `quick-start-${presetKey}-rate-limit`,
|
|
265
|
+
defaults: rateLimitDefaults,
|
|
266
|
+
includeDefault: true
|
|
267
|
+
})
|
|
268
|
+
: []
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function applyQuickStartConnectionPreset(current = {}, {
|
|
273
|
+
baseConfig = {},
|
|
274
|
+
nextCategory,
|
|
275
|
+
nextPresetKey,
|
|
276
|
+
defaultProviderUserAgent = QUICK_START_FALLBACK_USER_AGENT
|
|
277
|
+
} = {}) {
|
|
278
|
+
const preset = findPresetByKey(nextPresetKey);
|
|
279
|
+
const isApi = nextCategory === "api";
|
|
280
|
+
const currentPresetKey = current.selectedConnection || "custom";
|
|
281
|
+
const currentPreset = findPresetByKey(currentPresetKey);
|
|
282
|
+
const currentDefaults = getQuickStartConnectionDefaults(currentPresetKey);
|
|
283
|
+
const existingProviders = Array.isArray(baseConfig?.providers) ? baseConfig.providers : [];
|
|
284
|
+
const deduped = deduplicateProviderId(preset.providerId, preset.providerName, existingProviders);
|
|
285
|
+
const currentHeaderDefaults = current.connectionType === "api"
|
|
286
|
+
? getQuickStartDefaultHeaderRows(defaultProviderUserAgent)
|
|
287
|
+
: [];
|
|
288
|
+
const nextHeaderDefaults = isApi
|
|
289
|
+
? getQuickStartDefaultHeaderRows(defaultProviderUserAgent)
|
|
290
|
+
: [];
|
|
291
|
+
const currentHeaderRows = normalizeQuickStartHeaderRows(current.headerRows || []);
|
|
292
|
+
const providerIdWasAuto = !current.providerId
|
|
293
|
+
|| current.providerId === currentPreset.providerId
|
|
294
|
+
|| current.providerId === currentDefaults.providerId
|
|
295
|
+
|| current.providerId === slugifyProviderId(current.providerName || "");
|
|
296
|
+
const providerNameWasAuto = !current.providerName
|
|
297
|
+
|| current.providerName === currentPreset.providerName
|
|
298
|
+
|| current.providerName === currentDefaults.providerName;
|
|
299
|
+
const profileWasAuto = !current.subscriptionProfile || current.subscriptionProfile === currentDefaults.subscriptionProfile;
|
|
300
|
+
const currentDefaultModels = Array.isArray(currentPreset.defaultModels) ? currentPreset.defaultModels : [];
|
|
301
|
+
const modelsWereDefault = (current.modelIds || []).length === 0
|
|
302
|
+
|| JSON.stringify(current.modelIds || []) === JSON.stringify(currentDefaultModels)
|
|
303
|
+
|| JSON.stringify(current.modelIds || []) === JSON.stringify(currentDefaults.modelIds || []);
|
|
304
|
+
const headerRowsWereDefault = JSON.stringify(currentHeaderRows) === JSON.stringify(currentHeaderDefaults);
|
|
305
|
+
const currentEndpoints = mergeChipValuesAndDraft(current.endpoints, current.endpointDraft);
|
|
306
|
+
const currentDefaultEndpoints = Array.isArray(currentDefaults.endpoints) ? currentDefaults.endpoints : [];
|
|
307
|
+
const endpointsWereAuto = currentEndpoints.length === 0
|
|
308
|
+
|| JSON.stringify(currentEndpoints) === JSON.stringify(currentDefaultEndpoints);
|
|
309
|
+
const currentApiKeyEnv = String(current.apiKeyEnv || "").trim();
|
|
310
|
+
const apiKeyEnvWasAuto = !currentApiKeyEnv
|
|
311
|
+
|| currentApiKeyEnv === String(currentPreset.apiKeyEnv || "").trim()
|
|
312
|
+
|| currentApiKeyEnv === String(currentDefaults.apiKeyEnv || "").trim();
|
|
313
|
+
const presetModels = Array.isArray(preset.defaultModels)
|
|
314
|
+
? [...preset.defaultModels]
|
|
315
|
+
: [];
|
|
316
|
+
const nextDefaults = getQuickStartConnectionDefaults(nextPresetKey);
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
...current,
|
|
320
|
+
connectionType: nextCategory,
|
|
321
|
+
selectedConnection: nextPresetKey,
|
|
322
|
+
providerName: providerNameWasAuto ? deduped.providerName : current.providerName,
|
|
323
|
+
providerId: providerIdWasAuto ? deduped.providerId : current.providerId,
|
|
324
|
+
endpoints: isApi
|
|
325
|
+
? (endpointsWereAuto ? [...nextDefaults.endpoints] : (Array.isArray(current.endpoints) ? current.endpoints : []))
|
|
326
|
+
: [],
|
|
327
|
+
endpointDraft: isApi
|
|
328
|
+
? (endpointsWereAuto ? "" : String(current.endpointDraft || ""))
|
|
329
|
+
: "",
|
|
330
|
+
apiKeyEnv: isApi
|
|
331
|
+
? (apiKeyEnvWasAuto ? (preset.apiKeyEnv || "") : current.apiKeyEnv)
|
|
332
|
+
: "",
|
|
333
|
+
subscriptionProfile: isApi
|
|
334
|
+
? ""
|
|
335
|
+
: (profileWasAuto ? nextDefaults.subscriptionProfile : current.subscriptionProfile),
|
|
336
|
+
modelIds: modelsWereDefault ? (presetModels.length > 0 ? presetModels : nextDefaults.modelIds) : current.modelIds,
|
|
337
|
+
modelContextWindows: modelsWereDefault ? {} : (current.modelContextWindows || {}),
|
|
338
|
+
modelDraft: "",
|
|
339
|
+
rateLimitRows: isApi
|
|
340
|
+
? createRateLimitDraftRows([], {
|
|
341
|
+
keyPrefix: `quick-start-${nextPresetKey}-rate-limit`,
|
|
342
|
+
defaults: preset.rateLimitDefaults || PROVIDER_PRESET_BY_KEY.custom.rateLimitDefaults,
|
|
343
|
+
includeDefault: true
|
|
344
|
+
})
|
|
345
|
+
: [],
|
|
346
|
+
headerRows: isApi
|
|
347
|
+
? ((currentHeaderRows.length === 0 || headerRowsWereDefault) ? nextHeaderDefaults : currentHeaderRows)
|
|
348
|
+
: []
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function findQuickStartAliasEntry(baseConfig = {}, providerId = "", { aliasId = "" } = {}) {
|
|
353
|
+
if (!providerId) return null;
|
|
354
|
+
const aliases = baseConfig?.modelAliases && typeof baseConfig.modelAliases === "object" && !Array.isArray(baseConfig.modelAliases)
|
|
355
|
+
? baseConfig.modelAliases
|
|
356
|
+
: {};
|
|
357
|
+
const entries = aliasId
|
|
358
|
+
? [[aliasId, aliases[aliasId]]]
|
|
359
|
+
: Object.entries(aliases);
|
|
360
|
+
|
|
361
|
+
for (const [candidateAliasId, alias] of entries) {
|
|
362
|
+
if (!alias || typeof alias !== "object") continue;
|
|
363
|
+
const targets = Array.isArray(alias?.targets) ? alias.targets : [];
|
|
364
|
+
const fallbackTargets = Array.isArray(alias?.fallbackTargets) ? alias.fallbackTargets : [];
|
|
365
|
+
if ([...targets, ...fallbackTargets].some((target) => isProviderReference(target?.ref, providerId))) {
|
|
366
|
+
return { aliasId: candidateAliasId, alias };
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function getQuickStartAliasTargetModelIds(aliasEntry, providerId = "", fallbackModelIds = []) {
|
|
373
|
+
const normalizedFallbackModelIds = Array.from(new Set((fallbackModelIds || []).map((modelId) => String(modelId || "").trim()).filter(Boolean)));
|
|
374
|
+
if (!providerId || !aliasEntry?.alias) return normalizedFallbackModelIds;
|
|
375
|
+
|
|
376
|
+
const allowedModelIds = new Set(normalizedFallbackModelIds);
|
|
377
|
+
const aliasTargets = Array.isArray(aliasEntry.alias?.targets) ? aliasEntry.alias.targets : [];
|
|
378
|
+
const orderedAliasModelIds = [];
|
|
379
|
+
|
|
380
|
+
for (const target of aliasTargets) {
|
|
381
|
+
const ref = String(target?.ref || "").trim();
|
|
382
|
+
if (!isProviderReference(ref, providerId)) continue;
|
|
383
|
+
const modelId = ref.slice(providerId.length + 1);
|
|
384
|
+
if (!allowedModelIds.has(modelId) || orderedAliasModelIds.includes(modelId)) continue;
|
|
385
|
+
orderedAliasModelIds.push(modelId);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return orderedAliasModelIds.length > 0 ? orderedAliasModelIds : normalizedFallbackModelIds;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function syncQuickStartAliasModelIds(aliasModelIds = [], modelIds = []) {
|
|
392
|
+
const normalizedModelIds = Array.from(new Set((modelIds || []).map((modelId) => String(modelId || "").trim()).filter(Boolean)));
|
|
393
|
+
const remaining = new Set(normalizedModelIds);
|
|
394
|
+
const ordered = [];
|
|
395
|
+
|
|
396
|
+
for (const modelId of (aliasModelIds || []).map((entry) => String(entry || "").trim()).filter(Boolean)) {
|
|
397
|
+
if (!remaining.has(modelId)) continue;
|
|
398
|
+
ordered.push(modelId);
|
|
399
|
+
remaining.delete(modelId);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return [
|
|
403
|
+
...ordered,
|
|
404
|
+
...normalizedModelIds.filter((modelId) => remaining.has(modelId))
|
|
405
|
+
];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function rewriteQuickStartAliasTarget(target, { fromProviderId, toProviderId, allowedModelIds }) {
|
|
409
|
+
if (!target || typeof target !== "object") return target;
|
|
410
|
+
const ref = String(target.ref || "").trim();
|
|
411
|
+
if (!fromProviderId || !isProviderReference(ref, fromProviderId)) return target;
|
|
412
|
+
const modelId = ref.slice(fromProviderId.length + 1);
|
|
413
|
+
if (!allowedModelIds.has(modelId)) return null;
|
|
414
|
+
return {
|
|
415
|
+
...target,
|
|
416
|
+
ref: `${toProviderId}/${modelId}`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function rewriteQuickStartAlias(alias, options) {
|
|
421
|
+
if (!alias || typeof alias !== "object") return alias;
|
|
422
|
+
const nextAlias = { ...alias };
|
|
423
|
+
const hasTargets = Array.isArray(alias.targets);
|
|
424
|
+
const hasFallbackTargets = Array.isArray(alias.fallbackTargets);
|
|
425
|
+
|
|
426
|
+
if (hasTargets) {
|
|
427
|
+
nextAlias.targets = alias.targets
|
|
428
|
+
.map((target) => rewriteQuickStartAliasTarget(target, options))
|
|
429
|
+
.filter(Boolean);
|
|
430
|
+
}
|
|
431
|
+
if (hasFallbackTargets) {
|
|
432
|
+
nextAlias.fallbackTargets = alias.fallbackTargets
|
|
433
|
+
.map((target) => rewriteQuickStartAliasTarget(target, options))
|
|
434
|
+
.filter(Boolean);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return nextAlias;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function rewriteQuickStartProviderRef(value, { fromProviderId, toProviderId, allowedModelIds }) {
|
|
441
|
+
const ref = String(value || "").trim();
|
|
442
|
+
if (!ref || !fromProviderId || !isProviderReference(ref, fromProviderId)) return ref;
|
|
443
|
+
const modelId = ref.slice(fromProviderId.length + 1);
|
|
444
|
+
if (!allowedModelIds.has(modelId)) return "";
|
|
445
|
+
return `${toProviderId}/${modelId}`;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export function collectQuickStartProviderRefs(providers = []) {
|
|
449
|
+
return new Set(
|
|
450
|
+
(providers || []).flatMap((provider) =>
|
|
451
|
+
(provider?.models || []).map((model) => `${provider.id}/${model.id}`)
|
|
452
|
+
)
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export function createQuickStartState(baseConfig = {}, { seedMode = "blank", targetProviderId = "", defaultProviderUserAgent = QUICK_START_FALLBACK_USER_AGENT } = {}) {
|
|
457
|
+
const providerList = Array.isArray(baseConfig?.providers) ? baseConfig.providers : [];
|
|
458
|
+
const targetedProvider = targetProviderId
|
|
459
|
+
? providerList.find((entry) => entry?.id === targetProviderId) || null
|
|
460
|
+
: null;
|
|
461
|
+
const useExistingProvider = seedMode === "existing" || Boolean(targetedProvider);
|
|
462
|
+
const provider = useExistingProvider ? (targetedProvider || providerList[0] || {}) : {};
|
|
463
|
+
const connectionType = useExistingProvider ? inferQuickStartConnectionType(provider) : "api";
|
|
464
|
+
const presetKey = useExistingProvider ? inferQuickStartPresetKey(provider) : "custom";
|
|
465
|
+
const protocol = provider?.format === "claude" ? "claude" : "openai";
|
|
466
|
+
const defaults = getQuickStartConnectionDefaults(presetKey, protocol);
|
|
467
|
+
const rateLimitDefaults = getQuickStartRateLimitDefaults(presetKey);
|
|
468
|
+
const providerModels = Array.isArray(provider?.models)
|
|
469
|
+
? provider.models.map((model) => model?.id).filter(Boolean)
|
|
470
|
+
: [];
|
|
471
|
+
const resolvedProviderId = String(provider?.id || defaults.providerId || slugifyProviderId(provider?.name || defaults.providerName || "my-provider") || "my-provider");
|
|
472
|
+
const aliasEntry = useExistingProvider
|
|
473
|
+
? findQuickStartAliasEntry(baseConfig, resolvedProviderId, { aliasId: DEFAULT_MODEL_ALIAS_ID })
|
|
474
|
+
: null;
|
|
475
|
+
const resolvedModelIds = providerModels.length > 0 ? providerModels : [...defaults.modelIds];
|
|
476
|
+
const modelContextWindows = Object.fromEntries(
|
|
477
|
+
(Array.isArray(provider?.models) ? provider.models : [])
|
|
478
|
+
.map((model) => {
|
|
479
|
+
const modelId = String(model?.id || "").trim();
|
|
480
|
+
const contextWindow = Number(model?.contextWindow);
|
|
481
|
+
if (!modelId || !Number.isFinite(contextWindow) || contextWindow <= 0) return null;
|
|
482
|
+
return [modelId, Math.floor(contextWindow)];
|
|
483
|
+
})
|
|
484
|
+
.filter(Boolean)
|
|
485
|
+
);
|
|
486
|
+
const headerRows = connectionType === "api"
|
|
487
|
+
? headerObjectToRows(provider?.headers, defaultProviderUserAgent)
|
|
488
|
+
: [];
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
connectionType,
|
|
492
|
+
selectedConnection: presetKey,
|
|
493
|
+
providerName: String(provider?.name || defaults.providerName),
|
|
494
|
+
providerId: resolvedProviderId,
|
|
495
|
+
endpoints: collectQuickStartEndpoints(provider).length > 0 ? collectQuickStartEndpoints(provider) : defaults.endpoints,
|
|
496
|
+
endpointDraft: "",
|
|
497
|
+
apiKeyEnv: String(provider?.apiKeyEnv || provider?.apiKey || defaults.apiKeyEnv),
|
|
498
|
+
subscriptionProfile: String(provider?.subscriptionProfile || defaults.subscriptionProfile),
|
|
499
|
+
modelIds: resolvedModelIds,
|
|
500
|
+
modelContextWindows,
|
|
501
|
+
modelDraft: "",
|
|
502
|
+
aliasModelIds: getQuickStartAliasTargetModelIds(aliasEntry, resolvedProviderId, resolvedModelIds),
|
|
503
|
+
headerRows,
|
|
504
|
+
rateLimitRows: connectionType === "api"
|
|
505
|
+
? createRateLimitDraftRows(provider?.rateLimits, {
|
|
506
|
+
keyPrefix: `quick-start-${resolvedProviderId}-rate-limit`,
|
|
507
|
+
defaults: rateLimitDefaults,
|
|
508
|
+
includeDefault: true
|
|
509
|
+
})
|
|
510
|
+
: [],
|
|
511
|
+
enableAlias: true,
|
|
512
|
+
aliasId: DEFAULT_MODEL_ALIAS_ID,
|
|
513
|
+
sourceAliasId: aliasEntry?.aliasId || "",
|
|
514
|
+
useAliasAsDefault: useExistingProvider ? Boolean(aliasEntry) : true
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function buildQuickStartModelEntries(modelIds, modelPreferredFormat = {}, modelContextWindows = {}) {
|
|
519
|
+
return (modelIds || []).map((id) => {
|
|
520
|
+
const preferred = modelPreferredFormat[id];
|
|
521
|
+
const contextWindow = Number(modelContextWindows?.[id]);
|
|
522
|
+
return {
|
|
523
|
+
id,
|
|
524
|
+
...(preferred ? { formats: [preferred] } : {}),
|
|
525
|
+
...(Number.isFinite(contextWindow) && contextWindow > 0 ? { contextWindow: Math.floor(contextWindow) } : {})
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export function resolveQuickStartSubscriptionProfile(quickStart = {}) {
|
|
531
|
+
const providerId = slugifyProviderId(quickStart?.providerId || quickStart?.providerName || "") || "";
|
|
532
|
+
return String(quickStart?.subscriptionProfile || providerId || "default").trim() || providerId || "default";
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export function buildQuickStartConfig(baseConfig = {}, quickStart, testedProviderConfig = null, { targetProviderId = "" } = {}) {
|
|
536
|
+
const next = safeClone(baseConfig && typeof baseConfig === "object" ? baseConfig : {});
|
|
537
|
+
const providerId = slugifyProviderId(quickStart?.providerId || quickStart?.providerName || "my-provider") || "my-provider";
|
|
538
|
+
const providerName = String(quickStart?.providerName || providerId).trim() || providerId;
|
|
539
|
+
const modelIds = Array.isArray(quickStart?.modelIds) ? quickStart.modelIds.map((id) => String(id || "").trim()).filter(Boolean) : [];
|
|
540
|
+
const orderedModelIds = syncQuickStartAliasModelIds(quickStart?.aliasModelIds, modelIds);
|
|
541
|
+
const effectiveRateLimitDefaults = getQuickStartRateLimitDefaults(quickStart?.selectedConnection || quickStart?.connectionType);
|
|
542
|
+
const resolvedRateLimitRows = Array.isArray(quickStart?.rateLimitRows)
|
|
543
|
+
? quickStart.rateLimitRows
|
|
544
|
+
: [];
|
|
545
|
+
const effectiveApiRateLimitRows = resolvedRateLimitRows.length > 0
|
|
546
|
+
? resolvedRateLimitRows
|
|
547
|
+
: [{
|
|
548
|
+
models: [RATE_LIMIT_ALL_MODELS_SELECTOR],
|
|
549
|
+
requests: effectiveRateLimitDefaults.limit,
|
|
550
|
+
windowValue: effectiveRateLimitDefaults.windowValue,
|
|
551
|
+
windowUnit: effectiveRateLimitDefaults.windowUnit
|
|
552
|
+
}];
|
|
553
|
+
const hadProviders = Array.isArray(baseConfig?.providers) && baseConfig.providers.length > 0;
|
|
554
|
+
const sourceProviderId = String(targetProviderId || "").trim();
|
|
555
|
+
const sourceAliasId = String(quickStart?.sourceAliasId || "").trim();
|
|
556
|
+
let provider;
|
|
557
|
+
|
|
558
|
+
if (quickStart?.connectionType === "api") {
|
|
559
|
+
const endpoints = Array.isArray(quickStart?.endpoints) ? quickStart.endpoints.map((entry) => String(entry || "").trim()).filter(Boolean) : [];
|
|
560
|
+
const workingFormats = Array.isArray(testedProviderConfig?.workingFormats)
|
|
561
|
+
? testedProviderConfig.workingFormats.filter(Boolean)
|
|
562
|
+
: [];
|
|
563
|
+
const preferredFormat = testedProviderConfig?.preferredFormat || workingFormats[0] || "openai";
|
|
564
|
+
const baseUrlByFormat = testedProviderConfig?.baseUrlByFormat && typeof testedProviderConfig.baseUrlByFormat === "object"
|
|
565
|
+
? testedProviderConfig.baseUrlByFormat
|
|
566
|
+
: (endpoints[0] ? { [preferredFormat]: endpoints[0] } : undefined);
|
|
567
|
+
const baseUrl = (preferredFormat && baseUrlByFormat?.[preferredFormat])
|
|
568
|
+
|| baseUrlByFormat?.openai
|
|
569
|
+
|| baseUrlByFormat?.claude
|
|
570
|
+
|| endpoints[0]
|
|
571
|
+
|| "";
|
|
572
|
+
const confirmedModelIds = Array.isArray(testedProviderConfig?.models) && testedProviderConfig.models.length > 0
|
|
573
|
+
? orderedModelIds.filter((id) => testedProviderConfig.models.includes(id))
|
|
574
|
+
: orderedModelIds;
|
|
575
|
+
const effectiveModelIds = confirmedModelIds.length > 0 ? confirmedModelIds : orderedModelIds;
|
|
576
|
+
const providerMetadata = endpoints.length > 1 ? { endpointCandidates: endpoints } : undefined;
|
|
577
|
+
|
|
578
|
+
const credentialInput = String(quickStart?.apiKeyEnv || "").trim();
|
|
579
|
+
const providerCredential = looksLikeEnvVarName(credentialInput)
|
|
580
|
+
? { apiKeyEnv: credentialInput }
|
|
581
|
+
: (credentialInput ? { apiKey: credentialInput } : {});
|
|
582
|
+
const customHeaders = headerRowsToObject(quickStart?.headerRows || []);
|
|
583
|
+
|
|
584
|
+
provider = {
|
|
585
|
+
id: providerId,
|
|
586
|
+
name: providerName,
|
|
587
|
+
baseUrl,
|
|
588
|
+
baseUrlByFormat,
|
|
589
|
+
...providerCredential,
|
|
590
|
+
...(Object.keys(customHeaders).length > 0 ? { headers: customHeaders } : {}),
|
|
591
|
+
format: preferredFormat,
|
|
592
|
+
formats: workingFormats.length > 0 ? workingFormats : [preferredFormat],
|
|
593
|
+
models: buildQuickStartModelEntries(
|
|
594
|
+
effectiveModelIds,
|
|
595
|
+
testedProviderConfig?.modelPreferredFormat || {},
|
|
596
|
+
quickStart?.modelContextWindows || {}
|
|
597
|
+
),
|
|
598
|
+
...(providerMetadata ? { metadata: providerMetadata } : {})
|
|
599
|
+
};
|
|
600
|
+
} else {
|
|
601
|
+
const preset = findPresetByKey(quickStart?.selectedConnection || "oauth-gpt");
|
|
602
|
+
const subscriptionType = preset.subscriptionType || "chatgpt-codex";
|
|
603
|
+
const providerFormat = preset.format || "openai";
|
|
604
|
+
|
|
605
|
+
provider = {
|
|
606
|
+
id: providerId,
|
|
607
|
+
name: providerName,
|
|
608
|
+
type: "subscription",
|
|
609
|
+
subscriptionType,
|
|
610
|
+
subscriptionProfile: resolveQuickStartSubscriptionProfile(quickStart),
|
|
611
|
+
format: providerFormat,
|
|
612
|
+
formats: [providerFormat],
|
|
613
|
+
models: buildQuickStartModelEntries(orderedModelIds, {}, quickStart?.modelContextWindows || {})
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
provider.rateLimits = quickStart?.connectionType === "api"
|
|
618
|
+
? buildRateLimitBucketsFromDraftRows(effectiveApiRateLimitRows, {
|
|
619
|
+
fallbackRequests: effectiveRateLimitDefaults.limit,
|
|
620
|
+
fallbackWindowValue: effectiveRateLimitDefaults.windowValue,
|
|
621
|
+
fallbackWindowUnit: effectiveRateLimitDefaults.windowUnit
|
|
622
|
+
})
|
|
623
|
+
: buildRateLimitBucketsFromDraftRows([{
|
|
624
|
+
models: [RATE_LIMIT_ALL_MODELS_SELECTOR],
|
|
625
|
+
requests: effectiveRateLimitDefaults.limit,
|
|
626
|
+
windowValue: effectiveRateLimitDefaults.windowValue,
|
|
627
|
+
windowUnit: effectiveRateLimitDefaults.windowUnit
|
|
628
|
+
}], {
|
|
629
|
+
fallbackRequests: effectiveRateLimitDefaults.limit,
|
|
630
|
+
fallbackWindowValue: effectiveRateLimitDefaults.windowValue,
|
|
631
|
+
fallbackWindowUnit: effectiveRateLimitDefaults.windowUnit
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
if (!String(next.masterKey || "").trim()) {
|
|
635
|
+
next.masterKey = createMasterKey();
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
next.version = typeof next.version === "number" ? next.version : 2;
|
|
639
|
+
|
|
640
|
+
const existingProviders = Array.isArray(next.providers) ? next.providers : [];
|
|
641
|
+
let providerIndex = sourceProviderId
|
|
642
|
+
? existingProviders.findIndex((entry) => entry?.id === sourceProviderId)
|
|
643
|
+
: -1;
|
|
644
|
+
if (providerIndex === -1) {
|
|
645
|
+
providerIndex = existingProviders.findIndex((entry) => entry?.id === providerId);
|
|
646
|
+
}
|
|
647
|
+
next.providers = providerIndex === -1
|
|
648
|
+
? [...existingProviders, provider]
|
|
649
|
+
: existingProviders.map((entry, index) => (index === providerIndex ? provider : entry));
|
|
650
|
+
|
|
651
|
+
const existingAliases = next.modelAliases && typeof next.modelAliases === "object" && !Array.isArray(next.modelAliases)
|
|
652
|
+
? next.modelAliases
|
|
653
|
+
: {};
|
|
654
|
+
const allowedModelIds = new Set((provider.models || []).map((model) => model.id));
|
|
655
|
+
const shouldManageDefaultAlias = !hadProviders || quickStart?.useAliasAsDefault === true;
|
|
656
|
+
const nextAliases = {};
|
|
657
|
+
|
|
658
|
+
for (const [aliasId, alias] of Object.entries(existingAliases)) {
|
|
659
|
+
const rewrittenAlias = sourceProviderId
|
|
660
|
+
? rewriteQuickStartAlias(alias, { fromProviderId: sourceProviderId, toProviderId: providerId, allowedModelIds })
|
|
661
|
+
: alias;
|
|
662
|
+
nextAliases[aliasId] = rewrittenAlias;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const primaryRef = provider.models?.[0]?.id ? `${providerId}/${provider.models[0].id}` : "";
|
|
666
|
+
const aliasTargetModelIds = (orderedModelIds.length > 0 ? orderedModelIds : (provider.models || []).map((model) => model.id))
|
|
667
|
+
.filter((modelId) => allowedModelIds.has(modelId));
|
|
668
|
+
const defaultAliasId = DEFAULT_MODEL_ALIAS_ID;
|
|
669
|
+
const existingDefaultAlias = nextAliases[defaultAliasId] && typeof nextAliases[defaultAliasId] === "object"
|
|
670
|
+
? nextAliases[defaultAliasId]
|
|
671
|
+
: { id: defaultAliasId, strategy: "ordered", targets: [], fallbackTargets: [] };
|
|
672
|
+
nextAliases[defaultAliasId] = shouldManageDefaultAlias
|
|
673
|
+
? {
|
|
674
|
+
...existingDefaultAlias,
|
|
675
|
+
id: defaultAliasId,
|
|
676
|
+
strategy: "ordered",
|
|
677
|
+
targets: aliasTargetModelIds.map((modelId) => ({ ref: `${providerId}/${modelId}` })),
|
|
678
|
+
fallbackTargets: []
|
|
679
|
+
}
|
|
680
|
+
: {
|
|
681
|
+
...existingDefaultAlias,
|
|
682
|
+
id: defaultAliasId,
|
|
683
|
+
strategy: String(existingDefaultAlias.strategy || "ordered").trim() || "ordered",
|
|
684
|
+
targets: Array.isArray(existingDefaultAlias.targets) ? existingDefaultAlias.targets : [],
|
|
685
|
+
fallbackTargets: Array.isArray(existingDefaultAlias.fallbackTargets) ? existingDefaultAlias.fallbackTargets : []
|
|
686
|
+
};
|
|
687
|
+
next.modelAliases = nextAliases;
|
|
688
|
+
|
|
689
|
+
const remainingModelRefs = collectQuickStartProviderRefs(next.providers);
|
|
690
|
+
next.defaultModel = DEFAULT_MODEL_ALIAS_ID;
|
|
691
|
+
|
|
692
|
+
if (!next.amp || typeof next.amp !== "object" || Array.isArray(next.amp)) {
|
|
693
|
+
next.amp = { restrictManagementToLocalhost: true, overrides: { entities: [] } };
|
|
694
|
+
}
|
|
695
|
+
if (next.amp.restrictManagementToLocalhost === undefined) {
|
|
696
|
+
next.amp.restrictManagementToLocalhost = true;
|
|
697
|
+
}
|
|
698
|
+
if (!next.amp.overrides || typeof next.amp.overrides !== "object" || Array.isArray(next.amp.overrides)) {
|
|
699
|
+
next.amp.overrides = { entities: [] };
|
|
700
|
+
}
|
|
701
|
+
if (!Array.isArray(next.amp.overrides.entities)) {
|
|
702
|
+
next.amp.overrides.entities = [];
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
let nextAmpDefaultRoute = String(next.amp.defaultRoute || "").trim();
|
|
706
|
+
if (sourceProviderId) {
|
|
707
|
+
nextAmpDefaultRoute = rewriteQuickStartProviderRef(nextAmpDefaultRoute, {
|
|
708
|
+
fromProviderId: sourceProviderId,
|
|
709
|
+
toProviderId: providerId,
|
|
710
|
+
allowedModelIds
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
if (shouldManageDefaultAlias || !nextAmpDefaultRoute || (!next.modelAliases?.[nextAmpDefaultRoute] && !remainingModelRefs.has(nextAmpDefaultRoute))) {
|
|
714
|
+
nextAmpDefaultRoute = DEFAULT_MODEL_ALIAS_ID;
|
|
715
|
+
}
|
|
716
|
+
if (nextAmpDefaultRoute) {
|
|
717
|
+
next.amp.defaultRoute = nextAmpDefaultRoute;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (!next.metadata || typeof next.metadata !== "object" || Array.isArray(next.metadata)) {
|
|
721
|
+
next.metadata = {};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return next;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export function hasCompletedProviderSetup(config = {}) {
|
|
728
|
+
const providers = Array.isArray(config?.providers) ? config.providers : [];
|
|
729
|
+
return providers.some((provider) => {
|
|
730
|
+
if (provider?.enabled === false) return false;
|
|
731
|
+
const models = Array.isArray(provider?.models)
|
|
732
|
+
? provider.models.filter((model) => String(model?.id || "").trim())
|
|
733
|
+
: [];
|
|
734
|
+
const rateLimits = Array.isArray(provider?.rateLimits) ? provider.rateLimits : [];
|
|
735
|
+
return models.length > 0 && rateLimits.length > 0;
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export function getQuickStartStepError(stepIndex, quickStart, baseConfig = {}, { targetProviderId = "" } = {}) {
|
|
740
|
+
const providerId = slugifyProviderId(quickStart?.providerId || quickStart?.providerName || "");
|
|
741
|
+
const modelIds = mergeChipValuesAndDraft(quickStart?.modelIds, quickStart?.modelDraft);
|
|
742
|
+
const aliasModelIds = syncQuickStartAliasModelIds(quickStart?.aliasModelIds, modelIds);
|
|
743
|
+
const rateLimitRows = resolveRateLimitDraftRows(quickStart?.rateLimitRows);
|
|
744
|
+
const endpoints = quickStart?.connectionType === "api"
|
|
745
|
+
? mergeChipValuesAndDraft(quickStart?.endpoints, quickStart?.endpointDraft)
|
|
746
|
+
: [];
|
|
747
|
+
const providerList = Array.isArray(baseConfig?.providers) ? baseConfig.providers : [];
|
|
748
|
+
const aliasMap = baseConfig?.modelAliases && typeof baseConfig.modelAliases === "object" && !Array.isArray(baseConfig.modelAliases)
|
|
749
|
+
? baseConfig.modelAliases
|
|
750
|
+
: {};
|
|
751
|
+
|
|
752
|
+
if (stepIndex === 0) {
|
|
753
|
+
if (!String(quickStart?.providerName || "").trim()) return "Add a provider name to continue.";
|
|
754
|
+
if (!providerId || !QUICK_START_PROVIDER_ID_PATTERN.test(providerId)) return "Provider id must start with a letter and use lowercase letters, numbers, or hyphens.";
|
|
755
|
+
if (providerList.some((provider) => provider?.id === providerId && provider?.id !== targetProviderId)) {
|
|
756
|
+
return "Provider id already exists. Choose another id or edit that provider instead.";
|
|
757
|
+
}
|
|
758
|
+
if (quickStart?.connectionType === "api") {
|
|
759
|
+
if (endpoints.length === 0) return "Add at least one endpoint to continue.";
|
|
760
|
+
if (!String(quickStart?.apiKeyEnv || "").trim()) return "API key or env is required before testing config.";
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (stepIndex === 1) {
|
|
765
|
+
if (modelIds.length === 0) return "Add at least one model id.";
|
|
766
|
+
if (quickStart?.connectionType === "api") {
|
|
767
|
+
const rateLimitIssue = validateRateLimitDraftRows(rateLimitRows, {
|
|
768
|
+
knownModelIds: modelIds,
|
|
769
|
+
requireAtLeastOne: true
|
|
770
|
+
});
|
|
771
|
+
if (rateLimitIssue) return rateLimitIssue;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (stepIndex === 2) {
|
|
776
|
+
if (quickStart?.useAliasAsDefault && Object.prototype.hasOwnProperty.call(aliasMap, String(quickStart?.aliasId || DEFAULT_MODEL_ALIAS_ID).trim()) === false) {
|
|
777
|
+
return "";
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
return "";
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// ── Internal dependency notes ──
|
|
785
|
+
// The following are referenced from app.jsx scope and not yet in separate modules:
|
|
786
|
+
// safeClone, looksLikeEnvVarName, slugifyProviderId, createRateLimitDraftRows,
|
|
787
|
+
// createRateLimitDraftRow, mergeChipValuesAndDraft, resolveRateLimitDraftRows,
|
|
788
|
+
// validateRateLimitDraftRows, createMasterKey,
|
|
789
|
+
// findPresetByKey, findPresetByHost, PROVIDER_PRESET_BY_KEY,
|
|
790
|
+
// PROVIDER_PRESET_FREE_TIER_RPM_BY_HOST, presetModelCache
|