@scrawn/core 0.0.6 → 0.0.7
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/core/errors/index.d.ts +1 -1
- package/dist/core/errors/index.js +1 -1
- package/dist/core/pricing/index.d.ts +1 -1
- package/dist/core/pricing/index.js +1 -1
- package/dist/core/scrawn.d.ts +29 -13
- package/dist/core/scrawn.d.ts.map +1 -1
- package/dist/core/scrawn.js +156 -63
- package/dist/core/scrawn.js.map +1 -1
- package/dist/core/types/event.d.ts +39 -2
- package/dist/core/types/event.d.ts.map +1 -1
- package/dist/core/types/event.js +12 -0
- package/dist/core/types/event.js.map +1 -1
- package/dist/gen/event/v1/event_pb.d.ts +91 -27
- package/dist/gen/event/v1/event_pb.js +573 -117
- package/dist/gen/query/v1/query_pb.d.ts +40 -18
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @example
|
|
8
8
|
* ```typescript
|
|
9
9
|
* try {
|
|
10
|
-
* await scrawn.
|
|
10
|
+
* await scrawn.basicUsageEventConsumer({ ... });
|
|
11
11
|
* } catch (error) {
|
|
12
12
|
* if (error instanceof ScrawnAuthenticationError) {
|
|
13
13
|
* console.error('Auth failed:', error.message);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @example
|
|
8
8
|
* ```typescript
|
|
9
9
|
* try {
|
|
10
|
-
* await scrawn.
|
|
10
|
+
* await scrawn.basicUsageEventConsumer({ ... });
|
|
11
11
|
* } catch (error) {
|
|
12
12
|
* if (error instanceof ScrawnAuthenticationError) {
|
|
13
13
|
* console.error('Auth failed:', error.message);
|
package/dist/core/scrawn.d.ts
CHANGED
|
@@ -22,8 +22,8 @@ import type { StreamEventResponse } from "../gen/event/v1/event_pb.js";
|
|
|
22
22
|
* });
|
|
23
23
|
*
|
|
24
24
|
* // Tags are compile-time checked
|
|
25
|
-
* biller.
|
|
26
|
-
* // biller.
|
|
25
|
+
* biller.basicUsageEventConsumer({ userId: 'u123', debitTag: 'PREMIUM_FEATURE' });
|
|
26
|
+
* // biller.basicUsageEventConsumer({ userId: 'u123', debitTag: 'UNKNOWN' }); // Type error!
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
export declare class Scrawn<TTags extends string = string, TExprs extends string = string> {
|
|
@@ -38,10 +38,14 @@ export declare class Scrawn<TTags extends string = string, TExprs extends string
|
|
|
38
38
|
private apiKey;
|
|
39
39
|
/** gRPC client for making type-safe API calls */
|
|
40
40
|
private grpcClient;
|
|
41
|
+
/** Number of auto-retry attempts on retryable errors before falling back to onError */
|
|
42
|
+
private retryCount;
|
|
41
43
|
/** Public access to the gRPC client for use by other packages (e.g. @scrawn/analytics) */
|
|
42
44
|
get grpc(): GrpcClient;
|
|
43
45
|
/** API key used for authorizing gRPC calls */
|
|
44
46
|
get apikey(): string;
|
|
47
|
+
private sleep;
|
|
48
|
+
private backoffMs;
|
|
45
49
|
private notifyEventConsumerError;
|
|
46
50
|
private notifyValidationError;
|
|
47
51
|
/**
|
|
@@ -65,6 +69,7 @@ export declare class Scrawn<TTags extends string = string, TExprs extends string
|
|
|
65
69
|
baseURL: string;
|
|
66
70
|
secure?: boolean;
|
|
67
71
|
credentials?: import("@grpc/grpc-js").ChannelCredentials;
|
|
72
|
+
retryCount?: number;
|
|
68
73
|
});
|
|
69
74
|
private parseURLToTarget;
|
|
70
75
|
/**
|
|
@@ -99,13 +104,13 @@ export declare class Scrawn<TTags extends string = string, TExprs extends string
|
|
|
99
104
|
* @example
|
|
100
105
|
* ```typescript
|
|
101
106
|
* // Reference a persisted expression
|
|
102
|
-
* biller.
|
|
107
|
+
* biller.basicUsageEventConsumer({
|
|
103
108
|
* userId: 'u123',
|
|
104
109
|
* debitExpr: biller.expr("MY_EXPR"),
|
|
105
110
|
* });
|
|
106
111
|
*
|
|
107
112
|
* // Inline expression passthrough
|
|
108
|
-
* biller.
|
|
113
|
+
* biller.basicUsageEventConsumer({
|
|
109
114
|
* userId: 'u123',
|
|
110
115
|
* debitExpr: biller.expr(mul(biller.tag("PREMIUM_CALL"), 3)),
|
|
111
116
|
* });
|
|
@@ -147,17 +152,19 @@ export declare class Scrawn<TTags extends string = string, TExprs extends string
|
|
|
147
152
|
*/
|
|
148
153
|
private getCredsFor;
|
|
149
154
|
/**
|
|
150
|
-
* Track
|
|
155
|
+
* Track a basic usage event.
|
|
151
156
|
*
|
|
152
|
-
* Records
|
|
157
|
+
* Records basic usage to the Scrawn backend for billing tracking.
|
|
153
158
|
* The event is authenticated using the API key provided during SDK initialization.
|
|
154
159
|
*
|
|
155
|
-
* @param payload - The
|
|
160
|
+
* @param payload - The usage data to track
|
|
156
161
|
* @param payload.userId - Unique identifier of the user making the call
|
|
157
162
|
* @param payload.debitAmount - (Optional) Direct amount in cents to debit from the user's account
|
|
158
163
|
* @param payload.debitTag - (Optional) Named price tag for backend-managed pricing
|
|
159
164
|
* @param payload.debitExpr - (Optional) Pricing expression for complex calculations
|
|
165
|
+
* @param payload.metadata - (Optional) Arbitrary metadata to associate with the event
|
|
160
166
|
* @param options - Optional configuration
|
|
167
|
+
* @param options.eventId - (Optional) Override the auto-generated event ID
|
|
161
168
|
* @param options.onError - Optional callback for handling validation or gRPC errors
|
|
162
169
|
* @returns A promise that resolves when the event is tracked or returns early on error
|
|
163
170
|
*
|
|
@@ -166,25 +173,26 @@ export declare class Scrawn<TTags extends string = string, TExprs extends string
|
|
|
166
173
|
* import { add, mul, tag } from '@scrawn/core';
|
|
167
174
|
*
|
|
168
175
|
* // Using direct amount (500 cents = $5.00)
|
|
169
|
-
* await scrawn.
|
|
176
|
+
* await scrawn.basicUsageEventConsumer({
|
|
170
177
|
* userId: 'user_abc123',
|
|
171
178
|
* debitAmount: 500
|
|
172
179
|
* });
|
|
173
180
|
*
|
|
174
181
|
* // Using price tag
|
|
175
|
-
* await scrawn.
|
|
182
|
+
* await scrawn.basicUsageEventConsumer({
|
|
176
183
|
* userId: 'user_abc123',
|
|
177
184
|
* debitTag: 'PREMIUM_FEATURE'
|
|
178
185
|
* });
|
|
179
186
|
*
|
|
180
187
|
* // Using pricing expression: (PREMIUM_CALL * 3) + EXTRA_FEE + 250 cents
|
|
181
|
-
* await scrawn.
|
|
188
|
+
* await scrawn.basicUsageEventConsumer({
|
|
182
189
|
* userId: 'user_abc123',
|
|
183
190
|
* debitExpr: add(mul(tag('PREMIUM_CALL'), 3), tag('EXTRA_FEE'), 250)
|
|
184
191
|
* });
|
|
185
192
|
* ```
|
|
186
193
|
*/
|
|
187
|
-
|
|
194
|
+
basicUsageEventConsumer(payload: EventPayload<TTags>, options?: {
|
|
195
|
+
eventId?: string;
|
|
188
196
|
onError?: EventConsumerErrorCallback;
|
|
189
197
|
}): Promise<void>;
|
|
190
198
|
/**
|
|
@@ -271,6 +279,8 @@ export declare class Scrawn<TTags extends string = string, TExprs extends string
|
|
|
271
279
|
* @param payload - Event payload data
|
|
272
280
|
* @param authMethodName - Name of the auth method to use (must be in AuthRegistry)
|
|
273
281
|
* @param eventType - Type of event for categorization (RAW or MIDDLEWARE_CALL)
|
|
282
|
+
* @param eventId - Stable event ID (generated by caller, reused across retries)
|
|
283
|
+
* @param idempotencyKey - Stable idempotency key (generated by caller, reused across retries)
|
|
274
284
|
* @returns A promise that resolves when the event is processed
|
|
275
285
|
* @throws Error if auth method is not registered or gRPC call fails
|
|
276
286
|
*
|
|
@@ -361,6 +371,12 @@ export interface ScrawnInitConfig {
|
|
|
361
371
|
credentials?: import("@grpc/grpc-js").ChannelCredentials;
|
|
362
372
|
tags?: readonly string[];
|
|
363
373
|
expressions?: readonly string[];
|
|
374
|
+
/**
|
|
375
|
+
* Number of automatic retry attempts on transient network errors
|
|
376
|
+
* (UNAVAILABLE, DEADLINE_EXCEEDED). Defaults to 2. Set to 0 to disable.
|
|
377
|
+
* Each event also gets a manual `.retry()` context in the onError callback.
|
|
378
|
+
*/
|
|
379
|
+
retryCount?: number;
|
|
364
380
|
}
|
|
365
381
|
/**
|
|
366
382
|
* Create a type-safe Scrawn billing instance.
|
|
@@ -380,11 +396,11 @@ export interface ScrawnInitConfig {
|
|
|
380
396
|
* expressions: ["MY_EXPR"] as const,
|
|
381
397
|
* });
|
|
382
398
|
*
|
|
383
|
-
* biller.
|
|
399
|
+
* biller.basicUsageEventConsumer({
|
|
384
400
|
* userId: 'u123',
|
|
385
401
|
* debitExpr: biller.expr("MY_EXPR"), // persisted expression
|
|
386
402
|
* });
|
|
387
|
-
* biller.
|
|
403
|
+
* biller.basicUsageEventConsumer({
|
|
388
404
|
* userId: 'u123',
|
|
389
405
|
* debitExpr: mul(biller.tag("PREMIUM_CALL"), 3), // inline
|
|
390
406
|
* });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrawn.d.ts","sourceRoot":"","sources":["../../src/core/scrawn.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,
|
|
1
|
+
{"version":3,"file":"scrawn.d.ts","sourceRoot":"","sources":["../../src/core/scrawn.ts"],"names":[],"mappings":"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"}
|
package/dist/core/scrawn.js
CHANGED
|
@@ -5,12 +5,13 @@ import { forkAsyncIterable } from "../utils/forkAsyncIterable.js";
|
|
|
5
5
|
import { EventPayloadSchema, AITokenUsagePayloadSchema, } from "./types/event.js";
|
|
6
6
|
import { GrpcClient } from "./grpc/index.js";
|
|
7
7
|
import { EventServiceClient } from "../gen/event/v1/event_grpc_pb.js";
|
|
8
|
-
import { RegisterEventRequest, StreamEventRequest, EventType,
|
|
8
|
+
import { RegisterEventRequest, StreamEventRequest, EventType, BasicUsageType, BasicUsage, AITokenUsage, } from "../gen/event/v1/event_pb.js";
|
|
9
9
|
import { PaymentServiceClient } from "../gen/payment/v1/payment_grpc_pb.js";
|
|
10
10
|
import { CreateCheckoutLinkRequest, } from "../gen/payment/v1/payment_pb.js";
|
|
11
|
-
import { ScrawnConfigError, ScrawnValidationError, convertGrpcError, isScrawnError, } from "./errors/index.js";
|
|
11
|
+
import { ScrawnConfigError, ScrawnValidationError, convertGrpcError, isScrawnError, isRetryableError, } from "./errors/index.js";
|
|
12
12
|
import { serializeExpr, resolveTokens, prettyPrintExpr, tag as _tag } from "./pricing/index.js";
|
|
13
13
|
import { ScrawnConfig } from "../config.js";
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
14
15
|
const log = new ScrawnLogger("Scrawn");
|
|
15
16
|
/**
|
|
16
17
|
* Main SDK class for Scrawn billing infrastructure.
|
|
@@ -31,8 +32,8 @@ const log = new ScrawnLogger("Scrawn");
|
|
|
31
32
|
* });
|
|
32
33
|
*
|
|
33
34
|
* // Tags are compile-time checked
|
|
34
|
-
* biller.
|
|
35
|
-
* // biller.
|
|
35
|
+
* biller.basicUsageEventConsumer({ userId: 'u123', debitTag: 'PREMIUM_FEATURE' });
|
|
36
|
+
* // biller.basicUsageEventConsumer({ userId: 'u123', debitTag: 'UNKNOWN' }); // Type error!
|
|
36
37
|
* ```
|
|
37
38
|
*/
|
|
38
39
|
export class Scrawn {
|
|
@@ -44,6 +45,12 @@ export class Scrawn {
|
|
|
44
45
|
get apikey() {
|
|
45
46
|
return this.apiKey;
|
|
46
47
|
}
|
|
48
|
+
sleep(ms) {
|
|
49
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
50
|
+
}
|
|
51
|
+
backoffMs(attempt) {
|
|
52
|
+
return Math.min(1000 * Math.pow(2, attempt), 8000);
|
|
53
|
+
}
|
|
47
54
|
notifyEventConsumerError(error, onError) {
|
|
48
55
|
const converted = isScrawnError(error) ? error : convertGrpcError(error);
|
|
49
56
|
onError?.(converted);
|
|
@@ -90,6 +97,7 @@ export class Scrawn {
|
|
|
90
97
|
});
|
|
91
98
|
}
|
|
92
99
|
this.apiKey = config.apiKey;
|
|
100
|
+
this.retryCount = config.retryCount ?? 2;
|
|
93
101
|
this.grpcClient = new GrpcClient(this.parseURLToTarget(config.baseURL), { secure: config.secure ?? true, credentials: config.credentials });
|
|
94
102
|
this.registerAuthMethod("api", new ApiKeyAuth(this.apiKey));
|
|
95
103
|
}
|
|
@@ -183,17 +191,19 @@ export class Scrawn {
|
|
|
183
191
|
return creds;
|
|
184
192
|
}
|
|
185
193
|
/**
|
|
186
|
-
* Track
|
|
194
|
+
* Track a basic usage event.
|
|
187
195
|
*
|
|
188
|
-
* Records
|
|
196
|
+
* Records basic usage to the Scrawn backend for billing tracking.
|
|
189
197
|
* The event is authenticated using the API key provided during SDK initialization.
|
|
190
198
|
*
|
|
191
|
-
* @param payload - The
|
|
199
|
+
* @param payload - The usage data to track
|
|
192
200
|
* @param payload.userId - Unique identifier of the user making the call
|
|
193
201
|
* @param payload.debitAmount - (Optional) Direct amount in cents to debit from the user's account
|
|
194
202
|
* @param payload.debitTag - (Optional) Named price tag for backend-managed pricing
|
|
195
203
|
* @param payload.debitExpr - (Optional) Pricing expression for complex calculations
|
|
204
|
+
* @param payload.metadata - (Optional) Arbitrary metadata to associate with the event
|
|
196
205
|
* @param options - Optional configuration
|
|
206
|
+
* @param options.eventId - (Optional) Override the auto-generated event ID
|
|
197
207
|
* @param options.onError - Optional callback for handling validation or gRPC errors
|
|
198
208
|
* @returns A promise that resolves when the event is tracked or returns early on error
|
|
199
209
|
*
|
|
@@ -202,37 +212,38 @@ export class Scrawn {
|
|
|
202
212
|
* import { add, mul, tag } from '@scrawn/core';
|
|
203
213
|
*
|
|
204
214
|
* // Using direct amount (500 cents = $5.00)
|
|
205
|
-
* await scrawn.
|
|
215
|
+
* await scrawn.basicUsageEventConsumer({
|
|
206
216
|
* userId: 'user_abc123',
|
|
207
217
|
* debitAmount: 500
|
|
208
218
|
* });
|
|
209
219
|
*
|
|
210
220
|
* // Using price tag
|
|
211
|
-
* await scrawn.
|
|
221
|
+
* await scrawn.basicUsageEventConsumer({
|
|
212
222
|
* userId: 'user_abc123',
|
|
213
223
|
* debitTag: 'PREMIUM_FEATURE'
|
|
214
224
|
* });
|
|
215
225
|
*
|
|
216
226
|
* // Using pricing expression: (PREMIUM_CALL * 3) + EXTRA_FEE + 250 cents
|
|
217
|
-
* await scrawn.
|
|
227
|
+
* await scrawn.basicUsageEventConsumer({
|
|
218
228
|
* userId: 'user_abc123',
|
|
219
229
|
* debitExpr: add(mul(tag('PREMIUM_CALL'), 3), tag('EXTRA_FEE'), 250)
|
|
220
230
|
* });
|
|
221
231
|
* ```
|
|
222
232
|
*/
|
|
223
|
-
async
|
|
233
|
+
async basicUsageEventConsumer(payload, options) {
|
|
224
234
|
const rawPayload = {
|
|
225
235
|
userId: payload.userId,
|
|
226
236
|
debitAmount: payload.debitAmount,
|
|
227
237
|
debitTag: payload.debitTag,
|
|
228
238
|
debitExpr: payload.debitExpr?._expr,
|
|
239
|
+
metadata: payload.metadata,
|
|
229
240
|
};
|
|
230
241
|
const validationResult = EventPayloadSchema.safeParse(rawPayload);
|
|
231
242
|
if (!validationResult.success) {
|
|
232
243
|
const errors = validationResult.error.issues
|
|
233
244
|
.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
234
245
|
.join(", ");
|
|
235
|
-
log.error(`Invalid payload for
|
|
246
|
+
log.error(`Invalid payload for basicUsageEventConsumer: ${errors}`);
|
|
236
247
|
const error = new ScrawnValidationError("Payload validation failed", {
|
|
237
248
|
details: {
|
|
238
249
|
errors: validationResult.error.issues.map((e) => ({
|
|
@@ -244,12 +255,34 @@ export class Scrawn {
|
|
|
244
255
|
this.notifyValidationError(error, options?.onError);
|
|
245
256
|
return;
|
|
246
257
|
}
|
|
258
|
+
// Fixed identity for this event — survives retries
|
|
259
|
+
const eventId = options?.eventId ?? randomUUID();
|
|
260
|
+
const idempotencyKey = randomUUID();
|
|
261
|
+
const attempt = () => this.consumeEvent(validationResult.data, "api", "RAW", eventId, idempotencyKey);
|
|
247
262
|
try {
|
|
248
|
-
await
|
|
263
|
+
await attempt();
|
|
249
264
|
}
|
|
250
265
|
catch (error) {
|
|
251
|
-
log.error(`Failed to track
|
|
252
|
-
|
|
266
|
+
log.error(`Failed to track basicUsageEventConsumer event: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
267
|
+
if (options?.onError) {
|
|
268
|
+
const converted = isScrawnError(error)
|
|
269
|
+
? error
|
|
270
|
+
: convertGrpcError(error);
|
|
271
|
+
const retryContext = {
|
|
272
|
+
retry: async () => {
|
|
273
|
+
try {
|
|
274
|
+
await attempt();
|
|
275
|
+
}
|
|
276
|
+
catch (retryError) {
|
|
277
|
+
const convertedRetry = isScrawnError(retryError)
|
|
278
|
+
? retryError
|
|
279
|
+
: convertGrpcError(retryError);
|
|
280
|
+
options.onError(convertedRetry, retryContext);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
options.onError(converted, retryContext);
|
|
285
|
+
}
|
|
253
286
|
return;
|
|
254
287
|
}
|
|
255
288
|
}
|
|
@@ -335,6 +368,7 @@ export class Scrawn {
|
|
|
335
368
|
debitAmount: extractedPayload.debitAmount,
|
|
336
369
|
debitTag: extractedPayload.debitTag,
|
|
337
370
|
debitExpr: extractedPayload.debitExpr?._expr,
|
|
371
|
+
metadata: extractedPayload.metadata,
|
|
338
372
|
};
|
|
339
373
|
const validationResult = EventPayloadSchema.safeParse(rawPayload);
|
|
340
374
|
if (!validationResult.success) {
|
|
@@ -353,7 +387,9 @@ export class Scrawn {
|
|
|
353
387
|
this.notifyValidationError(error, config.onError);
|
|
354
388
|
return next();
|
|
355
389
|
}
|
|
356
|
-
|
|
390
|
+
const eventId = randomUUID();
|
|
391
|
+
const idempotencyKey = randomUUID();
|
|
392
|
+
this.consumeEvent(validationResult.data, "api", "MIDDLEWARE_CALL", eventId, idempotencyKey).catch((error) => {
|
|
357
393
|
log.error(`Failed to track middleware event: ${error.message}`);
|
|
358
394
|
this.notifyEventConsumerError(error, config.onError);
|
|
359
395
|
});
|
|
@@ -422,12 +458,14 @@ export class Scrawn {
|
|
|
422
458
|
* @param payload - Event payload data
|
|
423
459
|
* @param authMethodName - Name of the auth method to use (must be in AuthRegistry)
|
|
424
460
|
* @param eventType - Type of event for categorization (RAW or MIDDLEWARE_CALL)
|
|
461
|
+
* @param eventId - Stable event ID (generated by caller, reused across retries)
|
|
462
|
+
* @param idempotencyKey - Stable idempotency key (generated by caller, reused across retries)
|
|
425
463
|
* @returns A promise that resolves when the event is processed
|
|
426
464
|
* @throws Error if auth method is not registered or gRPC call fails
|
|
427
465
|
*
|
|
428
466
|
* @internal
|
|
429
467
|
*/
|
|
430
|
-
async consumeEvent(payload, authMethodName, eventType) {
|
|
468
|
+
async consumeEvent(payload, authMethodName, eventType, eventId, idempotencyKey) {
|
|
431
469
|
const auth = this.authMethods.get(authMethodName);
|
|
432
470
|
if (!auth) {
|
|
433
471
|
throw new ScrawnConfigError(`No auth registered for type ${authMethodName}`, {
|
|
@@ -439,52 +477,67 @@ export class Scrawn {
|
|
|
439
477
|
await auth.preRun();
|
|
440
478
|
// Get creds (from cache or fresh)
|
|
441
479
|
const creds = await this.getCredsFor(authMethodName);
|
|
442
|
-
// Map event type to
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
480
|
+
// Map event type to BasicUsageType
|
|
481
|
+
const basicUsageType = eventType === "RAW" ? BasicUsageType.RAW : BasicUsageType.MIDDLEWARE_CALL;
|
|
482
|
+
// Build debit field based on which debit option is provided
|
|
483
|
+
let debitField;
|
|
484
|
+
if (payload.debitAmount !== undefined) {
|
|
485
|
+
debitField = { case: "amount", value: payload.debitAmount };
|
|
486
|
+
}
|
|
487
|
+
else if (payload.debitTag !== undefined) {
|
|
488
|
+
debitField = { case: "tag", value: payload.debitTag };
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
const serialized = serializeExpr(payload.debitExpr);
|
|
492
|
+
log.debug(`Serialized pricing expression: ${serialized}\n${prettyPrintExpr(payload.debitExpr)}`);
|
|
493
|
+
debitField = {
|
|
494
|
+
case: "expr",
|
|
495
|
+
value: serialized,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
// Retry loop for retryable failures
|
|
499
|
+
for (let attempt = 0;; attempt++) {
|
|
500
|
+
try {
|
|
501
|
+
log.info(`Ingesting event (type: ${eventType}) — attempt ${attempt + 1}`);
|
|
502
|
+
const basicUsage = new BasicUsage();
|
|
503
|
+
basicUsage.setBasicusagetype(basicUsageType);
|
|
504
|
+
if (debitField.case === "amount") {
|
|
505
|
+
basicUsage.setAmount(debitField.value);
|
|
506
|
+
}
|
|
507
|
+
else if (debitField.case === "tag") {
|
|
508
|
+
basicUsage.setTag(debitField.value);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
basicUsage.setExpr(debitField.value);
|
|
512
|
+
}
|
|
513
|
+
if (payload.metadata) {
|
|
514
|
+
basicUsage.setMetadata(JSON.stringify(payload.metadata));
|
|
515
|
+
}
|
|
516
|
+
const request = new RegisterEventRequest();
|
|
517
|
+
request.setType(EventType.BASIC_USAGE);
|
|
518
|
+
request.setUserid(payload.userId);
|
|
519
|
+
request.setEventid(eventId);
|
|
520
|
+
request.setIdempotencykey(idempotencyKey);
|
|
521
|
+
request.setBasicusage(basicUsage);
|
|
522
|
+
const response = await this.grpcClient
|
|
523
|
+
.newCall(EventServiceClient, "registerEvent")
|
|
524
|
+
.addMetadata("authorization", `Bearer ${creds.apiKey}`)
|
|
525
|
+
.addPayload(request)
|
|
526
|
+
.request();
|
|
527
|
+
log.info(`Event registered successfully: ${JSON.stringify(response)}`);
|
|
528
|
+
break;
|
|
470
529
|
}
|
|
471
|
-
|
|
472
|
-
|
|
530
|
+
catch (error) {
|
|
531
|
+
const converted = convertGrpcError(error);
|
|
532
|
+
if (attempt < this.retryCount && isRetryableError(converted)) {
|
|
533
|
+
const delay = this.backoffMs(attempt);
|
|
534
|
+
log.warn(`Retryable error on attempt ${attempt + 1}, retrying in ${delay}ms: ${converted.message}`);
|
|
535
|
+
await this.sleep(delay);
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
log.error(`Failed to register event: ${converted.message}`);
|
|
539
|
+
throw converted;
|
|
473
540
|
}
|
|
474
|
-
const request = new RegisterEventRequest();
|
|
475
|
-
request.setType(EventType.SDK_CALL);
|
|
476
|
-
request.setUserid(payload.userId);
|
|
477
|
-
request.setSdkcall(sdkCall);
|
|
478
|
-
const response = await this.grpcClient
|
|
479
|
-
.newCall(EventServiceClient, "registerEvent")
|
|
480
|
-
.addMetadata("authorization", `Bearer ${creds.apiKey}`)
|
|
481
|
-
.addPayload(request)
|
|
482
|
-
.request();
|
|
483
|
-
log.info(`Event registered successfully: ${JSON.stringify(response)}`);
|
|
484
|
-
}
|
|
485
|
-
catch (error) {
|
|
486
|
-
log.error(`Failed to register event: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
487
|
-
throw convertGrpcError(error);
|
|
488
541
|
}
|
|
489
542
|
if (auth.postRun)
|
|
490
543
|
await auth.postRun();
|
|
@@ -520,8 +573,8 @@ export class Scrawn {
|
|
|
520
573
|
* model: 'gpt-4',
|
|
521
574
|
* inputTokens: 100,
|
|
522
575
|
* outputTokens: 50,
|
|
523
|
-
* inputDebit: { amount:
|
|
524
|
-
* outputDebit: { amount:
|
|
576
|
+
* inputDebit: { amount: 1 },
|
|
577
|
+
* outputDebit: { amount: 1 }
|
|
525
578
|
* };
|
|
526
579
|
* }
|
|
527
580
|
*
|
|
@@ -617,6 +670,16 @@ export class Scrawn {
|
|
|
617
670
|
tag: payload.outputDebit.tag,
|
|
618
671
|
expr: payload.outputDebit.expr?._expr,
|
|
619
672
|
},
|
|
673
|
+
metadata: payload.metadata,
|
|
674
|
+
provider: payload.provider,
|
|
675
|
+
inputCacheTokens: payload.inputCacheTokens,
|
|
676
|
+
inputCacheDebit: payload.inputCacheDebit
|
|
677
|
+
? {
|
|
678
|
+
amount: payload.inputCacheDebit.amount,
|
|
679
|
+
tag: payload.inputCacheDebit.tag,
|
|
680
|
+
expr: payload.inputCacheDebit.expr?._expr,
|
|
681
|
+
}
|
|
682
|
+
: undefined,
|
|
620
683
|
};
|
|
621
684
|
// Validate each payload
|
|
622
685
|
const validationResult = AITokenUsagePayloadSchema.safeParse(rawPayload);
|
|
@@ -710,9 +773,38 @@ export class Scrawn {
|
|
|
710
773
|
else {
|
|
711
774
|
aiTokenUsage.setOutputexpr(outputDebit.value);
|
|
712
775
|
}
|
|
776
|
+
// Set metadata on AITokenUsage if provided
|
|
777
|
+
if (validated.metadata) {
|
|
778
|
+
aiTokenUsage.setMetadata(JSON.stringify(validated.metadata));
|
|
779
|
+
}
|
|
780
|
+
// Set provider if provided
|
|
781
|
+
if (validated.provider) {
|
|
782
|
+
aiTokenUsage.setProvider(validated.provider);
|
|
783
|
+
}
|
|
784
|
+
// Set input cache tokens if provided
|
|
785
|
+
if (validated.inputCacheTokens !== undefined) {
|
|
786
|
+
aiTokenUsage.setInputcachetokens(validated.inputCacheTokens);
|
|
787
|
+
}
|
|
788
|
+
// Set input cache debit if provided
|
|
789
|
+
if (validated.inputCacheDebit) {
|
|
790
|
+
if (validated.inputCacheDebit.amount !== undefined) {
|
|
791
|
+
aiTokenUsage.setInputcacheamount(validated.inputCacheDebit.amount);
|
|
792
|
+
}
|
|
793
|
+
else if (validated.inputCacheDebit.tag !== undefined) {
|
|
794
|
+
aiTokenUsage.setInputcachetag(validated.inputCacheDebit.tag);
|
|
795
|
+
}
|
|
796
|
+
else if (validated.inputCacheDebit.expr) {
|
|
797
|
+
const resolved = resolveTokens(validated.inputCacheDebit.expr, tokenContext);
|
|
798
|
+
aiTokenUsage.setInputcacheexpr(serializeExpr(resolved));
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const eventId = randomUUID();
|
|
802
|
+
const idempotencyKey = randomUUID();
|
|
713
803
|
const request = new StreamEventRequest();
|
|
714
804
|
request.setType(EventType.AI_TOKEN_USAGE);
|
|
715
805
|
request.setUserid(validated.userId);
|
|
806
|
+
request.setEventid(eventId);
|
|
807
|
+
request.setIdempotencykey(idempotencyKey);
|
|
716
808
|
request.setAitokenusage(aiTokenUsage);
|
|
717
809
|
yield request;
|
|
718
810
|
}
|
|
@@ -724,6 +816,7 @@ export function createScrawn(config) {
|
|
|
724
816
|
baseURL: config.baseURL,
|
|
725
817
|
secure: config.secure,
|
|
726
818
|
credentials: config.credentials,
|
|
819
|
+
retryCount: config.retryCount,
|
|
727
820
|
});
|
|
728
821
|
}
|
|
729
822
|
//# sourceMappingURL=scrawn.js.map
|