mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 22:12:23 +00:00
Contain Assistant model catalog failures
This commit is contained in:
@@ -2247,6 +2247,19 @@ provider route-switch metadata without changing the selected route, and labeling
|
||||
failed-turn or readiness recovery buttons as explicit route/model-route choices
|
||||
instead of implying automatic route adoption.
|
||||
|
||||
Assistant model-catalog failure is selector-local state, not a drawer
|
||||
initialization failure. The OpenCode reference at fetched `dev` commit
|
||||
`3867fa2bad0e644166e360e2e99cfe426fe71105`
|
||||
`packages/opencode/src/cli/error.ts` lines 58-69 formats missing model/catalog
|
||||
state as an operator-facing model-selection problem with a list-models hint,
|
||||
while `packages/opencode/src/session/llm.ts` lines 96-104 resolves the
|
||||
selected provider/model route independently for the stream. Pulse adapts that
|
||||
by letting Assistant sessions, settings, route health, and the composer finish
|
||||
opening when `/api/ai/models` fails; the catalog error stays attached to the
|
||||
model selector and may be refreshed explicitly, but startup must not log or
|
||||
render a broad Assistant initialization failure, and it must not replace the
|
||||
selected model route.
|
||||
|
||||
Assistant completed-turn chrome is route-owned summary, not raw usage output.
|
||||
The OpenCode reference at fetched `dev` commit
|
||||
`3867fa2bad0e644166e360e2e99cfe426fe71105` imports `turnSummaryCommit` in
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Show, createSignal } from 'solid-js';
|
||||
import type { ChatMessage, ModelInfo, ModelRouteRecoveryOption } from '../types';
|
||||
import type { QueuedFollowUp } from '../hooks/useChat';
|
||||
import { WORKFLOW_STATUS_PACE_MS } from '../workflowStatusDisplay';
|
||||
import { logger } from '@/utils/logger';
|
||||
|
||||
// ── Hoisted mocks (vi.mock factories reference these) ──────────────────────
|
||||
|
||||
@@ -37,6 +38,7 @@ const {
|
||||
recentModelIds?: string[];
|
||||
openRequest?: number;
|
||||
initialSearchQuery?: string;
|
||||
error?: string;
|
||||
onManageProviders?: () => void;
|
||||
onModelSelect?: (modelId: string) => void;
|
||||
}> = [];
|
||||
@@ -335,6 +337,7 @@ vi.mock('../ModelSelector', () => ({
|
||||
recentModelIds?: string[];
|
||||
openRequest?: number;
|
||||
initialSearchQuery?: string;
|
||||
error?: string;
|
||||
onManageProviders?: () => void;
|
||||
onModelSelect?: (modelId: string) => void;
|
||||
}) => {
|
||||
@@ -347,6 +350,7 @@ vi.mock('../ModelSelector', () => ({
|
||||
data-open-request={String(props.openRequest || 0)}
|
||||
data-initial-search={props.initialSearchQuery || ''}
|
||||
data-recent-models={(props.recentModelIds || []).join('|')}
|
||||
data-error={props.error || ''}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@@ -4321,6 +4325,32 @@ describe('AIChat', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps Assistant initialized when the model catalog fails on mount', async () => {
|
||||
const modelCatalogError = new Error('Model catalog unavailable');
|
||||
mockAIAPI.getModels.mockRejectedValue(modelCatalogError);
|
||||
|
||||
renderChat();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAIChatAPI.getStatus).toHaveBeenCalledTimes(1);
|
||||
expect(mockAIChatAPI.listSessions).toHaveBeenCalledWith({ limit: 30 });
|
||||
expect(mockAIAPI.getSettings).toHaveBeenCalledTimes(1);
|
||||
expect(mockAIAPI.getModels).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('model-selector')).toHaveAttribute(
|
||||
'data-error',
|
||||
'Model catalog unavailable',
|
||||
);
|
||||
});
|
||||
expect(mockAIAPI.testProvider).toHaveBeenCalledWith('openai', 'gpt-4');
|
||||
expect(logger.error).not.toHaveBeenCalledWith(
|
||||
'[AIChat] Failed to initialize:',
|
||||
modelCatalogError,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not load sessions, settings, or models when AI is not running', async () => {
|
||||
mockAIChatAPI.getStatus.mockResolvedValue({ running: false });
|
||||
renderChat();
|
||||
|
||||
@@ -2274,7 +2274,20 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
setControlLevel('read_only');
|
||||
return;
|
||||
}
|
||||
await Promise.all([refreshSessions(), loadAIRuntimeSettings(), loadAIRuntimeModels()]);
|
||||
const [sessionsResult, settingsResult, modelsResult] = await Promise.allSettled([
|
||||
refreshSessions(),
|
||||
loadAIRuntimeSettings(),
|
||||
loadAIRuntimeModels(),
|
||||
]);
|
||||
if (sessionsResult.status === 'rejected') {
|
||||
logger.error('[AIChat] Failed to load sessions:', sessionsResult.reason);
|
||||
}
|
||||
if (settingsResult.status === 'rejected') {
|
||||
logger.error('[AIChat] Failed to load AI settings:', settingsResult.reason);
|
||||
}
|
||||
if (modelsResult.status === 'rejected') {
|
||||
logger.debug('[AIChat] Model catalog unavailable during initialization:', modelsResult.reason);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[AIChat] Failed to initialize:', error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user