feat(chat): make reasoning-v1 vision-capable; remove pro-reasoning tier (#3545)

This commit is contained in:
sanil-23
2026-06-09 06:01:08 -07:00
committed by GitHub
parent f277a410cb
commit b01e1d5c43
27 changed files with 46 additions and 188 deletions
+1 -2
View File
@@ -1828,7 +1828,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'نوع ملف غير مدعوم. استخدم صورة (PNG أو JPEG أو WebP أو GIF أو BMP) أو ملف PDF أو TXT أو Markdown.',
'chat.attachment.imageNotSupported':
'لا يمكن لهذا النموذج قراءة الصور. بدّل إلى تفكير احترافي لإرفاق صورتك.',
'لا يمكن لهذا النموذج قراءة الصور. يمكنك استخدام مستوى التفكير من OpenHuman لإرفاق صورتك.',
'chat.attachment.readFailed': 'تعذر قراءة الملف',
'memory.searchAria': 'البحث في الذاكرة',
'memory.searchPlaceholder': 'البحث في إدخالات الذاكرة...',
@@ -4362,7 +4362,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': 'ملف تعريف الوكيل',
'chat.agentProfile.quick': 'سريع',
'chat.agentProfile.reasoning': 'تفكير',
'chat.agentProfile.proReasoning': 'تفكير احترافي',
'chat.agentProfile.namePlaceholder': 'اسم ملف التعريف',
'chat.agentProfile.promptStylePlaceholder': 'نمط المطالبة',
'chat.agentProfile.allowedToolsPlaceholder': 'الأدوات المسموح بها',
+1 -2
View File
@@ -1870,7 +1870,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'অসমর্থিত ফাইল প্রকার। একটি ছবি (PNG, JPEG, WebP, GIF, BMP) অথবা একটি PDF, TXT, বা Markdown ফাইল ব্যবহার করুন।',
'chat.attachment.imageNotSupported':
'এই মডেলটি ছবি পড়তে পারে না। আপনার ছবি সংযুক্ত করতে প্রো যুক্তিতে স্যুইচ করুন।',
'এই মডেলটি ছবি পড়তে পারে না। আপনার ছবি সংযুক্ত করতে আপনি OpenHuman যুক্তি স্তর ব্যবহার করতে পারেন।',
'chat.attachment.readFailed': 'ফাইল পড়া যায়নি',
'memory.searchAria': 'মেমোরি খুঁজুন',
'memory.searchPlaceholder': 'মেমোরি এন্ট্রি খুঁজুন...',
@@ -4446,7 +4446,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': 'এজেন্ট প্রোফাইল',
'chat.agentProfile.quick': 'দ্রুত',
'chat.agentProfile.reasoning': 'যুক্তি',
'chat.agentProfile.proReasoning': 'প্রো যুক্তি',
'chat.agentProfile.namePlaceholder': 'প্রোফাইল নাম',
'chat.agentProfile.promptStylePlaceholder': 'প্রম্পট স্টাইল',
'chat.agentProfile.allowedToolsPlaceholder': 'অনুমোদিত টুল',
+1 -2
View File
@@ -1917,7 +1917,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'Nicht unterstützter Dateityp. Verwenden Sie ein Bild (PNG, JPEG, WebP, GIF, BMP) oder eine PDF-, TXT- oder Markdown-Datei.',
'chat.attachment.imageNotSupported':
'Dieses Modell kann keine Bilder lesen. Wechsle zu Pro-Denkmodus, um dein Bild anzuhängen.',
'Dieses Modell kann keine Bilder lesen. Du kannst die OpenHuman-Stufe „Denkmodus“ verwenden, um dein Bild anzuhängen.',
'chat.attachment.readFailed': 'Datei konnte nicht gelesen werden',
'memory.searchAria': 'Speicher durchsuchen',
'memory.searchPlaceholder': 'Speichereinträge durchsuchen...',
@@ -4563,7 +4563,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': 'Agentenprofil',
'chat.agentProfile.quick': 'Schnell',
'chat.agentProfile.reasoning': 'Denkmodus',
'chat.agentProfile.proReasoning': 'Pro-Denkmodus',
'chat.agentProfile.namePlaceholder': 'Profilname',
'chat.agentProfile.promptStylePlaceholder': 'Eingabeaufforderungsstil',
'chat.agentProfile.allowedToolsPlaceholder': 'Zulässige Tools',
+1 -2
View File
@@ -2244,7 +2244,7 @@ const en: TranslationMap = {
'chat.attachment.unsupportedType':
'Unsupported file type. Use an image (PNG, JPEG, WebP, GIF, BMP) or a PDF, TXT, or Markdown file.',
'chat.attachment.imageNotSupported':
'This model cant read images. Switch to Pro-reasoning to attach your image.',
'This model cant read images. You can use the OpenHuman Reasoning tier to attach your image.',
'chat.attachment.readFailed': 'Could not read file',
// Memory (additional)
@@ -4980,7 +4980,6 @@ const en: TranslationMap = {
'chat.agentProfile.label': 'Agent profile',
'chat.agentProfile.quick': 'Quick',
'chat.agentProfile.reasoning': 'Reasoning',
'chat.agentProfile.proReasoning': 'Pro-reasoning',
'chat.agentProfile.namePlaceholder': 'Profile name',
'chat.agentProfile.promptStylePlaceholder': 'Prompt style',
'chat.agentProfile.allowedToolsPlaceholder': 'Allowed tools',
+1 -2
View File
@@ -1909,7 +1909,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'Tipo de archivo no compatible. Use una imagen (PNG, JPEG, WebP, GIF, BMP) o un archivo PDF, TXT o Markdown.',
'chat.attachment.imageNotSupported':
'Este modelo no puede leer imágenes. Cambia a Pro-razonamiento para adjuntar tu imagen.',
'Este modelo no puede leer imágenes. Puedes usar el nivel Razonamiento de OpenHuman para adjuntar tu imagen.',
'chat.attachment.readFailed': 'No se pudo leer el archivo',
'memory.searchAria': 'Buscar en memoria',
'memory.searchPlaceholder': 'Buscar entradas de memoria...',
@@ -4532,7 +4532,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': 'Perfil del agente',
'chat.agentProfile.quick': 'Rápido',
'chat.agentProfile.reasoning': 'Razonamiento',
'chat.agentProfile.proReasoning': 'Pro-razonamiento',
'chat.agentProfile.namePlaceholder': 'Nombre del perfil',
'chat.agentProfile.promptStylePlaceholder': 'estilo rápido',
'chat.agentProfile.allowedToolsPlaceholder': 'Herramientas permitidas',
+1 -2
View File
@@ -1917,7 +1917,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'Type de fichier non pris en charge. Utilise une image (PNG, JPEG, WebP, GIF, BMP) ou un fichier PDF, TXT ou Markdown.',
'chat.attachment.imageNotSupported':
'Ce modèle ne peut pas lire les images. Passe en Pro-raisonnement pour joindre ton image.',
'Ce modèle ne peut pas lire les images. Tu peux utiliser le niveau Raisonnement dOpenHuman pour joindre ton image.',
'chat.attachment.readFailed': 'Impossible de lire le fichier',
'memory.searchAria': 'Rechercher dans la mémoire',
'memory.searchPlaceholder': 'Rechercher des entrées de mémoire…',
@@ -4549,7 +4549,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': "Profil d'agent",
'chat.agentProfile.quick': 'Rapide',
'chat.agentProfile.reasoning': 'Raisonnement',
'chat.agentProfile.proReasoning': 'Pro-raisonnement',
'chat.agentProfile.namePlaceholder': 'Nom du profil',
'chat.agentProfile.promptStylePlaceholder': 'Style de prompt',
'chat.agentProfile.allowedToolsPlaceholder': 'Outils autorisés',
+1 -2
View File
@@ -1869,7 +1869,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'असमर्थित फ़ाइल प्रकार। कोई छवि (PNG, JPEG, WebP, GIF, BMP) या PDF, TXT, या Markdown फ़ाइल का उपयोग करें।',
'chat.attachment.imageNotSupported':
'यह मॉडल छवियाँ नहीं पढ़ सकता। अपनी छवि संलग्न करने के लिए प्रो तर्क पर स्विच करें।',
'यह मॉडल छवियाँ नहीं पढ़ सकता। अपनी छवि संलग्न करने के लिए आप OpenHuman तर्क टियर का उपयोग कर सकते हैं।',
'chat.attachment.readFailed': 'फ़ाइल पढ़ नहीं सकी',
'memory.searchAria': 'मेमोरी सर्च करें',
'memory.searchPlaceholder': 'मेमोरी एंट्रीज़ सर्च करें...',
@@ -4455,7 +4455,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': 'एजेंट प्रोफ़ाइल',
'chat.agentProfile.quick': 'त्वरित',
'chat.agentProfile.reasoning': 'तर्क',
'chat.agentProfile.proReasoning': 'प्रो तर्क',
'chat.agentProfile.namePlaceholder': 'प्रोफ़ाइल नाम',
'chat.agentProfile.promptStylePlaceholder': 'शीघ्र शैली',
'chat.agentProfile.allowedToolsPlaceholder': 'अनुमत उपकरण',
+1 -2
View File
@@ -1872,7 +1872,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'Jenis file tidak didukung. Gunakan gambar (PNG, JPEG, WebP, GIF, BMP) atau file PDF, TXT, atau Markdown.',
'chat.attachment.imageNotSupported':
'Model ini tidak dapat membaca gambar. Beralih ke Penalaran Pro untuk melampirkan gambar Anda.',
'Model ini tidak dapat membaca gambar. Anda dapat menggunakan tingkat Penalaran OpenHuman untuk melampirkan gambar Anda.',
'chat.attachment.readFailed': 'Tidak dapat membaca file',
'memory.searchAria': 'Cari memori',
'memory.searchPlaceholder': 'Cari entri memori...',
@@ -4466,7 +4466,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': 'Profil agen',
'chat.agentProfile.quick': 'Cepat',
'chat.agentProfile.reasoning': 'Penalaran',
'chat.agentProfile.proReasoning': 'Penalaran Pro',
'chat.agentProfile.namePlaceholder': 'Nama profil',
'chat.agentProfile.promptStylePlaceholder': 'Gaya perintah',
'chat.agentProfile.allowedToolsPlaceholder': 'Alat yang diizinkan',
+1 -2
View File
@@ -1901,7 +1901,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'Tipo di file non supportato. Usa un file immagine (PNG, JPEG, WebP, GIF, BMP) oppure PDF, TXT o Markdown.',
'chat.attachment.imageNotSupported':
'Questo modello non può leggere le immagini. Passa a Pro-ragionamento per allegare la tua immagine.',
'Questo modello non può leggere le immagini. Puoi usare il livello Ragionamento di OpenHuman per allegare la tua immagine.',
'chat.attachment.readFailed': 'Impossibile leggere il file',
'memory.searchAria': 'Cerca memoria',
'memory.searchPlaceholder': 'Cerca voci di memoria...',
@@ -4525,7 +4525,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': 'Profilo agente',
'chat.agentProfile.quick': 'Veloce',
'chat.agentProfile.reasoning': 'Ragionamento',
'chat.agentProfile.proReasoning': 'Pro-ragionamento',
'chat.agentProfile.namePlaceholder': 'Nome profilo',
'chat.agentProfile.promptStylePlaceholder': 'Stile prompt',
'chat.agentProfile.allowedToolsPlaceholder': 'Strumenti consentiti',
+1 -2
View File
@@ -1849,7 +1849,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'지원되지 않는 파일 형식입니다. 이미지(PNG, JPEG, WebP, GIF, BMP) 또는 PDF, TXT, Markdown 파일을 사용하세요.',
'chat.attachment.imageNotSupported':
'이 모델은 이미지를 읽을 수 없습니다. 이미지를 첨부하려면 프로 추론으로 전환하세요.',
'이 모델은 이미지를 읽을 수 없습니다. 이미지를 첨부하려면 OpenHuman 추론 등급을 사용할 수 있습니다.',
'chat.attachment.readFailed': '파일을 읽을 수 없습니다',
'memory.searchAria': '메모리 검색',
'memory.searchPlaceholder': '메모리 항목 검색...',
@@ -4407,7 +4407,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': '에이전트 프로필',
'chat.agentProfile.quick': '빠름',
'chat.agentProfile.reasoning': '추론',
'chat.agentProfile.proReasoning': '프로 추론',
'chat.agentProfile.namePlaceholder': '프로필 이름',
'chat.agentProfile.promptStylePlaceholder': '프롬프트 스타일',
'chat.agentProfile.allowedToolsPlaceholder': '허용된 도구',
+1 -2
View File
@@ -1890,7 +1890,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'Nieobsługiwany typ pliku. Użyj obrazu (PNG, JPEG, WebP, GIF, BMP) lub pliku PDF, TXT albo Markdown.',
'chat.attachment.imageNotSupported':
'Ten model nie może odczytywać obrazów. Przełącz na Pro-rozumowanie, aby dołączyć obraz.',
'Ten model nie może odczytywać obrazów. Możesz użyć poziomu Rozumowanie OpenHuman, aby dołączyć obraz.',
'chat.attachment.readFailed': 'Nie można odczytać pliku',
'memory.searchAria': 'Szukaj w pamięci',
'memory.searchPlaceholder': 'Szukaj wpisów pamięci...',
@@ -4523,7 +4523,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': 'Profil agenta',
'chat.agentProfile.quick': 'Szybki',
'chat.agentProfile.reasoning': 'Rozumowanie',
'chat.agentProfile.proReasoning': 'Pro-rozumowanie',
'chat.agentProfile.namePlaceholder': 'Nazwa profilu',
'chat.agentProfile.promptStylePlaceholder': 'Styl promptu',
'chat.agentProfile.allowedToolsPlaceholder': 'Dozwolone narzędzia',
+1 -2
View File
@@ -1908,7 +1908,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'Tipo de arquivo não suportado. Use uma imagem (PNG, JPEG, WebP, GIF, BMP) ou um arquivo PDF, TXT ou Markdown.',
'chat.attachment.imageNotSupported':
'Este modelo não consegue ler imagens. Mude para Pro-raciocínio para anexar sua imagem.',
'Este modelo não consegue ler imagens. Você pode usar o nível Raciocínio do OpenHuman para anexar sua imagem.',
'chat.attachment.readFailed': 'Não foi possível ler o arquivo',
'memory.searchAria': 'Pesquisar memória',
'memory.searchPlaceholder': 'Pesquisar entradas de memória...',
@@ -4522,7 +4522,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': 'Perfil do agente',
'chat.agentProfile.quick': 'Rápido',
'chat.agentProfile.reasoning': 'Raciocínio',
'chat.agentProfile.proReasoning': 'Pro-raciocínio',
'chat.agentProfile.namePlaceholder': 'Nome do perfil',
'chat.agentProfile.promptStylePlaceholder': 'Estilo de prompt',
'chat.agentProfile.allowedToolsPlaceholder': 'Ferramentas permitidas',
+1 -2
View File
@@ -1884,7 +1884,7 @@ const messages: TranslationMap = {
'chat.attachment.unsupportedType':
'Неподдерживаемый тип файла. Используйте изображение (PNG, JPEG, WebP, GIF, BMP) или файл PDF, TXT либо Markdown.',
'chat.attachment.imageNotSupported':
'Эта модель не может читать изображения. Переключитесь на Про-рассуждение, чтобы прикрепить изображение.',
'Эта модель не может читать изображения. Вы можете использовать уровень «Рассуждение» от OpenHuman, чтобы прикрепить изображение.',
'chat.attachment.readFailed': 'Не удалось прочитать файл',
'memory.searchAria': 'Поиск в памяти',
'memory.searchPlaceholder': 'Поиск записей памяти...',
@@ -4485,7 +4485,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': 'Профиль агента',
'chat.agentProfile.quick': 'Быстрый',
'chat.agentProfile.reasoning': 'Рассуждение',
'chat.agentProfile.proReasoning': 'Про-рассуждение',
'chat.agentProfile.namePlaceholder': 'Имя профиля',
'chat.agentProfile.promptStylePlaceholder': 'Стиль подсказки',
'chat.agentProfile.allowedToolsPlaceholder': 'Разрешенные инструменты',
+2 -2
View File
@@ -1765,7 +1765,8 @@ const messages: TranslationMap = {
'chat.attachment.tooLarge': '图片超过 {max} 大小限制',
'chat.attachment.unsupportedType':
'不支持的文件类型。请使用图片(PNG、JPEG、WebP、GIF、BMP)或 PDF、TXT、Markdown 文件。',
'chat.attachment.imageNotSupported': '此模型无法读取图像。请切换到专业推理以附加您的图像。',
'chat.attachment.imageNotSupported':
'此模型无法读取图像。您可以使用 OpenHuman 推理层级来附加您的图像。',
'chat.attachment.readFailed': '无法读取文件',
'memory.searchAria': '搜索记忆',
'memory.searchPlaceholder': '搜索记忆条目...',
@@ -4231,7 +4232,6 @@ const messages: TranslationMap = {
'chat.agentProfile.label': '代理简介',
'chat.agentProfile.quick': '快速',
'chat.agentProfile.reasoning': '推理',
'chat.agentProfile.proReasoning': '专业推理',
'chat.agentProfile.namePlaceholder': '个人资料名称',
'chat.agentProfile.promptStylePlaceholder': '提示风格',
'chat.agentProfile.allowedToolsPlaceholder': '允许使用的工具',
-13
View File
@@ -1574,19 +1574,6 @@ const Conversations = ({
}`}>
{t('chat.agentProfile.reasoning')}
</button>
<button
type="button"
role="radio"
aria-checked={selectedAgentProfileId === 'pro-reasoning'}
data-analytics-id="chat-header-mode-pro-reasoning"
onClick={() => void handleSelectAgentProfile('pro-reasoning')}
className={`px-2.5 py-0.5 rounded-full text-xs font-medium transition-all ${
selectedAgentProfileId === 'pro-reasoning'
? 'bg-white dark:bg-neutral-600 text-stone-800 dark:text-neutral-100 shadow-sm'
: 'text-stone-500 dark:text-neutral-400 hover:text-stone-700 dark:hover:text-neutral-200'
}`}>
{t('chat.agentProfile.proReasoning')}
</button>
</div>
{(selectedThreadId ?? activeThreadId) && (
<ChatFilesChip threadId={(selectedThreadId ?? activeThreadId) as string} />
@@ -417,20 +417,20 @@ describe('Conversations — attachment feature', () => {
expect(mockSelectAgentProfile).not.toHaveBeenCalled();
});
it('selects the Pro-reasoning tier from the chat-header toggle', async () => {
it('selects the Reasoning tier from the chat-header toggle', async () => {
await renderWithSelectedThread();
const proButton = await screen.findByRole('radio', { name: 'Pro-reasoning' });
expect(proButton).toHaveAttribute('aria-checked', 'false');
const reasoningButton = await screen.findByRole('radio', { name: 'Reasoning' });
expect(reasoningButton).toHaveAttribute('aria-checked', 'false');
await act(async () => {
fireEvent.click(proButton);
fireEvent.click(reasoningButton);
});
await waitFor(() => {
expect(mockSelectAgentProfile).toHaveBeenCalledWith('pro-reasoning');
expect(mockSelectAgentProfile).toHaveBeenCalledWith('reasoning');
// Store updates to the new active profile → toggle reflects the selection.
expect(screen.getByRole('radio', { name: 'Pro-reasoning' })).toHaveAttribute(
expect(screen.getByRole('radio', { name: 'Reasoning' })).toHaveAttribute(
'aria-checked',
'true'
);
@@ -450,7 +450,7 @@ describe('Conversations — attachment feature', () => {
// Advisory points users at the vision-capable managed tier.
await waitFor(() => {
expect(screen.getByText(/Switch to Pro-reasoning/i)).toBeInTheDocument();
expect(screen.getByText(/OpenHuman Reasoning tier/i)).toBeInTheDocument();
});
// The image is not attached, and the profile is left untouched.
expect(screen.queryByText('no-vision.png')).not.toBeInTheDocument();
-7
View File
@@ -69,13 +69,6 @@ pub const PRICING_TABLE: &[ModelPricing] = &[
cached_input_per_mtok_usd: 1.50,
output_per_mtok_usd: 75.00,
},
// Pro-reasoning chat tier — pricing copied from reasoning-v1.
ModelPricing {
model: "pro-reasoning-v1",
input_per_mtok_usd: 15.00,
cached_input_per_mtok_usd: 1.50,
output_per_mtok_usd: 75.00,
},
// Chat tier — Kimi K2.6 Turbo on Fireworks (backend PR #760).
// Low TTFT, 128k context, `supportsThinking: false`. Rates track
// Fireworks' published Kimi turbo pricing at time of writing.
@@ -413,9 +413,6 @@ impl Agent {
Some("hint:coding") => "coding",
Some("hint:summarization") => "summarization",
Some("hint:reasoning") => "reasoning",
// Always-managed chat tier — keep in sync with
// `factory::provider_for_role` and `web::session::provider_role_for_model_override`.
Some("hint:pro-reasoning") => "pro-reasoning",
_ => "chat",
};
let (provider, mut model_name): (Box<dyn Provider>, String) =
-19
View File
@@ -382,25 +382,6 @@ pub fn built_in_profiles() -> Vec<AgentProfile> {
is_master: false,
sort_order: None,
},
AgentProfile {
id: "pro-reasoning".to_string(),
name: "Pro-reasoning".to_string(),
description: "Premium reasoning tier (always managed).".to_string(),
agent_id: "orchestrator".to_string(),
model_override: Some("hint:pro-reasoning".to_string()),
temperature: None,
system_prompt_suffix: None,
allowed_tools: None,
built_in: true,
avatar_url: None,
voice_id: None,
soul_md: None,
soul_md_path: None,
composio_integrations: None,
memory_dir_suffix: None,
is_master: false,
sort_order: None,
},
AgentProfile {
id: "research".to_string(),
name: "Research".to_string(),
@@ -39,9 +39,6 @@ pub(crate) fn provider_role_for_model_override(model_override: Option<&str>) ->
Some("hint:coding") | Some("coding-v1") => "coding",
Some("hint:summarization") | Some("summarization-v1") => "summarization",
Some("hint:reasoning") => "reasoning",
// pro-reasoning routes to its own always-managed role (never the BYOK
// chat provider) — keep in sync with `factory::provider_for_role`.
Some("hint:pro-reasoning") | Some("pro-reasoning-v1") => "pro-reasoning",
_ => "chat",
}
}
@@ -1533,14 +1533,6 @@ fn provider_role_override_routes_hint_workloads() {
provider_role_for_model_override(Some("hint:reasoning")),
"reasoning"
);
assert_eq!(
provider_role_for_model_override(Some("hint:pro-reasoning")),
"pro-reasoning"
);
assert_eq!(
provider_role_for_model_override(Some("pro-reasoning-v1")),
"pro-reasoning"
);
assert_eq!(
provider_role_for_model_override(Some("gpt-4.1-mini")),
"chat"
+2 -3
View File
@@ -49,9 +49,8 @@ pub use schema::{
VoiceActivationMode, VoiceServerConfig, WebSearchConfig, WebhookConfig,
DEFAULT_CLOUD_LLM_MODEL, DEFAULT_MEMORY_SYNC_INTERVAL_SECS, DEFAULT_MODEL,
MEMORY_SYNC_INTERVAL_PRESETS_SECS, MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1,
MODEL_PRO_REASONING_V1, MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1,
SEARCH_ENGINE_BRAVE, SEARCH_ENGINE_DISABLED, SEARCH_ENGINE_MANAGED, SEARCH_ENGINE_PARALLEL,
SEARCH_ENGINE_QUERIT,
MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, SEARCH_ENGINE_BRAVE,
SEARCH_ENGINE_DISABLED, SEARCH_ENGINE_MANAGED, SEARCH_ENGINE_PARALLEL, SEARCH_ENGINE_QUERIT,
};
pub use schemas::{
all_controller_schemas as all_config_controller_schemas,
+2 -10
View File
@@ -150,8 +150,8 @@ impl Default for CostConfig {
/// Default pricing for popular models (USD per 1M tokens)
fn get_default_pricing() -> HashMap<String, ModelPricing> {
use super::types::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_PRO_REASONING_V1,
MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1,
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
MODEL_REASONING_V1,
};
let mut prices = HashMap::new();
@@ -163,14 +163,6 @@ fn get_default_pricing() -> HashMap<String, ModelPricing> {
output: 2.52,
},
);
// Pro-reasoning chat tier — pricing copied from reasoning-v1.
prices.insert(
MODEL_PRO_REASONING_V1.into(),
ModelPricing {
input: 0.84,
output: 2.52,
},
);
// Kimi K2.6 Turbo on Fireworks — see backend PR #760.
prices.insert(
MODEL_CHAT_V1.into(),
-4
View File
@@ -9,10 +9,6 @@ use std::path::PathBuf;
/// Standard model identifiers matching the backend model registry.
pub const MODEL_AGENTIC_V1: &str = "agentic-v1";
pub const MODEL_REASONING_V1: &str = "reasoning-v1";
/// Premium reasoning tier for chat. Always managed (OpenHuman backend) — never
/// BYOK/cloud-overridable — and selected only via the chat-header toggle, so it
/// is intentionally not a configurable Advanced-LLM workload.
pub const MODEL_PRO_REASONING_V1: &str = "pro-reasoning-v1";
/// Low-latency conversational tier.
pub const MODEL_CHAT_V1: &str = "chat-v1";
/// Legacy low-latency chat tier slug retained for older persisted configs.
+6 -9
View File
@@ -6,8 +6,7 @@
//! metadata is not yet available.
use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_PRO_REASONING_V1,
MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1,
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1,
};
/// Conservative default for OpenHuman abstract tier models (tokens).
@@ -98,9 +97,7 @@ pub fn context_window_for_model(model: &str) -> Option<u64> {
fn tier_context_window(model: &str) -> Option<u64> {
match model {
MODEL_REASONING_V1 | MODEL_PRO_REASONING_V1 | MODEL_AGENTIC_V1 | MODEL_CODING_V1 => {
Some(TIER_LARGE_CONTEXT)
}
MODEL_REASONING_V1 | MODEL_AGENTIC_V1 | MODEL_CODING_V1 => Some(TIER_LARGE_CONTEXT),
"summarization-v1" => Some(TIER_SUMMARIZATION_CONTEXT),
MODEL_CHAT_V1 | MODEL_REASONING_QUICK_V1 | "chat" => Some(TIER_STANDARD_CONTEXT),
m if m.starts_with("gemma") || m.contains(":1b") || m.contains("270m") => {
@@ -172,7 +169,7 @@ pub fn model_vision_enabled(model: &str, config: &crate::openhuman::config::Conf
/// - **Managed OpenHuman tiers** consult the hardcoded per-tier map
/// ([`crate::openhuman::inference::provider::factory::oh_tier_supports_vision`]) —
/// the remote backend does not advertise per-tier capability, so the core owns
/// it. Currently every managed tier is `false`.
/// it. Currently only `reasoning-v1` is vision-capable.
/// - **Custom/BYOK models** consult the user-set `model_registry.vision` flag
/// ([`model_vision_enabled`]).
pub fn model_supports_vision(model: &str, config: &crate::openhuman::config::Config) -> bool {
@@ -221,7 +218,6 @@ mod tests {
#[test]
fn tier_aliases_resolve() {
assert_eq!(context_window_for_model("reasoning-v1"), Some(200_000));
assert_eq!(context_window_for_model("pro-reasoning-v1"), Some(200_000));
assert_eq!(context_window_for_model("agentic-v1"), Some(200_000));
assert_eq!(context_window_for_model("chat-v1"), Some(128_000));
assert_eq!(
@@ -288,9 +284,10 @@ mod tests {
cost_per_1m_output: 0.0,
vision: true,
}];
// Managed tiers are non-vision (the per-tier map is all `false` today).
// `reasoning-v1` is the one vision-capable managed tier; the rest are not.
assert!(model_supports_vision("reasoning-v1", &config));
assert!(model_supports_vision("hint:reasoning", &config));
assert!(!model_supports_vision("chat-v1", &config));
assert!(!model_supports_vision("reasoning-v1", &config));
assert!(!model_supports_vision("hint:chat", &config));
// BYOK model flagged in the registry is vision-capable.
assert!(model_supports_vision("my-llava", &config));
+8 -29
View File
@@ -62,15 +62,14 @@ pub const BYOK_INCOMPLETE_SENTINEL: &str = "__byok_incomplete__";
fn is_abstract_tier_model(model: &str) -> bool {
use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_PRO_REASONING_V1,
MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1,
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
MODEL_REASONING_V1,
};
// No dedicated constant for the summarization tier yet; keep the literal
// in sync with the tier name used by the summarizer sub-agent.
const MODEL_SUMMARIZATION_V1: &str = "summarization-v1";
let trimmed = model.trim();
trimmed == MODEL_REASONING_V1
|| trimmed == MODEL_PRO_REASONING_V1
|| trimmed == MODEL_REASONING_QUICK_V1
|| trimmed == MODEL_CHAT_V1
|| trimmed == MODEL_AGENTIC_V1
@@ -94,10 +93,6 @@ pub fn auth_key_for_slug(slug: &str) -> String {
pub fn resolve_model_for_hint(hint_or_tier: &str, config: &Config) -> String {
let hint_to_tier: &[(&str, &str)] = &[
("reasoning", crate::openhuman::config::MODEL_REASONING_V1),
(
"pro-reasoning",
crate::openhuman::config::MODEL_PRO_REASONING_V1,
),
("chat", crate::openhuman::config::MODEL_CHAT_V1),
("agentic", crate::openhuman::config::MODEL_AGENTIC_V1),
("coding", crate::openhuman::config::MODEL_CODING_V1),
@@ -105,12 +100,6 @@ pub fn resolve_model_for_hint(hint_or_tier: &str, config: &Config) -> String {
];
let tier_to_role: &[(&str, &str)] = &[
(crate::openhuman::config::MODEL_REASONING_V1, "reasoning"),
// Dedicated role so `provider_for_role` can force the managed backend —
// pro-reasoning must never inherit a BYOK/cloud chat provider.
(
crate::openhuman::config::MODEL_PRO_REASONING_V1,
"pro-reasoning",
),
(crate::openhuman::config::MODEL_CHAT_V1, "chat"),
(crate::openhuman::config::MODEL_REASONING_QUICK_V1, "chat"),
(crate::openhuman::config::MODEL_AGENTIC_V1, "agentic"),
@@ -164,20 +153,18 @@ pub fn resolve_model_for_hint(hint_or_tier: &str, config: &Config) -> String {
/// to the backend.
pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_PRO_REASONING_V1,
MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1,
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1,
};
matches!(
model,
MODEL_REASONING_V1
| MODEL_PRO_REASONING_V1
| MODEL_CHAT_V1
| MODEL_AGENTIC_V1
| MODEL_CODING_V1
| MODEL_REASONING_QUICK_V1
| MODEL_SUMMARIZATION_V1
| "hint:reasoning"
| "hint:pro-reasoning"
| "hint:chat"
| "hint:agentic"
| "hint:coding"
@@ -192,20 +179,19 @@ pub(crate) fn is_known_openhuman_tier(model: &str) -> bool {
/// constants and their `hint:*` forms (callers may pass either pre- or
/// post-resolution).
///
/// `pro-reasoning-v1` is multimodal; the rest return `false` — flip an individual
/// `reasoning-v1` is multimodal; the rest return `false` — flip an individual
/// arm to `true` once that tier is confirmed multimodal on the backend. This is
/// the **only** place to change managed-model vision; BYOK/custom models are
/// handled separately by the user-set `model_registry.vision` flag
/// ([`crate::openhuman::inference::model_context::model_vision_enabled`]).
pub(crate) fn oh_tier_supports_vision(model: &str) -> bool {
use crate::openhuman::config::{
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_PRO_REASONING_V1,
MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1,
MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1,
MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1,
};
match model {
MODEL_PRO_REASONING_V1 | "hint:pro-reasoning" => true,
MODEL_REASONING_V1 | "hint:reasoning" => true,
MODEL_CHAT_V1 | "hint:chat" => false,
MODEL_REASONING_V1 | "hint:reasoning" => false,
MODEL_REASONING_QUICK_V1 => false,
MODEL_AGENTIC_V1 | "hint:agentic" => false,
MODEL_CODING_V1 | "hint:coding" => false,
@@ -232,12 +218,6 @@ pub(crate) fn oh_tier_supports_vision(model: &str) -> bool {
/// migration 1→2 preserved the URL as a custom provider entry but older
/// configs did not explicitly set per-workload routes.
pub fn provider_for_role(role: &str, config: &Config) -> String {
// `pro-reasoning` is always managed: it has no per-workload config knob and
// must never inherit a BYOK/cloud provider (or `primary_cloud`). Force the
// OpenHuman backend before any inheritance/fallback logic runs.
if role == "pro-reasoning" {
return PROVIDER_OPENHUMAN.to_string();
}
let opt = match role {
"chat" => config.chat_provider.as_deref(),
"reasoning" => config.reasoning_provider.as_deref(),
@@ -750,7 +730,6 @@ fn make_openhuman_backend(config: &Config) -> anyhow::Result<(Box<dyn Provider>,
// "deepseek-v4-pro", "claude-opus-4-7") fall back to the platform default.
let model = match model.strip_prefix("hint:") {
Some("reasoning") => crate::openhuman::config::MODEL_REASONING_V1.to_string(),
Some("pro-reasoning") => crate::openhuman::config::MODEL_PRO_REASONING_V1.to_string(),
Some("chat") => crate::openhuman::config::MODEL_CHAT_V1.to_string(),
Some("agentic") => crate::openhuman::config::MODEL_AGENTIC_V1.to_string(),
Some("coding") => crate::openhuman::config::MODEL_CODING_V1.to_string(),
@@ -830,7 +830,6 @@ fn lookup_key_for_slug_routes_openai_oauth_lookup_path() {
fn known_tiers_pass() {
for tier in [
"reasoning-v1",
"pro-reasoning-v1",
"chat-v1",
"agentic-v1",
"coding-v1",
@@ -847,7 +846,6 @@ fn known_tiers_pass() {
#[test]
fn known_hints_pass() {
assert!(is_known_openhuman_tier("hint:reasoning"));
assert!(is_known_openhuman_tier("hint:pro-reasoning"));
assert!(is_known_openhuman_tier("hint:chat"));
assert!(is_known_openhuman_tier("hint:agentic"));
assert!(is_known_openhuman_tier("hint:coding"));
@@ -872,18 +870,20 @@ fn invalid_models_fail() {
// ── oh_tier_supports_vision ──────────────────────────────────────────────────────
#[test]
fn no_managed_tier_is_vision_capable_yet() {
// Every managed tier (and its hint form) is non-vision until confirmed
fn reasoning_is_the_vision_capable_managed_tier() {
// `reasoning-v1` (and its hint form) is the one vision-capable managed tier.
assert!(oh_tier_supports_vision("reasoning-v1"));
assert!(oh_tier_supports_vision("hint:reasoning"));
// Every other managed tier (and its hint form) is non-vision until confirmed
// multimodal on the backend. Flip the corresponding arm in
// `oh_tier_supports_vision` to enable one.
for model in [
"reasoning-v1",
"chat-v1",
"agentic-v1",
"coding-v1",
"reasoning-quick-v1",
"summarization-v1",
"hint:reasoning",
"hint:chat",
"hint:agentic",
"hint:coding",
@@ -903,45 +903,6 @@ fn unknown_models_are_not_vision_capable() {
assert!(!oh_tier_supports_vision(""));
}
#[test]
fn pro_reasoning_is_vision_capable() {
assert!(oh_tier_supports_vision("pro-reasoning-v1"));
assert!(oh_tier_supports_vision("hint:pro-reasoning"));
}
#[test]
fn pro_reasoning_is_classified_as_abstract_tier() {
// Must be detected as an abstract managed tier alongside the others so
// custom/BYOK routes remap/reject it instead of forwarding it as a
// provider-native model id.
assert!(is_abstract_tier_model("pro-reasoning-v1"));
assert!(is_abstract_tier_model(" pro-reasoning-v1 "));
}
// ── pro-reasoning is always managed ──────────────────────────────────────────
#[test]
fn pro_reasoning_role_forces_openhuman_even_with_byok_chat() {
// pro-reasoning has no per-workload knob and must never inherit the user's
// BYOK chat provider — `provider_for_role` forces the managed backend.
let mut config = Config::default();
config.chat_provider = Some("openai:gpt-5".to_string());
config.reasoning_provider = Some("openai:gpt-5".to_string());
assert_eq!(provider_for_role("pro-reasoning", &config), "openhuman");
}
#[test]
fn pro_reasoning_hint_resolves_to_managed_tier_despite_byok() {
// Even with a BYOK chat provider configured, resolving the pro-reasoning
// hint yields the managed tier id (not the BYOK model).
let mut config = Config::default();
config.chat_provider = Some("openai:gpt-5".to_string());
assert_eq!(
resolve_model_for_hint("hint:pro-reasoning", &config),
"pro-reasoning-v1"
);
}
#[test]
fn make_openhuman_backend_forwards_unknown_hint_verbatim() {
// Unrecognised hint:* strings (e.g. hint:reaction for lightweight models)