@wrongstack/core 0.1.9 → 0.1.10
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/agent-bridge-6KPqsFx6.d.ts +33 -0
- package/dist/compactor-B4mQZXf2.d.ts +17 -0
- package/dist/config-BU9f_5yH.d.ts +193 -0
- package/dist/{provider-txgB0Oq9.d.ts → context-BmM2xGUZ.d.ts} +532 -472
- package/dist/coordination/index.d.ts +694 -0
- package/dist/coordination/index.js +1995 -0
- package/dist/coordination/index.js.map +1 -0
- package/dist/defaults/index.d.ts +34 -2309
- package/dist/defaults/index.js +3893 -3803
- package/dist/defaults/index.js.map +1 -1
- package/dist/events-BMNaEFZl.d.ts +218 -0
- package/dist/execution/index.d.ts +260 -0
- package/dist/execution/index.js +1625 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/index.d.ts +47 -10
- package/dist/index.js +6617 -6093
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +10 -0
- package/dist/infrastructure/index.js +575 -0
- package/dist/infrastructure/index.js.map +1 -0
- package/dist/input-reader-E-ffP2ee.d.ts +12 -0
- package/dist/kernel/index.d.ts +15 -4
- package/dist/kernel/index.js.map +1 -1
- package/dist/logger-BH6AE0W9.d.ts +24 -0
- package/dist/logger-BMQgxvdy.d.ts +12 -0
- package/dist/mcp-servers-Dzgg4x1w.d.ts +100 -0
- package/dist/memory-CEXuo7sz.d.ts +16 -0
- package/dist/mode-CV077NjV.d.ts +27 -0
- package/dist/models/index.d.ts +60 -0
- package/dist/models/index.js +621 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models-registry-DqzwpBQy.d.ts +46 -0
- package/dist/models-registry-Y2xbog0E.d.ts +95 -0
- package/dist/multi-agent-fmkRHtof.d.ts +283 -0
- package/dist/observability/index.d.ts +353 -0
- package/dist/observability/index.js +691 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability-BhnVLBLS.d.ts +67 -0
- package/dist/path-resolver-CPRj4bFY.d.ts +10 -0
- package/dist/path-resolver-DBjaoXFq.d.ts +54 -0
- package/dist/plugin-DJk6LL8B.d.ts +434 -0
- package/dist/renderer-rk_1Swwc.d.ts +158 -0
- package/dist/sdd/index.d.ts +206 -0
- package/dist/sdd/index.js +864 -0
- package/dist/sdd/index.js.map +1 -0
- package/dist/secret-scrubber-CicHLN4G.d.ts +31 -0
- package/dist/secret-scrubber-DF88luOe.d.ts +54 -0
- package/dist/secret-vault-DoISxaKO.d.ts +19 -0
- package/dist/security/index.d.ts +30 -0
- package/dist/security/index.js +524 -0
- package/dist/security/index.js.map +1 -0
- package/dist/selector-BbJqiEP4.d.ts +51 -0
- package/dist/session-reader-Drq8RvJu.d.ts +150 -0
- package/dist/skill-DhfSizKv.d.ts +72 -0
- package/dist/storage/index.d.ts +382 -0
- package/dist/storage/index.js +1530 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/{system-prompt-vAB0F54-.d.ts → system-prompt-BC_8ypCG.d.ts} +1 -1
- package/dist/task-graph-BITvWt4t.d.ts +160 -0
- package/dist/tool-executor-CpuJPYm9.d.ts +97 -0
- package/dist/types/index.d.ts +26 -4
- package/dist/types/index.js +1787 -4
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +49 -2
- package/dist/utils/index.js +100 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +34 -2
- package/dist/mode-Pjt5vMS6.d.ts +0 -815
- package/dist/session-reader-9sOTgmeC.d.ts +0 -1087
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
// src/observability/metrics.ts
|
|
4
|
+
var RESERVOIR_SIZE = 1024;
|
|
5
|
+
function labelKey(labels) {
|
|
6
|
+
if (!labels) return "";
|
|
7
|
+
const keys = Object.keys(labels).sort();
|
|
8
|
+
return keys.map((k) => `${k}=${labels[k]}`).join(",");
|
|
9
|
+
}
|
|
10
|
+
function quantile(sorted, q) {
|
|
11
|
+
if (sorted.length === 0) return 0;
|
|
12
|
+
const idx = Math.min(sorted.length - 1, Math.floor(q * sorted.length));
|
|
13
|
+
return sorted[idx] ?? 0;
|
|
14
|
+
}
|
|
15
|
+
var InMemoryMetricsSink = class {
|
|
16
|
+
counters = /* @__PURE__ */ new Map();
|
|
17
|
+
gauges = /* @__PURE__ */ new Map();
|
|
18
|
+
histograms = /* @__PURE__ */ new Map();
|
|
19
|
+
counter(name, value = 1, labels) {
|
|
20
|
+
const series = this.getOrCreate(this.counters, name);
|
|
21
|
+
const key = labelKey(labels);
|
|
22
|
+
const state = series.get(key) ?? { value: 0 };
|
|
23
|
+
state.value += value;
|
|
24
|
+
series.set(key, state);
|
|
25
|
+
}
|
|
26
|
+
gauge(name, value, labels) {
|
|
27
|
+
const series = this.getOrCreate(this.gauges, name);
|
|
28
|
+
series.set(labelKey(labels), { value });
|
|
29
|
+
}
|
|
30
|
+
histogram(name, value, labels) {
|
|
31
|
+
const series = this.getOrCreate(this.histograms, name);
|
|
32
|
+
const key = labelKey(labels);
|
|
33
|
+
let state = series.get(key);
|
|
34
|
+
if (!state) {
|
|
35
|
+
state = { count: 0, sum: 0, min: value, max: value, samples: [] };
|
|
36
|
+
series.set(key, state);
|
|
37
|
+
}
|
|
38
|
+
state.count++;
|
|
39
|
+
state.sum += value;
|
|
40
|
+
if (value < state.min) state.min = value;
|
|
41
|
+
if (value > state.max) state.max = value;
|
|
42
|
+
if (state.samples.length < RESERVOIR_SIZE) {
|
|
43
|
+
state.samples.push(value);
|
|
44
|
+
} else {
|
|
45
|
+
const r = Math.floor(Math.random() * state.count);
|
|
46
|
+
if (r < RESERVOIR_SIZE) state.samples[r] = value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
snapshot() {
|
|
50
|
+
const series = [];
|
|
51
|
+
for (const [name, byLabel] of this.counters) {
|
|
52
|
+
for (const [key, state] of byLabel) {
|
|
53
|
+
series.push({
|
|
54
|
+
name,
|
|
55
|
+
type: "counter",
|
|
56
|
+
labels: parseLabelKey(key),
|
|
57
|
+
values: { value: state.value }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const [name, byLabel] of this.gauges) {
|
|
62
|
+
for (const [key, state] of byLabel) {
|
|
63
|
+
series.push({
|
|
64
|
+
name,
|
|
65
|
+
type: "gauge",
|
|
66
|
+
labels: parseLabelKey(key),
|
|
67
|
+
values: { value: state.value }
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const [name, byLabel] of this.histograms) {
|
|
72
|
+
for (const [key, state] of byLabel) {
|
|
73
|
+
const sorted = [...state.samples].sort((a, b) => a - b);
|
|
74
|
+
series.push({
|
|
75
|
+
name,
|
|
76
|
+
type: "histogram",
|
|
77
|
+
labels: parseLabelKey(key),
|
|
78
|
+
values: {
|
|
79
|
+
count: state.count,
|
|
80
|
+
sum: state.sum,
|
|
81
|
+
min: state.min,
|
|
82
|
+
max: state.max,
|
|
83
|
+
p50: quantile(sorted, 0.5),
|
|
84
|
+
p95: quantile(sorted, 0.95),
|
|
85
|
+
p99: quantile(sorted, 0.99)
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { timestamp: Date.now(), series };
|
|
91
|
+
}
|
|
92
|
+
reset() {
|
|
93
|
+
this.counters.clear();
|
|
94
|
+
this.gauges.clear();
|
|
95
|
+
this.histograms.clear();
|
|
96
|
+
}
|
|
97
|
+
getOrCreate(bag, name) {
|
|
98
|
+
let series = bag.get(name);
|
|
99
|
+
if (!series) {
|
|
100
|
+
series = /* @__PURE__ */ new Map();
|
|
101
|
+
bag.set(name, series);
|
|
102
|
+
}
|
|
103
|
+
return series;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
function parseLabelKey(key) {
|
|
107
|
+
if (!key) return {};
|
|
108
|
+
const labels = {};
|
|
109
|
+
for (const pair of key.split(",")) {
|
|
110
|
+
const eq = pair.indexOf("=");
|
|
111
|
+
if (eq > 0) labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
112
|
+
}
|
|
113
|
+
return labels;
|
|
114
|
+
}
|
|
115
|
+
var NoopMetricsSink = class {
|
|
116
|
+
counter() {
|
|
117
|
+
}
|
|
118
|
+
gauge() {
|
|
119
|
+
}
|
|
120
|
+
histogram() {
|
|
121
|
+
}
|
|
122
|
+
snapshot() {
|
|
123
|
+
return { timestamp: Date.now(), series: [] };
|
|
124
|
+
}
|
|
125
|
+
reset() {
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/observability/health.ts
|
|
130
|
+
var SEVERITY = {
|
|
131
|
+
healthy: 0,
|
|
132
|
+
degraded: 1,
|
|
133
|
+
unhealthy: 2
|
|
134
|
+
};
|
|
135
|
+
var DefaultHealthRegistry = class {
|
|
136
|
+
checks = /* @__PURE__ */ new Map();
|
|
137
|
+
timeoutMs;
|
|
138
|
+
constructor(opts = {}) {
|
|
139
|
+
this.timeoutMs = opts.timeoutMs ?? 5e3;
|
|
140
|
+
}
|
|
141
|
+
register(check) {
|
|
142
|
+
this.checks.set(check.name, check);
|
|
143
|
+
}
|
|
144
|
+
unregister(name) {
|
|
145
|
+
this.checks.delete(name);
|
|
146
|
+
}
|
|
147
|
+
async run() {
|
|
148
|
+
const results = await Promise.all(
|
|
149
|
+
Array.from(this.checks.values()).map(async (c) => {
|
|
150
|
+
const result = await this.runOne(c);
|
|
151
|
+
return { name: c.name, ...result };
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
let status = "healthy";
|
|
155
|
+
for (const r of results) {
|
|
156
|
+
if (SEVERITY[r.status] > SEVERITY[status]) status = r.status;
|
|
157
|
+
}
|
|
158
|
+
return { status, timestamp: Date.now(), checks: results };
|
|
159
|
+
}
|
|
160
|
+
async runOne(check) {
|
|
161
|
+
let timer = null;
|
|
162
|
+
const timeout = new Promise((resolve) => {
|
|
163
|
+
timer = setTimeout(
|
|
164
|
+
() => resolve({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
165
|
+
this.timeoutMs
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
try {
|
|
169
|
+
return await Promise.race([check.check(), timeout]);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
return { status: "unhealthy", detail: err instanceof Error ? err.message : String(err) };
|
|
172
|
+
} finally {
|
|
173
|
+
if (timer) clearTimeout(timer);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/observability/tracer.ts
|
|
179
|
+
var NoopTracer = class {
|
|
180
|
+
startSpan() {
|
|
181
|
+
return NOOP_SPAN;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var NOOP_SPAN = {
|
|
185
|
+
setAttribute() {
|
|
186
|
+
},
|
|
187
|
+
recordError() {
|
|
188
|
+
},
|
|
189
|
+
end() {
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/observability/otel-tracer.ts
|
|
194
|
+
var OTEL_STATUS_ERROR = 2;
|
|
195
|
+
var OTelTracer = class {
|
|
196
|
+
constructor(upstream) {
|
|
197
|
+
this.upstream = upstream;
|
|
198
|
+
}
|
|
199
|
+
upstream;
|
|
200
|
+
startSpan(name, attrs) {
|
|
201
|
+
const otelSpan = this.upstream.startSpan(name, attrs ? { attributes: attrs } : void 0);
|
|
202
|
+
return new OTelSpan(otelSpan);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
var OTelSpan = class {
|
|
206
|
+
constructor(span) {
|
|
207
|
+
this.span = span;
|
|
208
|
+
}
|
|
209
|
+
span;
|
|
210
|
+
setAttribute(key, value) {
|
|
211
|
+
this.span.setAttribute(key, value);
|
|
212
|
+
}
|
|
213
|
+
recordError(err) {
|
|
214
|
+
this.span.recordException(err);
|
|
215
|
+
this.span.setStatus?.({ code: OTEL_STATUS_ERROR, message: err.message });
|
|
216
|
+
}
|
|
217
|
+
end() {
|
|
218
|
+
this.span.end();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// src/observability/event-bridge.ts
|
|
223
|
+
function wireMetricsToEvents(events, sink) {
|
|
224
|
+
const unsubs = [];
|
|
225
|
+
unsubs.push(
|
|
226
|
+
events.on("session.started", () => sink.counter("agent.sessions.started")),
|
|
227
|
+
events.on("session.ended", (e) => {
|
|
228
|
+
sink.counter("agent.sessions.ended");
|
|
229
|
+
sink.histogram("agent.session.tokens.input", e.usage.input);
|
|
230
|
+
sink.histogram("agent.session.tokens.output", e.usage.output);
|
|
231
|
+
}),
|
|
232
|
+
events.on("session.damaged", () => sink.counter("agent.sessions.damaged")),
|
|
233
|
+
events.on("iteration.completed", () => sink.counter("agent.iterations.total")),
|
|
234
|
+
events.on("iteration.limit_reached", () => sink.counter("agent.iteration_limit.hit")),
|
|
235
|
+
events.on("provider.response", (e) => {
|
|
236
|
+
sink.counter("provider.responses.total", 1, { stop_reason: e.stopReason });
|
|
237
|
+
sink.counter("provider.tokens.input", e.usage.input);
|
|
238
|
+
sink.counter("provider.tokens.output", e.usage.output);
|
|
239
|
+
if (e.usage.cacheRead) sink.counter("provider.tokens.cache_read", e.usage.cacheRead);
|
|
240
|
+
if (e.usage.cacheWrite) sink.counter("provider.tokens.cache_write", e.usage.cacheWrite);
|
|
241
|
+
}),
|
|
242
|
+
events.on(
|
|
243
|
+
"provider.retry",
|
|
244
|
+
(e) => sink.counter("provider.retries.total", 1, {
|
|
245
|
+
provider: e.providerId,
|
|
246
|
+
status: String(e.status)
|
|
247
|
+
})
|
|
248
|
+
),
|
|
249
|
+
events.on(
|
|
250
|
+
"provider.error",
|
|
251
|
+
(e) => sink.counter("provider.errors.total", 1, {
|
|
252
|
+
provider: e.providerId,
|
|
253
|
+
status: String(e.status),
|
|
254
|
+
retryable: String(e.retryable)
|
|
255
|
+
})
|
|
256
|
+
),
|
|
257
|
+
events.on("tool.started", (e) => sink.counter("tool.starts.total", 1, { tool: e.name })),
|
|
258
|
+
events.on("tool.executed", (e) => {
|
|
259
|
+
sink.counter("tool.executions.total", 1, { tool: e.name, ok: String(e.ok) });
|
|
260
|
+
sink.histogram("tool.duration_ms", e.durationMs, { tool: e.name });
|
|
261
|
+
}),
|
|
262
|
+
events.on("token.threshold", (e) => sink.gauge("agent.tokens.used", e.used)),
|
|
263
|
+
events.on("compaction.fired", (e) => {
|
|
264
|
+
sink.counter("compaction.fired.total");
|
|
265
|
+
sink.histogram("compaction.reduction_tokens", e.before - e.after);
|
|
266
|
+
}),
|
|
267
|
+
events.on(
|
|
268
|
+
"mcp.server.connected",
|
|
269
|
+
(e) => sink.counter("mcp.connects.total", 1, { server: e.name })
|
|
270
|
+
),
|
|
271
|
+
events.on(
|
|
272
|
+
"mcp.server.reconnected",
|
|
273
|
+
(e) => sink.counter("mcp.reconnects.total", 1, { server: e.name })
|
|
274
|
+
),
|
|
275
|
+
events.on(
|
|
276
|
+
"mcp.server.disconnected",
|
|
277
|
+
(e) => sink.counter("mcp.disconnects.total", 1, { server: e.name })
|
|
278
|
+
),
|
|
279
|
+
events.on("error", (e) => sink.counter("agent.errors.total", 1, { phase: e.phase }))
|
|
280
|
+
);
|
|
281
|
+
return () => {
|
|
282
|
+
for (const u of unsubs) u();
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/observability/prometheus.ts
|
|
287
|
+
var NUMBER_FORMAT_INFINITY = "NaN";
|
|
288
|
+
function escapeLabelValue(v) {
|
|
289
|
+
return v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, '\\"');
|
|
290
|
+
}
|
|
291
|
+
function formatLabels(labels) {
|
|
292
|
+
const keys = Object.keys(labels);
|
|
293
|
+
if (keys.length === 0) return "";
|
|
294
|
+
const parts = keys.map((k) => `${k}="${escapeLabelValue(labels[k] ?? "")}"`);
|
|
295
|
+
return `{${parts.join(",")}}`;
|
|
296
|
+
}
|
|
297
|
+
function formatNumber(n) {
|
|
298
|
+
if (!Number.isFinite(n)) return NUMBER_FORMAT_INFINITY;
|
|
299
|
+
return Number.isInteger(n) ? n.toString() : n.toString();
|
|
300
|
+
}
|
|
301
|
+
function joinLabels(base, extra) {
|
|
302
|
+
return { ...base, ...extra };
|
|
303
|
+
}
|
|
304
|
+
function renderPrometheus(snapshot) {
|
|
305
|
+
const groups = /* @__PURE__ */ new Map();
|
|
306
|
+
for (const s of snapshot.series) {
|
|
307
|
+
let g = groups.get(s.name);
|
|
308
|
+
if (!g) {
|
|
309
|
+
g = { type: s.type, rows: [] };
|
|
310
|
+
groups.set(s.name, g);
|
|
311
|
+
}
|
|
312
|
+
g.rows.push({ labels: s.labels, values: s.values });
|
|
313
|
+
}
|
|
314
|
+
const lines = [];
|
|
315
|
+
for (const [name, g] of groups) {
|
|
316
|
+
const promType = g.type === "histogram" ? "summary" : g.type;
|
|
317
|
+
lines.push(`# HELP ${name} ${name}`);
|
|
318
|
+
lines.push(`# TYPE ${name} ${promType}`);
|
|
319
|
+
if (g.type === "counter" || g.type === "gauge") {
|
|
320
|
+
for (const row of g.rows) {
|
|
321
|
+
lines.push(`${name}${formatLabels(row.labels)} ${formatNumber(row.values.value ?? 0)}`);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
for (const row of g.rows) {
|
|
325
|
+
const { count = 0, sum = 0, p50 = 0, p95 = 0, p99 = 0 } = row.values;
|
|
326
|
+
lines.push(
|
|
327
|
+
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.5" }))} ${formatNumber(p50)}`
|
|
328
|
+
);
|
|
329
|
+
lines.push(
|
|
330
|
+
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.95" }))} ${formatNumber(p95)}`
|
|
331
|
+
);
|
|
332
|
+
lines.push(
|
|
333
|
+
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.99" }))} ${formatNumber(p99)}`
|
|
334
|
+
);
|
|
335
|
+
lines.push(`${name}_sum${formatLabels(row.labels)} ${formatNumber(sum)}`);
|
|
336
|
+
lines.push(`${name}_count${formatLabels(row.labels)} ${formatNumber(count)}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return lines.join("\n") + "\n";
|
|
341
|
+
}
|
|
342
|
+
var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
343
|
+
async function startMetricsServer(opts) {
|
|
344
|
+
const tls = opts.tls;
|
|
345
|
+
const useHttps = !!(tls?.cert && tls?.key);
|
|
346
|
+
const host = opts.host ?? "127.0.0.1";
|
|
347
|
+
const path = opts.path ?? "/metrics";
|
|
348
|
+
const healthPath = opts.healthPath ?? "/healthz";
|
|
349
|
+
const healthRegistry = opts.healthRegistry;
|
|
350
|
+
const listener = (req, res) => {
|
|
351
|
+
if (!req.url || req.method !== "GET") {
|
|
352
|
+
res.statusCode = req.url ? 405 : 400;
|
|
353
|
+
res.end();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const url = req.url.split("?")[0];
|
|
357
|
+
if (url === path) {
|
|
358
|
+
let body;
|
|
359
|
+
try {
|
|
360
|
+
body = renderPrometheus(opts.sink.snapshot());
|
|
361
|
+
} catch (err) {
|
|
362
|
+
res.statusCode = 500;
|
|
363
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
364
|
+
res.end(`metrics render failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
res.statusCode = 200;
|
|
368
|
+
res.setHeader("content-type", PROMETHEUS_CONTENT_TYPE);
|
|
369
|
+
res.end(body);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (healthRegistry && url === healthPath) {
|
|
373
|
+
healthRegistry.run().then(
|
|
374
|
+
(agg) => {
|
|
375
|
+
res.statusCode = agg.status === "unhealthy" ? 503 : 200;
|
|
376
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
377
|
+
res.end(JSON.stringify(agg, null, 2));
|
|
378
|
+
},
|
|
379
|
+
(err) => {
|
|
380
|
+
res.statusCode = 500;
|
|
381
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
382
|
+
res.end(`health run failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
res.statusCode = 404;
|
|
388
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
389
|
+
res.end("Not Found");
|
|
390
|
+
};
|
|
391
|
+
let server;
|
|
392
|
+
if (useHttps && tls) {
|
|
393
|
+
const { createServer } = await import('https');
|
|
394
|
+
const { readFileSync } = await import('fs');
|
|
395
|
+
server = createServer(
|
|
396
|
+
{ cert: readFileSync(tls.cert), key: readFileSync(tls.key) },
|
|
397
|
+
listener
|
|
398
|
+
);
|
|
399
|
+
} else {
|
|
400
|
+
const { createServer } = await import('http');
|
|
401
|
+
server = createServer(listener);
|
|
402
|
+
}
|
|
403
|
+
await new Promise((resolve, reject) => {
|
|
404
|
+
const onError = (err) => {
|
|
405
|
+
server.off("listening", onListening);
|
|
406
|
+
reject(err);
|
|
407
|
+
};
|
|
408
|
+
const onListening = () => {
|
|
409
|
+
server.off("error", onError);
|
|
410
|
+
resolve();
|
|
411
|
+
};
|
|
412
|
+
server.once("error", onError);
|
|
413
|
+
server.once("listening", onListening);
|
|
414
|
+
server.listen(opts.port, host);
|
|
415
|
+
});
|
|
416
|
+
const addr = server.address();
|
|
417
|
+
const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
418
|
+
const protocol = useHttps ? "https" : "http";
|
|
419
|
+
return {
|
|
420
|
+
port: boundPort,
|
|
421
|
+
url: `${protocol}://${host}:${boundPort}${path}`,
|
|
422
|
+
close: () => new Promise((resolve, reject) => {
|
|
423
|
+
server.close((err) => err ? reject(err) : resolve());
|
|
424
|
+
})
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// src/observability/otlp-metrics.ts
|
|
429
|
+
var DEFAULT_INTERVAL_MS = 3e4;
|
|
430
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
431
|
+
function joinEndpoint(base) {
|
|
432
|
+
if (/\/v1\/metrics\/?$/.test(base)) return base;
|
|
433
|
+
return base.replace(/\/$/, "") + "/v1/metrics";
|
|
434
|
+
}
|
|
435
|
+
function attributesFor(labels) {
|
|
436
|
+
return Object.entries(labels).map(([key, value]) => ({
|
|
437
|
+
key,
|
|
438
|
+
value: { stringValue: value }
|
|
439
|
+
}));
|
|
440
|
+
}
|
|
441
|
+
function buildExportBody(opts) {
|
|
442
|
+
const metrics = [];
|
|
443
|
+
for (const s of opts.series) {
|
|
444
|
+
const dp = {
|
|
445
|
+
attributes: attributesFor(s.labels),
|
|
446
|
+
timeUnixNano: opts.timeUnixNano
|
|
447
|
+
};
|
|
448
|
+
if (s.type === "counter") {
|
|
449
|
+
dp.asDouble = s.values.value ?? 0;
|
|
450
|
+
metrics.push({
|
|
451
|
+
name: s.name,
|
|
452
|
+
sum: { dataPoints: [dp], aggregationTemporality: 2, isMonotonic: true }
|
|
453
|
+
});
|
|
454
|
+
} else if (s.type === "gauge") {
|
|
455
|
+
dp.asDouble = s.values.value ?? 0;
|
|
456
|
+
metrics.push({ name: s.name, gauge: { dataPoints: [dp] } });
|
|
457
|
+
} else {
|
|
458
|
+
dp.count = String(s.values.count ?? 0);
|
|
459
|
+
dp.sum = s.values.sum ?? 0;
|
|
460
|
+
dp.quantileValues = [
|
|
461
|
+
{ quantile: 0.5, value: s.values.p50 ?? 0 },
|
|
462
|
+
{ quantile: 0.95, value: s.values.p95 ?? 0 },
|
|
463
|
+
{ quantile: 0.99, value: s.values.p99 ?? 0 }
|
|
464
|
+
];
|
|
465
|
+
metrics.push({ name: s.name, summary: { dataPoints: [dp] } });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
resourceMetrics: [
|
|
470
|
+
{
|
|
471
|
+
resource: { attributes: attributesFor(opts.resourceAttributes) },
|
|
472
|
+
scopeMetrics: [
|
|
473
|
+
{
|
|
474
|
+
scope: { name: opts.scopeName },
|
|
475
|
+
metrics
|
|
476
|
+
}
|
|
477
|
+
]
|
|
478
|
+
}
|
|
479
|
+
]
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function buildOtlpMetricsRequest(sink, opts = {}) {
|
|
483
|
+
return buildExportBody({
|
|
484
|
+
series: sink.snapshot().series,
|
|
485
|
+
resourceAttributes: opts.resourceAttributes ?? { "service.name": "wrongstack" },
|
|
486
|
+
scopeName: opts.scopeName ?? "wrongstack",
|
|
487
|
+
timeUnixNano: String(BigInt(Date.now()) * 1000000n)
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
function startOtlpMetricsExporter(opts) {
|
|
491
|
+
const url = joinEndpoint(opts.endpoint);
|
|
492
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
493
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
494
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
495
|
+
const onError = opts.onError ?? (() => {
|
|
496
|
+
});
|
|
497
|
+
const resourceAttributes = opts.resourceAttributes ?? { "service.name": "wrongstack" };
|
|
498
|
+
const scopeName = opts.scopeName ?? "wrongstack";
|
|
499
|
+
let stopped = false;
|
|
500
|
+
const headers = {
|
|
501
|
+
"content-type": "application/json",
|
|
502
|
+
...opts.headers ?? {}
|
|
503
|
+
};
|
|
504
|
+
if (opts.authorization) headers.authorization = opts.authorization;
|
|
505
|
+
async function pushOnce() {
|
|
506
|
+
if (stopped) return;
|
|
507
|
+
const body = buildExportBody({
|
|
508
|
+
series: opts.sink.snapshot().series,
|
|
509
|
+
resourceAttributes,
|
|
510
|
+
scopeName,
|
|
511
|
+
timeUnixNano: String(BigInt(Date.now()) * 1000000n)
|
|
512
|
+
});
|
|
513
|
+
const controller = new AbortController();
|
|
514
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
515
|
+
try {
|
|
516
|
+
const res = await fetchImpl(url, {
|
|
517
|
+
method: "POST",
|
|
518
|
+
headers,
|
|
519
|
+
body: JSON.stringify(body),
|
|
520
|
+
signal: controller.signal
|
|
521
|
+
});
|
|
522
|
+
if (!res.ok) {
|
|
523
|
+
const text = await res.text().catch(() => "");
|
|
524
|
+
onError(new Error(`OTLP push failed: ${res.status} ${res.statusText} ${text}`));
|
|
525
|
+
}
|
|
526
|
+
} catch (err) {
|
|
527
|
+
onError(err);
|
|
528
|
+
} finally {
|
|
529
|
+
clearTimeout(timer);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const handle = setInterval(() => {
|
|
533
|
+
void pushOnce();
|
|
534
|
+
}, intervalMs);
|
|
535
|
+
handle.unref?.();
|
|
536
|
+
return {
|
|
537
|
+
flush: pushOnce,
|
|
538
|
+
async stop() {
|
|
539
|
+
stopped = true;
|
|
540
|
+
clearInterval(handle);
|
|
541
|
+
await pushOnce().catch(onError);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
var SPAN_STATUS_CODE_UNSET = 0;
|
|
546
|
+
var SPAN_STATUS_CODE_OK = 1;
|
|
547
|
+
var SPAN_STATUS_CODE_ERROR = 2;
|
|
548
|
+
function hex(bytes) {
|
|
549
|
+
return crypto.randomBytes(bytes).toString("hex");
|
|
550
|
+
}
|
|
551
|
+
function nowNs() {
|
|
552
|
+
return BigInt(Date.now()) * 1000000n;
|
|
553
|
+
}
|
|
554
|
+
var CapturingSpan = class {
|
|
555
|
+
constructor(state, onEnd) {
|
|
556
|
+
this.state = state;
|
|
557
|
+
this.onEnd = onEnd;
|
|
558
|
+
}
|
|
559
|
+
state;
|
|
560
|
+
onEnd;
|
|
561
|
+
setAttribute(key, value) {
|
|
562
|
+
this.state.attributes[key] = value;
|
|
563
|
+
}
|
|
564
|
+
recordError(err) {
|
|
565
|
+
this.state.status = { code: SPAN_STATUS_CODE_ERROR, message: err.message };
|
|
566
|
+
this.state.attributes["exception.message"] = err.message;
|
|
567
|
+
if (err.name) this.state.attributes["exception.type"] = err.name;
|
|
568
|
+
}
|
|
569
|
+
end() {
|
|
570
|
+
if (this.state.endTimeUnixNano !== void 0) return;
|
|
571
|
+
this.state.endTimeUnixNano = nowNs();
|
|
572
|
+
if (this.state.status.code === SPAN_STATUS_CODE_UNSET) {
|
|
573
|
+
this.state.status.code = SPAN_STATUS_CODE_OK;
|
|
574
|
+
}
|
|
575
|
+
this.onEnd(this.state);
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
var DEFAULT_INTERVAL_MS2 = 5e3;
|
|
579
|
+
var DEFAULT_BUFFER_CAP = 2048;
|
|
580
|
+
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
581
|
+
function joinEndpoint2(base) {
|
|
582
|
+
if (/\/v1\/traces\/?$/.test(base)) return base;
|
|
583
|
+
return base.replace(/\/$/, "") + "/v1/traces";
|
|
584
|
+
}
|
|
585
|
+
function encodeAttr(key, value) {
|
|
586
|
+
if (typeof value === "boolean") return { key, value: { boolValue: value } };
|
|
587
|
+
if (typeof value === "number") {
|
|
588
|
+
return Number.isInteger(value) ? { key, value: { intValue: String(value) } } : { key, value: { doubleValue: value } };
|
|
589
|
+
}
|
|
590
|
+
return { key, value: { stringValue: value } };
|
|
591
|
+
}
|
|
592
|
+
function buildOtlpTracesRequest(spans, opts = {}) {
|
|
593
|
+
const resourceAttributes = opts.resourceAttributes ?? { "service.name": "wrongstack" };
|
|
594
|
+
const scopeName = opts.scopeName ?? "wrongstack";
|
|
595
|
+
const otlpSpans = spans.map((s) => ({
|
|
596
|
+
traceId: s.traceId,
|
|
597
|
+
spanId: s.spanId,
|
|
598
|
+
name: s.name,
|
|
599
|
+
kind: 1,
|
|
600
|
+
// SPAN_KIND_INTERNAL
|
|
601
|
+
startTimeUnixNano: s.startTimeUnixNano.toString(),
|
|
602
|
+
endTimeUnixNano: (s.endTimeUnixNano ?? s.startTimeUnixNano).toString(),
|
|
603
|
+
attributes: Object.entries(s.attributes).map(([k, v]) => encodeAttr(k, v)),
|
|
604
|
+
status: s.status
|
|
605
|
+
}));
|
|
606
|
+
return {
|
|
607
|
+
resourceSpans: [
|
|
608
|
+
{
|
|
609
|
+
resource: {
|
|
610
|
+
attributes: Object.entries(resourceAttributes).map(([k, v]) => encodeAttr(k, v))
|
|
611
|
+
},
|
|
612
|
+
scopeSpans: [{ scope: { name: scopeName }, spans: otlpSpans }]
|
|
613
|
+
}
|
|
614
|
+
]
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
function startOtlpTraceExporter(opts) {
|
|
618
|
+
const url = joinEndpoint2(opts.endpoint);
|
|
619
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS2;
|
|
620
|
+
const maxBuffered = opts.maxBufferedSpans ?? DEFAULT_BUFFER_CAP;
|
|
621
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
622
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
623
|
+
const onError = opts.onError ?? (() => {
|
|
624
|
+
});
|
|
625
|
+
const resourceAttributes = opts.resourceAttributes ?? { "service.name": "wrongstack" };
|
|
626
|
+
const scopeName = opts.scopeName ?? "wrongstack";
|
|
627
|
+
let stopped = false;
|
|
628
|
+
const buffer = [];
|
|
629
|
+
const headers = {
|
|
630
|
+
"content-type": "application/json",
|
|
631
|
+
...opts.headers ?? {}
|
|
632
|
+
};
|
|
633
|
+
if (opts.authorization) headers.authorization = opts.authorization;
|
|
634
|
+
const tracer = {
|
|
635
|
+
startSpan(name, attrs) {
|
|
636
|
+
const state = {
|
|
637
|
+
traceId: hex(16),
|
|
638
|
+
spanId: hex(8),
|
|
639
|
+
name,
|
|
640
|
+
startTimeUnixNano: nowNs(),
|
|
641
|
+
attributes: { ...attrs ?? {} },
|
|
642
|
+
status: { code: SPAN_STATUS_CODE_UNSET }
|
|
643
|
+
};
|
|
644
|
+
return new CapturingSpan(state, (ended) => {
|
|
645
|
+
if (buffer.length >= maxBuffered) buffer.shift();
|
|
646
|
+
buffer.push(ended);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
async function pushOnce() {
|
|
651
|
+
if (buffer.length === 0) return;
|
|
652
|
+
const batch = buffer.splice(0, buffer.length);
|
|
653
|
+
const body = buildOtlpTracesRequest(batch, { resourceAttributes, scopeName });
|
|
654
|
+
const controller = new AbortController();
|
|
655
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
656
|
+
try {
|
|
657
|
+
const res = await fetchImpl(url, {
|
|
658
|
+
method: "POST",
|
|
659
|
+
headers,
|
|
660
|
+
body: JSON.stringify(body),
|
|
661
|
+
signal: controller.signal
|
|
662
|
+
});
|
|
663
|
+
if (!res.ok) {
|
|
664
|
+
const text = await res.text().catch(() => "");
|
|
665
|
+
onError(new Error(`OTLP traces push failed: ${res.status} ${res.statusText} ${text}`));
|
|
666
|
+
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
onError(err);
|
|
669
|
+
} finally {
|
|
670
|
+
clearTimeout(timer);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const handle = setInterval(() => {
|
|
674
|
+
if (!stopped) void pushOnce();
|
|
675
|
+
}, intervalMs);
|
|
676
|
+
handle.unref?.();
|
|
677
|
+
return {
|
|
678
|
+
tracer,
|
|
679
|
+
flush: pushOnce,
|
|
680
|
+
async stop() {
|
|
681
|
+
stopped = true;
|
|
682
|
+
clearInterval(handle);
|
|
683
|
+
await pushOnce().catch(onError);
|
|
684
|
+
},
|
|
685
|
+
buffered: () => [...buffer]
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export { DefaultHealthRegistry, InMemoryMetricsSink, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, buildOtlpMetricsRequest, buildOtlpTracesRequest, renderPrometheus, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, wireMetricsToEvents };
|
|
690
|
+
//# sourceMappingURL=index.js.map
|
|
691
|
+
//# sourceMappingURL=index.js.map
|