feat: stateful_code_sessions capability for warm Code API sandbox sessions

Wire the @librechat/agents stateful sandbox sub-config behind a new,
off-by-default stateful_code_sessions agent capability. createRun sets
toolExecution.sandbox.statefulSessions when code execution is active in the
run AND the capability is enabled; execute_code and bash_tool factories get
the param so their descriptions hedge toward persistence. Rides the existing
variable-not-literal runConfig pattern, so it no-ops until @librechat/agents
is bumped to the version shipping the sandbox sub-config.
This commit is contained in:
Danny Avila 2026-07-07 07:38:06 -04:00
parent 73c43ded25
commit 19d736811e
5 changed files with 86 additions and 1 deletions

View file

@ -22,6 +22,7 @@ const {
Permissions,
EToolResources,
PermissionTypes,
AgentCapabilities,
} = require('librechat-data-provider');
const {
availableTools,
@ -51,7 +52,7 @@ const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSe
const { primeFiles: primeCodeFiles } = require('~/server/services/Files/Code/process');
const { getUserPluginAuthValue } = require('~/server/services/PluginService');
const { loadAuthValues } = require('~/server/services/Tools/credentials');
const { getMCPServerTools } = require('~/server/services/Config');
const { getMCPServerTools, checkCapability } = require('~/server/services/Config');
const { getMCPServersRegistry } = require('~/config');
const { getRoleByName, setMemory, deleteMemory, getFormattedMemories } = require('~/models');
@ -301,10 +302,19 @@ const loadTools = async ({
if (files?.length) {
primedCodeFiles = files;
}
/* Hedge the execute_code description toward persistence only when the
* admin `stateful_code_sessions` capability is on (off by default); the
* matching wire hint is set in the run config. Older @librechat/agents
* ignore the param. */
const statefulSessions = await checkCapability(
options.req,
AgentCapabilities.stateful_code_sessions,
);
return createCodeExecutionTool({
user_id: user,
files,
authHeaders: () => getCodeApiAuthHeaders(options.req),
statefulSessions,
});
};
continue;

View file

@ -1483,6 +1483,16 @@ async function loadToolsForExecution({
enabledCapabilities?.has(AgentCapabilities.execute_code) === true &&
agent?.tools?.includes(Tools.execute_code) === true;
/**
* Opt bash_tool into the hedged stateful-session description. Gated on code
* execution being enabled AND the admin `stateful_code_sessions` capability;
* off by default. Sets prompt text only (the wire hint is set at run config).
* PTC keeps its stateless prompt in v1. Older @librechat/agents ignore the param.
*/
const statefulCodeSessions =
codeExecutionEnabled &&
enabledCapabilities?.has(AgentCapabilities.stateful_code_sessions) === true;
const isPTC =
isPTCRequested &&
enabledCapabilities.has(AgentCapabilities.programmatic_tools) &&
@ -1534,6 +1544,7 @@ async function loadToolsForExecution({
try {
const bashTool = createBashExecutionTool({
authHeaders: () => getCodeApiAuthHeaders(req),
statefulSessions: statefulCodeSessions,
});
allLoadedTools.push(bashTool);
} catch (error) {

View file

@ -3,6 +3,7 @@ import { Run, Providers, Constants } from '@librechat/agents';
import {
KnownEndpoints,
EModelEndpoint,
AgentCapabilities,
MAX_SUBAGENT_DEPTH,
MAX_SUBAGENT_RUN_CONFIGS,
extractEnvVariable,
@ -778,6 +779,26 @@ function isAskUserQuestionAdminDisabled(appConfig?: AppConfig): boolean {
return appConfig?.filteredTools?.includes(ASK_USER_QUESTION_TOOL_NAME) === true;
}
/**
* Whether this run opts its remote sandbox tools into best-effort stateful
* runtime sessions (a warm per-session MicroVM workspace on the Code API).
* Requires BOTH code execution active in the run (`codeEnvActive`, i.e. some
* reachable agent has execute_code/bash) AND the admin `stateful_code_sessions`
* capability a stateful sandbox session is meaningless without code
* execution. Off by default: the capability is absent from
* `defaultAgentCapabilities`, and the SDK derives the session hint from
* `thread_id` (= conversationId), so this is never a trust boundary.
*/
export function resolveStatefulCodeSessions(
codeEnvActive: boolean,
agentsEndpointConfig: { capabilities?: AgentCapabilities[] } | undefined,
): boolean {
if (!codeEnvActive) {
return false;
}
return new Set(agentsEndpointConfig?.capabilities).has(AgentCapabilities.stateful_code_sessions);
}
/**
* Whether any agent reachable in the run primary, handoff/parallel, or a
* nested subagent opts into cross-turn `reasoning_content` reconstruction.
@ -1282,6 +1303,12 @@ export async function createRun({
* and the resume route). When disabled, nothing attaches and the run is identical
* to before this feature shipped.
*/
// `enableToolOutputReferences` (bash present anywhere in the run) doubles as
// the "code execution active" signal for the stateful-session gate.
const statefulCodeSessions = resolveStatefulCodeSessions(
enableToolOutputReferences,
agentsEndpointConfig,
);
// Resolve the effective policy through the single seam so per-agent / per-skill
// sources can layer in later without touching this call site (see
// `resolveToolApprovalPolicy`). Only the endpoint layer is wired today, so this
@ -1378,6 +1405,15 @@ export async function createRun({
...(enableToolOutputReferences && {
toolOutputReferences: { enabled: true },
}),
// Best-effort stateful runtime sessions on the remote Code API. The SDK
// stamps a per-conversation session hint on execute_code/bash requests and
// hedges those tools' descriptions; the transport is otherwise unchanged.
// `engine` is omitted (defaults to `sandbox`) and the hint defaults to
// thread_id. Requires @librechat/agents with `toolExecution.sandbox`;
// older versions ignore the field.
...(statefulCodeSessions && {
toolExecution: { sandbox: { statefulSessions: true } },
}),
// HITL opt-in: the `humanInTheLoop` switch + the PreToolUse policy hook. Spread
// here (not just `compileOptions.checkpointer` above) so an `ask` decision raises
// a real interrupt — without these the run would never pause. Absent when disabled.

View file

@ -0,0 +1,27 @@
import { AgentCapabilities } from 'librechat-data-provider';
import { resolveStatefulCodeSessions } from './run';
describe('resolveStatefulCodeSessions', () => {
const withCap = {
capabilities: [AgentCapabilities.execute_code, AgentCapabilities.stateful_code_sessions],
};
const withoutCap = { capabilities: [AgentCapabilities.execute_code] };
it('enables only when code execution is active AND the capability is present', () => {
expect(resolveStatefulCodeSessions(true, withCap)).toBe(true);
});
it('stays off when the capability is absent, even with code execution active', () => {
expect(resolveStatefulCodeSessions(true, withoutCap)).toBe(false);
});
it('stays off when code execution is inactive, even with the capability present', () => {
expect(resolveStatefulCodeSessions(false, withCap)).toBe(false);
});
it('stays off (default) when the endpoint config or capabilities are missing', () => {
expect(resolveStatefulCodeSessions(true, undefined)).toBe(false);
expect(resolveStatefulCodeSessions(true, {})).toBe(false);
expect(resolveStatefulCodeSessions(true, { capabilities: [] })).toBe(false);
});
});

View file

@ -565,6 +565,7 @@ export enum AgentCapabilities {
end_after_tools = 'end_after_tools',
deferred_tools = 'deferred_tools',
execute_code = 'execute_code',
stateful_code_sessions = 'stateful_code_sessions',
file_search = 'file_search',
web_search = 'web_search',
artifacts = 'artifacts',