@tensorcost/sdk 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +201 -0
  3. package/README.md +119 -0
  4. package/dist/applied.d.ts +98 -0
  5. package/dist/applied.d.ts.map +1 -0
  6. package/dist/applied.js +174 -0
  7. package/dist/applied.js.map +1 -0
  8. package/dist/config.d.ts +29 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +86 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/index.d.ts +6 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +5 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/providers/anthropic.d.ts +13 -0
  17. package/dist/providers/anthropic.d.ts.map +1 -0
  18. package/dist/providers/anthropic.js +119 -0
  19. package/dist/providers/anthropic.js.map +1 -0
  20. package/dist/providers/detect.d.ts +13 -0
  21. package/dist/providers/detect.d.ts.map +1 -0
  22. package/dist/providers/detect.js +39 -0
  23. package/dist/providers/detect.js.map +1 -0
  24. package/dist/providers/index.d.ts +4 -0
  25. package/dist/providers/index.d.ts.map +1 -0
  26. package/dist/providers/index.js +4 -0
  27. package/dist/providers/index.js.map +1 -0
  28. package/dist/providers/openai.d.ts +16 -0
  29. package/dist/providers/openai.d.ts.map +1 -0
  30. package/dist/providers/openai.js +137 -0
  31. package/dist/providers/openai.js.map +1 -0
  32. package/dist/transport.d.ts +56 -0
  33. package/dist/transport.d.ts.map +1 -0
  34. package/dist/transport.js +150 -0
  35. package/dist/transport.js.map +1 -0
  36. package/dist/types.d.ts +143 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +9 -0
  39. package/dist/types.js.map +1 -0
  40. package/dist/wrap.d.ts +16 -0
  41. package/dist/wrap.d.ts.map +1 -0
  42. package/dist/wrap.js +48 -0
  43. package/dist/wrap.js.map +1 -0
  44. package/package.json +48 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Fire-and-forget HTTP transport for observation events.
3
+ *
4
+ * Design:
5
+ *
6
+ * * `post(observation)` returns synchronously the moment the observation
7
+ * is enqueued. The actual fetch runs on the microtask queue via
8
+ * `queueMicrotask`, so the customer's hot path is never blocked on
9
+ * network IO.
10
+ * * If the in-flight queue saturates (>= MAX_QUEUE_DEPTH), we drop the
11
+ * oldest observation rather than block. Losing one observation is
12
+ * strictly preferable to slowing down the customer's request loop.
13
+ * * A short-lived JWT is fetched once via
14
+ * `POST /api/inference-proxy/sdk-token/exchange` using the long-lived
15
+ * API key.
16
+ * The token is cached in-memory and refreshed when within
17
+ * TOKEN_REFRESH_LEEWAY_MS of expiry.
18
+ * * All errors during posting are swallowed when `failOpen` is true
19
+ * (the default) and surfaced via console.warn. The customer's call
20
+ * site never sees an exception.
21
+ */
22
+ const SDK_VERSION = "tensorcost-node/0.3.0";
23
+ const TOKEN_REFRESH_LEEWAY_MS = 60_000;
24
+ const DEFAULT_TOKEN_LIFETIME_MS = 15 * 60_000;
25
+ const MAX_QUEUE_DEPTH = 1024;
26
+ const DEFAULT_TIMEOUT_MS = 2_000;
27
+ export class ObservationTransport {
28
+ constructor(opts) {
29
+ this.pending = 0;
30
+ this.tokenCache = null;
31
+ /** In-flight token-exchange promise; coalesces concurrent refreshes. */
32
+ this.tokenInflight = null;
33
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
34
+ this.apiKey = opts.apiKey;
35
+ this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
36
+ this.failOpen = opts.failOpen ?? true;
37
+ this.fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
38
+ }
39
+ /**
40
+ * Enqueue an observation for fire-and-forget delivery. Returns
41
+ * immediately (synchronously). Errors are logged via console.warn,
42
+ * never thrown, when failOpen is true.
43
+ */
44
+ post(observation) {
45
+ if (this.pending >= MAX_QUEUE_DEPTH) {
46
+ // Backpressure: drop *this* observation (the "oldest" semantically
47
+ // is whichever is in-flight; in a microtask-dispatched queue we
48
+ // cannot pull in-flight work back, so we drop the newest enqueue
49
+ // attempt — the practical effect of "drop oldest when full" once
50
+ // the queue stays saturated.).
51
+ // eslint-disable-next-line no-console
52
+ console.warn("tensorcost: observation queue saturated; dropping observation");
53
+ return;
54
+ }
55
+ this.pending += 1;
56
+ queueMicrotask(() => {
57
+ void this.deliver(observation).finally(() => {
58
+ this.pending -= 1;
59
+ });
60
+ });
61
+ }
62
+ async deliver(observation) {
63
+ try {
64
+ const token = await this.getToken();
65
+ const url = `${this.baseUrl}/api/inference-proxy/observation`;
66
+ const controller = new AbortController();
67
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
68
+ try {
69
+ const resp = await this.fetchImpl(url, {
70
+ method: "POST",
71
+ headers: {
72
+ Authorization: `Bearer ${token}`,
73
+ "Content-Type": "application/json",
74
+ "User-Agent": SDK_VERSION,
75
+ },
76
+ body: JSON.stringify(observation),
77
+ signal: controller.signal,
78
+ });
79
+ if (!resp.ok) {
80
+ // eslint-disable-next-line no-console
81
+ console.warn(`tensorcost: observation POST failed status=${resp.status}`);
82
+ }
83
+ }
84
+ finally {
85
+ clearTimeout(timer);
86
+ }
87
+ }
88
+ catch (err) {
89
+ if (!this.failOpen) {
90
+ throw err;
91
+ }
92
+ // eslint-disable-next-line no-console
93
+ console.warn(`tensorcost: observation delivery failed: ${err.message}`);
94
+ }
95
+ }
96
+ async getToken() {
97
+ const now = Date.now();
98
+ if (this.tokenCache &&
99
+ now < this.tokenCache.expiresAt - TOKEN_REFRESH_LEEWAY_MS) {
100
+ return this.tokenCache.token;
101
+ }
102
+ if (this.tokenInflight) {
103
+ return this.tokenInflight;
104
+ }
105
+ this.tokenInflight = this.exchangeToken().finally(() => {
106
+ this.tokenInflight = null;
107
+ });
108
+ return this.tokenInflight;
109
+ }
110
+ async exchangeToken() {
111
+ const url = `${this.baseUrl}/api/inference-proxy/sdk-token/exchange`;
112
+ const controller = new AbortController();
113
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
114
+ try {
115
+ const resp = await this.fetchImpl(url, {
116
+ method: "POST",
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ "User-Agent": SDK_VERSION,
120
+ },
121
+ body: JSON.stringify({ api_key: this.apiKey }),
122
+ signal: controller.signal,
123
+ });
124
+ if (!resp.ok) {
125
+ throw new Error(`token-exchange failed status=${resp.status}`);
126
+ }
127
+ const body = (await resp.json());
128
+ const token = body.token ?? body.access_token;
129
+ if (!token) {
130
+ throw new Error("token-exchange response missing token field");
131
+ }
132
+ const lifetimeMs = typeof body.expires_in === "number"
133
+ ? body.expires_in * 1000
134
+ : DEFAULT_TOKEN_LIFETIME_MS;
135
+ this.tokenCache = {
136
+ token,
137
+ expiresAt: Date.now() + lifetimeMs,
138
+ };
139
+ return token;
140
+ }
141
+ finally {
142
+ clearTimeout(timer);
143
+ }
144
+ }
145
+ }
146
+ export const __test__ = {
147
+ SDK_VERSION,
148
+ MAX_QUEUE_DEPTH,
149
+ };
150
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Internal type defs for the TensorCost Node SDK.
3
+ *
4
+ * Kept intentionally narrow — the wrap() entry-point is generic over
5
+ * the customer's client type, so most provider shapes are duck-typed
6
+ * at runtime rather than expressed in TypeScript.
7
+ */
8
+ export type Provider = "openai" | "anthropic";
9
+ export type Operation = "chat.completions" | "completions" | "messages";
10
+ export type ObservationStatus = "success" | "error";
11
+ /**
12
+ * Wire shape posted to `POST /api/proxy/observation`. Mirrors the
13
+ * Python SDK's `Observation` model byte-for-byte so the two SDKs
14
+ * are interchangeable from the backend's point of view.
15
+ */
16
+ export interface Observation {
17
+ sdk_version: string;
18
+ provider: Provider;
19
+ model: string;
20
+ operation: Operation;
21
+ request_at: string;
22
+ response_at: string;
23
+ input_tokens: number | null;
24
+ output_tokens: number | null;
25
+ cost_usd_cents: number | null;
26
+ status: ObservationStatus;
27
+ error_message: string | null;
28
+ correlation_id: string;
29
+ tenant_id?: string | null;
30
+ /**
31
+ * Phase A4 batch 3 — per-environment data scoping. Optional. Stamped
32
+ * onto every observation envelope when the SDK was constructed with
33
+ * `wrap(client, { environment: 'staging' })`. Forwarded verbatim by
34
+ * the inference-proxy ObservationService onto the Kafka envelope;
35
+ * ai-service falls back to 'production' on omit.
36
+ */
37
+ environment?: string;
38
+ /**
39
+ * Phase A4 batch 4 — provider-connection identifier. Optional.
40
+ * Stamped onto every observation envelope when the SDK was
41
+ * constructed with `wrap(client, { connectionId: '...' })`.
42
+ * Forwarded verbatim by the inference-proxy onto the Kafka
43
+ * envelope; ai-service uses it as the second tier of its
44
+ * env-resolution chain (see ProxyObservationConsumer) when the SDK
45
+ * did not set `environment`.
46
+ */
47
+ connection_id?: string;
48
+ }
49
+ export interface WrapOptions {
50
+ /** Long-lived TensorCost API key. Falls back to `TENSORCOST_API_KEY`. */
51
+ apiKey?: string;
52
+ /** Backend base URL. Falls back to `TENSORCOST_BASE_URL`, then default. */
53
+ baseUrl?: string;
54
+ /** Optional explicit tenant id, useful for multi-tenant test harnesses. */
55
+ tenantId?: string;
56
+ /**
57
+ * Phase A4 batch 3 — environment tag. When present, the SDK reads
58
+ * the value once at `wrap()` time and stamps every outbound
59
+ * observation envelope with it. Falls back to
60
+ * `TENSORCOST_ENVIRONMENT`. When omitted entirely the field is
61
+ * absent from the envelope; the backend then falls back to the
62
+ * column default ('production'). Free-text per
63
+ * project_environment_scoping.md (1..64 chars).
64
+ */
65
+ environment?: string;
66
+ /**
67
+ * Phase A4 batch 4 — provider-connection identifier. When present,
68
+ * the SDK stamps it on every outbound observation envelope. Falls
69
+ * back to `TENSORCOST_CONNECTION_ID`. When the SDK is wrapping a
70
+ * client that talks to ONE provider connection, set this so the
71
+ * backend can resolve the connection's `environment` even if the
72
+ * SDK was not given an explicit `environment`.
73
+ *
74
+ * Precedence (most-specific wins):
75
+ * 1. SDK envelope `environment` (when set on the SDK or env var).
76
+ * 2. SDK envelope `connectionId` → backend looks up
77
+ * `integration.cloud_account.environment` (60s LRU cache).
78
+ * 3. Backend column default `'production'`.
79
+ *
80
+ * Customers using ONE SDK instance across multiple connections
81
+ * should leave this unset and rely on `environment` per-init, OR
82
+ * construct a separate `wrap(...)` per connection.
83
+ */
84
+ connectionId?: string;
85
+ /**
86
+ * When true (the default), any internal observation-pipeline error is
87
+ * caught and logged via console.warn — the customer's call ALWAYS
88
+ * returns normally. Set to false only in SDK tests / CI smoke runs.
89
+ */
90
+ failOpen?: boolean;
91
+ /**
92
+ * Layer 2 applied mode. When true, the SDK routes every inference
93
+ * request through the TensorCost inference-proxy rather than sending
94
+ * it directly to the upstream AI provider. The proxy decides
95
+ * per-request whether to actually re-route or pass through unchanged.
96
+ *
97
+ * Defaults to false. Observe-only telemetry continues to fire
98
+ * regardless of this setting.
99
+ *
100
+ * Requires `proxyUrl` (or `TENSORCOST_PROXY_URL` env var) to be set;
101
+ * throws `TensorCostConfigError` at `wrap()` time if neither is present.
102
+ *
103
+ * Not yet supported for AWS Bedrock clients — `wrap()` will throw
104
+ * immediately if you enable applied mode with a Bedrock client.
105
+ */
106
+ appliedMode?: boolean;
107
+ /**
108
+ * TensorCost inference-proxy base URL used in applied mode.
109
+ * Falls back to `TENSORCOST_PROXY_URL`. When `appliedMode=true` and
110
+ * neither is set, `wrap()` throws `TensorCostConfigError`.
111
+ *
112
+ * Example: `https://proxy.tensorcost.com`
113
+ */
114
+ proxyUrl?: string;
115
+ }
116
+ export interface ResolvedConfig {
117
+ apiKey: string;
118
+ baseUrl: string;
119
+ tenantId: string | null;
120
+ /**
121
+ * Phase A4 batch 3 — resolved environment tag. `null` when the
122
+ * customer did not configure one; the SDK omits the `environment`
123
+ * field from the envelope in that case so the backend's default
124
+ * ('production') applies.
125
+ */
126
+ environment: string | null;
127
+ /**
128
+ * Phase A4 batch 4 — resolved provider-connection identifier.
129
+ * `null` when the customer did not configure one; the SDK omits the
130
+ * `connection_id` field from the envelope in that case.
131
+ */
132
+ connectionId: string | null;
133
+ failOpen: boolean;
134
+ /** Resolved applied-mode flag. */
135
+ appliedMode: boolean;
136
+ /**
137
+ * Resolved proxy base URL. Non-null only when `appliedMode=true`.
138
+ * Guaranteed by config resolution — `wrap()` throws before returning
139
+ * a config with `appliedMode=true` and `proxyUrl=null`.
140
+ */
141
+ proxyUrl: string | null;
142
+ }
143
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG,kBAAkB,GAAG,aAAa,GAAG,UAAU,CAAC;AAExE,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;;;OAKG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Internal type defs for the TensorCost Node SDK.
3
+ *
4
+ * Kept intentionally narrow — the wrap() entry-point is generic over
5
+ * the customer's client type, so most provider shapes are duck-typed
6
+ * at runtime rather than expressed in TypeScript.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/dist/wrap.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The single public entry point: `wrap(client, options?)`.
3
+ *
4
+ * Returns the SAME client reference (mutates in place), per the SDK
5
+ * ergonomics non-negotiable: customer changes one line, no other code
6
+ * paths shift.
7
+ *
8
+ * Applied mode (Layer 2): when `options.appliedMode=true`, the client's
9
+ * baseURL is permanently redirected to the TensorCost inference-proxy and
10
+ * two custom headers (`x-tc-provider-url`, `x-tc-provider-auth`) are
11
+ * injected into `client.defaultHeaders`. The proxy handles per-request
12
+ * routing decisions. Observe-only telemetry still fires post-call.
13
+ */
14
+ import type { WrapOptions } from "./types.js";
15
+ export declare function wrap<T>(client: T, options?: WrapOptions): T;
16
+ //# sourceMappingURL=wrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../src/wrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,GAAE,WAAgB,GAAG,CAAC,CAyC/D"}
package/dist/wrap.js ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The single public entry point: `wrap(client, options?)`.
3
+ *
4
+ * Returns the SAME client reference (mutates in place), per the SDK
5
+ * ergonomics non-negotiable: customer changes one line, no other code
6
+ * paths shift.
7
+ *
8
+ * Applied mode (Layer 2): when `options.appliedMode=true`, the client's
9
+ * baseURL is permanently redirected to the TensorCost inference-proxy and
10
+ * two custom headers (`x-tc-provider-url`, `x-tc-provider-auth`) are
11
+ * injected into `client.defaultHeaders`. The proxy handles per-request
12
+ * routing decisions. Observe-only telemetry still fires post-call.
13
+ */
14
+ import { resolveConfig } from "./config.js";
15
+ import { ObservationTransport } from "./transport.js";
16
+ import { detectProvider } from "./providers/detect.js";
17
+ import { installAppliedMode } from "./applied.js";
18
+ import * as openaiProvider from "./providers/openai.js";
19
+ import * as anthropicProvider from "./providers/anthropic.js";
20
+ export function wrap(client, options = {}) {
21
+ // resolveConfig throws MissingConfigError when apiKey is absent, and
22
+ // TensorCostConfigError when appliedMode=true with no proxyUrl.
23
+ // Both are intentional non-fail-open cases — see config.ts header.
24
+ const config = resolveConfig(options);
25
+ const transport = new ObservationTransport({
26
+ baseUrl: config.baseUrl,
27
+ apiKey: config.apiKey,
28
+ failOpen: config.failOpen,
29
+ });
30
+ const provider = detectProvider(client);
31
+ // Applied mode: redirect the client to the proxy before installing
32
+ // the observe-only wrappers. Order matters — installAppliedMode mutates
33
+ // baseURL and defaultHeaders, and the provider wrappers capture the
34
+ // (now-modified) bound methods, so post-call observations correctly
35
+ // attribute the response to the original provider model even though
36
+ // the network call went through the proxy.
37
+ if (config.appliedMode && config.proxyUrl !== null) {
38
+ installAppliedMode(client, config.proxyUrl, provider);
39
+ }
40
+ if (provider === "openai") {
41
+ openaiProvider.install(client, transport, config.tenantId, config.environment, config.connectionId);
42
+ }
43
+ else {
44
+ anthropicProvider.install(client, transport, config.tenantId, config.environment, config.connectionId);
45
+ }
46
+ return client;
47
+ }
48
+ //# sourceMappingURL=wrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap.js","sourceRoot":"","sources":["../src/wrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,cAAc,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAC;AAG9D,MAAM,UAAU,IAAI,CAAI,MAAS,EAAE,UAAuB,EAAE;IAC1D,qEAAqE;IACrE,gEAAgE;IAChE,mEAAmE;IACnE,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,IAAI,oBAAoB,CAAC;QACzC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAExC,mEAAmE;IACnE,wEAAwE;IACxE,oEAAoE;IACpE,oEAAoE;IACpE,oEAAoE;IACpE,2CAA2C;IAC3C,IAAI,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QACnD,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,cAAc,CAAC,OAAO,CACpB,MAAM,EACN,SAAS,EACT,MAAM,CAAC,QAAQ,EACf,MAAM,CAAC,WAAW,EAClB,MAAM,CAAC,YAAY,CACpB,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,iBAAiB,CAAC,OAAO,CACvB,MAAM,EACN,SAAS,EACT,MAAM,CAAC,QAAQ,EACf,MAAM,CAAC,WAAW,EAClB,MAAM,CAAC,YAAY,CACpB,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@tensorcost/sdk",
3
+ "version": "0.3.0",
4
+ "description": "One-line wrapper SDK for TensorCost — fire-and-forget LLM observability for OpenAI and Anthropic clients.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "CHANGELOG.md",
20
+ "LICENSE"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18.0.0"
24
+ },
25
+ "keywords": [
26
+ "llm",
27
+ "openai",
28
+ "anthropic",
29
+ "observability",
30
+ "cost",
31
+ "tensorcost"
32
+ ],
33
+ "author": "Vaadh Labs / TensorCost <engineering@tensorcost.com>",
34
+ "homepage": "https://tensorcost.com",
35
+ "scripts": {
36
+ "build": "tsc -b",
37
+ "dev": "tsc -b --watch",
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "vitest run --coverage",
40
+ "lint": "echo 'no lint configured'"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.0.0",
44
+ "@vitest/coverage-v8": "^2.1",
45
+ "typescript": "^5.6.0",
46
+ "vitest": "^2.1.0"
47
+ }
48
+ }