manifest 5.34.0 → 5.35.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/dist/backend/database/models-dev-sync.service.js +204 -0
- package/dist/backend/model-discovery/model-discovery.service.js +44 -7
- package/dist/backend/model-discovery/model-fallback.js +20 -0
- package/dist/backend/model-discovery/provider-model-fetcher.service.js +20 -3
- package/dist/backend/model-prices/model-prices.module.js +8 -1
- package/dist/backend/model-prices/model-pricing-cache.service.js +48 -3
- package/dist/backend/routing/proxy/provider-client-converters.js +10 -0
- package/dist/openclaw.plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var ModelsDevSyncService_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ModelsDevSyncService = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const schedule_1 = require("@nestjs/schedule");
|
|
16
|
+
const providers_1 = require("../common/constants/providers");
|
|
17
|
+
const PROVIDER_ID_MAP = {
|
|
18
|
+
anthropic: 'anthropic',
|
|
19
|
+
openai: 'openai',
|
|
20
|
+
gemini: 'google',
|
|
21
|
+
deepseek: 'deepseek',
|
|
22
|
+
mistral: 'mistral',
|
|
23
|
+
xai: 'xai',
|
|
24
|
+
minimax: 'minimax',
|
|
25
|
+
moonshot: 'moonshotai',
|
|
26
|
+
qwen: 'alibaba',
|
|
27
|
+
zai: 'zai',
|
|
28
|
+
copilot: 'github-copilot',
|
|
29
|
+
};
|
|
30
|
+
const SUPPORTED_PROVIDERS = new Set(Object.keys(PROVIDER_ID_MAP));
|
|
31
|
+
function resolveProviderId(providerId) {
|
|
32
|
+
const lower = providerId.toLowerCase();
|
|
33
|
+
if (SUPPORTED_PROVIDERS.has(lower))
|
|
34
|
+
return lower;
|
|
35
|
+
const entry = providers_1.PROVIDER_BY_ID_OR_ALIAS.get(lower);
|
|
36
|
+
return entry?.id ?? lower;
|
|
37
|
+
}
|
|
38
|
+
const MODELS_DEV_API = 'https://models.dev/api.json';
|
|
39
|
+
const FETCH_TIMEOUT_MS = 10000;
|
|
40
|
+
const VERSION_SUFFIX_RE = /-\d{3}$/;
|
|
41
|
+
const DATE_SUFFIX_RE = /-\d{4}-?\d{2}-?\d{2}$/;
|
|
42
|
+
const SHORT_DATE_SUFFIX_RE = /-\d{4}$/;
|
|
43
|
+
const LATEST_SUFFIX = '-latest';
|
|
44
|
+
const REASONING_SUFFIX_RE = /-(reasoning|non-reasoning)$/;
|
|
45
|
+
let ModelsDevSyncService = ModelsDevSyncService_1 = class ModelsDevSyncService {
|
|
46
|
+
logger = new common_1.Logger(ModelsDevSyncService_1.name);
|
|
47
|
+
cache = new Map();
|
|
48
|
+
lastFetchedAt = null;
|
|
49
|
+
async onModuleInit() {
|
|
50
|
+
try {
|
|
51
|
+
await this.refreshCache();
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
this.logger.error(`Startup models.dev cache refresh failed: ${err}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async refreshCache() {
|
|
58
|
+
this.logger.log('Refreshing models.dev cache...');
|
|
59
|
+
const raw = await this.fetchModelsDevData();
|
|
60
|
+
if (!raw)
|
|
61
|
+
return 0;
|
|
62
|
+
const newCache = new Map();
|
|
63
|
+
let totalModels = 0;
|
|
64
|
+
for (const [ourId, modelsDevId] of Object.entries(PROVIDER_ID_MAP)) {
|
|
65
|
+
const provider = raw[modelsDevId];
|
|
66
|
+
if (!provider?.models)
|
|
67
|
+
continue;
|
|
68
|
+
const modelMap = new Map();
|
|
69
|
+
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
70
|
+
if (!this.isChatCompatible(model))
|
|
71
|
+
continue;
|
|
72
|
+
const entry = this.parseModel(modelId, model);
|
|
73
|
+
modelMap.set(modelId, entry);
|
|
74
|
+
totalModels++;
|
|
75
|
+
}
|
|
76
|
+
if (modelMap.size > 0) {
|
|
77
|
+
newCache.set(ourId, modelMap);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this.cache = newCache;
|
|
81
|
+
this.lastFetchedAt = new Date();
|
|
82
|
+
this.logger.log(`models.dev cache loaded: ${newCache.size} providers, ${totalModels} models`);
|
|
83
|
+
return totalModels;
|
|
84
|
+
}
|
|
85
|
+
lookupModel(providerId, modelId) {
|
|
86
|
+
const providerModels = this.cache.get(resolveProviderId(providerId));
|
|
87
|
+
if (!providerModels)
|
|
88
|
+
return null;
|
|
89
|
+
const exact = providerModels.get(modelId);
|
|
90
|
+
if (exact)
|
|
91
|
+
return exact;
|
|
92
|
+
const noVersion = modelId.replace(VERSION_SUFFIX_RE, '');
|
|
93
|
+
if (noVersion !== modelId) {
|
|
94
|
+
const found = providerModels.get(noVersion);
|
|
95
|
+
if (found)
|
|
96
|
+
return found;
|
|
97
|
+
}
|
|
98
|
+
const noDate = modelId.replace(DATE_SUFFIX_RE, '');
|
|
99
|
+
if (noDate !== modelId) {
|
|
100
|
+
const found = providerModels.get(noDate);
|
|
101
|
+
if (found)
|
|
102
|
+
return found;
|
|
103
|
+
}
|
|
104
|
+
if (!modelId.endsWith(LATEST_SUFFIX)) {
|
|
105
|
+
const withLatest = providerModels.get(modelId + LATEST_SUFFIX);
|
|
106
|
+
if (withLatest)
|
|
107
|
+
return withLatest;
|
|
108
|
+
}
|
|
109
|
+
if (noDate !== modelId && !noDate.endsWith(LATEST_SUFFIX)) {
|
|
110
|
+
const found = providerModels.get(noDate + LATEST_SUFFIX);
|
|
111
|
+
if (found)
|
|
112
|
+
return found;
|
|
113
|
+
}
|
|
114
|
+
const noReasoning = modelId.replace(REASONING_SUFFIX_RE, '');
|
|
115
|
+
if (noReasoning !== modelId) {
|
|
116
|
+
const found = providerModels.get(noReasoning);
|
|
117
|
+
if (found)
|
|
118
|
+
return found;
|
|
119
|
+
}
|
|
120
|
+
const noShortDate = modelId.replace(SHORT_DATE_SUFFIX_RE, '');
|
|
121
|
+
if (noShortDate !== modelId) {
|
|
122
|
+
const found = providerModels.get(noShortDate);
|
|
123
|
+
if (found)
|
|
124
|
+
return found;
|
|
125
|
+
if (!noShortDate.endsWith(LATEST_SUFFIX)) {
|
|
126
|
+
const withLatest = providerModels.get(noShortDate + LATEST_SUFFIX);
|
|
127
|
+
if (withLatest)
|
|
128
|
+
return withLatest;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
getModelsForProvider(providerId) {
|
|
134
|
+
const providerModels = this.cache.get(resolveProviderId(providerId));
|
|
135
|
+
if (!providerModels)
|
|
136
|
+
return [];
|
|
137
|
+
return [...providerModels.values()];
|
|
138
|
+
}
|
|
139
|
+
isProviderSupported(providerId) {
|
|
140
|
+
return SUPPORTED_PROVIDERS.has(resolveProviderId(providerId));
|
|
141
|
+
}
|
|
142
|
+
getLastFetchedAt() {
|
|
143
|
+
return this.lastFetchedAt;
|
|
144
|
+
}
|
|
145
|
+
parseModel(modelId, raw) {
|
|
146
|
+
const inputPerMillion = raw.cost?.input ?? null;
|
|
147
|
+
const outputPerMillion = raw.cost?.output ?? null;
|
|
148
|
+
const cacheReadPerMillion = raw.cost?.cache_read ?? null;
|
|
149
|
+
const cacheWritePerMillion = raw.cost?.cache_write ?? null;
|
|
150
|
+
return {
|
|
151
|
+
id: modelId,
|
|
152
|
+
name: raw.name || modelId,
|
|
153
|
+
family: raw.family,
|
|
154
|
+
reasoning: raw.reasoning ?? false,
|
|
155
|
+
toolCall: raw.tool_call ?? false,
|
|
156
|
+
structuredOutput: raw.structured_output ?? false,
|
|
157
|
+
contextWindow: raw.limit?.context,
|
|
158
|
+
maxOutputTokens: raw.limit?.output,
|
|
159
|
+
inputPricePerToken: inputPerMillion !== null ? inputPerMillion / 1_000_000 : null,
|
|
160
|
+
outputPricePerToken: outputPerMillion !== null ? outputPerMillion / 1_000_000 : null,
|
|
161
|
+
cacheReadPricePerToken: cacheReadPerMillion !== null ? cacheReadPerMillion / 1_000_000 : null,
|
|
162
|
+
cacheWritePricePerToken: cacheWritePerMillion !== null ? cacheWritePerMillion / 1_000_000 : null,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
isChatCompatible(model) {
|
|
166
|
+
const inputMods = model.modalities?.input?.map((m) => m.toLowerCase());
|
|
167
|
+
if (inputMods && inputMods.length > 0 && !inputMods.includes('text')) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const outputMods = model.modalities?.output?.map((m) => m.toLowerCase());
|
|
171
|
+
if (outputMods && outputMods.length > 0) {
|
|
172
|
+
return outputMods.every((m) => m === 'text');
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
async fetchModelsDevData() {
|
|
177
|
+
try {
|
|
178
|
+
const controller = new AbortController();
|
|
179
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
180
|
+
const res = await fetch(MODELS_DEV_API, { signal: controller.signal });
|
|
181
|
+
clearTimeout(timeout);
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
this.logger.error(`models.dev API returned ${res.status}`);
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
return (await res.json());
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
this.logger.error(`Failed to fetch models.dev data: ${err}`);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
exports.ModelsDevSyncService = ModelsDevSyncService;
|
|
195
|
+
__decorate([
|
|
196
|
+
(0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_DAY_AT_4AM),
|
|
197
|
+
__metadata("design:type", Function),
|
|
198
|
+
__metadata("design:paramtypes", []),
|
|
199
|
+
__metadata("design:returntype", Promise)
|
|
200
|
+
], ModelsDevSyncService.prototype, "refreshCache", null);
|
|
201
|
+
exports.ModelsDevSyncService = ModelsDevSyncService = ModelsDevSyncService_1 = __decorate([
|
|
202
|
+
(0, common_1.Injectable)()
|
|
203
|
+
], ModelsDevSyncService);
|
|
204
|
+
//# sourceMappingURL=models-dev-sync.service.js.map
|
|
@@ -24,6 +24,7 @@ const provider_model_registry_service_1 = require("./provider-model-registry.ser
|
|
|
24
24
|
const crypto_util_1 = require("../common/utils/crypto.util");
|
|
25
25
|
const quality_score_util_1 = require("../database/quality-score.util");
|
|
26
26
|
const pricing_sync_service_1 = require("../database/pricing-sync.service");
|
|
27
|
+
const models_dev_sync_service_1 = require("../database/models-dev-sync.service");
|
|
27
28
|
const openai_oauth_types_1 = require("../routing/oauth/openai-oauth.types");
|
|
28
29
|
const qwen_region_1 = require("../routing/qwen-region");
|
|
29
30
|
const copilot_token_service_1 = require("../routing/proxy/copilot-token.service");
|
|
@@ -39,14 +40,16 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
39
40
|
customProviderRepo;
|
|
40
41
|
fetcher;
|
|
41
42
|
pricingSync;
|
|
43
|
+
modelsDevSync;
|
|
42
44
|
modelRegistry;
|
|
43
45
|
copilotTokenService;
|
|
44
46
|
logger = new common_1.Logger(ModelDiscoveryService_1.name);
|
|
45
|
-
constructor(providerRepo, customProviderRepo, fetcher, pricingSync, modelRegistry, copilotTokenService) {
|
|
47
|
+
constructor(providerRepo, customProviderRepo, fetcher, pricingSync, modelsDevSync, modelRegistry, copilotTokenService) {
|
|
46
48
|
this.providerRepo = providerRepo;
|
|
47
49
|
this.customProviderRepo = customProviderRepo;
|
|
48
50
|
this.fetcher = fetcher;
|
|
49
51
|
this.pricingSync = pricingSync;
|
|
52
|
+
this.modelsDevSync = modelsDevSync;
|
|
50
53
|
this.modelRegistry = modelRegistry;
|
|
51
54
|
this.copilotTokenService = copilotTokenService;
|
|
52
55
|
}
|
|
@@ -98,11 +101,17 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
98
101
|
if (raw.length > 0 && this.modelRegistry) {
|
|
99
102
|
this.modelRegistry.registerModels(provider.provider, raw.map((m) => m.id));
|
|
100
103
|
}
|
|
104
|
+
if (raw.length === 0) {
|
|
105
|
+
raw = (0, model_fallback_1.buildModelsDevFallback)(this.modelsDevSync, provider.provider);
|
|
106
|
+
if (raw.length > 0) {
|
|
107
|
+
this.logger.log(`Native API returned 0 models for ${provider.provider} — using ${raw.length} models from models.dev`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
101
110
|
if (raw.length === 0 && !isQwenProvider(provider.provider)) {
|
|
102
111
|
const confirmed = this.modelRegistry?.getConfirmedModels(provider.provider) ?? null;
|
|
103
112
|
raw = (0, model_fallback_1.buildFallbackModels)(this.pricingSync, provider.provider, confirmed);
|
|
104
113
|
if (raw.length > 0) {
|
|
105
|
-
this.logger.log(`
|
|
114
|
+
this.logger.log(`No models.dev data for ${provider.provider} — using ${raw.length} models from OpenRouter`);
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
}
|
|
@@ -193,8 +202,22 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
193
202
|
return all.find((m) => m.id === modelName);
|
|
194
203
|
}
|
|
195
204
|
enrichModel(model, providerId) {
|
|
196
|
-
if (model.inputPricePerToken !== null && model.inputPricePerToken
|
|
197
|
-
return this.computeScore(model);
|
|
205
|
+
if (model.inputPricePerToken !== null && model.inputPricePerToken >= 0) {
|
|
206
|
+
return this.computeScore(this.applyCapabilities(model, providerId));
|
|
207
|
+
}
|
|
208
|
+
if (this.modelsDevSync) {
|
|
209
|
+
const mdEntry = this.modelsDevSync.lookupModel(providerId, model.id);
|
|
210
|
+
if (mdEntry && mdEntry.inputPricePerToken !== null) {
|
|
211
|
+
return this.computeScore({
|
|
212
|
+
...model,
|
|
213
|
+
inputPricePerToken: mdEntry.inputPricePerToken,
|
|
214
|
+
outputPricePerToken: mdEntry.outputPricePerToken,
|
|
215
|
+
contextWindow: mdEntry.contextWindow ?? model.contextWindow,
|
|
216
|
+
displayName: mdEntry.name || model.displayName,
|
|
217
|
+
capabilityReasoning: mdEntry.reasoning ?? model.capabilityReasoning,
|
|
218
|
+
capabilityCode: mdEntry.toolCall ?? model.capabilityCode,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
198
221
|
}
|
|
199
222
|
if (this.pricingSync) {
|
|
200
223
|
const orPrefix = (0, model_fallback_1.findOpenRouterPrefix)(providerId);
|
|
@@ -223,6 +246,18 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
223
246
|
}
|
|
224
247
|
return this.computeScore(model);
|
|
225
248
|
}
|
|
249
|
+
applyCapabilities(model, providerId) {
|
|
250
|
+
if (!this.modelsDevSync)
|
|
251
|
+
return model;
|
|
252
|
+
const mdEntry = this.modelsDevSync.lookupModel(providerId, model.id);
|
|
253
|
+
if (!mdEntry)
|
|
254
|
+
return model;
|
|
255
|
+
return {
|
|
256
|
+
...model,
|
|
257
|
+
capabilityReasoning: mdEntry.reasoning ?? model.capabilityReasoning,
|
|
258
|
+
capabilityCode: mdEntry.toolCall ?? model.capabilityCode,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
226
261
|
computeScore(model) {
|
|
227
262
|
const score = (0, quality_score_util_1.computeQualityScore)({
|
|
228
263
|
model_name: model.id,
|
|
@@ -243,11 +278,13 @@ exports.ModelDiscoveryService = ModelDiscoveryService = ModelDiscoveryService_1
|
|
|
243
278
|
__param(3, (0, common_1.Optional)()),
|
|
244
279
|
__param(3, (0, common_1.Inject)(pricing_sync_service_1.PricingSyncService)),
|
|
245
280
|
__param(4, (0, common_1.Optional)()),
|
|
246
|
-
__param(4, (0, common_1.Inject)(
|
|
281
|
+
__param(4, (0, common_1.Inject)(models_dev_sync_service_1.ModelsDevSyncService)),
|
|
247
282
|
__param(5, (0, common_1.Optional)()),
|
|
248
|
-
__param(5, (0, common_1.Inject)(
|
|
283
|
+
__param(5, (0, common_1.Inject)(provider_model_registry_service_1.ProviderModelRegistryService)),
|
|
284
|
+
__param(6, (0, common_1.Optional)()),
|
|
285
|
+
__param(6, (0, common_1.Inject)(copilot_token_service_1.CopilotTokenService)),
|
|
249
286
|
__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
250
287
|
typeorm_2.Repository,
|
|
251
|
-
provider_model_fetcher_service_1.ProviderModelFetcherService, Object, Object, Object])
|
|
288
|
+
provider_model_fetcher_service_1.ProviderModelFetcherService, Object, Object, Object, Object])
|
|
252
289
|
], ModelDiscoveryService);
|
|
253
290
|
//# sourceMappingURL=model-discovery.service.js.map
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.findOpenRouterPrefix = findOpenRouterPrefix;
|
|
4
4
|
exports.lookupWithVariants = lookupWithVariants;
|
|
5
|
+
exports.buildModelsDevFallback = buildModelsDevFallback;
|
|
5
6
|
exports.buildFallbackModels = buildFallbackModels;
|
|
6
7
|
exports.buildSubscriptionFallbackModels = buildSubscriptionFallbackModels;
|
|
7
8
|
exports.supplementWithKnownModels = supplementWithKnownModels;
|
|
@@ -58,8 +59,27 @@ function lookupWithVariants(pricingSync, prefix, modelId) {
|
|
|
58
59
|
return noDateDotResult;
|
|
59
60
|
}
|
|
60
61
|
}
|
|
62
|
+
const freeResult = pricingSync.lookupPricing(`${prefix}/${modelId}:free`);
|
|
63
|
+
if (freeResult)
|
|
64
|
+
return freeResult;
|
|
61
65
|
return null;
|
|
62
66
|
}
|
|
67
|
+
function buildModelsDevFallback(modelsDevSync, providerId) {
|
|
68
|
+
if (!modelsDevSync)
|
|
69
|
+
return [];
|
|
70
|
+
const entries = modelsDevSync.getModelsForProvider(providerId);
|
|
71
|
+
return entries.map((e) => ({
|
|
72
|
+
id: e.id,
|
|
73
|
+
displayName: e.name || e.id,
|
|
74
|
+
provider: providerId,
|
|
75
|
+
contextWindow: e.contextWindow ?? 128000,
|
|
76
|
+
inputPricePerToken: e.inputPricePerToken,
|
|
77
|
+
outputPricePerToken: e.outputPricePerToken,
|
|
78
|
+
capabilityReasoning: e.reasoning ?? false,
|
|
79
|
+
capabilityCode: e.toolCall ?? false,
|
|
80
|
+
qualityScore: 3,
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
63
83
|
function buildFallbackModels(pricingSync, providerId, confirmedModels) {
|
|
64
84
|
if (!pricingSync)
|
|
65
85
|
return [];
|
|
@@ -48,6 +48,15 @@ const parseOpenAI = createModelParser({
|
|
|
48
48
|
getId: (entry) => entry.id,
|
|
49
49
|
getDisplayName: (_entry, id) => id,
|
|
50
50
|
});
|
|
51
|
+
const OPENAI_NON_CHAT_RE = /(?:embed|tts|whisper|dall-e|moderation|davinci|babbage|^text-|audio|realtime|-transcribe|^sora|^gpt-3\.5-turbo-instruct)/i;
|
|
52
|
+
const OPENAI_RESPONSES_ONLY_RE = /(?:-codex(?!-mini-latest)|^gpt-5[^/]*-pro(?:-|$))/i;
|
|
53
|
+
function parseOpenAIChatOnly(body, provider) {
|
|
54
|
+
return parseOpenAI(body, provider).filter((m) => !OPENAI_NON_CHAT_RE.test(m.id) && !OPENAI_RESPONSES_ONLY_RE.test(m.id));
|
|
55
|
+
}
|
|
56
|
+
const MISTRAL_NON_CHAT_RE = /(?:^mistral-ocr|embed)/i;
|
|
57
|
+
function parseMistralChatOnly(body, provider) {
|
|
58
|
+
return parseOpenAI(body, provider).filter((m) => !MISTRAL_NON_CHAT_RE.test(m.id));
|
|
59
|
+
}
|
|
51
60
|
function bearerHeaders(key) {
|
|
52
61
|
return { Authorization: `Bearer ${key}` };
|
|
53
62
|
}
|
|
@@ -58,11 +67,12 @@ const parseAnthropic = createModelParser({
|
|
|
58
67
|
getDisplayName: (entry, id) => entry.display_name || id,
|
|
59
68
|
contextWindow: ANTHROPIC_DEFAULT_CONTEXT,
|
|
60
69
|
});
|
|
70
|
+
const GEMINI_VERSION_SUFFIX_RE = /-\d{3}$/;
|
|
61
71
|
function parseGemini(body, provider) {
|
|
62
72
|
const models = body?.models;
|
|
63
73
|
if (!Array.isArray(models))
|
|
64
74
|
return [];
|
|
65
|
-
|
|
75
|
+
const parsed = models
|
|
66
76
|
.filter((m) => {
|
|
67
77
|
const entry = m;
|
|
68
78
|
if (typeof entry.name !== 'string')
|
|
@@ -85,6 +95,13 @@ function parseGemini(body, provider) {
|
|
|
85
95
|
qualityScore: 3,
|
|
86
96
|
};
|
|
87
97
|
});
|
|
98
|
+
const ids = new Set(parsed.map((m) => m.id));
|
|
99
|
+
return parsed.filter((m) => {
|
|
100
|
+
if (!GEMINI_VERSION_SUFFIX_RE.test(m.id))
|
|
101
|
+
return true;
|
|
102
|
+
const alias = m.id.replace(GEMINI_VERSION_SUFFIX_RE, '');
|
|
103
|
+
return !ids.has(alias);
|
|
104
|
+
});
|
|
88
105
|
}
|
|
89
106
|
function parseOpenRouter(body, provider) {
|
|
90
107
|
const data = body?.data;
|
|
@@ -149,7 +166,7 @@ exports.PROVIDER_CONFIGS = {
|
|
|
149
166
|
openai: {
|
|
150
167
|
endpoint: 'https://api.openai.com/v1/models',
|
|
151
168
|
buildHeaders: bearerHeaders,
|
|
152
|
-
parse:
|
|
169
|
+
parse: parseOpenAIChatOnly,
|
|
153
170
|
},
|
|
154
171
|
'openai-subscription': {
|
|
155
172
|
endpoint: 'https://chatgpt.com/backend-api/codex/models?client_version=0.99.0',
|
|
@@ -169,7 +186,7 @@ exports.PROVIDER_CONFIGS = {
|
|
|
169
186
|
mistral: {
|
|
170
187
|
endpoint: 'https://api.mistral.ai/v1/models',
|
|
171
188
|
buildHeaders: bearerHeaders,
|
|
172
|
-
parse:
|
|
189
|
+
parse: parseMistralChatOnly,
|
|
173
190
|
},
|
|
174
191
|
moonshot: {
|
|
175
192
|
endpoint: 'https://api.moonshot.ai/v1/models',
|
|
@@ -13,6 +13,7 @@ const model_prices_controller_1 = require("./model-prices.controller");
|
|
|
13
13
|
const model_prices_service_1 = require("./model-prices.service");
|
|
14
14
|
const model_pricing_cache_service_1 = require("./model-pricing-cache.service");
|
|
15
15
|
const pricing_sync_service_1 = require("../database/pricing-sync.service");
|
|
16
|
+
const models_dev_sync_service_1 = require("../database/models-dev-sync.service");
|
|
16
17
|
const provider_model_registry_service_1 = require("../model-discovery/provider-model-registry.service");
|
|
17
18
|
const user_provider_entity_1 = require("../entities/user-provider.entity");
|
|
18
19
|
let ModelPricesModule = class ModelPricesModule {
|
|
@@ -26,9 +27,15 @@ exports.ModelPricesModule = ModelPricesModule = __decorate([
|
|
|
26
27
|
model_prices_service_1.ModelPricesService,
|
|
27
28
|
model_pricing_cache_service_1.ModelPricingCacheService,
|
|
28
29
|
pricing_sync_service_1.PricingSyncService,
|
|
30
|
+
models_dev_sync_service_1.ModelsDevSyncService,
|
|
31
|
+
provider_model_registry_service_1.ProviderModelRegistryService,
|
|
32
|
+
],
|
|
33
|
+
exports: [
|
|
34
|
+
model_pricing_cache_service_1.ModelPricingCacheService,
|
|
35
|
+
pricing_sync_service_1.PricingSyncService,
|
|
36
|
+
models_dev_sync_service_1.ModelsDevSyncService,
|
|
29
37
|
provider_model_registry_service_1.ProviderModelRegistryService,
|
|
30
38
|
],
|
|
31
|
-
exports: [model_pricing_cache_service_1.ModelPricingCacheService, pricing_sync_service_1.PricingSyncService, provider_model_registry_service_1.ProviderModelRegistryService],
|
|
32
39
|
})
|
|
33
40
|
], ModelPricesModule);
|
|
34
41
|
//# sourceMappingURL=model-prices.module.js.map
|
|
@@ -17,16 +17,19 @@ exports.ModelPricingCacheService = void 0;
|
|
|
17
17
|
const common_1 = require("@nestjs/common");
|
|
18
18
|
const model_name_normalizer_1 = require("./model-name-normalizer");
|
|
19
19
|
const pricing_sync_service_1 = require("../database/pricing-sync.service");
|
|
20
|
+
const models_dev_sync_service_1 = require("../database/models-dev-sync.service");
|
|
20
21
|
const providers_1 = require("../common/constants/providers");
|
|
21
22
|
const provider_model_registry_service_1 = require("../model-discovery/provider-model-registry.service");
|
|
22
23
|
let ModelPricingCacheService = ModelPricingCacheService_1 = class ModelPricingCacheService {
|
|
23
24
|
pricingSync;
|
|
25
|
+
modelsDevSync;
|
|
24
26
|
modelRegistry;
|
|
25
27
|
logger = new common_1.Logger(ModelPricingCacheService_1.name);
|
|
26
28
|
cache = new Map();
|
|
27
29
|
aliasMap = new Map();
|
|
28
|
-
constructor(pricingSync, modelRegistry) {
|
|
30
|
+
constructor(pricingSync, modelsDevSync, modelRegistry) {
|
|
29
31
|
this.pricingSync = pricingSync;
|
|
32
|
+
this.modelsDevSync = modelsDevSync;
|
|
30
33
|
this.modelRegistry = modelRegistry;
|
|
31
34
|
}
|
|
32
35
|
async onApplicationBootstrap() {
|
|
@@ -44,12 +47,14 @@ let ModelPricingCacheService = ModelPricingCacheService_1 = class ModelPricingCa
|
|
|
44
47
|
output_price_per_token: entry.output,
|
|
45
48
|
display_name: entry.displayName ?? null,
|
|
46
49
|
validated: this.resolveValidated(providerId, canonical),
|
|
50
|
+
source: 'openrouter',
|
|
47
51
|
};
|
|
48
52
|
this.cache.set(fullId, pricingEntry);
|
|
49
53
|
if (canonical !== fullId && !this.cache.has(canonical)) {
|
|
50
54
|
this.cache.set(canonical, pricingEntry);
|
|
51
55
|
}
|
|
52
56
|
}
|
|
57
|
+
this.loadModelsDevEntries();
|
|
53
58
|
this.aliasMap = (0, model_name_normalizer_1.buildAliasMap)([...this.cache.keys()]);
|
|
54
59
|
this.logger.log(`Loaded ${this.cache.size} pricing entries`);
|
|
55
60
|
}
|
|
@@ -92,6 +97,38 @@ let ModelPricingCacheService = ModelPricingCacheService_1 = class ModelPricingCa
|
|
|
92
97
|
}
|
|
93
98
|
return { provider: 'OpenRouter', canonical: openRouterId, providerId: null };
|
|
94
99
|
}
|
|
100
|
+
loadModelsDevEntries() {
|
|
101
|
+
if (!this.modelsDevSync)
|
|
102
|
+
return;
|
|
103
|
+
let count = 0;
|
|
104
|
+
for (const [providerId, registryEntry] of providers_1.PROVIDER_BY_ID) {
|
|
105
|
+
const models = this.modelsDevSync.getModelsForProvider(providerId);
|
|
106
|
+
for (const model of models) {
|
|
107
|
+
if (model.inputPricePerToken === null)
|
|
108
|
+
continue;
|
|
109
|
+
const pricingEntry = {
|
|
110
|
+
model_name: model.id,
|
|
111
|
+
provider: registryEntry.displayName,
|
|
112
|
+
input_price_per_token: model.inputPricePerToken,
|
|
113
|
+
output_price_per_token: model.outputPricePerToken,
|
|
114
|
+
display_name: model.name || null,
|
|
115
|
+
validated: this.resolveValidatedForModelsDev(providerId, model.id),
|
|
116
|
+
source: 'models.dev',
|
|
117
|
+
};
|
|
118
|
+
this.cache.set(model.id, pricingEntry);
|
|
119
|
+
for (const prefix of registryEntry.openRouterPrefixes) {
|
|
120
|
+
const prefixedKey = `${prefix}/${model.id}`;
|
|
121
|
+
if (this.cache.has(prefixedKey)) {
|
|
122
|
+
this.cache.set(prefixedKey, { ...pricingEntry, model_name: prefixedKey });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
count++;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (count > 0) {
|
|
129
|
+
this.logger.log(`Overlaid ${count} models.dev pricing entries`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
95
132
|
resolveValidated(providerId, canonical) {
|
|
96
133
|
if (!this.modelRegistry || !providerId)
|
|
97
134
|
return undefined;
|
|
@@ -100,12 +137,20 @@ let ModelPricingCacheService = ModelPricingCacheService_1 = class ModelPricingCa
|
|
|
100
137
|
const result = this.modelRegistry.isModelConfirmed(canonicalProviderId, canonical);
|
|
101
138
|
return result ?? undefined;
|
|
102
139
|
}
|
|
140
|
+
resolveValidatedForModelsDev(providerId, modelId) {
|
|
141
|
+
if (!this.modelRegistry)
|
|
142
|
+
return undefined;
|
|
143
|
+
const result = this.modelRegistry.isModelConfirmed(providerId, modelId);
|
|
144
|
+
return result ?? undefined;
|
|
145
|
+
}
|
|
103
146
|
};
|
|
104
147
|
exports.ModelPricingCacheService = ModelPricingCacheService;
|
|
105
148
|
exports.ModelPricingCacheService = ModelPricingCacheService = ModelPricingCacheService_1 = __decorate([
|
|
106
149
|
(0, common_1.Injectable)(),
|
|
107
150
|
__param(1, (0, common_1.Optional)()),
|
|
108
|
-
__param(1, (0, common_1.Inject)(
|
|
109
|
-
|
|
151
|
+
__param(1, (0, common_1.Inject)(models_dev_sync_service_1.ModelsDevSyncService)),
|
|
152
|
+
__param(2, (0, common_1.Optional)()),
|
|
153
|
+
__param(2, (0, common_1.Inject)(provider_model_registry_service_1.ProviderModelRegistryService)),
|
|
154
|
+
__metadata("design:paramtypes", [pricing_sync_service_1.PricingSyncService, Object, Object])
|
|
110
155
|
], ModelPricingCacheService);
|
|
111
156
|
//# sourceMappingURL=model-pricing-cache.service.js.map
|
|
@@ -49,6 +49,7 @@ const OPENAI_ONLY_FIELDS = new Set([
|
|
|
49
49
|
const PASSTHROUGH_PROVIDERS = new Set(['openai', 'openrouter']);
|
|
50
50
|
const MISTRAL_TOOL_CALL_ID_REGEX = /^[A-Za-z0-9]{9}$/;
|
|
51
51
|
const DEEPSEEK_MAX_TOKENS_LIMIT = 8192;
|
|
52
|
+
const OPENAI_MAX_COMPLETION_TOKENS_RE = /^(o\d|gpt-5)/i;
|
|
52
53
|
function supportsReasoningContent(endpointKey, model) {
|
|
53
54
|
if (endpointKey === 'deepseek')
|
|
54
55
|
return true;
|
|
@@ -153,6 +154,11 @@ function normalizeDeepSeekMaxTokens(body) {
|
|
|
153
154
|
}
|
|
154
155
|
function sanitizeOpenAiBody(body, endpointKey, model) {
|
|
155
156
|
const passthroughTopLevel = PASSTHROUGH_PROVIDERS.has(endpointKey);
|
|
157
|
+
const bareForRegex = model.includes('/') ? model.substring(model.indexOf('/') + 1) : model;
|
|
158
|
+
const convertMaxTokens = endpointKey === 'openai' &&
|
|
159
|
+
OPENAI_MAX_COMPLETION_TOKENS_RE.test(bareForRegex) &&
|
|
160
|
+
'max_tokens' in body &&
|
|
161
|
+
!('max_completion_tokens' in body);
|
|
156
162
|
const cleaned = {};
|
|
157
163
|
for (const [key, value] of Object.entries(body)) {
|
|
158
164
|
if (key === 'messages') {
|
|
@@ -160,6 +166,10 @@ function sanitizeOpenAiBody(body, endpointKey, model) {
|
|
|
160
166
|
continue;
|
|
161
167
|
}
|
|
162
168
|
if (passthroughTopLevel) {
|
|
169
|
+
if (convertMaxTokens && key === 'max_tokens') {
|
|
170
|
+
cleaned['max_completion_tokens'] = value;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
163
173
|
cleaned[key] = value;
|
|
164
174
|
continue;
|
|
165
175
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "manifest",
|
|
3
3
|
"name": "Manifest Self-Hosted LLM Router",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.35.0",
|
|
5
5
|
"description": "Run the Manifest LLM router locally with SQLite. Zero-config dashboard included.",
|
|
6
6
|
"author": "MNFST Inc.",
|
|
7
7
|
"homepage": "https://manifest.build",
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "manifest",
|
|
3
3
|
"name": "Manifest Self-Hosted LLM Router",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.35.0",
|
|
5
5
|
"description": "Run the Manifest LLM router locally with SQLite. Zero-config dashboard included.",
|
|
6
6
|
"author": "MNFST Inc.",
|
|
7
7
|
"homepage": "https://manifest.build",
|