@omote/core 0.9.7 → 0.10.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.
Files changed (36) hide show
  1. package/README.md +77 -35
  2. package/dist/{chunk-X5OTUOE6.mjs → chunk-3FILA2CD.mjs} +63 -205
  3. package/dist/chunk-3FILA2CD.mjs.map +1 -0
  4. package/dist/{chunk-CYBTTLG7.mjs → chunk-5WIOGMJA.mjs} +77 -219
  5. package/dist/chunk-5WIOGMJA.mjs.map +1 -0
  6. package/dist/{chunk-3NDJA3I4.mjs → chunk-NWZMIQK4.mjs} +135 -206
  7. package/dist/chunk-NWZMIQK4.mjs.map +1 -0
  8. package/dist/{chunk-Y3DTP5P3.mjs → chunk-VSYYT4HO.mjs} +1 -1
  9. package/dist/{chunk-X5OTUOE6.mjs.map → chunk-VSYYT4HO.mjs.map} +1 -1
  10. package/dist/chunk-WW4XAUJ3.mjs +208 -0
  11. package/dist/chunk-WW4XAUJ3.mjs.map +1 -0
  12. package/dist/index.d.mts +336 -1375
  13. package/dist/index.d.ts +336 -1375
  14. package/dist/index.js +6738 -11284
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +6099 -10719
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/logging/index.js +5 -0
  19. package/dist/logging/index.js.map +1 -1
  20. package/dist/logging/index.mjs +1 -1
  21. package/dist/otlp-2BML6FIK.mjs +7 -0
  22. package/dist/otlp-2BML6FIK.mjs.map +1 -0
  23. package/package.json +1 -2
  24. package/dist/Logger-BeUI6jG7.d.mts +0 -145
  25. package/dist/Logger-BeUI6jG7.d.ts +0 -145
  26. package/dist/Logger-DSoGAYJu.d.mts +0 -141
  27. package/dist/Logger-DSoGAYJu.d.ts +0 -141
  28. package/dist/chunk-3NDJA3I4.mjs.map +0 -1
  29. package/dist/chunk-CYBTTLG7.mjs.map +0 -1
  30. package/dist/chunk-ESU52TDS.mjs +0 -287
  31. package/dist/chunk-ESU52TDS.mjs.map +0 -1
  32. package/dist/chunk-MXKJOF4I.mjs +0 -38
  33. package/dist/chunk-MXKJOF4I.mjs.map +0 -1
  34. package/dist/chunk-XK22BRG4.mjs +0 -38
  35. package/dist/chunk-XK22BRG4.mjs.map +0 -1
  36. package/dist/chunk-Y3DTP5P3.mjs.map +0 -1
package/README.md CHANGED
@@ -14,8 +14,8 @@
14
14
  - **TTSSpeaker** — High-level speak(text) with abort, queueing, and LLM streaming
15
15
  - **SpeechListener** — Mic → VAD → ASR orchestration with adaptive silence detection
16
16
  - **createTTSPlayer()** — Factory composing Kokoro TTS + TTSSpeaker for zero-config playback
17
- - **VoicePipeline** — Full conversational agent loop with local TTS support (cloud or offline)
18
- - **configureOrtCdn()** — Enterprise CDN override for ORT WASM/WebGPU binaries
17
+ - **VoiceOrchestrator** — Full conversational agent loop with local TTS support (cloud or offline)
18
+ - **configureModelUrls()** — Self-host model files from your own CDN
19
19
  - **Animation Graph** — State machine (idle/listening/thinking/speaking) with emotion blending
20
20
  - **Emotion Controller** — Preset-based emotion system with smooth transitions
21
21
  - **Model Caching** — IndexedDB with versioning, LRU eviction, and quota monitoring
@@ -81,15 +81,15 @@ const { blendshapes } = await a2e.infer(audioSamples); // Float32Array (16kHz)
81
81
  // → 52 ARKit blendshape weights
82
82
  ```
83
83
 
84
- #### Direct API
84
+ #### Custom Configuration
85
85
 
86
86
  ```typescript
87
- import { A2EInference, ARKIT_BLENDSHAPES } from '@omote/core';
87
+ import { createA2E, ARKIT_BLENDSHAPES } from '@omote/core';
88
88
 
89
- const lam = new A2EInference({ modelUrl: '/models/model_fp16.onnx' });
90
- await lam.load();
89
+ const a2e = createA2E({ backend: 'wasm' }); // Force WASM for testing
90
+ await a2e.load();
91
91
 
92
- const { blendshapes } = await lam.infer(audioSamples);
92
+ const { blendshapes } = await a2e.infer(audioSamples);
93
93
  const jawOpen = blendshapes[ARKIT_BLENDSHAPES.indexOf('jawOpen')];
94
94
  ```
95
95
 
@@ -136,11 +136,9 @@ const frame = processor.getFrameForTime(audioContext.currentTime);
136
136
  SenseVoice ASR — 15x faster than Whisper, with progressive transcription and emotion detection.
137
137
 
138
138
  ```typescript
139
- import { SenseVoiceInference } from '@omote/core';
139
+ import { createSenseVoice } from '@omote/core';
140
140
 
141
- const asr = new SenseVoiceInference({
142
- modelUrl: '/models/sensevoice/model.int8.onnx',
143
- });
141
+ const asr = createSenseVoice(); // Auto-detects platform, fetches from HF CDN
144
142
  await asr.load();
145
143
 
146
144
  const { text, emotion, language } = await asr.transcribe(audioSamples);
@@ -149,22 +147,19 @@ const { text, emotion, language } = await asr.transcribe(audioSamples);
149
147
  #### Platform-Aware ASR
150
148
 
151
149
  ```typescript
152
- import { shouldUseNativeASR, SafariSpeechRecognition, SenseVoiceInference } from '@omote/core';
150
+ import { shouldUseNativeASR, SafariSpeechRecognition, createSenseVoice } from '@omote/core';
153
151
 
154
152
  const asr = shouldUseNativeASR()
155
153
  ? new SafariSpeechRecognition({ language: 'en-US' })
156
- : new SenseVoiceInference({ modelUrl: '/models/sensevoice/model.int8.onnx' });
154
+ : createSenseVoice();
157
155
  ```
158
156
 
159
157
  ### Voice Activity Detection (Silero VAD)
160
158
 
161
- #### Factory API (Recommended)
162
-
163
159
  ```typescript
164
160
  import { createSileroVAD } from '@omote/core';
165
161
 
166
162
  const vad = createSileroVAD({
167
- modelUrl: '/models/silero-vad.onnx',
168
163
  threshold: 0.5,
169
164
  // useWorker: true // Force off-main-thread
170
165
  // useWorker: false // Force main thread
@@ -174,18 +169,6 @@ await vad.load();
174
169
  const { isSpeech, probability } = await vad.process(audioSamples);
175
170
  ```
176
171
 
177
- #### Direct API
178
-
179
- ```typescript
180
- import { SileroVADInference, SileroVADWorker } from '@omote/core';
181
-
182
- // Main thread (mobile-friendly)
183
- const vad = new SileroVADInference({ modelUrl: '/models/silero-vad.onnx' });
184
-
185
- // Web Worker (desktop, off-main-thread)
186
- const vadWorker = new SileroVADWorker({ modelUrl: '/models/silero-vad.onnx' });
187
- ```
188
-
189
172
  ### Animation Graph
190
173
 
191
174
  State machine for avatar animation states with emotion blending and audio energy.
@@ -248,7 +231,7 @@ const data = await fetchWithCache('/models/model.onnx', {
248
231
  });
249
232
 
250
233
  // Cache quota monitoring
251
- import { configureCacheLimit, getQuotaInfo } from '@omote/core';
234
+ import { configureCacheLimit } from '@omote/core';
252
235
 
253
236
  configureCacheLimit({
254
237
  maxSizeBytes: 500 * 1024 * 1024, // 500MB limit
@@ -307,17 +290,76 @@ const span = telemetry.startSpan('custom-operation');
307
290
  span.end();
308
291
  ```
309
292
 
310
- ## Models
293
+ ### Text-to-Speech (Kokoro TTS)
294
+
295
+ ```typescript
296
+ import { createKokoroTTS } from '@omote/core';
311
297
 
312
- Place models in your public assets directory:
298
+ const tts = createKokoroTTS({ defaultVoice: 'af_heart' });
299
+ await tts.load();
313
300
 
301
+ const audio = await tts.synthesize('Hello world!');
302
+ // audio: Float32Array @ 24kHz
314
303
  ```
315
- public/models/
316
- model_fp16.onnx # A2E lip sync WebGPU (192MB fp16, from omote-ai/lam-a2e)
317
- sensevoice/model.int8.onnx # SenseVoice ASR (239MB)
318
- silero-vad.onnx # Voice activity detection (~2MB)
304
+
305
+ Kokoro auto-detects the platform: mixed-fp16 WebGPU model (156MB) on Chrome/Edge, q8 WASM model (92MB) on Safari/iOS/Firefox.
306
+
307
+ ### Eager Load & Warmup
308
+
309
+ Use `eagerLoad` to preload models at construction time:
310
+
311
+ ```typescript
312
+ const tts = createKokoroTTS({ eagerLoad: true }); // Starts loading immediately
319
313
  ```
320
314
 
315
+ Use `warmup()` to prime AudioContext for iOS/Safari autoplay policy. Call from a user gesture handler:
316
+
317
+ ```typescript
318
+ button.onclick = async () => {
319
+ await avatar.warmup(); // Primes AudioContext
320
+ await avatar.connectVoice({ ... });
321
+ };
322
+ ```
323
+
324
+ ### Observability
325
+
326
+ The SDK includes built-in OpenTelemetry-compatible tracing and metrics:
327
+
328
+ ```typescript
329
+ import { configureTelemetry, getTelemetry, MetricNames } from '@omote/core';
330
+
331
+ configureTelemetry({
332
+ enabled: true,
333
+ serviceName: 'my-app',
334
+ exporter: 'console', // or OTLPExporter for production
335
+ });
336
+ ```
337
+
338
+ All inference calls, model loads, cache operations, and voice turns are automatically instrumented.
339
+
340
+ ## Models
341
+
342
+ All models default to the HuggingFace CDN and are auto-downloaded on first use. Self-host with `configureModelUrls()`:
343
+
344
+ ```typescript
345
+ import { configureModelUrls } from '@omote/core';
346
+
347
+ configureModelUrls({
348
+ lam: 'https://your-cdn.com/models/lam.onnx',
349
+ lamData: 'https://your-cdn.com/models/lam.onnx.data',
350
+ senseVoice: 'https://your-cdn.com/models/sensevoice.onnx',
351
+ sileroVad: 'https://your-cdn.com/models/silero_vad.onnx',
352
+ });
353
+ ```
354
+
355
+ | Model | HuggingFace Repo | Size |
356
+ |-------|-------------------|------|
357
+ | LAM A2E | `omote-ai/lam-a2e` | `lam.onnx` (230KB) + `lam.onnx.data` (192MB) |
358
+ | SenseVoice | `omote-ai/sensevoice-asr` | 228MB |
359
+ | Silero VAD | `deepghs/silero-vad-onnx` | ~2MB |
360
+ | Kokoro TTS (WASM) | `onnx-community/Kokoro-82M-v1.0-ONNX` | 92MB q8 |
361
+ | Kokoro TTS (WebGPU) | `omote-ai/kokoro-tts` | 156MB mixed-fp16 |
362
+
321
363
  ## Browser Compatibility
322
364
 
323
365
  WebGPU-first with automatic WASM fallback.
@@ -82,6 +82,8 @@ var jsonFormatter = (entry) => {
82
82
  if (entry.data && Object.keys(entry.data).length > 0) {
83
83
  output.data = entry.data;
84
84
  }
85
+ if (entry.traceId) output.traceId = entry.traceId;
86
+ if (entry.spanId) output.spanId = entry.spanId;
85
87
  if (entry.error) {
86
88
  output.error = {
87
89
  name: entry.error.name,
@@ -103,6 +105,9 @@ var prettyFormatter = (entry) => {
103
105
  const color = LEVEL_COLORS[entry.level];
104
106
  output = `${COLORS.gray}${time}${COLORS.reset} ${color}${level}${COLORS.reset} ${COLORS.cyan}[${module}]${COLORS.reset} ${message}`;
105
107
  }
108
+ if (entry.traceId) {
109
+ output += ` [trace:${entry.traceId.slice(0, 8)}]`;
110
+ }
106
111
  if (entry.data && Object.keys(entry.data).length > 0) {
107
112
  const dataStr = safeStringify(entry.data);
108
113
  if (dataStr.length > 80) {
@@ -207,198 +212,6 @@ var ConsoleExporter = class {
207
212
  }
208
213
  };
209
214
 
210
- // src/telemetry/exporters/otlp.ts
211
- var StatusCode = {
212
- UNSET: 0,
213
- OK: 1,
214
- ERROR: 2
215
- };
216
- function spanToOTLP(span, serviceName, serviceVersion) {
217
- const attributes = Object.entries(span.attributes).filter(([, v]) => v !== void 0).map(([key, value]) => ({
218
- key,
219
- value: typeof value === "string" ? { stringValue: value } : typeof value === "number" ? Number.isInteger(value) ? { intValue: value } : { doubleValue: value } : { boolValue: value }
220
- }));
221
- return {
222
- resourceSpans: [{
223
- resource: {
224
- attributes: [
225
- { key: "service.name", value: { stringValue: serviceName } },
226
- { key: "service.version", value: { stringValue: serviceVersion } },
227
- { key: "telemetry.sdk.name", value: { stringValue: "omote-sdk" } },
228
- { key: "telemetry.sdk.language", value: { stringValue: "javascript" } }
229
- ]
230
- },
231
- scopeSpans: [{
232
- scope: {
233
- name: "omote-sdk",
234
- version: serviceVersion
235
- },
236
- spans: [{
237
- traceId: span.traceId,
238
- spanId: span.spanId,
239
- parentSpanId: span.parentSpanId || "",
240
- name: span.name,
241
- kind: 1,
242
- // INTERNAL
243
- startTimeUnixNano: String(span.startTime * 1e6),
244
- endTimeUnixNano: String(span.endTime * 1e6),
245
- attributes,
246
- status: {
247
- code: span.status === "ok" ? StatusCode.OK : StatusCode.ERROR,
248
- message: span.error?.message || ""
249
- }
250
- }]
251
- }]
252
- }]
253
- };
254
- }
255
- function metricToOTLP(metric, serviceName, serviceVersion) {
256
- const attributes = Object.entries(metric.attributes).filter(([, v]) => v !== void 0).map(([key, value]) => ({
257
- key,
258
- value: typeof value === "string" ? { stringValue: value } : typeof value === "number" ? Number.isInteger(value) ? { intValue: value } : { doubleValue: value } : { boolValue: value }
259
- }));
260
- const dataPoint = {
261
- attributes,
262
- timeUnixNano: String(metric.timestamp * 1e6),
263
- ...metric.type === "counter" ? { asInt: metric.value } : { asDouble: metric.value }
264
- };
265
- return {
266
- resourceMetrics: [{
267
- resource: {
268
- attributes: [
269
- { key: "service.name", value: { stringValue: serviceName } },
270
- { key: "service.version", value: { stringValue: serviceVersion } }
271
- ]
272
- },
273
- scopeMetrics: [{
274
- scope: {
275
- name: "omote-sdk",
276
- version: serviceVersion
277
- },
278
- metrics: [{
279
- name: metric.name,
280
- ...metric.type === "counter" ? {
281
- sum: {
282
- dataPoints: [dataPoint],
283
- aggregationTemporality: 2,
284
- // CUMULATIVE
285
- isMonotonic: true
286
- }
287
- } : {
288
- gauge: {
289
- dataPoints: [dataPoint]
290
- }
291
- }
292
- }]
293
- }]
294
- }]
295
- };
296
- }
297
- var OTLPExporter = class {
298
- constructor(config, serviceName = "omote-sdk", serviceVersion = "0.1.0") {
299
- this.spanBuffer = [];
300
- this.metricBuffer = [];
301
- this.flushIntervalId = null;
302
- this.BUFFER_SIZE = 100;
303
- this.FLUSH_INTERVAL_MS = 5e3;
304
- this.isShutdown = false;
305
- const parsed = new URL(config.endpoint);
306
- if (parsed.protocol !== "https:") {
307
- const isLocalhost = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
308
- if (!isLocalhost) {
309
- throw new Error("OTLP endpoint must use HTTPS (or localhost for development)");
310
- }
311
- }
312
- this.config = {
313
- endpoint: config.endpoint,
314
- timeoutMs: config.timeoutMs ?? 1e4,
315
- headers: config.headers ? { ...config.headers } : {}
316
- };
317
- this.serviceName = serviceName;
318
- this.serviceVersion = serviceVersion;
319
- this.flushIntervalId = setInterval(() => {
320
- this.flush().catch(console.error);
321
- }, this.FLUSH_INTERVAL_MS);
322
- }
323
- exportSpan(span) {
324
- if (this.isShutdown) return;
325
- this.spanBuffer.push(span);
326
- if (this.spanBuffer.length >= this.BUFFER_SIZE) {
327
- this.flush().catch(console.error);
328
- }
329
- }
330
- exportMetric(metric) {
331
- if (this.isShutdown) return;
332
- this.metricBuffer.push(metric);
333
- if (this.metricBuffer.length >= this.BUFFER_SIZE) {
334
- this.flush().catch(console.error);
335
- }
336
- }
337
- async flush() {
338
- if (this.isShutdown) return;
339
- const spans = this.spanBuffer.splice(0);
340
- const metrics = this.metricBuffer.splice(0);
341
- const promises = [];
342
- if (spans.length > 0) {
343
- promises.push(this.exportSpans(spans));
344
- }
345
- if (metrics.length > 0) {
346
- promises.push(this.exportMetrics(metrics));
347
- }
348
- await Promise.all(promises);
349
- }
350
- async shutdown() {
351
- if (this.flushIntervalId) {
352
- clearInterval(this.flushIntervalId);
353
- this.flushIntervalId = null;
354
- }
355
- await this.flush();
356
- this.isShutdown = true;
357
- }
358
- async exportSpans(spans) {
359
- const resourceSpans = spans.map(
360
- (span) => spanToOTLP(span, this.serviceName, this.serviceVersion).resourceSpans[0]
361
- );
362
- const body = { resourceSpans };
363
- const endpoint = this.config.endpoint.replace(/\/$/, "") + "/v1/traces";
364
- await this.sendRequest(endpoint, body);
365
- }
366
- async exportMetrics(metrics) {
367
- const resourceMetrics = metrics.map(
368
- (metric) => metricToOTLP(metric, this.serviceName, this.serviceVersion).resourceMetrics[0]
369
- );
370
- const body = { resourceMetrics };
371
- const endpoint = this.config.endpoint.replace(/\/$/, "") + "/v1/metrics";
372
- await this.sendRequest(endpoint, body);
373
- }
374
- async sendRequest(endpoint, body) {
375
- const controller = new AbortController();
376
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
377
- try {
378
- const response = await fetch(endpoint, {
379
- method: "POST",
380
- headers: {
381
- "Content-Type": "application/json",
382
- ...this.config.headers
383
- },
384
- body: JSON.stringify(body),
385
- signal: controller.signal
386
- });
387
- if (!response.ok) {
388
- console.warn(`[OTLP] Export failed: ${response.status} ${response.statusText}`);
389
- }
390
- } catch (error) {
391
- if (error.name === "AbortError") {
392
- console.warn("[OTLP] Export timed out");
393
- } else {
394
- console.warn("[OTLP] Export error:", error);
395
- }
396
- } finally {
397
- clearTimeout(timeoutId);
398
- }
399
- }
400
- };
401
-
402
215
  // src/logging/Clock.ts
403
216
  var defaultClock = {
404
217
  now: () => performance.now(),
@@ -413,6 +226,7 @@ function getClock() {
413
226
  }
414
227
 
415
228
  // src/telemetry/OmoteTelemetry.ts
229
+ var DEFAULT_HISTOGRAM_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3, 1e4, 3e4, 6e4];
416
230
  function generateId(length = 16) {
417
231
  const bytes = new Uint8Array(length);
418
232
  crypto.getRandomValues(bytes);
@@ -432,6 +246,7 @@ function getTelemetry() {
432
246
  var OmoteTelemetry = class {
433
247
  constructor(config) {
434
248
  this.exporter = null;
249
+ this.exporterReady = null;
435
250
  this.activeTraceId = null;
436
251
  this.metricsIntervalId = null;
437
252
  // Span stack for log-to-span correlation
@@ -450,14 +265,22 @@ var OmoteTelemetry = class {
450
265
  metricsIntervalMs: config.metricsIntervalMs ?? 6e4
451
266
  };
452
267
  if (this.config.enabled) {
453
- this.initExporter();
268
+ if (config.customExporter) {
269
+ this.exporter = config.customExporter;
270
+ } else {
271
+ this.exporterReady = this.initExporter();
272
+ }
454
273
  this.startMetricsCollection();
455
274
  }
456
275
  }
457
276
  /**
458
277
  * Initialize the configured exporter
459
278
  */
460
- initExporter() {
279
+ async initExporter() {
280
+ if (this.config.customExporter) {
281
+ this.exporter = this.config.customExporter;
282
+ return;
283
+ }
461
284
  switch (this.config.exporter) {
462
285
  case "console":
463
286
  this.exporter = new ConsoleExporter({ enabled: true });
@@ -467,11 +290,14 @@ var OmoteTelemetry = class {
467
290
  console.warn("[Telemetry] OTLP exporter requires exporterConfig with endpoint");
468
291
  return;
469
292
  }
470
- this.exporter = new OTLPExporter(
471
- this.config.exporterConfig,
472
- this.config.serviceName,
473
- this.config.serviceVersion
474
- );
293
+ {
294
+ const { OTLPExporter } = await import("./otlp-2BML6FIK.mjs");
295
+ this.exporter = new OTLPExporter(
296
+ this.config.exporterConfig,
297
+ this.config.serviceName,
298
+ this.config.serviceVersion
299
+ );
300
+ }
475
301
  break;
476
302
  case "none":
477
303
  default:
@@ -521,6 +347,7 @@ var OmoteTelemetry = class {
521
347
  const spanId = generateId(8);
522
348
  const parentSpanId = parentContext?.spanId;
523
349
  const startTime = getClock().now();
350
+ const epochMs = Date.now();
524
351
  if (!parentContext && !this.activeTraceId) {
525
352
  this.activeTraceId = traceId;
526
353
  }
@@ -536,6 +363,7 @@ var OmoteTelemetry = class {
536
363
  if (idx !== -1) this.spanStack.splice(idx, 1);
537
364
  const endTime = getClock().now();
538
365
  const durationMs = endTime - startTime;
366
+ const endEpochMs = epochMs + (endTime - startTime);
539
367
  if (status === "error" && !sampled) {
540
368
  sampled = this.shouldSample(true);
541
369
  }
@@ -548,6 +376,8 @@ var OmoteTelemetry = class {
548
376
  startTime,
549
377
  endTime,
550
378
  durationMs,
379
+ epochMs,
380
+ endEpochMs,
551
381
  status,
552
382
  attributes: spanAttributes,
553
383
  error
@@ -622,7 +452,7 @@ var OmoteTelemetry = class {
622
452
  * });
623
453
  * ```
624
454
  */
625
- recordHistogram(name, value, attributes = {}) {
455
+ recordHistogram(name, value, attributes = {}, bucketBoundaries = DEFAULT_HISTOGRAM_BUCKETS) {
626
456
  if (!this.config.enabled || !this.config.metricsEnabled) return;
627
457
  const key = this.getMetricKey(name, attributes);
628
458
  const existing = this.histograms.get(key);
@@ -631,8 +461,27 @@ var OmoteTelemetry = class {
631
461
  existing.sum += value;
632
462
  if (value < existing.min) existing.min = value;
633
463
  if (value > existing.max) existing.max = value;
464
+ let placed = false;
465
+ for (let i = 0; i < existing.bucketBoundaries.length; i++) {
466
+ if (value <= existing.bucketBoundaries[i]) {
467
+ existing.bucketCounts[i]++;
468
+ placed = true;
469
+ break;
470
+ }
471
+ }
472
+ if (!placed) existing.bucketCounts[existing.bucketCounts.length - 1]++;
634
473
  } else {
635
- this.histograms.set(key, { count: 1, sum: value, min: value, max: value, attributes });
474
+ const bucketCounts = new Array(bucketBoundaries.length + 1).fill(0);
475
+ let placed = false;
476
+ for (let i = 0; i < bucketBoundaries.length; i++) {
477
+ if (value <= bucketBoundaries[i]) {
478
+ bucketCounts[i]++;
479
+ placed = true;
480
+ break;
481
+ }
482
+ }
483
+ if (!placed) bucketCounts[bucketCounts.length - 1]++;
484
+ this.histograms.set(key, { count: 1, sum: value, min: value, max: value, bucketBoundaries, bucketCounts, attributes });
636
485
  }
637
486
  }
638
487
  /**
@@ -649,7 +498,7 @@ var OmoteTelemetry = class {
649
498
  */
650
499
  flushMetrics() {
651
500
  if (!this.exporter) return;
652
- const timestamp = getClock().now();
501
+ const timestamp = Date.now();
653
502
  for (const [key, data] of this.counters) {
654
503
  if (data.value === 0) continue;
655
504
  const name = key.split("|")[0];
@@ -678,19 +527,29 @@ var OmoteTelemetry = class {
678
527
  min: data.min,
679
528
  max: data.max
680
529
  },
681
- timestamp
530
+ timestamp,
531
+ histogramData: {
532
+ count: data.count,
533
+ sum: data.sum,
534
+ min: data.min,
535
+ max: data.max,
536
+ bucketBoundaries: [...data.bucketBoundaries],
537
+ bucketCounts: [...data.bucketCounts]
538
+ }
682
539
  };
683
540
  this.exporter.exportMetric(metric);
684
541
  data.count = 0;
685
542
  data.sum = 0;
686
543
  data.min = Infinity;
687
544
  data.max = -Infinity;
545
+ data.bucketCounts.fill(0);
688
546
  }
689
547
  }
690
548
  /**
691
549
  * Force flush all pending data
692
550
  */
693
551
  async flush() {
552
+ if (this.exporterReady) await this.exporterReady;
694
553
  this.flushMetrics();
695
554
  await this.exporter?.flush();
696
555
  }
@@ -906,7 +765,6 @@ export {
906
765
  prettyFormatter,
907
766
  getFormatter,
908
767
  ConsoleExporter,
909
- OTLPExporter,
910
768
  defaultClock,
911
769
  configureClock,
912
770
  getClock,
@@ -924,4 +782,4 @@ export {
924
782
  getNoopLogger,
925
783
  ErrorCodes
926
784
  };
927
- //# sourceMappingURL=chunk-X5OTUOE6.mjs.map
785
+ //# sourceMappingURL=chunk-3FILA2CD.mjs.map