diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py
index 32dfdb7ca9..2e3254a08c 100644
--- a/backend/open_webui/config.py
+++ b/backend/open_webui/config.py
@@ -2296,20 +2296,31 @@ CODE_INTERPRETER_BLOCKED_MODULES = [
]
DEFAULT_CODE_INTERPRETER_PROMPT = """
-#### Tools Available
+#### Code Interpreter
-1. **Code Interpreter**: ``
- - You have access to a Python shell that runs directly in the user's browser, enabling fast execution of code for analysis, calculations, or problem-solving. Use it in this response.
- - The Python code you write can incorporate a wide array of libraries, handle data manipulation or visualization, perform API calls for web-related tasks, or tackle virtually any computational challenge. Use this flexibility to **think outside the box, craft elegant solutions, and harness Python's full potential**.
- - To use it, **you must enclose your code within `` XML tags** and stop right away. If you don't, the code won't execute.
- - When writing code in the code_interpreter XML tag, Do NOT use the triple backticks code block for markdown formatting, example: ```py # python code ``` will cause an error because it is markdown formatting, it is not python code.
- - When coding, **always aim to print meaningful outputs** (e.g., results, tables, summaries, or visuals) to better interpret and verify the findings. Avoid relying on implicit outputs; prioritize explicit and clear print statements so the results are effectively communicated to the user.
- - After obtaining the printed output, **always provide a concise analysis, interpretation, or next steps to help the user understand the findings or refine the outcome further.**
- - If the results are unclear, unexpected, or require validation, refine the code and execute it again as needed. Always aim to deliver meaningful insights from the results, iterating if necessary.
- - **If a link to an image, audio, or any file is provided in markdown format in the output, ALWAYS regurgitate word for word, explicitly display it as part of the response to ensure the user can access it easily, do NOT change the link.**
- - All responses should be communicated in the chat's primary language, ensuring seamless understanding. If the chat is multilingual, default to English for clarity.
+You have access to a Python code interpreter via: ``
-Ensure that the tools are effectively utilized to achieve the highest-quality analysis for the user."""
+- The Python shell runs directly in the user's browser for fast execution of analysis, calculations, or problem-solving. Use it in this response.
+- You can use a wide array of libraries for data manipulation, visualization, API calls, or any computational task. Think outside the box and harness Python's full potential.
+- **You must enclose your code within `` XML tags** and stop right away. If you don't, the code won't execute.
+- Do NOT use triple backticks (```py ... ```) inside the XML tags — that is markdown formatting, not executable Python code.
+- **Always print meaningful outputs** (results, tables, summaries, visuals). Avoid implicit outputs; use explicit print statements.
+- After obtaining output, **provide a concise analysis, interpretation, or next steps** to help the user understand the findings.
+- If results are unclear or unexpected, refine the code and re-execute. Iterate until you deliver meaningful insights.
+- **If a link to an image, audio, or any file appears in the output, display it exactly as-is** in your response so the user can access it. Do not modify the link.
+- Respond in the chat's primary language. Default to English if multilingual.
+
+Ensure the code interpreter is effectively utilized to achieve the highest-quality analysis for the user."""
+
+# Appended to the code interpreter prompt only when engine is pyodide (not jupyter)
+CODE_INTERPRETER_PYODIDE_FS_PROMPT = """
+
+##### Persistent File System
+
+- User-uploaded files are available at `/mnt/uploads/`. When the user asks you to work with their files, read from this directory.
+- You can also write output files to `/mnt/uploads/` so the user can access and download them from the file browser.
+- The file system persists across code executions within the same session.
+- Use `import os; os.listdir('/mnt/uploads')` to discover available files."""
####################################
diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py
index 328c49b6c8..4c27e811cb 100644
--- a/backend/open_webui/main.py
+++ b/backend/open_webui/main.py
@@ -2193,6 +2193,7 @@ async def get_app_config(request: Request):
"user_count": user_count,
"code": {
"engine": app.state.config.CODE_EXECUTION_ENGINE,
+ "interpreter_engine": app.state.config.CODE_INTERPRETER_ENGINE,
},
"audio": {
"tts": {
diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py
index 00de970ef4..4aa7c2f41e 100644
--- a/backend/open_webui/utils/middleware.py
+++ b/backend/open_webui/utils/middleware.py
@@ -1,3 +1,4 @@
+import copy
import time
import logging
import sys
@@ -119,6 +120,7 @@ from open_webui.config import (
DEFAULT_VOICE_MODE_PROMPT_TEMPLATE,
DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
DEFAULT_CODE_INTERPRETER_PROMPT,
+ CODE_INTERPRETER_PYODIDE_FS_PROMPT,
CODE_INTERPRETER_BLOCKED_MODULES,
)
from open_webui.env import (
@@ -2352,18 +2354,36 @@ async def process_chat_payload(request, form_data, user, metadata, model):
)
if "code_interpreter" in features and features["code_interpreter"]:
+ engine = getattr(
+ request.app.state.config, "CODE_INTERPRETER_ENGINE", "pyodide"
+ )
+
# Skip XML-tag prompt injection when native FC is enabled —
# execute_code will be injected as a builtin tool instead
if metadata.get("params", {}).get("function_calling") != "native":
+ prompt = (
+ request.app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE
+ if request.app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE
+ != ""
+ else DEFAULT_CODE_INTERPRETER_PROMPT
+ )
+
+ # Append filesystem awareness only for pyodide engine
+ if engine != "jupyter":
+ prompt += CODE_INTERPRETER_PYODIDE_FS_PROMPT
+
form_data["messages"] = add_or_update_user_message(
- (
- request.app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE
- if request.app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE
- != ""
- else DEFAULT_CODE_INTERPRETER_PROMPT
- ),
+ prompt,
form_data["messages"],
)
+ else:
+ # Native FC: tool docstring can't be dynamic, so inject
+ # filesystem context into messages for pyodide engine
+ if engine != "jupyter":
+ form_data["messages"] = add_or_update_user_message(
+ CODE_INTERPRETER_PYODIDE_FS_PROMPT,
+ form_data["messages"],
+ )
tool_ids = form_data.pop("tool_ids", None)
terminal_id = form_data.pop("terminal_id", None)
diff --git a/src/lib/components/admin/Settings/CodeExecution.svelte b/src/lib/components/admin/Settings/CodeExecution.svelte
index 23388c0a45..cad7970de1 100644
--- a/src/lib/components/admin/Settings/CodeExecution.svelte
+++ b/src/lib/components/admin/Settings/CodeExecution.svelte
@@ -67,7 +67,7 @@
>
{#each engines as engine}
-
+
{/each}
@@ -193,7 +193,7 @@
>
{#each engines as engine}
-
+
{/each}
diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte
index b510dd0b65..b391cd1a51 100644
--- a/src/lib/components/chat/Chat.svelte
+++ b/src/lib/components/chat/Chat.svelte
@@ -148,6 +148,8 @@
let webSearchEnabled = false;
let codeInterpreterEnabled = false;
+
+
let showCommands = false;
let generating = false;
@@ -2924,6 +2926,7 @@
{stopResponse}
{showMessage}
{eventTarget}
+ {codeInterpreterEnabled}
/>
diff --git a/src/lib/components/chat/ChatControls.svelte b/src/lib/components/chat/ChatControls.svelte
index 60ec1aa27d..f5957e40e4 100644
--- a/src/lib/components/chat/ChatControls.svelte
+++ b/src/lib/components/chat/ChatControls.svelte
@@ -10,6 +10,7 @@
import { onDestroy, onMount, tick, getContext } from 'svelte';
import {
+ config,
terminalServers,
mobile,
showControls,
@@ -31,6 +32,7 @@
import Artifacts from './Artifacts.svelte';
import Embeds from './ChatControls/Embeds.svelte';
import FileNav from './FileNav.svelte';
+ import PyodideFileNav from './PyodideFileNav.svelte';
import Overview from './Overview.svelte';
const i18n = getContext('i18n');
@@ -50,6 +52,8 @@
export let files;
export let modelId;
+ export let codeInterpreterEnabled = false;
+
export let pane: Pane | null = null;
let largeScreen = false;
@@ -67,7 +71,7 @@
$: hasMessages = history?.messages && Object.keys(history.messages).length > 0;
$: showControlsTab = $user?.role === 'admin' || ($user?.permissions?.chat?.controls ?? true);
- $: showFilesTab = !!$selectedTerminalId;
+ $: showFilesTab = !!$selectedTerminalId || (codeInterpreterEnabled && $config?.code?.interpreter_engine !== 'jupyter');
$: showOverviewTab = hasMessages;
// Tab fallback: if active tab becomes hidden, switch to next available
@@ -355,6 +359,8 @@
/>
{:else if activeTab === 'files' && $selectedTerminalId}
+ {:else if activeTab === 'files' && codeInterpreterEnabled}
+
{:else}
{/if}
@@ -504,6 +510,8 @@
/>
{:else if activeTab === 'files' && $selectedTerminalId}
+ {:else if activeTab === 'files' && codeInterpreterEnabled}
+
{:else}
{/if}
diff --git a/src/lib/components/chat/FileNav/FileEntryRow.svelte b/src/lib/components/chat/FileNav/FileEntryRow.svelte
index b572b15255..eb217bcc9b 100644
--- a/src/lib/components/chat/FileNav/FileEntryRow.svelte
+++ b/src/lib/components/chat/FileNav/FileEntryRow.svelte
@@ -12,8 +12,8 @@
export let entry: FileEntry;
export let currentPath: string;
- export let terminalUrl: string;
- export let terminalKey: string;
+ export let terminalUrl: string = '';
+ export let terminalKey: string = '';
export let onOpen: (entry: FileEntry) => void = () => {};
export let onDownload: (path: string) => void = () => {};
diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte
index 2cbd0d9557..9bf3a08139 100644
--- a/src/lib/components/chat/MessageInput.svelte
+++ b/src/lib/components/chat/MessageInput.svelte
@@ -508,11 +508,17 @@
let showCodeInterpreterButton = false;
$: showCodeInterpreterButton =
+ !$selectedTerminalId &&
(atSelectedModel?.id ? [atSelectedModel.id] : selectedModels).length ===
codeInterpreterCapableModels.length &&
$config?.features?.enable_code_interpreter &&
($_user.role === 'admin' || $_user?.permissions?.features?.code_interpreter);
+ // Disable code interpreter when terminal is active (mutually exclusive)
+ $: if ($selectedTerminalId && codeInterpreterEnabled) {
+ codeInterpreterEnabled = false;
+ }
+
const scrollToBottom = () => {
const element = document.getElementById('messages-container');
element.scrollTo({
diff --git a/src/lib/components/chat/PyodideFileNav.svelte b/src/lib/components/chat/PyodideFileNav.svelte
new file mode 100644
index 0000000000..45e9693207
--- /dev/null
+++ b/src/lib/components/chat/PyodideFileNav.svelte
@@ -0,0 +1,367 @@
+
+
+
+
+
+
+