@glassflow-ai/rius 0.1.0 → 0.2.1
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 +49 -0
- package/dist/index.cjs +340 -14
- package/dist/index.d.cts +52 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +335 -10
- package/package.json +22 -2
package/README.md
CHANGED
|
@@ -233,6 +233,11 @@ variables, then the defaults below.
|
|
|
233
233
|
| `sampleRate` | `RIUS_SAMPLE_RATE` | `1.0` |
|
|
234
234
|
| `captureContent` | `RIUS_CAPTURE_CONTENT` | `true` |
|
|
235
235
|
| `mask` | (options only) | none |
|
|
236
|
+
| `heartbeat` | `RIUS_HEARTBEAT` | `true` |
|
|
237
|
+
| `heartbeatInterval` | `RIUS_HEARTBEAT_INTERVAL` | `15` (seconds; clamped 5-300) |
|
|
238
|
+
| `agentName` | `RIUS_AGENT_NAME` | `serviceName` |
|
|
239
|
+
| `partialSpans` | `RIUS_PARTIAL_SPANS` | `false` |
|
|
240
|
+
| `partialSpansDelay` | `RIUS_PARTIAL_SPANS_DELAY` | `0` (seconds; clamped 0-60) |
|
|
236
241
|
|
|
237
242
|
Traces are posted to `<endpoint>/v1/traces`.
|
|
238
243
|
|
|
@@ -255,6 +260,50 @@ optional integration is loaded, so no third-party module is patched in your
|
|
|
255
260
|
process. `client.ready` resolves to an empty list. Spans can still be
|
|
256
261
|
created and are simply dropped.
|
|
257
262
|
|
|
263
|
+
## Agent liveness
|
|
264
|
+
|
|
265
|
+
Traces only leave the process when a span ends, so an idle-but-alive agent
|
|
266
|
+
and a crashed one look identical from the outside. Two independent
|
|
267
|
+
mechanisms close that gap: a heartbeat that reports the process is still
|
|
268
|
+
running, and partial spans that leave a durable trace of in-flight work if
|
|
269
|
+
the process disappears before finishing it.
|
|
270
|
+
|
|
271
|
+
### Heartbeat
|
|
272
|
+
|
|
273
|
+
With `heartbeat: true` (the default), the SDK posts to `<endpoint>/v1/heartbeat`
|
|
274
|
+
for the life of the process: an immediate first ping when `init()` runs,
|
|
275
|
+
then one every `heartbeatInterval` seconds (default 15, clamped to 5-300).
|
|
276
|
+
Each ping reports the count of currently open root spans, which is what
|
|
277
|
+
lets the backend tell a live-but-idle agent from one that has vanished.
|
|
278
|
+
|
|
279
|
+
The final ping, carrying `stopped: true`, is sent by `client.shutdown()`.
|
|
280
|
+
The SDK also makes a best-effort attempt to send it when the process ends
|
|
281
|
+
naturally, on Node's `beforeExit` event. An application that never calls
|
|
282
|
+
`shutdown()` and exits abruptly, killed, crashed, or via `process.exit()`,
|
|
283
|
+
misses that last ping and reports as gone rather than stopped, the same way
|
|
284
|
+
a hard-killed agent looks in any language. Call `client.shutdown()` on a
|
|
285
|
+
graceful exit if you want that distinction to show up.
|
|
286
|
+
|
|
287
|
+
The heartbeat never keeps the process alive, its timer is unref'd, and never
|
|
288
|
+
throws into your code; delivery failures are logged once per client and
|
|
289
|
+
silent after that. `agentName` identifies the agent in heartbeat payloads
|
|
290
|
+
and defaults to `serviceName`.
|
|
291
|
+
|
|
292
|
+
### Partial spans
|
|
293
|
+
|
|
294
|
+
With `partialSpans: true` (default `false`), every sampled span additionally
|
|
295
|
+
exports a content-free snapshot the moment it starts, on top of the normal
|
|
296
|
+
export when it ends. The snapshot carries only identity and taxonomy
|
|
297
|
+
attributes, trace id, span id, name and timestamps, never content, whatever
|
|
298
|
+
instrumentation set on the span. The backend stores it as an unfinished row
|
|
299
|
+
and replaces it with the real span at end, so a snapshot that is never
|
|
300
|
+
replaced is the durable record of what a crashed agent was doing.
|
|
301
|
+
|
|
302
|
+
`partialSpansDelay` (default `0`, clamped to 0-60 seconds) debounces the
|
|
303
|
+
snapshot: it is held for that long and only sent if the span is still open
|
|
304
|
+
once the delay elapses, so a span that finishes quickly costs zero network
|
|
305
|
+
calls.
|
|
306
|
+
|
|
258
307
|
## Reliability
|
|
259
308
|
|
|
260
309
|
Export is designed to never block or crash your application:
|
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,16 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
20
20
|
};
|
|
21
21
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
22
|
|
|
23
|
+
// node_modules/tsup/assets/cjs_shims.js
|
|
24
|
+
var getImportMetaUrl, importMetaUrl;
|
|
25
|
+
var init_cjs_shims = __esm({
|
|
26
|
+
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
27
|
+
"use strict";
|
|
28
|
+
getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
29
|
+
importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
23
33
|
// src/config.ts
|
|
24
34
|
function bool(name, raw, fallback) {
|
|
25
35
|
if (raw === void 0) return fallback;
|
|
@@ -37,24 +47,64 @@ function rate(raw, fallback) {
|
|
|
37
47
|
if (Number.isNaN(parsed)) return fallback;
|
|
38
48
|
return Math.min(1, Math.max(0, parsed));
|
|
39
49
|
}
|
|
50
|
+
function seconds(raw, fallback) {
|
|
51
|
+
if (raw === void 0) return fallback;
|
|
52
|
+
const parsed = Number.parseFloat(raw);
|
|
53
|
+
if (Number.isNaN(parsed)) return fallback;
|
|
54
|
+
return parsed;
|
|
55
|
+
}
|
|
56
|
+
function clamp(name, value, min, max) {
|
|
57
|
+
if (value >= min && value <= max) return value;
|
|
58
|
+
const clamped = Math.min(Math.max(value, min), max);
|
|
59
|
+
console.warn(`[rius] ${name}=${value} is outside [${min}, ${max}]; clamped to ${clamped}.`);
|
|
60
|
+
return clamped;
|
|
61
|
+
}
|
|
40
62
|
function resolveConfig(options = {}, env = process.env) {
|
|
41
63
|
const endpoint = (options.endpoint ?? env.RIUS_ENDPOINT ?? DEFAULT_ENDPOINT).replace(/\/+$/, "");
|
|
64
|
+
const serviceName = options.serviceName ?? env.RIUS_SERVICE_NAME ?? DEFAULT_SERVICE_NAME;
|
|
65
|
+
const heartbeatIntervalSeconds = clamp(
|
|
66
|
+
"heartbeatInterval",
|
|
67
|
+
options.heartbeatInterval ?? seconds(env.RIUS_HEARTBEAT_INTERVAL, DEFAULT_HEARTBEAT_INTERVAL),
|
|
68
|
+
HEARTBEAT_INTERVAL_MIN,
|
|
69
|
+
HEARTBEAT_INTERVAL_MAX
|
|
70
|
+
);
|
|
71
|
+
const partialSpansDelaySeconds = clamp(
|
|
72
|
+
"partialSpansDelay",
|
|
73
|
+
options.partialSpansDelay ?? seconds(env.RIUS_PARTIAL_SPANS_DELAY, DEFAULT_PARTIAL_SPANS_DELAY),
|
|
74
|
+
PARTIAL_SPANS_DELAY_MIN,
|
|
75
|
+
PARTIAL_SPANS_DELAY_MAX
|
|
76
|
+
);
|
|
42
77
|
return {
|
|
43
78
|
endpoint,
|
|
44
79
|
apiKey: options.apiKey ?? env.RIUS_API_KEY,
|
|
45
|
-
serviceName
|
|
80
|
+
serviceName,
|
|
46
81
|
disabled: options.disabled ?? bool("RIUS_DISABLED", env.RIUS_DISABLED, false),
|
|
47
82
|
sampleRate: options.sampleRate ?? rate(env.RIUS_SAMPLE_RATE, 1),
|
|
48
83
|
captureContent: options.captureContent ?? bool("RIUS_CAPTURE_CONTENT", env.RIUS_CAPTURE_CONTENT, true),
|
|
49
|
-
mask: options.mask
|
|
84
|
+
mask: options.mask,
|
|
85
|
+
heartbeat: options.heartbeat ?? bool("RIUS_HEARTBEAT", env.RIUS_HEARTBEAT, true),
|
|
86
|
+
heartbeatIntervalMs: heartbeatIntervalSeconds * 1e3,
|
|
87
|
+
heartbeatEndpoint: `${endpoint}/v1/heartbeat`,
|
|
88
|
+
// Empty falls through to serviceName rather than shipping a blank identity
|
|
89
|
+
// on every heartbeat payload, so `??` would be wrong here.
|
|
90
|
+
agentName: options.agentName || env.RIUS_AGENT_NAME || serviceName,
|
|
91
|
+
partialSpans: options.partialSpans ?? bool("RIUS_PARTIAL_SPANS", env.RIUS_PARTIAL_SPANS, false),
|
|
92
|
+
partialSpansDelayMs: partialSpansDelaySeconds * 1e3
|
|
50
93
|
};
|
|
51
94
|
}
|
|
52
|
-
var DEFAULT_ENDPOINT, DEFAULT_SERVICE_NAME, AFFIRMATIVE, NEGATIVE;
|
|
95
|
+
var DEFAULT_ENDPOINT, DEFAULT_SERVICE_NAME, HEARTBEAT_INTERVAL_MIN, HEARTBEAT_INTERVAL_MAX, DEFAULT_HEARTBEAT_INTERVAL, PARTIAL_SPANS_DELAY_MIN, PARTIAL_SPANS_DELAY_MAX, DEFAULT_PARTIAL_SPANS_DELAY, AFFIRMATIVE, NEGATIVE;
|
|
53
96
|
var init_config = __esm({
|
|
54
97
|
"src/config.ts"() {
|
|
55
98
|
"use strict";
|
|
99
|
+
init_cjs_shims();
|
|
56
100
|
DEFAULT_ENDPOINT = "https://ingest.eu.console.rius-glassflow.com";
|
|
57
101
|
DEFAULT_SERVICE_NAME = "unknown_service";
|
|
102
|
+
HEARTBEAT_INTERVAL_MIN = 5;
|
|
103
|
+
HEARTBEAT_INTERVAL_MAX = 300;
|
|
104
|
+
DEFAULT_HEARTBEAT_INTERVAL = 15;
|
|
105
|
+
PARTIAL_SPANS_DELAY_MIN = 0;
|
|
106
|
+
PARTIAL_SPANS_DELAY_MAX = 60;
|
|
107
|
+
DEFAULT_PARTIAL_SPANS_DELAY = 0;
|
|
58
108
|
AFFIRMATIVE = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
59
109
|
NEGATIVE = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
|
|
60
110
|
}
|
|
@@ -65,6 +115,7 @@ var DelegatingSpanProcessor;
|
|
|
65
115
|
var init_delegatingProcessor = __esm({
|
|
66
116
|
"src/delegatingProcessor.ts"() {
|
|
67
117
|
"use strict";
|
|
118
|
+
init_cjs_shims();
|
|
68
119
|
DelegatingSpanProcessor = class {
|
|
69
120
|
delegates = [];
|
|
70
121
|
add(processor) {
|
|
@@ -102,6 +153,7 @@ var ExportResultCode;
|
|
|
102
153
|
var init_ExportResult = __esm({
|
|
103
154
|
"node_modules/@opentelemetry/core/build/esm/ExportResult.js"() {
|
|
104
155
|
"use strict";
|
|
156
|
+
init_cjs_shims();
|
|
105
157
|
(function(ExportResultCode2) {
|
|
106
158
|
ExportResultCode2[ExportResultCode2["SUCCESS"] = 0] = "SUCCESS";
|
|
107
159
|
ExportResultCode2[ExportResultCode2["FAILED"] = 1] = "FAILED";
|
|
@@ -113,6 +165,7 @@ var init_ExportResult = __esm({
|
|
|
113
165
|
var init_esm = __esm({
|
|
114
166
|
"node_modules/@opentelemetry/core/build/esm/index.js"() {
|
|
115
167
|
"use strict";
|
|
168
|
+
init_cjs_shims();
|
|
116
169
|
init_ExportResult();
|
|
117
170
|
}
|
|
118
171
|
});
|
|
@@ -122,6 +175,7 @@ var ExportOutcomeExporter;
|
|
|
122
175
|
var init_exportHealth = __esm({
|
|
123
176
|
"src/exportHealth.ts"() {
|
|
124
177
|
"use strict";
|
|
178
|
+
init_cjs_shims();
|
|
125
179
|
init_esm();
|
|
126
180
|
ExportOutcomeExporter = class {
|
|
127
181
|
constructor(inner, onFirstFailure = (m) => console.warn(m)) {
|
|
@@ -164,6 +218,134 @@ var init_exportHealth = __esm({
|
|
|
164
218
|
}
|
|
165
219
|
});
|
|
166
220
|
|
|
221
|
+
// src/heartbeat.ts
|
|
222
|
+
function httpTransport(url, headers) {
|
|
223
|
+
return async (payload, timeoutMs) => {
|
|
224
|
+
const response = await fetch(url, {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: { "content-type": "application/json", ...headers },
|
|
227
|
+
body: JSON.stringify(payload),
|
|
228
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
229
|
+
});
|
|
230
|
+
await response.body?.cancel().catch(() => {
|
|
231
|
+
});
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
throw new Error(`heartbeat POST ${url} returned ${response.status}`);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
var import_node_crypto, import_node_module, PAYLOAD_VERSION, OPEN_TRACES_CAP, DEFAULT_PING_TIMEOUT_MS, DEFAULT_FINAL_PING_TIMEOUT_MS, SDK_VERSION, OpenRootSpanTracker, HeartbeatSender;
|
|
238
|
+
var init_heartbeat = __esm({
|
|
239
|
+
"src/heartbeat.ts"() {
|
|
240
|
+
"use strict";
|
|
241
|
+
init_cjs_shims();
|
|
242
|
+
import_node_crypto = require("crypto");
|
|
243
|
+
import_node_module = require("module");
|
|
244
|
+
PAYLOAD_VERSION = 1;
|
|
245
|
+
OPEN_TRACES_CAP = 32;
|
|
246
|
+
DEFAULT_PING_TIMEOUT_MS = 3e3;
|
|
247
|
+
DEFAULT_FINAL_PING_TIMEOUT_MS = 1e3;
|
|
248
|
+
SDK_VERSION = (() => {
|
|
249
|
+
try {
|
|
250
|
+
const pkg = (0, import_node_module.createRequire)(importMetaUrl)("../package.json");
|
|
251
|
+
return pkg.version ?? "0.0.0";
|
|
252
|
+
} catch {
|
|
253
|
+
return "0.0.0";
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
256
|
+
OpenRootSpanTracker = class {
|
|
257
|
+
// trace id (32-hex) -> count of open root spans in that trace. A trace id
|
|
258
|
+
// normally has one root, but the count guards against duplicates.
|
|
259
|
+
open = /* @__PURE__ */ new Map();
|
|
260
|
+
onStart(span, _parentContext) {
|
|
261
|
+
if (span.parentSpanContext !== void 0) return;
|
|
262
|
+
const traceId = span.spanContext().traceId;
|
|
263
|
+
this.open.set(traceId, (this.open.get(traceId) ?? 0) + 1);
|
|
264
|
+
}
|
|
265
|
+
onEnd(span) {
|
|
266
|
+
if (span.parentSpanContext !== void 0) return;
|
|
267
|
+
const traceId = span.spanContext().traceId;
|
|
268
|
+
const count = (this.open.get(traceId) ?? 0) - 1;
|
|
269
|
+
if (count <= 0) {
|
|
270
|
+
this.open.delete(traceId);
|
|
271
|
+
} else {
|
|
272
|
+
this.open.set(traceId, count);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/** Trace ids of currently-open root spans, in the order they opened. */
|
|
276
|
+
openTraceIds() {
|
|
277
|
+
return [...this.open.keys()];
|
|
278
|
+
}
|
|
279
|
+
async forceFlush() {
|
|
280
|
+
}
|
|
281
|
+
async shutdown() {
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
HeartbeatSender = class {
|
|
285
|
+
/** Identity of one process lifetime, fresh per sender. */
|
|
286
|
+
instanceId = (0, import_node_crypto.randomUUID)();
|
|
287
|
+
agentName;
|
|
288
|
+
tracker;
|
|
289
|
+
intervalMs;
|
|
290
|
+
pingTimeoutMs;
|
|
291
|
+
finalPingTimeoutMs;
|
|
292
|
+
send;
|
|
293
|
+
timer;
|
|
294
|
+
stopped = false;
|
|
295
|
+
deliveryWarned = false;
|
|
296
|
+
constructor(opts) {
|
|
297
|
+
this.agentName = opts.agentName;
|
|
298
|
+
this.tracker = opts.tracker;
|
|
299
|
+
this.intervalMs = opts.intervalMs;
|
|
300
|
+
this.pingTimeoutMs = opts.pingTimeoutMs ?? DEFAULT_PING_TIMEOUT_MS;
|
|
301
|
+
this.finalPingTimeoutMs = opts.finalPingTimeoutMs ?? DEFAULT_FINAL_PING_TIMEOUT_MS;
|
|
302
|
+
this.send = opts.transport ?? httpTransport(opts.url, opts.headers);
|
|
303
|
+
}
|
|
304
|
+
/** Starts pinging: an immediate first ping, then every `intervalMs`. */
|
|
305
|
+
start() {
|
|
306
|
+
void this.ping(this.pingTimeoutMs, false);
|
|
307
|
+
const timer = setInterval(() => void this.ping(this.pingTimeoutMs, false), this.intervalMs);
|
|
308
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
309
|
+
this.timer = timer;
|
|
310
|
+
}
|
|
311
|
+
/** Stops the interval and sends the final `stopped: true` ping. Idempotent. */
|
|
312
|
+
async stop() {
|
|
313
|
+
if (this.stopped) return;
|
|
314
|
+
this.stopped = true;
|
|
315
|
+
if (this.timer !== void 0) clearInterval(this.timer);
|
|
316
|
+
await this.ping(this.finalPingTimeoutMs, true);
|
|
317
|
+
}
|
|
318
|
+
buildPayload(stopped) {
|
|
319
|
+
const openIds = this.tracker.openTraceIds();
|
|
320
|
+
const payload = {
|
|
321
|
+
v: PAYLOAD_VERSION,
|
|
322
|
+
instance_id: this.instanceId,
|
|
323
|
+
agent_name: this.agentName,
|
|
324
|
+
// RFC3339 UTC with millisecond precision and a Z suffix, already.
|
|
325
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
326
|
+
sdk_language: "typescript",
|
|
327
|
+
sdk_version: SDK_VERSION,
|
|
328
|
+
open_traces: openIds.slice(0, OPEN_TRACES_CAP),
|
|
329
|
+
open_trace_count: openIds.length
|
|
330
|
+
};
|
|
331
|
+
if (stopped) payload.stopped = true;
|
|
332
|
+
return payload;
|
|
333
|
+
}
|
|
334
|
+
async ping(timeoutMs, stopped) {
|
|
335
|
+
try {
|
|
336
|
+
await this.send(this.buildPayload(stopped), timeoutMs);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if (!this.deliveryWarned) {
|
|
339
|
+
this.deliveryWarned = true;
|
|
340
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
341
|
+
console.warn(`[rius] heartbeat delivery failed (${message}); further failures are silent.`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
167
349
|
// src/semconv.ts
|
|
168
350
|
function kindAttributes(kind) {
|
|
169
351
|
const attributes = { [OPENINFERENCE_SPAN_KIND]: kind };
|
|
@@ -171,10 +353,11 @@ function kindAttributes(kind) {
|
|
|
171
353
|
if (operation !== void 0) attributes[GEN_AI_OPERATION_NAME] = operation;
|
|
172
354
|
return attributes;
|
|
173
355
|
}
|
|
174
|
-
var TRACER_NAME, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_REQUEST_PREFIX, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_ATTRIBUTE_SUFFIXES;
|
|
356
|
+
var TRACER_NAME, OPENINFERENCE_SPAN_KIND, INPUT_VALUE, OUTPUT_VALUE, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_TOOL_NAME, GEN_AI_REQUEST_PREFIX, MCP_RESULT_TYPE, GEN_AI_FIRST_TOKEN_EVENT, SpanKind, OPERATION_BY_KIND, CONTENT_ATTRIBUTES, CONTENT_ATTRIBUTE_PREFIXES, CONTENT_ATTRIBUTE_SUFFIXES, GLASSFLOW_SPAN_PENDING, PENDING_IDENTITY_ATTRIBUTES, PENDING_IDENTITY_PREFIXES;
|
|
175
357
|
var init_semconv = __esm({
|
|
176
358
|
"src/semconv.ts"() {
|
|
177
359
|
"use strict";
|
|
360
|
+
init_cjs_shims();
|
|
178
361
|
TRACER_NAME = "glassflow";
|
|
179
362
|
OPENINFERENCE_SPAN_KIND = "openinference.span.kind";
|
|
180
363
|
INPUT_VALUE = "input.value";
|
|
@@ -239,6 +422,14 @@ var init_semconv = __esm({
|
|
|
239
422
|
".document.content",
|
|
240
423
|
".embedding.text"
|
|
241
424
|
];
|
|
425
|
+
GLASSFLOW_SPAN_PENDING = "glassflow.span.pending";
|
|
426
|
+
PENDING_IDENTITY_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
427
|
+
OPENINFERENCE_SPAN_KIND,
|
|
428
|
+
GEN_AI_OPERATION_NAME,
|
|
429
|
+
GEN_AI_PROVIDER_NAME,
|
|
430
|
+
GEN_AI_TOOL_NAME
|
|
431
|
+
]);
|
|
432
|
+
PENDING_IDENTITY_PREFIXES = [GEN_AI_REQUEST_PREFIX];
|
|
242
433
|
}
|
|
243
434
|
});
|
|
244
435
|
|
|
@@ -267,6 +458,7 @@ function toAttributeValue(value) {
|
|
|
267
458
|
var init_serde = __esm({
|
|
268
459
|
"src/serde.ts"() {
|
|
269
460
|
"use strict";
|
|
461
|
+
init_cjs_shims();
|
|
270
462
|
}
|
|
271
463
|
});
|
|
272
464
|
|
|
@@ -303,6 +495,7 @@ var import_api, Observation;
|
|
|
303
495
|
var init_spans = __esm({
|
|
304
496
|
"src/spans.ts"() {
|
|
305
497
|
"use strict";
|
|
498
|
+
init_cjs_shims();
|
|
306
499
|
import_api = require("@opentelemetry/api");
|
|
307
500
|
init_client();
|
|
308
501
|
init_semconv();
|
|
@@ -396,6 +589,7 @@ var INPUT_REQUIRED;
|
|
|
396
589
|
var init_instrumentationMcp = __esm({
|
|
397
590
|
"src/instrumentationMcp.ts"() {
|
|
398
591
|
"use strict";
|
|
592
|
+
init_cjs_shims();
|
|
399
593
|
init_semconv();
|
|
400
594
|
init_spans();
|
|
401
595
|
INPUT_REQUIRED = "input_required";
|
|
@@ -446,7 +640,7 @@ function warnBroken(subject, error) {
|
|
|
446
640
|
function cachedCjsExports(pkg, toPatchable) {
|
|
447
641
|
let cache;
|
|
448
642
|
try {
|
|
449
|
-
cache = (0,
|
|
643
|
+
cache = (0, import_node_module2.createRequire)((0, import_node_path.join)(process.cwd(), "noop.js")).cache;
|
|
450
644
|
} catch {
|
|
451
645
|
return void 0;
|
|
452
646
|
}
|
|
@@ -511,11 +705,12 @@ async function enableInstrumentations(sink, tracerProvider, names) {
|
|
|
511
705
|
}
|
|
512
706
|
return enabled;
|
|
513
707
|
}
|
|
514
|
-
var
|
|
708
|
+
var import_node_module2, import_node_path, import_instrumentation, dynamicImport, REGISTRY;
|
|
515
709
|
var init_instrumentation = __esm({
|
|
516
710
|
"src/instrumentation.ts"() {
|
|
517
711
|
"use strict";
|
|
518
|
-
|
|
712
|
+
init_cjs_shims();
|
|
713
|
+
import_node_module2 = require("module");
|
|
519
714
|
import_node_path = require("path");
|
|
520
715
|
import_instrumentation = require("@opentelemetry/instrumentation");
|
|
521
716
|
dynamicImport = new Function("s", "return import(s)");
|
|
@@ -611,6 +806,7 @@ var import_api2, EXCEPTION_EVENT_NAME, EXCEPTION_CONTENT_KEYS, MaskingSpanExport
|
|
|
611
806
|
var init_masking = __esm({
|
|
612
807
|
"src/masking.ts"() {
|
|
613
808
|
"use strict";
|
|
809
|
+
init_cjs_shims();
|
|
614
810
|
import_api2 = require("@opentelemetry/api");
|
|
615
811
|
init_semconv();
|
|
616
812
|
init_serde();
|
|
@@ -673,6 +869,93 @@ var init_masking = __esm({
|
|
|
673
869
|
}
|
|
674
870
|
});
|
|
675
871
|
|
|
872
|
+
// src/pending.ts
|
|
873
|
+
function identityAttributes(attributes) {
|
|
874
|
+
if (!attributes) return {};
|
|
875
|
+
const result = {};
|
|
876
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
877
|
+
if (PENDING_IDENTITY_ATTRIBUTES.has(key) || PENDING_IDENTITY_PREFIXES.some((p) => key.startsWith(p))) {
|
|
878
|
+
result[key] = value;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
function buildSnapshot(span) {
|
|
884
|
+
const attributes = identityAttributes(span.attributes);
|
|
885
|
+
attributes[GLASSFLOW_SPAN_PENDING] = true;
|
|
886
|
+
return {
|
|
887
|
+
name: span.name,
|
|
888
|
+
kind: span.kind,
|
|
889
|
+
spanContext: () => span.spanContext(),
|
|
890
|
+
parentSpanContext: span.parentSpanContext,
|
|
891
|
+
startTime: span.startTime,
|
|
892
|
+
endTime: span.startTime,
|
|
893
|
+
duration: [0, 0],
|
|
894
|
+
status: { code: import_api3.SpanStatusCode.UNSET },
|
|
895
|
+
attributes,
|
|
896
|
+
links: [],
|
|
897
|
+
events: [],
|
|
898
|
+
resource: span.resource,
|
|
899
|
+
instrumentationScope: span.instrumentationScope,
|
|
900
|
+
droppedAttributesCount: 0,
|
|
901
|
+
droppedEventsCount: 0,
|
|
902
|
+
droppedLinksCount: 0,
|
|
903
|
+
ended: true
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function spanKey(context) {
|
|
907
|
+
return `${context.traceId}:${context.spanId}`;
|
|
908
|
+
}
|
|
909
|
+
var import_api3, PendingSpanProcessor;
|
|
910
|
+
var init_pending = __esm({
|
|
911
|
+
"src/pending.ts"() {
|
|
912
|
+
"use strict";
|
|
913
|
+
init_cjs_shims();
|
|
914
|
+
import_api3 = require("@opentelemetry/api");
|
|
915
|
+
init_semconv();
|
|
916
|
+
PendingSpanProcessor = class {
|
|
917
|
+
delegate;
|
|
918
|
+
delayMs;
|
|
919
|
+
scheduled = /* @__PURE__ */ new Map();
|
|
920
|
+
constructor(delegate, opts) {
|
|
921
|
+
this.delegate = delegate;
|
|
922
|
+
this.delayMs = opts?.delayMs ?? 0;
|
|
923
|
+
}
|
|
924
|
+
onStart(span, _parentContext) {
|
|
925
|
+
if (!span.isRecording()) return;
|
|
926
|
+
const snapshot = buildSnapshot(span);
|
|
927
|
+
if (this.delayMs <= 0) {
|
|
928
|
+
this.delegate.onEnd(snapshot);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const key = spanKey(span.spanContext());
|
|
932
|
+
const timer = setTimeout(() => {
|
|
933
|
+
this.scheduled.delete(key);
|
|
934
|
+
try {
|
|
935
|
+
this.delegate.onEnd(snapshot);
|
|
936
|
+
} catch {
|
|
937
|
+
}
|
|
938
|
+
}, this.delayMs);
|
|
939
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
940
|
+
this.scheduled.set(key, timer);
|
|
941
|
+
}
|
|
942
|
+
onEnd(span) {
|
|
943
|
+
const key = spanKey(span.spanContext());
|
|
944
|
+
const timer = this.scheduled.get(key);
|
|
945
|
+
if (timer === void 0) return;
|
|
946
|
+
clearTimeout(timer);
|
|
947
|
+
this.scheduled.delete(key);
|
|
948
|
+
}
|
|
949
|
+
async shutdown() {
|
|
950
|
+
for (const timer of this.scheduled.values()) clearTimeout(timer);
|
|
951
|
+
this.scheduled.clear();
|
|
952
|
+
}
|
|
953
|
+
async forceFlush() {
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
|
|
676
959
|
// src/client.ts
|
|
677
960
|
function init(options = {}) {
|
|
678
961
|
if (globalClient !== void 0) {
|
|
@@ -683,18 +966,23 @@ function init(options = {}) {
|
|
|
683
966
|
}
|
|
684
967
|
const config = resolveConfig(options);
|
|
685
968
|
const processors = new DelegatingSpanProcessor();
|
|
969
|
+
const authHeaders = config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {};
|
|
686
970
|
let health;
|
|
687
971
|
if (!config.disabled) {
|
|
688
972
|
const base = options.spanExporter ?? new import_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
689
973
|
url: `${config.endpoint}/v1/traces`,
|
|
690
|
-
headers: config.apiKey ?
|
|
974
|
+
headers: config.apiKey ? authHeaders : void 0
|
|
691
975
|
});
|
|
692
976
|
health = new ExportOutcomeExporter(base);
|
|
693
977
|
const exporter = !config.captureContent || config.mask !== void 0 ? new MaskingSpanExporter(health, {
|
|
694
978
|
captureContent: config.captureContent,
|
|
695
979
|
mask: config.mask
|
|
696
980
|
}) : health;
|
|
697
|
-
|
|
981
|
+
const batch = new import_sdk_trace_base.BatchSpanProcessor(exporter);
|
|
982
|
+
if (config.partialSpans) {
|
|
983
|
+
processors.add(new PendingSpanProcessor(batch, { delayMs: config.partialSpansDelayMs }));
|
|
984
|
+
}
|
|
985
|
+
processors.add(batch);
|
|
698
986
|
}
|
|
699
987
|
const provider = new import_sdk_trace_node.NodeTracerProvider({
|
|
700
988
|
resource: (0, import_resources.resourceFromAttributes)({ [import_semantic_conventions.ATTR_SERVICE_NAME]: config.serviceName }),
|
|
@@ -708,17 +996,37 @@ function init(options = {}) {
|
|
|
708
996
|
});
|
|
709
997
|
const ready = config.disabled ? Promise.resolve([]) : enableInstrumentations(processors, provider).catch(() => []);
|
|
710
998
|
provider.register();
|
|
711
|
-
|
|
999
|
+
let heartbeat;
|
|
1000
|
+
if (config.heartbeat && !config.disabled) {
|
|
1001
|
+
const tracker = new OpenRootSpanTracker();
|
|
1002
|
+
processors.add(tracker);
|
|
1003
|
+
const sender = new HeartbeatSender({
|
|
1004
|
+
url: config.heartbeatEndpoint,
|
|
1005
|
+
headers: authHeaders,
|
|
1006
|
+
intervalMs: config.heartbeatIntervalMs,
|
|
1007
|
+
agentName: config.agentName,
|
|
1008
|
+
tracker,
|
|
1009
|
+
transport: options.heartbeatTransport
|
|
1010
|
+
});
|
|
1011
|
+
sender.start();
|
|
1012
|
+
const beforeExitHandler = () => {
|
|
1013
|
+
void sender.stop();
|
|
1014
|
+
};
|
|
1015
|
+
process.once("beforeExit", beforeExitHandler);
|
|
1016
|
+
heartbeat = { sender, beforeExitHandler };
|
|
1017
|
+
}
|
|
1018
|
+
globalClient = createClient({ provider, processors, health, ready, heartbeat });
|
|
712
1019
|
return globalClient;
|
|
713
1020
|
}
|
|
714
1021
|
function getTracer() {
|
|
715
|
-
return
|
|
1022
|
+
return import_api4.trace.getTracer(TRACER_NAME);
|
|
716
1023
|
}
|
|
717
|
-
var
|
|
1024
|
+
var import_api4, import_exporter_trace_otlp_http, import_resources, import_sdk_trace_base, import_sdk_trace_node, import_semantic_conventions, sinks, heartbeats, createClient, RiusClient, globalClient;
|
|
718
1025
|
var init_client = __esm({
|
|
719
1026
|
"src/client.ts"() {
|
|
720
1027
|
"use strict";
|
|
721
|
-
|
|
1028
|
+
init_cjs_shims();
|
|
1029
|
+
import_api4 = require("@opentelemetry/api");
|
|
722
1030
|
import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
723
1031
|
import_resources = require("@opentelemetry/resources");
|
|
724
1032
|
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
@@ -727,10 +1035,13 @@ var init_client = __esm({
|
|
|
727
1035
|
init_config();
|
|
728
1036
|
init_delegatingProcessor();
|
|
729
1037
|
init_exportHealth();
|
|
1038
|
+
init_heartbeat();
|
|
730
1039
|
init_instrumentation();
|
|
731
1040
|
init_masking();
|
|
1041
|
+
init_pending();
|
|
732
1042
|
init_semconv();
|
|
733
1043
|
sinks = /* @__PURE__ */ new WeakMap();
|
|
1044
|
+
heartbeats = /* @__PURE__ */ new WeakMap();
|
|
734
1045
|
RiusClient = class _RiusClient {
|
|
735
1046
|
provider;
|
|
736
1047
|
health;
|
|
@@ -741,6 +1052,7 @@ var init_client = __esm({
|
|
|
741
1052
|
this.health = parts.health;
|
|
742
1053
|
this.ready = parts.ready;
|
|
743
1054
|
sinks.set(this, parts.processors);
|
|
1055
|
+
if (parts.heartbeat) heartbeats.set(this, parts.heartbeat);
|
|
744
1056
|
}
|
|
745
1057
|
static {
|
|
746
1058
|
createClient = (parts) => new _RiusClient(parts);
|
|
@@ -753,14 +1065,25 @@ var init_client = __esm({
|
|
|
753
1065
|
/**
|
|
754
1066
|
* Drains and tears down the provider, then releases the global registration
|
|
755
1067
|
* so a later init() can reconfigure the SDK.
|
|
1068
|
+
*
|
|
1069
|
+
* The heartbeat's final `stopped: true` ping is sent before the provider
|
|
1070
|
+
* shuts down, so the backend hears "stopped" while the trace pipeline can
|
|
1071
|
+
* still export it. Idempotent: `sender.stop()` no-ops on a second call, and
|
|
1072
|
+
* the `beforeExit` listener is removed here so repeated init/shutdown
|
|
1073
|
+
* cycles never leak listeners.
|
|
756
1074
|
*/
|
|
757
1075
|
async shutdown() {
|
|
1076
|
+
const heartbeat = heartbeats.get(this);
|
|
1077
|
+
if (heartbeat) {
|
|
1078
|
+
process.removeListener("beforeExit", heartbeat.beforeExitHandler);
|
|
1079
|
+
await heartbeat.sender.stop();
|
|
1080
|
+
}
|
|
758
1081
|
try {
|
|
759
1082
|
await this.provider.shutdown();
|
|
760
1083
|
} finally {
|
|
761
1084
|
if (globalClient === this) {
|
|
762
1085
|
globalClient = void 0;
|
|
763
|
-
|
|
1086
|
+
import_api4.trace.disable();
|
|
764
1087
|
}
|
|
765
1088
|
}
|
|
766
1089
|
}
|
|
@@ -785,9 +1108,11 @@ __export(index_exports, {
|
|
|
785
1108
|
startSpan: () => startSpan
|
|
786
1109
|
});
|
|
787
1110
|
module.exports = __toCommonJS(index_exports);
|
|
1111
|
+
init_cjs_shims();
|
|
788
1112
|
init_client();
|
|
789
1113
|
|
|
790
1114
|
// src/generation.ts
|
|
1115
|
+
init_cjs_shims();
|
|
791
1116
|
init_client();
|
|
792
1117
|
init_semconv();
|
|
793
1118
|
init_serde();
|
|
@@ -875,6 +1200,7 @@ function startAsCurrentGeneration(name, optionsOrFn, maybeFn) {
|
|
|
875
1200
|
}
|
|
876
1201
|
|
|
877
1202
|
// src/observe.ts
|
|
1203
|
+
init_cjs_shims();
|
|
878
1204
|
init_spans();
|
|
879
1205
|
function observe(fn, options = {}) {
|
|
880
1206
|
const name = options.name ?? (fn.name || "anonymous");
|