This commit is contained in:
2026-02-19 18:05:50 +00:00
parent 9870fa7ae8
commit 6a83342013
38 changed files with 1459 additions and 267 deletions
+59 -7
View File
@@ -1,7 +1,7 @@
import { Hono } from 'hono';
import { mkdir, readdir, rename, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { getClaudeDir, getSessionDir, getArchivedSessionDir, getOpencodeDir, getOpencodeSessionDir } from '@@/data-path';
import { getClaudeDir, getSessionDir, getArchivedSessionDir, getOpencodeDir, getOpencodeSessionDir, getPiMonoDir, getPiMonoSessionDir } from '@@/data-path';
import type { HonoVariables } from '@@/create-router';
const OPENCODE_PORT = process.env.OPENCODE_PORT ?? '10006';
@@ -14,9 +14,13 @@ export const sessionsRouter = new Hono<{ Variables: HonoVariables }>();
sessionsRouter.get('/sessions', async (ctx) => {
const { email } = ctx.get('user');
const [claudeSessions, opencodeSessions] = await Promise.all([fetchClaudeSessions(email), fetchOpencodeSessions(email)]);
const [claudeSessions, opencodeSessions, piMonoSessions] = await Promise.all([
fetchClaudeSessions(email),
fetchOpencodeSessions(email),
fetchPiMonoSessions(email),
]);
const merged = [...claudeSessions, ...opencodeSessions].sort((a, b) => b.createdAt - a.createdAt);
const merged = [...claudeSessions, ...opencodeSessions, ...piMonoSessions].sort((a, b) => b.createdAt - a.createdAt);
return ctx.json(merged);
});
@@ -37,6 +41,12 @@ sessionsRouter.get('/sessions/:provider/:id/messages', async (ctx) => {
return ctx.json(await fetchOpencodeMessages(id));
}
if (provider === 'pi-mono') {
const file = Bun.file(join(getPiMonoSessionDir(email, id), 'messages.json'));
if (!(await file.exists())) return ctx.json([]);
return ctx.json(await file.json());
}
return ctx.json({ error: 'invalid provider' }, 400);
});
@@ -46,10 +56,10 @@ sessionsRouter.put('/sessions/:provider/:id/messages', async (ctx) => {
const id = ctx.req.param('id');
if (provider === 'opencode') return ctx.json({ error: 'opencode sessions are read-only' }, 400);
if (provider !== 'claude') return ctx.json({ error: 'invalid provider' }, 400);
if (provider !== 'claude' && provider !== 'pi-mono') return ctx.json({ error: 'invalid provider' }, 400);
const messages = ctx.get('body');
const dir = getSessionDir(email, id);
const dir = provider === 'pi-mono' ? getPiMonoSessionDir(email, id) : getSessionDir(email, id);
await Bun.write(join(dir, 'messages.json'), JSON.stringify(messages));
return ctx.json({ ok: true });
});
@@ -92,6 +102,16 @@ sessionsRouter.put('/sessions/:provider/:id', async (ctx) => {
return ctx.json({ ok: true });
}
if (provider === 'pi-mono') {
const dir = getPiMonoSessionDir(email, id);
const metaFile = Bun.file(join(dir, 'meta.json'));
if (!(await metaFile.exists())) return ctx.json({ error: 'session not found' }, 404);
const meta = await metaFile.json();
meta.title = body.title.slice(0, 200);
await Bun.write(join(dir, 'meta.json'), JSON.stringify(meta));
return ctx.json({ ok: true });
}
return ctx.json({ error: 'invalid provider' }, 400);
});
@@ -124,6 +144,16 @@ sessionsRouter.delete('/sessions/:provider/:id', async (ctx) => {
return ctx.json({ ok: true });
}
if (provider === 'pi-mono') {
const dir = getPiMonoSessionDir(email, id);
try {
await rm(dir, { recursive: true });
} catch {
// dir may not exist
}
return ctx.json({ ok: true });
}
return ctx.json({ error: 'invalid provider' }, 400);
});
@@ -134,7 +164,7 @@ sessionsRouter.post('/sessions/:provider/:id/archive', async (ctx) => {
const provider = ctx.req.param('provider');
const id = ctx.req.param('id');
if (provider === 'opencode') return ctx.json({ error: 'opencode sessions cannot be archived' }, 400);
if (provider === 'opencode' || provider === 'pi-mono') return ctx.json({ error: `${provider} sessions cannot be archived` }, 400);
if (provider !== 'claude') return ctx.json({ error: 'invalid provider' }, 400);
const src = getSessionDir(email, id);
@@ -150,7 +180,7 @@ type SessionMeta = {
id: string;
title: string;
createdAt: number;
provider: 'claude' | 'opencode';
provider: 'claude' | 'opencode' | 'pi-mono';
model?: string | null;
};
@@ -200,6 +230,28 @@ async function fetchOpencodeSessions(email: string): Promise<SessionMeta[]> {
}
}
async function fetchPiMonoSessions(email: string): Promise<SessionMeta[]> {
const dir = getPiMonoDir(email);
try {
const entries = await readdir(dir);
const sessions = await Promise.all(
entries.map(async (id) => {
try {
const metaFile = Bun.file(join(dir, id, 'meta.json'));
if (!(await metaFile.exists())) return null;
const meta = await metaFile.json();
return { ...meta, provider: 'pi-mono' as const };
} catch {
return null;
}
}),
);
return sessions.filter((s): s is SessionMeta => s !== null);
} catch {
return [];
}
}
async function fetchOpencodeMessages(id: string) {
try {
const res = await fetch(`${OPENCODE_BASE}/session/${id}/message`);