modelrig 0.1.0 → 0.2.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 +94 -0
- package/README.md +41 -11
- package/data/PROVENANCE.md +19 -0
- package/data/pricing-manual.json +22 -0
- package/data/pricing-map.json +46362 -0
- package/data/pricing-overlay.json +419 -0
- package/dist/adapters/anthropic.d.ts +50 -6
- package/dist/adapters/anthropic.d.ts.map +1 -1
- package/dist/adapters/anthropic.js +123 -29
- package/dist/adapters/build-adapters.d.ts +11 -0
- package/dist/adapters/build-adapters.d.ts.map +1 -0
- package/dist/adapters/build-adapters.js +45 -0
- package/dist/adapters/deepseek.d.ts +6 -0
- package/dist/adapters/deepseek.d.ts.map +1 -1
- package/dist/adapters/deepseek.js +54 -1
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +46 -6
- package/dist/adapters/grok.d.ts +9 -0
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +67 -3
- package/dist/adapters/openai-compat.d.ts +6 -0
- package/dist/adapters/openai-compat.d.ts.map +1 -1
- package/dist/adapters/openai-compat.js +58 -1
- package/dist/adapters/openai-stream.d.ts +58 -0
- package/dist/adapters/openai-stream.d.ts.map +1 -0
- package/dist/adapters/openai-stream.js +120 -0
- package/dist/adapters/openai.d.ts +6 -0
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +60 -1
- package/dist/adapters/sampling.d.ts +28 -0
- package/dist/adapters/sampling.d.ts.map +1 -0
- package/dist/adapters/sampling.js +34 -0
- package/dist/adapters/tool-mapping.d.ts +92 -0
- package/dist/adapters/tool-mapping.d.ts.map +1 -0
- package/dist/adapters/tool-mapping.js +143 -0
- package/dist/artifacts/artifact.d.ts +137 -0
- package/dist/artifacts/artifact.d.ts.map +1 -0
- package/dist/artifacts/artifact.js +377 -0
- package/dist/artifacts/content-uploader.d.ts +87 -0
- package/dist/artifacts/content-uploader.d.ts.map +1 -0
- package/dist/artifacts/content-uploader.js +124 -0
- package/dist/artifacts/index.d.ts +63 -0
- package/dist/artifacts/index.d.ts.map +1 -0
- package/dist/artifacts/index.js +80 -0
- package/dist/artifacts/run-context.d.ts +129 -0
- package/dist/artifacts/run-context.d.ts.map +1 -0
- package/dist/artifacts/run-context.js +208 -0
- package/dist/artifacts/scrub.d.ts +60 -0
- package/dist/artifacts/scrub.d.ts.map +1 -0
- package/dist/artifacts/scrub.js +139 -0
- package/dist/artifacts/step.d.ts +26 -0
- package/dist/artifacts/step.d.ts.map +1 -0
- package/dist/artifacts/step.js +37 -0
- package/dist/artifacts/taint.d.ts +63 -0
- package/dist/artifacts/taint.d.ts.map +1 -0
- package/dist/artifacts/taint.js +76 -0
- package/dist/bakeoff/cache-sim.d.ts +99 -0
- package/dist/bakeoff/cache-sim.d.ts.map +1 -0
- package/dist/bakeoff/cache-sim.js +126 -0
- package/dist/bakeoff/eval-cases.d.ts +96 -0
- package/dist/bakeoff/eval-cases.d.ts.map +1 -0
- package/dist/bakeoff/eval-cases.js +105 -0
- package/dist/bakeoff/report.d.ts +41 -0
- package/dist/bakeoff/report.d.ts.map +1 -0
- package/dist/bakeoff/report.js +142 -0
- package/dist/bakeoff/samples.d.ts +15 -0
- package/dist/bakeoff/samples.d.ts.map +1 -1
- package/dist/bakeoff/samples.js +20 -0
- package/dist/cli.d.ts +1 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +134 -116
- package/dist/config.d.ts +100 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +104 -0
- package/dist/deprecations.d.ts +32 -0
- package/dist/deprecations.d.ts.map +1 -0
- package/dist/deprecations.js +59 -0
- package/dist/gateway/http-protocol.d.ts +175 -0
- package/dist/gateway/http-protocol.d.ts.map +1 -0
- package/dist/gateway/http-protocol.js +105 -0
- package/dist/index.d.ts +105 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +266 -138
- package/dist/init/finder.d.ts +15 -0
- package/dist/init/finder.d.ts.map +1 -1
- package/dist/init/finder.js +46 -0
- package/dist/init/init-cli.d.ts +10 -0
- package/dist/init/init-cli.d.ts.map +1 -0
- package/dist/init/init-cli.js +86 -0
- package/dist/init/observe-cli.d.ts +19 -0
- package/dist/init/observe-cli.d.ts.map +1 -0
- package/dist/init/observe-cli.js +73 -0
- package/dist/init/observe.d.ts +81 -0
- package/dist/init/observe.d.ts.map +1 -0
- package/dist/init/observe.js +245 -0
- package/dist/registry/build.d.ts +94 -1
- package/dist/registry/build.d.ts.map +1 -1
- package/dist/registry/build.js +125 -4
- package/dist/registry/cache-policy.d.ts +94 -0
- package/dist/registry/cache-policy.d.ts.map +1 -0
- package/dist/registry/cache-policy.js +238 -0
- package/dist/registry/call-notes.d.ts.map +1 -1
- package/dist/registry/call-notes.js +40 -2
- package/dist/registry/effective-rate.d.ts +151 -0
- package/dist/registry/effective-rate.d.ts.map +1 -0
- package/dist/registry/effective-rate.js +156 -0
- package/dist/registry/facts.d.ts.map +1 -1
- package/dist/registry/facts.js +16 -0
- package/dist/registry/parity.d.ts +26 -0
- package/dist/registry/parity.d.ts.map +1 -1
- package/dist/registry/parity.js +43 -2
- package/dist/registry/pricing-shape.d.ts +113 -0
- package/dist/registry/pricing-shape.d.ts.map +1 -0
- package/dist/registry/pricing-shape.js +123 -0
- package/dist/registry/pricing.d.ts +25 -1
- package/dist/registry/pricing.d.ts.map +1 -1
- package/dist/registry/pricing.js +64 -6
- package/dist/rigindex/bootstrap.d.ts +36 -0
- package/dist/rigindex/bootstrap.d.ts.map +1 -0
- package/dist/rigindex/bootstrap.js +97 -0
- package/dist/rigindex/fit.d.ts +24 -0
- package/dist/rigindex/fit.d.ts.map +1 -0
- package/dist/rigindex/fit.js +76 -0
- package/dist/rigindex/gates.d.ts +52 -0
- package/dist/rigindex/gates.d.ts.map +1 -0
- package/dist/rigindex/gates.js +109 -0
- package/dist/rigindex/graph.d.ts +31 -0
- package/dist/rigindex/graph.d.ts.map +1 -0
- package/dist/rigindex/graph.js +120 -0
- package/dist/rigindex/index.d.ts +23 -0
- package/dist/rigindex/index.d.ts.map +1 -0
- package/dist/rigindex/index.js +112 -0
- package/dist/rigindex/types.d.ts +76 -0
- package/dist/rigindex/types.d.ts.map +1 -0
- package/dist/rigindex/types.js +8 -0
- package/dist/routes/cache-lint.d.ts +41 -0
- package/dist/routes/cache-lint.d.ts.map +1 -0
- package/dist/routes/cache-lint.js +81 -0
- package/dist/routes/loader.d.ts +37 -0
- package/dist/routes/loader.d.ts.map +1 -1
- package/dist/routes/loader.js +138 -0
- package/dist/routes/resolved-bundle.d.ts +79 -0
- package/dist/routes/resolved-bundle.d.ts.map +1 -0
- package/dist/routes/resolved-bundle.js +148 -0
- package/dist/routes/sampling-config.d.ts +32 -0
- package/dist/routes/sampling-config.d.ts.map +1 -0
- package/dist/routes/sampling-config.js +91 -0
- package/dist/run/ajv.d.ts +22 -0
- package/dist/run/ajv.d.ts.map +1 -0
- package/dist/run/ajv.js +32 -0
- package/dist/run/cache-handle.d.ts +58 -0
- package/dist/run/cache-handle.d.ts.map +1 -0
- package/dist/run/cache-handle.js +54 -0
- package/dist/run/cache-stamp.d.ts +65 -0
- package/dist/run/cache-stamp.d.ts.map +1 -0
- package/dist/run/cache-stamp.js +0 -0
- package/dist/run/capture-write.d.ts +33 -0
- package/dist/run/capture-write.d.ts.map +1 -0
- package/dist/run/capture-write.js +55 -0
- package/dist/run/cost.d.ts.map +1 -1
- package/dist/run/cost.js +1 -1
- package/dist/run/dispatch.d.ts +99 -0
- package/dist/run/dispatch.d.ts.map +1 -0
- package/dist/run/dispatch.js +191 -0
- package/dist/run/raw.d.ts +88 -0
- package/dist/run/raw.d.ts.map +1 -0
- package/dist/run/raw.js +286 -0
- package/dist/run/record-attempt.d.ts +40 -0
- package/dist/run/record-attempt.d.ts.map +1 -0
- package/dist/run/record-attempt.js +99 -0
- package/dist/run/resolve.d.ts +75 -2
- package/dist/run/resolve.d.ts.map +1 -1
- package/dist/run/resolve.js +126 -4
- package/dist/run/run.d.ts +14 -0
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +0 -0
- package/dist/run/stream.d.ts +49 -0
- package/dist/run/stream.d.ts.map +1 -0
- package/dist/run/stream.js +87 -0
- package/dist/run/validate.d.ts.map +1 -1
- package/dist/run/validate.js +6 -7
- package/dist/savings/cache-ledger.d.ts +108 -0
- package/dist/savings/cache-ledger.d.ts.map +1 -0
- package/dist/savings/cache-ledger.js +174 -0
- package/dist/savings/cache-reconcile.d.ts +64 -0
- package/dist/savings/cache-reconcile.d.ts.map +1 -0
- package/dist/savings/cache-reconcile.js +59 -0
- package/dist/savings/cache-stats.d.ts +63 -0
- package/dist/savings/cache-stats.d.ts.map +1 -0
- package/dist/savings/cache-stats.js +108 -0
- package/dist/savings/emit.d.ts +13 -0
- package/dist/savings/emit.d.ts.map +1 -1
- package/dist/savings/emit.js +25 -0
- package/dist/savings/swap-execute.d.ts +14 -0
- package/dist/savings/swap-execute.d.ts.map +1 -1
- package/dist/savings/swap-execute.js +44 -1
- package/dist/savings/swaps-client.d.ts +14 -0
- package/dist/savings/swaps-client.d.ts.map +1 -1
- package/dist/savings/swaps-client.js +16 -2
- package/dist/settings/api-fetcher.d.ts +39 -0
- package/dist/settings/api-fetcher.d.ts.map +1 -0
- package/dist/settings/api-fetcher.js +92 -0
- package/dist/settings/index.d.ts +10 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +15 -0
- package/dist/settings/task-settings.d.ts +91 -0
- package/dist/settings/task-settings.d.ts.map +1 -0
- package/dist/settings/task-settings.js +116 -0
- package/dist/status.d.ts +77 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +113 -0
- package/dist/telemetry/artifact-store.d.ts +48 -0
- package/dist/telemetry/artifact-store.d.ts.map +1 -0
- package/dist/telemetry/artifact-store.js +189 -0
- package/dist/telemetry/cache-miss-streak.d.ts +79 -0
- package/dist/telemetry/cache-miss-streak.d.ts.map +1 -0
- package/dist/telemetry/cache-miss-streak.js +99 -0
- package/dist/telemetry/capture.d.ts +3 -0
- package/dist/telemetry/capture.d.ts.map +1 -1
- package/dist/telemetry/capture.js +15 -8
- package/dist/telemetry/exporter-setup.d.ts +28 -0
- package/dist/telemetry/exporter-setup.d.ts.map +1 -0
- package/dist/telemetry/exporter-setup.js +34 -0
- package/dist/telemetry/exporter.d.ts +75 -0
- package/dist/telemetry/exporter.d.ts.map +1 -1
- package/dist/telemetry/exporter.js +324 -47
- package/dist/telemetry/health.d.ts +76 -0
- package/dist/telemetry/health.d.ts.map +1 -0
- package/dist/telemetry/health.js +92 -0
- package/dist/telemetry/ingest-protocol.d.ts +152 -3
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/ingest-protocol.js +204 -4
- package/dist/telemetry/load-error-report.d.ts +24 -0
- package/dist/telemetry/load-error-report.d.ts.map +1 -0
- package/dist/telemetry/load-error-report.js +50 -0
- package/dist/telemetry/otel-map.d.ts +121 -0
- package/dist/telemetry/otel-map.d.ts.map +1 -0
- package/dist/telemetry/otel-map.js +224 -0
- package/dist/telemetry/otel-normalize.d.ts +104 -0
- package/dist/telemetry/otel-normalize.d.ts.map +1 -0
- package/dist/telemetry/otel-normalize.js +530 -0
- package/dist/telemetry/otlp-emitter.d.ts +52 -0
- package/dist/telemetry/otlp-emitter.d.ts.map +1 -0
- package/dist/telemetry/otlp-emitter.js +144 -0
- package/dist/telemetry/otlp-proto.d.ts +29 -0
- package/dist/telemetry/otlp-proto.d.ts.map +1 -0
- package/dist/telemetry/otlp-proto.js +266 -0
- package/dist/telemetry/sinks.d.ts +37 -1
- package/dist/telemetry/sinks.d.ts.map +1 -1
- package/dist/telemetry/sinks.js +121 -30
- package/dist/telemetry/store.d.ts +28 -1
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/store.js +218 -3
- package/dist/tenancy/api-key-format.d.ts +15 -5
- package/dist/tenancy/api-key-format.d.ts.map +1 -1
- package/dist/tenancy/api-key-format.js +24 -5
- package/dist/types/config-teaching.d.ts +62 -0
- package/dist/types/config-teaching.d.ts.map +1 -0
- package/dist/types/config-teaching.js +183 -0
- package/dist/types/provider.d.ts +122 -1
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/provider.js +21 -1
- package/dist/types/route.d.ts +79 -1
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +117 -0
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/validate-cli.d.ts +12 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate-cli.js +57 -0
- package/dist/verify/capture.d.ts +66 -0
- package/dist/verify/capture.d.ts.map +1 -0
- package/dist/verify/capture.js +180 -0
- package/dist/verify/cli-run.d.ts +23 -0
- package/dist/verify/cli-run.d.ts.map +1 -0
- package/dist/verify/cli-run.js +57 -0
- package/dist/verify/diff.d.ts +48 -0
- package/dist/verify/diff.d.ts.map +1 -0
- package/dist/verify/diff.js +157 -0
- package/dist/verify/index.d.ts +17 -0
- package/dist/verify/index.d.ts.map +1 -0
- package/dist/verify/index.js +24 -0
- package/dist/verify/report.d.ts +14 -0
- package/dist/verify/report.d.ts.map +1 -0
- package/dist/verify/report.js +88 -0
- package/dist/verify/types.d.ts +101 -0
- package/dist/verify/types.d.ts.map +1 -0
- package/dist/verify/types.js +17 -0
- package/dist/verify/verify.d.ts +29 -0
- package/dist/verify/verify.d.ts.map +1 -0
- package/dist/verify/verify.js +148 -0
- package/dist/watch/evaluate.d.ts +162 -3
- package/dist/watch/evaluate.d.ts.map +1 -1
- package/dist/watch/evaluate.js +61 -0
- package/dist/watch/monitor.d.ts +116 -0
- package/dist/watch/monitor.d.ts.map +1 -0
- package/dist/watch/monitor.js +199 -0
- package/dist/watch/proposals.d.ts +20 -4
- package/dist/watch/proposals.d.ts.map +1 -1
- package/dist/watch/proposals.js +31 -3
- package/dist/watch/run.d.ts +33 -0
- package/dist/watch/run.d.ts.map +1 -1
- package/dist/watch/run.js +39 -0
- package/package.json +7 -1
- package/registry/LICENSE +20 -0
- package/registry/leaderboard.json +3639 -0
- package/registry/registry.json +7263 -0
- package/dist/telemetry/supabase-exporter.d.ts +0 -64
- package/dist/telemetry/supabase-exporter.d.ts.map +0 -1
- package/dist/telemetry/supabase-exporter.js +0 -211
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Load-error console reporting — follow-up #2.
|
|
4
|
+
*
|
|
5
|
+
* When a route bundle fails to load, tell the console WHY. `loadRoutes` throws
|
|
6
|
+
* before the exporter loop exists, so the run-loop path never gets a chance to
|
|
7
|
+
* mirror the failure. This sends a one-shot manifest batch carrying
|
|
8
|
+
* `describeConfigFailure(error)` onto `rig_manifests.load_errors`, which drives
|
|
9
|
+
* /setup's "Define your first route" step to `failed` with what we looked for
|
|
10
|
+
* and what we found.
|
|
11
|
+
*
|
|
12
|
+
* Fire-and-forget, and best-effort by design: `createRig` is synchronous and
|
|
13
|
+
* re-throws (the rig genuinely cannot run), and telemetry must never change the
|
|
14
|
+
* error the caller sees. KNOWN LIMITATION: this only reaches the console once
|
|
15
|
+
* the app runs with an export configured (MODELRIG_API_KEY / direct creds); a
|
|
16
|
+
* first-timer who hasn't connected yet still gets the full diagnosis at their
|
|
17
|
+
* terminal via `renderConfigErrorForTerminal`.
|
|
18
|
+
*
|
|
19
|
+
* Lives here rather than in index.ts to keep that file under the LOC cap (P1) —
|
|
20
|
+
* the same relocation `buildTelemetrySink` made to `sinks.ts`.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.reportLoadError = reportLoadError;
|
|
24
|
+
const node_path_1 = require("node:path");
|
|
25
|
+
const node_os_1 = require("node:os");
|
|
26
|
+
const manifest_1 = require("../routes/manifest");
|
|
27
|
+
const config_teaching_1 = require("../types/config-teaching");
|
|
28
|
+
const exporter_1 = require("./exporter");
|
|
29
|
+
const sinks_1 = require("./sinks");
|
|
30
|
+
function reportLoadError(config, error) {
|
|
31
|
+
if (config.export === undefined)
|
|
32
|
+
return;
|
|
33
|
+
const sourceId = config.export.sourceId ?? (0, node_os_1.hostname)();
|
|
34
|
+
// rig.yaml can load fine even when a route bundle can't — carry its
|
|
35
|
+
// name/dimensions/contribution so the reporting upsert doesn't blank them.
|
|
36
|
+
let manifestMeta = null;
|
|
37
|
+
try {
|
|
38
|
+
manifestMeta = (0, manifest_1.loadManifest)((0, node_path_1.dirname)(config.routesDir));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// rig.yaml is itself broken/absent; fall back to source-id naming so the
|
|
42
|
+
// row still lands and the console still learns of the failure.
|
|
43
|
+
}
|
|
44
|
+
const sink = (0, sinks_1.buildTelemetrySink)(config.export, sourceId);
|
|
45
|
+
const batch = (0, exporter_1.buildLoadErrorBatch)(sourceId, manifestMeta, [(0, config_teaching_1.describeConfigFailure)(error)]);
|
|
46
|
+
void sink.send(batch).catch((err) => {
|
|
47
|
+
console.warn(`[modelrig] could not report the route load error to the console: ` +
|
|
48
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTLP mapping — E1 (spec §3, §4). PURE: no I/O, no env, no imports from the
|
|
3
|
+
* exporter/sinks/store. One `inferences` row → one OTLP/HTTP JSON span.
|
|
4
|
+
*
|
|
5
|
+
* ── THE ONE PLACE gen_ai.* NAMES LIVE ────────────────────────────────────
|
|
6
|
+
* Every OpenTelemetry semantic-convention attribute name this product emits is
|
|
7
|
+
* declared in THIS module and nowhere else (architect-review-2026-08 §2(f)).
|
|
8
|
+
* The pin is `SEMCONV_VERSION` below — bump it here and the wire changes in one
|
|
9
|
+
* edit, not scattered across the codebase. If you find a `"gen_ai.*"` string
|
|
10
|
+
* literal outside this file, it is a bug.
|
|
11
|
+
*
|
|
12
|
+
* ── WHY HAND-BUILT JSON, NOT @opentelemetry/* ────────────────────────────
|
|
13
|
+
* We are mapping completed rows, not instrumenting live code, so the SDK's
|
|
14
|
+
* tracer runtime would be dead weight (spec §2.2). The shape below is the
|
|
15
|
+
* OTLP/HTTP `ExportTraceServiceRequest` JSON encoding. Two encoding rules bite
|
|
16
|
+
* here and are easy to get wrong (plan §"risks"):
|
|
17
|
+
* • trace_id / span_id are LOWERCASE HEX strings — the documented OTLP/JSON
|
|
18
|
+
* exception to Protobuf's base64-for-bytes. NOT base64.
|
|
19
|
+
* • int64 attribute + timestamp values are DECIMAL STRINGS; doubles are
|
|
20
|
+
* numbers. `intValue`/`startTimeUnixNano` are strings on the wire.
|
|
21
|
+
*/
|
|
22
|
+
import type { TelemetryRow } from "../types/telemetry";
|
|
23
|
+
import type { CacheContext } from "../watch/evaluate";
|
|
24
|
+
/** Semantic-conventions pin (architect ruling §2(f)). The scope version on the
|
|
25
|
+
* wire and the single source of truth for which gen_ai vintage we speak. */
|
|
26
|
+
export declare const SEMCONV_VERSION = "1.41.0";
|
|
27
|
+
/** STATUS_CODE_UNSET / STATUS_CODE_ERROR (OK is never set — gen_ai convention
|
|
28
|
+
* leaves a successful span UNSET). */
|
|
29
|
+
declare const STATUS_UNSET = 0;
|
|
30
|
+
declare const STATUS_ERROR = 2;
|
|
31
|
+
export type OtlpAnyValue = {
|
|
32
|
+
readonly stringValue: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly intValue: string;
|
|
35
|
+
} | {
|
|
36
|
+
readonly doubleValue: number;
|
|
37
|
+
} | {
|
|
38
|
+
readonly boolValue: boolean;
|
|
39
|
+
};
|
|
40
|
+
export interface OtlpAttribute {
|
|
41
|
+
readonly key: string;
|
|
42
|
+
readonly value: OtlpAnyValue;
|
|
43
|
+
}
|
|
44
|
+
export interface OtlpStatus {
|
|
45
|
+
readonly code: typeof STATUS_UNSET | typeof STATUS_ERROR;
|
|
46
|
+
readonly message?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface OtlpSpanJson {
|
|
49
|
+
/** 32 lowercase hex chars (16 bytes). */
|
|
50
|
+
readonly traceId: string;
|
|
51
|
+
/** 16 lowercase hex chars (8 bytes). */
|
|
52
|
+
readonly spanId: string;
|
|
53
|
+
/** 16 lowercase hex chars — present only when a caller traceparent nests us. */
|
|
54
|
+
readonly parentSpanId?: string;
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly kind: number;
|
|
57
|
+
readonly startTimeUnixNano: string;
|
|
58
|
+
readonly endTimeUnixNano: string;
|
|
59
|
+
readonly attributes: readonly OtlpAttribute[];
|
|
60
|
+
readonly status: OtlpStatus;
|
|
61
|
+
}
|
|
62
|
+
export interface OtlpResource {
|
|
63
|
+
readonly serviceName: string;
|
|
64
|
+
/** Optional extra resource attributes (e.g. deployment.environment). */
|
|
65
|
+
readonly attributes?: readonly OtlpAttribute[];
|
|
66
|
+
}
|
|
67
|
+
export interface ExportTraceServiceRequestJson {
|
|
68
|
+
readonly resourceSpans: ReadonlyArray<{
|
|
69
|
+
readonly resource: {
|
|
70
|
+
readonly attributes: readonly OtlpAttribute[];
|
|
71
|
+
};
|
|
72
|
+
readonly scopeSpans: ReadonlyArray<{
|
|
73
|
+
readonly scope: {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly version: string;
|
|
76
|
+
};
|
|
77
|
+
readonly spans: readonly OtlpSpanJson[];
|
|
78
|
+
}>;
|
|
79
|
+
}>;
|
|
80
|
+
}
|
|
81
|
+
/** The tag key the gateway stashes an inbound traceparent under (spec §2.5). */
|
|
82
|
+
export declare const TRACEPARENT_TAG = "otel.traceparent";
|
|
83
|
+
export interface Traceparent {
|
|
84
|
+
/** Caller's trace id — the emitted span joins this trace. */
|
|
85
|
+
readonly traceId: string;
|
|
86
|
+
/** Caller's span id — the emitted span's parent. */
|
|
87
|
+
readonly parentSpanId: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Parse a W3C `traceparent` value, or null when absent/malformed. The gateway
|
|
91
|
+
* (packages/modelrig-server) reuses this so the SDK and the server cannot
|
|
92
|
+
* disagree about what "well-formed" means. All-zero trace/parent ids are
|
|
93
|
+
* rejected per the W3C spec (they mean "no parent").
|
|
94
|
+
*/
|
|
95
|
+
export declare function parseTraceparent(value: string | undefined | null): Traceparent | null;
|
|
96
|
+
/**
|
|
97
|
+
* Map one telemetry row to one OTLP span (spec §3, normative). Pure and total:
|
|
98
|
+
* every field comes from the row; nulls are omitted (never emitted as empty).
|
|
99
|
+
*/
|
|
100
|
+
export declare function inferenceToSpan(row: TelemetryRow): OtlpSpanJson;
|
|
101
|
+
/**
|
|
102
|
+
* Decision-span attributes for a cache-aware routing decision (cache-aware-routing
|
|
103
|
+
* spec §5.5 — "routing must never be opaque"). Returns the `modelrig.cache.*`
|
|
104
|
+
* attribute set a caller attaches to the swap/proposal decision span so the
|
|
105
|
+
* verdict + its economics travel to any OTLP backend alongside the inference
|
|
106
|
+
* spans. Declared HERE because this module is the one place OTLP attribute names
|
|
107
|
+
* live (the pin at the top of this file).
|
|
108
|
+
*
|
|
109
|
+
* `breakeven_conformant_calls` can be `Infinity` ("never breaks even"), which is
|
|
110
|
+
* not a valid JSON number — so it is emitted as a double ONLY when finite, and a
|
|
111
|
+
* boolean `modelrig.cache.breakeven_reachable` records the never-case explicitly
|
|
112
|
+
* rather than dropping the fact.
|
|
113
|
+
*/
|
|
114
|
+
export declare function cacheDecisionSpanAttributes(ctx: CacheContext): OtlpAttribute[];
|
|
115
|
+
/**
|
|
116
|
+
* Wrap spans in the OTLP `ExportTraceServiceRequest` envelope (spec §4, pure).
|
|
117
|
+
* One resource, one scope (`modelrig` @ SEMCONV_VERSION), N spans.
|
|
118
|
+
*/
|
|
119
|
+
export declare function buildExportTraceRequest(spans: readonly OtlpSpanJson[], resource: OtlpResource): ExportTraceServiceRequestJson;
|
|
120
|
+
export {};
|
|
121
|
+
//# sourceMappingURL=otel-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel-map.d.ts","sourceRoot":"","sources":["../../src/telemetry/otel-map.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OTLP mapping — E1 (spec §3, §4). PURE: no I/O, no env, no imports from the
|
|
4
|
+
* exporter/sinks/store. One `inferences` row → one OTLP/HTTP JSON span.
|
|
5
|
+
*
|
|
6
|
+
* ── THE ONE PLACE gen_ai.* NAMES LIVE ────────────────────────────────────
|
|
7
|
+
* Every OpenTelemetry semantic-convention attribute name this product emits is
|
|
8
|
+
* declared in THIS module and nowhere else (architect-review-2026-08 §2(f)).
|
|
9
|
+
* The pin is `SEMCONV_VERSION` below — bump it here and the wire changes in one
|
|
10
|
+
* edit, not scattered across the codebase. If you find a `"gen_ai.*"` string
|
|
11
|
+
* literal outside this file, it is a bug.
|
|
12
|
+
*
|
|
13
|
+
* ── WHY HAND-BUILT JSON, NOT @opentelemetry/* ────────────────────────────
|
|
14
|
+
* We are mapping completed rows, not instrumenting live code, so the SDK's
|
|
15
|
+
* tracer runtime would be dead weight (spec §2.2). The shape below is the
|
|
16
|
+
* OTLP/HTTP `ExportTraceServiceRequest` JSON encoding. Two encoding rules bite
|
|
17
|
+
* here and are easy to get wrong (plan §"risks"):
|
|
18
|
+
* • trace_id / span_id are LOWERCASE HEX strings — the documented OTLP/JSON
|
|
19
|
+
* exception to Protobuf's base64-for-bytes. NOT base64.
|
|
20
|
+
* • int64 attribute + timestamp values are DECIMAL STRINGS; doubles are
|
|
21
|
+
* numbers. `intValue`/`startTimeUnixNano` are strings on the wire.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.TRACEPARENT_TAG = exports.SEMCONV_VERSION = void 0;
|
|
25
|
+
exports.parseTraceparent = parseTraceparent;
|
|
26
|
+
exports.inferenceToSpan = inferenceToSpan;
|
|
27
|
+
exports.cacheDecisionSpanAttributes = cacheDecisionSpanAttributes;
|
|
28
|
+
exports.buildExportTraceRequest = buildExportTraceRequest;
|
|
29
|
+
const node_crypto_1 = require("node:crypto");
|
|
30
|
+
/** Semantic-conventions pin (architect ruling §2(f)). The scope version on the
|
|
31
|
+
* wire and the single source of truth for which gen_ai vintage we speak. */
|
|
32
|
+
exports.SEMCONV_VERSION = "1.41.0";
|
|
33
|
+
/** SPAN_KIND_CLIENT — an LLM call leaving this process to a provider. */
|
|
34
|
+
const SPAN_KIND_CLIENT = 3;
|
|
35
|
+
/** SPAN_KIND_INTERNAL — the synthetic search/tool-execution span. */
|
|
36
|
+
const SPAN_KIND_INTERNAL = 1;
|
|
37
|
+
/** STATUS_CODE_UNSET / STATUS_CODE_ERROR (OK is never set — gen_ai convention
|
|
38
|
+
* leaves a successful span UNSET). */
|
|
39
|
+
const STATUS_UNSET = 0;
|
|
40
|
+
const STATUS_ERROR = 2;
|
|
41
|
+
// ── attribute constructors (typed value wrappers) ────────────────────────
|
|
42
|
+
const strAttr = (key, value) => ({ key, value: { stringValue: value } });
|
|
43
|
+
const intAttr = (key, value) => ({
|
|
44
|
+
key,
|
|
45
|
+
value: { intValue: String(Math.trunc(value)) },
|
|
46
|
+
});
|
|
47
|
+
const dblAttr = (key, value) => ({ key, value: { doubleValue: value } });
|
|
48
|
+
const boolAttr = (key, value) => ({ key, value: { boolValue: value } });
|
|
49
|
+
// ── W3C traceparent ──────────────────────────────────────────────────────
|
|
50
|
+
/** The tag key the gateway stashes an inbound traceparent under (spec §2.5). */
|
|
51
|
+
exports.TRACEPARENT_TAG = "otel.traceparent";
|
|
52
|
+
/** version(00) - trace-id(32hex) - parent-id(16hex) - flags(2hex). */
|
|
53
|
+
const TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
|
|
54
|
+
/**
|
|
55
|
+
* Parse a W3C `traceparent` value, or null when absent/malformed. The gateway
|
|
56
|
+
* (packages/modelrig-server) reuses this so the SDK and the server cannot
|
|
57
|
+
* disagree about what "well-formed" means. All-zero trace/parent ids are
|
|
58
|
+
* rejected per the W3C spec (they mean "no parent").
|
|
59
|
+
*/
|
|
60
|
+
function parseTraceparent(value) {
|
|
61
|
+
if (value === undefined || value === null)
|
|
62
|
+
return null;
|
|
63
|
+
const match = TRACEPARENT_RE.exec(value.trim());
|
|
64
|
+
if (match === null)
|
|
65
|
+
return null;
|
|
66
|
+
const traceId = match[1].toLowerCase();
|
|
67
|
+
const parentSpanId = match[2].toLowerCase();
|
|
68
|
+
if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
|
|
69
|
+
return null;
|
|
70
|
+
return { traceId, parentSpanId };
|
|
71
|
+
}
|
|
72
|
+
// ── deterministic span identity (spec §2.6) ──────────────────────────────
|
|
73
|
+
// Same row twice ⇒ byte-identical span, so a re-emit after a crash is
|
|
74
|
+
// idempotent at the sink. Both are truncations of sha256(inference id).
|
|
75
|
+
function sha256Hex(input) {
|
|
76
|
+
return (0, node_crypto_1.createHash)("sha256").update(input).digest("hex");
|
|
77
|
+
}
|
|
78
|
+
function traceIdFor(inferenceId) {
|
|
79
|
+
return sha256Hex(inferenceId).slice(0, 32); // 16 bytes
|
|
80
|
+
}
|
|
81
|
+
function spanIdFor(inferenceId) {
|
|
82
|
+
return sha256Hex(inferenceId).slice(0, 16); // 8 bytes
|
|
83
|
+
}
|
|
84
|
+
/** ISO timestamp (+ optional ms offset) → OTLP `*UnixNano` decimal string.
|
|
85
|
+
* BigInt keeps nanosecond precision that Number would lose past 2^53. */
|
|
86
|
+
function toUnixNano(iso, plusMs = 0) {
|
|
87
|
+
const ms = Date.parse(iso);
|
|
88
|
+
const baseMs = Number.isFinite(ms) ? ms : 0;
|
|
89
|
+
return (BigInt(baseMs + Math.trunc(plusMs)) * 1000000n).toString();
|
|
90
|
+
}
|
|
91
|
+
/** The synthetic search-cost row is emitted as a tool-execution span (§2.7). */
|
|
92
|
+
function isSearchRow(row) {
|
|
93
|
+
return row.provider === "search";
|
|
94
|
+
}
|
|
95
|
+
function readTraceparentTag(row) {
|
|
96
|
+
let tags;
|
|
97
|
+
try {
|
|
98
|
+
tags = JSON.parse(row.tagsJson);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (tags === null || typeof tags !== "object")
|
|
104
|
+
return null;
|
|
105
|
+
const raw = tags[exports.TRACEPARENT_TAG];
|
|
106
|
+
return typeof raw === "string" ? parseTraceparent(raw) : null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Map one telemetry row to one OTLP span (spec §3, normative). Pure and total:
|
|
110
|
+
* every field comes from the row; nulls are omitted (never emitted as empty).
|
|
111
|
+
*/
|
|
112
|
+
function inferenceToSpan(row) {
|
|
113
|
+
const search = isSearchRow(row);
|
|
114
|
+
const operationName = search ? "execute_tool" : "chat";
|
|
115
|
+
const spanName = search ? "execute_tool search" : `chat ${row.model}`;
|
|
116
|
+
const attrs = [
|
|
117
|
+
strAttr("gen_ai.operation.name", operationName),
|
|
118
|
+
strAttr("gen_ai.provider.name", row.provider),
|
|
119
|
+
strAttr("gen_ai.request.model", row.model),
|
|
120
|
+
strAttr("gen_ai.response.model", row.model),
|
|
121
|
+
intAttr("gen_ai.usage.input_tokens", row.tokensIn),
|
|
122
|
+
intAttr("gen_ai.usage.output_tokens", row.tokensOut),
|
|
123
|
+
intAttr("gen_ai.usage.cached_input_tokens", row.tokensCached),
|
|
124
|
+
strAttr("modelrig.route", row.route),
|
|
125
|
+
intAttr("modelrig.route_version", row.routeVersion),
|
|
126
|
+
// Honest zero: cost stays 0 for an unpriced model; the flag says so rather
|
|
127
|
+
// than the code inventing a dollar figure (spec §3, addendum A1).
|
|
128
|
+
dblAttr("modelrig.cost_estimate_usd", row.costEstimateUsd),
|
|
129
|
+
strAttr("modelrig.served_tier", row.servedTier),
|
|
130
|
+
];
|
|
131
|
+
// Cache accounting (WS-A / E1 parity). Emitted only when non-zero to keep the
|
|
132
|
+
// vast majority of spans lean. `cache_read_tokens` carries the same quantity
|
|
133
|
+
// as the pre-existing `cached_input_tokens` under the spelling the cache-aware
|
|
134
|
+
// spec (§2) names; both are kept — S3 retires nothing, and a consumer reads a
|
|
135
|
+
// specific key rather than summing, so this is detail, not double-count.
|
|
136
|
+
if (row.tokensCached > 0)
|
|
137
|
+
attrs.push(intAttr("gen_ai.usage.cache_read_tokens", row.tokensCached));
|
|
138
|
+
const cacheWrite = row.tokensCacheWrite ?? 0;
|
|
139
|
+
if (cacheWrite > 0)
|
|
140
|
+
attrs.push(intAttr("gen_ai.usage.cache_creation_tokens", cacheWrite));
|
|
141
|
+
if (row.pricingMissing)
|
|
142
|
+
attrs.push(boolAttr("modelrig.pricing_missing", true));
|
|
143
|
+
if (row.ttfbMs !== null)
|
|
144
|
+
attrs.push(intAttr("modelrig.ttfb_ms", row.ttfbMs));
|
|
145
|
+
if (row.servedVariant !== null)
|
|
146
|
+
attrs.push(strAttr("modelrig.served_variant", row.servedVariant));
|
|
147
|
+
if (row.repairedBy !== null)
|
|
148
|
+
attrs.push(strAttr("modelrig.repaired_by", row.repairedBy));
|
|
149
|
+
if (row.fingerprintCell !== null) {
|
|
150
|
+
// Irreversible by construction (a schema hash) — safe to emit (spec §3).
|
|
151
|
+
attrs.push(strAttr("modelrig.fingerprint_cell", row.fingerprintCell));
|
|
152
|
+
}
|
|
153
|
+
if (row.failureClass !== null)
|
|
154
|
+
attrs.push(strAttr("modelrig.failure_class", row.failureClass));
|
|
155
|
+
const parent = readTraceparentTag(row);
|
|
156
|
+
const traceId = parent !== null ? parent.traceId : traceIdFor(row.id);
|
|
157
|
+
const span = {
|
|
158
|
+
traceId,
|
|
159
|
+
spanId: spanIdFor(row.id),
|
|
160
|
+
...(parent !== null ? { parentSpanId: parent.parentSpanId } : {}),
|
|
161
|
+
name: spanName,
|
|
162
|
+
kind: search ? SPAN_KIND_INTERNAL : SPAN_KIND_CLIENT,
|
|
163
|
+
startTimeUnixNano: toUnixNano(row.ts),
|
|
164
|
+
endTimeUnixNano: toUnixNano(row.ts, row.latencyMs),
|
|
165
|
+
attributes: attrs,
|
|
166
|
+
// Failures are facts: a non-null class flips the span to ERROR (spec §2.7).
|
|
167
|
+
status: row.failureClass !== null
|
|
168
|
+
? { code: STATUS_ERROR, message: row.failureClass }
|
|
169
|
+
: { code: STATUS_UNSET },
|
|
170
|
+
};
|
|
171
|
+
return span;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Decision-span attributes for a cache-aware routing decision (cache-aware-routing
|
|
175
|
+
* spec §5.5 — "routing must never be opaque"). Returns the `modelrig.cache.*`
|
|
176
|
+
* attribute set a caller attaches to the swap/proposal decision span so the
|
|
177
|
+
* verdict + its economics travel to any OTLP backend alongside the inference
|
|
178
|
+
* spans. Declared HERE because this module is the one place OTLP attribute names
|
|
179
|
+
* live (the pin at the top of this file).
|
|
180
|
+
*
|
|
181
|
+
* `breakeven_conformant_calls` can be `Infinity` ("never breaks even"), which is
|
|
182
|
+
* not a valid JSON number — so it is emitted as a double ONLY when finite, and a
|
|
183
|
+
* boolean `modelrig.cache.breakeven_reachable` records the never-case explicitly
|
|
184
|
+
* rather than dropping the fact.
|
|
185
|
+
*/
|
|
186
|
+
function cacheDecisionSpanAttributes(ctx) {
|
|
187
|
+
const attrs = [
|
|
188
|
+
strAttr("modelrig.cache.verdict", ctx.verdict),
|
|
189
|
+
dblAttr("modelrig.cache.observed_hit_rate", ctx.observed_hit_rate),
|
|
190
|
+
dblAttr("modelrig.cache.observed_write_rate", ctx.observed_write_rate),
|
|
191
|
+
intAttr("modelrig.cache.window_days", ctx.window_days),
|
|
192
|
+
dblAttr("modelrig.cache.incumbent_effective_in_per_mtok", ctx.incumbent_effective_in_per_mtok),
|
|
193
|
+
dblAttr("modelrig.cache.challenger_cold_in_per_mtok", ctx.challenger_cold_in_per_mtok),
|
|
194
|
+
dblAttr("modelrig.cache.switch_cost_usd", ctx.switch_cost_usd),
|
|
195
|
+
dblAttr("modelrig.cache.cache_option_value_usd", ctx.cache_option_value_usd),
|
|
196
|
+
];
|
|
197
|
+
const reachable = Number.isFinite(ctx.breakeven_conformant_calls);
|
|
198
|
+
attrs.push(boolAttr("modelrig.cache.breakeven_reachable", reachable));
|
|
199
|
+
if (reachable) {
|
|
200
|
+
attrs.push(dblAttr("modelrig.cache.breakeven_conformant_calls", ctx.breakeven_conformant_calls));
|
|
201
|
+
}
|
|
202
|
+
return attrs;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Wrap spans in the OTLP `ExportTraceServiceRequest` envelope (spec §4, pure).
|
|
206
|
+
* One resource, one scope (`modelrig` @ SEMCONV_VERSION), N spans.
|
|
207
|
+
*/
|
|
208
|
+
function buildExportTraceRequest(spans, resource) {
|
|
209
|
+
return {
|
|
210
|
+
resourceSpans: [
|
|
211
|
+
{
|
|
212
|
+
resource: {
|
|
213
|
+
attributes: [strAttr("service.name", resource.serviceName), ...(resource.attributes ?? [])],
|
|
214
|
+
},
|
|
215
|
+
scopeSpans: [
|
|
216
|
+
{
|
|
217
|
+
scope: { name: "modelrig", version: exports.SEMCONV_VERSION },
|
|
218
|
+
spans,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTLP-in v0 normalizer — Expansion E3-Harvest (spec §3). PURE: no I/O, no env,
|
|
3
|
+
* no imports from the exporter/sinks/server. The INBOUND sibling of `otel-map.ts`
|
|
4
|
+
* (which is the OUTBOUND our-rows → OTLP mapper). Foreign OTLP spans in →
|
|
5
|
+
* `runs` + attested `steps` out.
|
|
6
|
+
*
|
|
7
|
+
* ── SCOPE (spec §3.1): identity, structure, timing, status, model/provider,
|
|
8
|
+
* usage ONLY. ZERO content extraction — prompts/completions/messages in foreign
|
|
9
|
+
* spans are never read, never stored, never passed to the envelope. Retention-
|
|
10
|
+
* free default; content custody arrives with E2-C. This deletes ~60% of
|
|
11
|
+
* Langfuse's problem surface deliberately.
|
|
12
|
+
*
|
|
13
|
+
* ── VINTAGE HANDLING is pure NAME-UNION, not versioned dispatch (learnings
|
|
14
|
+
* §1.1, their proven approach), with our two divergences (spec §3.3):
|
|
15
|
+
* (1) namespaced attributes outrank bare ones — `gen_ai.*`/`llm.*` are checked
|
|
16
|
+
* BEFORE a bare `model`;
|
|
17
|
+
* (2) provider IS normalized (Langfuse never reads provider on ingest), because
|
|
18
|
+
* `model_key = provider/model` feeds our registry/priors/leaderboard —
|
|
19
|
+
* recorded honestly, ending at "unknown", never guessed.
|
|
20
|
+
*
|
|
21
|
+
* The usage de-double-counting and the agent-span aggregate skip are adopted
|
|
22
|
+
* from OtelIngestionProcessor's generic extractor (learnings §1.2, read for
|
|
23
|
+
* pattern per §5.3 — implemented fresh here, no bodies copied).
|
|
24
|
+
*/
|
|
25
|
+
import type { WireRun, WireStep } from "./ingest-protocol";
|
|
26
|
+
export interface AttestedUsage {
|
|
27
|
+
/** Non-cached input tokens (cache buckets subtracted, floored 0). */
|
|
28
|
+
readonly input?: number;
|
|
29
|
+
/** Non-reasoning/-audio output tokens (subtracted, floored 0). */
|
|
30
|
+
readonly output?: number;
|
|
31
|
+
readonly total?: number;
|
|
32
|
+
readonly input_cached_tokens?: number;
|
|
33
|
+
readonly input_cache_creation?: number;
|
|
34
|
+
readonly output_reasoning_tokens?: number;
|
|
35
|
+
readonly output_audio_tokens?: number;
|
|
36
|
+
/** Any other gen_ai.usage.* / llm.token_count.* bucket, name preserved. */
|
|
37
|
+
readonly [bucket: string]: number | undefined;
|
|
38
|
+
}
|
|
39
|
+
export interface AttestedEnvelope {
|
|
40
|
+
/** Resolved model, or null when no model attribute was present. */
|
|
41
|
+
readonly model: string | null;
|
|
42
|
+
/** Resolved provider — honest, ending at "unknown", NEVER guessed. */
|
|
43
|
+
readonly provider: string;
|
|
44
|
+
/** Classified operation (generation/tool/agent/retriever/embedding/span). */
|
|
45
|
+
readonly operation: string;
|
|
46
|
+
/** De-double-counted usage; omitted entirely on agent-aggregate spans. */
|
|
47
|
+
readonly usage?: AttestedUsage;
|
|
48
|
+
/** The emitting instrumentation scope (name + optional version). */
|
|
49
|
+
readonly scope: {
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly version: string | null;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
type OtlpAnyValueIn = Record<string, unknown>;
|
|
55
|
+
interface OtlpKeyValueIn {
|
|
56
|
+
key?: string;
|
|
57
|
+
value?: OtlpAnyValueIn;
|
|
58
|
+
}
|
|
59
|
+
/** What a decoded span looks like to us (both transports converge here). */
|
|
60
|
+
export interface OtlpSpanIn {
|
|
61
|
+
traceId?: unknown;
|
|
62
|
+
trace_id?: unknown;
|
|
63
|
+
spanId?: unknown;
|
|
64
|
+
span_id?: unknown;
|
|
65
|
+
parentSpanId?: unknown;
|
|
66
|
+
parent_span_id?: unknown;
|
|
67
|
+
name?: unknown;
|
|
68
|
+
kind?: unknown;
|
|
69
|
+
startTimeUnixNano?: unknown;
|
|
70
|
+
start_time_unix_nano?: unknown;
|
|
71
|
+
endTimeUnixNano?: unknown;
|
|
72
|
+
end_time_unix_nano?: unknown;
|
|
73
|
+
attributes?: OtlpKeyValueIn[];
|
|
74
|
+
status?: {
|
|
75
|
+
code?: unknown;
|
|
76
|
+
message?: unknown;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export interface NormalizeResult {
|
|
80
|
+
readonly runs: WireRun[];
|
|
81
|
+
/** Attested steps (each carries the `attested` envelope). */
|
|
82
|
+
readonly steps: WireStep[];
|
|
83
|
+
/** reason → count (bad_otlp_span, unmapped_attribute, ts_copied_edge, …). */
|
|
84
|
+
readonly drops: Record<string, number>;
|
|
85
|
+
/** Total spans seen (before drops) — for the conversion-failure log. */
|
|
86
|
+
readonly spanCount: number;
|
|
87
|
+
/** Distinct instrumentation-scope names seen — named in the logs-mistake 400. */
|
|
88
|
+
readonly scopeNames: string[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Normalize a decoded OTLP `TracesData` structure into runs + attested steps.
|
|
92
|
+
* Total and defensive: a malformed span is dropped-and-counted, never thrown.
|
|
93
|
+
* `nowMs` is injected so callers/tests are deterministic (default Date.now()).
|
|
94
|
+
*/
|
|
95
|
+
export declare function normalizeOtlpTraces(data: unknown, nowMs?: number): NormalizeResult;
|
|
96
|
+
/**
|
|
97
|
+
* Logs-exporter detection (learnings §1.2). ResourceLogs and ResourceSpans share
|
|
98
|
+
* protobuf field numbers, so a logs exporter pointed at the traces endpoint
|
|
99
|
+
* decodes as id-less "spans". A JSON logs body carries `resourceLogs` outright.
|
|
100
|
+
* Returns true when the body is a logs payload that must be rejected 400.
|
|
101
|
+
*/
|
|
102
|
+
export declare function looksLikeLogsPayload(data: unknown): boolean;
|
|
103
|
+
export {};
|
|
104
|
+
//# sourceMappingURL=otel-normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel-normalize.d.ts","sourceRoot":"","sources":["../../src/telemetry/otel-normalize.ts"],"names":[],"mappings":"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"}
|