manifest 5.20.0 → 5.21.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 (63) hide show
  1. package/README.md +5 -5
  2. package/dist/backend/analytics/services/aggregation.service.js +18 -18
  3. package/dist/backend/auth/auth.instance.js +1 -1
  4. package/dist/backend/common/utils/period.util.js +26 -1
  5. package/dist/backend/database/database-seeder.service.js +4 -20
  6. package/dist/backend/database/database.module.js +13 -0
  7. package/dist/backend/database/local-bootstrap.service.js +24 -40
  8. package/dist/backend/database/migrations/1771800000000-AddQualityScore.js +2 -2
  9. package/dist/backend/database/migrations/1771800100000-SeedQualityScores.js +2 -2
  10. package/dist/backend/database/migrations/1772668898071-AddCustomProviders.js +31 -0
  11. package/dist/backend/database/migrations/1772682112419-NullablePricing.js +17 -0
  12. package/dist/backend/database/migrations/1772843035514-AddPerformanceIndexes.js +56 -0
  13. package/dist/backend/database/pricing-sync.service.js +26 -0
  14. package/dist/backend/database/quality-score.util.js +5 -4
  15. package/dist/backend/database/seed-messages.js +13 -3
  16. package/dist/backend/entities/agent-message.entity.js +4 -1
  17. package/dist/backend/entities/cost-snapshot.entity.js +2 -1
  18. package/dist/backend/entities/custom-provider.entity.js +64 -0
  19. package/dist/backend/entities/model-pricing.entity.js +4 -4
  20. package/dist/backend/entities/security-event.entity.js +2 -1
  21. package/dist/backend/entities/user-provider.entity.js +5 -0
  22. package/dist/backend/health/health.controller.js +3 -5
  23. package/dist/backend/main.js +3 -8
  24. package/dist/backend/model-prices/model-name-normalizer.js +5 -5
  25. package/dist/backend/model-prices/model-prices.service.js +2 -2
  26. package/dist/backend/model-prices/model-pricing-cache.service.js +5 -1
  27. package/dist/backend/notifications/emails/reset-password.js +8 -8
  28. package/dist/backend/notifications/emails/test-email.js +9 -8
  29. package/dist/backend/notifications/emails/threshold-alert.js +52 -13
  30. package/dist/backend/notifications/emails/verify-email.js +8 -8
  31. package/dist/backend/notifications/services/limit-check.service.js +25 -2
  32. package/dist/backend/notifications/services/notification-cron.service.js +14 -2
  33. package/dist/backend/notifications/services/notification-email.service.js +2 -1
  34. package/dist/backend/otlp/guards/otlp-auth.guard.js +8 -0
  35. package/dist/backend/otlp/services/log-ingest.service.js +7 -8
  36. package/dist/backend/otlp/services/metric-ingest.service.js +17 -20
  37. package/dist/backend/otlp/services/trace-ingest.service.js +124 -25
  38. package/dist/backend/routing/custom-provider.controller.js +144 -0
  39. package/dist/backend/routing/custom-provider.service.js +190 -0
  40. package/dist/backend/routing/dto/custom-provider.dto.js +124 -0
  41. package/dist/backend/routing/provider-aliases.js +4 -1
  42. package/dist/backend/routing/proxy/google-adapter.js +9 -9
  43. package/dist/backend/routing/proxy/provider-client.js +44 -9
  44. package/dist/backend/routing/proxy/provider-endpoints.js +18 -0
  45. package/dist/backend/routing/proxy/proxy-rate-limiter.js +10 -5
  46. package/dist/backend/routing/proxy/proxy.controller.js +18 -4
  47. package/dist/backend/routing/proxy/proxy.service.js +65 -38
  48. package/dist/backend/routing/proxy/stream-writer.js +4 -0
  49. package/dist/backend/routing/resolve-agent.util.js +16 -0
  50. package/dist/backend/routing/routing.controller.js +33 -24
  51. package/dist/backend/routing/routing.module.js +7 -2
  52. package/dist/backend/routing/routing.service.js +19 -12
  53. package/dist/backend/routing/tier-auto-assign.service.js +2 -3
  54. package/dist/backend/telemetry/telemetry.service.js +39 -16
  55. package/dist/index.js +11 -11
  56. package/package.json +1 -1
  57. package/public/assets/index-CM4JbafH.css +1 -0
  58. package/public/assets/index-XSLPss-g.js +7 -0
  59. package/public/index.html +5 -5
  60. package/public/manifest-logo.png +0 -0
  61. package/skills/manifest/SKILL.md +73 -10
  62. package/public/assets/index-BGkNs31t.js +0 -7
  63. package/public/assets/index-CTglHtZk.css +0 -1
package/README.md CHANGED
@@ -30,9 +30,9 @@ OpenClaw costs
30
30
 
31
31
  ## What do you get?
32
32
 
33
- - 🔀 **Routes every request to the right model** — and cuts costs up to 70%
34
- - 📊 **Track your expenses** — real-time dashboard that shows tokens and costs per model
35
- - 🔔 **Set limits** — set up alerts (soft or hard) if your consumption exceeds a certain volume
33
+ - 🔀 **Route requests to the right model** — cut costs up to 70%
34
+ - 📊 **Track spending** — see tokens and costs per model in real time
35
+ - 🔔 **Set limits** — get alerts when usage goes over a threshold
36
36
 
37
37
  ## Why Manifest
38
38
 
@@ -122,14 +122,14 @@ Or add `"telemetryOptOut": true` to `~/.openclaw/manifest/config.json`.
122
122
 
123
123
  ## Supported Providers
124
124
 
125
- Manifest supports **300+ models** across all major LLM providers. Every provider supports smart routing, real-time cost tracking, and OTLP telemetry.
125
+ Works with **300+ models** across these providers:
126
126
 
127
127
  | Provider | Models |
128
128
  |----------|--------|
129
129
  | [OpenAI](https://platform.openai.com/) | `gpt-5.3`, `gpt-4.1`, `o3`, `o4-mini` + 54 more |
130
130
  | [Anthropic](https://www.anthropic.com/) | `claude-opus-4-6`, `claude-sonnet-4.5`, `claude-haiku-4.5` + 14 more |
131
131
  | [Google Gemini](https://ai.google.dev/) | `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-3-pro` + 19 more |
132
- | [DeepSeek](https://www.deepseek.com/) | `deepseek-v3`, `deepseek-r1` + 11 more |
132
+ | [DeepSeek](https://www.deepseek.com/) | `deepseek-chat`, `deepseek-reasoner` + 11 more |
133
133
  | [xAI](https://x.ai/) | `grok-4`, `grok-3`, `grok-3-mini` + 8 more |
134
134
  | [Mistral AI](https://mistral.ai/) | `mistral-large`, `codestral`, `devstral` + 26 more |
135
135
  | [Qwen (Alibaba)](https://www.alibabacloud.com/en/solutions/generative-ai/qwen) | `qwen3-235b`, `qwen3-coder`, `qwq-32b` + 42 more |
@@ -174,21 +174,23 @@ let AggregationService = class AggregationService {
174
174
  .set(agentUpdate)
175
175
  .where('id = :id', { id: agent.id })
176
176
  .execute();
177
- const tables = ['agent_messages', 'notification_rules', 'notification_logs', 'token_usage_snapshots', 'cost_snapshots'];
178
- for (const table of tables) {
179
- await manager
180
- .createQueryBuilder()
181
- .update(table)
182
- .set({ agent_name: newName })
183
- .where('agent_name = :currentName', { currentName })
184
- .execute();
185
- }
177
+ const tables = [
178
+ 'agent_messages',
179
+ 'notification_rules',
180
+ 'notification_logs',
181
+ 'token_usage_snapshots',
182
+ 'cost_snapshots',
183
+ ];
184
+ await Promise.all(tables.map((table) => manager
185
+ .createQueryBuilder()
186
+ .update(table)
187
+ .set({ agent_name: newName })
188
+ .where('agent_name = :currentName', { currentName })
189
+ .execute()));
186
190
  });
187
191
  }
188
192
  async getMessages(params) {
189
- const cutoff = params.range
190
- ? (0, sql_dialect_1.computeCutoff)((0, range_util_1.rangeToInterval)(params.range))
191
- : undefined;
193
+ const cutoff = params.range ? (0, sql_dialect_1.computeCutoff)((0, range_util_1.rangeToInterval)(params.range)) : undefined;
192
194
  const baseQb = this.turnRepo.createQueryBuilder('at');
193
195
  if (cutoff) {
194
196
  baseQb.where('at.timestamp >= :cutoff', { cutoff });
@@ -210,7 +212,8 @@ let AggregationService = class AggregationService {
210
212
  const countResult = await countQb.getRawOne();
211
213
  const totalCount = Number(countResult?.total ?? 0);
212
214
  const costExpr = (0, sql_dialect_1.sqlCastFloat)((0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd'), this.dialect);
213
- const dataQb = baseQb.clone()
215
+ const dataQb = baseQb
216
+ .clone()
214
217
  .select('at.id', 'id')
215
218
  .addSelect('at.timestamp', 'timestamp')
216
219
  .addSelect('at.agent_name', 'agent_name')
@@ -234,9 +237,7 @@ let AggregationService = class AggregationService {
234
237
  const cursorTs = params.cursor.substring(0, sepIdx);
235
238
  const cursorId = params.cursor.substring(sepIdx + 1);
236
239
  dataQb.andWhere(new typeorm_2.Brackets((sub) => {
237
- sub
238
- .where('at.timestamp < :cursorTs', { cursorTs })
239
- .orWhere(new typeorm_2.Brackets((inner) => {
240
+ sub.where('at.timestamp < :cursorTs', { cursorTs }).orWhere(new typeorm_2.Brackets((inner) => {
240
241
  inner
241
242
  .where('at.timestamp = :cursorTs2', { cursorTs2: cursorTs })
242
243
  .andWhere('at.id < :cursorId', { cursorId });
@@ -255,8 +256,7 @@ let AggregationService = class AggregationService {
255
256
  const ts = lastItem?.['timestamp'];
256
257
  const tsStr = ts instanceof Date ? (0, query_helpers_1.formatTimestamp)(ts) : String(ts ?? '');
257
258
  const lastId = lastItem?.['id'];
258
- const nextCursor = hasMore && lastItem
259
- ? `${tsStr}|${String(lastId)}` : null;
259
+ const nextCursor = hasMore && lastItem ? `${tsStr}|${String(lastId)}` : null;
260
260
  const modelsQb = this.turnRepo
261
261
  .createQueryBuilder('at')
262
262
  .select('DISTINCT at.model', 'model')
@@ -7,6 +7,7 @@ const verify_email_1 = require("../notifications/emails/verify-email");
7
7
  const reset_password_1 = require("../notifications/emails/reset-password");
8
8
  const send_email_1 = require("../notifications/services/email-providers/send-email");
9
9
  const product_telemetry_1 = require("../common/utils/product-telemetry");
10
+ const local_mode_constants_1 = require("../common/constants/local-mode.constants");
10
11
  const isLocalMode = process.env['MANIFEST_MODE'] === 'local';
11
12
  const port = process.env['PORT'] ?? '3001';
12
13
  const isDev = (process.env['NODE_ENV'] ?? '') !== 'production';
@@ -24,7 +25,6 @@ const nodeEnv = process.env['NODE_ENV'] ?? '';
24
25
  if (!isLocalMode && nodeEnv !== 'test' && (!betterAuthSecret || betterAuthSecret.length < 32)) {
25
26
  throw new Error('BETTER_AUTH_SECRET must be set to a value of at least 32 characters');
26
27
  }
27
- const local_mode_constants_1 = require("../common/constants/local-mode.constants");
28
28
  function buildTrustedOrigins() {
29
29
  const origins = [];
30
30
  if (process.env['BETTER_AUTH_URL']) {
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.computePeriodBoundaries = computePeriodBoundaries;
4
+ exports.computePeriodResetDate = computePeriodResetDate;
5
+ const fmt = (d) => d.toISOString().replace('T', ' ').replace('Z', '').slice(0, 19);
4
6
  function computePeriodBoundaries(period) {
5
7
  const now = new Date();
6
8
  let start;
@@ -24,7 +26,30 @@ function computePeriodBoundaries(period) {
24
26
  start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours() - 1));
25
27
  }
26
28
  const end = new Date(now.getTime());
27
- const fmt = (d) => d.toISOString().replace('T', ' ').replace('Z', '').slice(0, 19);
28
29
  return { periodStart: fmt(start), periodEnd: fmt(end) };
29
30
  }
31
+ function computePeriodResetDate(period) {
32
+ const now = new Date();
33
+ let reset;
34
+ switch (period) {
35
+ case 'hour':
36
+ reset = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours() + 1));
37
+ break;
38
+ case 'day':
39
+ reset = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1));
40
+ break;
41
+ case 'week': {
42
+ const dayOfWeek = now.getUTCDay();
43
+ const daysUntilMonday = (8 - dayOfWeek) % 7 || 7;
44
+ reset = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + daysUntilMonday));
45
+ break;
46
+ }
47
+ case 'month':
48
+ reset = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1));
49
+ break;
50
+ default:
51
+ reset = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours() + 1));
52
+ }
53
+ return fmt(reset);
54
+ }
30
55
  //# sourceMappingURL=period.util.js.map
@@ -177,29 +177,25 @@ let DatabaseSeederService = DatabaseSeederService_1 = class DatabaseSeederServic
177
177
  ['o3', 'OpenAI', 0.000002, 0.000008, 200000, true, true],
178
178
  ['o3-mini', 'OpenAI', 0.0000011, 0.0000044, 200000, true, true],
179
179
  ['o4-mini', 'OpenAI', 0.0000011, 0.0000044, 200000, true, true],
180
- ['gpt-5.3', 'OpenAI', 0.00001, 0.00003, 200000, true, true],
181
- ['gpt-5.3-codex', 'OpenAI', 0.00001, 0.00003, 200000, true, true],
182
- ['gpt-5.3-mini', 'OpenAI', 0.0000015, 0.000006, 200000, true, true],
183
180
  ['gemini-2.5-pro', 'Google', 0.00000125, 0.00001, 1048576, true, true],
184
181
  ['gemini-2.5-flash', 'Google', 0.00000015, 0.0000006, 1048576, false, true],
185
182
  ['gemini-2.5-flash-lite', 'Google', 0.0000001, 0.0000004, 1048576, false, false],
186
183
  ['gemini-2.0-flash', 'Google', 0.0000001, 0.0000004, 1048576, false, true],
187
- ['deepseek-v3', 'DeepSeek', 0.00000014, 0.00000028, 128000, false, true],
188
- ['deepseek-r1', 'DeepSeek', 0.00000055, 0.00000219, 128000, true, false],
184
+ ['deepseek-chat', 'DeepSeek', 0.00000014, 0.00000028, 128000, false, true],
185
+ ['deepseek-reasoner', 'DeepSeek', 0.00000055, 0.00000219, 128000, true, false],
189
186
  ['kimi-k2', 'Moonshot', 0.0000006, 0.0000024, 262144, true, true],
190
187
  ['qwen-2.5-72b-instruct', 'Alibaba', 0.00000034, 0.00000039, 131072, false, true],
191
188
  ['qwq-32b', 'Alibaba', 0.00000012, 0.00000018, 131072, true, false],
192
189
  ['qwen-2.5-coder-32b-instruct', 'Alibaba', 0.00000018, 0.00000018, 131072, false, true],
193
190
  ['qwen3-235b-a22b', 'Alibaba', 0.0000003, 0.0000012, 131072, true, true],
194
191
  ['qwen3-32b', 'Alibaba', 0.0000001, 0.0000003, 131072, true, true],
195
- ['mistral-large', 'Mistral', 0.000002, 0.000006, 128000, false, true],
192
+ ['mistral-large-latest', 'Mistral', 0.000002, 0.000006, 128000, false, true],
196
193
  ['mistral-small', 'Mistral', 0.0000002, 0.0000006, 128000, false, false],
197
- ['codestral', 'Mistral', 0.0000003, 0.0000009, 256000, false, true],
194
+ ['codestral-latest', 'Mistral', 0.0000003, 0.0000009, 256000, false, true],
198
195
  ['grok-3', 'xAI', 0.000003, 0.000015, 131072, true, true],
199
196
  ['grok-3-mini', 'xAI', 0.0000003, 0.0000005, 131072, true, true],
200
197
  ['grok-3-fast', 'xAI', 0.000005, 0.000025, 131072, false, true],
201
198
  ['grok-3-mini-fast', 'xAI', 0.0000006, 0.000004, 131072, false, true],
202
- ['grok-2', 'xAI', 0.000002, 0.00001, 131072, false, true],
203
199
  ['openrouter/auto', 'OpenRouter', 0.000003, 0.000015, 200000, true, true],
204
200
  ['anthropic/claude-opus-4-6', 'OpenRouter', 0.000015, 0.000075, 200000, true, true],
205
201
  ['anthropic/claude-sonnet-4-5', 'OpenRouter', 0.000003, 0.000015, 200000, true, true],
@@ -213,13 +209,6 @@ let DatabaseSeederService = DatabaseSeederService_1 = class DatabaseSeederServic
213
209
  ['mistralai/mistral-large', 'OpenRouter', 0.000002, 0.000006, 128000, false, true],
214
210
  ['x-ai/grok-3', 'OpenRouter', 0.000003, 0.000015, 131072, true, true],
215
211
  ['openrouter/free', 'OpenRouter', 0, 0, 200000, true, true],
216
- ['stepfun/step-3.5-flash:free', 'OpenRouter', 0, 0, 256000, false, true],
217
- ['arcee-ai/trinity-large-preview:free', 'OpenRouter', 0, 0, 131072, false, true],
218
- ['upstage/solar-pro-3:free', 'OpenRouter', 0, 0, 128000, false, true],
219
- ['liquid/lfm-2.5-1.2b-thinking:free', 'OpenRouter', 0, 0, 32768, true, false],
220
- ['liquid/lfm-2.5-1.2b-instruct:free', 'OpenRouter', 0, 0, 32768, false, false],
221
- ['arcee-ai/trinity-mini:free', 'OpenRouter', 0, 0, 131072, false, false],
222
- ['nvidia/nemotron-3-nano-30b-a3b:free', 'OpenRouter', 0, 0, 256000, false, true],
223
212
  ['minimax/minimax-m2.5', 'OpenRouter', 0.000000295, 0.0000012, 196608, true, true],
224
213
  ['minimax/minimax-m1', 'OpenRouter', 0.0000004, 0.0000022, 1000000, true, true],
225
214
  ['minimax-m2.5', 'MiniMax', 0.000000295, 0.0000012, 196608, true, true],
@@ -227,9 +216,7 @@ let DatabaseSeederService = DatabaseSeederService_1 = class DatabaseSeederServic
227
216
  ['minimax-m2.1', 'MiniMax', 0.00000027, 0.00000095, 196608, true, true],
228
217
  ['minimax-m2.1-highspeed', 'MiniMax', 0.00000027, 0.00000095, 196608, true, true],
229
218
  ['minimax-m2', 'MiniMax', 0.000000255, 0.000001, 196608, true, true],
230
- ['minimax-m2-her', 'MiniMax', 0.0000003, 0.0000012, 65536, false, false],
231
219
  ['minimax-m1', 'MiniMax', 0.0000004, 0.0000022, 1000000, true, true],
232
- ['minimax-01', 'MiniMax', 0.0000002, 0.0000011, 1000192, false, false],
233
220
  ['glm-5', 'Z.ai', 0.00000095, 0.00000255, 204800, true, true],
234
221
  ['glm-4.7', 'Z.ai', 0.0000003, 0.0000014, 202752, true, true],
235
222
  ['glm-4.7-flash', 'Z.ai', 0.00000006, 0.0000004, 202752, false, false],
@@ -242,9 +229,6 @@ let DatabaseSeederService = DatabaseSeederService_1 = class DatabaseSeederServic
242
229
  ['z-ai/glm-4.7', 'OpenRouter', 0.0000003, 0.0000014, 202752, true, true],
243
230
  ['glm-4-plus', 'Zhipu', 0.0000005, 0.0000005, 128000, false, true],
244
231
  ['glm-4-flash', 'Zhipu', 0.00000005, 0.00000005, 128000, false, false],
245
- ['nova-pro', 'Amazon', 0.0000008, 0.0000032, 300000, false, true],
246
- ['nova-lite', 'Amazon', 0.00000006, 0.00000024, 300000, false, true],
247
- ['nova-micro', 'Amazon', 0.000000035, 0.00000014, 128000, false, false],
248
232
  ];
249
233
  for (const [name, provider, inputPrice, outputPrice, ctxWindow, reasoning, code] of models) {
250
234
  await this.pricingRepo.upsert({
@@ -30,6 +30,7 @@ const notification_log_entity_1 = require("../entities/notification-log.entity")
30
30
  const email_provider_config_entity_1 = require("../entities/email-provider-config.entity");
31
31
  const user_provider_entity_1 = require("../entities/user-provider.entity");
32
32
  const tier_assignment_entity_1 = require("../entities/tier-assignment.entity");
33
+ const custom_provider_entity_1 = require("../entities/custom-provider.entity");
33
34
  const database_seeder_service_1 = require("./database-seeder.service");
34
35
  const local_bootstrap_service_1 = require("./local-bootstrap.service");
35
36
  const model_prices_module_1 = require("../model-prices/model-prices.module");
@@ -49,6 +50,9 @@ const _1772200000000_AddLimitAction_1 = require("./migrations/1772200000000-AddL
49
50
  const _1772300000000_AddRoutingReason_1 = require("./migrations/1772300000000-AddRoutingReason");
50
51
  const _1772400000000_AddAgentDisplayName_1 = require("./migrations/1772400000000-AddAgentDisplayName");
51
52
  const _1772500000000_PerAgentRouting_1 = require("./migrations/1772500000000-PerAgentRouting");
53
+ const _1772668898071_AddCustomProviders_1 = require("./migrations/1772668898071-AddCustomProviders");
54
+ const _1772682112419_NullablePricing_1 = require("./migrations/1772682112419-NullablePricing");
55
+ const _1772843035514_AddPerformanceIndexes_1 = require("./migrations/1772843035514-AddPerformanceIndexes");
52
56
  const entities = [
53
57
  agent_message_entity_1.AgentMessage,
54
58
  llm_call_entity_1.LlmCall,
@@ -69,6 +73,7 @@ const entities = [
69
73
  email_provider_config_entity_1.EmailProviderConfig,
70
74
  user_provider_entity_1.UserProvider,
71
75
  tier_assignment_entity_1.TierAssignment,
76
+ custom_provider_entity_1.CustomProvider,
72
77
  ];
73
78
  const migrations = [
74
79
  _1771464895790_InitialSchema_1.InitialSchema1771464895790,
@@ -87,6 +92,9 @@ const migrations = [
87
92
  _1772300000000_AddRoutingReason_1.AddRoutingReason1772300000000,
88
93
  _1772400000000_AddAgentDisplayName_1.AddAgentDisplayName1772400000000,
89
94
  _1772500000000_PerAgentRouting_1.PerAgentRouting1772500000000,
95
+ _1772668898071_AddCustomProviders_1.AddCustomProviders1772668898071,
96
+ _1772682112419_NullablePricing_1.NullablePricing1772682112419,
97
+ _1772843035514_AddPerformanceIndexes_1.AddPerformanceIndexes1772843035514,
90
98
  ];
91
99
  const isLocalMode = process.env['MANIFEST_MODE'] === 'local';
92
100
  function buildModeServices() {
@@ -124,6 +132,10 @@ exports.DatabaseModule = DatabaseModule = __decorate([
124
132
  migrationsTransactionMode: 'all',
125
133
  migrations,
126
134
  logging: false,
135
+ extra: {
136
+ max: 20,
137
+ idleTimeoutMillis: 30000,
138
+ },
127
139
  };
128
140
  },
129
141
  }),
@@ -137,6 +149,7 @@ exports.DatabaseModule = DatabaseModule = __decorate([
137
149
  security_event_entity_1.SecurityEvent,
138
150
  user_provider_entity_1.UserProvider,
139
151
  tier_assignment_entity_1.TierAssignment,
152
+ custom_provider_entity_1.CustomProvider,
140
153
  ]),
141
154
  model_prices_module_1.ModelPricesModule,
142
155
  ],
@@ -72,28 +72,28 @@ let LocalBootstrapService = LocalBootstrapService_1 = class LocalBootstrapServic
72
72
  }
73
73
  async ensureTenantAndAgent() {
74
74
  const count = await this.tenantRepo.count({ where: { id: local_mode_constants_1.LOCAL_TENANT_ID } });
75
- if (count > 0)
76
- return;
77
- await this.tenantRepo.insert({
78
- id: local_mode_constants_1.LOCAL_TENANT_ID,
79
- name: local_mode_constants_1.LOCAL_USER_ID,
80
- organization_name: 'Local',
81
- email: local_mode_constants_1.LOCAL_EMAIL,
82
- is_active: true,
83
- });
84
- await this.agentRepo.insert({
85
- id: local_mode_constants_1.LOCAL_AGENT_ID,
86
- name: local_mode_constants_1.LOCAL_AGENT_NAME,
87
- description: 'Default local agent',
88
- is_active: true,
89
- tenant_id: local_mode_constants_1.LOCAL_TENANT_ID,
90
- });
91
- (0, product_telemetry_1.trackEvent)('agent_created', { agent_name: local_mode_constants_1.LOCAL_AGENT_NAME });
75
+ if (count === 0) {
76
+ await this.tenantRepo.insert({
77
+ id: local_mode_constants_1.LOCAL_TENANT_ID,
78
+ name: local_mode_constants_1.LOCAL_USER_ID,
79
+ organization_name: 'Local',
80
+ email: local_mode_constants_1.LOCAL_EMAIL,
81
+ is_active: true,
82
+ });
83
+ await this.agentRepo.insert({
84
+ id: local_mode_constants_1.LOCAL_AGENT_ID,
85
+ name: local_mode_constants_1.LOCAL_AGENT_NAME,
86
+ description: 'Default local agent',
87
+ is_active: true,
88
+ tenant_id: local_mode_constants_1.LOCAL_TENANT_ID,
89
+ });
90
+ (0, product_telemetry_1.trackEvent)('agent_created', { agent_name: local_mode_constants_1.LOCAL_AGENT_NAME });
91
+ this.logger.log(`Created tenant/agent for local mode`);
92
+ }
92
93
  const apiKey = this.readApiKeyFromConfig();
93
94
  if (apiKey) {
94
95
  await this.registerApiKey(apiKey);
95
96
  }
96
- this.logger.log(`Created tenant/agent for local mode`);
97
97
  }
98
98
  readApiKeyFromConfig() {
99
99
  try {
@@ -112,7 +112,7 @@ let LocalBootstrapService = LocalBootstrapService_1 = class LocalBootstrapServic
112
112
  const existing = await this.agentKeyRepo.count({ where: { key_hash: hash } });
113
113
  if (existing > 0)
114
114
  return;
115
- await this.agentKeyRepo.insert({
115
+ await this.agentKeyRepo.upsert({
116
116
  id: 'local-otlp-key-001',
117
117
  key: null,
118
118
  key_hash: hash,
@@ -121,7 +121,7 @@ let LocalBootstrapService = LocalBootstrapService_1 = class LocalBootstrapServic
121
121
  tenant_id: local_mode_constants_1.LOCAL_TENANT_ID,
122
122
  agent_id: local_mode_constants_1.LOCAL_AGENT_ID,
123
123
  is_active: true,
124
- });
124
+ }, ['id']);
125
125
  }
126
126
  async fixupRoutingAgentIds() {
127
127
  const orphanedProviders = await this.providerRepo.find({
@@ -159,29 +159,25 @@ let LocalBootstrapService = LocalBootstrapService_1 = class LocalBootstrapServic
159
159
  ['o3', 'OpenAI', 0.000002, 0.000008, 200000, true, true],
160
160
  ['o3-mini', 'OpenAI', 0.0000011, 0.0000044, 200000, true, true],
161
161
  ['o4-mini', 'OpenAI', 0.0000011, 0.0000044, 200000, true, true],
162
- ['gpt-5.3', 'OpenAI', 0.00001, 0.00003, 200000, true, true],
163
- ['gpt-5.3-codex', 'OpenAI', 0.00001, 0.00003, 200000, true, true],
164
- ['gpt-5.3-mini', 'OpenAI', 0.0000015, 0.000006, 200000, true, true],
165
162
  ['gemini-2.5-pro', 'Google', 0.00000125, 0.00001, 1048576, true, true],
166
163
  ['gemini-2.5-flash', 'Google', 0.00000015, 0.0000006, 1048576, false, true],
167
164
  ['gemini-2.5-flash-lite', 'Google', 0.0000001, 0.0000004, 1048576, false, false],
168
165
  ['gemini-2.0-flash', 'Google', 0.0000001, 0.0000004, 1048576, false, true],
169
- ['deepseek-v3', 'DeepSeek', 0.00000014, 0.00000028, 128000, false, true],
170
- ['deepseek-r1', 'DeepSeek', 0.00000055, 0.00000219, 128000, true, false],
166
+ ['deepseek-chat', 'DeepSeek', 0.00000014, 0.00000028, 128000, false, true],
167
+ ['deepseek-reasoner', 'DeepSeek', 0.00000055, 0.00000219, 128000, true, false],
171
168
  ['kimi-k2', 'Moonshot', 0.0000006, 0.0000024, 262144, true, true],
172
169
  ['qwen-2.5-72b-instruct', 'Alibaba', 0.00000034, 0.00000039, 131072, false, true],
173
170
  ['qwq-32b', 'Alibaba', 0.00000012, 0.00000018, 131072, true, false],
174
171
  ['qwen-2.5-coder-32b-instruct', 'Alibaba', 0.00000018, 0.00000018, 131072, false, true],
175
172
  ['qwen3-235b-a22b', 'Alibaba', 0.0000003, 0.0000012, 131072, true, true],
176
173
  ['qwen3-32b', 'Alibaba', 0.0000001, 0.0000003, 131072, true, true],
177
- ['mistral-large', 'Mistral', 0.000002, 0.000006, 128000, false, true],
174
+ ['mistral-large-latest', 'Mistral', 0.000002, 0.000006, 128000, false, true],
178
175
  ['mistral-small', 'Mistral', 0.0000002, 0.0000006, 128000, false, false],
179
- ['codestral', 'Mistral', 0.0000003, 0.0000009, 256000, false, true],
176
+ ['codestral-latest', 'Mistral', 0.0000003, 0.0000009, 256000, false, true],
180
177
  ['grok-3', 'xAI', 0.000003, 0.000015, 131072, true, true],
181
178
  ['grok-3-mini', 'xAI', 0.0000003, 0.0000005, 131072, true, true],
182
179
  ['grok-3-fast', 'xAI', 0.000005, 0.000025, 131072, false, true],
183
180
  ['grok-3-mini-fast', 'xAI', 0.0000006, 0.000004, 131072, false, true],
184
- ['grok-2', 'xAI', 0.000002, 0.00001, 131072, false, true],
185
181
  ['openrouter/auto', 'OpenRouter', 0.000003, 0.000015, 200000, true, true],
186
182
  ['anthropic/claude-opus-4-6', 'OpenRouter', 0.000015, 0.000075, 200000, true, true],
187
183
  ['anthropic/claude-sonnet-4-5', 'OpenRouter', 0.000003, 0.000015, 200000, true, true],
@@ -195,21 +191,12 @@ let LocalBootstrapService = LocalBootstrapService_1 = class LocalBootstrapServic
195
191
  ['mistralai/mistral-large', 'OpenRouter', 0.000002, 0.000006, 128000, false, true],
196
192
  ['x-ai/grok-3', 'OpenRouter', 0.000003, 0.000015, 131072, true, true],
197
193
  ['openrouter/free', 'OpenRouter', 0, 0, 200000, true, true],
198
- ['stepfun/step-3.5-flash:free', 'OpenRouter', 0, 0, 256000, false, true],
199
- ['arcee-ai/trinity-large-preview:free', 'OpenRouter', 0, 0, 131072, false, true],
200
- ['upstage/solar-pro-3:free', 'OpenRouter', 0, 0, 128000, false, true],
201
- ['liquid/lfm-2.5-1.2b-thinking:free', 'OpenRouter', 0, 0, 32768, true, false],
202
- ['liquid/lfm-2.5-1.2b-instruct:free', 'OpenRouter', 0, 0, 32768, false, false],
203
- ['arcee-ai/trinity-mini:free', 'OpenRouter', 0, 0, 131072, false, false],
204
- ['nvidia/nemotron-3-nano-30b-a3b:free', 'OpenRouter', 0, 0, 256000, false, true],
205
194
  ['minimax-m2.5', 'MiniMax', 0.000000295, 0.0000012, 196608, true, true],
206
195
  ['minimax-m2.5-highspeed', 'MiniMax', 0.000000295, 0.0000012, 196608, true, true],
207
196
  ['minimax-m2.1', 'MiniMax', 0.00000027, 0.00000095, 196608, true, true],
208
197
  ['minimax-m2.1-highspeed', 'MiniMax', 0.00000027, 0.00000095, 196608, true, true],
209
198
  ['minimax-m2', 'MiniMax', 0.000000255, 0.000001, 196608, true, true],
210
- ['minimax-m2-her', 'MiniMax', 0.0000003, 0.0000012, 65536, false, false],
211
199
  ['minimax-m1', 'MiniMax', 0.0000004, 0.0000022, 1000000, true, true],
212
- ['minimax-01', 'MiniMax', 0.0000002, 0.0000011, 1000192, false, false],
213
200
  ['glm-5', 'Z.ai', 0.00000095, 0.00000255, 204800, true, true],
214
201
  ['glm-4.7', 'Z.ai', 0.0000003, 0.0000014, 202752, true, true],
215
202
  ['glm-4.7-flash', 'Z.ai', 0.00000006, 0.0000004, 202752, false, false],
@@ -220,9 +207,6 @@ let LocalBootstrapService = LocalBootstrapService_1 = class LocalBootstrapServic
220
207
  ['glm-4.5-flash', 'Z.ai', 0, 0, 131072, false, false],
221
208
  ['glm-4-plus', 'Zhipu', 0.0000005, 0.0000005, 128000, false, true],
222
209
  ['glm-4-flash', 'Zhipu', 0.00000005, 0.00000005, 128000, false, false],
223
- ['nova-pro', 'Amazon', 0.0000008, 0.0000032, 300000, false, true],
224
- ['nova-lite', 'Amazon', 0.00000006, 0.00000024, 300000, false, true],
225
- ['nova-micro', 'Amazon', 0.000000035, 0.00000014, 128000, false, false],
226
210
  ];
227
211
  for (const [name, provider, inputPrice, outputPrice, ctxWindow, reasoning, code] of models) {
228
212
  await this.pricingRepo.upsert({
@@ -24,8 +24,8 @@ class AddQualityScore1771800000000 {
24
24
  ['gemini-2.5-flash', 2],
25
25
  ['gemini-2.5-flash-lite', 1],
26
26
  ['gemini-2.0-flash', 2],
27
- ['deepseek-v3', 2],
28
- ['deepseek-r1', 4],
27
+ ['deepseek-chat', 2],
28
+ ['deepseek-reasoner', 4],
29
29
  ['kimi-k2', 3],
30
30
  ['qwen-2.5-72b-instruct', 2],
31
31
  ['qwq-32b', 1],
@@ -20,8 +20,8 @@ class SeedQualityScores1771800100000 {
20
20
  ['gemini-2.5-flash', 2],
21
21
  ['gemini-2.5-flash-lite', 1],
22
22
  ['gemini-2.0-flash', 2],
23
- ['deepseek-v3', 2],
24
- ['deepseek-r1', 4],
23
+ ['deepseek-chat', 2],
24
+ ['deepseek-reasoner', 4],
25
25
  ['kimi-k2', 3],
26
26
  ['qwen-2.5-72b-instruct', 2],
27
27
  ['qwq-32b', 1],
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AddCustomProviders1772668898071 = void 0;
4
+ class AddCustomProviders1772668898071 {
5
+ async up(queryRunner) {
6
+ await queryRunner.query(`
7
+ CREATE TABLE "custom_providers" (
8
+ "id" varchar NOT NULL,
9
+ "agent_id" varchar NOT NULL,
10
+ "user_id" varchar NOT NULL,
11
+ "name" varchar NOT NULL,
12
+ "base_url" varchar NOT NULL,
13
+ "models" text NOT NULL,
14
+ "created_at" timestamp NOT NULL DEFAULT NOW(),
15
+ CONSTRAINT "PK_custom_providers" PRIMARY KEY ("id"),
16
+ CONSTRAINT "FK_custom_providers_agent" FOREIGN KEY ("agent_id")
17
+ REFERENCES "agents"("id") ON DELETE CASCADE
18
+ )
19
+ `);
20
+ await queryRunner.query(`
21
+ CREATE UNIQUE INDEX "IDX_custom_providers_agent_name"
22
+ ON "custom_providers" ("agent_id", "name")
23
+ `);
24
+ }
25
+ async down(queryRunner) {
26
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_custom_providers_agent_name"`);
27
+ await queryRunner.query(`DROP TABLE IF EXISTS "custom_providers"`);
28
+ }
29
+ }
30
+ exports.AddCustomProviders1772668898071 = AddCustomProviders1772668898071;
31
+ //# sourceMappingURL=1772668898071-AddCustomProviders.js.map
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NullablePricing1772682112419 = void 0;
4
+ class NullablePricing1772682112419 {
5
+ async up(queryRunner) {
6
+ await queryRunner.query(`ALTER TABLE model_pricing ALTER COLUMN input_price_per_token DROP NOT NULL`);
7
+ await queryRunner.query(`ALTER TABLE model_pricing ALTER COLUMN output_price_per_token DROP NOT NULL`);
8
+ }
9
+ async down(queryRunner) {
10
+ await queryRunner.query(`UPDATE model_pricing SET input_price_per_token = 0 WHERE input_price_per_token IS NULL`);
11
+ await queryRunner.query(`UPDATE model_pricing SET output_price_per_token = 0 WHERE output_price_per_token IS NULL`);
12
+ await queryRunner.query(`ALTER TABLE model_pricing ALTER COLUMN input_price_per_token SET NOT NULL`);
13
+ await queryRunner.query(`ALTER TABLE model_pricing ALTER COLUMN output_price_per_token SET NOT NULL`);
14
+ }
15
+ }
16
+ exports.NullablePricing1772682112419 = NullablePricing1772682112419;
17
+ //# sourceMappingURL=1772682112419-NullablePricing.js.map
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AddPerformanceIndexes1772843035514 = void 0;
4
+ const crypto_util_1 = require("../../common/utils/crypto.util");
5
+ class AddPerformanceIndexes1772843035514 {
6
+ name = 'AddPerformanceIndexes1772843035514';
7
+ async up(queryRunner) {
8
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_agent_messages_user_id_timestamp" ON "agent_messages" ("user_id", "timestamp")`);
9
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_agent_messages_tenant_agent_name_ts" ON "agent_messages" ("tenant_id", "agent_name", "timestamp")`);
10
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_agent_messages_timestamp" ON "agent_messages" ("timestamp")`);
11
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_security_event_user_id_timestamp" ON "security_event" ("user_id", "timestamp")`);
12
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_cost_snapshots_tenant_agent_time" ON "cost_snapshots" ("tenant_id", "agent_id", "snapshot_time")`);
13
+ await queryRunner.query(`ALTER TABLE "user_providers" ADD COLUMN "key_prefix" varchar DEFAULT NULL`);
14
+ await this.backfillKeyPrefix(queryRunner);
15
+ }
16
+ async down(queryRunner) {
17
+ await queryRunner.query(`ALTER TABLE "user_providers" DROP COLUMN "key_prefix"`);
18
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_cost_snapshots_tenant_agent_time"`);
19
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_security_event_user_id_timestamp"`);
20
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_agent_messages_timestamp"`);
21
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_agent_messages_tenant_agent_name_ts"`);
22
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_agent_messages_user_id_timestamp"`);
23
+ }
24
+ async backfillKeyPrefix(queryRunner) {
25
+ let secret;
26
+ try {
27
+ secret = (0, crypto_util_1.getEncryptionSecret)();
28
+ }
29
+ catch {
30
+ console.warn('AddPerformanceIndexes: No encryption secret found. Skipping key_prefix backfill.');
31
+ return;
32
+ }
33
+ const rows = await queryRunner.query(`SELECT id, api_key_encrypted FROM "user_providers" WHERE api_key_encrypted IS NOT NULL AND api_key_encrypted != ''`);
34
+ let backfilled = 0;
35
+ for (const row of rows) {
36
+ let prefix;
37
+ try {
38
+ const plaintext = (0, crypto_util_1.decrypt)(row.api_key_encrypted, secret);
39
+ prefix = plaintext.substring(0, 8);
40
+ }
41
+ catch {
42
+ continue;
43
+ }
44
+ await queryRunner.query(`UPDATE "user_providers" SET key_prefix = $1 WHERE id = $2`, [
45
+ prefix,
46
+ row.id,
47
+ ]);
48
+ backfilled++;
49
+ }
50
+ if (backfilled > 0) {
51
+ console.log(`AddPerformanceIndexes: Backfilled key_prefix for ${backfilled} provider(s).`);
52
+ }
53
+ }
54
+ }
55
+ exports.AddPerformanceIndexes1772843035514 = AddPerformanceIndexes1772843035514;
56
+ //# sourceMappingURL=1772843035514-AddPerformanceIndexes.js.map
@@ -74,6 +74,7 @@ const PROVIDER_DISPLAY_NAMES = {
74
74
  'z-ai': 'Z.ai',
75
75
  openrouter: 'OpenRouter',
76
76
  };
77
+ const SUPPORTED_PREFIXES = new Set(Object.keys(PROVIDER_DISPLAY_NAMES));
77
78
  const OPENROUTER_API = 'https://openrouter.ai/api/v1/models';
78
79
  const FRESHNESS_HOURS = 12;
79
80
  let PricingSyncService = PricingSyncService_1 = class PricingSyncService {
@@ -111,6 +112,7 @@ let PricingSyncService = PricingSyncService_1 = class PricingSyncService {
111
112
  return 0;
112
113
  const updated = await this.syncAllModels(data);
113
114
  await this.resolveUnresolvedModels(data);
115
+ await this.removeUnsupportedModels();
114
116
  this.logger.log(`Pricing sync complete: ${updated} models updated`);
115
117
  if (updated > 0) {
116
118
  await this.pricingCache.reload();
@@ -167,6 +169,12 @@ let PricingSyncService = PricingSyncService_1 = class PricingSyncService {
167
169
  failed++;
168
170
  continue;
169
171
  }
172
+ if (!model.id.startsWith('openrouter/')) {
173
+ const slashIdx = model.id.indexOf('/');
174
+ if (slashIdx === -1 || !SUPPORTED_PREFIXES.has(model.id.substring(0, slashIdx))) {
175
+ continue;
176
+ }
177
+ }
170
178
  const { canonical, provider } = this.deriveNames(model.id);
171
179
  const existing = await this.pricingRepo.findOneBy({
172
180
  model_name: canonical,
@@ -236,6 +244,24 @@ let PricingSyncService = PricingSyncService_1 = class PricingSyncService {
236
244
  titleCase(str) {
237
245
  return str.charAt(0).toUpperCase() + str.slice(1);
238
246
  }
247
+ async removeUnsupportedModels() {
248
+ const all = await this.pricingRepo.find({ select: ['model_name'] });
249
+ const toDelete = [];
250
+ for (const row of all) {
251
+ const slashIdx = row.model_name.indexOf('/');
252
+ if (slashIdx === -1)
253
+ continue;
254
+ const prefix = row.model_name.substring(0, slashIdx);
255
+ if (prefix === 'openrouter')
256
+ continue;
257
+ if (!SUPPORTED_PREFIXES.has(prefix))
258
+ toDelete.push(row.model_name);
259
+ }
260
+ if (toDelete.length > 0) {
261
+ await this.pricingRepo.delete({ model_name: (0, typeorm_2.In)(toDelete) });
262
+ this.logger.log(`Removed ${toDelete.length} models from unsupported providers`);
263
+ }
264
+ }
239
265
  async resolveUnresolvedModels(data) {
240
266
  const unresolved = await this.unresolvedTracker.getUnresolved();
241
267
  if (unresolved.length === 0)
@@ -6,8 +6,7 @@ exports.QUALITY_OVERRIDES = new Map([
6
6
  ['claude-sonnet-4-5-20250929', 4],
7
7
  ['claude-sonnet-4-20250514', 4],
8
8
  ['gpt-4o', 3],
9
- ['grok-2', 3],
10
- ['mistral-large', 3],
9
+ ['mistral-large-latest', 3],
11
10
  ['kimi-k2', 3],
12
11
  ['openrouter/auto', 5],
13
12
  ['openrouter/free', 3],
@@ -17,6 +16,8 @@ function computeQualityScore(model) {
17
16
  const override = exports.QUALITY_OVERRIDES.get(model.model_name);
18
17
  if (override !== undefined)
19
18
  return override;
19
+ if (model.input_price_per_token == null || model.output_price_per_token == null)
20
+ return 2;
20
21
  const totalPerM = (Number(model.input_price_per_token) + Number(model.output_price_per_token)) * 1_000_000;
21
22
  const hasReasoning = model.capability_reasoning;
22
23
  const hasCode = model.capability_code;
@@ -44,9 +45,9 @@ function computeQualityScore(model) {
44
45
  return 4;
45
46
  if (totalPerM >= 3.0 && hasCode && !isMini)
46
47
  return 3;
47
- if (totalPerM >= 0.50 && hasBoth && !isMini)
48
+ if (totalPerM >= 0.5 && hasBoth && !isMini)
48
49
  return 3;
49
- if (hasReasoning && isMini && totalPerM >= 0.50)
50
+ if (hasReasoning && isMini && totalPerM >= 0.5)
50
51
  return 3;
51
52
  if (hasCode)
52
53
  return 2;