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.
Files changed (50) hide show
  1. package/README.md +1 -1
  2. package/dist/backend/database/seed-messages.js +1 -0
  3. package/dist/backend/otlp/services/trace-ingest.service.js +27 -1
  4. package/dist/backend/routing/model-discovery/model-discovery.service.js +45 -89
  5. package/dist/backend/routing/model-discovery/model-fallback.js +164 -0
  6. package/dist/backend/routing/model-discovery/provider-model-fetcher.service.js +39 -1
  7. package/dist/backend/routing/openai-oauth.controller.js +132 -0
  8. package/dist/backend/routing/openai-oauth.service.js +274 -0
  9. package/dist/backend/routing/openai-oauth.types.js +23 -0
  10. package/dist/backend/routing/proxy/chatgpt-adapter.js +137 -0
  11. package/dist/backend/routing/proxy/provider-client.js +18 -2
  12. package/dist/backend/routing/proxy/provider-endpoints.js +13 -0
  13. package/dist/backend/routing/proxy/proxy-message-recorder.js +354 -0
  14. package/dist/backend/routing/proxy/proxy.controller.js +39 -303
  15. package/dist/backend/routing/proxy/proxy.service.js +20 -5
  16. package/dist/backend/routing/routing-invalidation.service.js +89 -0
  17. package/dist/backend/routing/routing.controller.js +1 -0
  18. package/dist/backend/routing/routing.module.js +22 -2
  19. package/dist/backend/routing/routing.service.js +2 -45
  20. package/dist/backend/routing/tier-auto-assign.service.js +21 -15
  21. package/dist/index.js +2 -2
  22. package/dist/local-mode.js +1 -1
  23. package/dist/openclaw.plugin.json +9 -3
  24. package/dist/subscription.js +1 -1
  25. package/openclaw.plugin.json +9 -3
  26. package/package.json +1 -1
  27. package/public/assets/{Account-DtR3Bovj.js → Account-CONa2CGc.js} +1 -1
  28. package/public/assets/Limits-BmVBnqQ8.js +1 -0
  29. package/public/assets/Login-CnKzDsL7.js +1 -0
  30. package/public/assets/{MessageLog-BfdBjKNj.js → MessageLog-U-hPyvnx.js} +1 -1
  31. package/public/assets/{ModelPrices-D9JiRM0x.js → ModelPrices-Cu8cQZq3.js} +1 -1
  32. package/public/assets/{Overview-C5W2WzOa.js → Overview-C6Vae67r.js} +1 -1
  33. package/public/assets/ProviderIcon-DuNcnqvr.js +1 -0
  34. package/public/assets/{Register-CK1-HR7-.js → Register-gAPDw0Q2.js} +1 -1
  35. package/public/assets/{ResetPassword-BXCgyBPz.js → ResetPassword-BMTzQWBS.js} +1 -1
  36. package/public/assets/Routing-1s85NXlk.js +3 -0
  37. package/public/assets/{Settings-Cs8Y-DYp.js → Settings-CBoPohdM.js} +1 -1
  38. package/public/assets/{SocialButtons-B29ADQVX.js → SocialButtons-7ZsVfsLs.js} +1 -1
  39. package/public/assets/{index-BJbiHRsE.css → index-BZMbFWG-.css} +1 -1
  40. package/public/assets/index-C0MaOoW6.js +2 -0
  41. package/public/assets/{model-display-3wEhsKxc.js → model-display-Dcao1u69.js} +1 -1
  42. package/public/assets/{overview-cTUikYPe.js → overview-ulMqu_nV.js} +1 -1
  43. package/public/index.html +2 -2
  44. package/subscription-capabilities/index.d.ts +18 -1
  45. package/subscription-capabilities/index.js +36 -0
  46. package/public/assets/Limits-DYEhhHov.js +0 -1
  47. package/public/assets/Login-sV6eKBAf.js +0 -1
  48. package/public/assets/ProviderIcon-ZusUWlbr.js +0 -1
  49. package/public/assets/Routing-ByCp9o3v.js +0 -3
  50. 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
- - 📊 **Track spending** — see tokens and costs per model in real time
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
- let raw = await this.fetcher.fetch(provider.provider, apiKey, provider.auth_type);
53
- if (raw.length === 0) {
54
- raw = this.buildFallbackModels(provider.provider);
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(`Native API returned 0 models for ${provider.provider} — using ${raw.length} models from pricing data`);
68
+ this.logger.log(`No token for subscription provider ${provider.provider} — using ${raw.length} fallback models`);
57
69
  }
58
70
  }
59
- const enriched = raw.map((model) => this.enrichModel(model, provider.provider));
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 Set();
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.add(m.id);
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.add(modelKey);
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 = this.findOpenRouterPrefix(providerId);
171
+ const orPrefix = (0, model_fallback_1.findOpenRouterPrefix)(providerId);
138
172
  if (orPrefix) {
139
- const orPricing = this.lookupWithVariants(orPrefix, model.id);
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
- const config = exports.PROVIDER_CONFIGS[providerId.toLowerCase()];
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