mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🐛 fix: address Codex review findings for stateful code sessions
- OpenAI-compatible service (packages/api/src/agents/openai/service.ts) now derives and passes statefulSessionsAvailable alongside codeEnvAvailable, so the feature activates on that route (previously statefulCodeSessions resolved false there and createRun never sent toolExecution.sandbox). - Thread runtime_session_hint through the host file-authoring tools (create_file/edit_file/read_file): those host branches return before the generic tool path, so readSandboxFile/writeSandboxFile now forward the per-conversation hint instead of falling back to the Code API default session. - StatefulSessions builder toggle clears its form value when Code Interpreter is disabled, so a saved agent matches the disabled UI and re-enabling code doesn't silently reactivate stateful sessions.
This commit is contained in:
parent
45c1cd74c6
commit
c0919bc726
4 changed files with 53 additions and 2 deletions
|
|
@ -990,7 +990,7 @@ const primeFiles = async (options) => {
|
|||
* @param {ServerRequest} [params.req] - Current authenticated request, used to mint Code API auth.
|
||||
* @returns {Promise<{content: string} | null>}
|
||||
*/
|
||||
async function readSandboxFile({ file_path, session_id, files, req }) {
|
||||
async function readSandboxFile({ file_path, session_id, files, runtime_session_hint, req }) {
|
||||
const baseURL = getCodeBaseURL();
|
||||
if (!baseURL) {
|
||||
return null;
|
||||
|
|
@ -1006,6 +1006,9 @@ async function readSandboxFile({ file_path, session_id, files, req }) {
|
|||
if (session_id) {
|
||||
postData.session_id = session_id;
|
||||
}
|
||||
if (runtime_session_hint) {
|
||||
postData.runtime_session_hint = runtime_session_hint;
|
||||
}
|
||||
if (files && files.length > 0) {
|
||||
postData.files = files;
|
||||
}
|
||||
|
|
@ -1056,7 +1059,14 @@ async function readSandboxFile({ file_path, session_id, files, req }) {
|
|||
* @param {ServerRequest} [params.req] - Current authenticated request, used to mint Code API auth.
|
||||
* @returns {Promise<{stdout?: string, stderr?: string, session_id?: string, files?: Array<Object>} | null>}
|
||||
*/
|
||||
async function writeSandboxFile({ file_path, content, session_id, files, req }) {
|
||||
async function writeSandboxFile({
|
||||
file_path,
|
||||
content,
|
||||
session_id,
|
||||
files,
|
||||
runtime_session_hint,
|
||||
req,
|
||||
}) {
|
||||
const baseURL = getCodeBaseURL();
|
||||
if (!baseURL) {
|
||||
return null;
|
||||
|
|
@ -1090,6 +1100,9 @@ async function writeSandboxFile({ file_path, content, session_id, files, req })
|
|||
if (session_id) {
|
||||
postData.session_id = session_id;
|
||||
}
|
||||
if (runtime_session_hint) {
|
||||
postData.runtime_session_hint = runtime_session_hint;
|
||||
}
|
||||
if (files && files.length > 0) {
|
||||
postData.files = files;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { AgentCapabilities } from 'librechat-data-provider';
|
||||
import {
|
||||
|
|
@ -20,6 +21,15 @@ export default function StatefulSessions() {
|
|||
const enabled = watch(AgentCapabilities.stateful_code_sessions) ?? false;
|
||||
const codeEnabled = watch(AgentCapabilities.execute_code);
|
||||
|
||||
/** Clear the persisted opt-in when Code Interpreter is turned off so the saved
|
||||
* agent matches the disabled UI — otherwise `composeAgentUpdatePayload` keeps
|
||||
* `stateful_code_sessions: true` and re-enabling code silently reactivates it. */
|
||||
useEffect(() => {
|
||||
if (codeEnabled !== true && enabled) {
|
||||
setValue(AgentCapabilities.stateful_code_sessions, false, { shouldDirty: true });
|
||||
}
|
||||
}, [codeEnabled, enabled, setValue]);
|
||||
|
||||
const handleChange = (value: boolean) => {
|
||||
setValue(AgentCapabilities.stateful_code_sessions, value, { shouldDirty: true });
|
||||
};
|
||||
|
|
|
|||
|
|
@ -232,6 +232,10 @@ export interface ToolExecuteOptions {
|
|||
file_path: string;
|
||||
session_id?: string;
|
||||
files?: Array<{ id: string; name: string; session_id?: string; storage_session_id?: string }>;
|
||||
/** Per-conversation stateful runtime-session hint (thread_id); forwarded so a
|
||||
* host file op that is the first sandbox call joins the same runtime session
|
||||
* as bash_tool instead of the Code API's default session. */
|
||||
runtime_session_hint?: string;
|
||||
req?: ServerRequest;
|
||||
}) => Promise<{ content: string } | null>;
|
||||
/**
|
||||
|
|
@ -245,6 +249,8 @@ export interface ToolExecuteOptions {
|
|||
content: string;
|
||||
session_id?: string;
|
||||
files?: Array<{ id: string; name: string; session_id?: string; storage_session_id?: string }>;
|
||||
/** @see readSandboxFile.runtime_session_hint */
|
||||
runtime_session_hint?: string;
|
||||
req?: ServerRequest;
|
||||
}) => Promise<{
|
||||
stdout?: string;
|
||||
|
|
@ -1263,6 +1269,7 @@ async function handleSandboxFileFallback(
|
|||
file_path: filePath,
|
||||
session_id: ctx?.session_id,
|
||||
files: ctx?.files,
|
||||
...(tc.runtimeSessionHint ? { runtime_session_hint: tc.runtimeSessionHint } : {}),
|
||||
...(req ? { req } : {}),
|
||||
});
|
||||
if (!result || result.content == null) {
|
||||
|
|
@ -1431,6 +1438,7 @@ async function loadSandboxTextForAuthoring({
|
|||
file_path: filePath,
|
||||
session_id: ctx?.session_id,
|
||||
files: ctx?.files,
|
||||
...(tc.runtimeSessionHint ? { runtime_session_hint: tc.runtimeSessionHint } : {}),
|
||||
...(req ? { req } : {}),
|
||||
});
|
||||
if (!result || result.content == null) {
|
||||
|
|
@ -1505,6 +1513,7 @@ async function writeSandboxTextForAuthoring({
|
|||
content,
|
||||
session_id: ctx?.session_id,
|
||||
files: ctx?.files,
|
||||
...(tc.runtimeSessionHint ? { runtime_session_hint: tc.runtimeSessionHint } : {}),
|
||||
...(req ? { req } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -146,6 +146,13 @@ interface InitializeAgentParams {
|
|||
* skips the expansion (same semantics as the in-repo controllers).
|
||||
*/
|
||||
codeEnvAvailable?: boolean;
|
||||
/**
|
||||
* Whether the admin-level `stateful_code_sessions` capability is enabled.
|
||||
* Threaded to `initializeAgent` alongside `codeEnvAvailable` so this
|
||||
* OpenAI-compatible route resolves stateful sessions identically to the
|
||||
* in-repo controllers; absent / `undefined` disables the feature.
|
||||
*/
|
||||
statefulSessionsAvailable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -444,6 +451,17 @@ export async function createAgentChatCompletion(
|
|||
AgentCapabilities.execute_code,
|
||||
)
|
||||
: undefined;
|
||||
/** Mirror `codeEnvAvailable` for the stateful-session gate so an agent with
|
||||
* `execute_code`, the app `stateful_code_sessions` capability, and its own
|
||||
* builder opt-in resolves stateful sessions on this route too — otherwise
|
||||
* `statefulCodeSessions` stays false and `createRun` never sends
|
||||
* `toolExecution.sandbox`. */
|
||||
const statefulSessionsAvailable =
|
||||
agentsConfig != null && typeof agentsConfig === 'object'
|
||||
? ((agentsConfig as { capabilities?: string[] }).capabilities ?? []).includes(
|
||||
AgentCapabilities.stateful_code_sessions,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
// Initialize the agent first to check for disableStreaming
|
||||
const initializedAgent = await deps.initializeAgent({
|
||||
|
|
@ -460,6 +478,7 @@ export async function createAgentChatCompletion(
|
|||
allowedProviders,
|
||||
isInitialAgent: true,
|
||||
codeEnvAvailable,
|
||||
statefulSessionsAvailable,
|
||||
});
|
||||
|
||||
// Determine if streaming is enabled (check both request and agent config)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue