Claude/venice ai acode plugin cpwzba#2413
Conversation
Adds a full-featured Venice AI coding assistant plugin for Acode with: - Sidebar chat panel with streaming responses and conversation history - Full codebase context: active file, selection, all open tabs - Per-code-block actions: insert at cursor, replace file, new file, copy - Inline system-prompt editor with live preview and reset-to-default - Model picker: Gemma 4 Uncensored, Llama 3.3 70B, DeepSeek R1, Qwen 2.5 Coder, Mistral 3.1, Venice Uncensored, Llama 3.2 3B - Acode settings integration (API key, model, temperature, max tokens) - Context toggles: current file / selection / all open files - Auto file-creation banner when AI mentions a filename - Lightweight markdown renderer with fenced code highlighting - localStorage persistence for all settings across sessions Plugin files live in acode-plugins/venice-ai-wizard/ (source) and acode-plugins/venice-ai-wizard.zip (installable package). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KzaGGLg7cz4jMb85KMAXrb
Replace Element.after() DOM calls (unsupported on some Android WebViews) with strict appendChild ordering. Wrap init() in try/catch so any failure logs to console instead of marking the plugin broken. Add inline system prompt editor in the sidebar panel. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KzaGGLg7cz4jMb85KMAXrb
| try { | ||
| var messages = [{ role: "system", content: cfg.systemPrompt }].concat(buildContextualHistory()); | ||
| var assistantContent = ""; | ||
| var streamDiv = null; | ||
| var rawBuf = ""; | ||
|
|
||
| try { | ||
| removeTypingEl(); | ||
| streamDiv = el("div", "vai-message assistant"); | ||
| if ($messagesEl) $messagesEl.appendChild(streamDiv); | ||
|
|
||
| await callVenice(messages, function (delta, full) { | ||
| rawBuf = full; | ||
| if (streamDiv) streamDiv.textContent = full; | ||
| if ($messagesEl) $messagesEl.scrollTop = $messagesEl.scrollHeight; | ||
| }); | ||
|
|
||
| assistantContent = rawBuf; | ||
| if (streamDiv) { | ||
| streamDiv.textContent = ""; | ||
| streamDiv.appendChild(renderMarkdown(assistantContent)); | ||
| maybeAddFileCreation(streamDiv, assistantContent); | ||
| } | ||
| } catch (streamErr) { | ||
| if (streamDiv && streamDiv.parentNode) streamDiv.parentNode.removeChild(streamDiv); | ||
| removeTypingEl(); | ||
| assistantContent = await callVenice(messages); | ||
| addAssistantMsg(assistantContent); | ||
| } |
There was a problem hiding this comment.
Double API call on any streaming-layer error
When callVenice throws because of a non-OK HTTP status (401 invalid key, 429 rate-limit, 500 server error, etc.), the error is thrown before the reader is opened. The inner catch (streamErr) silently swallows that error and immediately fires a second identical callVenice(messages) call at line 796, which will fail for the same reason. The user sees only the error from the second attempt, but two API requests have been charged against the account. The inner catch should re-throw errors that originate from a non-OK HTTP response rather than treating all errors as recoverable streaming failures.
| function loadSettings() { | ||
| try { | ||
| const raw = localStorage.getItem(STORAGE_KEY); | ||
| if (raw) return Object.assign(defaults(), JSON.parse(raw)); | ||
| } catch (_) {} | ||
| return defaults(); | ||
| } | ||
|
|
||
| const cfg = loadSettings(); | ||
|
|
||
| function saveSettings() { | ||
| try { | ||
| localStorage.setItem(STORAGE_KEY, JSON.stringify(cfg)); | ||
| } catch (_) {} | ||
| } |
There was a problem hiding this comment.
Venice AI API key stored in plaintext
localStorage
cfg.apiKey (including the raw Venice AI API key) is serialized into localStorage["venice_ai_wizard_settings"] via JSON.stringify. In the Acode WebView environment, any other plugin that runs in the same WebView context can read localStorage directly and extract the key without any user interaction. An API key leak allows an attacker to make authenticated requests to Venice AI billed to the victim's account. Ideally the key should be stored in a more isolated store (Acode's secure storage API if available), or at minimum users should be warned that the key is stored in accessible local storage.
| await callVenice(messages, function (delta, full) { | ||
| rawBuf = full; | ||
| if (streamDiv) streamDiv.textContent = full; | ||
| if ($messagesEl) $messagesEl.scrollTop = $messagesEl.scrollHeight; |
There was a problem hiding this comment.
O(n²) streaming rendering on mobile
streamDiv.textContent = full is called on every streamed chunk, replacing the entire accumulated response string each time. For a max-4096-token response arriving in small chunks, this results in roughly O(n²) total character writes. On low-end Android devices (the primary Acode target), this can cause visible jank as responses grow longer. An incremental approach — appending only delta to a text node, or periodically flushing — would keep rendering O(n).
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| function buildContextualHistory() { | ||
| if (!conversationHistory.length) return []; | ||
| var history = conversationHistory.map(function (m) { return { role: m.role, content: m.content }; }); | ||
|
|
||
| for (var i = history.length - 1; i >= 0; i--) { | ||
| if (history[i].role === "user") { | ||
| var original = history[i].content; | ||
| var parts = []; | ||
|
|
||
| if (cfg.includeCurrentFile) { | ||
| var ctx = getActiveFileContext(); | ||
| if (ctx && ctx.content) { | ||
| parts.push("## Current file: " + ctx.filename + "\n```" + ctx.language + "\n" + ctx.content + "\n```"); | ||
| } | ||
| } | ||
| if (cfg.includeSelection) { | ||
| var sel = getSelection(); | ||
| if (sel) parts.push("## Selected text\n```\n" + sel + "\n```"); | ||
| } | ||
| if (cfg.includeOpenFiles) { | ||
| var others = getOpenFilesContext(); | ||
| if (others.length) { | ||
| var txt = others.map(function (f) { | ||
| return "### " + f.filename + "\n```" + f.language + "\n" + f.content + "\n```"; | ||
| }).join("\n\n"); | ||
| parts.push("## Other open files\n" + txt); | ||
| } | ||
| } | ||
|
|
||
| history[i] = { | ||
| role: "user", | ||
| content: parts.length ? parts.join("\n\n") + "\n\n---\n\n" + original : original, | ||
| }; | ||
| break; | ||
| } | ||
| } | ||
| return history; | ||
| } |
There was a problem hiding this comment.
Unbounded conversation history grows request payload without limit
conversationHistory is never pruned. buildContextualHistory() also injects the full active file content into the last user message on every send. For a long session with a large file open (includeCurrentFile: true), the HTTP request body grows indefinitely — duplicate file content is prepended on every round-trip until the request exceeds Venice AI's context window or the API returns a 413/400. Consider capping history length (e.g. last N pairs) and only injecting file context into the very latest message rather than the full history.
|
This is not the right place to publish your plugin, you should publish the plugin zip on https://acode.app |
No description provided.