manifest 5.24.0 → 5.24.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.
- package/README.md +1 -0
- package/dist/backend/analytics/controllers/agent-analytics.controller.js +7 -1
- package/dist/backend/analytics/controllers/agents.controller.js +16 -6
- package/dist/backend/common/utils/product-telemetry.js +5 -2
- package/dist/backend/common/utils/sql-dialect.js +14 -11
- package/dist/backend/database/migrations/1772960000000-PurgeNonCuratedModels.js +4 -0
- package/dist/backend/database/seed-models.js +25 -3
- package/dist/backend/model-prices/model-name-normalizer.js +22 -0
- package/dist/backend/notifications/emails/threshold-alert.js +3 -2
- package/dist/backend/otlp/guards/otlp-auth.guard.js +37 -1
- package/dist/backend/otlp/otlp.controller.js +0 -3
- package/dist/backend/routing/proxy/google-adapter.js +42 -1
- package/dist/backend/routing/proxy/proxy.service.js +12 -13
- package/dist/backend/routing/resolve.controller.js +8 -2
- package/dist/backend/routing/routing.controller.js +2 -1
- package/dist/backend/routing/routing.service.js +44 -0
- package/dist/index.js +13 -13
- package/package.json +1 -1
- package/public/assets/{Account-BAroz-2n.js → Account-D79xeKyT.js} +1 -1
- package/public/assets/{Limits-_v1HGbea.js → Limits-68Jol4KI.js} +1 -1
- package/public/assets/{Login-C6lI_KqN.js → Login-q9JoAR_x.js} +1 -1
- package/public/assets/{MessageLog-BfxZUc6f.js → MessageLog-CUR9du7l.js} +1 -1
- package/public/assets/{ModelPrices-DlrK7bqu.js → ModelPrices-Dfn6eK1-.js} +1 -1
- package/public/assets/Overview-QSa9JMZx.js +1 -0
- package/public/assets/{Register-B-nLlx_2.js → Register-DH-OYkxl.js} +1 -1
- package/public/assets/{ResetPassword-CJEPkh6G.js → ResetPassword-Bd22sqer.js} +1 -1
- package/public/assets/{Routing-D1z4fqN4.js → Routing-C-8PKTEa.js} +1 -1
- package/public/assets/{Settings-DBVVmq_T.js → Settings-B7NcLutw.js} +1 -1
- package/public/assets/{SocialButtons-BQ4JTq-l.js → SocialButtons-BmEEEVZg.js} +1 -1
- package/public/assets/index-DBqZDW9Z.css +1 -0
- package/public/assets/{index-BhcU06cr.js → index-DKNLgS0h.js} +2 -2
- package/public/assets/{overview-_flXQc4-.js → overview-B6JfuXXv.js} +1 -1
- package/public/index.html +2 -2
- package/public/assets/Overview-CfezCKlq.js +0 -1
- package/public/assets/index-D4x6Xoo9.css +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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 },
|
|
@@ -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:
|
|
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,
|
|
@@ -18,13 +18,12 @@ function timestampType() {
|
|
|
18
18
|
return process.env['MANIFEST_MODE'] === 'local' ? 'datetime' : 'timestamp';
|
|
19
19
|
}
|
|
20
20
|
function timestampDefault() {
|
|
21
|
-
return process.env['MANIFEST_MODE'] === 'local'
|
|
22
|
-
? () => 'CURRENT_TIMESTAMP'
|
|
23
|
-
: () => 'NOW()';
|
|
21
|
+
return process.env['MANIFEST_MODE'] === 'local' ? () => 'CURRENT_TIMESTAMP' : () => 'NOW()';
|
|
24
22
|
}
|
|
25
23
|
function computeCutoff(interval) {
|
|
26
24
|
const ms = intervalToMs(interval);
|
|
27
|
-
|
|
25
|
+
const cutoff = new Date(Date.now() - ms);
|
|
26
|
+
return formatLocalIso(cutoff);
|
|
28
27
|
}
|
|
29
28
|
function intervalToMs(interval) {
|
|
30
29
|
const match = interval.match(/^(\d+)\s+(hour|hours|day|days)$/);
|
|
@@ -37,12 +36,13 @@ function intervalToMs(interval) {
|
|
|
37
36
|
return unit.startsWith('hour') ? n * 3600_000 : n * 86400_000;
|
|
38
37
|
}
|
|
39
38
|
function sqlNow() {
|
|
40
|
-
return new Date()
|
|
39
|
+
return formatLocalIso(new Date());
|
|
41
40
|
}
|
|
42
41
|
function sqlHourBucket(col, dialect) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
if (dialect === 'sqlite') {
|
|
43
|
+
return `strftime('%Y-%m-%dT%H:00:00', ${col})`;
|
|
44
|
+
}
|
|
45
|
+
return `to_char(date_trunc('hour', ${col}), 'YYYY-MM-DD"T"HH24:MI:SS')`;
|
|
46
46
|
}
|
|
47
47
|
function sqlDateBucket(col, dialect) {
|
|
48
48
|
return dialect === 'sqlite'
|
|
@@ -61,8 +61,11 @@ function portableSql(sql, dialect) {
|
|
|
61
61
|
return sql.replace(/\$\d+/g, '?');
|
|
62
62
|
}
|
|
63
63
|
function sqlCastInterval(paramName, dialect) {
|
|
64
|
-
return dialect === 'sqlite'
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
return dialect === 'sqlite' ? `:${paramName}` : `CAST(:${paramName} AS interval)`;
|
|
65
|
+
}
|
|
66
|
+
function formatLocalIso(d) {
|
|
67
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
68
|
+
return (`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +
|
|
69
|
+
`T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`);
|
|
67
70
|
}
|
|
68
71
|
//# sourceMappingURL=sql-dialect.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.
|
|
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.
|
|
104
|
-
0.
|
|
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
|
|
@@ -17,9 +17,10 @@ function formatTimestamp(raw) {
|
|
|
17
17
|
return `${monthName} ${dayNum}, ${timePart}`;
|
|
18
18
|
}
|
|
19
19
|
function formatValue(value, metric) {
|
|
20
|
+
const num = Number(value);
|
|
20
21
|
if (metric === 'cost')
|
|
21
|
-
return `$${
|
|
22
|
-
return
|
|
22
|
+
return `$${num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
23
|
+
return num.toLocaleString(undefined, { maximumFractionDigits: 0 });
|
|
23
24
|
}
|
|
24
25
|
function ThresholdAlertEmail(props) {
|
|
25
26
|
const { agentName, metricType, threshold, actualValue, period, timestamp, agentUrl, logoUrl = 'https://app.manifest.build/manifest-logo.png', alertType = 'hard', periodResetDate, } = props;
|
|
@@ -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
|
|
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
|
});
|
|
@@ -4,6 +4,47 @@ exports.toGoogleRequest = toGoogleRequest;
|
|
|
4
4
|
exports.fromGoogleResponse = fromGoogleResponse;
|
|
5
5
|
exports.transformGoogleStreamChunk = transformGoogleStreamChunk;
|
|
6
6
|
const crypto_1 = require("crypto");
|
|
7
|
+
const UNSUPPORTED_SCHEMA_FIELDS = new Set([
|
|
8
|
+
'patternProperties',
|
|
9
|
+
'additionalProperties',
|
|
10
|
+
'$schema',
|
|
11
|
+
'$id',
|
|
12
|
+
'$ref',
|
|
13
|
+
'$defs',
|
|
14
|
+
'definitions',
|
|
15
|
+
'allOf',
|
|
16
|
+
'anyOf',
|
|
17
|
+
'oneOf',
|
|
18
|
+
'not',
|
|
19
|
+
'if',
|
|
20
|
+
'then',
|
|
21
|
+
'else',
|
|
22
|
+
'dependentSchemas',
|
|
23
|
+
'dependentRequired',
|
|
24
|
+
'unevaluatedProperties',
|
|
25
|
+
'unevaluatedItems',
|
|
26
|
+
'contentMediaType',
|
|
27
|
+
'contentEncoding',
|
|
28
|
+
'examples',
|
|
29
|
+
'default',
|
|
30
|
+
'const',
|
|
31
|
+
'title',
|
|
32
|
+
]);
|
|
33
|
+
function sanitizeSchema(schema, isPropertiesMap = false) {
|
|
34
|
+
if (schema === null || schema === undefined || typeof schema !== 'object') {
|
|
35
|
+
return schema;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(schema)) {
|
|
38
|
+
return schema.map((item) => sanitizeSchema(item));
|
|
39
|
+
}
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
42
|
+
if (!isPropertiesMap && UNSUPPORTED_SCHEMA_FIELDS.has(key))
|
|
43
|
+
continue;
|
|
44
|
+
result[key] = sanitizeSchema(value, key === 'properties');
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
7
48
|
function mapRole(role) {
|
|
8
49
|
if (role === 'assistant')
|
|
9
50
|
return 'model';
|
|
@@ -64,7 +105,7 @@ function convertTools(tools) {
|
|
|
64
105
|
return {
|
|
65
106
|
name: fn.name,
|
|
66
107
|
description: fn.description,
|
|
67
|
-
parameters: fn.parameters,
|
|
108
|
+
parameters: fn.parameters ? sanitizeSchema(fn.parameters) : undefined,
|
|
68
109
|
};
|
|
69
110
|
})
|
|
70
111
|
.filter(Boolean);
|
|
@@ -173,11 +173,11 @@ let ProxyService = ProxyService_1 = class ProxyService {
|
|
|
173
173
|
if (!exceeded)
|
|
174
174
|
return;
|
|
175
175
|
const fmt = exceeded.metricType === 'cost'
|
|
176
|
-
? `$${exceeded.actual.
|
|
177
|
-
: exceeded.actual.toLocaleString();
|
|
176
|
+
? `$${Number(exceeded.actual).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
|
177
|
+
: Number(exceeded.actual).toLocaleString(undefined, { maximumFractionDigits: 0 });
|
|
178
178
|
const threshFmt = exceeded.metricType === 'cost'
|
|
179
|
-
? `$${exceeded.threshold.
|
|
180
|
-
: exceeded.threshold.toLocaleString();
|
|
179
|
+
? `$${Number(exceeded.threshold).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
|
180
|
+
: Number(exceeded.threshold).toLocaleString(undefined, { maximumFractionDigits: 0 });
|
|
181
181
|
throw new common_1.HttpException({
|
|
182
182
|
error: {
|
|
183
183
|
message: `Limit exceeded: ${exceeded.metricType} usage (${fmt}) exceeds ${threshFmt} per ${exceeded.period}`,
|
|
@@ -192,16 +192,15 @@ let ProxyService = ProxyService_1 = class ProxyService {
|
|
|
192
192
|
.slice(-SCORING_RECENT_MESSAGES);
|
|
193
193
|
}
|
|
194
194
|
detectHeartbeat(scoringMessages) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return false;
|
|
198
|
-
if (typeof m.content === 'string')
|
|
199
|
-
return m.content.includes('HEARTBEAT_OK');
|
|
200
|
-
if (Array.isArray(m.content)) {
|
|
201
|
-
return m.content.some((p) => p.type === 'text' && typeof p.text === 'string' && p.text.includes('HEARTBEAT_OK'));
|
|
202
|
-
}
|
|
195
|
+
const lastUser = [...scoringMessages].reverse().find((m) => m.role === 'user');
|
|
196
|
+
if (!lastUser)
|
|
203
197
|
return false;
|
|
204
|
-
|
|
198
|
+
if (typeof lastUser.content === 'string')
|
|
199
|
+
return lastUser.content.includes('HEARTBEAT_OK');
|
|
200
|
+
if (Array.isArray(lastUser.content)) {
|
|
201
|
+
return lastUser.content.some((p) => p.type === 'text' && typeof p.text === 'string' && p.text.includes('HEARTBEAT_OK'));
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
205
204
|
}
|
|
206
205
|
async forwardToProvider(provider, apiKey, model, body, stream, sessionKey, signal, authType) {
|
|
207
206
|
const extraHeaders = {};
|
|
@@ -21,6 +21,7 @@ const otlp_auth_guard_1 = require("../otlp/guards/otlp-auth.guard");
|
|
|
21
21
|
const resolve_service_1 = require("./resolve.service");
|
|
22
22
|
const routing_service_1 = require("./routing.service");
|
|
23
23
|
const resolve_request_dto_1 = require("./dto/resolve-request.dto");
|
|
24
|
+
const product_telemetry_1 = require("../common/utils/product-telemetry");
|
|
24
25
|
class SubscriptionProviderItem {
|
|
25
26
|
provider;
|
|
26
27
|
}
|
|
@@ -55,8 +56,13 @@ let ResolveController = class ResolveController {
|
|
|
55
56
|
const { agentId, userId } = req.ingestionContext;
|
|
56
57
|
let registered = 0;
|
|
57
58
|
for (const item of body.providers) {
|
|
58
|
-
await this.routingService.
|
|
59
|
-
|
|
59
|
+
const { isNew } = await this.routingService.registerSubscriptionProvider(agentId, userId, item.provider);
|
|
60
|
+
if (isNew) {
|
|
61
|
+
(0, product_telemetry_1.trackCloudEvent)('routing_provider_connected', userId, {
|
|
62
|
+
provider: `${item.provider} (Subscription)`,
|
|
63
|
+
});
|
|
64
|
+
registered++;
|
|
65
|
+
}
|
|
60
66
|
}
|
|
61
67
|
return { registered };
|
|
62
68
|
}
|
|
@@ -62,8 +62,9 @@ let RoutingController = class RoutingController {
|
|
|
62
62
|
}
|
|
63
63
|
const { provider: result, isNew } = await this.routingService.upsertProvider(agent.id, user.id, body.provider, body.apiKey, body.authType);
|
|
64
64
|
if (isNew) {
|
|
65
|
+
const providerLabel = body.authType === 'subscription' ? `${body.provider} (Subscription)` : body.provider;
|
|
65
66
|
(0, product_telemetry_1.trackCloudEvent)('routing_provider_connected', user.id, {
|
|
66
|
-
provider:
|
|
67
|
+
provider: providerLabel,
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
return {
|
|
@@ -69,6 +69,9 @@ let RoutingService = RoutingService_1 = class RoutingService {
|
|
|
69
69
|
existing.is_active = true;
|
|
70
70
|
existing.updated_at = new Date().toISOString();
|
|
71
71
|
await this.providerRepo.save(existing);
|
|
72
|
+
if (effectiveAuthType === 'api_key') {
|
|
73
|
+
await this.deactivateSubscriptionForProvider(agentId, provider);
|
|
74
|
+
}
|
|
72
75
|
await this.autoAssign.recalculate(agentId);
|
|
73
76
|
this.routingCache.invalidateAgent(agentId);
|
|
74
77
|
return { provider: existing, isNew: false };
|
|
@@ -86,10 +89,51 @@ let RoutingService = RoutingService_1 = class RoutingService {
|
|
|
86
89
|
updated_at: new Date().toISOString(),
|
|
87
90
|
});
|
|
88
91
|
await this.providerRepo.insert(record);
|
|
92
|
+
if (effectiveAuthType === 'api_key') {
|
|
93
|
+
await this.deactivateSubscriptionForProvider(agentId, provider);
|
|
94
|
+
}
|
|
89
95
|
await this.autoAssign.recalculate(agentId);
|
|
90
96
|
this.routingCache.invalidateAgent(agentId);
|
|
91
97
|
return { provider: record, isNew: true };
|
|
92
98
|
}
|
|
99
|
+
async registerSubscriptionProvider(agentId, userId, provider) {
|
|
100
|
+
const existing = await this.providerRepo.findOne({
|
|
101
|
+
where: { agent_id: agentId, provider, auth_type: 'subscription' },
|
|
102
|
+
});
|
|
103
|
+
if (existing)
|
|
104
|
+
return { isNew: false };
|
|
105
|
+
const hasApiKey = await this.providerRepo.findOne({
|
|
106
|
+
where: { agent_id: agentId, provider, auth_type: 'api_key', is_active: true },
|
|
107
|
+
});
|
|
108
|
+
if (hasApiKey)
|
|
109
|
+
return { isNew: false };
|
|
110
|
+
const record = Object.assign(new user_provider_entity_1.UserProvider(), {
|
|
111
|
+
id: (0, crypto_1.randomUUID)(),
|
|
112
|
+
user_id: userId,
|
|
113
|
+
agent_id: agentId,
|
|
114
|
+
provider,
|
|
115
|
+
auth_type: 'subscription',
|
|
116
|
+
api_key_encrypted: null,
|
|
117
|
+
key_prefix: null,
|
|
118
|
+
is_active: true,
|
|
119
|
+
connected_at: new Date().toISOString(),
|
|
120
|
+
updated_at: new Date().toISOString(),
|
|
121
|
+
});
|
|
122
|
+
await this.providerRepo.insert(record);
|
|
123
|
+
await this.autoAssign.recalculate(agentId);
|
|
124
|
+
this.routingCache.invalidateAgent(agentId);
|
|
125
|
+
return { isNew: true };
|
|
126
|
+
}
|
|
127
|
+
async deactivateSubscriptionForProvider(agentId, provider) {
|
|
128
|
+
const sub = await this.providerRepo.findOne({
|
|
129
|
+
where: { agent_id: agentId, provider, auth_type: 'subscription', is_active: true },
|
|
130
|
+
});
|
|
131
|
+
if (!sub)
|
|
132
|
+
return;
|
|
133
|
+
sub.is_active = false;
|
|
134
|
+
sub.updated_at = new Date().toISOString();
|
|
135
|
+
await this.providerRepo.save(sub);
|
|
136
|
+
}
|
|
93
137
|
async removeProvider(agentId, provider, authType) {
|
|
94
138
|
const where = { agent_id: agentId, provider };
|
|
95
139
|
if (authType)
|