chat: rename the pi chat transport + model list to chat (Stage 4b/1)

Pure rename, no behavior change. Moves the misnamed "pi" chat harness into the
chat namespace:

- api/pi/{websocket,session-manager,types,logger,list-models} → api/chat/
- merge api/pi/rest.ts into api/chat/chat.ts (/pi/models → /chat/models,
  /pi/stt → /chat/stt); drop the piRestRouter mount
- PiEvent → ChatEvent, piWebsocket → chatWebsocket, listPiModels → listChatModels
- WS route /api/pi/chat/ws → /api/chat/ws, provider tag 'pi' → 'chat'
- frontend: useChat/useAudioRecording URLs, usePiModels→useModels /
  useVisiblePiModels→useVisibleModels / useEnabledPiModels→useEnabledModels,
  'PI_MODELS' query key → 'CHAT_MODELS', attachments provider 'pi-mono' → 'chat'

The /pi-mono provider/harness settings router is renamed separately (next commit).
Note: the WS route change requires the mobile app to point at /api/chat/ws.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 15:47:40 +00:00
co-authored by Claude Opus 4.8
parent 70555c8d71
commit 49df1c0b0c
31 changed files with 119 additions and 129 deletions
+43
View File
@@ -1,5 +1,6 @@
import type { Context } from 'hono';
import { createRouter } from '../../create-router';
import { getUserSettings } from 'officerdb';
import {
getClaudeSessionsCwd,
listClaudePwds,
@@ -8,6 +9,10 @@ import {
deleteClaudeSession,
renameClaudeSession,
} from './claude-sessions';
import { listChatModels } from './list-models';
import { logger } from './logger';
import { readSttConfig } from '../server-settings/stt';
import { transcribeAudio } from '../stt/transcribe';
export const chatRouter = createRouter();
@@ -52,3 +57,41 @@ chatRouter.patch('/sessions/:id/title', async (ctx) => {
if (!ok) return ctx.text('Not found', 404);
return ctx.json({ ok: true });
});
// GET /chat/models — Claude tiers only (the runner is the `claude` CLI).
chatRouter.get('/models', async (ctx: Context) => {
try {
const models = await listChatModels();
const providerNames: Record<string, string> = { 'claude-code': 'Claude Code' };
return ctx.json({ models, providerNames, hostHome: process.env.HOME ?? '' });
} catch (err) {
logger.error('Failed to list models', { error: String(err) });
return ctx.json({ models: [], providerNames: {} });
}
});
// POST /chat/stt — proxy an audio clip to the configured Whisper server.
chatRouter.post('/stt', async (ctx: Context) => {
const sttConfig = await readSttConfig();
if (!sttConfig?.url) {
return ctx.json({ error: 'Whisper not configured — set it up in Settings → Speech to Text' }, 400);
}
const user = ctx.get('user');
const body = await ctx.req.parseBody();
const file = body['file'];
if (!file || !(file instanceof File)) {
return ctx.json({ error: 'file is required' }, 400);
}
const settings = (await getUserSettings(user.id)) as { languages?: { spoken?: string[] } };
const spokenLanguages = settings.languages?.spoken ?? [];
try {
const result = await transcribeAudio({ file, whisperUrl: sttConfig.url, spokenLanguages });
return ctx.json(result);
} catch (err) {
logger.error('STT proxy failed', { error: String(err) });
return ctx.json({ error: err instanceof Error ? err.message : 'Failed to reach Whisper server' }, 502);
}
});