@juspay/neurolink 13.5.0 → 13.6.0
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/CHANGELOG.md +2 -2
- package/dist/adapters/video/videoAnalyzer.d.ts +1 -0
- package/dist/adapters/video/videoAnalyzer.js +4 -0
- package/dist/autoresearch/index.d.ts +1 -0
- package/dist/autoresearch/index.js +4 -0
- package/dist/autoresearch/worker.js +4 -0
- package/dist/browser/neurolink.min.js +476 -476
- package/dist/cli/commands/observability.js +11 -4
- package/dist/cli/commands/telemetry.js +36 -6
- package/dist/cli/utils/formatters.d.ts +17 -0
- package/dist/cli/utils/formatters.js +39 -0
- package/dist/constants/langfuse.d.ts +17 -1
- package/dist/constants/langfuse.js +23 -1
- package/dist/constants/telemetry.d.ts +6 -0
- package/dist/constants/telemetry.js +6 -0
- package/dist/core/baseProvider.d.ts +3 -1
- package/dist/core/baseProvider.js +30 -8
- package/dist/core/geminiLoopAdapter.js +19 -0
- package/dist/core/loopEngine.js +10 -7
- package/dist/core/modules/StreamHandler.js +5 -4
- package/dist/core/modules/TelemetryHandler.d.ts +5 -1
- package/dist/core/modules/TelemetryHandler.js +16 -0
- package/dist/core/modules/ToolsManager.js +14 -3
- package/dist/core/nativeGenerateLoop.d.ts +2 -0
- package/dist/core/nativeGenerateLoop.js +35 -9
- package/dist/evaluation/index.d.ts +1 -0
- package/dist/evaluation/index.js +4 -0
- package/dist/evaluation/ragasEvaluator.js +1 -0
- package/dist/mcp/externalServerManager.js +6 -2
- package/dist/mcp/toolRegistry.js +3 -0
- package/dist/neurolink.d.ts +16 -8
- package/dist/neurolink.js +115 -61
- package/dist/observability/FEATURE-STATUS.md +0 -4
- package/dist/observability/exporterRegistry.d.ts +6 -1
- package/dist/observability/exporterRegistry.js +18 -0
- package/dist/observability/exporterSpanProcessor.d.ts +20 -6
- package/dist/observability/exporterSpanProcessor.js +166 -34
- package/dist/observability/exporters/baseExporter.d.ts +22 -3
- package/dist/observability/exporters/baseExporter.js +87 -5
- package/dist/observability/exporters/langfuseExporter.d.ts +29 -3
- package/dist/observability/exporters/langfuseExporter.js +251 -41
- package/dist/observability/langfuseMasking.d.ts +1 -1
- package/dist/observability/langfuseMasking.js +1 -1
- package/dist/observability/otlpEncoding.d.ts +18 -5
- package/dist/observability/otlpEncoding.js +69 -12
- package/dist/observability/utils/spanSerializer.d.ts +3 -2
- package/dist/observability/utils/spanSerializer.js +5 -3
- package/dist/processors/base/BaseFileProcessor.d.ts +1 -0
- package/dist/processors/base/BaseFileProcessor.js +4 -0
- package/dist/processors/index.d.ts +1 -0
- package/dist/processors/index.js +4 -0
- package/dist/processors/registry/ProcessorRegistry.d.ts +1 -0
- package/dist/processors/registry/ProcessorRegistry.js +5 -0
- package/dist/providers/amazonBedrock/client.js +7 -5
- package/dist/providers/amazonSagemaker.js +32 -12
- package/dist/providers/anthropic/client.js +1 -0
- package/dist/providers/anthropic/loopAdapter.js +7 -3
- package/dist/providers/googleAiStudio/client.js +2 -0
- package/dist/providers/googleVertex/client.d.ts +6 -5
- package/dist/providers/googleVertex/client.js +46 -19
- package/dist/providers/openaiChatCompletionsBase.js +29 -18
- package/dist/proxy/codexUsage.js +59 -2
- package/dist/proxy/proxyTracer.d.ts +25 -1
- package/dist/proxy/proxyTracer.js +146 -22
- package/dist/proxy/proxyTranslationEngine.js +4 -2
- package/dist/proxy/sseInterceptor.js +6 -4
- package/dist/rag/chunkers/BaseChunker.js +1 -0
- package/dist/rag/index.d.ts +1 -0
- package/dist/rag/index.js +4 -0
- package/dist/rag/ragIntegration.js +1 -0
- package/dist/rag/reranker/reranker.js +2 -0
- package/dist/rag/retrieval/vectorQueryTool.js +1 -0
- package/dist/server/routes/codexProxyRoutes.js +3 -1
- package/dist/server/utils/validation.d.ts +3 -3
- package/dist/services/server/ai/observability/instrumentation.d.ts +8 -7
- package/dist/services/server/ai/observability/instrumentation.js +217 -47
- package/dist/services/server/ai/observability/langfuseSetup.d.ts +19 -1
- package/dist/services/server/ai/observability/langfuseSetup.js +62 -4
- package/dist/services/server/ai/observability/otlpSetup.d.ts +12 -2
- package/dist/services/server/ai/observability/otlpSetup.js +55 -4
- package/dist/telemetry/aiMetrics.d.ts +56 -0
- package/dist/telemetry/aiMetrics.js +229 -0
- package/dist/telemetry/attributes.d.ts +24 -1
- package/dist/telemetry/attributes.js +94 -9
- package/dist/telemetry/chatMessages.d.ts +6 -0
- package/dist/telemetry/chatMessages.js +37 -0
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.js +1 -1
- package/dist/telemetry/observation.d.ts +5 -5
- package/dist/telemetry/observation.js +128 -39
- package/dist/telemetry/spanSanitizer.d.ts +2 -2
- package/dist/telemetry/spanSanitizer.js +76 -8
- package/dist/telemetry/telemetryStarter.d.ts +16 -0
- package/dist/telemetry/telemetryStarter.js +28 -0
- package/dist/telemetry/traceContext.d.ts +3 -3
- package/dist/telemetry/traceContext.js +3 -3
- package/dist/telemetry/tracers.d.ts +13 -1
- package/dist/telemetry/tracers.js +49 -10
- package/dist/telemetry/withSpan.d.ts +19 -3
- package/dist/telemetry/withSpan.js +149 -20
- package/dist/tools/toolDiscovery.js +2 -1
- package/dist/tools/toolPolicy.js +22 -2
- package/dist/types/config.d.ts +19 -0
- package/dist/types/exporter.d.ts +98 -3
- package/dist/types/generate.d.ts +7 -1
- package/dist/types/livekit.d.ts +16 -1
- package/dist/types/loopEngine.d.ts +16 -1
- package/dist/types/observability.d.ts +38 -5
- package/dist/types/proxy.d.ts +2 -0
- package/dist/types/span.d.ts +26 -0
- package/dist/types/toolResolution.d.ts +5 -1
- package/dist/utils/timeout.js +4 -2
- package/dist/voice/livekit/realtimeMcpTools.js +11 -2
- package/dist/voice/livekit/realtimeMemory.d.ts +20 -2
- package/dist/voice/livekit/realtimeMemory.js +139 -46
- package/dist/voice/livekit/realtimeVoiceAgent.js +20 -8
- package/dist/voice/livekit/turnRecorder.js +22 -9
- package/dist/workflow/core/ensembleExecutor.js +2 -0
- package/dist/workflow/core/judgeScorer.js +1 -0
- package/dist/workflow/core/responseConditioner.js +1 -0
- package/dist/workflow/core/workflowRunner.js +6 -1
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.js +4 -0
- package/docs-site/static/search-index.json +32 -27
- package/package.json +21 -2
|
@@ -1,23 +1,81 @@
|
|
|
1
|
-
import { DEFAULT_BASE_URL, DEFAULT_FLUSH_AT, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_TIMEOUT_MS, HEALTH_PATH, TRACES_PATH, } from "../../constants/langfuse.js";
|
|
1
|
+
import { DEFAULT_BASE_URL, DEFAULT_FLUSH_AT, DEFAULT_FLUSH_BUDGET_MS, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_TIMEOUT_MS, HEALTH_PATH, MAX_CONCURRENT_DELIVERIES, MAX_EXPORT_RETRY_AFTER_MS, MAX_QUEUED_SPANS, MAX_SPANS_PER_REQUEST, QUEUE_DROP_WARN_INTERVAL_MS, TRACES_PATH, } from "../../constants/langfuse.js";
|
|
2
2
|
import { logger } from "../../utils/logger.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { parseRetryAfterMs } from "../../utils/retryAfter.js";
|
|
4
|
+
import { chunkOtlpSpans, toOtlpEncodedSpan, toOtlpRequest, } from "../otlpEncoding.js";
|
|
5
|
+
import { BaseExporter, exponentialRetryDelayMs, exporterStatusOf, reportDeliveriesWith, } from "./baseExporter.js";
|
|
5
6
|
let credentialsWarned = false;
|
|
7
|
+
let malformedRejectionLogged = false;
|
|
6
8
|
function isRetryable(status) {
|
|
7
9
|
return status === 429 || status >= 500;
|
|
8
10
|
}
|
|
11
|
+
function messageOf(error) {
|
|
12
|
+
return error instanceof Error ? error.message : String(error);
|
|
13
|
+
}
|
|
14
|
+
function retryAfterOf(error) {
|
|
15
|
+
return error instanceof Error &&
|
|
16
|
+
"retryAfterMs" in error &&
|
|
17
|
+
typeof error.retryAfterMs === "number"
|
|
18
|
+
? error.retryAfterMs
|
|
19
|
+
: undefined;
|
|
20
|
+
}
|
|
21
|
+
// A flush or shutdown still ends at its budget: the delivery signal cuts the
|
|
22
|
+
// wait short.
|
|
23
|
+
function retryDelayFor(error, attempt) {
|
|
24
|
+
const requested = retryAfterOf(error);
|
|
25
|
+
return requested === undefined
|
|
26
|
+
? exponentialRetryDelayMs(error, attempt)
|
|
27
|
+
: Math.min(requested, MAX_EXPORT_RETRY_AFTER_MS);
|
|
28
|
+
}
|
|
29
|
+
function partialSuccessOf(body) {
|
|
30
|
+
return typeof body === "object" &&
|
|
31
|
+
body !== null &&
|
|
32
|
+
"partialSuccess" in body &&
|
|
33
|
+
typeof body.partialSuccess === "object" &&
|
|
34
|
+
body.partialSuccess !== null
|
|
35
|
+
? body.partialSuccess
|
|
36
|
+
: undefined;
|
|
37
|
+
}
|
|
38
|
+
// A count Langfuse cannot have meant (not a whole number, negative, or more
|
|
39
|
+
// than was sent) is read as none, so it neither hides earlier drops nor
|
|
40
|
+
// invents new ones.
|
|
41
|
+
function rejectedCount(partial, sent) {
|
|
42
|
+
const raw = partial && "rejectedSpans" in partial ? partial.rejectedSpans : undefined;
|
|
43
|
+
if (raw === undefined) {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
const count = typeof raw === "number" || typeof raw === "string"
|
|
47
|
+
? Number(raw)
|
|
48
|
+
: Number.NaN;
|
|
49
|
+
if (Number.isInteger(count) && count >= 0 && count <= sent) {
|
|
50
|
+
return count;
|
|
51
|
+
}
|
|
52
|
+
if (!malformedRejectionLogged) {
|
|
53
|
+
malformedRejectionLogged = true;
|
|
54
|
+
logger.debug("[langfuse] Ignoring a partialSuccess.rejectedSpans that is not a count of the spans sent", { sent });
|
|
55
|
+
}
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
9
58
|
/**
|
|
10
|
-
* @description Exporter that delivers observation spans to Langfuse's OTLP/HTTP JSON endpoint (`POST /api/public/otel/v1/traces`), which keeps native observation types and identity on every observation. Spans are queued without blocking and sent in batches;
|
|
59
|
+
* @description Exporter that delivers observation spans to Langfuse's OTLP/HTTP JSON endpoint (`POST /api/public/otel/v1/traces`), which keeps native observation types and identity on every observation. Spans are queued without blocking and sent in batches, with at most `MAX_CONCURRENT_DELIVERIES` requests in flight; spans that end while every one is busy wait, at most `MAX_QUEUED_SPANS` of them, and the oldest beyond that are dropped, counted and warned about once a minute. Failed requests are retried with backoff when the failure is transient (429, 5xx, network), after the `Retry-After` of a 429 or 503 when it names one (at most `MAX_EXPORT_RETRY_AFTER_MS`). `flush()` and `shutdown()` wait at most `flushBudgetMs` and then drop what is still in flight, and a batch that could not be delivered is reported by `exportBatch`, logged at warn level and counted in the health check; when Langfuse took none of the latest batch (refused, failed or every span rejected), `getStatus()` and `healthCheck()` read unhealthy with the dropped count until a batch is accepted again. `exportSpan` reports only the buffer step, so a failed delivery never opens the registry's circuit breaker and spans that end after an outage are still buffered and sent.
|
|
11
60
|
*/
|
|
12
61
|
export class LangfuseExporter extends BaseExporter {
|
|
13
62
|
langfuse;
|
|
14
63
|
authorization;
|
|
15
64
|
baseUrl;
|
|
16
65
|
policy;
|
|
66
|
+
flushBudgetMs;
|
|
17
67
|
inflight = new Set();
|
|
68
|
+
delivery = new AbortController();
|
|
69
|
+
droppedSpans = 0;
|
|
70
|
+
// Counted when Langfuse accepts a batch; the base counter also counts a span
|
|
71
|
+
// queued by exportSpan, which is not a delivery.
|
|
72
|
+
acceptedSpans = 0;
|
|
73
|
+
lastDeliveryError;
|
|
74
|
+
unwarnedQueueDrops = 0;
|
|
75
|
+
lastQueueDropWarning = 0;
|
|
18
76
|
/**
|
|
19
77
|
* @description Creates an exporter for one Langfuse project.
|
|
20
|
-
* @param config {LangfuseExporterConfig} - Credentials, endpoint, environment, release, masking and
|
|
78
|
+
* @param config {LangfuseExporterConfig} - Credentials, endpoint, environment, release, masking, batching and flush-budget options. `environment` and `release` fall back to `LANGFUSE_TRACING_ENVIRONMENT` and `LANGFUSE_RELEASE`, and only fill spans that do not carry them already.
|
|
21
79
|
*/
|
|
22
80
|
constructor(langfuse) {
|
|
23
81
|
super("langfuse", {
|
|
@@ -27,12 +85,21 @@ export class LangfuseExporter extends BaseExporter {
|
|
|
27
85
|
this.langfuse = langfuse;
|
|
28
86
|
this.authorization = `Basic ${Buffer.from(`${langfuse.publicKey}:${langfuse.secretKey}`).toString("base64")}`;
|
|
29
87
|
this.baseUrl = (langfuse.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
88
|
+
this.flushBudgetMs =
|
|
89
|
+
langfuse.flushBudgetMs !== undefined && langfuse.flushBudgetMs > 0
|
|
90
|
+
? langfuse.flushBudgetMs
|
|
91
|
+
: DEFAULT_FLUSH_BUDGET_MS;
|
|
30
92
|
this.policy = {
|
|
31
93
|
environment: langfuse.environment ?? process.env.LANGFUSE_TRACING_ENVIRONMENT,
|
|
32
94
|
release: langfuse.release ?? process.env.LANGFUSE_RELEASE,
|
|
33
95
|
redactIO: langfuse.redactIO ?? false,
|
|
34
96
|
mask: langfuse.mask,
|
|
35
97
|
};
|
|
98
|
+
reportDeliveriesWith(this, () => ({
|
|
99
|
+
delivered: this.acceptedSpans,
|
|
100
|
+
failed: this.droppedSpans,
|
|
101
|
+
lastError: this.lastDeliveryError,
|
|
102
|
+
}));
|
|
36
103
|
}
|
|
37
104
|
async initialize() {
|
|
38
105
|
if (this.initialized) {
|
|
@@ -42,8 +109,10 @@ export class LangfuseExporter extends BaseExporter {
|
|
|
42
109
|
throw new Error("Langfuse publicKey and secretKey are required");
|
|
43
110
|
}
|
|
44
111
|
this.initialized = true;
|
|
45
|
-
|
|
46
|
-
|
|
112
|
+
// Periodic and size-triggered sends are not subject to the flush budget:
|
|
113
|
+
// only a caller that waits for delivery is.
|
|
114
|
+
this.flushInterval = setInterval(() => this.dispatch(), this.config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS);
|
|
115
|
+
this.flushInterval.unref();
|
|
47
116
|
}
|
|
48
117
|
async exportSpan(span) {
|
|
49
118
|
this.bufferSpan(span);
|
|
@@ -51,41 +120,132 @@ export class LangfuseExporter extends BaseExporter {
|
|
|
51
120
|
}
|
|
52
121
|
async exportBatch(spans) {
|
|
53
122
|
const startTime = Date.now();
|
|
54
|
-
await this.deliver(spans.map((span) =>
|
|
55
|
-
return this.
|
|
123
|
+
const report = await this.deliver(spans.map((span) => toOtlpEncodedSpan(span, this.policy)), this.delivery.signal);
|
|
124
|
+
return this.record(spans.length, report, Date.now() - startTime);
|
|
56
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* @description Sends everything buffered and waits for delivery, but no longer than the flush budget; spans still in flight when it runs out are dropped and counted, and spans still waiting for a delivery slot stay buffered.
|
|
128
|
+
* @returns {Promise<void>} Resolves when delivery finished or the budget ran out.
|
|
129
|
+
*/
|
|
57
130
|
async flush() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
131
|
+
await this.settle();
|
|
132
|
+
}
|
|
133
|
+
bufferSpan(span) {
|
|
134
|
+
this.buffer.push(span);
|
|
135
|
+
if (this.buffer.length >= this.maxBufferSize ||
|
|
136
|
+
this.buffer.length > MAX_QUEUED_SPANS) {
|
|
137
|
+
this.dispatch();
|
|
138
|
+
}
|
|
139
|
+
if (this.buffer.length > MAX_QUEUED_SPANS) {
|
|
140
|
+
this.dropOldestWaiting(this.buffer.length - MAX_QUEUED_SPANS);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
dispatch() {
|
|
144
|
+
while (this.buffer.length > 0 &&
|
|
145
|
+
this.inflight.size < MAX_CONCURRENT_DELIVERIES) {
|
|
146
|
+
const spans = this.buffer.slice(0, MAX_SPANS_PER_REQUEST);
|
|
147
|
+
this.buffer = this.buffer.slice(spans.length);
|
|
61
148
|
const pending = this.exportQuietly(spans);
|
|
62
149
|
this.inflight.add(pending);
|
|
63
150
|
void pending.finally(() => this.inflight.delete(pending));
|
|
64
151
|
}
|
|
65
|
-
|
|
152
|
+
}
|
|
153
|
+
// Langfuse is not taking spans as fast as they end. Spans keep being
|
|
154
|
+
// accepted, so the oldest waiting ones make room; no breaker is involved.
|
|
155
|
+
dropOldestWaiting(count) {
|
|
156
|
+
this.buffer = this.buffer.slice(count);
|
|
157
|
+
this.droppedSpans += count;
|
|
158
|
+
this.unwarnedQueueDrops += count;
|
|
159
|
+
this.lastDeliveryError = `${MAX_CONCURRENT_DELIVERIES} deliveries in flight and ${MAX_QUEUED_SPANS} spans waiting; dropping the oldest`;
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
if (now - this.lastQueueDropWarning >= QUEUE_DROP_WARN_INTERVAL_MS) {
|
|
162
|
+
logger.warn(`[langfuse] Langfuse is not keeping up; dropped the ${this.unwarnedQueueDrops} oldest waiting spans`);
|
|
163
|
+
this.lastQueueDropWarning = now;
|
|
164
|
+
this.unwarnedQueueDrops = 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Deliveries free their slot for the next waiting spans until nothing is
|
|
168
|
+
// left or the budget runs out, so spans that end meanwhile are sent too.
|
|
169
|
+
async settle() {
|
|
170
|
+
const deadline = Date.now() + this.flushBudgetMs;
|
|
171
|
+
for (this.dispatch(); this.inflight.size > 0; this.dispatch()) {
|
|
172
|
+
if ((await this.nextDelivery(deadline)) === "expired") {
|
|
173
|
+
logger.warn(`[langfuse] Delivery did not finish within ${this.flushBudgetMs}ms; dropping what is still in flight`);
|
|
174
|
+
const abandoned = this.delivery;
|
|
175
|
+
this.delivery = new AbortController();
|
|
176
|
+
abandoned.abort(new Error("Langfuse flush budget exhausted"));
|
|
177
|
+
await Promise.all(this.inflight);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async nextDelivery(deadline) {
|
|
183
|
+
let timer;
|
|
184
|
+
const expired = new Promise((resolve) => {
|
|
185
|
+
timer = setTimeout(() => resolve("expired"), Math.max(0, deadline - Date.now()));
|
|
186
|
+
});
|
|
187
|
+
const outcome = await Promise.race([
|
|
188
|
+
Promise.race(this.inflight).then(() => "delivered"),
|
|
189
|
+
expired,
|
|
190
|
+
]);
|
|
191
|
+
clearTimeout(timer);
|
|
192
|
+
return outcome;
|
|
66
193
|
}
|
|
67
194
|
async exportQuietly(spans) {
|
|
68
195
|
try {
|
|
69
196
|
await this.exportBatch(spans);
|
|
70
197
|
}
|
|
71
198
|
catch (error) {
|
|
199
|
+
this.droppedSpans += spans.length;
|
|
200
|
+
this.lastDeliveryError = messageOf(error);
|
|
72
201
|
logger.warn("[langfuse] Export failed", {
|
|
73
|
-
error:
|
|
202
|
+
error: this.lastDeliveryError,
|
|
74
203
|
});
|
|
75
204
|
}
|
|
76
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* @description Delivers what is buffered, including spans that end while it drains, within the flush budget, then stops the exporter. Spans that could not be sent by then are dropped and counted, so none is left pending.
|
|
208
|
+
* @returns {Promise<void>} Resolves when the exporter has stopped.
|
|
209
|
+
*/
|
|
77
210
|
async shutdown() {
|
|
78
|
-
await this.
|
|
211
|
+
await this.settle();
|
|
79
212
|
this.stopFlushInterval();
|
|
80
213
|
this.initialized = false;
|
|
214
|
+
const stranded = this.buffer.length;
|
|
215
|
+
if (stranded > 0) {
|
|
216
|
+
this.buffer = [];
|
|
217
|
+
this.droppedSpans += stranded;
|
|
218
|
+
this.lastDeliveryError = "Langfuse exporter shut down before delivery";
|
|
219
|
+
logger.warn(`[langfuse] Dropped ${stranded} spans still waiting at shutdown`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* @description Snapshot of this exporter for status reporting, without a network call: what is queued, what Langfuse accepted and what was dropped, and unhealthy while the latest delivery failed.
|
|
224
|
+
* @returns {ExporterStatus} The exporter's current state.
|
|
225
|
+
*/
|
|
226
|
+
getStatus() {
|
|
227
|
+
return exporterStatusOf(this);
|
|
81
228
|
}
|
|
229
|
+
// Reachable is not enough: like getStatus(), the exporter reads unhealthy
|
|
230
|
+
// while its latest delivery failed, until Langfuse accepts a batch again.
|
|
82
231
|
async healthCheck() {
|
|
232
|
+
const problems = [
|
|
233
|
+
...(this.lastDeliveryError !== undefined ? [this.lastDeliveryError] : []),
|
|
234
|
+
...(this.droppedSpans > 0
|
|
235
|
+
? [
|
|
236
|
+
`${this.droppedSpans} ${this.droppedSpans === 1 ? "span" : "spans"} dropped since start`,
|
|
237
|
+
]
|
|
238
|
+
: []),
|
|
239
|
+
];
|
|
83
240
|
try {
|
|
84
241
|
await this.withRetry(() => this.ping(), "health check");
|
|
85
|
-
return this.createHealthStatus(
|
|
242
|
+
return this.createHealthStatus(this.lastDeliveryError === undefined, problems.length > 0 ? problems : undefined);
|
|
86
243
|
}
|
|
87
244
|
catch {
|
|
88
|
-
return this.createHealthStatus(false, [
|
|
245
|
+
return this.createHealthStatus(false, [
|
|
246
|
+
"Health check failed",
|
|
247
|
+
...problems,
|
|
248
|
+
]);
|
|
89
249
|
}
|
|
90
250
|
}
|
|
91
251
|
async ping() {
|
|
@@ -97,19 +257,67 @@ export class LangfuseExporter extends BaseExporter {
|
|
|
97
257
|
throw new Error(`Langfuse API unreachable: ${response.status}`);
|
|
98
258
|
}
|
|
99
259
|
}
|
|
100
|
-
|
|
101
|
-
|
|
260
|
+
record(total, report, durationMs) {
|
|
261
|
+
if (total === 0) {
|
|
262
|
+
return this.createSuccessResult(0, durationMs);
|
|
263
|
+
}
|
|
264
|
+
const failed = report.droppedIds.length + report.rejected;
|
|
265
|
+
this.droppedSpans += failed;
|
|
266
|
+
// Langfuse took none of the batch, refused or rejected: that is a failed
|
|
267
|
+
// delivery even when the response was a 200 with partialSuccess.
|
|
268
|
+
this.lastDeliveryError =
|
|
269
|
+
report.delivered === 0 && failed > 0
|
|
270
|
+
? `Dropped ${failed} of ${total} spans: ${report.error ?? "Langfuse rejected every span"}`
|
|
271
|
+
: report.error;
|
|
272
|
+
if (failed > 0) {
|
|
273
|
+
logger.warn(`[langfuse] Dropped ${failed} of ${total} spans`, {
|
|
274
|
+
error: report.error,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (report.delivered > 0) {
|
|
278
|
+
this.lastExportTime = Date.now();
|
|
279
|
+
this.acceptedSpans += report.delivered;
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
success: failed === 0,
|
|
283
|
+
exportedCount: report.delivered,
|
|
284
|
+
failedCount: failed,
|
|
285
|
+
...(report.droppedIds.length > 0 && {
|
|
286
|
+
errors: report.droppedIds.map((spanId) => ({
|
|
287
|
+
spanId,
|
|
288
|
+
error: report.error ?? "Delivery failed",
|
|
289
|
+
retryable: true,
|
|
290
|
+
})),
|
|
291
|
+
}),
|
|
292
|
+
durationMs,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
async deliver(items, signal) {
|
|
296
|
+
const report = {
|
|
297
|
+
delivered: 0,
|
|
298
|
+
rejected: 0,
|
|
299
|
+
droppedIds: [],
|
|
300
|
+
};
|
|
301
|
+
for (const chunk of chunkOtlpSpans(items)) {
|
|
102
302
|
try {
|
|
103
|
-
await this.withRetry(() => this.post(chunk), "export");
|
|
303
|
+
const outcome = await this.withRetry(() => this.post(chunk, signal), "export", signal, retryDelayFor);
|
|
304
|
+
if (outcome.accepted) {
|
|
305
|
+
report.delivered += chunk.length - outcome.rejected;
|
|
306
|
+
report.rejected += outcome.rejected;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
report.droppedIds.push(...chunk.map(({ span }) => span.spanId));
|
|
310
|
+
report.error = "Langfuse did not accept the batch";
|
|
311
|
+
}
|
|
104
312
|
}
|
|
105
313
|
catch (error) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
});
|
|
314
|
+
report.droppedIds.push(...chunk.map(({ span }) => span.spanId));
|
|
315
|
+
report.error = messageOf(error);
|
|
109
316
|
}
|
|
110
317
|
}
|
|
318
|
+
return report;
|
|
111
319
|
}
|
|
112
|
-
async post(
|
|
320
|
+
async post(chunk, signal) {
|
|
113
321
|
const response = await fetch(`${this.baseUrl}${TRACES_PATH}`, {
|
|
114
322
|
method: "POST",
|
|
115
323
|
headers: {
|
|
@@ -119,38 +327,40 @@ export class LangfuseExporter extends BaseExporter {
|
|
|
119
327
|
"x-langfuse-ingestion-version": "4",
|
|
120
328
|
"x-langfuse-public-key": this.langfuse.publicKey,
|
|
121
329
|
},
|
|
122
|
-
body: JSON.stringify(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
},
|
|
128
|
-
scopeSpans: [{ scope: { name: "sdk" }, spans }],
|
|
129
|
-
},
|
|
130
|
-
],
|
|
131
|
-
}),
|
|
132
|
-
signal: AbortSignal.timeout(this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
330
|
+
body: JSON.stringify(toOtlpRequest(chunk)),
|
|
331
|
+
signal: AbortSignal.any([
|
|
332
|
+
AbortSignal.timeout(this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
333
|
+
signal,
|
|
334
|
+
]),
|
|
133
335
|
});
|
|
134
336
|
if (response.status === 401 || response.status === 403) {
|
|
135
337
|
if (!credentialsWarned) {
|
|
136
338
|
credentialsWarned = true;
|
|
137
339
|
logger.warn("[langfuse] Langfuse credentials rejected");
|
|
138
340
|
}
|
|
139
|
-
return;
|
|
341
|
+
return { accepted: false };
|
|
140
342
|
}
|
|
141
343
|
if (isRetryable(response.status)) {
|
|
142
|
-
|
|
344
|
+
const retryAfterMs = response.status === 429 || response.status === 503
|
|
345
|
+
? parseRetryAfterMs(response.headers)
|
|
346
|
+
: undefined;
|
|
347
|
+
throw Object.assign(new Error(`Langfuse API error: ${response.status}`), retryAfterMs === undefined ? {} : { retryAfterMs });
|
|
143
348
|
}
|
|
144
349
|
if (!response.ok) {
|
|
145
350
|
logger.warn(`[langfuse] Export rejected with ${response.status}`);
|
|
146
|
-
return;
|
|
351
|
+
return { accepted: false };
|
|
147
352
|
}
|
|
148
|
-
|
|
149
|
-
|
|
353
|
+
// Langfuse took the request; a body that is not an object, or not JSON,
|
|
354
|
+
// reports no rejections.
|
|
355
|
+
const partial = partialSuccessOf(await response.json().catch(() => undefined));
|
|
356
|
+
const rejected = rejectedCount(partial, chunk.length);
|
|
150
357
|
if (rejected > 0) {
|
|
151
358
|
logger.warn(`[langfuse] ${rejected} spans rejected`, {
|
|
152
|
-
message:
|
|
359
|
+
message: partial && "errorMessage" in partial
|
|
360
|
+
? partial.errorMessage
|
|
361
|
+
: undefined,
|
|
153
362
|
});
|
|
154
363
|
}
|
|
364
|
+
return { accepted: true, rejected };
|
|
155
365
|
}
|
|
156
366
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LangfuseAttributePolicy, SpanAttributes } from "../types/index.js";
|
|
2
2
|
/**
|
|
3
|
-
* @description Applies the exporter's policy to a span's attributes: fills environment and release when the span has none, removes input and output when `redactIO` is set, and masks input, output and metadata when a mask is configured.
|
|
3
|
+
* @description Applies the Langfuse exporter's policy to a span's attributes as they are sent to Langfuse: fills environment and release when the span has none, removes input and output when `redactIO` is set, and masks input, output and metadata when a mask is configured. Nothing else is touched: other attributes, events and the status message go to Langfuse as they are, and no other exporter sees the policy.
|
|
4
4
|
* @param attributes {SpanAttributes} - Attributes of the span being exported.
|
|
5
5
|
* @param policy {LangfuseAttributePolicy} - Environment, release, redaction and mask settings.
|
|
6
6
|
* @returns {SpanAttributes} A new attribute record; the input is not modified.
|
|
@@ -18,7 +18,7 @@ function maskValue(value, mask) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* @description Applies the exporter's policy to a span's attributes: fills environment and release when the span has none, removes input and output when `redactIO` is set, and masks input, output and metadata when a mask is configured.
|
|
21
|
+
* @description Applies the Langfuse exporter's policy to a span's attributes as they are sent to Langfuse: fills environment and release when the span has none, removes input and output when `redactIO` is set, and masks input, output and metadata when a mask is configured. Nothing else is touched: other attributes, events and the status message go to Langfuse as they are, and no other exporter sees the policy.
|
|
22
22
|
* @param attributes {SpanAttributes} - Attributes of the span being exported.
|
|
23
23
|
* @param policy {LangfuseAttributePolicy} - Environment, release, redaction and mask settings.
|
|
24
24
|
* @returns {SpanAttributes} A new attribute record; the input is not modified.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LangfuseAttributePolicy, OtlpKeyValue, OtlpSpan, SpanAttributes, SpanData } from "../types/index.js";
|
|
1
|
+
import type { LangfuseAttributePolicy, OtlpEncodedSpan, OtlpKeyValue, OtlpSpan, OtlpTraceRequest, SpanAttributes, SpanData } from "../types/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* @description Encodes attributes as OTLP/JSON key-values, dropping `undefined` and `null` entries.
|
|
4
4
|
* @param attributes {SpanAttributes} - Attribute record to encode.
|
|
@@ -6,15 +6,28 @@ import type { LangfuseAttributePolicy, OtlpKeyValue, OtlpSpan, SpanAttributes, S
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function toOtlpAttributes(attributes: SpanAttributes): OtlpKeyValue[];
|
|
8
8
|
/**
|
|
9
|
-
* @description Encodes one span as OTLP/JSON, applying the exporter's attribute policy to its attributes.
|
|
9
|
+
* @description Encodes one span as OTLP/JSON, applying the exporter's attribute policy to its attributes (its events and status message are sent as they are), leaving GenAI model and usage keys off a plain span or event observation, and keeping the span's links and nanosecond times. Every span is sent as kind INTERNAL, as Langfuse has always received it.
|
|
10
10
|
* @param span {SpanData} - Span to encode.
|
|
11
11
|
* @param policy {LangfuseAttributePolicy} - Environment, release, redaction and mask settings.
|
|
12
12
|
* @returns {OtlpSpan} The span in the shape Langfuse's ingestion endpoint reads.
|
|
13
13
|
*/
|
|
14
14
|
export declare function toOtlpSpan(span: SpanData, policy: LangfuseAttributePolicy): OtlpSpan;
|
|
15
|
+
/**
|
|
16
|
+
* @description Encodes a span together with the resource it was produced under, falling back to a generic service name for a span that carries none, and the instrumentation scope `sdk` that the Langfuse request has always named.
|
|
17
|
+
* @param span {SpanData} - Span to encode.
|
|
18
|
+
* @param policy {LangfuseAttributePolicy} - Environment, release, redaction and mask settings.
|
|
19
|
+
* @returns {OtlpEncodedSpan} The encoded span with its resource and scope.
|
|
20
|
+
*/
|
|
21
|
+
export declare function toOtlpEncodedSpan(span: SpanData, policy: LangfuseAttributePolicy): OtlpEncodedSpan;
|
|
22
|
+
/**
|
|
23
|
+
* @description Builds an OTLP/JSON export request, grouping spans under the resource and instrumentation scope they were produced under.
|
|
24
|
+
* @param items {OtlpEncodedSpan[]} - Encoded spans, in export order.
|
|
25
|
+
* @returns {OtlpTraceRequest} The request body, with one `resourceSpans` entry per distinct resource and one `scopeSpans` entry per distinct scope within it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function toOtlpRequest(items: OtlpEncodedSpan[]): OtlpTraceRequest;
|
|
15
28
|
/**
|
|
16
29
|
* @description Splits encoded spans into request-sized batches, bounded by span count and serialized bytes.
|
|
17
|
-
* @param
|
|
18
|
-
* @returns {
|
|
30
|
+
* @param items {OtlpEncodedSpan[]} - Encoded spans, in export order.
|
|
31
|
+
* @returns {OtlpEncodedSpan[][]} Batches that each fit one export request; a single oversized span still gets its own batch.
|
|
19
32
|
*/
|
|
20
|
-
export declare function chunkOtlpSpans(
|
|
33
|
+
export declare function chunkOtlpSpans(items: OtlpEncodedSpan[]): OtlpEncodedSpan[][];
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LangfuseOtelSpanAttributes } from "@langfuse/tracing";
|
|
2
|
+
import { MAX_BYTES_PER_REQUEST, MAX_SPANS_PER_REQUEST, MODEL_ATTRIBUTE_KEYS, OTLP_STATUS_CODES, UNPRICED_OBSERVATION_TYPES, USAGE_ATTRIBUTE_PREFIX, } from "../constants/langfuse.js";
|
|
2
3
|
import { SpanStatus } from "../types/index.js";
|
|
3
4
|
import { protectAttributes } from "./langfuseMasking.js";
|
|
4
5
|
const SPAN_KIND_INTERNAL = 1;
|
|
6
|
+
const FALLBACK_SERVICE_NAME = "service";
|
|
7
|
+
const SCOPE_NAME = "sdk";
|
|
5
8
|
function toNanos(iso) {
|
|
6
9
|
return (BigInt(Date.parse(iso)) * 1000000n).toString();
|
|
7
10
|
}
|
|
@@ -29,8 +32,18 @@ export function toOtlpAttributes(attributes) {
|
|
|
29
32
|
.filter(([, value]) => value !== undefined && value !== null)
|
|
30
33
|
.map(([key, value]) => ({ key, value: toOtlpValue(value) }));
|
|
31
34
|
}
|
|
35
|
+
// The proxy root also carries gen_ai model and usage keys for collectors;
|
|
36
|
+
// Langfuse would price it a second time next to the request's generation.
|
|
37
|
+
function withoutUnpricedUsage(attributes) {
|
|
38
|
+
const type = attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE];
|
|
39
|
+
if (typeof type !== "string" || !UNPRICED_OBSERVATION_TYPES.has(type)) {
|
|
40
|
+
return attributes;
|
|
41
|
+
}
|
|
42
|
+
return Object.fromEntries(Object.entries(attributes).filter(([key]) => !MODEL_ATTRIBUTE_KEYS.has(key) &&
|
|
43
|
+
!key.startsWith(USAGE_ATTRIBUTE_PREFIX)));
|
|
44
|
+
}
|
|
32
45
|
/**
|
|
33
|
-
* @description Encodes one span as OTLP/JSON, applying the exporter's attribute policy to its attributes.
|
|
46
|
+
* @description Encodes one span as OTLP/JSON, applying the exporter's attribute policy to its attributes (its events and status message are sent as they are), leaving GenAI model and usage keys off a plain span or event observation, and keeping the span's links and nanosecond times. Every span is sent as kind INTERNAL, as Langfuse has always received it.
|
|
34
47
|
* @param span {SpanData} - Span to encode.
|
|
35
48
|
* @param policy {LangfuseAttributePolicy} - Environment, release, redaction and mask settings.
|
|
36
49
|
* @returns {OtlpSpan} The span in the shape Langfuse's ingestion endpoint reads.
|
|
@@ -43,31 +56,75 @@ export function toOtlpSpan(span, policy) {
|
|
|
43
56
|
...(span.parentSpanId && { parentSpanId: span.parentSpanId }),
|
|
44
57
|
name: span.name,
|
|
45
58
|
kind: SPAN_KIND_INTERNAL,
|
|
46
|
-
startTimeUnixNano: toNanos(span.startTime),
|
|
47
|
-
endTimeUnixNano: toNanos(span.endTime ?? span.startTime),
|
|
48
|
-
attributes: toOtlpAttributes(protectAttributes(span.attributes, policy)),
|
|
59
|
+
startTimeUnixNano: span.startTimeUnixNano ?? toNanos(span.startTime),
|
|
60
|
+
endTimeUnixNano: span.endTimeUnixNano ?? toNanos(span.endTime ?? span.startTime),
|
|
61
|
+
attributes: toOtlpAttributes(protectAttributes(withoutUnpricedUsage(span.attributes), policy)),
|
|
49
62
|
events: span.events.map((event) => ({
|
|
50
63
|
name: event.name,
|
|
51
|
-
timeUnixNano: toNanos(event.timestamp),
|
|
64
|
+
timeUnixNano: event.timestampUnixNano ?? toNanos(event.timestamp),
|
|
52
65
|
attributes: toOtlpAttributes(event.attributes ?? {}),
|
|
53
66
|
})),
|
|
67
|
+
links: span.links.map((link) => ({
|
|
68
|
+
traceId: link.traceId,
|
|
69
|
+
spanId: link.spanId,
|
|
70
|
+
attributes: toOtlpAttributes(link.attributes ?? {}),
|
|
71
|
+
})),
|
|
54
72
|
status: {
|
|
55
73
|
code: OTLP_STATUS_CODES[span.status],
|
|
56
74
|
...(failed && span.statusMessage && { message: span.statusMessage }),
|
|
57
75
|
},
|
|
58
76
|
};
|
|
59
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* @description Encodes a span together with the resource it was produced under, falling back to a generic service name for a span that carries none, and the instrumentation scope `sdk` that the Langfuse request has always named.
|
|
80
|
+
* @param span {SpanData} - Span to encode.
|
|
81
|
+
* @param policy {LangfuseAttributePolicy} - Environment, release, redaction and mask settings.
|
|
82
|
+
* @returns {OtlpEncodedSpan} The encoded span with its resource and scope.
|
|
83
|
+
*/
|
|
84
|
+
export function toOtlpEncodedSpan(span, policy) {
|
|
85
|
+
return {
|
|
86
|
+
span: toOtlpSpan(span, policy),
|
|
87
|
+
resource: toOtlpAttributes(span.resource ?? { "service.name": FALLBACK_SERVICE_NAME }),
|
|
88
|
+
scope: { name: SCOPE_NAME },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @description Builds an OTLP/JSON export request, grouping spans under the resource and instrumentation scope they were produced under.
|
|
93
|
+
* @param items {OtlpEncodedSpan[]} - Encoded spans, in export order.
|
|
94
|
+
* @returns {OtlpTraceRequest} The request body, with one `resourceSpans` entry per distinct resource and one `scopeSpans` entry per distinct scope within it.
|
|
95
|
+
*/
|
|
96
|
+
export function toOtlpRequest(items) {
|
|
97
|
+
const resources = new Map();
|
|
98
|
+
for (const { span, resource, scope } of items) {
|
|
99
|
+
const resourceKey = JSON.stringify(resource);
|
|
100
|
+
const group = resources.get(resourceKey) ?? {
|
|
101
|
+
resource,
|
|
102
|
+
scopes: new Map(),
|
|
103
|
+
};
|
|
104
|
+
resources.set(resourceKey, group);
|
|
105
|
+
const scopeKey = `${scope.name}\u0000${scope.version ?? ""}`;
|
|
106
|
+
const scoped = group.scopes.get(scopeKey) ?? { scope, spans: [] };
|
|
107
|
+
group.scopes.set(scopeKey, scoped);
|
|
108
|
+
scoped.spans.push(span);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
resourceSpans: [...resources.values()].map((group) => ({
|
|
112
|
+
resource: { attributes: group.resource },
|
|
113
|
+
scopeSpans: [...group.scopes.values()],
|
|
114
|
+
})),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
60
117
|
/**
|
|
61
118
|
* @description Splits encoded spans into request-sized batches, bounded by span count and serialized bytes.
|
|
62
|
-
* @param
|
|
63
|
-
* @returns {
|
|
119
|
+
* @param items {OtlpEncodedSpan[]} - Encoded spans, in export order.
|
|
120
|
+
* @returns {OtlpEncodedSpan[][]} Batches that each fit one export request; a single oversized span still gets its own batch.
|
|
64
121
|
*/
|
|
65
|
-
export function chunkOtlpSpans(
|
|
122
|
+
export function chunkOtlpSpans(items) {
|
|
66
123
|
const chunks = [];
|
|
67
124
|
let current = [];
|
|
68
125
|
let bytes = 0;
|
|
69
|
-
for (const
|
|
70
|
-
const size = Buffer.byteLength(JSON.stringify(span));
|
|
126
|
+
for (const item of items) {
|
|
127
|
+
const size = Buffer.byteLength(JSON.stringify(item.span));
|
|
71
128
|
if (current.length > 0 &&
|
|
72
129
|
(current.length >= MAX_SPANS_PER_REQUEST ||
|
|
73
130
|
bytes + size > MAX_BYTES_PER_REQUEST)) {
|
|
@@ -75,7 +132,7 @@ export function chunkOtlpSpans(spans) {
|
|
|
75
132
|
current = [];
|
|
76
133
|
bytes = 0;
|
|
77
134
|
}
|
|
78
|
-
current.push(
|
|
135
|
+
current.push(item);
|
|
79
136
|
bytes += size;
|
|
80
137
|
}
|
|
81
138
|
if (current.length > 0) {
|
|
@@ -11,8 +11,9 @@ export declare class SpanSerializer {
|
|
|
11
11
|
* Create a new span with generated IDs.
|
|
12
12
|
*
|
|
13
13
|
* When `traceId` / `parentSpanId` are omitted, the method automatically
|
|
14
|
-
* attempts to inherit them from the active OTel context so that
|
|
15
|
-
*
|
|
14
|
+
* attempts to inherit them from the active OTel context so that these
|
|
15
|
+
* in-process span records carry the traceId of the exported OTEL trace they
|
|
16
|
+
* belong to (fix A5). The records themselves are never exported.
|
|
16
17
|
*/
|
|
17
18
|
static createSpan(type: SpanType, name: string, attributes?: Partial<SpanAttributes>, parentSpanId?: string, traceId?: string): SpanData;
|
|
18
19
|
/**
|
|
@@ -13,12 +13,14 @@ export class SpanSerializer {
|
|
|
13
13
|
* Create a new span with generated IDs.
|
|
14
14
|
*
|
|
15
15
|
* When `traceId` / `parentSpanId` are omitted, the method automatically
|
|
16
|
-
* attempts to inherit them from the active OTel context so that
|
|
17
|
-
*
|
|
16
|
+
* attempts to inherit them from the active OTel context so that these
|
|
17
|
+
* in-process span records carry the traceId of the exported OTEL trace they
|
|
18
|
+
* belong to (fix A5). The records themselves are never exported.
|
|
18
19
|
*/
|
|
19
20
|
static createSpan(type, name, attributes = {}, parentSpanId, traceId) {
|
|
20
21
|
// A5 fix: When no explicit traceId is provided, try to inherit from the
|
|
21
|
-
// active OTel context so
|
|
22
|
+
// active OTel context so these in-process records can be matched to the
|
|
23
|
+
// OTEL trace of the request.
|
|
22
24
|
let resolvedTraceId = traceId;
|
|
23
25
|
let resolvedParentSpanId = parentSpanId;
|
|
24
26
|
if (!resolvedTraceId) {
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
* }
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
|
+
import "../../services/server/ai/observability/instrumentation.js";
|
|
46
47
|
import { FileErrorCode } from "../errors/index.js";
|
|
47
48
|
import type { BatchProcessingSummary, FileInfo, FileProcessingError, ProcessorFileProcessingResult, FileProcessorConfig, ProcessorOperationResult, ProcessedFileBase, ProcessOptions } from "../../types/index.js";
|
|
48
49
|
/**
|
|
@@ -49,6 +49,10 @@ import { SIZE_LIMITS } from "../config/index.js";
|
|
|
49
49
|
import { isAbortError } from "../../utils/errorHandling.js";
|
|
50
50
|
import { withSpan } from "../../telemetry/withSpan.js";
|
|
51
51
|
import { tracers } from "../../telemetry/tracers.js";
|
|
52
|
+
// Starts tracing at the first span, as this module always did: the shared
|
|
53
|
+
// tracers only start what a loaded entry registered, and this path can be
|
|
54
|
+
// imported without the package entry. The client entry stays free of it.
|
|
55
|
+
import "../../services/server/ai/observability/instrumentation.js";
|
|
52
56
|
import { createFileError, extractHttpStatus, FileErrorCode, isRetryableError, } from "../errors/index.js";
|
|
53
57
|
import { DEFAULT_RETRY_CONFIG } from "../../types/index.js";
|
|
54
58
|
const gunzipAsync = promisify(gunzip);
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
* });
|
|
65
65
|
* ```
|
|
66
66
|
*/
|
|
67
|
+
import "../services/server/ai/observability/instrumentation.js";
|
|
67
68
|
export { BaseFileProcessor, DEFAULT_IMAGE_MAX_SIZE_MB, DEFAULT_IMAGE_TIMEOUT_MS, DEFAULT_RETRY_CONFIG, DEFAULT_TEXT_MAX_SIZE_MB, DEFAULT_TEXT_TIMEOUT_MS, getDefaultImageMaxSizeMB, getDefaultImageTimeout, getDefaultTextMaxSizeMB, getDefaultTextTimeout, PROCESSOR_PRIORITIES, } from "./base/index.js";
|
|
68
69
|
export { AI_VISION_EXTENSIONS, ARCHIVE_EXTENSIONS, ARCHIVE_LIMITS, ARCHIVE_MIME_TYPES, AUDIO_EXTENSIONS, AUDIO_MIME_TYPES, bytesToMB, CONFIG_EXTENSIONS, CSV_EXTENSIONS, DATA_EXTENSIONS, DATA_MIME_TYPES, DATABASE_EXTENSIONS, DESIGN_EXTENSIONS, DOCUMENT_EXTENSIONS, DOCUMENT_MIME_TYPES, detectLanguageFromFilename, EXACT_FILENAME_MAP, EXCEL_EXTENSIONS, EXECUTABLE_EXTENSIONS, FILE_EXTENSIONS, formatBytes, getLanguageIdentifier, getSizeLimitForType, getSupportedExtensions, getSupportedFilenames, HTML_EXTENSIONS, IMAGE_EXTENSIONS, IMAGE_MIME_TYPES, isSourceCodeFile, isWithinSizeLimit, JSON_EXTENSIONS, LANGUAGE_MAP, MARKDOWN_EXTENSIONS, MIME_TYPES, mbToBytes, OPENDOCUMENT_EXTENSIONS, PDF_EXTENSIONS, POWERPOINT_EXTENSIONS, PROCESSING_LIMITS, RTF_EXTENSIONS, SIZE_LIMITS, SIZE_LIMITS_BYTES, SIZE_LIMITS_MB, SOURCE_CODE_EXTENSIONS, SOURCE_CODE_MIME_TYPES, TEXT_EXTENSIONS, TEXT_MIME_TYPES, VIDEO_EXTENSIONS, VIDEO_MIME_TYPES, validateFileSize, WORD_EXTENSIONS, XML_EXTENSIONS, YAML_EXTENSIONS, YAML_LIMITS, } from "./config/index.js";
|
|
69
70
|
export { combineSummaries, createCustomFileError, createFileError, createProcessingSummary, ERROR_MESSAGES, extractHttpStatus, extractSafeMetadata, FileErrorCode, formatFileError, generateErrorFingerprint, getErrorTemplate, getRetryDelay, isFileProcessingError, isRetryableError, isRetryableErrorCode, mapErrorToCode, safeStringify, serializeError, summarizeError, } from "./errors/index.js";
|