manifest 5.35.0 → 5.35.2

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 (44) hide show
  1. package/dist/backend/database/models-dev-sync.service.js +7 -0
  2. package/dist/backend/model-discovery/model-fallback.js +7 -0
  3. package/dist/backend/model-discovery/provider-model-fetcher.service.js +9 -1
  4. package/dist/backend/model-prices/model-name-normalizer.js +16 -0
  5. package/dist/backend/model-prices/model-pricing-cache.service.js +16 -1
  6. package/dist/backend/notifications/emails/threshold-alert.js +5 -1
  7. package/dist/backend/notifications/notifications.controller.js +16 -1
  8. package/dist/backend/notifications/services/limit-check.service.js +11 -14
  9. package/dist/backend/notifications/services/notification-cron.service.js +15 -18
  10. package/dist/backend/notifications/services/notification-email.service.js +3 -2
  11. package/dist/backend/notifications/services/notification-log.service.js +9 -0
  12. package/dist/backend/routing/proxy/anthropic-adapter.js +1 -3
  13. package/dist/backend/routing/proxy/chatgpt-adapter.js +119 -85
  14. package/dist/backend/routing/proxy/chatgpt-helpers.js +108 -0
  15. package/dist/backend/routing/proxy/proxy-fallback.service.js +15 -3
  16. package/dist/backend/routing/proxy/proxy-response-handler.js +2 -2
  17. package/dist/backend/routing/proxy/proxy.service.js +17 -6
  18. package/dist/backend/routing/resolve/resolve.service.js +2 -1
  19. package/dist/backend/routing/routing-core/provider-key.service.js +12 -2
  20. package/dist/openclaw.plugin.json +1 -1
  21. package/openclaw.plugin.json +1 -1
  22. package/package.json +1 -1
  23. package/public/assets/{Account-Bmij8ZrS.js → Account-B_oSTrdf.js} +1 -1
  24. package/public/assets/Limits-HwYePaDb.js +1 -0
  25. package/public/assets/{Login-B6PWrEGq.js → Login-BWgCdMOB.js} +1 -1
  26. package/public/assets/{MessageLog-Cb_ucG8H.js → MessageLog-TkfamA_u.js} +1 -1
  27. package/public/assets/ModelPrices-Bg-zVU9G.js +1 -0
  28. package/public/assets/{Overview-DY7f_oAi.js → Overview-ByF5CHrf.js} +1 -1
  29. package/public/assets/{Register-CbGDtQku.js → Register-BDPUwPD6.js} +1 -1
  30. package/public/assets/{ResetPassword-DFQ1lTig.js → ResetPassword-BuNNDKRS.js} +1 -1
  31. package/public/assets/{Routing-Kn93Tqlz.js → Routing-95JQekwl.js} +1 -1
  32. package/public/assets/{Settings-BZw5xW8c.js → Settings-DPVOsBPN.js} +1 -1
  33. package/public/assets/{SocialButtons-DEIg2R3v.js → SocialButtons-tQY9m0lz.js} +1 -1
  34. package/public/assets/{index-BCCvBxnZ.js → index--P4PCJDm.js} +2 -2
  35. package/public/assets/index-BsqwVwB3.css +1 -0
  36. package/public/assets/{model-display-C23X2Y_y.js → model-display-DmgfqEg8.js} +1 -1
  37. package/public/assets/{overview-DGoLsOFJ.js → overview-D5Ohx_UN.js} +1 -1
  38. package/public/assets/{routing-C26EmiVE.js → routing-Bf9SE33r.js} +1 -1
  39. package/public/assets/{routing-utils-DGg0JMgE.js → routing-utils-U3G1-aWH.js} +1 -1
  40. package/public/index.html +2 -2
  41. package/public/logo-white.svg +81 -79
  42. package/public/assets/Limits-CJay2wDy.js +0 -1
  43. package/public/assets/ModelPrices-CK2tG0zg.js +0 -1
  44. package/public/assets/index-D6OW2yDC.css +0 -1
@@ -41,6 +41,7 @@ const VERSION_SUFFIX_RE = /-\d{3}$/;
41
41
  const DATE_SUFFIX_RE = /-\d{4}-?\d{2}-?\d{2}$/;
42
42
  const SHORT_DATE_SUFFIX_RE = /-\d{4}$/;
43
43
  const LATEST_SUFFIX = '-latest';
44
+ const GOOGLE_VARIANT_RE = /-(?:preview(?:-\d{2,4}){1,3}|exp-\d{4}|latest)$/;
44
45
  const REASONING_SUFFIX_RE = /-(reasoning|non-reasoning)$/;
45
46
  let ModelsDevSyncService = ModelsDevSyncService_1 = class ModelsDevSyncService {
46
47
  logger = new common_1.Logger(ModelsDevSyncService_1.name);
@@ -111,6 +112,12 @@ let ModelsDevSyncService = ModelsDevSyncService_1 = class ModelsDevSyncService {
111
112
  if (found)
112
113
  return found;
113
114
  }
115
+ const noGoogleVariant = modelId.replace(GOOGLE_VARIANT_RE, '');
116
+ if (noGoogleVariant !== modelId) {
117
+ const found = providerModels.get(noGoogleVariant);
118
+ if (found)
119
+ return found;
120
+ }
114
121
  const noReasoning = modelId.replace(REASONING_SUFFIX_RE, '');
115
122
  if (noReasoning !== modelId) {
116
123
  const found = providerModels.get(noReasoning);
@@ -9,6 +9,7 @@ exports.supplementWithKnownModels = supplementWithKnownModels;
9
9
  const providers_1 = require("../common/constants/providers");
10
10
  const manifest_shared_1 = require("manifest-shared");
11
11
  const anthropic_model_id_1 = require("../common/utils/anthropic-model-id");
12
+ const model_name_normalizer_1 = require("../model-prices/model-name-normalizer");
12
13
  function normalizeProviderModelId(providerId, modelId) {
13
14
  return providerId.toLowerCase() === 'anthropic'
14
15
  ? (0, anthropic_model_id_1.normalizeAnthropicShortModelId)(modelId)
@@ -62,6 +63,12 @@ function lookupWithVariants(pricingSync, prefix, modelId) {
62
63
  const freeResult = pricingSync.lookupPricing(`${prefix}/${modelId}:free`);
63
64
  if (freeResult)
64
65
  return freeResult;
66
+ const noGoogleVariant = modelId.replace(model_name_normalizer_1.GOOGLE_VARIANT_RE, '');
67
+ if (noGoogleVariant !== modelId) {
68
+ const result = pricingSync.lookupPricing(`${prefix}/${noGoogleVariant}`);
69
+ if (result)
70
+ return result;
71
+ }
65
72
  return null;
66
73
  }
67
74
  function buildModelsDevFallback(modelsDevSync, providerId) {
@@ -49,9 +49,17 @@ const parseOpenAI = createModelParser({
49
49
  getDisplayName: (_entry, id) => id,
50
50
  });
51
51
  const OPENAI_NON_CHAT_RE = /(?:embed|tts|whisper|dall-e|moderation|davinci|babbage|^text-|audio|realtime|-transcribe|^sora|^gpt-3\.5-turbo-instruct)/i;
52
+ const OPENAI_DATE_SUFFIX_RE = /-\d{4}-\d{2}-\d{2}$/;
52
53
  const OPENAI_RESPONSES_ONLY_RE = /(?:-codex(?!-mini-latest)|^gpt-5[^/]*-pro(?:-|$))/i;
53
54
  function parseOpenAIChatOnly(body, provider) {
54
- return parseOpenAI(body, provider).filter((m) => !OPENAI_NON_CHAT_RE.test(m.id) && !OPENAI_RESPONSES_ONLY_RE.test(m.id));
55
+ const filtered = parseOpenAI(body, provider).filter((m) => !OPENAI_NON_CHAT_RE.test(m.id) && !OPENAI_RESPONSES_ONLY_RE.test(m.id));
56
+ const ids = new Set(filtered.map((m) => m.id));
57
+ return filtered.filter((m) => {
58
+ if (!OPENAI_DATE_SUFFIX_RE.test(m.id))
59
+ return true;
60
+ const alias = m.id.replace(OPENAI_DATE_SUFFIX_RE, '');
61
+ return !ids.has(alias);
62
+ });
55
63
  }
56
64
  const MISTRAL_NON_CHAT_RE = /(?:^mistral-ocr|embed)/i;
57
65
  function parseMistralChatOnly(body, provider) {
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GOOGLE_VARIANT_RE = void 0;
4
+ exports.stripGoogleVariant = stripGoogleVariant;
3
5
  exports.stripProviderPrefix = stripProviderPrefix;
4
6
  exports.stripDateSuffix = stripDateSuffix;
5
7
  exports.buildAliasMap = buildAliasMap;
@@ -37,6 +39,10 @@ const PROVIDER_PREFIXES = [
37
39
  ];
38
40
  const DATE_SUFFIX_RE = /-\d{4}-?\d{2}-?\d{2}$/;
39
41
  const VERSION_SUFFIX_RE = /-\d{3}$/;
42
+ exports.GOOGLE_VARIANT_RE = /-(?:preview(?:-\d{2,4}){1,3}|exp-\d{4}|latest)$/;
43
+ function stripGoogleVariant(name) {
44
+ return name.replace(exports.GOOGLE_VARIANT_RE, '');
45
+ }
40
46
  function stripProviderPrefix(name) {
41
47
  for (const prefix of PROVIDER_PREFIXES) {
42
48
  if (name.startsWith(prefix)) {
@@ -65,6 +71,10 @@ function buildAliasMap(canonicalNames) {
65
71
  if (bareNoVersion !== bare && !map.has(bareNoVersion)) {
66
72
  map.set(bareNoVersion, name);
67
73
  }
74
+ const bareNoVariant = stripGoogleVariant(bare);
75
+ if (bareNoVariant !== bare && !map.has(bareNoVariant)) {
76
+ map.set(bareNoVariant, name);
77
+ }
68
78
  for (const variant of (0, anthropic_model_id_1.buildAnthropicShortModelIdVariants)(bare)) {
69
79
  if (!map.has(variant)) {
70
80
  map.set(variant, name);
@@ -96,6 +106,12 @@ function resolveModelName(name, aliasMap) {
96
106
  if (fromNoDate)
97
107
  return fromNoDate;
98
108
  }
109
+ const noVariant = stripGoogleVariant(stripped);
110
+ if (noVariant !== stripped) {
111
+ const fromNoVariant = aliasMap.get(noVariant);
112
+ if (fromNoVariant)
113
+ return fromNoVariant;
114
+ }
99
115
  const dotNorm = normalizeDots(stripped);
100
116
  if (dotNorm !== stripped) {
101
117
  const fromDotNorm = aliasMap.get(dotNorm);
@@ -15,6 +15,7 @@ var ModelPricingCacheService_1;
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.ModelPricingCacheService = void 0;
17
17
  const common_1 = require("@nestjs/common");
18
+ const schedule_1 = require("@nestjs/schedule");
18
19
  const model_name_normalizer_1 = require("./model-name-normalizer");
19
20
  const pricing_sync_service_1 = require("../database/pricing-sync.service");
20
21
  const models_dev_sync_service_1 = require("../database/models-dev-sync.service");
@@ -35,6 +36,9 @@ let ModelPricingCacheService = ModelPricingCacheService_1 = class ModelPricingCa
35
36
  async onApplicationBootstrap() {
36
37
  await this.reload();
37
38
  }
39
+ async scheduledReload() {
40
+ await this.reload();
41
+ }
38
42
  async reload() {
39
43
  this.cache.clear();
40
44
  const orCache = this.pricingSync.getAll();
@@ -115,7 +119,12 @@ let ModelPricingCacheService = ModelPricingCacheService_1 = class ModelPricingCa
115
119
  validated: this.resolveValidatedForModelsDev(providerId, model.id),
116
120
  source: 'models.dev',
117
121
  };
118
- this.cache.set(model.id, pricingEntry);
122
+ const existing = this.cache.get(model.id);
123
+ const hasRealPricing = existing && (existing.input_price_per_token ?? 0) > 0;
124
+ const isZeroPricing = (model.inputPricePerToken ?? 0) === 0 && (model.outputPricePerToken ?? 0) === 0;
125
+ if (!hasRealPricing || !isZeroPricing) {
126
+ this.cache.set(model.id, pricingEntry);
127
+ }
119
128
  for (const prefix of registryEntry.openRouterPrefixes) {
120
129
  const prefixedKey = `${prefix}/${model.id}`;
121
130
  if (this.cache.has(prefixedKey)) {
@@ -145,6 +154,12 @@ let ModelPricingCacheService = ModelPricingCacheService_1 = class ModelPricingCa
145
154
  }
146
155
  };
147
156
  exports.ModelPricingCacheService = ModelPricingCacheService;
157
+ __decorate([
158
+ (0, schedule_1.Cron)('0 5 * * *'),
159
+ __metadata("design:type", Function),
160
+ __metadata("design:paramtypes", []),
161
+ __metadata("design:returntype", Promise)
162
+ ], ModelPricingCacheService.prototype, "scheduledReload", null);
148
163
  exports.ModelPricingCacheService = ModelPricingCacheService = ModelPricingCacheService_1 = __decorate([
149
164
  (0, common_1.Injectable)(),
150
165
  __param(1, (0, common_1.Optional)()),
@@ -28,7 +28,11 @@ function ThresholdAlertEmail(props) {
28
28
  const accentColor = isSoft ? '#ea580c' : '#dc2626';
29
29
  const accentBg = isSoft ? '#fff7ed' : '#fef2f2';
30
30
  const accentBorder = isSoft ? '#fed7aa' : '#fecaca';
31
- return ((0, jsx_runtime_1.jsxs)(components_1.Html, { children: [(0, jsx_runtime_1.jsx)(components_1.Head, {}), (0, jsx_runtime_1.jsxs)(components_1.Preview, { children: [agentName, " exceeded ", metricType, " threshold (", formatValue(actualValue, metricType), ")"] }), (0, jsx_runtime_1.jsx)(components_1.Body, { style: body, children: (0, jsx_runtime_1.jsxs)(components_1.Container, { style: container, children: [(0, jsx_runtime_1.jsx)(components_1.Section, { style: logoSection, children: (0, jsx_runtime_1.jsx)(components_1.Img, { src: logoUrl, alt: "Manifest", width: "140", height: "32", style: logoImg }) }), (0, jsx_runtime_1.jsxs)(components_1.Section, { style: card, children: [(0, jsx_runtime_1.jsx)(components_1.Section, { style: alertBadgeContainer, children: (0, jsx_runtime_1.jsx)(components_1.Text, { style: { ...alertBadge, color: accentColor, backgroundColor: accentBg }, children: isSoft ? 'Warning' : 'Threshold exceeded' }) }), (0, jsx_runtime_1.jsxs)(components_1.Text, { style: heading, children: [agentName, " exceeded the ", metricType, " limit"] }), (0, jsx_runtime_1.jsxs)(components_1.Text, { style: paragraph, children: ["Your agent ", (0, jsx_runtime_1.jsx)("strong", { children: agentName }), " has exceeded the ", (0, jsx_runtime_1.jsx)("strong", { children: metricType }), ' ', "threshold for the current ", (0, jsx_runtime_1.jsx)("strong", { children: period }), " period."] }), isSoft ? ((0, jsx_runtime_1.jsx)(components_1.Text, { style: paragraph, children: "Requests are still being processed normally." })) : ((0, jsx_runtime_1.jsx)(components_1.Section, { style: { ...hardLimitBox, backgroundColor: accentBg, borderColor: accentBorder }, children: (0, jsx_runtime_1.jsxs)(components_1.Text, { style: { ...hardLimitText, color: accentColor }, children: ["Requests are now blocked until the next period resets", periodResetDate ? ` on ${formatTimestamp(periodResetDate)}` : '', "."] }) })), (0, jsx_runtime_1.jsxs)(components_1.Section, { style: statsRow, children: [(0, jsx_runtime_1.jsxs)(components_1.Section, { style: statBox, children: [(0, jsx_runtime_1.jsx)(components_1.Text, { style: statLabel, children: "Threshold" }), (0, jsx_runtime_1.jsx)(components_1.Text, { style: statValue, children: formatValue(threshold, metricType) })] }), (0, jsx_runtime_1.jsxs)(components_1.Section, { style: { ...statBoxAlert, backgroundColor: accentBg, borderColor: accentBorder }, children: [(0, jsx_runtime_1.jsx)(components_1.Text, { style: statLabel, children: "Actual usage" }), (0, jsx_runtime_1.jsx)(components_1.Text, { style: { ...statValueAlert, color: accentColor }, children: formatValue(actualValue, metricType) })] })] }), (0, jsx_runtime_1.jsxs)(components_1.Section, { style: metaRow, children: [(0, jsx_runtime_1.jsxs)(components_1.Text, { style: metaText, children: ["Period: ", period] }), (0, jsx_runtime_1.jsxs)(components_1.Text, { style: metaText, children: ["Triggered: ", formatTimestamp(timestamp)] })] }), (0, jsx_runtime_1.jsx)(components_1.Section, { style: ctaContainer, children: (0, jsx_runtime_1.jsx)(components_1.Button, { style: ctaButton, href: agentUrl, children: "View Agent Dashboard \u2192" }) })] }), (0, jsx_runtime_1.jsx)(components_1.Hr, { style: divider }), (0, jsx_runtime_1.jsxs)(components_1.Section, { style: footer, children: [(0, jsx_runtime_1.jsx)(components_1.Text, { style: footerNote, children: "You are receiving this because you set up a notification rule in Manifest." }), (0, jsx_runtime_1.jsxs)(components_1.Text, { style: footerMuted, children: ["\u00A9 2026 MNFST Inc. All rights reserved.", ' ', (0, jsx_runtime_1.jsx)(components_1.Link, { href: "https://manifest.build", style: footerLink, children: "manifest.build" })] })] })] }) })] }));
31
+ return ((0, jsx_runtime_1.jsxs)(components_1.Html, { children: [(0, jsx_runtime_1.jsx)(components_1.Head, {}), (0, jsx_runtime_1.jsx)(components_1.Preview, { children: isSoft
32
+ ? `${agentName} exceeded ${metricType} threshold (${formatValue(actualValue, metricType)})`
33
+ : `${agentName} has been blocked — ${metricType} limit reached (${formatValue(actualValue, metricType)} / ${formatValue(threshold, metricType)})` }), (0, jsx_runtime_1.jsx)(components_1.Body, { style: body, children: (0, jsx_runtime_1.jsxs)(components_1.Container, { style: container, children: [(0, jsx_runtime_1.jsx)(components_1.Section, { style: logoSection, children: (0, jsx_runtime_1.jsx)(components_1.Img, { src: logoUrl, alt: "Manifest", width: "140", height: "32", style: logoImg }) }), (0, jsx_runtime_1.jsxs)(components_1.Section, { style: card, children: [(0, jsx_runtime_1.jsx)(components_1.Section, { style: alertBadgeContainer, children: (0, jsx_runtime_1.jsx)(components_1.Text, { style: { ...alertBadge, color: accentColor, backgroundColor: accentBg }, children: isSoft ? 'Warning' : 'Agent blocked' }) }), (0, jsx_runtime_1.jsx)(components_1.Text, { style: heading, children: isSoft
34
+ ? `${agentName} exceeded the ${metricType} limit`
35
+ : `${agentName} has been blocked` }), (0, jsx_runtime_1.jsx)(components_1.Text, { style: paragraph, children: isSoft ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["Your agent ", (0, jsx_runtime_1.jsx)("strong", { children: agentName }), " has exceeded the", ' ', (0, jsx_runtime_1.jsx)("strong", { children: metricType }), " threshold for the current ", (0, jsx_runtime_1.jsx)("strong", { children: period }), ' ', "period."] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["Your agent ", (0, jsx_runtime_1.jsx)("strong", { children: agentName }), " has reached the", ' ', (0, jsx_runtime_1.jsx)("strong", { children: metricType }), " limit of", ' ', (0, jsx_runtime_1.jsx)("strong", { children: formatValue(threshold, metricType) }), " per", ' ', (0, jsx_runtime_1.jsx)("strong", { children: period }), ". New requests are blocked to protect your budget."] })) }), isSoft ? ((0, jsx_runtime_1.jsx)(components_1.Text, { style: paragraph, children: "Requests are still being processed normally." })) : ((0, jsx_runtime_1.jsx)(components_1.Section, { style: { ...hardLimitBox, backgroundColor: accentBg, borderColor: accentBorder }, children: (0, jsx_runtime_1.jsxs)(components_1.Text, { style: { ...hardLimitText, color: accentColor }, children: ["Requests are now blocked until the next period resets", periodResetDate ? ` on ${formatTimestamp(periodResetDate)}` : '', "."] }) })), (0, jsx_runtime_1.jsxs)(components_1.Section, { style: statsRow, children: [(0, jsx_runtime_1.jsxs)(components_1.Section, { style: statBox, children: [(0, jsx_runtime_1.jsx)(components_1.Text, { style: statLabel, children: "Threshold" }), (0, jsx_runtime_1.jsx)(components_1.Text, { style: statValue, children: formatValue(threshold, metricType) })] }), (0, jsx_runtime_1.jsxs)(components_1.Section, { style: { ...statBoxAlert, backgroundColor: accentBg, borderColor: accentBorder }, children: [(0, jsx_runtime_1.jsx)(components_1.Text, { style: statLabel, children: isSoft ? 'Actual usage' : 'Current usage' }), (0, jsx_runtime_1.jsx)(components_1.Text, { style: { ...statValueAlert, color: accentColor }, children: formatValue(actualValue, metricType) })] })] }), (0, jsx_runtime_1.jsxs)(components_1.Section, { style: metaRow, children: [(0, jsx_runtime_1.jsxs)(components_1.Text, { style: metaText, children: ["Period: ", period] }), (0, jsx_runtime_1.jsxs)(components_1.Text, { style: metaText, children: ["Triggered: ", formatTimestamp(timestamp)] })] }), (0, jsx_runtime_1.jsx)(components_1.Section, { style: ctaContainer, children: (0, jsx_runtime_1.jsx)(components_1.Button, { style: ctaButton, href: agentUrl, children: "View Agent Dashboard \u2192" }) })] }), (0, jsx_runtime_1.jsx)(components_1.Hr, { style: divider }), (0, jsx_runtime_1.jsxs)(components_1.Section, { style: footer, children: [(0, jsx_runtime_1.jsx)(components_1.Text, { style: footerNote, children: "You are receiving this because you set up a notification rule in Manifest." }), (0, jsx_runtime_1.jsxs)(components_1.Text, { style: footerMuted, children: ["\u00A9 2026 MNFST Inc. All rights reserved.", ' ', (0, jsx_runtime_1.jsx)(components_1.Link, { href: "https://manifest.build", style: footerLink, children: "manifest.build" })] })] })] }) })] }));
32
36
  }
33
37
  const brandBg = '#f8f6f1';
34
38
  const brandCardBg = '#ffffff';
@@ -17,6 +17,7 @@ exports.NotificationsController = void 0;
17
17
  const common_1 = require("@nestjs/common");
18
18
  const current_user_decorator_1 = require("../auth/current-user.decorator");
19
19
  const notification_rules_service_1 = require("./services/notification-rules.service");
20
+ const notification_log_service_1 = require("./services/notification-log.service");
20
21
  const email_provider_config_service_1 = require("./services/email-provider-config.service");
21
22
  const notification_cron_service_1 = require("./services/notification-cron.service");
22
23
  const limit_check_service_1 = require("./services/limit-check.service");
@@ -24,12 +25,14 @@ const notification_rule_dto_1 = require("./dto/notification-rule.dto");
24
25
  const set_email_provider_dto_1 = require("./dto/set-email-provider.dto");
25
26
  let NotificationsController = NotificationsController_1 = class NotificationsController {
26
27
  rulesService;
28
+ notificationLog;
27
29
  emailProviderConfigService;
28
30
  cronService;
29
31
  limitCheck;
30
32
  logger = new common_1.Logger(NotificationsController_1.name);
31
- constructor(rulesService, emailProviderConfigService, cronService, limitCheck) {
33
+ constructor(rulesService, notificationLog, emailProviderConfigService, cronService, limitCheck) {
32
34
  this.rulesService = rulesService;
35
+ this.notificationLog = notificationLog;
33
36
  this.emailProviderConfigService = emailProviderConfigService;
34
37
  this.cronService = cronService;
35
38
  this.limitCheck = limitCheck;
@@ -64,6 +67,9 @@ let NotificationsController = NotificationsController_1 = class NotificationsCon
64
67
  const triggered = await this.cronService.checkThresholds(user.id);
65
68
  return { triggered, message: `${triggered} notification(s) triggered` };
66
69
  }
70
+ async getLogs(agentName, user) {
71
+ return this.notificationLog.getLogsForAgent(user.id, agentName);
72
+ }
67
73
  async listRules(agentName, user) {
68
74
  return this.rulesService.listRules(user.id, agentName);
69
75
  }
@@ -149,6 +155,14 @@ __decorate([
149
155
  __metadata("design:paramtypes", [Object]),
150
156
  __metadata("design:returntype", Promise)
151
157
  ], NotificationsController.prototype, "triggerCheck", null);
158
+ __decorate([
159
+ (0, common_1.Get)('logs'),
160
+ __param(0, (0, common_1.Query)('agent_name')),
161
+ __param(1, (0, current_user_decorator_1.CurrentUser)()),
162
+ __metadata("design:type", Function),
163
+ __metadata("design:paramtypes", [String, Object]),
164
+ __metadata("design:returntype", Promise)
165
+ ], NotificationsController.prototype, "getLogs", null);
152
166
  __decorate([
153
167
  (0, common_1.Get)(),
154
168
  __param(0, (0, common_1.Query)('agent_name')),
@@ -185,6 +199,7 @@ __decorate([
185
199
  exports.NotificationsController = NotificationsController = NotificationsController_1 = __decorate([
186
200
  (0, common_1.Controller)('api/v1/notifications'),
187
201
  __metadata("design:paramtypes", [notification_rules_service_1.NotificationRulesService,
202
+ notification_log_service_1.NotificationLogService,
188
203
  email_provider_config_service_1.EmailProviderConfigService,
189
204
  notification_cron_service_1.NotificationCronService,
190
205
  limit_check_service_1.LimitCheckService])
@@ -85,9 +85,18 @@ let LimitCheckService = LimitCheckService_1 = class LimitCheckService {
85
85
  const now = (0, notification_log_service_1.formatNotificationTimestamp)();
86
86
  const providerConfig = await this.emailProviderConfig.getFullConfig(rule.user_id);
87
87
  const email = await this.notificationLog.resolveUserEmail(rule.user_id, providerConfig?.notificationEmail);
88
- let emailSent = false;
88
+ await this.notificationLog.insertLog({
89
+ ruleId: rule.id,
90
+ periodStart,
91
+ periodEnd,
92
+ actualValue: actual,
93
+ thresholdValue: rule.threshold,
94
+ metricType: rule.metric_type,
95
+ agentName: rule.agent_name,
96
+ sentAt: now,
97
+ });
89
98
  if (email) {
90
- emailSent = await this.emailService.sendThresholdAlert(email, {
99
+ await this.emailService.sendThresholdAlert(email, {
91
100
  agentName: rule.agent_name,
92
101
  metricType: rule.metric_type,
93
102
  threshold: rule.threshold,
@@ -99,18 +108,6 @@ let LimitCheckService = LimitCheckService_1 = class LimitCheckService {
99
108
  periodResetDate: (0, period_util_1.computePeriodResetDate)(rule.period),
100
109
  }, providerConfig ?? undefined);
101
110
  }
102
- if (emailSent || !email) {
103
- await this.notificationLog.insertLog({
104
- ruleId: rule.id,
105
- periodStart,
106
- periodEnd,
107
- actualValue: actual,
108
- thresholdValue: rule.threshold,
109
- metricType: rule.metric_type,
110
- agentName: rule.agent_name,
111
- sentAt: now,
112
- });
113
- }
114
111
  }
115
112
  async getCachedRules(tenantId, agentName) {
116
113
  const key = `${tenantId}:${agentName}`;
@@ -102,9 +102,18 @@ let NotificationCronService = NotificationCronService_1 = class NotificationCron
102
102
  const now = (0, notification_log_service_1.formatNotificationTimestamp)();
103
103
  const fullConfig = await this.emailProviderConfigService.getFullConfig(rule.user_id);
104
104
  const email = await this.notificationLog.resolveUserEmail(rule.user_id, fullConfig?.notificationEmail);
105
- let emailSent = false;
105
+ await this.notificationLog.insertLog({
106
+ ruleId: rule.id,
107
+ periodStart,
108
+ periodEnd,
109
+ actualValue: actual,
110
+ thresholdValue: rule.threshold,
111
+ metricType: rule.metric_type,
112
+ agentName: rule.agent_name,
113
+ sentAt: now,
114
+ });
106
115
  if (email) {
107
- emailSent = await this.emailService.sendThresholdAlert(email, {
116
+ const emailSent = await this.emailService.sendThresholdAlert(email, {
108
117
  agentName: rule.agent_name,
109
118
  metricType: rule.metric_type,
110
119
  threshold: rule.threshold,
@@ -114,26 +123,14 @@ let NotificationCronService = NotificationCronService_1 = class NotificationCron
114
123
  agentUrl: `${this.runtime.getAuthBaseUrl()}/agents/${encodeURIComponent(rule.agent_name)}`,
115
124
  alertType: 'soft',
116
125
  }, fullConfig ?? undefined);
126
+ if (!emailSent) {
127
+ this.logger.warn(`Failed to send alert for rule ${rule.id}, will retry next cron run`);
128
+ }
117
129
  }
118
130
  else {
119
131
  this.logger.warn(`No email found for user ${rule.user_id}, skipping alert for rule ${rule.id}`);
120
132
  }
121
- if (emailSent || !email) {
122
- await this.notificationLog.insertLog({
123
- ruleId: rule.id,
124
- periodStart,
125
- periodEnd,
126
- actualValue: actual,
127
- thresholdValue: rule.threshold,
128
- metricType: rule.metric_type,
129
- agentName: rule.agent_name,
130
- sentAt: now,
131
- });
132
- }
133
- else {
134
- this.logger.warn(`Failed to send alert for rule ${rule.id}, will retry next cron run`);
135
- }
136
- return emailSent || !email;
133
+ return true;
137
134
  }
138
135
  };
139
136
  exports.NotificationCronService = NotificationCronService;
@@ -29,8 +29,9 @@ let NotificationEmailService = NotificationEmailService_1 = class NotificationEm
29
29
  const element = (0, threshold_alert_1.ThresholdAlertEmail)(props);
30
30
  const html = await (0, render_1.render)(element);
31
31
  const text = await (0, render_1.render)(element, { plainText: true });
32
- const prefix = props.alertType === 'soft' ? 'Warning' : 'Alert';
33
- const subject = `${prefix}: ${props.agentName} exceeded ${props.metricType} threshold`;
32
+ const subject = props.alertType === 'soft'
33
+ ? `Warning: ${props.agentName} exceeded ${props.metricType} threshold`
34
+ : `Blocked: ${props.agentName} reached ${props.metricType} limit`;
34
35
  if (providerConfig) {
35
36
  const defaultFrom = this.fromEmail;
36
37
  const from = providerConfig.domain
@@ -51,6 +51,15 @@ let NotificationLogService = class NotificationLogService {
51
51
  params.sentAt,
52
52
  ]);
53
53
  }
54
+ async getLogsForAgent(userId, agentName) {
55
+ return this.ds.query(this.sql(`SELECT nl.id, nl.sent_at, nl.actual_value, nl.threshold_value,
56
+ nl.metric_type, nl.period_start, nl.period_end, nl.agent_name
57
+ FROM notification_logs nl
58
+ JOIN notification_rules nr ON nr.id = nl.rule_id
59
+ WHERE nr.user_id = $1 AND nl.agent_name = $2
60
+ ORDER BY nl.sent_at DESC
61
+ LIMIT 50`), [userId, agentName]);
62
+ }
54
63
  async resolveUserEmail(userId, notificationEmail) {
55
64
  if (notificationEmail)
56
65
  return notificationEmail;
@@ -34,7 +34,7 @@ function extractSystemBlocks(messages) {
34
34
  }
35
35
  function toContentBlocks(content) {
36
36
  if (typeof content === 'string')
37
- return [{ type: 'text', text: content }];
37
+ return content ? [{ type: 'text', text: content }] : [];
38
38
  if (Array.isArray(content)) {
39
39
  return content
40
40
  .filter((b) => b.type === 'text' && typeof b.text === 'string')
@@ -98,8 +98,6 @@ function toAnthropicRequest(body, _model, options) {
98
98
  messages: converted,
99
99
  max_tokens: body.max_tokens || 4096,
100
100
  };
101
- if (shouldCache)
102
- result.cache_control = { type: 'ephemeral' };
103
101
  if (systemBlocks.length > 0)
104
102
  result.system = systemBlocks;
105
103
  const tools = convertTools(body.tools);
@@ -4,37 +4,80 @@ exports.toResponsesRequest = toResponsesRequest;
4
4
  exports.fromResponsesResponse = fromResponsesResponse;
5
5
  exports.transformResponsesStreamChunk = transformResponsesStreamChunk;
6
6
  const crypto_1 = require("crypto");
7
- const DEFAULT_INSTRUCTIONS = 'You are a helpful assistant.';
7
+ const chatgpt_helpers_1 = require("./chatgpt-helpers");
8
8
  function toResponsesRequest(body, model) {
9
9
  const messages = (body.messages ?? []);
10
- const input = messages
11
- .filter((m) => m.role !== 'system' && m.role !== 'developer')
12
- .map((m) => ({ role: m.role, content: convertContent(m.content, m.role) }));
10
+ const input = [];
11
+ for (const message of messages) {
12
+ if (message.role === 'system' || message.role === 'developer')
13
+ continue;
14
+ if (message.role === 'assistant' &&
15
+ Array.isArray(message.tool_calls) &&
16
+ message.tool_calls.length > 0) {
17
+ const assistantText = (0, chatgpt_helpers_1.extractTextContent)(message.content);
18
+ if (assistantText) {
19
+ input.push({ role: 'assistant', content: (0, chatgpt_helpers_1.convertContent)(assistantText, 'assistant') });
20
+ }
21
+ input.push(...(0, chatgpt_helpers_1.convertAssistantToolCalls)(message.tool_calls));
22
+ continue;
23
+ }
24
+ if (message.role === 'tool' || message.role === 'function') {
25
+ input.push({
26
+ type: 'function_call_output',
27
+ call_id: typeof message.tool_call_id === 'string' ? message.tool_call_id : (0, crypto_1.randomUUID)(),
28
+ output: (0, chatgpt_helpers_1.extractTextContent)(message.content) ?? JSON.stringify(message.content ?? ''),
29
+ });
30
+ continue;
31
+ }
32
+ input.push({ role: message.role, content: (0, chatgpt_helpers_1.convertContent)(message.content, message.role) });
33
+ }
13
34
  const request = {
14
35
  model,
15
36
  input,
16
37
  stream: body.stream !== false,
17
38
  store: false,
18
- instructions: extractInstructions(messages),
39
+ instructions: (0, chatgpt_helpers_1.extractInstructions)(messages),
19
40
  };
41
+ if (Array.isArray(body.tools)) {
42
+ request.tools = (0, chatgpt_helpers_1.convertTools)(body.tools);
43
+ }
20
44
  return request;
21
45
  }
22
46
  function fromResponsesResponse(data, model) {
23
47
  const output = (data.output ?? []);
24
48
  let text = '';
49
+ const toolCalls = [];
25
50
  for (const item of output) {
26
- if (item.type !== 'message')
27
- continue;
28
- const content = item.content;
29
- if (!content)
51
+ if (item.type === 'message') {
52
+ const content = item.content;
53
+ if (!content)
54
+ continue;
55
+ for (const part of content) {
56
+ if (part.type === 'output_text' && part.text)
57
+ text += part.text;
58
+ }
30
59
  continue;
31
- for (const part of content) {
32
- if (part.type === 'output_text' && part.text)
33
- text += part.text;
60
+ }
61
+ if (item.type === 'function_call') {
62
+ toolCalls.push({
63
+ id: item.call_id ?? (0, crypto_1.randomUUID)(),
64
+ type: 'function',
65
+ function: {
66
+ name: item.name ?? '',
67
+ arguments: item.arguments ?? '{}',
68
+ },
69
+ });
34
70
  }
35
71
  }
36
72
  const usage = data.usage ?? {};
37
73
  const inputDetails = usage.input_tokens_details;
74
+ const message = {
75
+ role: 'assistant',
76
+ content: text || null,
77
+ };
78
+ if (toolCalls.length > 0) {
79
+ message.tool_calls = toolCalls;
80
+ }
38
81
  return {
39
82
  id: `chatcmpl-${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 29)}`,
40
83
  object: 'chat.completion',
@@ -43,8 +86,8 @@ function fromResponsesResponse(data, model) {
43
86
  choices: [
44
87
  {
45
88
  index: 0,
46
- message: { role: 'assistant', content: text },
47
- finish_reason: 'stop',
89
+ message,
90
+ finish_reason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
48
91
  },
49
92
  ],
50
93
  usage: {
@@ -74,84 +117,75 @@ function transformResponsesStreamChunk(chunk, model) {
74
117
  if (!eventType && !dataStr)
75
118
  return null;
76
119
  if (eventType === 'response.output_text.delta') {
77
- const data = safeParse(dataStr);
120
+ const data = (0, chatgpt_helpers_1.safeParse)(dataStr);
78
121
  if (!data)
79
122
  return null;
80
123
  const delta = typeof data.delta === 'string' ? data.delta : '';
81
- return formatSSE({ delta: { content: delta }, finish_reason: null }, model);
82
- }
83
- if (eventType === 'response.completed') {
84
- const data = safeParse(dataStr);
85
- const respUsage = data?.response?.usage;
86
- const respDetails = data?.response?.usage;
87
- const cachedTokens = respDetails?.input_tokens_details?.cached_tokens ?? 0;
88
- const usage = respUsage
89
- ? {
90
- prompt_tokens: respUsage.input_tokens ?? 0,
91
- completion_tokens: respUsage.output_tokens ?? 0,
92
- total_tokens: respUsage.total_tokens ?? 0,
93
- cache_read_tokens: cachedTokens,
94
- cache_creation_tokens: 0,
95
- }
96
- : undefined;
97
- const finish = formatSSE({ delta: {}, finish_reason: 'stop' }, model, usage);
98
- return `${finish}\ndata: [DONE]\n\n`;
124
+ return (0, chatgpt_helpers_1.formatSSE)({ delta: { content: delta }, finish_reason: null }, model);
99
125
  }
100
- return null;
101
- }
102
- function convertContent(content, role) {
103
- const partType = role === 'assistant' ? 'output_text' : 'input_text';
104
- if (typeof content === 'string') {
105
- return [{ type: partType, text: content }];
106
- }
107
- if (!Array.isArray(content))
108
- return content;
109
- return content.map((part) => {
110
- if (part.type === 'text')
111
- return { ...part, type: partType };
112
- return part;
113
- });
114
- }
115
- function extractInstructions(messages) {
116
- const parts = [];
117
- for (const message of messages) {
118
- if (message.role !== 'system' && message.role !== 'developer')
119
- continue;
120
- parts.push(...extractTextParts(message.content));
126
+ if (eventType === 'response.function_call_arguments.delta') {
127
+ const data = (0, chatgpt_helpers_1.safeParse)(dataStr);
128
+ if (!data)
129
+ return null;
130
+ const delta = typeof data.delta === 'string' ? data.delta : '';
131
+ return (0, chatgpt_helpers_1.formatSSE)({
132
+ delta: {
133
+ tool_calls: [
134
+ {
135
+ index: typeof data.output_index === 'number' ? data.output_index : 0,
136
+ function: { arguments: delta },
137
+ },
138
+ ],
139
+ },
140
+ finish_reason: null,
141
+ }, model);
121
142
  }
122
- const instructions = parts
123
- .map((part) => part.trim())
124
- .filter(Boolean)
125
- .join('\n\n');
126
- return instructions || DEFAULT_INSTRUCTIONS;
127
- }
128
- function extractTextParts(content) {
129
- if (typeof content === 'string')
130
- return [content];
131
- if (!Array.isArray(content))
132
- return [];
133
- return content
134
- .filter((part) => part.type === 'text' && typeof part.text === 'string')
135
- .map((part) => part.text);
136
- }
137
- function safeParse(str) {
138
- try {
139
- return JSON.parse(str);
143
+ if (eventType === 'response.output_item.added') {
144
+ const data = (0, chatgpt_helpers_1.safeParse)(dataStr);
145
+ if (!data)
146
+ return null;
147
+ const item = (0, chatgpt_helpers_1.isObjectRecord)(data.item) ? data.item : undefined;
148
+ if (item?.type !== 'function_call')
149
+ return null;
150
+ return (0, chatgpt_helpers_1.formatSSE)({
151
+ delta: {
152
+ tool_calls: [
153
+ {
154
+ index: typeof data.output_index === 'number' ? data.output_index : 0,
155
+ id: item.call_id ?? '',
156
+ type: 'function',
157
+ function: { name: item.name ?? '', arguments: '' },
158
+ },
159
+ ],
160
+ },
161
+ finish_reason: null,
162
+ }, model);
140
163
  }
141
- catch {
142
- return null;
164
+ if (eventType === 'response.completed') {
165
+ return handleCompletedEvent(dataStr, model);
143
166
  }
167
+ return null;
144
168
  }
145
- function formatSSE(choice, model, usage) {
146
- const payload = {
147
- id: `chatcmpl-${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 29)}`,
148
- object: 'chat.completion.chunk',
149
- created: Math.floor(Date.now() / 1000),
150
- model,
151
- choices: [{ index: 0, ...choice }],
152
- };
153
- if (usage)
154
- payload.usage = usage;
155
- return `data: ${JSON.stringify(payload)}\n\n`;
169
+ function handleCompletedEvent(dataStr, model) {
170
+ const data = (0, chatgpt_helpers_1.safeParse)(dataStr);
171
+ const response = (0, chatgpt_helpers_1.isObjectRecord)(data?.response) ? data.response : undefined;
172
+ const responseUsage = response?.usage;
173
+ const inputDetails = responseUsage?.input_tokens_details;
174
+ const cachedTokens = inputDetails?.cached_tokens ?? 0;
175
+ const usage = responseUsage
176
+ ? {
177
+ prompt_tokens: responseUsage.input_tokens ?? 0,
178
+ completion_tokens: responseUsage.output_tokens ?? 0,
179
+ total_tokens: responseUsage.total_tokens ?? 0,
180
+ cache_read_tokens: cachedTokens,
181
+ cache_creation_tokens: 0,
182
+ }
183
+ : undefined;
184
+ const responseOutput = Array.isArray(response?.output)
185
+ ? response.output
186
+ : [];
187
+ const hasFunctionCalls = responseOutput.some((item) => item.type === 'function_call');
188
+ const finish = (0, chatgpt_helpers_1.formatSSE)({ delta: {}, finish_reason: hasFunctionCalls ? 'tool_calls' : 'stop' }, model, usage);
189
+ return `${finish}\ndata: [DONE]\n\n`;
156
190
  }
157
191
  //# sourceMappingURL=chatgpt-adapter.js.map