{"_id":"@countly/ai-sdk-mastra","_rev":"6-1d81e57159ceca54e5931884db6e05a9","name":"@countly/ai-sdk-mastra","dist-tags":{"latest":"0.0.6"},"versions":{"0.0.1":{"name":"@countly/ai-sdk-mastra","version":"0.0.1","_id":"@countly/ai-sdk-mastra@0.0.1","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"102d54ee073c0651d92df3e24f46bf3c1c75d5ea","tarball":"https://registry.npmjs.org/@countly/ai-sdk-mastra/-/ai-sdk-mastra-0.0.1.tgz","fileCount":13,"integrity":"sha512-MZXQX2qI4wawwuk8dNeUBFdUD4AkQTTJne3+43uDYW9K6nVO3LewStZlGjCY3CIhZf6q8mhnSyW9Qmca/yNk2g==","signatures":[{"sig":"MEUCIAOLAyjomeLSc1EoFsZEBg0J2xwKnXaYfkIQJpBMQoNeAiEA5Nh6HkKUGTBB8DPs0eq0b0QlzGLryR02lOAVLBwu5eg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31777},"type":"module","_from":"file:countly-ai-sdk-mastra-0.0.1.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/5742e1401b653e7bb245229af7aaadb1/countly-ai-sdk-mastra-0.0.1.tgz","_integrity":"sha512-MZXQX2qI4wawwuk8dNeUBFdUD4AkQTTJne3+43uDYW9K6nVO3LewStZlGjCY3CIhZf6q8mhnSyW9Qmca/yNk2g==","_npmVersion":"10.8.2","description":"Countly AI observability adapter for Mastra","directories":{},"_nodeVersion":"20.19.2","dependencies":{"@countly/ai-sdk-core":"0.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@countly/ai-sdk-tsconfig":"0.0.0"},"peerDependencies":{"@mastra/core":">=1.0.0","@mastra/observability":">=0.1.0"},"peerDependenciesMeta":{"@mastra/core":{"optional":false},"@mastra/observability":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-mastra_0.0.1_1778593952049_0.2146338958588243","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@countly/ai-sdk-mastra","version":"0.0.2","_id":"@countly/ai-sdk-mastra@0.0.2","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"5d54d6cfdfc319e415cb5cc228625fec0110d62a","tarball":"https://registry.npmjs.org/@countly/ai-sdk-mastra/-/ai-sdk-mastra-0.0.2.tgz","fileCount":14,"integrity":"sha512-Vo1RkVnfr0GurMrEOJCkuuKuHu3LM5JqyV+M/doqlCpXclvTn1QrF83KMn3bJhftT97jwhldkLx2/FRmwh1F7Q==","signatures":[{"sig":"MEUCIBmy1d1/OapsRDPbD80zd4EWdSLCx6LbWcQenZJyBqVlAiEAj+RFtLG0+mXRr8JHpvOlnIMVH6L+EYSir/qr0E7BoTA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":36284},"type":"module","_from":"file:countly-ai-sdk-mastra-0.0.2.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/e33bca10c6e436e92eb6f70d17950ad5/countly-ai-sdk-mastra-0.0.2.tgz","_integrity":"sha512-Vo1RkVnfr0GurMrEOJCkuuKuHu3LM5JqyV+M/doqlCpXclvTn1QrF83KMn3bJhftT97jwhldkLx2/FRmwh1F7Q==","_npmVersion":"10.8.2","description":"Countly AI observability adapter for Mastra","directories":{},"_nodeVersion":"20.19.2","dependencies":{"@countly/ai-sdk-core":"0.0.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@countly/ai-sdk-tsconfig":"0.0.0"},"peerDependencies":{"@mastra/core":">=1.0.0","@mastra/observability":">=0.1.0"},"peerDependenciesMeta":{"@mastra/core":{"optional":false},"@mastra/observability":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-mastra_0.0.2_1778594301416_0.5244907787305935","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@countly/ai-sdk-mastra","version":"0.0.3","_id":"@countly/ai-sdk-mastra@0.0.3","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"cccaf4574815e5da8c20988c0c536a9e92406502","tarball":"https://registry.npmjs.org/@countly/ai-sdk-mastra/-/ai-sdk-mastra-0.0.3.tgz","fileCount":14,"integrity":"sha512-WdLEty6ThsAxVnfmRLBZ9rfYVjzW2/5NdFn/Op0Xkimtl1J/TWiMoa7vRL6jCygAtTFLSEI0v0FYS2rmgKkaiA==","signatures":[{"sig":"MEQCIArZF7cp8wj4k+LmgoH4EgPR3S2nMZCkq0kpsAz51BpDAiAFgTcBLQPLTMrlxogdnN454hreqNz88zjtRhBBOmo5/g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":38401},"type":"module","_from":"file:countly-ai-sdk-mastra-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/d46c4163873ba0d09d16039d28cfdf38/countly-ai-sdk-mastra-0.0.3.tgz","_integrity":"sha512-WdLEty6ThsAxVnfmRLBZ9rfYVjzW2/5NdFn/Op0Xkimtl1J/TWiMoa7vRL6jCygAtTFLSEI0v0FYS2rmgKkaiA==","_npmVersion":"11.12.1","description":"Countly AI observability adapter for Mastra","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":{"@mastra/core":">=1.0.0","@mastra/observability":">=0.1.0"},"peerDependenciesMeta":{"@mastra/core":{"optional":false},"@mastra/observability":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-mastra_0.0.3_1783443622649_0.22352407050399048","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"name":"@countly/ai-sdk-mastra","version":"0.0.4","_id":"@countly/ai-sdk-mastra@0.0.4","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"0810293b7b9c42a65501ab0a6b5843d63f7fa991","tarball":"https://registry.npmjs.org/@countly/ai-sdk-mastra/-/ai-sdk-mastra-0.0.4.tgz","fileCount":14,"integrity":"sha512-wfTEMDoEbbwnwP7ZdqHQA0G1OYbO5JLlM5OUeiVcFKX6xQvD6JSC7Fr6B5unoCzweplMMNfv5Fuxj4yp2zKRsw==","signatures":[{"sig":"MEQCIH70KxZR5jYDzXAZWE80qx8rcMyu/LP4ZhG9rzkDmI7UAiBJgkdc4o9p4xiBxA5CNbJ+o6eXi4wYpZLE52DTy988Tg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":54505},"type":"module","_from":"file:countly-ai-sdk-mastra-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/d3cc208ef53cf5e54d7660268c09b7af/countly-ai-sdk-mastra-0.0.4.tgz","_integrity":"sha512-wfTEMDoEbbwnwP7ZdqHQA0G1OYbO5JLlM5OUeiVcFKX6xQvD6JSC7Fr6B5unoCzweplMMNfv5Fuxj4yp2zKRsw==","_npmVersion":"10.8.2","description":"Countly AI observability adapter for Mastra","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":{"@mastra/core":">=1.0.0","@mastra/observability":">=0.1.0"},"peerDependenciesMeta":{"@mastra/core":{"optional":false},"@mastra/observability":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-mastra_0.0.4_1784147100841_0.11484551422272093","host":"s3://npm-registry-packages-npm-production"}},"0.0.5":{"name":"@countly/ai-sdk-mastra","version":"0.0.5","_id":"@countly/ai-sdk-mastra@0.0.5","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"6f7fdfab619b29975a1c242d5c920c5b470543a5","tarball":"https://registry.npmjs.org/@countly/ai-sdk-mastra/-/ai-sdk-mastra-0.0.5.tgz","fileCount":14,"integrity":"sha512-2SARLTjnAt6LtwWTv4zT5jlyhGLrJ/WMG1IwlrfO3wF1/UQ04vSSO8+m4DbVzoEG/6H6YR6t1T8Jo43Evy7O9Q==","signatures":[{"sig":"MEUCIQC+zX87iuYVqP21CdJvV/DJnonRFcm52dHqGgIEHQJM7QIgBLvBWmdpzJSmQQERP+AMk0ckjnkfHFcf9lBF2zMlWCs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":56001},"type":"module","_from":"file:countly-ai-sdk-mastra-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/a6514880b0284129be6d393a7fc87eac/countly-ai-sdk-mastra-0.0.5.tgz","_integrity":"sha512-2SARLTjnAt6LtwWTv4zT5jlyhGLrJ/WMG1IwlrfO3wF1/UQ04vSSO8+m4DbVzoEG/6H6YR6t1T8Jo43Evy7O9Q==","_npmVersion":"10.8.2","description":"Countly AI observability adapter for Mastra","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":{"@mastra/core":">=1.0.0","@mastra/observability":">=0.1.0"},"peerDependenciesMeta":{"@mastra/core":{"optional":false},"@mastra/observability":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-mastra_0.0.5_1784810342786_0.26929985300605774","host":"s3://npm-registry-packages-npm-production"}},"0.0.6":{"name":"@countly/ai-sdk-mastra","version":"0.0.6","description":"Countly AI observability adapter for Mastra","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":{"@mastra/core":">=1.26.0 <2","@mastra/observability":">=1.11.0 <2"},"peerDependenciesMeta":{"@mastra/core":{"optional":false},"@mastra/observability":{"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-mastra@0.0.6","_integrity":"sha512-Qt6Y6Qwa6NIGFEDWhaGLhEQYcM2ABmNksh82AX6EEz5x54RXxy26E1MSPVOlkhViGEPckCQb609MsaPhuEnVfw==","_resolved":"/private/var/folders/df/nbmn_hp51n177rvbz8zh44sh0000gn/T/06630cd85ca673c5c26b99946d20e299/countly-ai-sdk-mastra-0.0.6.tgz","_from":"file:countly-ai-sdk-mastra-0.0.6.tgz","_nodeVersion":"20.19.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-Qt6Y6Qwa6NIGFEDWhaGLhEQYcM2ABmNksh82AX6EEz5x54RXxy26E1MSPVOlkhViGEPckCQb609MsaPhuEnVfw==","shasum":"8c3a209620cadab2ce182ce59a24e2c12db42aac","tarball":"https://registry.npmjs.org/@countly/ai-sdk-mastra/-/ai-sdk-mastra-0.0.6.tgz","fileCount":18,"unpackedSize":108355,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDTVYG6F1JymWWWvsKwJ2v5vB0xqhQJDV8oMDkee3dIywIgb8LrTb/zWfDMNWEZRT8XYiVQMa8b4iCjzji+kpvthiI="}]},"_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-mastra_0.0.6_1785687689745_0.8089226373618295"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-12T13:52:31.938Z","modified":"2026-08-02T16:21:30.072Z","0.0.1":"2026-05-12T13:52:32.204Z","0.0.2":"2026-05-12T13:58:21.585Z","0.0.3":"2026-07-07T17:00:22.791Z","0.0.4":"2026-07-15T20:25:00.975Z","0.0.5":"2026-07-23T12:39:02.925Z","0.0.6":"2026-08-02T16:21:29.900Z"},"description":"Countly AI observability adapter for Mastra","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"readme":"# @countly/ai-sdk-mastra\n\n> Countly AI observability adapter for [Mastra](https://mastra.ai).\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-mastra\n```\n\n`@countly/ai-sdk-core` is pulled in automatically.\n\n## Peer dependencies\n\n```\n@mastra/core          >= 1.26.0 < 2\n@mastra/observability >= 1.11.0 < 2\n```\n\n**The floor was raised in 0.0.6** (it was `>= 1.0.0` for both, which was wishful).\nCache-token capture reads `usage.inputDetails` / `usage.outputDetails`, a shape\n`@mastra/observability` only emits from 1.11.0. With `auto-install-peers=true`, an\nolder pair installs cleanly and then reports **zero cache tokens** — which\nmis-prices every prompt-cached call in exactly the direction that hides the\noverbilling this release removes. Silence, not an error. Hence the floor, and hence\nCI's `mastra-peer-floor` job, which proves the declared minimum actually emits\ncache tokens instead of merely declaring that it does.\n\nIf you cannot move to those versions, pin `@countly/ai-sdk-mastra@0.0.5` and accept\nthe known cost error rather than run 0.0.6 below its floor.\n\n## Quick Start\n\n> **Note**: the exporter must be wrapped in `new Observability({...})` and passed via Mastra's `observability:` field. Passing it directly as `exporters:` on Mastra won't work — no spans reach the exporter.\n\n```typescript\nimport { Mastra } from \"@mastra/core/mastra\";\nimport { Observability } from \"@mastra/observability\";\nimport { CountlyMastraExporter } from \"@countly/ai-sdk-mastra\";\n\nnew Mastra({\n  observability: new Observability({\n    configs: {\n      default: {\n        serviceName: \"my-ai-app\",\n        exporters: [\n          new CountlyMastraExporter({\n            appKey: \"YOUR_APP_KEY\",\n            url: \"https://your-countly-server.com\",\n            requestContextDeviceIdKey: \"countlyDeviceId\",\n          }),\n        ],\n      },\n    },\n  }),\n});\n```\n\nIn your request handler:\n\n```typescript\nimport { RequestContext } from \"@mastra/core/request-context\";\n\napp.post(\"/chat\", async (req, res) => {\n  const ctx = new RequestContext();\n  ctx.set(\"countlyDeviceId\", req.user.id);\n  await mastra.getAgent(\"intent\").stream(messages, { requestContext: ctx });\n});\n```\n\n## How the user ID reaches the event\n\nThe bridge is Mastra's runtime, not our SDK. We read a public field Mastra puts on every exported span:\n\n```\nYour handler                     Mastra runtime                  @countly/ai-sdk-mastra\n─────────────                    ──────────────                  ──────────────────────\nctx = new RequestContext()\nctx.set(\"countlyDeviceId\", id)\n                                 run scope carries ctx\nagent.stream(msg, {              ↓\n  requestContext: ctx            spans created during run\n})                               (AGENT_RUN, MODEL_GEN, TOOL_CALL)\n                                 ↓\n                                 ExportedSpan.requestContext = {\n                                   countlyDeviceId: id              adapter reads\n                                 }                                  span.requestContext\n                                                                    ↓\n                                                                    event.deviceId = id\n                                                                    POST /i?device_id=id\n```\n\n- Rename the key via `requestContextDeviceIdKey` (e.g. `\"myUserId\"`)\n- Set to `null` to disable — falls back to `getDeviceId()` / `deviceId` / process UUID\n- No Mastra version requirement beyond `>=1.0` — stable v1 observability surface\n\n## What's captured\n\n- Per-event tracing via `exportTracingEvent` (`span_started`, `span_updated`, `span_ended`)\n- Automatic trace completion when the root span ends\n- **One `[CLY]_llm_interaction` per `model_generation` span** — its own model,\n  provider, usage, cost, finish reason and span duration (see below)\n- Tool calls within agent workflows (`function_call` and `mcp_tool_call` types),\n  with per-call latency and the requesting generation as their parent\n- **Workflow steps** — each `createStep` node's `inputData` captured as tool params (see below)\n- Mastra's own **scorer results and user feedback** (`onScoreEvent` /\n  `onFeedbackEvent` / `addScoreToTrace`) as `[CLY]_llm_interaction_feedback` rows\n- Token usage from `usage.inputDetails` / `usage.outputDetails`, with cache read,\n  cache write and reasoning as **subsets** of the input/output totals. `usage_input`\n  is the provider's total, inclusive of both cache buckets, so the cache premium is\n  billed exactly once\n- Usage deduped by span id (it used to be counted twice), and usage the trace never\n  reported is **omitted** with `usage_state: \"not_reported\"` rather than zeroed\n- Error reporting for failed **generations** (a failed tool or step is reported on\n  its own tool row, not as a model failure)\n- Per-user aggregation across agent runs\n\n### One row per generation\n\nA Mastra trace is not a turn. Mastra roots traces at both `agent_run` and\n`workflow_run`, so one user turn produces several traces — and a single trace can\ncontain several generations with *different* models.\n\n- Each `model_generation` span becomes exactly one interaction row, carrying its\n  own `model`, `provider`, `usage_*`, `cost_*`, `finish_reason` and\n  `latency_total`. Cost is therefore correct per call instead of pricing the whole\n  turn at the first model's rate.\n- Turn context is denormalized onto every row: `run_id`, `generation_index`\n  (0-based, ordered by start time), `run_latency_total` (the root span's\n  end-to-end duration), `workflow_name`, `agent_name`, `thread_id`, `deviceId`,\n  `trace_id` and the text previews.\n- **A trace that contained no generation emits no interaction row.** Workflow\n  roots, `rag_*` pipelines, memory operations, tripwire aborts and directly\n  executed tools are orchestration, not LLM calls. Their tool rows still ship,\n  parented to the run (`parent_event_key: \"run\"`).\n\n> ⚠️ **Row volume rises.** A turn that previously emitted one interaction row now\n> emits one per generation — roughly 2x for a single-agent turn, more for a\n> multi-agent or multi-model one. Size your ingest accordingly. Conversely, prompt\n> count **drops** by the share of traces that never contained a generation (200 of\n> 601 rows on observed traffic), which is the point of the change.\n\n### 🔴 `api_host_type` is `\"unknown\"` on every Mastra row\n\n**Every** Mastra row reports `api_host_type: \"unknown\"` and omits `api_host`. Not\na bug in this adapter — Mastra does not tell us the endpoint, and we will not guess\none.\n\n`@mastra/core` declares `serverAddress` and `serverPort` on\n`ModelGenerationAttributes`, but no code path assigns either. Verified against the\ninstalled artifacts rather than the type declarations: in `@mastra/core` 1.55.0\n(what this workspace resolves) and 1.24.1, and in `@mastra/observability` 1.11.1,\n`serverAddress` appears **only** in `dist/observability/types/tracing.d.ts` and in\nsource maps — zero occurrences in emitted JavaScript. What a `model_generation`\nspan actually carries at runtime is:\n\n| when | attributes populated |\n|---|---|\n| span created | `model`, `provider`, `streaming`, `parameters` |\n| span ended | `finishReason`, `responseId`, `responseModel`, `usage` |\n\nNo other attribute carries a URL, host or region, so there is nothing to fall back\nto. `providerMetadata` cannot help either: Mastra consumes it inside\n`extractUsageMetrics` and it is in the exporter's `DEFAULT_KEYS_TO_STRIP`, so it\nnever reaches an exported span — and it holds no endpoint in any case.\n\nConsequences, stated plainly:\n\n- The dashboard's **\"By host type\" card is empty for Mastra traffic.** Gateway vs\n  vendor-direct vs Azure cannot be broken out.\n- `\"unknown\"` here means *\"Mastra did not report it\"*, not *\"the call went\n  somewhere unusual\"*. Do not read it as a finding.\n\nThe classification is already wired (`describeApiHost`), so it starts working the\nmoment Mastra populates the attribute — no change needed on our side. Every other\nadapter reads its client's `baseURL` directly and does report a real host; see the\n[capability matrix](../../docs/capability-matrix.md).\n\n### `run_id` — grouping a turn\n\nEvery row of a turn shares one `run_id`, resolved in this order:\n\n1. `getPromptId()` — your own id for the turn.\n2. `requestContextPromptIdKey` read from the trace's `requestContext`.\n3. Mastra's own run id (`span.metadata.runId`).\n4. The trace's root span id.\n\nRules 1 and 2 are the **only** ones that can group a workflow trace with the agent\ntraces it spawns, because those are separate traces with different `traceId`s.\nWithout one, a workflow turn appears as several runs. Set the value **before**\ninvoking the agent or workflow: Mastra clones `requestContext` in the span\nconstructor, so a value set inside a step is only visible on that step's own span\n(the exporter falls back to scanning child spans for exactly that case).\n\n### `flush()` vs `shutdown()`\n\n`flush()` drains the network buffer but does **not** emit still-running traces —\n`ObservabilityBus` calls it mid-request, and reporting a live turn would produce a\nzero-latency premature success plus a duplicate row when the root finally ends.\n`shutdown()` does emit whatever is left, explicitly marked\n`status: \"incomplete\"`, so a process exiting mid-turn loses nothing and claims\nnothing.\n\n## Capturing workflow steps\n\nIf you orchestrate agents with Mastra **workflows** (`createWorkflow` / `createStep`),\nthe steps only reach the exporter when the workflow is **registered on the Mastra\ninstance** and run **through that instance**. A workflow imported and run standalone\n(`myWorkflow.createRun()`) has no observability wired to it — only the agent runs it\ninvokes are traced, and the step decisions (routing, handoffs, gathered inputs) are\ninvisible.\n\nRegister the workflow and run it via `getWorkflow`:\n\n```typescript\nconst mastra = new Mastra({\n  agents: { intentAgent, pluginsAgent },\n  workflows: { myWorkflow },        // ← register it\n  observability: new Observability({ /* … exporter … */ }),\n});\n\n// Run THROUGH the instance so its spans inherit observability:\nconst run = await mastra.getWorkflow(\"myWorkflow\").createRun();\nawait run.stream({ inputData, requestContext: ctx });\n```\n\nOnce registered, each **leaf** `createStep` surfaces as a tool row:\n\n- `tool_name` = the step's `entityId` (hyphens normalized to underscores, e.g.\n  `potential-handoff` → `potential_handoff`), so it aggregates with function/MCP\n  tool rows of the same name.\n- `[CLY]_llm_tool_usage_parameter` rows are emitted per key of the step's\n  `inputData` — `{ handoff_to, confidence, user_input, … }` become named params\n  you can break down in analytics.\n- **Structural wrapper steps are skipped.** A `.then(nestedWorkflow)` /\n  `.branch([… nestedWorkflow])` surfaces as a `workflow_step` whose `entityId`\n  is that nested workflow's id; those are excluded so only real decision steps\n  become rows.\n- A step is orchestration, not a tool the model chose, so its rows are parented\n  to the **run** (`parent_event_key: \"run\"`, `prompt_id` = `run_id`) rather than\n  to a generation. They are labelled with `workflow_name` = the root workflow's\n  `entityName` (and `agent_name` when an `agent_run` span exists in the trace).\n- A **`workflow_run` root produces no `[CLY]_llm_interaction`** — no LLM was\n  invoked, so there is no generation to report. `[CLY]_llm_interaction` now means\n  strictly one LLM generation. Earlier versions emitted a row here with no model,\n  `provider: \"unknown\"`, zero tokens and $0 cost but a real end-to-end latency;\n  on real traffic that was a third of all rows, inflating prompt counts,\n  understating cost per prompt and mixing workflow durations into the latency\n  percentiles.\n\n> Nested workflows are traced too: register only the top-level workflow you run —\n> its `.then()` / `.branch()` children are traced automatically as part of the run.\n\n## Configuration\n\nAll adapters accept the same `CountlyAIConfig` object:\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `appKey` | *required* | Countly app key |\n| `url` | *required* | Countly server URL |\n| `requestContextDeviceIdKey` | `\"countlyDeviceId\"` | `requestContext` key holding the device/user id. `null` disables it. |\n| `requestContextThreadIdKey` | `\"threadId\"` | `requestContext` key holding the conversation id. Falls back to Mastra's `span.metadata.threadId`. `null` disables it. |\n| `requestContextWorkflowNameKey` | `\"workflowName\"` | `requestContext` key holding the workflow name. Falls back to the root `workflow_run`'s `entityName`. `null` disables it. |\n| `requestContextPromptIdKey` | — | `requestContext` key holding **your** id for the turn → becomes `run_id`. Opt-in. |\n| `getPromptId` | — | Same thing as a function; outranks `requestContextPromptIdKey`. |\n| `observabilityLevel` | `0` | `0` = metrics only, `1` = + tool calls, `2` = + text previews and tool params |\n| `tags` | `[]` | Labels for cost attribution and filtering |\n| `environment` | `\"production\"` | Environment tag |\n| `costModel` | — | Custom pricing overrides |\n| `flushInterval` | `10000` | Buffer flush interval in ms |\n| `maxBatchSize` | `20` | Max events before auto-flush |\n| `paramValueLength` | `1024` | Cap on a tool argument's serialized value |\n| `debug` | `false` | Log transport errors and dropped/unrecognised spans |\n| `disabled` | `false` | Disable all telemetry |\n\n## Feedback\n\nMastra's **native** signals need no wiring: the exporter implements\n`onScoreEvent`, `onFeedbackEvent` and `addScoreToTrace`, so every scorer result\nand every `recordedSpan.addFeedback()` becomes a `[CLY]_llm_interaction_feedback`\nrow against the run that produced it.\n\nFor feedback that comes from **your own UI**, capture the `prompt_id` of each\ntracked interaction via the `onPrompt` callback, then record feedback against it\nwith `createFeedbackTracker` (re-exported from this package, so no extra install is\nneeded):\n\n```typescript\nimport { CountlyMastraExporter, createFeedbackTracker, type PromptInfo } from \"@countly/ai-sdk-mastra\";\n\nconst countly = { appKey: \"YOUR_APP_KEY\", url: \"https://your-countly-server.com\" };\n\nlet lastPrompt: PromptInfo | undefined;\nconst exporter = new CountlyMastraExporter({\n  ...countly,\n  requestContextDeviceIdKey: \"countlyDeviceId\",\n  onPrompt: (info) => { lastPrompt = info; }, // fires once per exported generation\n});\n// wire the exporter into new Observability({...}) as shown in Quick Start\n\nconst feedback = createFeedbackTracker(countly, { sdk_adapter: \"mastra\" });\n\nawait mastra.getAgent(\"intent\").generate(messages, { requestContext: ctx });\n\n// ...later, when the user rates the answer:\nfeedback.track({\n  prompt_id: lastPrompt!.prompt_id, // rates the whole TURN (== run_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// ...or rate ONE generation of a multi-generation turn:\nfeedback.track({\n  prompt_id: lastPrompt!.event_id,  // that row's own id\n  run_id: lastPrompt!.run_id,\n  rating: \"thumbs_down\",\n});\n```\n\n`PromptInfo` carries `prompt_id` (identical to `run_id`, so this flow is unchanged\nfrom earlier versions), `run_id`, the generation's own `event_id`,\n`generation_index`, plus `provider` / `model` / `status` / `thread_id` /\n`agent_name` / `workflow_name` / `trace_id`. It fires only when a generation was\nactually recorded — never for an orchestration trace.\n\nEach `track()` call emits a `[CLY]_llm_interaction_feedback` event whose\n`prompt_id` links back to the run (or to one generation's `event_id`), with\n`parent_event_key` recording which — powering prompt → feedback funnels and\nper-model satisfaction breakdowns in Countly. In a real app, store the id\nalongside the rendered message (or return it to your client) and read it back when\nthe user rates the answer. Feedback is batched like interaction events; call\n`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"}