diff options
Diffstat (limited to 'modules/core/core-bundle.js')
| -rw-r--r-- | modules/core/core-bundle.js | 38 |
1 files changed, 28 insertions, 10 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 1a92ef9..fcf1505 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -1290,6 +1290,19 @@ class OllamaManager { // ─── Provider management ───────────────────────────────────────────────── + // Ollama's server-side default context is small (2k-4k depending on the build). The agent + // sends a system prompt, up to 20 messages and up to 24k chars of tool results, so without an + // explicit num_ctx the server silently truncates the prompt — the model "forgets" what it was + // just told. Send it on every Ollama request; the OpenAI-compatible branch has its own params. + _ctxTokens() { + const n = Number(this.numCtx || (typeof localStorage !== 'undefined' ? localStorage.getItem('webx11_ollama_numctx') : 0)); + return Number.isFinite(n) && n >= 512 ? Math.floor(n) : 16384; + } + + _ollamaOptions(extra) { + return Object.assign({ num_ctx: this._ctxTokens() }, extra || {}); + } + _loadProviders() { const stored = this._getSecret('webx11_llm_providers'); if (stored) { @@ -1506,7 +1519,7 @@ class OllamaManager { break; } catch (error) { this.terminal.log('WARN', 'Ollama', provider.name + ' endpoint ' + ep + ' failed: ' + error.message); - this.terminal.print(`${provider.name} endpoint ${ep} failed: ${error.message}`, 'warning'); + if (!this._quietConnect) this.terminal.print(`${provider.name} endpoint ${ep} failed: ${error.message}`, 'warning'); } } } else { @@ -1544,7 +1557,7 @@ class OllamaManager { success = true; } catch (error) { this.terminal.log('WARN', 'Ollama', provider.name + ' failed: ' + error.message); - this.terminal.print(`${provider.name} failed: ${error.message}`, 'warning'); + if (!this._quietConnect) this.terminal.print(`${provider.name} failed: ${error.message}`, 'warning'); } } @@ -1554,7 +1567,7 @@ class OllamaManager { // If provider-based connect failed, try bare endpoint fallback (legacy) for (const ep of allEndpoints) { try { - this.terminal.print(`Connecting (legacy fallback) to ${ep}...`, 'info'); + if (!this._quietConnect) this.terminal.print(`Connecting (legacy fallback) to ${ep}...`, 'info'); const response = await fetch(`${ep}/api/tags`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); @@ -1580,7 +1593,7 @@ class OllamaManager { return true; } catch (error) { this.terminal.log('WARN', 'Ollama', 'Endpoint ' + ep + ' failed: ' + error.message); - this.terminal.print(`Endpoint ${ep} failed: ${error.message}`, 'warning'); + if (!this._quietConnect) this.terminal.print(`Endpoint ${ep} failed: ${error.message}`, 'warning'); } } @@ -1622,6 +1635,10 @@ class OllamaManager { this.terminal.print('Ollama: connecting on first use…', 'info'); let timedOut = false; try { + // One failure should read as one failure: connect() prints a line per endpoint it + // tries, which made an auto-connect failure seven lines of noise. Keep the detail in + // the log, print only the outcome for an automatic attempt. + this._quietConnect = true; this._connecting = this.connect(); // connect() uses a bare fetch(), so a blackholed endpoint would hang this promise // forever and — because the promise is shared — every later message with it. @@ -1635,6 +1652,7 @@ class OllamaManager { // A throwing terminal.log() must not leave _connecting poisoned: log defensively. try { this.terminal.log('WARN', 'Ollama', 'Auto-connect failed: ' + (e && e.message ? e.message : e)); } catch (_) {} } finally { + this._quietConnect = false; this._connecting = null; } if (this.connected && savedModel && !(this.models || []).some(m => m.name === savedModel)) { @@ -1706,7 +1724,7 @@ class OllamaManager { const response = await fetch(`${provider.endpoint}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled }), + body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled, options: this._ollamaOptions() }), signal }); if (!response.ok) throw new Error(`HTTP ${response.status}`); @@ -1876,7 +1894,7 @@ class OllamaManager { const response = await fetch(`${provider.endpoint}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled }), + body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled, options: this._ollamaOptions() }), signal }); if (!response.ok) throw new Error(`HTTP ${response.status}`); @@ -2053,7 +2071,7 @@ class OllamaManager { try { if (provider.type === 'ollama') { - const body = { model: useModel, messages, stream: false }; + const body = { model: useModel, messages, stream: false, options: this._ollamaOptions() }; if (tools && tools.length > 0) body.tools = tools; const t0 = Date.now(); const response = await fetch(`${provider.endpoint}/api/chat`, { @@ -2286,7 +2304,7 @@ ${toolDesc} Only output XML function calls for tool tasks. If no tool is needed, answer normally.` }; const msgsWithTools = [toolContext, ...messages]; - const body = { model: useModel, messages: msgsWithTools, stream: false, options: { temperature: 0.1 } }; + const body = { model: useModel, messages: msgsWithTools, stream: false, options: this._ollamaOptions({ temperature: 0.1 }) }; const response = await fetch(`${this.endpoint}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -2337,7 +2355,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm // Original Ollama fallback this.terminal.print(`Model doesn't support tool calling. Retrying /api/chat without tools...`, 'warning'); try { - const body = { model: useModel, messages, stream: false }; + const body = { model: useModel, messages, stream: false, options: this._ollamaOptions() }; const response = await fetch(`${this.endpoint}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), @@ -11084,7 +11102,7 @@ buildSystemPrompt() { const why = (typeof this.terminal.fs._isVirtualPath === 'function' && this.terminal.fs._isVirtualPath(wp)) ? 'read-only virtual path' : 'parent directory missing or target is a directory'; - return `Write FAILED: ${wp} — ${why}; nothing was written`; + return `Write FAILED: ${String(wp).replace(/\/+$/, '') || '/'} — ${why}; nothing was written`; } return `File written: ${wp} (${content.length} chars)`; } |