manifest 5.24.1 → 5.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +1 -0
  2. package/dist/backend/analytics/controllers/agent-analytics.controller.js +7 -1
  3. package/dist/backend/analytics/controllers/agents.controller.js +16 -6
  4. package/dist/backend/analytics/controllers/messages.controller.js +1 -2
  5. package/dist/backend/analytics/dto/messages-query.dto.js +2 -8
  6. package/dist/backend/analytics/services/messages-query.service.js +23 -8
  7. package/dist/backend/auth/auth.instance.js +2 -1
  8. package/dist/backend/common/utils/product-telemetry.js +5 -2
  9. package/dist/backend/common/utils/provider-inference.js +30 -0
  10. package/dist/backend/database/migrations/1772960000000-PurgeNonCuratedModels.js +4 -0
  11. package/dist/backend/database/seed-models.js +25 -3
  12. package/dist/backend/model-prices/model-name-normalizer.js +22 -0
  13. package/dist/backend/otlp/guards/otlp-auth.guard.js +37 -1
  14. package/dist/backend/otlp/otlp.controller.js +0 -3
  15. package/dist/backend/routing/routing.service.js +9 -0
  16. package/dist/index.js +11 -11
  17. package/package.json +1 -1
  18. package/public/assets/{Account-D79xeKyT.js → Account-BTS5z9ZI.js} +1 -1
  19. package/public/assets/{AuthBadge-D4H7aZ6T.js → AuthBadge-CXkjgzF2.js} +1 -1
  20. package/public/assets/Help-BCxDh7o6.js +1 -0
  21. package/public/assets/{InfoTooltip-BgPRXOPt.js → InfoTooltip-Bno8U6nE.js} +1 -1
  22. package/public/assets/Limits-CRdLGW1-.js +1 -0
  23. package/public/assets/Login-CfGUFi77.js +1 -0
  24. package/public/assets/MessageLog-D_Cu7rch.js +1 -0
  25. package/public/assets/{ModelPrices-Dfn6eK1-.js → ModelPrices-BbHSkT6Z.js} +1 -1
  26. package/public/assets/Overview-kF_Bct6K.js +1 -0
  27. package/public/assets/{Pagination-qcyFxNyW.js → Pagination-CQk_Ps2L.js} +1 -1
  28. package/public/assets/{ProviderIcon-DkiiWOHz.js → ProviderIcon-CsB_Gx2t.js} +1 -1
  29. package/public/assets/{Register-DH-OYkxl.js → Register-CnO4K1tM.js} +1 -1
  30. package/public/assets/{ResetPassword-Bd22sqer.js → ResetPassword-iCeAfd1k.js} +1 -1
  31. package/public/assets/Routing-BRLZGQzX.js +3 -0
  32. package/public/assets/Settings-BKKkYuZx.js +1 -0
  33. package/public/assets/{SetupStepInstall-FQkRe6aV.js → SetupStepInstall-gm9pOgFh.js} +1 -1
  34. package/public/assets/{SetupStepVerify-NS7Lj6qk.js → SetupStepVerify-B01hQCeb.js} +1 -1
  35. package/public/assets/{SocialButtons-BmEEEVZg.js → SocialButtons-CJFTtTz6.js} +1 -1
  36. package/public/assets/{auth-yjhjkKyr.js → auth-C2bQ9WcQ.js} +1 -1
  37. package/public/assets/index-DrLOx3nR.js +2 -0
  38. package/public/assets/index-fyETryMo.css +1 -0
  39. package/public/assets/{overview-B6JfuXXv.js → overview-DoY4lTQc.js} +1 -1
  40. package/public/assets/vendor-K8fEFSq9.js +1 -0
  41. package/public/icons/providers/moonshot.svg +1 -1
  42. package/public/index.html +4 -4
  43. package/public/assets/Help-0qmJVFn8.js +0 -1
  44. package/public/assets/Limits-68Jol4KI.js +0 -1
  45. package/public/assets/Login-q9JoAR_x.js +0 -1
  46. package/public/assets/MessageLog-CUR9du7l.js +0 -1
  47. package/public/assets/Overview-QSa9JMZx.js +0 -1
  48. package/public/assets/Routing-C-8PKTEa.js +0 -3
  49. package/public/assets/Settings-B7NcLutw.js +0 -1
  50. package/public/assets/index-DBqZDW9Z.css +0 -1
  51. package/public/assets/index-DKNLgS0h.js +0 -2
  52. package/public/assets/vendor-COodrVsO.js +0 -1
package/README.md CHANGED
@@ -57,6 +57,7 @@ Manifest is available in cloud and local versions. While both versions install t
57
57
  - You don't want the telemetry data to move from your computer
58
58
  - You don’t need multi-device access
59
59
  - You don't want to subscribe to a cloud service
60
+ - You are using a local model like Ollama
60
61
 
61
62
  If you don't know which version to choose, start with the **cloud version**.
62
63
 
@@ -21,6 +21,7 @@ const agent_analytics_service_1 = require("../services/agent-analytics.service")
21
21
  const range_query_dto_1 = require("../../common/dto/range-query.dto");
22
22
  const agent_cache_interceptor_1 = require("../../common/interceptors/agent-cache.interceptor");
23
23
  const cache_constants_1 = require("../../common/constants/cache.constants");
24
+ const product_telemetry_1 = require("../../common/utils/product-telemetry");
24
25
  let AgentAnalyticsController = class AgentAnalyticsController {
25
26
  analytics;
26
27
  constructor(analytics) {
@@ -29,7 +30,12 @@ let AgentAnalyticsController = class AgentAnalyticsController {
29
30
  async getUsage(query, req) {
30
31
  const range = query.range ?? '24h';
31
32
  const ctx = req.ingestionContext;
32
- return this.analytics.getUsage(range, ctx);
33
+ const usage = await this.analytics.getUsage(range, ctx);
34
+ return {
35
+ ...usage,
36
+ agentName: ctx.agentName,
37
+ telemetryId: (0, product_telemetry_1.hashForTelemetry)(ctx.userId),
38
+ };
33
39
  }
34
40
  async getCosts(query, req) {
35
41
  const range = query.range ?? '7d';
@@ -14,6 +14,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.AgentsController = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
+ const typeorm_1 = require("typeorm");
17
18
  const cache_manager_1 = require("@nestjs/cache-manager");
18
19
  const config_1 = require("@nestjs/config");
19
20
  const timeseries_queries_service_1 = require("../services/timeseries-queries.service");
@@ -52,12 +53,21 @@ let AgentsController = class AgentsController {
52
53
  throw new common_1.BadRequestException('Agent name produces an empty slug');
53
54
  }
54
55
  const displayName = body.name.trim();
55
- const result = await this.apiKeyGenerator.onboardAgent({
56
- tenantName: user.id,
57
- agentName: slug,
58
- displayName,
59
- email: user.email,
60
- });
56
+ let result;
57
+ try {
58
+ result = await this.apiKeyGenerator.onboardAgent({
59
+ tenantName: user.id,
60
+ agentName: slug,
61
+ displayName,
62
+ email: user.email,
63
+ });
64
+ }
65
+ catch (error) {
66
+ if (error instanceof typeorm_1.QueryFailedError && /unique|duplicate/i.test(error.message)) {
67
+ throw new common_1.ConflictException(`Agent "${slug}" already exists`);
68
+ }
69
+ throw error;
70
+ }
61
71
  (0, product_telemetry_1.trackCloudEvent)('agent_created', user.id, { agent_name: slug });
62
72
  return {
63
73
  agent: { id: result.agentId, name: slug, display_name: displayName },
@@ -26,9 +26,8 @@ let MessagesController = class MessagesController {
26
26
  return this.messagesQuery.getMessages({
27
27
  range: query.range,
28
28
  userId: user.id,
29
- status: query.status,
29
+ provider: query.provider,
30
30
  service_type: query.service_type,
31
- model: query.model,
32
31
  cost_min: query.cost_min,
33
32
  cost_max: query.cost_max,
34
33
  limit: Math.min(query.limit ?? 50, 200),
@@ -14,9 +14,8 @@ const class_transformer_1 = require("class-transformer");
14
14
  const class_validator_1 = require("class-validator");
15
15
  class MessagesQueryDto {
16
16
  range;
17
- status;
17
+ provider;
18
18
  service_type;
19
- model;
20
19
  cost_min;
21
20
  cost_max;
22
21
  limit;
@@ -33,17 +32,12 @@ __decorate([
33
32
  (0, class_validator_1.IsOptional)(),
34
33
  (0, class_validator_1.IsString)(),
35
34
  __metadata("design:type", String)
36
- ], MessagesQueryDto.prototype, "status", void 0);
35
+ ], MessagesQueryDto.prototype, "provider", void 0);
37
36
  __decorate([
38
37
  (0, class_validator_1.IsOptional)(),
39
38
  (0, class_validator_1.IsString)(),
40
39
  __metadata("design:type", String)
41
40
  ], MessagesQueryDto.prototype, "service_type", void 0);
42
- __decorate([
43
- (0, class_validator_1.IsOptional)(),
44
- (0, class_validator_1.IsString)(),
45
- __metadata("design:type", String)
46
- ], MessagesQueryDto.prototype, "model", void 0);
47
41
  __decorate([
48
42
  (0, class_validator_1.IsOptional)(),
49
43
  (0, class_validator_1.IsNumber)(),
@@ -21,6 +21,7 @@ const range_util_1 = require("../../common/utils/range.util");
21
21
  const query_helpers_1 = require("./query-helpers");
22
22
  const tenant_cache_service_1 = require("../../common/services/tenant-cache.service");
23
23
  const sql_dialect_1 = require("../../common/utils/sql-dialect");
24
+ const provider_inference_1 = require("../../common/utils/provider-inference");
24
25
  const MODELS_CACHE_TTL_MS = 60_000;
25
26
  const COUNT_CACHE_TTL_MS = 30_000;
26
27
  const MAX_CACHE_ENTRIES = 5_000;
@@ -45,18 +46,23 @@ let MessagesQueryService = class MessagesQueryService {
45
46
  baseQb.where('at.timestamp >= :cutoff', { cutoff });
46
47
  }
47
48
  (0, query_helpers_1.addTenantFilter)(baseQb, params.userId, undefined, tenantId);
48
- if (params.status)
49
- baseQb.andWhere('at.status = :status', { status: params.status });
50
49
  if (params.service_type)
51
50
  baseQb.andWhere('at.service_type = :serviceType', { serviceType: params.service_type });
52
- if (params.model)
53
- baseQb.andWhere('at.model = :model', { model: params.model });
54
51
  if (params.cost_min !== undefined)
55
52
  baseQb.andWhere('at.cost_usd >= :costMin', { costMin: params.cost_min });
56
53
  if (params.cost_max !== undefined)
57
54
  baseQb.andWhere('at.cost_usd <= :costMax', { costMax: params.cost_max });
58
55
  if (params.agent_name)
59
56
  baseQb.andWhere('at.agent_name = :filterAgent', { filterAgent: params.agent_name });
57
+ if (params.provider) {
58
+ const allModels = await this.getDistinctModels(params.userId, params.range, tenantId, params.agent_name);
59
+ const matching = allModels.filter((m) => (0, provider_inference_1.inferProviderFromModel)(m) === params.provider);
60
+ if (matching.length === 0) {
61
+ const providers = this.deriveProviders(allModels);
62
+ return { items: [], next_cursor: null, total_count: 0, providers };
63
+ }
64
+ baseQb.andWhere('at.model IN (:...providerModels)', { providerModels: matching });
65
+ }
60
66
  const countCacheKey = this.buildCountCacheKey(params);
61
67
  const countQb = baseQb.clone().select('COUNT(*)', 'total');
62
68
  const costExpr = (0, sql_dialect_1.sqlCastFloat)((0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd'), this.dialect);
@@ -98,7 +104,7 @@ let MessagesQueryService = class MessagesQueryService {
98
104
  }
99
105
  const cachedCount = params.cursor ? this.countCache.get(countCacheKey) : undefined;
100
106
  const countHit = cachedCount && cachedCount.expiresAt > Date.now();
101
- const [countResult, rows, models] = await Promise.all([
107
+ const [countResult, rows, allModels] = await Promise.all([
102
108
  countHit ? null : countQb.getRawOne(),
103
109
  dataQb
104
110
  .orderBy('at.timestamp', 'DESC')
@@ -122,13 +128,23 @@ let MessagesQueryService = class MessagesQueryService {
122
128
  const tsStr = ts instanceof Date ? (0, query_helpers_1.formatTimestamp)(ts) : String(ts ?? '');
123
129
  const lastId = lastItem?.['id'];
124
130
  const nextCursor = hasMore && lastItem ? `${tsStr}|${String(lastId)}` : null;
131
+ const providers = this.deriveProviders(allModels);
125
132
  return {
126
133
  items,
127
134
  next_cursor: nextCursor,
128
135
  total_count: totalCount,
129
- models,
136
+ providers,
130
137
  };
131
138
  }
139
+ deriveProviders(models) {
140
+ const seen = new Set();
141
+ for (const m of models) {
142
+ const p = (0, provider_inference_1.inferProviderFromModel)(m);
143
+ if (p)
144
+ seen.add(p);
145
+ }
146
+ return [...seen].sort();
147
+ }
132
148
  async getDistinctModels(userId, range, tenantId, agentName) {
133
149
  const cacheKey = `${userId}:${agentName ?? ''}:${range ?? 'all'}`;
134
150
  const cached = this.modelsCache.get(cacheKey);
@@ -161,9 +177,8 @@ let MessagesQueryService = class MessagesQueryService {
161
177
  return [
162
178
  params.userId,
163
179
  params.range ?? '',
164
- params.status ?? '',
180
+ params.provider ?? '',
165
181
  params.service_type ?? '',
166
- params.model ?? '',
167
182
  params.agent_name ?? '',
168
183
  params.cost_min ?? '',
169
184
  params.cost_max ?? '',
@@ -44,7 +44,7 @@ function buildTrustedOrigins() {
44
44
  }
45
45
  return origins;
46
46
  }
47
- exports.auth = isLocalMode
47
+ const authInstance = isLocalMode
48
48
  ? null
49
49
  : (0, better_auth_1.betterAuth)({
50
50
  database: database,
@@ -126,4 +126,5 @@ exports.auth = isLocalMode
126
126
  },
127
127
  },
128
128
  });
129
+ exports.auth = authInstance;
129
130
  //# sourceMappingURL=auth.instance.js.map
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getMachineId = getMachineId;
4
4
  exports.trackEvent = trackEvent;
5
+ exports.hashForTelemetry = hashForTelemetry;
5
6
  exports.trackCloudEvent = trackCloudEvent;
6
7
  const crypto_1 = require("crypto");
7
8
  const os_1 = require("os");
@@ -34,13 +35,15 @@ function trackEvent(event, properties) {
34
35
  ...properties,
35
36
  });
36
37
  }
38
+ function hashForTelemetry(id) {
39
+ return (0, crypto_1.createHash)('sha256').update(id).digest('hex').slice(0, 16);
40
+ }
37
41
  function trackCloudEvent(event, tenantId, properties) {
38
42
  if (isOptedOut())
39
43
  return;
40
- const hashedTenant = (0, crypto_1.createHash)('sha256').update(tenantId).digest('hex').slice(0, 16);
41
44
  const version = getPackageVersion();
42
45
  (0, posthog_sender_1.sendToPostHog)(event, {
43
- distinct_id: hashedTenant,
46
+ distinct_id: hashForTelemetry(tenantId),
44
47
  os: (0, os_1.platform)(),
45
48
  os_version: (0, os_1.release)(),
46
49
  node_version: process.versions.node,
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inferProviderFromModel = inferProviderFromModel;
4
+ const MODEL_PREFIX_MAP = [
5
+ [/^openrouter\//, 'openrouter'],
6
+ [/^claude-/, 'anthropic'],
7
+ [/^gpt-|^o[134]-|^o[134] |^chatgpt-/, 'openai'],
8
+ [/^gemini-/, 'gemini'],
9
+ [/^deepseek-/, 'deepseek'],
10
+ [/^grok-/, 'xai'],
11
+ [/^mistral-|^codestral|^pixtral|^open-mistral/, 'mistral'],
12
+ [/^kimi-|^moonshot-/, 'moonshot'],
13
+ [/^minimax-/i, 'minimax'],
14
+ [/^glm-/, 'zai'],
15
+ [/^qwen[23]|^qwq-/, 'qwen'],
16
+ [/^[a-z][\w-]*\//, 'openrouter'],
17
+ ];
18
+ function inferProviderFromModel(model) {
19
+ if (model.startsWith('custom:'))
20
+ return 'custom';
21
+ if (/:/.test(model) && !model.endsWith(':free'))
22
+ return 'ollama';
23
+ const lower = model.toLowerCase();
24
+ for (const [re, id] of MODEL_PREFIX_MAP) {
25
+ if (re.test(lower))
26
+ return id;
27
+ }
28
+ return undefined;
29
+ }
30
+ //# sourceMappingURL=provider-inference.js.map
@@ -3,8 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PurgeNonCuratedModels1772960000000 = void 0;
4
4
  const CURATED_MODELS = [
5
5
  'claude-opus-4-6',
6
+ 'claude-sonnet-4-6',
6
7
  'claude-sonnet-4-5-20250929',
8
+ 'claude-opus-4-5-20251101',
9
+ 'claude-opus-4-1-20250805',
7
10
  'claude-sonnet-4-20250514',
11
+ 'claude-opus-4-20250514',
8
12
  'claude-haiku-4-5-20251001',
9
13
  'gpt-4o',
10
14
  'gpt-4o-mini',
@@ -2,7 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SEED_MODELS = void 0;
4
4
  exports.SEED_MODELS = [
5
- ['claude-opus-4-6', 'Anthropic', 0.000015, 0.000075, 200000, true, true, 'Claude Opus 4.6'],
5
+ ['claude-opus-4-6', 'Anthropic', 0.000005, 0.000025, 200000, true, true, 'Claude Opus 4.6'],
6
+ ['claude-sonnet-4-6', 'Anthropic', 0.000003, 0.000015, 200000, true, true, 'Claude Sonnet 4.6'],
6
7
  [
7
8
  'claude-sonnet-4-5-20250929',
8
9
  'Anthropic',
@@ -13,6 +14,26 @@ exports.SEED_MODELS = [
13
14
  true,
14
15
  'Claude Sonnet 4.5',
15
16
  ],
17
+ [
18
+ 'claude-opus-4-5-20251101',
19
+ 'Anthropic',
20
+ 0.000015,
21
+ 0.000075,
22
+ 200000,
23
+ true,
24
+ true,
25
+ 'Claude Opus 4.5',
26
+ ],
27
+ [
28
+ 'claude-opus-4-1-20250805',
29
+ 'Anthropic',
30
+ 0.000015,
31
+ 0.000075,
32
+ 200000,
33
+ true,
34
+ true,
35
+ 'Claude Opus 4.1',
36
+ ],
16
37
  [
17
38
  'claude-sonnet-4-20250514',
18
39
  'Anthropic',
@@ -23,6 +44,7 @@ exports.SEED_MODELS = [
23
44
  true,
24
45
  'Claude Sonnet 4',
25
46
  ],
47
+ ['claude-opus-4-20250514', 'Anthropic', 0.000015, 0.000075, 200000, true, true, 'Claude Opus 4'],
26
48
  [
27
49
  'claude-haiku-4-5-20251001',
28
50
  'Anthropic',
@@ -100,8 +122,8 @@ exports.SEED_MODELS = [
100
122
  [
101
123
  'anthropic/claude-opus-4-6',
102
124
  'OpenRouter',
103
- 0.000015,
104
- 0.000075,
125
+ 0.000005,
126
+ 0.000025,
105
127
  200000,
106
128
  true,
107
129
  true,
@@ -3,12 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.stripProviderPrefix = stripProviderPrefix;
4
4
  exports.stripDateSuffix = stripDateSuffix;
5
5
  exports.buildAliasMap = buildAliasMap;
6
+ exports.normalizeDots = normalizeDots;
6
7
  exports.resolveModelName = resolveModelName;
7
8
  const KNOWN_ALIASES = [
8
9
  ['claude-opus-4', 'claude-opus-4-6'],
9
10
  ['claude-sonnet-4.5', 'claude-sonnet-4-5-20250929'],
11
+ ['claude-sonnet-4-5', 'claude-sonnet-4-5-20250929'],
12
+ ['claude-opus-4-5', 'claude-opus-4-5-20251101'],
13
+ ['claude-opus-4-1', 'claude-opus-4-1-20250805'],
14
+ ['claude-sonnet-4-0', 'claude-sonnet-4-20250514'],
15
+ ['claude-opus-4-0', 'claude-opus-4-20250514'],
10
16
  ['claude-sonnet-4', 'claude-sonnet-4-20250514'],
11
17
  ['claude-haiku-4.5', 'claude-haiku-4-5-20251001'],
18
+ ['claude-haiku-4-5', 'claude-haiku-4-5-20251001'],
12
19
  ['deepseek-v3', 'deepseek-chat'],
13
20
  ['deepseek-chat-v3-0324', 'deepseek-chat'],
14
21
  ['deepseek-r1', 'deepseek-reasoner'],
@@ -67,6 +74,9 @@ function buildAliasMap(canonicalNames) {
67
74
  }
68
75
  return map;
69
76
  }
77
+ function normalizeDots(name) {
78
+ return name.replace(/\./g, '-');
79
+ }
70
80
  function resolveModelName(name, aliasMap) {
71
81
  const exact = aliasMap.get(name);
72
82
  if (exact)
@@ -81,6 +91,18 @@ function resolveModelName(name, aliasMap) {
81
91
  if (fromNoDate)
82
92
  return fromNoDate;
83
93
  }
94
+ const dotNorm = normalizeDots(stripped);
95
+ if (dotNorm !== stripped) {
96
+ const fromDotNorm = aliasMap.get(dotNorm);
97
+ if (fromDotNorm)
98
+ return fromDotNorm;
99
+ const dotNormNoDate = stripDateSuffix(dotNorm);
100
+ if (dotNormNoDate !== dotNorm) {
101
+ const fromDotNormNoDate = aliasMap.get(dotNormNoDate);
102
+ if (fromDotNormNoDate)
103
+ return fromDotNormNoDate;
104
+ }
105
+ }
84
106
  return undefined;
85
107
  }
86
108
  //# sourceMappingURL=model-name-normalizer.js.map
@@ -26,6 +26,7 @@ let OtlpAuthGuard = OtlpAuthGuard_1 = class OtlpAuthGuard {
26
26
  keyRepo;
27
27
  logger = new common_1.Logger(OtlpAuthGuard_1.name);
28
28
  cache = new Map();
29
+ devContext = null;
29
30
  CACHE_TTL_MS = 5 * 60 * 1000;
30
31
  MAX_CACHE_SIZE = 10_000;
31
32
  cleanupTimer;
@@ -42,7 +43,9 @@ let OtlpAuthGuard = OtlpAuthGuard_1 = class OtlpAuthGuard {
42
43
  async canActivate(context) {
43
44
  const request = context.switchToHttp().getRequest();
44
45
  const authHeader = request.headers['authorization'];
45
- const isLocal = process.env['MANIFEST_MODE'] === 'local' && LOOPBACK_IPS.has(request.ip ?? '');
46
+ const isLoopback = LOOPBACK_IPS.has(request.ip ?? '');
47
+ const isLocal = process.env['MANIFEST_MODE'] === 'local' && isLoopback;
48
+ const isDevLoopback = process.env['NODE_ENV'] === 'development' && isLoopback;
46
49
  if (!authHeader && isLocal) {
47
50
  request.ingestionContext = {
48
51
  tenantId: local_mode_constants_1.LOCAL_TENANT_ID,
@@ -52,6 +55,13 @@ let OtlpAuthGuard = OtlpAuthGuard_1 = class OtlpAuthGuard {
52
55
  };
53
56
  return true;
54
57
  }
58
+ if (!authHeader && isDevLoopback) {
59
+ const devCtx = await this.resolveDevContext();
60
+ if (devCtx) {
61
+ request.ingestionContext = devCtx;
62
+ return true;
63
+ }
64
+ }
55
65
  if (!authHeader) {
56
66
  this.logger.warn(`OTLP request without auth from ${request.ip}`);
57
67
  throw new common_1.UnauthorizedException('Authorization header required');
@@ -70,6 +80,13 @@ let OtlpAuthGuard = OtlpAuthGuard_1 = class OtlpAuthGuard {
70
80
  };
71
81
  return true;
72
82
  }
83
+ if (isDevLoopback) {
84
+ const devCtx = await this.resolveDevContext();
85
+ if (devCtx) {
86
+ request.ingestionContext = devCtx;
87
+ return true;
88
+ }
89
+ }
73
90
  throw new common_1.UnauthorizedException('Invalid API key format');
74
91
  }
75
92
  const cached = this.cache.get(token);
@@ -127,6 +144,25 @@ let OtlpAuthGuard = OtlpAuthGuard_1 = class OtlpAuthGuard {
127
144
  clearCache() {
128
145
  this.cache.clear();
129
146
  }
147
+ async resolveDevContext() {
148
+ if (this.devContext && this.devContext.expiresAt > Date.now()) {
149
+ return this.devContext.context;
150
+ }
151
+ const keyRecord = await this.keyRepo.findOne({
152
+ where: { is_active: true },
153
+ relations: ['agent', 'tenant'],
154
+ });
155
+ if (!keyRecord)
156
+ return null;
157
+ const ctx = {
158
+ tenantId: keyRecord.tenant_id,
159
+ agentId: keyRecord.agent_id,
160
+ agentName: keyRecord.agent.name,
161
+ userId: keyRecord.tenant.name,
162
+ };
163
+ this.devContext = { context: ctx, expiresAt: Date.now() + this.CACHE_TTL_MS };
164
+ return ctx;
165
+ }
130
166
  evictExpired() {
131
167
  const now = Date.now();
132
168
  for (const [key, entry] of this.cache) {
@@ -91,9 +91,6 @@ let OtlpController = OtlpController_1 = class OtlpController {
91
91
  if (this.seenAgents.has(ctx.agentId))
92
92
  return;
93
93
  this.seenAgents.add(ctx.agentId);
94
- (0, product_telemetry_1.trackCloudEvent)('plugin_registered', ctx.userId, {
95
- source: 'backend',
96
- });
97
94
  (0, product_telemetry_1.trackCloudEvent)('first_telemetry_received', ctx.userId, {
98
95
  agent_id_hash: ctx.agentId.slice(0, 8),
99
96
  });
@@ -96,6 +96,11 @@ let RoutingService = RoutingService_1 = class RoutingService {
96
96
  });
97
97
  if (existing)
98
98
  return { isNew: false };
99
+ const hasApiKey = await this.providerRepo.findOne({
100
+ where: { agent_id: agentId, provider, auth_type: 'api_key', is_active: true },
101
+ });
102
+ if (hasApiKey)
103
+ return { isNew: false };
99
104
  const record = Object.assign(new user_provider_entity_1.UserProvider(), {
100
105
  id: (0, crypto_1.randomUUID)(),
101
106
  user_id: userId,
@@ -279,6 +284,10 @@ let RoutingService = RoutingService_1 = class RoutingService {
279
284
  if (existing) {
280
285
  existing.override_model = model;
281
286
  existing.override_auth_type = authType ?? null;
287
+ if (existing.fallback_models?.includes(model)) {
288
+ const filtered = existing.fallback_models.filter((m) => m !== model);
289
+ existing.fallback_models = filtered.length > 0 ? filtered : null;
290
+ }
282
291
  existing.updated_at = new Date().toISOString();
283
292
  await this.tierRepo.save(existing);
284
293
  this.routingCache.invalidateAgent(agentId);