@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 +1 -1
- package/dist/index.js +1 -10
- package/dist/index.test.js +65 -71
- package/dist/types/types.d.ts +71 -25
- package/package.json +1 -1
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,
|
|
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 {
|
package/dist/index.test.js
CHANGED
|
@@ -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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
recordId: "event-123",
|
|
49
|
+
totalTokens: 150,
|
|
50
|
+
totalCost: 0.001,
|
|
51
|
+
status: "ok",
|
|
51
52
|
};
|
|
52
53
|
vi.mocked(fetch).mockResolvedValue({
|
|
53
|
-
status:
|
|
54
|
-
statusText: "
|
|
54
|
+
status: 201,
|
|
55
|
+
statusText: "Created",
|
|
55
56
|
text: async () => JSON.stringify(mockResponse),
|
|
56
57
|
});
|
|
57
58
|
const event = {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
recordId: "123",
|
|
84
|
+
totalTokens: 150,
|
|
85
|
+
totalCost: 0.001,
|
|
86
|
+
status: "ok",
|
|
91
87
|
}),
|
|
92
88
|
});
|
|
93
89
|
const event = {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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.
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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(
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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));
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
26
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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