diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/AISettings.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/AISettings.tsx index e70854fc..0d9719e1 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/AISettings.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/AISettings.tsx @@ -10,7 +10,7 @@ import { useAuth } from 'hooks/useAuth'; import { createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel'; import { useSettings } from 'state/useSettings'; import type { UserSettings } from 'state/useSettings'; -import { usePiModels, useVisiblePiModels, modelKey, getProviderDisplayName, type ModelOption } from 'state/useModels'; +import { useModels, useVisibleModels, modelKey, getProviderDisplayName, type ModelOption } from 'state/useModels'; import { useAccessPolicy } from 'state/useAccessPolicy'; import { useUserState } from 'state/useUserState'; import { AIHarnessesSection } from './ServerSettings/AIHarnessesSection'; @@ -106,8 +106,8 @@ const DropZone = ({ label, children, onDrop }: DropZoneProps) => { function MyModelsSection() { const { user } = useAuth(); const { settings, saveSettings } = useSettings(); - const allModels = usePiModels(); - const policyModels = useVisiblePiModels(); + const allModels = useModels(); + const policyModels = useVisibleModels(); const isAdmin = user?.role !== 'Member'; const visibleModels = isAdmin ? allModels : policyModels; const [activeProvider, setActiveProvider] = useUserState('my-models-provider', ''); @@ -244,7 +244,7 @@ function MemberModelsSection() { const client = useClient(); const queryClient = useQueryClient(); const { policy, savePolicy } = useAccessPolicy(); - const piModels = usePiModels(); + const piModels = useModels(); const [activeProvider, setActiveProvider] = useUserState('member-models-provider', ''); const [refreshing, setRefreshing] = useState(false); @@ -252,7 +252,7 @@ function MemberModelsSection() { setRefreshing(true); try { await client.post('/server-settings/pi-mono/local-providers/refresh'); - queryClient.invalidateQueries({ queryKey: ['PI_MODELS'] }); + queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] }); toast.success('Models refreshed'); } catch { toast.error('Failed to refresh models'); @@ -402,7 +402,7 @@ function MemberModelsSection() { // function ChatDefaultsSection() { // const { settings, saveSettings } = useSettings(); -// const piModels = useVisiblePiModels(); +// const piModels = useVisibleModels(); // const [isSaving, setIsSaving] = useState(false); // const [model, setModel] = useState(settings.chat.defaultModel); // const [systemPrompt, setSystemPrompt] = useState(settings.chat.systemPrompt); diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/AIHarnessesSection.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/AIHarnessesSection.tsx index 67a0e292..6df8faff 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/AIHarnessesSection.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/AIHarnessesSection.tsx @@ -117,7 +117,7 @@ export const AIHarnessesSection = () => { await client.put('/server-settings/pi-mono/api-keys', { provider: piId, value }); queryClient.invalidateQueries({ queryKey: ['PI_MONO_API_KEYS'] }); queryClient.invalidateQueries({ queryKey: ['PI_MONO_REMOTE_HEALTH'] }); - queryClient.invalidateQueries({ queryKey: ['PI_MODELS'] }); + queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] }); setKeyInputs((prev) => { const next = { ...prev }; delete next[piId]; @@ -133,7 +133,7 @@ export const AIHarnessesSection = () => { await client.put('/server-settings/pi-mono/api-keys', { provider: provider.piId, value: '' }); queryClient.invalidateQueries({ queryKey: ['PI_MONO_API_KEYS'] }); queryClient.invalidateQueries({ queryKey: ['PI_MONO_REMOTE_HEALTH'] }); - queryClient.invalidateQueries({ queryKey: ['PI_MODELS'] }); + queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] }); if (editingProvider === provider.key) setEditingProvider(null); }; @@ -217,7 +217,7 @@ export const AIHarnessesSection = () => { }); queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_PROVIDERS'] }); queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_HEALTH'] }); - queryClient.invalidateQueries({ queryKey: ['PI_MODELS'] }); + queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] }); toast.success(`Connected to ${probe.name}`); resetLocalForm(); } catch { @@ -230,7 +230,7 @@ export const AIHarnessesSection = () => { await client.delete(`/server-settings/pi-mono/local-providers/${id}`); queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_PROVIDERS'] }); queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_HEALTH'] }); - queryClient.invalidateQueries({ queryKey: ['PI_MODELS'] }); + queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] }); }; return ( diff --git a/src/apps/officer-web/state/useInitialData.ts b/src/apps/officer-web/state/useInitialData.ts index 47f58923..7349f867 100644 --- a/src/apps/officer-web/state/useInitialData.ts +++ b/src/apps/officer-web/state/useInitialData.ts @@ -1,13 +1,13 @@ import { usePlans } from 'state/usePlans'; import { useSettings } from 'state/useSettings'; -import { usePiModels } from 'state/useModels'; +import { useModels } from 'state/useModels'; import { useAccessPolicy } from 'state/useAccessPolicy'; import { useColorModeSync } from './useThemeSync'; export const useInitialData = () => { const { plans } = usePlans(); const { settings } = useSettings(); - usePiModels(); + useModels(); useAccessPolicy(); useColorModeSync(); diff --git a/src/server.tsx b/src/server.tsx index 2a43455d..0299b04f 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -5,7 +5,7 @@ import { honoServer } from './servers/hono'; import { verify } from './servers/jwt'; import { isTokenBlacklisted } from 'officerdb'; import { terminalWebsocket } from './servers/api/terminal/websocket'; -import { piWebsocket } from './servers/api/pi/websocket'; +import { chatWebsocket } from './servers/api/chat/websocket'; import { taskRunnerWebsocket } from './servers/api/tasks/task-executor'; import { pipelineWebsocket } from './servers/api/tasks/pipeline-executor'; import { cliampWebsocket } from './servers/api/cliamp/websocket'; @@ -33,7 +33,7 @@ type WSData = { email: string; username: string; role: string; - provider: 'terminal' | 'pi' | 'task-runner' | 'pipeline' | 'dev-server' | 'cliamp' | 'cliamp-audio' | 'desktop' | 'sidecar'; + provider: 'terminal' | 'chat' | 'task-runner' | 'pipeline' | 'dev-server' | 'cliamp' | 'cliamp-audio' | 'desktop' | 'sidecar'; sandboxed: boolean; sessionId?: string; cwd?: string; @@ -131,7 +131,7 @@ async function handleSidecarQueueCommand(ws: ServerWebSocket, msg: Recor const handlers: Record = { terminal: terminalWebsocket, - pi: piWebsocket, + chat: chatWebsocket, 'task-runner': taskRunnerWebsocket, pipeline: pipelineWebsocket, cliamp: cliampWebsocket, @@ -205,7 +205,7 @@ const devServerWebsocket = { }; handlers['dev-server'] = devServerWebsocket; -async function upgradeWs(req: Request, server: any, provider: 'terminal' | 'pi' | 'task-runner' | 'pipeline' | 'cliamp' | 'cliamp-audio' | 'desktop') { +async function upgradeWs(req: Request, server: any, provider: 'terminal' | 'chat' | 'task-runner' | 'pipeline' | 'cliamp' | 'cliamp-audio' | 'desktop') { const token = new URL(req.url).searchParams.get('token'); if (!token) return new Response('Unauthorized', { status: 401 }); @@ -292,7 +292,7 @@ const server = serve({ '/api/tasks/run/ws': (req, server) => upgradeWs(req, server, 'task-runner'), '/api/tasks/pipeline/ws': (req, server) => upgradeWs(req, server, 'pipeline'), '/api/terminal/ws': (req, server) => upgradeWs(req, server, 'terminal'), - '/api/pi/chat/ws': (req, server) => upgradeWs(req, server, 'pi'), + '/api/chat/ws': (req, server) => upgradeWs(req, server, 'chat'), '/api/cliamp/ws': (req, server) => upgradeWs(req, server, 'cliamp'), '/api/cliamp/audio/ws': (req, server) => upgradeWs(req, server, 'cliamp-audio'), '/api/desktop/ws': (req, server) => upgradeWs(req, server, 'desktop'), diff --git a/src/servers/api/chat-types.ts b/src/servers/api/chat-types.ts index b999d0e1..c18a489e 100644 --- a/src/servers/api/chat-types.ts +++ b/src/servers/api/chat-types.ts @@ -1,8 +1,8 @@ /** - * @deprecated Legacy chat types - Use types from ./pi/types.ts instead + * @deprecated Legacy chat types - Use types from ./chat/types.ts instead * * This file is kept for backward compatibility with existing code. - * New code should import from ./pi/types.ts + * New code should import from ./chat/types.ts */ // Re-export new Pi types for compatibility @@ -13,8 +13,8 @@ export type { MessageCost, SessionMeta, ModelInfo, - PiEvent, -} from './pi/types'; + ChatEvent, +} from './chat/types'; // Legacy types (kept for compatibility) export type ImageData = { mediaType: string; data: string }; diff --git a/src/servers/api/chat/chat.ts b/src/servers/api/chat/chat.ts index 7af40d81..2ac17b68 100644 --- a/src/servers/api/chat/chat.ts +++ b/src/servers/api/chat/chat.ts @@ -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 = { '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); + } +}); diff --git a/src/servers/api/pi/list-models.ts b/src/servers/api/chat/list-models.ts similarity index 92% rename from src/servers/api/pi/list-models.ts rename to src/servers/api/chat/list-models.ts index b96f46f9..0abc04fa 100644 --- a/src/servers/api/pi/list-models.ts +++ b/src/servers/api/chat/list-models.ts @@ -11,6 +11,6 @@ export function invalidateModelCache(): void { // No-op: the Claude model list is static. Kept for call-site compatibility. } -export async function listPiModels(): Promise { +export async function listChatModels(): Promise { return [...CLAUDE_CODE_MODELS]; } diff --git a/src/servers/api/pi/logger.ts b/src/servers/api/chat/logger.ts similarity index 100% rename from src/servers/api/pi/logger.ts rename to src/servers/api/chat/logger.ts diff --git a/src/servers/api/pi/session-manager.ts b/src/servers/api/chat/session-manager.ts similarity index 100% rename from src/servers/api/pi/session-manager.ts rename to src/servers/api/chat/session-manager.ts diff --git a/src/servers/api/pi/types.ts b/src/servers/api/chat/types.ts similarity index 99% rename from src/servers/api/pi/types.ts rename to src/servers/api/chat/types.ts index ff8bf916..b71123e1 100644 --- a/src/servers/api/pi/types.ts +++ b/src/servers/api/chat/types.ts @@ -120,7 +120,7 @@ export type ServerMessage = type: 'stopped'; }; -export type PiEvent = +export type ChatEvent = | { type: 'text'; text: string } | { type: 'delta'; text: string } | { diff --git a/src/servers/api/pi/websocket.ts b/src/servers/api/chat/websocket.ts similarity index 98% rename from src/servers/api/pi/websocket.ts rename to src/servers/api/chat/websocket.ts index f2626ded..e20a5737 100644 --- a/src/servers/api/pi/websocket.ts +++ b/src/servers/api/chat/websocket.ts @@ -1,9 +1,9 @@ import type { ServerWebSocket } from 'bun'; import { randomUUID } from 'crypto'; -import type { ClientMessage, ServerMessage, Message, PiEvent } from './types'; +import type { ClientMessage, ServerMessage, Message, ChatEvent } from './types'; import { sessionManager } from './session-manager'; import { sendClaudeCodeStreaming } from '@@/channels/send-claude-code'; -import { ensureClaudeSessionsCwd } from '../chat/claude-sessions'; +import { ensureClaudeSessionsCwd } from './claude-sessions'; import * as sidecar from '@@/sidecar-registry'; import { join } from 'path'; import { getHomeDirForRole } from '../../../servers/data-path'; @@ -113,7 +113,7 @@ export function close(ws: ServerWebSocket): void { } function createEventHandler(sessionId: string, model: string, cwd: string) { - return async (event: PiEvent): Promise => { + return async (event: ChatEvent): Promise => { const session = sessionManager.getSession(sessionId); if (!session) return; @@ -441,7 +441,7 @@ async function handleStop(ws: ServerWebSocket): Promise { sendToClient(ws, { type: 'stopped' }); } -export const piWebsocket = { +export const chatWebsocket = { open, message, close, diff --git a/src/servers/api/pi/rest.ts b/src/servers/api/pi/rest.ts deleted file mode 100644 index d728d849..00000000 --- a/src/servers/api/pi/rest.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { Context } from 'hono'; -import { createRouter } from '../../create-router'; -import { readSttConfig } from '../server-settings/stt'; -import { listPiModels } from './list-models'; -import { logger } from './logger'; -import { transcribeAudio } from '../stt/transcribe'; -import { getUserSettings } from 'officerdb'; - -export const piRestRouter = createRouter(); - -/** - * GET /api/pi/models — Claude tiers only. - */ -piRestRouter.get('/pi/models', async (ctx: Context) => { - try { - const models = await listPiModels(); - const providerNames: Record = { '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 /api/pi/stt - */ -piRestRouter.post('/pi/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); - } -}); diff --git a/src/servers/api/server-settings/pi-mono.ts b/src/servers/api/server-settings/pi-mono.ts index a0193e6a..ae1332f2 100644 --- a/src/servers/api/server-settings/pi-mono.ts +++ b/src/servers/api/server-settings/pi-mono.ts @@ -1,8 +1,8 @@ import { join } from 'node:path'; import { createRouter } from '../../create-router'; import { PI_CONFIG_DIR } from '../../data-path'; -import { invalidateModelCache } from '../pi/list-models'; -import { logger } from '../pi/logger'; +import { invalidateModelCache } from '../chat/list-models'; +import { logger } from '../chat/logger'; import { readConfigValue, writeConfigValue } from 'officerdb'; export const piMonoRouter = createRouter(); diff --git a/src/servers/api/tasks/pipeline-executor.ts b/src/servers/api/tasks/pipeline-executor.ts index 8596cdc4..d179647c 100644 --- a/src/servers/api/tasks/pipeline-executor.ts +++ b/src/servers/api/tasks/pipeline-executor.ts @@ -6,10 +6,10 @@ import { tmpdir } from 'node:os'; import { getUserSettings } from 'officerdb'; import { getTaskByDirName } from './task-files'; import { getHomeDirForRole, getHomeDir } from '../../data-path'; -import { resolveBaseCwd } from '../pi/websocket'; +import { resolveBaseCwd } from '../chat/websocket'; import { SANDBOX_HOME } from '../../sidecar/sandbox'; import { sendClaudeCodeStreaming } from '../../channels/send-claude-code'; -import type { PiEvent, MessageCost } from '../pi/types'; +import type { ChatEvent, MessageCost } from '../chat/types'; import * as jobManager from './pipeline-job-manager'; const DEFAULT_MODEL = 'claude-code'; @@ -117,7 +117,7 @@ async function runAgenticStep({ userId, email, username, role, taskDirName, prom fn(); }; - const onEvent = (event: PiEvent) => { + const onEvent = (event: ChatEvent) => { if (abortSignal.aborted) return; lastActivity = Date.now(); diff --git a/src/servers/channels/discord/handler.ts b/src/servers/channels/discord/handler.ts index 3808242b..a85dfcfe 100644 --- a/src/servers/channels/discord/handler.ts +++ b/src/servers/channels/discord/handler.ts @@ -3,11 +3,11 @@ import { findUserByIntegrationConfig, readConfigValue } from 'officerdb'; import { sendAndAwait, getSessionModel, setSessionModel } from '../send-and-await'; import { consumePairingCode } from '../pairing'; import { chunkMessage } from './chunker'; -import { listPiModels } from '@@/api/pi/list-models'; +import { listChatModels } from '@@/api/chat/list-models'; import { enqueueJob } from '../../queue/init'; import { readJob } from '@@/queue/storage'; import { openUserEmailDb } from '@@/api/email/email-db'; -import type { ModelInfo } from '@@/api/pi/types'; +import type { ModelInfo } from '@@/api/chat/types'; import { toShellUsername } from '@@/data-path'; const PAIRING_CODE_PATTERN = /^[A-Z0-9]{6}$/; @@ -19,7 +19,7 @@ type AccessPolicy = { allowedModels: string[] }; const ACCESS_POLICY_KEY = 'pi-access-policy'; async function getVisibleModels(): Promise { - const allModels = await listPiModels(); + const allModels = await listChatModels(); const policy = await readConfigValue(ACCESS_POLICY_KEY, { allowedModels: [] }); const allowed = policy.allowedModels; diff --git a/src/servers/channels/send-and-await.ts b/src/servers/channels/send-and-await.ts index 3f078266..1fa6445b 100644 --- a/src/servers/channels/send-and-await.ts +++ b/src/servers/channels/send-and-await.ts @@ -1,6 +1,6 @@ -import type { MessageCost } from '@@/api/pi/types'; +import type { MessageCost } from '@@/api/chat/types'; import { getUserSettings } from 'officerdb'; -import { logger } from '@@/api/pi/logger'; +import { logger } from '@@/api/chat/logger'; import { sendClaudeCode, clearClaudeCodeSession } from './send-claude-code'; const DEFAULT_MODEL = 'claude-code'; diff --git a/src/servers/channels/send-claude-code.ts b/src/servers/channels/send-claude-code.ts index 4cec6641..28975e7c 100644 --- a/src/servers/channels/send-claude-code.ts +++ b/src/servers/channels/send-claude-code.ts @@ -1,5 +1,5 @@ -import { logger } from '@@/api/pi/logger'; -import type { MessageCost, PiEvent } from '@@/api/pi/types'; +import { logger } from '@@/api/chat/logger'; +import type { MessageCost, ChatEvent } from '@@/api/chat/types'; import * as sidecar from '@@/sidecar-registry'; type ClaudeCodeParams = { @@ -40,7 +40,7 @@ type ClaudeCodeStreamingParams = { model?: string; role?: string; resumeSessionId?: string; - onEvent: (event: PiEvent) => void; + onEvent: (event: ChatEvent) => void; }; type ClaudeCodeStreamingHandle = { diff --git a/src/servers/channels/telegram/handler.ts b/src/servers/channels/telegram/handler.ts index 8cae8666..f34ac171 100644 --- a/src/servers/channels/telegram/handler.ts +++ b/src/servers/channels/telegram/handler.ts @@ -4,11 +4,11 @@ import { sendAndAwait, getSessionModel, setSessionModel } from '../send-and-awai import { consumePairingCode } from '../pairing'; import { chunkMessage } from './chunker'; import { getTelegramBot } from './bot'; -import { listPiModels } from '@@/api/pi/list-models'; +import { listChatModels } from '@@/api/chat/list-models'; import { enqueueJob } from '../../queue/init'; import { readJob } from '@@/queue/storage'; import { openUserEmailDb } from '@@/api/email/email-db'; -import type { ModelInfo } from '@@/api/pi/types'; +import type { ModelInfo } from '@@/api/chat/types'; import { toShellUsername } from '@@/data-path'; const PAIRING_CODE_PATTERN = /^[A-Z0-9]{6}$/; @@ -20,7 +20,7 @@ type AccessPolicy = { allowedModels: string[] }; const ACCESS_POLICY_KEY = 'pi-access-policy'; async function getVisibleModels(): Promise { - const allModels = await listPiModels(); + const allModels = await listChatModels(); const policy = await readConfigValue(ACCESS_POLICY_KEY, { allowedModels: [] }); const allowed = policy.allowedModels; diff --git a/src/servers/channels/whatsapp/handler.ts b/src/servers/channels/whatsapp/handler.ts index b02a8bae..117ecabc 100644 --- a/src/servers/channels/whatsapp/handler.ts +++ b/src/servers/channels/whatsapp/handler.ts @@ -3,11 +3,11 @@ import { findUserByIntegrationConfig, readConfigValue } from 'officerdb'; import { sendAndAwait, getSessionModel, setSessionModel } from '../send-and-await'; import { consumePairingCode } from '../pairing'; import { getWhatsAppClient } from './bot'; -import { listPiModels } from '@@/api/pi/list-models'; +import { listChatModels } from '@@/api/chat/list-models'; import { enqueueJob } from '../../queue/init'; import { readJob } from '@@/queue/storage'; import { openUserEmailDb } from '@@/api/email/email-db'; -import type { ModelInfo } from '@@/api/pi/types'; +import type { ModelInfo } from '@@/api/chat/types'; import { toShellUsername } from '@@/data-path'; const PAIRING_CODE_PATTERN = /^[A-Z0-9]{6}$/; @@ -24,7 +24,7 @@ function extractPhone(waId: string): string { } async function getVisibleModels(): Promise { - const allModels = await listPiModels(); + const allModels = await listChatModels(); const policy = await readConfigValue(ACCESS_POLICY_KEY, { allowedModels: [] }); const allowed = policy.allowedModels; diff --git a/src/servers/hono.ts b/src/servers/hono.ts index b1236e33..5e765558 100644 --- a/src/servers/hono.ts +++ b/src/servers/hono.ts @@ -19,7 +19,6 @@ import { settingsRouter } from './api/settings/settings'; import { dashboardsRouter } from './api/dashboards'; import { taskLogsRouter } from './api/task-logs/task-logs'; import { router as fileBrowserRouter } from './api/file-browser/router'; -import { piRestRouter } from './api/pi/rest'; import { devServerRouter, devServerProxyRouter } from './api/dev-server/router'; import { dockRouter } from './api/dock/dock'; import { integrationsRouter, googleCallbackHandler } from './api/integrations/integrations'; @@ -107,7 +106,6 @@ protectedRouter.route('/pipeline-jobs', pipelineJobsRouter); protectedRouter.route('/jobs', pipelineJobsRouter); // unified jobs API (script + pipeline); /pipeline-jobs kept for the existing UI desktopRouter.use(superAdminMiddleware); protectedRouter.route('/desktop', desktopRouter); -protectedRouter.route('/', piRestRouter); honoServer.route('/api', protectedRouter); diff --git a/src/servers/sidecar-registry.ts b/src/servers/sidecar-registry.ts index e9fbf0d1..e7e84084 100644 --- a/src/servers/sidecar-registry.ts +++ b/src/servers/sidecar-registry.ts @@ -14,7 +14,7 @@ import type { VncSessionInfo, } from './sidecar/protocol'; import type { SidecarRegistration } from './sidecar/registration-protocol'; -import type { PiEvent } from './api/pi/types'; +import type { ChatEvent } from './api/chat/types'; // ── Types ── @@ -339,7 +339,7 @@ export function clearClaudeSession(sessionKey: string, email?: string): void { } } -export function onClaudeEvent(handler: (sessionKey: string, event: PiEvent) => void): () => void { +export function onClaudeEvent(handler: (sessionKey: string, event: ChatEvent) => void): () => void { return on('claude:event', (msg) => { if (msg.type === 'claude:event') { handler( diff --git a/src/servers/sidecar/claude/claude-manager.ts b/src/servers/sidecar/claude/claude-manager.ts index 36cb5668..638ef659 100644 --- a/src/servers/sidecar/claude/claude-manager.ts +++ b/src/servers/sidecar/claude/claude-manager.ts @@ -1,6 +1,6 @@ import { join } from 'node:path'; import type { Subprocess } from 'bun'; -import type { PiEvent } from '../../api/pi/types'; +import type { ChatEvent } from '../../api/chat/types'; import type { ClaudeSpawnParams, ClaudeSpawnStreamingParams, ClaudeCodeResult } from '../protocol'; import { buildSandboxPrefix, buildRunuserSuffix, SANDBOX_HOME } from '../sandbox'; import { setClaudeSession, clearClaudeSession, getClaudeSession } from './state'; @@ -145,7 +145,7 @@ export async function spawnClaude(params: ClaudeSpawnParams): Promise void, + onEvent: (event: ChatEvent) => void, ): Promise { const { prompt, sessionKey, email } = params; diff --git a/src/servers/sidecar/claude/stream-parser.test.ts b/src/servers/sidecar/claude/stream-parser.test.ts index c529f52d..07457fc8 100644 --- a/src/servers/sidecar/claude/stream-parser.test.ts +++ b/src/servers/sidecar/claude/stream-parser.test.ts @@ -1,10 +1,10 @@ import { describe, test, expect } from 'bun:test'; import { processLine, createParseState, parseStream } from './stream-parser'; -import type { PiEvent } from '../../api/pi/types'; +import type { ChatEvent } from '../../api/chat/types'; import type { StreamParserCallbacks, ParseState } from './stream-parser'; -function makeCallbacks(): { events: PiEvent[]; sessionIds: string[]; callbacks: StreamParserCallbacks } { - const events: PiEvent[] = []; +function makeCallbacks(): { events: ChatEvent[]; sessionIds: string[]; callbacks: StreamParserCallbacks } { + const events: ChatEvent[] = []; const sessionIds: string[] = []; return { events, diff --git a/src/servers/sidecar/claude/stream-parser.ts b/src/servers/sidecar/claude/stream-parser.ts index e48f0e84..4ba0ffac 100644 --- a/src/servers/sidecar/claude/stream-parser.ts +++ b/src/servers/sidecar/claude/stream-parser.ts @@ -6,12 +6,12 @@ * system init, and result messages. */ -import type { PiEvent, MessageCost } from '../../api/pi/types'; +import type { ChatEvent, MessageCost } from '../../api/chat/types'; type SessionCallback = (sessionId: string) => void; type StreamParserCallbacks = { - onEvent: (event: PiEvent) => void; + onEvent: (event: ChatEvent) => void; onSessionId: SessionCallback; }; @@ -20,14 +20,14 @@ type ParseState = { gotResult: boolean; }; -function flushTextBuffer(state: ParseState, onEvent: (event: PiEvent) => void): void { +function flushTextBuffer(state: ParseState, onEvent: (event: ChatEvent) => void): void { if (state.textBuffer) { onEvent({ type: 'text', text: state.textBuffer }); state.textBuffer = ''; } } -function handleStreamEvent(msg: Record, state: ParseState, onEvent: (event: PiEvent) => void): void { +function handleStreamEvent(msg: Record, state: ParseState, onEvent: (event: ChatEvent) => void): void { const event = msg.event as Record | undefined; if (event?.type === 'content_block_delta') { const delta = event.delta as Record | undefined; @@ -38,7 +38,7 @@ function handleStreamEvent(msg: Record, state: ParseState, onEv } } -function handleAssistant(msg: Record, state: ParseState, onEvent: (event: PiEvent) => void): void { +function handleAssistant(msg: Record, state: ParseState, onEvent: (event: ChatEvent) => void): void { const message = msg.message as Record | undefined; const content = message?.content as Array> | undefined; if (!Array.isArray(content)) return; @@ -59,7 +59,7 @@ function handleAssistant(msg: Record, state: ParseState, onEven } } -function handleUser(msg: Record, onEvent: (event: PiEvent) => void): void { +function handleUser(msg: Record, onEvent: (event: ChatEvent) => void): void { const message = msg.message as Record | undefined; const content = message?.content as Array> | undefined; if (!Array.isArray(content)) return; diff --git a/src/servers/sidecar/claude/user-instance.ts b/src/servers/sidecar/claude/user-instance.ts index f435cbe4..dfe66220 100644 --- a/src/servers/sidecar/claude/user-instance.ts +++ b/src/servers/sidecar/claude/user-instance.ts @@ -172,7 +172,7 @@ async function handleCommand(cmd: SidecarCommand, reply: ReplyFn) { case 'claude:spawn-streaming': { reply({ type: 'claude:spawned', id: cmd.id, sessionKey: cmd.params.sessionKey }); - const onEvent = (event: import('../../api/pi/types').PiEvent) => { + const onEvent = (event: import('../../api/chat/types').ChatEvent) => { connection.send({ type: 'claude:event', sessionKey: cmd.params.sessionKey, event }); }; diff --git a/src/servers/sidecar/protocol.ts b/src/servers/sidecar/protocol.ts index aabc997b..d1b6c257 100644 --- a/src/servers/sidecar/protocol.ts +++ b/src/servers/sidecar/protocol.ts @@ -1,4 +1,4 @@ -import type { MessageCost, PiEvent } from '../api/pi/types'; +import type { MessageCost, ChatEvent } from '../api/chat/types'; // ── Envelope ── @@ -29,7 +29,7 @@ export type SidecarEvent = | { type: 'proxy:secret'; id: string; secret: string } // Claude Code | { type: 'claude:spawned'; id: string; sessionKey: string } - | { type: 'claude:event'; sessionKey: string; event: PiEvent } + | { type: 'claude:event'; sessionKey: string; event: ChatEvent } | { type: 'claude:result'; id: string; result: ClaudeCodeResult } | { type: 'claude:error'; id: string; error: string } | { type: 'claude:killed'; id: string } diff --git a/src/workspaces/officerdev/src/apps/Chat/useAttachments.ts b/src/workspaces/officerdev/src/apps/Chat/useAttachments.ts index 7afcc7a8..c6c0bc6b 100644 --- a/src/workspaces/officerdev/src/apps/Chat/useAttachments.ts +++ b/src/workspaces/officerdev/src/apps/Chat/useAttachments.ts @@ -22,7 +22,7 @@ export function useAttachments(params?: UseAttachmentsParams) { const res = await client.post<{ url: string; title: string; content: string; attachmentId: string }>('/scrape', { url, ...(params?.sessionId ? { sessionId: params.sessionId } : {}), - provider: 'pi-mono', + provider: 'chat', }); setAttachments((prev) => prev.map((a, i) => @@ -48,7 +48,7 @@ export function useAttachments(params?: UseAttachmentsParams) { const formData = new FormData(); formData.append('file', file); if (params?.sessionId) formData.append('sessionId', params.sessionId); - formData.append('provider', 'pi-mono'); + formData.append('provider', 'chat'); const res = await client.post<{ filename: string; dataUrl: string; attachmentId: string }>('/upload', formData); setAttachments((prev) => diff --git a/src/workspaces/officerdev/src/apps/Chat/useAudioRecording.ts b/src/workspaces/officerdev/src/apps/Chat/useAudioRecording.ts index f22533a3..cbca46bd 100644 --- a/src/workspaces/officerdev/src/apps/Chat/useAudioRecording.ts +++ b/src/workspaces/officerdev/src/apps/Chat/useAudioRecording.ts @@ -47,7 +47,7 @@ export function useAudioRecording(onTranscription: (text: string) => void) { formData.append('response_format', 'json'); const token = localStorage.getItem('BEARER_TOKEN') ?? sessionStorage.getItem('BEARER_TOKEN'); - const res = await fetch('/api/pi/stt', { + const res = await fetch('/api/chat/stt', { method: 'POST', body: formData, headers: token ? { Authorization: `Bearer ${token}` } : {}, diff --git a/src/workspaces/officerdev/src/hooks/useChat.ts b/src/workspaces/officerdev/src/hooks/useChat.ts index 573ac52e..d11cec4c 100644 --- a/src/workspaces/officerdev/src/hooks/useChat.ts +++ b/src/workspaces/officerdev/src/hooks/useChat.ts @@ -80,7 +80,7 @@ export function useChat(initialSessionId?: string, initialModel?: string | null, const token = localStorage.getItem('BEARER_TOKEN'); const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const wsUrl = `${protocol}//${window.location.host}/api/pi/chat/ws?token=${token}`; + const wsUrl = `${protocol}//${window.location.host}/api/chat/chat/ws?token=${token}`; function flushStreaming() { if (rafRef.current !== null) cancelAnimationFrame(rafRef.current); diff --git a/src/workspaces/state/src/index.ts b/src/workspaces/state/src/index.ts index deba72a5..706d373f 100644 --- a/src/workspaces/state/src/index.ts +++ b/src/workspaces/state/src/index.ts @@ -2,7 +2,7 @@ export { useSettings, DEFAULT_SETTINGS } from './useSettings'; export type { UseSettingsType, UserSettings, UserState } from './useSettings'; export { useUserState } from './useUserState'; export { useDashboardState } from './useDashboardState'; -export { usePiModels, useVisiblePiModels, useUserVisibleModels, useEnabledPiModels, modelKey } from './useModels'; +export { useModels, useVisibleModels, useUserVisibleModels, useEnabledModels, modelKey } from './useModels'; export type { ModelOption } from './useModels'; export { useAccessPolicy } from './useAccessPolicy'; export { useClaudeSessions, useChatPwds } from './useClaudeSessions'; diff --git a/src/workspaces/state/src/useModels.ts b/src/workspaces/state/src/useModels.ts index bcc30189..935b38ce 100644 --- a/src/workspaces/state/src/useModels.ts +++ b/src/workspaces/state/src/useModels.ts @@ -23,19 +23,19 @@ export function getHostHome(): string { return globalHostHome; } -export function usePiModels() { +export function useModels() { const client = useClient(); const { isAuthenticated } = useAuth(); const { data: models = [] } = useQuery({ - queryKey: ['PI_MODELS'], + queryKey: ['CHAT_MODELS'], enabled: isAuthenticated, queryFn: async () => { const data = await client.get<{ models: ModelOption[]; providerNames?: Record; hostHome?: string; - }>('/pi/models'); + }>('/chat/models'); if (data.providerNames) { globalProviderNames = data.providerNames; @@ -54,8 +54,8 @@ export function usePiModels() { } /** Filter models by system-wide access policy. New providers pass through. */ -export function useVisiblePiModels() { - const models = usePiModels(); +export function useVisibleModels() { + const models = useModels(); const { policy } = useAccessPolicy(); const allowed = policy.allowedModels; @@ -74,8 +74,8 @@ export function useVisiblePiModels() { /** Models visible to the current user: system policy (members) or all (admins), minus per-user hidden. */ export function useUserVisibleModels() { - const allModels = usePiModels(); - const policyModels = useVisiblePiModels(); + const allModels = useModels(); + const policyModels = useVisibleModels(); const { user } = useAuth(); const { settings } = useSettings(); @@ -90,8 +90,8 @@ export function useUserVisibleModels() { } /** Strict filtering — only explicitly allowed models, no "new provider" passthrough. */ -export function useEnabledPiModels() { - const models = usePiModels(); +export function useEnabledModels() { + const models = useModels(); const { policy } = useAccessPolicy(); const allowed = policy.allowedModels;