96 lines
2.6 KiB
TypeScript
96 lines
2.6 KiB
TypeScript
import { mkdir } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
import { getTaskLogsDir } from '@@/data-path';
|
|
import type { TaskInfo } from '@@/api/chat-types';
|
|
|
|
type ChatMessage =
|
|
| { role: 'user'; text: string }
|
|
| { role: 'assistant'; text: string }
|
|
| {
|
|
role: 'tool';
|
|
toolName: string;
|
|
toolInput: Record<string, unknown>;
|
|
toolUseId: string;
|
|
output?: string;
|
|
isError?: boolean;
|
|
}
|
|
| { role: 'result'; costUsd: number; durationMs: number; numTurns: number; isError: boolean }
|
|
| { role: 'error'; text: string };
|
|
|
|
type TaskLog = {
|
|
taskName: string;
|
|
taskDirName: string;
|
|
entryName: string;
|
|
entryType: 'file' | 'directory';
|
|
provider: string;
|
|
model: string;
|
|
startedAt: string;
|
|
completedAt: string | null;
|
|
messages: ChatMessage[];
|
|
};
|
|
|
|
type LogEntry = {
|
|
email: string;
|
|
filePath: string;
|
|
log: TaskLog;
|
|
};
|
|
|
|
const activeLogs = new Map<string, LogEntry>();
|
|
let logCounter = 0;
|
|
|
|
export function createTaskLog(email: string, taskInfo: TaskInfo, provider: string, model: string): string {
|
|
const logId = `log_${Date.now()}_${++logCounter}`;
|
|
const dir = getTaskLogsDir(email);
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
const filename = `${timestamp}-${taskInfo.taskDirName}.json`;
|
|
const filePath = join(dir, filename);
|
|
|
|
const log: TaskLog = {
|
|
taskName: taskInfo.taskName,
|
|
taskDirName: taskInfo.taskDirName,
|
|
entryName: taskInfo.entryName,
|
|
entryType: taskInfo.entryType,
|
|
provider,
|
|
model,
|
|
startedAt: new Date().toISOString(),
|
|
completedAt: null,
|
|
messages: [],
|
|
};
|
|
|
|
activeLogs.set(logId, { email, filePath, log });
|
|
return logId;
|
|
}
|
|
|
|
export function appendToLog(logId: string, message: ChatMessage) {
|
|
const entry = activeLogs.get(logId);
|
|
if (!entry) return;
|
|
|
|
// For tool results, update the existing tool message instead of appending
|
|
if (message.role === 'tool' && message.output !== undefined) {
|
|
const existing = entry.log.messages.find((m) => m.role === 'tool' && m.toolUseId === message.toolUseId);
|
|
if (existing && existing.role === 'tool') {
|
|
existing.output = message.output;
|
|
existing.isError = message.isError;
|
|
return;
|
|
}
|
|
}
|
|
|
|
entry.log.messages.push(message);
|
|
}
|
|
|
|
export async function finalizeLog(logId: string) {
|
|
const entry = activeLogs.get(logId);
|
|
if (!entry) return;
|
|
|
|
entry.log.completedAt = new Date().toISOString();
|
|
|
|
try {
|
|
await mkdir(join(entry.filePath, '..'), { recursive: true });
|
|
await Bun.write(entry.filePath, JSON.stringify(entry.log, null, 2));
|
|
} catch (err) {
|
|
console.error('[task-logger] Failed to write log:', err);
|
|
}
|
|
|
|
activeLogs.delete(logId);
|
|
}
|