{"version":3,"file":"telemetry-client.mjs","names":["isTelemetryDisabled","TelemetryClient","packageJson"],"sources":["../../../../src/v2/runtime/telemetry/telemetry-client.ts"],"sourcesContent":["import type { AnalyticsEvents } from \"./events\";\nimport {\n  lambdaClient,\n  parseAndWarnTelemetryId,\n  computeSamplingMeta,\n  TELEMETRY_EMITTER_V2,\n} from \"@copilotkit/shared\";\nimport * as packageJson from \"../../../../package.json\";\nimport { firstNonBlankTelemetryId } from \"./telemetry-identity\";\n\n/** Transport identity and sampling authority resolved for one runtime. */\nexport interface TelemetryIdentity {\n  telemetryId?: string;\n  licenseToken?: string;\n}\n\n/** Capture-only telemetry client bound to one runtime identity. */\nexport interface TelemetryCapture {\n  capture<K extends keyof AnalyticsEvents>(\n    event: K,\n    properties: AnalyticsEvents[K],\n  ): Promise<void>;\n}\n\ninterface ResolvedTelemetryIdentity {\n  telemetryId: string | null;\n  licenseToken: string | null;\n  licenseTelemetryId: string | null;\n}\n\nexport function isTelemetryDisabled(): boolean {\n  return (\n    (process.env as Record<string, string | undefined>)\n      .COPILOTKIT_TELEMETRY_DISABLED === \"true\" ||\n    (process.env as Record<string, string | undefined>)\n      .COPILOTKIT_TELEMETRY_DISABLED === \"1\" ||\n    (process.env as Record<string, string | undefined>).DO_NOT_TRACK ===\n      \"true\" ||\n    (process.env as Record<string, string | undefined>).DO_NOT_TRACK === \"1\"\n  );\n}\n\nexport class TelemetryClient {\n  private telemetryDisabled: boolean = false;\n  // Client-side sampling rate for anonymous events. Identified callers\n  // (license token with telemetry_id) bypass the gate. Default 0.05\n  // caps anonymous OSS-runtime egress; identified customers send at\n  // full fidelity. Override via COPILOTKIT_TELEMETRY_SAMPLE_RATE.\n  private sampleRate: number = 0.05;\n  // EIP / Intelligence license token (Ed25519-signed JWT). Kept separate\n  // from standalone identity so the transport receives only the selected\n  // identity source.\n  private licenseToken: string | null = null;\n  // Standalone identity sent as a transport claim. It does not grant sampling\n  // authority.\n  private telemetryId: string | null = null;\n  // License-derived identity used only as sampling authority.\n  private licenseTelemetryId: string | null = null;\n  // Properties merged into every event this client sends.\n  //\n  // For facts about the caller that are true for the whole process rather than\n  // for one event: which product is embedding the runtime, for instance. A\n  // distribution built on top of this can then be told apart in the existing\n  // events instead of sending its own, which is the difference between one\n  // extra field and a second pipeline nobody asked for.\n  //\n  // Sent as `global_properties`, a field of its own, so these stay separable\n  // from an event's own properties all the way to the sink.\n  //\n  // Do not reuse a key an event already sets: the fanout spreads this bag last\n  // for `oss.runtime.*`, so a collision resolves in favour of the global and\n  // does so silently. Names here should describe the caller, not the call.\n  private globalProperties: Record<string, unknown> = {};\n\n  constructor({\n    telemetryDisabled,\n    sampleRate,\n  }: {\n    telemetryDisabled?: boolean;\n    sampleRate?: number;\n  } = {}) {\n    this.telemetryDisabled = telemetryDisabled || isTelemetryDisabled();\n    this.setSampleRate(sampleRate);\n  }\n\n  private shouldSendEvent() {\n    if (this.sampleRate >= 1) return true;\n    return Math.random() < this.sampleRate;\n  }\n\n  /**\n   * Add properties carried by every subsequent event.\n   *\n   * Merged rather than replaced, so two callers setting different fields do not\n   * erase each other. Set once at runtime construction in practice; nothing\n   * here is per-request.\n   */\n  setGlobalProperties(properties: Record<string, unknown>) {\n    this.globalProperties = { ...this.globalProperties, ...properties };\n  }\n\n  /** Atomically replace the process-wide telemetry identity. */\n  setTelemetryIdentity(identity: TelemetryIdentity): void {\n    const resolvedIdentity = this.resolveTelemetryIdentity(identity);\n    this.telemetryId = resolvedIdentity.telemetryId;\n    this.licenseToken = resolvedIdentity.licenseToken;\n    this.licenseTelemetryId = resolvedIdentity.licenseTelemetryId;\n  }\n\n  /** @deprecated Prefer {@link setTelemetryIdentity}. */\n  setLicenseToken(licenseToken: string): void {\n    this.setTelemetryIdentity({ licenseToken });\n  }\n\n  /** Create an immutable capture scope for one Runtime instance. */\n  createScope(identity: TelemetryIdentity): TelemetryCapture {\n    const resolvedIdentity = this.resolveTelemetryIdentity(identity);\n    return {\n      capture: <K extends keyof AnalyticsEvents>(\n        event: K,\n        properties: AnalyticsEvents[K],\n      ) => this.captureWithIdentity(event, properties, resolvedIdentity),\n    };\n  }\n\n  async capture<K extends keyof AnalyticsEvents>(\n    event: K,\n    properties: AnalyticsEvents[K],\n  ): Promise<void> {\n    return this.captureWithIdentity(event, properties, {\n      telemetryId: this.telemetryId,\n      licenseToken: this.licenseToken,\n      licenseTelemetryId: this.licenseTelemetryId,\n    });\n  }\n\n  private async captureWithIdentity<K extends keyof AnalyticsEvents>(\n    event: K,\n    properties: AnalyticsEvents[K],\n    identity: ResolvedTelemetryIdentity,\n  ): Promise<void> {\n    if (this.telemetryDisabled) return;\n    // Standalone identity is a transport claim, not sampling authority.\n    // Only a legacy license token with telemetry_id bypasses sampleRate.\n    if (!identity.licenseTelemetryId && !this.shouldSendEvent()) return;\n\n    await lambdaClient.send({\n      event,\n      properties: properties as Record<string, unknown>,\n      // Its own field on the wire rather than folded into `properties`, which\n      // is what the sink expects: for `oss.runtime.*` the fanout treats\n      // `global_properties` as the SDK's pass-through bag and spreads it into\n      // the analytics event, and v1's client sends package name and version the\n      // same way. Folding it in would work and would put a process-level fact\n      // in the per-event slot, where nothing downstream expects to find one.\n      // Sampling metadata rides in the same slot, as it does in v1: an event\n      // that records no sampling decision can't be weighted, and anyone\n      // counting raw events understates anonymous volume ~20× (OSS-1017).\n      globalProperties: {\n        ...this.globalProperties,\n        ...computeSamplingMeta({\n          telemetryId: identity.licenseTelemetryId,\n          sampleRate: this.sampleRate,\n        }),\n        telemetry_emitter: TELEMETRY_EMITTER_V2,\n        // This client has one transport, so the marker is constant here. It\n        // is stamped anyway so the property means the same thing on every\n        // event whichever client produced it (OSS-1019).\n        telemetry_transport: \"lambda\",\n      },\n      packageName: packageJson.name,\n      packageVersion: packageJson.version,\n      telemetryId: identity.telemetryId ?? undefined,\n      licenseToken: identity.licenseToken ?? undefined,\n    });\n  }\n\n  private resolveTelemetryIdentity(\n    identity: TelemetryIdentity,\n  ): ResolvedTelemetryIdentity {\n    const telemetryId = firstNonBlankTelemetryId(identity.telemetryId);\n    if (telemetryId !== undefined) {\n      return {\n        telemetryId,\n        licenseToken: null,\n        licenseTelemetryId: null,\n      };\n    }\n\n    return {\n      telemetryId: null,\n      licenseToken: identity.licenseToken ?? null,\n      licenseTelemetryId: identity.licenseToken\n        ? parseAndWarnTelemetryId(identity.licenseToken)\n        : null,\n    };\n  }\n\n  private setSampleRate(sampleRate: number | undefined) {\n    let _sampleRate: number;\n\n    _sampleRate = sampleRate ?? 0.05;\n\n    if (process.env.COPILOTKIT_TELEMETRY_SAMPLE_RATE) {\n      _sampleRate = parseFloat(process.env.COPILOTKIT_TELEMETRY_SAMPLE_RATE);\n    }\n\n    // Number.isNaN guards against parseFloat(\"nonsense\") slipping past the\n    // range check (all NaN comparisons are false), which would silently\n    // drop every anonymous event with no signal.\n    if (Number.isNaN(_sampleRate) || _sampleRate < 0 || _sampleRate > 1) {\n      throw new Error(\"Sample rate must be between 0 and 1\");\n    }\n\n    this.sampleRate = _sampleRate;\n  }\n}\n\nconst telemetry = new TelemetryClient();\nexport default telemetry;\n"],"mappings":";;;;;;;;AA8BA,SAAgBA,wBAA+B;AAC7C,QACG,QAAQ,IACN,kCAAkC,UACpC,QAAQ,IACN,kCAAkC,OACpC,QAAQ,IAA2C,iBAClD,UACD,QAAQ,IAA2C,iBAAiB;;AAIzE,IAAaC,oBAAb,MAA6B;CAgC3B,YAAY,EACV,mBACA,eAIE,EAAE,EAAE;2BArC6B;oBAKR;sBAIS;qBAGD;4BAEO;0BAeQ,EAAE;AASpD,OAAK,oBAAoB,qBAAqBD,uBAAqB;AACnE,OAAK,cAAc,WAAW;;CAGhC,AAAQ,kBAAkB;AACxB,MAAI,KAAK,cAAc,EAAG,QAAO;AACjC,SAAO,KAAK,QAAQ,GAAG,KAAK;;;;;;;;;CAU9B,oBAAoB,YAAqC;AACvD,OAAK,mBAAmB;GAAE,GAAG,KAAK;GAAkB,GAAG;GAAY;;;CAIrE,qBAAqB,UAAmC;EACtD,MAAM,mBAAmB,KAAK,yBAAyB,SAAS;AAChE,OAAK,cAAc,iBAAiB;AACpC,OAAK,eAAe,iBAAiB;AACrC,OAAK,qBAAqB,iBAAiB;;;CAI7C,gBAAgB,cAA4B;AAC1C,OAAK,qBAAqB,EAAE,cAAc,CAAC;;;CAI7C,YAAY,UAA+C;EACzD,MAAM,mBAAmB,KAAK,yBAAyB,SAAS;AAChE,SAAO,EACL,UACE,OACA,eACG,KAAK,oBAAoB,OAAO,YAAY,iBAAiB,EACnE;;CAGH,MAAM,QACJ,OACA,YACe;AACf,SAAO,KAAK,oBAAoB,OAAO,YAAY;GACjD,aAAa,KAAK;GAClB,cAAc,KAAK;GACnB,oBAAoB,KAAK;GAC1B,CAAC;;CAGJ,MAAc,oBACZ,OACA,YACA,UACe;AACf,MAAI,KAAK,kBAAmB;AAG5B,MAAI,CAAC,SAAS,sBAAsB,CAAC,KAAK,iBAAiB,CAAE;AAE7D,QAAM,aAAa,KAAK;GACtB;GACY;GAUZ,kBAAkB;IAChB,GAAG,KAAK;IACR,GAAG,oBAAoB;KACrB,aAAa,SAAS;KACtB,YAAY,KAAK;KAClB,CAAC;IACF,mBAAmB;IAInB,qBAAqB;IACtB;GACD,aAAaE,eAAY;GACzB,gBAAgBA,eAAY;GAC5B,aAAa,SAAS,eAAe;GACrC,cAAc,SAAS,gBAAgB;GACxC,CAAC;;CAGJ,AAAQ,yBACN,UAC2B;EAC3B,MAAM,cAAc,yBAAyB,SAAS,YAAY;AAClE,MAAI,gBAAgB,OAClB,QAAO;GACL;GACA,cAAc;GACd,oBAAoB;GACrB;AAGH,SAAO;GACL,aAAa;GACb,cAAc,SAAS,gBAAgB;GACvC,oBAAoB,SAAS,eACzB,wBAAwB,SAAS,aAAa,GAC9C;GACL;;CAGH,AAAQ,cAAc,YAAgC;EACpD,IAAI;AAEJ,gBAAc,cAAc;AAE5B,MAAI,QAAQ,IAAI,iCACd,eAAc,WAAW,QAAQ,IAAI,iCAAiC;AAMxE,MAAI,OAAO,MAAM,YAAY,IAAI,cAAc,KAAK,cAAc,EAChE,OAAM,IAAI,MAAM,sCAAsC;AAGxD,OAAK,aAAa;;;AAItB,MAAM,YAAY,IAAID,mBAAiB"}