Switch the local inference provider MLX backend to use the safemlx crate (#10304)

This commit is contained in:
Jasper
2026-07-07 14:01:15 -07:00
committed by GitHub
parent f96f62d985
commit 1bced6616b
58 changed files with 1835 additions and 646 deletions
@@ -81,6 +81,7 @@ function snapshotWithName(name: string): AcpChatSessionSnapshot {
accumulatedTotalTokens: 0,
},
notifications: [],
progressMessage: undefined,
chatState: ChatState.Idle,
sessionLoadError: undefined,
activePromptAttemptId: null,
@@ -102,6 +103,7 @@ function snapshotWithoutSession(): AcpChatSessionSnapshot {
accumulatedTotalTokens: 0,
},
notifications: [],
progressMessage: undefined,
chatState: ChatState.Idle,
sessionLoadError: undefined,
activePromptAttemptId: null,
@@ -108,6 +108,7 @@ function snapshotWithActivePrompt(activePromptAttemptId: string | null): AcpChat
accumulatedTotalTokens: 0,
},
notifications: [],
progressMessage: undefined,
chatState: activePromptAttemptId ? ChatState.Streaming : ChatState.Idle,
sessionLoadError: undefined,
activePromptAttemptId,
@@ -583,13 +583,9 @@ describe('createAcpSessionNotificationAdapter', () => {
status: { type: 'progress', message: 'Still working' },
})
);
messages = expectOnlyMessagesChange(progressStateChanges);
expect(firstContent(messages[2])).toMatchObject({
type: 'systemNotification',
notificationType: 'thinkingMessage',
msg: 'Still working',
});
expect(progressStateChanges).toEqual([
{ type: 'progressMessage', message: 'Still working' },
]);
});
});
@@ -38,7 +38,9 @@ function applyStatusMessage(
sessionId: string,
update: Extract<GooseSessionNotification_unstable['update'], { sessionUpdate: 'status_message' }>
): AcpChatStateChange[] {
const notificationType = update.status.type === 'notice' ? 'inlineMessage' : 'thinkingMessage';
if (update.status.type === 'progress') {
return [{ type: 'progressMessage', message: update.status.message }];
}
state.messages.push({
id: `acp_status_${sessionId}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
@@ -47,7 +49,7 @@ function applyStatusMessage(
content: [
{
type: 'systemNotification',
notificationType,
notificationType: 'inlineMessage',
msg: update.status.message,
},
],
+1
View File
@@ -5,6 +5,7 @@ import type { Message, NotificationEvent } from '../../types/message';
export type AcpChatStateChange =
| { type: 'messages'; messages: Message[] }
| { type: 'tokenState'; tokenState: Partial<TokenState> }
| { type: 'progressMessage'; message: string | undefined }
| {
type: 'sessionInfo';
name?: string;
+30
View File
@@ -19,6 +19,7 @@ export interface AcpChatSessionSnapshot {
messages: Message[];
tokenState: TokenState;
notifications: NotificationEvent[];
progressMessage: string | undefined;
chatState: ChatState;
sessionLoadError: string | undefined;
activePromptAttemptId: string | null;
@@ -155,6 +156,7 @@ function createAcpChatSessionStoreInternal(): AcpChatSessionStoreInternal {
messages: [],
tokenState: { ...initialTokenState },
notifications: [],
progressMessage: undefined,
chatState: ChatState.Idle,
sessionLoadError: undefined,
activePromptAttemptId: null,
@@ -190,6 +192,7 @@ function createAcpChatSessionStoreInternal(): AcpChatSessionStoreInternal {
const entry = getOrCreateEntry(sessionId);
resetReplayState(entry);
entry.sessionLoadError = undefined;
entry.progressMessage = undefined;
entry.chatState = ChatState.LoadingConversation;
return notify(sessionId, entry);
};
@@ -198,6 +201,7 @@ function createAcpChatSessionStoreInternal(): AcpChatSessionStoreInternal {
const entry = getOrCreateEntry(sessionId);
entry.session = session;
entry.sessionLoadError = undefined;
entry.progressMessage = undefined;
entry.chatState = entry.activePromptAttemptId ? ChatState.Streaming : ChatState.Idle;
return notify(sessionId, entry);
};
@@ -208,6 +212,7 @@ function createAcpChatSessionStoreInternal(): AcpChatSessionStoreInternal {
) => {
const entry = getOrCreateEntry(sessionId);
entry.sessionLoadError = sessionLoadError;
entry.progressMessage = undefined;
entry.chatState = ChatState.Idle;
return notify(sessionId, entry);
};
@@ -237,6 +242,9 @@ function createAcpChatSessionStoreInternal(): AcpChatSessionStoreInternal {
const setChatState: AcpChatSessionActions['setChatState'] = (sessionId, chatState) => {
const entry = getOrCreateEntry(sessionId);
if (chatState === ChatState.Idle) {
entry.progressMessage = undefined;
}
entry.chatState = chatState;
return notify(sessionId, entry);
};
@@ -287,6 +295,7 @@ function createAcpChatSessionStoreInternal(): AcpChatSessionStoreInternal {
entry.chatState = ChatState.Streaming;
entry.sessionLoadError = undefined;
entry.notifications = [];
entry.progressMessage = undefined;
return notify(sessionId, entry);
};
@@ -309,6 +318,7 @@ function createAcpChatSessionStoreInternal(): AcpChatSessionStoreInternal {
entry.pendingCancelPromptAttemptId = promptAttemptId;
entry.pendingUserInputRequestIds.clear();
discardPendingLocalSteerMessages(entry);
entry.progressMessage = undefined;
entry.chatState = ChatState.Idle;
return notify(sessionId, entry);
};
@@ -385,6 +395,7 @@ function createAcpChatSessionStoreInternal(): AcpChatSessionStoreInternal {
entry.promptCancellationRestoreState = null;
entry.pendingUserInputRequestIds.clear();
discardPendingLocalSteerMessages(entry);
entry.progressMessage = undefined;
entry.chatState = ChatState.Idle;
entry.sessionLoadError = error;
notify(sessionId, entry);
@@ -416,6 +427,9 @@ function createAcpChatSessionStoreInternal(): AcpChatSessionStoreInternal {
notification
) => {
const entry = getOrCreateEntry(notification.sessionId);
if (shouldClearProgressMessage(notification)) {
entry.progressMessage = undefined;
}
const changes = entry.adapter.apply(notification);
applyChatStateChanges(entry, changes);
return notify(notification.sessionId, entry);
@@ -580,6 +594,9 @@ function applyChatStateChanges(entry: StoreEntry, changes: AcpChatStateChange[])
case 'tokenState':
entry.tokenState = { ...entry.tokenState, ...change.tokenState };
break;
case 'progressMessage':
entry.progressMessage = change.message;
break;
case 'sessionInfo':
if (change.name && entry.session) {
entry.session = { ...entry.session, name: change.name };
@@ -598,10 +615,22 @@ function applyChatStateChanges(entry: StoreEntry, changes: AcpChatStateChange[])
}
}
function shouldClearProgressMessage(notification: SessionNotification): boolean {
switch (notification.update.sessionUpdate) {
case 'agent_message_chunk':
case 'agent_thought_chunk':
case 'tool_call':
return true;
default:
return false;
}
}
function resetReplayState(entry: StoreEntry): void {
entry.messages = [];
entry.tokenState = { ...initialTokenState };
entry.notifications = [];
entry.progressMessage = undefined;
entry.activeRunId = null;
entry.pendingCancelPromptAttemptId = null;
entry.promptCancellationRestoreState = null;
@@ -675,6 +704,7 @@ function snapshotFromEntry(entry: StoreEntry): AcpChatSessionSnapshot {
messages: cloneMessages(entry.messages),
tokenState: { ...entry.tokenState },
notifications: [...entry.notifications],
progressMessage: entry.progressMessage,
chatState: entry.chatState,
sessionLoadError: entry.sessionLoadError,
activePromptAttemptId: entry.activePromptAttemptId,
+5
View File
@@ -56,6 +56,11 @@ export async function deleteLocalModel(modelId: string): Promise<void> {
await client.goose.localInferenceModelsDelete_unstable({ modelId });
}
export async function evictLocalModel(modelId: string): Promise<void> {
const client = await getAcpClient();
await client.goose.localInferenceModelsEvict_unstable({ modelId });
}
export async function getModelSettings(modelId: string): Promise<ModelSettings> {
const client = await getAcpClient();
const response = await client.goose.localInferenceModelsSettingsRead_unstable({ modelId });
+3 -14
View File
@@ -23,12 +23,7 @@ import { RecipeWarningModal } from './ui/RecipeWarningModal';
import { scanRecipe } from '../recipe';
import type { Recipe } from '../recipe';
import RecipeActivities from './recipes/RecipeActivities';
import {
getThinkingMessage,
getTextAndImageContent,
type Message,
type UserInput,
} from '../types/message';
import { getTextAndImageContent, type Message, type UserInput } from '../types/message';
import { substituteParameters } from '../utils/parameterSubstitution';
import { useAutoSubmit } from '../hooks/useAutoSubmit';
import { Goose } from './icons';
@@ -92,6 +87,7 @@ export default function BaseChat({
session,
messages,
chatState,
progressMessage,
updateSession,
handleSubmit,
onSteerQueuedMessage,
@@ -469,14 +465,7 @@ export default function BaseChat({
{chatState !== ChatState.Idle && (
<div className="absolute bottom-1 left-4 z-20 pointer-events-none">
<LoadingGoose
chatState={chatState}
message={
messages.length > 0
? getThinkingMessage(messages[messages.length - 1])
: undefined
}
/>
<LoadingGoose chatState={chatState} message={progressMessage} />
</div>
)}
</div>
@@ -8,6 +8,8 @@ import {
Settings2,
Eye,
RefreshCw,
Cpu,
PowerOff,
} from 'lucide-react';
import { Button } from '../../ui/button';
import { useModelAndProvider } from '../../ModelAndProviderContext';
@@ -18,6 +20,7 @@ import {
getLocalModelDownloadProgress,
cancelLocalModelDownload,
deleteLocalModel,
evictLocalModel,
type DownloadProgress,
type DownloadModelRequest,
type LocalModelResponse,
@@ -106,6 +109,14 @@ const i18n = defineMessages({
id: 'localInferenceSettings.modelSettingsTitle',
defaultMessage: 'Model settings',
},
loadedInMemory: {
id: 'localInferenceSettings.loadedInMemory',
defaultMessage: 'Loaded in memory',
},
evictFromMemory: {
id: 'localInferenceSettings.evictFromMemory',
defaultMessage: 'Evict from memory',
},
vision: {
id: 'localInferenceSettings.vision',
defaultMessage: 'Vision',
@@ -181,6 +192,7 @@ export const LocalInferenceSettings = () => {
const [downloadRequests, setDownloadRequests] = useState<Map<string, DownloadModelRequest>>(
new Map()
);
const [evictingModelId, setEvictingModelId] = useState<string | null>(null);
const [showAllFeatured, setShowAllFeatured] = useState(false);
const [settingsOpenFor, setSettingsOpenFor] = useState<string | null>(null);
const { currentModel, currentProvider, refreshCurrentModelAndProvider } = useModelAndProvider();
@@ -371,6 +383,18 @@ export const LocalInferenceSettings = () => {
}
};
const handleEvictModel = async (modelId: string) => {
setEvictingModelId(modelId);
try {
await evictLocalModel(modelId);
await loadModels();
} catch (error) {
console.error('Failed to evict model:', error);
} finally {
setEvictingModelId(null);
}
};
const handleHfDownloadStarted = (modelId: string, request: DownloadModelRequest) => {
setDownloadRequests((prev) => new Map(prev).set(modelId, request));
pollDownloadProgress(modelId);
@@ -393,6 +417,15 @@ export const LocalInferenceSettings = () => {
.map(([modelId]) => modelId)
);
useEffect(() => {
if (downloadedModels.length === 0) return;
const interval = setInterval(() => {
loadModels();
}, 3000);
return () => clearInterval(interval);
}, [downloadedModels.length, loadModels]);
return (
<div className="space-y-6">
<div>
@@ -526,18 +559,26 @@ export const LocalInferenceSettings = () => {
: 'border-border-subtle bg-background-default hover:border-border-default'
}`}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="flex items-center justify-between gap-3">
<div className="flex min-w-0 items-center gap-2 flex-wrap">
<input
type="radio"
checked={isSelected}
onChange={() => selectModel(model.id)}
className="cursor-pointer"
/>
<span className="text-sm font-medium text-text-default">{model.id}</span>
<span className="text-sm font-medium text-text-default break-all">
{model.id}
</span>
<span className="text-xs text-text-muted">
{formatBytes(model.sizeBytes)}
</span>
{model.isLoaded && (
<span className="inline-flex items-center gap-1 text-xs text-green-400 bg-green-500/10 px-2 py-0.5 rounded">
<Cpu className="w-3 h-3" />
{intl.formatMessage(i18n.loadedInMemory)}
</span>
)}
{model.recommended && (
<span className="text-xs bg-blue-500 text-white px-2 py-0.5 rounded">
{intl.formatMessage(i18n.recommended)}
@@ -545,7 +586,7 @@ export const LocalInferenceSettings = () => {
)}
<VisionBadge model={model} intl={intl} />
</div>
<div className="flex items-center gap-1">
<div className="flex shrink-0 items-center gap-1">
<Button
variant="ghost"
size="sm"
@@ -554,6 +595,17 @@ export const LocalInferenceSettings = () => {
>
<Settings2 className="w-4 h-4" />
</Button>
{model.isLoaded && (
<Button
variant="ghost"
size="sm"
onClick={() => handleEvictModel(model.id)}
disabled={evictingModelId === model.id}
title={intl.formatMessage(i18n.evictFromMemory)}
>
<PowerOff className="w-4 h-4" />
</Button>
)}
<Button
variant="ghost"
size="sm"
+2
View File
@@ -61,6 +61,7 @@ export function useChatSession({
const messages = acpSnapshot?.messages ?? [];
const session = acpSnapshot?.session;
const chatState = acpSnapshot?.chatState ?? ChatState.LoadingConversation;
const progressMessage = acpSnapshot?.progressMessage;
const sessionLoadError = acpSnapshot?.sessionLoadError;
const tokenState = acpSnapshot?.tokenState ?? initialTokenState;
const queueProcessingBlocked = acpSnapshot?.pendingCancelPromptAttemptId != null;
@@ -319,6 +320,7 @@ export function useChatSession({
messages,
session,
chatState,
progressMessage,
updateSession,
handleSubmit,
onSteerQueuedMessage,
@@ -13,6 +13,7 @@ export interface UseChatSessionResult {
session?: Session;
messages: Message[];
chatState: ChatState;
progressMessage?: string;
updateSession: (updater: (session: Session) => Session) => void;
handleSubmit: (input: UserInput) => Promise<void>;
onSteerQueuedMessage?: (input: UserInput) => Promise<boolean>;
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloading": {
"defaultMessage": "Wird heruntergeladen"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Empfohlene Modelle"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Melden Sie sich an, um die Ratenbegrenzungen beim Suchen und Herunterladen von Modellen zu erhöhen und auf private oder zugriffsbeschränkte Hugging-Face-Repositories zuzugreifen."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Modelleinstellungen"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloading": {
"defaultMessage": "Downloading"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Featured Models"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Sign in to increase rate limits when searching and downloading models, and to access private or gated Hugging Face repositories."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Model Settings"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloadProgress": {
"defaultMessage": "{downloaded} / {total} ({percent}%)"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Modelos destacados"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Inicia sesión para aumentar los límites de uso al buscar y descargar modelos, y para acceder a repositorios de Hugging Face privados o restringidos."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Ajustes del modelo"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloading": {
"defaultMessage": "Téléchargement en cours"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Modèles en vedette"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Connectez-vous pour augmenter les limites de débit lors de la recherche et du téléchargement de modèles, et pour accéder aux dépôts Hugging Face privés ou à accès restreint."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Paramètres du modèle"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloadProgress": {
"defaultMessage": "{downloaded} / {total} ({percent}%)"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "विशेष रुप से प्रदर्शित मॉडल"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "मॉडल खोजते और डाउनलोड करते समय दर सीमा बढ़ाने के लिए और निजी या गेटेड हगिंग फेस रिपॉजिटरी तक पहुंचने के लिए साइन इन करें।"
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "मॉडल Settings"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloading": {
"defaultMessage": "Mengunduh"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Model Unggulan"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Masuk untuk meningkatkan batas laju saat mencari dan mengunduh model, serta untuk mengakses repositori Hugging Face yang privat atau terbatas."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Pengaturan Model"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloading": {
"defaultMessage": "Download in corso"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Modelli in evidenza"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Accedi per aumentare i limiti di frequenza durante la ricerca e il download dei modelli e per accedere ai repository Hugging Face privati o ad accesso limitato."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Impostazioni modello"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloadProgress": {
"defaultMessage": "{downloaded} / {total} ({percent}%)"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "注目モデル"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "モデルの検索・ダウンロード時のレート制限を緩和し、非公開またはゲート付きHugging Faceリポジトリにアクセスするにはサインインしてください。"
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "モデル設定"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloadProgress": {
"defaultMessage": "{downloaded} / {total} ({percent}%)"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "주요 모델"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "모델 검색 및 다운로드 시 속도 제한 한도를 높이고 비공개 또는 접근 제한된 Hugging Face 리포지토리에 액세스하려면 로그인하세요."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "모델 설정"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloading": {
"defaultMessage": "Memuat turun"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Model Pilihan"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Log masuk untuk meningkatkan had kadar semasa mencari dan memuat turun model, dan untuk mengakses repositori Hugging Face peribadi atau berpagar."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Tetapan Model"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloading": {
"defaultMessage": "Baixando"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Modelos em Destaque"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Faça login para aumentar os limites de taxa ao pesquisar e baixar modelos, e para acessar repositórios privados ou restritos do Hugging Face."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Configurações do Modelo"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloadProgress": {
"defaultMessage": "{downloaded} / {total} ({percent}%)"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Рекомендуемые модели"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Войдите, чтобы увеличить лимиты запросов при поиске и загрузке моделей, а также получить доступ к приватным или ограниченным репозиториям Hugging Face."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Настройки модели"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloadProgress": {
"defaultMessage": "{downloaded} / {total} (%{percent})"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Öne Çıkan Modeller"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Model ararken ve indirirken hız sınırlarını artırmak ve özel ya da erişimi kısıtlı Hugging Face depolarına erişmek için oturum açın."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Modeli Ayarları"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloading": {
"defaultMessage": "Đang tải xuống"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "Mô hình nổi bật"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "Đăng nhập để tăng giới hạn tốc độ khi tìm kiếm và tải xuống mô hình, và để truy cập các kho lưu trữ Hugging Face riêng tư hoặc bị giới hạn."
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "Cài đặt mô hình"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloadProgress": {
"defaultMessage": "{downloaded} / {total}({percent}%)"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "推荐模型"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "登录后可在搜索和下载模型时提高速率限制,并访问私有或受限的 Hugging Face 仓库。"
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "模型设置"
},
+6
View File
@@ -1736,12 +1736,18 @@
"localInferenceSettings.downloading": {
"defaultMessage": "下載中"
},
"localInferenceSettings.evictFromMemory": {
"defaultMessage": "Evict from memory"
},
"localInferenceSettings.featuredModels": {
"defaultMessage": "精選模型"
},
"localInferenceSettings.huggingFaceSignInNote": {
"defaultMessage": "登入以在搜尋與下載模型時提高速率上限,並存取私人或受限的 Hugging Face 儲存庫。"
},
"localInferenceSettings.loadedInMemory": {
"defaultMessage": "Loaded in memory"
},
"localInferenceSettings.modelSettings": {
"defaultMessage": "模型設定"
},
+5 -15
View File
@@ -93,7 +93,11 @@ type ContentIcon = {
theme?: 'light' | 'dark' | JsonObject;
};
export type SystemNotificationType = 'thinkingMessage' | 'inlineMessage' | 'creditsExhausted';
export type SystemNotificationType =
| 'thinkingMessage'
| 'progressMessage'
| 'inlineMessage'
| 'creditsExhausted';
export type SystemNotificationContent = {
data?: unknown;
@@ -443,17 +447,3 @@ export function hasCompletedToolCalls(message: Message): boolean {
const toolRequests = getToolRequests(message);
return toolRequests.length > 0;
}
export function getThinkingMessage(message: Message | undefined): string | undefined {
if (!message || message.role !== 'assistant') {
return undefined;
}
for (const content of message.content) {
if (content.type === 'systemNotification' && content.notificationType === 'thinkingMessage') {
return content.msg;
}
}
return undefined;
}
@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest';
import { errorMessage } from './conversionUtils';
describe('errorMessage', () => {
it('prefers ACP JSON-RPC error data over generic messages', () => {
expect(
errorMessage({
error: {
message: 'Invalid params',
data: 'MLX backend error: failed to load model',
},
})
).toBe('MLX backend error: failed to load model');
});
it('prefers ACP JSON-RPC error data from Error instances', () => {
const error = Object.assign(new Error('Invalid params'), {
error: {
message: 'Invalid params',
data: 'MLX backend error: failed to load model',
},
});
expect(errorMessage(error)).toBe('MLX backend error: failed to load model');
});
});
+18
View File
@@ -13,6 +13,11 @@ export async function safeJsonParse<T>(
}
export function errorMessage(err: Error | unknown, default_value?: string) {
const acpData = acpErrorData(err);
if (typeof acpData === 'string') {
return acpData;
}
if (err instanceof Error) {
return err.message;
} else if (typeof err === 'object' && err !== null && 'message' in err) {
@@ -22,6 +27,19 @@ export function errorMessage(err: Error | unknown, default_value?: string) {
}
}
function acpErrorData(err: unknown): unknown {
if (typeof err !== 'object' || err === null) {
return undefined;
}
const candidate = 'error' in err && isRecord(err.error) ? err.error : err;
return isRecord(candidate) ? candidate.data : undefined;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
export function formatErrorForLogging(error: unknown): string {
if (error instanceof Error) {
return `${error.name}: ${error.message}${error.stack ? `\n${error.stack}` : ''}`;
+10
View File
@@ -121,6 +121,7 @@ import type {
LocalInferenceModelDownloadProgressResponse_unstable,
LocalInferenceModelDownloadRequest_unstable,
LocalInferenceModelDownloadResponse_unstable,
LocalInferenceModelEvictRequest_unstable,
LocalInferenceModelSettingsReadRequest_unstable,
LocalInferenceModelSettingsReadResponse_unstable,
LocalInferenceModelSettingsUpdateRequest_unstable,
@@ -1377,6 +1378,15 @@ export class GooseExtClient {
);
}
async localInferenceModelsEvict_unstable(
params: LocalInferenceModelEvictRequest_unstable,
): Promise<void> {
await this.conn.extMethod(
"_goose/unstable/local-inference/models/evict",
params,
);
}
async localInferenceModelsSettingsRead_unstable(
params: LocalInferenceModelSettingsReadRequest_unstable,
): Promise<LocalInferenceModelSettingsReadResponse_unstable> {
File diff suppressed because one or more lines are too long
+6 -1
View File
@@ -2329,6 +2329,7 @@ export type LocalInferenceModelDto = {
sizeBytes: number;
status: LocalInferenceModelDownloadStatusDto;
recommended: boolean;
isLoaded: boolean;
settings: LocalInferenceModelSettingsDto;
visionCapable: boolean;
mmprojStatus?: LocalInferenceModelDownloadStatusDto | null;
@@ -2433,6 +2434,10 @@ export type LocalInferenceModelDeleteRequest_unstable = {
modelId: string;
};
export type LocalInferenceModelEvictRequest_unstable = {
modelId: string;
};
export type LocalInferenceModelSettingsReadRequest_unstable = {
modelId: string;
};
@@ -2618,7 +2623,7 @@ export type RecipeParamsAction = 'submit' | 'cancel';
export type ExtRequest = {
id: string;
method: string;
params?: AddSessionExtensionRequest_unstable | RemoveSessionExtensionRequest_unstable | GetToolsRequest_unstable | SetToolPermissionsRequest_unstable | GooseToolCallRequest_unstable | ReadResourceRequest_unstable | AppsListRequest_unstable | AppsExportRequest_unstable | AppsImportRequest_unstable | UpdateWorkingDirRequest_unstable | SetSessionSystemPromptRequest_unstable | SteerSessionRequest_unstable | DiagnosticsGetRequest_unstable | ListPromptsRequest_unstable | GetPromptRequest_unstable | SavePromptRequest_unstable | ResetPromptRequest_unstable | DeleteSessionRequest | GetConfigExtensionsRequest_unstable | GetAvailableExtensionsRequest_unstable | AddConfigExtensionRequest_unstable | RemoveConfigExtensionRequest_unstable | SetConfigExtensionEnabledRequest_unstable | GetSessionExtensionsRequest_unstable | ListProvidersRequest_unstable | ProviderSupportedModelsListRequest_unstable | ProviderCatalogListRequest_unstable | ProviderSetupCatalogListRequest_unstable | ProviderCatalogTemplateRequest_unstable | CustomProviderCreateRequest_unstable | CustomProviderReadRequest_unstable | CustomProviderUpdateRequest_unstable | CustomProviderDeleteRequest_unstable | RefreshProviderInventoryRequest_unstable | ProviderConfigReadRequest_unstable | ProviderConfigStatusRequest_unstable | ProviderConfigSaveRequest_unstable | ProviderConfigDeleteRequest_unstable | ProviderConfigAuthenticateRequest_unstable | ProviderSecretsListRequest_unstable | ProviderSecretDeleteRequest_unstable | CanonicalModelInfoRequest_unstable | PreferencesReadRequest_unstable | PreferencesSaveRequest_unstable | PreferencesRemoveRequest_unstable | ConfigReadRequest_unstable | ConfigUpsertRequest_unstable | ConfigRemoveRequest_unstable | ConfigReadAllRequest_unstable | DefaultsReadRequest_unstable | DefaultsSaveRequest_unstable | DefaultsClearRequest_unstable | OnboardingImportScanRequest_unstable | OnboardingImportApplyRequest_unstable | ExportSessionRequest_unstable | ImportSessionRequest_unstable | ShareSessionNostrRequest_unstable | EncodeRecipeRequest_unstable | DecodeRecipeRequest_unstable | ScanRecipeRequest_unstable | ListRecipesRequest_unstable | DeleteRecipeRequest_unstable | ScheduleRecipeRequest_unstable | SetRecipeSlashCommandRequest_unstable | SaveRecipeRequest_unstable | ParseRecipeRequest_unstable | RecipeToYamlRequest_unstable | ListSchedulesRequest_unstable | ListScheduleSessionsRequest_unstable | CreateScheduleRequest_unstable | DeleteScheduleRequest_unstable | PauseScheduleRequest_unstable | UnpauseScheduleRequest_unstable | UpdateScheduleRequest_unstable | RunScheduleNowRequest_unstable | KillRunningJobRequest_unstable | InspectRunningJobRequest_unstable | GetSessionInfoRequest_unstable | TruncateSessionConversationRequest_unstable | UpdateSessionProjectRequest_unstable | RenameSessionRequest_unstable | ArchiveSessionRequest_unstable | UnarchiveSessionRequest_unstable | CreateSourceRequest_unstable | ListSourcesRequest_unstable | ListAgentMentionsRequest_unstable | ListSlashCommandsRequest_unstable | UpdateSourceRequest_unstable | DeleteSourceRequest_unstable | ExportSourceRequest_unstable | ImportSourcesRequest_unstable | DictationTranscribeRequest_unstable | DictationConfigRequest_unstable | DictationSecretSaveRequest_unstable | DictationSecretDeleteRequest_unstable | DictationModelsListRequest_unstable | DictationModelDownloadRequest_unstable | DictationModelDownloadProgressRequest_unstable | DictationModelCancelRequest_unstable | DictationModelDeleteRequest_unstable | DictationModelSelectRequest_unstable | LocalInferenceModelsListRequest_unstable | LocalInferenceModelDownloadRequest_unstable | LocalInferenceModelDownloadProgressRequest_unstable | LocalInferenceModelDownloadCancelRequest_unstable | LocalInferenceModelDeleteRequest_unstable | LocalInferenceModelSettingsReadRequest_unstable | LocalInferenceModelSettingsUpdateRequest_unstable | LocalInferenceHuggingFaceSearchRequest_unstable | LocalInferenceHuggingFaceRepoVariantsRequest_unstable | LocalInferenceBuiltinChatTemplatesListRequest_unstable | {
params?: AddSessionExtensionRequest_unstable | RemoveSessionExtensionRequest_unstable | GetToolsRequest_unstable | SetToolPermissionsRequest_unstable | GooseToolCallRequest_unstable | ReadResourceRequest_unstable | AppsListRequest_unstable | AppsExportRequest_unstable | AppsImportRequest_unstable | UpdateWorkingDirRequest_unstable | SetSessionSystemPromptRequest_unstable | SteerSessionRequest_unstable | DiagnosticsGetRequest_unstable | ListPromptsRequest_unstable | GetPromptRequest_unstable | SavePromptRequest_unstable | ResetPromptRequest_unstable | DeleteSessionRequest | GetConfigExtensionsRequest_unstable | GetAvailableExtensionsRequest_unstable | AddConfigExtensionRequest_unstable | RemoveConfigExtensionRequest_unstable | SetConfigExtensionEnabledRequest_unstable | GetSessionExtensionsRequest_unstable | ListProvidersRequest_unstable | ProviderSupportedModelsListRequest_unstable | ProviderCatalogListRequest_unstable | ProviderSetupCatalogListRequest_unstable | ProviderCatalogTemplateRequest_unstable | CustomProviderCreateRequest_unstable | CustomProviderReadRequest_unstable | CustomProviderUpdateRequest_unstable | CustomProviderDeleteRequest_unstable | RefreshProviderInventoryRequest_unstable | ProviderConfigReadRequest_unstable | ProviderConfigStatusRequest_unstable | ProviderConfigSaveRequest_unstable | ProviderConfigDeleteRequest_unstable | ProviderConfigAuthenticateRequest_unstable | ProviderSecretsListRequest_unstable | ProviderSecretDeleteRequest_unstable | CanonicalModelInfoRequest_unstable | PreferencesReadRequest_unstable | PreferencesSaveRequest_unstable | PreferencesRemoveRequest_unstable | ConfigReadRequest_unstable | ConfigUpsertRequest_unstable | ConfigRemoveRequest_unstable | ConfigReadAllRequest_unstable | DefaultsReadRequest_unstable | DefaultsSaveRequest_unstable | DefaultsClearRequest_unstable | OnboardingImportScanRequest_unstable | OnboardingImportApplyRequest_unstable | ExportSessionRequest_unstable | ImportSessionRequest_unstable | ShareSessionNostrRequest_unstable | EncodeRecipeRequest_unstable | DecodeRecipeRequest_unstable | ScanRecipeRequest_unstable | ListRecipesRequest_unstable | DeleteRecipeRequest_unstable | ScheduleRecipeRequest_unstable | SetRecipeSlashCommandRequest_unstable | SaveRecipeRequest_unstable | ParseRecipeRequest_unstable | RecipeToYamlRequest_unstable | ListSchedulesRequest_unstable | ListScheduleSessionsRequest_unstable | CreateScheduleRequest_unstable | DeleteScheduleRequest_unstable | PauseScheduleRequest_unstable | UnpauseScheduleRequest_unstable | UpdateScheduleRequest_unstable | RunScheduleNowRequest_unstable | KillRunningJobRequest_unstable | InspectRunningJobRequest_unstable | GetSessionInfoRequest_unstable | TruncateSessionConversationRequest_unstable | UpdateSessionProjectRequest_unstable | RenameSessionRequest_unstable | ArchiveSessionRequest_unstable | UnarchiveSessionRequest_unstable | CreateSourceRequest_unstable | ListSourcesRequest_unstable | ListAgentMentionsRequest_unstable | ListSlashCommandsRequest_unstable | UpdateSourceRequest_unstable | DeleteSourceRequest_unstable | ExportSourceRequest_unstable | ImportSourcesRequest_unstable | DictationTranscribeRequest_unstable | DictationConfigRequest_unstable | DictationSecretSaveRequest_unstable | DictationSecretDeleteRequest_unstable | DictationModelsListRequest_unstable | DictationModelDownloadRequest_unstable | DictationModelDownloadProgressRequest_unstable | DictationModelCancelRequest_unstable | DictationModelDeleteRequest_unstable | DictationModelSelectRequest_unstable | LocalInferenceModelsListRequest_unstable | LocalInferenceModelDownloadRequest_unstable | LocalInferenceModelDownloadProgressRequest_unstable | LocalInferenceModelDownloadCancelRequest_unstable | LocalInferenceModelDeleteRequest_unstable | LocalInferenceModelEvictRequest_unstable | LocalInferenceModelSettingsReadRequest_unstable | LocalInferenceModelSettingsUpdateRequest_unstable | LocalInferenceHuggingFaceSearchRequest_unstable | LocalInferenceHuggingFaceRepoVariantsRequest_unstable | LocalInferenceBuiltinChatTemplatesListRequest_unstable | {
[key: string]: unknown;
} | null;
};
+6
View File
@@ -2505,6 +2505,7 @@ export const zLocalInferenceModelDto = z.object({
sizeBytes: z.number().int().gte(0),
status: zLocalInferenceModelDownloadStatusDto,
recommended: z.boolean(),
isLoaded: z.boolean(),
settings: zLocalInferenceModelSettingsDto,
visionCapable: z.boolean(),
mmprojStatus: z.union([
@@ -2573,6 +2574,10 @@ export const zLocalInferenceModelDeleteRequest_unstable = z.object({
modelId: z.string()
});
export const zLocalInferenceModelEvictRequest_unstable = z.object({
modelId: z.string()
});
export const zLocalInferenceModelSettingsReadRequest_unstable = z.object({
modelId: z.string()
});
@@ -2914,6 +2919,7 @@ export const zExtRequest = z.object({
zLocalInferenceModelDownloadProgressRequest_unstable,
zLocalInferenceModelDownloadCancelRequest_unstable,
zLocalInferenceModelDeleteRequest_unstable,
zLocalInferenceModelEvictRequest_unstable,
zLocalInferenceModelSettingsReadRequest_unstable,
zLocalInferenceModelSettingsUpdateRequest_unstable,
zLocalInferenceHuggingFaceSearchRequest_unstable,