{"_id":"@countly/ai-sdk-openai","_rev":"4-77c77c617c7bd232b1b94ef1320cdb61","name":"@countly/ai-sdk-openai","dist-tags":{"latest":"0.0.6"},"versions":{"0.0.3":{"name":"@countly/ai-sdk-openai","version":"0.0.3","_id":"@countly/ai-sdk-openai@0.0.3","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"c06307aaa662c2244349588a5303e4c64f220d9c","tarball":"https://registry.npmjs.org/@countly/ai-sdk-openai/-/ai-sdk-openai-0.0.3.tgz","fileCount":10,"integrity":"sha512-PoRzeTsYGlY08w2Ajpgnnr303iXy3qQAPub2VBOWbwVZSzIZbISZ1qC3hT7oLHpg7q6dSsdKzbdtjfJ8OXwL4Q==","signatures":[{"sig":"MEUCIQDnS73qFUC3To63URPC4tDQqYvwG5J9o7yzHy+1iUPD/AIgCftrwWGBf+PqbEe8juOB5vvqSIZlNCeB4x9KRDyXghw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31678},"type":"module","_from":"file:countly-ai-sdk-openai-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/4ed3200df81b79446393103a4bb9c780/countly-ai-sdk-openai-0.0.3.tgz","_integrity":"sha512-PoRzeTsYGlY08w2Ajpgnnr303iXy3qQAPub2VBOWbwVZSzIZbISZ1qC3hT7oLHpg7q6dSsdKzbdtjfJ8OXwL4Q==","_npmVersion":"11.12.1","description":"Countly AI observability adapter for OpenAI 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":{"openai":">=4.68.0"},"peerDependenciesMeta":{"openai":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-openai_0.0.3_1783443600142_0.43526773369120364","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"name":"@countly/ai-sdk-openai","version":"0.0.4","_id":"@countly/ai-sdk-openai@0.0.4","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"e37fe16618b163e05f983426557d42737b554329","tarball":"https://registry.npmjs.org/@countly/ai-sdk-openai/-/ai-sdk-openai-0.0.4.tgz","fileCount":10,"integrity":"sha512-ciHNNt8ah1NFFLIgsKZLIVIslcf+zkbe0b++FoFOocAjngP6J045j1NpoC1O6d5fR4r0fMUwM7ByoaRes8qO8A==","signatures":[{"sig":"MEQCICuqQZUP3hanezfoeBb3tgr+aUSySu2JvEf6aATBQ8TqAiB8Hn8kl+axG3p64tO9Rb6GSVtzuaiV72qDyaZ//dPAPQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35067},"type":"module","_from":"file:countly-ai-sdk-openai-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/01239828b1c1660a86a38894cc970116/countly-ai-sdk-openai-0.0.4.tgz","_integrity":"sha512-ciHNNt8ah1NFFLIgsKZLIVIslcf+zkbe0b++FoFOocAjngP6J045j1NpoC1O6d5fR4r0fMUwM7ByoaRes8qO8A==","_npmVersion":"10.8.2","description":"Countly AI observability adapter for OpenAI 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":{"openai":">=4.68.0"},"peerDependenciesMeta":{"openai":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-openai_0.0.4_1784147101187_0.24389858273230325","host":"s3://npm-registry-packages-npm-production"}},"0.0.5":{"name":"@countly/ai-sdk-openai","version":"0.0.5","_id":"@countly/ai-sdk-openai@0.0.5","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"dist":{"shasum":"6dfe479d79a857cb3bc6ba94d9a6c95f78963bb1","tarball":"https://registry.npmjs.org/@countly/ai-sdk-openai/-/ai-sdk-openai-0.0.5.tgz","fileCount":10,"integrity":"sha512-Xnnng2KzL7Hv06HsFHep0I3LKWJcDD+TTrjTGJjwDvn/+K7vR6F0Z8XtHyrDGSC8Vx6lk8ArD+N7qFciDxvDBw==","signatures":[{"sig":"MEYCIQDyp0xh4RHvhKaXe6mO9Ozw3mA4ZzrfecxqLFjoYx2WYAIhAODno4iVa3sT6CAR8VlJxxsiVAihbsDcaUoAtPHCz45i","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35067},"type":"module","_from":"file:countly-ai-sdk-openai-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/60386195bc083c0cc03a5e29264a652a/countly-ai-sdk-openai-0.0.5.tgz","_integrity":"sha512-Xnnng2KzL7Hv06HsFHep0I3LKWJcDD+TTrjTGJjwDvn/+K7vR6F0Z8XtHyrDGSC8Vx6lk8ArD+N7qFciDxvDBw==","_npmVersion":"10.8.2","description":"Countly AI observability adapter for OpenAI 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":{"openai":">=4.68.0"},"peerDependenciesMeta":{"openai":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ai-sdk-openai_0.0.5_1784810342525_0.39638584775482744","host":"s3://npm-registry-packages-npm-production"}},"0.0.6":{"name":"@countly/ai-sdk-openai","version":"0.0.6","description":"Countly AI observability adapter for OpenAI 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":{"openai":">=4.68.0"},"peerDependenciesMeta":{"openai":{"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-openai@0.0.6","_integrity":"sha512-EhXZMfACVwbuhU1L+VTjyGVaX5dtvFDze43XByh9eZ+hCUVnbh94yQx8WKlVrjcaSR1AQZC4thB8NYrVtUdBSQ==","_resolved":"/private/var/folders/df/nbmn_hp51n177rvbz8zh44sh0000gn/T/52003c6c08ffe9ef5d66704addbf2270/countly-ai-sdk-openai-0.0.6.tgz","_from":"file:countly-ai-sdk-openai-0.0.6.tgz","_nodeVersion":"20.19.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-EhXZMfACVwbuhU1L+VTjyGVaX5dtvFDze43XByh9eZ+hCUVnbh94yQx8WKlVrjcaSR1AQZC4thB8NYrVtUdBSQ==","shasum":"a4027c4bdd01679bcd5e09c8bc96267650cee508","tarball":"https://registry.npmjs.org/@countly/ai-sdk-openai/-/ai-sdk-openai-0.0.6.tgz","fileCount":18,"unpackedSize":79721,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDhXZKPJu5QxadOpvOC6LPfv0u5HzcROq0G0Au5Xoi3GAIga/iisdodD1442Qv3ByGqz8gR+4cllN5JSzwOQ0tMXnY="}]},"_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-openai_0.0.6_1785687689606_0.1442332423372552"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-07T16:59:59.967Z","modified":"2026-08-02T16:21:29.916Z","0.0.3":"2026-07-07T17:00:00.259Z","0.0.4":"2026-07-15T20:25:01.331Z","0.0.5":"2026-07-23T12:39:02.685Z","0.0.6":"2026-08-02T16:21:29.745Z"},"description":"Countly AI observability adapter for OpenAI SDK","maintainers":[{"name":"mksglu","email":"code.bm.ksglu@gmail.com"}],"readme":"# @countly/ai-sdk-openai\n\n> Countly AI observability adapter for the [OpenAI Node.js SDK](https://github.com/openai/openai-node).\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-openai\n```\n\n`@countly/ai-sdk-core` is pulled in automatically.\n\n## Peer dependency\n\n```\nopenai >= 4.68.0\n```\n\n## Quick Start\n\n```typescript\nimport OpenAI from \"openai\";\nimport { observeOpenAI } from \"@countly/ai-sdk-openai\";\n\n// Per-user attribution via AsyncLocalStorage (Node.js)\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nconst userStore = new AsyncLocalStorage<{ userId: string }>();\n\n// In your middleware — run once per request\napp.use((req, res, next) => {\n  userStore.run({ userId: req.user.id }, next);\n});\n\n// Wrap your client — getDeviceId is called per event at enqueue time\nconst openai = observeOpenAI(new OpenAI(), {\n  appKey: \"YOUR_APP_KEY\",\n  url: \"https://your-countly-server.com\",\n  getDeviceId: () => userStore.getStore()?.userId,\n});\n\n// Use exactly as before — observability is automatic\nconst response = await openai.chat.completions.create({\n  model: \"gpt-4o\",\n  messages: [{ role: \"user\", content: \"Explain quantum computing\" }],\n});\n```\n\n## Streaming\n\n```typescript\nconst stream = await openai.chat.completions.create({\n  model: \"gpt-4o\",\n  messages: [{ role: \"user\", content: \"Hello\" }],\n  stream: true,\n});\n\nfor await (const chunk of stream) {\n  process.stdout.write(chunk.choices[0]?.delta?.content || \"\");\n}\n// Events reported automatically after stream completes\n```\n\n**Token usage for streams requires opting in.** OpenAI only puts `usage` in the\nstream when you pass `stream_options: { include_usage: true }`. Without it the\ntokens genuinely are not reported, so the row carries\n`usage_state: \"not_reported\"` and **no** token or cost fields — rather than a\n`0` that would read as a free call:\n\n```typescript\nconst stream = await openai.chat.completions.create({\n  model: \"gpt-4o\",\n  messages: [{ role: \"user\", content: \"Hello\" }],\n  stream: true,\n  stream_options: { include_usage: true }, // ← required for streaming cost data\n});\n```\n\nBreaking out of the loop early, or aborting via `stream.controller.abort()`,\nstill records the row — the request was billed for everything streamed so far —\nwith `status: \"incomplete\"` and `error: \"stream_cancelled\"`.\n\n## Tracked surfaces\n\n| Entrypoint | Tracked |\n|---|---|\n| `client.chat.completions.create()` (incl. `stream: true`) | ✅ |\n| `client.chat.completions.parse()` | ✅ |\n| `client.responses.create()` (incl. `stream: true`) | ✅ |\n| `client.responses.parse()` | ✅ |\n| `client.chat.completions.stream()` / `client.responses.stream()` helper runners | ❌ — they build their own request through the unwrapped client |\n| `embeddings`, `moderations`, `files`, `batches`, … | ❌ by design — a non-generation operation must never produce an interaction row |\n\nSet `debug: true` to get a one-time warning when an untracked generation\nentrypoint is accessed.\n\n## What's captured\n\n- Token usage — input and output totals (inclusive of cache and reasoning), plus\n  the `cached` / `reasoning` / audio subsets, and `usage_state` stating whether\n  the provider reported any of it\n- Cost from model pricing, plus `cost_priced` stating whether the model could be\n  priced at all (an unpriced model emits **no** cost fields rather than `$0`)\n- Latency (total + time to first token for streaming)\n- Model config (temperature, top_p, max_tokens, frequency/presence penalties) —\n  on streaming rows too\n- `api_host_type` (`vendor_direct` / `azure_openai` / `gateway` / `local_infra`)\n  and `api_host` — the client's `baseURL` **hostname only**; path, query and\n  credentials are never emitted\n- Tool calls and their parameters, with `call_id` so repeat calls of one tool\n  stay distinguishable. A tool's `status` is `\"unknown\"` unless the outcome was\n  actually observed (the hosted Responses tools — MCP, file search, code\n  interpreter — do report theirs)\n- Error tracking with categorization (rate_limit, context_length, content_filter, timeout, auth_error)\n- APM traces for performance monitoring\n- Per-user usage aggregation\n\n## Browser\n\nIn the browser there is no multi-user request mixing — each user runs in their own tab. Use a static device ID:\n\n```typescript\nconst openai = observeOpenAI(new OpenAI(), {\n  appKey: \"YOUR_APP_KEY\",\n  url: \"https://your-countly-server.com\",\n  deviceId: loggedInUser.id,\n});\n```\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 OpenAI from \"openai\";\nimport { observeOpenAI, createFeedbackTracker, type PromptInfo } from \"@countly/ai-sdk-openai\";\n\nconst countly = { appKey: \"YOUR_APP_KEY\", url: \"https://your-countly-server.com\" };\n\nlet lastPrompt: PromptInfo | undefined;\nconst openai = observeOpenAI(new OpenAI(), {\n  ...countly,\n  onPrompt: (info) => { lastPrompt = info; }, // fires after every tracked call\n});\n\nconst feedback = createFeedbackTracker(countly); // stamped as sdk_adapter \"openai\"\n\nconst response = await openai.chat.completions.create({\n  model: \"gpt-4o\",\n  messages: [{ role: \"user\", content: \"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 that joins back\nto the interaction — powering prompt → feedback funnels and per-model\nsatisfaction breakdowns in Countly. `lastPrompt.prompt_id` (identical to\n`lastPrompt.run_id`) rates the whole turn; to rate **one** generation of a\nmulti-call turn instead, pass that generation's row id:\n\n```typescript\nfeedback.track({ prompt_id: lastPrompt!.event_id, run_id: lastPrompt!.run_id, rating: \"thumbs_down\" });\n```\n\nIn a real app, store the 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## Caller-supplied prompt IDs\n\nBy default each tracked call gets an auto-generated id. If you already mint your own request/trace ID upstream, supply it with the `getPromptId` callback — it is read once per call and becomes that turn's `run_id`, the join column every emitted row carries. Return `undefined` to fall back to auto-generation for that call:\n\n```typescript\nconst openai = observeOpenAI(new OpenAI(), {\n  ...countly,\n  getPromptId: () => currentRequestId(), // your own id, or undefined to auto-generate\n});\n```\n\nBecause that id becomes the turn's `run_id`, you can correlate user feedback without the `onPrompt` round-trip: pass your known id straight into `createFeedbackTracker().track({ prompt_id })`. This is handy when the id already flows through your app (e.g. a chat message id), so both the interaction and its feedback are keyed on a value you control.\n\n> On the wire, each row's own primary key is `event_id`, and `prompt_id` is a\n> **foreign key**: on an interaction it points at that row's own `event_id`, and\n> on a tool row at the generation that requested the tool. Your id is `run_id`.\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"}