manifest 5.35.1 → 5.36.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.
- package/dist/backend/database/models-dev-sync.service.js +8 -1
- package/dist/backend/database/pricing-sync.service.js +1 -1
- package/dist/backend/model-discovery/model-discovery.service.js +9 -3
- package/dist/backend/model-discovery/model-fallback.js +7 -0
- package/dist/backend/model-discovery/provider-model-fetcher.service.js +31 -10
- package/dist/backend/model-prices/model-name-normalizer.js +16 -0
- package/dist/backend/model-prices/model-pricing-cache.service.js +16 -1
- package/dist/backend/notifications/emails/threshold-alert.js +5 -1
- package/dist/backend/notifications/notifications.controller.js +16 -1
- package/dist/backend/notifications/services/limit-check.service.js +11 -14
- package/dist/backend/notifications/services/notification-cron.service.js +15 -18
- package/dist/backend/notifications/services/notification-email.service.js +3 -2
- package/dist/backend/notifications/services/notification-log.service.js +9 -0
- package/dist/backend/routing/proxy/anthropic-adapter.js +1 -3
- package/dist/backend/routing/proxy/chatgpt-adapter.js +119 -85
- package/dist/backend/routing/proxy/chatgpt-helpers.js +108 -0
- package/dist/backend/routing/proxy/google-adapter.js +37 -13
- package/dist/backend/routing/proxy/provider-client.js +2 -2
- package/dist/backend/routing/proxy/proxy-exception.filter.js +67 -0
- package/dist/backend/routing/proxy/proxy-fallback.service.js +18 -4
- package/dist/backend/routing/proxy/proxy-friendly-response.js +115 -0
- package/dist/backend/routing/proxy/proxy-rate-limiter.js +2 -2
- package/dist/backend/routing/proxy/proxy-response-handler.js +25 -5
- package/dist/backend/routing/proxy/proxy.controller.js +23 -8
- package/dist/backend/routing/proxy/proxy.module.js +4 -0
- package/dist/backend/routing/proxy/proxy.service.js +30 -82
- package/dist/backend/routing/proxy/thought-signature-cache.js +45 -0
- package/dist/backend/routing/resolve/resolve.service.js +2 -1
- package/dist/backend/routing/routing-core/provider-key.service.js +12 -2
- package/dist/backend/routing/routing-core/tier-auto-assign.service.js +9 -4
- package/dist/openclaw.plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/public/assets/{Account-CDKYuc8Y.js → Account-mbrfK614.js} +1 -1
- package/public/assets/{AuthBadge-CN6tvI2S.js → AuthBadge-zJLfTd7g.js} +1 -1
- package/public/assets/{CopyButton-BrMDM22y.js → CopyButton-CZjeoxZT.js} +1 -1
- package/public/assets/{Help-BCxDh7o6.js → Help-Bfmf4yw8.js} +1 -1
- package/public/assets/Limits-D6l8xhUA.js +1 -0
- package/public/assets/{Login-DBUKUcpO.js → Login-Dn1aZjXw.js} +1 -1
- package/public/assets/{MessageLog-DB_ZGtRS.js → MessageLog-CWiKoRpB.js} +1 -1
- package/public/assets/{ModelPrices-CsTVSmEf.js → ModelPrices-CbrlrT8V.js} +1 -1
- package/public/assets/{Overview-WPFffGwu.js → Overview-Bpuw4RLJ.js} +1 -1
- package/public/assets/{Pagination-CQk_Ps2L.js → Pagination-Ctujg2Tx.js} +1 -1
- package/public/assets/{Register-BPUSuyhr.js → Register-CIAm21m5.js} +1 -1
- package/public/assets/{ResetPassword-DbYikLII.js → ResetPassword-Bir2RHWf.js} +1 -1
- package/public/assets/{Routing-CqkpJ5K7.js → Routing-Bc7D8_7D.js} +1 -1
- package/public/assets/Settings-CciPiVro.js +1 -0
- package/public/assets/{SetupStepAddProvider-CYgcYYLH.js → SetupStepAddProvider-CJemlPav.js} +1 -1
- package/public/assets/{SocialButtons-Bz_LKRqa.js → SocialButtons-8xBLnR-O.js} +1 -1
- package/public/assets/{auth-B7LxODhZ.js → auth-DmX5tAfx.js} +1 -1
- package/public/assets/index-BsqwVwB3.css +1 -0
- package/public/assets/{index-6zwEk5De.js → index-JMxG9oFW.js} +2 -2
- package/public/assets/{model-display-bwKW0oKN.js → model-display-1OeS4GNc.js} +1 -1
- package/public/assets/{overview-CsP0GEBQ.js → overview-DIAj72dm.js} +1 -1
- package/public/assets/{routing-C7G-3vs6.js → routing-CuE7sS2j.js} +1 -1
- package/public/assets/{routing-utils-TY9NvF2y.js → routing-utils-B_Dl8Sof.js} +1 -1
- package/public/assets/vendor-pl6Q4jbW.js +1 -0
- package/public/index.html +4 -4
- package/public/logo-white.svg +81 -79
- package/public/assets/Limits-DpYfj80Z.js +0 -1
- package/public/assets/Settings-CPrWA4SD.js +0 -1
- package/public/assets/index-DHEiPwfc.css +0 -1
- package/public/assets/vendor-K8fEFSq9.js +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);
|
|
@@ -169,7 +176,7 @@ let ModelsDevSyncService = ModelsDevSyncService_1 = class ModelsDevSyncService {
|
|
|
169
176
|
}
|
|
170
177
|
const outputMods = model.modalities?.output?.map((m) => m.toLowerCase());
|
|
171
178
|
if (outputMods && outputMods.length > 0) {
|
|
172
|
-
return outputMods.
|
|
179
|
+
return outputMods.includes('text');
|
|
173
180
|
}
|
|
174
181
|
return true;
|
|
175
182
|
}
|
|
@@ -98,7 +98,7 @@ let PricingSyncService = PricingSyncService_1 = class PricingSyncService {
|
|
|
98
98
|
}
|
|
99
99
|
const outputModalities = model.architecture?.output_modalities?.map((m) => m.toLowerCase());
|
|
100
100
|
if (outputModalities && outputModalities.length > 0) {
|
|
101
|
-
return outputModalities.
|
|
101
|
+
return outputModalities.includes('text');
|
|
102
102
|
}
|
|
103
103
|
return true;
|
|
104
104
|
}
|
|
@@ -123,11 +123,17 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
123
123
|
...this.enrichModel(model, provider.provider),
|
|
124
124
|
authType: authType,
|
|
125
125
|
}));
|
|
126
|
-
|
|
126
|
+
const filtered = enriched.filter((model) => {
|
|
127
|
+
const mdEntry = this.modelsDevSync?.lookupModel(provider.provider, model.id);
|
|
128
|
+
if (mdEntry && mdEntry.toolCall === false)
|
|
129
|
+
return false;
|
|
130
|
+
return true;
|
|
131
|
+
});
|
|
132
|
+
provider.cached_models = filtered;
|
|
127
133
|
provider.models_fetched_at = new Date().toISOString();
|
|
128
134
|
await this.providerRepo.save(provider);
|
|
129
|
-
this.logger.log(`Discovered ${
|
|
130
|
-
return
|
|
135
|
+
this.logger.log(`Discovered ${filtered.length} models for provider ${provider.provider} (agent ${provider.agent_id})`);
|
|
136
|
+
return filtered;
|
|
131
137
|
}
|
|
132
138
|
async discoverAllForAgent(agentId) {
|
|
133
139
|
const providers = await this.providerRepo.find({
|
|
@@ -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) {
|
|
@@ -7,7 +7,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
};
|
|
8
8
|
var ProviderModelFetcherService_1;
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.ProviderModelFetcherService = exports.PROVIDER_CONFIGS = void 0;
|
|
10
|
+
exports.ProviderModelFetcherService = exports.PROVIDER_CONFIGS = exports.PROVIDER_NON_CHAT = exports.UNIVERSAL_NON_CHAT_RE = void 0;
|
|
11
|
+
exports.filterNonChatModels = filterNonChatModels;
|
|
11
12
|
const common_1 = require("@nestjs/common");
|
|
12
13
|
const ollama_1 = require("../common/constants/ollama");
|
|
13
14
|
const provider_base_url_1 = require("../routing/provider-base-url");
|
|
@@ -48,14 +49,34 @@ const parseOpenAI = createModelParser({
|
|
|
48
49
|
getId: (entry) => entry.id,
|
|
49
50
|
getDisplayName: (_entry, id) => id,
|
|
50
51
|
});
|
|
51
|
-
const
|
|
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
|
-
function
|
|
54
|
-
|
|
54
|
+
function parseOpenAIDeduped(body, provider) {
|
|
55
|
+
const filtered = parseOpenAI(body, provider).filter((m) => !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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
exports.UNIVERSAL_NON_CHAT_RE = /(?:embed|tts|whisper|dall-e|imagen|cogview|wanx|sambert|paraformer|text-embedding|speech-to|voice-|audio-turbo)/i;
|
|
65
|
+
exports.PROVIDER_NON_CHAT = {
|
|
66
|
+
openai: /(?:moderation|davinci|babbage|^text-|realtime|-transcribe|^sora|^gpt-3\.5-turbo-instruct|audio)/i,
|
|
67
|
+
'openai-subscription': /(?:moderation|davinci|babbage|^text-|realtime|-transcribe|^sora|audio)/i,
|
|
68
|
+
gemini: /(?:^aqs-|nano-banana)/i,
|
|
69
|
+
mistral: /(?:^mistral-ocr)/i,
|
|
70
|
+
};
|
|
71
|
+
function filterNonChatModels(models, configKey) {
|
|
72
|
+
const providerFilter = exports.PROVIDER_NON_CHAT[configKey];
|
|
73
|
+
return models.filter((m) => {
|
|
74
|
+
if (exports.UNIVERSAL_NON_CHAT_RE.test(m.id))
|
|
75
|
+
return false;
|
|
76
|
+
if (providerFilter && providerFilter.test(m.id))
|
|
77
|
+
return false;
|
|
78
|
+
return true;
|
|
79
|
+
});
|
|
59
80
|
}
|
|
60
81
|
function bearerHeaders(key) {
|
|
61
82
|
return { Authorization: `Bearer ${key}` };
|
|
@@ -166,7 +187,7 @@ exports.PROVIDER_CONFIGS = {
|
|
|
166
187
|
openai: {
|
|
167
188
|
endpoint: 'https://api.openai.com/v1/models',
|
|
168
189
|
buildHeaders: bearerHeaders,
|
|
169
|
-
parse:
|
|
190
|
+
parse: parseOpenAIDeduped,
|
|
170
191
|
},
|
|
171
192
|
'openai-subscription': {
|
|
172
193
|
endpoint: 'https://chatgpt.com/backend-api/codex/models?client_version=0.99.0',
|
|
@@ -186,7 +207,7 @@ exports.PROVIDER_CONFIGS = {
|
|
|
186
207
|
mistral: {
|
|
187
208
|
endpoint: 'https://api.mistral.ai/v1/models',
|
|
188
209
|
buildHeaders: bearerHeaders,
|
|
189
|
-
parse:
|
|
210
|
+
parse: parseOpenAI,
|
|
190
211
|
},
|
|
191
212
|
moonshot: {
|
|
192
213
|
endpoint: 'https://api.moonshot.ai/v1/models',
|
|
@@ -313,7 +334,7 @@ let ProviderModelFetcherService = ProviderModelFetcherService_1 = class Provider
|
|
|
313
334
|
return [];
|
|
314
335
|
}
|
|
315
336
|
const body = await res.json();
|
|
316
|
-
return config.parse(body, providerId);
|
|
337
|
+
return filterNonChatModels(config.parse(body, providerId), configKey);
|
|
317
338
|
}
|
|
318
339
|
catch (err) {
|
|
319
340
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
33
|
-
|
|
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);
|