@khanglvm/llm-router 1.3.1 → 2.0.0-beta.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.
- package/CHANGELOG.md +39 -0
- package/README.md +337 -41
- package/package.json +19 -3
- package/src/cli/router-module.js +7331 -3805
- package/src/cli/wrangler-toml.js +1 -1
- package/src/cli-entry.js +162 -24
- package/src/node/amp-client-config.js +426 -0
- package/src/node/coding-tool-config.js +763 -0
- package/src/node/config-store.js +49 -18
- package/src/node/instance-state.js +213 -12
- package/src/node/listen-port.js +5 -37
- package/src/node/local-server-settings.js +122 -0
- package/src/node/local-server.js +3 -2
- package/src/node/provider-probe.js +13 -0
- package/src/node/start-command.js +282 -40
- package/src/node/startup-manager.js +64 -29
- package/src/node/web-command.js +106 -0
- package/src/node/web-console-assets.js +26 -0
- package/src/node/web-console-client.js +56 -0
- package/src/node/web-console-dev-assets.js +258 -0
- package/src/node/web-console-server.js +3146 -0
- package/src/node/web-console-styles.generated.js +1 -0
- package/src/node/web-console-ui/config-editor-utils.js +616 -0
- package/src/node/web-console-ui/lib/utils.js +6 -0
- package/src/node/web-console-ui/rate-limit-utils.js +144 -0
- package/src/node/web-console-ui/select-search-utils.js +36 -0
- package/src/runtime/codex-request-transformer.js +46 -5
- package/src/runtime/codex-response-transformer.js +268 -35
- package/src/runtime/config.js +1394 -35
- package/src/runtime/handler/amp-gemini.js +913 -0
- package/src/runtime/handler/amp-response.js +308 -0
- package/src/runtime/handler/amp.js +290 -0
- package/src/runtime/handler/auth.js +17 -2
- package/src/runtime/handler/provider-call.js +168 -50
- package/src/runtime/handler/provider-translation.js +937 -26
- package/src/runtime/handler/request.js +149 -6
- package/src/runtime/handler/route-debug.js +22 -1
- package/src/runtime/handler.js +449 -9
- package/src/runtime/subscription-auth.js +1 -6
- package/src/shared/local-router-defaults.js +62 -0
- package/src/translator/index.js +3 -1
- package/src/translator/request/openai-to-claude.js +217 -6
- package/src/translator/response/openai-to-claude.js +206 -58
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import { DEFAULT_MODEL_ALIAS_ID } from "../../runtime/config.js";
|
|
2
|
+
import {
|
|
3
|
+
buildRateLimitBucketsFromDraftRows,
|
|
4
|
+
RATE_LIMIT_ALL_MODELS_SELECTOR
|
|
5
|
+
} from "./rate-limit-utils.js";
|
|
6
|
+
|
|
7
|
+
function safeClone(value) {
|
|
8
|
+
return JSON.parse(JSON.stringify(value ?? {}));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function dedupeStrings(values = []) {
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
const result = [];
|
|
14
|
+
for (const value of values) {
|
|
15
|
+
const trimmed = String(value || "").trim();
|
|
16
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
17
|
+
seen.add(trimmed);
|
|
18
|
+
result.push(trimmed);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeAliasLookup(value) {
|
|
24
|
+
const text = String(value || "").trim();
|
|
25
|
+
if (!text) return "";
|
|
26
|
+
return text.startsWith("alias:") ? text.slice("alias:".length).trim() : text;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildDefaultAlias(config = {}, aliases = {}) {
|
|
30
|
+
const existingDefault = aliases?.[DEFAULT_MODEL_ALIAS_ID];
|
|
31
|
+
if (existingDefault && typeof existingDefault === "object" && !Array.isArray(existingDefault)) {
|
|
32
|
+
return {
|
|
33
|
+
...existingDefault,
|
|
34
|
+
id: DEFAULT_MODEL_ALIAS_ID,
|
|
35
|
+
strategy: String(existingDefault.strategy || "ordered").trim() || "ordered",
|
|
36
|
+
targets: rewriteAliasTargetList(existingDefault.targets || [], (ref) => ref),
|
|
37
|
+
fallbackTargets: rewriteAliasTargetList(existingDefault.fallbackTargets || [], (ref) => ref)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const configuredDefault = String(config?.defaultModel || "").trim();
|
|
42
|
+
if (!configuredDefault || configuredDefault === "smart") {
|
|
43
|
+
return {
|
|
44
|
+
id: DEFAULT_MODEL_ALIAS_ID,
|
|
45
|
+
strategy: "ordered",
|
|
46
|
+
targets: [],
|
|
47
|
+
fallbackTargets: []
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const aliasFromDefault = normalizeAliasLookup(configuredDefault);
|
|
52
|
+
if (aliasFromDefault && aliases?.[aliasFromDefault]) {
|
|
53
|
+
const sourceAlias = aliases[aliasFromDefault];
|
|
54
|
+
return {
|
|
55
|
+
...sourceAlias,
|
|
56
|
+
id: DEFAULT_MODEL_ALIAS_ID,
|
|
57
|
+
strategy: String(sourceAlias.strategy || "ordered").trim() || "ordered",
|
|
58
|
+
targets: rewriteAliasTargetList(sourceAlias.targets || [], (ref) => ref),
|
|
59
|
+
fallbackTargets: rewriteAliasTargetList(sourceAlias.fallbackTargets || [], (ref) => ref)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
id: DEFAULT_MODEL_ALIAS_ID,
|
|
65
|
+
strategy: "ordered",
|
|
66
|
+
targets: configuredDefault ? [{ ref: configuredDefault }] : [],
|
|
67
|
+
fallbackTargets: []
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ensureDefaultAlias(config = {}) {
|
|
72
|
+
if (!config.modelAliases || typeof config.modelAliases !== "object" || Array.isArray(config.modelAliases)) {
|
|
73
|
+
config.modelAliases = {};
|
|
74
|
+
}
|
|
75
|
+
config.modelAliases[DEFAULT_MODEL_ALIAS_ID] = buildDefaultAlias(config, config.modelAliases);
|
|
76
|
+
return config;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function collectManagedRouteRefs(config = {}) {
|
|
80
|
+
const refs = new Set();
|
|
81
|
+
for (const aliasId of Object.keys(config?.modelAliases || {})) {
|
|
82
|
+
const normalizedAliasId = String(aliasId || "").trim();
|
|
83
|
+
if (normalizedAliasId) refs.add(normalizedAliasId);
|
|
84
|
+
}
|
|
85
|
+
for (const provider of (config?.providers || [])) {
|
|
86
|
+
const providerId = String(provider?.id || "").trim();
|
|
87
|
+
if (!providerId) continue;
|
|
88
|
+
for (const model of (provider?.models || [])) {
|
|
89
|
+
const modelId = String(model?.id || "").trim();
|
|
90
|
+
if (!modelId) continue;
|
|
91
|
+
refs.add(`${providerId}/${modelId}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return refs;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function pickFallbackDefaultModel(config = {}) {
|
|
98
|
+
const aliasIds = Object.keys(config?.modelAliases || {}).map((aliasId) => String(aliasId || "").trim()).filter(Boolean);
|
|
99
|
+
if (aliasIds.includes(DEFAULT_MODEL_ALIAS_ID)) return DEFAULT_MODEL_ALIAS_ID;
|
|
100
|
+
if (aliasIds.length > 0) return aliasIds[0];
|
|
101
|
+
|
|
102
|
+
for (const provider of (config?.providers || [])) {
|
|
103
|
+
const providerId = String(provider?.id || "").trim();
|
|
104
|
+
if (!providerId) continue;
|
|
105
|
+
for (const model of (provider?.models || [])) {
|
|
106
|
+
const modelId = String(model?.id || "").trim();
|
|
107
|
+
if (!modelId) continue;
|
|
108
|
+
return `${providerId}/${modelId}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return "";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function ensureTopLevelRoutes(config = {}) {
|
|
116
|
+
ensureDefaultAlias(config);
|
|
117
|
+
const knownRoutes = collectManagedRouteRefs(config);
|
|
118
|
+
config.defaultModel = DEFAULT_MODEL_ALIAS_ID;
|
|
119
|
+
|
|
120
|
+
if (!config.amp || typeof config.amp !== "object" || Array.isArray(config.amp)) {
|
|
121
|
+
return config;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const nextAmpDefaultRoute = String(config.amp.defaultRoute || "").trim();
|
|
125
|
+
if (!nextAmpDefaultRoute || !knownRoutes.has(normalizeAliasLookup(nextAmpDefaultRoute))) {
|
|
126
|
+
const fallback = String(config.defaultModel || "").trim() || pickFallbackDefaultModel(config);
|
|
127
|
+
if (fallback) {
|
|
128
|
+
config.amp.defaultRoute = fallback;
|
|
129
|
+
} else {
|
|
130
|
+
delete config.amp.defaultRoute;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return config;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function rewriteProviderModelRef(ref, providerId, renameMap, validModelIds) {
|
|
138
|
+
const text = String(ref || "").trim();
|
|
139
|
+
if (!text) return "";
|
|
140
|
+
const prefix = `${providerId}/`;
|
|
141
|
+
if (!text.startsWith(prefix)) return text;
|
|
142
|
+
|
|
143
|
+
const modelId = text.slice(prefix.length);
|
|
144
|
+
const nextModelId = renameMap.get(modelId) || modelId;
|
|
145
|
+
if (!validModelIds.has(nextModelId)) return "";
|
|
146
|
+
return `${providerId}/${nextModelId}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function rewriteProviderReference(ref, currentProviderId, nextProviderId = "") {
|
|
150
|
+
const text = String(ref || "").trim();
|
|
151
|
+
if (!text) return "";
|
|
152
|
+
const prefix = `${currentProviderId}/`;
|
|
153
|
+
if (!text.startsWith(prefix)) return text;
|
|
154
|
+
if (!nextProviderId) return "";
|
|
155
|
+
return `${nextProviderId}/${text.slice(prefix.length)}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function normalizeEndpointCandidates(values = []) {
|
|
159
|
+
return dedupeStrings(Array.isArray(values) ? values : [values]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function rewriteProviderEndpoints(provider = {}, endpoints = []) {
|
|
163
|
+
const nextProvider = { ...provider };
|
|
164
|
+
const nextEndpoints = normalizeEndpointCandidates(endpoints);
|
|
165
|
+
const primaryEndpoint = String(nextEndpoints[0] || "").trim();
|
|
166
|
+
let nextMetadata = nextProvider.metadata && typeof nextProvider.metadata === "object" && !Array.isArray(nextProvider.metadata)
|
|
167
|
+
? { ...nextProvider.metadata }
|
|
168
|
+
: null;
|
|
169
|
+
const shouldPersistEndpointCandidates = nextEndpoints.length > 1
|
|
170
|
+
|| Boolean(nextMetadata && Object.prototype.hasOwnProperty.call(nextMetadata, "endpointCandidates"));
|
|
171
|
+
|
|
172
|
+
if (!primaryEndpoint) {
|
|
173
|
+
delete nextProvider.baseUrl;
|
|
174
|
+
delete nextProvider.baseUrlByFormat;
|
|
175
|
+
if (nextMetadata && Object.prototype.hasOwnProperty.call(nextMetadata, "endpointCandidates")) {
|
|
176
|
+
delete nextMetadata.endpointCandidates;
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
nextProvider.baseUrl = primaryEndpoint;
|
|
180
|
+
const baseUrlByFormat = nextProvider.baseUrlByFormat && typeof nextProvider.baseUrlByFormat === "object" && !Array.isArray(nextProvider.baseUrlByFormat)
|
|
181
|
+
? nextProvider.baseUrlByFormat
|
|
182
|
+
: null;
|
|
183
|
+
if (baseUrlByFormat && Object.keys(baseUrlByFormat).length > 0) {
|
|
184
|
+
nextProvider.baseUrlByFormat = Object.fromEntries(Object.keys(baseUrlByFormat).map((format) => [format, primaryEndpoint]));
|
|
185
|
+
}
|
|
186
|
+
if (shouldPersistEndpointCandidates) {
|
|
187
|
+
nextMetadata = nextMetadata || {};
|
|
188
|
+
nextMetadata.endpointCandidates = nextEndpoints;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (nextMetadata && Object.keys(nextMetadata).length > 0) {
|
|
193
|
+
nextProvider.metadata = nextMetadata;
|
|
194
|
+
} else if (nextMetadata) {
|
|
195
|
+
delete nextProvider.metadata;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return nextProvider;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function normalizePositiveInteger(value, fallback) {
|
|
202
|
+
const parsed = Number.parseInt(String(value || ""), 10);
|
|
203
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildLegacyRateLimitDraftRows(draftProvider = {}) {
|
|
207
|
+
const hasLegacyFields =
|
|
208
|
+
draftProvider?.rateLimitLimit !== undefined
|
|
209
|
+
|| draftProvider?.rateLimitWindowValue !== undefined
|
|
210
|
+
|| draftProvider?.rateLimitWindowUnit !== undefined;
|
|
211
|
+
if (!hasLegacyFields) return [];
|
|
212
|
+
return [{
|
|
213
|
+
sourceId: String(draftProvider?.rateLimitSourceId || "").trim(),
|
|
214
|
+
models: [RATE_LIMIT_ALL_MODELS_SELECTOR],
|
|
215
|
+
requests: draftProvider?.rateLimitLimit,
|
|
216
|
+
windowValue: draftProvider?.rateLimitWindowValue,
|
|
217
|
+
windowUnit: draftProvider?.rateLimitWindowUnit
|
|
218
|
+
}];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function rewriteRateLimits(provider = {}, draftProvider = {}, providerId = "") {
|
|
222
|
+
const nextProvider = { ...provider };
|
|
223
|
+
const currentRateLimits = Array.isArray(nextProvider.rateLimits)
|
|
224
|
+
? nextProvider.rateLimits.map((rateLimit) => (rateLimit && typeof rateLimit === "object" ? safeClone(rateLimit) : rateLimit)).filter(Boolean)
|
|
225
|
+
: [];
|
|
226
|
+
const draftRows = Array.isArray(draftProvider?.rateLimitRows)
|
|
227
|
+
? draftProvider.rateLimitRows
|
|
228
|
+
: buildLegacyRateLimitDraftRows(draftProvider);
|
|
229
|
+
const currentPrimary = currentRateLimits[0] && typeof currentRateLimits[0] === "object" ? currentRateLimits[0] : null;
|
|
230
|
+
const currentRequests = Number(currentPrimary?.requests ?? currentPrimary?.limit) || 60;
|
|
231
|
+
const currentWindowSize = Number(currentPrimary?.window?.size ?? currentPrimary?.window?.value) || 1;
|
|
232
|
+
const currentWindowUnit = String(currentPrimary?.window?.unit || "minute").trim() || "minute";
|
|
233
|
+
const existingBucketsBySourceId = new Map(
|
|
234
|
+
currentRateLimits
|
|
235
|
+
.map((bucket) => [String(bucket?.id || "").trim(), bucket])
|
|
236
|
+
.filter(([bucketId]) => Boolean(bucketId))
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
nextProvider.rateLimits = buildRateLimitBucketsFromDraftRows(draftRows, {
|
|
240
|
+
existingBucketsBySourceId,
|
|
241
|
+
fallbackRequests: currentRequests,
|
|
242
|
+
fallbackWindowValue: currentWindowSize,
|
|
243
|
+
fallbackWindowUnit: currentWindowUnit
|
|
244
|
+
});
|
|
245
|
+
return nextProvider;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function rewriteAliasReference(ref, fromAliasId, toAliasId = "") {
|
|
249
|
+
const text = String(ref || "").trim();
|
|
250
|
+
if (!text) return "";
|
|
251
|
+
if (text === fromAliasId) return toAliasId;
|
|
252
|
+
if (text === `alias:${fromAliasId}`) return toAliasId ? `alias:${toAliasId}` : "";
|
|
253
|
+
return text;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function rewriteAliasTargetList(targets = [], rewriter) {
|
|
257
|
+
const seenRefs = new Set();
|
|
258
|
+
const nextTargets = [];
|
|
259
|
+
|
|
260
|
+
for (const target of (Array.isArray(targets) ? targets : [])) {
|
|
261
|
+
const currentRef = String(target?.ref || "").trim();
|
|
262
|
+
if (!currentRef) continue;
|
|
263
|
+
const nextRef = String(rewriter(currentRef) || "").trim();
|
|
264
|
+
if (!nextRef || seenRefs.has(nextRef)) continue;
|
|
265
|
+
seenRefs.add(nextRef);
|
|
266
|
+
nextTargets.push({
|
|
267
|
+
...target,
|
|
268
|
+
ref: nextRef
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return nextTargets;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function cleanupAliasReferences(config = {}) {
|
|
276
|
+
const aliases = config?.modelAliases && typeof config.modelAliases === "object" && !Array.isArray(config.modelAliases)
|
|
277
|
+
? config.modelAliases
|
|
278
|
+
: {};
|
|
279
|
+
const nextAliases = {};
|
|
280
|
+
|
|
281
|
+
for (const [aliasId, alias] of Object.entries(aliases)) {
|
|
282
|
+
const targets = rewriteAliasTargetList(alias?.targets || [], (ref) => ref);
|
|
283
|
+
const fallbackTargets = rewriteAliasTargetList(alias?.fallbackTargets || [], (ref) => ref);
|
|
284
|
+
nextAliases[aliasId] = {
|
|
285
|
+
...alias,
|
|
286
|
+
targets,
|
|
287
|
+
fallbackTargets
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
config.modelAliases = nextAliases;
|
|
292
|
+
return ensureTopLevelRoutes(config);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function normalizeProviderModelRows(rows = []) {
|
|
296
|
+
const seenIds = new Set();
|
|
297
|
+
const normalizedRows = [];
|
|
298
|
+
|
|
299
|
+
for (const row of (Array.isArray(rows) ? rows : [])) {
|
|
300
|
+
const id = String(row?.id || "").trim();
|
|
301
|
+
const sourceId = String(row?.sourceId || row?.originalId || "").trim();
|
|
302
|
+
if (!id || seenIds.has(id)) continue;
|
|
303
|
+
seenIds.add(id);
|
|
304
|
+
normalizedRows.push({ id, sourceId });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return normalizedRows;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function normalizeAliasTargetRows(rows = []) {
|
|
311
|
+
const seenRefs = new Set();
|
|
312
|
+
const normalizedRows = [];
|
|
313
|
+
|
|
314
|
+
for (const row of (Array.isArray(rows) ? rows : [])) {
|
|
315
|
+
const ref = String(row?.ref || "").trim();
|
|
316
|
+
const sourceRef = String(row?.sourceRef || row?.originalRef || "").trim();
|
|
317
|
+
const rawWeight = String(row?.weight ?? "").trim();
|
|
318
|
+
const parsedWeight = Math.floor(Number(rawWeight));
|
|
319
|
+
if (!ref || seenRefs.has(ref)) continue;
|
|
320
|
+
seenRefs.add(ref);
|
|
321
|
+
normalizedRows.push({
|
|
322
|
+
ref,
|
|
323
|
+
sourceRef,
|
|
324
|
+
...(rawWeight ? { weight: (Number.isFinite(parsedWeight) && parsedWeight > 0 ? parsedWeight : 1) } : {})
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return normalizedRows;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function applyProviderModelEdits(config = {}, providerId, rows = []) {
|
|
332
|
+
const nextConfig = safeClone(config);
|
|
333
|
+
const providerList = Array.isArray(nextConfig.providers) ? nextConfig.providers : [];
|
|
334
|
+
const providerIndex = providerList.findIndex((provider) => provider?.id === providerId);
|
|
335
|
+
if (providerIndex === -1) return nextConfig;
|
|
336
|
+
|
|
337
|
+
const provider = providerList[providerIndex];
|
|
338
|
+
const existingModels = Array.isArray(provider?.models) ? provider.models : [];
|
|
339
|
+
const existingModelMap = new Map(existingModels.map((model) => [String(model?.id || "").trim(), model]));
|
|
340
|
+
const normalizedRows = normalizeProviderModelRows(rows);
|
|
341
|
+
const usedSourceIds = new Set();
|
|
342
|
+
const renameMap = new Map();
|
|
343
|
+
const nextModels = [];
|
|
344
|
+
|
|
345
|
+
for (const row of normalizedRows) {
|
|
346
|
+
const existingBySourceId = row.sourceId && existingModelMap.has(row.sourceId) && !usedSourceIds.has(row.sourceId)
|
|
347
|
+
? row.sourceId
|
|
348
|
+
: "";
|
|
349
|
+
const existingByCurrentId = !existingBySourceId && existingModelMap.has(row.id) && !usedSourceIds.has(row.id)
|
|
350
|
+
? row.id
|
|
351
|
+
: "";
|
|
352
|
+
const matchedSourceId = existingBySourceId || existingByCurrentId;
|
|
353
|
+
|
|
354
|
+
if (matchedSourceId) {
|
|
355
|
+
usedSourceIds.add(matchedSourceId);
|
|
356
|
+
if (matchedSourceId !== row.id) {
|
|
357
|
+
renameMap.set(matchedSourceId, row.id);
|
|
358
|
+
}
|
|
359
|
+
nextModels.push({
|
|
360
|
+
...existingModelMap.get(matchedSourceId),
|
|
361
|
+
id: row.id
|
|
362
|
+
});
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
nextModels.push({ id: row.id });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const validModelIds = new Set(nextModels.map((model) => String(model?.id || "").trim()).filter(Boolean));
|
|
370
|
+
|
|
371
|
+
provider.models = nextModels;
|
|
372
|
+
provider.rateLimits = (Array.isArray(provider.rateLimits) ? provider.rateLimits : []).map((bucket) => {
|
|
373
|
+
const bucketModels = Array.isArray(bucket?.models) ? bucket.models : [];
|
|
374
|
+
return {
|
|
375
|
+
...bucket,
|
|
376
|
+
models: dedupeStrings(bucketModels.map((modelId) => {
|
|
377
|
+
if (String(modelId || "").trim() === "all") return "all";
|
|
378
|
+
const nextModelId = renameMap.get(String(modelId || "").trim()) || String(modelId || "").trim();
|
|
379
|
+
return validModelIds.has(nextModelId) ? nextModelId : "";
|
|
380
|
+
}))
|
|
381
|
+
};
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
nextConfig.providers = providerList.map((entry, index) => (index === providerIndex ? provider : entry)).map((entry) => ({
|
|
385
|
+
...entry,
|
|
386
|
+
models: (Array.isArray(entry?.models) ? entry.models : []).map((model) => {
|
|
387
|
+
if (!Array.isArray(model?.fallbackModels)) return model;
|
|
388
|
+
return {
|
|
389
|
+
...model,
|
|
390
|
+
fallbackModels: dedupeStrings(model.fallbackModels.map((ref) => rewriteProviderModelRef(ref, providerId, renameMap, validModelIds)))
|
|
391
|
+
};
|
|
392
|
+
})
|
|
393
|
+
}));
|
|
394
|
+
|
|
395
|
+
const existingAliases = nextConfig.modelAliases && typeof nextConfig.modelAliases === "object" && !Array.isArray(nextConfig.modelAliases)
|
|
396
|
+
? nextConfig.modelAliases
|
|
397
|
+
: {};
|
|
398
|
+
const nextAliases = {};
|
|
399
|
+
|
|
400
|
+
for (const [aliasId, alias] of Object.entries(existingAliases)) {
|
|
401
|
+
const targets = rewriteAliasTargetList(alias?.targets || [], (ref) => rewriteProviderModelRef(ref, providerId, renameMap, validModelIds));
|
|
402
|
+
const fallbackTargets = rewriteAliasTargetList(alias?.fallbackTargets || [], (ref) => rewriteProviderModelRef(ref, providerId, renameMap, validModelIds));
|
|
403
|
+
nextAliases[aliasId] = {
|
|
404
|
+
...alias,
|
|
405
|
+
targets,
|
|
406
|
+
fallbackTargets
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
nextConfig.modelAliases = nextAliases;
|
|
411
|
+
nextConfig.defaultModel = rewriteProviderModelRef(nextConfig.defaultModel, providerId, renameMap, validModelIds);
|
|
412
|
+
if (nextConfig?.amp && typeof nextConfig.amp === "object" && !Array.isArray(nextConfig.amp)) {
|
|
413
|
+
nextConfig.amp.defaultRoute = rewriteProviderModelRef(nextConfig.amp.defaultRoute, providerId, renameMap, validModelIds);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return ensureTopLevelRoutes(nextConfig);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function applyProviderInlineEdits(config = {}, currentProviderId = "", draftProvider = {}) {
|
|
420
|
+
const nextConfig = safeClone(config);
|
|
421
|
+
const providerList = Array.isArray(nextConfig.providers) ? nextConfig.providers : [];
|
|
422
|
+
const providerIndex = providerList.findIndex((provider) => provider?.id === currentProviderId);
|
|
423
|
+
if (providerIndex === -1) return nextConfig;
|
|
424
|
+
|
|
425
|
+
const currentProvider = providerList[providerIndex] || {};
|
|
426
|
+
const nextProviderId = String(draftProvider?.id || currentProviderId || "").trim();
|
|
427
|
+
const nextProviderName = String(draftProvider?.name ?? currentProvider?.name ?? "").trim();
|
|
428
|
+
const nextEndpoints = normalizeEndpointCandidates(
|
|
429
|
+
Array.isArray(draftProvider?.endpoints) ? draftProvider.endpoints : [draftProvider?.endpoint]
|
|
430
|
+
);
|
|
431
|
+
const isSubscription = currentProvider?.type === "subscription";
|
|
432
|
+
const renamedProviderId = nextProviderId || currentProviderId;
|
|
433
|
+
|
|
434
|
+
let nextProvider = {
|
|
435
|
+
...currentProvider,
|
|
436
|
+
id: renamedProviderId,
|
|
437
|
+
name: nextProviderName || renamedProviderId
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
if (!isSubscription) {
|
|
441
|
+
nextProvider = rewriteProviderEndpoints(nextProvider, nextEndpoints);
|
|
442
|
+
nextProvider = rewriteRateLimits(nextProvider, draftProvider, renamedProviderId);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
providerList[providerIndex] = nextProvider;
|
|
446
|
+
nextConfig.providers = providerList.map((provider) => ({
|
|
447
|
+
...provider,
|
|
448
|
+
models: (Array.isArray(provider?.models) ? provider.models : []).map((model) => {
|
|
449
|
+
if (!Array.isArray(model?.fallbackModels)) return model;
|
|
450
|
+
return {
|
|
451
|
+
...model,
|
|
452
|
+
fallbackModels: dedupeStrings(model.fallbackModels.map((ref) => rewriteProviderReference(ref, currentProviderId, renamedProviderId)))
|
|
453
|
+
};
|
|
454
|
+
})
|
|
455
|
+
}));
|
|
456
|
+
|
|
457
|
+
if (renamedProviderId !== currentProviderId) {
|
|
458
|
+
const aliasMap = nextConfig.modelAliases && typeof nextConfig.modelAliases === "object" && !Array.isArray(nextConfig.modelAliases)
|
|
459
|
+
? nextConfig.modelAliases
|
|
460
|
+
: {};
|
|
461
|
+
const nextAliases = {};
|
|
462
|
+
|
|
463
|
+
for (const [aliasId, alias] of Object.entries(aliasMap)) {
|
|
464
|
+
nextAliases[aliasId] = {
|
|
465
|
+
...alias,
|
|
466
|
+
targets: rewriteAliasTargetList(alias?.targets || [], (ref) => rewriteProviderReference(ref, currentProviderId, renamedProviderId)),
|
|
467
|
+
fallbackTargets: rewriteAliasTargetList(alias?.fallbackTargets || [], (ref) => rewriteProviderReference(ref, currentProviderId, renamedProviderId))
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
nextConfig.modelAliases = nextAliases;
|
|
472
|
+
nextConfig.defaultModel = rewriteProviderReference(nextConfig.defaultModel, currentProviderId, renamedProviderId);
|
|
473
|
+
if (nextConfig?.amp && typeof nextConfig.amp === "object" && !Array.isArray(nextConfig.amp)) {
|
|
474
|
+
nextConfig.amp.defaultRoute = rewriteProviderReference(nextConfig.amp.defaultRoute, currentProviderId, renamedProviderId);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return ensureTopLevelRoutes(nextConfig);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function applyModelAliasEdits(config = {}, currentAliasId = "", draftAlias = {}) {
|
|
482
|
+
const nextConfig = safeClone(config);
|
|
483
|
+
const aliasMap = nextConfig.modelAliases && typeof nextConfig.modelAliases === "object" && !Array.isArray(nextConfig.modelAliases)
|
|
484
|
+
? nextConfig.modelAliases
|
|
485
|
+
: {};
|
|
486
|
+
const existingAlias = currentAliasId ? aliasMap[currentAliasId] : undefined;
|
|
487
|
+
const nextAliasId = currentAliasId === DEFAULT_MODEL_ALIAS_ID
|
|
488
|
+
? DEFAULT_MODEL_ALIAS_ID
|
|
489
|
+
: String(draftAlias?.id || currentAliasId || "").trim();
|
|
490
|
+
if (!nextAliasId) return nextConfig;
|
|
491
|
+
|
|
492
|
+
const currentTargetsByRef = new Map((existingAlias?.targets || []).map((target) => [String(target?.ref || "").trim(), target]));
|
|
493
|
+
const currentFallbackTargetsByRef = new Map((existingAlias?.fallbackTargets || []).map((target) => [String(target?.ref || "").trim(), target]));
|
|
494
|
+
|
|
495
|
+
const usedPrimarySources = new Set();
|
|
496
|
+
const targets = normalizeAliasTargetRows(draftAlias?.targets).map((row) => {
|
|
497
|
+
const matchedSourceRef = row.sourceRef && currentTargetsByRef.has(row.sourceRef) && !usedPrimarySources.has(row.sourceRef)
|
|
498
|
+
? row.sourceRef
|
|
499
|
+
: (currentTargetsByRef.has(row.ref) && !usedPrimarySources.has(row.ref) ? row.ref : "");
|
|
500
|
+
if (matchedSourceRef) usedPrimarySources.add(matchedSourceRef);
|
|
501
|
+
return {
|
|
502
|
+
...(matchedSourceRef ? currentTargetsByRef.get(matchedSourceRef) : {}),
|
|
503
|
+
ref: row.ref,
|
|
504
|
+
...(row.weight !== undefined ? { weight: row.weight } : {})
|
|
505
|
+
};
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
const usedFallbackSources = new Set();
|
|
509
|
+
const fallbackTargets = normalizeAliasTargetRows(draftAlias?.fallbackTargets).map((row) => {
|
|
510
|
+
const matchedSourceRef = row.sourceRef && currentFallbackTargetsByRef.has(row.sourceRef) && !usedFallbackSources.has(row.sourceRef)
|
|
511
|
+
? row.sourceRef
|
|
512
|
+
: (currentFallbackTargetsByRef.has(row.ref) && !usedFallbackSources.has(row.ref) ? row.ref : "");
|
|
513
|
+
if (matchedSourceRef) usedFallbackSources.add(matchedSourceRef);
|
|
514
|
+
return {
|
|
515
|
+
...(matchedSourceRef ? currentFallbackTargetsByRef.get(matchedSourceRef) : {}),
|
|
516
|
+
ref: row.ref,
|
|
517
|
+
...(row.weight !== undefined ? { weight: row.weight } : {})
|
|
518
|
+
};
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
if (currentAliasId && currentAliasId !== nextAliasId) {
|
|
522
|
+
delete aliasMap[currentAliasId];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
aliasMap[nextAliasId] = {
|
|
526
|
+
...(existingAlias?.metadata ? { metadata: existingAlias.metadata } : {}),
|
|
527
|
+
...(draftAlias?.metadata && typeof draftAlias.metadata === "object" && !Array.isArray(draftAlias.metadata)
|
|
528
|
+
? { metadata: draftAlias.metadata }
|
|
529
|
+
: {}),
|
|
530
|
+
id: nextAliasId,
|
|
531
|
+
strategy: String(draftAlias?.strategy || existingAlias?.strategy || "ordered").trim() || "ordered",
|
|
532
|
+
targets,
|
|
533
|
+
fallbackTargets
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
nextConfig.modelAliases = aliasMap;
|
|
537
|
+
|
|
538
|
+
if (currentAliasId && currentAliasId !== nextAliasId) {
|
|
539
|
+
for (const [aliasId, alias] of Object.entries(aliasMap)) {
|
|
540
|
+
if (aliasId === nextAliasId) continue;
|
|
541
|
+
alias.targets = rewriteAliasTargetList(alias?.targets || [], (ref) => rewriteAliasReference(ref, currentAliasId, nextAliasId));
|
|
542
|
+
alias.fallbackTargets = rewriteAliasTargetList(alias?.fallbackTargets || [], (ref) => rewriteAliasReference(ref, currentAliasId, nextAliasId));
|
|
543
|
+
}
|
|
544
|
+
nextConfig.defaultModel = rewriteAliasReference(nextConfig.defaultModel, currentAliasId, nextAliasId);
|
|
545
|
+
if (nextConfig?.amp && typeof nextConfig.amp === "object" && !Array.isArray(nextConfig.amp)) {
|
|
546
|
+
nextConfig.amp.defaultRoute = rewriteAliasReference(nextConfig.amp.defaultRoute, currentAliasId, nextAliasId);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return cleanupAliasReferences(nextConfig);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export function removeModelAlias(config = {}, aliasId = "") {
|
|
554
|
+
const nextConfig = safeClone(config);
|
|
555
|
+
const targetAliasId = String(aliasId || "").trim();
|
|
556
|
+
if (!targetAliasId) return nextConfig;
|
|
557
|
+
|
|
558
|
+
const aliasMap = nextConfig.modelAliases && typeof nextConfig.modelAliases === "object" && !Array.isArray(nextConfig.modelAliases)
|
|
559
|
+
? nextConfig.modelAliases
|
|
560
|
+
: {};
|
|
561
|
+
if (!Object.prototype.hasOwnProperty.call(aliasMap, targetAliasId)) return nextConfig;
|
|
562
|
+
|
|
563
|
+
if (targetAliasId === DEFAULT_MODEL_ALIAS_ID) {
|
|
564
|
+
aliasMap[targetAliasId] = {
|
|
565
|
+
...aliasMap[targetAliasId],
|
|
566
|
+
id: DEFAULT_MODEL_ALIAS_ID,
|
|
567
|
+
strategy: String(aliasMap[targetAliasId]?.strategy || "ordered").trim() || "ordered",
|
|
568
|
+
targets: [],
|
|
569
|
+
fallbackTargets: []
|
|
570
|
+
};
|
|
571
|
+
nextConfig.modelAliases = aliasMap;
|
|
572
|
+
return ensureTopLevelRoutes(nextConfig);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
delete aliasMap[targetAliasId];
|
|
576
|
+
nextConfig.modelAliases = aliasMap;
|
|
577
|
+
|
|
578
|
+
for (const alias of Object.values(aliasMap)) {
|
|
579
|
+
alias.targets = rewriteAliasTargetList(alias?.targets || [], (ref) => rewriteAliasReference(ref, targetAliasId));
|
|
580
|
+
alias.fallbackTargets = rewriteAliasTargetList(alias?.fallbackTargets || [], (ref) => rewriteAliasReference(ref, targetAliasId));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
nextConfig.defaultModel = rewriteAliasReference(nextConfig.defaultModel, targetAliasId);
|
|
584
|
+
if (nextConfig?.amp && typeof nextConfig.amp === "object" && !Array.isArray(nextConfig.amp)) {
|
|
585
|
+
nextConfig.amp.defaultRoute = rewriteAliasReference(nextConfig.amp.defaultRoute, targetAliasId);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return cleanupAliasReferences(nextConfig);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export function createAliasDraftState(aliasId = "", alias = {}) {
|
|
592
|
+
return {
|
|
593
|
+
id: String(aliasId || alias?.id || "").trim(),
|
|
594
|
+
strategy: String(alias?.strategy || "ordered").trim() || "ordered",
|
|
595
|
+
targets: (Array.isArray(alias?.targets) ? alias.targets : []).map((target, index) => ({
|
|
596
|
+
key: `target-${aliasId || "alias"}-${index}-${String(target?.ref || "").trim() || "empty"}`,
|
|
597
|
+
ref: String(target?.ref || "").trim(),
|
|
598
|
+
sourceRef: String(target?.ref || "").trim(),
|
|
599
|
+
weight: String(Number.isFinite(Number(target?.weight)) && Number(target.weight) > 0 ? Math.floor(Number(target.weight)) : 1)
|
|
600
|
+
})),
|
|
601
|
+
fallbackTargets: (Array.isArray(alias?.fallbackTargets) ? alias.fallbackTargets : []).map((target, index) => ({
|
|
602
|
+
key: `fallback-${aliasId || "alias"}-${index}-${String(target?.ref || "").trim() || "empty"}`,
|
|
603
|
+
ref: String(target?.ref || "").trim(),
|
|
604
|
+
sourceRef: String(target?.ref || "").trim(),
|
|
605
|
+
weight: String(Number.isFinite(Number(target?.weight)) && Number(target.weight) > 0 ? Math.floor(Number(target.weight)) : 1)
|
|
606
|
+
}))
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export function createProviderModelDraftRows(provider = {}) {
|
|
611
|
+
return (Array.isArray(provider?.models) ? provider.models : []).map((model, index) => ({
|
|
612
|
+
key: `model-${provider?.id || "provider"}-${index}-${String(model?.id || "").trim() || "empty"}`,
|
|
613
|
+
id: String(model?.id || "").trim(),
|
|
614
|
+
sourceId: String(model?.id || "").trim()
|
|
615
|
+
}));
|
|
616
|
+
}
|