titan-agent 5.3.0 → 5.3.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 +5 -5
- package/dist/agent/agent.js +33 -4
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/subAgent.js +16 -1
- package/dist/agent/subAgent.js.map +1 -1
- package/dist/agent/toolRunner.js +17 -0
- package/dist/agent/toolRunner.js.map +1 -1
- package/dist/config/schema.js +10 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/eval/record.js +21 -2
- package/dist/eval/record.js.map +1 -1
- package/dist/gateway/metrics.js +26 -3
- package/dist/gateway/metrics.js.map +1 -1
- package/dist/gateway/server.js +58 -6
- package/dist/gateway/server.js.map +1 -1
- package/dist/organism/drives.js +47 -11
- package/dist/organism/drives.js.map +1 -1
- package/dist/organism/pressure.js +16 -0
- package/dist/organism/pressure.js.map +1 -1
- package/dist/skills/builtin/fb_autopilot.js +16 -1
- package/dist/skills/builtin/fb_autopilot.js.map +1 -1
- package/dist/telemetry/activityLog.js +158 -0
- package/dist/telemetry/activityLog.js.map +1 -0
- package/dist/utils/constants.js +3 -1
- package/dist/utils/constants.js.map +1 -1
- package/package.json +1 -1
- package/ui/dist/assets/{AuditPanel-C31LRHZX.js → AuditPanel-CM6Wg9hO.js} +1 -1
- package/ui/dist/assets/{AutonomyPanel-CxQU72ZY.js → AutonomyPanel-CESx3ANg.js} +1 -1
- package/ui/dist/assets/{AutopilotPanel-D4FnBwJm.js → AutopilotPanel-DtEet1hJ.js} +1 -1
- package/ui/dist/assets/{AutoresearchPanel-BYHXZ9AO.js → AutoresearchPanel-DR47NqT5.js} +1 -1
- package/ui/dist/assets/{BackupPanel-C4CQKf2P.js → BackupPanel-BGP8p3l3.js} +1 -1
- package/ui/dist/assets/{BrowserPanel-C-OFYyLm.js → BrowserPanel-C15x9bLn.js} +1 -1
- package/ui/dist/assets/{CPAgents-CvkZDm_3.js → CPAgents-DYUtPzSq.js} +1 -1
- package/ui/dist/assets/{CPDashboard-JmBLBbj7.js → CPDashboard-Bf0-SyCh.js} +1 -1
- package/ui/dist/assets/{CPFiles-BDToRw0a.js → CPFiles-CxgxjQcO.js} +1 -1
- package/ui/dist/assets/{CPGoals-Dh9qJNWa.js → CPGoals-BsmCMTvT.js} +1 -1
- package/ui/dist/assets/{CPInbox-B6iaIbNG.js → CPInbox-tMSbmQ9H.js} +1 -1
- package/ui/dist/assets/{CPSocial-CsFrwZRC.js → CPSocial-nb-j7sOE.js} +1 -1
- package/ui/dist/assets/{ChannelsPanel-D-S4ktFn.js → ChannelsPanel-DP5C2OKd.js} +1 -1
- package/ui/dist/assets/{CheckpointsPanel-D-sP9ZuS.js → CheckpointsPanel-DlranVLZ.js} +1 -1
- package/ui/dist/assets/{CommandPostHub-BhlNyeDH.js → CommandPostHub-BgxIa4Ev.js} +3 -3
- package/ui/dist/assets/{CronPanel-Bf3rV7N2.js → CronPanel-LoT5yKwJ.js} +1 -1
- package/ui/dist/assets/{DaemonPanel-GGBWjTG2.js → DaemonPanel-DBGMqaE_.js} +1 -1
- package/ui/dist/assets/{DataTable-D2Px4o6f.js → DataTable-B2Ma8hfi.js} +1 -1
- package/ui/dist/assets/{EmptyState-DH6-Jy6A.js → EmptyState-CcKyk5Yn.js} +1 -1
- package/ui/dist/assets/EvalHarnessPanel-BqtMc1ZM.js +2 -0
- package/ui/dist/assets/{EvalPanel-CdjxzHlJ.js → EvalPanel-Bc33j0pN.js} +1 -1
- package/ui/dist/assets/{FilesPanel-Dz8TFydL.js → FilesPanel-3QKvrWPo.js} +1 -1
- package/ui/dist/assets/{FleetPanel-CWwWWTD4.js → FleetPanel-CSsXuQYj.js} +1 -1
- package/ui/dist/assets/{HomelabPanel-C7VBV7AC.js → HomelabPanel-DhrjTX9m.js} +1 -1
- package/ui/dist/assets/{InfraView-B1TgXARJ.js → InfraView-CR6HyrL6.js} +2 -2
- package/ui/dist/assets/{InlineEditableField-DOJNOL8m.js → InlineEditableField-CnvF-yFR.js} +1 -1
- package/ui/dist/assets/{Input-BgyHgQ3D.js → Input-GTHp2Rkr.js} +1 -1
- package/ui/dist/assets/{IntegrationsPanel-O26b4nhv.js → IntegrationsPanel-CymCRE3T.js} +1 -1
- package/ui/dist/assets/{IntelligenceView-DUhTQ8f_.js → IntelligenceView-C1IHxJRC.js} +2 -2
- package/ui/dist/assets/{LearningPanel-DX5S9ovg.js → LearningPanel-DOCES3lH.js} +1 -1
- package/ui/dist/assets/{LogsPanel-DdeTnATQ.js → LogsPanel-BLnAqEaZ.js} +1 -1
- package/ui/dist/assets/{McpPanel-BpXWrP1a.js → McpPanel-ChUzmr3z.js} +1 -1
- package/ui/dist/assets/{MemoryGraphPanel-CNkZmTUy.js → MemoryGraphPanel-Bzvjmzvk.js} +1 -1
- package/ui/dist/assets/{MemoryWikiPanel-o4L8Df-n.js → MemoryWikiPanel-Dwk3Aqwd.js} +1 -1
- package/ui/dist/assets/{MeshPanel-DMBQJFCC.js → MeshPanel-C3LJSlht.js} +1 -1
- package/ui/dist/assets/{NvidiaPanel-C8P-tJFG.js → NvidiaPanel-CeZK_-CV.js} +1 -1
- package/ui/dist/assets/{OrganismPanel-CcfHDWDk.js → OrganismPanel-BB6YOiQV.js} +1 -1
- package/ui/dist/assets/{OverviewPanel-BSotI1Zv.js → OverviewPanel-BmtBhQnv.js} +1 -1
- package/ui/dist/assets/{PageHeader-DPJuAgJk.js → PageHeader-BimceqQo.js} +1 -1
- package/ui/dist/assets/{PaperclipPanel-aXoXUjo6.js → PaperclipPanel-C-brgwA3.js} +1 -1
- package/ui/dist/assets/{PersonasPanel-DdPZxz2C.js → PersonasPanel-L1j78p6H.js} +1 -1
- package/ui/dist/assets/{RecipesPanel-D7qffXQN.js → RecipesPanel-34lCfynJ.js} +1 -1
- package/ui/dist/assets/{SecurityPanel-BDRK5el7.js → SecurityPanel-CBTPWLj6.js} +1 -1
- package/ui/dist/assets/{SelfImprovePanel-oYiMwFnA.js → SelfImprovePanel-BrPbFHhG.js} +1 -1
- package/ui/dist/assets/{SelfProposalsPanel-DOpNU_rr.js → SelfProposalsPanel-lNmiDThB.js} +1 -1
- package/ui/dist/assets/{SessionsPanel-eRbM3D9P.js → SessionsPanel-DAEYIn83.js} +1 -1
- package/ui/dist/assets/{SessionsTab-Jq3UKQCI.js → SessionsTab-JQbltWww.js} +1 -1
- package/ui/dist/assets/{SettingsPanel-DBIvKUYY.js → SettingsPanel-CzRROAYQ.js} +1 -1
- package/ui/dist/assets/{SettingsView-yfSY4OLt.js → SettingsView-CN7ii2uw.js} +2 -2
- package/ui/dist/assets/{SkeletonLoader-D1d-Gyyg.js → SkeletonLoader-atQtpcF5.js} +1 -1
- package/ui/dist/assets/{SkillsPanel-bubl9nag.js → SkillsPanel-DlFs2ih7.js} +1 -1
- package/ui/dist/assets/{SomaView-D3aFL8Tw.js → SomaView-Ba642Oqb.js} +1 -1
- package/ui/dist/assets/{StatCard-CEVFsz7t.js → StatCard-DciE_Iqc.js} +1 -1
- package/ui/dist/assets/{StatusBadge-DxeA9LNd.js → StatusBadge-BtfSPoW2.js} +1 -1
- package/ui/dist/assets/{TeamsPanel-D6IJJIR_.js → TeamsPanel-DKQ5z2Qe.js} +1 -1
- package/ui/dist/assets/{TelemetryPanel-SMPebdjQ.js → TelemetryPanel-B6KAc55Q.js} +1 -1
- package/ui/dist/assets/{TitanCanvas-BQU1yxqf.js → TitanCanvas-C-s0A-lv.js} +3 -3
- package/ui/dist/assets/{ToolsView-DgP4uRPr.js → ToolsView-Dei0KMP0.js} +2 -2
- package/ui/dist/assets/{Tooltip-CNPQr7IO.js → Tooltip-70UK0E2I.js} +1 -1
- package/ui/dist/assets/{TraceViewer-BbISy_ET.js → TraceViewer-BniolyBx.js} +1 -1
- package/ui/dist/assets/{TrainingPanel-BdCHcv6t.js → TrainingPanel-Bz4CTPGW.js} +1 -1
- package/ui/dist/assets/{VoiceOverlay-l6yoasVz.js → VoiceOverlay-CmNCrLcd.js} +1 -1
- package/ui/dist/assets/{VramPanel-XLhmen92.js → VramPanel-Xh_OtRDR.js} +1 -1
- package/ui/dist/assets/{WatchView-Bt-lNNWJ.js → WatchView-C-sGFpVy.js} +1 -1
- package/ui/dist/assets/{WorkTab-IG-F6Qll.js → WorkTab-BjLNmgIK.js} +1 -1
- package/ui/dist/assets/{WorkflowsPanel-DsMpnwLK.js → WorkflowsPanel-CvgQU1xI.js} +1 -1
- package/ui/dist/assets/{arrow-left-C_H9Z2Tm.js → arrow-left-DwqHtJiU.js} +1 -1
- package/ui/dist/assets/{chart-column-rR6tb72l.js → chart-column-BtNO6sRy.js} +1 -1
- package/ui/dist/assets/{circle-check-big-B1hMwau0.js → circle-check-big-DZRE_MbN.js} +1 -1
- package/ui/dist/assets/{dollar-sign-DhYwsTnR.js → dollar-sign-aVG3a5eL.js} +1 -1
- package/ui/dist/assets/{download-UDDcAlZC.js → download-BxiWJU4G.js} +1 -1
- package/ui/dist/assets/{eye-off-Cx0M_VQb.js → eye-off-CkgfFYhm.js} +1 -1
- package/ui/dist/assets/{funnel-B7YvM1ei.js → funnel-PkLdxKyC.js} +1 -1
- package/ui/dist/assets/{git-branch-BhTBN3J6.js → git-branch-BM-Gw95X.js} +1 -1
- package/ui/dist/assets/{index-D7Clon2u.js → index-CahJbWSR.js} +2 -2
- package/ui/dist/assets/{layers-B6jDzitD.js → layers-BuGf4FIJ.js} +1 -1
- package/ui/dist/assets/{legacy-av079XKu.js → legacy-CR6o4t-y.js} +1 -1
- package/ui/dist/assets/{lightbulb-DRuQ3Chf.js → lightbulb-n8gc_XAL.js} +1 -1
- package/ui/dist/assets/{pause-DqkRWPB_.js → pause-DCV52koX.js} +1 -1
- package/ui/dist/assets/{play-hUyR3CVS.js → play-CcJ9BnCh.js} +1 -1
- package/ui/dist/assets/{plug-CvpyjJt_.js → plug-CfWBXfCl.js} +1 -1
- package/ui/dist/assets/{proxy-Cc5bR828.js → proxy-CzZDfLmm.js} +1 -1
- package/ui/dist/assets/{square-CdiC0J8Z.js → square-DJpUhlxi.js} +1 -1
- package/ui/dist/assets/{target-DemL8_0v.js → target-DWcmM_9m.js} +1 -1
- package/ui/dist/assets/{toggle-right-Dsk892k5.js → toggle-right-YusFQ69L.js} +1 -1
- package/ui/dist/assets/{trash-2-Byj4OvKB.js → trash-2-CK7yQ55V.js} +1 -1
- package/ui/dist/assets/{trending-up-Dh_CffGX.js → trending-up-DGjFyubC.js} +1 -1
- package/ui/dist/assets/{trophy-DDr2AePx.js → trophy-uQv_NgDB.js} +1 -1
- package/ui/dist/index.html +1 -1
- package/ui/dist/assets/EvalHarnessPanel-Cz9dRg61.js +0 -2
package/dist/gateway/metrics.js
CHANGED
|
@@ -193,12 +193,29 @@ const titanEvalCasesTotal = new Counter(
|
|
|
193
193
|
"titan_eval_cases_total",
|
|
194
194
|
"Total eval cases executed, by suite and outcome"
|
|
195
195
|
);
|
|
196
|
+
const titanEvalTimeoutTotal = new Counter(
|
|
197
|
+
"titan_eval_timeout_total",
|
|
198
|
+
"Total eval suite invocations that exceeded their timeout"
|
|
199
|
+
);
|
|
200
|
+
const titanEvalErrorTotal = new Counter(
|
|
201
|
+
"titan_eval_error_total",
|
|
202
|
+
"Total eval suite invocations that threw an unhandled exception"
|
|
203
|
+
);
|
|
196
204
|
function recordEvalSuiteResult(suite, passed, total) {
|
|
197
|
-
|
|
198
|
-
|
|
205
|
+
if (total > 0) {
|
|
206
|
+
const rate = Math.round(passed / total * 100);
|
|
207
|
+
titanEvalPassRate.set(rate, { suite });
|
|
208
|
+
} else {
|
|
209
|
+
}
|
|
199
210
|
titanEvalCasesTotal.increment({ suite, outcome: "passed" }, passed);
|
|
200
211
|
titanEvalCasesTotal.increment({ suite, outcome: "failed" }, Math.max(0, total - passed));
|
|
201
212
|
}
|
|
213
|
+
function recordEvalTimeout(suite) {
|
|
214
|
+
titanEvalTimeoutTotal.increment({ suite });
|
|
215
|
+
}
|
|
216
|
+
function recordEvalError(suite, errorClass = "unknown") {
|
|
217
|
+
titanEvalErrorTotal.increment({ suite, errorClass });
|
|
218
|
+
}
|
|
202
219
|
const allMetrics = [
|
|
203
220
|
titanRequestsTotal,
|
|
204
221
|
titanRequestDuration,
|
|
@@ -208,7 +225,9 @@ const allMetrics = [
|
|
|
208
225
|
titanToolCallsTotal,
|
|
209
226
|
titanModelRequestsTotal,
|
|
210
227
|
titanEvalPassRate,
|
|
211
|
-
titanEvalCasesTotal
|
|
228
|
+
titanEvalCasesTotal,
|
|
229
|
+
titanEvalTimeoutTotal,
|
|
230
|
+
titanEvalErrorTotal
|
|
212
231
|
];
|
|
213
232
|
function serializePrometheus() {
|
|
214
233
|
return allMetrics.map((m) => m.serialize()).join("\n\n") + "\n";
|
|
@@ -242,12 +261,16 @@ export {
|
|
|
242
261
|
Gauge,
|
|
243
262
|
Histogram,
|
|
244
263
|
getMetricsSummary,
|
|
264
|
+
recordEvalError,
|
|
245
265
|
recordEvalSuiteResult,
|
|
266
|
+
recordEvalTimeout,
|
|
246
267
|
serializePrometheus,
|
|
247
268
|
titanActiveSessions,
|
|
248
269
|
titanErrorsTotal,
|
|
249
270
|
titanEvalCasesTotal,
|
|
271
|
+
titanEvalErrorTotal,
|
|
250
272
|
titanEvalPassRate,
|
|
273
|
+
titanEvalTimeoutTotal,
|
|
251
274
|
titanModelRequestsTotal,
|
|
252
275
|
titanRequestDuration,
|
|
253
276
|
titanRequestsTotal,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/gateway/metrics.ts"],"sourcesContent":["/**\n * TITAN — Prometheus Metrics Engine\n * Zero-dependency metrics collection with Prometheus text exposition format.\n */\n\n// ── Metric Types ─────────────────────────────────────────────────────\n\ntype Labels = Record<string, string>;\n\nfunction labelsKey(labels?: Labels): string {\n if (!labels || Object.keys(labels).length === 0) return '';\n return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}=\"${v}\"`).join(',');\n}\n\nexport class Counter {\n readonly name: string;\n readonly help: string;\n private values = new Map<string, number>();\n\n constructor(name: string, help: string) {\n this.name = name;\n this.help = help;\n }\n\n increment(labels?: Labels, amount = 1): void {\n const key = labelsKey(labels);\n this.values.set(key, (this.values.get(key) || 0) + amount);\n }\n\n get(labels?: Labels): number {\n return this.values.get(labelsKey(labels)) || 0;\n }\n\n serialize(): string {\n const lines: string[] = [\n `# HELP ${this.name} ${this.help}`,\n `# TYPE ${this.name} counter`,\n ];\n if (this.values.size === 0) {\n lines.push(`${this.name} 0`);\n } else {\n for (const [key, val] of this.values) {\n const lbl = key ? `{${key}}` : '';\n lines.push(`${this.name}${lbl} ${val}`);\n }\n }\n return lines.join('\\n');\n }\n\n /** Get all label combinations and their values */\n getAll(): Array<{ labels: Labels; value: number }> {\n const result: Array<{ labels: Labels; value: number }> = [];\n for (const [key, value] of this.values) {\n const labels: Labels = {};\n if (key) {\n for (const pair of key.split(',')) {\n const [k, v] = pair.split('=');\n labels[k] = v.replace(/\"/g, '');\n }\n }\n result.push({ labels, value });\n }\n return result;\n }\n}\n\nconst DEFAULT_BUCKETS = [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\nexport class Histogram {\n readonly name: string;\n readonly help: string;\n readonly buckets: number[];\n // key → { bucketCounts, sum, count }\n private data = new Map<string, { bucketCounts: number[]; sum: number; count: number }>();\n\n constructor(name: string, help: string, buckets = DEFAULT_BUCKETS) {\n this.name = name;\n this.help = help;\n this.buckets = [...buckets].sort((a, b) => a - b);\n }\n\n observe(value: number, labels?: Labels): void {\n const key = labelsKey(labels);\n let entry = this.data.get(key);\n if (!entry) {\n entry = { bucketCounts: new Array(this.buckets.length).fill(0), sum: 0, count: 0 };\n this.data.set(key, entry);\n }\n entry.sum += value;\n entry.count++;\n for (let i = 0; i < this.buckets.length; i++) {\n if (value <= this.buckets[i]) {\n entry.bucketCounts[i]++;\n break;\n }\n }\n }\n\n get(labels?: Labels): { sum: number; count: number; buckets: Record<string, number> } {\n const key = labelsKey(labels);\n const entry = this.data.get(key);\n if (!entry) return { sum: 0, count: 0, buckets: {} };\n const buckets: Record<string, number> = {};\n let cumulative = 0;\n for (let i = 0; i < this.buckets.length; i++) {\n cumulative += entry.bucketCounts[i];\n buckets[String(this.buckets[i])] = cumulative;\n }\n buckets['+Inf'] = entry.count;\n return { sum: entry.sum, count: entry.count, buckets };\n }\n\n serialize(): string {\n const lines: string[] = [\n `# HELP ${this.name} ${this.help}`,\n `# TYPE ${this.name} histogram`,\n ];\n if (this.data.size === 0) {\n // Emit empty histogram\n for (const b of this.buckets) {\n lines.push(`${this.name}_bucket{le=\"${b}\"} 0`);\n }\n lines.push(`${this.name}_bucket{le=\"+Inf\"} 0`);\n lines.push(`${this.name}_sum 0`);\n lines.push(`${this.name}_count 0`);\n } else {\n for (const [key, entry] of this.data) {\n const baseLabels = key ? `${key},` : '';\n let cumulative = 0;\n for (let i = 0; i < this.buckets.length; i++) {\n cumulative += entry.bucketCounts[i];\n lines.push(`${this.name}_bucket{${baseLabels}le=\"${this.buckets[i]}\"} ${cumulative}`);\n }\n lines.push(`${this.name}_bucket{${baseLabels}le=\"+Inf\"} ${entry.count}`);\n const lbl = key ? `{${key}}` : '';\n lines.push(`${this.name}_sum${lbl} ${entry.sum}`);\n lines.push(`${this.name}_count${lbl} ${entry.count}`);\n }\n }\n return lines.join('\\n');\n }\n}\n\nexport class Gauge {\n readonly name: string;\n readonly help: string;\n private values = new Map<string, number>();\n\n constructor(name: string, help: string) {\n this.name = name;\n this.help = help;\n }\n\n set(value: number, labels?: Labels): void {\n this.values.set(labelsKey(labels), value);\n }\n\n inc(labels?: Labels, amount = 1): void {\n const key = labelsKey(labels);\n this.values.set(key, (this.values.get(key) || 0) + amount);\n }\n\n dec(labels?: Labels, amount = 1): void {\n const key = labelsKey(labels);\n this.values.set(key, (this.values.get(key) || 0) - amount);\n }\n\n get(labels?: Labels): number {\n return this.values.get(labelsKey(labels)) || 0;\n }\n\n serialize(): string {\n const lines: string[] = [\n `# HELP ${this.name} ${this.help}`,\n `# TYPE ${this.name} gauge`,\n ];\n if (this.values.size === 0) {\n lines.push(`${this.name} 0`);\n } else {\n for (const [key, val] of this.values) {\n const lbl = key ? `{${key}}` : '';\n lines.push(`${this.name}${lbl} ${val}`);\n }\n }\n return lines.join('\\n');\n }\n}\n\n// ── Pre-defined TITAN Metrics ────────────────────────────────────────\n\nexport const titanRequestsTotal = new Counter(\n 'titan_requests_total',\n 'Total number of requests handled',\n);\n\nexport const titanRequestDuration = new Histogram(\n 'titan_request_duration_seconds',\n 'Request duration in seconds',\n);\n\nexport const titanTokensTotal = new Counter(\n 'titan_tokens_total',\n 'Total tokens consumed',\n);\n\nexport const titanErrorsTotal = new Counter(\n 'titan_errors_total',\n 'Total errors encountered',\n);\n\nexport const titanActiveSessions = new Gauge(\n 'titan_active_sessions',\n 'Number of currently active sessions',\n);\n\nexport const titanToolCallsTotal = new Counter(\n 'titan_tool_calls_total',\n 'Total tool invocations',\n);\n\nexport const titanModelRequestsTotal = new Counter(\n 'titan_model_requests_total',\n 'Total model requests by model and provider',\n);\n\n/**\n * Eval-suite pass rate, 0–100, labelled by suite name. Updated by the\n * /api/eval/run endpoint after each run completes. Lets ops graph\n * regressions over time and alert when a suite drops below threshold.\n *\n * Use the helper `recordEvalSuiteResult(suite, passed, total)` instead of\n * touching this gauge directly so the rate calc + zero-total guard stay\n * in one place.\n */\nexport const titanEvalPassRate = new Gauge(\n 'titan_eval_pass_rate',\n 'Pass rate (0-100) of the most recent eval suite run, labelled by suite',\n);\n\n/** Total eval cases executed, labelled by suite. Counter so a graph\n * shows whether the suite is actually being exercised. */\nexport const titanEvalCasesTotal = new Counter(\n 'titan_eval_cases_total',\n 'Total eval cases executed, by suite and outcome',\n);\n\n/**\n * Record the outcome of an eval suite run on the metrics gauges + counter.\n * Safe to call with `total=0` (gauge stays at 0, no divide-by-zero).\n */\nexport function recordEvalSuiteResult(suite: string, passed: number, total: number): void {\n const rate = total > 0 ? Math.round((passed / total) * 100) : 0;\n titanEvalPassRate.set(rate, { suite });\n titanEvalCasesTotal.increment({ suite, outcome: 'passed' }, passed);\n titanEvalCasesTotal.increment({ suite, outcome: 'failed' }, Math.max(0, total - passed));\n}\n\n// ── Registry & Serialization ─────────────────────────────────────────\n\nconst allMetrics = [\n titanRequestsTotal,\n titanRequestDuration,\n titanTokensTotal,\n titanErrorsTotal,\n titanActiveSessions,\n titanToolCallsTotal,\n titanModelRequestsTotal,\n titanEvalPassRate,\n titanEvalCasesTotal,\n];\n\nexport function serializePrometheus(): string {\n return allMetrics.map(m => m.serialize()).join('\\n\\n') + '\\n';\n}\n\n/** JSON summary for the dashboard telemetry panel */\nexport function getMetricsSummary(): {\n totalRequests: number;\n avgLatencyMs: number;\n topTools: Array<{ tool: string; count: number }>;\n errorRate: number;\n totalErrors: number;\n /**\n * Token counts. The dashboard displays `.total`; `.prompt` + `.completion`\n * are kept for per-bucket breakdowns and Prometheus label parity.\n */\n totalTokens: { prompt: number; completion: number; total: number };\n} {\n // Total requests\n let totalRequests = 0;\n for (const entry of titanRequestsTotal.getAll()) {\n totalRequests += entry.value;\n }\n\n // Average latency\n const duration = titanRequestDuration.get();\n const avgLatencyMs = duration.count > 0 ? (duration.sum / duration.count) * 1000 : 0;\n\n // Top 5 tools by usage\n const toolEntries = titanToolCallsTotal.getAll()\n .map(e => ({ tool: e.labels['tool'] || 'unknown', count: e.value }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 5);\n\n // Error rate\n let totalErrors = 0;\n for (const entry of titanErrorsTotal.getAll()) {\n totalErrors += entry.value;\n }\n const errorRate = totalRequests > 0 ? totalErrors / totalRequests : 0;\n\n // Tokens\n const promptTokens = titanTokensTotal.get({ type: 'prompt' });\n const completionTokens = titanTokensTotal.get({ type: 'completion' });\n\n return {\n totalRequests,\n avgLatencyMs: Math.round(avgLatencyMs * 100) / 100,\n topTools: toolEntries,\n errorRate: Math.round(errorRate * 10000) / 10000,\n totalErrors,\n totalTokens: { prompt: promptTokens, completion: completionTokens, total: promptTokens + completionTokens },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/gateway/metrics.ts"],"sourcesContent":["/**\n * TITAN — Prometheus Metrics Engine\n * Zero-dependency metrics collection with Prometheus text exposition format.\n */\n\n// ── Metric Types ─────────────────────────────────────────────────────\n\ntype Labels = Record<string, string>;\n\nfunction labelsKey(labels?: Labels): string {\n if (!labels || Object.keys(labels).length === 0) return '';\n return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}=\"${v}\"`).join(',');\n}\n\nexport class Counter {\n readonly name: string;\n readonly help: string;\n private values = new Map<string, number>();\n\n constructor(name: string, help: string) {\n this.name = name;\n this.help = help;\n }\n\n increment(labels?: Labels, amount = 1): void {\n const key = labelsKey(labels);\n this.values.set(key, (this.values.get(key) || 0) + amount);\n }\n\n get(labels?: Labels): number {\n return this.values.get(labelsKey(labels)) || 0;\n }\n\n serialize(): string {\n const lines: string[] = [\n `# HELP ${this.name} ${this.help}`,\n `# TYPE ${this.name} counter`,\n ];\n if (this.values.size === 0) {\n lines.push(`${this.name} 0`);\n } else {\n for (const [key, val] of this.values) {\n const lbl = key ? `{${key}}` : '';\n lines.push(`${this.name}${lbl} ${val}`);\n }\n }\n return lines.join('\\n');\n }\n\n /** Get all label combinations and their values */\n getAll(): Array<{ labels: Labels; value: number }> {\n const result: Array<{ labels: Labels; value: number }> = [];\n for (const [key, value] of this.values) {\n const labels: Labels = {};\n if (key) {\n for (const pair of key.split(',')) {\n const [k, v] = pair.split('=');\n labels[k] = v.replace(/\"/g, '');\n }\n }\n result.push({ labels, value });\n }\n return result;\n }\n}\n\nconst DEFAULT_BUCKETS = [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\nexport class Histogram {\n readonly name: string;\n readonly help: string;\n readonly buckets: number[];\n // key → { bucketCounts, sum, count }\n private data = new Map<string, { bucketCounts: number[]; sum: number; count: number }>();\n\n constructor(name: string, help: string, buckets = DEFAULT_BUCKETS) {\n this.name = name;\n this.help = help;\n this.buckets = [...buckets].sort((a, b) => a - b);\n }\n\n observe(value: number, labels?: Labels): void {\n const key = labelsKey(labels);\n let entry = this.data.get(key);\n if (!entry) {\n entry = { bucketCounts: new Array(this.buckets.length).fill(0), sum: 0, count: 0 };\n this.data.set(key, entry);\n }\n entry.sum += value;\n entry.count++;\n for (let i = 0; i < this.buckets.length; i++) {\n if (value <= this.buckets[i]) {\n entry.bucketCounts[i]++;\n break;\n }\n }\n }\n\n get(labels?: Labels): { sum: number; count: number; buckets: Record<string, number> } {\n const key = labelsKey(labels);\n const entry = this.data.get(key);\n if (!entry) return { sum: 0, count: 0, buckets: {} };\n const buckets: Record<string, number> = {};\n let cumulative = 0;\n for (let i = 0; i < this.buckets.length; i++) {\n cumulative += entry.bucketCounts[i];\n buckets[String(this.buckets[i])] = cumulative;\n }\n buckets['+Inf'] = entry.count;\n return { sum: entry.sum, count: entry.count, buckets };\n }\n\n serialize(): string {\n const lines: string[] = [\n `# HELP ${this.name} ${this.help}`,\n `# TYPE ${this.name} histogram`,\n ];\n if (this.data.size === 0) {\n // Emit empty histogram\n for (const b of this.buckets) {\n lines.push(`${this.name}_bucket{le=\"${b}\"} 0`);\n }\n lines.push(`${this.name}_bucket{le=\"+Inf\"} 0`);\n lines.push(`${this.name}_sum 0`);\n lines.push(`${this.name}_count 0`);\n } else {\n for (const [key, entry] of this.data) {\n const baseLabels = key ? `${key},` : '';\n let cumulative = 0;\n for (let i = 0; i < this.buckets.length; i++) {\n cumulative += entry.bucketCounts[i];\n lines.push(`${this.name}_bucket{${baseLabels}le=\"${this.buckets[i]}\"} ${cumulative}`);\n }\n lines.push(`${this.name}_bucket{${baseLabels}le=\"+Inf\"} ${entry.count}`);\n const lbl = key ? `{${key}}` : '';\n lines.push(`${this.name}_sum${lbl} ${entry.sum}`);\n lines.push(`${this.name}_count${lbl} ${entry.count}`);\n }\n }\n return lines.join('\\n');\n }\n}\n\nexport class Gauge {\n readonly name: string;\n readonly help: string;\n private values = new Map<string, number>();\n\n constructor(name: string, help: string) {\n this.name = name;\n this.help = help;\n }\n\n set(value: number, labels?: Labels): void {\n this.values.set(labelsKey(labels), value);\n }\n\n inc(labels?: Labels, amount = 1): void {\n const key = labelsKey(labels);\n this.values.set(key, (this.values.get(key) || 0) + amount);\n }\n\n dec(labels?: Labels, amount = 1): void {\n const key = labelsKey(labels);\n this.values.set(key, (this.values.get(key) || 0) - amount);\n }\n\n get(labels?: Labels): number {\n return this.values.get(labelsKey(labels)) || 0;\n }\n\n serialize(): string {\n const lines: string[] = [\n `# HELP ${this.name} ${this.help}`,\n `# TYPE ${this.name} gauge`,\n ];\n if (this.values.size === 0) {\n lines.push(`${this.name} 0`);\n } else {\n for (const [key, val] of this.values) {\n const lbl = key ? `{${key}}` : '';\n lines.push(`${this.name}${lbl} ${val}`);\n }\n }\n return lines.join('\\n');\n }\n}\n\n// ── Pre-defined TITAN Metrics ────────────────────────────────────────\n\nexport const titanRequestsTotal = new Counter(\n 'titan_requests_total',\n 'Total number of requests handled',\n);\n\nexport const titanRequestDuration = new Histogram(\n 'titan_request_duration_seconds',\n 'Request duration in seconds',\n);\n\nexport const titanTokensTotal = new Counter(\n 'titan_tokens_total',\n 'Total tokens consumed',\n);\n\nexport const titanErrorsTotal = new Counter(\n 'titan_errors_total',\n 'Total errors encountered',\n);\n\nexport const titanActiveSessions = new Gauge(\n 'titan_active_sessions',\n 'Number of currently active sessions',\n);\n\nexport const titanToolCallsTotal = new Counter(\n 'titan_tool_calls_total',\n 'Total tool invocations',\n);\n\nexport const titanModelRequestsTotal = new Counter(\n 'titan_model_requests_total',\n 'Total model requests by model and provider',\n);\n\n/**\n * Eval-suite pass rate, 0–100, labelled by suite name. Updated by the\n * /api/eval/run endpoint after each run completes. Lets ops graph\n * regressions over time and alert when a suite drops below threshold.\n *\n * Use the helper `recordEvalSuiteResult(suite, passed, total)` instead of\n * touching this gauge directly so the rate calc + zero-total guard stay\n * in one place.\n */\nexport const titanEvalPassRate = new Gauge(\n 'titan_eval_pass_rate',\n 'Pass rate (0-100) of the most recent eval suite run, labelled by suite',\n);\n\n/** Total eval cases executed, labelled by suite. Counter so a graph\n * shows whether the suite is actually being exercised. */\nexport const titanEvalCasesTotal = new Counter(\n 'titan_eval_cases_total',\n 'Total eval cases executed, by suite and outcome',\n);\n\n/** Total eval suite invocations that timed out, labelled by suite.\n * v5.3.1: surfaced when /api/eval/run hits its Promise.race deadline. */\nexport const titanEvalTimeoutTotal = new Counter(\n 'titan_eval_timeout_total',\n 'Total eval suite invocations that exceeded their timeout',\n);\n\n/** Total eval suite invocations that threw, labelled by suite.\n * v5.3.1: surfaced when /api/eval/run catches an unexpected exception\n * (gateway must not crash on a single bad case). */\nexport const titanEvalErrorTotal = new Counter(\n 'titan_eval_error_total',\n 'Total eval suite invocations that threw an unhandled exception',\n);\n\n/**\n * Record the outcome of an eval suite run on the metrics gauges + counter.\n *\n * Safe to call with `total=0` — we skip the gauge update in that case\n * (rate is undefined when no cases ran; setting a gauge to 0 would\n * lie). The counter still increments by 0, which is a no-op but keeps\n * the labelled time-series alive in Prometheus.\n *\n * Counters are MONOTONIC by contract (per Prometheus semantics): each\n * call adds to the running total, never resets, never decrements.\n * The gauge is ATOMIC PER SUITE — set() replaces the previous value\n * for the matching label set, never accumulates.\n */\nexport function recordEvalSuiteResult(suite: string, passed: number, total: number): void {\n if (total > 0) {\n const rate = Math.round((passed / total) * 100);\n titanEvalPassRate.set(rate, { suite });\n } else {\n // Don't touch the gauge — leaving it at its previous value (or\n // unset) is more honest than overwriting with 0 on an empty run.\n // The endpoint logs a warning so this isn't silent.\n }\n titanEvalCasesTotal.increment({ suite, outcome: 'passed' }, passed);\n titanEvalCasesTotal.increment({ suite, outcome: 'failed' }, Math.max(0, total - passed));\n}\n\n/** Increment the timeout counter when /api/eval/run hits its deadline. */\nexport function recordEvalTimeout(suite: string): void {\n titanEvalTimeoutTotal.increment({ suite });\n}\n\n/** Increment the error counter when /api/eval/run throws.\n * `errorClass` lets dashboards split timeout vs schema vs runtime. */\nexport function recordEvalError(suite: string, errorClass = 'unknown'): void {\n titanEvalErrorTotal.increment({ suite, errorClass });\n}\n\n// ── Registry & Serialization ─────────────────────────────────────────\n\nconst allMetrics = [\n titanRequestsTotal,\n titanRequestDuration,\n titanTokensTotal,\n titanErrorsTotal,\n titanActiveSessions,\n titanToolCallsTotal,\n titanModelRequestsTotal,\n titanEvalPassRate,\n titanEvalCasesTotal,\n titanEvalTimeoutTotal,\n titanEvalErrorTotal,\n];\n\nexport function serializePrometheus(): string {\n return allMetrics.map(m => m.serialize()).join('\\n\\n') + '\\n';\n}\n\n/** JSON summary for the dashboard telemetry panel */\nexport function getMetricsSummary(): {\n totalRequests: number;\n avgLatencyMs: number;\n topTools: Array<{ tool: string; count: number }>;\n errorRate: number;\n totalErrors: number;\n /**\n * Token counts. The dashboard displays `.total`; `.prompt` + `.completion`\n * are kept for per-bucket breakdowns and Prometheus label parity.\n */\n totalTokens: { prompt: number; completion: number; total: number };\n} {\n // Total requests\n let totalRequests = 0;\n for (const entry of titanRequestsTotal.getAll()) {\n totalRequests += entry.value;\n }\n\n // Average latency\n const duration = titanRequestDuration.get();\n const avgLatencyMs = duration.count > 0 ? (duration.sum / duration.count) * 1000 : 0;\n\n // Top 5 tools by usage\n const toolEntries = titanToolCallsTotal.getAll()\n .map(e => ({ tool: e.labels['tool'] || 'unknown', count: e.value }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 5);\n\n // Error rate\n let totalErrors = 0;\n for (const entry of titanErrorsTotal.getAll()) {\n totalErrors += entry.value;\n }\n const errorRate = totalRequests > 0 ? totalErrors / totalRequests : 0;\n\n // Tokens\n const promptTokens = titanTokensTotal.get({ type: 'prompt' });\n const completionTokens = titanTokensTotal.get({ type: 'completion' });\n\n return {\n totalRequests,\n avgLatencyMs: Math.round(avgLatencyMs * 100) / 100,\n topTools: toolEntries,\n errorRate: Math.round(errorRate * 10000) / 10000,\n totalErrors,\n totalTokens: { prompt: promptTokens, completion: completionTokens, total: promptTokens + completionTokens },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/gateway/server.js
CHANGED
|
@@ -45,7 +45,7 @@ import { TITAN_VERSION, TITAN_NAME, TITAN_LOGS_DIR, TITAN_HOME } from "../utils/
|
|
|
45
45
|
import { collectSystemProfile, recordStartupAnalytics, startHeartbeatAnalytics } from "../analytics/collector.js";
|
|
46
46
|
import { getUpdateInfo } from "../utils/updater.js";
|
|
47
47
|
import { getMissionControlHTML } from "./dashboard.js";
|
|
48
|
-
import { serializePrometheus, getMetricsSummary, titanRequestsTotal, titanRequestDuration, titanErrorsTotal, titanActiveSessions, titanToolCallsTotal, titanTokensTotal, titanModelRequestsTotal, recordEvalSuiteResult } from "./metrics.js";
|
|
48
|
+
import { serializePrometheus, getMetricsSummary, titanRequestsTotal, titanRequestDuration, titanErrorsTotal, titanActiveSessions, titanToolCallsTotal, titanTokensTotal, titanModelRequestsTotal, recordEvalSuiteResult, recordEvalTimeout, recordEvalError } from "./metrics.js";
|
|
49
49
|
import { initSlashCommands, handleSlashCommand } from "./slashCommands.js";
|
|
50
50
|
import { initMcpServers, listMcpServers, addMcpServer, removeMcpServer, setMcpServerEnabled, getMcpStatus, BUILTIN_PRESETS } from "../mcp/registry.js";
|
|
51
51
|
import { connectMcpServer, testMcpServer } from "../mcp/client.js";
|
|
@@ -2266,8 +2266,20 @@ ${msg.content}
|
|
|
2266
2266
|
}
|
|
2267
2267
|
});
|
|
2268
2268
|
app.post("/api/eval/run", async (req, res) => {
|
|
2269
|
+
const requestedSuite = req.body?.suite ?? "";
|
|
2270
|
+
const timeoutMs = (() => {
|
|
2271
|
+
const raw = req.query?.timeoutMs;
|
|
2272
|
+
if (typeof raw !== "string") return 6e5;
|
|
2273
|
+
const n = Number(raw);
|
|
2274
|
+
if (!Number.isFinite(n) || n <= 0) return 6e5;
|
|
2275
|
+
return Math.max(1e4, Math.min(36e5, Math.round(n)));
|
|
2276
|
+
})();
|
|
2277
|
+
if (requestedSuite) {
|
|
2278
|
+
res.setHeader("X-Eval-Suite", requestedSuite);
|
|
2279
|
+
}
|
|
2280
|
+
const tStart = Date.now();
|
|
2281
|
+
logger.info(COMPONENT, `/api/eval/run START suite=${requestedSuite || "(none)"} timeoutMs=${timeoutMs}`);
|
|
2269
2282
|
try {
|
|
2270
|
-
const { suite } = req.body;
|
|
2271
2283
|
const {
|
|
2272
2284
|
runEvalSuite,
|
|
2273
2285
|
WIDGET_CREATION_SUITE,
|
|
@@ -2315,6 +2327,7 @@ _____widget
|
|
|
2315
2327
|
};
|
|
2316
2328
|
};
|
|
2317
2329
|
let cases;
|
|
2330
|
+
const suite = requestedSuite;
|
|
2318
2331
|
switch (suite) {
|
|
2319
2332
|
case "widget-creation":
|
|
2320
2333
|
cases = WIDGET_CREATION_SUITE;
|
|
@@ -2350,18 +2363,57 @@ _____widget
|
|
|
2350
2363
|
cases = CONTENT_SUITE;
|
|
2351
2364
|
break;
|
|
2352
2365
|
default:
|
|
2353
|
-
res.status(
|
|
2366
|
+
res.status(404).json({
|
|
2367
|
+
error: `Unknown suite: ${suite || "(empty)"}. Choose: widget-creation, safety, tool-routing, gate-format, pipeline, adversarial, tool-routing-v2, session, widget-v2, gate-format-v2, content.`
|
|
2368
|
+
});
|
|
2354
2369
|
return;
|
|
2355
2370
|
}
|
|
2356
|
-
const
|
|
2371
|
+
const TIMEOUT = /* @__PURE__ */ Symbol("eval-timeout");
|
|
2372
|
+
const result = await Promise.race([
|
|
2373
|
+
runEvalSuite(suite, cases, agentCall),
|
|
2374
|
+
new Promise((resolve2) => {
|
|
2375
|
+
setTimeout(() => resolve2(TIMEOUT), timeoutMs);
|
|
2376
|
+
})
|
|
2377
|
+
]);
|
|
2378
|
+
if (result === TIMEOUT) {
|
|
2379
|
+
const elapsed2 = Date.now() - tStart;
|
|
2380
|
+
logger.warn(COMPONENT, `/api/eval/run TIMEOUT suite=${suite} elapsedMs=${elapsed2} timeoutMs=${timeoutMs}`);
|
|
2381
|
+
try {
|
|
2382
|
+
recordEvalTimeout(suite);
|
|
2383
|
+
} catch {
|
|
2384
|
+
}
|
|
2385
|
+
res.status(504).json({
|
|
2386
|
+
suite,
|
|
2387
|
+
passed: 0,
|
|
2388
|
+
total: 0,
|
|
2389
|
+
results: [],
|
|
2390
|
+
timedOut: true,
|
|
2391
|
+
timeoutMs,
|
|
2392
|
+
elapsedMs: elapsed2,
|
|
2393
|
+
error: `Eval suite '${suite}' timed out after ${timeoutMs}ms`
|
|
2394
|
+
});
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2357
2397
|
try {
|
|
2358
2398
|
recordEvalSuiteResult(suite, result.passed, result.total);
|
|
2359
2399
|
} catch {
|
|
2360
2400
|
}
|
|
2401
|
+
const elapsed = Date.now() - tStart;
|
|
2402
|
+
logger.info(COMPONENT, `/api/eval/run DONE suite=${suite} passed=${result.passed}/${result.total} elapsedMs=${elapsed}`);
|
|
2361
2403
|
res.json(result);
|
|
2362
2404
|
} catch (e) {
|
|
2363
|
-
|
|
2364
|
-
|
|
2405
|
+
const err = e;
|
|
2406
|
+
const elapsed = Date.now() - tStart;
|
|
2407
|
+
logger.warn(COMPONENT, `/api/eval/run ERROR suite=${requestedSuite || "(none)"} elapsedMs=${elapsed} message=${err.message}`);
|
|
2408
|
+
try {
|
|
2409
|
+
recordEvalError(requestedSuite || "unknown", err.name || "unknown");
|
|
2410
|
+
} catch {
|
|
2411
|
+
}
|
|
2412
|
+
res.status(500).json({
|
|
2413
|
+
suite: requestedSuite || void 0,
|
|
2414
|
+
error: err.message || "Eval run failed",
|
|
2415
|
+
errorClass: err.name || "Error"
|
|
2416
|
+
});
|
|
2365
2417
|
}
|
|
2366
2418
|
});
|
|
2367
2419
|
app.get("/api/eval/suites", async (_req, res) => {
|