@convex-dev/ai-budget 0.0.2-alpha.8 → 0.0.2-alpha.9
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/client/index.js +19 -10
- package/package.json +1 -1
- package/src/client/index.ts +21 -9
package/dist/client/index.js
CHANGED
|
@@ -51,20 +51,25 @@ function extractUsage(usage) {
|
|
|
51
51
|
usage?.cached_tokens),
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
-
// The
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
54
|
+
// The AI Gateway reports the authoritative dollar cost of each request.
|
|
55
|
+
// @convex-dev/ai-sdk-provider surfaces it at
|
|
56
|
+
// `providerMetadata.convexGateway.cost` (USD); convert to nanodollars and pass
|
|
57
|
+
// it through as authoritative (finishRequest prefers it over the token-based
|
|
58
|
+
// estimate). No-op on older provider versions that don't surface it.
|
|
59
59
|
function extractGatewayCostNanos(result) {
|
|
60
60
|
const meta = result?.providerMetadata?.convexGateway;
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
const costUsd = meta?.cost;
|
|
62
|
+
if (typeof costUsd === "number" && Number.isFinite(costUsd) && costUsd >= 0) {
|
|
63
|
+
return Math.round(costUsd * NANOS_PER_DOLLAR);
|
|
64
|
+
}
|
|
65
|
+
// Back-compat: honor an explicitly nano-denominated field if ever present.
|
|
66
|
+
const costNanos = meta?.costNanos ?? result?.usage?.costNanos;
|
|
67
|
+
if (typeof costNanos === "number" && Number.isFinite(costNanos) && costNanos >= 0) {
|
|
68
|
+
return Math.round(costNanos);
|
|
65
69
|
}
|
|
66
70
|
return undefined;
|
|
67
71
|
}
|
|
72
|
+
const NANOS_PER_DOLLAR = 1e9;
|
|
68
73
|
// Flatten an AI SDK prompt (roles + content parts) into simple storable messages.
|
|
69
74
|
function simplifyPrompt(prompt) {
|
|
70
75
|
if (!Array.isArray(prompt))
|
|
@@ -286,6 +291,7 @@ export class AIBudget {
|
|
|
286
291
|
const start = Date.now();
|
|
287
292
|
let text = "";
|
|
288
293
|
let usage = undefined;
|
|
294
|
+
let providerMetadata = undefined;
|
|
289
295
|
try {
|
|
290
296
|
const result = await doStream();
|
|
291
297
|
// finishRequest is idempotent (terminal-guarded server-side), so
|
|
@@ -302,6 +308,7 @@ export class AIBudget {
|
|
|
302
308
|
responseText: text,
|
|
303
309
|
error,
|
|
304
310
|
...extractUsage(usage),
|
|
311
|
+
costNanos: extractGatewayCostNanos({ providerMetadata }),
|
|
305
312
|
latencyMs: Date.now() - start,
|
|
306
313
|
});
|
|
307
314
|
};
|
|
@@ -310,8 +317,10 @@ export class AIBudget {
|
|
|
310
317
|
if (chunk?.type === "text-delta") {
|
|
311
318
|
text += chunk.delta ?? chunk.textDelta ?? "";
|
|
312
319
|
}
|
|
313
|
-
if (chunk?.type === "finish")
|
|
320
|
+
if (chunk?.type === "finish") {
|
|
314
321
|
usage = chunk.usage;
|
|
322
|
+
providerMetadata = chunk.providerMetadata ?? providerMetadata;
|
|
323
|
+
}
|
|
315
324
|
if (chunk?.type === "error")
|
|
316
325
|
void settle(String(chunk.error));
|
|
317
326
|
controller.enqueue(chunk);
|
package/package.json
CHANGED
package/src/client/index.ts
CHANGED
|
@@ -174,20 +174,27 @@ function extractUsage(usage: any): {
|
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
// The
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
177
|
+
// The AI Gateway reports the authoritative dollar cost of each request.
|
|
178
|
+
// @convex-dev/ai-sdk-provider surfaces it at
|
|
179
|
+
// `providerMetadata.convexGateway.cost` (USD); convert to nanodollars and pass
|
|
180
|
+
// it through as authoritative (finishRequest prefers it over the token-based
|
|
181
|
+
// estimate). No-op on older provider versions that don't surface it.
|
|
182
182
|
function extractGatewayCostNanos(result: any): number | undefined {
|
|
183
183
|
const meta = result?.providerMetadata?.convexGateway;
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
const costUsd = meta?.cost;
|
|
185
|
+
if (typeof costUsd === "number" && Number.isFinite(costUsd) && costUsd >= 0) {
|
|
186
|
+
return Math.round(costUsd * NANOS_PER_DOLLAR);
|
|
187
|
+
}
|
|
188
|
+
// Back-compat: honor an explicitly nano-denominated field if ever present.
|
|
189
|
+
const costNanos = meta?.costNanos ?? result?.usage?.costNanos;
|
|
190
|
+
if (typeof costNanos === "number" && Number.isFinite(costNanos) && costNanos >= 0) {
|
|
191
|
+
return Math.round(costNanos);
|
|
187
192
|
}
|
|
188
193
|
return undefined;
|
|
189
194
|
}
|
|
190
195
|
|
|
196
|
+
const NANOS_PER_DOLLAR = 1e9;
|
|
197
|
+
|
|
191
198
|
// Flatten an AI SDK prompt (roles + content parts) into simple storable messages.
|
|
192
199
|
function simplifyPrompt(prompt: any): Message[] {
|
|
193
200
|
if (!Array.isArray(prompt)) return [];
|
|
@@ -483,6 +490,7 @@ export class AIBudget {
|
|
|
483
490
|
const start = Date.now();
|
|
484
491
|
let text = "";
|
|
485
492
|
let usage: any = undefined;
|
|
493
|
+
let providerMetadata: any = undefined;
|
|
486
494
|
try {
|
|
487
495
|
const result = await doStream();
|
|
488
496
|
// finishRequest is idempotent (terminal-guarded server-side), so
|
|
@@ -498,6 +506,7 @@ export class AIBudget {
|
|
|
498
506
|
responseText: text,
|
|
499
507
|
error,
|
|
500
508
|
...extractUsage(usage),
|
|
509
|
+
costNanos: extractGatewayCostNanos({ providerMetadata }),
|
|
501
510
|
latencyMs: Date.now() - start,
|
|
502
511
|
});
|
|
503
512
|
};
|
|
@@ -507,7 +516,10 @@ export class AIBudget {
|
|
|
507
516
|
if (chunk?.type === "text-delta") {
|
|
508
517
|
text += chunk.delta ?? chunk.textDelta ?? "";
|
|
509
518
|
}
|
|
510
|
-
if (chunk?.type === "finish")
|
|
519
|
+
if (chunk?.type === "finish") {
|
|
520
|
+
usage = chunk.usage;
|
|
521
|
+
providerMetadata = chunk.providerMetadata ?? providerMetadata;
|
|
522
|
+
}
|
|
511
523
|
if (chunk?.type === "error") void settle(String(chunk.error));
|
|
512
524
|
controller.enqueue(chunk);
|
|
513
525
|
},
|