chatsee-sdk
v2.2.0
Published
Production-grade tracker for LLM conversations, tool calls, errors, PII redaction, and closed-loop remediation for the Chatsee platform
Maintainers
Readme
Chatsee SDK (JavaScript / TypeScript)
Production-grade tracker for LLM conversations, tool calls, errors, local PII redaction, and closed-loop remediation for the Chatsee platform.
Feature parity with the Python chatsee-ai SDK.
Installation
npm install chatsee-sdk
# Optional — only needed for closed-loop remediation (MCP) calls:
npm install @modelcontextprotocol/sdkEnvironments
Select the environment by setting api_base_url to an alias or a full URL.
| Alias | URL |
|-------|-----|
| dev | https://dev.chatsee.ai/api |
| qa | https://qa.chatsee.ai/api |
| demo | https://gcp-demo.chatsee.ai/api |
| poc | https://app.chatsee.ai/api |
Defaults to qa. Any full URL is also accepted (e.g. https://my-host.com/api).
Usage (tracking)
import { ChatseeTracker } from 'chatsee-sdk';
const tracker = new ChatseeTracker({
agent_id: '<agent_id>',
tenant_id: '<tenant_id>', // 24-char hex ObjectId
user_id: 'user-42', // optional, but see "Identifying the user"
api_base_url: 'dev', // or "qa" / "demo" / "poc"
redaction_enabled: true, // optional: locally redact before sending
});
tracker.startTurn('My email is [email protected]', { any: 'metadata' });
tracker.logToolCall('search', { query: 'weather' }, { temp: 21 });
await tracker.endTurn('It is 21°C.');
// Signal the last turn of a conversation (flushed immediately downstream):
await tracker.endTurn('Goodbye!', /* isFinalTurn */ true);All network methods (endTurn, sendBatch, redact, and the remediation
calls) are async and return Promises.
Identifying the user
user_id is optional. Without it the backend stamps an anonymous, per-session
identifier, which is fine for volume but cannot tell you that the same person
came back tomorrow or failed the same tool three times. Pass a real id and the
Chatsee UI can filter traces down to one user.
One tracker serving many users can override it per turn instead:
tracker.startTurn('Where is my order?', {}, undefined, 'user-99');Precedence is turn-level, then the tracker default, then metadata.user_id.
Durations
Turn duration is measured automatically: the clock starts at startTurn and
the elapsed milliseconds ship with endTurn as duration_ms.
Tool calls are only timed if you ask. Wrap the call and the SDK measures it, logging failures (and re-throwing) as well as successes:
const weather = await tracker.trackToolCall('search', { query: 'weather' },
() => lookupWeather('weather'));Or pass a duration you measured yourself:
tracker.logToolCall('search', { query: 'weather' }, result, undefined, 143);An untimed tool call simply omits duration_ms — it is never reported as
zero, so a missing measurement is never mistaken for an instant one.
Token usage
The SDK never sees your model calls, so it cannot count tokens for you — hand it the usage object the provider already returned, once per call:
const resp = await client.chat.completions.create({ model, messages });
tracker.logModelCall({ model, provider: 'openai', usage: resp.usage });Field names are mapped for you, so the object goes in as it comes out:
resp.usage for OpenAI and Anthropic, resp.usage_metadata for Gemini. Pass the
numbers directly instead if you already have them:
tracker.logModelCall({ model, prompt_tokens: 1180, completion_tokens: 240 });Log every call the turn made — a retry, a router call, a summarizer — and ChatSee
reports the turn's total from the sum. It also reports how many of those calls
reported usage at all, which is what stops an unmeasured turn being read as a
cheap one: a turn where nothing reported is shown as unavailable, not as zero
tokens. cost_usd is accepted but never inferred; omit it unless the provider
priced the call.
Batch
await tracker.sendBatch([
{ user_message: 'hi', bot_message: 'hello' },
{ user_message: 'bye', bot_message: 'goodbye', system_prompt: '...' },
]);Usage (redaction)
Module-level helper (does not send anything to Chatsee):
import { redact } from 'chatsee-sdk';
const clean = await redact(
{ message: 'Card 4111 1111 1111 1111', email: '[email protected]' },
{ api_base_url: 'qa', fields_to_redact: '*', verify_ssl: false }
);Tracker instance:
const clean = await tracker.redact({ user_message: 'call 9876543210' }, ['user_message']);Closed-loop remediation (MCP)
Requires @modelcontextprotocol/sdk and MCP credentials.
const tracker = new ChatseeTracker({
agent_id: '<agent_id>',
tenant_id: '<tenant_id>',
api_base_url: 'dev',
mcp_server_url: 'https://<host>/mcp/',
mcp_api_key: '<mcp_api_key>',
});
const pending = await tracker.fetchRemediations('pending');
// ... inject pending.skills_markdown into your system prompt ...
await tracker.acknowledgeRemediations(pending.failure_memory_ids as string[], 'injected');Available: fetchRemediations, acknowledgeRemediations, rejectRemediations,
getRemediationStatus.
Tenant encryption (optional)
EncryptDecryptUtil provides per-tenant Fernet encryption, interoperable with
the Chatsee backend's crypto_utils.py (same master key + tenant id). It is a
no-op unless a master key is set via the masterKeyB64 argument or the
CHATSEE_CRYPTO_SECRET_KEY / CRYPTO_SECRET_KEY env var.
import { EncryptDecryptUtil } from 'chatsee-sdk';
const token = EncryptDecryptUtil.encryptJson('<tenant_id>', { pan: 'ABCDE1234F' });
const back = EncryptDecryptUtil.decryptJson('<tenant_id>', token);Keys are derived with PBKDF2-HMAC-SHA256(master, salt=tenant_id, 100k iters),
so each tenant gets a distinct key from a shared master secret.
Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| agent_id | string | (required) | Agent identifier. |
| tenant_id | string | (required) | Tenant identifier (24-char hex ObjectId). |
| session_id | string | auto | Conversation session ID. Assigned by the API if omitted. |
| user_id | string | anon | End-user identifier. Anonymous per-session id if omitted. |
| timeout | number | 10 | HTTP timeout in seconds. |
| verify_ssl | boolean | false | Verify SSL certificates. |
| api_base_url | string | "qa" | Environment alias or full URL. |
| mcp_server_url | string | — | chatsee-mcp Streamable HTTP endpoint. |
| mcp_api_key | string | — | Bearer token for the MCP server. |
| redaction_enabled | boolean | false | Locally redact tracked turns before sending. |
| redaction_fields_to_redact | Set/Array/"*" | {user_message, bot_message, interactions} | Fields to redact. |
| redaction_cache_ttl_seconds | number | 300 | Classifier cache TTL. |
| redaction_timeout_seconds | number | 5.0 | Classifier fetch timeout. |
| redaction_classifiers_url | string | — | Override the classifiers endpoint. |
Notes
- No API key is required for tracking (parity with the Python SDK).
- The SDK sets Node's
insecureHTTPParseron its HTTP client so it can read the Chatsee gateways' multi-line CSP response header, which Node's strict parser otherwise rejects.
License
MIT
