refac
This commit is contained in:
@@ -105,6 +105,10 @@ async def proxy_terminal(
|
|||||||
target_url += f'?{request.query_params}'
|
target_url += f'?{request.query_params}'
|
||||||
|
|
||||||
headers = {'X-User-Id': user.id}
|
headers = {'X-User-Id': user.id}
|
||||||
|
# Forward per-session cwd tracking header
|
||||||
|
session_id = request.headers.get('x-session-id')
|
||||||
|
if session_id:
|
||||||
|
headers['X-Session-Id'] = session_id
|
||||||
cookies = {}
|
cookies = {}
|
||||||
auth_type = connection.get('auth_type', 'bearer')
|
auth_type = connection.get('auth_type', 'bearer')
|
||||||
|
|
||||||
|
|||||||
@@ -1009,6 +1009,13 @@ async def get_terminal_tools(
|
|||||||
# auth_type == "none": no Authorization header
|
# auth_type == "none": no Authorization header
|
||||||
|
|
||||||
system_prompt = server_data.get('system_prompt')
|
system_prompt = server_data.get('system_prompt')
|
||||||
|
|
||||||
|
# Use chat_id as the per-session key for cwd tracking
|
||||||
|
metadata = extra_params.get('__metadata__', {})
|
||||||
|
session_id = metadata.get('chat_id')
|
||||||
|
if session_id:
|
||||||
|
headers['X-Session-Id'] = session_id
|
||||||
|
|
||||||
terminal_cwd = await get_terminal_cwd(connection.get('url', ''), headers, cookies)
|
terminal_cwd = await get_terminal_cwd(connection.get('url', ''), headers, cookies)
|
||||||
|
|
||||||
tools_dict = {}
|
tools_dict = {}
|
||||||
|
|||||||
@@ -454,7 +454,8 @@ export const executeToolServer = async (
|
|||||||
url: string,
|
url: string,
|
||||||
name: string,
|
name: string,
|
||||||
params: Record<string, any>,
|
params: Record<string, any>,
|
||||||
serverData: { openapi: any; info: any; specs: any }
|
serverData: { openapi: any; info: any; specs: any },
|
||||||
|
sessionId?: string
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
@@ -531,6 +532,7 @@ export const executeToolServer = async (
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
...(token && { authorization: `Bearer ${token}` })
|
...(token && { authorization: `Bearer ${token}` })
|
||||||
};
|
};
|
||||||
|
if (sessionId) headers['X-Session-Id'] = sessionId;
|
||||||
|
|
||||||
const requestOptions: RequestInit = {
|
const requestOptions: RequestInit = {
|
||||||
method: httpMethod.toUpperCase(),
|
method: httpMethod.toUpperCase(),
|
||||||
|
|||||||
@@ -45,11 +45,11 @@ export const getTerminalConfig = async (
|
|||||||
return res.json().catch(() => null);
|
return res.json().catch(() => null);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCwd = async (baseUrl: string, apiKey: string): Promise<string | null> => {
|
export const getCwd = async (baseUrl: string, apiKey: string, sessionId?: string): Promise<string | null> => {
|
||||||
const url = `${baseUrl.replace(/\/$/, '')}/files/cwd`;
|
const url = `${baseUrl.replace(/\/$/, '')}/files/cwd`;
|
||||||
const res = await fetch(url, {
|
const headers: Record<string, string> = { Authorization: `Bearer ${apiKey}` };
|
||||||
headers: { Authorization: `Bearer ${apiKey}` }
|
if (sessionId) headers['X-Session-Id'] = sessionId;
|
||||||
}).catch(() => null);
|
const res = await fetch(url, { headers }).catch(() => null);
|
||||||
if (!res || !res.ok) return null;
|
if (!res || !res.ok) return null;
|
||||||
const json = await res.json().catch(() => null);
|
const json = await res.json().catch(() => null);
|
||||||
return json?.cwd ?? null;
|
return json?.cwd ?? null;
|
||||||
@@ -218,15 +218,18 @@ export const deleteEntry = async (
|
|||||||
export const setCwd = async (
|
export const setCwd = async (
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
path: string
|
path: string,
|
||||||
|
sessionId?: string
|
||||||
): Promise<{ cwd: string } | null> => {
|
): Promise<{ cwd: string } | null> => {
|
||||||
const url = `${baseUrl.replace(/\/$/, '')}/files/cwd`;
|
const url = `${baseUrl.replace(/\/$/, '')}/files/cwd`;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
if (sessionId) headers['X-Session-Id'] = sessionId;
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers,
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ path })
|
body: JSON.stringify({ path })
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
|
|||||||
@@ -362,7 +362,7 @@
|
|||||||
onClose={() => showControls.set(false)}
|
onClose={() => showControls.set(false)}
|
||||||
/>
|
/>
|
||||||
{:else if activeTab === 'files' && $selectedTerminalId}
|
{:else if activeTab === 'files' && $selectedTerminalId}
|
||||||
<FileNav onAttach={handleTerminalAttach} />
|
<FileNav onAttach={handleTerminalAttach} {chatId} />
|
||||||
{:else if activeTab === 'files' && codeInterpreterEnabled}
|
{:else if activeTab === 'files' && codeInterpreterEnabled}
|
||||||
<PyodideFileNav />
|
<PyodideFileNav />
|
||||||
{:else}
|
{:else}
|
||||||
@@ -513,7 +513,7 @@
|
|||||||
onClose={() => showControls.set(false)}
|
onClose={() => showControls.set(false)}
|
||||||
/>
|
/>
|
||||||
{:else if activeTab === 'files' && $selectedTerminalId}
|
{:else if activeTab === 'files' && $selectedTerminalId}
|
||||||
<FileNav onAttach={handleTerminalAttach} overlay={dragged} />
|
<FileNav onAttach={handleTerminalAttach} overlay={dragged} {chatId} />
|
||||||
{:else if activeTab === 'files' && codeInterpreterEnabled}
|
{:else if activeTab === 'files' && codeInterpreterEnabled}
|
||||||
<PyodideFileNav overlay={dragged} />
|
<PyodideFileNav overlay={dragged} />
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
|
|
||||||
export let onAttach: ((blob: Blob, name: string, contentType: string) => void) | null = null;
|
export let onAttach: ((blob: Blob, name: string, contentType: string) => void) | null = null;
|
||||||
export let overlay = false;
|
export let overlay = false;
|
||||||
|
export let chatId: string | null = null;
|
||||||
|
|
||||||
// ── Terminal panel state ────────────────────────────────────────────
|
// ── Terminal panel state ────────────────────────────────────────────
|
||||||
let terminalExpanded = false;
|
let terminalExpanded = false;
|
||||||
@@ -215,30 +216,48 @@
|
|||||||
return url ? { url, key } : null;
|
return url ? { url, key } : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Detect terminal changes — the explicit store references ensure
|
// Detect terminal or chat changes — the explicit store references ensure
|
||||||
// Svelte re-runs this block when any of them update.
|
// Svelte re-runs this block when any of them update.
|
||||||
|
// The `mounted` flag prevents the initial run from racing with onMount.
|
||||||
let prevTerminalUrl = '';
|
let prevTerminalUrl = '';
|
||||||
|
let prevChatId = chatId;
|
||||||
|
let mounted = false;
|
||||||
$: {
|
$: {
|
||||||
($selectedTerminalId, $terminalServers, $settings);
|
($selectedTerminalId, $terminalServers, $settings);
|
||||||
const terminal = getTerminal();
|
const terminal = getTerminal();
|
||||||
selectedTerminal = terminal;
|
selectedTerminal = terminal;
|
||||||
|
|
||||||
if (terminal && terminal.url !== prevTerminalUrl) {
|
const chatChanged = chatId !== prevChatId;
|
||||||
prevTerminalUrl = terminal.url;
|
const oldChatId = prevChatId;
|
||||||
loading = true;
|
if (chatChanged) prevChatId = chatId;
|
||||||
error = null;
|
|
||||||
entries = [];
|
|
||||||
(async () => {
|
|
||||||
// Discover server features (terminal enabled/disabled)
|
|
||||||
const config = await getTerminalConfig(terminal.url, terminal.key);
|
|
||||||
terminalEnabled = config?.features?.terminal !== false;
|
|
||||||
|
|
||||||
const rawCwd = await getCwd(terminal.url, terminal.key);
|
const terminalChanged = terminal && terminal.url !== prevTerminalUrl;
|
||||||
const cwd = rawCwd ? normalizePath(rawCwd) : null;
|
if (terminalChanged) prevTerminalUrl = terminal.url;
|
||||||
const dir = cwd ? (cwd.endsWith('/') ? cwd : cwd + '/') : '/';
|
|
||||||
savedPath = dir;
|
if (mounted && terminal) {
|
||||||
loadDir(dir);
|
if (chatChanged && chatId && !oldChatId) {
|
||||||
})();
|
// Chat just got created (null → real ID): persist the current
|
||||||
|
// browsed path as the new session's cwd — don't re-fetch.
|
||||||
|
setCwd(terminal.url, terminal.key, savedPath, chatId);
|
||||||
|
} else if (terminalChanged || chatChanged) {
|
||||||
|
// Terminal switched, new chat started, or switched between
|
||||||
|
// existing chats — re-fetch the session cwd.
|
||||||
|
loading = true;
|
||||||
|
error = null;
|
||||||
|
entries = [];
|
||||||
|
(async () => {
|
||||||
|
if (terminalChanged) {
|
||||||
|
const config = await getTerminalConfig(terminal.url, terminal.key);
|
||||||
|
terminalEnabled = config?.features?.terminal !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawCwd = await getCwd(terminal.url, terminal.key, chatId ?? undefined);
|
||||||
|
const cwd = rawCwd ? normalizePath(rawCwd) : null;
|
||||||
|
const dir = cwd ? (cwd.endsWith('/') ? cwd : cwd + '/') : '/';
|
||||||
|
savedPath = dir;
|
||||||
|
loadDir(dir);
|
||||||
|
})();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +335,7 @@
|
|||||||
loading = false;
|
loading = false;
|
||||||
|
|
||||||
// Set working directory on the terminal server (fire-and-forget)
|
// Set working directory on the terminal server (fire-and-forget)
|
||||||
setCwd(terminal.url, terminal.key, path);
|
setCwd(terminal.url, terminal.key, path, chatId ?? undefined);
|
||||||
|
|
||||||
if (result === null) {
|
if (result === null) {
|
||||||
error =
|
error =
|
||||||
@@ -736,14 +755,22 @@
|
|||||||
|
|
||||||
if (!handledDisplayFile) {
|
if (!handledDisplayFile) {
|
||||||
loading = true;
|
loading = true;
|
||||||
if (savedPath === '/') {
|
|
||||||
const rawCwd = await getCwd(terminal.url, terminal.key);
|
// Discover server features on initial mount
|
||||||
|
const config = await getTerminalConfig(terminal.url, terminal.key);
|
||||||
|
terminalEnabled = config?.features?.terminal !== false;
|
||||||
|
|
||||||
|
if (chatId || savedPath === '/') {
|
||||||
|
// Fetch session-specific cwd from the server (or global default for new chats)
|
||||||
|
const rawCwd = await getCwd(terminal.url, terminal.key, chatId ?? undefined);
|
||||||
const cwd = rawCwd ? normalizePath(rawCwd) : null;
|
const cwd = rawCwd ? normalizePath(rawCwd) : null;
|
||||||
if (cwd) savedPath = cwd.endsWith('/') ? cwd : cwd + '/';
|
if (cwd) savedPath = cwd.endsWith('/') ? cwd : cwd + '/';
|
||||||
}
|
}
|
||||||
loadDir(savedPath);
|
loadDir(savedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mounted = true;
|
||||||
|
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Shift') shiftKey = true;
|
if (e.key === 'Shift') shiftKey = true;
|
||||||
};
|
};
|
||||||
@@ -1368,6 +1395,7 @@
|
|||||||
overlay={overlay || isDraggingHandle}
|
overlay={overlay || isDraggingHandle}
|
||||||
bind:connected={terminalConnected}
|
bind:connected={terminalConnected}
|
||||||
bind:connecting={terminalConnecting}
|
bind:connecting={terminalConnecting}
|
||||||
|
{chatId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
export let overlay = false;
|
export let overlay = false;
|
||||||
|
export let chatId: string | null = null;
|
||||||
|
|
||||||
let terminalEl: HTMLDivElement;
|
let terminalEl: HTMLDivElement;
|
||||||
let term: Terminal | null = null;
|
let term: Terminal | null = null;
|
||||||
@@ -67,9 +68,11 @@
|
|||||||
authToken = apiKey;
|
authToken = apiKey;
|
||||||
|
|
||||||
// Create session
|
// Create session
|
||||||
|
const createHeaders: Record<string, string> = { Authorization: `Bearer ${apiKey}` };
|
||||||
|
if (chatId) createHeaders['X-Session-Id'] = chatId;
|
||||||
const res = await fetch(`${base}/api/terminals`, {
|
const res = await fetch(`${base}/api/terminals`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { Authorization: `Bearer ${apiKey}` }
|
headers: createHeaders
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`Failed to create session: ${res.status}`);
|
if (!res.ok) throw new Error(`Failed to create session: ${res.status}`);
|
||||||
const session = await res.json();
|
const session = await res.json();
|
||||||
@@ -83,9 +86,11 @@
|
|||||||
authToken = token;
|
authToken = token;
|
||||||
|
|
||||||
// Create session via proxy
|
// Create session via proxy
|
||||||
|
const proxyHeaders: Record<string, string> = { Authorization: `Bearer ${token}` };
|
||||||
|
if (chatId) proxyHeaders['X-Session-Id'] = chatId;
|
||||||
const res = await fetch(`${base}/terminals/${info.serverId}/api/terminals`, {
|
const res = await fetch(`${base}/terminals/${info.serverId}/api/terminals`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: proxyHeaders
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`Failed to create session: ${res.status}`);
|
if (!res.ok) throw new Error(`Failed to create session: ${res.status}`);
|
||||||
const session = await res.json();
|
const session = await res.json();
|
||||||
|
|||||||
@@ -375,7 +375,7 @@
|
|||||||
return { toolServer, toolServerData, token };
|
return { toolServer, toolServerData, token };
|
||||||
};
|
};
|
||||||
|
|
||||||
const executeTool = async (data, cb) => {
|
const executeTool = async (data, cb, chatId) => {
|
||||||
const { toolServer, toolServerData, token } = resolveToolServer(data.server?.url);
|
const { toolServer, toolServerData, token } = resolveToolServer(data.server?.url);
|
||||||
|
|
||||||
console.log('executeTool', data, toolServer);
|
console.log('executeTool', data, toolServer);
|
||||||
@@ -386,7 +386,8 @@
|
|||||||
toolServer.url,
|
toolServer.url,
|
||||||
data?.name,
|
data?.name,
|
||||||
data?.params,
|
data?.params,
|
||||||
toolServerData
|
toolServerData,
|
||||||
|
chatId
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('executeToolServer', res);
|
console.log('executeToolServer', res);
|
||||||
@@ -485,7 +486,7 @@
|
|||||||
executePythonAsWorker(data.id, data.code, cb, data.files || []);
|
executePythonAsWorker(data.id, data.code, cb, data.files || []);
|
||||||
} else if (type === 'execute:tool') {
|
} else if (type === 'execute:tool') {
|
||||||
console.log('execute:tool', data);
|
console.log('execute:tool', data);
|
||||||
executeTool(data, cb);
|
executeTool(data, cb, event.chat_id);
|
||||||
} else if (type === 'request:chat:completion') {
|
} else if (type === 'request:chat:completion') {
|
||||||
console.log(data, $socket.id);
|
console.log(data, $socket.id);
|
||||||
const { session_id, channel, form_data, model } = data;
|
const { session_id, channel, form_data, model } = data;
|
||||||
|
|||||||
Reference in New Issue
Block a user