- Introduced RequestContentDialog for displaying request content details. - Added UsageBarChart for visualizing token usage data. - Implemented UsageHistorySection to manage and display usage history with filtering and pagination. - Created provider-types for managing provider-related types. - Developed ModelsPage to encapsulate models configuration, providers, and usage history. - Defined usage-history types and utility functions for managing usage data. - Updated routing to include models page and redirect agents to models. - Refactored chat store to integrate models instead of agents. - Established models store for managing model-related state and data fetching.
141 lines
4.8 KiB
TypeScript
141 lines
4.8 KiB
TypeScript
import { sessionStore } from '@electron/gateway/session-store';
|
|
import {
|
|
getTranscriptFilePath,
|
|
getTranscriptPathCandidates,
|
|
} from '@electron/utils/token-usage-writer';
|
|
import { buildAgentSessionKey, normalizeAgentSessionKey, parseSessionKey } from '@runtime/lib/models';
|
|
import type { HostApiContext } from '../context';
|
|
import type { NormalizedHostApiRequest } from '../route-utils';
|
|
import { fail, ok, parseJsonBody } from '../route-utils';
|
|
|
|
function parseSessionIdentity(request: NormalizedHostApiRequest): { agentId: string; sessionId: string; sessionKey: string } | null {
|
|
const sessionKey = normalizeAgentSessionKey(request.url.searchParams.get('sessionKey')?.trim() || '');
|
|
const parsed = parseSessionKey(sessionKey);
|
|
if (sessionKey && parsed.isAgentSession) {
|
|
return {
|
|
agentId: parsed.agentId,
|
|
sessionId: parsed.sessionId,
|
|
sessionKey: parsed.sessionKey,
|
|
};
|
|
}
|
|
|
|
const agentId = request.url.searchParams.get('agentId')?.trim() || '';
|
|
const sessionId = request.url.searchParams.get('sessionId')?.trim() || '';
|
|
if (!agentId || !sessionId) return null;
|
|
|
|
return {
|
|
agentId: parseSessionKey(buildAgentSessionKey(agentId, sessionId)).agentId,
|
|
sessionId: parseSessionKey(buildAgentSessionKey(agentId, sessionId)).sessionId,
|
|
sessionKey: buildAgentSessionKey(agentId, sessionId),
|
|
};
|
|
}
|
|
|
|
export async function handleSessionRoutes(
|
|
request: NormalizedHostApiRequest,
|
|
_ctx: HostApiContext,
|
|
) {
|
|
const { pathname, method } = request;
|
|
|
|
if (pathname === '/api/sessions/transcript' && method === 'GET') {
|
|
const identity = parseSessionIdentity(request);
|
|
if (!identity) {
|
|
return fail(400, 'sessionKey or agentId/sessionId is required');
|
|
}
|
|
|
|
try {
|
|
const fsP = await import('node:fs/promises');
|
|
let transcriptPath = getTranscriptFilePath(identity.sessionKey);
|
|
let raw: string | null = null;
|
|
let lastError: unknown = null;
|
|
|
|
for (const candidatePath of getTranscriptPathCandidates(identity.sessionKey)) {
|
|
try {
|
|
raw = await fsP.readFile(candidatePath, 'utf8');
|
|
transcriptPath = candidatePath;
|
|
break;
|
|
} catch (error) {
|
|
lastError = error;
|
|
if ((error as { code?: string } | null)?.code !== 'ENOENT') {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (raw === null) {
|
|
const requestedSessionKey = request.url.searchParams.get('sessionKey')?.trim() || '';
|
|
if (requestedSessionKey && requestedSessionKey !== identity.sessionKey) {
|
|
for (const candidatePath of getTranscriptPathCandidates(requestedSessionKey)) {
|
|
try {
|
|
raw = await fsP.readFile(candidatePath, 'utf8');
|
|
transcriptPath = candidatePath;
|
|
break;
|
|
} catch (candidateError) {
|
|
lastError = candidateError;
|
|
if ((candidateError as { code?: string } | null)?.code !== 'ENOENT') {
|
|
throw candidateError;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (raw === null) {
|
|
throw lastError ?? Object.assign(new Error('Transcript not found'), { code: 'ENOENT' });
|
|
}
|
|
|
|
const lines = raw.split(/\r?\n/).filter(Boolean);
|
|
const messages = lines.flatMap((line) => {
|
|
try {
|
|
const entry = JSON.parse(line) as { type?: string; message?: unknown; timestamp?: string };
|
|
return entry.type === 'message' && entry.message ? [{ ...entry.message, timestamp: entry.timestamp }] : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
});
|
|
|
|
return ok({
|
|
agentId: identity.agentId,
|
|
sessionId: identity.sessionId,
|
|
sessionKey: identity.sessionKey,
|
|
transcriptPath,
|
|
messages,
|
|
});
|
|
} catch (error: any) {
|
|
if (error?.code === 'ENOENT') {
|
|
return fail(404, 'Transcript not found');
|
|
}
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/sessions/delete' && method === 'POST') {
|
|
const body = parseJsonBody<{ sessionKey: string }>(request.body);
|
|
const rawSessionKey = String(body?.sessionKey || '').trim();
|
|
const sessionKey = normalizeAgentSessionKey(rawSessionKey);
|
|
if (!sessionKey) {
|
|
return fail(400, 'sessionKey is required');
|
|
}
|
|
|
|
sessionStore.deleteSession(sessionKey);
|
|
|
|
const fsP = await import('node:fs/promises');
|
|
const transcriptPathCandidates = Array.from(new Set([
|
|
...getTranscriptPathCandidates(sessionKey),
|
|
...(rawSessionKey && rawSessionKey !== sessionKey ? getTranscriptPathCandidates(rawSessionKey) : []),
|
|
]));
|
|
|
|
for (const transcriptPath of transcriptPathCandidates) {
|
|
try {
|
|
await fsP.rename(transcriptPath, transcriptPath.replace(/\.jsonl$/, '.deleted.jsonl'));
|
|
break;
|
|
} catch {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return ok({ success: true });
|
|
}
|
|
|
|
return null;
|
|
}
|