feat: remove legacy OpenCode runtime

Cut product flows over to Coding/Pi and retain only the migration-owned v1 boundary. Promote supported native optional packages because electron-builder omitted pnpm transitive optional closure from the packaged ASAR.
This commit is contained in:
2026-08-24 12:17:43 +08:00
parent 61fede2b4d
commit 5a275b93a7
199 changed files with 1330 additions and 64725 deletions

View File

@@ -1,131 +0,0 @@
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
CircleDot,
Play,
RefreshCw,
ServerCog,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useOpencodeStore } from '@/stores/opencode';
import { cn } from '@/lib/utils';
function statusClass(state: string): string {
if (state === 'running') {
return 'border-emerald-500/20 bg-emerald-500/10 text-emerald-700';
}
if (state === 'error') {
return 'border-red-500/20 bg-red-500/10 text-red-700';
}
if (state === 'starting') {
return 'border-amber-500/20 bg-amber-500/10 text-amber-700';
}
return 'border-border bg-surface-subtle text-muted-foreground';
}
export function OpencodeRuntimePanel() {
const { t } = useTranslation('dashboard');
const status = useOpencodeStore((state) => state.status);
const summary = useOpencodeStore((state) => state.runtimeConfigSummary);
const loading = useOpencodeStore((state) => state.loading);
const error = useOpencodeStore((state) => state.error);
const refreshStatus = useOpencodeStore((state) => state.refreshStatus);
const loadRuntimeConfigSummary = useOpencodeStore((state) => state.loadRuntimeConfigSummary);
const restart = useOpencodeStore((state) => state.restart);
const start = useOpencodeStore((state) => state.start);
useEffect(() => {
void refreshStatus();
void loadRuntimeConfigSummary();
}, [loadRuntimeConfigSummary, refreshStatus]);
const isStopped = status.state === 'stopped';
const isBusy = loading || status.state === 'starting';
const handleRuntimeAction = async () => {
if (isStopped) {
await start();
} else {
await restart();
}
await refreshStatus();
await loadRuntimeConfigSummary();
};
return (
<section data-testid="opencode-runtime-panel" className="space-y-5">
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div className="min-w-0">
<div className="flex items-center gap-2">
<ServerCog className="h-5 w-5 text-foreground/70" aria-hidden="true" />
<h2 className="text-2xl font-semibold tracking-[-0.03em] text-foreground">
{t('opencodeRuntime.title', 'Makelore runtime')}
</h2>
</div>
</div>
<Button
type="button"
variant="outline"
onClick={() => void handleRuntimeAction()}
disabled={isBusy}
aria-label={isStopped
? t('opencodeRuntime.start', 'Start runtime')
: t('opencodeRuntime.restart', 'Restart runtime')}
className="h-10 rounded-xl border-border bg-transparent px-4 hover:bg-surface-subtle"
>
{isStopped ? (
<Play className="mr-2 h-4 w-4" aria-hidden="true" />
) : (
<RefreshCw className={cn('mr-2 h-4 w-4', isBusy && 'animate-spin')} aria-hidden="true" />
)}
{isStopped
? t('opencodeRuntime.start', 'Start runtime')
: t('opencodeRuntime.restart', 'Restart runtime')}
</Button>
</div>
<div className="grid gap-3 sm:grid-cols-3">
<div className="surface-card rounded-2xl border border-border/70 bg-background p-4 shadow-none">
<p className="text-xs font-semibold uppercase text-muted-foreground">
{t('opencodeRuntime.status', 'Status')}
</p>
<div className={cn('mt-3 inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm font-semibold', statusClass(status.state))}>
<CircleDot className="h-3.5 w-3.5" aria-hidden="true" />
{status.state}
</div>
</div>
<div className="surface-card rounded-2xl border border-border/70 bg-background p-4 shadow-none">
<p className="text-xs font-semibold uppercase text-muted-foreground">
{t('opencodeRuntime.port', 'Port')}
</p>
<p className="mt-3 font-mono text-lg font-semibold text-foreground">
{status.port}
</p>
</div>
<div className="surface-card rounded-2xl border border-border/70 bg-background p-4 shadow-none">
<p className="text-xs font-semibold uppercase text-muted-foreground">
{t('opencodeRuntime.providers', 'Providers')}
</p>
<p className="mt-3 font-mono text-lg font-semibold text-foreground">
{summary?.providerCount ?? 0}
</p>
</div>
</div>
<div className="surface-card rounded-2xl border border-border/70 bg-background p-4 shadow-none">
<p className="text-xs font-semibold uppercase text-muted-foreground">
{t('opencodeRuntime.defaultModel', 'Default model')}
</p>
<p className="mt-2 break-all font-mono text-sm font-semibold text-foreground">
{summary?.model || t('opencodeRuntime.noModel', 'No configured model')}
</p>
</div>
{error && (
<p className="rounded-xl border border-red-500/20 bg-red-500/10 px-4 py-3 text-sm font-medium text-red-700">
{error}
</p>
)}
</section>
);
}

View File

@@ -6,13 +6,11 @@ import {
X,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useOpencodeStore } from '@/stores/opencode';
import { useSettingsStore } from '@/stores/settings';
import { hostApiFetch } from '@/lib/host-api';
import { trackUiEvent } from '@/lib/telemetry';
import { ProvidersSettings } from '@/components/settings/ProvidersSettings';
import { FeedbackState } from '@/components/common/FeedbackState';
import { OpencodeRuntimePanel } from './OpencodeRuntimePanel';
import {
filterUsageHistoryByWindow,
groupUsageHistory,
@@ -37,9 +35,7 @@ function isHiddenUsageSource(source?: string): boolean {
export function Models() {
const { t } = useTranslation(['dashboard', 'settings']);
const opencodeStatus = useOpencodeStore((state) => state.status);
const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked);
const runtimeRunning = opencodeStatus.state === 'running';
const usageFetchMaxAttempts = window.electron.platform === 'win32'
? WINDOWS_USAGE_FETCH_MAX_ATTEMPTS
: DEFAULT_USAGE_FETCH_MAX_ATTEMPTS;
@@ -112,10 +108,6 @@ export function Models() {
}, []);
useEffect(() => {
if (!runtimeRunning) {
return;
}
const requestRefresh = () => {
if (usageFetchStatusRef.current === 'loading') return;
if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
@@ -140,7 +132,7 @@ export function Models() {
window.removeEventListener('focus', handleFocus);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [runtimeRunning]);
}, []);
useEffect(() => {
if (usageFetchTimerRef.current) {
@@ -148,18 +140,11 @@ export function Models() {
usageFetchTimerRef.current = null;
}
if (!runtimeRunning) {
dispatchFetch({ type: 'reset' });
return;
}
dispatchFetch({ type: 'start' });
const generation = usageFetchGenerationRef.current + 1;
usageFetchGenerationRef.current = generation;
const restartMarker = `${opencodeStatus.pid ?? 'na'}:${opencodeStatus.startedAt ?? 'na'}`;
trackUiEvent('models.token_usage_fetch_started', {
generation,
restartMarker,
});
// Safety timeout: if the fetch cycle hasn't resolved after 30 s,
@@ -168,7 +153,6 @@ export function Models() {
if (usageFetchGenerationRef.current !== generation) return;
trackUiEvent('models.token_usage_fetch_safety_timeout', {
generation,
restartMarker,
});
dispatchFetch({ type: 'failed' });
}, 30_000);
@@ -177,7 +161,6 @@ export function Models() {
trackUiEvent('models.token_usage_fetch_attempt', {
generation,
attempt,
restartMarker,
});
try {
const entries = await hostApiFetch<UsageHistoryEntry[]>('/api/usage/recent-token-history');
@@ -189,7 +172,6 @@ export function Models() {
generation,
attempt,
records: normalized.length,
restartMarker,
});
if (normalized.length === 0 && attempt < usageFetchMaxAttempts) {
@@ -197,7 +179,6 @@ export function Models() {
generation,
attempt,
reason: 'empty',
restartMarker,
});
usageFetchTimerRef.current = setTimeout(() => {
void fetchUsageHistoryWithRetry(attempt + 1);
@@ -208,7 +189,6 @@ export function Models() {
generation,
attempt,
reason: 'empty',
restartMarker,
});
}
dispatchFetch({ type: 'done', data: normalized });
@@ -218,7 +198,6 @@ export function Models() {
trackUiEvent('models.token_usage_fetch_failed_attempt', {
generation,
attempt,
restartMarker,
message: error instanceof Error ? error.message : String(error),
});
if (attempt < usageFetchMaxAttempts) {
@@ -226,7 +205,6 @@ export function Models() {
generation,
attempt,
reason: 'error',
restartMarker,
});
usageFetchTimerRef.current = setTimeout(() => {
void fetchUsageHistoryWithRetry(attempt + 1);
@@ -238,7 +216,6 @@ export function Models() {
generation,
attempt,
reason: 'error',
restartMarker,
});
}
};
@@ -252,16 +229,12 @@ export function Models() {
usageFetchTimerRef.current = null;
}
};
}, [runtimeRunning, opencodeStatus.startedAt, opencodeStatus.pid, usageFetchMaxAttempts, usageRefreshNonce]);
}, [usageFetchMaxAttempts, usageRefreshNonce]);
const usageHistory = runtimeRunning
? fetchState.data.filter((entry) => !shouldHideUsageEntry(entry))
: [];
const stableUsageHistory = runtimeRunning
? fetchState.stableData.filter((entry) => !shouldHideUsageEntry(entry))
: [];
const usageHistory = fetchState.data.filter((entry) => !shouldHideUsageEntry(entry));
const stableUsageHistory = fetchState.stableData.filter((entry) => !shouldHideUsageEntry(entry));
const visibleUsageHistory = resolveVisibleUsageHistory(usageHistory, stableUsageHistory, {
preferStableOnEmpty: runtimeRunning && fetchState.status === 'loading',
preferStableOnEmpty: fetchState.status === 'loading',
});
const filteredUsageHistory = filterUsageHistoryByWindow(visibleUsageHistory, usageWindow);
const usageGroups = groupUsageHistory(filteredUsageHistory, usageGroupBy);
@@ -269,8 +242,8 @@ export function Models() {
const usageTotalPages = Math.max(1, Math.ceil(filteredUsageHistory.length / usagePageSize));
const safeUsagePage = Math.min(usagePage, usageTotalPages);
const pagedUsageHistory = filteredUsageHistory.slice((safeUsagePage - 1) * usagePageSize, safeUsagePage * usagePageSize);
const usageLoading = runtimeRunning && fetchState.status === 'loading' && visibleUsageHistory.length === 0;
const usageRefreshing = runtimeRunning && fetchState.status === 'loading' && visibleUsageHistory.length > 0;
const usageLoading = fetchState.status === 'loading' && visibleUsageHistory.length === 0;
const usageRefreshing = fetchState.status === 'loading' && visibleUsageHistory.length > 0;
return (
<div data-testid="models-page" className="flex h-[calc(100vh-2.5rem)] flex-col -m-6 overflow-hidden bg-background">
@@ -290,8 +263,6 @@ export function Models() {
{/* Content Area */}
<div className="-mr-2 min-h-0 flex-1 space-y-10 overflow-y-auto pb-10 pr-2">
<OpencodeRuntimePanel />
{/* AI Providers Section */}
<ProvidersSettings />