manifest 5.26.0 → 5.27.1
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/README.md +1 -1
- package/dist/backend/database/seed-messages.js +1 -0
- package/dist/backend/otlp/services/trace-ingest.service.js +27 -1
- package/dist/backend/routing/model-discovery/model-discovery.service.js +45 -89
- package/dist/backend/routing/model-discovery/model-fallback.js +164 -0
- package/dist/backend/routing/model-discovery/provider-model-fetcher.service.js +39 -1
- package/dist/backend/routing/openai-oauth.controller.js +132 -0
- package/dist/backend/routing/openai-oauth.service.js +274 -0
- package/dist/backend/routing/openai-oauth.types.js +23 -0
- package/dist/backend/routing/proxy/chatgpt-adapter.js +137 -0
- package/dist/backend/routing/proxy/provider-client.js +18 -2
- package/dist/backend/routing/proxy/provider-endpoints.js +13 -0
- package/dist/backend/routing/proxy/proxy-message-recorder.js +354 -0
- package/dist/backend/routing/proxy/proxy.controller.js +39 -303
- package/dist/backend/routing/proxy/proxy.service.js +20 -5
- package/dist/backend/routing/routing-invalidation.service.js +89 -0
- package/dist/backend/routing/routing.controller.js +1 -0
- package/dist/backend/routing/routing.module.js +22 -2
- package/dist/backend/routing/routing.service.js +2 -45
- package/dist/backend/routing/tier-auto-assign.service.js +21 -15
- package/dist/index.js +2 -2
- package/dist/local-mode.js +1 -1
- package/dist/openclaw.plugin.json +9 -3
- package/dist/subscription.js +1 -1
- package/openclaw.plugin.json +9 -3
- package/package.json +1 -1
- package/public/assets/{Account-DtR3Bovj.js → Account-CONa2CGc.js} +1 -1
- package/public/assets/Limits-BmVBnqQ8.js +1 -0
- package/public/assets/Login-CnKzDsL7.js +1 -0
- package/public/assets/{MessageLog-BfdBjKNj.js → MessageLog-U-hPyvnx.js} +1 -1
- package/public/assets/{ModelPrices-D9JiRM0x.js → ModelPrices-Cu8cQZq3.js} +1 -1
- package/public/assets/{Overview-C5W2WzOa.js → Overview-C6Vae67r.js} +1 -1
- package/public/assets/ProviderIcon-DuNcnqvr.js +1 -0
- package/public/assets/{Register-CK1-HR7-.js → Register-gAPDw0Q2.js} +1 -1
- package/public/assets/{ResetPassword-BXCgyBPz.js → ResetPassword-BMTzQWBS.js} +1 -1
- package/public/assets/Routing-1s85NXlk.js +3 -0
- package/public/assets/{Settings-Cs8Y-DYp.js → Settings-CBoPohdM.js} +1 -1
- package/public/assets/{SocialButtons-B29ADQVX.js → SocialButtons-7ZsVfsLs.js} +1 -1
- package/public/assets/{index-BJbiHRsE.css → index-BZMbFWG-.css} +1 -1
- package/public/assets/index-C0MaOoW6.js +2 -0
- package/public/assets/{model-display-3wEhsKxc.js → model-display-Dcao1u69.js} +1 -1
- package/public/assets/{overview-cTUikYPe.js → overview-ulMqu_nV.js} +1 -1
- package/public/index.html +2 -2
- package/subscription-capabilities/index.d.ts +18 -1
- package/subscription-capabilities/index.js +36 -0
- package/public/assets/Limits-DYEhhHov.js +0 -1
- package/public/assets/Login-sV6eKBAf.js +0 -1
- package/public/assets/ProviderIcon-ZusUWlbr.js +0 -1
- package/public/assets/Routing-ByCp9o3v.js +0 -3
- package/public/assets/index-BV7oRqov.js +0 -2
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ OpenClaw costs
|
|
|
31
31
|
## What do you get?
|
|
32
32
|
|
|
33
33
|
- 🔀 **Route requests to the right model** — cut costs up to 70%
|
|
34
|
-
-
|
|
34
|
+
- 🔄 **Automatic fallbacks** — if a model fails, retry with backup models instantly
|
|
35
35
|
- 🔔 **Set limits** — get alerts when usage goes over a threshold
|
|
36
36
|
|
|
37
37
|
## Why Manifest
|
|
@@ -18,6 +18,7 @@ async function seedAgentMessages(messageRepo, userId, logger, ctx = {
|
|
|
18
18
|
{ name: 'gpt-4o', auth_type: 'api_key' },
|
|
19
19
|
{ name: 'claude-haiku-4-5-20251001', auth_type: 'subscription' },
|
|
20
20
|
{ name: 'gemini-2.5-flash', auth_type: 'api_key' },
|
|
21
|
+
{ name: 'gpt-4.1', auth_type: 'subscription' },
|
|
21
22
|
];
|
|
22
23
|
const now = Date.now();
|
|
23
24
|
const messages = [];
|
|
@@ -176,7 +176,7 @@ let TraceIngestService = class TraceIngestService {
|
|
|
176
176
|
if (traceId)
|
|
177
177
|
traceIds.push(traceId);
|
|
178
178
|
}
|
|
179
|
-
const [errorByTrace, recentErrors, recentOkMessages, recentMessages] = await Promise.all([
|
|
179
|
+
const [errorByTrace, successByTrace, recentErrors, recentOkMessages, recentMessages] = await Promise.all([
|
|
180
180
|
traceIds.length > 0
|
|
181
181
|
? turnRepo.find({
|
|
182
182
|
where: {
|
|
@@ -187,6 +187,16 @@ let TraceIngestService = class TraceIngestService {
|
|
|
187
187
|
select: ['id', 'trace_id'],
|
|
188
188
|
})
|
|
189
189
|
: Promise.resolve([]),
|
|
190
|
+
traceIds.length > 0
|
|
191
|
+
? turnRepo.find({
|
|
192
|
+
where: {
|
|
193
|
+
trace_id: (0, typeorm_3.In)(traceIds),
|
|
194
|
+
tenant_id: ctx.tenantId,
|
|
195
|
+
status: 'ok',
|
|
196
|
+
},
|
|
197
|
+
select: ['id', 'trace_id'],
|
|
198
|
+
})
|
|
199
|
+
: Promise.resolve([]),
|
|
190
200
|
turnRepo.find({
|
|
191
201
|
where: {
|
|
192
202
|
tenant_id: ctx.tenantId,
|
|
@@ -212,6 +222,7 @@ let TraceIngestService = class TraceIngestService {
|
|
|
212
222
|
]);
|
|
213
223
|
return {
|
|
214
224
|
errorTraceIds: new Set(errorByTrace.map((e) => e.trace_id)),
|
|
225
|
+
successTraceIds: new Set(successByTrace.map((e) => e.trace_id)),
|
|
215
226
|
recentErrors: recentErrors.map((e) => ({ id: e.id, timestamp: e.timestamp })),
|
|
216
227
|
recentOkMessages: recentOkMessages.map((m) => ({
|
|
217
228
|
id: m.id,
|
|
@@ -361,6 +372,8 @@ let TraceIngestService = class TraceIngestService {
|
|
|
361
372
|
const traceId = (0, otlp_helpers_1.toHexString)(span.traceId);
|
|
362
373
|
if (traceId && dedup.errorTraceIds.has(traceId))
|
|
363
374
|
return null;
|
|
375
|
+
if (traceId && dedup.successTraceIds.has(traceId))
|
|
376
|
+
return null;
|
|
364
377
|
const spanTime = new Date((0, otlp_helpers_1.nanoToDatetime)(span.startTimeUnixNano)).getTime();
|
|
365
378
|
const hasNearbyError = dedup.recentErrors.some((e) => {
|
|
366
379
|
const errorTime = new Date(e.timestamp).getTime();
|
|
@@ -370,6 +383,7 @@ let TraceIngestService = class TraceIngestService {
|
|
|
370
383
|
return null;
|
|
371
384
|
const spanInputTokens = (0, otlp_helpers_1.attrNumber)(attrs, 'gen_ai.usage.input_tokens') ?? 0;
|
|
372
385
|
const spanOutputTokens = (0, otlp_helpers_1.attrNumber)(attrs, 'gen_ai.usage.output_tokens') ?? 0;
|
|
386
|
+
const spanModel = (0, otlp_helpers_1.attrString)(attrs, 'gen_ai.request.model') ?? (0, otlp_helpers_1.attrString)(attrs, 'gen_ai.response.model');
|
|
373
387
|
if (spanInputTokens === 0 && spanOutputTokens === 0) {
|
|
374
388
|
const hasProxyData = dedup.recentOkMessages.some((m) => {
|
|
375
389
|
const mTime = new Date(m.timestamp).getTime();
|
|
@@ -378,6 +392,18 @@ let TraceIngestService = class TraceIngestService {
|
|
|
378
392
|
if (hasProxyData)
|
|
379
393
|
return null;
|
|
380
394
|
}
|
|
395
|
+
else {
|
|
396
|
+
const hasMatchingProxy = dedup.recentMessages.some((m) => {
|
|
397
|
+
if (m.model !== spanModel && !m.model?.startsWith(`${spanModel}-`))
|
|
398
|
+
return false;
|
|
399
|
+
const mTime = new Date(m.timestamp).getTime();
|
|
400
|
+
if (Math.abs(mTime - spanTime) > 30_000)
|
|
401
|
+
return false;
|
|
402
|
+
return m.input_tokens === spanInputTokens;
|
|
403
|
+
});
|
|
404
|
+
if (hasMatchingProxy)
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
381
407
|
if (this.isEmptyOkSpan(span, attrs)) {
|
|
382
408
|
const sessionKey = (0, otlp_helpers_1.attrString)(attrs, 'session.key');
|
|
383
409
|
const candidates = sessionKey
|
|
@@ -22,8 +22,8 @@ const custom_provider_entity_1 = require("../../entities/custom-provider.entity"
|
|
|
22
22
|
const provider_model_fetcher_service_1 = require("./provider-model-fetcher.service");
|
|
23
23
|
const crypto_util_1 = require("../../common/utils/crypto.util");
|
|
24
24
|
const quality_score_util_1 = require("../../database/quality-score.util");
|
|
25
|
-
const providers_1 = require("../../common/constants/providers");
|
|
26
25
|
const pricing_sync_service_1 = require("../../database/pricing-sync.service");
|
|
26
|
+
const model_fallback_1 = require("./model-fallback");
|
|
27
27
|
const customProviderKey = (id) => `custom:${id}`;
|
|
28
28
|
const customModelKey = (id, modelName) => `custom:${id}/${modelName}`;
|
|
29
29
|
let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryService {
|
|
@@ -49,14 +49,42 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
49
49
|
return [];
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
if (provider.auth_type === 'subscription' &&
|
|
53
|
+
provider.provider.toLowerCase() === 'openai' &&
|
|
54
|
+
apiKey) {
|
|
55
|
+
try {
|
|
56
|
+
const blob = JSON.parse(apiKey);
|
|
57
|
+
if (blob.t) {
|
|
58
|
+
apiKey = blob.t;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
let raw;
|
|
65
|
+
if (provider.auth_type === 'subscription' && !apiKey) {
|
|
66
|
+
raw = (0, model_fallback_1.buildSubscriptionFallbackModels)(this.pricingSync, provider.provider);
|
|
55
67
|
if (raw.length > 0) {
|
|
56
|
-
this.logger.log(`
|
|
68
|
+
this.logger.log(`No token for subscription provider ${provider.provider} — using ${raw.length} fallback models`);
|
|
57
69
|
}
|
|
58
70
|
}
|
|
59
|
-
|
|
71
|
+
else {
|
|
72
|
+
raw = await this.fetcher.fetch(provider.provider, apiKey, provider.auth_type);
|
|
73
|
+
if (raw.length === 0) {
|
|
74
|
+
raw = (0, model_fallback_1.buildFallbackModels)(this.pricingSync, provider.provider);
|
|
75
|
+
if (raw.length > 0) {
|
|
76
|
+
this.logger.log(`Native API returned 0 models for ${provider.provider} — using ${raw.length} models from pricing data`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (provider.auth_type === 'subscription') {
|
|
81
|
+
raw = (0, model_fallback_1.supplementWithKnownModels)(raw, provider.provider);
|
|
82
|
+
}
|
|
83
|
+
const authType = provider.auth_type === 'subscription' ? 'subscription' : 'api_key';
|
|
84
|
+
const enriched = raw.map((model) => ({
|
|
85
|
+
...this.enrichModel(model, provider.provider),
|
|
86
|
+
authType: authType,
|
|
87
|
+
}));
|
|
60
88
|
provider.cached_models = enriched;
|
|
61
89
|
provider.models_fetched_at = new Date().toISOString();
|
|
62
90
|
await this.providerRepo.save(provider);
|
|
@@ -78,18 +106,24 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
78
106
|
where: { agent_id: agentId, is_active: true },
|
|
79
107
|
});
|
|
80
108
|
const models = [];
|
|
81
|
-
const seen = new
|
|
109
|
+
const seen = new Map();
|
|
82
110
|
for (const p of providers) {
|
|
83
111
|
if (p.provider.startsWith('custom:'))
|
|
84
112
|
continue;
|
|
85
113
|
const cached = p.cached_models;
|
|
86
114
|
if (!Array.isArray(cached))
|
|
87
115
|
continue;
|
|
116
|
+
const providerAuthType = p.auth_type === 'subscription' ? 'subscription' : 'api_key';
|
|
88
117
|
for (const m of cached) {
|
|
118
|
+
const effectiveAuthType = m.authType ?? providerAuthType;
|
|
89
119
|
if (!seen.has(m.id)) {
|
|
90
|
-
seen.
|
|
120
|
+
seen.set(m.id, models.length);
|
|
91
121
|
models.push(m);
|
|
92
122
|
}
|
|
123
|
+
else if (effectiveAuthType === 'subscription' &&
|
|
124
|
+
models[seen.get(m.id)]?.authType !== 'subscription') {
|
|
125
|
+
models[seen.get(m.id)] = m;
|
|
126
|
+
}
|
|
93
127
|
}
|
|
94
128
|
}
|
|
95
129
|
const customProviders = await this.customProviderRepo.find({
|
|
@@ -103,7 +137,7 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
103
137
|
const modelKey = customModelKey(cp.id, m.model_name);
|
|
104
138
|
if (seen.has(modelKey))
|
|
105
139
|
continue;
|
|
106
|
-
seen.
|
|
140
|
+
seen.set(modelKey, models.length);
|
|
107
141
|
const inputPerToken = m.input_price_per_million_tokens != null
|
|
108
142
|
? m.input_price_per_million_tokens / 1_000_000
|
|
109
143
|
: null;
|
|
@@ -134,9 +168,9 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
134
168
|
return this.computeScore(model);
|
|
135
169
|
}
|
|
136
170
|
if (this.pricingSync) {
|
|
137
|
-
const orPrefix =
|
|
171
|
+
const orPrefix = (0, model_fallback_1.findOpenRouterPrefix)(providerId);
|
|
138
172
|
if (orPrefix) {
|
|
139
|
-
const orPricing =
|
|
173
|
+
const orPricing = (0, model_fallback_1.lookupWithVariants)(this.pricingSync, orPrefix, model.id);
|
|
140
174
|
if (orPricing) {
|
|
141
175
|
return this.computeScore({
|
|
142
176
|
...model,
|
|
@@ -160,84 +194,6 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
160
194
|
}
|
|
161
195
|
return this.computeScore(model);
|
|
162
196
|
}
|
|
163
|
-
findOpenRouterPrefix(providerId) {
|
|
164
|
-
const lower = providerId.toLowerCase();
|
|
165
|
-
if (providers_1.OPENROUTER_PREFIX_TO_PROVIDER.has(lower))
|
|
166
|
-
return lower;
|
|
167
|
-
const entry = providers_1.PROVIDER_BY_ID_OR_ALIAS.get(lower);
|
|
168
|
-
if (entry) {
|
|
169
|
-
for (const prefix of entry.openRouterPrefixes) {
|
|
170
|
-
if (providers_1.OPENROUTER_PREFIX_TO_PROVIDER.has(prefix))
|
|
171
|
-
return prefix;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
for (const [prefix, displayName] of providers_1.OPENROUTER_PREFIX_TO_PROVIDER) {
|
|
175
|
-
if (displayName.toLowerCase() === lower)
|
|
176
|
-
return prefix;
|
|
177
|
-
}
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
lookupWithVariants(prefix, modelId) {
|
|
181
|
-
if (!this.pricingSync)
|
|
182
|
-
return null;
|
|
183
|
-
const exact = this.pricingSync.lookupPricing(`${prefix}/${modelId}`);
|
|
184
|
-
if (exact)
|
|
185
|
-
return exact;
|
|
186
|
-
const dotVariant = modelId.replace(/-(\d+)-(\d)/g, '-$1.$2');
|
|
187
|
-
if (dotVariant !== modelId) {
|
|
188
|
-
const dotResult = this.pricingSync.lookupPricing(`${prefix}/${dotVariant}`);
|
|
189
|
-
if (dotResult)
|
|
190
|
-
return dotResult;
|
|
191
|
-
}
|
|
192
|
-
const dashVariant = modelId.replace(/\.(\d)/g, '-$1');
|
|
193
|
-
if (dashVariant !== modelId) {
|
|
194
|
-
const dashResult = this.pricingSync.lookupPricing(`${prefix}/${dashVariant}`);
|
|
195
|
-
if (dashResult)
|
|
196
|
-
return dashResult;
|
|
197
|
-
}
|
|
198
|
-
const noDate = modelId.replace(/-\d{8}$/, '');
|
|
199
|
-
if (noDate !== modelId) {
|
|
200
|
-
const noDateResult = this.pricingSync.lookupPricing(`${prefix}/${noDate}`);
|
|
201
|
-
if (noDateResult)
|
|
202
|
-
return noDateResult;
|
|
203
|
-
const noDateDot = noDate.replace(/-(\d+)-(\d)/g, '-$1.$2');
|
|
204
|
-
if (noDateDot !== noDate) {
|
|
205
|
-
const noDateDotResult = this.pricingSync.lookupPricing(`${prefix}/${noDateDot}`);
|
|
206
|
-
if (noDateDotResult)
|
|
207
|
-
return noDateDotResult;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
buildFallbackModels(providerId) {
|
|
213
|
-
const models = [];
|
|
214
|
-
const seen = new Set();
|
|
215
|
-
if (this.pricingSync) {
|
|
216
|
-
const orPrefix = this.findOpenRouterPrefix(providerId);
|
|
217
|
-
if (orPrefix) {
|
|
218
|
-
for (const [fullId, entry] of this.pricingSync.getAll()) {
|
|
219
|
-
if (!fullId.startsWith(`${orPrefix}/`))
|
|
220
|
-
continue;
|
|
221
|
-
const modelId = fullId.substring(orPrefix.length + 1);
|
|
222
|
-
if (seen.has(modelId))
|
|
223
|
-
continue;
|
|
224
|
-
seen.add(modelId);
|
|
225
|
-
models.push({
|
|
226
|
-
id: modelId,
|
|
227
|
-
displayName: entry.displayName || modelId,
|
|
228
|
-
provider: providerId,
|
|
229
|
-
contextWindow: entry.contextWindow ?? 128000,
|
|
230
|
-
inputPricePerToken: entry.input,
|
|
231
|
-
outputPricePerToken: entry.output,
|
|
232
|
-
capabilityReasoning: false,
|
|
233
|
-
capabilityCode: false,
|
|
234
|
-
qualityScore: 3,
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return models;
|
|
240
|
-
}
|
|
241
197
|
computeScore(model) {
|
|
242
198
|
const score = (0, quality_score_util_1.computeQualityScore)({
|
|
243
199
|
model_name: model.id,
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findOpenRouterPrefix = findOpenRouterPrefix;
|
|
4
|
+
exports.lookupWithVariants = lookupWithVariants;
|
|
5
|
+
exports.buildFallbackModels = buildFallbackModels;
|
|
6
|
+
exports.buildSubscriptionFallbackModels = buildSubscriptionFallbackModels;
|
|
7
|
+
exports.supplementWithKnownModels = supplementWithKnownModels;
|
|
8
|
+
const providers_1 = require("../../common/constants/providers");
|
|
9
|
+
const subscription_capabilities_1 = require("../../../../subscription-capabilities");
|
|
10
|
+
function findOpenRouterPrefix(providerId) {
|
|
11
|
+
const lower = providerId.toLowerCase();
|
|
12
|
+
if (providers_1.OPENROUTER_PREFIX_TO_PROVIDER.has(lower))
|
|
13
|
+
return lower;
|
|
14
|
+
const entry = providers_1.PROVIDER_BY_ID_OR_ALIAS.get(lower);
|
|
15
|
+
if (entry) {
|
|
16
|
+
for (const prefix of entry.openRouterPrefixes) {
|
|
17
|
+
if (providers_1.OPENROUTER_PREFIX_TO_PROVIDER.has(prefix))
|
|
18
|
+
return prefix;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
for (const [prefix, displayName] of providers_1.OPENROUTER_PREFIX_TO_PROVIDER) {
|
|
22
|
+
if (displayName.toLowerCase() === lower)
|
|
23
|
+
return prefix;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function lookupWithVariants(pricingSync, prefix, modelId) {
|
|
28
|
+
const exact = pricingSync.lookupPricing(`${prefix}/${modelId}`);
|
|
29
|
+
if (exact)
|
|
30
|
+
return exact;
|
|
31
|
+
const dotVariant = modelId.replace(/-(\d+)-(\d)/g, '-$1.$2');
|
|
32
|
+
if (dotVariant !== modelId) {
|
|
33
|
+
const dotResult = pricingSync.lookupPricing(`${prefix}/${dotVariant}`);
|
|
34
|
+
if (dotResult)
|
|
35
|
+
return dotResult;
|
|
36
|
+
}
|
|
37
|
+
const dashVariant = modelId.replace(/\.(\d)/g, '-$1');
|
|
38
|
+
if (dashVariant !== modelId) {
|
|
39
|
+
const dashResult = pricingSync.lookupPricing(`${prefix}/${dashVariant}`);
|
|
40
|
+
if (dashResult)
|
|
41
|
+
return dashResult;
|
|
42
|
+
}
|
|
43
|
+
const noDate = modelId.replace(/-\d{8}$/, '');
|
|
44
|
+
if (noDate !== modelId) {
|
|
45
|
+
const noDateResult = pricingSync.lookupPricing(`${prefix}/${noDate}`);
|
|
46
|
+
if (noDateResult)
|
|
47
|
+
return noDateResult;
|
|
48
|
+
const noDateDot = noDate.replace(/-(\d+)-(\d)/g, '-$1.$2');
|
|
49
|
+
if (noDateDot !== noDate) {
|
|
50
|
+
const noDateDotResult = pricingSync.lookupPricing(`${prefix}/${noDateDot}`);
|
|
51
|
+
if (noDateDotResult)
|
|
52
|
+
return noDateDotResult;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function buildFallbackModels(pricingSync, providerId) {
|
|
58
|
+
if (!pricingSync)
|
|
59
|
+
return [];
|
|
60
|
+
const models = [];
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
const orPrefix = findOpenRouterPrefix(providerId);
|
|
63
|
+
if (!orPrefix)
|
|
64
|
+
return [];
|
|
65
|
+
for (const [fullId, entry] of pricingSync.getAll()) {
|
|
66
|
+
if (!fullId.startsWith(`${orPrefix}/`))
|
|
67
|
+
continue;
|
|
68
|
+
const modelId = fullId.substring(orPrefix.length + 1);
|
|
69
|
+
if (seen.has(modelId))
|
|
70
|
+
continue;
|
|
71
|
+
seen.add(modelId);
|
|
72
|
+
models.push({
|
|
73
|
+
id: modelId,
|
|
74
|
+
displayName: entry.displayName || modelId,
|
|
75
|
+
provider: providerId,
|
|
76
|
+
contextWindow: entry.contextWindow ?? 128000,
|
|
77
|
+
inputPricePerToken: entry.input,
|
|
78
|
+
outputPricePerToken: entry.output,
|
|
79
|
+
capabilityReasoning: false,
|
|
80
|
+
capabilityCode: false,
|
|
81
|
+
qualityScore: 3,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return models;
|
|
85
|
+
}
|
|
86
|
+
function buildSubscriptionFallbackModels(pricingSync, providerId) {
|
|
87
|
+
const knownPrefixes = (0, subscription_capabilities_1.getSubscriptionKnownModels)(providerId);
|
|
88
|
+
if (!knownPrefixes)
|
|
89
|
+
return [];
|
|
90
|
+
const capabilities = (0, subscription_capabilities_1.getSubscriptionCapabilities)(providerId);
|
|
91
|
+
const models = [];
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
const orPrefix = pricingSync ? findOpenRouterPrefix(providerId) : null;
|
|
94
|
+
if (pricingSync && orPrefix) {
|
|
95
|
+
for (const [fullId, entry] of pricingSync.getAll()) {
|
|
96
|
+
if (!fullId.startsWith(`${orPrefix}/`))
|
|
97
|
+
continue;
|
|
98
|
+
const modelId = fullId.substring(orPrefix.length + 1);
|
|
99
|
+
if (!knownPrefixes.some((p) => modelId.startsWith(p)))
|
|
100
|
+
continue;
|
|
101
|
+
if (seen.has(modelId))
|
|
102
|
+
continue;
|
|
103
|
+
seen.add(modelId);
|
|
104
|
+
let contextWindow = entry.contextWindow ?? 128000;
|
|
105
|
+
if (capabilities?.maxContextWindow && contextWindow > capabilities.maxContextWindow) {
|
|
106
|
+
contextWindow = capabilities.maxContextWindow;
|
|
107
|
+
}
|
|
108
|
+
models.push({
|
|
109
|
+
id: modelId,
|
|
110
|
+
displayName: entry.displayName || modelId,
|
|
111
|
+
provider: providerId,
|
|
112
|
+
contextWindow,
|
|
113
|
+
inputPricePerToken: entry.input,
|
|
114
|
+
outputPricePerToken: entry.output,
|
|
115
|
+
capabilityReasoning: false,
|
|
116
|
+
capabilityCode: false,
|
|
117
|
+
qualityScore: 3,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const defaultCtx = capabilities?.maxContextWindow ?? 200000;
|
|
122
|
+
for (const modelId of knownPrefixes) {
|
|
123
|
+
const covered = models.some((m) => m.id === modelId || m.id.startsWith(`${modelId}-`));
|
|
124
|
+
if (covered)
|
|
125
|
+
continue;
|
|
126
|
+
models.push({
|
|
127
|
+
id: modelId,
|
|
128
|
+
displayName: modelId,
|
|
129
|
+
provider: providerId,
|
|
130
|
+
contextWindow: defaultCtx,
|
|
131
|
+
inputPricePerToken: 0,
|
|
132
|
+
outputPricePerToken: 0,
|
|
133
|
+
capabilityReasoning: false,
|
|
134
|
+
capabilityCode: false,
|
|
135
|
+
qualityScore: 3,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return models;
|
|
139
|
+
}
|
|
140
|
+
function supplementWithKnownModels(raw, providerId) {
|
|
141
|
+
const knownModels = (0, subscription_capabilities_1.getSubscriptionKnownModels)(providerId);
|
|
142
|
+
if (!knownModels)
|
|
143
|
+
return raw;
|
|
144
|
+
const capabilities = (0, subscription_capabilities_1.getSubscriptionCapabilities)(providerId);
|
|
145
|
+
const defaultCtx = capabilities?.maxContextWindow ?? 200000;
|
|
146
|
+
for (const modelId of knownModels) {
|
|
147
|
+
const covered = raw.some((m) => m.id === modelId || m.id.startsWith(`${modelId}-`));
|
|
148
|
+
if (covered)
|
|
149
|
+
continue;
|
|
150
|
+
raw.push({
|
|
151
|
+
id: modelId,
|
|
152
|
+
displayName: modelId,
|
|
153
|
+
provider: providerId,
|
|
154
|
+
contextWindow: defaultCtx,
|
|
155
|
+
inputPricePerToken: 0,
|
|
156
|
+
outputPricePerToken: 0,
|
|
157
|
+
capabilityReasoning: false,
|
|
158
|
+
capabilityCode: false,
|
|
159
|
+
qualityScore: 3,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return raw;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=model-fallback.js.map
|
|
@@ -147,12 +147,46 @@ function parseOllama(body, provider) {
|
|
|
147
147
|
};
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
|
+
function parseOpenaiSubscription(body, provider) {
|
|
151
|
+
const data = body?.models;
|
|
152
|
+
if (!Array.isArray(data))
|
|
153
|
+
return [];
|
|
154
|
+
return data
|
|
155
|
+
.filter((m) => {
|
|
156
|
+
const entry = m;
|
|
157
|
+
return typeof entry.slug === 'string' && entry.visibility === 'list';
|
|
158
|
+
})
|
|
159
|
+
.map((m) => {
|
|
160
|
+
const entry = m;
|
|
161
|
+
return {
|
|
162
|
+
id: entry.slug,
|
|
163
|
+
displayName: entry.display_name || entry.slug,
|
|
164
|
+
provider,
|
|
165
|
+
contextWindow: entry.context_window ?? 200000,
|
|
166
|
+
inputPricePerToken: 0,
|
|
167
|
+
outputPricePerToken: 0,
|
|
168
|
+
capabilityReasoning: false,
|
|
169
|
+
capabilityCode: true,
|
|
170
|
+
qualityScore: 3,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
|
150
174
|
exports.PROVIDER_CONFIGS = {
|
|
151
175
|
openai: {
|
|
152
176
|
endpoint: 'https://api.openai.com/v1/models',
|
|
153
177
|
buildHeaders: bearerHeaders,
|
|
154
178
|
parse: parseOpenAI,
|
|
155
179
|
},
|
|
180
|
+
'openai-subscription': {
|
|
181
|
+
endpoint: 'https://chatgpt.com/backend-api/codex/models?client_version=0.99.0',
|
|
182
|
+
buildHeaders: (key) => ({
|
|
183
|
+
Authorization: `Bearer ${key}`,
|
|
184
|
+
'Content-Type': 'application/json',
|
|
185
|
+
originator: 'codex_cli_rs',
|
|
186
|
+
'user-agent': 'codex_cli_rs/0.0.0 (Unknown 0; unknown) unknown',
|
|
187
|
+
}),
|
|
188
|
+
parse: parseOpenaiSubscription,
|
|
189
|
+
},
|
|
156
190
|
deepseek: {
|
|
157
191
|
endpoint: 'https://api.deepseek.com/models',
|
|
158
192
|
buildHeaders: bearerHeaders,
|
|
@@ -224,7 +258,11 @@ exports.PROVIDER_CONFIGS = {
|
|
|
224
258
|
let ProviderModelFetcherService = ProviderModelFetcherService_1 = class ProviderModelFetcherService {
|
|
225
259
|
logger = new common_1.Logger(ProviderModelFetcherService_1.name);
|
|
226
260
|
async fetch(providerId, apiKey, authType) {
|
|
227
|
-
|
|
261
|
+
let configKey = providerId.toLowerCase();
|
|
262
|
+
if (configKey === 'openai' && authType === 'subscription') {
|
|
263
|
+
configKey = 'openai-subscription';
|
|
264
|
+
}
|
|
265
|
+
const config = exports.PROVIDER_CONFIGS[configKey];
|
|
228
266
|
if (!config) {
|
|
229
267
|
this.logger.warn(`No fetcher config for provider: ${providerId}`);
|
|
230
268
|
return [];
|
|
@@ -0,0 +1,132 @@
|
|
|
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 __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var OpenaiOauthController_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.OpenaiOauthController = void 0;
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const public_decorator_1 = require("../common/decorators/public.decorator");
|
|
19
|
+
const current_user_decorator_1 = require("../auth/current-user.decorator");
|
|
20
|
+
const openai_oauth_service_1 = require("./openai-oauth.service");
|
|
21
|
+
const resolve_agent_service_1 = require("./resolve-agent.service");
|
|
22
|
+
const routing_service_1 = require("./routing.service");
|
|
23
|
+
let OpenaiOauthController = OpenaiOauthController_1 = class OpenaiOauthController {
|
|
24
|
+
oauthService;
|
|
25
|
+
resolveAgent;
|
|
26
|
+
routingService;
|
|
27
|
+
logger = new common_1.Logger(OpenaiOauthController_1.name);
|
|
28
|
+
constructor(oauthService, resolveAgent, routingService) {
|
|
29
|
+
this.oauthService = oauthService;
|
|
30
|
+
this.resolveAgent = resolveAgent;
|
|
31
|
+
this.routingService = routingService;
|
|
32
|
+
}
|
|
33
|
+
async authorize(agentName, user, req) {
|
|
34
|
+
if (!agentName) {
|
|
35
|
+
throw new common_1.HttpException('agentName query parameter is required', common_1.HttpStatus.BAD_REQUEST);
|
|
36
|
+
}
|
|
37
|
+
const agent = await this.resolveAgent.resolve(user.id, agentName);
|
|
38
|
+
const backendUrl = `${req.protocol}://${req.get('host')}`;
|
|
39
|
+
try {
|
|
40
|
+
const url = await this.oauthService.generateAuthorizationUrl(agent.id, user.id, backendUrl);
|
|
41
|
+
return { url };
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const message = err instanceof Error ? err.message : 'Failed to start OAuth callback server';
|
|
45
|
+
throw new common_1.HttpException(message, common_1.HttpStatus.SERVICE_UNAVAILABLE);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async revoke(agentName, user) {
|
|
49
|
+
if (!agentName) {
|
|
50
|
+
throw new common_1.HttpException('agentName query parameter is required', common_1.HttpStatus.BAD_REQUEST);
|
|
51
|
+
}
|
|
52
|
+
const agent = await this.resolveAgent.resolve(user.id, agentName);
|
|
53
|
+
const apiKey = await this.routingService.getProviderApiKey(agent.id, 'openai', 'subscription');
|
|
54
|
+
if (apiKey) {
|
|
55
|
+
try {
|
|
56
|
+
const blob = JSON.parse(apiKey);
|
|
57
|
+
if (blob.t)
|
|
58
|
+
await this.oauthService.revokeToken(blob.t);
|
|
59
|
+
if (blob.r)
|
|
60
|
+
await this.oauthService.revokeToken(blob.r);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
this.logger.warn('Could not parse OAuth token blob for revocation');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
await this.routingService.removeProvider(agent.id, 'openai', 'subscription');
|
|
67
|
+
return { ok: true };
|
|
68
|
+
}
|
|
69
|
+
async callback(code, state, user) {
|
|
70
|
+
if (!code || !state) {
|
|
71
|
+
throw new common_1.HttpException('code and state are required', common_1.HttpStatus.BAD_REQUEST);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await this.oauthService.exchangeCode(state, code);
|
|
75
|
+
return { ok: true };
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const message = err instanceof Error ? err.message : 'Token exchange failed';
|
|
79
|
+
this.logger.error(`OAuth callback exchange failed: ${message}`);
|
|
80
|
+
throw new common_1.HttpException(message, common_1.HttpStatus.BAD_REQUEST);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
done(ok, res) {
|
|
84
|
+
const success = ok === '1';
|
|
85
|
+
res.setHeader('Content-Type', 'text/html');
|
|
86
|
+
res.setHeader('Content-Security-Policy', "default-src 'none'; script-src 'unsafe-inline'");
|
|
87
|
+
res.send((0, openai_oauth_service_1.oauthDoneHtml)(success));
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
exports.OpenaiOauthController = OpenaiOauthController;
|
|
91
|
+
__decorate([
|
|
92
|
+
(0, common_1.Get)('authorize'),
|
|
93
|
+
__param(0, (0, common_1.Query)('agentName')),
|
|
94
|
+
__param(1, (0, current_user_decorator_1.CurrentUser)()),
|
|
95
|
+
__param(2, (0, common_1.Req)()),
|
|
96
|
+
__metadata("design:type", Function),
|
|
97
|
+
__metadata("design:paramtypes", [String, Object, Object]),
|
|
98
|
+
__metadata("design:returntype", Promise)
|
|
99
|
+
], OpenaiOauthController.prototype, "authorize", null);
|
|
100
|
+
__decorate([
|
|
101
|
+
(0, common_1.Post)('revoke'),
|
|
102
|
+
__param(0, (0, common_1.Query)('agentName')),
|
|
103
|
+
__param(1, (0, current_user_decorator_1.CurrentUser)()),
|
|
104
|
+
__metadata("design:type", Function),
|
|
105
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
106
|
+
__metadata("design:returntype", Promise)
|
|
107
|
+
], OpenaiOauthController.prototype, "revoke", null);
|
|
108
|
+
__decorate([
|
|
109
|
+
(0, common_1.Post)('callback'),
|
|
110
|
+
__param(0, (0, common_1.Body)('code')),
|
|
111
|
+
__param(1, (0, common_1.Body)('state')),
|
|
112
|
+
__param(2, (0, current_user_decorator_1.CurrentUser)()),
|
|
113
|
+
__metadata("design:type", Function),
|
|
114
|
+
__metadata("design:paramtypes", [String, String, Object]),
|
|
115
|
+
__metadata("design:returntype", Promise)
|
|
116
|
+
], OpenaiOauthController.prototype, "callback", null);
|
|
117
|
+
__decorate([
|
|
118
|
+
(0, common_1.Get)('done'),
|
|
119
|
+
(0, public_decorator_1.Public)(),
|
|
120
|
+
__param(0, (0, common_1.Query)('ok')),
|
|
121
|
+
__param(1, (0, common_1.Res)()),
|
|
122
|
+
__metadata("design:type", Function),
|
|
123
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
124
|
+
__metadata("design:returntype", void 0)
|
|
125
|
+
], OpenaiOauthController.prototype, "done", null);
|
|
126
|
+
exports.OpenaiOauthController = OpenaiOauthController = OpenaiOauthController_1 = __decorate([
|
|
127
|
+
(0, common_1.Controller)('api/v1/oauth/openai'),
|
|
128
|
+
__metadata("design:paramtypes", [openai_oauth_service_1.OpenaiOauthService,
|
|
129
|
+
resolve_agent_service_1.ResolveAgentService,
|
|
130
|
+
routing_service_1.RoutingService])
|
|
131
|
+
], OpenaiOauthController);
|
|
132
|
+
//# sourceMappingURL=openai-oauth.controller.js.map
|