@odla-ai/o11y 1.0.0 → 1.0.2
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/LICENSE +21 -0
- package/README.md +12 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/llms.txt +13 -1
- package/package.json +21 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cory Ondrejka
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# @odla-ai/o11y
|
|
2
2
|
|
|
3
|
+
> ⚠️ **Experimental — an agentic-coding experiment.** This package is built and
|
|
4
|
+
> operated by autonomous coding agents as an experiment in agentic loops. APIs
|
|
5
|
+
> change without notice and nothing here is production-hardened. **Use at your
|
|
6
|
+
> own risk.**
|
|
7
|
+
|
|
3
8
|
Official observability client for [odla](https://github.com/) Cloudflare Workers.
|
|
4
9
|
Wrap your Worker once and get OpenTelemetry **traces**, **metrics**, structured
|
|
5
10
|
**errors**, and **LLM cost** — exported over OTLP to the odla-o11y collector.
|
|
@@ -44,6 +49,13 @@ Config is read from env vars (override per call via the second `withObservabilit
|
|
|
44
49
|
|
|
45
50
|
`wrangler.jsonc` needs `"compatibility_flags": ["nodejs_compat"]` (for AsyncLocalStorage).
|
|
46
51
|
|
|
52
|
+
Keep `ODLA_O11Y_ENDPOINT` and `ODLA_O11Y_SERVICE` set as plain vars in every
|
|
53
|
+
environment and treat only `ODLA_O11Y_TOKEN` as an optional secret: the
|
|
54
|
+
metrics/logs sink no-ops without an endpoint, but the trace exporter builds
|
|
55
|
+
its URL from it, so an unset endpoint yields export attempts against a
|
|
56
|
+
relative URL (harmless to responses, noisy in logs). Without a token the
|
|
57
|
+
collector rejects the export; the app itself is unaffected.
|
|
58
|
+
|
|
47
59
|
## API
|
|
48
60
|
|
|
49
61
|
- `withObservability(handler, opts?)` — wrap a Worker handler (fetch/scheduled).
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/instrument.ts","../src/context.ts","../../o11y-core/src/encode.ts","../../o11y-core/src/pricing.ts","../src/span.ts","../src/metrics.ts","../src/logs.ts","../src/llm.ts"],"sourcesContent":["export { instrumentDurableObject, withObservability } from \"./instrument.js\";\nexport { span } from \"./span.js\";\nexport { count, metrics } from \"./metrics.js\";\nexport { recordError } from \"./logs.js\";\nexport { recordLlmUsage } from \"./llm.js\";\n\nexport type { ObservabilityOptions, OptsFn } from \"./context.js\";\nexport type { SpanKindName, SpanOpts } from \"./span.js\";\nexport type { Metrics } from \"./metrics.js\";\nexport type { ErrorReport } from \"./logs.js\";\nexport type { LlmCostOpts, LlmUsage } from \"./llm.js\";\nexport type { Attrs } from \"@odla/o11y-core\";\n","import { instrument, instrumentDO, type ResolveConfigFn, type TraceConfig } from \"@microlabs/otel-cf-workers\";\nimport { createSink, resolveConfig, runWithSink, type OptsFn } from \"./context.js\";\n\n/** Build the otel-cf-workers trace config (exporter + service) from env/opts. */\nfunction traceConfig<E>(env: E, opts?: OptsFn<E>): TraceConfig {\n const c = resolveConfig(env, opts);\n const headers: Record<string, string> = {};\n if (c.token) headers.authorization = `Bearer ${c.token}`;\n const config: TraceConfig = {\n exporter: { url: `${c.endpoint}/v1/traces`, headers },\n service: { name: c.service, version: c.version },\n };\n if (c.sampleRatio != null) config.sampling = { headSampler: { ratio: c.sampleRatio } };\n return config;\n}\n\n/** Wrap each handler entrypoint so an ALS metrics/logs sink is live for the\n * duration of the invocation and flushed via ctx.waitUntil at the end. */\nfunction wrapHandler<E>(handler: ExportedHandler<E>, opts?: OptsFn<E>): ExportedHandler<E> {\n const wrapped: ExportedHandler<E> = { ...handler };\n\n const fetchImpl = handler.fetch;\n if (fetchImpl) {\n wrapped.fetch = (req, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await fetchImpl(req, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n const scheduledImpl = handler.scheduled;\n if (scheduledImpl) {\n wrapped.scheduled = (controller, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await scheduledImpl(controller, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n return wrapped;\n}\n\n/** Wrap a Worker handler with tracing (otel-cf-workers) + the metrics/logs sink.\n * Config is read from env vars ODLA_O11Y_{ENDPOINT,SERVICE,TOKEN,VERSION} unless\n * overridden by `opts`. */\nexport function withObservability<E = unknown>(\n handler: ExportedHandler<E>,\n opts?: OptsFn<E>,\n): ExportedHandler<E> {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env as E, opts);\n // instrument constrains E to its own Env type; we support any service env, so\n // cross the boundary with `any` and restore the caller's E on the way out.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrument(wrapHandler(handler, opts) as ExportedHandler<any>, configFn) as ExportedHandler<E>;\n}\n\n/** Wrap a Durable Object class with tracing. (Establishing the metrics/logs sink\n * inside DO methods is added in P1 when odla-db's usage counters are bridged.) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function instrumentDurableObject<T extends abstract new (...args: any[]) => object>(\n cls: T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n opts?: OptsFn<any>,\n): T {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env, opts);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrumentDO(cls as any, configFn) as unknown as T;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n encodeLogsRequest,\n encodeMetricsRequest,\n type Attrs,\n type LogRecord,\n type MetricPoint,\n} from \"@odla/o11y-core\";\n\n/** The instrumentation scope name stamped on every signal. */\nexport const SCOPE = \"@odla-ai/o11y\";\n\nexport interface ObservabilityOptions {\n /** Service name (default: env.ODLA_O11Y_SERVICE). */\n service?: string;\n /** Collector base URL (default: env.ODLA_O11Y_ENDPOINT). */\n endpoint?: string;\n /** Per-service bearer token (default: env.ODLA_O11Y_TOKEN). */\n token?: string;\n /** Release/version tag (default: env.ODLA_O11Y_VERSION ?? \"0.0.0\"). */\n version?: string;\n /** Head-sampling ratio 0..1 for traces (default: 1). */\n sampleRatio?: number;\n /** Static resource attributes merged onto every signal. */\n attributes?: Attrs;\n}\n\nexport type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);\n\nexport interface ResolvedConfig {\n service: string;\n endpoint: string;\n token?: string;\n version: string;\n attributes: Attrs;\n sampleRatio?: number;\n}\n\nfunction evalOpts<E>(opts: OptsFn<E> | undefined, env: E): ObservabilityOptions {\n return typeof opts === \"function\" ? opts(env) : (opts ?? {});\n}\n\n/** Resolve options against the service's env vars. */\nexport function resolveConfig<E>(env: E, opts?: OptsFn<E>): ResolvedConfig {\n const o = evalOpts(opts, env);\n const e = env as Record<string, string | undefined>;\n return {\n service: o.service ?? e[\"ODLA_O11Y_SERVICE\"] ?? \"unknown\",\n endpoint: (o.endpoint ?? e[\"ODLA_O11Y_ENDPOINT\"] ?? \"\").replace(/\\/$/, \"\"),\n token: o.token ?? e[\"ODLA_O11Y_TOKEN\"],\n version: o.version ?? e[\"ODLA_O11Y_VERSION\"] ?? \"0.0.0\",\n attributes: o.attributes ?? {},\n sampleRatio: o.sampleRatio,\n };\n}\n\n/** Nanoseconds since epoch as a decimal string (OTLP timestamp format). */\nexport function nowNano(): string {\n return `${Date.now()}000000`;\n}\n\nexport interface Sink {\n config: ResolvedConfig;\n metrics: MetricPoint[];\n logs: LogRecord[];\n flush(): Promise<void>;\n}\n\nconst als = new AsyncLocalStorage<Sink>();\n\nexport function currentSink(): Sink | undefined {\n return als.getStore();\n}\n\nexport function runWithSink<T>(sink: Sink, fn: () => Promise<T>): Promise<T> {\n return als.run(sink, fn);\n}\n\nfunction resourceAttrs(config: ResolvedConfig): Attrs {\n return { \"service.name\": config.service, \"service.version\": config.version, ...config.attributes };\n}\n\nasync function post(url: string, body: unknown, token?: string): Promise<void> {\n try {\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify(body),\n });\n } catch {\n // Telemetry export is best-effort — never surface into the app.\n }\n}\n\n/** A per-invocation sink: accumulates metric/log records and flushes them to the\n * collector as OTLP/JSON (traces are exported separately by otel-cf-workers). */\nexport function createSink(config: ResolvedConfig): Sink {\n return {\n config,\n metrics: [],\n logs: [],\n async flush(): Promise<void> {\n if (!this.config.endpoint) {\n this.metrics = [];\n this.logs = [];\n return;\n }\n const resource = resourceAttrs(this.config);\n const jobs: Promise<void>[] = [];\n if (this.metrics.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics), this.config.token),\n );\n this.metrics = [];\n }\n if (this.logs.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs), this.config.token),\n );\n this.logs = [];\n }\n await Promise.all(jobs);\n },\n };\n}\n","// OTLP/JSON encoders — the emit side of the wire contract (the SDK produces\n// these; the collector decodes them in later phases). Traces are produced by\n// @microlabs/otel-cf-workers; metrics and logs are hand-rolled here because that\n// library is traces-only and the Node OTel metrics SDK doesn't fit the isolate\n// model.\n\nimport type { Attrs, OtlpKeyValue, OtlpResource, OtlpScope } from \"./otlp.js\";\n\n/** Inverse of kvListToAttrs: a flat attr map → OTLP KeyValue[]. */\nexport function attrsToKvList(attrs: Attrs): OtlpKeyValue[] {\n const out: OtlpKeyValue[] = [];\n for (const [key, value] of Object.entries(attrs)) {\n if (typeof value === \"string\") out.push({ key, value: { stringValue: value } });\n else if (typeof value === \"boolean\") out.push({ key, value: { boolValue: value } });\n else if (Number.isInteger(value)) out.push({ key, value: { intValue: String(value) } });\n else out.push({ key, value: { doubleValue: value } });\n }\n return out;\n}\n\n// ---- Metrics ----\n\nexport type MetricKind = \"sum\" | \"gauge\";\n\nexport interface MetricPoint {\n name: string;\n unit?: string;\n kind: MetricKind;\n value: number;\n attrs: Attrs;\n startUnixNano: string;\n timeUnixNano: string;\n}\n\ninterface OtlpNumberDataPoint {\n attributes?: OtlpKeyValue[];\n startTimeUnixNano?: string;\n timeUnixNano: string;\n asDouble: number;\n}\n\ninterface OtlpMetric {\n name: string;\n unit?: string;\n sum?: { aggregationTemporality: number; isMonotonic: boolean; dataPoints: OtlpNumberDataPoint[] };\n gauge?: { dataPoints: OtlpNumberDataPoint[] };\n}\n\nexport interface ExportMetricsServiceRequest {\n resourceMetrics: Array<{\n resource: OtlpResource;\n scopeMetrics: Array<{ scope: OtlpScope; metrics: OtlpMetric[] }>;\n }>;\n}\n\n// OTLP AggregationTemporality: 1 = DELTA. Each ephemeral isolate reports its own\n// delta; the collector aggregates across invocations into Analytics Engine.\nconst DELTA = 1;\n\nexport function encodeMetricsRequest(\n resource: Attrs,\n scopeName: string,\n points: MetricPoint[],\n): ExportMetricsServiceRequest {\n const metrics: OtlpMetric[] = points.map((p) => {\n const dp: OtlpNumberDataPoint = {\n attributes: attrsToKvList(p.attrs),\n startTimeUnixNano: p.startUnixNano,\n timeUnixNano: p.timeUnixNano,\n asDouble: p.value,\n };\n return p.kind === \"gauge\"\n ? { name: p.name, unit: p.unit, gauge: { dataPoints: [dp] } }\n : { name: p.name, unit: p.unit, sum: { aggregationTemporality: DELTA, isMonotonic: true, dataPoints: [dp] } };\n });\n return {\n resourceMetrics: [\n { resource: { attributes: attrsToKvList(resource) }, scopeMetrics: [{ scope: { name: scopeName }, metrics }] },\n ],\n };\n}\n\n// ---- Logs (errors ride the logs signal) ----\n\nexport interface LogRecord {\n timeUnixNano: string;\n severityNumber: number; // OTel: 17 = ERROR, 9 = INFO\n severityText: string;\n body: string;\n attrs: Attrs;\n traceId?: string;\n spanId?: string;\n}\n\nexport interface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OtlpResource;\n scopeLogs: Array<{\n scope: OtlpScope;\n logRecords: Array<{\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: { stringValue: string };\n attributes: OtlpKeyValue[];\n traceId?: string;\n spanId?: string;\n }>;\n }>;\n }>;\n}\n\nexport function encodeLogsRequest(\n resource: Attrs,\n scopeName: string,\n records: LogRecord[],\n): ExportLogsServiceRequest {\n return {\n resourceLogs: [\n {\n resource: { attributes: attrsToKvList(resource) },\n scopeLogs: [\n {\n scope: { name: scopeName },\n logRecords: records.map((r) => ({\n timeUnixNano: r.timeUnixNano,\n observedTimeUnixNano: r.timeUnixNano,\n severityNumber: r.severityNumber,\n severityText: r.severityText,\n body: { stringValue: r.body },\n attributes: attrsToKvList(r.attrs),\n ...(r.traceId ? { traceId: r.traceId } : {}),\n ...(r.spanId ? { spanId: r.spanId } : {}),\n })),\n },\n ],\n },\n ],\n };\n}\n","// LLM price table + cost computation. OTel GenAI semconv has no cost attribute,\n// so we compute cost downstream from raw token counts × model price. Keep this\n// table in sync with current model pricing (verify against the claude-api\n// reference); unknown models cost 0 and should be added here.\n\nexport interface ModelPrice {\n /** USD per million input tokens. */\n inputPerMTok: number;\n /** USD per million output tokens. */\n outputPerMTok: number;\n}\n\n/** Prices in USD per million tokens (MTok). */\nexport const MODEL_PRICING: Record<string, ModelPrice> = {\n \"claude-haiku-4-5\": { inputPerMTok: 1, outputPerMTok: 5 },\n \"claude-sonnet-4-6\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-sonnet-5\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-opus-4-6\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-7\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-8\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-fable-5\": { inputPerMTok: 10, outputPerMTok: 50 },\n};\n\n/** Compute USD cost for a call. Pass `override` to price a model not in the\n * table (or to apply promotional pricing). Returns 0 for unknown models. */\nexport function costUsd(\n model: string,\n inputTokens: number,\n outputTokens: number,\n override?: ModelPrice,\n): number {\n const price = override ?? MODEL_PRICING[model];\n if (!price) return 0;\n return (inputTokens / 1_000_000) * price.inputPerMTok + (outputTokens / 1_000_000) * price.outputPerMTok;\n}\n","import { SpanKind, SpanStatusCode, trace, type Span } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { SCOPE } from \"./context.js\";\n\nexport type SpanKindName = \"internal\" | \"server\" | \"client\" | \"producer\" | \"consumer\";\n\nexport interface SpanOpts {\n attributes?: Attrs;\n kind?: SpanKindName;\n}\n\nconst KIND: Record<SpanKindName, SpanKind> = {\n internal: SpanKind.INTERNAL,\n server: SpanKind.SERVER,\n client: SpanKind.CLIENT,\n producer: SpanKind.PRODUCER,\n consumer: SpanKind.CONSUMER,\n};\n\n/** Run `fn` inside a new active span. The span is ended automatically and its\n * status set from success/throw. Works whether or not tracing is exporting —\n * falls back to a no-op span if no tracer is installed. */\nexport async function span<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n opts?: SpanOpts,\n): Promise<T> {\n const tracer = trace.getTracer(SCOPE);\n return tracer.startActiveSpan(\n name,\n { attributes: opts?.attributes, kind: opts?.kind ? KIND[opts.kind] : undefined },\n async (s) => {\n try {\n const result = await fn(s);\n s.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n s.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });\n throw err;\n } finally {\n s.end();\n }\n },\n );\n}\n","import type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface Metrics {\n /** Monotonic counter (delta). Formalizes odla-db's usage counters. */\n count(name: string, by?: number, attrs?: Attrs): void;\n /** Last-value gauge. */\n gauge(name: string, value: number, attrs?: Attrs): void;\n /** A single observation (recorded as a delta sum for now; histograms later). */\n histogram(name: string, value: number, attrs?: Attrs, unit?: string): void;\n}\n\nfunction record(kind: \"sum\" | \"gauge\", name: string, value: number, attrs?: Attrs, unit?: string): void {\n const sink = currentSink();\n if (!sink) return; // outside an instrumented invocation — no-op\n const ts = nowNano();\n sink.metrics.push({ name, unit, kind, value, attrs: attrs ?? {}, startUnixNano: ts, timeUnixNano: ts });\n}\n\n/** The ambient metrics API for the current invocation. */\nexport function metrics(): Metrics {\n return {\n count: (name, by = 1, attrs) => record(\"sum\", name, by, attrs),\n gauge: (name, value, attrs) => record(\"gauge\", name, value, attrs),\n histogram: (name, value, attrs, unit) => record(\"sum\", name, value, attrs, unit),\n };\n}\n\n/** Convenience: increment a monotonic counter by `by` (default 1). */\nexport function count(name: string, by = 1, attrs?: Attrs): void {\n record(\"sum\", name, by, attrs);\n}\n","import { SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface ErrorReport {\n /** Stable, low-cardinality error code (e.g. \"unique_violation\"). */\n code?: string;\n /** Low-cardinality route/step where it happened. */\n route?: string;\n /** Extra low-cardinality attributes for the index. */\n attributes?: Attrs;\n /** Rich context for triage — kept only in the R2 artifact bundle. */\n artifacts?: Record<string, unknown>;\n /** Dedup key; the collector fingerprints by (type+code+route) if absent. */\n fingerprint?: string;\n}\n\n// OTel severity number for ERROR.\nconst SEVERITY_ERROR = 17;\n\n/** Record a structured error. Returns an artifact id. The message and stack ride\n * the log body + a denied attribute so the collector persists them ONLY in the\n * R2 artifact bundle, never in metrics or the queryable index. */\nexport function recordError(err: unknown, report?: ErrorReport): string {\n const error = err instanceof Error ? err : new Error(String(err));\n const artifactId = `${Date.now().toString(16)}${crypto.randomUUID().replace(/-/g, \"\")}`;\n const span = trace.getActiveSpan();\n\n const attrs: Attrs = {\n \"error.type\": error.name,\n \"odla.artifact_id\": artifactId,\n ...(report?.code ? { \"error.code\": report.code } : {}),\n ...(report?.route ? { \"odla.route\": report.route } : {}),\n ...(report?.fingerprint ? { \"odla.fingerprint\": report.fingerprint } : {}),\n ...(report?.attributes ?? {}),\n };\n\n if (span) {\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });\n span.setAttributes(attrs);\n }\n\n const sink = currentSink();\n if (sink) {\n const ctx = span?.spanContext();\n sink.logs.push({\n timeUnixNano: nowNano(),\n severityNumber: SEVERITY_ERROR,\n severityText: \"ERROR\",\n body: error.message, // full message → R2 bundle only (collector-enforced)\n attrs: {\n ...attrs,\n // Denied keys: stripped from metrics/index, retained in the R2 bundle.\n ...(error.stack ? { \"exception.stacktrace\": error.stack } : {}),\n ...(report?.artifacts ? { \"odla.artifacts\": JSON.stringify(report.artifacts) } : {}),\n },\n ...(ctx ? { traceId: ctx.traceId, spanId: ctx.spanId } : {}),\n });\n }\n\n return artifactId;\n}\n","import { trace } from \"@opentelemetry/api\";\nimport { costUsd, type Attrs, type ModelPrice } from \"@odla/o11y-core\";\nimport { count } from \"./metrics.js\";\n\n/** The token accounting odla-kg's Provider already returns (and drops today). */\nexport interface LlmUsage {\n calls: number;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface LlmCostOpts {\n provider: string; // \"claude\" | \"openai\" | ...\n model: string; // e.g. \"claude-sonnet-4-6\"\n operation?: string; // \"extract\" | \"search\" | ...\n attributes?: Attrs;\n /** Override the built-in price table for this call. */\n price?: ModelPrice;\n}\n\n/** Turn LLM token usage into first-class cost. Emits cost/token/call counters\n * and stamps the active span with GenAI semconv attributes. */\nexport function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): { costUsd: number } {\n const cost = costUsd(opts.model, usage.inputTokens, usage.outputTokens, opts.price);\n const base: Attrs = {\n \"gen_ai.provider.name\": opts.provider,\n \"gen_ai.request.model\": opts.model,\n ...(opts.operation ? { \"gen_ai.operation.name\": opts.operation } : {}),\n ...(opts.attributes ?? {}),\n };\n\n count(\"odla.llm.cost.usd\", cost, base);\n count(\"odla.llm.calls\", usage.calls, base);\n count(\"odla.llm.tokens\", usage.inputTokens, { ...base, \"gen_ai.token.type\": \"input\" });\n count(\"odla.llm.tokens\", usage.outputTokens, { ...base, \"gen_ai.token.type\": \"output\" });\n\n trace.getActiveSpan()?.setAttributes({\n ...base,\n \"gen_ai.usage.input_tokens\": usage.inputTokens,\n \"gen_ai.usage.output_tokens\": usage.outputTokens,\n \"odla.llm.cost.usd\": cost,\n });\n\n return { costUsd: cost };\n}\n"],"mappings":"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","names":["metrics","import_api","span","import_api"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/instrument.ts","../src/context.ts","../../o11y-core/src/encode.ts","../../o11y-core/src/pricing.ts","../src/span.ts","../src/metrics.ts","../src/logs.ts","../src/llm.ts"],"sourcesContent":["export { instrumentDurableObject, withObservability } from \"./instrument.js\";\nexport { span } from \"./span.js\";\nexport { count, metrics } from \"./metrics.js\";\nexport { recordError } from \"./logs.js\";\nexport { recordLlmUsage } from \"./llm.js\";\n\nexport type { ObservabilityOptions, OptsFn } from \"./context.js\";\nexport type { SpanKindName, SpanOpts } from \"./span.js\";\nexport type { Metrics } from \"./metrics.js\";\nexport type { ErrorReport } from \"./logs.js\";\nexport type { LlmCostOpts, LlmUsage } from \"./llm.js\";\nexport type { Attrs } from \"@odla/o11y-core\";\n","import { instrument, instrumentDO, type ResolveConfigFn, type TraceConfig } from \"@microlabs/otel-cf-workers\";\nimport { createSink, resolveConfig, runWithSink, type OptsFn } from \"./context.js\";\n\n/** Build the otel-cf-workers trace config (exporter + service) from env/opts.\n * Exported for tests; not part of the package's public surface (see index.ts). */\nexport function traceConfig<E>(env: E, opts?: OptsFn<E>): TraceConfig {\n const c = resolveConfig(env, opts);\n const headers: Record<string, string> = {};\n if (c.token) headers.authorization = `Bearer ${c.token}`;\n const config: TraceConfig = {\n exporter: { url: `${c.endpoint}/v1/traces`, headers },\n service: { name: c.service, version: c.version },\n };\n if (c.sampleRatio != null) config.sampling = { headSampler: { ratio: c.sampleRatio } };\n return config;\n}\n\n/** Wrap each handler entrypoint so an ALS metrics/logs sink is live for the\n * duration of the invocation and flushed via ctx.waitUntil at the end.\n * Exported for tests; not part of the package's public surface (see index.ts). */\nexport function wrapHandler<E>(handler: ExportedHandler<E>, opts?: OptsFn<E>): ExportedHandler<E> {\n const wrapped: ExportedHandler<E> = { ...handler };\n\n const fetchImpl = handler.fetch;\n if (fetchImpl) {\n wrapped.fetch = (req, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await fetchImpl(req, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n const scheduledImpl = handler.scheduled;\n if (scheduledImpl) {\n wrapped.scheduled = (controller, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await scheduledImpl(controller, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n return wrapped;\n}\n\n/** Wrap a Worker handler with tracing (otel-cf-workers) + the metrics/logs sink.\n * Config is read from env vars ODLA_O11Y_{ENDPOINT,SERVICE,TOKEN,VERSION} unless\n * overridden by `opts`. */\nexport function withObservability<E = unknown>(\n handler: ExportedHandler<E>,\n opts?: OptsFn<E>,\n): ExportedHandler<E> {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env as E, opts);\n // instrument constrains E to its own Env type; we support any service env, so\n // cross the boundary with `any` and restore the caller's E on the way out.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrument(wrapHandler(handler, opts) as ExportedHandler<any>, configFn) as ExportedHandler<E>;\n}\n\n/** Wrap a Durable Object class with tracing. (Establishing the metrics/logs sink\n * inside DO methods is added in P1 when odla-db's usage counters are bridged.) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function instrumentDurableObject<T extends abstract new (...args: any[]) => object>(\n cls: T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n opts?: OptsFn<any>,\n): T {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env, opts);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrumentDO(cls as any, configFn) as unknown as T;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n encodeLogsRequest,\n encodeMetricsRequest,\n type Attrs,\n type LogRecord,\n type MetricPoint,\n} from \"@odla/o11y-core\";\n\n/** The instrumentation scope name stamped on every signal. */\nexport const SCOPE = \"@odla-ai/o11y\";\n\nexport interface ObservabilityOptions {\n /** Service name (default: env.ODLA_O11Y_SERVICE). */\n service?: string;\n /** Collector base URL (default: env.ODLA_O11Y_ENDPOINT). */\n endpoint?: string;\n /** Per-service bearer token (default: env.ODLA_O11Y_TOKEN). */\n token?: string;\n /** Release/version tag (default: env.ODLA_O11Y_VERSION ?? \"0.0.0\"). */\n version?: string;\n /** Head-sampling ratio 0..1 for traces (default: 1). */\n sampleRatio?: number;\n /** Static resource attributes merged onto every signal. */\n attributes?: Attrs;\n}\n\nexport type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);\n\nexport interface ResolvedConfig {\n service: string;\n endpoint: string;\n token?: string;\n version: string;\n attributes: Attrs;\n sampleRatio?: number;\n}\n\nfunction evalOpts<E>(opts: OptsFn<E> | undefined, env: E): ObservabilityOptions {\n return typeof opts === \"function\" ? opts(env) : (opts ?? {});\n}\n\n/** Resolve options against the service's env vars. */\nexport function resolveConfig<E>(env: E, opts?: OptsFn<E>): ResolvedConfig {\n const o = evalOpts(opts, env);\n const e = env as Record<string, string | undefined>;\n return {\n service: o.service ?? e[\"ODLA_O11Y_SERVICE\"] ?? \"unknown\",\n endpoint: (o.endpoint ?? e[\"ODLA_O11Y_ENDPOINT\"] ?? \"\").replace(/\\/$/, \"\"),\n token: o.token ?? e[\"ODLA_O11Y_TOKEN\"],\n version: o.version ?? e[\"ODLA_O11Y_VERSION\"] ?? \"0.0.0\",\n attributes: o.attributes ?? {},\n sampleRatio: o.sampleRatio,\n };\n}\n\n/** Nanoseconds since epoch as a decimal string (OTLP timestamp format). */\nexport function nowNano(): string {\n return `${Date.now()}000000`;\n}\n\nexport interface Sink {\n config: ResolvedConfig;\n metrics: MetricPoint[];\n logs: LogRecord[];\n flush(): Promise<void>;\n}\n\nconst als = new AsyncLocalStorage<Sink>();\n\nexport function currentSink(): Sink | undefined {\n return als.getStore();\n}\n\nexport function runWithSink<T>(sink: Sink, fn: () => Promise<T>): Promise<T> {\n return als.run(sink, fn);\n}\n\nfunction resourceAttrs(config: ResolvedConfig): Attrs {\n return { \"service.name\": config.service, \"service.version\": config.version, ...config.attributes };\n}\n\nasync function post(url: string, body: unknown, token?: string): Promise<void> {\n try {\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify(body),\n });\n } catch {\n // Telemetry export is best-effort — never surface into the app.\n }\n}\n\n/** A per-invocation sink: accumulates metric/log records and flushes them to the\n * collector as OTLP/JSON (traces are exported separately by otel-cf-workers). */\nexport function createSink(config: ResolvedConfig): Sink {\n return {\n config,\n metrics: [],\n logs: [],\n async flush(): Promise<void> {\n if (!this.config.endpoint) {\n this.metrics = [];\n this.logs = [];\n return;\n }\n const resource = resourceAttrs(this.config);\n const jobs: Promise<void>[] = [];\n if (this.metrics.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics), this.config.token),\n );\n this.metrics = [];\n }\n if (this.logs.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs), this.config.token),\n );\n this.logs = [];\n }\n await Promise.all(jobs);\n },\n };\n}\n","// OTLP/JSON encoders — the emit side of the wire contract (the SDK produces\n// these; the collector decodes them in later phases). Traces are produced by\n// @microlabs/otel-cf-workers; metrics and logs are hand-rolled here because that\n// library is traces-only and the Node OTel metrics SDK doesn't fit the isolate\n// model.\n\nimport type { Attrs, OtlpKeyValue, OtlpResource, OtlpScope } from \"./otlp.js\";\n\n/** Inverse of kvListToAttrs: a flat attr map → OTLP KeyValue[]. */\nexport function attrsToKvList(attrs: Attrs): OtlpKeyValue[] {\n const out: OtlpKeyValue[] = [];\n for (const [key, value] of Object.entries(attrs)) {\n if (typeof value === \"string\") out.push({ key, value: { stringValue: value } });\n else if (typeof value === \"boolean\") out.push({ key, value: { boolValue: value } });\n else if (Number.isInteger(value)) out.push({ key, value: { intValue: String(value) } });\n else out.push({ key, value: { doubleValue: value } });\n }\n return out;\n}\n\n// ---- Metrics ----\n\nexport type MetricKind = \"sum\" | \"gauge\";\n\nexport interface MetricPoint {\n name: string;\n unit?: string;\n kind: MetricKind;\n value: number;\n attrs: Attrs;\n startUnixNano: string;\n timeUnixNano: string;\n}\n\ninterface OtlpNumberDataPoint {\n attributes?: OtlpKeyValue[];\n startTimeUnixNano?: string;\n timeUnixNano: string;\n asDouble: number;\n}\n\ninterface OtlpMetric {\n name: string;\n unit?: string;\n sum?: { aggregationTemporality: number; isMonotonic: boolean; dataPoints: OtlpNumberDataPoint[] };\n gauge?: { dataPoints: OtlpNumberDataPoint[] };\n}\n\nexport interface ExportMetricsServiceRequest {\n resourceMetrics: Array<{\n resource: OtlpResource;\n scopeMetrics: Array<{ scope: OtlpScope; metrics: OtlpMetric[] }>;\n }>;\n}\n\n// OTLP AggregationTemporality: 1 = DELTA. Each ephemeral isolate reports its own\n// delta; the collector aggregates across invocations into Analytics Engine.\nconst DELTA = 1;\n\nexport function encodeMetricsRequest(\n resource: Attrs,\n scopeName: string,\n points: MetricPoint[],\n): ExportMetricsServiceRequest {\n const metrics: OtlpMetric[] = points.map((p) => {\n const dp: OtlpNumberDataPoint = {\n attributes: attrsToKvList(p.attrs),\n startTimeUnixNano: p.startUnixNano,\n timeUnixNano: p.timeUnixNano,\n asDouble: p.value,\n };\n return p.kind === \"gauge\"\n ? { name: p.name, unit: p.unit, gauge: { dataPoints: [dp] } }\n : { name: p.name, unit: p.unit, sum: { aggregationTemporality: DELTA, isMonotonic: true, dataPoints: [dp] } };\n });\n return {\n resourceMetrics: [\n { resource: { attributes: attrsToKvList(resource) }, scopeMetrics: [{ scope: { name: scopeName }, metrics }] },\n ],\n };\n}\n\n// ---- Logs (errors ride the logs signal) ----\n\nexport interface LogRecord {\n timeUnixNano: string;\n severityNumber: number; // OTel: 17 = ERROR, 9 = INFO\n severityText: string;\n body: string;\n attrs: Attrs;\n traceId?: string;\n spanId?: string;\n}\n\nexport interface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OtlpResource;\n scopeLogs: Array<{\n scope: OtlpScope;\n logRecords: Array<{\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: { stringValue: string };\n attributes: OtlpKeyValue[];\n traceId?: string;\n spanId?: string;\n }>;\n }>;\n }>;\n}\n\nexport function encodeLogsRequest(\n resource: Attrs,\n scopeName: string,\n records: LogRecord[],\n): ExportLogsServiceRequest {\n return {\n resourceLogs: [\n {\n resource: { attributes: attrsToKvList(resource) },\n scopeLogs: [\n {\n scope: { name: scopeName },\n logRecords: records.map((r) => ({\n timeUnixNano: r.timeUnixNano,\n observedTimeUnixNano: r.timeUnixNano,\n severityNumber: r.severityNumber,\n severityText: r.severityText,\n body: { stringValue: r.body },\n attributes: attrsToKvList(r.attrs),\n ...(r.traceId ? { traceId: r.traceId } : {}),\n ...(r.spanId ? { spanId: r.spanId } : {}),\n })),\n },\n ],\n },\n ],\n };\n}\n","// LLM price table + cost computation. OTel GenAI semconv has no cost attribute,\n// so we compute cost downstream from raw token counts × model price. Keep this\n// table in sync with current model pricing (verify against the claude-api\n// reference); unknown models cost 0 and should be added here.\n\nexport interface ModelPrice {\n /** USD per million input tokens. */\n inputPerMTok: number;\n /** USD per million output tokens. */\n outputPerMTok: number;\n}\n\n/** Prices in USD per million tokens (MTok). */\nexport const MODEL_PRICING: Record<string, ModelPrice> = {\n \"claude-haiku-4-5\": { inputPerMTok: 1, outputPerMTok: 5 },\n \"claude-sonnet-4-6\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-sonnet-5\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-opus-4-6\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-7\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-8\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-fable-5\": { inputPerMTok: 10, outputPerMTok: 50 },\n};\n\n/** Compute USD cost for a call. Pass `override` to price a model not in the\n * table (or to apply promotional pricing). Returns 0 for unknown models. */\nexport function costUsd(\n model: string,\n inputTokens: number,\n outputTokens: number,\n override?: ModelPrice,\n): number {\n const price = override ?? MODEL_PRICING[model];\n if (!price) return 0;\n return (inputTokens / 1_000_000) * price.inputPerMTok + (outputTokens / 1_000_000) * price.outputPerMTok;\n}\n","import { SpanKind, SpanStatusCode, trace, type Span } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { SCOPE } from \"./context.js\";\n\nexport type SpanKindName = \"internal\" | \"server\" | \"client\" | \"producer\" | \"consumer\";\n\nexport interface SpanOpts {\n attributes?: Attrs;\n kind?: SpanKindName;\n}\n\nconst KIND: Record<SpanKindName, SpanKind> = {\n internal: SpanKind.INTERNAL,\n server: SpanKind.SERVER,\n client: SpanKind.CLIENT,\n producer: SpanKind.PRODUCER,\n consumer: SpanKind.CONSUMER,\n};\n\n/** Run `fn` inside a new active span. The span is ended automatically and its\n * status set from success/throw. Works whether or not tracing is exporting —\n * falls back to a no-op span if no tracer is installed. */\nexport async function span<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n opts?: SpanOpts,\n): Promise<T> {\n const tracer = trace.getTracer(SCOPE);\n return tracer.startActiveSpan(\n name,\n { attributes: opts?.attributes, kind: opts?.kind ? KIND[opts.kind] : undefined },\n async (s) => {\n try {\n const result = await fn(s);\n s.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n s.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });\n throw err;\n } finally {\n s.end();\n }\n },\n );\n}\n","import type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface Metrics {\n /** Monotonic counter (delta). Formalizes odla-db's usage counters. */\n count(name: string, by?: number, attrs?: Attrs): void;\n /** Last-value gauge. */\n gauge(name: string, value: number, attrs?: Attrs): void;\n /** A single observation (recorded as a delta sum for now; histograms later). */\n histogram(name: string, value: number, attrs?: Attrs, unit?: string): void;\n}\n\nfunction record(kind: \"sum\" | \"gauge\", name: string, value: number, attrs?: Attrs, unit?: string): void {\n const sink = currentSink();\n if (!sink) return; // outside an instrumented invocation — no-op\n const ts = nowNano();\n sink.metrics.push({ name, unit, kind, value, attrs: attrs ?? {}, startUnixNano: ts, timeUnixNano: ts });\n}\n\n/** The ambient metrics API for the current invocation. */\nexport function metrics(): Metrics {\n return {\n count: (name, by = 1, attrs) => record(\"sum\", name, by, attrs),\n gauge: (name, value, attrs) => record(\"gauge\", name, value, attrs),\n histogram: (name, value, attrs, unit) => record(\"sum\", name, value, attrs, unit),\n };\n}\n\n/** Convenience: increment a monotonic counter by `by` (default 1). */\nexport function count(name: string, by = 1, attrs?: Attrs): void {\n record(\"sum\", name, by, attrs);\n}\n","import { SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface ErrorReport {\n /** Stable, low-cardinality error code (e.g. \"unique_violation\"). */\n code?: string;\n /** Low-cardinality route/step where it happened. */\n route?: string;\n /** Extra low-cardinality attributes for the index. */\n attributes?: Attrs;\n /** Rich context for triage — kept only in the R2 artifact bundle. */\n artifacts?: Record<string, unknown>;\n /** Dedup key; the collector fingerprints by (type+code+route) if absent. */\n fingerprint?: string;\n}\n\n// OTel severity number for ERROR.\nconst SEVERITY_ERROR = 17;\n\n/** Record a structured error. Returns an artifact id. The message and stack ride\n * the log body + a denied attribute so the collector persists them ONLY in the\n * R2 artifact bundle, never in metrics or the queryable index. */\nexport function recordError(err: unknown, report?: ErrorReport): string {\n const error = err instanceof Error ? err : new Error(String(err));\n const artifactId = `${Date.now().toString(16)}${crypto.randomUUID().replace(/-/g, \"\")}`;\n const span = trace.getActiveSpan();\n\n const attrs: Attrs = {\n \"error.type\": error.name,\n \"odla.artifact_id\": artifactId,\n ...(report?.code ? { \"error.code\": report.code } : {}),\n ...(report?.route ? { \"odla.route\": report.route } : {}),\n ...(report?.fingerprint ? { \"odla.fingerprint\": report.fingerprint } : {}),\n ...(report?.attributes ?? {}),\n };\n\n if (span) {\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });\n span.setAttributes(attrs);\n }\n\n const sink = currentSink();\n if (sink) {\n const ctx = span?.spanContext();\n sink.logs.push({\n timeUnixNano: nowNano(),\n severityNumber: SEVERITY_ERROR,\n severityText: \"ERROR\",\n body: error.message, // full message → R2 bundle only (collector-enforced)\n attrs: {\n ...attrs,\n // Denied keys: stripped from metrics/index, retained in the R2 bundle.\n ...(error.stack ? { \"exception.stacktrace\": error.stack } : {}),\n ...(report?.artifacts ? { \"odla.artifacts\": JSON.stringify(report.artifacts) } : {}),\n },\n ...(ctx ? { traceId: ctx.traceId, spanId: ctx.spanId } : {}),\n });\n }\n\n return artifactId;\n}\n","import { trace } from \"@opentelemetry/api\";\nimport { costUsd, type Attrs, type ModelPrice } from \"@odla/o11y-core\";\nimport { count } from \"./metrics.js\";\n\n/** The token accounting odla-kg's Provider already returns (and drops today). */\nexport interface LlmUsage {\n calls: number;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface LlmCostOpts {\n provider: string; // \"claude\" | \"openai\" | ...\n model: string; // e.g. \"claude-sonnet-4-6\"\n operation?: string; // \"extract\" | \"search\" | ...\n attributes?: Attrs;\n /** Override the built-in price table for this call. */\n price?: ModelPrice;\n}\n\n/** Turn LLM token usage into first-class cost. Emits cost/token/call counters\n * and stamps the active span with GenAI semconv attributes. */\nexport function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): { costUsd: number } {\n const cost = costUsd(opts.model, usage.inputTokens, usage.outputTokens, opts.price);\n const base: Attrs = {\n \"gen_ai.provider.name\": opts.provider,\n \"gen_ai.request.model\": opts.model,\n ...(opts.operation ? { \"gen_ai.operation.name\": opts.operation } : {}),\n ...(opts.attributes ?? {}),\n };\n\n count(\"odla.llm.cost.usd\", cost, base);\n count(\"odla.llm.calls\", usage.calls, base);\n count(\"odla.llm.tokens\", usage.inputTokens, { ...base, \"gen_ai.token.type\": \"input\" });\n count(\"odla.llm.tokens\", usage.outputTokens, { ...base, \"gen_ai.token.type\": \"output\" });\n\n trace.getActiveSpan()?.setAttributes({\n ...base,\n \"gen_ai.usage.input_tokens\": usage.inputTokens,\n \"gen_ai.usage.output_tokens\": usage.outputTokens,\n \"odla.llm.cost.usd\": cost,\n });\n\n return { costUsd: cost };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,6BAAiF;;;ACAjF,8BAAkC;;;ACS3B,SAAS,cAAc,OAA8B;AAC1D,QAAM,MAAsB,CAAC;AAC7B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,OAAO,UAAU,SAAU,KAAI,KAAK,EAAE,KAAK,OAAO,EAAE,aAAa,MAAM,EAAE,CAAC;AAAA,aACrE,OAAO,UAAU,UAAW,KAAI,KAAK,EAAE,KAAK,OAAO,EAAE,WAAW,MAAM,EAAE,CAAC;AAAA,aACzE,OAAO,UAAU,KAAK,EAAG,KAAI,KAAK,EAAE,KAAK,OAAO,EAAE,UAAU,OAAO,KAAK,EAAE,EAAE,CAAC;AAAA,QACjF,KAAI,KAAK,EAAE,KAAK,OAAO,EAAE,aAAa,MAAM,EAAE,CAAC;AAAA,EACtD;AACA,SAAO;AACT;AAuCA,IAAM,QAAQ;AAEP,SAAS,qBACd,UACA,WACA,QAC6B;AAC7B,QAAMA,WAAwB,OAAO,IAAI,CAAC,MAAM;AAC9C,UAAM,KAA0B;AAAA,MAC9B,YAAY,cAAc,EAAE,KAAK;AAAA,MACjC,mBAAmB,EAAE;AAAA,MACrB,cAAc,EAAE;AAAA,MAChB,UAAU,EAAE;AAAA,IACd;AACA,WAAO,EAAE,SAAS,UACd,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,OAAO,EAAE,YAAY,CAAC,EAAE,EAAE,EAAE,IAC1D,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,KAAK,EAAE,wBAAwB,OAAO,aAAa,MAAM,YAAY,CAAC,EAAE,EAAE,EAAE;AAAA,EAChH,CAAC;AACD,SAAO;AAAA,IACL,iBAAiB;AAAA,MACf,EAAE,UAAU,EAAE,YAAY,cAAc,QAAQ,EAAE,GAAG,cAAc,CAAC,EAAE,OAAO,EAAE,MAAM,UAAU,GAAG,SAAAA,SAAQ,CAAC,EAAE;AAAA,IAC/G;AAAA,EACF;AACF;AAiCO,SAAS,kBACd,UACA,WACA,SAC0B;AAC1B,SAAO;AAAA,IACL,cAAc;AAAA,MACZ;AAAA,QACE,UAAU,EAAE,YAAY,cAAc,QAAQ,EAAE;AAAA,QAChD,WAAW;AAAA,UACT;AAAA,YACE,OAAO,EAAE,MAAM,UAAU;AAAA,YACzB,YAAY,QAAQ,IAAI,CAAC,OAAO;AAAA,cAC9B,cAAc,EAAE;AAAA,cAChB,sBAAsB,EAAE;AAAA,cACxB,gBAAgB,EAAE;AAAA,cAClB,cAAc,EAAE;AAAA,cAChB,MAAM,EAAE,aAAa,EAAE,KAAK;AAAA,cAC5B,YAAY,cAAc,EAAE,KAAK;AAAA,cACjC,GAAI,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,cAC1C,GAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,YACzC,EAAE;AAAA,UACJ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC/HO,IAAM,gBAA4C;AAAA,EACvD,oBAAoB,EAAE,cAAc,GAAG,eAAe,EAAE;AAAA,EACxD,qBAAqB,EAAE,cAAc,GAAG,eAAe,GAAG;AAAA,EAC1D,mBAAmB,EAAE,cAAc,GAAG,eAAe,GAAG;AAAA,EACxD,mBAAmB,EAAE,cAAc,GAAG,eAAe,GAAG;AAAA,EACxD,mBAAmB,EAAE,cAAc,GAAG,eAAe,GAAG;AAAA,EACxD,mBAAmB,EAAE,cAAc,GAAG,eAAe,GAAG;AAAA,EACxD,kBAAkB,EAAE,cAAc,IAAI,eAAe,GAAG;AAC1D;AAIO,SAAS,QACd,OACA,aACA,cACA,UACQ;AACR,QAAM,QAAQ,YAAY,cAAc,KAAK;AAC7C,MAAI,CAAC,MAAO,QAAO;AACnB,SAAQ,cAAc,MAAa,MAAM,eAAgB,eAAe,MAAa,MAAM;AAC7F;;;AFxBO,IAAM,QAAQ;AA4BrB,SAAS,SAAY,MAA6B,KAA8B;AAC9E,SAAO,OAAO,SAAS,aAAa,KAAK,GAAG,IAAK,QAAQ,CAAC;AAC5D;AAGO,SAAS,cAAiB,KAAQ,MAAkC;AACzE,QAAM,IAAI,SAAS,MAAM,GAAG;AAC5B,QAAM,IAAI;AACV,SAAO;AAAA,IACL,SAAS,EAAE,WAAW,EAAE,mBAAmB,KAAK;AAAA,IAChD,WAAW,EAAE,YAAY,EAAE,oBAAoB,KAAK,IAAI,QAAQ,OAAO,EAAE;AAAA,IACzE,OAAO,EAAE,SAAS,EAAE,iBAAiB;AAAA,IACrC,SAAS,EAAE,WAAW,EAAE,mBAAmB,KAAK;AAAA,IAChD,YAAY,EAAE,cAAc,CAAC;AAAA,IAC7B,aAAa,EAAE;AAAA,EACjB;AACF;AAGO,SAAS,UAAkB;AAChC,SAAO,GAAG,KAAK,IAAI,CAAC;AACtB;AASA,IAAM,MAAM,IAAI,0CAAwB;AAEjC,SAAS,cAAgC;AAC9C,SAAO,IAAI,SAAS;AACtB;AAEO,SAAS,YAAe,MAAY,IAAkC;AAC3E,SAAO,IAAI,IAAI,MAAM,EAAE;AACzB;AAEA,SAAS,cAAc,QAA+B;AACpD,SAAO,EAAE,gBAAgB,OAAO,SAAS,mBAAmB,OAAO,SAAS,GAAG,OAAO,WAAW;AACnG;AAEA,eAAe,KAAK,KAAa,MAAe,OAA+B;AAC7E,MAAI;AACF,UAAM,MAAM,KAAK;AAAA,MACf,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAI,QAAQ,EAAE,eAAe,UAAU,KAAK,GAAG,IAAI,CAAC;AAAA,MACtD;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;AAIO,SAAS,WAAW,QAA8B;AACvD,SAAO;AAAA,IACL;AAAA,IACA,SAAS,CAAC;AAAA,IACV,MAAM,CAAC;AAAA,IACP,MAAM,QAAuB;AAC3B,UAAI,CAAC,KAAK,OAAO,UAAU;AACzB,aAAK,UAAU,CAAC;AAChB,aAAK,OAAO,CAAC;AACb;AAAA,MACF;AACA,YAAM,WAAW,cAAc,KAAK,MAAM;AAC1C,YAAM,OAAwB,CAAC;AAC/B,UAAI,KAAK,QAAQ,QAAQ;AACvB,aAAK;AAAA,UACH,KAAK,GAAG,KAAK,OAAO,QAAQ,eAAe,qBAAqB,UAAU,OAAO,KAAK,OAAO,GAAG,KAAK,OAAO,KAAK;AAAA,QACnH;AACA,aAAK,UAAU,CAAC;AAAA,MAClB;AACA,UAAI,KAAK,KAAK,QAAQ;AACpB,aAAK;AAAA,UACH,KAAK,GAAG,KAAK,OAAO,QAAQ,YAAY,kBAAkB,UAAU,OAAO,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK;AAAA,QAC1G;AACA,aAAK,OAAO,CAAC;AAAA,MACf;AACA,YAAM,QAAQ,IAAI,IAAI;AAAA,IACxB;AAAA,EACF;AACF;;;AD1HO,SAAS,YAAe,KAAQ,MAA+B;AACpE,QAAM,IAAI,cAAc,KAAK,IAAI;AACjC,QAAM,UAAkC,CAAC;AACzC,MAAI,EAAE,MAAO,SAAQ,gBAAgB,UAAU,EAAE,KAAK;AACtD,QAAM,SAAsB;AAAA,IAC1B,UAAU,EAAE,KAAK,GAAG,EAAE,QAAQ,cAAc,QAAQ;AAAA,IACpD,SAAS,EAAE,MAAM,EAAE,SAAS,SAAS,EAAE,QAAQ;AAAA,EACjD;AACA,MAAI,EAAE,eAAe,KAAM,QAAO,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE;AACrF,SAAO;AACT;AAKO,SAAS,YAAe,SAA6B,MAAsC;AAChG,QAAM,UAA8B,EAAE,GAAG,QAAQ;AAEjD,QAAM,YAAY,QAAQ;AAC1B,MAAI,WAAW;AACb,YAAQ,QAAQ,CAAC,KAAK,KAAK,QAAQ;AACjC,YAAM,OAAO,WAAW,cAAc,KAAK,IAAI,CAAC;AAChD,aAAO,YAAY,MAAM,YAAY;AACnC,YAAI;AACF,iBAAO,MAAM,UAAU,KAAK,KAAK,GAAG;AAAA,QACtC,UAAE;AACA,cAAI,UAAU,KAAK,MAAM,CAAC;AAAA,QAC5B;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,gBAAgB,QAAQ;AAC9B,MAAI,eAAe;AACjB,YAAQ,YAAY,CAAC,YAAY,KAAK,QAAQ;AAC5C,YAAM,OAAO,WAAW,cAAc,KAAK,IAAI,CAAC;AAChD,aAAO,YAAY,MAAM,YAAY;AACnC,YAAI;AACF,iBAAO,MAAM,cAAc,YAAY,KAAK,GAAG;AAAA,QACjD,UAAE;AACA,cAAI,UAAU,KAAK,MAAM,CAAC;AAAA,QAC5B;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,kBACd,SACA,MACoB;AACpB,QAAM,WAA4B,CAAC,KAAK,aAAa,YAAY,KAAU,IAAI;AAI/E,aAAO,mCAAW,YAAY,SAAS,IAAI,GAA2B,QAAQ;AAChF;AAKO,SAAS,wBACd,KAEA,MACG;AACH,QAAM,WAA4B,CAAC,KAAK,aAAa,YAAY,KAAK,IAAI;AAE1E,aAAO,qCAAa,KAAY,QAAQ;AAC1C;;;AI/EA,iBAA2D;AAW3D,IAAM,OAAuC;AAAA,EAC3C,UAAU,oBAAS;AAAA,EACnB,QAAQ,oBAAS;AAAA,EACjB,QAAQ,oBAAS;AAAA,EACjB,UAAU,oBAAS;AAAA,EACnB,UAAU,oBAAS;AACrB;AAKA,eAAsB,KACpB,MACA,IACA,MACY;AACZ,QAAM,SAAS,iBAAM,UAAU,KAAK;AACpC,SAAO,OAAO;AAAA,IACZ;AAAA,IACA,EAAE,YAAY,MAAM,YAAY,MAAM,MAAM,OAAO,KAAK,KAAK,IAAI,IAAI,OAAU;AAAA,IAC/E,OAAO,MAAM;AACX,UAAI;AACF,cAAM,SAAS,MAAM,GAAG,CAAC;AACzB,UAAE,UAAU,EAAE,MAAM,0BAAe,GAAG,CAAC;AACvC,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,UAAE,UAAU,EAAE,MAAM,0BAAe,OAAO,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AACrG,cAAM;AAAA,MACR,UAAE;AACA,UAAE,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;;;AChCA,SAAS,OAAO,MAAuB,MAAc,OAAe,OAAe,MAAqB;AACtG,QAAM,OAAO,YAAY;AACzB,MAAI,CAAC,KAAM;AACX,QAAM,KAAK,QAAQ;AACnB,OAAK,QAAQ,KAAK,EAAE,MAAM,MAAM,MAAM,OAAO,OAAO,SAAS,CAAC,GAAG,eAAe,IAAI,cAAc,GAAG,CAAC;AACxG;AAGO,SAAS,UAAmB;AACjC,SAAO;AAAA,IACL,OAAO,CAAC,MAAM,KAAK,GAAG,UAAU,OAAO,OAAO,MAAM,IAAI,KAAK;AAAA,IAC7D,OAAO,CAAC,MAAM,OAAO,UAAU,OAAO,SAAS,MAAM,OAAO,KAAK;AAAA,IACjE,WAAW,CAAC,MAAM,OAAO,OAAO,SAAS,OAAO,OAAO,MAAM,OAAO,OAAO,IAAI;AAAA,EACjF;AACF;AAGO,SAAS,MAAM,MAAc,KAAK,GAAG,OAAqB;AAC/D,SAAO,OAAO,MAAM,IAAI,KAAK;AAC/B;;;AC/BA,IAAAC,cAAsC;AAkBtC,IAAM,iBAAiB;AAKhB,SAAS,YAAY,KAAc,QAA8B;AACtE,QAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,QAAM,aAAa,GAAG,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,GAAG,OAAO,WAAW,EAAE,QAAQ,MAAM,EAAE,CAAC;AACrF,QAAMC,QAAO,kBAAM,cAAc;AAEjC,QAAM,QAAe;AAAA,IACnB,cAAc,MAAM;AAAA,IACpB,oBAAoB;AAAA,IACpB,GAAI,QAAQ,OAAO,EAAE,cAAc,OAAO,KAAK,IAAI,CAAC;AAAA,IACpD,GAAI,QAAQ,QAAQ,EAAE,cAAc,OAAO,MAAM,IAAI,CAAC;AAAA,IACtD,GAAI,QAAQ,cAAc,EAAE,oBAAoB,OAAO,YAAY,IAAI,CAAC;AAAA,IACxE,GAAI,QAAQ,cAAc,CAAC;AAAA,EAC7B;AAEA,MAAIA,OAAM;AACR,IAAAA,MAAK,gBAAgB,KAAK;AAC1B,IAAAA,MAAK,UAAU,EAAE,MAAM,2BAAe,OAAO,SAAS,MAAM,QAAQ,CAAC;AACrE,IAAAA,MAAK,cAAc,KAAK;AAAA,EAC1B;AAEA,QAAM,OAAO,YAAY;AACzB,MAAI,MAAM;AACR,UAAM,MAAMA,OAAM,YAAY;AAC9B,SAAK,KAAK,KAAK;AAAA,MACb,cAAc,QAAQ;AAAA,MACtB,gBAAgB;AAAA,MAChB,cAAc;AAAA,MACd,MAAM,MAAM;AAAA;AAAA,MACZ,OAAO;AAAA,QACL,GAAG;AAAA;AAAA,QAEH,GAAI,MAAM,QAAQ,EAAE,wBAAwB,MAAM,MAAM,IAAI,CAAC;AAAA,QAC7D,GAAI,QAAQ,YAAY,EAAE,kBAAkB,KAAK,UAAU,OAAO,SAAS,EAAE,IAAI,CAAC;AAAA,MACpF;AAAA,MACA,GAAI,MAAM,EAAE,SAAS,IAAI,SAAS,QAAQ,IAAI,OAAO,IAAI,CAAC;AAAA,IAC5D,CAAC;AAAA,EACH;AAEA,SAAO;AACT;;;AC9DA,IAAAC,cAAsB;AAsBf,SAAS,eAAe,OAAiB,MAAwC;AACtF,QAAM,OAAO,QAAQ,KAAK,OAAO,MAAM,aAAa,MAAM,cAAc,KAAK,KAAK;AAClF,QAAM,OAAc;AAAA,IAClB,wBAAwB,KAAK;AAAA,IAC7B,wBAAwB,KAAK;AAAA,IAC7B,GAAI,KAAK,YAAY,EAAE,yBAAyB,KAAK,UAAU,IAAI,CAAC;AAAA,IACpE,GAAI,KAAK,cAAc,CAAC;AAAA,EAC1B;AAEA,QAAM,qBAAqB,MAAM,IAAI;AACrC,QAAM,kBAAkB,MAAM,OAAO,IAAI;AACzC,QAAM,mBAAmB,MAAM,aAAa,EAAE,GAAG,MAAM,qBAAqB,QAAQ,CAAC;AACrF,QAAM,mBAAmB,MAAM,cAAc,EAAE,GAAG,MAAM,qBAAqB,SAAS,CAAC;AAEvF,oBAAM,cAAc,GAAG,cAAc;AAAA,IACnC,GAAG;AAAA,IACH,6BAA6B,MAAM;AAAA,IACnC,8BAA8B,MAAM;AAAA,IACpC,qBAAqB;AAAA,EACvB,CAAC;AAED,SAAO,EAAE,SAAS,KAAK;AACzB;","names":["metrics","import_api","span","import_api"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/instrument.ts","../src/context.ts","../../o11y-core/src/encode.ts","../../o11y-core/src/pricing.ts","../src/span.ts","../src/metrics.ts","../src/logs.ts","../src/llm.ts"],"sourcesContent":["import { instrument, instrumentDO, type ResolveConfigFn, type TraceConfig } from \"@microlabs/otel-cf-workers\";\nimport { createSink, resolveConfig, runWithSink, type OptsFn } from \"./context.js\";\n\n/** Build the otel-cf-workers trace config (exporter + service) from env/opts. */\nfunction traceConfig<E>(env: E, opts?: OptsFn<E>): TraceConfig {\n const c = resolveConfig(env, opts);\n const headers: Record<string, string> = {};\n if (c.token) headers.authorization = `Bearer ${c.token}`;\n const config: TraceConfig = {\n exporter: { url: `${c.endpoint}/v1/traces`, headers },\n service: { name: c.service, version: c.version },\n };\n if (c.sampleRatio != null) config.sampling = { headSampler: { ratio: c.sampleRatio } };\n return config;\n}\n\n/** Wrap each handler entrypoint so an ALS metrics/logs sink is live for the\n * duration of the invocation and flushed via ctx.waitUntil at the end. */\nfunction wrapHandler<E>(handler: ExportedHandler<E>, opts?: OptsFn<E>): ExportedHandler<E> {\n const wrapped: ExportedHandler<E> = { ...handler };\n\n const fetchImpl = handler.fetch;\n if (fetchImpl) {\n wrapped.fetch = (req, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await fetchImpl(req, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n const scheduledImpl = handler.scheduled;\n if (scheduledImpl) {\n wrapped.scheduled = (controller, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await scheduledImpl(controller, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n return wrapped;\n}\n\n/** Wrap a Worker handler with tracing (otel-cf-workers) + the metrics/logs sink.\n * Config is read from env vars ODLA_O11Y_{ENDPOINT,SERVICE,TOKEN,VERSION} unless\n * overridden by `opts`. */\nexport function withObservability<E = unknown>(\n handler: ExportedHandler<E>,\n opts?: OptsFn<E>,\n): ExportedHandler<E> {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env as E, opts);\n // instrument constrains E to its own Env type; we support any service env, so\n // cross the boundary with `any` and restore the caller's E on the way out.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrument(wrapHandler(handler, opts) as ExportedHandler<any>, configFn) as ExportedHandler<E>;\n}\n\n/** Wrap a Durable Object class with tracing. (Establishing the metrics/logs sink\n * inside DO methods is added in P1 when odla-db's usage counters are bridged.) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function instrumentDurableObject<T extends abstract new (...args: any[]) => object>(\n cls: T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n opts?: OptsFn<any>,\n): T {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env, opts);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrumentDO(cls as any, configFn) as unknown as T;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n encodeLogsRequest,\n encodeMetricsRequest,\n type Attrs,\n type LogRecord,\n type MetricPoint,\n} from \"@odla/o11y-core\";\n\n/** The instrumentation scope name stamped on every signal. */\nexport const SCOPE = \"@odla-ai/o11y\";\n\nexport interface ObservabilityOptions {\n /** Service name (default: env.ODLA_O11Y_SERVICE). */\n service?: string;\n /** Collector base URL (default: env.ODLA_O11Y_ENDPOINT). */\n endpoint?: string;\n /** Per-service bearer token (default: env.ODLA_O11Y_TOKEN). */\n token?: string;\n /** Release/version tag (default: env.ODLA_O11Y_VERSION ?? \"0.0.0\"). */\n version?: string;\n /** Head-sampling ratio 0..1 for traces (default: 1). */\n sampleRatio?: number;\n /** Static resource attributes merged onto every signal. */\n attributes?: Attrs;\n}\n\nexport type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);\n\nexport interface ResolvedConfig {\n service: string;\n endpoint: string;\n token?: string;\n version: string;\n attributes: Attrs;\n sampleRatio?: number;\n}\n\nfunction evalOpts<E>(opts: OptsFn<E> | undefined, env: E): ObservabilityOptions {\n return typeof opts === \"function\" ? opts(env) : (opts ?? {});\n}\n\n/** Resolve options against the service's env vars. */\nexport function resolveConfig<E>(env: E, opts?: OptsFn<E>): ResolvedConfig {\n const o = evalOpts(opts, env);\n const e = env as Record<string, string | undefined>;\n return {\n service: o.service ?? e[\"ODLA_O11Y_SERVICE\"] ?? \"unknown\",\n endpoint: (o.endpoint ?? e[\"ODLA_O11Y_ENDPOINT\"] ?? \"\").replace(/\\/$/, \"\"),\n token: o.token ?? e[\"ODLA_O11Y_TOKEN\"],\n version: o.version ?? e[\"ODLA_O11Y_VERSION\"] ?? \"0.0.0\",\n attributes: o.attributes ?? {},\n sampleRatio: o.sampleRatio,\n };\n}\n\n/** Nanoseconds since epoch as a decimal string (OTLP timestamp format). */\nexport function nowNano(): string {\n return `${Date.now()}000000`;\n}\n\nexport interface Sink {\n config: ResolvedConfig;\n metrics: MetricPoint[];\n logs: LogRecord[];\n flush(): Promise<void>;\n}\n\nconst als = new AsyncLocalStorage<Sink>();\n\nexport function currentSink(): Sink | undefined {\n return als.getStore();\n}\n\nexport function runWithSink<T>(sink: Sink, fn: () => Promise<T>): Promise<T> {\n return als.run(sink, fn);\n}\n\nfunction resourceAttrs(config: ResolvedConfig): Attrs {\n return { \"service.name\": config.service, \"service.version\": config.version, ...config.attributes };\n}\n\nasync function post(url: string, body: unknown, token?: string): Promise<void> {\n try {\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify(body),\n });\n } catch {\n // Telemetry export is best-effort — never surface into the app.\n }\n}\n\n/** A per-invocation sink: accumulates metric/log records and flushes them to the\n * collector as OTLP/JSON (traces are exported separately by otel-cf-workers). */\nexport function createSink(config: ResolvedConfig): Sink {\n return {\n config,\n metrics: [],\n logs: [],\n async flush(): Promise<void> {\n if (!this.config.endpoint) {\n this.metrics = [];\n this.logs = [];\n return;\n }\n const resource = resourceAttrs(this.config);\n const jobs: Promise<void>[] = [];\n if (this.metrics.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics), this.config.token),\n );\n this.metrics = [];\n }\n if (this.logs.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs), this.config.token),\n );\n this.logs = [];\n }\n await Promise.all(jobs);\n },\n };\n}\n","// OTLP/JSON encoders — the emit side of the wire contract (the SDK produces\n// these; the collector decodes them in later phases). Traces are produced by\n// @microlabs/otel-cf-workers; metrics and logs are hand-rolled here because that\n// library is traces-only and the Node OTel metrics SDK doesn't fit the isolate\n// model.\n\nimport type { Attrs, OtlpKeyValue, OtlpResource, OtlpScope } from \"./otlp.js\";\n\n/** Inverse of kvListToAttrs: a flat attr map → OTLP KeyValue[]. */\nexport function attrsToKvList(attrs: Attrs): OtlpKeyValue[] {\n const out: OtlpKeyValue[] = [];\n for (const [key, value] of Object.entries(attrs)) {\n if (typeof value === \"string\") out.push({ key, value: { stringValue: value } });\n else if (typeof value === \"boolean\") out.push({ key, value: { boolValue: value } });\n else if (Number.isInteger(value)) out.push({ key, value: { intValue: String(value) } });\n else out.push({ key, value: { doubleValue: value } });\n }\n return out;\n}\n\n// ---- Metrics ----\n\nexport type MetricKind = \"sum\" | \"gauge\";\n\nexport interface MetricPoint {\n name: string;\n unit?: string;\n kind: MetricKind;\n value: number;\n attrs: Attrs;\n startUnixNano: string;\n timeUnixNano: string;\n}\n\ninterface OtlpNumberDataPoint {\n attributes?: OtlpKeyValue[];\n startTimeUnixNano?: string;\n timeUnixNano: string;\n asDouble: number;\n}\n\ninterface OtlpMetric {\n name: string;\n unit?: string;\n sum?: { aggregationTemporality: number; isMonotonic: boolean; dataPoints: OtlpNumberDataPoint[] };\n gauge?: { dataPoints: OtlpNumberDataPoint[] };\n}\n\nexport interface ExportMetricsServiceRequest {\n resourceMetrics: Array<{\n resource: OtlpResource;\n scopeMetrics: Array<{ scope: OtlpScope; metrics: OtlpMetric[] }>;\n }>;\n}\n\n// OTLP AggregationTemporality: 1 = DELTA. Each ephemeral isolate reports its own\n// delta; the collector aggregates across invocations into Analytics Engine.\nconst DELTA = 1;\n\nexport function encodeMetricsRequest(\n resource: Attrs,\n scopeName: string,\n points: MetricPoint[],\n): ExportMetricsServiceRequest {\n const metrics: OtlpMetric[] = points.map((p) => {\n const dp: OtlpNumberDataPoint = {\n attributes: attrsToKvList(p.attrs),\n startTimeUnixNano: p.startUnixNano,\n timeUnixNano: p.timeUnixNano,\n asDouble: p.value,\n };\n return p.kind === \"gauge\"\n ? { name: p.name, unit: p.unit, gauge: { dataPoints: [dp] } }\n : { name: p.name, unit: p.unit, sum: { aggregationTemporality: DELTA, isMonotonic: true, dataPoints: [dp] } };\n });\n return {\n resourceMetrics: [\n { resource: { attributes: attrsToKvList(resource) }, scopeMetrics: [{ scope: { name: scopeName }, metrics }] },\n ],\n };\n}\n\n// ---- Logs (errors ride the logs signal) ----\n\nexport interface LogRecord {\n timeUnixNano: string;\n severityNumber: number; // OTel: 17 = ERROR, 9 = INFO\n severityText: string;\n body: string;\n attrs: Attrs;\n traceId?: string;\n spanId?: string;\n}\n\nexport interface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OtlpResource;\n scopeLogs: Array<{\n scope: OtlpScope;\n logRecords: Array<{\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: { stringValue: string };\n attributes: OtlpKeyValue[];\n traceId?: string;\n spanId?: string;\n }>;\n }>;\n }>;\n}\n\nexport function encodeLogsRequest(\n resource: Attrs,\n scopeName: string,\n records: LogRecord[],\n): ExportLogsServiceRequest {\n return {\n resourceLogs: [\n {\n resource: { attributes: attrsToKvList(resource) },\n scopeLogs: [\n {\n scope: { name: scopeName },\n logRecords: records.map((r) => ({\n timeUnixNano: r.timeUnixNano,\n observedTimeUnixNano: r.timeUnixNano,\n severityNumber: r.severityNumber,\n severityText: r.severityText,\n body: { stringValue: r.body },\n attributes: attrsToKvList(r.attrs),\n ...(r.traceId ? { traceId: r.traceId } : {}),\n ...(r.spanId ? { spanId: r.spanId } : {}),\n })),\n },\n ],\n },\n ],\n };\n}\n","// LLM price table + cost computation. OTel GenAI semconv has no cost attribute,\n// so we compute cost downstream from raw token counts × model price. Keep this\n// table in sync with current model pricing (verify against the claude-api\n// reference); unknown models cost 0 and should be added here.\n\nexport interface ModelPrice {\n /** USD per million input tokens. */\n inputPerMTok: number;\n /** USD per million output tokens. */\n outputPerMTok: number;\n}\n\n/** Prices in USD per million tokens (MTok). */\nexport const MODEL_PRICING: Record<string, ModelPrice> = {\n \"claude-haiku-4-5\": { inputPerMTok: 1, outputPerMTok: 5 },\n \"claude-sonnet-4-6\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-sonnet-5\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-opus-4-6\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-7\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-8\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-fable-5\": { inputPerMTok: 10, outputPerMTok: 50 },\n};\n\n/** Compute USD cost for a call. Pass `override` to price a model not in the\n * table (or to apply promotional pricing). Returns 0 for unknown models. */\nexport function costUsd(\n model: string,\n inputTokens: number,\n outputTokens: number,\n override?: ModelPrice,\n): number {\n const price = override ?? MODEL_PRICING[model];\n if (!price) return 0;\n return (inputTokens / 1_000_000) * price.inputPerMTok + (outputTokens / 1_000_000) * price.outputPerMTok;\n}\n","import { SpanKind, SpanStatusCode, trace, type Span } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { SCOPE } from \"./context.js\";\n\nexport type SpanKindName = \"internal\" | \"server\" | \"client\" | \"producer\" | \"consumer\";\n\nexport interface SpanOpts {\n attributes?: Attrs;\n kind?: SpanKindName;\n}\n\nconst KIND: Record<SpanKindName, SpanKind> = {\n internal: SpanKind.INTERNAL,\n server: SpanKind.SERVER,\n client: SpanKind.CLIENT,\n producer: SpanKind.PRODUCER,\n consumer: SpanKind.CONSUMER,\n};\n\n/** Run `fn` inside a new active span. The span is ended automatically and its\n * status set from success/throw. Works whether or not tracing is exporting —\n * falls back to a no-op span if no tracer is installed. */\nexport async function span<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n opts?: SpanOpts,\n): Promise<T> {\n const tracer = trace.getTracer(SCOPE);\n return tracer.startActiveSpan(\n name,\n { attributes: opts?.attributes, kind: opts?.kind ? KIND[opts.kind] : undefined },\n async (s) => {\n try {\n const result = await fn(s);\n s.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n s.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });\n throw err;\n } finally {\n s.end();\n }\n },\n );\n}\n","import type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface Metrics {\n /** Monotonic counter (delta). Formalizes odla-db's usage counters. */\n count(name: string, by?: number, attrs?: Attrs): void;\n /** Last-value gauge. */\n gauge(name: string, value: number, attrs?: Attrs): void;\n /** A single observation (recorded as a delta sum for now; histograms later). */\n histogram(name: string, value: number, attrs?: Attrs, unit?: string): void;\n}\n\nfunction record(kind: \"sum\" | \"gauge\", name: string, value: number, attrs?: Attrs, unit?: string): void {\n const sink = currentSink();\n if (!sink) return; // outside an instrumented invocation — no-op\n const ts = nowNano();\n sink.metrics.push({ name, unit, kind, value, attrs: attrs ?? {}, startUnixNano: ts, timeUnixNano: ts });\n}\n\n/** The ambient metrics API for the current invocation. */\nexport function metrics(): Metrics {\n return {\n count: (name, by = 1, attrs) => record(\"sum\", name, by, attrs),\n gauge: (name, value, attrs) => record(\"gauge\", name, value, attrs),\n histogram: (name, value, attrs, unit) => record(\"sum\", name, value, attrs, unit),\n };\n}\n\n/** Convenience: increment a monotonic counter by `by` (default 1). */\nexport function count(name: string, by = 1, attrs?: Attrs): void {\n record(\"sum\", name, by, attrs);\n}\n","import { SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface ErrorReport {\n /** Stable, low-cardinality error code (e.g. \"unique_violation\"). */\n code?: string;\n /** Low-cardinality route/step where it happened. */\n route?: string;\n /** Extra low-cardinality attributes for the index. */\n attributes?: Attrs;\n /** Rich context for triage — kept only in the R2 artifact bundle. */\n artifacts?: Record<string, unknown>;\n /** Dedup key; the collector fingerprints by (type+code+route) if absent. */\n fingerprint?: string;\n}\n\n// OTel severity number for ERROR.\nconst SEVERITY_ERROR = 17;\n\n/** Record a structured error. Returns an artifact id. The message and stack ride\n * the log body + a denied attribute so the collector persists them ONLY in the\n * R2 artifact bundle, never in metrics or the queryable index. */\nexport function recordError(err: unknown, report?: ErrorReport): string {\n const error = err instanceof Error ? err : new Error(String(err));\n const artifactId = `${Date.now().toString(16)}${crypto.randomUUID().replace(/-/g, \"\")}`;\n const span = trace.getActiveSpan();\n\n const attrs: Attrs = {\n \"error.type\": error.name,\n \"odla.artifact_id\": artifactId,\n ...(report?.code ? { \"error.code\": report.code } : {}),\n ...(report?.route ? { \"odla.route\": report.route } : {}),\n ...(report?.fingerprint ? { \"odla.fingerprint\": report.fingerprint } : {}),\n ...(report?.attributes ?? {}),\n };\n\n if (span) {\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });\n span.setAttributes(attrs);\n }\n\n const sink = currentSink();\n if (sink) {\n const ctx = span?.spanContext();\n sink.logs.push({\n timeUnixNano: nowNano(),\n severityNumber: SEVERITY_ERROR,\n severityText: \"ERROR\",\n body: error.message, // full message → R2 bundle only (collector-enforced)\n attrs: {\n ...attrs,\n // Denied keys: stripped from metrics/index, retained in the R2 bundle.\n ...(error.stack ? { \"exception.stacktrace\": error.stack } : {}),\n ...(report?.artifacts ? { \"odla.artifacts\": JSON.stringify(report.artifacts) } : {}),\n },\n ...(ctx ? { traceId: ctx.traceId, spanId: ctx.spanId } : {}),\n });\n }\n\n return artifactId;\n}\n","import { trace } from \"@opentelemetry/api\";\nimport { costUsd, type Attrs, type ModelPrice } from \"@odla/o11y-core\";\nimport { count } from \"./metrics.js\";\n\n/** The token accounting odla-kg's Provider already returns (and drops today). */\nexport interface LlmUsage {\n calls: number;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface LlmCostOpts {\n provider: string; // \"claude\" | \"openai\" | ...\n model: string; // e.g. \"claude-sonnet-4-6\"\n operation?: string; // \"extract\" | \"search\" | ...\n attributes?: Attrs;\n /** Override the built-in price table for this call. */\n price?: ModelPrice;\n}\n\n/** Turn LLM token usage into first-class cost. Emits cost/token/call counters\n * and stamps the active span with GenAI semconv attributes. */\nexport function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): { costUsd: number } {\n const cost = costUsd(opts.model, usage.inputTokens, usage.outputTokens, opts.price);\n const base: Attrs = {\n \"gen_ai.provider.name\": opts.provider,\n \"gen_ai.request.model\": opts.model,\n ...(opts.operation ? { \"gen_ai.operation.name\": opts.operation } : {}),\n ...(opts.attributes ?? {}),\n };\n\n count(\"odla.llm.cost.usd\", cost, base);\n count(\"odla.llm.calls\", usage.calls, base);\n count(\"odla.llm.tokens\", usage.inputTokens, { ...base, \"gen_ai.token.type\": \"input\" });\n count(\"odla.llm.tokens\", usage.outputTokens, { ...base, \"gen_ai.token.type\": \"output\" });\n\n trace.getActiveSpan()?.setAttributes({\n ...base,\n \"gen_ai.usage.input_tokens\": usage.inputTokens,\n \"gen_ai.usage.output_tokens\": usage.outputTokens,\n \"odla.llm.cost.usd\": cost,\n });\n\n return { costUsd: cost };\n}\n"],"mappings":"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","names":["metrics","SpanStatusCode","trace","span","trace","SpanStatusCode","trace","trace"]}
|
|
1
|
+
{"version":3,"sources":["../src/instrument.ts","../src/context.ts","../../o11y-core/src/encode.ts","../../o11y-core/src/pricing.ts","../src/span.ts","../src/metrics.ts","../src/logs.ts","../src/llm.ts"],"sourcesContent":["import { instrument, instrumentDO, type ResolveConfigFn, type TraceConfig } from \"@microlabs/otel-cf-workers\";\nimport { createSink, resolveConfig, runWithSink, type OptsFn } from \"./context.js\";\n\n/** Build the otel-cf-workers trace config (exporter + service) from env/opts.\n * Exported for tests; not part of the package's public surface (see index.ts). */\nexport function traceConfig<E>(env: E, opts?: OptsFn<E>): TraceConfig {\n const c = resolveConfig(env, opts);\n const headers: Record<string, string> = {};\n if (c.token) headers.authorization = `Bearer ${c.token}`;\n const config: TraceConfig = {\n exporter: { url: `${c.endpoint}/v1/traces`, headers },\n service: { name: c.service, version: c.version },\n };\n if (c.sampleRatio != null) config.sampling = { headSampler: { ratio: c.sampleRatio } };\n return config;\n}\n\n/** Wrap each handler entrypoint so an ALS metrics/logs sink is live for the\n * duration of the invocation and flushed via ctx.waitUntil at the end.\n * Exported for tests; not part of the package's public surface (see index.ts). */\nexport function wrapHandler<E>(handler: ExportedHandler<E>, opts?: OptsFn<E>): ExportedHandler<E> {\n const wrapped: ExportedHandler<E> = { ...handler };\n\n const fetchImpl = handler.fetch;\n if (fetchImpl) {\n wrapped.fetch = (req, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await fetchImpl(req, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n const scheduledImpl = handler.scheduled;\n if (scheduledImpl) {\n wrapped.scheduled = (controller, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await scheduledImpl(controller, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n return wrapped;\n}\n\n/** Wrap a Worker handler with tracing (otel-cf-workers) + the metrics/logs sink.\n * Config is read from env vars ODLA_O11Y_{ENDPOINT,SERVICE,TOKEN,VERSION} unless\n * overridden by `opts`. */\nexport function withObservability<E = unknown>(\n handler: ExportedHandler<E>,\n opts?: OptsFn<E>,\n): ExportedHandler<E> {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env as E, opts);\n // instrument constrains E to its own Env type; we support any service env, so\n // cross the boundary with `any` and restore the caller's E on the way out.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrument(wrapHandler(handler, opts) as ExportedHandler<any>, configFn) as ExportedHandler<E>;\n}\n\n/** Wrap a Durable Object class with tracing. (Establishing the metrics/logs sink\n * inside DO methods is added in P1 when odla-db's usage counters are bridged.) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function instrumentDurableObject<T extends abstract new (...args: any[]) => object>(\n cls: T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n opts?: OptsFn<any>,\n): T {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env, opts);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrumentDO(cls as any, configFn) as unknown as T;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n encodeLogsRequest,\n encodeMetricsRequest,\n type Attrs,\n type LogRecord,\n type MetricPoint,\n} from \"@odla/o11y-core\";\n\n/** The instrumentation scope name stamped on every signal. */\nexport const SCOPE = \"@odla-ai/o11y\";\n\nexport interface ObservabilityOptions {\n /** Service name (default: env.ODLA_O11Y_SERVICE). */\n service?: string;\n /** Collector base URL (default: env.ODLA_O11Y_ENDPOINT). */\n endpoint?: string;\n /** Per-service bearer token (default: env.ODLA_O11Y_TOKEN). */\n token?: string;\n /** Release/version tag (default: env.ODLA_O11Y_VERSION ?? \"0.0.0\"). */\n version?: string;\n /** Head-sampling ratio 0..1 for traces (default: 1). */\n sampleRatio?: number;\n /** Static resource attributes merged onto every signal. */\n attributes?: Attrs;\n}\n\nexport type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);\n\nexport interface ResolvedConfig {\n service: string;\n endpoint: string;\n token?: string;\n version: string;\n attributes: Attrs;\n sampleRatio?: number;\n}\n\nfunction evalOpts<E>(opts: OptsFn<E> | undefined, env: E): ObservabilityOptions {\n return typeof opts === \"function\" ? opts(env) : (opts ?? {});\n}\n\n/** Resolve options against the service's env vars. */\nexport function resolveConfig<E>(env: E, opts?: OptsFn<E>): ResolvedConfig {\n const o = evalOpts(opts, env);\n const e = env as Record<string, string | undefined>;\n return {\n service: o.service ?? e[\"ODLA_O11Y_SERVICE\"] ?? \"unknown\",\n endpoint: (o.endpoint ?? e[\"ODLA_O11Y_ENDPOINT\"] ?? \"\").replace(/\\/$/, \"\"),\n token: o.token ?? e[\"ODLA_O11Y_TOKEN\"],\n version: o.version ?? e[\"ODLA_O11Y_VERSION\"] ?? \"0.0.0\",\n attributes: o.attributes ?? {},\n sampleRatio: o.sampleRatio,\n };\n}\n\n/** Nanoseconds since epoch as a decimal string (OTLP timestamp format). */\nexport function nowNano(): string {\n return `${Date.now()}000000`;\n}\n\nexport interface Sink {\n config: ResolvedConfig;\n metrics: MetricPoint[];\n logs: LogRecord[];\n flush(): Promise<void>;\n}\n\nconst als = new AsyncLocalStorage<Sink>();\n\nexport function currentSink(): Sink | undefined {\n return als.getStore();\n}\n\nexport function runWithSink<T>(sink: Sink, fn: () => Promise<T>): Promise<T> {\n return als.run(sink, fn);\n}\n\nfunction resourceAttrs(config: ResolvedConfig): Attrs {\n return { \"service.name\": config.service, \"service.version\": config.version, ...config.attributes };\n}\n\nasync function post(url: string, body: unknown, token?: string): Promise<void> {\n try {\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify(body),\n });\n } catch {\n // Telemetry export is best-effort — never surface into the app.\n }\n}\n\n/** A per-invocation sink: accumulates metric/log records and flushes them to the\n * collector as OTLP/JSON (traces are exported separately by otel-cf-workers). */\nexport function createSink(config: ResolvedConfig): Sink {\n return {\n config,\n metrics: [],\n logs: [],\n async flush(): Promise<void> {\n if (!this.config.endpoint) {\n this.metrics = [];\n this.logs = [];\n return;\n }\n const resource = resourceAttrs(this.config);\n const jobs: Promise<void>[] = [];\n if (this.metrics.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics), this.config.token),\n );\n this.metrics = [];\n }\n if (this.logs.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs), this.config.token),\n );\n this.logs = [];\n }\n await Promise.all(jobs);\n },\n };\n}\n","// OTLP/JSON encoders — the emit side of the wire contract (the SDK produces\n// these; the collector decodes them in later phases). Traces are produced by\n// @microlabs/otel-cf-workers; metrics and logs are hand-rolled here because that\n// library is traces-only and the Node OTel metrics SDK doesn't fit the isolate\n// model.\n\nimport type { Attrs, OtlpKeyValue, OtlpResource, OtlpScope } from \"./otlp.js\";\n\n/** Inverse of kvListToAttrs: a flat attr map → OTLP KeyValue[]. */\nexport function attrsToKvList(attrs: Attrs): OtlpKeyValue[] {\n const out: OtlpKeyValue[] = [];\n for (const [key, value] of Object.entries(attrs)) {\n if (typeof value === \"string\") out.push({ key, value: { stringValue: value } });\n else if (typeof value === \"boolean\") out.push({ key, value: { boolValue: value } });\n else if (Number.isInteger(value)) out.push({ key, value: { intValue: String(value) } });\n else out.push({ key, value: { doubleValue: value } });\n }\n return out;\n}\n\n// ---- Metrics ----\n\nexport type MetricKind = \"sum\" | \"gauge\";\n\nexport interface MetricPoint {\n name: string;\n unit?: string;\n kind: MetricKind;\n value: number;\n attrs: Attrs;\n startUnixNano: string;\n timeUnixNano: string;\n}\n\ninterface OtlpNumberDataPoint {\n attributes?: OtlpKeyValue[];\n startTimeUnixNano?: string;\n timeUnixNano: string;\n asDouble: number;\n}\n\ninterface OtlpMetric {\n name: string;\n unit?: string;\n sum?: { aggregationTemporality: number; isMonotonic: boolean; dataPoints: OtlpNumberDataPoint[] };\n gauge?: { dataPoints: OtlpNumberDataPoint[] };\n}\n\nexport interface ExportMetricsServiceRequest {\n resourceMetrics: Array<{\n resource: OtlpResource;\n scopeMetrics: Array<{ scope: OtlpScope; metrics: OtlpMetric[] }>;\n }>;\n}\n\n// OTLP AggregationTemporality: 1 = DELTA. Each ephemeral isolate reports its own\n// delta; the collector aggregates across invocations into Analytics Engine.\nconst DELTA = 1;\n\nexport function encodeMetricsRequest(\n resource: Attrs,\n scopeName: string,\n points: MetricPoint[],\n): ExportMetricsServiceRequest {\n const metrics: OtlpMetric[] = points.map((p) => {\n const dp: OtlpNumberDataPoint = {\n attributes: attrsToKvList(p.attrs),\n startTimeUnixNano: p.startUnixNano,\n timeUnixNano: p.timeUnixNano,\n asDouble: p.value,\n };\n return p.kind === \"gauge\"\n ? { name: p.name, unit: p.unit, gauge: { dataPoints: [dp] } }\n : { name: p.name, unit: p.unit, sum: { aggregationTemporality: DELTA, isMonotonic: true, dataPoints: [dp] } };\n });\n return {\n resourceMetrics: [\n { resource: { attributes: attrsToKvList(resource) }, scopeMetrics: [{ scope: { name: scopeName }, metrics }] },\n ],\n };\n}\n\n// ---- Logs (errors ride the logs signal) ----\n\nexport interface LogRecord {\n timeUnixNano: string;\n severityNumber: number; // OTel: 17 = ERROR, 9 = INFO\n severityText: string;\n body: string;\n attrs: Attrs;\n traceId?: string;\n spanId?: string;\n}\n\nexport interface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OtlpResource;\n scopeLogs: Array<{\n scope: OtlpScope;\n logRecords: Array<{\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: { stringValue: string };\n attributes: OtlpKeyValue[];\n traceId?: string;\n spanId?: string;\n }>;\n }>;\n }>;\n}\n\nexport function encodeLogsRequest(\n resource: Attrs,\n scopeName: string,\n records: LogRecord[],\n): ExportLogsServiceRequest {\n return {\n resourceLogs: [\n {\n resource: { attributes: attrsToKvList(resource) },\n scopeLogs: [\n {\n scope: { name: scopeName },\n logRecords: records.map((r) => ({\n timeUnixNano: r.timeUnixNano,\n observedTimeUnixNano: r.timeUnixNano,\n severityNumber: r.severityNumber,\n severityText: r.severityText,\n body: { stringValue: r.body },\n attributes: attrsToKvList(r.attrs),\n ...(r.traceId ? { traceId: r.traceId } : {}),\n ...(r.spanId ? { spanId: r.spanId } : {}),\n })),\n },\n ],\n },\n ],\n };\n}\n","// LLM price table + cost computation. OTel GenAI semconv has no cost attribute,\n// so we compute cost downstream from raw token counts × model price. Keep this\n// table in sync with current model pricing (verify against the claude-api\n// reference); unknown models cost 0 and should be added here.\n\nexport interface ModelPrice {\n /** USD per million input tokens. */\n inputPerMTok: number;\n /** USD per million output tokens. */\n outputPerMTok: number;\n}\n\n/** Prices in USD per million tokens (MTok). */\nexport const MODEL_PRICING: Record<string, ModelPrice> = {\n \"claude-haiku-4-5\": { inputPerMTok: 1, outputPerMTok: 5 },\n \"claude-sonnet-4-6\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-sonnet-5\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-opus-4-6\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-7\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-8\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-fable-5\": { inputPerMTok: 10, outputPerMTok: 50 },\n};\n\n/** Compute USD cost for a call. Pass `override` to price a model not in the\n * table (or to apply promotional pricing). Returns 0 for unknown models. */\nexport function costUsd(\n model: string,\n inputTokens: number,\n outputTokens: number,\n override?: ModelPrice,\n): number {\n const price = override ?? MODEL_PRICING[model];\n if (!price) return 0;\n return (inputTokens / 1_000_000) * price.inputPerMTok + (outputTokens / 1_000_000) * price.outputPerMTok;\n}\n","import { SpanKind, SpanStatusCode, trace, type Span } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { SCOPE } from \"./context.js\";\n\nexport type SpanKindName = \"internal\" | \"server\" | \"client\" | \"producer\" | \"consumer\";\n\nexport interface SpanOpts {\n attributes?: Attrs;\n kind?: SpanKindName;\n}\n\nconst KIND: Record<SpanKindName, SpanKind> = {\n internal: SpanKind.INTERNAL,\n server: SpanKind.SERVER,\n client: SpanKind.CLIENT,\n producer: SpanKind.PRODUCER,\n consumer: SpanKind.CONSUMER,\n};\n\n/** Run `fn` inside a new active span. The span is ended automatically and its\n * status set from success/throw. Works whether or not tracing is exporting —\n * falls back to a no-op span if no tracer is installed. */\nexport async function span<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n opts?: SpanOpts,\n): Promise<T> {\n const tracer = trace.getTracer(SCOPE);\n return tracer.startActiveSpan(\n name,\n { attributes: opts?.attributes, kind: opts?.kind ? KIND[opts.kind] : undefined },\n async (s) => {\n try {\n const result = await fn(s);\n s.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n s.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });\n throw err;\n } finally {\n s.end();\n }\n },\n );\n}\n","import type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface Metrics {\n /** Monotonic counter (delta). Formalizes odla-db's usage counters. */\n count(name: string, by?: number, attrs?: Attrs): void;\n /** Last-value gauge. */\n gauge(name: string, value: number, attrs?: Attrs): void;\n /** A single observation (recorded as a delta sum for now; histograms later). */\n histogram(name: string, value: number, attrs?: Attrs, unit?: string): void;\n}\n\nfunction record(kind: \"sum\" | \"gauge\", name: string, value: number, attrs?: Attrs, unit?: string): void {\n const sink = currentSink();\n if (!sink) return; // outside an instrumented invocation — no-op\n const ts = nowNano();\n sink.metrics.push({ name, unit, kind, value, attrs: attrs ?? {}, startUnixNano: ts, timeUnixNano: ts });\n}\n\n/** The ambient metrics API for the current invocation. */\nexport function metrics(): Metrics {\n return {\n count: (name, by = 1, attrs) => record(\"sum\", name, by, attrs),\n gauge: (name, value, attrs) => record(\"gauge\", name, value, attrs),\n histogram: (name, value, attrs, unit) => record(\"sum\", name, value, attrs, unit),\n };\n}\n\n/** Convenience: increment a monotonic counter by `by` (default 1). */\nexport function count(name: string, by = 1, attrs?: Attrs): void {\n record(\"sum\", name, by, attrs);\n}\n","import { SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface ErrorReport {\n /** Stable, low-cardinality error code (e.g. \"unique_violation\"). */\n code?: string;\n /** Low-cardinality route/step where it happened. */\n route?: string;\n /** Extra low-cardinality attributes for the index. */\n attributes?: Attrs;\n /** Rich context for triage — kept only in the R2 artifact bundle. */\n artifacts?: Record<string, unknown>;\n /** Dedup key; the collector fingerprints by (type+code+route) if absent. */\n fingerprint?: string;\n}\n\n// OTel severity number for ERROR.\nconst SEVERITY_ERROR = 17;\n\n/** Record a structured error. Returns an artifact id. The message and stack ride\n * the log body + a denied attribute so the collector persists them ONLY in the\n * R2 artifact bundle, never in metrics or the queryable index. */\nexport function recordError(err: unknown, report?: ErrorReport): string {\n const error = err instanceof Error ? err : new Error(String(err));\n const artifactId = `${Date.now().toString(16)}${crypto.randomUUID().replace(/-/g, \"\")}`;\n const span = trace.getActiveSpan();\n\n const attrs: Attrs = {\n \"error.type\": error.name,\n \"odla.artifact_id\": artifactId,\n ...(report?.code ? { \"error.code\": report.code } : {}),\n ...(report?.route ? { \"odla.route\": report.route } : {}),\n ...(report?.fingerprint ? { \"odla.fingerprint\": report.fingerprint } : {}),\n ...(report?.attributes ?? {}),\n };\n\n if (span) {\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });\n span.setAttributes(attrs);\n }\n\n const sink = currentSink();\n if (sink) {\n const ctx = span?.spanContext();\n sink.logs.push({\n timeUnixNano: nowNano(),\n severityNumber: SEVERITY_ERROR,\n severityText: \"ERROR\",\n body: error.message, // full message → R2 bundle only (collector-enforced)\n attrs: {\n ...attrs,\n // Denied keys: stripped from metrics/index, retained in the R2 bundle.\n ...(error.stack ? { \"exception.stacktrace\": error.stack } : {}),\n ...(report?.artifacts ? { \"odla.artifacts\": JSON.stringify(report.artifacts) } : {}),\n },\n ...(ctx ? { traceId: ctx.traceId, spanId: ctx.spanId } : {}),\n });\n }\n\n return artifactId;\n}\n","import { trace } from \"@opentelemetry/api\";\nimport { costUsd, type Attrs, type ModelPrice } from \"@odla/o11y-core\";\nimport { count } from \"./metrics.js\";\n\n/** The token accounting odla-kg's Provider already returns (and drops today). */\nexport interface LlmUsage {\n calls: number;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface LlmCostOpts {\n provider: string; // \"claude\" | \"openai\" | ...\n model: string; // e.g. \"claude-sonnet-4-6\"\n operation?: string; // \"extract\" | \"search\" | ...\n attributes?: Attrs;\n /** Override the built-in price table for this call. */\n price?: ModelPrice;\n}\n\n/** Turn LLM token usage into first-class cost. Emits cost/token/call counters\n * and stamps the active span with GenAI semconv attributes. */\nexport function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): { costUsd: number } {\n const cost = costUsd(opts.model, usage.inputTokens, usage.outputTokens, opts.price);\n const base: Attrs = {\n \"gen_ai.provider.name\": opts.provider,\n \"gen_ai.request.model\": opts.model,\n ...(opts.operation ? { \"gen_ai.operation.name\": opts.operation } : {}),\n ...(opts.attributes ?? {}),\n };\n\n count(\"odla.llm.cost.usd\", cost, base);\n count(\"odla.llm.calls\", usage.calls, base);\n count(\"odla.llm.tokens\", usage.inputTokens, { ...base, \"gen_ai.token.type\": \"input\" });\n count(\"odla.llm.tokens\", usage.outputTokens, { ...base, \"gen_ai.token.type\": \"output\" });\n\n trace.getActiveSpan()?.setAttributes({\n ...base,\n \"gen_ai.usage.input_tokens\": usage.inputTokens,\n \"gen_ai.usage.output_tokens\": usage.outputTokens,\n \"odla.llm.cost.usd\": cost,\n });\n\n return { costUsd: cost };\n}\n"],"mappings":"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","names":["metrics","SpanStatusCode","trace","span","trace","SpanStatusCode","trace","trace"]}
|
package/llms.txt
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> Official observability client for odla Cloudflare Workers — OpenTelemetry traces, metrics, structured errors, and LLM cost, exported over OTLP to the odla-o11y collector.
|
|
4
4
|
|
|
5
|
+
> ⚠️ **Experimental — an agentic-coding experiment.** This package is built and
|
|
6
|
+
> operated by autonomous coding agents as an experiment in agentic loops. APIs
|
|
7
|
+
> change without notice and nothing here is production-hardened. **Use at your
|
|
8
|
+
> own risk.**
|
|
9
|
+
|
|
5
10
|
Official observability client for [odla](https://github.com/) Cloudflare Workers.
|
|
6
11
|
Wrap your Worker once and get OpenTelemetry **traces**, **metrics**, structured
|
|
7
12
|
**errors**, and **LLM cost** — exported over OTLP to the odla-o11y collector.
|
|
@@ -46,6 +51,13 @@ Config is read from env vars (override per call via the second `withObservabilit
|
|
|
46
51
|
|
|
47
52
|
`wrangler.jsonc` needs `"compatibility_flags": ["nodejs_compat"]` (for AsyncLocalStorage).
|
|
48
53
|
|
|
54
|
+
Keep `ODLA_O11Y_ENDPOINT` and `ODLA_O11Y_SERVICE` set as plain vars in every
|
|
55
|
+
environment and treat only `ODLA_O11Y_TOKEN` as an optional secret: the
|
|
56
|
+
metrics/logs sink no-ops without an endpoint, but the trace exporter builds
|
|
57
|
+
its URL from it, so an unset endpoint yields export attempts against a
|
|
58
|
+
relative URL (harmless to responses, noisy in logs). Without a token the
|
|
59
|
+
collector rejects the export; the app itself is unaffected.
|
|
60
|
+
|
|
49
61
|
## API
|
|
50
62
|
|
|
51
63
|
- `withObservability(handler, opts?)` — wrap a Worker handler (fetch/scheduled).
|
|
@@ -61,7 +73,7 @@ Config is read from env vars (override per call via the second `withObservabilit
|
|
|
61
73
|
|
|
62
74
|
MIT
|
|
63
75
|
|
|
64
|
-
## API reference (generated from dist/index.d.ts, v1.0.
|
|
76
|
+
## API reference (generated from dist/index.d.ts, v1.0.2)
|
|
65
77
|
|
|
66
78
|
```ts
|
|
67
79
|
import { Attrs, ModelPrice } from '@odla/o11y-core';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@odla-ai/o11y",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Official observability client for odla Cloudflare Workers
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Official observability client for odla Cloudflare Workers \u2014 OpenTelemetry traces, metrics, structured errors, and LLM cost, exported over OTLP to the odla-o11y collector.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.cjs",
|
|
@@ -14,16 +14,32 @@
|
|
|
14
14
|
"require": "./dist/index.cjs"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
-
"files": [
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"llms.txt"
|
|
21
|
+
],
|
|
18
22
|
"sideEffects": false,
|
|
19
23
|
"publishConfig": {
|
|
20
24
|
"access": "public"
|
|
21
25
|
},
|
|
22
|
-
"keywords": [
|
|
26
|
+
"keywords": [
|
|
27
|
+
"odla",
|
|
28
|
+
"odla-o11y",
|
|
29
|
+
"observability",
|
|
30
|
+
"opentelemetry",
|
|
31
|
+
"otlp",
|
|
32
|
+
"tracing",
|
|
33
|
+
"metrics",
|
|
34
|
+
"llm-cost",
|
|
35
|
+
"cloudflare",
|
|
36
|
+
"workers"
|
|
37
|
+
],
|
|
23
38
|
"scripts": {
|
|
24
39
|
"build": "tsup",
|
|
25
40
|
"clean": "rm -rf dist",
|
|
26
|
-
"test": "vitest run
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"coverage": "vitest run --coverage",
|
|
27
43
|
"gen:llms": "node ../../scripts/gen-llms.mjs",
|
|
28
44
|
"prepublishOnly": "npm run build && npm run gen:llms"
|
|
29
45
|
},
|