@librechat/agents 3.2.58 → 3.2.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/main.cjs +7 -0
- package/dist/cjs/stream.cjs +2 -1
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +58 -9
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +4 -2
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +57 -7
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +9 -3
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +19 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/eagerEventExecution.cjs +18 -1
- package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -1
- package/dist/esm/main.mjs +3 -3
- package/dist/esm/stream.mjs +2 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +56 -10
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +4 -2
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +54 -8
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +9 -3
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +20 -2
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/eagerEventExecution.mjs +18 -2
- package/dist/esm/tools/eagerEventExecution.mjs.map +1 -1
- package/dist/types/tools/BashExecutor.d.ts +13 -0
- package/dist/types/tools/CodeExecutor.d.ts +14 -0
- package/dist/types/tools/ToolNode.d.ts +3 -0
- package/dist/types/tools/eagerEventExecution.d.ts +8 -0
- package/dist/types/types/tools.d.ts +58 -0
- package/package.json +1 -1
- package/src/stream.ts +17 -1
- package/src/tools/BashExecutor.ts +107 -14
- package/src/tools/BashProgrammaticToolCalling.ts +20 -1
- package/src/tools/CodeExecutor.ts +113 -9
- package/src/tools/ProgrammaticToolCalling.ts +27 -1
- package/src/tools/ToolNode.ts +36 -4
- package/src/tools/__tests__/BashExecutor.test.ts +39 -0
- package/src/tools/__tests__/CodeExecutor.stateful.test.ts +113 -0
- package/src/tools/__tests__/ToolNode.session.test.ts +86 -0
- package/src/tools/__tests__/eagerEventExecution.session.test.ts +92 -0
- package/src/tools/eagerEventExecution.ts +32 -5
- package/src/types/tools.ts +65 -0
|
@@ -22,6 +22,19 @@ function recordArgsEqual(left, right) {
|
|
|
22
22
|
function normalizeError(error) {
|
|
23
23
|
return error instanceof Error ? error : new Error(String(error));
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Stateful runtime session hint for the remote sandbox: only when
|
|
27
|
+
* `toolExecution.sandbox.statefulSessions` is on; explicit host hint else the
|
|
28
|
+
* conversation `thread_id`. Undefined disables the wire field. Shared by the
|
|
29
|
+
* direct ToolNode path and both event-driven planners so they stay in lockstep.
|
|
30
|
+
*/
|
|
31
|
+
function resolveRuntimeSessionHint(toolExecution, threadId) {
|
|
32
|
+
const sandbox = toolExecution?.sandbox;
|
|
33
|
+
if (sandbox?.statefulSessions !== true) return;
|
|
34
|
+
const explicit = sandbox.runtimeSessionHint;
|
|
35
|
+
if (explicit != null && explicit !== "") return explicit;
|
|
36
|
+
return threadId != null && threadId !== "" ? threadId : void 0;
|
|
37
|
+
}
|
|
25
38
|
function buildToolExecutionRequestPlan(args) {
|
|
26
39
|
const invalidArgsBehavior = args.invalidArgsBehavior ?? "abort";
|
|
27
40
|
const prepared = [];
|
|
@@ -36,6 +49,7 @@ function buildToolExecutionRequestPlan(args) {
|
|
|
36
49
|
args: {},
|
|
37
50
|
stepId: toolCall.stepId,
|
|
38
51
|
codeSessionContext: toolCall.codeSessionContext,
|
|
52
|
+
runtimeSessionHint: toolCall.runtimeSessionHint,
|
|
39
53
|
rejectedErrorMessage: "Invalid tool call arguments: expected a JSON object."
|
|
40
54
|
});
|
|
41
55
|
continue;
|
|
@@ -45,7 +59,8 @@ function buildToolExecutionRequestPlan(args) {
|
|
|
45
59
|
name: toolCall.name,
|
|
46
60
|
args: coercedArgs,
|
|
47
61
|
stepId: toolCall.stepId,
|
|
48
|
-
codeSessionContext: toolCall.codeSessionContext
|
|
62
|
+
codeSessionContext: toolCall.codeSessionContext,
|
|
63
|
+
runtimeSessionHint: toolCall.runtimeSessionHint
|
|
49
64
|
});
|
|
50
65
|
}
|
|
51
66
|
const nextUsageCount = new Map(args.usageCount);
|
|
@@ -60,6 +75,7 @@ function buildToolExecutionRequestPlan(args) {
|
|
|
60
75
|
turn
|
|
61
76
|
};
|
|
62
77
|
if (toolCall.codeSessionContext != null) request.codeSessionContext = toolCall.codeSessionContext;
|
|
78
|
+
if (toolCall.runtimeSessionHint != null && toolCall.runtimeSessionHint !== "") request.runtimeSessionHint = toolCall.runtimeSessionHint;
|
|
63
79
|
return request;
|
|
64
80
|
});
|
|
65
81
|
const requests = allRequests.filter((_, index) => prepared[index].rejectedErrorMessage == null);
|
|
@@ -81,6 +97,6 @@ function buildToolExecutionRequestPlan(args) {
|
|
|
81
97
|
};
|
|
82
98
|
}
|
|
83
99
|
//#endregion
|
|
84
|
-
export { buildToolExecutionRequestPlan, coerceRecordArgs, normalizeError, recordArgsEqual };
|
|
100
|
+
export { buildToolExecutionRequestPlan, coerceRecordArgs, normalizeError, recordArgsEqual, resolveRuntimeSessionHint };
|
|
85
101
|
|
|
86
102
|
//# sourceMappingURL=eagerEventExecution.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eagerEventExecution.mjs","names":[],"sources":["../../../src/tools/eagerEventExecution.ts"],"sourcesContent":["import type * as t from '@/types';\n\nexport function coerceRecordArgs(\n args: unknown\n): Record<string, unknown> | undefined {\n if (typeof args === 'string') {\n try {\n const parsed = JSON.parse(args) as unknown;\n return coerceRecordArgs(parsed);\n } catch {\n return undefined;\n }\n }\n\n if (args === null || typeof args !== 'object' || Array.isArray(args)) {\n return undefined;\n }\n\n return args as Record<string, unknown>;\n}\n\nexport function stableStringify(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map((item) => stableStringify(item)).join(',')}]`;\n }\n\n if (value !== null && typeof value === 'object') {\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(',')}}`;\n }\n\n return JSON.stringify(value);\n}\n\nexport function recordArgsEqual(\n left: Record<string, unknown>,\n right: Record<string, unknown>\n): boolean {\n return stableStringify(left) === stableStringify(right);\n}\n\nexport function normalizeError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nexport type ToolExecutionPlanCall = {\n id?: string;\n name: string;\n args: unknown;\n stepId?: string;\n codeSessionContext?: t.ToolCallRequest['codeSessionContext'];\n};\n\nexport type ToolExecutionRequestPlan = {\n allRequests: t.ToolCallRequest[];\n requests: t.ToolCallRequest[];\n rejectedResults: t.ToolExecuteResult[];\n};\n\nexport function buildToolExecutionRequestPlan(args: {\n toolCalls: ToolExecutionPlanCall[];\n usageCount: Map<string, number>;\n invalidArgsBehavior?: 'abort' | 'error-result';\n recordTurn?: (toolName: string, turn: number, callId: string) => void;\n}): ToolExecutionRequestPlan | undefined {\n const invalidArgsBehavior = args.invalidArgsBehavior ?? 'abort';\n const prepared: Array<{\n id: string;\n name: string;\n args: Record<string, unknown>;\n stepId?: string;\n codeSessionContext?: t.ToolCallRequest['codeSessionContext'];\n rejectedErrorMessage?: string;\n }> = [];\n\n for (const toolCall of args.toolCalls) {\n if (
|
|
1
|
+
{"version":3,"file":"eagerEventExecution.mjs","names":[],"sources":["../../../src/tools/eagerEventExecution.ts"],"sourcesContent":["import type * as t from '@/types';\n\nexport function coerceRecordArgs(\n args: unknown\n): Record<string, unknown> | undefined {\n if (typeof args === 'string') {\n try {\n const parsed = JSON.parse(args) as unknown;\n return coerceRecordArgs(parsed);\n } catch {\n return undefined;\n }\n }\n\n if (args === null || typeof args !== 'object' || Array.isArray(args)) {\n return undefined;\n }\n\n return args as Record<string, unknown>;\n}\n\nexport function stableStringify(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map((item) => stableStringify(item)).join(',')}]`;\n }\n\n if (value !== null && typeof value === 'object') {\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(',')}}`;\n }\n\n return JSON.stringify(value);\n}\n\nexport function recordArgsEqual(\n left: Record<string, unknown>,\n right: Record<string, unknown>\n): boolean {\n return stableStringify(left) === stableStringify(right);\n}\n\nexport function normalizeError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nexport type ToolExecutionPlanCall = {\n id?: string;\n name: string;\n args: unknown;\n stepId?: string;\n codeSessionContext?: t.ToolCallRequest['codeSessionContext'];\n runtimeSessionHint?: string;\n};\n\n/**\n * Stateful runtime session hint for the remote sandbox: only when\n * `toolExecution.sandbox.statefulSessions` is on; explicit host hint else the\n * conversation `thread_id`. Undefined disables the wire field. Shared by the\n * direct ToolNode path and both event-driven planners so they stay in lockstep.\n */\nexport function resolveRuntimeSessionHint(\n toolExecution: t.ToolExecutionConfig | undefined,\n threadId: string | undefined\n): string | undefined {\n const sandbox = toolExecution?.sandbox;\n if (sandbox?.statefulSessions !== true) {\n return undefined;\n }\n const explicit = sandbox.runtimeSessionHint;\n if (explicit != null && explicit !== '') {\n return explicit;\n }\n return threadId != null && threadId !== '' ? threadId : undefined;\n}\n\nexport type ToolExecutionRequestPlan = {\n allRequests: t.ToolCallRequest[];\n requests: t.ToolCallRequest[];\n rejectedResults: t.ToolExecuteResult[];\n};\n\nexport function buildToolExecutionRequestPlan(args: {\n toolCalls: ToolExecutionPlanCall[];\n usageCount: Map<string, number>;\n invalidArgsBehavior?: 'abort' | 'error-result';\n recordTurn?: (toolName: string, turn: number, callId: string) => void;\n}): ToolExecutionRequestPlan | undefined {\n const invalidArgsBehavior = args.invalidArgsBehavior ?? 'abort';\n const prepared: Array<{\n id: string;\n name: string;\n args: Record<string, unknown>;\n stepId?: string;\n codeSessionContext?: t.ToolCallRequest['codeSessionContext'];\n runtimeSessionHint?: string;\n rejectedErrorMessage?: string;\n }> = [];\n\n for (const toolCall of args.toolCalls) {\n if (toolCall.id == null || toolCall.id === '' || toolCall.name === '') {\n return undefined;\n }\n const coercedArgs = coerceRecordArgs(toolCall.args);\n if (coercedArgs == null) {\n if (invalidArgsBehavior === 'abort') {\n return undefined;\n }\n prepared.push({\n id: toolCall.id,\n name: toolCall.name,\n args: {},\n stepId: toolCall.stepId,\n codeSessionContext: toolCall.codeSessionContext,\n runtimeSessionHint: toolCall.runtimeSessionHint,\n rejectedErrorMessage:\n 'Invalid tool call arguments: expected a JSON object.',\n });\n continue;\n }\n prepared.push({\n id: toolCall.id,\n name: toolCall.name,\n args: coercedArgs,\n stepId: toolCall.stepId,\n codeSessionContext: toolCall.codeSessionContext,\n runtimeSessionHint: toolCall.runtimeSessionHint,\n });\n }\n\n const nextUsageCount = new Map(args.usageCount);\n const allRequests = prepared.map((toolCall): t.ToolCallRequest => {\n const turn = nextUsageCount.get(toolCall.name) ?? 0;\n nextUsageCount.set(toolCall.name, turn + 1);\n const request: t.ToolCallRequest = {\n id: toolCall.id,\n name: toolCall.name,\n args: toolCall.args,\n stepId: toolCall.stepId,\n turn,\n };\n if (toolCall.codeSessionContext != null) {\n request.codeSessionContext = toolCall.codeSessionContext;\n }\n if (\n toolCall.runtimeSessionHint != null &&\n toolCall.runtimeSessionHint !== ''\n ) {\n request.runtimeSessionHint = toolCall.runtimeSessionHint;\n }\n return request;\n });\n const requests = allRequests.filter(\n (_, index) => prepared[index].rejectedErrorMessage == null\n );\n const rejectedResults = prepared.flatMap((toolCall) => {\n if (toolCall.rejectedErrorMessage == null) {\n return [];\n }\n return [\n {\n toolCallId: toolCall.id,\n status: 'error' as const,\n content: '',\n errorMessage: toolCall.rejectedErrorMessage,\n },\n ];\n });\n\n for (const [toolName, count] of nextUsageCount) {\n args.usageCount.set(toolName, count);\n }\n for (const request of allRequests) {\n args.recordTurn?.(request.name, request.turn ?? 0, request.id);\n }\n\n return { allRequests, requests, rejectedResults };\n}\n"],"mappings":"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"}
|
|
@@ -19,6 +19,15 @@ export declare const BashExecutionToolSchema: {
|
|
|
19
19
|
readonly required: readonly ["command"];
|
|
20
20
|
};
|
|
21
21
|
export declare const BashExecutionToolDescription: string;
|
|
22
|
+
/**
|
|
23
|
+
* Bash statefulness is filesystem-tier: on a warm session the machine (files
|
|
24
|
+
* including /tmp, installed packages, background processes) persists between
|
|
25
|
+
* calls, but each call may start a fresh shell — so shell variables and cwd
|
|
26
|
+
* are NOT reliable, and the machine can be reset at any time. Only /mnt/data
|
|
27
|
+
* is durable.
|
|
28
|
+
*/
|
|
29
|
+
export declare const STATEFUL_BASH_NOTE = "Session state (best-effort): commands in this conversation usually run on the same machine, so files (including /tmp), installed packages, and running background processes from earlier calls typically persist. Each call may still start a fresh shell \u2014 do not rely on shell variables or the working directory carrying over \u2014 and the machine may be reset at any time. Only /mnt/data is durable.";
|
|
30
|
+
export declare const StatefulBashExecutionToolDescription: string;
|
|
22
31
|
/**
|
|
23
32
|
* Supplemental prompt documenting the tool-output reference feature.
|
|
24
33
|
*
|
|
@@ -39,7 +48,11 @@ export declare const BashToolOutputReferencesGuide: string;
|
|
|
39
48
|
*/
|
|
40
49
|
export declare function buildBashExecutionToolDescription(options?: {
|
|
41
50
|
enableToolOutputReferences?: boolean;
|
|
51
|
+
statefulSessions?: boolean;
|
|
42
52
|
}): string;
|
|
53
|
+
export declare function buildBashExecutionToolSchema(opts?: {
|
|
54
|
+
statefulSessions?: boolean;
|
|
55
|
+
}): typeof BashExecutionToolSchema;
|
|
43
56
|
export declare const BashExecutionToolName = Constants.BASH_TOOL;
|
|
44
57
|
/**
|
|
45
58
|
* Default bash tool definition using the base description.
|
|
@@ -38,6 +38,20 @@ export declare function buildCodeApiHttpErrorMessage(method: string, endpoint: s
|
|
|
38
38
|
text: () => Promise<string>;
|
|
39
39
|
}): Promise<string>;
|
|
40
40
|
export declare const CodeExecutionToolDescription: string;
|
|
41
|
+
/**
|
|
42
|
+
* Best-effort statefulness note. Deliberately hedged: warm reuse is an
|
|
43
|
+
* optimization, not a guarantee (the runtime may be reset on idle timeout,
|
|
44
|
+
* eviction, or the 8h VM lifetime), so the model must never depend on carried
|
|
45
|
+
* state for correctness and must persist anything durable to /mnt/data.
|
|
46
|
+
*/
|
|
47
|
+
export declare const STATEFUL_ENV_NOTE = "Session state (best-effort): consecutive executions in this conversation usually share one runtime, so variables, imports, and in-memory data from earlier successful calls are typically still available. The runtime may be reset at any time, so treat carried-over state as an optimization, never a guarantee. Anything that must survive MUST be written to /mnt/data. If a NameError/ImportError signals lost state, re-run the needed setup and continue.";
|
|
48
|
+
export declare const StatefulCodeExecutionToolDescription: string;
|
|
49
|
+
export declare function buildCodeExecutionToolDescription(opts?: {
|
|
50
|
+
statefulSessions?: boolean;
|
|
51
|
+
}): string;
|
|
52
|
+
export declare function buildCodeExecutionToolSchema(opts?: {
|
|
53
|
+
statefulSessions?: boolean;
|
|
54
|
+
}): typeof CodeExecutionToolSchema;
|
|
41
55
|
export declare const CodeExecutionToolName = Constants.EXECUTE_CODE;
|
|
42
56
|
export declare const CodeExecutionToolDefinition: {
|
|
43
57
|
readonly name: Constants.EXECUTE_CODE;
|
|
@@ -314,6 +314,9 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
314
314
|
* opt-in so only host-declared tools can influence the shared session.
|
|
315
315
|
*/
|
|
316
316
|
private participatesInCodeSession;
|
|
317
|
+
/** Delegates to the shared resolver so the direct and event-driven planning
|
|
318
|
+
* paths derive the runtime session hint identically. */
|
|
319
|
+
private resolveRuntimeSessionHint;
|
|
317
320
|
private storeCodeSessionFromResults;
|
|
318
321
|
/**
|
|
319
322
|
* Post-processes standard runTool outputs: dispatches ON_RUN_STEP_COMPLETED
|
|
@@ -9,7 +9,15 @@ export type ToolExecutionPlanCall = {
|
|
|
9
9
|
args: unknown;
|
|
10
10
|
stepId?: string;
|
|
11
11
|
codeSessionContext?: t.ToolCallRequest['codeSessionContext'];
|
|
12
|
+
runtimeSessionHint?: string;
|
|
12
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Stateful runtime session hint for the remote sandbox: only when
|
|
16
|
+
* `toolExecution.sandbox.statefulSessions` is on; explicit host hint else the
|
|
17
|
+
* conversation `thread_id`. Undefined disables the wire field. Shared by the
|
|
18
|
+
* direct ToolNode path and both event-driven planners so they stay in lockstep.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveRuntimeSessionHint(toolExecution: t.ToolExecutionConfig | undefined, threadId: string | undefined): string | undefined;
|
|
13
21
|
export type ToolExecutionRequestPlan = {
|
|
14
22
|
allRequests: t.ToolCallRequest[];
|
|
15
23
|
requests: t.ToolCallRequest[];
|
|
@@ -278,6 +278,15 @@ export type CodeExecutionToolParams = undefined | {
|
|
|
278
278
|
files?: CodeEnvFile[];
|
|
279
279
|
/** Optional host-supplied Code API auth headers. */
|
|
280
280
|
authHeaders?: CodeApiAuthHeaders;
|
|
281
|
+
/**
|
|
282
|
+
* Advertise best-effort stateful sessions in the tool description
|
|
283
|
+
* (variables/files may persist between calls, may reset). Prompt text
|
|
284
|
+
* only, and it must be set here because the description is bound to the
|
|
285
|
+
* LLM at construction time. Pair it with the run-scoped
|
|
286
|
+
* `toolExecution.sandbox.statefulSessions` gate, which drives the wire
|
|
287
|
+
* hint — set both from one flag so the prompt and the backend agree.
|
|
288
|
+
*/
|
|
289
|
+
statefulSessions?: boolean;
|
|
281
290
|
};
|
|
282
291
|
export type CodeApiAuthHeaderMap = Record<string, string>;
|
|
283
292
|
export type CodeApiAuthHeaders = CodeApiAuthHeaderMap | (() => CodeApiAuthHeaderMap | Promise<CodeApiAuthHeaderMap>);
|
|
@@ -326,6 +335,13 @@ export type ExecuteResult = {
|
|
|
326
335
|
stdout: string;
|
|
327
336
|
stderr: string;
|
|
328
337
|
files?: FileRefs;
|
|
338
|
+
/**
|
|
339
|
+
* Durable runtime session id echoed by a stateful Code API backend
|
|
340
|
+
* (hash of tenant+user+hint). Additive; absent on stateless servers.
|
|
341
|
+
*/
|
|
342
|
+
runtime_session_id?: string;
|
|
343
|
+
/** Whether this execution reused a warm runtime session or started fresh. */
|
|
344
|
+
runtime_status?: 'new' | 'reused';
|
|
329
345
|
};
|
|
330
346
|
/** JSON Schema type definition for tool parameters */
|
|
331
347
|
export type JsonSchemaType = {
|
|
@@ -381,6 +397,12 @@ export type ToolCallRequest = {
|
|
|
381
397
|
session_id: string;
|
|
382
398
|
files?: CodeEnvFile[];
|
|
383
399
|
};
|
|
400
|
+
/**
|
|
401
|
+
* Stable runtime session hint for stateful sandbox sessions. Orthogonal to
|
|
402
|
+
* `codeSessionContext` (which threads the transient exec-session for file
|
|
403
|
+
* continuity): the hint identifies the durable server-side runtime session.
|
|
404
|
+
*/
|
|
405
|
+
runtimeSessionHint?: string;
|
|
384
406
|
};
|
|
385
407
|
/** Batch request containing ALL tool calls for a graph step */
|
|
386
408
|
export type ToolExecuteBatchRequest = {
|
|
@@ -896,6 +918,31 @@ export type CloudflareSandboxExecutionConfig = {
|
|
|
896
918
|
/** Run a fast per-file syntax check after successful edits/writes. */
|
|
897
919
|
postEditSyntaxCheck?: LocalExecutionConfig['postEditSyntaxCheck'];
|
|
898
920
|
};
|
|
921
|
+
export type SandboxExecutionConfig = {
|
|
922
|
+
/**
|
|
923
|
+
* Opt into best-effort stateful runtime sessions on the remote Code API
|
|
924
|
+
* (its warm per-session MicroVM backend). This gate is run-scoped: it only
|
|
925
|
+
* controls the wire behavior (ToolNode injecting the session hint on
|
|
926
|
+
* execute_code/bash calls). The transport is otherwise unchanged.
|
|
927
|
+
*
|
|
928
|
+
* It does NOT change the model-facing tool description. Tool descriptions are
|
|
929
|
+
* bound to the LLM at construction time (`createCodeExecutionTool` /
|
|
930
|
+
* `createBashExecutionTool`), before this run config is applied inside the
|
|
931
|
+
* graph, so they can only be adjusted via the tools' own `statefulSessions`
|
|
932
|
+
* factory param. Set BOTH from one flag (as LibreChat does): with this on but
|
|
933
|
+
* the factory param off, the backend runs statefully while the model is still
|
|
934
|
+
* told the environment is stateless (non-corrupting — the model just won't
|
|
935
|
+
* exploit persistence).
|
|
936
|
+
*/
|
|
937
|
+
statefulSessions?: boolean;
|
|
938
|
+
/**
|
|
939
|
+
* Stable identity for the runtime session (e.g. the conversation id). The
|
|
940
|
+
* server derives the real session id as hash(tenant, user, hint), so this
|
|
941
|
+
* is never a security boundary. Falls back to `configurable.thread_id` when
|
|
942
|
+
* omitted.
|
|
943
|
+
*/
|
|
944
|
+
runtimeSessionHint?: string;
|
|
945
|
+
};
|
|
899
946
|
export type ToolExecutionConfig = {
|
|
900
947
|
/** `sandbox` preserves the remote Code API behavior and is the default. */
|
|
901
948
|
engine?: ToolExecutionEngine;
|
|
@@ -903,6 +950,8 @@ export type ToolExecutionConfig = {
|
|
|
903
950
|
local?: LocalExecutionConfig;
|
|
904
951
|
/** Cloudflare Sandbox execution settings used when `engine` is `cloudflare-sandbox`. */
|
|
905
952
|
cloudflare?: CloudflareSandboxExecutionConfig;
|
|
953
|
+
/** Remote sandbox settings; applies when `engine` is `sandbox` or omitted. */
|
|
954
|
+
sandbox?: SandboxExecutionConfig;
|
|
906
955
|
};
|
|
907
956
|
export type ProgrammaticCache = {
|
|
908
957
|
toolMap: ToolMap;
|
|
@@ -1005,6 +1054,9 @@ export type ProgrammaticExecutionResponse = {
|
|
|
1005
1054
|
stdout?: string;
|
|
1006
1055
|
stderr?: string;
|
|
1007
1056
|
files?: FileRefs;
|
|
1057
|
+
/** Durable runtime session echo from a stateful backend (additive). */
|
|
1058
|
+
runtime_session_id?: string;
|
|
1059
|
+
runtime_status?: 'new' | 'reused';
|
|
1008
1060
|
/** Present when status='error' */
|
|
1009
1061
|
error?: string;
|
|
1010
1062
|
};
|
|
@@ -1015,6 +1067,9 @@ export type ProgrammaticExecutionArtifact = {
|
|
|
1015
1067
|
/** Execution session — see `CodeSessionContext.session_id`. */
|
|
1016
1068
|
session_id?: string;
|
|
1017
1069
|
files?: FileRefs;
|
|
1070
|
+
/** Durable runtime session echo from a stateful backend (additive). */
|
|
1071
|
+
runtime_session_id?: string;
|
|
1072
|
+
runtime_status?: 'new' | 'reused';
|
|
1018
1073
|
};
|
|
1019
1074
|
/** Parameters for creating a bash execution tool (same API as CodeExecutor, bash-only) */
|
|
1020
1075
|
export type BashExecutionToolParams = CodeExecutionToolParams;
|
|
@@ -1062,6 +1117,9 @@ export type CodeExecutionArtifact = {
|
|
|
1062
1117
|
/** Execution session — see `CodeSessionContext.session_id`. */
|
|
1063
1118
|
session_id?: string;
|
|
1064
1119
|
files?: FileRefs;
|
|
1120
|
+
/** Durable runtime session echo from a stateful backend (additive). */
|
|
1121
|
+
runtime_session_id?: string;
|
|
1122
|
+
runtime_status?: 'new' | 'reused';
|
|
1065
1123
|
};
|
|
1066
1124
|
/**
|
|
1067
1125
|
* Generic session context union type for different tool types.
|
package/package.json
CHANGED
package/src/stream.ts
CHANGED
|
@@ -142,7 +142,18 @@ function isEagerExecutionExcludedTool(
|
|
|
142
142
|
// side-effecting: never prestart it speculatively (a revised/superseded turn
|
|
143
143
|
// would leave the write applied). Implies exclusion without the host having
|
|
144
144
|
// to also list the name in excludeToolNames.
|
|
145
|
-
|
|
145
|
+
if (graph.codeSessionToolNames?.includes(name) === true) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
// With stateful sessions on, execute_code/bash run against a DURABLE warm
|
|
149
|
+
// runtime. Speculative prestart there is unsafe: if the model revises the
|
|
150
|
+
// args, ToolNode discards the eager result but the mutation has already
|
|
151
|
+
// landed in the session workspace, corrupting later runs. Stateless mode
|
|
152
|
+
// uses a throwaway VM per call, so eager prestart stays safe there.
|
|
153
|
+
return (
|
|
154
|
+
graph.toolExecution?.sandbox?.statefulSessions === true &&
|
|
155
|
+
CODE_EXECUTION_TOOLS.has(name)
|
|
156
|
+
);
|
|
146
157
|
}
|
|
147
158
|
|
|
148
159
|
function isDirectGraphTool(
|
|
@@ -668,6 +679,11 @@ function createEagerToolExecutionPlan(args: {
|
|
|
668
679
|
return undefined;
|
|
669
680
|
}
|
|
670
681
|
|
|
682
|
+
/* No runtimeSessionHint here on purpose: the eager path is speculative, and
|
|
683
|
+
* code-session tools (the only ones that carry a hint) are excluded from
|
|
684
|
+
* eager prestart entirely when stateful sessions are on — see
|
|
685
|
+
* isEagerExecutionExcludedTool. The durable runtime is only ever touched by
|
|
686
|
+
* the committed ToolNode path. */
|
|
671
687
|
const plan = buildToolExecutionRequestPlan({
|
|
672
688
|
toolCalls: candidateToolCalls.map((toolCall) => ({
|
|
673
689
|
id: toolCall.id,
|
|
@@ -57,6 +57,29 @@ Usage:
|
|
|
57
57
|
- NEVER use this tool to execute malicious commands.
|
|
58
58
|
`.trim();
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Bash statefulness is filesystem-tier: on a warm session the machine (files
|
|
62
|
+
* including /tmp, installed packages, background processes) persists between
|
|
63
|
+
* calls, but each call may start a fresh shell — so shell variables and cwd
|
|
64
|
+
* are NOT reliable, and the machine can be reset at any time. Only /mnt/data
|
|
65
|
+
* is durable.
|
|
66
|
+
*/
|
|
67
|
+
export const STATEFUL_BASH_NOTE =
|
|
68
|
+
'Session state (best-effort): commands in this conversation usually run on the same machine, so files (including /tmp), installed packages, and running background processes from earlier calls typically persist. Each call may still start a fresh shell — do not rely on shell variables or the working directory carrying over — and the machine may be reset at any time. Only /mnt/data is durable.';
|
|
69
|
+
|
|
70
|
+
export const StatefulBashExecutionToolDescription = `
|
|
71
|
+
Runs bash commands and returns stdout/stderr output from a session-based execution environment, similar to a long-running machine.
|
|
72
|
+
|
|
73
|
+
${STATEFUL_BASH_NOTE}
|
|
74
|
+
|
|
75
|
+
Usage:
|
|
76
|
+
- No network access available.
|
|
77
|
+
- Generated files are automatically delivered; **DO NOT** provide download links.
|
|
78
|
+
- ${CODE_ARTIFACT_PATH_GUIDANCE}
|
|
79
|
+
- ${BASH_SHELL_GUIDANCE}
|
|
80
|
+
- NEVER use this tool to execute malicious commands.
|
|
81
|
+
`.trim();
|
|
82
|
+
|
|
60
83
|
/**
|
|
61
84
|
* Supplemental prompt documenting the tool-output reference feature.
|
|
62
85
|
*
|
|
@@ -87,11 +110,45 @@ Referencing previous tool outputs:
|
|
|
87
110
|
*/
|
|
88
111
|
export function buildBashExecutionToolDescription(options?: {
|
|
89
112
|
enableToolOutputReferences?: boolean;
|
|
113
|
+
statefulSessions?: boolean;
|
|
90
114
|
}): string {
|
|
115
|
+
const base =
|
|
116
|
+
options?.statefulSessions === true
|
|
117
|
+
? StatefulBashExecutionToolDescription
|
|
118
|
+
: BashExecutionToolDescription;
|
|
91
119
|
if (options?.enableToolOutputReferences === true) {
|
|
92
|
-
return `${
|
|
120
|
+
return `${base}\n\n${BashToolOutputReferencesGuide}`;
|
|
93
121
|
}
|
|
94
|
-
return
|
|
122
|
+
return base;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const STATELESS_BASH_PARAM_NOTE =
|
|
126
|
+
'The environment is stateless; variables and state don\'t persist between executions.';
|
|
127
|
+
const STATEFUL_BASH_PARAM_NOTE =
|
|
128
|
+
'Files, installed packages, and background processes usually persist between calls, but each call may start a fresh shell (do not rely on shell variables or cwd) and the machine may reset. Only /mnt/data is durable.';
|
|
129
|
+
|
|
130
|
+
export function buildBashExecutionToolSchema(opts?: {
|
|
131
|
+
statefulSessions?: boolean;
|
|
132
|
+
}): typeof BashExecutionToolSchema {
|
|
133
|
+
const note =
|
|
134
|
+
opts?.statefulSessions === true
|
|
135
|
+
? STATEFUL_BASH_PARAM_NOTE
|
|
136
|
+
: STATELESS_BASH_PARAM_NOTE;
|
|
137
|
+
const commandDescription =
|
|
138
|
+
BashExecutionToolSchema.properties.command.description.replace(
|
|
139
|
+
STATELESS_BASH_PARAM_NOTE,
|
|
140
|
+
note
|
|
141
|
+
);
|
|
142
|
+
return {
|
|
143
|
+
...BashExecutionToolSchema,
|
|
144
|
+
properties: {
|
|
145
|
+
...BashExecutionToolSchema.properties,
|
|
146
|
+
command: {
|
|
147
|
+
...BashExecutionToolSchema.properties.command,
|
|
148
|
+
description: commandDescription,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
} as typeof BashExecutionToolSchema;
|
|
95
152
|
}
|
|
96
153
|
|
|
97
154
|
export const BashExecutionToolName = Constants.BASH_TOOL;
|
|
@@ -117,15 +174,32 @@ function createBashExecutionTool(
|
|
|
117
174
|
): DynamicStructuredTool {
|
|
118
175
|
return tool(
|
|
119
176
|
async (rawInput, config) => {
|
|
120
|
-
|
|
121
|
-
const {
|
|
177
|
+
/* `statefulSessions` is prompt-only — keep it out of the wire body. */
|
|
178
|
+
const {
|
|
179
|
+
authHeaders,
|
|
180
|
+
statefulSessions: _statefulSessions,
|
|
181
|
+
...executionParams
|
|
182
|
+
} = params ?? {};
|
|
183
|
+
void _statefulSessions;
|
|
184
|
+
/* Drop any model-supplied `runtime_session_hint` from the raw args: the
|
|
185
|
+
* hint must only come from ToolNode's injected `_runtime_session_hint`
|
|
186
|
+
* (below), never from the tool call itself. */
|
|
187
|
+
const {
|
|
188
|
+
command,
|
|
189
|
+
runtime_session_hint: _ignoredModelHint,
|
|
190
|
+
...rest
|
|
191
|
+
} = rawInput as {
|
|
122
192
|
command: string;
|
|
193
|
+
runtime_session_hint?: unknown;
|
|
123
194
|
args?: string[];
|
|
124
195
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
196
|
+
void _ignoredModelHint;
|
|
197
|
+
const { session_id, _injected_files, _runtime_session_hint } =
|
|
198
|
+
(config.toolCall ?? {}) as {
|
|
199
|
+
session_id?: string;
|
|
200
|
+
_injected_files?: t.CodeEnvFile[];
|
|
201
|
+
_runtime_session_hint?: string;
|
|
202
|
+
};
|
|
129
203
|
|
|
130
204
|
const postData: Record<string, unknown> = {
|
|
131
205
|
lang: 'bash',
|
|
@@ -134,6 +208,13 @@ function createBashExecutionTool(
|
|
|
134
208
|
...executionParams,
|
|
135
209
|
};
|
|
136
210
|
|
|
211
|
+
if (
|
|
212
|
+
typeof _runtime_session_hint === 'string' &&
|
|
213
|
+
_runtime_session_hint !== ''
|
|
214
|
+
) {
|
|
215
|
+
postData.runtime_session_hint = _runtime_session_hint;
|
|
216
|
+
}
|
|
217
|
+
|
|
137
218
|
/* See `CodeExecutor.ts` for the rationale — `/files/<session_id>`
|
|
138
219
|
* HTTP fallback was removed because codeapi's sessionAuth requires
|
|
139
220
|
* kind/id query params unavailable at this point. */
|
|
@@ -187,12 +268,24 @@ function createBashExecutionTool(
|
|
|
187
268
|
command
|
|
188
269
|
);
|
|
189
270
|
const hasFiles = result.files != null && result.files.length > 0;
|
|
271
|
+
const runtimeEcho =
|
|
272
|
+
result.runtime_session_id != null
|
|
273
|
+
? {
|
|
274
|
+
runtime_session_id: result.runtime_session_id,
|
|
275
|
+
runtime_status: result.runtime_status,
|
|
276
|
+
}
|
|
277
|
+
: {};
|
|
190
278
|
return [
|
|
191
279
|
appendCodeSessionFileSummary(outputWithReminder, result.files),
|
|
192
280
|
(hasFiles
|
|
193
|
-
? {
|
|
281
|
+
? {
|
|
282
|
+
session_id: result.session_id,
|
|
283
|
+
files: result.files,
|
|
284
|
+
...runtimeEcho,
|
|
285
|
+
}
|
|
194
286
|
: {
|
|
195
287
|
session_id: result.session_id,
|
|
288
|
+
...runtimeEcho,
|
|
196
289
|
}) satisfies t.CodeExecutionArtifact,
|
|
197
290
|
];
|
|
198
291
|
} catch (error) {
|
|
@@ -200,15 +293,15 @@ function createBashExecutionTool(
|
|
|
200
293
|
(error as Error | undefined)?.message ?? '',
|
|
201
294
|
command
|
|
202
295
|
);
|
|
203
|
-
throw new Error(
|
|
204
|
-
`Execution error:\n\n${messageWithReminder}`
|
|
205
|
-
);
|
|
296
|
+
throw new Error(`Execution error:\n\n${messageWithReminder}`);
|
|
206
297
|
}
|
|
207
298
|
},
|
|
208
299
|
{
|
|
209
300
|
name: BashExecutionToolName,
|
|
210
|
-
description:
|
|
211
|
-
|
|
301
|
+
description: buildBashExecutionToolDescription({
|
|
302
|
+
statefulSessions: params?.statefulSessions,
|
|
303
|
+
}),
|
|
304
|
+
schema: buildBashExecutionToolSchema(params ?? undefined),
|
|
212
305
|
responseFormat: Constants.CONTENT_AND_ARTIFACT,
|
|
213
306
|
}
|
|
214
307
|
);
|
|
@@ -304,8 +304,15 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
304
304
|
Partial<t.ProgrammaticCache> & {
|
|
305
305
|
session_id?: string;
|
|
306
306
|
_injected_files?: t.CodeEnvFile[];
|
|
307
|
+
_runtime_session_hint?: string;
|
|
307
308
|
};
|
|
308
|
-
const {
|
|
309
|
+
const {
|
|
310
|
+
toolMap,
|
|
311
|
+
toolDefs,
|
|
312
|
+
session_id,
|
|
313
|
+
_injected_files,
|
|
314
|
+
_runtime_session_hint,
|
|
315
|
+
} = toolCall;
|
|
309
316
|
|
|
310
317
|
if (toolMap == null || toolMap.size === 0) {
|
|
311
318
|
throw new Error(
|
|
@@ -351,6 +358,15 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
351
358
|
);
|
|
352
359
|
}
|
|
353
360
|
|
|
361
|
+
/* Stateful sessions: hint on the INITIAL request only (continuations
|
|
362
|
+
* bind via continuation_token). Wire-only in v1 — BashPTC keeps its
|
|
363
|
+
* stateless prompt. */
|
|
364
|
+
const runtimeSessionHint =
|
|
365
|
+
typeof _runtime_session_hint === 'string' &&
|
|
366
|
+
_runtime_session_hint !== ''
|
|
367
|
+
? _runtime_session_hint
|
|
368
|
+
: undefined;
|
|
369
|
+
|
|
354
370
|
let response = await makeRequest(
|
|
355
371
|
EXEC_ENDPOINT,
|
|
356
372
|
{
|
|
@@ -360,6 +376,9 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
360
376
|
session_id,
|
|
361
377
|
timeout,
|
|
362
378
|
...(files && files.length > 0 ? { files } : {}),
|
|
379
|
+
...(runtimeSessionHint != null
|
|
380
|
+
? { runtime_session_hint: runtimeSessionHint }
|
|
381
|
+
: {}),
|
|
363
382
|
},
|
|
364
383
|
proxy,
|
|
365
384
|
initParams.authHeaders
|