mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
Merge pull request #234 from vvrfxyz/feature/usage-thinking-column
Show thinking intensity in request events
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
|||||||
formatDurationMs,
|
formatDurationMs,
|
||||||
LATENCY_SOURCE_FIELD,
|
LATENCY_SOURCE_FIELD,
|
||||||
normalizeAuthIndex,
|
normalizeAuthIndex,
|
||||||
|
type UsageThinking,
|
||||||
} from '@/utils/usage';
|
} from '@/utils/usage';
|
||||||
import { downloadBlob } from '@/utils/download';
|
import { downloadBlob } from '@/utils/download';
|
||||||
import styles from '@/pages/UsagePage.module.scss';
|
import styles from '@/pages/UsagePage.module.scss';
|
||||||
@@ -37,6 +38,8 @@ type RequestEventRow = {
|
|||||||
authIndex: string;
|
authIndex: string;
|
||||||
failed: boolean;
|
failed: boolean;
|
||||||
latencyMs: number | null;
|
latencyMs: number | null;
|
||||||
|
thinking: UsageThinking | null;
|
||||||
|
thinkingLabel: string;
|
||||||
inputTokens: number;
|
inputTokens: number;
|
||||||
outputTokens: number;
|
outputTokens: number;
|
||||||
reasoningTokens: number;
|
reasoningTokens: number;
|
||||||
@@ -60,6 +63,37 @@ const toNumber = (value: unknown): number => {
|
|||||||
return parsed;
|
return parsed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeThinkingText = (value: unknown): string => {
|
||||||
|
if (typeof value !== 'string') return '';
|
||||||
|
return value.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatThinkingLabel = (thinking: UsageThinking | null): string => {
|
||||||
|
if (!thinking) return '-';
|
||||||
|
|
||||||
|
const intensity = normalizeThinkingText(thinking.intensity);
|
||||||
|
const level = normalizeThinkingText(thinking.level);
|
||||||
|
const mode = normalizeThinkingText(thinking.mode);
|
||||||
|
const budget =
|
||||||
|
typeof thinking.budget === 'number' && Number.isFinite(thinking.budget)
|
||||||
|
? thinking.budget
|
||||||
|
: null;
|
||||||
|
const label = intensity || level || (budget !== null ? String(budget) : mode);
|
||||||
|
const budgetLabel = budget !== null ? budget.toLocaleString() : null;
|
||||||
|
|
||||||
|
if (!label) return '-';
|
||||||
|
if (budgetLabel !== null && label === String(budget)) {
|
||||||
|
return budgetLabel;
|
||||||
|
}
|
||||||
|
if (mode === 'budget' && budget !== null && budget > 0) {
|
||||||
|
return `${label} (${budgetLabel})`;
|
||||||
|
}
|
||||||
|
if (budget === -1 && label !== 'auto') {
|
||||||
|
return `${label} (-1)`;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
|
||||||
const encodeCsv = (value: string | number): string => {
|
const encodeCsv = (value: string | number): string => {
|
||||||
const text = String(value ?? '');
|
const text = String(value ?? '');
|
||||||
const trimmedLeft = text.replace(/^\s+/, '');
|
const trimmedLeft = text.replace(/^\s+/, '');
|
||||||
@@ -127,63 +161,61 @@ export function RequestEventsDetailsCard({
|
|||||||
const rows = useMemo<RequestEventRow[]>(() => {
|
const rows = useMemo<RequestEventRow[]>(() => {
|
||||||
const details = collectUsageDetails(usage);
|
const details = collectUsageDetails(usage);
|
||||||
|
|
||||||
const baseRows = details
|
const baseRows = details.map((detail, index) => {
|
||||||
.map((detail, index) => {
|
const timestamp = detail.timestamp;
|
||||||
const timestamp = detail.timestamp;
|
const timestampMs =
|
||||||
const timestampMs =
|
typeof detail.__timestampMs === 'number' && detail.__timestampMs > 0
|
||||||
typeof detail.__timestampMs === 'number' && detail.__timestampMs > 0
|
? detail.__timestampMs
|
||||||
? detail.__timestampMs
|
: parseTimestampMs(timestamp);
|
||||||
: parseTimestampMs(timestamp);
|
const date = Number.isNaN(timestampMs) ? null : new Date(timestampMs);
|
||||||
const date = Number.isNaN(timestampMs) ? null : new Date(timestampMs);
|
const sourceRaw = String(detail.source ?? '').trim();
|
||||||
const sourceRaw = String(detail.source ?? '').trim();
|
const authIndexRaw = detail.auth_index as unknown;
|
||||||
const authIndexRaw = detail.auth_index as unknown;
|
const authIndex =
|
||||||
const authIndex =
|
authIndexRaw === null || authIndexRaw === undefined || authIndexRaw === ''
|
||||||
authIndexRaw === null || authIndexRaw === undefined || authIndexRaw === ''
|
? '-'
|
||||||
? '-'
|
: String(authIndexRaw);
|
||||||
: String(authIndexRaw);
|
const sourceInfo = resolveSourceDisplay(sourceRaw, authIndexRaw, sourceInfoMap, authFileMap);
|
||||||
const sourceInfo = resolveSourceDisplay(
|
const source = sourceInfo.displayName;
|
||||||
sourceRaw,
|
const sourceKey = sourceInfo.identityKey ?? `source:${sourceRaw || source}`;
|
||||||
authIndexRaw,
|
const sourceType = sourceInfo.type;
|
||||||
sourceInfoMap,
|
const model = String(detail.__modelName ?? '').trim() || '-';
|
||||||
authFileMap
|
const inputTokens = Math.max(toNumber(detail.tokens?.input_tokens), 0);
|
||||||
);
|
const outputTokens = Math.max(toNumber(detail.tokens?.output_tokens), 0);
|
||||||
const source = sourceInfo.displayName;
|
const reasoningTokens = Math.max(toNumber(detail.tokens?.reasoning_tokens), 0);
|
||||||
const sourceKey = sourceInfo.identityKey ?? `source:${sourceRaw || source}`;
|
const cachedTokens = Math.max(
|
||||||
const sourceType = sourceInfo.type;
|
Math.max(toNumber(detail.tokens?.cached_tokens), 0),
|
||||||
const model = String(detail.__modelName ?? '').trim() || '-';
|
Math.max(toNumber(detail.tokens?.cache_tokens), 0)
|
||||||
const inputTokens = Math.max(toNumber(detail.tokens?.input_tokens), 0);
|
);
|
||||||
const outputTokens = Math.max(toNumber(detail.tokens?.output_tokens), 0);
|
const totalTokens = Math.max(
|
||||||
const reasoningTokens = Math.max(toNumber(detail.tokens?.reasoning_tokens), 0);
|
toNumber(detail.tokens?.total_tokens),
|
||||||
const cachedTokens = Math.max(
|
extractTotalTokens(detail)
|
||||||
Math.max(toNumber(detail.tokens?.cached_tokens), 0),
|
);
|
||||||
Math.max(toNumber(detail.tokens?.cache_tokens), 0)
|
const latencyMs = extractLatencyMs(detail);
|
||||||
);
|
const thinking = detail.thinking ?? null;
|
||||||
const totalTokens = Math.max(
|
const thinkingLabel = formatThinkingLabel(thinking);
|
||||||
toNumber(detail.tokens?.total_tokens),
|
|
||||||
extractTotalTokens(detail)
|
|
||||||
);
|
|
||||||
const latencyMs = extractLatencyMs(detail);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: `${timestamp}-${model}-${sourceKey}-${authIndex}-${index}`,
|
id: `${timestamp}-${model}-${sourceKey}-${authIndex}-${index}`,
|
||||||
timestamp,
|
timestamp,
|
||||||
timestampMs: Number.isNaN(timestampMs) ? 0 : timestampMs,
|
timestampMs: Number.isNaN(timestampMs) ? 0 : timestampMs,
|
||||||
timestampLabel: date ? date.toLocaleString(i18n.language) : timestamp || '-',
|
timestampLabel: date ? date.toLocaleString(i18n.language) : timestamp || '-',
|
||||||
model,
|
model,
|
||||||
sourceKey,
|
sourceKey,
|
||||||
sourceRaw: sourceRaw || '-',
|
sourceRaw: sourceRaw || '-',
|
||||||
source,
|
source,
|
||||||
sourceType,
|
sourceType,
|
||||||
authIndex,
|
authIndex,
|
||||||
failed: detail.failed === true,
|
failed: detail.failed === true,
|
||||||
latencyMs,
|
latencyMs,
|
||||||
inputTokens,
|
thinking,
|
||||||
outputTokens,
|
thinkingLabel,
|
||||||
reasoningTokens,
|
inputTokens,
|
||||||
cachedTokens,
|
outputTokens,
|
||||||
totalTokens,
|
reasoningTokens,
|
||||||
};
|
cachedTokens,
|
||||||
});
|
totalTokens,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const sourceLabelKeyMap = new Map<string, Set<string>>();
|
const sourceLabelKeyMap = new Map<string, Set<string>>();
|
||||||
baseRows.forEach((row) => {
|
baseRows.forEach((row) => {
|
||||||
@@ -319,6 +351,10 @@ export function RequestEventsDetailsCard({
|
|||||||
'auth_index',
|
'auth_index',
|
||||||
'result',
|
'result',
|
||||||
...(hasLatencyData ? ['latency_ms'] : []),
|
...(hasLatencyData ? ['latency_ms'] : []),
|
||||||
|
'thinking_intensity',
|
||||||
|
'thinking_mode',
|
||||||
|
'thinking_level',
|
||||||
|
'thinking_budget',
|
||||||
'input_tokens',
|
'input_tokens',
|
||||||
'output_tokens',
|
'output_tokens',
|
||||||
'reasoning_tokens',
|
'reasoning_tokens',
|
||||||
@@ -335,6 +371,10 @@ export function RequestEventsDetailsCard({
|
|||||||
row.authIndex,
|
row.authIndex,
|
||||||
row.failed ? 'failed' : 'success',
|
row.failed ? 'failed' : 'success',
|
||||||
...(hasLatencyData ? [row.latencyMs ?? ''] : []),
|
...(hasLatencyData ? [row.latencyMs ?? ''] : []),
|
||||||
|
row.thinking?.intensity ?? '',
|
||||||
|
row.thinking?.mode ?? '',
|
||||||
|
row.thinking?.level ?? '',
|
||||||
|
row.thinking?.budget ?? '',
|
||||||
row.inputTokens,
|
row.inputTokens,
|
||||||
row.outputTokens,
|
row.outputTokens,
|
||||||
row.reasoningTokens,
|
row.reasoningTokens,
|
||||||
@@ -364,6 +404,7 @@ export function RequestEventsDetailsCard({
|
|||||||
auth_index: row.authIndex,
|
auth_index: row.authIndex,
|
||||||
failed: row.failed,
|
failed: row.failed,
|
||||||
...(hasLatencyData && row.latencyMs !== null ? { latency_ms: row.latencyMs } : {}),
|
...(hasLatencyData && row.latencyMs !== null ? { latency_ms: row.latencyMs } : {}),
|
||||||
|
...(row.thinking ? { thinking: row.thinking } : {}),
|
||||||
tokens: {
|
tokens: {
|
||||||
input_tokens: row.inputTokens,
|
input_tokens: row.inputTokens,
|
||||||
output_tokens: row.outputTokens,
|
output_tokens: row.outputTokens,
|
||||||
@@ -492,6 +533,7 @@ export function RequestEventsDetailsCard({
|
|||||||
<th>{t('usage_stats.request_events_auth_index')}</th>
|
<th>{t('usage_stats.request_events_auth_index')}</th>
|
||||||
<th>{t('usage_stats.request_events_result')}</th>
|
<th>{t('usage_stats.request_events_result')}</th>
|
||||||
{hasLatencyData && <th title={latencyHint}>{t('usage_stats.time')}</th>}
|
{hasLatencyData && <th title={latencyHint}>{t('usage_stats.time')}</th>}
|
||||||
|
<th>{t('usage_stats.thinking_intensity')}</th>
|
||||||
<th>{t('usage_stats.input_tokens')}</th>
|
<th>{t('usage_stats.input_tokens')}</th>
|
||||||
<th>{t('usage_stats.output_tokens')}</th>
|
<th>{t('usage_stats.output_tokens')}</th>
|
||||||
<th>{t('usage_stats.reasoning_tokens')}</th>
|
<th>{t('usage_stats.reasoning_tokens')}</th>
|
||||||
@@ -529,6 +571,34 @@ export function RequestEventsDetailsCard({
|
|||||||
{hasLatencyData && (
|
{hasLatencyData && (
|
||||||
<td className={styles.durationCell}>{formatDurationMs(row.latencyMs)}</td>
|
<td className={styles.durationCell}>{formatDurationMs(row.latencyMs)}</td>
|
||||||
)}
|
)}
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
row.thinking
|
||||||
|
? styles.requestEventsThinkingBadge
|
||||||
|
: styles.requestEventsThinkingEmpty
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
row.thinking
|
||||||
|
? [
|
||||||
|
row.thinking.mode
|
||||||
|
? `${t('usage_stats.thinking_mode')}: ${row.thinking.mode}`
|
||||||
|
: '',
|
||||||
|
row.thinking.level
|
||||||
|
? `${t('usage_stats.thinking_level')}: ${row.thinking.level}`
|
||||||
|
: '',
|
||||||
|
typeof row.thinking.budget === 'number'
|
||||||
|
? `${t('usage_stats.thinking_budget')}: ${row.thinking.budget.toLocaleString()}`
|
||||||
|
: '',
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' · ')
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{row.thinkingLabel}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td>{row.inputTokens.toLocaleString()}</td>
|
<td>{row.inputTokens.toLocaleString()}</td>
|
||||||
<td>{row.outputTokens.toLocaleString()}</td>
|
<td>{row.outputTokens.toLocaleString()}</td>
|
||||||
<td>{row.reasoningTokens.toLocaleString()}</td>
|
<td>{row.reasoningTokens.toLocaleString()}</td>
|
||||||
|
|||||||
@@ -1025,6 +1025,10 @@
|
|||||||
"request_events_source": "Source",
|
"request_events_source": "Source",
|
||||||
"request_events_auth_index": "Auth Index",
|
"request_events_auth_index": "Auth Index",
|
||||||
"request_events_result": "Result",
|
"request_events_result": "Result",
|
||||||
|
"thinking_intensity": "Thinking",
|
||||||
|
"thinking_mode": "Thinking Mode",
|
||||||
|
"thinking_level": "Thinking Level",
|
||||||
|
"thinking_budget": "Thinking Budget",
|
||||||
"time": "Latency",
|
"time": "Latency",
|
||||||
"avg_time": "Avg Latency",
|
"avg_time": "Avg Latency",
|
||||||
"total_time": "Total Latency",
|
"total_time": "Total Latency",
|
||||||
|
|||||||
@@ -1022,6 +1022,10 @@
|
|||||||
"request_events_source": "Источник",
|
"request_events_source": "Источник",
|
||||||
"request_events_auth_index": "Auth Index",
|
"request_events_auth_index": "Auth Index",
|
||||||
"request_events_result": "Результат",
|
"request_events_result": "Результат",
|
||||||
|
"thinking_intensity": "Рассуждение",
|
||||||
|
"thinking_mode": "Режим рассуждения",
|
||||||
|
"thinking_level": "Уровень рассуждения",
|
||||||
|
"thinking_budget": "Бюджет рассуждения",
|
||||||
"time": "Задержка",
|
"time": "Задержка",
|
||||||
"avg_time": "Средняя задержка",
|
"avg_time": "Средняя задержка",
|
||||||
"total_time": "Суммарная задержка",
|
"total_time": "Суммарная задержка",
|
||||||
|
|||||||
@@ -1025,6 +1025,10 @@
|
|||||||
"request_events_source": "来源",
|
"request_events_source": "来源",
|
||||||
"request_events_auth_index": "认证索引",
|
"request_events_auth_index": "认证索引",
|
||||||
"request_events_result": "结果",
|
"request_events_result": "结果",
|
||||||
|
"thinking_intensity": "思考强度",
|
||||||
|
"thinking_mode": "思考模式",
|
||||||
|
"thinking_level": "思考等级",
|
||||||
|
"thinking_budget": "思考预算",
|
||||||
"time": "延迟",
|
"time": "延迟",
|
||||||
"avg_time": "平均延迟",
|
"avg_time": "平均延迟",
|
||||||
"total_time": "总延迟",
|
"total_time": "总延迟",
|
||||||
|
|||||||
@@ -1051,6 +1051,10 @@
|
|||||||
"request_events_source": "來源",
|
"request_events_source": "來源",
|
||||||
"request_events_auth_index": "驗證索引",
|
"request_events_auth_index": "驗證索引",
|
||||||
"request_events_result": "結果",
|
"request_events_result": "結果",
|
||||||
|
"thinking_intensity": "思考強度",
|
||||||
|
"thinking_mode": "思考模式",
|
||||||
|
"thinking_level": "思考等級",
|
||||||
|
"thinking_budget": "思考預算",
|
||||||
"time": "延遲",
|
"time": "延遲",
|
||||||
"avg_time": "平均延遲",
|
"avg_time": "平均延遲",
|
||||||
"total_time": "總延遲",
|
"total_time": "總延遲",
|
||||||
|
|||||||
@@ -969,6 +969,31 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.requestEventsThinkingBadge,
|
||||||
|
.requestEventsThinkingEmpty {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 48px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: $radius-full;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requestEventsThinkingBadge {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background: color-mix(in srgb, var(--primary-color) 12%, transparent);
|
||||||
|
border: 1px solid color-mix(in srgb, var(--primary-color) 28%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.requestEventsThinkingEmpty {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
.chartLineHeader {
|
.chartLineHeader {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
+41
-10
@@ -39,6 +39,13 @@ export interface TokenBreakdown {
|
|||||||
reasoningTokens: number;
|
reasoningTokens: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UsageThinking {
|
||||||
|
intensity?: string;
|
||||||
|
mode?: string;
|
||||||
|
level?: string;
|
||||||
|
budget?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RateStats {
|
export interface RateStats {
|
||||||
rpm: number;
|
rpm: number;
|
||||||
tpm: number;
|
tpm: number;
|
||||||
@@ -66,6 +73,7 @@ export interface UsageDetail {
|
|||||||
cache_tokens?: number;
|
cache_tokens?: number;
|
||||||
total_tokens: number;
|
total_tokens: number;
|
||||||
};
|
};
|
||||||
|
thinking?: UsageThinking | null;
|
||||||
failed: boolean;
|
failed: boolean;
|
||||||
__modelName?: string;
|
__modelName?: string;
|
||||||
__timestampMs?: number;
|
__timestampMs?: number;
|
||||||
@@ -123,6 +131,29 @@ const getApisRecord = (usageData: unknown): Record<string, unknown> | null => {
|
|||||||
return isRecord(apisRaw) ? apisRaw : null;
|
return isRecord(apisRaw) ? apisRaw : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeUsageThinking = (value: unknown): UsageThinking | null => {
|
||||||
|
if (!isRecord(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const intensity = typeof value.intensity === 'string' ? value.intensity.trim() : '';
|
||||||
|
const mode = typeof value.mode === 'string' ? value.mode.trim() : '';
|
||||||
|
const level = typeof value.level === 'string' ? value.level.trim() : '';
|
||||||
|
const budget =
|
||||||
|
typeof value.budget === 'number' && Number.isFinite(value.budget) ? value.budget : undefined;
|
||||||
|
|
||||||
|
if (!intensity && !mode && !level && budget === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(intensity ? { intensity } : {}),
|
||||||
|
...(mode ? { mode } : {}),
|
||||||
|
...(level ? { level } : {}),
|
||||||
|
...(budget !== undefined ? { budget } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
interface UsageSummary {
|
interface UsageSummary {
|
||||||
totalRequests: number;
|
totalRequests: number;
|
||||||
successCount: number;
|
successCount: number;
|
||||||
@@ -552,13 +583,13 @@ export function collectUsageDetails(usageData: unknown): UsageDetail[] {
|
|||||||
details.push({
|
details.push({
|
||||||
timestamp,
|
timestamp,
|
||||||
source: normalizeSource(detailRaw.source),
|
source: normalizeSource(detailRaw.source),
|
||||||
auth_index:
|
auth_index: (detailRaw?.auth_index ??
|
||||||
(detailRaw?.auth_index ??
|
detailRaw?.authIndex ??
|
||||||
detailRaw?.authIndex ??
|
detailRaw?.AuthIndex ??
|
||||||
detailRaw?.AuthIndex ??
|
null) as UsageDetail['auth_index'],
|
||||||
null) as UsageDetail['auth_index'],
|
|
||||||
latency_ms: latencyMs ?? undefined,
|
latency_ms: latencyMs ?? undefined,
|
||||||
tokens: tokensRaw as unknown as UsageDetail['tokens'],
|
tokens: tokensRaw as unknown as UsageDetail['tokens'],
|
||||||
|
thinking: normalizeUsageThinking(detailRaw.thinking),
|
||||||
failed: detailRaw.failed === true,
|
failed: detailRaw.failed === true,
|
||||||
__modelName: modelName,
|
__modelName: modelName,
|
||||||
__timestampMs: Number.isNaN(timestampMs) ? 0 : timestampMs,
|
__timestampMs: Number.isNaN(timestampMs) ? 0 : timestampMs,
|
||||||
@@ -629,13 +660,13 @@ export function collectUsageDetailsWithEndpoint(usageData: unknown): UsageDetail
|
|||||||
details.push({
|
details.push({
|
||||||
timestamp,
|
timestamp,
|
||||||
source: normalizeSource(detailRaw.source),
|
source: normalizeSource(detailRaw.source),
|
||||||
auth_index:
|
auth_index: (detailRaw?.auth_index ??
|
||||||
(detailRaw?.auth_index ??
|
detailRaw?.authIndex ??
|
||||||
detailRaw?.authIndex ??
|
detailRaw?.AuthIndex ??
|
||||||
detailRaw?.AuthIndex ??
|
null) as UsageDetail['auth_index'],
|
||||||
null) as UsageDetail['auth_index'],
|
|
||||||
latency_ms: latencyMs ?? undefined,
|
latency_ms: latencyMs ?? undefined,
|
||||||
tokens: tokensRaw as unknown as UsageDetail['tokens'],
|
tokens: tokensRaw as unknown as UsageDetail['tokens'],
|
||||||
|
thinking: normalizeUsageThinking(detailRaw.thinking),
|
||||||
failed: detailRaw.failed === true,
|
failed: detailRaw.failed === true,
|
||||||
__modelName: modelName,
|
__modelName: modelName,
|
||||||
__endpoint: endpoint,
|
__endpoint: endpoint,
|
||||||
|
|||||||
Reference in New Issue
Block a user