@threadplane/telemetry 0.0.55 → 0.0.56

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.
@@ -22,10 +22,18 @@ function errorClass(error) {
22
22
  }
23
23
  return 'UnknownError';
24
24
  }
25
+ /**
26
+ * Browser-side telemetry service.
27
+ *
28
+ * The service no-ops unless `provideThreadplaneTelemetry({ enabled: true })`
29
+ * configured it. It enriches sent events with `sample_weight`, then delivers
30
+ * them through `sink`, `endpoint`, or legacy PostHog configuration.
31
+ */
25
32
  class ThreadplaneTelemetryService {
26
33
  config = inject(THREADPLANE_TELEMETRY_CONFIG, { optional: true });
27
34
  postHogPromise = null;
28
35
  distinctId = null;
36
+ /** Capture an arbitrary enabled browser telemetry event. */
29
37
  async capture(event, properties) {
30
38
  if (!this.config?.enabled)
31
39
  return;
@@ -58,18 +66,23 @@ class ThreadplaneTelemetryService {
58
66
  // silent fail
59
67
  }
60
68
  }
69
+ /** Capture a runtime construction event. */
61
70
  captureRuntimeInstanceCreated(input) {
62
71
  return this.capture('tplane:runtime_instance_created', { ...input });
63
72
  }
73
+ /** Capture a runtime request creation event. */
64
74
  captureRuntimeRequestCreated(input) {
65
75
  return this.capture('tplane:runtime_request_created', { ...input });
66
76
  }
77
+ /** Capture a stream-start event. */
67
78
  captureStreamStarted(input) {
68
79
  return this.capture('tplane:stream_started', { ...input });
69
80
  }
81
+ /** Capture a stream-end event. */
70
82
  captureStreamEnded(input) {
71
83
  return this.capture('tplane:stream_ended', { ...input });
72
84
  }
85
+ /** Capture a stream-error event, sending only the derived error class. */
73
86
  captureStreamErrored(input) {
74
87
  const { error, ...rest } = input;
75
88
  return this.capture('tplane:stream_errored', {
@@ -122,6 +135,13 @@ class ThreadplaneTelemetryService {
122
135
  args: [{ providedIn: 'root' }]
123
136
  }], null, null); })();
124
137
 
138
+ /**
139
+ * Provide browser telemetry configuration for an Angular app.
140
+ *
141
+ * Browser telemetry remains off unless this provider is installed with
142
+ * `enabled: true`. Delivery goes through `sink`, `endpoint`, or the legacy
143
+ * PostHog options configured on `ThreadplaneTelemetryConfig`.
144
+ */
125
145
  function provideThreadplaneTelemetry(config) {
126
146
  return makeEnvironmentProviders([
127
147
  { provide: THREADPLANE_TELEMETRY_CONFIG, useValue: config },
@@ -1 +1 @@
1
- {"version":3,"file":"threadplane-telemetry.mjs","sources":["../../../../../libs/telemetry/src/browser/tokens.ts","../../../../../libs/telemetry/src/browser/service.ts","../../../../../libs/telemetry/src/browser/provide.ts","../../../../../libs/telemetry/src/browser/properties.ts","../../../../../libs/telemetry/src/browser/threadplane-telemetry.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport type ThreadplaneTelemetryEvent =\n | 'tplane:browser_provided'\n | 'tplane:browser_chat_init'\n | 'tplane:runtime_instance_created'\n | 'tplane:runtime_request_created'\n | 'tplane:stream_started'\n | 'tplane:stream_ended'\n | 'tplane:stream_errored';\n\nexport interface ThreadplaneTelemetryEventPayload {\n event: ThreadplaneTelemetryEvent;\n properties?: Record<string, unknown>;\n}\n\nexport type ThreadplaneTelemetrySink = (payload: ThreadplaneTelemetryEventPayload) => void | Promise<void>;\n\nexport interface ThreadplaneTelemetryConfig {\n enabled: boolean;\n /**\n * Preferred app-owned delivery hook. Use this when the consuming app wants\n * to forward events through its own analytics boundary.\n */\n sink?: ThreadplaneTelemetrySink;\n /**\n * Preferred app-owned ingest URL. The browser service POSTs neutral event\n * payloads here; the endpoint decides where they ultimately go.\n */\n endpoint?: string;\n /** @deprecated Prefer sink or endpoint so public app code is vendor-neutral. */\n posthogKey?: string;\n /** @deprecated Prefer sink or endpoint so public app code is vendor-neutral. */\n posthogHost?: string;\n sampleRate?: number;\n}\n\nexport const THREADPLANE_TELEMETRY_CONFIG = new InjectionToken<ThreadplaneTelemetryConfig | null>(\n 'THREADPLANE_TELEMETRY_CONFIG',\n);\n","import { Injectable, inject } from '@angular/core';\nimport {\n THREADPLANE_TELEMETRY_CONFIG,\n type ThreadplaneTelemetryConfig,\n type ThreadplaneTelemetryEvent,\n} from './tokens';\n\n// Inlined from shared/events.ts: ng-packagr enforces rootDir at the entry-file\n// level (src/browser/), so the browser entry cannot import from ../shared/.\n// Keep this type in sync with shared/events.ts.\nexport type ThreadplaneBrowserEvent = ThreadplaneTelemetryEvent;\n\nexport interface ThreadplaneBrowserRuntimeTelemetry {\n transport: string;\n surface?: string;\n provider?: string;\n model?: string;\n}\n\nexport interface ThreadplaneBrowserStreamTelemetry extends ThreadplaneBrowserRuntimeTelemetry {\n durationMs?: number;\n}\n\nexport interface ThreadplaneBrowserStreamErrorTelemetry extends ThreadplaneBrowserStreamTelemetry {\n error?: unknown;\n}\n\nfunction normalizeSampleRate(sampleRate: number | undefined): number {\n if (sampleRate === undefined) return 1;\n if (!Number.isFinite(sampleRate)) return 1;\n if (sampleRate <= 0) return 0;\n if (sampleRate >= 1) return 1;\n return sampleRate;\n}\n\nfunction errorClass(error: unknown): string {\n if (error instanceof Error && error.name) return error.name;\n if (error && typeof error === 'object' && 'name' in error && typeof error.name === 'string') {\n return error.name;\n }\n return 'UnknownError';\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ThreadplaneTelemetryService {\n private config: ThreadplaneTelemetryConfig | null = inject(THREADPLANE_TELEMETRY_CONFIG, { optional: true });\n private postHogPromise: Promise<typeof import('posthog-js')['default'] | null> | null = null;\n private distinctId: string | null = null;\n\n async capture(event: ThreadplaneTelemetryEvent, properties?: Record<string, unknown>): Promise<void> {\n if (!this.config?.enabled) return;\n const sampleRate = normalizeSampleRate(this.config.sampleRate);\n if (sampleRate === 0) return;\n if (sampleRate < 1 && Math.random() >= sampleRate) return;\n\n const enrichedProperties = {\n ...(properties ?? {}),\n sample_weight: properties?.['sample_weight'] ?? 1 / sampleRate,\n };\n\n try {\n if (this.config.sink) {\n await this.config.sink({ event, properties: enrichedProperties });\n return;\n }\n if (this.config.endpoint) {\n await this.captureEndpoint(event, enrichedProperties);\n return;\n }\n if (!this.config.posthogKey) return;\n const ph = await this.loadPostHog();\n if (!ph) return;\n ph.capture(event, enrichedProperties);\n } catch {\n // silent fail\n }\n }\n\n captureRuntimeInstanceCreated(input: ThreadplaneBrowserRuntimeTelemetry): Promise<void> {\n return this.capture('tplane:runtime_instance_created', { ...input });\n }\n\n captureRuntimeRequestCreated(input: ThreadplaneBrowserRuntimeTelemetry & { requestType: string }): Promise<void> {\n return this.capture('tplane:runtime_request_created', { ...input });\n }\n\n captureStreamStarted(input: ThreadplaneBrowserStreamTelemetry): Promise<void> {\n return this.capture('tplane:stream_started', { ...input });\n }\n\n captureStreamEnded(input: ThreadplaneBrowserStreamTelemetry): Promise<void> {\n return this.capture('tplane:stream_ended', { ...input });\n }\n\n captureStreamErrored(input: ThreadplaneBrowserStreamErrorTelemetry): Promise<void> {\n const { error, ...rest } = input;\n return this.capture('tplane:stream_errored', {\n ...rest,\n errorClass: errorClass(error),\n });\n }\n\n private async captureEndpoint(event: ThreadplaneTelemetryEvent, properties: Record<string, unknown>): Promise<void> {\n if (typeof fetch !== 'function' || !this.config?.endpoint) return;\n await fetch(this.config.endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n keepalive: true,\n body: JSON.stringify({\n event,\n distinctId: this.getDistinctId(),\n properties,\n }),\n });\n }\n\n private getDistinctId(): string {\n if (!this.distinctId) {\n const cryptoApi = globalThis.crypto as Crypto | undefined;\n const value = typeof cryptoApi?.randomUUID === 'function'\n ? cryptoApi.randomUUID()\n : Math.random().toString(36).slice(2, 12);\n this.distinctId = `browser:${value}`;\n }\n return this.distinctId;\n }\n\n private loadPostHog(): Promise<typeof import('posthog-js')['default'] | null> {\n if (!this.postHogPromise) {\n this.postHogPromise = import('posthog-js').then((mod) => {\n if (!this.config?.posthogKey) return null;\n mod.default.init(this.config.posthogKey, {\n api_host: this.config.posthogHost ?? 'https://us.i.posthog.com',\n });\n return mod.default;\n }).catch(() => null);\n }\n return this.postHogPromise;\n }\n}\n","import { makeEnvironmentProviders, type EnvironmentProviders } from '@angular/core';\nimport { THREADPLANE_TELEMETRY_CONFIG, type ThreadplaneTelemetryConfig } from './tokens';\nimport { ThreadplaneTelemetryService } from './service';\n\nexport function provideThreadplaneTelemetry(config: ThreadplaneTelemetryConfig): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: THREADPLANE_TELEMETRY_CONFIG, useValue: config },\n ThreadplaneTelemetryService,\n ]);\n}\n","// SPDX-License-Identifier: MIT\n// Note: kept self-contained (no `../shared` import) so ng-packagr can compile\n// this entry point in isolation. The trim+truncate hardening is duplicated\n// from shared/properties.ts on purpose — it's a few lines, and the alternative\n// is a secondary ng-packagr entry point for shared, which is heavier.\nfunction toSafeString(value: unknown, maxLength: number): string | undefined {\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n return trimmed.slice(0, maxLength);\n}\n\nexport type CaptureConfig = {\n token?: string;\n captureLocal?: boolean;\n host?: string;\n};\n\nexport function isLocalAnalyticsHost(host: unknown): boolean {\n const value = toSafeString(host, 300)?.toLowerCase();\n if (!value) return false;\n\n // IPv6 literal `::1` or `[::1]:port` — don't naively split on `:`.\n if (value === '::1' || value.startsWith('[::1]')) return true;\n\n const hostname = value.split(':')[0];\n return hostname === 'localhost' || hostname === '127.0.0.1';\n}\n\nexport function shouldCaptureAnalytics({ token, captureLocal = false, host }: CaptureConfig): boolean {\n if (!toSafeString(token, 500)) return false;\n if (isLocalAnalyticsHost(host) && !captureLocal) return false;\n return true;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"threadplane-telemetry.mjs","sources":["../../../../../libs/telemetry/src/browser/tokens.ts","../../../../../libs/telemetry/src/browser/service.ts","../../../../../libs/telemetry/src/browser/provide.ts","../../../../../libs/telemetry/src/browser/properties.ts","../../../../../libs/telemetry/src/browser/threadplane-telemetry.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport type ThreadplaneTelemetryEvent =\n | 'tplane:browser_provided'\n | 'tplane:browser_chat_init'\n | 'tplane:runtime_instance_created'\n | 'tplane:runtime_request_created'\n | 'tplane:stream_started'\n | 'tplane:stream_ended'\n | 'tplane:stream_errored';\n\nexport interface ThreadplaneTelemetryEventPayload {\n event: ThreadplaneTelemetryEvent;\n properties?: Record<string, unknown>;\n}\n\nexport type ThreadplaneTelemetrySink = (payload: ThreadplaneTelemetryEventPayload) => void | Promise<void>;\n\nexport interface ThreadplaneTelemetryConfig {\n enabled: boolean;\n /**\n * Preferred app-owned delivery hook. Use this when the consuming app wants\n * to forward events through its own analytics boundary.\n */\n sink?: ThreadplaneTelemetrySink;\n /**\n * Preferred app-owned ingest URL. The browser service POSTs neutral event\n * payloads here; the endpoint decides where they ultimately go.\n */\n endpoint?: string;\n /** @deprecated Prefer sink or endpoint so public app code is vendor-neutral. */\n posthogKey?: string;\n /** @deprecated Prefer sink or endpoint so public app code is vendor-neutral. */\n posthogHost?: string;\n sampleRate?: number;\n}\n\nexport const THREADPLANE_TELEMETRY_CONFIG = new InjectionToken<ThreadplaneTelemetryConfig | null>(\n 'THREADPLANE_TELEMETRY_CONFIG',\n);\n","import { Injectable, inject } from '@angular/core';\nimport {\n THREADPLANE_TELEMETRY_CONFIG,\n type ThreadplaneTelemetryConfig,\n type ThreadplaneTelemetryEvent,\n} from './tokens';\n\n// Inlined from shared/events.ts: ng-packagr enforces rootDir at the entry-file\n// level (src/browser/), so the browser entry cannot import from ../shared/.\n// Keep this type in sync with shared/events.ts.\nexport type ThreadplaneBrowserEvent = ThreadplaneTelemetryEvent;\n\n/** Runtime lifecycle properties captured from browser-side agent adapters. */\nexport interface ThreadplaneBrowserRuntimeTelemetry {\n /** Runtime transport, such as `langgraph` or `ag-ui`. */\n transport: string;\n /** Optional product or app surface tag. */\n surface?: string;\n /** Optional model provider name. */\n provider?: string;\n /** Optional model name. */\n model?: string;\n}\n\n/** Stream telemetry properties captured from browser-side agent adapters. */\nexport interface ThreadplaneBrowserStreamTelemetry extends ThreadplaneBrowserRuntimeTelemetry {\n /** Stream duration in milliseconds, when known. */\n durationMs?: number;\n}\n\n/** Stream error telemetry. The raw error is reduced to an error class. */\nexport interface ThreadplaneBrowserStreamErrorTelemetry extends ThreadplaneBrowserStreamTelemetry {\n /** Raw error object or value; capture sends only `errorClass`. */\n error?: unknown;\n}\n\nfunction normalizeSampleRate(sampleRate: number | undefined): number {\n if (sampleRate === undefined) return 1;\n if (!Number.isFinite(sampleRate)) return 1;\n if (sampleRate <= 0) return 0;\n if (sampleRate >= 1) return 1;\n return sampleRate;\n}\n\nfunction errorClass(error: unknown): string {\n if (error instanceof Error && error.name) return error.name;\n if (error && typeof error === 'object' && 'name' in error && typeof error.name === 'string') {\n return error.name;\n }\n return 'UnknownError';\n}\n\n/**\n * Browser-side telemetry service.\n *\n * The service no-ops unless `provideThreadplaneTelemetry({ enabled: true })`\n * configured it. It enriches sent events with `sample_weight`, then delivers\n * them through `sink`, `endpoint`, or legacy PostHog configuration.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThreadplaneTelemetryService {\n private config: ThreadplaneTelemetryConfig | null = inject(THREADPLANE_TELEMETRY_CONFIG, { optional: true });\n private postHogPromise: Promise<typeof import('posthog-js')['default'] | null> | null = null;\n private distinctId: string | null = null;\n\n /** Capture an arbitrary enabled browser telemetry event. */\n async capture(event: ThreadplaneTelemetryEvent, properties?: Record<string, unknown>): Promise<void> {\n if (!this.config?.enabled) return;\n const sampleRate = normalizeSampleRate(this.config.sampleRate);\n if (sampleRate === 0) return;\n if (sampleRate < 1 && Math.random() >= sampleRate) return;\n\n const enrichedProperties = {\n ...(properties ?? {}),\n sample_weight: properties?.['sample_weight'] ?? 1 / sampleRate,\n };\n\n try {\n if (this.config.sink) {\n await this.config.sink({ event, properties: enrichedProperties });\n return;\n }\n if (this.config.endpoint) {\n await this.captureEndpoint(event, enrichedProperties);\n return;\n }\n if (!this.config.posthogKey) return;\n const ph = await this.loadPostHog();\n if (!ph) return;\n ph.capture(event, enrichedProperties);\n } catch {\n // silent fail\n }\n }\n\n /** Capture a runtime construction event. */\n captureRuntimeInstanceCreated(input: ThreadplaneBrowserRuntimeTelemetry): Promise<void> {\n return this.capture('tplane:runtime_instance_created', { ...input });\n }\n\n /** Capture a runtime request creation event. */\n captureRuntimeRequestCreated(input: ThreadplaneBrowserRuntimeTelemetry & { requestType: string }): Promise<void> {\n return this.capture('tplane:runtime_request_created', { ...input });\n }\n\n /** Capture a stream-start event. */\n captureStreamStarted(input: ThreadplaneBrowserStreamTelemetry): Promise<void> {\n return this.capture('tplane:stream_started', { ...input });\n }\n\n /** Capture a stream-end event. */\n captureStreamEnded(input: ThreadplaneBrowserStreamTelemetry): Promise<void> {\n return this.capture('tplane:stream_ended', { ...input });\n }\n\n /** Capture a stream-error event, sending only the derived error class. */\n captureStreamErrored(input: ThreadplaneBrowserStreamErrorTelemetry): Promise<void> {\n const { error, ...rest } = input;\n return this.capture('tplane:stream_errored', {\n ...rest,\n errorClass: errorClass(error),\n });\n }\n\n private async captureEndpoint(event: ThreadplaneTelemetryEvent, properties: Record<string, unknown>): Promise<void> {\n if (typeof fetch !== 'function' || !this.config?.endpoint) return;\n await fetch(this.config.endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n keepalive: true,\n body: JSON.stringify({\n event,\n distinctId: this.getDistinctId(),\n properties,\n }),\n });\n }\n\n private getDistinctId(): string {\n if (!this.distinctId) {\n const cryptoApi = globalThis.crypto as Crypto | undefined;\n const value = typeof cryptoApi?.randomUUID === 'function'\n ? cryptoApi.randomUUID()\n : Math.random().toString(36).slice(2, 12);\n this.distinctId = `browser:${value}`;\n }\n return this.distinctId;\n }\n\n private loadPostHog(): Promise<typeof import('posthog-js')['default'] | null> {\n if (!this.postHogPromise) {\n this.postHogPromise = import('posthog-js').then((mod) => {\n if (!this.config?.posthogKey) return null;\n mod.default.init(this.config.posthogKey, {\n api_host: this.config.posthogHost ?? 'https://us.i.posthog.com',\n });\n return mod.default;\n }).catch(() => null);\n }\n return this.postHogPromise;\n }\n}\n","import { makeEnvironmentProviders, type EnvironmentProviders } from '@angular/core';\nimport { THREADPLANE_TELEMETRY_CONFIG, type ThreadplaneTelemetryConfig } from './tokens';\nimport { ThreadplaneTelemetryService } from './service';\n\n/**\n * Provide browser telemetry configuration for an Angular app.\n *\n * Browser telemetry remains off unless this provider is installed with\n * `enabled: true`. Delivery goes through `sink`, `endpoint`, or the legacy\n * PostHog options configured on `ThreadplaneTelemetryConfig`.\n */\nexport function provideThreadplaneTelemetry(config: ThreadplaneTelemetryConfig): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: THREADPLANE_TELEMETRY_CONFIG, useValue: config },\n ThreadplaneTelemetryService,\n ]);\n}\n","// SPDX-License-Identifier: MIT\n// Note: kept self-contained (no `../shared` import) so ng-packagr can compile\n// this entry point in isolation. The trim+truncate hardening is duplicated\n// from shared/properties.ts on purpose — it's a few lines, and the alternative\n// is a secondary ng-packagr entry point for shared, which is heavier.\nfunction toSafeString(value: unknown, maxLength: number): string | undefined {\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n return trimmed.slice(0, maxLength);\n}\n\nexport type CaptureConfig = {\n token?: string;\n captureLocal?: boolean;\n host?: string;\n};\n\nexport function isLocalAnalyticsHost(host: unknown): boolean {\n const value = toSafeString(host, 300)?.toLowerCase();\n if (!value) return false;\n\n // IPv6 literal `::1` or `[::1]:port` — don't naively split on `:`.\n if (value === '::1' || value.startsWith('[::1]')) return true;\n\n const hostname = value.split(':')[0];\n return hostname === 'localhost' || hostname === '127.0.0.1';\n}\n\nexport function shouldCaptureAnalytics({ token, captureLocal = false, host }: CaptureConfig): boolean {\n if (!toSafeString(token, 500)) return false;\n if (isLocalAnalyticsHost(host) && !captureLocal) return false;\n return true;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -27,32 +27,61 @@ interface ThreadplaneTelemetryConfig {
27
27
  }
28
28
  declare const THREADPLANE_TELEMETRY_CONFIG: InjectionToken<ThreadplaneTelemetryConfig | null>;
29
29
 
30
+ /**
31
+ * Provide browser telemetry configuration for an Angular app.
32
+ *
33
+ * Browser telemetry remains off unless this provider is installed with
34
+ * `enabled: true`. Delivery goes through `sink`, `endpoint`, or the legacy
35
+ * PostHog options configured on `ThreadplaneTelemetryConfig`.
36
+ */
30
37
  declare function provideThreadplaneTelemetry(config: ThreadplaneTelemetryConfig): EnvironmentProviders;
31
38
 
32
39
  type ThreadplaneBrowserEvent = ThreadplaneTelemetryEvent;
40
+ /** Runtime lifecycle properties captured from browser-side agent adapters. */
33
41
  interface ThreadplaneBrowserRuntimeTelemetry {
42
+ /** Runtime transport, such as `langgraph` or `ag-ui`. */
34
43
  transport: string;
44
+ /** Optional product or app surface tag. */
35
45
  surface?: string;
46
+ /** Optional model provider name. */
36
47
  provider?: string;
48
+ /** Optional model name. */
37
49
  model?: string;
38
50
  }
51
+ /** Stream telemetry properties captured from browser-side agent adapters. */
39
52
  interface ThreadplaneBrowserStreamTelemetry extends ThreadplaneBrowserRuntimeTelemetry {
53
+ /** Stream duration in milliseconds, when known. */
40
54
  durationMs?: number;
41
55
  }
56
+ /** Stream error telemetry. The raw error is reduced to an error class. */
42
57
  interface ThreadplaneBrowserStreamErrorTelemetry extends ThreadplaneBrowserStreamTelemetry {
58
+ /** Raw error object or value; capture sends only `errorClass`. */
43
59
  error?: unknown;
44
60
  }
61
+ /**
62
+ * Browser-side telemetry service.
63
+ *
64
+ * The service no-ops unless `provideThreadplaneTelemetry({ enabled: true })`
65
+ * configured it. It enriches sent events with `sample_weight`, then delivers
66
+ * them through `sink`, `endpoint`, or legacy PostHog configuration.
67
+ */
45
68
  declare class ThreadplaneTelemetryService {
46
69
  private config;
47
70
  private postHogPromise;
48
71
  private distinctId;
72
+ /** Capture an arbitrary enabled browser telemetry event. */
49
73
  capture(event: ThreadplaneTelemetryEvent, properties?: Record<string, unknown>): Promise<void>;
74
+ /** Capture a runtime construction event. */
50
75
  captureRuntimeInstanceCreated(input: ThreadplaneBrowserRuntimeTelemetry): Promise<void>;
76
+ /** Capture a runtime request creation event. */
51
77
  captureRuntimeRequestCreated(input: ThreadplaneBrowserRuntimeTelemetry & {
52
78
  requestType: string;
53
79
  }): Promise<void>;
80
+ /** Capture a stream-start event. */
54
81
  captureStreamStarted(input: ThreadplaneBrowserStreamTelemetry): Promise<void>;
82
+ /** Capture a stream-end event. */
55
83
  captureStreamEnded(input: ThreadplaneBrowserStreamTelemetry): Promise<void>;
84
+ /** Capture a stream-error event, sending only the derived error class. */
56
85
  captureStreamErrored(input: ThreadplaneBrowserStreamErrorTelemetry): Promise<void>;
57
86
  private captureEndpoint;
58
87
  private getDistinctId;
@@ -1 +1 @@
1
- {"version":3,"file":"threadplane-telemetry.d.ts","sources":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","names":[]}
1
+ {"version":3,"file":"threadplane-telemetry.d.ts","sources":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadplane/telemetry",
3
- "version": "0.0.55",
3
+ "version": "0.0.56",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"