@jeffreycao/copilot-api 1.10.0 → 1.10.2
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/README.md +28 -5
- package/README.zh-CN.md +28 -5
- package/dist/{auth-D7YCTWpx.js → auth-BHa2OHXf.js} +3 -3
- package/dist/{auth-D7YCTWpx.js.map → auth-BHa2OHXf.js.map} +1 -1
- package/dist/{check-usage-Dgg0nNEw.js → check-usage-BdXGp1Wr.js} +3 -3
- package/dist/{check-usage-Dgg0nNEw.js.map → check-usage-BdXGp1Wr.js.map} +1 -1
- package/dist/main.js +3 -3
- package/dist/{proxy-De0Po8kG.js → proxy-DQLzdeq3.js} +106 -6
- package/dist/proxy-DQLzdeq3.js.map +1 -0
- package/dist/{server-BnKth1Jp.js → server-csGHkK-m.js} +192 -45
- package/dist/server-csGHkK-m.js.map +1 -0
- package/dist/{start-1KA2mHVS.js → start-BVraN8xz.js} +5 -5
- package/dist/{start-1KA2mHVS.js.map → start-BVraN8xz.js.map} +1 -1
- package/dist/{token-BQlDdqtI.js → token-Dj8XsAxn.js} +2 -2
- package/dist/{token-BQlDdqtI.js.map → token-Dj8XsAxn.js.map} +1 -1
- package/dist/{utils-C5ej0z8n.js → utils-jHLgqAq2.js} +3 -3
- package/dist/{utils-C5ej0z8n.js.map → utils-jHLgqAq2.js.map} +1 -1
- package/package.json +1 -1
- package/pages/index.html +432 -10
- package/dist/proxy-De0Po8kG.js.map +0 -1
- package/dist/server-BnKth1Jp.js.map +0 -1
package/pages/index.html
CHANGED
|
@@ -205,8 +205,8 @@
|
|
|
205
205
|
"
|
|
206
206
|
>
|
|
207
207
|
<option value="day">Day</option>
|
|
208
|
-
<option value="week">
|
|
209
|
-
<option value="month">
|
|
208
|
+
<option value="week">Last 7 days</option>
|
|
209
|
+
<option value="month">Last 30 days</option>
|
|
210
210
|
</select>
|
|
211
211
|
</div>
|
|
212
212
|
<button
|
|
@@ -273,6 +273,8 @@
|
|
|
273
273
|
const DEFAULT_ENDPOINT = "http://localhost:4141/usage";
|
|
274
274
|
const DEFAULT_TOKEN_USAGE_PERIOD = "day";
|
|
275
275
|
const DEFAULT_TOKEN_USAGE_EVENTS_PAGE_SIZE = 20;
|
|
276
|
+
const ALL_METRICS_VALUE = "__all__";
|
|
277
|
+
const ALL_MODELS_VALUE = "__all__";
|
|
276
278
|
const API_KEY_STORAGE_KEY = "copilot-api.usage-viewer.x-api-key";
|
|
277
279
|
const VALID_PERIODS = new Set(["day", "week", "month"]);
|
|
278
280
|
const EMPTY_TOKEN_USAGE_TOTALS = {
|
|
@@ -291,11 +293,16 @@
|
|
|
291
293
|
isEventsLoading: false,
|
|
292
294
|
error: null,
|
|
293
295
|
data: null,
|
|
296
|
+
tokenUsageDailySummary: null,
|
|
294
297
|
tokenUsageSummary: null,
|
|
295
298
|
tokenUsageEventsPage: null,
|
|
299
|
+
tokenUsageDailyError: null,
|
|
296
300
|
tokenUsageSummaryError: null,
|
|
297
301
|
tokenUsageEventsError: null,
|
|
298
302
|
tokenUsagePeriod: DEFAULT_TOKEN_USAGE_PERIOD,
|
|
303
|
+
tokenUsageTrendDayIndex: null,
|
|
304
|
+
tokenUsageTrendMetric: ALL_METRICS_VALUE,
|
|
305
|
+
tokenUsageTrendModel: ALL_MODELS_VALUE,
|
|
299
306
|
};
|
|
300
307
|
|
|
301
308
|
// --- Rendering Logic ---
|
|
@@ -402,6 +409,14 @@
|
|
|
402
409
|
return url.toString();
|
|
403
410
|
}
|
|
404
411
|
|
|
412
|
+
function buildTokenUsageDailyUrl(usageEndpoint, period) {
|
|
413
|
+
const url = new URL(
|
|
414
|
+
deriveEndpointUrl(usageEndpoint, "token-usage/daily")
|
|
415
|
+
);
|
|
416
|
+
url.searchParams.set("period", period);
|
|
417
|
+
return url.toString();
|
|
418
|
+
}
|
|
419
|
+
|
|
405
420
|
function buildTokenUsageEventsUrl(usageEndpoint, period, page) {
|
|
406
421
|
const url = new URL(
|
|
407
422
|
deriveEndpointUrl(usageEndpoint, "token-usage/events")
|
|
@@ -503,8 +518,8 @@
|
|
|
503
518
|
function renderTokenUsageRangeText(period, range) {
|
|
504
519
|
const labels = {
|
|
505
520
|
day: "Day window",
|
|
506
|
-
week: "
|
|
507
|
-
month: "
|
|
521
|
+
week: "Last 7 days",
|
|
522
|
+
month: "Last 30 days",
|
|
508
523
|
};
|
|
509
524
|
|
|
510
525
|
if (!range) {
|
|
@@ -533,8 +548,10 @@
|
|
|
533
548
|
|| state.data
|
|
534
549
|
|| state.isTokenUsageLoading
|
|
535
550
|
|| state.isEventsLoading
|
|
551
|
+
|| state.tokenUsageDailySummary
|
|
536
552
|
|| state.tokenUsageSummary
|
|
537
553
|
|| state.tokenUsageEventsPage
|
|
554
|
+
|| state.tokenUsageDailyError
|
|
538
555
|
|| state.tokenUsageSummaryError
|
|
539
556
|
|| state.tokenUsageEventsError
|
|
540
557
|
);
|
|
@@ -692,8 +709,10 @@
|
|
|
692
709
|
const hasTokenUsageContent = Boolean(
|
|
693
710
|
state.isTokenUsageLoading
|
|
694
711
|
|| state.isEventsLoading
|
|
712
|
+
|| state.tokenUsageDailySummary
|
|
695
713
|
|| state.tokenUsageSummary
|
|
696
714
|
|| state.tokenUsageEventsPage
|
|
715
|
+
|| state.tokenUsageDailyError
|
|
697
716
|
|| state.tokenUsageSummaryError
|
|
698
717
|
|| state.tokenUsageEventsError
|
|
699
718
|
);
|
|
@@ -703,9 +722,11 @@
|
|
|
703
722
|
}
|
|
704
723
|
|
|
705
724
|
const summary = state.tokenUsageSummary;
|
|
725
|
+
const dailySummary = state.tokenUsageDailySummary;
|
|
706
726
|
const eventsPage = state.tokenUsageEventsPage;
|
|
707
727
|
const totals = summary?.totals || EMPTY_TOKEN_USAGE_TOTALS;
|
|
708
|
-
const activeRange =
|
|
728
|
+
const activeRange =
|
|
729
|
+
summary?.range || dailySummary?.range || eventsPage?.range || null;
|
|
709
730
|
const totalPages = eventsPage ? Math.max(eventsPage.total_pages, 1) : 1;
|
|
710
731
|
const eventsMeta = eventsPage
|
|
711
732
|
? `Page ${eventsPage.page} / ${totalPages} · ${formatNumber(
|
|
@@ -782,6 +803,11 @@
|
|
|
782
803
|
)}
|
|
783
804
|
</div>
|
|
784
805
|
|
|
806
|
+
${renderTokenUsageDailyTrend(
|
|
807
|
+
dailySummary,
|
|
808
|
+
state.tokenUsageDailyError
|
|
809
|
+
)}
|
|
810
|
+
|
|
785
811
|
<div class="mt-4 border" style="background-color: var(--color-bg); border-color: var(--color-bg-light-2);">
|
|
786
812
|
<div class="px-4 py-3 border-b flex items-center justify-between gap-3" style="background-color: var(--color-bg-soft); border-color: var(--color-bg-light-2);">
|
|
787
813
|
<div>
|
|
@@ -829,6 +855,321 @@
|
|
|
829
855
|
`;
|
|
830
856
|
}
|
|
831
857
|
|
|
858
|
+
function getTokenUsageTrendMetrics() {
|
|
859
|
+
return [
|
|
860
|
+
{
|
|
861
|
+
color: "var(--color-fg-lightest)",
|
|
862
|
+
key: "total_tokens",
|
|
863
|
+
label: "Total",
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
color: "var(--color-blue-accent)",
|
|
867
|
+
key: "input_tokens",
|
|
868
|
+
label: "Input",
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
color: "var(--color-green-accent)",
|
|
872
|
+
key: "output_tokens",
|
|
873
|
+
label: "Output",
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
color: "var(--color-aqua-accent)",
|
|
877
|
+
key: "cache_read_input_tokens",
|
|
878
|
+
label: "Cache Read",
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
color: "var(--color-yellow-accent)",
|
|
882
|
+
key: "cache_creation_input_tokens",
|
|
883
|
+
label: "Cache Write",
|
|
884
|
+
},
|
|
885
|
+
];
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function getDailyTrendTotals(day, selectedModel) {
|
|
889
|
+
if (selectedModel === ALL_MODELS_VALUE) {
|
|
890
|
+
return day.totals || EMPTY_TOKEN_USAGE_TOTALS;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return (
|
|
894
|
+
day.byModel?.find((model) => model.model === selectedModel)
|
|
895
|
+
|| EMPTY_TOKEN_USAGE_TOTALS
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function formatChartDate(value) {
|
|
900
|
+
const parts = String(value).split("-");
|
|
901
|
+
return parts.length === 3 ? `${parts[1]}/${parts[2]}` : value;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function renderTokenUsageDailyTrend(dailySummary, error) {
|
|
905
|
+
if (state.tokenUsagePeriod === "day") {
|
|
906
|
+
return "";
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const models = dailySummary?.byModel || [];
|
|
910
|
+
const selectedModel = models.some(
|
|
911
|
+
(model) => model.model === state.tokenUsageTrendModel
|
|
912
|
+
)
|
|
913
|
+
? state.tokenUsageTrendModel
|
|
914
|
+
: ALL_MODELS_VALUE;
|
|
915
|
+
const options = [
|
|
916
|
+
`<option value="${ALL_MODELS_VALUE}">All models</option>`,
|
|
917
|
+
...models.map(
|
|
918
|
+
(model) =>
|
|
919
|
+
`<option value="${escapeHtml(model.model)}" ${
|
|
920
|
+
selectedModel === model.model ? "selected" : ""
|
|
921
|
+
}>${escapeHtml(model.model)}</option>`
|
|
922
|
+
),
|
|
923
|
+
].join("");
|
|
924
|
+
|
|
925
|
+
return `
|
|
926
|
+
<div class="mt-4 border" style="background-color: var(--color-bg); border-color: var(--color-bg-light-2);">
|
|
927
|
+
<div class="px-4 py-3 border-b flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between" style="background-color: var(--color-bg-soft); border-color: var(--color-bg-light-2);">
|
|
928
|
+
<p class="text-sm font-semibold" style="color: var(--color-fg-lightest);">Daily Trend</p>
|
|
929
|
+
<select
|
|
930
|
+
data-token-usage-trend-model
|
|
931
|
+
class="px-3 py-1.5 border input-focus text-xs"
|
|
932
|
+
style="background-color: var(--color-bg); border-color: var(--color-bg-light-2); color: var(--color-fg-light);"
|
|
933
|
+
>${options}</select>
|
|
934
|
+
</div>
|
|
935
|
+
${
|
|
936
|
+
error
|
|
937
|
+
? `<div class="p-4">${renderError(
|
|
938
|
+
error,
|
|
939
|
+
"Token usage daily trend failed"
|
|
940
|
+
)}</div>`
|
|
941
|
+
: ""
|
|
942
|
+
}
|
|
943
|
+
${renderTokenUsageTrendChart(dailySummary, selectedModel)}
|
|
944
|
+
</div>
|
|
945
|
+
`;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function renderTokenUsageTrendChart(dailySummary, selectedModel) {
|
|
949
|
+
const days = dailySummary?.days || [];
|
|
950
|
+
const metrics = getTokenUsageTrendMetrics();
|
|
951
|
+
const visibleMetrics =
|
|
952
|
+
state.tokenUsageTrendMetric === ALL_METRICS_VALUE
|
|
953
|
+
? metrics
|
|
954
|
+
: metrics.filter(
|
|
955
|
+
(metric) => metric.key === state.tokenUsageTrendMetric
|
|
956
|
+
);
|
|
957
|
+
const hasUsage = days.some(
|
|
958
|
+
(day) => getDailyTrendTotals(day, selectedModel).request_count > 0
|
|
959
|
+
);
|
|
960
|
+
const latestUsageIndex = days.reduce((latest, day, index) => {
|
|
961
|
+
return getDailyTrendTotals(day, selectedModel).request_count > 0
|
|
962
|
+
? index
|
|
963
|
+
: latest;
|
|
964
|
+
}, -1);
|
|
965
|
+
const activeIndex =
|
|
966
|
+
Number.isInteger(state.tokenUsageTrendDayIndex)
|
|
967
|
+
&& state.tokenUsageTrendDayIndex >= 0
|
|
968
|
+
&& state.tokenUsageTrendDayIndex < days.length
|
|
969
|
+
? state.tokenUsageTrendDayIndex
|
|
970
|
+
: latestUsageIndex >= 0
|
|
971
|
+
? latestUsageIndex
|
|
972
|
+
: Math.max(0, days.length - 1);
|
|
973
|
+
const activeDay = days[activeIndex];
|
|
974
|
+
const activeTotals = activeDay
|
|
975
|
+
? getDailyTrendTotals(activeDay, selectedModel)
|
|
976
|
+
: EMPTY_TOKEN_USAGE_TOTALS;
|
|
977
|
+
|
|
978
|
+
if (state.isTokenUsageLoading && !dailySummary) {
|
|
979
|
+
return `<div class="p-4 min-h-[14rem] text-sm animate-pulse" style="color: var(--color-gray);">Loading...</div>`;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (!dailySummary || days.length === 0 || !hasUsage) {
|
|
983
|
+
return renderEmptyState(
|
|
984
|
+
"No token usage recorded for the selected period."
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const width = 720;
|
|
989
|
+
const height = 220;
|
|
990
|
+
const padding = { bottom: 34, left: 58, right: 16, top: 16 };
|
|
991
|
+
const plotWidth = width - padding.left - padding.right;
|
|
992
|
+
const plotHeight = height - padding.top - padding.bottom;
|
|
993
|
+
const allValues = days.flatMap((day) => {
|
|
994
|
+
const totals = getDailyTrendTotals(day, selectedModel);
|
|
995
|
+
return visibleMetrics.map((metric) => totals[metric.key] || 0);
|
|
996
|
+
});
|
|
997
|
+
const maxValue = Math.max(1, ...allValues);
|
|
998
|
+
const xForIndex = (index) =>
|
|
999
|
+
days.length <= 1
|
|
1000
|
+
? padding.left + plotWidth / 2
|
|
1001
|
+
: padding.left + (plotWidth * index) / (days.length - 1);
|
|
1002
|
+
const hitWidth =
|
|
1003
|
+
days.length <= 1 ? plotWidth : plotWidth / (days.length - 1);
|
|
1004
|
+
const yForValue = (value) =>
|
|
1005
|
+
padding.top
|
|
1006
|
+
+ plotHeight
|
|
1007
|
+
- (Math.max(0, value) / maxValue) * plotHeight;
|
|
1008
|
+
const labelEvery = Math.max(1, Math.ceil(days.length / 6));
|
|
1009
|
+
const yTicks = [0, 0.25, 0.5, 0.75, 1].map((ratio) =>
|
|
1010
|
+
Math.round(maxValue * ratio)
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
const grid = yTicks
|
|
1014
|
+
.map((tick) => {
|
|
1015
|
+
const y = yForValue(tick);
|
|
1016
|
+
return `
|
|
1017
|
+
<line x1="${padding.left}" x2="${
|
|
1018
|
+
width - padding.right
|
|
1019
|
+
}" y1="${y}" y2="${y}" stroke="var(--color-bg-light-2)" stroke-width="1" />
|
|
1020
|
+
<text x="${padding.left - 8}" y="${
|
|
1021
|
+
y + 4
|
|
1022
|
+
}" text-anchor="end" font-size="11" fill="var(--color-gray-accent)">${formatNumber(
|
|
1023
|
+
tick
|
|
1024
|
+
)}</text>
|
|
1025
|
+
`;
|
|
1026
|
+
})
|
|
1027
|
+
.join("");
|
|
1028
|
+
const xLabels = days
|
|
1029
|
+
.map((day, index) => {
|
|
1030
|
+
if (index % labelEvery !== 0 && index !== days.length - 1) {
|
|
1031
|
+
return "";
|
|
1032
|
+
}
|
|
1033
|
+
return `<text x="${xForIndex(index)}" y="${
|
|
1034
|
+
height - 10
|
|
1035
|
+
}" text-anchor="middle" font-size="11" fill="var(--color-gray-accent)">${escapeHtml(
|
|
1036
|
+
formatChartDate(day.date)
|
|
1037
|
+
)}</text>`;
|
|
1038
|
+
})
|
|
1039
|
+
.join("");
|
|
1040
|
+
const lines = visibleMetrics
|
|
1041
|
+
.map((metric) => {
|
|
1042
|
+
const points = days
|
|
1043
|
+
.map((day, index) => {
|
|
1044
|
+
const totals = getDailyTrendTotals(day, selectedModel);
|
|
1045
|
+
return `${xForIndex(index)},${yForValue(
|
|
1046
|
+
totals[metric.key] || 0
|
|
1047
|
+
)}`;
|
|
1048
|
+
})
|
|
1049
|
+
.join(" ");
|
|
1050
|
+
const circles = days
|
|
1051
|
+
.map((day, index) => {
|
|
1052
|
+
const totals = getDailyTrendTotals(day, selectedModel);
|
|
1053
|
+
const value = totals[metric.key] || 0;
|
|
1054
|
+
return `<circle cx="${xForIndex(index)}" cy="${yForValue(
|
|
1055
|
+
value
|
|
1056
|
+
)}" r="2.5" fill="${
|
|
1057
|
+
metric.color
|
|
1058
|
+
}" data-trend-day-index="${index}">
|
|
1059
|
+
<title>${escapeHtml(
|
|
1060
|
+
`${formatChartDate(day.date)} ${metric.label}: ${formatNumber(
|
|
1061
|
+
value
|
|
1062
|
+
)}`
|
|
1063
|
+
)}</title>
|
|
1064
|
+
</circle>`;
|
|
1065
|
+
})
|
|
1066
|
+
.join("");
|
|
1067
|
+
return `
|
|
1068
|
+
<polyline fill="none" points="${points}" stroke="${metric.color}" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.25">
|
|
1069
|
+
<title>${escapeHtml(metric.label)}</title>
|
|
1070
|
+
</polyline>
|
|
1071
|
+
${circles}
|
|
1072
|
+
`;
|
|
1073
|
+
})
|
|
1074
|
+
.join("");
|
|
1075
|
+
const activeMarker = activeDay
|
|
1076
|
+
? `<line x1="${xForIndex(activeIndex)}" x2="${xForIndex(
|
|
1077
|
+
activeIndex
|
|
1078
|
+
)}" y1="${padding.top}" y2="${
|
|
1079
|
+
padding.top + plotHeight
|
|
1080
|
+
}" stroke="var(--color-bg-light-4)" stroke-dasharray="4 4" stroke-width="1" />`
|
|
1081
|
+
: "";
|
|
1082
|
+
const hitRects = days
|
|
1083
|
+
.map((day, index) => {
|
|
1084
|
+
const x = xForIndex(index);
|
|
1085
|
+
const hitStart = Math.max(padding.left, x - hitWidth / 2);
|
|
1086
|
+
const hitEnd = Math.min(
|
|
1087
|
+
width - padding.right,
|
|
1088
|
+
x + hitWidth / 2
|
|
1089
|
+
);
|
|
1090
|
+
const totals = getDailyTrendTotals(day, selectedModel);
|
|
1091
|
+
const title = [
|
|
1092
|
+
formatChartDate(day.date),
|
|
1093
|
+
...visibleMetrics.map(
|
|
1094
|
+
(metric) =>
|
|
1095
|
+
`${metric.label}: ${formatNumber(
|
|
1096
|
+
totals[metric.key] || 0
|
|
1097
|
+
)}`
|
|
1098
|
+
),
|
|
1099
|
+
].join("\n");
|
|
1100
|
+
return `<rect x="${hitStart}" y="${padding.top}" width="${
|
|
1101
|
+
hitEnd - hitStart
|
|
1102
|
+
}" height="${plotHeight}" fill="transparent" style="cursor: pointer;" data-trend-day-index="${index}">
|
|
1103
|
+
<title>${escapeHtml(title)}</title>
|
|
1104
|
+
</rect>`;
|
|
1105
|
+
})
|
|
1106
|
+
.join("");
|
|
1107
|
+
const legend = [
|
|
1108
|
+
`<button type="button" data-trend-metric="${ALL_METRICS_VALUE}" class="inline-flex items-center border px-2 py-1 text-xs ${
|
|
1109
|
+
state.tokenUsageTrendMetric === ALL_METRICS_VALUE
|
|
1110
|
+
? "font-semibold"
|
|
1111
|
+
: ""
|
|
1112
|
+
}" style="background-color: ${
|
|
1113
|
+
state.tokenUsageTrendMetric === ALL_METRICS_VALUE
|
|
1114
|
+
? "var(--color-bg-light-1)"
|
|
1115
|
+
: "var(--color-bg)"
|
|
1116
|
+
}; border-color: var(--color-bg-light-2); color: var(--color-fg-light);">All</button>`,
|
|
1117
|
+
...metrics.map(
|
|
1118
|
+
(metric) => `
|
|
1119
|
+
<button type="button" data-trend-metric="${
|
|
1120
|
+
metric.key
|
|
1121
|
+
}" class="inline-flex items-center gap-1.5 border px-2 py-1 text-xs ${
|
|
1122
|
+
state.tokenUsageTrendMetric === metric.key
|
|
1123
|
+
? "font-semibold"
|
|
1124
|
+
: ""
|
|
1125
|
+
}" style="background-color: ${
|
|
1126
|
+
state.tokenUsageTrendMetric === metric.key
|
|
1127
|
+
? "var(--color-bg-light-1)"
|
|
1128
|
+
: "var(--color-bg)"
|
|
1129
|
+
}; border-color: var(--color-bg-light-2); color: var(--color-fg-light);">
|
|
1130
|
+
<span class="h-2 w-2 rounded-full" style="background-color: ${
|
|
1131
|
+
metric.color
|
|
1132
|
+
};"></span>
|
|
1133
|
+
${escapeHtml(metric.label)}
|
|
1134
|
+
</button>
|
|
1135
|
+
`
|
|
1136
|
+
),
|
|
1137
|
+
].join("");
|
|
1138
|
+
const activeValues = activeDay
|
|
1139
|
+
? `
|
|
1140
|
+
<div class="mt-2 flex flex-wrap gap-3 px-3 py-2 text-xs" style="background-color: var(--color-bg-soft); color: var(--color-fg-dark);">
|
|
1141
|
+
<span class="font-semibold" style="color: var(--color-fg-lightest);">${escapeHtml(
|
|
1142
|
+
formatChartDate(activeDay.date)
|
|
1143
|
+
)}</span>
|
|
1144
|
+
${visibleMetrics
|
|
1145
|
+
.map(
|
|
1146
|
+
(metric) =>
|
|
1147
|
+
`<span>${escapeHtml(metric.label)}: ${formatNumber(
|
|
1148
|
+
activeTotals[metric.key] || 0
|
|
1149
|
+
)}</span>`
|
|
1150
|
+
)
|
|
1151
|
+
.join("")}
|
|
1152
|
+
</div>
|
|
1153
|
+
`
|
|
1154
|
+
: "";
|
|
1155
|
+
|
|
1156
|
+
return `
|
|
1157
|
+
<div class="p-4 ${state.isTokenUsageLoading ? "opacity-60" : ""}">
|
|
1158
|
+
<svg class="w-full h-56" viewBox="0 0 ${width} ${height}" role="img">
|
|
1159
|
+
${grid}
|
|
1160
|
+
${xLabels}
|
|
1161
|
+
${activeMarker}
|
|
1162
|
+
${lines}
|
|
1163
|
+
${hitRects}
|
|
1164
|
+
</svg>
|
|
1165
|
+
<div class="mt-2 flex flex-wrap gap-3">
|
|
1166
|
+
${legend}
|
|
1167
|
+
</div>
|
|
1168
|
+
${activeValues}
|
|
1169
|
+
</div>
|
|
1170
|
+
`;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
832
1173
|
function renderTokenUsageMetric(label, value, accentColor) {
|
|
833
1174
|
return `
|
|
834
1175
|
<div class="p-4 border" style="background-color: var(--color-bg); border-color: var(--color-bg-light-2);">
|
|
@@ -1059,9 +1400,12 @@
|
|
|
1059
1400
|
const endpoint = endpointUrlInput.value.trim();
|
|
1060
1401
|
if (!endpoint) {
|
|
1061
1402
|
state.data = null;
|
|
1403
|
+
state.tokenUsageDailySummary = null;
|
|
1062
1404
|
state.tokenUsageSummary = null;
|
|
1063
1405
|
state.tokenUsageEventsPage = null;
|
|
1064
1406
|
state.error = "Endpoint URL cannot be empty.";
|
|
1407
|
+
state.tokenUsageDailyError =
|
|
1408
|
+
"Token usage requires a valid endpoint URL.";
|
|
1065
1409
|
state.tokenUsageSummaryError =
|
|
1066
1410
|
"Token usage requires a valid endpoint URL.";
|
|
1067
1411
|
state.tokenUsageEventsError = null;
|
|
@@ -1072,16 +1416,18 @@
|
|
|
1072
1416
|
const period = getSelectedPeriod();
|
|
1073
1417
|
state.isLoading = true;
|
|
1074
1418
|
state.error = null;
|
|
1419
|
+
state.tokenUsageDailyError = null;
|
|
1075
1420
|
state.tokenUsageSummaryError = null;
|
|
1076
1421
|
state.tokenUsageEventsError = null;
|
|
1077
1422
|
render();
|
|
1078
1423
|
|
|
1079
1424
|
try {
|
|
1080
1425
|
const usageUrl = resolveUsageUrl(endpoint);
|
|
1081
|
-
const [usageResult, summaryResult, eventsResult] =
|
|
1426
|
+
const [usageResult, summaryResult, dailyResult, eventsResult] =
|
|
1082
1427
|
await Promise.allSettled([
|
|
1083
1428
|
fetchJson(usageUrl),
|
|
1084
1429
|
fetchJson(buildTokenUsageSummaryUrl(usageUrl, period)),
|
|
1430
|
+
fetchJson(buildTokenUsageDailyUrl(usageUrl, period)),
|
|
1085
1431
|
fetchJson(buildTokenUsageEventsUrl(usageUrl, period, page)),
|
|
1086
1432
|
]);
|
|
1087
1433
|
|
|
@@ -1106,6 +1452,20 @@
|
|
|
1106
1452
|
);
|
|
1107
1453
|
}
|
|
1108
1454
|
|
|
1455
|
+
if (dailyResult.status === "fulfilled") {
|
|
1456
|
+
state.tokenUsageDailySummary = dailyResult.value;
|
|
1457
|
+
} else if (
|
|
1458
|
+
isMissingTokenUsageEndpointError(dailyResult.reason)
|
|
1459
|
+
) {
|
|
1460
|
+
state.tokenUsageDailySummary = null;
|
|
1461
|
+
state.tokenUsageDailyError = null;
|
|
1462
|
+
} else {
|
|
1463
|
+
state.tokenUsageDailySummary = null;
|
|
1464
|
+
state.tokenUsageDailyError = getErrorMessage(
|
|
1465
|
+
dailyResult.reason
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1109
1469
|
if (eventsResult.status === "fulfilled") {
|
|
1110
1470
|
state.tokenUsageEventsPage = eventsResult.value;
|
|
1111
1471
|
} else if (
|
|
@@ -1122,9 +1482,11 @@
|
|
|
1122
1482
|
} catch (error) {
|
|
1123
1483
|
console.error("Fetch error:", error);
|
|
1124
1484
|
state.data = null;
|
|
1485
|
+
state.tokenUsageDailySummary = null;
|
|
1125
1486
|
state.tokenUsageSummary = null;
|
|
1126
1487
|
state.tokenUsageEventsPage = null;
|
|
1127
1488
|
state.error = getErrorMessage(error);
|
|
1489
|
+
state.tokenUsageDailyError = getErrorMessage(error);
|
|
1128
1490
|
state.tokenUsageSummaryError = getErrorMessage(error);
|
|
1129
1491
|
state.tokenUsageEventsError = getErrorMessage(error);
|
|
1130
1492
|
} finally {
|
|
@@ -1136,8 +1498,11 @@
|
|
|
1136
1498
|
async function fetchTokenUsageSummaryAndEvents(page = 1) {
|
|
1137
1499
|
const endpoint = endpointUrlInput.value.trim();
|
|
1138
1500
|
if (!endpoint) {
|
|
1501
|
+
state.tokenUsageDailySummary = null;
|
|
1139
1502
|
state.tokenUsageSummary = null;
|
|
1140
1503
|
state.tokenUsageEventsPage = null;
|
|
1504
|
+
state.tokenUsageDailyError =
|
|
1505
|
+
"Token usage requires a valid endpoint URL.";
|
|
1141
1506
|
state.tokenUsageSummaryError =
|
|
1142
1507
|
"Token usage requires a valid endpoint URL.";
|
|
1143
1508
|
state.tokenUsageEventsError = null;
|
|
@@ -1147,18 +1512,22 @@
|
|
|
1147
1512
|
|
|
1148
1513
|
const period = getSelectedPeriod();
|
|
1149
1514
|
state.isTokenUsageLoading = true;
|
|
1515
|
+
state.tokenUsageDailySummary = null;
|
|
1150
1516
|
state.tokenUsageSummary = null;
|
|
1151
1517
|
state.tokenUsageEventsPage = null;
|
|
1518
|
+
state.tokenUsageDailyError = null;
|
|
1152
1519
|
state.tokenUsageSummaryError = null;
|
|
1153
1520
|
state.tokenUsageEventsError = null;
|
|
1154
1521
|
render();
|
|
1155
1522
|
|
|
1156
1523
|
try {
|
|
1157
1524
|
const usageUrl = resolveUsageUrl(endpoint);
|
|
1158
|
-
const [summaryResult, eventsResult] =
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1525
|
+
const [summaryResult, dailyResult, eventsResult] =
|
|
1526
|
+
await Promise.allSettled([
|
|
1527
|
+
fetchJson(buildTokenUsageSummaryUrl(usageUrl, period)),
|
|
1528
|
+
fetchJson(buildTokenUsageDailyUrl(usageUrl, period)),
|
|
1529
|
+
fetchJson(buildTokenUsageEventsUrl(usageUrl, period, page)),
|
|
1530
|
+
]);
|
|
1162
1531
|
|
|
1163
1532
|
if (summaryResult.status === "fulfilled") {
|
|
1164
1533
|
state.tokenUsageSummary = summaryResult.value;
|
|
@@ -1174,6 +1543,20 @@
|
|
|
1174
1543
|
);
|
|
1175
1544
|
}
|
|
1176
1545
|
|
|
1546
|
+
if (dailyResult.status === "fulfilled") {
|
|
1547
|
+
state.tokenUsageDailySummary = dailyResult.value;
|
|
1548
|
+
} else if (
|
|
1549
|
+
isMissingTokenUsageEndpointError(dailyResult.reason)
|
|
1550
|
+
) {
|
|
1551
|
+
state.tokenUsageDailySummary = null;
|
|
1552
|
+
state.tokenUsageDailyError = null;
|
|
1553
|
+
} else {
|
|
1554
|
+
state.tokenUsageDailySummary = null;
|
|
1555
|
+
state.tokenUsageDailyError = getErrorMessage(
|
|
1556
|
+
dailyResult.reason
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1177
1560
|
if (eventsResult.status === "fulfilled") {
|
|
1178
1561
|
state.tokenUsageEventsPage = eventsResult.value;
|
|
1179
1562
|
} else if (
|
|
@@ -1189,8 +1572,10 @@
|
|
|
1189
1572
|
}
|
|
1190
1573
|
} catch (error) {
|
|
1191
1574
|
console.error("Token usage fetch error:", error);
|
|
1575
|
+
state.tokenUsageDailySummary = null;
|
|
1192
1576
|
state.tokenUsageSummary = null;
|
|
1193
1577
|
state.tokenUsageEventsPage = null;
|
|
1578
|
+
state.tokenUsageDailyError = getErrorMessage(error);
|
|
1194
1579
|
state.tokenUsageSummaryError = getErrorMessage(error);
|
|
1195
1580
|
state.tokenUsageEventsError = getErrorMessage(error);
|
|
1196
1581
|
} finally {
|
|
@@ -1254,6 +1639,28 @@
|
|
|
1254
1639
|
return;
|
|
1255
1640
|
}
|
|
1256
1641
|
|
|
1642
|
+
const trendMetric = event.target.closest("[data-trend-metric]");
|
|
1643
|
+
if (trendMetric) {
|
|
1644
|
+
state.tokenUsageTrendMetric =
|
|
1645
|
+
trendMetric.getAttribute("data-trend-metric")
|
|
1646
|
+
|| ALL_METRICS_VALUE;
|
|
1647
|
+
render();
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
const trendPoint = event.target.closest("[data-trend-day-index]");
|
|
1652
|
+
if (trendPoint) {
|
|
1653
|
+
const index = Number.parseInt(
|
|
1654
|
+
trendPoint.getAttribute("data-trend-day-index") || "",
|
|
1655
|
+
10
|
|
1656
|
+
);
|
|
1657
|
+
if (Number.isFinite(index) && index >= 0) {
|
|
1658
|
+
state.tokenUsageTrendDayIndex = index;
|
|
1659
|
+
render();
|
|
1660
|
+
}
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1257
1664
|
const actionButton = event.target.closest("[data-page-action]");
|
|
1258
1665
|
if (!actionButton || !state.tokenUsageEventsPage || state.isEventsLoading) {
|
|
1259
1666
|
return;
|
|
@@ -1276,6 +1683,20 @@
|
|
|
1276
1683
|
}
|
|
1277
1684
|
}
|
|
1278
1685
|
|
|
1686
|
+
function handleContentAreaChange(event) {
|
|
1687
|
+
if (!(event.target instanceof HTMLSelectElement)) {
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
if (!event.target.matches("[data-token-usage-trend-model]")) {
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
state.tokenUsageTrendModel =
|
|
1696
|
+
event.target.value || ALL_MODELS_VALUE;
|
|
1697
|
+
render();
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1279
1700
|
/**
|
|
1280
1701
|
* Initializes the application.
|
|
1281
1702
|
*/
|
|
@@ -1286,6 +1707,7 @@
|
|
|
1286
1707
|
storeApiKey(apiKeyInput.value);
|
|
1287
1708
|
});
|
|
1288
1709
|
contentArea.addEventListener("click", handleContentAreaClick);
|
|
1710
|
+
contentArea.addEventListener("change", handleContentAreaChange);
|
|
1289
1711
|
|
|
1290
1712
|
apiKeyInput.value = loadStoredApiKey();
|
|
1291
1713
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-De0Po8kG.js","names":["get"],"sources":["../src/lib/config.ts","../src/lib/proxy.ts"],"sourcesContent":["import consola from \"consola\"\nimport fs from \"node:fs\"\n\nimport { PATHS } from \"./paths\"\n\nexport interface AppConfig {\n auth?: {\n apiKeys?: Array<string>\n }\n providers?: Record<string, ProviderConfig>\n extraPrompts?: Record<string, string>\n smallModel?: string\n responsesApiContextManagementModels?: Array<string>\n modelReasoningEfforts?: Record<\n string,\n \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"\n >\n useFunctionApplyPatch?: boolean\n useMessagesApi?: boolean\n useResponsesApiWebSocket?: boolean\n anthropicApiKey?: string\n useResponsesApiWebSearch?: boolean\n claudeTokenMultiplier?: number\n}\n\nexport interface ModelConfig {\n temperature?: number\n topP?: number\n topK?: number\n extraBody?: Record<string, unknown>\n contextCache?: boolean\n supportPdf?: boolean\n toolContentSupportType?: Array<ToolContentSupportType>\n}\n\nexport type ProviderAuthType = \"authorization\" | \"x-api-key\"\nexport type ProviderType = \"anthropic\" | \"openai-compatible\"\nexport type ToolContentSupportType = \"array\" | \"image\" | \"pdf\"\n\nexport interface ProviderConfig {\n type?: string\n enabled?: boolean\n baseUrl?: string\n apiKey?: string\n authType?: ProviderAuthType\n models?: Record<string, ModelConfig>\n adjustInputTokens?: boolean\n}\n\nexport interface ResolvedProviderConfig {\n name: string\n type: ProviderType\n baseUrl: string\n apiKey: string\n authType: ProviderAuthType\n models?: Record<string, ModelConfig>\n adjustInputTokens?: boolean\n}\n\nconst gpt5ExplorationPrompt = `## Exploration and reading files\n- **Think first.** Before any tool call, decide ALL files/resources you will need.\n- **Batch everything.** If you need multiple files (even from different places), read them together.\n- **multi_tool_use.parallel** Use multi_tool_use.parallel to parallelize tool calls and only this.\n- **Only make sequential calls if you truly cannot know the next file without seeing a result first.**\n- **Workflow:** (a) plan all needed reads → (b) issue one parallel batch → (c) analyze results → (d) repeat if new, unpredictable reads arise.`\n\nconst gpt5CommentaryPrompt = `# Working with the user\n\nYou interact with the user through a terminal. You have 2 ways of communicating with the users: \n- Share intermediary updates in \\`commentary\\` channel. \n- After you have completed all your work, send a message to the \\`final\\` channel. \n\n## Intermediary updates\n\n- Intermediary updates go to the \\`commentary\\` channel.\n- User updates are short updates while you are working, they are NOT final answers.\n- You use 1-2 sentence user updates to communicate progress and new information to the user as you are doing work.\n- Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements (“Done —”, “Got it”, “Great question, ”) or framing phrases.\n- You provide user updates frequently, every 20s.\n- Before exploring or doing substantial work, you start with a user update acknowledging the request and explaining your first step. You should include your understanding of the user request and explain what you will do. Avoid commenting on the request or using starters such as \"Got it -\" or \"Understood -\" etc.\n- When exploring, e.g. searching, reading files, you provide user updates as you go, every 20s, explaining what context you are gathering and what you've learned. Vary your sentence structure when providing these updates to avoid sounding repetitive - in particular, don't start each sentence the same way.\n- After you have sufficient context, and the work is substantial, you provide a longer plan (this is the only user update that may be longer than 2 sentences and can contain formatting).\n- Before performing file edits of any kind, you provide updates explaining what edits you are making.\n- As you are thinking, you very frequently provide updates even if not taking any actions, informing the user of your progress. You interrupt your thinking and send multiple updates in a row if thinking for more than 100 words.\n- Tone of your updates MUST match your personality.`\n\nconst defaultConfig: AppConfig = {\n auth: {\n apiKeys: [],\n },\n providers: {},\n extraPrompts: {\n \"gpt-5-mini\": gpt5ExplorationPrompt,\n \"gpt-5.3-codex\": gpt5CommentaryPrompt,\n \"gpt-5.4-mini\": gpt5CommentaryPrompt,\n \"gpt-5.4\": gpt5CommentaryPrompt,\n \"gpt-5.5\": gpt5CommentaryPrompt,\n },\n smallModel: \"gpt-5-mini\",\n responsesApiContextManagementModels: [],\n modelReasoningEfforts: {\n \"gpt-5-mini\": \"low\",\n \"gpt-5.3-codex\": \"xhigh\",\n \"gpt-5.4-mini\": \"xhigh\",\n \"gpt-5.4\": \"xhigh\",\n \"gpt-5.5\": \"xhigh\",\n },\n useFunctionApplyPatch: true,\n useMessagesApi: true,\n useResponsesApiWebSocket: true,\n useResponsesApiWebSearch: true,\n}\n\nlet cachedConfig: AppConfig | null = null\n\nfunction ensureConfigFile(): void {\n try {\n fs.accessSync(PATHS.CONFIG_PATH, fs.constants.R_OK | fs.constants.W_OK)\n } catch {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n try {\n fs.chmodSync(PATHS.CONFIG_PATH, 0o600)\n } catch {\n return\n }\n }\n}\n\nfunction readConfigFromDisk(): AppConfig {\n ensureConfigFile()\n try {\n const raw = fs.readFileSync(PATHS.CONFIG_PATH, \"utf8\")\n if (!raw.trim()) {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n return defaultConfig\n }\n return JSON.parse(raw) as AppConfig\n } catch (error) {\n consola.error(\"Failed to read config file, using default config\", error)\n return defaultConfig\n }\n}\n\nfunction mergeDefaultConfig(config: AppConfig): {\n mergedConfig: AppConfig\n changed: boolean\n} {\n const extraPrompts = config.extraPrompts ?? {}\n const defaultExtraPrompts = defaultConfig.extraPrompts ?? {}\n const modelReasoningEfforts = config.modelReasoningEfforts ?? {}\n const defaultModelReasoningEfforts = defaultConfig.modelReasoningEfforts ?? {}\n\n const missingExtraPromptModels = Object.keys(defaultExtraPrompts).filter(\n (model) => !Object.hasOwn(extraPrompts, model),\n )\n\n const missingReasoningEffortModels = Object.keys(\n defaultModelReasoningEfforts,\n ).filter((model) => !Object.hasOwn(modelReasoningEfforts, model))\n\n const hasExtraPromptChanges = missingExtraPromptModels.length > 0\n const hasReasoningEffortChanges = missingReasoningEffortModels.length > 0\n\n if (!hasExtraPromptChanges && !hasReasoningEffortChanges) {\n return { mergedConfig: config, changed: false }\n }\n\n return {\n mergedConfig: {\n ...config,\n extraPrompts: {\n ...defaultExtraPrompts,\n ...extraPrompts,\n },\n modelReasoningEfforts: {\n ...defaultModelReasoningEfforts,\n ...modelReasoningEfforts,\n },\n },\n changed: true,\n }\n}\n\nexport function mergeConfigWithDefaults(): AppConfig {\n const config = readConfigFromDisk()\n const { mergedConfig, changed } = mergeDefaultConfig(config)\n\n if (changed) {\n try {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(mergedConfig, null, 2)}\\n`,\n \"utf8\",\n )\n } catch (writeError) {\n consola.warn(\n \"Failed to write merged extraPrompts to config file\",\n writeError,\n )\n }\n }\n\n cachedConfig = mergedConfig\n return mergedConfig\n}\n\nexport function getConfig(): AppConfig {\n cachedConfig ??= mergeDefaultConfig(readConfigFromDisk()).mergedConfig\n return cachedConfig\n}\n\nexport function getExtraPromptForModel(model: string): string {\n const config = getConfig()\n return config.extraPrompts?.[model] ?? \"\"\n}\n\nexport function getSmallModel(): string {\n const config = getConfig()\n return config.smallModel ?? \"gpt-5-mini\"\n}\n\nexport function getResponsesApiContextManagementModels(): Array<string> {\n const config = getConfig()\n return (\n config.responsesApiContextManagementModels\n ?? defaultConfig.responsesApiContextManagementModels\n ?? []\n )\n}\n\nexport function isResponsesApiContextManagementModel(model: string): boolean {\n return getResponsesApiContextManagementModels().includes(model)\n}\n\nexport function getReasoningEffortForModel(\n model: string,\n): \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" {\n const config = getConfig()\n return config.modelReasoningEfforts?.[model] ?? \"high\"\n}\n\nexport function normalizeProviderBaseUrl(url: string): string {\n return url.trim().replace(/\\/+$/u, \"\")\n}\n\nfunction getDefaultProviderAuthType(\n providerType: ProviderType,\n): ProviderAuthType {\n return providerType === \"openai-compatible\" ? \"authorization\" : \"x-api-key\"\n}\n\nexport function resolveProviderAuthType(\n providerName: string,\n authType: string | undefined,\n providerType: ProviderType,\n): ProviderAuthType {\n if (authType === undefined) {\n return getDefaultProviderAuthType(providerType)\n }\n\n if (authType === \"x-api-key\") {\n return \"x-api-key\"\n }\n\n if (authType === \"authorization\") {\n return authType\n }\n\n consola.warn(\n `Provider ${providerName} has invalid authType '${authType}', falling back to ${getDefaultProviderAuthType(providerType)}`,\n )\n return getDefaultProviderAuthType(providerType)\n}\n\nexport function getProviderConfig(name: string): ResolvedProviderConfig | null {\n const providerName = name.trim()\n if (!providerName) {\n return null\n }\n\n const config = getConfig()\n const provider = config.providers?.[providerName]\n if (!provider) {\n return null\n }\n\n if (provider.enabled === false) {\n return null\n }\n\n const type = provider.type ?? \"anthropic\"\n if (type !== \"anthropic\" && type !== \"openai-compatible\") {\n consola.warn(\n `Provider ${providerName} is ignored because type '${type}' is not supported`,\n )\n return null\n }\n\n const baseUrl = normalizeProviderBaseUrl(provider.baseUrl ?? \"\")\n const apiKey = (provider.apiKey ?? \"\").trim()\n const authType = resolveProviderAuthType(\n providerName,\n provider.authType,\n type,\n )\n if (!baseUrl || !apiKey) {\n consola.warn(\n `Provider ${providerName} is enabled but missing baseUrl or apiKey`,\n )\n return null\n }\n\n return {\n name: providerName,\n type,\n baseUrl,\n apiKey,\n authType,\n models: provider.models,\n adjustInputTokens: provider.adjustInputTokens,\n }\n}\n\nexport function listEnabledProviders(): Array<string> {\n const config = getConfig()\n const providerNames = Object.keys(config.providers ?? {})\n return providerNames.filter((name) => getProviderConfig(name) !== null)\n}\n\nexport function isMessagesApiEnabled(): boolean {\n const config = getConfig()\n return config.useMessagesApi ?? true\n}\n\nexport function isResponsesApiWebSocketEnabled(): boolean {\n const config = getConfig()\n return config.useResponsesApiWebSocket ?? true\n}\n\nexport function getAnthropicApiKey(): string | undefined {\n const config = getConfig()\n return config.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY ?? undefined\n}\n\nexport function isResponsesApiWebSearchEnabled(): boolean {\n const config = getConfig()\n return config.useResponsesApiWebSearch ?? true\n}\n\nexport function getClaudeTokenMultiplier(): number {\n const config = getConfig()\n return config.claudeTokenMultiplier ?? 1.15\n}\n","import consola from \"consola\"\nimport { getProxyForUrl } from \"proxy-from-env\"\nimport { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from \"undici\"\n\nlet proxyEnvDispatcher: Dispatcher | undefined\n\nexport function getProxyEnvDispatcher(): Dispatcher | undefined {\n return proxyEnvDispatcher\n}\n\nexport function initProxyFromEnv(): void {\n try {\n const direct = new Agent()\n const proxies = new Map<string, ProxyAgent>()\n\n // We only need a minimal dispatcher that implements `dispatch` at runtime.\n // Typing the object as `Dispatcher` forces TypeScript to require many\n // additional methods. Instead, keep a plain object and cast when passing\n // to `setGlobalDispatcher`.\n const dispatcher = {\n dispatch(\n options: Dispatcher.DispatchOptions,\n handler: Dispatcher.DispatchHandler,\n ) {\n try {\n const origin =\n typeof options.origin === \"string\" ?\n new URL(options.origin)\n : (options.origin as URL)\n const get = getProxyForUrl as unknown as (\n u: string,\n ) => string | undefined\n const raw = get(origin.toString())\n const proxyUrl = raw && raw.length > 0 ? raw : undefined\n if (!proxyUrl) {\n consola.debug(`HTTP proxy bypass: ${origin.hostname}`)\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n let agent = proxies.get(proxyUrl)\n if (!agent) {\n agent = new ProxyAgent(proxyUrl)\n proxies.set(proxyUrl, agent)\n }\n let label = proxyUrl\n try {\n const u = new URL(proxyUrl)\n label = `${u.protocol}//${u.host}`\n } catch {\n /* noop */\n }\n consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`)\n return (agent as unknown as Dispatcher).dispatch(options, handler)\n } catch {\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n },\n close() {\n return direct.close()\n },\n destroy() {\n return direct.destroy()\n },\n }\n\n proxyEnvDispatcher = dispatcher as unknown as Dispatcher\n\n if (typeof Bun !== \"undefined\") {\n consola.debug(\"WebSocket proxy configured from environment (per-URL)\")\n return\n }\n\n setGlobalDispatcher(proxyEnvDispatcher)\n consola.debug(\"HTTP proxy configured from environment (per-URL)\")\n } catch (err) {\n consola.debug(\"Proxy setup skipped:\", err)\n }\n}\n"],"mappings":"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"}
|