{"version":3,"file":"lambda-client.cjs","names":[],"sources":["../../src/telemetry/lambda-client.ts"],"sourcesContent":["// Telemetry sink client.\n//\n// Posts events to a CopilotKit-controlled telemetry-sink endpoint, which\n// fans out to Scarf, Reo, and any future destinations. Replaces the direct\n// per-vendor calls (scarf-client.ts) so that vendor changes don't require\n// SDK releases and so that downstream services we don't want exposed to\n// OSS readers (e.g. the email-enrichment service backing Reo) stay\n// private.\n//\n// Two attribution modes:\n//   - Identified: a CopilotKit license token is configured. The token is\n//     a JWT (header.payload.sig) whose payload carries `telemetry_id`.\n//     The SDK base64url-decodes the payload — without verifying the\n//     Ed25519 signature, which is the license-verifier's job — and\n//     emits the id via `X-CopilotKit-Telemetry-Id`. The Lambda uses it\n//     to enrich events with the customer's email.\n//   - Anonymous: no license token, or a malformed/non-JWT one. No\n//     telemetry-id header; events still flow, attribution is best-effort\n//     from request-level signals (IP, UA).\n//\n// Note: CopilotCloud customer API keys (`ck_<env>_<id>.<secret>`) are\n// unrelated to telemetry attribution. They flow into Segment / PostHog\n// via the v1 shared TelemetryClient and never reach this code path.\n//\n// Best-effort: every error is swallowed. Telemetry must not break the\n// host application.\n\nconst TELEMETRY_SINK_URL =\n  (typeof process !== \"undefined\" && process.env?.COPILOTKIT_TELEMETRY_URL) ||\n  \"https://telemetry.copilotkit.ai/ingest\";\n\nconst FETCH_TIMEOUT_MS = 3000;\n\nexport interface LambdaSendOptions {\n  event: string;\n  properties?: Record<string, unknown>;\n  globalProperties?: Record<string, unknown>;\n  packageName?: string;\n  packageVersion?: string;\n  // The CopilotKit license token (Ed25519-signed JWT), when one is\n  // configured on the runtime. The sender base64url-decodes the payload\n  // segment to extract `telemetry_id`; missing or malformed tokens\n  // produce an anonymous send.\n  licenseToken?: string;\n}\n\n// These fields aren't used by the telemetry service, so we strip them\n// at the wire boundary rather than rely on every caller to omit them.\n// Both the snake_case and camelCase variants are listed because callers\n// upstream use different conventions.\nconst STRIPPED_KEYS = new Set([\"cloud.public_api_key\", \"cloud.publicApiKey\"]);\n\nfunction stripCloudKeys(\n  obj: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n  if (!obj) return {};\n  const out: Record<string, unknown> = {};\n  for (const [k, v] of Object.entries(obj)) {\n    if (!STRIPPED_KEYS.has(k)) out[k] = v;\n  }\n  return out;\n}\n\n// Pull telemetry_id out of a CopilotKit license token without verifying\n// the signature. The token shape is a standard JWT\n// (`<header>.<payload>.<sig>`) with base64url-encoded segments; the\n// payload is JSON with a `telemetry_id` string field.\n//\n// Verification (Ed25519, key rotation, expiry) is the license-verifier\n// package's job. For telemetry attribution we only need the claimed id —\n// the trust model is claim-only on the Lambda side anyway.\n//\n// Exported so TelemetryClient setters can detect unparseable tokens at\n// configuration time and surface a single warning, instead of silently\n// emitting anonymous events on every capture.\nexport function parseTelemetryIdFromLicense(token?: string): string | null {\n  if (!token) return null;\n  const parts = token.split(\".\");\n  if (parts.length !== 3) return null;\n  try {\n    let b64 = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n    const padding = (4 - (b64.length % 4)) % 4;\n    b64 += \"=\".repeat(padding);\n    const json =\n      typeof atob === \"function\"\n        ? atob(b64)\n        : Buffer.from(b64, \"base64\").toString(\"utf8\");\n    const decoded = JSON.parse(json) as { telemetry_id?: unknown };\n    return typeof decoded.telemetry_id === \"string\"\n      ? decoded.telemetry_id\n      : null;\n  } catch {\n    return null;\n  }\n}\n\n// Parse the telemetry_id from a license token AND emit the rollout smoke\n// signal if the parse returned null. Returning the parsed id lets callers\n// cache it in one step (avoiding a second parseTelemetryIdFromLicense\n// pass) while keeping the warn text in lockstep between v1 (shared) and\n// v2 (runtime) TelemetryClient.setLicenseToken.\nexport function parseAndWarnTelemetryId(licenseToken: string): string | null {\n  const telemetryId = parseTelemetryIdFromLicense(licenseToken);\n  if (!telemetryId) {\n    console.warn(\n      \"[CopilotKit] License token did not yield a telemetry_id; telemetry events will be sent anonymously.\",\n    );\n  }\n  return telemetryId;\n}\n\nexport async function send(opts: LambdaSendOptions): Promise<void> {\n  try {\n    const body = JSON.stringify({\n      event: opts.event,\n      properties: stripCloudKeys(opts.properties),\n      global_properties: stripCloudKeys(opts.globalProperties),\n      package: {\n        name: opts.packageName,\n        version: opts.packageVersion,\n      },\n      ts: Math.floor(Date.now() / 1000),\n    });\n\n    const telemetryId = parseTelemetryIdFromLicense(opts.licenseToken);\n    const headers: Record<string, string> = {\n      \"Content-Type\": \"application/json\",\n      \"User-Agent\": opts.packageName\n        ? `CopilotKit-Runtime/${opts.packageVersion ?? \"unknown\"} (${opts.packageName})`\n        : \"CopilotKit-Runtime\",\n    };\n    if (telemetryId) {\n      headers[\"X-CopilotKit-Telemetry-Id\"] = telemetryId;\n    }\n\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n    try {\n      await fetch(TELEMETRY_SINK_URL, {\n        method: \"POST\",\n        headers,\n        body,\n        signal: controller.signal,\n      });\n    } finally {\n      clearTimeout(timeoutId);\n    }\n  } catch {\n    // Silent failure — telemetry must not break the application.\n  }\n}\n\nexport const lambdaClient = { send };\n"],"mappings":";;AA2BA,MAAM,qBACH,OAAO,YAAY,eAAe,QAAQ,KAAK,4BAChD;AAEF,MAAM,mBAAmB;AAmBzB,MAAM,gBAAgB,IAAI,IAAI,CAAC,wBAAwB,qBAAqB,CAAC;AAE7E,SAAS,eACP,KACyB;AACzB,KAAI,CAAC,IAAK,QAAO,EAAE;CACnB,MAAM,MAA+B,EAAE;AACvC,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,IAAI,CACtC,KAAI,CAAC,cAAc,IAAI,EAAE,CAAE,KAAI,KAAK;AAEtC,QAAO;;AAeT,SAAgB,4BAA4B,OAA+B;AACzE,KAAI,CAAC,MAAO,QAAO;CACnB,MAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,KAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,KAAI;EACF,IAAI,MAAM,MAAM,GAAG,QAAQ,MAAM,IAAI,CAAC,QAAQ,MAAM,IAAI;EACxD,MAAM,WAAW,IAAK,IAAI,SAAS,KAAM;AACzC,SAAO,IAAI,OAAO,QAAQ;EAC1B,MAAM,OACJ,OAAO,SAAS,aACZ,KAAK,IAAI,GACT,OAAO,KAAK,KAAK,SAAS,CAAC,SAAS,OAAO;EACjD,MAAM,UAAU,KAAK,MAAM,KAAK;AAChC,SAAO,OAAO,QAAQ,iBAAiB,WACnC,QAAQ,eACR;SACE;AACN,SAAO;;;AASX,SAAgB,wBAAwB,cAAqC;CAC3E,MAAM,cAAc,4BAA4B,aAAa;AAC7D,KAAI,CAAC,YACH,SAAQ,KACN,sGACD;AAEH,QAAO;;AAGT,eAAsB,KAAK,MAAwC;AACjE,KAAI;EACF,MAAM,OAAO,KAAK,UAAU;GAC1B,OAAO,KAAK;GACZ,YAAY,eAAe,KAAK,WAAW;GAC3C,mBAAmB,eAAe,KAAK,iBAAiB;GACxD,SAAS;IACP,MAAM,KAAK;IACX,SAAS,KAAK;IACf;GACD,IAAI,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;GAClC,CAAC;EAEF,MAAM,cAAc,4BAA4B,KAAK,aAAa;EAClE,MAAM,UAAkC;GACtC,gBAAgB;GAChB,cAAc,KAAK,cACf,sBAAsB,KAAK,kBAAkB,UAAU,IAAI,KAAK,YAAY,KAC5E;GACL;AACD,MAAI,YACF,SAAQ,+BAA+B;EAGzC,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,iBAAiB;AACxE,MAAI;AACF,SAAM,MAAM,oBAAoB;IAC9B,QAAQ;IACR;IACA;IACA,QAAQ,WAAW;IACpB,CAAC;YACM;AACR,gBAAa,UAAU;;SAEnB;;AAKV,MAAa,eAAe,EAAE,MAAM"}