elasticdash-test 0.1.18-alpha-24 → 0.1.18-alpha-26
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/package.json
CHANGED
|
@@ -364,6 +364,35 @@ async function bufferSSEStream(
|
|
|
364
364
|
return completion
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
/** Extract usage from buffered raw SSE text */
|
|
368
|
+
function extractStreamUsage(provider: string, rawSSE: string): UsageInfo | undefined {
|
|
369
|
+
const lines = rawSSE.split('\n')
|
|
370
|
+
// Walk backwards to find usage in the final events
|
|
371
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
372
|
+
const line = lines[i]
|
|
373
|
+
if (!line.startsWith('data: ')) continue
|
|
374
|
+
const data = line.slice(6).trim()
|
|
375
|
+
if (data === '[DONE]') continue
|
|
376
|
+
try {
|
|
377
|
+
const obj = JSON.parse(data) as Record<string, unknown>
|
|
378
|
+
// OpenAI / Grok / Kimi: usage in the final chunk
|
|
379
|
+
const usage = extractUsage(provider, obj)
|
|
380
|
+
if (usage) return usage
|
|
381
|
+
// Anthropic: usage in message_delta event
|
|
382
|
+
if (obj.type === 'message_delta') {
|
|
383
|
+
const u = (obj as Record<string, unknown>).usage as Record<string, number> | undefined
|
|
384
|
+
if (u) return extractUsage('anthropic', { usage: u })
|
|
385
|
+
}
|
|
386
|
+
// Anthropic: usage in message_start event
|
|
387
|
+
if (obj.type === 'message_start') {
|
|
388
|
+
const msg = (obj as Record<string, unknown>).message as Record<string, unknown> | undefined
|
|
389
|
+
if (msg?.usage) return extractUsage('anthropic', { usage: msg.usage })
|
|
390
|
+
}
|
|
391
|
+
} catch { /* skip */ }
|
|
392
|
+
}
|
|
393
|
+
return undefined
|
|
394
|
+
}
|
|
395
|
+
|
|
367
396
|
/** Build a minimal non-streaming JSON response body from a completion string (for replay) */
|
|
368
397
|
function synthesizeCompletionJSON(
|
|
369
398
|
provider: string,
|
|
@@ -474,12 +503,30 @@ export function installAIInterceptor(): void {
|
|
|
474
503
|
url, provider, model: capturedModel, messages: capturedMessages,
|
|
475
504
|
body: capturedReq, promptSnippet: capturedSnippet,
|
|
476
505
|
}
|
|
506
|
+
const isStreaming = capturedReq.stream === true
|
|
477
507
|
try {
|
|
478
508
|
const cloned = response.clone()
|
|
479
|
-
|
|
480
|
-
|
|
509
|
+
if (!isStreaming) {
|
|
510
|
+
// Non-streaming: parse JSON response for usage
|
|
511
|
+
const responseBody = await cloned.json() as Record<string, unknown>
|
|
512
|
+
captured.usage = extractUsage(provider, responseBody)
|
|
513
|
+
} else if (cloned.body) {
|
|
514
|
+
// Streaming: read the raw SSE text to extract usage from final events
|
|
515
|
+
try {
|
|
516
|
+
const decoder = new TextDecoder()
|
|
517
|
+
const reader = cloned.body.getReader()
|
|
518
|
+
let rawSSE = ''
|
|
519
|
+
for (;;) {
|
|
520
|
+
const { done, value } = await reader.read()
|
|
521
|
+
if (done) break
|
|
522
|
+
rawSSE += decoder.decode(value, { stream: true })
|
|
523
|
+
}
|
|
524
|
+
reader.releaseLock()
|
|
525
|
+
captured.usage = extractStreamUsage(provider, rawSSE)
|
|
526
|
+
} catch { /* stream read failed */ }
|
|
527
|
+
}
|
|
481
528
|
} catch {
|
|
482
|
-
//
|
|
529
|
+
// Response body not available — usage won't be captured
|
|
483
530
|
}
|
|
484
531
|
;(globalThis as Record<string, unknown>)[LLM_REQUEST_KEY] = captured
|
|
485
532
|
}
|
package/src/interceptors/http.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { rawDateNow } from './side-effects.js'
|
|
|
7
7
|
// Skip them here to avoid duplicate observations.
|
|
8
8
|
const AI_URL_PATTERNS = [
|
|
9
9
|
/https?:\/\/api\.openai\.com\/v1\/((chat\/)?completions|embeddings)/,
|
|
10
|
+
/https?:\/\/api\.anthropic\.com\/v1\/messages/,
|
|
10
11
|
/https?:\/\/generativelanguage\.googleapis\.com\/.*\/models\/[^/:]+:(generateContent|streamGenerateContent)/,
|
|
11
12
|
/https?:\/\/api\.x\.ai\/v1\/(chat\/)?completions/,
|
|
12
13
|
/https?:\/\/api\.moonshot\.ai\/v1\/(chat\/)?completions/,
|
|
@@ -45,6 +45,14 @@ function extractUsage(output: unknown): UsageInfo | undefined {
|
|
|
45
45
|
totalTokens: u.total_tokens,
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
+
// Vercel AI SDK: { inputTokens, outputTokens }
|
|
49
|
+
if (u.inputTokens != null || u.outputTokens != null) {
|
|
50
|
+
return {
|
|
51
|
+
inputTokens: u.inputTokens,
|
|
52
|
+
outputTokens: u.outputTokens,
|
|
53
|
+
totalTokens: (u.inputTokens ?? 0) + (u.outputTokens ?? 0),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
48
56
|
}
|
|
49
57
|
// Gemini SDK: usageMetadata
|
|
50
58
|
if (o.usageMetadata && typeof o.usageMetadata === 'object') {
|
|
@@ -55,6 +63,14 @@ function extractUsage(output: unknown): UsageInfo | undefined {
|
|
|
55
63
|
totalTokens: u.totalTokenCount,
|
|
56
64
|
}
|
|
57
65
|
}
|
|
66
|
+
// Flat token fields (e.g. { tokens: N } or { outputTokens: N, inputTokens: N })
|
|
67
|
+
if (typeof o.tokens === 'number' || typeof o.outputTokens === 'number') {
|
|
68
|
+
return {
|
|
69
|
+
inputTokens: typeof o.inputTokens === 'number' ? o.inputTokens : undefined,
|
|
70
|
+
outputTokens: typeof o.outputTokens === 'number' ? o.outputTokens : (typeof o.tokens === 'number' ? o.tokens : undefined),
|
|
71
|
+
totalTokens: (typeof o.inputTokens === 'number' ? o.inputTokens : 0) + (typeof o.outputTokens === 'number' ? o.outputTokens : (typeof o.tokens === 'number' ? o.tokens : 0)),
|
|
72
|
+
}
|
|
73
|
+
}
|
|
58
74
|
return undefined
|
|
59
75
|
}
|
|
60
76
|
|