chat: rename the pi-mono provider router + purge residual pi names (Stage 4b/2)
Renames the AI-harness/provider settings router into the chat namespace and clears the remaining "pi" identifiers from the chat stack. - server-settings/pi-mono.ts → chat-providers.ts; piMonoRouter → chatProvidersRouter; route /server-settings/pi-mono → /server-settings/chat-providers (+ all callers) - piId → providerId (PROVIDERS map + AIHarnessesSection UI), PiProvider → ChatProvider, PI_MONO_* query keys → CHAT_PROVIDERS_*, installPiMono → installAgent - data-path: PI_CONFIG_DIR → AGENT_CONFIG_DIR (path ~/.pi/agent unchanged); drop dead getPiMonoDir/getPiMonoSessionDir exports - settings: flip the vestigial defaultProvider literal 'pi' → 'chat' (never read; only defaultModel drives behavior); access-policy config key 'pi-access-policy' → 'chat-access-policy' - misc: ModelSelector fallback label, TaskDefaults model grouping, CapabilityPage chat var, a stale stream-parser comment Intentionally left (genuine external `pi`/opencode references, not ours to rename): the `pi` binary install/version flow (@mariozechner/pi-coding-agent, `which pi`), the ~/.pi/agent config path, PI_TOOLS_DIRS/PI_SEARXNG_URL runtime env-var contract, TOOL.md `targets: pi` metadata, and the "Pi Mono" installer UI label. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -279,20 +279,20 @@ export const CapabilityChat = ({
|
||||
? (description ?? `Help me create the content for this new ${kind} file`)
|
||||
: `Help me understand and improve this ${kind} file`;
|
||||
|
||||
const pi = useChat(undefined, undefined, { replaceUrl: false });
|
||||
const chat = useChat(undefined, undefined, { replaceUrl: false });
|
||||
|
||||
const onResponseEndRef = useRef(onResponseEnd);
|
||||
onResponseEndRef.current = onResponseEnd;
|
||||
|
||||
const wasGenerating = useRef(false);
|
||||
useEffect(() => {
|
||||
if (wasGenerating.current && !pi.isGenerating) {
|
||||
if (wasGenerating.current && !chat.isGenerating) {
|
||||
onResponseEndRef.current?.();
|
||||
}
|
||||
wasGenerating.current = pi.isGenerating;
|
||||
}, [pi.isGenerating]);
|
||||
wasGenerating.current = chat.isGenerating;
|
||||
}, [chat.isGenerating]);
|
||||
|
||||
return <EmbeddableChat chat={pi} defaultInput={defaultInput} promptPrefix={promptFrontmatter} className="h-full" />;
|
||||
return <EmbeddableChat chat={chat} defaultInput={defaultInput} promptPrefix={promptFrontmatter} className="h-full" />;
|
||||
};
|
||||
|
||||
export const FrontmatterBlock = ({ yaml }: { yaml: string }) => {
|
||||
|
||||
@@ -251,7 +251,7 @@ function MemberModelsSection() {
|
||||
const refreshModels = async () => {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
await client.post('/server-settings/pi-mono/local-providers/refresh');
|
||||
await client.post('/server-settings/chat-providers/local-providers/refresh');
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
|
||||
toast.success('Models refreshed');
|
||||
} catch {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useUserVisibleModels, type ModelOption } from 'state/useModels';
|
||||
|
||||
export const TaskDefaults = () => {
|
||||
const { settings, saveSettings } = useSettings();
|
||||
const piModels = useUserVisibleModels();
|
||||
const models = useUserVisibleModels();
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const [model, setModel] = useState<string | null>(settings.tasks.defaultModel);
|
||||
@@ -37,13 +37,13 @@ export const TaskDefaults = () => {
|
||||
.map(([provider, models]) => ({ provider, models: models.sort((a, b) => a.name.localeCompare(b.name)) }));
|
||||
};
|
||||
|
||||
const piGroups = useMemo(() => buildGroups(piModels, 'Pi'), [piModels]);
|
||||
const modelGroups = useMemo(() => buildGroups(models, 'Chat'), [models]);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (isSaving) return;
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await saveSettings({ ...settings, tasks: { defaultProvider: 'pi', defaultModel: model } });
|
||||
await saveSettings({ ...settings, tasks: { defaultProvider: 'chat', defaultModel: model } });
|
||||
toast.success('Task defaults saved');
|
||||
} catch {
|
||||
toast.error('Failed to save settings');
|
||||
@@ -61,7 +61,7 @@ export const TaskDefaults = () => {
|
||||
<SelectValue placeholder="Same as chat default" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="z-[600] max-h-[300px]">
|
||||
{piGroups.map(({ provider, models }) => (
|
||||
{modelGroups.map(({ provider, models }) => (
|
||||
<SelectGroup key={`pi-${provider}`}>
|
||||
<SelectLabel>{provider}</SelectLabel>
|
||||
{models.map((m) => (
|
||||
|
||||
+61
-61
@@ -33,7 +33,7 @@ type ProbeResult = {
|
||||
error?: string;
|
||||
};
|
||||
|
||||
type PiProvider = { key: string; piId: string };
|
||||
type ChatProvider = { key: string; providerId: string };
|
||||
|
||||
type ProbeState = { step: 'url' } | { step: 'probing' } | { step: 'auth'; probe: ProbeResult } | { step: 'saving' };
|
||||
|
||||
@@ -42,31 +42,31 @@ export const AIHarnessesSection = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const [installing, setInstalling] = useState(false);
|
||||
|
||||
const { data: PI_PROVIDERS = [] as PiProvider[] } = useQuery({
|
||||
queryKey: ['PI_MONO_PROVIDERS'],
|
||||
queryFn: () => client.get<PiProvider[]>('/server-settings/pi-mono/providers'),
|
||||
const { data: CHAT_PROVIDERS = [] as ChatProvider[] } = useQuery({
|
||||
queryKey: ['CHAT_PROVIDERS_PROVIDERS'],
|
||||
queryFn: () => client.get<ChatProvider[]>('/server-settings/chat-providers/providers'),
|
||||
});
|
||||
|
||||
const { data: piMonoVersion, isLoading: piMonoLoading } = useQuery({
|
||||
queryKey: ['PI_MONO_VERSION'],
|
||||
queryFn: () => client.get<VersionInfo>('/server-settings/pi-mono/version'),
|
||||
queryKey: ['CHAT_PROVIDERS_VERSION'],
|
||||
queryFn: () => client.get<VersionInfo>('/server-settings/chat-providers/version'),
|
||||
});
|
||||
|
||||
const { data: piMonoKeys } = useQuery({
|
||||
queryKey: ['PI_MONO_API_KEYS'],
|
||||
queryFn: () => client.get<StoredApiKeys>('/server-settings/pi-mono/api-keys'),
|
||||
queryKey: ['CHAT_PROVIDERS_API_KEYS'],
|
||||
queryFn: () => client.get<StoredApiKeys>('/server-settings/chat-providers/api-keys'),
|
||||
enabled: !!piMonoVersion?.version,
|
||||
});
|
||||
|
||||
const { data: localProviders = [] as LocalProviderEntry[] } = useQuery({
|
||||
queryKey: ['PI_MONO_LOCAL_PROVIDERS'],
|
||||
queryFn: () => client.get<LocalProviderEntry[]>('/server-settings/pi-mono/local-providers'),
|
||||
queryKey: ['CHAT_PROVIDERS_LOCAL_PROVIDERS'],
|
||||
queryFn: () => client.get<LocalProviderEntry[]>('/server-settings/chat-providers/local-providers'),
|
||||
enabled: !!piMonoVersion?.version,
|
||||
});
|
||||
|
||||
const { data: localHealth = {} as Record<string, boolean> } = useQuery({
|
||||
queryKey: ['PI_MONO_LOCAL_HEALTH'],
|
||||
queryFn: () => client.get<Record<string, boolean>>('/server-settings/pi-mono/local-providers/health'),
|
||||
queryKey: ['CHAT_PROVIDERS_LOCAL_HEALTH'],
|
||||
queryFn: () => client.get<Record<string, boolean>>('/server-settings/chat-providers/local-providers/health'),
|
||||
enabled: localProviders.length > 0,
|
||||
refetchInterval: 15_000,
|
||||
});
|
||||
@@ -96,31 +96,31 @@ export const AIHarnessesSection = () => {
|
||||
};
|
||||
|
||||
const storedKeys: StoredApiKeys['keys'] = piMonoKeys?.keys ?? [];
|
||||
const storedPiIds = new Set(storedKeys.map((k) => k.provider));
|
||||
const connectedProviders = PI_PROVIDERS.filter((p) => storedPiIds.has(p.piId));
|
||||
const unconnectedProviders = PI_PROVIDERS.filter((p) => !storedPiIds.has(p.piId));
|
||||
const storedProviderIds = new Set(storedKeys.map((k) => k.provider));
|
||||
const connectedProviders = CHAT_PROVIDERS.filter((p) => storedProviderIds.has(p.providerId));
|
||||
const unconnectedProviders = CHAT_PROVIDERS.filter((p) => !storedProviderIds.has(p.providerId));
|
||||
|
||||
const { data: remoteHealth = {} as Record<string, boolean | null> } = useQuery({
|
||||
queryKey: ['PI_MONO_REMOTE_HEALTH'],
|
||||
queryFn: () => client.get<Record<string, boolean | null>>('/server-settings/pi-mono/api-keys/health'),
|
||||
queryKey: ['CHAT_PROVIDERS_REMOTE_HEALTH'],
|
||||
queryFn: () => client.get<Record<string, boolean | null>>('/server-settings/chat-providers/api-keys/health'),
|
||||
enabled: connectedProviders.length > 0,
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
|
||||
const getStoredMasked = (piId: string) => storedKeys.find((k) => k.provider === piId)?.value ?? '';
|
||||
const getStoredMasked = (providerId: string) => storedKeys.find((k) => k.provider === providerId)?.value ?? '';
|
||||
|
||||
const saveApiKey = async (piId: string) => {
|
||||
const value = keyInputs[piId];
|
||||
const saveApiKey = async (providerId: string) => {
|
||||
const value = keyInputs[providerId];
|
||||
if (value === undefined) return;
|
||||
setSavingKey(piId);
|
||||
setSavingKey(providerId);
|
||||
try {
|
||||
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'] });
|
||||
await client.put('/server-settings/chat-providers/api-keys', { provider: providerId, value });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_API_KEYS'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_REMOTE_HEALTH'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
|
||||
setKeyInputs((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[piId];
|
||||
delete next[providerId];
|
||||
return next;
|
||||
});
|
||||
setEditingProvider(null);
|
||||
@@ -129,19 +129,19 @@ export const AIHarnessesSection = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const disconnectProvider = async (provider: (typeof PI_PROVIDERS)[number]) => {
|
||||
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'] });
|
||||
const disconnectProvider = async (provider: (typeof CHAT_PROVIDERS)[number]) => {
|
||||
await client.put('/server-settings/chat-providers/api-keys', { provider: provider.providerId, value: '' });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_API_KEYS'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_REMOTE_HEALTH'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
|
||||
if (editingProvider === provider.key) setEditingProvider(null);
|
||||
};
|
||||
|
||||
const installPiMono = async () => {
|
||||
const installAgent = async () => {
|
||||
setInstalling(true);
|
||||
try {
|
||||
const result = await client.post<VersionInfo>('/server-settings/pi-mono/install');
|
||||
queryClient.setQueryData(['PI_MONO_VERSION'], result);
|
||||
const result = await client.post<VersionInfo>('/server-settings/chat-providers/install');
|
||||
queryClient.setQueryData(['CHAT_PROVIDERS_VERSION'], result);
|
||||
} finally {
|
||||
setInstalling(false);
|
||||
}
|
||||
@@ -152,7 +152,7 @@ export const AIHarnessesSection = () => {
|
||||
) => {
|
||||
setProbeState({ step: 'probing' });
|
||||
try {
|
||||
const result = await client.post<ProbeResult>('/server-settings/pi-mono/local-providers/probe', {
|
||||
const result = await client.post<ProbeResult>('/server-settings/chat-providers/local-providers/probe', {
|
||||
url: localUrl.trim(),
|
||||
auth,
|
||||
});
|
||||
@@ -182,7 +182,7 @@ export const AIHarnessesSection = () => {
|
||||
// Re-probe with credentials to verify they work
|
||||
setProbeState({ step: 'probing' });
|
||||
try {
|
||||
const result = await client.post<ProbeResult>('/server-settings/pi-mono/local-providers/probe', {
|
||||
const result = await client.post<ProbeResult>('/server-settings/chat-providers/local-providers/probe', {
|
||||
url: localUrl.trim(),
|
||||
auth,
|
||||
});
|
||||
@@ -209,14 +209,14 @@ export const AIHarnessesSection = () => {
|
||||
) => {
|
||||
setProbeState({ step: 'saving' });
|
||||
try {
|
||||
await client.post('/server-settings/pi-mono/local-providers', {
|
||||
await client.post('/server-settings/chat-providers/local-providers', {
|
||||
url: localUrl.trim(),
|
||||
name: localName.trim() || probe.name,
|
||||
apiType: probe.apiType,
|
||||
auth,
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_PROVIDERS'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_HEALTH'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_LOCAL_PROVIDERS'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_LOCAL_HEALTH'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
|
||||
toast.success(`Connected to ${probe.name}`);
|
||||
resetLocalForm();
|
||||
@@ -227,9 +227,9 @@ export const AIHarnessesSection = () => {
|
||||
};
|
||||
|
||||
const removeLocalProvider = async (id: string) => {
|
||||
await client.delete(`/server-settings/pi-mono/local-providers/${id}`);
|
||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_PROVIDERS'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_HEALTH'] });
|
||||
await client.delete(`/server-settings/chat-providers/local-providers/${id}`);
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_LOCAL_PROVIDERS'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_LOCAL_HEALTH'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
|
||||
};
|
||||
|
||||
@@ -245,7 +245,7 @@ export const AIHarnessesSection = () => {
|
||||
<span className="text-xs text-duck-dark/50">{piMonoVersion.version}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={installPiMono}
|
||||
onClick={installAgent}
|
||||
disabled={installing}
|
||||
className="p-0.5 rounded hover:bg-duck-teal/10 cursor-pointer transition-colors disabled:opacity-30"
|
||||
title="Update Pi Mono"
|
||||
@@ -257,7 +257,7 @@ export const AIHarnessesSection = () => {
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-6 text-xs bg-duck-teal text-duck-yellow hover:bg-duck-teal/90"
|
||||
onClick={installPiMono}
|
||||
onClick={installAgent}
|
||||
disabled={installing}
|
||||
>
|
||||
{installing ? 'Installing...' : 'Install'}
|
||||
@@ -423,20 +423,20 @@ export const AIHarnessesSection = () => {
|
||||
<span className="text-sm font-medium text-duck-dark dark:text-foreground">Remote Providers</span>
|
||||
<div className="mt-3 flex flex-col gap-2">
|
||||
{connectedProviders.map((provider) => (
|
||||
<div key={provider.piId} className="flex flex-col gap-2">
|
||||
<div key={provider.providerId} className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`inline-block h-2 w-2 shrink-0 rounded-full ${
|
||||
remoteHealth[provider.piId] === true
|
||||
remoteHealth[provider.providerId] === true
|
||||
? 'bg-green-500'
|
||||
: remoteHealth[provider.piId] === false
|
||||
: remoteHealth[provider.providerId] === false
|
||||
? 'bg-red-500'
|
||||
: 'bg-duck-dark/20'
|
||||
}`}
|
||||
title={
|
||||
remoteHealth[provider.piId] === true
|
||||
remoteHealth[provider.providerId] === true
|
||||
? 'Valid'
|
||||
: remoteHealth[provider.piId] === false
|
||||
: remoteHealth[provider.providerId] === false
|
||||
? 'Invalid'
|
||||
: 'Unknown'
|
||||
}
|
||||
@@ -464,19 +464,19 @@ export const AIHarnessesSection = () => {
|
||||
<Input
|
||||
type="password"
|
||||
className="h-7 text-xs flex-1"
|
||||
placeholder={getStoredMasked(provider.piId) || 'Enter API key'}
|
||||
value={keyInputs[provider.piId] ?? ''}
|
||||
onChange={(ev) => setKeyInputs((prev) => ({ ...prev, [provider.piId]: ev.target.value }))}
|
||||
placeholder={getStoredMasked(provider.providerId) || 'Enter API key'}
|
||||
value={keyInputs[provider.providerId] ?? ''}
|
||||
onChange={(ev) => setKeyInputs((prev) => ({ ...prev, [provider.providerId]: ev.target.value }))}
|
||||
onKeyDown={(ev) => {
|
||||
if (ev.key === 'Enter' && keyInputs[provider.piId]) saveApiKey(provider.piId);
|
||||
if (ev.key === 'Enter' && keyInputs[provider.providerId]) saveApiKey(provider.providerId);
|
||||
if (ev.key === 'Escape') setEditingProvider(null);
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
disabled={keyInputs[provider.piId] === undefined || savingKey === provider.piId}
|
||||
onClick={() => saveApiKey(provider.piId)}
|
||||
disabled={keyInputs[provider.providerId] === undefined || savingKey === provider.providerId}
|
||||
onClick={() => saveApiKey(provider.providerId)}
|
||||
className="shrink-0 p-1 rounded hover:bg-duck-teal/10 cursor-pointer transition-colors disabled:opacity-30 disabled:cursor-default"
|
||||
title="Save key"
|
||||
>
|
||||
@@ -495,10 +495,10 @@ export const AIHarnessesSection = () => {
|
||||
))}
|
||||
{editingProvider &&
|
||||
(() => {
|
||||
const provider = PI_PROVIDERS.find((p) => p.key === editingProvider);
|
||||
if (!provider || connectedProviders.some((cp) => cp.piId === provider.piId)) return null;
|
||||
const provider = CHAT_PROVIDERS.find((p) => p.key === editingProvider);
|
||||
if (!provider || connectedProviders.some((cp) => cp.providerId === provider.providerId)) return null;
|
||||
return (
|
||||
<div key={provider.piId} className="flex items-center gap-2">
|
||||
<div key={provider.providerId} className="flex items-center gap-2">
|
||||
<label className="w-36 text-duck-dark/70 dark:text-foreground/70 shrink-0 truncate font-medium text-[11px]">
|
||||
{provider.key}
|
||||
</label>
|
||||
@@ -506,18 +506,18 @@ export const AIHarnessesSection = () => {
|
||||
type="password"
|
||||
className="h-7 text-xs flex-1"
|
||||
placeholder="Enter API key"
|
||||
value={keyInputs[provider.piId] ?? ''}
|
||||
onChange={(ev) => setKeyInputs((prev) => ({ ...prev, [provider.piId]: ev.target.value }))}
|
||||
value={keyInputs[provider.providerId] ?? ''}
|
||||
onChange={(ev) => setKeyInputs((prev) => ({ ...prev, [provider.providerId]: ev.target.value }))}
|
||||
onKeyDown={(ev) => {
|
||||
if (ev.key === 'Enter' && keyInputs[provider.piId]) saveApiKey(provider.piId);
|
||||
if (ev.key === 'Enter' && keyInputs[provider.providerId]) saveApiKey(provider.providerId);
|
||||
if (ev.key === 'Escape') setEditingProvider(null);
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
disabled={keyInputs[provider.piId] === undefined || savingKey === provider.piId}
|
||||
onClick={() => saveApiKey(provider.piId)}
|
||||
disabled={keyInputs[provider.providerId] === undefined || savingKey === provider.providerId}
|
||||
onClick={() => saveApiKey(provider.providerId)}
|
||||
className="shrink-0 p-1 rounded hover:bg-duck-teal/10 cursor-pointer transition-colors disabled:opacity-30 disabled:cursor-default"
|
||||
title="Save key"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user