@symerian/symi 3.0.18 → 3.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  3. package/package.json +1 -1
  4. package/extensions/copilot-proxy/README.md +0 -24
  5. package/extensions/copilot-proxy/index.ts +0 -154
  6. package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
  7. package/extensions/copilot-proxy/package.json +0 -15
  8. package/extensions/copilot-proxy/symi.plugin.json +0 -9
  9. package/extensions/device-pair/index.ts +0 -642
  10. package/extensions/device-pair/symi.plugin.json +0 -20
  11. package/extensions/diagnostics-otel/index.ts +0 -15
  12. package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
  13. package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
  14. package/extensions/diagnostics-otel/package.json +0 -27
  15. package/extensions/diagnostics-otel/src/service.test.ts +0 -290
  16. package/extensions/diagnostics-otel/src/service.ts +0 -666
  17. package/extensions/diagnostics-otel/symi.plugin.json +0 -8
  18. package/extensions/google-antigravity-auth/README.md +0 -24
  19. package/extensions/google-antigravity-auth/index.ts +0 -424
  20. package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
  21. package/extensions/google-antigravity-auth/package.json +0 -15
  22. package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
  23. package/extensions/google-gemini-cli-auth/README.md +0 -35
  24. package/extensions/google-gemini-cli-auth/index.ts +0 -75
  25. package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
  26. package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
  27. package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
  28. package/extensions/google-gemini-cli-auth/package.json +0 -15
  29. package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
  30. package/extensions/learning-loop/index.ts +0 -159
  31. package/extensions/learning-loop/node_modules/.bin/symi +0 -21
  32. package/extensions/learning-loop/package.json +0 -18
  33. package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
  34. package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
  35. package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
  36. package/extensions/learning-loop/src/capture/serializer.ts +0 -74
  37. package/extensions/learning-loop/src/db.ts +0 -583
  38. package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
  39. package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
  40. package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
  41. package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
  42. package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
  43. package/extensions/learning-loop/src/hooks.ts +0 -244
  44. package/extensions/learning-loop/src/injection/cache.ts +0 -73
  45. package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
  46. package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
  47. package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
  48. package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
  49. package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
  50. package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
  51. package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
  52. package/extensions/learning-loop/src/math/ewma.ts +0 -51
  53. package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
  54. package/extensions/learning-loop/src/schema.ts +0 -176
  55. package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
  56. package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
  57. package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
  58. package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
  59. package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
  60. package/extensions/learning-loop/src/test/graph.test.ts +0 -711
  61. package/extensions/learning-loop/src/test/integration.test.ts +0 -312
  62. package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
  63. package/extensions/learning-loop/src/test/math.test.ts +0 -148
  64. package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
  65. package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
  66. package/extensions/learning-loop/src/types.ts +0 -281
  67. package/extensions/learning-loop/symi.plugin.json +0 -46
  68. package/extensions/llm-task/README.md +0 -97
  69. package/extensions/llm-task/index.ts +0 -6
  70. package/extensions/llm-task/package.json +0 -12
  71. package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
  72. package/extensions/llm-task/src/llm-task-tool.ts +0 -249
  73. package/extensions/llm-task/symi.plugin.json +0 -21
  74. package/extensions/memory-lancedb/config.ts +0 -161
  75. package/extensions/memory-lancedb/index.test.ts +0 -330
  76. package/extensions/memory-lancedb/index.ts +0 -670
  77. package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
  78. package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
  79. package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
  80. package/extensions/memory-lancedb/package.json +0 -20
  81. package/extensions/memory-lancedb/symi.plugin.json +0 -71
  82. package/extensions/minimax-portal-auth/README.md +0 -33
  83. package/extensions/minimax-portal-auth/index.ts +0 -161
  84. package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
  85. package/extensions/minimax-portal-auth/oauth.ts +0 -247
  86. package/extensions/minimax-portal-auth/package.json +0 -15
  87. package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
  88. package/extensions/model-equalizer/index.ts +0 -80
  89. package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
  90. package/extensions/model-equalizer/src/detection.ts +0 -62
  91. package/extensions/model-equalizer/src/enhancer.ts +0 -63
  92. package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
  93. package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
  94. package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
  95. package/extensions/model-equalizer/src/types.ts +0 -24
  96. package/extensions/model-equalizer/symi.plugin.json +0 -12
  97. package/extensions/phone-control/index.ts +0 -421
  98. package/extensions/phone-control/symi.plugin.json +0 -10
  99. package/extensions/pipeline/README.md +0 -75
  100. package/extensions/pipeline/SKILL.md +0 -97
  101. package/extensions/pipeline/index.ts +0 -18
  102. package/extensions/pipeline/package.json +0 -11
  103. package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
  104. package/extensions/pipeline/src/pipeline-tool.ts +0 -266
  105. package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
  106. package/extensions/pipeline/src/windows-spawn.ts +0 -193
  107. package/extensions/pipeline/symi.plugin.json +0 -10
  108. package/extensions/qwen-portal-auth/README.md +0 -24
  109. package/extensions/qwen-portal-auth/index.ts +0 -134
  110. package/extensions/qwen-portal-auth/oauth.ts +0 -190
  111. package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
  112. package/extensions/talk-voice/index.ts +0 -150
  113. package/extensions/talk-voice/symi.plugin.json +0 -10
  114. package/extensions/thread-ownership/index.test.ts +0 -180
  115. package/extensions/thread-ownership/index.ts +0 -133
  116. package/extensions/thread-ownership/symi.plugin.json +0 -28
@@ -1,159 +0,0 @@
1
- import path from "node:path";
2
- import { resolveMemorySearchConfig } from "../../src/agents/memory-search.js";
3
- import { createEmbeddingProvider } from "../../src/memory/embeddings.js";
4
- import type { SymiPluginApi } from "../../src/plugins/types.js";
5
- import { registerGatewayMethods } from "./src/analytics/gateway-methods.js";
6
- import { createMetricsAggregator } from "./src/analytics/metrics-aggregator.js";
7
- import { createRunTracker } from "./src/capture/run-tracker.js";
8
- import { createDatabaseManager } from "./src/db.js";
9
- import { createImplicitSignals } from "./src/feedback/implicit-signals.js";
10
- import { createEdgeInference } from "./src/graph/edge-inference.js";
11
- import { createGraphRetrieval } from "./src/graph/graph-retrieval.js";
12
- import { createGraphStore } from "./src/graph/graph-store.js";
13
- import { registerHooks } from "./src/hooks.js";
14
- import { createContextInjector } from "./src/injection/context-injector.js";
15
- import { createEmbeddingBridge, type EmbeddingBridge } from "./src/learning/embedding-bridge.js";
16
- import { createLearningExtractor } from "./src/learning/learning-extractor.js";
17
- import { createLearningStore } from "./src/learning/learning-store.js";
18
- import { createQualityEngine } from "./src/scoring/quality-engine.js";
19
- import { resolveConfig } from "./src/types.js";
20
-
21
- const plugin = {
22
- id: "learning-loop",
23
- name: "Learning Loop",
24
- description:
25
- "Closed-loop learning with deterministic quality scoring, pattern extraction, and context injection",
26
-
27
- register(api: SymiPluginApi) {
28
- const config = resolveConfig(api.pluginConfig);
29
- const logger = api.logger;
30
- const rootStateDir = api.runtime.state.resolveStateDir();
31
- const stateDir = path.join(rootStateDir, "learning-loop");
32
-
33
- const db = createDatabaseManager({ stateDir, config, logger });
34
- const tracker = createRunTracker();
35
- const qualityEngine = createQualityEngine({ config, db });
36
- const graphStore = createGraphStore({ db });
37
- const learningStore = createLearningStore({ db, graphStore });
38
- const edgeInference = createEdgeInference({ graphStore, learningStore });
39
- const graphRetrieval = createGraphRetrieval({ graphStore, learningStore, config });
40
-
41
- // Lazy embedding bridge: initializes on first use, avoids blocking sync register()
42
- let bridgePromise: Promise<EmbeddingBridge> | null = null;
43
- let bridgeResolved = false;
44
- let bridgeHasProvider = false;
45
-
46
- async function initBridge(): Promise<EmbeddingBridge> {
47
- try {
48
- const memCfg = resolveMemorySearchConfig(api.config, "main");
49
- const result = await createEmbeddingProvider({
50
- config: api.config,
51
- provider: memCfg?.provider ?? "auto",
52
- model: memCfg?.model ?? "",
53
- fallback: memCfg?.fallback ?? "none",
54
- local: memCfg?.local,
55
- remote: memCfg?.remote,
56
- });
57
- return createEmbeddingBridge({ provider: result.provider, logger });
58
- } catch (err) {
59
- logger.warn(`[learning-loop] embedding bridge init failed: ${String(err)}`);
60
- return createEmbeddingBridge({ provider: null, logger });
61
- }
62
- }
63
-
64
- async function initAndTrack(): Promise<EmbeddingBridge> {
65
- const bridge = await initBridge();
66
- bridgeResolved = true;
67
- bridgeHasProvider = bridge.available();
68
- return bridge;
69
- }
70
-
71
- const lazyBridge: EmbeddingBridge = {
72
- async embed(text) {
73
- if (!bridgePromise) bridgePromise = initAndTrack();
74
- return (await bridgePromise).embed(text);
75
- },
76
- async embedBatch(texts) {
77
- if (!bridgePromise) bridgePromise = initAndTrack();
78
- return (await bridgePromise).embedBatch(texts);
79
- },
80
- available: () => (bridgeResolved ? bridgeHasProvider : true),
81
- };
82
-
83
- const extractor = createLearningExtractor({
84
- db,
85
- learningStore,
86
- config,
87
- edgeInference,
88
- embeddingBridge: lazyBridge,
89
- });
90
- const injector = createContextInjector({
91
- db,
92
- learningStore,
93
- config,
94
- embeddingBridge: lazyBridge,
95
- graphRetrieval,
96
- });
97
- const metrics = createMetricsAggregator({ db });
98
- const implicit = createImplicitSignals({ db, config });
99
-
100
- registerHooks(api, {
101
- config,
102
- logger,
103
- db,
104
- tracker,
105
- qualityEngine,
106
- learningStore,
107
- extractor,
108
- injector,
109
- metrics,
110
- implicit,
111
- edgeInference,
112
- });
113
-
114
- registerGatewayMethods(api, {
115
- db,
116
- learningStore,
117
- metrics,
118
- injector,
119
- qualityEngine,
120
- graphStore,
121
- });
122
-
123
- // One-time async backfill: embed historical learnings that have no embeddings
124
- async function backfillEmbeddings(): Promise<void> {
125
- const BATCH_SIZE = 50;
126
- let totalBackfilled = 0;
127
-
128
- // eslint-disable-next-line no-constant-condition
129
- while (true) {
130
- const rows = db.getLearningsWithoutEmbeddings(BATCH_SIZE);
131
- if (rows.length === 0) break;
132
-
133
- const texts = rows.map((r) => r.content);
134
- const embeddings = await lazyBridge.embedBatch(texts);
135
-
136
- for (let i = 0; i < rows.length; i++) {
137
- const emb = embeddings[i];
138
- if (emb) {
139
- db.updateLearningEmbedding(rows[i]!.id, emb);
140
- totalBackfilled++;
141
- }
142
- }
143
-
144
- if (rows.length < BATCH_SIZE) break;
145
- }
146
-
147
- if (totalBackfilled > 0) {
148
- logger.debug?.(`[learning-loop] backfilled embeddings for ${totalBackfilled} learnings`);
149
- }
150
- }
151
-
152
- // Fire-and-forget: backfill after bridge is ready
153
- backfillEmbeddings().catch((err) => {
154
- logger.warn(`[learning-loop] embedding backfill failed: ${String(err)}`);
155
- });
156
- },
157
- };
158
-
159
- export default plugin;
@@ -1,21 +0,0 @@
1
- #!/bin/sh
2
- basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
-
4
- case `uname` in
5
- *CYGWIN*|*MINGW*|*MSYS*)
6
- if command -v cygpath > /dev/null 2>&1; then
7
- basedir=`cygpath -w "$basedir"`
8
- fi
9
- ;;
10
- esac
11
-
12
- if [ -z "$NODE_PATH" ]; then
13
- export NODE_PATH="/home/symi/projects/symi/node_modules:/home/symi/projects/node_modules:/home/symi/node_modules:/home/node_modules:/node_modules:/home/symi/projects/symi/node_modules/.pnpm/node_modules"
14
- else
15
- export NODE_PATH="/home/symi/projects/symi/node_modules:/home/symi/projects/node_modules:/home/symi/node_modules:/home/node_modules:/node_modules:/home/symi/projects/symi/node_modules/.pnpm/node_modules:$NODE_PATH"
16
- fi
17
- if [ -x "$basedir/node" ]; then
18
- exec "$basedir/node" "$basedir/../@symerian/symi/symi.mjs" "$@"
19
- else
20
- exec node "$basedir/../@symerian/symi/symi.mjs" "$@"
21
- fi
@@ -1,18 +0,0 @@
1
- {
2
- "name": "@symi/learning-loop",
3
- "version": "3.0.9",
4
- "private": true,
5
- "description": "Closed-loop learning extension with deterministic quality scoring and vector-indexed learning storage",
6
- "type": "module",
7
- "devDependencies": {
8
- "@symerian/symi": "workspace:*"
9
- },
10
- "peerDependencies": {
11
- "@symerian/symi": ">=2026.1.26"
12
- },
13
- "symi": {
14
- "extensions": [
15
- "./index.ts"
16
- ]
17
- }
18
- }
@@ -1,230 +0,0 @@
1
- /**
2
- * Gateway RPC methods for the Glass UI.
3
- */
4
-
5
- import type { SymiPluginApi } from "../../../../src/plugins/types.js";
6
- import type { DatabaseManager } from "../db.js";
7
- import { createExplicitFeedbackHandler } from "../feedback/explicit-feedback.js";
8
- import type { GraphStore } from "../graph/graph-store.js";
9
- import type { ContextInjector } from "../injection/context-injector.js";
10
- import type { LearningStore } from "../learning/learning-store.js";
11
- import type { QualityEngine } from "../scoring/quality-engine.js";
12
- import type { LearningCategory, EdgeType } from "../types.js";
13
- import type { MetricsAggregator } from "./metrics-aggregator.js";
14
-
15
- export function registerGatewayMethods(
16
- api: SymiPluginApi,
17
- deps: {
18
- db: DatabaseManager;
19
- learningStore: LearningStore;
20
- metrics: MetricsAggregator;
21
- injector: ContextInjector;
22
- qualityEngine?: QualityEngine;
23
- graphStore?: GraphStore;
24
- },
25
- ) {
26
- const { db, learningStore, metrics, injector, qualityEngine, graphStore } = deps;
27
- const feedbackHandler = createExplicitFeedbackHandler({ db, qualityEngine });
28
-
29
- // learning.metrics.models
30
- api.registerGatewayMethod("learning.metrics.models", (opts) => {
31
- try {
32
- const params = opts.params as { provider?: string; model?: string };
33
- const result = metrics.getModelMetrics(params.provider, params.model);
34
- opts.respond(true, result);
35
- } catch (err) {
36
- opts.respond(false, undefined, { code: 500, message: String(err) });
37
- }
38
- });
39
-
40
- // learning.metrics.timeseries
41
- api.registerGatewayMethod("learning.metrics.timeseries", (opts) => {
42
- try {
43
- const params = opts.params as {
44
- provider?: string;
45
- model?: string;
46
- fromHour?: string;
47
- toHour?: string;
48
- };
49
- const result = metrics.getTimeseries(params);
50
- opts.respond(true, result);
51
- } catch (err) {
52
- opts.respond(false, undefined, { code: 500, message: String(err) });
53
- }
54
- });
55
-
56
- // learning.metrics.leaderboard
57
- api.registerGatewayMethod("learning.metrics.leaderboard", (opts) => {
58
- try {
59
- const result = metrics.getLeaderboard();
60
- opts.respond(true, result);
61
- } catch (err) {
62
- opts.respond(false, undefined, { code: 500, message: String(err) });
63
- }
64
- });
65
-
66
- // learning.learnings.list
67
- api.registerGatewayMethod("learning.learnings.list", (opts) => {
68
- try {
69
- const params = opts.params as { category?: string; limit?: number };
70
- const result = learningStore.listLearnings({
71
- category: params.category as LearningCategory | undefined,
72
- limit: params.limit,
73
- });
74
- opts.respond(true, result);
75
- } catch (err) {
76
- opts.respond(false, undefined, { code: 500, message: String(err) });
77
- }
78
- });
79
-
80
- // learning.learnings.search
81
- api.registerGatewayMethod("learning.learnings.search", (opts) => {
82
- try {
83
- const params = opts.params as { query: string; limit?: number };
84
- if (!params.query) {
85
- opts.respond(false, undefined, { code: 400, message: "query is required" });
86
- return;
87
- }
88
- const result = learningStore.searchByText(params.query, params.limit ?? 20);
89
- opts.respond(true, result);
90
- } catch (err) {
91
- opts.respond(false, undefined, { code: 500, message: String(err) });
92
- }
93
- });
94
-
95
- // learning.feedback.submit
96
- api.registerGatewayMethod("learning.feedback.submit", (opts) => {
97
- try {
98
- const params = opts.params as { runId: string; score: number };
99
- if (!params.runId || params.score == null) {
100
- opts.respond(false, undefined, {
101
- code: 400,
102
- message: "runId and score (1-5) are required",
103
- });
104
- return;
105
- }
106
- const result = feedbackHandler.submitFeedback(params.runId, params.score);
107
- if (!result) {
108
- opts.respond(false, undefined, { code: 404, message: "Run not found" });
109
- return;
110
- }
111
- opts.respond(true, result);
112
- } catch (err) {
113
- opts.respond(false, undefined, { code: 500, message: String(err) });
114
- }
115
- });
116
-
117
- // learning.status
118
- api.registerGatewayMethod("learning.status", (opts) => {
119
- try {
120
- const stats = db.getStats();
121
- const cacheStats = injector.getCacheStats();
122
- opts.respond(true, {
123
- ...stats,
124
- cache: cacheStats,
125
- });
126
- } catch (err) {
127
- opts.respond(false, undefined, { code: 500, message: String(err) });
128
- }
129
- });
130
-
131
- // --- Graph methods ---
132
-
133
- if (graphStore) {
134
- // learning.graph.edges
135
- api.registerGatewayMethod("learning.graph.edges", (opts) => {
136
- try {
137
- const params = opts.params as { learningId: string; typeFilter?: EdgeType[] };
138
- if (!params.learningId) {
139
- opts.respond(false, undefined, { code: 400, message: "learningId is required" });
140
- return;
141
- }
142
- const edges = graphStore.getEdges(params.learningId, {
143
- typeFilter: params.typeFilter,
144
- });
145
- opts.respond(true, edges);
146
- } catch (err) {
147
- opts.respond(false, undefined, { code: 500, message: String(err) });
148
- }
149
- });
150
-
151
- // learning.graph.neighbors
152
- api.registerGatewayMethod("learning.graph.neighbors", (opts) => {
153
- try {
154
- const params = opts.params as {
155
- learningId: string;
156
- hops?: number;
157
- typeFilter?: EdgeType[];
158
- };
159
- if (!params.learningId) {
160
- opts.respond(false, undefined, { code: 400, message: "learningId is required" });
161
- return;
162
- }
163
- const neighbors = graphStore.getNeighbors(
164
- params.learningId,
165
- params.hops ?? 1,
166
- params.typeFilter,
167
- );
168
- // Convert Map to serializable object
169
- const result: Array<{ id: string; weight: number }> = [];
170
- for (const [id, weight] of neighbors) {
171
- result.push({ id, weight });
172
- }
173
- opts.respond(true, result);
174
- } catch (err) {
175
- opts.respond(false, undefined, { code: 500, message: String(err) });
176
- }
177
- });
178
-
179
- // learning.graph.contradictions
180
- api.registerGatewayMethod("learning.graph.contradictions", (opts) => {
181
- try {
182
- const params = opts.params as { learningId?: string } | undefined;
183
- const contradictions = graphStore.getContradictions(params?.learningId);
184
- opts.respond(true, contradictions);
185
- } catch (err) {
186
- opts.respond(false, undefined, { code: 500, message: String(err) });
187
- }
188
- });
189
-
190
- // learning.graph.stats
191
- api.registerGatewayMethod("learning.graph.stats", (opts) => {
192
- try {
193
- const edgeTypes: EdgeType[] = ["T", "S", "C", "U", "X", "R"];
194
- const countsByType: Record<string, number> = {};
195
- let totalEdges = 0;
196
- for (const t of edgeTypes) {
197
- const edges = db.getEdgesByType(t);
198
- countsByType[t] = edges.length;
199
- totalEdges += edges.length;
200
- }
201
-
202
- const learnings = db.getAllLearnings(10000);
203
- let centralitySum = 0;
204
- let centralityCount = 0;
205
- for (const l of learnings) {
206
- const score = db.getCentralityScore(l.id);
207
- if (score > 0) {
208
- centralitySum += score;
209
- centralityCount++;
210
- }
211
- }
212
-
213
- const avgCentrality = centralityCount > 0 ? centralitySum / centralityCount : 0;
214
- const nodeCount = learnings.length;
215
- const maxEdges = nodeCount > 1 ? (nodeCount * (nodeCount - 1)) / 2 : 0;
216
- const density = maxEdges > 0 ? totalEdges / maxEdges : 0;
217
-
218
- opts.respond(true, {
219
- totalEdges,
220
- countsByType,
221
- avgCentrality,
222
- nodeCount,
223
- density,
224
- });
225
- } catch (err) {
226
- opts.respond(false, undefined, { code: 500, message: String(err) });
227
- }
228
- });
229
- }
230
- }
@@ -1,153 +0,0 @@
1
- /**
2
- * Pre-aggregated time bucket metrics.
3
- *
4
- * Uses O(1) upsert per run via SQLite's ON CONFLICT UPDATE.
5
- * Variance computed via: var = sum_sq/n - (sum/n)^2
6
- */
7
-
8
- import type { DatabaseManager } from "../db.js";
9
- import { wilsonScoreLowerBound } from "../math/confidence-intervals.js";
10
- import type { CompletedRun, QualityScore, MetricsBucketRow } from "../types.js";
11
-
12
- export type MetricsAggregator = ReturnType<typeof createMetricsAggregator>;
13
-
14
- export function createMetricsAggregator(params: { db: DatabaseManager }) {
15
- const { db } = params;
16
-
17
- /**
18
- * Record a completed run in the metrics buckets. O(1) upsert.
19
- */
20
- function recordRun(run: CompletedRun, score: QualityScore): void {
21
- db.upsertMetricsBucket(
22
- run.provider,
23
- run.model,
24
- run.completedAt,
25
- score.score,
26
- run.durationMs,
27
- run.success,
28
- run.usage.input ?? 0,
29
- run.usage.output ?? 0,
30
- );
31
- }
32
-
33
- /**
34
- * Get per-model quality, success rate, latency, and token efficiency.
35
- */
36
- function getModelMetrics(
37
- provider?: string,
38
- model?: string,
39
- ): Array<{
40
- provider: string;
41
- model: string;
42
- runCount: number;
43
- successRate: number;
44
- successRateLowerBound: number;
45
- avgQuality: number;
46
- qualityVariance: number;
47
- avgLatency: number;
48
- latencyVariance: number;
49
- avgTokenInput: number;
50
- avgTokenOutput: number;
51
- }> {
52
- const buckets = db.getMetricsBuckets(provider, model);
53
- const grouped = new Map<string, MetricsBucketRow[]>();
54
-
55
- for (const b of buckets) {
56
- const key = `${b.provider}:${b.model}`;
57
- const group = grouped.get(key) ?? [];
58
- group.push(b);
59
- grouped.set(key, group);
60
- }
61
-
62
- const results: Array<{
63
- provider: string;
64
- model: string;
65
- runCount: number;
66
- successRate: number;
67
- successRateLowerBound: number;
68
- avgQuality: number;
69
- qualityVariance: number;
70
- avgLatency: number;
71
- latencyVariance: number;
72
- avgTokenInput: number;
73
- avgTokenOutput: number;
74
- }> = [];
75
-
76
- for (const [, group] of grouped) {
77
- const totalRuns = group.reduce((s, b) => s + b.run_count, 0);
78
- if (totalRuns === 0) continue;
79
-
80
- const totalSuccess = group.reduce((s, b) => s + b.success_count, 0);
81
- const qualitySum = group.reduce((s, b) => s + b.quality_sum, 0);
82
- const qualitySumSq = group.reduce((s, b) => s + b.quality_sum_sq, 0);
83
- const latencySum = group.reduce((s, b) => s + b.latency_sum, 0);
84
- const latencySumSq = group.reduce((s, b) => s + b.latency_sum_sq, 0);
85
- const tokenInputSum = group.reduce((s, b) => s + b.token_input_sum, 0);
86
- const tokenOutputSum = group.reduce((s, b) => s + b.token_output_sum, 0);
87
-
88
- const avgQuality = qualitySum / totalRuns;
89
- const avgLatency = latencySum / totalRuns;
90
- const successRate = totalSuccess / totalRuns;
91
-
92
- results.push({
93
- provider: group[0]!.provider,
94
- model: group[0]!.model,
95
- runCount: totalRuns,
96
- successRate,
97
- successRateLowerBound: wilsonScoreLowerBound(totalSuccess, totalRuns),
98
- avgQuality,
99
- qualityVariance: Math.max(0, qualitySumSq / totalRuns - avgQuality ** 2),
100
- avgLatency,
101
- latencyVariance: Math.max(0, latencySumSq / totalRuns - avgLatency ** 2),
102
- avgTokenInput: tokenInputSum / totalRuns,
103
- avgTokenOutput: tokenOutputSum / totalRuns,
104
- });
105
- }
106
-
107
- return results.sort((a, b) => b.avgQuality - a.avgQuality);
108
- }
109
-
110
- /**
111
- * Get time-bucketed quality trends.
112
- */
113
- function getTimeseries(params?: {
114
- provider?: string;
115
- model?: string;
116
- fromHour?: string;
117
- toHour?: string;
118
- }): Array<{
119
- bucketHour: string;
120
- provider: string;
121
- model: string;
122
- runCount: number;
123
- avgQuality: number;
124
- successRate: number;
125
- avgLatency: number;
126
- }> {
127
- const buckets = db.getMetricsBuckets(
128
- params?.provider,
129
- params?.model,
130
- params?.fromHour,
131
- params?.toHour,
132
- );
133
-
134
- return buckets.map((b) => ({
135
- bucketHour: b.bucket_hour,
136
- provider: b.provider,
137
- model: b.model,
138
- runCount: b.run_count,
139
- avgQuality: b.run_count > 0 ? b.quality_sum / b.run_count : 0,
140
- successRate: b.run_count > 0 ? b.success_count / b.run_count : 0,
141
- avgLatency: b.run_count > 0 ? b.latency_sum / b.run_count : 0,
142
- }));
143
- }
144
-
145
- /**
146
- * Get model comparison leaderboard.
147
- */
148
- function getLeaderboard() {
149
- return db.getModelLeaderboard();
150
- }
151
-
152
- return { recordRun, getModelMetrics, getTimeseries, getLeaderboard };
153
- }