@fluxgate/sdk 0.0.2-dev.0 → 0.0.3-dev.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.
package/dist/index.d.ts CHANGED
@@ -7,4 +7,4 @@ export declare class FluxGate {
7
7
  constructor(config: FluxGateConfig);
8
8
  recordEvent(event: LLMEvent): Promise<CreateAiEventResponse | null>;
9
9
  }
10
- export type { LLMEvent, CreateAiEventResponse, TrackedUser, AiEventMetadata, FluxGateCostTrackingResponse, WithTracking, AiEventStatus, AiEventUsage, ExtractedUsage, FluxGateConfig, } from "./types/types.js";
10
+ export type { LLMEvent, CreateAiEventResponse, TrackedUser, AiEventMetadata, AiEventStatus, AiEventUsage, Performance, CostOverride, ExtractedUsage, FluxGateCostTrackingResponse, WithTracking, FluxGateConfig, } from "./types/types.js";
package/dist/index.js CHANGED
@@ -19,9 +19,6 @@ export class FluxGate {
19
19
  if (this.debug) {
20
20
  console.log(`[fluxgate] Sending event to ${this.endpoint}:`, JSON.stringify(event, null, 2));
21
21
  }
22
- if (!event.status) {
23
- event.status = "SUCCESS";
24
- }
25
22
  const fetchPromise = fetch(this.endpoint, {
26
23
  method: "POST",
27
24
  headers: {
@@ -46,13 +43,7 @@ export class FluxGate {
46
43
  if (this.debug) {
47
44
  console.error("[fluxgate] Network error sending event:", err);
48
45
  }
49
- return {
50
- cost: 0,
51
- createdAt: new Date().toISOString(),
52
- id: "error-" + Math.random().toString(36).substring(2, 15),
53
- status: "ERROR",
54
- };
55
- // throw err;
46
+ return null;
56
47
  }
57
48
  let trackingData = null;
58
49
  try {
@@ -45,28 +45,23 @@ describe("FluxGate", () => {
45
45
  describe("recordEvent", () => {
46
46
  it("should send event to the correct endpoint", async () => {
47
47
  const mockResponse = {
48
- id: "event-123",
49
- createdAt: "2026-05-05T00:00:00Z",
50
- cost: 0.001,
48
+ recordId: "event-123",
49
+ totalTokens: 150,
50
+ totalCost: 0.001,
51
+ status: "ok",
51
52
  };
52
53
  vi.mocked(fetch).mockResolvedValue({
53
- status: 200,
54
- statusText: "OK",
54
+ status: 201,
55
+ statusText: "Created",
55
56
  text: async () => JSON.stringify(mockResponse),
56
57
  });
57
58
  const event = {
58
- usage: {
59
- inputTokens: 100,
60
- outputTokens: 50,
61
- model: "gpt-4",
62
- provider: "openai",
63
- latencyInMs: 1000,
64
- },
65
- status: "SUCCESS",
66
- metadata: {
67
- feature: "chat",
68
- user: "user-123",
69
- },
59
+ provider: "openai",
60
+ model: "gpt-4",
61
+ feature: "chat",
62
+ user: "user-123",
63
+ performance: { latency: 1000, status: "SUCCESS", isStreamed: false },
64
+ usage: { promptTokens: 100, completionTokens: 50 },
70
65
  };
71
66
  const result = await instance.recordEvent(event);
72
67
  expect(fetch).toHaveBeenCalledWith(mockEndpoint, expect.objectContaining({
@@ -80,26 +75,27 @@ describe("FluxGate", () => {
80
75
  }));
81
76
  expect(result).toEqual(mockResponse);
82
77
  });
83
- it("should default status to SUCCESS if not provided", async () => {
78
+ it("should send event body unchanged to the API", async () => {
84
79
  vi.mocked(fetch).mockResolvedValue({
85
80
  status: 200,
86
81
  statusText: "OK",
87
82
  text: async () => JSON.stringify({
88
- id: "123",
89
- createdAt: "2026-05-05T00:00:00Z",
90
- cost: 0.001,
83
+ recordId: "123",
84
+ totalTokens: 150,
85
+ totalCost: 0.001,
86
+ status: "ok",
91
87
  }),
92
88
  });
93
89
  const event = {
94
- usage: {
95
- inputTokens: 100,
96
- outputTokens: 50,
97
- },
90
+ provider: "openai",
91
+ model: "gpt-4o",
92
+ performance: { latency: 500, status: "SUCCESS", isStreamed: false },
93
+ usage: { promptTokens: 100, completionTokens: 50 },
98
94
  };
99
95
  await instance.recordEvent(event);
100
96
  const callArgs = vi.mocked(fetch).mock.calls[0];
101
97
  const body = JSON.parse(callArgs[1]?.body);
102
- expect(body.status).toBe("SUCCESS");
98
+ expect(body).toEqual(event);
103
99
  });
104
100
  it("should handle fetch errors gracefully", async () => {
105
101
  const mockResponse = {
@@ -113,10 +109,10 @@ describe("FluxGate", () => {
113
109
  text: async () => JSON.stringify(mockResponse),
114
110
  });
115
111
  const event = {
116
- usage: {
117
- inputTokens: 100,
118
- outputTokens: 50,
119
- },
112
+ provider: "openai",
113
+ model: "gpt-4o",
114
+ performance: { latency: 500, status: "SUCCESS", isStreamed: false },
115
+ usage: { promptTokens: 100, completionTokens: 50 },
120
116
  };
121
117
  const result = await instance.recordEvent(event);
122
118
  expect(result).toEqual(mockResponse);
@@ -128,10 +124,10 @@ describe("FluxGate", () => {
128
124
  text: async () => "invalid json",
129
125
  });
130
126
  const event = {
131
- usage: {
132
- inputTokens: 100,
133
- outputTokens: 50,
134
- },
127
+ provider: "openai",
128
+ model: "gpt-4o",
129
+ performance: { latency: 500, status: "SUCCESS", isStreamed: false },
130
+ usage: { promptTokens: 100, completionTokens: 50 },
135
131
  };
136
132
  const result = await instance.recordEvent(event);
137
133
  expect(result).toBeNull();
@@ -150,12 +146,12 @@ describe("FluxGate", () => {
150
146
  }), 1000);
151
147
  }));
152
148
  const event = {
153
- usage: {
154
- inputTokens: 100,
155
- outputTokens: 50,
156
- },
149
+ provider: "openai",
150
+ model: "gpt-4o",
151
+ performance: { latency: 500, status: "SUCCESS", isStreamed: false },
152
+ usage: { promptTokens: 100, completionTokens: 50 },
157
153
  };
158
- await expect(shortTimeoutTracker.recordEvent(event)).resolves.toHaveProperty("status", "ERROR");
154
+ await expect(shortTimeoutTracker.recordEvent(event)).resolves.toBeNull();
159
155
  });
160
156
  it("should include complex metadata in the event", async () => {
161
157
  vi.mocked(fetch).mockResolvedValue({
@@ -168,30 +164,26 @@ describe("FluxGate", () => {
168
164
  }),
169
165
  });
170
166
  const event = {
171
- usage: {
172
- inputTokens: 100,
173
- outputTokens: 50,
174
- cachedTokens: 20,
175
- model: "gpt-4",
176
- provider: "openai",
177
- latencyInMs: 1500,
178
- isStreamed: true,
179
- streamingDurationInMs: 2000,
167
+ provider: "openai",
168
+ model: "gpt-4",
169
+ feature: "chat",
170
+ step: "generation",
171
+ user: {
172
+ id: "user-123",
173
+ name: "Test User",
174
+ email: "test@example.com",
175
+ monthlyRevenue: "99.99",
180
176
  },
181
- status: "SUCCESS",
182
- metadata: {
183
- feature: "chat",
184
- step: "generation",
185
- user: {
186
- id: "user-123",
187
- name: "Test User",
188
- email: "test@example.com",
189
- monthlyRevenue: 99.99,
190
- },
191
- sessionId: "session-456",
192
- conversationId: "conv-789",
193
- customField: "custom value",
177
+ sessionId: "session-456",
178
+ conversationId: "conv-789",
179
+ performance: {
180
+ latency: 1500,
181
+ status: "SUCCESS",
182
+ isStreamed: true,
183
+ streamDuration: 2000,
194
184
  },
185
+ usage: { promptTokens: 100, completionTokens: 50, cacheReadTokens: 20 },
186
+ metadata: { customField: "custom value" },
195
187
  };
196
188
  await instance.recordEvent(event);
197
189
  const callArgs = vi.mocked(fetch).mock.calls[0];
@@ -210,19 +202,21 @@ describe("FluxGate", () => {
210
202
  }),
211
203
  });
212
204
  const event = {
213
- usage: {
214
- inputTokens: 100,
215
- outputTokens: 0,
216
- },
217
- status: {
205
+ provider: "openai",
206
+ model: "gpt-4o",
207
+ performance: {
208
+ latency: 500,
218
209
  status: "ERROR",
210
+ isStreamed: false,
219
211
  errorMessage: "API call failed",
220
212
  },
213
+ usage: { promptTokens: 100, completionTokens: 0 },
221
214
  };
222
215
  await instance.recordEvent(event);
223
216
  const callArgs = vi.mocked(fetch).mock.calls[0];
224
217
  const body = JSON.parse(callArgs[1]?.body);
225
- expect(body.status).toEqual(event.status);
218
+ expect(body.performance.status).toEqual("ERROR");
219
+ expect(body.performance.errorMessage).toEqual("API call failed");
226
220
  });
227
221
  });
228
222
  describe("debug mode", () => {
@@ -244,10 +238,10 @@ describe("FluxGate", () => {
244
238
  }),
245
239
  });
246
240
  const event = {
247
- usage: {
248
- inputTokens: 100,
249
- outputTokens: 50,
250
- },
241
+ provider: "openai",
242
+ model: "gpt-4o",
243
+ performance: { latency: 500, status: "SUCCESS", isStreamed: false },
244
+ usage: { promptTokens: 100, completionTokens: 50 },
251
245
  };
252
246
  await debugTracker.recordEvent(event);
253
247
  expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("[fluxgate] Sending event"), expect.any(String));
@@ -1,43 +1,89 @@
1
1
  export type AiEventStatus = "SUCCESS" | "ERROR" | "BLOCKED" | "MAX_TOKENS" | "CONTENT_FILTER" | "RECITATION" | "MALFORMED_REQUEST";
2
2
  export type TrackedUser = {
3
3
  id: string;
4
- name?: string;
5
- email?: string;
6
- image?: string;
7
- monthlyRevenue?: number;
4
+ name?: string | null;
5
+ email?: string | null;
6
+ image?: string | null;
7
+ /** Monthly revenue in USD as a string (e.g. "99.99") */
8
+ monthlyRevenue?: number | string | null;
9
+ };
10
+ export type Performance = {
11
+ /** Total round-trip time in milliseconds from request start to final response closure */
12
+ latency: number;
13
+ /** HTTP status category returned by the provider */
14
+ status: "SUCCESS" | "ERROR" | "BLOCKED" | "MAX_TOKENS" | "CONTENT_FILTER" | "RECITATION" | "MALFORMED_REQUEST";
15
+ /** Whether the response was processed via SSE streaming */
16
+ isStreamed: boolean;
17
+ /** Active streaming connection duration in ms (Total Duration - TTFT). Null if not streamed or request failed. */
18
+ streamDuration?: number | null;
19
+ /** Raw error string if the request failed. Null/undefined if successful. */
20
+ errorMessage?: string | null;
8
21
  };
9
22
  export type AiEventUsage = {
10
- inputTokens: number;
11
- outputTokens: number;
12
- cachedTokens?: number;
13
- model?: string;
14
- provider?: string;
15
- latencyInMs?: number;
16
- isStreamed?: boolean;
17
- streamingDurationInMs?: number;
23
+ /** Fresh text tokens evaluated down the wire (input/prompt tokens) */
24
+ promptTokens: number;
25
+ /** Generation tokens sent back from the inference machine (output tokens) */
26
+ completionTokens: number;
27
+ /** Tokens read from a warm cache layer (e.g. OpenAI/Anthropic prompt caching) */
28
+ cacheReadTokens?: number | null;
29
+ /** Tokens written to initialize or refresh a cold cache block */
30
+ cacheWriteTokens?: number | null;
31
+ /** Internal thinking/reasoning tokens (e.g. OpenAI o1/o3, DeepSeek R1) */
32
+ reasoningTokens?: number | null;
18
33
  };
19
34
  export type AiEventMetadata = {
35
+ /** Service tiering mode affecting pricing multipliers */
36
+ serviceTier?: "default" | "standard" | "batch" | "flex" | "priority";
37
+ /** Explicit hosting region for regional price variance (e.g. AWS Bedrock / Google Vertex) */
38
+ region?: string;
39
+ /** Explicit total cost in USD from an aggregator proxy (e.g. OpenRouter, LiteLLM). Skips server-side cost computation. */
40
+ openrouterCost?: number;
41
+ /** Provider cache expiration window. Accepts "5m", "1h", or a custom string. */
42
+ cacheTtl?: string;
43
+ [key: string]: unknown;
44
+ };
45
+ export type CostOverride = {
46
+ /** Price per 1,000,000 base prompt tokens */
47
+ inputCostPer1MTokens: number;
48
+ /** Price per 1,000,000 baseline generation output tokens */
49
+ outputCostPer1MTokens: number;
50
+ /** Surcharge rate for writing/saving a prompt segment to cache */
51
+ cacheWriteCostPer1MTokens?: number | null;
52
+ /** Discounted rate for reading from a warm prompt segment cache */
53
+ cacheReadCostPer1MTokens?: number | null;
54
+ /** Customized rate for isolated reasoning execution blocks */
55
+ reasoningCostPer1MTokens?: number | null;
56
+ };
57
+ export type LLMEvent = {
58
+ /** AI provider identifier (e.g. "openai", "anthropic", "google", "xai", "deepseek") */
59
+ provider: string;
60
+ /** Model identifier (e.g. "gpt-4o", "claude-opus-4", "gemini-2.0-flash") */
61
+ model: string;
62
+ /** End-user who triggered the event — either a plain string ID or a TrackedUser object */
63
+ user?: string | TrackedUser;
64
+ /** Product feature name (e.g. "chat", "summarization", "embedding") */
20
65
  feature?: string;
21
66
  step?: string;
22
- user?: string | TrackedUser;
23
67
  sessionId?: string;
24
68
  conversationId?: string;
25
- timestamp?: Date;
26
- [key: string]: unknown;
27
- };
28
- export type LLMEvent = {
69
+ /** Unix timestamp in milliseconds. Defaults to server ingest time if omitted. */
70
+ timestamp?: number;
71
+ performance: Performance;
29
72
  usage: AiEventUsage;
30
- status?: AiEventStatus | {
31
- status: AiEventStatus;
32
- errorMessage?: string;
33
- };
34
73
  metadata?: AiEventMetadata;
74
+ costOverride?: CostOverride;
35
75
  };
36
76
  export type CreateAiEventResponse = {
37
- id: string;
38
- createdAt: string;
39
- cost: number | null;
40
- status: "SUCCESS" | "ERROR";
77
+ /** ID of the persisted AiEvent record */
78
+ recordId: string;
79
+ /** Sum of all token categories */
80
+ totalTokens: number;
81
+ /** Computed total cost in USD. Null when no pricing data is available. */
82
+ totalCost: number | null;
83
+ /** ok — cost was successfully computed; no_pricing — model/provider not in pricing table */
84
+ status: "ok" | "no_pricing";
85
+ /** Human-readable explanation of how the cost was derived */
86
+ description?: string;
41
87
  };
42
88
  export interface FluxGateConfig {
43
89
  apiKey: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxgate/sdk",
3
- "version": "0.0.2-dev.0",
3
+ "version": "0.0.3-dev.0",
4
4
  "description": "Core tracking SDK for FluxGate token usage, latency, and cost monitoring.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",