llm-usage-metrics 0.1.9 → 0.1.10

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.js CHANGED
@@ -2766,7 +2766,6 @@ function renderReportHeader(options) {
2766
2766
 
2767
2767
  // src/render/terminal-table.ts
2768
2768
  import pc4 from "picocolors";
2769
- import { table } from "table";
2770
2769
 
2771
2770
  // src/render/terminal-style-policy.ts
2772
2771
  import pc3 from "picocolors";
@@ -2781,6 +2780,9 @@ var defaultTerminalStylePalette = {
2781
2780
  dim: pc3.dim
2782
2781
  };
2783
2782
  var passthroughStyler = (text) => text;
2783
+ function styleCellLines(cell, styler) {
2784
+ return cell.split("\n").map((line) => line.length === 0 ? "" : styler(line)).join("\n");
2785
+ }
2784
2786
  var sourceStylePolicies = /* @__PURE__ */ new Map([
2785
2787
  ["pi", (palette) => palette.cyan],
2786
2788
  ["codex", (palette) => palette.magenta],
@@ -2797,23 +2799,23 @@ var rowTypeStylePolicies = {
2797
2799
  period_source: (cells, palette) => {
2798
2800
  const styledCells = [...cells];
2799
2801
  const costColumnIndex = styledCells.length - 1;
2800
- styledCells[costColumnIndex] = palette.yellow(styledCells[costColumnIndex]);
2802
+ styledCells[costColumnIndex] = styleCellLines(styledCells[costColumnIndex], palette.yellow);
2801
2803
  return styledCells;
2802
2804
  },
2803
2805
  period_combined: (cells, palette) => cells.map((cell, cellIndex) => {
2804
2806
  if (cellIndex === 1) {
2805
- return palette.bold(palette.yellow(cell));
2807
+ return styleCellLines(cell, (line) => palette.bold(palette.yellow(line)));
2806
2808
  }
2807
- return palette.dim(cell);
2809
+ return styleCellLines(cell, palette.dim);
2808
2810
  }),
2809
2811
  grand_total: (cells, palette) => cells.map((cell, cellIndex) => {
2810
2812
  if (cellIndex === 0) {
2811
- return palette.bold(palette.white(cell));
2813
+ return styleCellLines(cell, (line) => palette.bold(palette.white(line)));
2812
2814
  }
2813
2815
  if (cellIndex === 1) {
2814
- return palette.bold(palette.green(cell));
2816
+ return styleCellLines(cell, (line) => palette.bold(palette.green(line)));
2815
2817
  }
2816
- return palette.bold(cell);
2818
+ return styleCellLines(cell, palette.bold);
2817
2819
  })
2818
2820
  };
2819
2821
  function applyRowTypeStyle(rowType, cells, palette = defaultTerminalStylePalette) {
@@ -2824,8 +2826,8 @@ function applyBaseCellStyle(cells, palette, sourceStyler) {
2824
2826
  return [...cells];
2825
2827
  }
2826
2828
  const styledCells = [...cells];
2827
- styledCells[0] = palette.white(styledCells[0]);
2828
- styledCells[1] = sourceStyler(styledCells[1]);
2829
+ styledCells[0] = styleCellLines(styledCells[0], palette.white);
2830
+ styledCells[1] = styleCellLines(styledCells[1], sourceStyler);
2829
2831
  return styledCells;
2830
2832
  }
2831
2833
  function colorizeUsageBodyRows(bodyRows, rows, options) {
@@ -2840,66 +2842,291 @@ function colorizeUsageBodyRows(bodyRows, rows, options) {
2840
2842
  });
2841
2843
  }
2842
2844
 
2843
- // src/render/terminal-table.ts
2844
- var modelsColumnIndex = 2;
2845
- var roundedBorders = {
2846
- topBody: "\u2500",
2847
- topJoin: "\u252C",
2848
- topLeft: "\u256D",
2849
- topRight: "\u256E",
2850
- bottomBody: "\u2500",
2851
- bottomJoin: "\u2534",
2852
- bottomLeft: "\u2570",
2853
- bottomRight: "\u256F",
2854
- bodyLeft: "\u2502",
2855
- bodyRight: "\u2502",
2856
- bodyJoin: "\u2502",
2857
- headerJoin: "\u252C",
2858
- joinBody: "\u2500",
2859
- joinLeft: "\u251C",
2860
- joinRight: "\u2524",
2861
- joinJoin: "\u253C",
2862
- joinMiddleDown: "\u252C",
2863
- joinMiddleUp: "\u2534",
2864
- joinMiddleLeft: "\u2524",
2865
- joinMiddleRight: "\u251C"
2866
- };
2867
- function shouldDrawHorizontalLine(index, rowCount, usageRows) {
2868
- if (index === 0 || index === 1 || index === rowCount) {
2845
+ // src/render/table-text-layout.ts
2846
+ var ansiEscapePattern = new RegExp(String.raw`\u001B\[[0-9;]*m`, "gu");
2847
+ var combiningMarkPattern = /\p{Mark}/u;
2848
+ var extendedPictographicPattern = /\p{Extended_Pictographic}/u;
2849
+ var emojiPresentationPattern = /\p{Emoji_Presentation}/u;
2850
+ var regionalIndicatorPattern = /\p{Regional_Indicator}/u;
2851
+ var graphemeSegmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
2852
+ function normalizeLineBreaks(value) {
2853
+ return value.replace(/\r\n?/gu, "\n");
2854
+ }
2855
+ function stripAnsi(value) {
2856
+ return value.replaceAll(ansiEscapePattern, "");
2857
+ }
2858
+ function isControlCodePoint(codePoint) {
2859
+ return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
2860
+ }
2861
+ function isZeroWidthCodePoint(codePoint) {
2862
+ return codePoint === 8203 || codePoint === 8205 || codePoint === 8288 || codePoint === 65279 || codePoint >= 65024 && codePoint <= 65039;
2863
+ }
2864
+ function isFullWidthCodePoint(codePoint) {
2865
+ if (Number.isNaN(codePoint)) {
2866
+ return false;
2867
+ }
2868
+ if (codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 12871 && codePoint !== 12351 || codePoint >= 12880 && codePoint <= 19903 || codePoint >= 19968 && codePoint <= 42182 || codePoint >= 43360 && codePoint <= 43388 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65131 || codePoint >= 65281 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 110592 && codePoint <= 110593 || codePoint >= 127488 && codePoint <= 127569 || codePoint >= 131072 && codePoint <= 262141)) {
2869
+ return true;
2870
+ }
2871
+ return false;
2872
+ }
2873
+ function codePointDisplayWidth(character) {
2874
+ const codePoint = character.codePointAt(0);
2875
+ if (codePoint === void 0) {
2876
+ return 0;
2877
+ }
2878
+ if (isControlCodePoint(codePoint) || isZeroWidthCodePoint(codePoint)) {
2879
+ return 0;
2880
+ }
2881
+ if (combiningMarkPattern.test(character)) {
2882
+ return 0;
2883
+ }
2884
+ if (isFullWidthCodePoint(codePoint)) {
2885
+ return 2;
2886
+ }
2887
+ return 1;
2888
+ }
2889
+ function segmentGraphemes(value) {
2890
+ return Array.from(graphemeSegmenter.segment(value), (segment) => segment.segment);
2891
+ }
2892
+ function isEmojiGrapheme(grapheme) {
2893
+ if (emojiPresentationPattern.test(grapheme)) {
2894
+ return true;
2895
+ }
2896
+ if (regionalIndicatorPattern.test(grapheme)) {
2897
+ return true;
2898
+ }
2899
+ if (grapheme.includes("\u20E3")) {
2869
2900
  return true;
2870
2901
  }
2871
- const previousRow = usageRows[index - 2];
2872
- const nextRow = usageRows[index - 1];
2902
+ if (grapheme.includes("\uFE0F")) {
2903
+ return true;
2904
+ }
2905
+ return grapheme.includes("\u200D") && extendedPictographicPattern.test(grapheme);
2906
+ }
2907
+ function graphemeDisplayWidth(grapheme) {
2908
+ if (isEmojiGrapheme(grapheme)) {
2909
+ return 2;
2910
+ }
2911
+ let width = 0;
2912
+ for (const character of grapheme) {
2913
+ width = Math.max(width, codePointDisplayWidth(character));
2914
+ }
2915
+ return width;
2916
+ }
2917
+ function visibleWidth(value) {
2918
+ let width = 0;
2919
+ for (const grapheme of segmentGraphemes(stripAnsi(value))) {
2920
+ width += graphemeDisplayWidth(grapheme);
2921
+ }
2922
+ return width;
2923
+ }
2924
+ function splitCellLines(value) {
2925
+ return normalizeLineBreaks(value).split("\n");
2926
+ }
2927
+ function sliceByVisibleWidth(value, maxWidth) {
2928
+ if (maxWidth <= 0) {
2929
+ return "";
2930
+ }
2931
+ let width = 0;
2932
+ let endOffset = 0;
2933
+ for (const grapheme of segmentGraphemes(value)) {
2934
+ const graphemeWidth = graphemeDisplayWidth(grapheme);
2935
+ if (width + graphemeWidth > maxWidth) {
2936
+ break;
2937
+ }
2938
+ width += graphemeWidth;
2939
+ endOffset += grapheme.length;
2940
+ }
2941
+ return value.slice(0, endOffset);
2942
+ }
2943
+ function getFirstGrapheme(value) {
2944
+ for (const grapheme of segmentGraphemes(value)) {
2945
+ return grapheme;
2946
+ }
2947
+ return "";
2948
+ }
2949
+ function wrapPlainLine(line, width) {
2950
+ if (visibleWidth(line) <= width) {
2951
+ return [line];
2952
+ }
2953
+ const wrappedLines = [];
2954
+ let remaining = line;
2955
+ while (visibleWidth(remaining) > width) {
2956
+ const slice = sliceByVisibleWidth(remaining, width + 1);
2957
+ const breakIndex = slice.lastIndexOf(" ");
2958
+ if (breakIndex > 0) {
2959
+ wrappedLines.push(remaining.slice(0, breakIndex));
2960
+ remaining = remaining.slice(breakIndex + 1);
2961
+ continue;
2962
+ }
2963
+ const chunk = sliceByVisibleWidth(remaining, width);
2964
+ if (chunk.length > 0) {
2965
+ wrappedLines.push(chunk);
2966
+ remaining = remaining.slice(chunk.length);
2967
+ continue;
2968
+ }
2969
+ const firstCharacter = getFirstGrapheme(remaining);
2970
+ wrappedLines.push(firstCharacter);
2971
+ remaining = remaining.slice(firstCharacter.length);
2972
+ }
2973
+ wrappedLines.push(remaining);
2974
+ return wrappedLines;
2975
+ }
2976
+ function wrapTableColumn(rows, options) {
2977
+ if (options.width <= 0) {
2978
+ throw new RangeError("wrapTableColumn width must be greater than 0");
2979
+ }
2980
+ return rows.map((row) => {
2981
+ const wrappedRow = [...row];
2982
+ const cell = wrappedRow[options.columnIndex] ?? "";
2983
+ const wrappedLines = splitCellLines(cell).flatMap((line) => wrapPlainLine(line, options.width));
2984
+ wrappedRow[options.columnIndex] = wrappedLines.join("\n");
2985
+ return wrappedRow;
2986
+ });
2987
+ }
2988
+
2989
+ // src/render/unicode-table.ts
2990
+ function getColumnAlignment(columnIndex, modelsColumnIndex2) {
2991
+ if (columnIndex <= modelsColumnIndex2) {
2992
+ return "left";
2993
+ }
2994
+ return "right";
2995
+ }
2996
+ function getVerticalAlignment(columnIndex, tableLayout, modelsColumnIndex2) {
2997
+ if (columnIndex === modelsColumnIndex2) {
2998
+ return "top";
2999
+ }
3000
+ return tableLayout === "per_model_columns" ? "top" : "middle";
3001
+ }
3002
+ function alignCellLine(value, width, alignment2) {
3003
+ const padding = Math.max(0, width - visibleWidth(value));
3004
+ if (alignment2 === "right") {
3005
+ return `${" ".repeat(padding)}${value}`;
3006
+ }
3007
+ return `${value}${" ".repeat(padding)}`;
3008
+ }
3009
+ function padCellLines(lines, rowHeight, verticalAlignment) {
3010
+ const missingLineCount = rowHeight - lines.length;
3011
+ if (missingLineCount <= 0) {
3012
+ return lines;
3013
+ }
3014
+ if (verticalAlignment === "top") {
3015
+ return [...lines, ...Array.from({ length: missingLineCount }, () => "")];
3016
+ }
3017
+ const topPadding = Math.floor(missingLineCount / 2);
3018
+ const bottomPadding = missingLineCount - topPadding;
3019
+ return [
3020
+ ...Array.from({ length: topPadding }, () => ""),
3021
+ ...lines,
3022
+ ...Array.from({ length: bottomPadding }, () => "")
3023
+ ];
3024
+ }
3025
+ function toRenderableRowLines(row, options) {
3026
+ const cellLines = row.map((cell) => splitCellLines(cell));
3027
+ const rowHeight = cellLines.reduce((max, lines) => Math.max(max, lines.length), 1);
3028
+ const paddedAlignedColumns = cellLines.map((lines, columnIndex) => {
3029
+ const verticalAlignment = getVerticalAlignment(
3030
+ columnIndex,
3031
+ options.tableLayout,
3032
+ options.modelsColumnIndex
3033
+ );
3034
+ const alignedLines = padCellLines(lines, rowHeight, verticalAlignment);
3035
+ const horizontalAlignment = getColumnAlignment(columnIndex, options.modelsColumnIndex);
3036
+ return alignedLines.map(
3037
+ (line) => alignCellLine(line, options.widths[columnIndex], horizontalAlignment)
3038
+ );
3039
+ });
3040
+ return Array.from({ length: rowHeight }, (_, lineIndex) => {
3041
+ const lineCells = paddedAlignedColumns.map((columnLines) => columnLines[lineIndex]);
3042
+ return `\u2502 ${lineCells.join(" \u2502 ")} \u2502`;
3043
+ });
3044
+ }
3045
+ function buildBorderLine(widths, chars) {
3046
+ const segments = widths.map((width) => "\u2500".repeat(width + 2));
3047
+ return `${chars.left}${segments.join(chars.join)}${chars.right}`;
3048
+ }
3049
+ function shouldDrawBodySeparator(index, usageRows) {
3050
+ if (index < 0 || index >= usageRows.length - 1) {
3051
+ return false;
3052
+ }
3053
+ const previousRow = usageRows[index];
3054
+ const nextRow = usageRows[index + 1];
2873
3055
  return previousRow.rowType === "period_combined" || nextRow.rowType === "grand_total" || previousRow.periodKey !== nextRow.periodKey;
2874
3056
  }
2875
- function createTableConfig(tableLayout, usageRows) {
2876
- const rowVerticalAlignment = tableLayout === "per_model_columns" ? "top" : "middle";
2877
- return {
2878
- border: roundedBorders,
2879
- drawHorizontalLine: (index, rowCount) => shouldDrawHorizontalLine(index, rowCount, usageRows),
2880
- columnDefault: {
2881
- paddingLeft: 1,
2882
- paddingRight: 1,
2883
- verticalAlignment: "top"
2884
- },
2885
- columns: {
2886
- 0: { alignment: "left", verticalAlignment: rowVerticalAlignment },
2887
- 1: { alignment: "left", verticalAlignment: rowVerticalAlignment },
2888
- [modelsColumnIndex]: {
2889
- alignment: "left",
2890
- width: 32,
2891
- wrapWord: true
2892
- },
2893
- 3: { alignment: "right", verticalAlignment: rowVerticalAlignment },
2894
- 4: { alignment: "right", verticalAlignment: rowVerticalAlignment },
2895
- 5: { alignment: "right", verticalAlignment: rowVerticalAlignment },
2896
- 6: { alignment: "right", verticalAlignment: rowVerticalAlignment },
2897
- 7: { alignment: "right", verticalAlignment: rowVerticalAlignment },
2898
- 8: { alignment: "right", verticalAlignment: rowVerticalAlignment },
2899
- 9: { alignment: "right", verticalAlignment: rowVerticalAlignment }
3057
+ function computeColumnWidths(measureRows, options) {
3058
+ const columnCount = measureRows[0]?.length ?? 0;
3059
+ const widths = Array.from({ length: columnCount }, () => 0);
3060
+ for (const row of measureRows) {
3061
+ for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {
3062
+ for (const line of splitCellLines(row[columnIndex])) {
3063
+ widths[columnIndex] = Math.max(widths[columnIndex], visibleWidth(line));
3064
+ }
2900
3065
  }
2901
- };
3066
+ }
3067
+ widths[options.modelsColumnIndex] = options.modelsColumnWidth;
3068
+ return widths;
3069
+ }
3070
+ function renderUnicodeTable(options) {
3071
+ const measureRows = [options.measureHeaderCells, ...options.measureBodyRows];
3072
+ const widths = computeColumnWidths(measureRows, {
3073
+ modelsColumnIndex: options.modelsColumnIndex,
3074
+ modelsColumnWidth: options.modelsColumnWidth
3075
+ });
3076
+ const renderedLines = [];
3077
+ renderedLines.push(
3078
+ buildBorderLine(widths, {
3079
+ left: "\u256D",
3080
+ join: "\u252C",
3081
+ right: "\u256E"
3082
+ })
3083
+ );
3084
+ renderedLines.push(
3085
+ ...toRenderableRowLines(options.headerCells, {
3086
+ widths,
3087
+ tableLayout: "per_model_columns",
3088
+ modelsColumnIndex: options.modelsColumnIndex
3089
+ })
3090
+ );
3091
+ renderedLines.push(
3092
+ buildBorderLine(widths, {
3093
+ left: "\u251C",
3094
+ join: "\u253C",
3095
+ right: "\u2524"
3096
+ })
3097
+ );
3098
+ options.bodyRows.forEach((row, rowIndex) => {
3099
+ renderedLines.push(
3100
+ ...toRenderableRowLines(row, {
3101
+ widths,
3102
+ tableLayout: options.tableLayout,
3103
+ modelsColumnIndex: options.modelsColumnIndex
3104
+ })
3105
+ );
3106
+ if (rowIndex < options.bodyRows.length - 1 && shouldDrawBodySeparator(rowIndex, options.usageRows)) {
3107
+ renderedLines.push(
3108
+ buildBorderLine(widths, {
3109
+ left: "\u251C",
3110
+ join: "\u253C",
3111
+ right: "\u2524"
3112
+ })
3113
+ );
3114
+ }
3115
+ });
3116
+ renderedLines.push(
3117
+ buildBorderLine(widths, {
3118
+ left: "\u2570",
3119
+ join: "\u2534",
3120
+ right: "\u256F"
3121
+ })
3122
+ );
3123
+ return `${renderedLines.join("\n")}
3124
+ `;
2902
3125
  }
3126
+
3127
+ // src/render/terminal-table.ts
3128
+ var modelsColumnIndex = 2;
3129
+ var modelsColumnWidth = 32;
2903
3130
  function shouldUseColorByDefault() {
2904
3131
  if (process.env.NO_COLOR !== void 0) {
2905
3132
  return false;
@@ -2921,9 +3148,21 @@ function renderTerminalTable(rows, options = {}) {
2921
3148
  const useColor = options.useColor ?? shouldUseColorByDefault();
2922
3149
  const tableLayout = options.tableLayout ?? "compact";
2923
3150
  const uncoloredBodyRows = toUsageTableCells(rows, { layout: tableLayout });
2924
- const bodyRows = colorizeUsageBodyRows(uncoloredBodyRows, rows, { useColor });
2925
- const displayRows = [colorizeHeader(useColor), ...bodyRows];
2926
- return table(displayRows, createTableConfig(tableLayout, rows));
3151
+ const wrappedBodyRows = wrapTableColumn(uncoloredBodyRows, {
3152
+ columnIndex: modelsColumnIndex,
3153
+ width: modelsColumnWidth
3154
+ });
3155
+ const bodyRows = colorizeUsageBodyRows(wrappedBodyRows, rows, { useColor });
3156
+ return renderUnicodeTable({
3157
+ headerCells: colorizeHeader(useColor),
3158
+ bodyRows,
3159
+ measureHeaderCells: usageTableHeaders,
3160
+ measureBodyRows: wrappedBodyRows,
3161
+ usageRows: rows,
3162
+ tableLayout,
3163
+ modelsColumnIndex,
3164
+ modelsColumnWidth
3165
+ });
2927
3166
  }
2928
3167
 
2929
3168
  // src/render/render-usage-report.ts