@opengeni/observability 0.4.16 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -23,10 +23,21 @@ export type Span = {
23
23
  }) => void;
24
24
  };
25
25
  export type MetricLabels = Record<string, AttributeValue>;
26
+ /**
27
+ * Stable selectors shared by OpenGeni's runtime metrics and optional
28
+ * Prometheus/Grafana distribution. Operators can use these values for custom
29
+ * namespaces and dashboard ConfigMaps without duplicating chart internals.
30
+ */
31
+ export declare const OPENGENI_OBSERVABILITY_DISTRIBUTION: {
32
+ readonly monitoringNamespaceLabel: "opengeni.ai/monitoring";
33
+ readonly monitoringNamespaceLabelValue: "enabled";
34
+ readonly grafanaDashboardLabel: "grafana_dashboard";
35
+ readonly grafanaDashboardLabelValue: "1";
36
+ };
26
37
  export type SandboxOperationMetricObservation = {
27
38
  backend: string;
28
39
  op: string;
29
- outcome: "ok" | "failed";
40
+ outcome: "ok" | "not_found" | "failed";
30
41
  durationMs: number;
31
42
  };
32
43
  export declare function createObservability(settings: ObservabilitySettings, options: ObservabilityOptions): Observability;
package/dist/index.js CHANGED
@@ -1,6 +1,12 @@
1
1
  // src/index.ts
2
2
  import { collectDefaultMetrics, Counter, Gauge, Histogram, Registry } from "prom-client";
3
3
  import { SandboxBackend } from "@opengeni/contracts";
4
+ var OPENGENI_OBSERVABILITY_DISTRIBUTION = {
5
+ monitoringNamespaceLabel: "opengeni.ai/monitoring",
6
+ monitoringNamespaceLabelValue: "enabled",
7
+ grafanaDashboardLabel: "grafana_dashboard",
8
+ grafanaDashboardLabelValue: "1"
9
+ };
4
10
  var httpHistogramBuckets = [5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
5
11
  var durationHistogramBuckets = [
6
12
  0.01,
@@ -40,6 +46,145 @@ var SANDBOX_OPERATION_NAMES = /* @__PURE__ */ new Set([
40
46
  "resolveExposedPort",
41
47
  "serializeSessionState"
42
48
  ]);
49
+ var PUBLIC_TELEMETRY_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set([
50
+ "http.request.method",
51
+ "http.response.status_code",
52
+ "opengeni.route",
53
+ "opengeni.duration_ms",
54
+ "opengeni.finalization_duration_ms",
55
+ "opengeni.trigger_kind",
56
+ "opengeni.status",
57
+ "error.type",
58
+ "error.status_code",
59
+ "method",
60
+ "route",
61
+ "status",
62
+ "durationMs",
63
+ "attempt",
64
+ "attempts",
65
+ "delayMs",
66
+ "provider",
67
+ "providerApi",
68
+ "model",
69
+ "inputTokens",
70
+ "outputTokens",
71
+ "cachedTokens",
72
+ "cacheWriteTokens",
73
+ "reasoningTokens",
74
+ "accountChangedFromPrevCall",
75
+ "rejectedFields",
76
+ "dependency",
77
+ "activity",
78
+ "backend",
79
+ "op",
80
+ "outcome",
81
+ "eventType",
82
+ "surface",
83
+ "reason",
84
+ "originalBytes",
85
+ "deliveredBytes",
86
+ "estimatedOriginalTokens",
87
+ "estimatedDeliveredTokens",
88
+ "fullEvidenceAvailable",
89
+ "retainedOutputKind"
90
+ ]);
91
+ var PUBLIC_TELEMETRY_ERROR_CLASSES = /* @__PURE__ */ new Set([
92
+ "OperationError",
93
+ "CodexCheckpointOperationError",
94
+ "CodexFleetShadowOperationError",
95
+ "ComputerActionTimeoutError",
96
+ "ComputerUnavailableError",
97
+ "CredentialRenewalOperationError",
98
+ "CredentialReadOperationError",
99
+ "EventPublishOperationError",
100
+ "GitCredentialRenewalOperationError",
101
+ "HostExportOperationError",
102
+ "HttpOperationError",
103
+ "McpLifecycleError",
104
+ "McpOperationError",
105
+ "MemoryEmbeddingOperationError",
106
+ "MemorySearchOperationError",
107
+ "NatsAuthCalloutOperationError",
108
+ "OAuthOperationError",
109
+ "RunCredentialRenewalOperationError",
110
+ "RunStateCompatibilityError",
111
+ "SnapshotOperationError",
112
+ "StartupDependencyError",
113
+ "TelemetryExportError",
114
+ "ToolspaceOperationError",
115
+ "ToolspaceTokenRenewalOperationError",
116
+ "WorkerLifecycleOperation",
117
+ "WorkerLifecycleOperationError",
118
+ "WorkerOperationError",
119
+ "WorkflowWakeOperationError"
120
+ ]);
121
+ var PUBLIC_TELEMETRY_ERROR_CODES = /* @__PURE__ */ new Set([
122
+ "agent_command_wake_failed",
123
+ "cleared_goal_live_publish_failed",
124
+ "codex_failover_checkpoint_failed",
125
+ "codex_fleet_shadow_failed",
126
+ "codex_lease_loss_checkpoint_failed",
127
+ "codex_active_credential_read_failed",
128
+ "command_yield_timeout",
129
+ "conflict",
130
+ "control_wake_dispatch_failed",
131
+ "fenced_event_live_publish_failed",
132
+ "forbidden",
133
+ "host_export_batch_delivery_failed",
134
+ "host_export_failure_settlement_stale",
135
+ "host_export_pump_iteration_failed",
136
+ "host_export_retention_prune_failed",
137
+ "idempotency_conflict",
138
+ "incompatible_exposed_ports",
139
+ "internal_error",
140
+ "limit_exceeded",
141
+ "mcp_close_failed",
142
+ "mcp_connect_failed",
143
+ "mcp_tool_call_failed",
144
+ "mcp_tools_list_failed",
145
+ "mcp_transport_failed",
146
+ "memory_edit_embedding_failed",
147
+ "memory_hybrid_vector_failed",
148
+ "memory_save_embedding_failed",
149
+ "nats_auth_callout_start_failed",
150
+ "nested_agent_depth_exceeded",
151
+ "nested_agent_depth_override_forbidden",
152
+ "not_found",
153
+ "oauth_operation_failed",
154
+ "otlp_export_failed",
155
+ "payment_required",
156
+ "provider_verification_failed",
157
+ "screenshot_capture_failed",
158
+ "session_event_live_publish_failed",
159
+ "session_workflow_wake_failed",
160
+ "snapshot_operation_failed",
161
+ "startup_dependency_retry",
162
+ "tool_list_too_large",
163
+ "tool_result_too_large",
164
+ "toolspace_operation_failed",
165
+ "unauthenticated",
166
+ "upstream_unavailable",
167
+ "validation_failed",
168
+ "worker_draining",
169
+ "worker_operation_failed",
170
+ "worker_shutdown_request_failed",
171
+ "workspace_control_live_publish_failed"
172
+ ]);
173
+ var PUBLIC_TELEMETRY_ERROR_ORIGINS = /* @__PURE__ */ new Set([
174
+ "api",
175
+ "core",
176
+ "db",
177
+ "events",
178
+ "host-export",
179
+ "oauth",
180
+ "observability",
181
+ "runtime",
182
+ "sandbox-computer",
183
+ "sandbox-resume",
184
+ "toolspace",
185
+ "worker",
186
+ "worker-lifecycle"
187
+ ]);
43
188
  function createObservability(settings, options) {
44
189
  return new Observability(settings, options);
45
190
  }
@@ -93,14 +238,14 @@ var Observability = class {
93
238
  this.log("error", message, attributes);
94
239
  }
95
240
  log(level, message, attributes = {}) {
241
+ const publicAttributes = projectPublicTelemetryAttributes(attributes);
96
242
  if (!this.settings.observabilityStructuredLogs) {
97
- const line = attributes.error ? `${message}: ${String(attributes.error)}` : message;
98
243
  if (level === "warn") {
99
- console.warn(line);
244
+ console.warn(message);
100
245
  } else if (level === "error") {
101
- console.error(line);
246
+ console.error(message);
102
247
  } else {
103
- console.log(line);
248
+ console.log(message);
104
249
  }
105
250
  return;
106
251
  }
@@ -111,7 +256,7 @@ var Observability = class {
111
256
  service: this.settings.serviceName,
112
257
  environment: this.settings.environment,
113
258
  component: this.options.component,
114
- ...cleanAttributes(attributes)
259
+ ...cleanAttributes(publicAttributes)
115
260
  };
116
261
  const serialized = JSON.stringify(record);
117
262
  if (level === "warn") {
@@ -135,8 +280,8 @@ var Observability = class {
135
280
  return;
136
281
  }
137
282
  ended = true;
138
- const sanitizedError = input.error !== void 0 && input.error !== null ? sanitizeSpanError(input.error) : void 0;
139
- const errorAttributes = sanitizedError ? errorToAttributes(sanitizedError) : {};
283
+ const telemetryError = input.error !== void 0 && input.error !== null ? projectSpanErrorForTelemetry(input.error) : void 0;
284
+ const errorAttributes = telemetryError ? errorToAttributes(telemetryError) : {};
140
285
  this.exportSpan({
141
286
  traceId,
142
287
  spanId,
@@ -144,11 +289,13 @@ var Observability = class {
144
289
  startMs,
145
290
  endMs: this.now(),
146
291
  attributes: {
147
- ...attributes,
148
- ...input.attributes,
149
- ...errorAttributes
292
+ ...projectPublicTelemetryAttributes({
293
+ ...attributes,
294
+ ...input.attributes,
295
+ ...errorAttributes
296
+ })
150
297
  },
151
- ...sanitizedError ? { error: sanitizedError } : {}
298
+ ...telemetryError ? { error: telemetryError } : {}
152
299
  });
153
300
  }
154
301
  };
@@ -325,8 +472,12 @@ var Observability = class {
325
472
  ]
326
473
  };
327
474
  void this.exporter(endpoint, body, parseHeaders(this.settings.observabilityOtlpHeaders)).catch(
328
- (error) => {
329
- this.warn("OTLP span export failed", { error: errorMessage(error), endpoint });
475
+ () => {
476
+ this.warn("OTLP span export failed", {
477
+ errorClass: "TelemetryExportError",
478
+ errorCode: "otlp_export_failed",
479
+ origin: "observability"
480
+ });
330
481
  }
331
482
  );
332
483
  }
@@ -360,13 +511,14 @@ function sandboxOperationMetricObserver(observability) {
360
511
  };
361
512
  }
362
513
  function logStartupDependencyRetry(observability, event) {
363
- const message = event.error instanceof Error ? event.error.message : String(event.error);
364
514
  observability.warn("Startup dependency connection failed; retrying", {
365
515
  dependency: event.label,
366
516
  attempt: event.attempt,
367
517
  attempts: event.attempts,
368
518
  delayMs: event.delayMs,
369
- error: message
519
+ errorClass: "StartupDependencyError",
520
+ errorCode: "startup_dependency_retry",
521
+ origin: "observability"
370
522
  });
371
523
  }
372
524
  function normalizeLabels(labels = {}) {
@@ -382,20 +534,45 @@ function cleanAttributes(attributes) {
382
534
  Object.entries(attributes).filter(([, value]) => value !== void 0)
383
535
  );
384
536
  }
385
- function sanitizeSpanError(error) {
386
- const type = error instanceof Error && /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(error.name) ? error.name : "Error";
537
+ function projectPublicTelemetryAttributes(attributes) {
538
+ if ("errorClass" in attributes || "errorCode" in attributes) {
539
+ return projectPublicDiagnosticAttributes(attributes);
540
+ }
541
+ return Object.fromEntries(
542
+ Object.entries(attributes).filter(
543
+ ([key, value]) => value !== void 0 && PUBLIC_TELEMETRY_ATTRIBUTE_KEYS.has(key)
544
+ )
545
+ );
546
+ }
547
+ function projectPublicDiagnosticAttributes(attributes) {
548
+ const errorClass = attributes.errorClass;
549
+ const errorCode = attributes.errorCode;
550
+ const status = attributes.status;
551
+ const origin = attributes.origin;
552
+ return {
553
+ errorClass: typeof errorClass === "string" && PUBLIC_TELEMETRY_ERROR_CLASSES.has(errorClass) ? errorClass : "OperationError",
554
+ ...typeof errorCode === "string" && PUBLIC_TELEMETRY_ERROR_CODES.has(errorCode) ? { errorCode } : {},
555
+ ...typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599 ? { status } : {},
556
+ ...typeof origin === "string" && PUBLIC_TELEMETRY_ERROR_ORIGINS.has(origin) ? { origin } : {}
557
+ };
558
+ }
559
+ function projectSpanErrorForTelemetry(error) {
387
560
  const statusCode = errorStatusCode(error);
388
561
  return {
389
- type,
562
+ type: "OperationError",
390
563
  ...statusCode === void 0 ? {} : { statusCode },
391
564
  statusMessage: statusCode === void 0 ? "operation failed" : `HTTP ${statusCode}`
392
565
  };
393
566
  }
394
567
  function errorStatusCode(error) {
395
568
  if (typeof error !== "object" || error === null) return void 0;
396
- const value = error.status;
397
- const statusCode = Number.isInteger(value) && typeof value === "number" ? value : error.statusCode;
398
- return Number.isInteger(statusCode) && typeof statusCode === "number" && statusCode >= 100 && statusCode <= 599 ? statusCode : void 0;
569
+ try {
570
+ const value = error.status;
571
+ const statusCode = Number.isInteger(value) && typeof value === "number" ? value : error.statusCode;
572
+ return Number.isInteger(statusCode) && typeof statusCode === "number" && statusCode >= 100 && statusCode <= 599 ? statusCode : void 0;
573
+ } catch {
574
+ return void 0;
575
+ }
399
576
  }
400
577
  function errorToAttributes(error) {
401
578
  return {
@@ -403,9 +580,6 @@ function errorToAttributes(error) {
403
580
  ...error.statusCode === void 0 ? {} : { "error.status_code": error.statusCode }
404
581
  };
405
582
  }
406
- function errorMessage(error) {
407
- return error instanceof Error ? error.message : String(error);
408
- }
409
583
  function otlpAttributes(attributes) {
410
584
  return Object.entries(cleanAttributes(attributes)).map(([key, value]) => ({
411
585
  key,
@@ -460,6 +634,7 @@ async function defaultExporter(url, body, headers) {
460
634
  }
461
635
  }
462
636
  export {
637
+ OPENGENI_OBSERVABILITY_DISTRIBUTION,
463
638
  Observability,
464
639
  createObservability,
465
640
  logStartupDependencyRetry,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { collectDefaultMetrics, Counter, Gauge, Histogram, Registry } from \"prom-client\";\nimport { SandboxBackend } from \"@opengeni/contracts\";\n\nexport type AttributeValue = string | number | boolean | null | undefined;\nexport type Attributes = Record<string, AttributeValue>;\n\nexport type ObservabilitySettings = {\n serviceName: string;\n environment: string;\n deploymentRevision?: string | undefined;\n observabilityStructuredLogs: boolean;\n observabilityMetricsEnabled: boolean;\n observabilityOtlpEndpoint?: string | undefined;\n observabilityOtlpHeaders: string;\n};\n\nexport type ObservabilityOptions = {\n component: string;\n now?: () => number;\n exporter?: (url: string, body: unknown, headers: Record<string, string>) => Promise<void>;\n};\n\nexport type Span = {\n traceId: string;\n spanId: string;\n end: (input?: { attributes?: Attributes; error?: unknown }) => void;\n};\n\nexport type MetricLabels = Record<string, AttributeValue>;\n\nconst httpHistogramBuckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\nconst durationHistogramBuckets = [\n 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300, 900, 1800, 3600,\n];\n\nconst SANDBOX_OPERATION_BACKENDS = new Set<string>([...SandboxBackend.options, \"unprovisioned\"]);\n\nconst SANDBOX_OPERATION_NAMES = new Set([\n \"desktopInput\",\n \"screenshot\",\n \"exec\",\n \"execCommand\",\n \"writeStdin\",\n \"cancelExecCommand\",\n \"readFile\",\n \"writeFile\",\n \"listDir\",\n \"pathExists\",\n \"viewImage\",\n \"materializeEntry\",\n \"editor.createFile\",\n \"editor.updateFile\",\n \"editor.deleteFile\",\n \"resolveExposedPort\",\n \"serializeSessionState\",\n]);\n\nexport type SandboxOperationMetricObservation = {\n backend: string;\n op: string;\n outcome: \"ok\" | \"failed\";\n durationMs: number;\n};\n\nexport function createObservability(\n settings: ObservabilitySettings,\n options: ObservabilityOptions,\n): Observability {\n return new Observability(settings, options);\n}\n\ntype MetricRegistration = {\n kind: \"counter\" | \"gauge\" | \"histogram\";\n labelNames: string[];\n};\n\nexport class Observability {\n private readonly registry = new Registry();\n private readonly counters = new Map<string, Counter<string>>();\n private readonly gauges = new Map<string, Gauge<string>>();\n private readonly histograms = new Map<string, Histogram<string>>();\n private readonly registrations = new Map<string, MetricRegistration>();\n private readonly now: () => number;\n private readonly exporter: (\n url: string,\n body: unknown,\n headers: Record<string, string>,\n ) => Promise<void>;\n private readonly resourceAttributes: Attributes;\n\n constructor(\n private readonly settings: ObservabilitySettings,\n private readonly options: ObservabilityOptions,\n ) {\n this.now = options.now ?? Date.now;\n this.exporter = options.exporter ?? defaultExporter;\n this.resourceAttributes = {\n \"service.name\": settings.serviceName,\n \"deployment.environment\": settings.environment,\n \"opengeni.component\": options.component,\n };\n this.registry.setDefaultLabels({\n service: settings.serviceName,\n environment: settings.environment,\n component: options.component,\n });\n if (settings.observabilityMetricsEnabled) {\n collectDefaultMetrics({ register: this.registry, prefix: \"opengeni_\" });\n this.setGauge({\n name: \"opengeni_build_info\",\n help: \"OpenGeni build information.\",\n labels: {\n version: buildVersion(),\n revision: settings.deploymentRevision ?? \"dev\",\n },\n value: 1,\n });\n }\n }\n\n debug(message: string, attributes: Attributes = {}): void {\n this.log(\"debug\", message, attributes);\n }\n\n info(message: string, attributes: Attributes = {}): void {\n this.log(\"info\", message, attributes);\n }\n\n warn(message: string, attributes: Attributes = {}): void {\n this.log(\"warn\", message, attributes);\n }\n\n error(message: string, attributes: Attributes = {}): void {\n this.log(\"error\", message, attributes);\n }\n\n log(\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n attributes: Attributes = {},\n ): void {\n if (!this.settings.observabilityStructuredLogs) {\n const line = attributes.error ? `${message}: ${String(attributes.error)}` : message;\n if (level === \"warn\") {\n console.warn(line);\n } else if (level === \"error\") {\n console.error(line);\n } else {\n console.log(line);\n }\n return;\n }\n const record = {\n timestamp: new Date(this.now()).toISOString(),\n level,\n message,\n service: this.settings.serviceName,\n environment: this.settings.environment,\n component: this.options.component,\n ...cleanAttributes(attributes),\n };\n const serialized = JSON.stringify(record);\n if (level === \"warn\") {\n console.warn(serialized);\n } else if (level === \"error\") {\n console.error(serialized);\n } else {\n console.log(serialized);\n }\n }\n\n startSpan(name: string, attributes: Attributes = {}): Span {\n const traceId = randomHex(16);\n const spanId = randomHex(8);\n const startMs = this.now();\n let ended = false;\n return {\n traceId,\n spanId,\n end: (input = {}) => {\n if (ended) {\n return;\n }\n ended = true;\n const sanitizedError =\n input.error !== undefined && input.error !== null\n ? sanitizeSpanError(input.error)\n : undefined;\n const errorAttributes = sanitizedError ? errorToAttributes(sanitizedError) : {};\n this.exportSpan({\n traceId,\n spanId,\n name,\n startMs,\n endMs: this.now(),\n attributes: {\n ...attributes,\n ...input.attributes,\n ...errorAttributes,\n },\n ...(sanitizedError ? { error: sanitizedError } : {}),\n });\n },\n };\n }\n\n recordHttpRequest(input: {\n method: string;\n route: string;\n status: number;\n durationSeconds: number;\n }): void {\n this.incrementCounter({\n name: \"opengeni_http_requests_total\",\n help: \"Total HTTP requests handled by OpenGeni.\",\n labels: {\n method: input.method,\n route: input.route,\n status: String(input.status),\n component: this.options.component,\n },\n });\n this.observeHistogram({\n name: \"opengeni_http_request_duration_seconds\",\n help: \"HTTP request duration in seconds.\",\n buckets: httpHistogramBuckets,\n value: input.durationSeconds,\n labels: {\n method: input.method,\n route: input.route,\n component: this.options.component,\n },\n });\n }\n\n recordWorkerActivity(input: { activity: string; status: string; durationSeconds: number }): void {\n this.incrementCounter({\n name: \"opengeni_worker_activity_runs_total\",\n help: \"Total worker activity executions.\",\n labels: {\n activity: input.activity,\n status: input.status,\n component: this.options.component,\n },\n });\n this.observeHistogram({\n name: \"opengeni_worker_activity_duration_seconds\",\n help: \"Worker activity duration in seconds.\",\n buckets: durationHistogramBuckets,\n value: input.durationSeconds,\n labels: {\n activity: input.activity,\n component: this.options.component,\n },\n });\n }\n\n incrementCounter(input: {\n name: string;\n help?: string;\n labels?: MetricLabels;\n amount?: number;\n }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const counter = this.counter(\n input.name,\n input.help ?? `${input.name} counter.`,\n Object.keys(labels),\n );\n counter.inc(labels as never, input.amount ?? 1);\n }\n\n setGauge(input: { name: string; help?: string; labels?: MetricLabels; value: number }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const gauge = this.gauge(input.name, input.help ?? `${input.name} gauge.`, Object.keys(labels));\n gauge.set(labels as never, input.value);\n }\n\n incrementGauge(input: {\n name: string;\n help?: string;\n labels?: MetricLabels;\n amount?: number;\n }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const gauge = this.gauge(input.name, input.help ?? `${input.name} gauge.`, Object.keys(labels));\n gauge.inc(labels as never, input.amount ?? 1);\n }\n\n observeHistogram(input: {\n name: string;\n help?: string;\n labels?: MetricLabels;\n value: number;\n buckets?: number[];\n }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const histogram = this.histogram(\n input.name,\n input.help ?? `${input.name} histogram.`,\n Object.keys(labels),\n input.buckets ?? durationHistogramBuckets,\n );\n histogram.observe(labels as never, input.value);\n }\n\n async prometheusMetrics(): Promise<string> {\n if (!this.settings.observabilityMetricsEnabled) {\n return \"\";\n }\n return await this.registry.metrics();\n }\n\n private counter(name: string, help: string, labelNames: string[]): Counter<string> {\n const existing = this.counters.get(name);\n if (existing) {\n this.assertRegistration(name, \"counter\", labelNames);\n return existing;\n }\n this.register(name, \"counter\", labelNames);\n const metric = new Counter({ name, help, labelNames, registers: [this.registry] });\n this.counters.set(name, metric);\n return metric;\n }\n\n private gauge(name: string, help: string, labelNames: string[]): Gauge<string> {\n const existing = this.gauges.get(name);\n if (existing) {\n this.assertRegistration(name, \"gauge\", labelNames);\n return existing;\n }\n this.register(name, \"gauge\", labelNames);\n const metric = new Gauge({ name, help, labelNames, registers: [this.registry] });\n this.gauges.set(name, metric);\n return metric;\n }\n\n private histogram(\n name: string,\n help: string,\n labelNames: string[],\n buckets: number[],\n ): Histogram<string> {\n const existing = this.histograms.get(name);\n if (existing) {\n this.assertRegistration(name, \"histogram\", labelNames);\n return existing;\n }\n this.register(name, \"histogram\", labelNames);\n const metric = new Histogram({ name, help, labelNames, buckets, registers: [this.registry] });\n this.histograms.set(name, metric);\n return metric;\n }\n\n private register(name: string, kind: MetricRegistration[\"kind\"], labelNames: string[]): void {\n const sorted = [...labelNames].sort();\n this.registrations.set(name, { kind, labelNames: sorted });\n }\n\n private assertRegistration(\n name: string,\n kind: MetricRegistration[\"kind\"],\n labelNames: string[],\n ): void {\n const registration = this.registrations.get(name);\n const sorted = [...labelNames].sort();\n if (\n !registration ||\n registration.kind !== kind ||\n registration.labelNames.length !== sorted.length ||\n registration.labelNames.some((label, index) => label !== sorted[index])\n ) {\n throw new Error(\n `Metric ${name} was already registered as ${registration?.kind ?? \"unknown\"} ` +\n `with labels [${registration?.labelNames.join(\",\") ?? \"\"}], not ${kind} [${sorted.join(\",\")}]`,\n );\n }\n }\n\n private exportSpan(span: {\n traceId: string;\n spanId: string;\n name: string;\n startMs: number;\n endMs: number;\n attributes: Attributes;\n error?: SanitizedSpanError;\n }): void {\n if (!this.settings.observabilityOtlpEndpoint) {\n return;\n }\n const endpoint = `${this.settings.observabilityOtlpEndpoint.replace(/\\/$/, \"\")}/v1/traces`;\n const body = {\n resourceSpans: [\n {\n resource: {\n attributes: otlpAttributes(this.resourceAttributes),\n },\n scopeSpans: [\n {\n scope: {\n name: \"@opengeni/observability\",\n version: \"0.1.0\",\n },\n spans: [\n {\n traceId: span.traceId,\n spanId: span.spanId,\n name: span.name,\n kind: 1,\n startTimeUnixNano: millisToNanos(span.startMs),\n endTimeUnixNano: millisToNanos(span.endMs),\n attributes: otlpAttributes(span.attributes),\n status: span.error ? { code: 2, message: span.error.statusMessage } : { code: 1 },\n },\n ],\n },\n ],\n },\n ],\n };\n void this.exporter(endpoint, body, parseHeaders(this.settings.observabilityOtlpHeaders)).catch(\n (error) => {\n this.warn(\"OTLP span export failed\", { error: errorMessage(error), endpoint });\n },\n );\n }\n}\n\n/**\n * Convert routed sandbox-provider observations into one bounded Prometheus\n * contract shared by API-direct and worker-turn execution. Provider/session\n * identifiers, commands, paths, and other request data can never become labels:\n * a future backend or operation is deliberately collapsed to `unknown` until\n * this allowlist is reviewed.\n *\n * The returned callback is fail-safe. Metrics must never change the provider\n * operation's result; a registration/exporter error is counted best-effort and\n * then swallowed at this telemetry boundary.\n */\nexport function sandboxOperationMetricObserver(\n observability: Observability,\n): (observation: SandboxOperationMetricObservation) => void {\n return (observation) => {\n const backend = SANDBOX_OPERATION_BACKENDS.has(observation.backend)\n ? observation.backend\n : \"unknown\";\n const op = SANDBOX_OPERATION_NAMES.has(observation.op) ? observation.op : \"unknown\";\n try {\n observability.incrementCounter({\n name: \"opengeni_sandbox_operations_total\",\n help: \"Physical routed sandbox provider operations by backend, operation, and outcome.\",\n labels: { backend, op, outcome: observation.outcome },\n });\n observability.observeHistogram({\n name: \"opengeni_sandbox_operation_duration_seconds\",\n help: \"Physical routed sandbox provider-operation duration in seconds.\",\n labels: { backend, op },\n value: Math.max(0, observation.durationMs) / 1_000,\n });\n } catch {\n try {\n observability.incrementCounter({\n name: \"opengeni_observability_observer_errors_total\",\n help: \"Observability observer failures isolated from product execution.\",\n labels: { observer: \"sandbox_operation\" },\n });\n } catch {\n // The metrics registry itself is unhealthy. Product execution remains\n // authoritative and must not inherit an observability failure.\n }\n }\n };\n}\n\nexport type StartupDependencyRetryEvent = {\n label: string;\n attempt: number;\n attempts: number;\n delayMs: number;\n error: unknown;\n};\n\nexport function logStartupDependencyRetry(\n observability: Observability,\n event: StartupDependencyRetryEvent,\n): void {\n const message = event.error instanceof Error ? event.error.message : String(event.error);\n observability.warn(\"Startup dependency connection failed; retrying\", {\n dependency: event.label,\n attempt: event.attempt,\n attempts: event.attempts,\n delayMs: event.delayMs,\n error: message,\n });\n}\n\nfunction normalizeLabels(labels: MetricLabels = {}): Record<string, string> {\n return Object.fromEntries(\n Object.entries(labels)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]): [string, string] => [key, String(value)])\n .sort((left, right) => left[0].localeCompare(right[0])),\n );\n}\n\nfunction buildVersion(): string {\n return process.env.OPENGENI_VERSION ?? process.env.npm_package_version ?? \"dev\";\n}\n\nfunction cleanAttributes(attributes: Attributes): Record<string, string | number | boolean | null> {\n return Object.fromEntries(\n Object.entries(attributes).filter(([, value]) => value !== undefined),\n ) as Record<string, string | number | boolean | null>;\n}\n\ntype SanitizedSpanError = {\n type: string;\n statusCode?: number;\n statusMessage: string;\n};\n\nfunction sanitizeSpanError(error: unknown): SanitizedSpanError {\n const type =\n error instanceof Error && /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(error.name)\n ? error.name\n : \"Error\";\n const statusCode = errorStatusCode(error);\n return {\n type,\n ...(statusCode === undefined ? {} : { statusCode }),\n statusMessage: statusCode === undefined ? \"operation failed\" : `HTTP ${statusCode}`,\n };\n}\n\nfunction errorStatusCode(error: unknown): number | undefined {\n if (typeof error !== \"object\" || error === null) return undefined;\n const value = (error as { status?: unknown; statusCode?: unknown }).status;\n const statusCode =\n Number.isInteger(value) && typeof value === \"number\"\n ? value\n : (error as { statusCode?: unknown }).statusCode;\n return Number.isInteger(statusCode) &&\n typeof statusCode === \"number\" &&\n statusCode >= 100 &&\n statusCode <= 599\n ? statusCode\n : undefined;\n}\n\nfunction errorToAttributes(error: SanitizedSpanError): Attributes {\n return {\n \"error.type\": error.type,\n ...(error.statusCode === undefined ? {} : { \"error.status_code\": error.statusCode }),\n };\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction otlpAttributes(\n attributes: Attributes,\n): Array<{ key: string; value: Record<string, string | number | boolean> }> {\n return Object.entries(cleanAttributes(attributes)).map(([key, value]) => ({\n key,\n value: otlpValue(value),\n }));\n}\n\nfunction otlpValue(\n value: string | number | boolean | null,\n): Record<string, string | number | boolean> {\n if (typeof value === \"number\") {\n return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };\n }\n if (typeof value === \"boolean\") {\n return { boolValue: value };\n }\n return { stringValue: value === null ? \"\" : boundedOtlpString(value) };\n}\n\nfunction boundedOtlpString(value: string): string {\n const bytes = new TextEncoder().encode(value);\n if (bytes.byteLength <= 512) return value;\n return `${new TextDecoder().decode(bytes.slice(0, 509))}…`;\n}\n\nfunction millisToNanos(ms: number): string {\n return String(BigInt(Math.round(ms)) * 1_000_000n);\n}\n\nfunction randomHex(bytes: number): string {\n const values = crypto.getRandomValues(new Uint8Array(bytes));\n return Array.from(values, (value) => value.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function parseHeaders(value: string): Record<string, string> {\n if (!value.trim()) {\n return {};\n }\n const entries: Array<[string, string]> = value\n .split(\",\")\n .map((pair): [string, string] => {\n const separator = pair.indexOf(\"=\");\n if (separator === -1) {\n return [pair.trim(), \"\"];\n }\n return [pair.slice(0, separator).trim(), pair.slice(separator + 1).trim()];\n })\n .filter(([key]) => key.length > 0);\n return Object.fromEntries(entries);\n}\n\nasync function defaultExporter(\n url: string,\n body: unknown,\n headers: Record<string, string>,\n): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n throw new Error(`OTLP endpoint returned HTTP ${response.status}`);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { collectDefaultMetrics, Counter, Gauge, Histogram, Registry } from \"prom-client\";\nimport { SandboxBackend } from \"@opengeni/contracts\";\n\nexport type AttributeValue = string | number | boolean | null | undefined;\nexport type Attributes = Record<string, AttributeValue>;\n\nexport type ObservabilitySettings = {\n serviceName: string;\n environment: string;\n deploymentRevision?: string | undefined;\n observabilityStructuredLogs: boolean;\n observabilityMetricsEnabled: boolean;\n observabilityOtlpEndpoint?: string | undefined;\n observabilityOtlpHeaders: string;\n};\n\nexport type ObservabilityOptions = {\n component: string;\n now?: () => number;\n exporter?: (url: string, body: unknown, headers: Record<string, string>) => Promise<void>;\n};\n\nexport type Span = {\n traceId: string;\n spanId: string;\n end: (input?: { attributes?: Attributes; error?: unknown }) => void;\n};\n\nexport type MetricLabels = Record<string, AttributeValue>;\n\n/**\n * Stable selectors shared by OpenGeni's runtime metrics and optional\n * Prometheus/Grafana distribution. Operators can use these values for custom\n * namespaces and dashboard ConfigMaps without duplicating chart internals.\n */\nexport const OPENGENI_OBSERVABILITY_DISTRIBUTION = {\n monitoringNamespaceLabel: \"opengeni.ai/monitoring\",\n monitoringNamespaceLabelValue: \"enabled\",\n grafanaDashboardLabel: \"grafana_dashboard\",\n grafanaDashboardLabelValue: \"1\",\n} as const;\n\nconst httpHistogramBuckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\nconst durationHistogramBuckets = [\n 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300, 900, 1800, 3600,\n];\n\nconst SANDBOX_OPERATION_BACKENDS = new Set<string>([...SandboxBackend.options, \"unprovisioned\"]);\n\nconst SANDBOX_OPERATION_NAMES = new Set([\n \"desktopInput\",\n \"screenshot\",\n \"exec\",\n \"execCommand\",\n \"writeStdin\",\n \"cancelExecCommand\",\n \"readFile\",\n \"writeFile\",\n \"listDir\",\n \"pathExists\",\n \"viewImage\",\n \"materializeEntry\",\n \"editor.createFile\",\n \"editor.updateFile\",\n \"editor.deleteFile\",\n \"resolveExposedPort\",\n \"serializeSessionState\",\n]);\n\n/**\n * External logs and OTLP are public/third-party projections, not canonical\n * OpenGeni storage. Only this reviewed closed set of operational fields may\n * cross that boundary. Unknown keys are omitted regardless of their value, so\n * a new diagnostic, identifier, command, response, or provider field cannot\n * become public by accident. This is schema projection, never value inspection\n * or rewriting.\n */\nconst PUBLIC_TELEMETRY_ATTRIBUTE_KEYS = new Set([\n \"http.request.method\",\n \"http.response.status_code\",\n \"opengeni.route\",\n \"opengeni.duration_ms\",\n \"opengeni.finalization_duration_ms\",\n \"opengeni.trigger_kind\",\n \"opengeni.status\",\n \"error.type\",\n \"error.status_code\",\n \"method\",\n \"route\",\n \"status\",\n \"durationMs\",\n \"attempt\",\n \"attempts\",\n \"delayMs\",\n \"provider\",\n \"providerApi\",\n \"model\",\n \"inputTokens\",\n \"outputTokens\",\n \"cachedTokens\",\n \"cacheWriteTokens\",\n \"reasoningTokens\",\n \"accountChangedFromPrevCall\",\n \"rejectedFields\",\n \"dependency\",\n \"activity\",\n \"backend\",\n \"op\",\n \"outcome\",\n \"eventType\",\n \"surface\",\n \"reason\",\n \"originalBytes\",\n \"deliveredBytes\",\n \"estimatedOriginalTokens\",\n \"estimatedDeliveredTokens\",\n \"fullEvidenceAvailable\",\n \"retainedOutputKind\",\n]);\n\n/**\n * Public diagnostic values are protocol constants, never values inferred from\n * an exception's name, constructor, code, message, or enumerable properties.\n * Unknown classes collapse to one fixed fallback; unknown codes and origins\n * are omitted rather than copied through based on syntax.\n */\nconst PUBLIC_TELEMETRY_ERROR_CLASSES = new Set([\n \"OperationError\",\n \"CodexCheckpointOperationError\",\n \"CodexFleetShadowOperationError\",\n \"ComputerActionTimeoutError\",\n \"ComputerUnavailableError\",\n \"CredentialRenewalOperationError\",\n \"CredentialReadOperationError\",\n \"EventPublishOperationError\",\n \"GitCredentialRenewalOperationError\",\n \"HostExportOperationError\",\n \"HttpOperationError\",\n \"McpLifecycleError\",\n \"McpOperationError\",\n \"MemoryEmbeddingOperationError\",\n \"MemorySearchOperationError\",\n \"NatsAuthCalloutOperationError\",\n \"OAuthOperationError\",\n \"RunCredentialRenewalOperationError\",\n \"RunStateCompatibilityError\",\n \"SnapshotOperationError\",\n \"StartupDependencyError\",\n \"TelemetryExportError\",\n \"ToolspaceOperationError\",\n \"ToolspaceTokenRenewalOperationError\",\n \"WorkerLifecycleOperation\",\n \"WorkerLifecycleOperationError\",\n \"WorkerOperationError\",\n \"WorkflowWakeOperationError\",\n]);\n\nconst PUBLIC_TELEMETRY_ERROR_CODES = new Set([\n \"agent_command_wake_failed\",\n \"cleared_goal_live_publish_failed\",\n \"codex_failover_checkpoint_failed\",\n \"codex_fleet_shadow_failed\",\n \"codex_lease_loss_checkpoint_failed\",\n \"codex_active_credential_read_failed\",\n \"command_yield_timeout\",\n \"conflict\",\n \"control_wake_dispatch_failed\",\n \"fenced_event_live_publish_failed\",\n \"forbidden\",\n \"host_export_batch_delivery_failed\",\n \"host_export_failure_settlement_stale\",\n \"host_export_pump_iteration_failed\",\n \"host_export_retention_prune_failed\",\n \"idempotency_conflict\",\n \"incompatible_exposed_ports\",\n \"internal_error\",\n \"limit_exceeded\",\n \"mcp_close_failed\",\n \"mcp_connect_failed\",\n \"mcp_tool_call_failed\",\n \"mcp_tools_list_failed\",\n \"mcp_transport_failed\",\n \"memory_edit_embedding_failed\",\n \"memory_hybrid_vector_failed\",\n \"memory_save_embedding_failed\",\n \"nats_auth_callout_start_failed\",\n \"nested_agent_depth_exceeded\",\n \"nested_agent_depth_override_forbidden\",\n \"not_found\",\n \"oauth_operation_failed\",\n \"otlp_export_failed\",\n \"payment_required\",\n \"provider_verification_failed\",\n \"screenshot_capture_failed\",\n \"session_event_live_publish_failed\",\n \"session_workflow_wake_failed\",\n \"snapshot_operation_failed\",\n \"startup_dependency_retry\",\n \"tool_list_too_large\",\n \"tool_result_too_large\",\n \"toolspace_operation_failed\",\n \"unauthenticated\",\n \"upstream_unavailable\",\n \"validation_failed\",\n \"worker_draining\",\n \"worker_operation_failed\",\n \"worker_shutdown_request_failed\",\n \"workspace_control_live_publish_failed\",\n]);\n\nconst PUBLIC_TELEMETRY_ERROR_ORIGINS = new Set([\n \"api\",\n \"core\",\n \"db\",\n \"events\",\n \"host-export\",\n \"oauth\",\n \"observability\",\n \"runtime\",\n \"sandbox-computer\",\n \"sandbox-resume\",\n \"toolspace\",\n \"worker\",\n \"worker-lifecycle\",\n]);\n\nexport type SandboxOperationMetricObservation = {\n backend: string;\n op: string;\n outcome: \"ok\" | \"not_found\" | \"failed\";\n durationMs: number;\n};\n\nexport function createObservability(\n settings: ObservabilitySettings,\n options: ObservabilityOptions,\n): Observability {\n return new Observability(settings, options);\n}\n\ntype MetricRegistration = {\n kind: \"counter\" | \"gauge\" | \"histogram\";\n labelNames: string[];\n};\n\nexport class Observability {\n private readonly registry = new Registry();\n private readonly counters = new Map<string, Counter<string>>();\n private readonly gauges = new Map<string, Gauge<string>>();\n private readonly histograms = new Map<string, Histogram<string>>();\n private readonly registrations = new Map<string, MetricRegistration>();\n private readonly now: () => number;\n private readonly exporter: (\n url: string,\n body: unknown,\n headers: Record<string, string>,\n ) => Promise<void>;\n private readonly resourceAttributes: Attributes;\n\n constructor(\n private readonly settings: ObservabilitySettings,\n private readonly options: ObservabilityOptions,\n ) {\n this.now = options.now ?? Date.now;\n this.exporter = options.exporter ?? defaultExporter;\n this.resourceAttributes = {\n \"service.name\": settings.serviceName,\n \"deployment.environment\": settings.environment,\n \"opengeni.component\": options.component,\n };\n this.registry.setDefaultLabels({\n service: settings.serviceName,\n environment: settings.environment,\n component: options.component,\n });\n if (settings.observabilityMetricsEnabled) {\n collectDefaultMetrics({ register: this.registry, prefix: \"opengeni_\" });\n this.setGauge({\n name: \"opengeni_build_info\",\n help: \"OpenGeni build information.\",\n labels: {\n version: buildVersion(),\n revision: settings.deploymentRevision ?? \"dev\",\n },\n value: 1,\n });\n }\n }\n\n debug(message: string, attributes: Attributes = {}): void {\n this.log(\"debug\", message, attributes);\n }\n\n info(message: string, attributes: Attributes = {}): void {\n this.log(\"info\", message, attributes);\n }\n\n warn(message: string, attributes: Attributes = {}): void {\n this.log(\"warn\", message, attributes);\n }\n\n error(message: string, attributes: Attributes = {}): void {\n this.log(\"error\", message, attributes);\n }\n\n log(\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n attributes: Attributes = {},\n ): void {\n const publicAttributes = projectPublicTelemetryAttributes(attributes);\n if (!this.settings.observabilityStructuredLogs) {\n if (level === \"warn\") {\n console.warn(message);\n } else if (level === \"error\") {\n console.error(message);\n } else {\n console.log(message);\n }\n return;\n }\n const record = {\n timestamp: new Date(this.now()).toISOString(),\n level,\n message,\n service: this.settings.serviceName,\n environment: this.settings.environment,\n component: this.options.component,\n ...cleanAttributes(publicAttributes),\n };\n const serialized = JSON.stringify(record);\n if (level === \"warn\") {\n console.warn(serialized);\n } else if (level === \"error\") {\n console.error(serialized);\n } else {\n console.log(serialized);\n }\n }\n\n startSpan(name: string, attributes: Attributes = {}): Span {\n const traceId = randomHex(16);\n const spanId = randomHex(8);\n const startMs = this.now();\n let ended = false;\n return {\n traceId,\n spanId,\n end: (input = {}) => {\n if (ended) {\n return;\n }\n ended = true;\n const telemetryError =\n input.error !== undefined && input.error !== null\n ? projectSpanErrorForTelemetry(input.error)\n : undefined;\n const errorAttributes = telemetryError ? errorToAttributes(telemetryError) : {};\n this.exportSpan({\n traceId,\n spanId,\n name,\n startMs,\n endMs: this.now(),\n attributes: {\n ...projectPublicTelemetryAttributes({\n ...attributes,\n ...input.attributes,\n ...errorAttributes,\n }),\n },\n ...(telemetryError ? { error: telemetryError } : {}),\n });\n },\n };\n }\n\n recordHttpRequest(input: {\n method: string;\n route: string;\n status: number;\n durationSeconds: number;\n }): void {\n this.incrementCounter({\n name: \"opengeni_http_requests_total\",\n help: \"Total HTTP requests handled by OpenGeni.\",\n labels: {\n method: input.method,\n route: input.route,\n status: String(input.status),\n component: this.options.component,\n },\n });\n this.observeHistogram({\n name: \"opengeni_http_request_duration_seconds\",\n help: \"HTTP request duration in seconds.\",\n buckets: httpHistogramBuckets,\n value: input.durationSeconds,\n labels: {\n method: input.method,\n route: input.route,\n component: this.options.component,\n },\n });\n }\n\n recordWorkerActivity(input: { activity: string; status: string; durationSeconds: number }): void {\n this.incrementCounter({\n name: \"opengeni_worker_activity_runs_total\",\n help: \"Total worker activity executions.\",\n labels: {\n activity: input.activity,\n status: input.status,\n component: this.options.component,\n },\n });\n this.observeHistogram({\n name: \"opengeni_worker_activity_duration_seconds\",\n help: \"Worker activity duration in seconds.\",\n buckets: durationHistogramBuckets,\n value: input.durationSeconds,\n labels: {\n activity: input.activity,\n component: this.options.component,\n },\n });\n }\n\n incrementCounter(input: {\n name: string;\n help?: string;\n labels?: MetricLabels;\n amount?: number;\n }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const counter = this.counter(\n input.name,\n input.help ?? `${input.name} counter.`,\n Object.keys(labels),\n );\n counter.inc(labels as never, input.amount ?? 1);\n }\n\n setGauge(input: { name: string; help?: string; labels?: MetricLabels; value: number }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const gauge = this.gauge(input.name, input.help ?? `${input.name} gauge.`, Object.keys(labels));\n gauge.set(labels as never, input.value);\n }\n\n incrementGauge(input: {\n name: string;\n help?: string;\n labels?: MetricLabels;\n amount?: number;\n }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const gauge = this.gauge(input.name, input.help ?? `${input.name} gauge.`, Object.keys(labels));\n gauge.inc(labels as never, input.amount ?? 1);\n }\n\n observeHistogram(input: {\n name: string;\n help?: string;\n labels?: MetricLabels;\n value: number;\n buckets?: number[];\n }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const histogram = this.histogram(\n input.name,\n input.help ?? `${input.name} histogram.`,\n Object.keys(labels),\n input.buckets ?? durationHistogramBuckets,\n );\n histogram.observe(labels as never, input.value);\n }\n\n async prometheusMetrics(): Promise<string> {\n if (!this.settings.observabilityMetricsEnabled) {\n return \"\";\n }\n return await this.registry.metrics();\n }\n\n private counter(name: string, help: string, labelNames: string[]): Counter<string> {\n const existing = this.counters.get(name);\n if (existing) {\n this.assertRegistration(name, \"counter\", labelNames);\n return existing;\n }\n this.register(name, \"counter\", labelNames);\n const metric = new Counter({ name, help, labelNames, registers: [this.registry] });\n this.counters.set(name, metric);\n return metric;\n }\n\n private gauge(name: string, help: string, labelNames: string[]): Gauge<string> {\n const existing = this.gauges.get(name);\n if (existing) {\n this.assertRegistration(name, \"gauge\", labelNames);\n return existing;\n }\n this.register(name, \"gauge\", labelNames);\n const metric = new Gauge({ name, help, labelNames, registers: [this.registry] });\n this.gauges.set(name, metric);\n return metric;\n }\n\n private histogram(\n name: string,\n help: string,\n labelNames: string[],\n buckets: number[],\n ): Histogram<string> {\n const existing = this.histograms.get(name);\n if (existing) {\n this.assertRegistration(name, \"histogram\", labelNames);\n return existing;\n }\n this.register(name, \"histogram\", labelNames);\n const metric = new Histogram({ name, help, labelNames, buckets, registers: [this.registry] });\n this.histograms.set(name, metric);\n return metric;\n }\n\n private register(name: string, kind: MetricRegistration[\"kind\"], labelNames: string[]): void {\n const sorted = [...labelNames].sort();\n this.registrations.set(name, { kind, labelNames: sorted });\n }\n\n private assertRegistration(\n name: string,\n kind: MetricRegistration[\"kind\"],\n labelNames: string[],\n ): void {\n const registration = this.registrations.get(name);\n const sorted = [...labelNames].sort();\n if (\n !registration ||\n registration.kind !== kind ||\n registration.labelNames.length !== sorted.length ||\n registration.labelNames.some((label, index) => label !== sorted[index])\n ) {\n throw new Error(\n `Metric ${name} was already registered as ${registration?.kind ?? \"unknown\"} ` +\n `with labels [${registration?.labelNames.join(\",\") ?? \"\"}], not ${kind} [${sorted.join(\",\")}]`,\n );\n }\n }\n\n private exportSpan(span: {\n traceId: string;\n spanId: string;\n name: string;\n startMs: number;\n endMs: number;\n attributes: Attributes;\n error?: TelemetrySpanError;\n }): void {\n if (!this.settings.observabilityOtlpEndpoint) {\n return;\n }\n const endpoint = `${this.settings.observabilityOtlpEndpoint.replace(/\\/$/, \"\")}/v1/traces`;\n const body = {\n resourceSpans: [\n {\n resource: {\n attributes: otlpAttributes(this.resourceAttributes),\n },\n scopeSpans: [\n {\n scope: {\n name: \"@opengeni/observability\",\n version: \"0.1.0\",\n },\n spans: [\n {\n traceId: span.traceId,\n spanId: span.spanId,\n name: span.name,\n kind: 1,\n startTimeUnixNano: millisToNanos(span.startMs),\n endTimeUnixNano: millisToNanos(span.endMs),\n attributes: otlpAttributes(span.attributes),\n status: span.error ? { code: 2, message: span.error.statusMessage } : { code: 1 },\n },\n ],\n },\n ],\n },\n ],\n };\n void this.exporter(endpoint, body, parseHeaders(this.settings.observabilityOtlpHeaders)).catch(\n () => {\n this.warn(\"OTLP span export failed\", {\n errorClass: \"TelemetryExportError\",\n errorCode: \"otlp_export_failed\",\n origin: \"observability\",\n });\n },\n );\n }\n}\n\n/**\n * Convert routed sandbox-provider observations into one bounded Prometheus\n * contract shared by API-direct and worker-turn execution. Provider/session\n * identifiers, commands, paths, and other request data can never become labels:\n * a future backend or operation is deliberately collapsed to `unknown` until\n * this allowlist is reviewed.\n *\n * The returned callback is fail-safe. Metrics must never change the provider\n * operation's result; a registration/exporter error is counted best-effort and\n * then swallowed at this telemetry boundary.\n */\nexport function sandboxOperationMetricObserver(\n observability: Observability,\n): (observation: SandboxOperationMetricObservation) => void {\n return (observation) => {\n const backend = SANDBOX_OPERATION_BACKENDS.has(observation.backend)\n ? observation.backend\n : \"unknown\";\n const op = SANDBOX_OPERATION_NAMES.has(observation.op) ? observation.op : \"unknown\";\n try {\n observability.incrementCounter({\n name: \"opengeni_sandbox_operations_total\",\n help: \"Physical routed sandbox provider operations by backend, operation, and outcome.\",\n labels: { backend, op, outcome: observation.outcome },\n });\n observability.observeHistogram({\n name: \"opengeni_sandbox_operation_duration_seconds\",\n help: \"Physical routed sandbox provider-operation duration in seconds.\",\n labels: { backend, op },\n value: Math.max(0, observation.durationMs) / 1_000,\n });\n } catch {\n try {\n observability.incrementCounter({\n name: \"opengeni_observability_observer_errors_total\",\n help: \"Observability observer failures isolated from product execution.\",\n labels: { observer: \"sandbox_operation\" },\n });\n } catch {\n // The metrics registry itself is unhealthy. Product execution remains\n // authoritative and must not inherit an observability failure.\n }\n }\n };\n}\n\nexport type StartupDependencyRetryEvent = {\n label: string;\n attempt: number;\n attempts: number;\n delayMs: number;\n error: unknown;\n};\n\nexport function logStartupDependencyRetry(\n observability: Observability,\n event: StartupDependencyRetryEvent,\n): void {\n observability.warn(\"Startup dependency connection failed; retrying\", {\n dependency: event.label,\n attempt: event.attempt,\n attempts: event.attempts,\n delayMs: event.delayMs,\n errorClass: \"StartupDependencyError\",\n errorCode: \"startup_dependency_retry\",\n origin: \"observability\",\n });\n}\n\nfunction normalizeLabels(labels: MetricLabels = {}): Record<string, string> {\n return Object.fromEntries(\n Object.entries(labels)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]): [string, string] => [key, String(value)])\n .sort((left, right) => left[0].localeCompare(right[0])),\n );\n}\n\nfunction buildVersion(): string {\n return process.env.OPENGENI_VERSION ?? process.env.npm_package_version ?? \"dev\";\n}\n\nfunction cleanAttributes(attributes: Attributes): Record<string, string | number | boolean | null> {\n return Object.fromEntries(\n Object.entries(attributes).filter(([, value]) => value !== undefined),\n ) as Record<string, string | number | boolean | null>;\n}\n\nfunction projectPublicTelemetryAttributes(attributes: Attributes): Attributes {\n if (\"errorClass\" in attributes || \"errorCode\" in attributes) {\n return projectPublicDiagnosticAttributes(attributes);\n }\n return Object.fromEntries(\n Object.entries(attributes).filter(\n ([key, value]) => value !== undefined && PUBLIC_TELEMETRY_ATTRIBUTE_KEYS.has(key),\n ),\n );\n}\n\nfunction projectPublicDiagnosticAttributes(attributes: Attributes): Attributes {\n const errorClass = attributes.errorClass;\n const errorCode = attributes.errorCode;\n const status = attributes.status;\n const origin = attributes.origin;\n return {\n errorClass:\n typeof errorClass === \"string\" && PUBLIC_TELEMETRY_ERROR_CLASSES.has(errorClass)\n ? errorClass\n : \"OperationError\",\n ...(typeof errorCode === \"string\" && PUBLIC_TELEMETRY_ERROR_CODES.has(errorCode)\n ? { errorCode }\n : {}),\n ...(typeof status === \"number\" && Number.isInteger(status) && status >= 100 && status <= 599\n ? { status }\n : {}),\n ...(typeof origin === \"string\" && PUBLIC_TELEMETRY_ERROR_ORIGINS.has(origin) ? { origin } : {}),\n };\n}\n\ntype TelemetrySpanError = {\n type: string;\n statusCode?: number;\n statusMessage: string;\n};\n\n/**\n * External OTLP projection. It intentionally exports only error class/status\n * metadata and never mutates canonical OpenGeni errors, events, or history.\n */\nfunction projectSpanErrorForTelemetry(error: unknown): TelemetrySpanError {\n const statusCode = errorStatusCode(error);\n return {\n type: \"OperationError\",\n ...(statusCode === undefined ? {} : { statusCode }),\n statusMessage: statusCode === undefined ? \"operation failed\" : `HTTP ${statusCode}`,\n };\n}\n\nfunction errorStatusCode(error: unknown): number | undefined {\n if (typeof error !== \"object\" || error === null) return undefined;\n try {\n const value = (error as { status?: unknown; statusCode?: unknown }).status;\n const statusCode =\n Number.isInteger(value) && typeof value === \"number\"\n ? value\n : (error as { statusCode?: unknown }).statusCode;\n return Number.isInteger(statusCode) &&\n typeof statusCode === \"number\" &&\n statusCode >= 100 &&\n statusCode <= 599\n ? statusCode\n : undefined;\n } catch {\n // OTLP projection must never mask the exact internal failure.\n return undefined;\n }\n}\n\nfunction errorToAttributes(error: TelemetrySpanError): Attributes {\n return {\n \"error.type\": error.type,\n ...(error.statusCode === undefined ? {} : { \"error.status_code\": error.statusCode }),\n };\n}\n\nfunction otlpAttributes(\n attributes: Attributes,\n): Array<{ key: string; value: Record<string, string | number | boolean> }> {\n return Object.entries(cleanAttributes(attributes)).map(([key, value]) => ({\n key,\n value: otlpValue(value),\n }));\n}\n\nfunction otlpValue(\n value: string | number | boolean | null,\n): Record<string, string | number | boolean> {\n if (typeof value === \"number\") {\n return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };\n }\n if (typeof value === \"boolean\") {\n return { boolValue: value };\n }\n return { stringValue: value === null ? \"\" : boundedOtlpString(value) };\n}\n\nfunction boundedOtlpString(value: string): string {\n const bytes = new TextEncoder().encode(value);\n if (bytes.byteLength <= 512) return value;\n return `${new TextDecoder().decode(bytes.slice(0, 509))}…`;\n}\n\nfunction millisToNanos(ms: number): string {\n return String(BigInt(Math.round(ms)) * 1_000_000n);\n}\n\nfunction randomHex(bytes: number): string {\n const values = crypto.getRandomValues(new Uint8Array(bytes));\n return Array.from(values, (value) => value.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function parseHeaders(value: string): Record<string, string> {\n if (!value.trim()) {\n return {};\n }\n const entries: Array<[string, string]> = value\n .split(\",\")\n .map((pair): [string, string] => {\n const separator = pair.indexOf(\"=\");\n if (separator === -1) {\n return [pair.trim(), \"\"];\n }\n return [pair.slice(0, separator).trim(), pair.slice(separator + 1).trim()];\n })\n .filter(([key]) => key.length > 0);\n return Object.fromEntries(entries);\n}\n\nasync function defaultExporter(\n url: string,\n body: unknown,\n headers: Record<string, string>,\n): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n throw new Error(`OTLP endpoint returned HTTP ${response.status}`);\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/observability",
3
- "version": "0.4.16",
3
+ "version": "0.5.6",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,7 +31,7 @@
31
31
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
32
32
  },
33
33
  "dependencies": {
34
- "@opengeni/contracts": "^0.38.3",
34
+ "@opengeni/contracts": "^0.40.0",
35
35
  "prom-client": "^15.1.3"
36
36
  }
37
37
  }
package/src/index.ts CHANGED
@@ -28,6 +28,18 @@ export type Span = {
28
28
 
29
29
  export type MetricLabels = Record<string, AttributeValue>;
30
30
 
31
+ /**
32
+ * Stable selectors shared by OpenGeni's runtime metrics and optional
33
+ * Prometheus/Grafana distribution. Operators can use these values for custom
34
+ * namespaces and dashboard ConfigMaps without duplicating chart internals.
35
+ */
36
+ export const OPENGENI_OBSERVABILITY_DISTRIBUTION = {
37
+ monitoringNamespaceLabel: "opengeni.ai/monitoring",
38
+ monitoringNamespaceLabelValue: "enabled",
39
+ grafanaDashboardLabel: "grafana_dashboard",
40
+ grafanaDashboardLabelValue: "1",
41
+ } as const;
42
+
31
43
  const httpHistogramBuckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
32
44
  const durationHistogramBuckets = [
33
45
  0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300, 900, 1800, 3600,
@@ -55,10 +67,167 @@ const SANDBOX_OPERATION_NAMES = new Set([
55
67
  "serializeSessionState",
56
68
  ]);
57
69
 
70
+ /**
71
+ * External logs and OTLP are public/third-party projections, not canonical
72
+ * OpenGeni storage. Only this reviewed closed set of operational fields may
73
+ * cross that boundary. Unknown keys are omitted regardless of their value, so
74
+ * a new diagnostic, identifier, command, response, or provider field cannot
75
+ * become public by accident. This is schema projection, never value inspection
76
+ * or rewriting.
77
+ */
78
+ const PUBLIC_TELEMETRY_ATTRIBUTE_KEYS = new Set([
79
+ "http.request.method",
80
+ "http.response.status_code",
81
+ "opengeni.route",
82
+ "opengeni.duration_ms",
83
+ "opengeni.finalization_duration_ms",
84
+ "opengeni.trigger_kind",
85
+ "opengeni.status",
86
+ "error.type",
87
+ "error.status_code",
88
+ "method",
89
+ "route",
90
+ "status",
91
+ "durationMs",
92
+ "attempt",
93
+ "attempts",
94
+ "delayMs",
95
+ "provider",
96
+ "providerApi",
97
+ "model",
98
+ "inputTokens",
99
+ "outputTokens",
100
+ "cachedTokens",
101
+ "cacheWriteTokens",
102
+ "reasoningTokens",
103
+ "accountChangedFromPrevCall",
104
+ "rejectedFields",
105
+ "dependency",
106
+ "activity",
107
+ "backend",
108
+ "op",
109
+ "outcome",
110
+ "eventType",
111
+ "surface",
112
+ "reason",
113
+ "originalBytes",
114
+ "deliveredBytes",
115
+ "estimatedOriginalTokens",
116
+ "estimatedDeliveredTokens",
117
+ "fullEvidenceAvailable",
118
+ "retainedOutputKind",
119
+ ]);
120
+
121
+ /**
122
+ * Public diagnostic values are protocol constants, never values inferred from
123
+ * an exception's name, constructor, code, message, or enumerable properties.
124
+ * Unknown classes collapse to one fixed fallback; unknown codes and origins
125
+ * are omitted rather than copied through based on syntax.
126
+ */
127
+ const PUBLIC_TELEMETRY_ERROR_CLASSES = new Set([
128
+ "OperationError",
129
+ "CodexCheckpointOperationError",
130
+ "CodexFleetShadowOperationError",
131
+ "ComputerActionTimeoutError",
132
+ "ComputerUnavailableError",
133
+ "CredentialRenewalOperationError",
134
+ "CredentialReadOperationError",
135
+ "EventPublishOperationError",
136
+ "GitCredentialRenewalOperationError",
137
+ "HostExportOperationError",
138
+ "HttpOperationError",
139
+ "McpLifecycleError",
140
+ "McpOperationError",
141
+ "MemoryEmbeddingOperationError",
142
+ "MemorySearchOperationError",
143
+ "NatsAuthCalloutOperationError",
144
+ "OAuthOperationError",
145
+ "RunCredentialRenewalOperationError",
146
+ "RunStateCompatibilityError",
147
+ "SnapshotOperationError",
148
+ "StartupDependencyError",
149
+ "TelemetryExportError",
150
+ "ToolspaceOperationError",
151
+ "ToolspaceTokenRenewalOperationError",
152
+ "WorkerLifecycleOperation",
153
+ "WorkerLifecycleOperationError",
154
+ "WorkerOperationError",
155
+ "WorkflowWakeOperationError",
156
+ ]);
157
+
158
+ const PUBLIC_TELEMETRY_ERROR_CODES = new Set([
159
+ "agent_command_wake_failed",
160
+ "cleared_goal_live_publish_failed",
161
+ "codex_failover_checkpoint_failed",
162
+ "codex_fleet_shadow_failed",
163
+ "codex_lease_loss_checkpoint_failed",
164
+ "codex_active_credential_read_failed",
165
+ "command_yield_timeout",
166
+ "conflict",
167
+ "control_wake_dispatch_failed",
168
+ "fenced_event_live_publish_failed",
169
+ "forbidden",
170
+ "host_export_batch_delivery_failed",
171
+ "host_export_failure_settlement_stale",
172
+ "host_export_pump_iteration_failed",
173
+ "host_export_retention_prune_failed",
174
+ "idempotency_conflict",
175
+ "incompatible_exposed_ports",
176
+ "internal_error",
177
+ "limit_exceeded",
178
+ "mcp_close_failed",
179
+ "mcp_connect_failed",
180
+ "mcp_tool_call_failed",
181
+ "mcp_tools_list_failed",
182
+ "mcp_transport_failed",
183
+ "memory_edit_embedding_failed",
184
+ "memory_hybrid_vector_failed",
185
+ "memory_save_embedding_failed",
186
+ "nats_auth_callout_start_failed",
187
+ "nested_agent_depth_exceeded",
188
+ "nested_agent_depth_override_forbidden",
189
+ "not_found",
190
+ "oauth_operation_failed",
191
+ "otlp_export_failed",
192
+ "payment_required",
193
+ "provider_verification_failed",
194
+ "screenshot_capture_failed",
195
+ "session_event_live_publish_failed",
196
+ "session_workflow_wake_failed",
197
+ "snapshot_operation_failed",
198
+ "startup_dependency_retry",
199
+ "tool_list_too_large",
200
+ "tool_result_too_large",
201
+ "toolspace_operation_failed",
202
+ "unauthenticated",
203
+ "upstream_unavailable",
204
+ "validation_failed",
205
+ "worker_draining",
206
+ "worker_operation_failed",
207
+ "worker_shutdown_request_failed",
208
+ "workspace_control_live_publish_failed",
209
+ ]);
210
+
211
+ const PUBLIC_TELEMETRY_ERROR_ORIGINS = new Set([
212
+ "api",
213
+ "core",
214
+ "db",
215
+ "events",
216
+ "host-export",
217
+ "oauth",
218
+ "observability",
219
+ "runtime",
220
+ "sandbox-computer",
221
+ "sandbox-resume",
222
+ "toolspace",
223
+ "worker",
224
+ "worker-lifecycle",
225
+ ]);
226
+
58
227
  export type SandboxOperationMetricObservation = {
59
228
  backend: string;
60
229
  op: string;
61
- outcome: "ok" | "failed";
230
+ outcome: "ok" | "not_found" | "failed";
62
231
  durationMs: number;
63
232
  };
64
233
 
@@ -139,14 +308,14 @@ export class Observability {
139
308
  message: string,
140
309
  attributes: Attributes = {},
141
310
  ): void {
311
+ const publicAttributes = projectPublicTelemetryAttributes(attributes);
142
312
  if (!this.settings.observabilityStructuredLogs) {
143
- const line = attributes.error ? `${message}: ${String(attributes.error)}` : message;
144
313
  if (level === "warn") {
145
- console.warn(line);
314
+ console.warn(message);
146
315
  } else if (level === "error") {
147
- console.error(line);
316
+ console.error(message);
148
317
  } else {
149
- console.log(line);
318
+ console.log(message);
150
319
  }
151
320
  return;
152
321
  }
@@ -157,7 +326,7 @@ export class Observability {
157
326
  service: this.settings.serviceName,
158
327
  environment: this.settings.environment,
159
328
  component: this.options.component,
160
- ...cleanAttributes(attributes),
329
+ ...cleanAttributes(publicAttributes),
161
330
  };
162
331
  const serialized = JSON.stringify(record);
163
332
  if (level === "warn") {
@@ -182,11 +351,11 @@ export class Observability {
182
351
  return;
183
352
  }
184
353
  ended = true;
185
- const sanitizedError =
354
+ const telemetryError =
186
355
  input.error !== undefined && input.error !== null
187
- ? sanitizeSpanError(input.error)
356
+ ? projectSpanErrorForTelemetry(input.error)
188
357
  : undefined;
189
- const errorAttributes = sanitizedError ? errorToAttributes(sanitizedError) : {};
358
+ const errorAttributes = telemetryError ? errorToAttributes(telemetryError) : {};
190
359
  this.exportSpan({
191
360
  traceId,
192
361
  spanId,
@@ -194,11 +363,13 @@ export class Observability {
194
363
  startMs,
195
364
  endMs: this.now(),
196
365
  attributes: {
197
- ...attributes,
198
- ...input.attributes,
199
- ...errorAttributes,
366
+ ...projectPublicTelemetryAttributes({
367
+ ...attributes,
368
+ ...input.attributes,
369
+ ...errorAttributes,
370
+ }),
200
371
  },
201
- ...(sanitizedError ? { error: sanitizedError } : {}),
372
+ ...(telemetryError ? { error: telemetryError } : {}),
202
373
  });
203
374
  },
204
375
  };
@@ -396,7 +567,7 @@ export class Observability {
396
567
  startMs: number;
397
568
  endMs: number;
398
569
  attributes: Attributes;
399
- error?: SanitizedSpanError;
570
+ error?: TelemetrySpanError;
400
571
  }): void {
401
572
  if (!this.settings.observabilityOtlpEndpoint) {
402
573
  return;
@@ -432,8 +603,12 @@ export class Observability {
432
603
  ],
433
604
  };
434
605
  void this.exporter(endpoint, body, parseHeaders(this.settings.observabilityOtlpHeaders)).catch(
435
- (error) => {
436
- this.warn("OTLP span export failed", { error: errorMessage(error), endpoint });
606
+ () => {
607
+ this.warn("OTLP span export failed", {
608
+ errorClass: "TelemetryExportError",
609
+ errorCode: "otlp_export_failed",
610
+ origin: "observability",
611
+ });
437
612
  },
438
613
  );
439
614
  }
@@ -497,13 +672,14 @@ export function logStartupDependencyRetry(
497
672
  observability: Observability,
498
673
  event: StartupDependencyRetryEvent,
499
674
  ): void {
500
- const message = event.error instanceof Error ? event.error.message : String(event.error);
501
675
  observability.warn("Startup dependency connection failed; retrying", {
502
676
  dependency: event.label,
503
677
  attempt: event.attempt,
504
678
  attempts: event.attempts,
505
679
  delayMs: event.delayMs,
506
- error: message,
680
+ errorClass: "StartupDependencyError",
681
+ errorCode: "startup_dependency_retry",
682
+ origin: "observability",
507
683
  });
508
684
  }
509
685
 
@@ -526,20 +702,51 @@ function cleanAttributes(attributes: Attributes): Record<string, string | number
526
702
  ) as Record<string, string | number | boolean | null>;
527
703
  }
528
704
 
529
- type SanitizedSpanError = {
705
+ function projectPublicTelemetryAttributes(attributes: Attributes): Attributes {
706
+ if ("errorClass" in attributes || "errorCode" in attributes) {
707
+ return projectPublicDiagnosticAttributes(attributes);
708
+ }
709
+ return Object.fromEntries(
710
+ Object.entries(attributes).filter(
711
+ ([key, value]) => value !== undefined && PUBLIC_TELEMETRY_ATTRIBUTE_KEYS.has(key),
712
+ ),
713
+ );
714
+ }
715
+
716
+ function projectPublicDiagnosticAttributes(attributes: Attributes): Attributes {
717
+ const errorClass = attributes.errorClass;
718
+ const errorCode = attributes.errorCode;
719
+ const status = attributes.status;
720
+ const origin = attributes.origin;
721
+ return {
722
+ errorClass:
723
+ typeof errorClass === "string" && PUBLIC_TELEMETRY_ERROR_CLASSES.has(errorClass)
724
+ ? errorClass
725
+ : "OperationError",
726
+ ...(typeof errorCode === "string" && PUBLIC_TELEMETRY_ERROR_CODES.has(errorCode)
727
+ ? { errorCode }
728
+ : {}),
729
+ ...(typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599
730
+ ? { status }
731
+ : {}),
732
+ ...(typeof origin === "string" && PUBLIC_TELEMETRY_ERROR_ORIGINS.has(origin) ? { origin } : {}),
733
+ };
734
+ }
735
+
736
+ type TelemetrySpanError = {
530
737
  type: string;
531
738
  statusCode?: number;
532
739
  statusMessage: string;
533
740
  };
534
741
 
535
- function sanitizeSpanError(error: unknown): SanitizedSpanError {
536
- const type =
537
- error instanceof Error && /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(error.name)
538
- ? error.name
539
- : "Error";
742
+ /**
743
+ * External OTLP projection. It intentionally exports only error class/status
744
+ * metadata and never mutates canonical OpenGeni errors, events, or history.
745
+ */
746
+ function projectSpanErrorForTelemetry(error: unknown): TelemetrySpanError {
540
747
  const statusCode = errorStatusCode(error);
541
748
  return {
542
- type,
749
+ type: "OperationError",
543
750
  ...(statusCode === undefined ? {} : { statusCode }),
544
751
  statusMessage: statusCode === undefined ? "operation failed" : `HTTP ${statusCode}`,
545
752
  };
@@ -547,30 +754,31 @@ function sanitizeSpanError(error: unknown): SanitizedSpanError {
547
754
 
548
755
  function errorStatusCode(error: unknown): number | undefined {
549
756
  if (typeof error !== "object" || error === null) return undefined;
550
- const value = (error as { status?: unknown; statusCode?: unknown }).status;
551
- const statusCode =
552
- Number.isInteger(value) && typeof value === "number"
553
- ? value
554
- : (error as { statusCode?: unknown }).statusCode;
555
- return Number.isInteger(statusCode) &&
556
- typeof statusCode === "number" &&
557
- statusCode >= 100 &&
558
- statusCode <= 599
559
- ? statusCode
560
- : undefined;
757
+ try {
758
+ const value = (error as { status?: unknown; statusCode?: unknown }).status;
759
+ const statusCode =
760
+ Number.isInteger(value) && typeof value === "number"
761
+ ? value
762
+ : (error as { statusCode?: unknown }).statusCode;
763
+ return Number.isInteger(statusCode) &&
764
+ typeof statusCode === "number" &&
765
+ statusCode >= 100 &&
766
+ statusCode <= 599
767
+ ? statusCode
768
+ : undefined;
769
+ } catch {
770
+ // OTLP projection must never mask the exact internal failure.
771
+ return undefined;
772
+ }
561
773
  }
562
774
 
563
- function errorToAttributes(error: SanitizedSpanError): Attributes {
775
+ function errorToAttributes(error: TelemetrySpanError): Attributes {
564
776
  return {
565
777
  "error.type": error.type,
566
778
  ...(error.statusCode === undefined ? {} : { "error.status_code": error.statusCode }),
567
779
  };
568
780
  }
569
781
 
570
- function errorMessage(error: unknown): string {
571
- return error instanceof Error ? error.message : String(error);
572
- }
573
-
574
782
  function otlpAttributes(
575
783
  attributes: Attributes,
576
784
  ): Array<{ key: string; value: Record<string, string | number | boolean> }> {