@copilotkit/shared
Version:
<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />
1 lines • 7.57 kB
Source Map (JSON)
{"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"}