fix: Pi harness - dynamic models, correct RPC protocol, proper event handling

Backend:
- /api/pi/models now calls 'pi --list-models' with stored API keys
- pi-bridge.ts: callback-based event handling (matches pi-monorepo)
- pi-bridge.ts: correct RPC format (type: 'prompt' not jsonrpc)
- pi-bridge.ts: pass API keys to Pi process env
- websocket.ts: event handler runs in background, no blocking
- rest.ts: fix user home path (getHomeDir instead of hardcoded)

Frontend:
- Fix /api/ double prefix in useChatSessions, useChatGroups, useModels
- Add PROVIDER_DISPLAY mapping in SystemSettings.tsx
- Provider tabs show friendly names (e.g., 'OpenCode Zen')

UI (from previous session):
- Grouped session list with collapsible folders
- CreateGroupDialog, GroupContextMenu, SessionContextMenu components
This commit is contained in:
2026-02-20 22:55:00 +00:00
parent ca497f9eff
commit 68c7973281
11 changed files with 978 additions and 457 deletions
+5 -5
View File
@@ -11,11 +11,11 @@ export function useChatGroups() {
const { data: groups = [] } = useQuery<GroupEntry[]>({
queryKey: ['PI_GROUPS'],
enabled: isAuthenticated,
queryFn: () => client.get<{ groups: GroupEntry[] }>('/api/pi/groups').then((r) => r.groups),
queryFn: () => client.get<{ groups: GroupEntry[] }>('/pi/groups').then((r) => r.groups),
});
async function createGroup(name: string, slug: string, description?: string, sessionIds?: string[]) {
const result = await client.post<{ group: GroupEntry }>('/api/pi/groups', {
const result = await client.post<{ group: GroupEntry }>('/pi/groups', {
name,
slug,
description,
@@ -27,18 +27,18 @@ export function useChatGroups() {
}
async function updateGroup(slug: string, updates: { name?: string; description?: string }) {
await client.patch(`/api/pi/groups/${slug}`, updates);
await client.patch(`/pi/groups/${slug}`, updates);
queryClient.invalidateQueries({ queryKey: ['PI_GROUPS'] });
}
async function deleteGroup(slug: string) {
await client.delete(`/api/pi/groups/${slug}`);
await client.delete(`/pi/groups/${slug}`);
queryClient.invalidateQueries({ queryKey: ['PI_GROUPS'] });
queryClient.invalidateQueries({ queryKey: ['PI_SESSIONS'] });
}
async function moveSession(sessionId: string, groupSlug: string | null) {
await client.post(`/api/pi/sessions/${sessionId}/move`, { groupSlug });
await client.post(`/pi/sessions/${sessionId}/move`, { groupSlug });
queryClient.invalidateQueries({ queryKey: ['PI_GROUPS'] });
queryClient.invalidateQueries({ queryKey: ['PI_SESSIONS'] });
}
@@ -28,24 +28,24 @@ export function useChatSessions() {
const { data: sessions = [] } = useQuery<SessionEntry[]>({
queryKey: ['PI_SESSIONS'],
enabled: isAuthenticated,
queryFn: () => client.post<{ sessions: SessionEntry[] }>('/api/pi/sessions').then((r) => r.sessions),
queryFn: () => client.post<{ sessions: SessionEntry[] }>('/pi/sessions').then((r) => r.sessions),
});
function getSession(sessionId: string) {
return client.get<{ session: SessionWithMessages }>(`/api/pi/sessions/${sessionId}`);
return client.get<{ session: SessionWithMessages }>(`/pi/sessions/${sessionId}`);
}
function saveMessages(sessionId: string, messages: ChatMessage[]) {
return client.put(`/api/pi/sessions/${sessionId}/messages`, messages);
return client.put(`/pi/sessions/${sessionId}/messages`, messages);
}
async function renameSession(sessionId: string, title: string) {
await client.patch(`/api/pi/sessions/${sessionId}`, { title });
await client.patch(`/pi/sessions/${sessionId}`, { title });
queryClient.invalidateQueries({ queryKey: ['PI_SESSIONS'] });
}
async function deleteSession(sessionId: string) {
await client.delete(`/api/pi/sessions/${sessionId}`);
await client.delete(`/pi/sessions/${sessionId}`);
queryClient.setQueryData<SessionEntry[]>(
['PI_SESSIONS'],
(prev) => prev?.filter((s) => s.id !== sessionId) ?? [],
@@ -53,7 +53,7 @@ export function useChatSessions() {
}
function searchSessions(query: string) {
return client.get<{ results: SessionEntry[] }>(`/api/pi/sessions/search?q=${encodeURIComponent(query)}`);
return client.get<{ results: SessionEntry[] }>(`/pi/sessions/search?q=${encodeURIComponent(query)}`);
}
return {
+1 -1
View File
@@ -18,7 +18,7 @@ export function usePiModels() {
queryKey: ['PI_MODELS'],
enabled: isAuthenticated,
queryFn: async () => {
const data = await client.get<{ models: ModelOption[] }>('/api/pi/models');
const data = await client.get<{ models: ModelOption[] }>('/pi/models');
return data.models;
},
staleTime: 5 * 60 * 1000,