@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 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9D,eAAO,MAAM,gBAAgB,+BAA+B,CAAC;AAE7D,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,yEAAyE;AACzE,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,aAAa,CAAC,OAAO,GAAE,WAAgB,GAAG,cAAc,CAsEvE"}
package/dist/config.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Configuration resolution for the SDK.
3
+ *
4
+ * Resolution order:
5
+ * 1. Explicit options to wrap()
6
+ * 2. Environment variables (TENSORCOST_API_KEY, TENSORCOST_BASE_URL,
7
+ * TENSORCOST_TENANT_ID, TENSORCOST_PROXY_URL)
8
+ * 3. Defaults (only for baseUrl)
9
+ *
10
+ * If apiKey is still missing after resolution, MissingConfigError is
11
+ * raised. This is the *one* case the SDK does not fail open — it means
12
+ * the SDK was never configured at all, and silently dropping every
13
+ * observation would be worse than a loud import-time failure.
14
+ *
15
+ * If appliedMode=true and proxyUrl is missing from both options and env,
16
+ * TensorCostConfigError is thrown. Applied mode needs an explicit proxy
17
+ * URL; there is no safe default.
18
+ */
19
+ export const DEFAULT_BASE_URL = "https://api.tensorcost.com";
20
+ export class MissingConfigError extends Error {
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = "MissingConfigError";
24
+ }
25
+ }
26
+ /** Thrown when applied mode is enabled but required config is absent. */
27
+ export class TensorCostConfigError extends Error {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = "TensorCostConfigError";
31
+ }
32
+ }
33
+ export function resolveConfig(options = {}) {
34
+ const apiKey = options.apiKey ?? process.env.TENSORCOST_API_KEY ?? "";
35
+ const baseUrlRaw = options.baseUrl ?? process.env.TENSORCOST_BASE_URL ?? DEFAULT_BASE_URL;
36
+ const tenantId = options.tenantId ?? process.env.TENSORCOST_TENANT_ID ?? null;
37
+ // Phase A4 batch 3 — environment tag. Trimmed; capped at 64 chars per
38
+ // project_environment_scoping.md. An empty / whitespace-only value
39
+ // collapses to null so the SDK omits the field from the envelope and
40
+ // the backend's column default ('production') applies.
41
+ const environmentRaw = options.environment ?? process.env.TENSORCOST_ENVIRONMENT ?? null;
42
+ let environment = null;
43
+ if (environmentRaw !== null) {
44
+ const trimmed = String(environmentRaw).trim();
45
+ if (trimmed.length > 64) {
46
+ throw new MissingConfigError("TensorCost environment must be 64 characters or fewer.");
47
+ }
48
+ environment = trimmed.length === 0 ? null : trimmed;
49
+ }
50
+ // Phase A4 batch 4 — provider-connection id. Same trim+cap rules as
51
+ // environment; collapsing blank to null so the SDK omits the field
52
+ // when not configured.
53
+ const connectionIdRaw = options.connectionId ?? process.env.TENSORCOST_CONNECTION_ID ?? null;
54
+ let connectionId = null;
55
+ if (connectionIdRaw !== null) {
56
+ const trimmed = String(connectionIdRaw).trim();
57
+ if (trimmed.length > 64) {
58
+ throw new MissingConfigError("TensorCost connectionId must be 64 characters or fewer.");
59
+ }
60
+ connectionId = trimmed.length === 0 ? null : trimmed;
61
+ }
62
+ const failOpen = options.failOpen ?? true;
63
+ const appliedMode = options.appliedMode ?? false;
64
+ const proxyUrlRaw = options.proxyUrl ?? process.env.TENSORCOST_PROXY_URL ?? null;
65
+ const proxyUrl = proxyUrlRaw !== null ? proxyUrlRaw.replace(/\/+$/, "") : null;
66
+ if (!apiKey) {
67
+ throw new MissingConfigError("TensorCost apiKey not found. Pass { apiKey } to wrap() or set the " +
68
+ "TENSORCOST_API_KEY environment variable.");
69
+ }
70
+ if (appliedMode && !proxyUrl) {
71
+ throw new TensorCostConfigError("TensorCost applied mode is enabled but no proxy URL was provided. " +
72
+ "Pass { proxyUrl } to wrap() or set the TENSORCOST_PROXY_URL " +
73
+ "environment variable.");
74
+ }
75
+ return {
76
+ apiKey,
77
+ baseUrl: baseUrlRaw.replace(/\/+$/, ""),
78
+ tenantId: tenantId || null,
79
+ environment,
80
+ connectionId,
81
+ failOpen,
82
+ appliedMode,
83
+ proxyUrl,
84
+ };
85
+ }
86
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { wrap } from "./wrap.js";
2
+ export { MissingConfigError, TensorCostConfigError } from "./config.js";
3
+ export { UnsupportedClientError } from "./providers/detect.js";
4
+ export type { WrapOptions, Observation, Provider } from "./types.js";
5
+ export declare const version = "0.3.0";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErE,eAAO,MAAM,OAAO,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { wrap } from "./wrap.js";
2
+ export { MissingConfigError, TensorCostConfigError } from "./config.js";
3
+ export { UnsupportedClientError } from "./providers/detect.js";
4
+ export const version = "0.3.0";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAG/D,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Anthropic client wrapping.
3
+ *
4
+ * Intercepts `client.messages.create`. Same observation contract as the
5
+ * OpenAI wrapper — only metadata is captured (model, input/output tokens),
6
+ * never the message content itself.
7
+ */
8
+ import type { ObservationTransport } from "../transport.js";
9
+ export declare function install(client: unknown, transport: ObservationTransport, tenantId: string | null, environment: string | null, connectionId: string | null): void;
10
+ export declare const __test__: {
11
+ MARKER: string;
12
+ };
13
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AA8G5D,wBAAgB,OAAO,CACrB,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,oBAAoB,EAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,IAAI,CAkBN;AAED,eAAO,MAAM,QAAQ;;CAAa,CAAC"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Anthropic client wrapping.
3
+ *
4
+ * Intercepts `client.messages.create`. Same observation contract as the
5
+ * OpenAI wrapper — only metadata is captured (model, input/output tokens),
6
+ * never the message content itself.
7
+ */
8
+ import { randomUUID } from "node:crypto";
9
+ const SDK_VERSION = "tensorcost-node/0.3.0";
10
+ const MARKER = "__tensorcost_wrapped__";
11
+ function nowIso() {
12
+ return new Date().toISOString();
13
+ }
14
+ function extractUsage(response) {
15
+ if (!response || typeof response !== "object") {
16
+ return { input: null, output: null };
17
+ }
18
+ const usage = response.usage;
19
+ if (!usage || typeof usage !== "object") {
20
+ return { input: null, output: null };
21
+ }
22
+ const u = usage;
23
+ const input = typeof u.input_tokens === "number" ? u.input_tokens : null;
24
+ const output = typeof u.output_tokens === "number" ? u.output_tokens : null;
25
+ return { input, output };
26
+ }
27
+ function wrapMethod(bound, opts) {
28
+ const wrapped = async function (...args) {
29
+ const requestAt = nowIso();
30
+ const firstArg = args[0];
31
+ const model = (firstArg && typeof firstArg === "object" && firstArg.model) ||
32
+ "unknown";
33
+ const correlationId = randomUUID();
34
+ try {
35
+ const response = await bound.apply(this, args);
36
+ const { input, output } = extractUsage(response);
37
+ const obs = {
38
+ sdk_version: SDK_VERSION,
39
+ provider: "anthropic",
40
+ model: String(model),
41
+ operation: "messages",
42
+ request_at: requestAt,
43
+ response_at: nowIso(),
44
+ input_tokens: input,
45
+ output_tokens: output,
46
+ cost_usd_cents: null,
47
+ status: "success",
48
+ error_message: null,
49
+ correlation_id: correlationId,
50
+ tenant_id: opts.tenantId,
51
+ ...(opts.environment !== null
52
+ ? { environment: opts.environment }
53
+ : {}),
54
+ ...(opts.connectionId !== null
55
+ ? { connection_id: opts.connectionId }
56
+ : {}),
57
+ };
58
+ try {
59
+ opts.transport.post(obs);
60
+ }
61
+ catch {
62
+ // transport already fail-open
63
+ }
64
+ return response;
65
+ }
66
+ catch (err) {
67
+ const obs = {
68
+ sdk_version: SDK_VERSION,
69
+ provider: "anthropic",
70
+ model: String(model),
71
+ operation: "messages",
72
+ request_at: requestAt,
73
+ response_at: nowIso(),
74
+ input_tokens: null,
75
+ output_tokens: null,
76
+ cost_usd_cents: null,
77
+ status: "error",
78
+ error_message: `${err.name}: ${err.message}`,
79
+ correlation_id: correlationId,
80
+ tenant_id: opts.tenantId,
81
+ ...(opts.environment !== null
82
+ ? { environment: opts.environment }
83
+ : {}),
84
+ ...(opts.connectionId !== null
85
+ ? { connection_id: opts.connectionId }
86
+ : {}),
87
+ };
88
+ try {
89
+ opts.transport.post(obs);
90
+ }
91
+ catch {
92
+ // see above
93
+ }
94
+ throw err;
95
+ }
96
+ };
97
+ Object.defineProperty(wrapped, MARKER, { value: true, enumerable: false });
98
+ return wrapped;
99
+ }
100
+ export function install(client, transport, tenantId, environment, connectionId) {
101
+ if (!client || typeof client !== "object")
102
+ return;
103
+ const c = client;
104
+ const messages = c.messages;
105
+ if (messages &&
106
+ typeof messages === "object" &&
107
+ typeof messages.create === "function" &&
108
+ !messages.create[MARKER]) {
109
+ const bound = messages.create.bind(messages);
110
+ messages.create = wrapMethod(bound, {
111
+ transport,
112
+ tenantId,
113
+ environment,
114
+ connectionId,
115
+ });
116
+ }
117
+ }
118
+ export const __test__ = { MARKER };
119
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Provider detection — duck-typed.
3
+ *
4
+ * We avoid importing `openai` or `@anthropic-ai/sdk` at runtime so that
5
+ * the SDK has zero peer-dep cost when the customer only uses one of the
6
+ * two providers. Detection inspects the shape of the client object.
7
+ */
8
+ import type { Provider } from "../types.js";
9
+ export declare class UnsupportedClientError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ export declare function detectProvider(client: unknown): Provider;
13
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/providers/detect.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,CAkCxD"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Provider detection — duck-typed.
3
+ *
4
+ * We avoid importing `openai` or `@anthropic-ai/sdk` at runtime so that
5
+ * the SDK has zero peer-dep cost when the customer only uses one of the
6
+ * two providers. Detection inspects the shape of the client object.
7
+ */
8
+ export class UnsupportedClientError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "UnsupportedClientError";
12
+ }
13
+ }
14
+ export function detectProvider(client) {
15
+ if (client === null || typeof client !== "object") {
16
+ throw new UnsupportedClientError("tensorcost: wrap() requires an object (an OpenAI or Anthropic client instance).");
17
+ }
18
+ const c = client;
19
+ // OpenAI: has chat.completions.create OR completions.create
20
+ const chat = c.chat;
21
+ const chatCompletions = chat && typeof chat === "object" ? chat.completions : undefined;
22
+ const hasOpenAIShape = (chatCompletions && typeof chatCompletions.create === "function") ||
23
+ (c.completions && typeof c.completions.create === "function");
24
+ // Anthropic: has messages.create
25
+ const messages = c.messages;
26
+ const hasAnthropicShape = messages && typeof messages.create === "function";
27
+ // Disambiguation: OpenAI also has a top-level `completions` AND `chat`.
28
+ // Anthropic only exposes `messages`. If both shapes match (unusual),
29
+ // prefer the more specific Anthropic match only when there's no chat.
30
+ if (hasOpenAIShape) {
31
+ return "openai";
32
+ }
33
+ if (hasAnthropicShape) {
34
+ return "anthropic";
35
+ }
36
+ throw new UnsupportedClientError("tensorcost: client does not look like an OpenAI or Anthropic instance " +
37
+ "(missing chat.completions.create / completions.create / messages.create).");
38
+ }
39
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../../src/providers/detect.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,UAAU,cAAc,CAAC,MAAe;IAC5C,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,IAAI,sBAAsB,CAC9B,iFAAiF,CAClF,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,GAAG,MAA6B,CAAC;IAExC,4DAA4D;IAC5D,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;IACpB,MAAM,eAAe,GACnB,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;IAClE,MAAM,cAAc,GAClB,CAAC,eAAe,IAAI,OAAO,eAAe,CAAC,MAAM,KAAK,UAAU,CAAC;QACjE,CAAC,CAAC,CAAC,WAAW,IAAI,OAAO,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC;IAEhE,iCAAiC;IACjC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;IAC5B,MAAM,iBAAiB,GACrB,QAAQ,IAAI,OAAO,QAAQ,CAAC,MAAM,KAAK,UAAU,CAAC;IAEpD,wEAAwE;IACxE,qEAAqE;IACrE,sEAAsE;IACtE,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,MAAM,IAAI,sBAAsB,CAC9B,wEAAwE;QACtE,2EAA2E,CAC9E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { detectProvider, UnsupportedClientError } from "./detect.js";
2
+ export * as openaiProvider from "./openai.js";
3
+ export * as anthropicProvider from "./anthropic.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,KAAK,cAAc,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,iBAAiB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { detectProvider, UnsupportedClientError } from "./detect.js";
2
+ export * as openaiProvider from "./openai.js";
3
+ export * as anthropicProvider from "./anthropic.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,KAAK,cAAc,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,iBAAiB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * OpenAI client wrapping.
3
+ *
4
+ * Intercepts:
5
+ * * `client.chat.completions.create`
6
+ * * `client.completions.create` (legacy text completions; v0 still ships)
7
+ *
8
+ * We capture only metadata (model, token usage) — never prompt content.
9
+ * Customer prompts stay on their machine.
10
+ */
11
+ import type { ObservationTransport } from "../transport.js";
12
+ export declare function install(client: unknown, transport: ObservationTransport, tenantId: string | null, environment: string | null, connectionId: string | null): void;
13
+ export declare const __test__: {
14
+ MARKER: string;
15
+ };
16
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAuH5D,wBAAgB,OAAO,CACrB,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,oBAAoB,EAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,IAAI,CAsCN;AAED,eAAO,MAAM,QAAQ;;CAAa,CAAC"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * OpenAI client wrapping.
3
+ *
4
+ * Intercepts:
5
+ * * `client.chat.completions.create`
6
+ * * `client.completions.create` (legacy text completions; v0 still ships)
7
+ *
8
+ * We capture only metadata (model, token usage) — never prompt content.
9
+ * Customer prompts stay on their machine.
10
+ */
11
+ import { randomUUID } from "node:crypto";
12
+ const SDK_VERSION = "tensorcost-node/0.3.0";
13
+ const MARKER = "__tensorcost_wrapped__";
14
+ function nowIso() {
15
+ return new Date().toISOString();
16
+ }
17
+ function extractUsage(response) {
18
+ if (!response || typeof response !== "object") {
19
+ return { input: null, output: null };
20
+ }
21
+ const usage = response.usage;
22
+ if (!usage || typeof usage !== "object") {
23
+ return { input: null, output: null };
24
+ }
25
+ const u = usage;
26
+ const input = typeof u.prompt_tokens === "number" ? u.prompt_tokens : null;
27
+ const output = typeof u.completion_tokens === "number" ? u.completion_tokens : null;
28
+ return { input, output };
29
+ }
30
+ function wrapMethod(bound, opts) {
31
+ const wrapped = async function (...args) {
32
+ const requestAt = nowIso();
33
+ const firstArg = args[0];
34
+ const model = (firstArg && typeof firstArg === "object" && firstArg.model) ||
35
+ "unknown";
36
+ const correlationId = randomUUID();
37
+ try {
38
+ const response = await bound.apply(this, args);
39
+ const { input, output } = extractUsage(response);
40
+ const obs = {
41
+ sdk_version: SDK_VERSION,
42
+ provider: "openai",
43
+ model: String(model),
44
+ operation: opts.operation,
45
+ request_at: requestAt,
46
+ response_at: nowIso(),
47
+ input_tokens: input,
48
+ output_tokens: output,
49
+ cost_usd_cents: null,
50
+ status: "success",
51
+ error_message: null,
52
+ correlation_id: correlationId,
53
+ tenant_id: opts.tenantId,
54
+ ...(opts.environment !== null
55
+ ? { environment: opts.environment }
56
+ : {}),
57
+ ...(opts.connectionId !== null
58
+ ? { connection_id: opts.connectionId }
59
+ : {}),
60
+ };
61
+ try {
62
+ opts.transport.post(obs);
63
+ }
64
+ catch {
65
+ // belt-and-suspenders: transport itself is fail-open
66
+ }
67
+ return response;
68
+ }
69
+ catch (err) {
70
+ const obs = {
71
+ sdk_version: SDK_VERSION,
72
+ provider: "openai",
73
+ model: String(model),
74
+ operation: opts.operation,
75
+ request_at: requestAt,
76
+ response_at: nowIso(),
77
+ input_tokens: null,
78
+ output_tokens: null,
79
+ cost_usd_cents: null,
80
+ status: "error",
81
+ error_message: `${err.name}: ${err.message}`,
82
+ correlation_id: correlationId,
83
+ tenant_id: opts.tenantId,
84
+ ...(opts.environment !== null
85
+ ? { environment: opts.environment }
86
+ : {}),
87
+ ...(opts.connectionId !== null
88
+ ? { connection_id: opts.connectionId }
89
+ : {}),
90
+ };
91
+ try {
92
+ opts.transport.post(obs);
93
+ }
94
+ catch {
95
+ // see above
96
+ }
97
+ throw err;
98
+ }
99
+ };
100
+ Object.defineProperty(wrapped, MARKER, { value: true, enumerable: false });
101
+ return wrapped;
102
+ }
103
+ export function install(client, transport, tenantId, environment, connectionId) {
104
+ if (!client || typeof client !== "object")
105
+ return;
106
+ const c = client;
107
+ const chat = c.chat;
108
+ const chatCompletions = chat && typeof chat === "object" ? chat.completions : undefined;
109
+ if (chatCompletions &&
110
+ typeof chatCompletions.create === "function" &&
111
+ !chatCompletions.create[MARKER]) {
112
+ const bound = chatCompletions.create.bind(chatCompletions);
113
+ chatCompletions.create = wrapMethod(bound, {
114
+ operation: "chat.completions",
115
+ transport,
116
+ tenantId,
117
+ environment,
118
+ connectionId,
119
+ });
120
+ }
121
+ const legacy = c.completions;
122
+ if (legacy &&
123
+ typeof legacy === "object" &&
124
+ typeof legacy.create === "function" &&
125
+ !legacy.create[MARKER]) {
126
+ const bound = legacy.create.bind(legacy);
127
+ legacy.create = wrapMethod(bound, {
128
+ operation: "completions",
129
+ transport,
130
+ tenantId,
131
+ environment,
132
+ connectionId,
133
+ });
134
+ }
135
+ }
136
+ export const __test__ = { MARKER };
137
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
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
+ import type { Observation } from "./types.js";
23
+ export interface TransportOptions {
24
+ baseUrl: string;
25
+ apiKey: string;
26
+ timeoutMs?: number;
27
+ failOpen?: boolean;
28
+ /** Optional fetch override for testing. Defaults to global fetch. */
29
+ fetchImpl?: typeof fetch;
30
+ }
31
+ export declare class ObservationTransport {
32
+ private readonly baseUrl;
33
+ private readonly apiKey;
34
+ private readonly timeoutMs;
35
+ private readonly failOpen;
36
+ private readonly fetchImpl;
37
+ private pending;
38
+ private tokenCache;
39
+ /** In-flight token-exchange promise; coalesces concurrent refreshes. */
40
+ private tokenInflight;
41
+ constructor(opts: TransportOptions);
42
+ /**
43
+ * Enqueue an observation for fire-and-forget delivery. Returns
44
+ * immediately (synchronously). Errors are logged via console.warn,
45
+ * never thrown, when failOpen is true.
46
+ */
47
+ post(observation: Observation): void;
48
+ private deliver;
49
+ private getToken;
50
+ private exchangeToken;
51
+ }
52
+ export declare const __test__: {
53
+ SDK_VERSION: string;
54
+ MAX_QUEUE_DEPTH: number;
55
+ };
56
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQ9C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAOD,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IAEzC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,UAAU,CAA2B;IAC7C,wEAAwE;IACxE,OAAO,CAAC,aAAa,CAAgC;gBAEzC,IAAI,EAAE,gBAAgB;IAQlC;;;;OAIG;IACH,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;YAqBtB,OAAO;YAqCP,QAAQ;YAiBR,aAAa;CAuC5B;AAED,eAAO,MAAM,QAAQ;;;CAGpB,CAAC"}