{"_id":"@cost-monitor/sdk","_rev":"2-830f2b9b99c1cc9f8ba591762758722c","name":"@cost-monitor/sdk","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@cost-monitor/sdk","version":"0.1.0","keywords":["cost-monitor","llm","observability","cost-tracking","sdk"],"license":"MIT","_id":"@cost-monitor/sdk@0.1.0","maintainers":[{"name":"cost-monitor","email":"maxim.miniev@gmail.com"}],"homepage":"https://github.com/vsesis/cost-monitor/tree/main/packages/sdk#readme","bugs":{"url":"https://github.com/vsesis/cost-monitor/issues"},"dist":{"shasum":"bb39fe37b7fadf52ca997fc1947874fba95f1dbc","tarball":"https://registry.npmjs.org/@cost-monitor/sdk/-/sdk-0.1.0.tgz","fileCount":55,"integrity":"sha512-SeDlpQHS8UoDmo50etfspE8cjQac9LuGdbJCc1w5wos+oNrAkHBfqxb2IGFcHdNncKlo9R4PDepqnWlOOHSTyg==","signatures":[{"sig":"MEUCIQDKgOlIfVYfzbrHHxJXnrxawW8CMQ3QfWCereI+AOiPKQIgV2kYewO0xF23RbIujHRsYFtcHeSXCVvooRFeewbkaXM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":109582},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"f79ac0c91ae11adead947d4227b0beaa7eb6e346","scripts":{"test":"vitest run","build":"tsc --build","clean":"rm -rf dist .turbo coverage","typecheck":"tsc --noEmit","prepublishOnly":"pnpm build"},"_npmUser":{"name":"cost-monitor","email":"maxim.miniev@gmail.com"},"repository":{"url":"git+https://github.com/vsesis/cost-monitor.git","type":"git","directory":"packages/sdk"},"_npmVersion":"11.16.0","description":"Typed client for cost-monitor: report LLM events and read back a project's events feed and spend metrics.","directories":{},"sideEffects":false,"_nodeVersion":"26.2.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"openai":">=4.0.0","@anthropic-ai/sdk":">=0.30.0"},"peerDependenciesMeta":{"openai":{"optional":true},"@anthropic-ai/sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.1.0_1780572170265_0.7228260550114829","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@cost-monitor/sdk","version":"0.1.1","description":"Typed client for cost-monitor: report LLM events and read back a project's events feed and spend metrics.","keywords":["cost-monitor","llm","observability","cost-tracking","sdk"],"license":"MIT","type":"module","sideEffects":false,"engines":{"node":">=18.0.0"},"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"publishConfig":{"access":"public"},"scripts":{"build":"tsc --build","typecheck":"tsc --noEmit","test":"vitest run","clean":"rm -rf dist .turbo coverage","prepublishOnly":"pnpm build"},"peerDependencies":{"@anthropic-ai/sdk":">=0.30.0","openai":">=4.0.0"},"peerDependenciesMeta":{"@anthropic-ai/sdk":{"optional":true},"openai":{"optional":true}},"gitHead":"f79ac0c91ae11adead947d4227b0beaa7eb6e346","_id":"@cost-monitor/sdk@0.1.1","_nodeVersion":"26.2.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-gHruTOLiHCOfsqtv7/neh76SOya/obSPg0buX3dHthh7z2CvdWLZa5sVsV42TGfheKjRTWFCCDN5UfwCBST7LA==","shasum":"6975311f9089916933c64b2db1e6a8b2f3c17a41","tarball":"https://registry.npmjs.org/@cost-monitor/sdk/-/sdk-0.1.1.tgz","fileCount":55,"unpackedSize":109260,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCoLhBDS7rNIeuEbeQXaR4pKN/sGk7xqnEFbnKb+LtjoQIgVx1kAzFe6G/RKd2nTKzPAEthj9hMnWQrN8x2P9a8nuY="}]},"_npmUser":{"name":"cost-monitor","email":"maxim.miniev@gmail.com"},"directories":{},"maintainers":[{"name":"cost-monitor","email":"maxim.miniev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_0.1.1_1780572552581_0.48321975441452536"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-04T11:22:50.098Z","modified":"2026-06-04T11:29:12.846Z","0.1.0":"2026-06-04T11:22:50.412Z","0.1.1":"2026-06-04T11:29:12.735Z"},"license":"MIT","keywords":["cost-monitor","llm","observability","cost-tracking","sdk"],"description":"Typed client for cost-monitor: report LLM events and read back a project's events feed and spend metrics.","maintainers":[{"name":"cost-monitor","email":"maxim.miniev@gmail.com"}],"readme":"# @cost-monitor/sdk\n\nTyped client for cost-monitor. Report\nLLM events and read a project's own data back — all authenticated with a single\nproject API key (`cm_live_…`) and scoped to that key's project.\n\n- Zero runtime dependencies.\n- ESM, Node ≥ 18 (uses the global `fetch` and `crypto.randomUUID`).\n- No automatic retries — a non-ok response or transport failure throws a typed\n  `CostMonitorError`.\n\n## Install\n\n```sh\nnpm install @cost-monitor/sdk\n# or: pnpm add @cost-monitor/sdk\n```\n\n## Usage\n\ncost-monitor is self-hosted, so `baseUrl` is **required** — there is no default\nhost. The API key is sent as `Authorization: Bearer <apiKey>`.\n\n```ts\nimport { CostMonitorClient } from \"@cost-monitor/sdk\";\n\nconst client = new CostMonitorClient({\n  apiKey: process.env.COST_MONITOR_API_KEY!, // cm_live_…\n  baseUrl: \"https://costs.your-company.com\",\n});\n```\n\n### Report an event\n\n```ts\nconst result = await client.track({\n  provider: \"openai\",\n  model: \"gpt-4o\",\n  inputTokens: 1200,\n  outputTokens: 350,\n  status: \"success\",\n  feature: \"chat\",\n  customerId: \"cust_123\",\n  // clientEventId is optional — the SDK fills it with a UUID for idempotency.\n});\n// → { id, created, costUsd, warnings? }\n```\n\n### Report many events at once\n\nUp to 100 events per call. Per-item failures are reported in the result rather\nthan throwing.\n\n```ts\nconst { results } = await client.trackBatch([\n  { provider: \"openai\", model: \"gpt-4o\", inputTokens: 10, outputTokens: 5, status: \"success\" },\n  { provider: \"anthropic\", model: \"claude-sonnet-4-5\", inputTokens: 8, outputTokens: 4, status: \"success\" },\n]);\n```\n\n### Buffered delivery (fire-and-forget)\n\n`client.track` / `client.trackBatch` send immediately and throw on failure — you\ndecide when and how to retry. For a long-lived process that would rather\n\"enqueue and forget\", wrap the client in an `EventBuffer`. It batches events\n(flush by size or interval) and retries transient failures (`5xx`, network,\n`429`) with exponential backoff; `400` / `401` / `413` are dropped, since a retry\ncan't fix them. A `clientEventId` is assigned at enqueue and reused across\nretries, so the server deduplicates and no event is double-counted.\n\n```ts\nimport { CostMonitorClient, EventBuffer } from \"@cost-monitor/sdk\";\n\nconst client = new CostMonitorClient({ apiKey, baseUrl });\nconst buffer = new EventBuffer(client, {\n  maxBatchSize: 50, // flush when this many are queued (default 50, max 100)\n  flushIntervalMs: 5000, // …or this often (default 5s)\n  maxQueueSize: 10_000, // drop-oldest beyond this (default 10k)\n  maxRetries: 5, // retries after the first attempt (default 5)\n  onError: (event, err) => log.warn({ event, err }, \"event dropped\"),\n  onDrop: (event) => log.warn({ event }, \"queue overflow\"),\n});\n\nbuffer.enqueue(event); // synchronous, never throws\n```\n\n`enqueue` never throws and `onError` / `onDrop` exceptions are swallowed, so the\nbuffer can't break your hot path. The interval timer is `unref`'d and won't keep\nthe process alive on its own. Flush remaining events before exit:\n\n```ts\nprocess.on(\"SIGTERM\", async () => {\n  await buffer.close(); // stops the timer + final flush; idempotent\n  process.exit(0);\n});\n```\n\n### Auto-tracking via wrappers\n\n`wrapOpenAI` / `wrapAnthropic` return a transparent proxy of your provider client\nthat reports each completion to a sink for you — you keep calling the provider\nSDK exactly as before. The response (and stream) you get back is untouched; the\nserver stays the source of truth for cost. `openai` and `@anthropic-ai/sdk` are\n**optional peer dependencies** — install only the one you use.\n\n```ts\nimport OpenAI from \"openai\";\nimport { CostMonitorClient, EventBuffer, wrapOpenAI, withCostMonitor } from \"@cost-monitor/sdk\";\n\nconst buffer = new EventBuffer(new CostMonitorClient({ apiKey, baseUrl }));\nconst openai = wrapOpenAI(new OpenAI(), { sink: buffer, defaultFeature: \"chat\" });\n\n// Plain call — attribution comes from the wrap-config defaults:\nawait openai.chat.completions.create({ model: \"gpt-4o\", messages });\n\n// Per-call attribution via withCostMonitor (stripped before the request hits OpenAI):\nawait openai.chat.completions.create(\n  withCostMonitor({ model: \"gpt-4o\", messages }, { customerId: \"cust_123\", feature: \"summarize\" }),\n);\n```\n\nThe `sink` is anything with `enqueue(event)` — an `EventBuffer` is the usual\nchoice. Per-call `feature` / `customerId` / `userId` / `metadata` go through\n`withCostMonitor`, which stashes them under a private `Symbol` that never reaches\nthe provider. Streaming works too (consume the stream as usual; usage is read\nfrom the final chunk — for OpenAI `stream_options.include_usage` is enabled for\nyou). If the provider call throws, an event with `status: \"error\"` is recorded\nand the original error is re-thrown unchanged. Tracking never throws into your\ncall path; route internal failures with `onError`.\n\nAnthropic is identical via `wrapAnthropic(new Anthropic(), { sink })`\n(intercepts `messages.create`). Do not wrap a client twice.\n\n### Read the events feed\n\nKeyset-paginated, newest-first by default. Pass the returned `nextCursor` back\nas `cursor` to page; `nextCursor: null` means the end of the feed.\n\n```ts\nlet cursor: string | undefined;\ndo {\n  const page = await client.getEvents({ cursor });\n  for (const event of page.items) {\n    console.log(event.createdAt, event.model, event.costUsd);\n  }\n  cursor = page.nextCursor ?? undefined;\n} while (cursor);\n```\n\n### Read spend metrics\n\nThe bundled dashboard metrics for a period (`\"7d\" | \"30d\" | \"90d\"`, default\n`\"30d\"`): total, top features/customers/models by spend, and a daily time series.\n\n```ts\nconst metrics = await client.getMetrics({ period: \"7d\" });\nconsole.log(metrics.total.costUsd, metrics.total.calls);\nconsole.log(metrics.byModel); // [{ provider, model, costUsd, calls }, …]\n```\n\n## Errors\n\nAll failures throw a subclass of `CostMonitorError`:\n\n| Class | When |\n| --- | --- |\n| `CostMonitorValidationError` | `400` — invalid request |\n| `CostMonitorAuthError` | `401` — missing/invalid API key |\n| `CostMonitorRateLimitError` | `429` — carries `retryAfter` |\n| `CostMonitorServerError` | `413`, `5xx`, or a transport failure (`status: 0`) |\n\n```ts\nimport { CostMonitorRateLimitError } from \"@cost-monitor/sdk\";\n\ntry {\n  await client.track(event);\n} catch (err) {\n  if (err instanceof CostMonitorRateLimitError) {\n    // back off for err.retryAfter seconds\n  }\n}\n```\n\n## Publishing (maintainers)\n\nManual, pre-flight checked. The `@cost-monitor` npm scope availability is TBD —\nconfirm before the first publish.\n\n1. `npm whoami` — confirm you are logged in.\n2. `npm org ls cost-monitor` / `npm access ls-packages` — is the `@cost-monitor`\n   scope yours?\n   - Scope free → `npm org create cost-monitor` (or publish under a personal\n     scope).\n   - If `@cost-monitor` is taken, fall back to the unscoped name\n     `cost-monitor-sdk` — change `name` in `package.json` accordingly.\n3. `pnpm --filter @cost-monitor/sdk build`\n4. `pnpm --filter @cost-monitor/sdk pack` → inspect the tarball; its `dist/` must\n   have no real `@cost-monitor/shared` imports (only the inline comment in\n   `shared-types.js` mentions the name).\n5. `npm publish --dry-run` (from `packages/sdk`) → review the files list.\n6. `npm publish` — `publishConfig.access: \"public\"` is already set.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}