@tonyclaw/agent-inspector 2.0.24 → 2.0.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/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-DrY-gAzy.js → CompareDrawer-DBTFzxG5.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-ivZk8MgE.js +115 -0
- package/.output/public/assets/{ReplayDialog-CFyw6Vu4.js → ReplayDialog-syW2hDNE.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-DHGJ9py7.js → RequestAnatomy-BkuRtY9o.js} +1 -1
- package/.output/public/assets/{ResponseView-CB_3yc08.js → ResponseView-BwyvEvoE.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-CqhHwgDn.js → StreamingChunkSequence-BY4MbPKg.js} +1 -1
- package/.output/public/assets/_sessionId-XlPUgIIb.js +1 -0
- package/.output/public/assets/index-By11a28-.js +1 -0
- package/.output/public/assets/index-DsiKfWCp.css +1 -0
- package/.output/public/assets/{main-CVw7-JyE.js → main-DKRDRBdd.js} +2 -2
- package/.output/server/{_sessionId-V33sBWBC.mjs → _sessionId-VDd4N_1B.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-C_Fk9joM.mjs → CompareDrawer-BQZlxsOC.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-CyFKksQa.mjs → ProxyViewerContainer-njY2oQCc.mjs} +208 -22
- package/.output/server/_ssr/{ReplayDialog-BpQHN7O2.mjs → ReplayDialog-B8lkdAIh.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-CuhmKAma.mjs → RequestAnatomy-CwOTfdwS.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-CO6LyBmj.mjs → ResponseView-3Iz_mZpd.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-DLeVetNR.mjs → StreamingChunkSequence-hmJcQNW5.mjs} +2 -2
- package/.output/server/_ssr/{index-DWUKl9Bf.mjs → index-UuTKM4aC.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-BrK_DcTS.mjs → router-C7InHrxE.mjs} +352 -10
- package/.output/server/{_tanstack-start-manifest_v-BjW2XeWE.mjs → _tanstack-start-manifest_v-B6yfnMHA.mjs} +1 -1
- package/.output/server/index.mjs +58 -58
- package/package.json +1 -1
- package/src/components/providers/ProviderForm.tsx +176 -16
- package/src/lib/sessionInfoContract.ts +69 -0
- package/src/lib/utils.ts +46 -0
- package/src/mcp/server.ts +26 -0
- package/src/mcp/toolHandlers.ts +27 -0
- package/src/proxy/sessionInfo.ts +222 -0
- package/src/proxy/sessionSupervisor.ts +8 -5
- package/src/proxy/store.ts +77 -0
- package/src/routes/api/sessions.ts +29 -2
- package/.output/public/assets/ProxyViewerContainer-CiLvRNie.js +0 -115
- package/.output/public/assets/_sessionId-CgG9CQ9r.js +0 -1
- package/.output/public/assets/index-780zTVwp.css +0 -1
- package/.output/public/assets/index-CViEXkqa.js +0 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import type { SessionInfo, SessionLogSummary, SessionTokenUsage } from "../lib/sessionInfoContract";
|
|
2
|
+
import { getSessionPath } from "../lib/sessionUrl";
|
|
3
|
+
import type { CapturedLog } from "./schemas";
|
|
4
|
+
import type { SessionSnapshot } from "./sessionSupervisor";
|
|
5
|
+
|
|
6
|
+
export const SESSION_INFO_DEFAULT_LATEST_LOG_LIMIT = 10;
|
|
7
|
+
export const SESSION_INFO_MAX_LATEST_LOG_LIMIT = 50;
|
|
8
|
+
|
|
9
|
+
type SessionStatus = SessionInfo["status"];
|
|
10
|
+
|
|
11
|
+
type BuildSessionInfoInput = {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
snapshot: SessionSnapshot | null;
|
|
15
|
+
summaries: readonly SessionLogSummary[];
|
|
16
|
+
logCoverage: SessionInfo["logCoverage"];
|
|
17
|
+
latestLogLimit?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function clampSessionInfoLogLimit(input: number | undefined): number {
|
|
21
|
+
if (input === undefined) return SESSION_INFO_DEFAULT_LATEST_LOG_LIMIT;
|
|
22
|
+
if (!Number.isFinite(input) || input < 1) return 1;
|
|
23
|
+
return Math.min(Math.floor(input), SESSION_INFO_MAX_LATEST_LOG_LIMIT);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeString(value: string | null | undefined): string | null {
|
|
27
|
+
if (value === null || value === undefined || value.length === 0) return null;
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function truncateError(value: string | null | undefined): string | null {
|
|
32
|
+
const normalized = normalizeString(value);
|
|
33
|
+
if (normalized === null) return null;
|
|
34
|
+
if (normalized.length <= 500) return normalized;
|
|
35
|
+
return `${normalized.slice(0, 500)}...`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hasLogError(log: CapturedLog): boolean {
|
|
39
|
+
const error = normalizeString(log.error);
|
|
40
|
+
if (error !== null) return true;
|
|
41
|
+
return log.responseStatus !== null && log.responseStatus >= 400;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function buildSessionLogSummary(log: CapturedLog): SessionLogSummary {
|
|
45
|
+
return {
|
|
46
|
+
id: log.id,
|
|
47
|
+
timestamp: log.timestamp,
|
|
48
|
+
provider: normalizeString(log.providerName),
|
|
49
|
+
model: normalizeString(log.model),
|
|
50
|
+
apiFormat: log.apiFormat,
|
|
51
|
+
method: log.method,
|
|
52
|
+
path: log.path,
|
|
53
|
+
status: log.responseStatus,
|
|
54
|
+
isStreaming: log.streaming,
|
|
55
|
+
hasError: hasLogError(log),
|
|
56
|
+
error: truncateError(log.error),
|
|
57
|
+
latencyMs: log.elapsedMs,
|
|
58
|
+
tokens: {
|
|
59
|
+
input: log.inputTokens,
|
|
60
|
+
output: log.outputTokens,
|
|
61
|
+
cacheCreate: log.cacheCreationInputTokens,
|
|
62
|
+
cacheRead: log.cacheReadInputTokens,
|
|
63
|
+
},
|
|
64
|
+
sessionId: normalizeString(log.sessionId),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function nonNegativeInteger(value: number | null): number {
|
|
69
|
+
if (value === null || !Number.isFinite(value) || value < 0) return 0;
|
|
70
|
+
return Math.floor(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function addTokenUsage(total: SessionTokenUsage, summary: SessionLogSummary): SessionTokenUsage {
|
|
74
|
+
const input = nonNegativeInteger(summary.tokens.input);
|
|
75
|
+
const output = nonNegativeInteger(summary.tokens.output);
|
|
76
|
+
const cacheCreate = nonNegativeInteger(summary.tokens.cacheCreate);
|
|
77
|
+
const cacheRead = nonNegativeInteger(summary.tokens.cacheRead);
|
|
78
|
+
return {
|
|
79
|
+
input: total.input + input,
|
|
80
|
+
output: total.output + output,
|
|
81
|
+
cacheCreate: total.cacheCreate + cacheCreate,
|
|
82
|
+
cacheRead: total.cacheRead + cacheRead,
|
|
83
|
+
total: total.total + input + output + cacheCreate + cacheRead,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildRelativePath(pathname: string, params: URLSearchParams): string {
|
|
88
|
+
const query = params.toString();
|
|
89
|
+
return query.length > 0 ? `${pathname}?${query}` : pathname;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildAbsoluteUrl(baseUrl: string, path: string): string {
|
|
93
|
+
try {
|
|
94
|
+
return new URL(path, baseUrl).toString();
|
|
95
|
+
} catch {
|
|
96
|
+
return path;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildApiPath(sessionId: string): string {
|
|
101
|
+
return buildRelativePath("/api/sessions", new URLSearchParams({ sessionId }));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildCompactLogsPath(sessionId: string): string {
|
|
105
|
+
return buildRelativePath("/api/logs", new URLSearchParams({ compact: "1", sessionId }));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function collectSortedValues(values: readonly (string | null)[]): string[] {
|
|
109
|
+
const set = new Set<string>();
|
|
110
|
+
for (const value of values) {
|
|
111
|
+
if (value !== null && value.length > 0) {
|
|
112
|
+
set.add(value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return [...set].sort();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function countCompletedLogs(summaries: readonly SessionLogSummary[]): number {
|
|
119
|
+
let count = 0;
|
|
120
|
+
for (const summary of summaries) {
|
|
121
|
+
if (summary.status !== null || summary.latencyMs !== null) {
|
|
122
|
+
count += 1;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return count;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function countErrorLogs(summaries: readonly SessionLogSummary[]): number {
|
|
129
|
+
let count = 0;
|
|
130
|
+
for (const summary of summaries) {
|
|
131
|
+
if (summary.hasError) {
|
|
132
|
+
count += 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return count;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function deriveSessionStatus(input: {
|
|
139
|
+
activeRequests: number;
|
|
140
|
+
runningTasks: number;
|
|
141
|
+
failedRequests: number;
|
|
142
|
+
errorCount: number;
|
|
143
|
+
lastTaskError: string | null;
|
|
144
|
+
completedRequests: number;
|
|
145
|
+
logCount: number;
|
|
146
|
+
}): SessionStatus {
|
|
147
|
+
if (input.activeRequests > 0 || input.runningTasks > 0) return "active";
|
|
148
|
+
if (input.failedRequests > 0 || input.errorCount > 0 || input.lastTaskError !== null) {
|
|
149
|
+
return "failed";
|
|
150
|
+
}
|
|
151
|
+
if (input.completedRequests > 0 || input.logCount > 0) return "completed";
|
|
152
|
+
return "empty";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function buildSessionInfo(input: BuildSessionInfoInput): SessionInfo {
|
|
156
|
+
const latestLogLimit = clampSessionInfoLogLimit(input.latestLogLimit);
|
|
157
|
+
const summaries = [...input.summaries].sort((left, right) => right.id - left.id);
|
|
158
|
+
const latestLog = summaries[0] ?? null;
|
|
159
|
+
const oldestLog = summaries.length > 0 ? summaries[summaries.length - 1] : null;
|
|
160
|
+
const logCount =
|
|
161
|
+
input.logCoverage === "history"
|
|
162
|
+
? summaries.length
|
|
163
|
+
: Math.max(input.snapshot?.requestCount ?? 0, summaries.length);
|
|
164
|
+
const errorCount = Math.max(countErrorLogs(summaries), input.snapshot?.failedRequests ?? 0);
|
|
165
|
+
const completedLogCount = countCompletedLogs(summaries);
|
|
166
|
+
const tokenUsage = summaries.reduce<SessionTokenUsage>(
|
|
167
|
+
(total, summary) => addTokenUsage(total, summary),
|
|
168
|
+
{ input: 0, output: 0, cacheCreate: 0, cacheRead: 0, total: 0 },
|
|
169
|
+
);
|
|
170
|
+
const requestCount = Math.max(input.snapshot?.requestCount ?? 0, logCount);
|
|
171
|
+
const activeRequests = input.snapshot?.activeRequests ?? 0;
|
|
172
|
+
const completedRequests = Math.max(input.snapshot?.completedRequests ?? 0, completedLogCount);
|
|
173
|
+
const failedRequests = Math.max(input.snapshot?.failedRequests ?? 0, errorCount);
|
|
174
|
+
const queuedTasks = input.snapshot?.queuedTasks ?? 0;
|
|
175
|
+
const runningTasks = input.snapshot?.runningTasks ?? 0;
|
|
176
|
+
const lastTaskError = normalizeString(input.snapshot?.lastTaskError);
|
|
177
|
+
const inspectorPath = getSessionPath(input.sessionId);
|
|
178
|
+
const apiPath = buildApiPath(input.sessionId);
|
|
179
|
+
const compactLogsPath = buildCompactLogsPath(input.sessionId);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
id: input.sessionId,
|
|
183
|
+
status: deriveSessionStatus({
|
|
184
|
+
activeRequests,
|
|
185
|
+
runningTasks,
|
|
186
|
+
failedRequests,
|
|
187
|
+
errorCount,
|
|
188
|
+
lastTaskError,
|
|
189
|
+
completedRequests,
|
|
190
|
+
logCount,
|
|
191
|
+
}),
|
|
192
|
+
logCoverage: input.logCoverage,
|
|
193
|
+
source: input.snapshot?.source ?? null,
|
|
194
|
+
runtimeMode: input.snapshot?.runtimeMode ?? null,
|
|
195
|
+
createdAt: input.snapshot?.createdAt ?? oldestLog?.timestamp ?? null,
|
|
196
|
+
updatedAt: input.snapshot?.updatedAt ?? latestLog?.timestamp ?? null,
|
|
197
|
+
requestCount,
|
|
198
|
+
activeRequests,
|
|
199
|
+
completedRequests,
|
|
200
|
+
failedRequests,
|
|
201
|
+
queuedTasks,
|
|
202
|
+
runningTasks,
|
|
203
|
+
lastTaskError,
|
|
204
|
+
logCount,
|
|
205
|
+
errorCount,
|
|
206
|
+
models: collectSortedValues(summaries.map((summary) => summary.model)),
|
|
207
|
+
providers: collectSortedValues(summaries.map((summary) => summary.provider)),
|
|
208
|
+
tokenUsage,
|
|
209
|
+
lastLogId: input.snapshot?.lastLogId ?? latestLog?.id ?? null,
|
|
210
|
+
lastModel: normalizeString(input.snapshot?.lastModel) ?? latestLog?.model ?? null,
|
|
211
|
+
clientPid: input.snapshot?.clientPid ?? null,
|
|
212
|
+
clientProjectFolder: normalizeString(input.snapshot?.clientProjectFolder),
|
|
213
|
+
inspectorPath,
|
|
214
|
+
inspectorUrl: buildAbsoluteUrl(input.baseUrl, inspectorPath),
|
|
215
|
+
apiPath,
|
|
216
|
+
apiUrl: buildAbsoluteUrl(input.baseUrl, apiPath),
|
|
217
|
+
compactLogsPath,
|
|
218
|
+
compactLogsUrl: buildAbsoluteUrl(input.baseUrl, compactLogsPath),
|
|
219
|
+
latestLogLimit,
|
|
220
|
+
latestLogs: summaries.slice(0, latestLogLimit),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -159,11 +159,14 @@ function upsertSession(
|
|
|
159
159
|
|
|
160
160
|
const existing = sessions.get(identity.id);
|
|
161
161
|
const session = existing ?? createSessionRecord(identity.id, source, log);
|
|
162
|
-
session.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
const isSameOrNewerLog = session.lastLogId === null || log.id >= session.lastLogId;
|
|
163
|
+
if (isSameOrNewerLog) {
|
|
164
|
+
session.updatedAt = new Date().toISOString();
|
|
165
|
+
session.lastLogId = log.id;
|
|
166
|
+
session.lastModel = log.model ?? session.lastModel;
|
|
167
|
+
session.clientPid = log.clientPid ?? session.clientPid;
|
|
168
|
+
session.clientProjectFolder = log.clientProjectFolder ?? session.clientProjectFolder;
|
|
169
|
+
}
|
|
167
170
|
|
|
168
171
|
sessions.set(identity.id, session);
|
|
169
172
|
return session;
|
package/src/proxy/store.ts
CHANGED
|
@@ -30,11 +30,14 @@ import {
|
|
|
30
30
|
type LogIndexEntry,
|
|
31
31
|
} from "./logIndex";
|
|
32
32
|
import { writeChunks } from "./chunkStorage";
|
|
33
|
+
import type { SessionInfo, SessionLogSummary } from "../lib/sessionInfoContract";
|
|
34
|
+
import { buildSessionInfo, buildSessionLogSummary } from "./sessionInfo";
|
|
33
35
|
import type { CapturedLog } from "./schemas";
|
|
34
36
|
import { CapturedLogSchema } from "./schemas";
|
|
35
37
|
import {
|
|
36
38
|
clearSessionRegistry,
|
|
37
39
|
getLogSessionId,
|
|
40
|
+
getSessionSnapshots,
|
|
38
41
|
getSessionIds,
|
|
39
42
|
markSessionFinished,
|
|
40
43
|
markSessionStarted,
|
|
@@ -42,6 +45,7 @@ import {
|
|
|
42
45
|
rebuildSessionRegistry,
|
|
43
46
|
resolveSessionIdentity,
|
|
44
47
|
type SessionClientInfo,
|
|
48
|
+
type SessionSnapshot,
|
|
45
49
|
} from "./sessionSupervisor";
|
|
46
50
|
|
|
47
51
|
export type { CapturedLog };
|
|
@@ -87,6 +91,12 @@ export type ListLogsPageResult = {
|
|
|
87
91
|
limit: number;
|
|
88
92
|
};
|
|
89
93
|
|
|
94
|
+
export type GetSessionInfoOptions = {
|
|
95
|
+
baseUrl: string;
|
|
96
|
+
includeHistory?: boolean;
|
|
97
|
+
latestLogLimit?: number;
|
|
98
|
+
};
|
|
99
|
+
|
|
90
100
|
// Memory cache: id -> CapturedLog (recent logs only, FIFO eviction)
|
|
91
101
|
const memoryCache: Map<number, CapturedLog> = new Map();
|
|
92
102
|
|
|
@@ -506,6 +516,73 @@ export async function listLogsPage(options: ListLogsPageOptions): Promise<ListLo
|
|
|
506
516
|
};
|
|
507
517
|
}
|
|
508
518
|
|
|
519
|
+
function findSessionSnapshot(sessionId: string): SessionSnapshot | null {
|
|
520
|
+
for (const snapshot of getSessionSnapshots()) {
|
|
521
|
+
if (snapshot.id === sessionId) return snapshot;
|
|
522
|
+
}
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function collectSessionLogSummaries(
|
|
527
|
+
sessionId: string,
|
|
528
|
+
includeHistory: boolean,
|
|
529
|
+
): Promise<SessionLogSummary[]> {
|
|
530
|
+
const summariesById = new Map<number, SessionLogSummary>();
|
|
531
|
+
const observeMatchingLog = (log: CapturedLog): void => {
|
|
532
|
+
if (!matchesLogFilters(log, sessionId, undefined)) return;
|
|
533
|
+
summariesById.set(log.id, buildSessionLogSummary(log));
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
if (includeHistory) {
|
|
537
|
+
await visitPersistedLogs((log) => {
|
|
538
|
+
observeSessionLog(log);
|
|
539
|
+
observeMatchingLog(log);
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
for (const log of memoryCache.values()) {
|
|
544
|
+
observeMatchingLog(log);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return [...summariesById.values()];
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function includeSnapshotLastLogSummary(
|
|
551
|
+
sessionId: string,
|
|
552
|
+
snapshot: SessionSnapshot | null,
|
|
553
|
+
summaries: SessionLogSummary[],
|
|
554
|
+
): Promise<SessionLogSummary[]> {
|
|
555
|
+
if (snapshot === null || snapshot.lastLogId === null) return summaries;
|
|
556
|
+
for (const summary of summaries) {
|
|
557
|
+
if (summary.id === snapshot.lastLogId) return summaries;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const log = await getLogById(snapshot.lastLogId);
|
|
561
|
+
if (log === null || !matchesLogFilters(log, sessionId, undefined)) return summaries;
|
|
562
|
+
return [...summaries, buildSessionLogSummary(log)];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export async function getSessionInfo(
|
|
566
|
+
sessionId: string,
|
|
567
|
+
options: GetSessionInfoOptions,
|
|
568
|
+
): Promise<SessionInfo | null> {
|
|
569
|
+
const includeHistory = options.includeHistory === true;
|
|
570
|
+
const collectedSummaries = await collectSessionLogSummaries(sessionId, includeHistory);
|
|
571
|
+
const snapshot = findSessionSnapshot(sessionId);
|
|
572
|
+
const summaries = includeHistory
|
|
573
|
+
? collectedSummaries
|
|
574
|
+
: await includeSnapshotLastLogSummary(sessionId, snapshot, collectedSummaries);
|
|
575
|
+
if (snapshot === null && summaries.length === 0) return null;
|
|
576
|
+
return buildSessionInfo({
|
|
577
|
+
sessionId,
|
|
578
|
+
baseUrl: options.baseUrl,
|
|
579
|
+
snapshot,
|
|
580
|
+
summaries,
|
|
581
|
+
logCoverage: includeHistory ? "history" : "recent",
|
|
582
|
+
latestLogLimit: options.latestLogLimit,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
509
586
|
export function getSessions(): string[] {
|
|
510
587
|
return getSessionIds();
|
|
511
588
|
}
|
|
@@ -1,11 +1,38 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import { getSessionSnapshots, getSessions } from "../../proxy/store";
|
|
2
|
+
import { getSessionInfo, getSessionSnapshots, getSessions } from "../../proxy/store";
|
|
3
|
+
|
|
4
|
+
function parsePositiveInt(value: string | null): number | undefined {
|
|
5
|
+
if (value === null) return undefined;
|
|
6
|
+
const parsed = Number(value);
|
|
7
|
+
if (!Number.isInteger(parsed) || parsed < 1) return undefined;
|
|
8
|
+
return parsed;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseBooleanFlag(value: string | null): boolean {
|
|
12
|
+
return value === "1" || value === "true";
|
|
13
|
+
}
|
|
3
14
|
|
|
4
15
|
export const Route = createFileRoute("/api/sessions")({
|
|
5
16
|
server: {
|
|
6
17
|
handlers: {
|
|
7
|
-
GET: ({ request }: { request: Request }) => {
|
|
18
|
+
GET: async ({ request }: { request: Request }) => {
|
|
8
19
|
const url = new URL(request.url);
|
|
20
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
21
|
+
if (sessionId !== null) {
|
|
22
|
+
if (sessionId.length === 0) {
|
|
23
|
+
return Response.json({ error: "sessionId is required" }, { status: 400 });
|
|
24
|
+
}
|
|
25
|
+
const info = await getSessionInfo(sessionId, {
|
|
26
|
+
baseUrl: url.origin,
|
|
27
|
+
includeHistory: parseBooleanFlag(url.searchParams.get("includeHistory")),
|
|
28
|
+
latestLogLimit: parsePositiveInt(url.searchParams.get("limit")),
|
|
29
|
+
});
|
|
30
|
+
if (info === null) {
|
|
31
|
+
return Response.json({ error: "Session not found" }, { status: 404 });
|
|
32
|
+
}
|
|
33
|
+
return Response.json(info);
|
|
34
|
+
}
|
|
35
|
+
|
|
9
36
|
const details = url.searchParams.get("details");
|
|
10
37
|
if (details === "true" || details === "1") {
|
|
11
38
|
return Response.json(getSessionSnapshots());
|