ai-speedometer-headless 2.1.6 → 2.1.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.
@@ -542,7 +542,7 @@ var getAllAvailableProviders = async (includeAllProviders = false) => {
542
542
  var TEST_PROMPT = `make a 300 word story`;
543
543
 
544
544
  // ../core/src/benchmark.ts
545
- async function benchmarkSingleModelRest(model) {
545
+ async function benchmarkSingleModelRest(model, logger) {
546
546
  try {
547
547
  if (!model.providerConfig || !model.providerConfig.apiKey) {
548
548
  throw new Error(`Missing API key for provider ${model.providerName}`);
@@ -559,6 +559,7 @@ async function benchmarkSingleModelRest(model) {
559
559
  actualModelId = model.name;
560
560
  }
561
561
  actualModelId = actualModelId.trim();
562
+ await logger?.logHeader(model.name, model.providerName, model.providerConfig.apiKey);
562
563
  const startTime = Date.now();
563
564
  let firstTokenTime = null;
564
565
  let streamedText = "";
@@ -631,21 +632,21 @@ async function benchmarkSingleModelRest(model) {
631
632
  const reader = response.body.getReader();
632
633
  const decoder = new TextDecoder;
633
634
  let buffer = "";
634
- let isFirstChunk = true;
635
+ let firstParsedTokenTime = null;
635
636
  while (true) {
636
637
  const { done, value } = await reader.read();
637
638
  if (done)
638
639
  break;
639
- if (isFirstChunk && !firstTokenTime) {
640
+ if (!firstTokenTime)
640
641
  firstTokenTime = Date.now();
641
- isFirstChunk = false;
642
- }
643
642
  buffer += decoder.decode(value, { stream: true });
644
643
  const lines = buffer.split(`
645
644
  `);
646
645
  buffer = lines.pop() || "";
647
646
  for (const line of lines) {
648
647
  const trimmedLine = line.trim();
648
+ if (trimmedLine)
649
+ await logger?.logRaw(trimmedLine);
649
650
  if (!trimmedLine)
650
651
  continue;
651
652
  try {
@@ -657,6 +658,8 @@ async function benchmarkSingleModelRest(model) {
657
658
  const chunk = JSON.parse(jsonStr);
658
659
  const chunkTyped = chunk;
659
660
  if (chunkTyped.type === "content_block_delta" && chunkTyped.delta?.text) {
661
+ if (!firstParsedTokenTime)
662
+ firstParsedTokenTime = Date.now();
660
663
  streamedText += chunkTyped.delta.text;
661
664
  } else if (chunkTyped.type === "message_start" && chunkTyped.message?.usage) {
662
665
  inputTokens = chunkTyped.message.usage.input_tokens || 0;
@@ -671,6 +674,8 @@ async function benchmarkSingleModelRest(model) {
671
674
  } else {
672
675
  const chunk = JSON.parse(trimmedLine);
673
676
  if (chunk.type === "content_block_delta" && chunk.delta?.text) {
677
+ if (!firstParsedTokenTime)
678
+ firstParsedTokenTime = Date.now();
674
679
  streamedText += chunk.delta.text;
675
680
  } else if (chunk.type === "message_start" && chunk.message?.usage) {
676
681
  inputTokens = chunk.message.usage.input_tokens || 0;
@@ -684,6 +689,8 @@ async function benchmarkSingleModelRest(model) {
684
689
  } else if (model.providerType === "google") {
685
690
  const chunk = JSON.parse(trimmedLine);
686
691
  if (chunk.candidates?.[0]?.content?.parts?.[0]?.text) {
692
+ if (!firstParsedTokenTime)
693
+ firstParsedTokenTime = Date.now();
687
694
  streamedText += chunk.candidates[0].content.parts[0].text;
688
695
  }
689
696
  if (chunk.usageMetadata?.promptTokenCount)
@@ -696,10 +703,15 @@ async function benchmarkSingleModelRest(model) {
696
703
  if (jsonStr === "[DONE]")
697
704
  continue;
698
705
  const chunk = JSON.parse(jsonStr);
699
- if (chunk.choices?.[0]?.delta?.content)
706
+ if (chunk.choices?.[0]?.delta?.content) {
707
+ if (!firstParsedTokenTime)
708
+ firstParsedTokenTime = Date.now();
700
709
  streamedText += chunk.choices[0].delta.content;
701
- else if (chunk.choices?.[0]?.delta?.reasoning)
710
+ } else if (chunk.choices?.[0]?.delta?.reasoning) {
711
+ if (!firstParsedTokenTime)
712
+ firstParsedTokenTime = Date.now();
702
713
  streamedText += chunk.choices[0].delta.reasoning;
714
+ }
703
715
  if (chunk.usage?.prompt_tokens)
704
716
  inputTokens = chunk.usage.prompt_tokens;
705
717
  if (chunk.usage?.completion_tokens)
@@ -711,15 +723,18 @@ async function benchmarkSingleModelRest(model) {
711
723
  }
712
724
  }
713
725
  }
726
+ await logger?.flush();
714
727
  const endTime = Date.now();
715
728
  const totalTime = endTime - startTime;
716
- const timeToFirstToken = firstTokenTime ? firstTokenTime - startTime : totalTime;
729
+ const effectiveFirstToken = firstParsedTokenTime ?? firstTokenTime;
730
+ const timeToFirstToken = effectiveFirstToken ? effectiveFirstToken - startTime : totalTime;
731
+ const generationTime = totalTime - timeToFirstToken;
717
732
  const usedEstimateForOutput = !outputTokens;
718
733
  const usedEstimateForInput = !inputTokens;
719
734
  const finalOutputTokens = outputTokens || Math.round(streamedText.length / 4);
720
735
  const finalInputTokens = inputTokens || Math.round(TEST_PROMPT.length / 4);
721
736
  const totalTokens = finalInputTokens + finalOutputTokens;
722
- const tokensPerSecond = totalTime > 0 ? finalOutputTokens / totalTime * 1000 : 0;
737
+ const tokensPerSecond = generationTime > 0 ? finalOutputTokens / generationTime * 1000 : 0;
723
738
  return {
724
739
  model: model.name,
725
740
  provider: model.providerName,
@@ -734,6 +749,7 @@ async function benchmarkSingleModelRest(model) {
734
749
  success: true
735
750
  };
736
751
  } catch (error) {
752
+ await logger?.flush();
737
753
  return {
738
754
  model: model.name,
739
755
  provider: model.providerName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-speedometer-headless",
3
- "version": "2.1.6",
3
+ "version": "2.1.7",
4
4
  "description": "Headless CLI for benchmarking AI models — runs on Node.js and Bun, no TUI dependencies",
5
5
  "bin": {
6
6
  "ai-speedometer-headless": "dist/ai-speedometer-headless"