From d026e13a5a54e20511148eb4e44b41a79461e950 Mon Sep 17 00:00:00 2001 From: pewdiepie-archdaemon Date: Mon, 1 Jun 2026 10:20:18 +0900 Subject: [PATCH] Fix provider setup and strip message metadata --- src/llm_core.py | 19 +++++++++++--- static/js/slashCommands.js | 52 ++++++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/llm_core.py b/src/llm_core.py index 1f80229..08d8db2 100644 --- a/src/llm_core.py +++ b/src/llm_core.py @@ -357,6 +357,19 @@ def _parse_anthropic_response(data: dict) -> str: return block.get("text", "") return "" + +def _sanitize_llm_messages(messages: List[Dict]) -> List[Dict]: + """Strip Odysseus-only metadata before sending messages to providers.""" + allowed = {"role", "content", "name", "tool_call_id", "tool_calls", "function_call"} + cleaned = [] + for msg in messages or []: + if not isinstance(msg, dict): + continue + item = {k: v for k, v in msg.items() if k in allowed and v is not None} + if "role" in item and "content" in item: + cleaned.append(item) + return cleaned + def _normalize_anthropic_url(url: str) -> str: """Ensure Anthropic URL points to /v1/messages.""" url = url.rstrip("/") @@ -422,7 +435,7 @@ def llm_call(url: str, model: str, messages: List[Dict], temperature: float = LL if isinstance(headers, dict): h.update(headers) - messages_copy = [msg.copy() for msg in messages] + messages_copy = _sanitize_llm_messages(messages) # Consolidate multiple system messages into one at the start. sys_parts = [] @@ -530,7 +543,7 @@ async def llm_call_async( ) -> str: """Asynchronous LLM call using httpx with connection pooling, timeout, retry logic, and performance logging.""" provider = _detect_provider(url) - messages_copy = [msg.copy() for msg in messages] + messages_copy = _sanitize_llm_messages(messages) # Consolidate multiple system messages into one at the start. sys_parts = [] @@ -627,7 +640,7 @@ async def stream_llm(url: str, model: str, messages: List[Dict], temperature: fl - data: [DONE] — end of stream """ provider = _detect_provider(url) - messages_copy = [msg.copy() for msg in messages] + messages_copy = _sanitize_llm_messages(messages) # Consolidate multiple system messages into one at the start. # Some models (e.g. Qwen3.5) reject system messages that aren't first. diff --git a/static/js/slashCommands.js b/static/js/slashCommands.js index a27ea74..6485c29 100644 --- a/static/js/slashCommands.js +++ b/static/js/slashCommands.js @@ -101,6 +101,24 @@ function _extractSetupProviderCredential(input) { return null; } +function _normalizeSetupBaseUrl(raw) { + let u = (raw || '').trim(); + u = u.replace(/^https?:\/(?!\/)/, m => m + '/'); + u = u.replace(/^htp:/, 'http:').replace(/^htps:/, 'https:'); + if (!/^https?:\/\//i.test(u)) u = 'http://' + u; + u = u.replace(/\/+$/, ''); + u = u.replace(/\/v1\/(models|chat\/completions|completions|messages)\/?$/i, '/v1'); + u = u.replace(/\/(models|chat\/completions|completions|v1\/messages)\/?$/i, ''); + u = u.replace(/\/v1\/v1$/i, '/v1'); + if (!u.includes('api.') && !u.includes('openrouter') && !u.endsWith('/v1')) { + try { + const parsed = new URL(u); + if (!parsed.pathname || parsed.pathname === '/') u += '/v1'; + } catch (_) {} + } + return u; +} + function _clearSetupGuideMessages() { Storage.remove('odysseus-setup-guide-messages'); } @@ -4668,15 +4686,39 @@ function _clearSetupCommandInput() { async function _cmdSetup(args, ctx) { _hideWelcomeScreen(); _clearSetupCommandInput(); + const topic = (args[0] || '').trim().toLowerCase(); + const topicArgs = args.slice(1); + const provider = _setupProviderFromInput(topic); + if (provider) { + _clearSetupGuideMessages(); + const credential = topicArgs.join(' ').trim(); + if (credential) { + await connectDetectedSetupEndpoint({ base_url: provider.url, api_key: credential, name: provider.name }); + } else { + pendingSetupProvider = provider; + setupMode = 'endpoint-key-for-provider'; + await _setupReply(`Paste your ${provider.name} API key.`); + } + return true; + } + if (topic === 'local') { + _clearSetupGuideMessages(); + const rawUrl = topicArgs.join(' ').trim(); + if (rawUrl) { + const normalized = _normalizeSetupBaseUrl(rawUrl); + await connectDetectedSetupEndpoint({ base_url: normalized, api_key: '', name: 'Local' }); + } else { + setupMode = 'endpoint-provider-first'; + await _setupReply('Paste your local endpoint URL, for example http://100.x.x.x:11434/v1.'); + } + return true; + } // Check if models are already configured const modelsBox = document.getElementById('models'); const hasModels = modelsBox && modelsBox.querySelector('.models-row'); if (hasModels) { - const topic = (args[0] || '').trim().toLowerCase(); - const topicArgs = args.slice(1); - if (!topic) { _clearSetupGuideMessages(); return _showSetupEndpointGuide(); @@ -5377,9 +5419,9 @@ const COMMANDS = { setup: { alias: ['su', 'seutp'], category: 'Getting started', - help: 'Quick endpoint setup wizard', + help: 'Add local or API model endpoints', handler: _cmdSetup, - usage: '/setup' + usage: '/setup local URL · /setup groq KEY · /setup endpoint' }, demo: { alias: ['tour'],