@librechat/agents 3.2.57 → 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/agents/AgentContext.cjs +7 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +2 -2
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +4 -0
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/langfuseTraceShaping.cjs +172 -0
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -0
- package/dist/cjs/main.cjs +7 -0
- package/dist/cjs/run.cjs +2 -2
- package/dist/cjs/run.cjs.map +1 -1
- 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/cjs/tools/search/keenable-search.cjs +68 -0
- package/dist/cjs/tools/search/keenable-search.cjs.map +1 -0
- package/dist/cjs/tools/search/rerankers.cjs +28 -11
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +30 -4
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +14 -6
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +12 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/utils/title.cjs +9 -9
- package/dist/cjs/utils/title.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +7 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +2 -2
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +4 -0
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/langfuseTraceShaping.mjs +171 -0
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -0
- package/dist/esm/main.mjs +3 -3
- package/dist/esm/run.mjs +2 -2
- package/dist/esm/run.mjs.map +1 -1
- 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/esm/tools/search/keenable-search.mjs +66 -0
- package/dist/esm/tools/search/keenable-search.mjs.map +1 -0
- package/dist/esm/tools/search/rerankers.mjs +28 -11
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +30 -4
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +14 -6
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +12 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/utils/title.mjs +9 -9
- package/dist/esm/utils/title.mjs.map +1 -1
- package/dist/types/langfuseTraceShaping.d.ts +20 -0
- 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/tools/search/keenable-search.d.ts +4 -0
- package/dist/types/tools/search/rerankers.d.ts +7 -2
- package/dist/types/tools/search/types.d.ts +38 -1
- package/dist/types/types/graph.d.ts +22 -0
- package/dist/types/types/tools.d.ts +58 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +9 -0
- package/src/agents/__tests__/AgentContext.test.ts +40 -0
- package/src/graphs/Graph.ts +25 -2
- package/src/graphs/__tests__/composition.smoke.test.ts +53 -0
- package/src/langfuseToolOutputTracing.ts +11 -0
- package/src/langfuseTraceShaping.ts +280 -0
- package/src/llm/anthropic/inherited-stream-events.spec.ts +6 -3
- package/src/run.ts +3 -3
- package/src/specs/langfuse-routing.integration.test.ts +1 -1
- package/src/specs/langfuse-trace-shaping.test.ts +194 -0
- 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__/SubagentExecutor.test.ts +44 -0
- package/src/tools/__tests__/ToolNode.session.test.ts +86 -0
- package/src/tools/__tests__/eagerEventExecution.session.test.ts +92 -0
- package/src/tools/__tests__/hitl.test.ts +85 -0
- package/src/tools/eagerEventExecution.ts +32 -5
- package/src/tools/search/jina-reranker.test.ts +70 -1
- package/src/tools/search/keenable-search.ts +98 -0
- package/src/tools/search/keenable.test.ts +183 -0
- package/src/tools/search/rerankers.ts +41 -4
- package/src/tools/search/search.ts +58 -2
- package/src/tools/search/source-processing.test.ts +86 -0
- package/src/tools/search/tool.ts +29 -4
- package/src/tools/search/types.ts +42 -1
- package/src/tools/subagent/SubagentExecutor.ts +11 -0
- package/src/types/graph.ts +22 -0
- package/src/types/tools.ts +65 -0
- package/src/utils/title.ts +9 -9
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
/**
|
|
3
|
+
* LangGraph plumbing observations that add noise without information:
|
|
4
|
+
* the duplicated `__start__` channel-seed nodes and anonymous
|
|
5
|
+
* `RunnableLambda` pass-throughs (Langfuse team feedback items 4 & 5).
|
|
6
|
+
*/
|
|
7
|
+
export declare function shouldDropLangfuseSpan(spanName: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Reshapes spans per Langfuse-team feedback before export:
|
|
10
|
+
* - `agent=<id>` / `tools=<id>` node names carry the ephemeral agent id
|
|
11
|
+
* (`provider__model`) — strip it so switching models doesn't break
|
|
12
|
+
* name-based logic (item 1).
|
|
13
|
+
* - Tool node spans are renamed to the actual tool name(s) and their
|
|
14
|
+
* input scoped to the pending tool-call args instead of the whole
|
|
15
|
+
* chat history (items 3 & 4).
|
|
16
|
+
* - The root span (and trace) input/output become the user question and
|
|
17
|
+
* assistant response so the session view reads as a conversation
|
|
18
|
+
* (item 2).
|
|
19
|
+
*/
|
|
20
|
+
export declare function shapeLangfuseSpan(span: ReadableSpan): void;
|
|
@@ -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[];
|
|
@@ -8,16 +8,20 @@ export declare abstract class BaseReranker {
|
|
|
8
8
|
}
|
|
9
9
|
export declare class JinaReranker extends BaseReranker {
|
|
10
10
|
private apiUrl;
|
|
11
|
-
|
|
11
|
+
private timeout;
|
|
12
|
+
constructor({ apiKey, apiUrl, timeout, logger, }: {
|
|
12
13
|
apiKey?: string;
|
|
13
14
|
apiUrl?: string;
|
|
15
|
+
timeout?: number;
|
|
14
16
|
logger?: t.Logger;
|
|
15
17
|
});
|
|
16
18
|
rerank(query: string, documents: string[], topK?: number): Promise<t.Highlight[]>;
|
|
17
19
|
}
|
|
18
20
|
export declare class CohereReranker extends BaseReranker {
|
|
19
|
-
|
|
21
|
+
private timeout;
|
|
22
|
+
constructor({ apiKey, timeout, logger, }: {
|
|
20
23
|
apiKey?: string;
|
|
24
|
+
timeout?: number;
|
|
21
25
|
logger?: t.Logger;
|
|
22
26
|
});
|
|
23
27
|
rerank(query: string, documents: string[], topK?: number): Promise<t.Highlight[]>;
|
|
@@ -34,5 +38,6 @@ export declare const createReranker: (config: {
|
|
|
34
38
|
jinaApiKey?: string;
|
|
35
39
|
jinaApiUrl?: string;
|
|
36
40
|
cohereApiKey?: string;
|
|
41
|
+
rerankerTimeout?: number;
|
|
37
42
|
logger?: t.Logger;
|
|
38
43
|
}) => BaseReranker | undefined;
|
|
@@ -2,7 +2,7 @@ import type { RunnableConfig } from '@langchain/core/runnables';
|
|
|
2
2
|
import type { Logger as WinstonLogger } from 'winston';
|
|
3
3
|
import type { BaseReranker } from './rerankers';
|
|
4
4
|
import { DATE_RANGE } from './schema';
|
|
5
|
-
export type SearchProvider = 'serper' | 'searxng' | 'tavily';
|
|
5
|
+
export type SearchProvider = 'serper' | 'searxng' | 'tavily' | 'keenable';
|
|
6
6
|
export type ScraperProvider = 'firecrawl' | 'serper' | 'tavily';
|
|
7
7
|
export type RerankerType = 'infinity' | 'jina' | 'cohere' | 'none';
|
|
8
8
|
export interface Highlight {
|
|
@@ -100,6 +100,32 @@ export interface SearchConfig {
|
|
|
100
100
|
tavilySearchUrl?: string;
|
|
101
101
|
tavilyExtractUrl?: string;
|
|
102
102
|
tavilySearchOptions?: TavilySearchOptions;
|
|
103
|
+
keenableApiKey?: string;
|
|
104
|
+
keenableApiUrl?: string;
|
|
105
|
+
keenableSearchOptions?: KeenableSearchOptions;
|
|
106
|
+
}
|
|
107
|
+
export interface KeenableSearchOptions {
|
|
108
|
+
maxResults?: number;
|
|
109
|
+
/** Restrict results to a single domain, e.g. "github.com". */
|
|
110
|
+
site?: string;
|
|
111
|
+
/** Sent as the X-Keenable-Title attribution header. Defaults to "LibreChat". */
|
|
112
|
+
attributionTitle?: string;
|
|
113
|
+
timeout?: number;
|
|
114
|
+
}
|
|
115
|
+
export interface KeenableSearchPayload {
|
|
116
|
+
query: string;
|
|
117
|
+
site?: string;
|
|
118
|
+
published_after?: string;
|
|
119
|
+
}
|
|
120
|
+
export interface KeenableSearchResult {
|
|
121
|
+
title?: string;
|
|
122
|
+
url?: string;
|
|
123
|
+
description?: string;
|
|
124
|
+
snippet?: string;
|
|
125
|
+
published_at?: string;
|
|
126
|
+
}
|
|
127
|
+
export interface KeenableSearchResponse {
|
|
128
|
+
results?: KeenableSearchResult[];
|
|
103
129
|
}
|
|
104
130
|
export type References = {
|
|
105
131
|
links: MediaReference[];
|
|
@@ -120,6 +146,15 @@ export interface ProcessSourcesConfig {
|
|
|
120
146
|
* chunker/reranker. Defaults to 50,000; also configurable via the
|
|
121
147
|
* `SEARCH_MAX_CONTENT_LENGTH` env var. */
|
|
122
148
|
maxContentLength?: number;
|
|
149
|
+
/** Chunk size (chars) for splitting scraped content before reranking.
|
|
150
|
+
* Defaults to 150; also configurable via the `SEARCH_CHUNK_SIZE` env var.
|
|
151
|
+
* Larger chunks send fewer documents to the reranker (lower cost/latency);
|
|
152
|
+
* highlights are expanded ±300-450 chars around each hit either way. */
|
|
153
|
+
chunkSize?: number;
|
|
154
|
+
/** Overlap (chars) between adjacent chunks. Defaults to 50; also
|
|
155
|
+
* configurable via the `SEARCH_CHUNK_OVERLAP` env var. Clamped below
|
|
156
|
+
* `chunkSize`. */
|
|
157
|
+
chunkOverlap?: number;
|
|
123
158
|
strategies?: string[];
|
|
124
159
|
filterContent?: boolean;
|
|
125
160
|
reranker?: BaseReranker;
|
|
@@ -202,6 +237,8 @@ export interface SearchToolConfig extends SearchConfig, ProcessSourcesConfig, Fi
|
|
|
202
237
|
jinaApiUrl?: string;
|
|
203
238
|
cohereApiKey?: string;
|
|
204
239
|
rerankerType?: RerankerType;
|
|
240
|
+
/** Timeout (ms) for rerank API requests. Defaults to 10,000. */
|
|
241
|
+
rerankerTimeout?: number;
|
|
205
242
|
scraperProvider?: ScraperProvider;
|
|
206
243
|
scraperTimeout?: number;
|
|
207
244
|
serperScraperOptions?: SerperScraperConfig;
|
|
@@ -480,6 +480,28 @@ export interface AgentInputs {
|
|
|
480
480
|
subagentConfigs?: SubagentConfig[];
|
|
481
481
|
/** Maximum subagent nesting depth. Default 1 means top-level agents can spawn subagents but subagents cannot nest further. */
|
|
482
482
|
maxSubagentDepth?: number;
|
|
483
|
+
/**
|
|
484
|
+
* Host-supplied tool instances that must execute IN-PROCESS inside the graph's
|
|
485
|
+
* ToolNode even when the run is event-driven (`toolDefinitions` non-empty). Each
|
|
486
|
+
* instance is bound to the model alongside the schema-only event tools and its
|
|
487
|
+
* name is marked direct, so calls bypass ON_TOOL_EXECUTE dispatch and run inside
|
|
488
|
+
* the Pregel task frame. This is the only execution mode where a tool body may
|
|
489
|
+
* raise a LangGraph `interrupt()` (e.g. a tool built on `askUserQuestion()`) —
|
|
490
|
+
* the host-side event handler runs outside the graph task, where `interrupt()`
|
|
491
|
+
* throws. Do NOT also list these tools in `toolDefinitions` (they would be bound
|
|
492
|
+
* twice). NOT inherited by SELF-SPAWNED subagent children (their config is a
|
|
493
|
+
* shallow spread of the parent's inputs, and child graphs compile without a
|
|
494
|
+
* checkpointer, so an interrupt-capable tool could never pause there) —
|
|
495
|
+
* `buildChildInputs` scrubs the inherited copy; an EXPLICIT child config that
|
|
496
|
+
* lists its own `graphTools` keeps them.
|
|
497
|
+
*
|
|
498
|
+
* Deliberately `GenericTool[]`, not `GraphTools`: the wider union admits
|
|
499
|
+
* schema-only shapes (OpenAI `BindToolsInput`, Google tool objects) that
|
|
500
|
+
* `initializeTools` cannot register in the ToolNode direct map — the model
|
|
501
|
+
* would bind a tool the SDK advertised as in-process but cannot execute.
|
|
502
|
+
* Every entry must be a real executable tool instance with a `name`.
|
|
503
|
+
*/
|
|
504
|
+
graphTools?: GenericTool[];
|
|
483
505
|
}
|
|
484
506
|
export interface ContextPruningConfig {
|
|
485
507
|
enabled?: boolean;
|
|
@@ -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
|
@@ -85,6 +85,7 @@ export class AgentContext {
|
|
|
85
85
|
toolSchemaTokens,
|
|
86
86
|
subagentConfigs,
|
|
87
87
|
maxSubagentDepth,
|
|
88
|
+
graphTools,
|
|
88
89
|
} = agentConfig;
|
|
89
90
|
|
|
90
91
|
const agentContext = new AgentContext({
|
|
@@ -116,6 +117,14 @@ export class AgentContext {
|
|
|
116
117
|
agentContext._sourceInputs = agentConfig;
|
|
117
118
|
agentContext.subagentConfigs = subagentConfigs;
|
|
118
119
|
agentContext.maxSubagentDepth = maxSubagentDepth;
|
|
120
|
+
/**
|
|
121
|
+
* Host-supplied direct tools (see `AgentInputs.graphTools`). Copied — never
|
|
122
|
+
* aliased — because the SDK later pushes graph-managed tools (handoff /
|
|
123
|
+
* subagent) into this same array and must not mutate the host's input.
|
|
124
|
+
*/
|
|
125
|
+
if (graphTools && graphTools.length > 0) {
|
|
126
|
+
agentContext.graphTools = [...graphTools];
|
|
127
|
+
}
|
|
119
128
|
|
|
120
129
|
if (initialSummary?.text != null && initialSummary.text !== '') {
|
|
121
130
|
agentContext.setInitialSummary(
|
|
@@ -42,6 +42,46 @@ describe('AgentContext', () => {
|
|
|
42
42
|
schema: { type: 'object', properties: {} },
|
|
43
43
|
}) as unknown as t.GenericTool;
|
|
44
44
|
|
|
45
|
+
describe('fromConfig — host-supplied graphTools', () => {
|
|
46
|
+
it('copies graphTools onto the context without aliasing the host array', () => {
|
|
47
|
+
const askTool = createMockTool('ask_user_question');
|
|
48
|
+
const hostGraphTools = [askTool];
|
|
49
|
+
const ctx = createBasicContext({
|
|
50
|
+
agentConfig: { graphTools: hostGraphTools },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(ctx.graphTools).toEqual([askTool]);
|
|
54
|
+
expect(ctx.graphTools).not.toBe(hostGraphTools);
|
|
55
|
+
|
|
56
|
+
/** The SDK pushes graph-managed tools (handoff/subagent) into this
|
|
57
|
+
* array later — that must never mutate the host's input. */
|
|
58
|
+
(ctx.graphTools as t.GenericTool[]).push(createMockTool('subagent'));
|
|
59
|
+
expect(hostGraphTools).toHaveLength(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('leaves graphTools undefined when the host supplies none (or an empty list)', () => {
|
|
63
|
+
expect(createBasicContext().graphTools).toBeUndefined();
|
|
64
|
+
expect(
|
|
65
|
+
createBasicContext({ agentConfig: { graphTools: [] } }).graphTools
|
|
66
|
+
).toBeUndefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('binds host graphTools to the model in event-driven mode', () => {
|
|
70
|
+
const askTool = createMockTool('ask_user_question');
|
|
71
|
+
const ctx = createBasicContext({
|
|
72
|
+
agentConfig: {
|
|
73
|
+
graphTools: [askTool],
|
|
74
|
+
toolDefinitions: [{ name: 'echo', description: 'host event tool' }],
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const bound = ctx.getToolsForBinding() ?? [];
|
|
79
|
+
const names = (bound as Array<{ name?: string }>).map((t) => t.name);
|
|
80
|
+
expect(names).toContain('ask_user_question');
|
|
81
|
+
expect(names).toContain('echo');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
45
85
|
describe('System Runnable - Lazy Creation', () => {
|
|
46
86
|
it('creates system runnable on first access', () => {
|
|
47
87
|
const ctx = createBasicContext({
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -1140,7 +1140,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1140
1140
|
getToolCount(): number {
|
|
1141
1141
|
const context = this.agentContexts.get(this.defaultAgentId);
|
|
1142
1142
|
return (
|
|
1143
|
-
(context?.tools?.length ?? 0) +
|
|
1143
|
+
(context?.tools?.length ?? 0) +
|
|
1144
|
+
(context?.toolDefinitions?.length ?? 0) +
|
|
1145
|
+
/**
|
|
1146
|
+
* Graph-managed + host-supplied direct tools (handoff, subagent,
|
|
1147
|
+
* `AgentInputs.graphTools`) are bound to the model and token-accounted,
|
|
1148
|
+
* so a count that omits them under-reports the run's tool surface
|
|
1149
|
+
* (Codex #289 P3).
|
|
1150
|
+
*/
|
|
1151
|
+
(context?.graphTools?.length ?? 0)
|
|
1144
1152
|
);
|
|
1145
1153
|
}
|
|
1146
1154
|
|
|
@@ -1286,10 +1294,25 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1286
1294
|
graphTools && graphTools.length > 0
|
|
1287
1295
|
? [...baseTools, ...graphTools]
|
|
1288
1296
|
: baseTools;
|
|
1297
|
+
/**
|
|
1298
|
+
* ToolNode treats a supplied `toolMap` as authoritative (it only derives
|
|
1299
|
+
* one from `tools` when the param is undefined), so when graphTools force
|
|
1300
|
+
* us to build a merged map here, an absent `currentToolMap` must be
|
|
1301
|
+
* seeded from the BASE tools first — otherwise ordinary tools stay bound
|
|
1302
|
+
* to the model but vanish from the execution map and every call to them
|
|
1303
|
+
* fails as an unknown tool (Codex #289 round 2).
|
|
1304
|
+
*/
|
|
1289
1305
|
const traditionalToolMap =
|
|
1290
1306
|
graphTools && graphTools.length > 0
|
|
1291
1307
|
? new Map([
|
|
1292
|
-
...(currentToolMap ??
|
|
1308
|
+
...(currentToolMap ??
|
|
1309
|
+
new Map(
|
|
1310
|
+
baseTools
|
|
1311
|
+
.filter(
|
|
1312
|
+
(t): t is t.GenericTool & { name: string } => 'name' in t
|
|
1313
|
+
)
|
|
1314
|
+
.map((t) => [t.name, t] as [string, t.GenericTool])
|
|
1315
|
+
)),
|
|
1293
1316
|
...graphTools
|
|
1294
1317
|
.filter((t): t is t.GenericTool & { name: string } => 'name' in t)
|
|
1295
1318
|
.map((t) => [t.name, t] as [string, t.GenericTool]),
|
|
@@ -73,6 +73,59 @@ const expectCompiledWorkflow = (
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
describe('LangGraph composition smoke tests', () => {
|
|
76
|
+
it('getToolCount includes direct graph tools (host-supplied graphTools) alongside instances and definitions', () => {
|
|
77
|
+
const askLikeTool = { name: 'ask_user_question' } as unknown as NonNullable<
|
|
78
|
+
t.AgentInputs['graphTools']
|
|
79
|
+
>[number];
|
|
80
|
+
const graph = new StandardGraph({
|
|
81
|
+
runId: 'toolcount-smoke',
|
|
82
|
+
agents: [
|
|
83
|
+
{
|
|
84
|
+
...makeAgent('agent'),
|
|
85
|
+
toolDefinitions: [
|
|
86
|
+
{ name: 'evt1', description: 'event tool one' },
|
|
87
|
+
{ name: 'evt2', description: 'event tool two' },
|
|
88
|
+
],
|
|
89
|
+
graphTools: [askLikeTool],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
// 2 schema-only event tools + 1 in-process direct tool — all bound to the
|
|
94
|
+
// model and token-accounted, so all must be counted (Codex #289 P3).
|
|
95
|
+
expect(graph.getToolCount()).toBe(3);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('keeps ordinary tools executable when graphTools are added without a host toolMap (traditional mode)', () => {
|
|
99
|
+
type HostTool = NonNullable<t.AgentInputs['graphTools']>[number];
|
|
100
|
+
const echoTool = { name: 'echo_tool' } as unknown as HostTool;
|
|
101
|
+
const askLikeTool = { name: 'ask_user_question' } as unknown as HostTool;
|
|
102
|
+
const graph = new StandardGraph({
|
|
103
|
+
runId: 'toolmap-merge-smoke',
|
|
104
|
+
agents: [
|
|
105
|
+
{
|
|
106
|
+
...makeAgent('agent'),
|
|
107
|
+
tools: [echoTool],
|
|
108
|
+
graphTools: [askLikeTool],
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
const agentContext = graph.agentContexts.get('agent');
|
|
113
|
+
const node = graph.initializeTools({
|
|
114
|
+
currentTools: agentContext?.tools,
|
|
115
|
+
currentToolMap: undefined,
|
|
116
|
+
agentContext,
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* ToolNode treats a provided toolMap as authoritative — if the merged map
|
|
120
|
+
* built for graphTools drops the base tools, they stay bound to the model
|
|
121
|
+
* but every call fails as an unknown tool (Codex #289 round 2).
|
|
122
|
+
*/
|
|
123
|
+
const toolMap = (node as unknown as { toolMap: Map<string, unknown> })
|
|
124
|
+
.toolMap;
|
|
125
|
+
expect(toolMap.has('echo_tool')).toBe(true);
|
|
126
|
+
expect(toolMap.has('ask_user_question')).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
76
129
|
it('clears run-scoped eager tool state on reset', () => {
|
|
77
130
|
const graph = new StandardGraph({
|
|
78
131
|
runId: 'standard-eager-reset',
|
|
@@ -16,6 +16,10 @@ import {
|
|
|
16
16
|
resolveLangfuseConfig,
|
|
17
17
|
resolveToolOutputTracingConfig,
|
|
18
18
|
} from '@/langfuseConfig';
|
|
19
|
+
import {
|
|
20
|
+
shapeLangfuseSpan,
|
|
21
|
+
shouldDropLangfuseSpan,
|
|
22
|
+
} from '@/langfuseTraceShaping';
|
|
19
23
|
import { resolveToolOutputTracingConfigForSpan } from '@/langfuseRuntimeScope';
|
|
20
24
|
|
|
21
25
|
export { LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT, resolveLangfuseConfig };
|
|
@@ -407,6 +411,9 @@ class ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {
|
|
|
407
411
|
}
|
|
408
412
|
|
|
409
413
|
onStart(span: Span, parentContext: Context): void {
|
|
414
|
+
if (shouldDropLangfuseSpan(span.name)) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
410
417
|
const config =
|
|
411
418
|
resolveToolOutputTracingConfigForSpan(parentContext) ??
|
|
412
419
|
this.fallbackConfig;
|
|
@@ -417,7 +424,11 @@ class ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {
|
|
|
417
424
|
}
|
|
418
425
|
|
|
419
426
|
onEnd(span: ReadableSpan): void {
|
|
427
|
+
if (shouldDropLangfuseSpan(span.name)) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
420
430
|
classifyLangfuseToolNodeSpan(span);
|
|
431
|
+
shapeLangfuseSpan(span);
|
|
421
432
|
const config = this.spanConfigs.get(span) ?? this.fallbackConfig;
|
|
422
433
|
if (config != null) {
|
|
423
434
|
redactLangfuseSpanToolOutputs(span, config);
|