{"_id":"@countly/ai-sdk-google-genai","_rev":"4-9b9476689fb1246a7137bf2767e9a756","name":"@countly/ai-sdk-google-genai","dist-tags":{"latest":"0.0.6"},"versions":{"0.0.3":{"name":"@countly/ai-sdk-google-genai","version":"0.0.3","_id":"@countly/ai-sdk-google-genai@0.0.3","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"7f936c4e56c9376bcc126481db143a8e97f8a116","tarball":"https://registry.npmjs.org/@countly/ai-sdk-google-genai/-/ai-sdk-google-genai-0.0.3.tgz","fileCount":10,"integrity":"sha512-ZnhvuV7YHPdWy7vn+O7ZArQygQcWY8XqIiOSPqh7wl0ziI45hYqfsV8iirtF5aZ2pTDV+HSnLZDrFQltuSkepA==","signatures":[{"sig":"MEYCIQDWWLJC5IIO5VuegwpV5u/x1HFustCBTLaJLWSb9YRmKwIhANVRigkz+EMHEinzSwvOlaKnqiaOT/MuK4eWOf9j1a/Q","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":27807},"type":"module","_from":"file:countly-ai-sdk-google-genai-0.0.3.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsc --project tsconfig.build.json --watch","lint":"tsc --noEmit","test":"vitest run","build":"tsc --project tsconfig.build.json"},"_npmUser":{"name":"mksglu","email":"code.bm.ksglu@gmail.com"},"_resolved":"/private/var/folders/df/nbmn_hp51n177rvbz8zh44sh0000gn/T/f7e98e67216f40e25aeea4d37c52d6de/countly-ai-sdk-google-genai-0.0.3.tgz","_integrity":"sha512-ZnhvuV7YHPdWy7vn+O7ZArQygQcWY8XqIiOSPqh7wl0ziI45hYqfsV8iirtF5aZ2pTDV+HSnLZDrFQltuSkepA==","_npmVersion":"11.12.1","description":"Countly AI observability adapter for Google GenAI SDK","directories":{},"_nodeVersion":"24.15.0","dependencies":{"@countly/ai-sdk-core":"0.0.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@countly/ai-sdk-tsconfig":"0.0.0"},"peerDependencies":{"@google/genai":">=1.0.0"},"peerDependenciesMeta":{"@google/genai":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-google-genai_0.0.3_1783443611655_0.6754849611146236","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"name":"@countly/ai-sdk-google-genai","version":"0.0.4","_id":"@countly/ai-sdk-google-genai@0.0.4","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"164af8d2941770fffac98c87219e55584eb9c4c0","tarball":"https://registry.npmjs.org/@countly/ai-sdk-google-genai/-/ai-sdk-google-genai-0.0.4.tgz","fileCount":10,"integrity":"sha512-fGPboUOz8tiziFGDuLOzhxIXpWMk7sQU8TWIeW0HJy9WzjueFwIZsLpx52vH1WmNZW80GADsixpbvb4/4FguDw==","signatures":[{"sig":"MEQCIAVBucZNfYrKoDzXLZ327is4ts9NLvCkzMt2Za2Wu+KdAiA1W1EEuwYB+gRRGHqXdVlE8zwh5DC+gV3IDCNPp6xlRA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31627},"type":"module","_from":"file:countly-ai-sdk-google-genai-0.0.4.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsc --project tsconfig.build.json --watch","lint":"tsc --noEmit","test":"vitest run","build":"tsc --project tsconfig.build.json"},"_npmUser":{"name":"mksglu","email":"code.bm.ksglu@gmail.com"},"_resolved":"/private/var/folders/df/nbmn_hp51n177rvbz8zh44sh0000gn/T/b29186e86b9b96543962ee81f299fd4f/countly-ai-sdk-google-genai-0.0.4.tgz","_integrity":"sha512-fGPboUOz8tiziFGDuLOzhxIXpWMk7sQU8TWIeW0HJy9WzjueFwIZsLpx52vH1WmNZW80GADsixpbvb4/4FguDw==","_npmVersion":"10.8.2","description":"Countly AI observability adapter for Google GenAI SDK","directories":{},"_nodeVersion":"20.19.2","dependencies":{"@countly/ai-sdk-core":"0.0.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@countly/ai-sdk-tsconfig":"0.0.0"},"peerDependencies":{"@google/genai":">=1.0.0"},"peerDependenciesMeta":{"@google/genai":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-google-genai_0.0.4_1784147100735_0.06414427103098697","host":"s3://npm-registry-packages-npm-production"}},"0.0.5":{"name":"@countly/ai-sdk-google-genai","version":"0.0.5","_id":"@countly/ai-sdk-google-genai@0.0.5","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"88cdee4d8fb5b8f1e6767e24736521dfdc823ef0","tarball":"https://registry.npmjs.org/@countly/ai-sdk-google-genai/-/ai-sdk-google-genai-0.0.5.tgz","fileCount":10,"integrity":"sha512-cPMOUcDhOYnwZCXAwEXyj2fNV3BhgdGRSwadKjVufefuyEmDAcffO9kZysGUtLGYreCFCDc41w7l/Hbx1wAo1Q==","signatures":[{"sig":"MEYCIQD6LqjSlw3TcBcl/MBIvmU4WyoIjoGozinC2lj2dto8mAIhAJhfFcK8M9ks7tvgup8uImhDAIaAJnE0SOG69gKW+/jx","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31627},"type":"module","_from":"file:countly-ai-sdk-google-genai-0.0.5.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"tsc --project tsconfig.build.json --watch","lint":"tsc --noEmit","test":"vitest run","build":"tsc --project tsconfig.build.json"},"_npmUser":{"name":"mksglu","email":"code.bm.ksglu@gmail.com"},"_resolved":"/private/var/folders/df/nbmn_hp51n177rvbz8zh44sh0000gn/T/e676149a73de3a3ad3625deea0f50b0a/countly-ai-sdk-google-genai-0.0.5.tgz","_integrity":"sha512-cPMOUcDhOYnwZCXAwEXyj2fNV3BhgdGRSwadKjVufefuyEmDAcffO9kZysGUtLGYreCFCDc41w7l/Hbx1wAo1Q==","_npmVersion":"10.8.2","description":"Countly AI observability adapter for Google GenAI SDK","directories":{},"_nodeVersion":"20.19.2","dependencies":{"@countly/ai-sdk-core":"0.0.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@countly/ai-sdk-tsconfig":"0.0.0"},"peerDependencies":{"@google/genai":">=1.0.0"},"peerDependenciesMeta":{"@google/genai":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-google-genai_0.0.5_1784810342493_0.09945240643559372","host":"s3://npm-registry-packages-npm-production"}},"0.0.6":{"name":"@countly/ai-sdk-google-genai","version":"0.0.6","description":"Countly AI observability adapter for Google GenAI SDK","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js","default":"./dist/index.js"},"./package.json":"./package.json"},"engines":{"node":"^20.19.0 || >=22.12.0"},"publishConfig":{"access":"public"},"dependencies":{"@countly/ai-sdk-core":"0.0.6"},"peerDependencies":{"@google/genai":">=1.0.0"},"peerDependenciesMeta":{"@google/genai":{"optional":false}},"devDependencies":{"typescript":"^5.8.3","@countly/ai-sdk-tsconfig":"0.0.0"},"scripts":{"build":"tsc --project tsconfig.build.json","dev":"tsc --project tsconfig.build.json --watch","lint":"tsc --noEmit","test":"vitest run"},"_id":"@countly/ai-sdk-google-genai@0.0.6","_integrity":"sha512-wSBOSaw3Oy79VappEsHUHu/AiOqrKufq4CuXJwtm9XPH0mnZjTpRnF6Tkg0+X55etCo/uWBIfobeFYgSiHthHQ==","_resolved":"/private/var/folders/df/nbmn_hp51n177rvbz8zh44sh0000gn/T/fe104d1d1cd0788c4c30d8147ff878ee/countly-ai-sdk-google-genai-0.0.6.tgz","_from":"file:countly-ai-sdk-google-genai-0.0.6.tgz","_nodeVersion":"20.19.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-wSBOSaw3Oy79VappEsHUHu/AiOqrKufq4CuXJwtm9XPH0mnZjTpRnF6Tkg0+X55etCo/uWBIfobeFYgSiHthHQ==","shasum":"c9f66a7a82310677a1cc84f0cab50f8c97191ef8","tarball":"https://registry.npmjs.org/@countly/ai-sdk-google-genai/-/ai-sdk-google-genai-0.0.6.tgz","fileCount":14,"unpackedSize":61061,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDh9xopHoBWYM24v8vzObakzH/THf9q8fd/JZBRBIiQmQIhAMotEhrarMFmtbicoDBFYCwsJBBVEYuTXdZcSsiqaJwh"}]},"_npmUser":{"name":"mksglu","email":"code.bm.ksglu@gmail.com"},"directories":{},"maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ai-sdk-google-genai_0.0.6_1785687689750_0.8723233010739324"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-07T17:00:11.259Z","modified":"2026-08-02T16:21:30.055Z","0.0.3":"2026-07-07T17:00:11.787Z","0.0.4":"2026-07-15T20:25:00.880Z","0.0.5":"2026-07-23T12:39:02.639Z","0.0.6":"2026-08-02T16:21:29.912Z"},"description":"Countly AI observability adapter for Google GenAI SDK","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"readme":"# @countly/ai-sdk-google-genai\n\n> Countly AI observability adapter for the [Google GenAI TypeScript SDK](https://github.com/googleapis/js-genai).\n\nPart of the [Countly AI SDK](https://github.com/Countly/countly-ai-sdk) — provider-agnostic LLM observability for every AI stack.\n\n## Install\n\n```bash\nnpm install @countly/ai-sdk-google-genai\n```\n\n`@countly/ai-sdk-core` is pulled in automatically.\n\n## Peer dependency\n\n```\n@google/genai >= 1.0.0\n```\n\n## Quick Start\n\n```typescript\nimport { GoogleGenAI } from \"@google/genai\";\nimport { observeGoogleGenAI } from \"@countly/ai-sdk-google-genai\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst userStore = new AsyncLocalStorage<{ userId: string }>();\n\napp.use((req, res, next) => {\n  userStore.run({ userId: req.user.id }, next);\n});\n\nconst client = observeGoogleGenAI(new GoogleGenAI({ apiKey: \"...\" }), {\n  appKey: \"YOUR_APP_KEY\",\n  url: \"https://your-countly-server.com\",\n  getDeviceId: () => userStore.getStore()?.userId,\n});\n\nconst response = await client.models.generateContent({\n  model: \"gemini-2.5-pro\",\n  contents: \"Explain quantum computing\",\n});\n```\n\n## Streaming\n\n```typescript\nconst stream = await client.models.generateContentStream({\n  model: \"gemini-2.5-flash\",\n  contents: \"Hello\",\n});\n\nfor await (const chunk of stream) {\n  process.stdout.write(chunk.text || \"\");\n}\n```\n\nStreamed responses are **accumulated across chunks**: text is concatenated, every\n`functionCall` part is collected (they arrive before the final chunk), and\n`usageMetadata` is taken from the last chunk that reports it — it is cumulative,\nso it is never summed. Breaking out of the loop early still emits a row for the\ntokens the request consumed.\n\n## Chat sessions\n\n```typescript\nconst chat = client.chats.create({ model: \"gemini-2.5-flash\" });\nconst answer = await chat.sendMessage({ message: \"Hello\" });\n```\n\n`chats.create()` is wrapped too, so `sendMessage` / `sendMessageStream` produce\nthe same `[CLY]_llm_interaction` rows as `models.generateContent`. Each message is\ncounted exactly once.\n\n## What's captured\n\n- Token usage. `usage_input` is the provider's **total** input count\n  (`promptTokenCount`, which already includes `cachedContentTokenCount`, plus the\n  separately-reported `toolUsePromptTokenCount`); `usage_output` is the\n  **inclusive** output total, i.e. `candidatesTokenCount + thoughtsTokenCount` —\n  Gemini is the only provider that reports those two as disjoint buckets, and\n  folding them keeps thinking tokens from being billed at $0 on the pro models.\n  `usage_total` is derived by core from the two.\n- Reasoning tokens (`thoughtsTokenCount`) as the `usage_reasoning` subset, and\n  cache reads (`cachedContentTokenCount`) as `usage_cache_read`.\n- Audio / image token buckets from `promptTokensDetails` /\n  `candidatesTokensDetails`; the cost is then marked `priced_modality_approx`\n  rather than presented as exact.\n- Cost, computed from model pricing. When the model has no price entry the cost\n  fields are **absent** and `cost_priced` says `unpriced_model`; when the provider\n  reported no usage at all the token fields are absent and `usage_state` says\n  `not_reported`. Neither is ever reported as a `0`.\n- Latency in whole milliseconds (total + TTFT for streaming).\n- `api_host_type` / `api_host` — the class of endpoint (`vendor_direct`,\n  `gateway`, `local_infra`, …) and its bare hostname, read from the client's\n  resolved `baseUrl`. The path and query are never emitted, so an API key in the\n  URL cannot leak.\n- Finish reason normalized to `stop | length | tool_calls | content_filter | error | other | not_reported`\n  (from `STOP`, `MAX_TOKENS`, `SAFETY`, `RECITATION`, …). A prompt rejected before\n  generation (`promptFeedback.blockReason`) is recorded as `status: \"incomplete\"`.\n- Function calls, with `call_id` when the model supplies one. Their `status` is\n  `\"unknown\"`: the response carries the model's *request* for a call, never its\n  outcome, so the adapter does not claim success it never observed.\n- Error tracking with categorization. Instrumentation is isolated from the\n  provider call — a bug in this SDK degrades to a missing row, never to a\n  fabricated provider error and never to an exception in your code.\n- APM traces, per-user aggregation.\n\n## Not tracked\n\n| Surface | Status |\n|---|---|\n| `models.generateContent` / `generateContentStream` | tracked |\n| `chats.create().sendMessage` / `sendMessageStream` | tracked |\n| `models.embedContent`, `countTokens`, `generateImages`, `generateVideos` | not tracked — not generations |\n| `live.*` (bidirectional sessions) | not tracked |\n| `caches.*`, `files.*`, `tunings.*`, `operations.*` | not tracked |\n\nContent-bearing metadata (`groundingMetadata`, `citationMetadata`,\n`logprobsResult`, `safetyRatings`) ships only at `observabilityLevel: 2`,\nalongside the text previews. At lower levels only derived counts\n(`grounding_chunk_count`, `citation_count`, `safety_blocked_count`,\n`avg_logprobs`) are emitted.\n\n## Caller-supplied prompt_id\n\nBy default every tracked call is stamped with an auto-generated `prompt_id`. If your app already has an identifier for the interaction (a chat message id, a request id, a trace id), supply it with `getPromptId` and the adapter uses it verbatim for the `[CLY]_llm_interaction` event instead of generating one. This lets you correlate Countly analytics with your own logs and store feedback against an id you already control:\n\n```typescript\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst requestStore = new AsyncLocalStorage<{ promptId: string }>();\n\nconst ai = observeGoogleGenAI(new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }), {\n  appKey: \"YOUR_APP_KEY\",\n  url: \"https://your-countly-server.com\",\n  // Return your own id; return undefined to fall back to the generated one.\n  getPromptId: () => requestStore.getStore()?.promptId,\n});\n```\n\n`getPromptId` is called once per tracked call (`generateContent`,\n`generateContentStream`, `chats.sendMessage*`), before the request runs. When it is\nabsent or returns `undefined`, the adapter falls back to the generated id\n(identical behavior to not setting it). The resolved id is exactly what surfaces\nthrough the `onPrompt` callback below, so caller-supplied ids flow straight into\nfeedback correlation.\n\nYour id is now the **turn identity**: it is emitted as `run_id` on every row of\nthe turn (interaction, tool, tool-parameter), and it is the join column the\ndashboard groups by. `PromptInfo.prompt_id` still returns it, so the feedback flow\nbelow is unchanged; `PromptInfo.event_id` is the id of that one generation's row,\nshould you want to rate a single answer instead of the whole turn.\n\n## Feedback\n\nUser feedback (thumbs up/down, ratings, comments) is not auto-collected — wire it from your UI. Capture the `prompt_id` of each tracked interaction via the `onPrompt` callback, then record feedback against it with `createFeedbackTracker` (re-exported from this package, so no extra install is needed):\n\n```typescript\nimport { GoogleGenAI } from \"@google/genai\";\nimport { observeGoogleGenAI, createFeedbackTracker, type PromptInfo } from \"@countly/ai-sdk-google-genai\";\n\nconst countly = { appKey: \"YOUR_APP_KEY\", url: \"https://your-countly-server.com\" };\n\nlet lastPrompt: PromptInfo | undefined;\nconst ai = observeGoogleGenAI(new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }), {\n  ...countly,\n  onPrompt: (info) => { lastPrompt = info; }, // fires after every tracked call\n});\n\nconst feedback = createFeedbackTracker(countly, { sdk_adapter: \"google-genai\" });\n\nconst response = await ai.models.generateContent({\n  model: \"gemini-2.0-flash\",\n  contents: \"Explain quantum computing\",\n});\n\n// ...later, when the user rates the answer:\nfeedback.track({\n  prompt_id: lastPrompt!.prompt_id,\n  rating: \"thumbs_up\", // or \"thumbs_down\", or any custom string\n  score: 0.9, // optional 0-1 numeric score\n  category: \"helpful\", // optional: hallucination, irrelevant, harmful, ...\n  comment: \"Great answer\", // optional free-form text\n  deviceId: user.id, // attribute to the same user as the interaction\n});\n```\n\nEach `track()` call emits a `[CLY]_llm_interaction_feedback` event whose `prompt_id` links back to the `[CLY]_llm_interaction` event — powering prompt → feedback funnels and per-model satisfaction breakdowns in Countly. In a real app, store `prompt_id` alongside the rendered message (or return it to your client) and read it back when the user rates the answer. Feedback is batched like interaction events; call `feedback.flush()` to send immediately, or `feedback.shutdown()` on process exit.\n\n## Full documentation\n\nSee the [Countly AI SDK repository](https://github.com/Countly/countly-ai-sdk) for the **schema v2 wire contract** (one row per generation, RULE A dimensions, RULE B measures with their `usage_state` / `cost_priced` markers, and the common envelope), the [adapter capability matrix](https://github.com/Countly/countly-ai-sdk/blob/main/docs/capability-matrix.md), observability levels (0/1/2), cost calculation, privacy controls, and Countly plugin integration (Drill, Funnels, Cohorts, APM, Crash Analytics).\n\n## License\n\nMIT\n","readmeFilename":"README.md"}