harness uniformization
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
import { Hono } from 'hono';
|
||||
import { mkdir, readdir, rename, rm } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { getClaudeDir, getSessionDir, getArchivedSessionDir } from '@@/data-path';
|
||||
import type { HonoVariables } from '@@/create-router';
|
||||
|
||||
export const sessionsRouter = new Hono<{ Variables: HonoVariables }>();
|
||||
export const claudeModelsRouter = new Hono<{ Variables: HonoVariables }>();
|
||||
|
||||
sessionsRouter.get('/claude/models', async (ctx) => {
|
||||
claudeModelsRouter.get('/claude/models', async (ctx) => {
|
||||
const apiKey = process.env.ANTHROPIC_API_KEY;
|
||||
if (!apiKey) return ctx.json([]);
|
||||
|
||||
@@ -23,92 +20,3 @@ sessionsRouter.get('/claude/models', async (ctx) => {
|
||||
return ctx.json([]);
|
||||
}
|
||||
});
|
||||
|
||||
sessionsRouter.get('/sessions', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
const dir = getClaudeDir(email);
|
||||
|
||||
try {
|
||||
const entries = await readdir(dir);
|
||||
const sessions = await Promise.all(
|
||||
entries
|
||||
.filter((name) => name !== 'archived')
|
||||
.map(async (id) => {
|
||||
try {
|
||||
const metaFile = Bun.file(join(dir, id, 'meta.json'));
|
||||
if (!(await metaFile.exists())) return null;
|
||||
return await metaFile.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const valid = sessions.filter(Boolean);
|
||||
valid.sort((a: any, b: any) => b.createdAt - a.createdAt);
|
||||
return ctx.json(valid);
|
||||
} catch {
|
||||
return ctx.json([]);
|
||||
}
|
||||
});
|
||||
|
||||
sessionsRouter.get('/sessions/:id/messages', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
const id = ctx.req.param('id');
|
||||
const file = Bun.file(join(getSessionDir(email, id), 'messages.json'));
|
||||
|
||||
if (!(await file.exists())) return ctx.json([]);
|
||||
const messages = await file.json();
|
||||
return ctx.json(messages);
|
||||
});
|
||||
|
||||
sessionsRouter.put('/sessions/:id/messages', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
const id = ctx.req.param('id');
|
||||
const messages = ctx.get('body');
|
||||
const dir = getSessionDir(email, id);
|
||||
|
||||
await Bun.write(join(dir, 'messages.json'), JSON.stringify(messages));
|
||||
return ctx.json({ ok: true });
|
||||
});
|
||||
|
||||
sessionsRouter.put('/sessions/:id', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
const id = ctx.req.param('id');
|
||||
const body = ctx.get('body') as { title?: string };
|
||||
if (!body?.title || typeof body.title !== 'string') return ctx.json({ error: 'title required' }, 400);
|
||||
|
||||
const dir = getSessionDir(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 });
|
||||
});
|
||||
|
||||
sessionsRouter.delete('/sessions/:id', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
const id = ctx.req.param('id');
|
||||
const dir = getSessionDir(email, id);
|
||||
|
||||
try {
|
||||
await rm(dir, { recursive: true });
|
||||
} catch {
|
||||
// dir may not exist
|
||||
}
|
||||
return ctx.json({ ok: true });
|
||||
});
|
||||
|
||||
sessionsRouter.post('/sessions/:id/archive', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
const id = ctx.req.param('id');
|
||||
const src = getSessionDir(email, id);
|
||||
const dest = getArchivedSessionDir(email, id);
|
||||
|
||||
await mkdir(join(getClaudeDir(email), 'archived'), { recursive: true });
|
||||
await rename(src, dest);
|
||||
return ctx.json({ ok: true });
|
||||
});
|
||||
|
||||
@@ -4,10 +4,9 @@ import type { HonoVariables } from '@@/create-router';
|
||||
const OPENCODE_PORT = process.env.OPENCODE_PORT ?? '10006';
|
||||
const BASE = `http://localhost:${OPENCODE_PORT}`;
|
||||
|
||||
export const opencodeSessionsRouter = new Hono<{ Variables: HonoVariables }>();
|
||||
export const opencodeModelsRouter = new Hono<{ Variables: HonoVariables }>();
|
||||
|
||||
// List all active models from OpenCode — visibility filtering is handled client-side
|
||||
opencodeSessionsRouter.get('/models', async (ctx) => {
|
||||
opencodeModelsRouter.get('/opencode/models', async (ctx) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE}/provider`);
|
||||
if (!res.ok) return ctx.json([]);
|
||||
@@ -34,120 +33,3 @@ opencodeSessionsRouter.get('/models', async (ctx) => {
|
||||
return ctx.json([]);
|
||||
}
|
||||
});
|
||||
|
||||
// List sessions — proxy to OpenCode API (SQLite-backed)
|
||||
opencodeSessionsRouter.get('/sessions', async (ctx) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE}/session`);
|
||||
if (!res.ok) return ctx.json([]);
|
||||
const data = (await res.json()) as any[];
|
||||
const sessions = data.map((s: any) => ({
|
||||
id: s.id,
|
||||
title: s.title ?? 'Untitled',
|
||||
createdAt: s.time?.created ?? 0,
|
||||
}));
|
||||
sessions.sort((a: any, b: any) => b.createdAt - a.createdAt);
|
||||
return ctx.json(sessions);
|
||||
} catch {
|
||||
return ctx.json([]);
|
||||
}
|
||||
});
|
||||
|
||||
// Get session messages — proxy to OpenCode
|
||||
opencodeSessionsRouter.get('/sessions/:id/messages', async (ctx) => {
|
||||
const id = ctx.req.param('id');
|
||||
try {
|
||||
const res = await fetch(`${BASE}/session/${id}/message`);
|
||||
if (!res.ok) return ctx.json([]);
|
||||
|
||||
const data = (await res.json()) as any[];
|
||||
const messages = Array.isArray(data) ? data : Object.values(data);
|
||||
|
||||
const chatMessages: any[] = [];
|
||||
for (const msg of messages) {
|
||||
const role = msg.info?.role ?? msg.role;
|
||||
if (role === 'user') {
|
||||
const text = Array.isArray(msg.parts)
|
||||
? msg.parts
|
||||
.filter((p: any) => p.type === 'text')
|
||||
.map((p: any) => p.text ?? p.content ?? '')
|
||||
.join('')
|
||||
: typeof msg.content === 'string'
|
||||
? msg.content
|
||||
: '';
|
||||
if (text) chatMessages.push({ role: 'user', text });
|
||||
} else if (role === 'assistant') {
|
||||
if (Array.isArray(msg.parts)) {
|
||||
for (const part of msg.parts) {
|
||||
if (part.type === 'text' && (part.text || part.content)) {
|
||||
chatMessages.push({ role: 'assistant', text: part.text ?? part.content ?? '' });
|
||||
} else if (part.type === 'tool') {
|
||||
chatMessages.push({
|
||||
role: 'tool',
|
||||
toolName: part.tool ?? 'unknown',
|
||||
toolInput: part.state?.input ?? {},
|
||||
toolUseId: part.callID ?? part.id ?? '',
|
||||
output:
|
||||
part.state?.output != null
|
||||
? typeof part.state.output === 'string'
|
||||
? part.state.output
|
||||
: JSON.stringify(part.state.output)
|
||||
: undefined,
|
||||
isError: part.state?.status === 'error',
|
||||
});
|
||||
} else if (part.type === 'tool-invocation') {
|
||||
const inv = part.toolInvocation ?? part;
|
||||
chatMessages.push({
|
||||
role: 'tool',
|
||||
toolName: inv.toolName ?? 'unknown',
|
||||
toolInput: inv.args ?? {},
|
||||
toolUseId: inv.toolCallId ?? part.id ?? '',
|
||||
output:
|
||||
inv.result != null
|
||||
? typeof inv.result === 'string'
|
||||
? inv.result
|
||||
: JSON.stringify(inv.result)
|
||||
: undefined,
|
||||
isError: !!part.isError,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.json(chatMessages);
|
||||
} catch {
|
||||
return ctx.json([]);
|
||||
}
|
||||
});
|
||||
|
||||
// Rename session — proxy to OpenCode API
|
||||
opencodeSessionsRouter.put('/sessions/:id', async (ctx) => {
|
||||
const id = ctx.req.param('id');
|
||||
const body = ctx.get('body') as { title?: string };
|
||||
if (!body?.title || typeof body.title !== 'string') return ctx.json({ error: 'title required' }, 400);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${BASE}/session/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: body.title.slice(0, 200) }),
|
||||
});
|
||||
if (!res.ok) return ctx.json({ error: 'failed to rename' }, 500);
|
||||
return ctx.json({ ok: true });
|
||||
} catch {
|
||||
return ctx.json({ error: 'failed to rename' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Delete session — proxy to OpenCode API
|
||||
opencodeSessionsRouter.delete('/sessions/:id', async (ctx) => {
|
||||
const id = ctx.req.param('id');
|
||||
try {
|
||||
await fetch(`${BASE}/session/${id}`, { method: 'DELETE' });
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
return ctx.json({ ok: true });
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ServerWebSocket } from 'bun';
|
||||
import { mkdir, rename } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import type { ClientMessage, ServerMessage, ImageData, TaskInfo } from '@@/api/chat-types';
|
||||
import { getTmpAttachmentsDir, getAttachmentsDir, getNativeSkillsDir, getGlobalSkillsDir, getUserSkillsDir } from '@@/data-path';
|
||||
import { getTmpAttachmentsDir, getAttachmentsDir, getOpencodeSessionDir, getNativeSkillsDir, getGlobalSkillsDir, getUserSkillsDir } from '@@/data-path';
|
||||
import { readSkillDirs, parseFrontmatter } from '@@/api/skills/skills';
|
||||
import { createTaskLog, appendToLog, finalizeLog } from '@@/api/task-logger';
|
||||
|
||||
@@ -377,6 +377,14 @@ async function handleChat({ ws, prompt, sessionId, model, attachmentIds, images,
|
||||
state.sessionId = sid;
|
||||
send(ws, { type: 'session:init', sessionId: sid, model: session.model ?? 'opencode' });
|
||||
|
||||
// Write meta.json for filesystem-backed session listing
|
||||
const metaDir = getOpencodeSessionDir(ws.data.email, sid);
|
||||
const title = prompt.slice(0, 80) || 'New chat';
|
||||
const meta = { id: sid, title, createdAt: Date.now(), model: session.model ?? 'opencode' };
|
||||
mkdir(metaDir, { recursive: true })
|
||||
.then(() => Bun.write(join(metaDir, 'meta.json'), JSON.stringify(meta)))
|
||||
.catch(() => {});
|
||||
|
||||
// Move tmp attachments to session dir
|
||||
if (state.pendingAttachmentIds.length > 0) {
|
||||
const tmpDir = getTmpAttachmentsDir(ws.data.email);
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
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 type { HonoVariables } from '@@/create-router';
|
||||
|
||||
const OPENCODE_PORT = process.env.OPENCODE_PORT ?? '10006';
|
||||
const OPENCODE_BASE = `http://localhost:${OPENCODE_PORT}`;
|
||||
|
||||
export const sessionsRouter = new Hono<{ Variables: HonoVariables }>();
|
||||
|
||||
// --- List all sessions (merged from both providers) ---
|
||||
|
||||
sessionsRouter.get('/sessions', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
|
||||
const [claudeSessions, opencodeSessions] = await Promise.all([fetchClaudeSessions(email), fetchOpencodeSessions(email)]);
|
||||
|
||||
const merged = [...claudeSessions, ...opencodeSessions].sort((a, b) => b.createdAt - a.createdAt);
|
||||
return ctx.json(merged);
|
||||
});
|
||||
|
||||
// --- Messages ---
|
||||
|
||||
sessionsRouter.get('/sessions/:provider/:id/messages', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
const provider = ctx.req.param('provider');
|
||||
const id = ctx.req.param('id');
|
||||
|
||||
if (provider === 'claude') {
|
||||
const file = Bun.file(join(getSessionDir(email, id), 'messages.json'));
|
||||
if (!(await file.exists())) return ctx.json([]);
|
||||
return ctx.json(await file.json());
|
||||
}
|
||||
|
||||
if (provider === 'opencode') {
|
||||
return ctx.json(await fetchOpencodeMessages(id));
|
||||
}
|
||||
|
||||
return ctx.json({ error: 'invalid provider' }, 400);
|
||||
});
|
||||
|
||||
sessionsRouter.put('/sessions/:provider/:id/messages', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
const provider = ctx.req.param('provider');
|
||||
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);
|
||||
|
||||
const messages = ctx.get('body');
|
||||
const dir = getSessionDir(email, id);
|
||||
await Bun.write(join(dir, 'messages.json'), JSON.stringify(messages));
|
||||
return ctx.json({ ok: true });
|
||||
});
|
||||
|
||||
// --- Rename ---
|
||||
|
||||
sessionsRouter.put('/sessions/:provider/:id', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
const provider = ctx.req.param('provider');
|
||||
const id = ctx.req.param('id');
|
||||
const body = ctx.get('body') as { title?: string };
|
||||
|
||||
if (!body?.title || typeof body.title !== 'string') return ctx.json({ error: 'title required' }, 400);
|
||||
|
||||
if (provider === 'claude') {
|
||||
const dir = getSessionDir(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 });
|
||||
}
|
||||
|
||||
if (provider === 'opencode') {
|
||||
const dir = getOpencodeSessionDir(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));
|
||||
|
||||
// Best-effort sync to OpenCode API
|
||||
fetch(`${OPENCODE_BASE}/session/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: meta.title }),
|
||||
}).catch(() => {});
|
||||
|
||||
return ctx.json({ ok: true });
|
||||
}
|
||||
|
||||
return ctx.json({ error: 'invalid provider' }, 400);
|
||||
});
|
||||
|
||||
// --- Delete ---
|
||||
|
||||
sessionsRouter.delete('/sessions/:provider/:id', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
const provider = ctx.req.param('provider');
|
||||
const id = ctx.req.param('id');
|
||||
|
||||
if (provider === 'claude') {
|
||||
const dir = getSessionDir(email, id);
|
||||
try {
|
||||
await rm(dir, { recursive: true });
|
||||
} catch {
|
||||
// dir may not exist
|
||||
}
|
||||
return ctx.json({ ok: true });
|
||||
}
|
||||
|
||||
if (provider === 'opencode') {
|
||||
const dir = getOpencodeSessionDir(email, id);
|
||||
try {
|
||||
await rm(dir, { recursive: true });
|
||||
} catch {
|
||||
// dir may not exist
|
||||
}
|
||||
// Best-effort sync to OpenCode API
|
||||
fetch(`${OPENCODE_BASE}/session/${id}`, { method: 'DELETE' }).catch(() => {});
|
||||
return ctx.json({ ok: true });
|
||||
}
|
||||
|
||||
return ctx.json({ error: 'invalid provider' }, 400);
|
||||
});
|
||||
|
||||
// --- Archive (Claude only) ---
|
||||
|
||||
sessionsRouter.post('/sessions/:provider/:id/archive', async (ctx) => {
|
||||
const { email } = ctx.get('user');
|
||||
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 !== 'claude') return ctx.json({ error: 'invalid provider' }, 400);
|
||||
|
||||
const src = getSessionDir(email, id);
|
||||
const dest = getArchivedSessionDir(email, id);
|
||||
await mkdir(join(getClaudeDir(email), 'archived'), { recursive: true });
|
||||
await rename(src, dest);
|
||||
return ctx.json({ ok: true });
|
||||
});
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
type SessionMeta = {
|
||||
id: string;
|
||||
title: string;
|
||||
createdAt: number;
|
||||
provider: 'claude' | 'opencode';
|
||||
model?: string | null;
|
||||
};
|
||||
|
||||
async function fetchClaudeSessions(email: string): Promise<SessionMeta[]> {
|
||||
const dir = getClaudeDir(email);
|
||||
try {
|
||||
const entries = await readdir(dir);
|
||||
const sessions = await Promise.all(
|
||||
entries
|
||||
.filter((name) => name !== 'archived')
|
||||
.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: 'claude' as const };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
);
|
||||
return sessions.filter((s): s is SessionMeta => s !== null);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchOpencodeSessions(email: string): Promise<SessionMeta[]> {
|
||||
const dir = getOpencodeDir(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: 'opencode' 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`);
|
||||
if (!res.ok) return [];
|
||||
|
||||
const data = (await res.json()) as any[];
|
||||
const messages = Array.isArray(data) ? data : Object.values(data);
|
||||
|
||||
const chatMessages: any[] = [];
|
||||
for (const msg of messages) {
|
||||
const role = msg.info?.role ?? msg.role;
|
||||
if (role === 'user') {
|
||||
const text = Array.isArray(msg.parts)
|
||||
? msg.parts
|
||||
.filter((p: any) => p.type === 'text')
|
||||
.map((p: any) => p.text ?? p.content ?? '')
|
||||
.join('')
|
||||
: typeof msg.content === 'string'
|
||||
? msg.content
|
||||
: '';
|
||||
if (text) chatMessages.push({ role: 'user', text });
|
||||
} else if (role === 'assistant') {
|
||||
if (Array.isArray(msg.parts)) {
|
||||
for (const part of msg.parts) {
|
||||
if (part.type === 'text' && (part.text || part.content)) {
|
||||
chatMessages.push({ role: 'assistant', text: part.text ?? part.content ?? '' });
|
||||
} else if (part.type === 'tool') {
|
||||
chatMessages.push({
|
||||
role: 'tool',
|
||||
toolName: part.tool ?? 'unknown',
|
||||
toolInput: part.state?.input ?? {},
|
||||
toolUseId: part.callID ?? part.id ?? '',
|
||||
output:
|
||||
part.state?.output != null
|
||||
? typeof part.state.output === 'string'
|
||||
? part.state.output
|
||||
: JSON.stringify(part.state.output)
|
||||
: undefined,
|
||||
isError: part.state?.status === 'error',
|
||||
});
|
||||
} else if (part.type === 'tool-invocation') {
|
||||
const inv = part.toolInvocation ?? part;
|
||||
chatMessages.push({
|
||||
role: 'tool',
|
||||
toolName: inv.toolName ?? 'unknown',
|
||||
toolInput: inv.args ?? {},
|
||||
toolUseId: inv.toolCallId ?? part.id ?? '',
|
||||
output:
|
||||
inv.result != null
|
||||
? typeof inv.result === 'string'
|
||||
? inv.result
|
||||
: JSON.stringify(inv.result)
|
||||
: undefined,
|
||||
isError: !!part.isError,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chatMessages;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,11 @@ export const getClaudeDir = (email: string) => join(DATA_PATH, email, 'chat_sess
|
||||
export const getSessionDir = (email: string, sessionId: string) =>
|
||||
join(DATA_PATH, email, 'chat_sessions', 'claude', sessionId);
|
||||
|
||||
export const getOpencodeDir = (email: string) => join(DATA_PATH, email, 'chat_sessions', 'opencode');
|
||||
|
||||
export const getOpencodeSessionDir = (email: string, sessionId: string) =>
|
||||
join(DATA_PATH, email, 'chat_sessions', 'opencode', sessionId);
|
||||
|
||||
export const getArchivedSessionDir = (email: string, sessionId: string) =>
|
||||
join(DATA_PATH, email, 'chat_sessions', 'claude', 'archived', sessionId);
|
||||
|
||||
|
||||
+7
-29
@@ -3,22 +3,21 @@ import { cors } from 'hono/cors';
|
||||
import { createRouter } from './create-router';
|
||||
import type { HonoVariables } from './create-router';
|
||||
import { authRouter } from './api/auth';
|
||||
import { serverSettingsRouter, settingsPath } from './api/server-settings/server-settings';
|
||||
import { serverSettingsRouter } from './api/server-settings/server-settings';
|
||||
import { landingPageDataRouter } from './api/landing-page-data/landing-page-data';
|
||||
import { updateUserHandler } from './api/users/update-user';
|
||||
import { plansRouter } from './api/plans/plans';
|
||||
import { skillsRouter } from './api/skills/skills';
|
||||
import { tasksRouter } from './api/tasks/tasks';
|
||||
import { processesRouter } from './api/processes/processes';
|
||||
import { sessionsRouter } from './api/claude/sessions';
|
||||
import { opencodeSessionsRouter } from './api/opencode/sessions';
|
||||
import { claudeModelsRouter } from './api/claude/sessions';
|
||||
import { opencodeModelsRouter } from './api/opencode/sessions';
|
||||
import { sessionsRouter } from './api/sessions/sessions';
|
||||
import { scrapeRouter } from './api/scrape/scrape';
|
||||
import { uploadRouter } from './api/upload/upload';
|
||||
import { settingsRouter } from './api/settings/settings';
|
||||
import { taskLogsRouter } from './api/task-logs/task-logs';
|
||||
import { router as fileBrowserRouter } from './api/file-browser/router';
|
||||
import { readdirSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { CustomError } from './custom-errors';
|
||||
import { userMiddleware, bodyParser } from './_middlewares';
|
||||
|
||||
@@ -51,36 +50,15 @@ protectedRouter.route('/skills', skillsRouter);
|
||||
protectedRouter.route('/tasks', tasksRouter);
|
||||
protectedRouter.route('/processes', processesRouter);
|
||||
protectedRouter.route('/', sessionsRouter);
|
||||
protectedRouter.route('/opencode', opencodeSessionsRouter);
|
||||
protectedRouter.route('/', claudeModelsRouter);
|
||||
protectedRouter.route('/', opencodeModelsRouter);
|
||||
protectedRouter.route('/scrape', scrapeRouter);
|
||||
protectedRouter.route('/upload', uploadRouter);
|
||||
protectedRouter.route('/user', settingsRouter);
|
||||
protectedRouter.route('/task-logs', taskLogsRouter);
|
||||
protectedRouter.route('/file-browser', fileBrowserRouter);
|
||||
|
||||
// Auto-discover and mount plugin routers, then mount the protected router.
|
||||
// Hono's .route() copies routes at call time, so plugins must be loaded first.
|
||||
export async function loadPlugins() {
|
||||
const pluginsDir = join(import.meta.dir, '../workspaces/plugins');
|
||||
const pluginDirs = readdirSync(pluginsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
||||
const settings = await Bun.file(settingsPath)
|
||||
.json()
|
||||
.catch(() => ({}));
|
||||
const pluginSettings: Record<string, boolean> = settings.plugins ?? {};
|
||||
|
||||
for (const dir of pluginDirs) {
|
||||
if (pluginSettings[dir.name] === false) continue;
|
||||
|
||||
const serverIndex = join(pluginsDir, dir.name, 'server', 'index.ts');
|
||||
if (!existsSync(serverIndex)) continue;
|
||||
const mod = await import(serverIndex);
|
||||
if (mod.router && mod.apiPath) {
|
||||
protectedRouter.route(mod.apiPath, mod.router);
|
||||
}
|
||||
}
|
||||
|
||||
honoServer.route('/api', protectedRouter);
|
||||
}
|
||||
honoServer.route('/api', protectedRouter);
|
||||
|
||||
honoServer.onError((error, ctx) => {
|
||||
if (error instanceof CustomError) {
|
||||
|
||||
Reference in New Issue
Block a user