refactor: move projects provider specific logic into respective session providers

This commit is contained in:
Haileyesus
2026-04-27 13:46:19 +03:00
parent d3adc7afb8
commit 7ceaa9e326
4 changed files with 473 additions and 540 deletions

View File

@@ -1,4 +1,7 @@
import { getCodexSessionMessages } from '@/projects.js';
import fsSync from 'node:fs';
import readline from 'node:readline';
import { sessionsDb } from '@/modules/database/index.js';
import type { IProviderSessions } from '@/shared/interfaces.js';
import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
import { createNormalizedMessage, generateMessageId, readObjectRecord } from '@/shared/utils.js';
@@ -11,14 +14,250 @@ type CodexHistoryResult =
messages?: AnyRecord[];
total?: number;
hasMore?: boolean;
offset?: number;
limit?: number | null;
tokenUsage?: unknown;
};
const loadCodexSessionMessages = getCodexSessionMessages as unknown as (
function isVisibleCodexUserMessage(payload: AnyRecord | null | undefined): boolean {
if (!payload || payload.type !== 'user_message') {
return false;
}
if (payload.kind && payload.kind !== 'plain') {
return false;
}
return typeof payload.message === 'string' && payload.message.trim().length > 0;
}
function extractCodexTextContent(content: unknown): string {
if (!Array.isArray(content)) {
return typeof content === 'string' ? content : '';
}
return content
.map((item) => {
if (!item || typeof item !== 'object') {
return '';
}
const record = item as AnyRecord;
if (
(record.type === 'input_text' || record.type === 'output_text' || record.type === 'text')
&& typeof record.text === 'string'
) {
return record.text;
}
return '';
})
.filter(Boolean)
.join('\n');
}
async function getCodexSessionMessages(
sessionId: string,
limit: number | null,
offset: number,
) => Promise<CodexHistoryResult>;
limit: number | null = null,
offset = 0,
): Promise<CodexHistoryResult> {
try {
const sessionFilePath = sessionsDb.getSessionById(sessionId)?.jsonl_path;
if (!sessionFilePath) {
console.warn(`Codex session file not found for session ${sessionId}`);
return { messages: [], total: 0, hasMore: false };
}
const messages: AnyRecord[] = [];
let tokenUsage: AnyRecord | null = null;
const fileStream = fsSync.createReadStream(sessionFilePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});
for await (const line of rl) {
if (!line.trim()) {
continue;
}
try {
const entry = JSON.parse(line) as AnyRecord;
if (entry.type === 'event_msg' && entry.payload?.type === 'token_count' && entry.payload?.info) {
const info = entry.payload.info as AnyRecord;
if (info.total_token_usage) {
const usage = info.total_token_usage as AnyRecord;
tokenUsage = {
used: usage.total_tokens || 0,
total: info.model_context_window || 200000,
};
}
}
if (entry.type === 'event_msg' && isVisibleCodexUserMessage(entry.payload as AnyRecord)) {
messages.push({
type: 'user',
timestamp: entry.timestamp,
message: {
role: 'user',
content: entry.payload.message,
},
});
}
if (
entry.type === 'response_item' &&
entry.payload?.type === 'message' &&
entry.payload.role === 'assistant'
) {
const textContent = extractCodexTextContent(entry.payload.content);
if (textContent.trim()) {
messages.push({
type: 'assistant',
timestamp: entry.timestamp,
message: {
role: 'assistant',
content: textContent,
},
});
}
}
if (entry.type === 'response_item' && entry.payload?.type === 'reasoning') {
const summaryText = Array.isArray(entry.payload.summary)
? entry.payload.summary
.map((item: AnyRecord) => item?.text)
.filter(Boolean)
.join('\n')
: '';
if (summaryText.trim()) {
messages.push({
type: 'thinking',
timestamp: entry.timestamp,
message: {
role: 'assistant',
content: summaryText,
},
});
}
}
if (entry.type === 'response_item' && entry.payload?.type === 'function_call') {
let toolName = entry.payload.name;
let toolInput = entry.payload.arguments;
if (toolName === 'shell_command') {
toolName = 'Bash';
try {
const args = JSON.parse(entry.payload.arguments) as AnyRecord;
toolInput = JSON.stringify({ command: args.command });
} catch {
// Keep original arguments when parsing fails.
}
}
messages.push({
type: 'tool_use',
timestamp: entry.timestamp,
toolName,
toolInput,
toolCallId: entry.payload.call_id,
});
}
if (entry.type === 'response_item' && entry.payload?.type === 'function_call_output') {
messages.push({
type: 'tool_result',
timestamp: entry.timestamp,
toolCallId: entry.payload.call_id,
output: entry.payload.output,
});
}
if (entry.type === 'response_item' && entry.payload?.type === 'custom_tool_call') {
const toolName = entry.payload.name || 'custom_tool';
const input = entry.payload.input || '';
if (toolName === 'apply_patch') {
const fileMatch = String(input).match(/\*\*\* Update File: (.+)/);
const filePath = fileMatch ? fileMatch[1].trim() : 'unknown';
const lines = String(input).split('\n');
const oldLines: string[] = [];
const newLines: string[] = [];
for (const lineContent of lines) {
if (lineContent.startsWith('-') && !lineContent.startsWith('---')) {
oldLines.push(lineContent.slice(1));
} else if (lineContent.startsWith('+') && !lineContent.startsWith('+++')) {
newLines.push(lineContent.slice(1));
}
}
messages.push({
type: 'tool_use',
timestamp: entry.timestamp,
toolName: 'Edit',
toolInput: JSON.stringify({
file_path: filePath,
old_string: oldLines.join('\n'),
new_string: newLines.join('\n'),
}),
toolCallId: entry.payload.call_id,
});
} else {
messages.push({
type: 'tool_use',
timestamp: entry.timestamp,
toolName,
toolInput: input,
toolCallId: entry.payload.call_id,
});
}
}
if (entry.type === 'response_item' && entry.payload?.type === 'custom_tool_call_output') {
messages.push({
type: 'tool_result',
timestamp: entry.timestamp,
toolCallId: entry.payload.call_id,
output: entry.payload.output || '',
});
}
} catch {
// Skip malformed lines.
}
}
messages.sort(
(a, b) => new Date(a.timestamp || 0).getTime() - new Date(b.timestamp || 0).getTime(),
);
const total = messages.length;
if (limit !== null) {
const startIndex = Math.max(0, total - offset - limit);
const endIndex = total - offset;
const paginatedMessages = messages.slice(startIndex, endIndex);
const hasMore = startIndex > 0;
return {
messages: paginatedMessages,
total,
hasMore,
offset,
limit,
tokenUsage,
};
}
return { messages, tokenUsage };
} catch (error) {
console.error(`Error reading Codex session messages for ${sessionId}:`, error);
return { messages: [], total: 0, hasMore: false };
}
}
export class CodexSessionsProvider implements IProviderSessions {
/**
@@ -275,7 +514,7 @@ export class CodexSessionsProvider implements IProviderSessions {
let result: CodexHistoryResult;
try {
result = await loadCodexSessionMessages(sessionId, limit, offset);
result = await getCodexSessionMessages(sessionId, limit, offset);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.warn(`[CodexProvider] Failed to load session ${sessionId}:`, message);