File size: 2,910 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import { getDefaultTelemetryClient } from './client';
import { EventSink } from './sink';
import { SystemMetricsCollector } from './systemMetrics';
import { AsyncTransport } from './transport';

export const TELEMETRY_DISABLE_ENV = 'KIMI_DISABLE_TELEMETRY';

const TRUE_ENV_VALUES = new Set(['1', 'true', 't', 'yes', 'y']);

export interface TelemetryBootstrapOptions {
  readonly enabled?: boolean;
  readonly homeDir: string;
  readonly deviceId: string;
  readonly sessionId?: string;
  readonly appName: string;
  readonly version: string;
  readonly uiMode?: string;
  readonly model?: string;
  readonly buildSha?: string;
  readonly terminal?: string;
  readonly locale?: string;
  readonly getAccessToken?: () => string | null | Promise<string | null>;
  /**
   * Invoked when a tracked property is dropped for not being a primitive.
   * Telemetry stays silent by default; hosts wire this to their logger.
   */
  readonly onUnexpectedError?: (error: Error) => void;
  /**
   * Region-aware endpoint derived by the composition root (this package stays
   * dependency-free and keeps the cn default in `TELEMETRY_ENDPOINT`). A
   * resolver is invoked per flush so an in-process region switch takes effect
   * without re-initialization.
   */
  readonly endpoint?: string | (() => string);
}

export function isTelemetryDisabledByEnv(env: NodeJS.ProcessEnv = process.env): boolean {
  const value = env[TELEMETRY_DISABLE_ENV];
  return value !== undefined && TRUE_ENV_VALUES.has(value.trim().toLowerCase());
}

export function shouldEnableTelemetry(
  input: { readonly enabled?: boolean; readonly env?: NodeJS.ProcessEnv } = {},
): boolean {
  return input.enabled !== false && !isTelemetryDisabledByEnv(input.env ?? process.env);
}

export function initializeTelemetry(options: TelemetryBootstrapOptions): void {
  const client = getDefaultTelemetryClient();
  client.setUnexpectedErrorHandler(options.onUnexpectedError ?? null);
  if (!shouldEnableTelemetry({ enabled: options.enabled })) {
    client.disable();
    return;
  }

  client.enable();
  client.setContext({
    deviceId: options.deviceId,
    sessionId: options.sessionId,
  });

  const transport = new AsyncTransport({
    homeDir: options.homeDir,
    deviceId: options.deviceId,
    endpoint: options.endpoint,
    getAccessToken: options.getAccessToken,
  });
  const sink = new EventSink({
    transport,
    context: {
      appName: options.appName,
      version: options.version,
      uiMode: options.uiMode,
      model: options.model,
      buildSha: options.buildSha,
      terminal: options.terminal,
      locale: options.locale,
    },
  });

  client.attachSink(sink);
  sink.startPeriodicFlush();

  const systemMetricsCollector = new SystemMetricsCollector({ client });
  client.setSystemMetricsCollector(systemMetricsCollector);
  systemMetricsCollector.start();

  void sink.retryDiskEvents().catch(() => {});
}