@librechat/agents 3.1.89 → 3.1.91
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 +9 -5
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +53 -14
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +14 -7
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +234 -0
- package/dist/cjs/langfuse.cjs.map +1 -0
- package/dist/cjs/llm/anthropic/index.cjs +8 -2
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +34 -0
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +34 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/run.cjs +44 -27
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +10 -3
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +10 -9
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +12 -8
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +35 -11
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeSessionFileSummary.cjs +63 -0
- package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -12
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +8 -5
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs +380 -0
- package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs.map +1 -0
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +997 -0
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -0
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +575 -0
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -0
- package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs +165 -0
- package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs.map +1 -0
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +17 -5
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +110 -6
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +319 -29
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +9 -5
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +53 -14
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +14 -7
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +226 -0
- package/dist/esm/langfuse.mjs.map +1 -0
- package/dist/esm/llm/anthropic/index.mjs +9 -3
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +7 -2
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/run.mjs +44 -27
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +10 -3
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +11 -10
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +13 -9
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +29 -12
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeSessionFileSummary.mjs +60 -0
- package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -13
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +8 -5
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs +378 -0
- package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs.map +1 -0
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +994 -0
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -0
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +566 -0
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -0
- package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs +155 -0
- package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs.map +1 -0
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +17 -6
- package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +111 -7
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +320 -31
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +4 -1
- package/dist/types/graphs/Graph.d.ts +6 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/langfuse.d.ts +48 -0
- package/dist/types/llm/anthropic/index.d.ts +3 -1
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +4 -0
- package/dist/types/tools/BashExecutor.d.ts +3 -3
- package/dist/types/tools/CodeExecutor.d.ts +10 -3
- package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -4
- package/dist/types/tools/cloudflare/CloudflareBridgeRuntime.d.ts +23 -0
- package/dist/types/tools/cloudflare/CloudflareProgrammaticToolCalling.d.ts +4 -0
- package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +21 -0
- package/dist/types/tools/cloudflare/CloudflareSandboxTools.d.ts +22 -0
- package/dist/types/tools/cloudflare/index.d.ts +4 -0
- package/dist/types/tools/local/LocalExecutionEngine.d.ts +1 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +8 -5
- package/dist/types/types/graph.d.ts +8 -0
- package/dist/types/types/tools.d.ts +120 -5
- package/package.json +4 -4
- package/src/__tests__/stream.eagerEventExecution.test.ts +66 -0
- package/src/agents/AgentContext.ts +13 -3
- package/src/graphs/Graph.ts +60 -16
- package/src/hooks/__tests__/executeHooks.test.ts +38 -0
- package/src/hooks/executeHooks.ts +27 -7
- package/src/index.ts +1 -0
- package/src/langfuse.ts +358 -0
- package/src/llm/anthropic/index.ts +27 -3
- package/src/llm/anthropic/llm.spec.ts +60 -1
- package/src/llm/anthropic/utils/message_inputs.ts +46 -0
- package/src/run.ts +60 -38
- package/src/specs/langfuse-config.test.ts +57 -0
- package/src/specs/langfuse-metadata.test.ts +19 -1
- package/src/stream.ts +13 -3
- package/src/tools/BashExecutor.ts +21 -10
- package/src/tools/BashProgrammaticToolCalling.ts +21 -9
- package/src/tools/CodeExecutor.ts +55 -12
- package/src/tools/CodeSessionFileSummary.ts +80 -0
- package/src/tools/ProgrammaticToolCalling.ts +25 -12
- package/src/tools/ToolNode.ts +8 -5
- package/src/tools/__tests__/BashExecutor.test.ts +9 -0
- package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +537 -0
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +43 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +100 -16
- package/src/tools/__tests__/SubagentExecutor.test.ts +540 -6
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +52 -0
- package/src/tools/__tests__/subagentHooks.test.ts +237 -0
- package/src/tools/cloudflare/CloudflareBridgeRuntime.ts +480 -0
- package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +1162 -0
- package/src/tools/cloudflare/CloudflareSandboxExecutionEngine.ts +744 -0
- package/src/tools/cloudflare/CloudflareSandboxTools.ts +225 -0
- package/src/tools/cloudflare/index.ts +4 -0
- package/src/tools/local/LocalExecutionEngine.ts +20 -4
- package/src/tools/local/resolveLocalExecutionTools.ts +169 -7
- package/src/tools/subagent/SubagentExecutor.ts +514 -36
- package/src/types/graph.ts +9 -0
- package/src/types/tools.ts +143 -5
|
@@ -288,6 +288,12 @@ export interface SubagentUpdateEvent {
|
|
|
288
288
|
/** ISO timestamp for ordering / display. */
|
|
289
289
|
timestamp: string;
|
|
290
290
|
}
|
|
291
|
+
export interface LangfuseConfig {
|
|
292
|
+
enabled?: boolean;
|
|
293
|
+
publicKey?: string;
|
|
294
|
+
secretKey?: string;
|
|
295
|
+
baseUrl?: string;
|
|
296
|
+
}
|
|
291
297
|
export interface AgentInputs {
|
|
292
298
|
agentId: string;
|
|
293
299
|
/** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
|
|
@@ -301,6 +307,8 @@ export interface AgentInputs {
|
|
|
301
307
|
streamBuffer?: number;
|
|
302
308
|
maxContextTokens?: number;
|
|
303
309
|
clientOptions?: ClientOptions;
|
|
310
|
+
/** Per-agent Langfuse tracing configuration. */
|
|
311
|
+
langfuse?: LangfuseConfig;
|
|
304
312
|
/** Dynamic system tail appended after stable instructions without provider cache markers. */
|
|
305
313
|
additional_instructions?: string;
|
|
306
314
|
reasoningKey?: 'reasoning_content' | 'reasoning';
|
|
@@ -284,9 +284,8 @@ export type FileRef = {
|
|
|
284
284
|
* `true` when the codeapi sandbox echoed this entry as an unchanged
|
|
285
285
|
* passthrough of an input the caller already owns (skill files,
|
|
286
286
|
* downloaded inputs whose hash matched the baseline, inherited
|
|
287
|
-
* `.dirkeep` markers). The
|
|
288
|
-
*
|
|
289
|
-
* conflate infrastructure inputs with newly-produced outputs.
|
|
287
|
+
* `.dirkeep` markers). The host can use this flag to skip
|
|
288
|
+
* post-processing work for files the caller already owns.
|
|
290
289
|
*/
|
|
291
290
|
inherited?: true;
|
|
292
291
|
};
|
|
@@ -468,10 +467,11 @@ export type ToolOutputReferencesConfig = {
|
|
|
468
467
|
*/
|
|
469
468
|
maxTotalSize?: number;
|
|
470
469
|
};
|
|
471
|
-
export type ToolExecutionEngine = 'sandbox' | 'local';
|
|
470
|
+
export type ToolExecutionEngine = 'sandbox' | 'local' | 'cloudflare-sandbox';
|
|
472
471
|
/**
|
|
473
472
|
* Records pre-write file contents so callers can rewind edits/writes
|
|
474
|
-
* made by
|
|
473
|
+
* made by local-compatible coding-tool engines. Implementations live
|
|
474
|
+
* in `src/tools/local`.
|
|
475
475
|
*/
|
|
476
476
|
export interface LocalFileCheckpointer {
|
|
477
477
|
/**
|
|
@@ -587,6 +587,12 @@ export type LocalExecConfig = {
|
|
|
587
587
|
* overridden together for non-host engines.
|
|
588
588
|
*/
|
|
589
589
|
fs?: import('@/tools/local/workspaceFS').WorkspaceFS;
|
|
590
|
+
/**
|
|
591
|
+
* Set by custom execution backends that already provide their own
|
|
592
|
+
* sandbox boundary. Suppresses the local host-sandbox warning while
|
|
593
|
+
* preserving the warning for plain host `child_process` execution.
|
|
594
|
+
*/
|
|
595
|
+
sandboxed?: boolean;
|
|
590
596
|
};
|
|
591
597
|
export type LocalExecutionConfig = {
|
|
592
598
|
/**
|
|
@@ -741,11 +747,120 @@ export type LocalExecutionConfig = {
|
|
|
741
747
|
timeoutMs?: number;
|
|
742
748
|
};
|
|
743
749
|
};
|
|
750
|
+
export type CloudflareSandboxExecOptions = {
|
|
751
|
+
cwd?: string;
|
|
752
|
+
env?: Record<string, string | undefined>;
|
|
753
|
+
timeout?: number;
|
|
754
|
+
stream?: boolean;
|
|
755
|
+
onOutput?: (stream: 'stdout' | 'stderr', data: string) => void;
|
|
756
|
+
signal?: AbortSignal;
|
|
757
|
+
};
|
|
758
|
+
export type CloudflareSandboxExecResult = {
|
|
759
|
+
success?: boolean;
|
|
760
|
+
exitCode: number;
|
|
761
|
+
stdout: string;
|
|
762
|
+
stderr: string;
|
|
763
|
+
command?: string;
|
|
764
|
+
duration?: number;
|
|
765
|
+
timestamp?: string;
|
|
766
|
+
};
|
|
767
|
+
export type CloudflareSandboxReadFileResult = string | Buffer | Uint8Array | {
|
|
768
|
+
content: string | Buffer | Uint8Array | ReadableStream<Uint8Array>;
|
|
769
|
+
size?: number;
|
|
770
|
+
mimeType?: string;
|
|
771
|
+
encoding?: 'utf-8' | 'utf8' | 'base64';
|
|
772
|
+
isBinary?: boolean;
|
|
773
|
+
};
|
|
774
|
+
export type CloudflareSandboxListFilesOptions = {
|
|
775
|
+
recursive?: boolean;
|
|
776
|
+
includeHidden?: boolean;
|
|
777
|
+
};
|
|
778
|
+
export type CloudflareSandboxFileInfo = {
|
|
779
|
+
name: string;
|
|
780
|
+
absolutePath?: string;
|
|
781
|
+
relativePath?: string;
|
|
782
|
+
type?: 'file' | 'directory' | 'symlink' | 'other';
|
|
783
|
+
size?: number;
|
|
784
|
+
modifiedAt?: string;
|
|
785
|
+
mode?: string;
|
|
786
|
+
};
|
|
787
|
+
export type CloudflareSandboxListFilesResult = CloudflareSandboxFileInfo[] | {
|
|
788
|
+
files: CloudflareSandboxFileInfo[];
|
|
789
|
+
count?: number;
|
|
790
|
+
path?: string;
|
|
791
|
+
success?: boolean;
|
|
792
|
+
};
|
|
793
|
+
export interface CloudflareSandboxRuntime {
|
|
794
|
+
exec(command: string, options?: CloudflareSandboxExecOptions): Promise<CloudflareSandboxExecResult>;
|
|
795
|
+
readFile(path: string, options?: {
|
|
796
|
+
encoding?: string;
|
|
797
|
+
}): Promise<CloudflareSandboxReadFileResult>;
|
|
798
|
+
writeFile(path: string, content: string | ReadableStream<Uint8Array>, options?: {
|
|
799
|
+
encoding?: string;
|
|
800
|
+
}): Promise<unknown>;
|
|
801
|
+
mkdir(path: string, options?: {
|
|
802
|
+
recursive?: boolean;
|
|
803
|
+
}): Promise<unknown>;
|
|
804
|
+
listFiles(path: string, options?: CloudflareSandboxListFilesOptions): Promise<CloudflareSandboxListFilesResult>;
|
|
805
|
+
deleteFile(path: string): Promise<unknown>;
|
|
806
|
+
}
|
|
807
|
+
export type CloudflareSandboxExecutionConfig = {
|
|
808
|
+
sandbox: CloudflareSandboxRuntime | (() => CloudflareSandboxRuntime | Promise<CloudflareSandboxRuntime>);
|
|
809
|
+
/** Working directory inside the sandbox. Defaults to `/workspace`. */
|
|
810
|
+
workspaceRoot?: string;
|
|
811
|
+
/** Extra environment variables merged into sandbox command executions. */
|
|
812
|
+
env?: Record<string, string | undefined>;
|
|
813
|
+
/** Default timeout for sandbox commands, in milliseconds. */
|
|
814
|
+
timeoutMs?: number;
|
|
815
|
+
/** Maximum stdout/stderr characters surfaced to the model. */
|
|
816
|
+
maxOutputChars?: number;
|
|
817
|
+
/**
|
|
818
|
+
* Add the built-in coding suite when `engine` is `cloudflare-sandbox`.
|
|
819
|
+
* Defaults to true, matching the local execution backend.
|
|
820
|
+
*/
|
|
821
|
+
includeCodingTools?: boolean;
|
|
822
|
+
/**
|
|
823
|
+
* Optional exact coding-tool names to expose when `includeCodingTools`
|
|
824
|
+
* is on. Defaults to the full local-parity Cloudflare bundle. Use this
|
|
825
|
+
* to publish a bash-only sandbox surface, for example file/search tools
|
|
826
|
+
* plus `bash_tool` and `run_tools_with_bash`, without exposing
|
|
827
|
+
* `execute_code`.
|
|
828
|
+
*/
|
|
829
|
+
codingToolNames?: readonly string[];
|
|
830
|
+
/** Optional shell executable for bash-style tools. Defaults to `bash`. */
|
|
831
|
+
shell?: string;
|
|
832
|
+
/**
|
|
833
|
+
* Optional project-level compile check configuration. Mirrors the local
|
|
834
|
+
* execution backend.
|
|
835
|
+
*/
|
|
836
|
+
compileCheck?: LocalExecutionConfig['compileCheck'];
|
|
837
|
+
/** Optional read-only guard for mutating coding tools. */
|
|
838
|
+
readOnly?: boolean;
|
|
839
|
+
/** Permit dangerous commands that the validator otherwise blocks. */
|
|
840
|
+
allowDangerousCommands?: LocalExecutionConfig['allowDangerousCommands'];
|
|
841
|
+
/** Tree-sitter-bash AST validation pass for bash commands. */
|
|
842
|
+
bashAst?: LocalExecutionConfig['bashAst'];
|
|
843
|
+
/**
|
|
844
|
+
* Enable per-Run file checkpointing for `edit_file` / `write_file`
|
|
845
|
+
* against the Cloudflare Sandbox workspace.
|
|
846
|
+
*/
|
|
847
|
+
fileCheckpointing?: LocalExecutionConfig['fileCheckpointing'];
|
|
848
|
+
/** Maximum bytes to read in `read_file` before returning a stub. */
|
|
849
|
+
maxReadBytes?: LocalExecutionConfig['maxReadBytes'];
|
|
850
|
+
/** Controls whether `read_file` returns binary files as attachments. */
|
|
851
|
+
attachReadAttachments?: LocalExecutionConfig['attachReadAttachments'];
|
|
852
|
+
/** Maximum pre-encoding byte size to embed inline. */
|
|
853
|
+
maxAttachmentBytes?: LocalExecutionConfig['maxAttachmentBytes'];
|
|
854
|
+
/** Run a fast per-file syntax check after successful edits/writes. */
|
|
855
|
+
postEditSyntaxCheck?: LocalExecutionConfig['postEditSyntaxCheck'];
|
|
856
|
+
};
|
|
744
857
|
export type ToolExecutionConfig = {
|
|
745
858
|
/** `sandbox` preserves the remote Code API behavior and is the default. */
|
|
746
859
|
engine?: ToolExecutionEngine;
|
|
747
860
|
/** Local process execution settings used when `engine` is `local`. */
|
|
748
861
|
local?: LocalExecutionConfig;
|
|
862
|
+
/** Cloudflare Sandbox execution settings used when `engine` is `cloudflare-sandbox`. */
|
|
863
|
+
cloudflare?: CloudflareSandboxExecutionConfig;
|
|
749
864
|
};
|
|
750
865
|
export type ProgrammaticCache = {
|
|
751
866
|
toolMap: ToolMap;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.91",
|
|
4
4
|
"main": "./dist/cjs/main.cjs",
|
|
5
5
|
"module": "./dist/esm/main.mjs",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -227,9 +227,9 @@
|
|
|
227
227
|
"@langchain/openai": "1.4.5",
|
|
228
228
|
"@langchain/textsplitters": "^1.0.1",
|
|
229
229
|
"@langchain/xai": "^1.3.17",
|
|
230
|
-
"@langfuse/langchain": "^4.
|
|
231
|
-
"@langfuse/otel": "^4.
|
|
232
|
-
"@langfuse/tracing": "^4.
|
|
230
|
+
"@langfuse/langchain": "^4.6.1",
|
|
231
|
+
"@langfuse/otel": "^4.6.1",
|
|
232
|
+
"@langfuse/tracing": "^4.6.1",
|
|
233
233
|
"@opentelemetry/sdk-node": "^0.218.0",
|
|
234
234
|
"@scarf/scarf": "^1.4.0",
|
|
235
235
|
"@types/diff": "^7.0.2",
|
|
@@ -2847,6 +2847,72 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2847
2847
|
expect(graph.eagerEventToolCallChunks.size).toBe(0);
|
|
2848
2848
|
});
|
|
2849
2849
|
|
|
2850
|
+
it('does not prestart streamed Cloudflare sandbox direct coding tools', async () => {
|
|
2851
|
+
const graph = createGraph({
|
|
2852
|
+
toolExecution: {
|
|
2853
|
+
engine: 'cloudflare-sandbox',
|
|
2854
|
+
cloudflare: { sandbox: {} },
|
|
2855
|
+
} as StandardGraph['toolExecution'],
|
|
2856
|
+
getAgentContext: jest.fn(
|
|
2857
|
+
(): Partial<AgentContext> => ({
|
|
2858
|
+
provider: Providers.OPENAI,
|
|
2859
|
+
reasoningKey: 'reasoning_content',
|
|
2860
|
+
toolDefinitions: [{ name: Constants.BASH_TOOL }, { name: 'weather' }],
|
|
2861
|
+
graphTools: [],
|
|
2862
|
+
agentId: 'agent_1',
|
|
2863
|
+
})
|
|
2864
|
+
) as unknown as StandardGraph['getAgentContext'],
|
|
2865
|
+
});
|
|
2866
|
+
const dispatchSpy = jest.spyOn(events, 'safeDispatchCustomEvent');
|
|
2867
|
+
const handler = new ChatModelStreamHandler();
|
|
2868
|
+
const metadata = { langgraph_node: 'agent' };
|
|
2869
|
+
|
|
2870
|
+
await handler.handle(
|
|
2871
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2872
|
+
{
|
|
2873
|
+
chunk: {
|
|
2874
|
+
content: '',
|
|
2875
|
+
tool_call_chunks: [
|
|
2876
|
+
{
|
|
2877
|
+
id: 'call_weather',
|
|
2878
|
+
name: 'weather',
|
|
2879
|
+
args: '{"city":"NYC"}',
|
|
2880
|
+
index: 0,
|
|
2881
|
+
},
|
|
2882
|
+
],
|
|
2883
|
+
} as unknown as t.StreamChunk,
|
|
2884
|
+
},
|
|
2885
|
+
metadata,
|
|
2886
|
+
graph
|
|
2887
|
+
);
|
|
2888
|
+
await handler.handle(
|
|
2889
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2890
|
+
{
|
|
2891
|
+
chunk: {
|
|
2892
|
+
content: '',
|
|
2893
|
+
tool_call_chunks: [
|
|
2894
|
+
{
|
|
2895
|
+
id: 'call_bash',
|
|
2896
|
+
name: Constants.BASH_TOOL,
|
|
2897
|
+
args: '{"command":"echo ok"}',
|
|
2898
|
+
index: 1,
|
|
2899
|
+
},
|
|
2900
|
+
],
|
|
2901
|
+
} as unknown as t.StreamChunk,
|
|
2902
|
+
},
|
|
2903
|
+
metadata,
|
|
2904
|
+
graph
|
|
2905
|
+
);
|
|
2906
|
+
|
|
2907
|
+
expect(dispatchSpy).not.toHaveBeenCalledWith(
|
|
2908
|
+
GraphEvents.ON_TOOL_EXECUTE,
|
|
2909
|
+
expect.anything(),
|
|
2910
|
+
expect.anything()
|
|
2911
|
+
);
|
|
2912
|
+
expect(graph.eagerEventToolExecutions.size).toBe(0);
|
|
2913
|
+
expect(graph.eagerEventToolCallChunks.size).toBe(0);
|
|
2914
|
+
});
|
|
2915
|
+
|
|
2850
2916
|
it('prestarts streamed remote bash tools when the next Anthropic tool call begins', async () => {
|
|
2851
2917
|
const graph = createGraph({
|
|
2852
2918
|
getAgentContext: jest.fn(
|
|
@@ -53,6 +53,7 @@ export class AgentContext {
|
|
|
53
53
|
name,
|
|
54
54
|
provider,
|
|
55
55
|
clientOptions,
|
|
56
|
+
langfuse,
|
|
56
57
|
tools,
|
|
57
58
|
toolMap,
|
|
58
59
|
toolEnd,
|
|
@@ -80,6 +81,7 @@ export class AgentContext {
|
|
|
80
81
|
name: name ?? agentId,
|
|
81
82
|
provider,
|
|
82
83
|
clientOptions,
|
|
84
|
+
langfuse,
|
|
83
85
|
maxContextTokens,
|
|
84
86
|
streamBuffer,
|
|
85
87
|
tools,
|
|
@@ -149,6 +151,8 @@ export class AgentContext {
|
|
|
149
151
|
provider: Providers;
|
|
150
152
|
/** Client options for this agent */
|
|
151
153
|
clientOptions?: t.ClientOptions;
|
|
154
|
+
/** Per-agent Langfuse tracing configuration. */
|
|
155
|
+
langfuse?: t.LangfuseConfig;
|
|
152
156
|
/** Token count map indexed by message position */
|
|
153
157
|
indexTokenCountMap: Record<string, number | undefined> = {};
|
|
154
158
|
/** Canonical pre-run token map used to restore token accounting on reset */
|
|
@@ -309,6 +313,7 @@ export class AgentContext {
|
|
|
309
313
|
name,
|
|
310
314
|
provider,
|
|
311
315
|
clientOptions,
|
|
316
|
+
langfuse,
|
|
312
317
|
maxContextTokens,
|
|
313
318
|
streamBuffer,
|
|
314
319
|
tokenCounter,
|
|
@@ -332,6 +337,7 @@ export class AgentContext {
|
|
|
332
337
|
name?: string;
|
|
333
338
|
provider: Providers;
|
|
334
339
|
clientOptions?: t.ClientOptions;
|
|
340
|
+
langfuse?: t.LangfuseConfig;
|
|
335
341
|
maxContextTokens?: number;
|
|
336
342
|
streamBuffer?: number;
|
|
337
343
|
tokenCounter?: t.TokenCounter;
|
|
@@ -355,6 +361,7 @@ export class AgentContext {
|
|
|
355
361
|
this.name = name;
|
|
356
362
|
this.provider = provider;
|
|
357
363
|
this.clientOptions = clientOptions;
|
|
364
|
+
this.langfuse = langfuse;
|
|
358
365
|
this.maxContextTokens = maxContextTokens;
|
|
359
366
|
this.streamBuffer = streamBuffer;
|
|
360
367
|
this.tokenCounter = tokenCounter;
|
|
@@ -458,11 +465,14 @@ export class AgentContext {
|
|
|
458
465
|
}
|
|
459
466
|
|
|
460
467
|
private hasAvailableTool(name: string): boolean {
|
|
461
|
-
if (this.toolDefinitions?.some((tool) => tool.name === name)
|
|
462
|
-
|
|
468
|
+
if (this.toolDefinitions?.some((tool) => tool.name === name) === true)
|
|
469
|
+
return true;
|
|
470
|
+
if (
|
|
471
|
+
this.tools?.some((tool) => 'name' in tool && tool.name === name) === true
|
|
472
|
+
) {
|
|
463
473
|
return true;
|
|
464
474
|
}
|
|
465
|
-
if (this.toolMap?.has(name)) return true;
|
|
475
|
+
if (this.toolMap?.has(name) === true) return true;
|
|
466
476
|
return this.toolRegistry?.has(name) === true;
|
|
467
477
|
}
|
|
468
478
|
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -58,8 +58,10 @@ import { createFakeStreamingLLM } from '@/llm/fake';
|
|
|
58
58
|
import { handleToolCalls } from '@/tools/handlers';
|
|
59
59
|
import { resolveLocalToolsForBinding } from '@/tools/local';
|
|
60
60
|
import { createLocalCodingToolBundle } from '@/tools/local/LocalCodingTools';
|
|
61
|
+
import { createCloudflareCodingToolBundle } from '@/tools/cloudflare';
|
|
61
62
|
import { isThinkingEnabled } from '@/llm/request';
|
|
62
63
|
import { initializeModel } from '@/llm/init';
|
|
64
|
+
import { createLangfuseHandler, disposeLangfuseHandler } from '@/langfuse';
|
|
63
65
|
import { HandlerRegistry } from '@/events';
|
|
64
66
|
import { ChatOpenAI } from '@/llm/openai';
|
|
65
67
|
import { partitionAndMarkOpenRouterToolCache } from '@/llm/openrouter/toolCache';
|
|
@@ -337,11 +339,12 @@ export abstract class Graph<
|
|
|
337
339
|
/**
|
|
338
340
|
* Single per-Run file checkpointer shared across every ToolNode the
|
|
339
341
|
* graph compiles. Lazily constructed when
|
|
340
|
-
* `toolExecution.local.fileCheckpointing === true`
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
342
|
+
* `toolExecution.local.fileCheckpointing === true` or
|
|
343
|
+
* `toolExecution.cloudflare.fileCheckpointing === true` so
|
|
344
|
+
* multi-agent graphs see ONE snapshot store, not one-per-agent.
|
|
345
|
+
* Returns undefined when checkpointing is disabled or a supported
|
|
346
|
+
* coding-tool engine isn't selected. Exposed via
|
|
347
|
+
* `Run.getFileCheckpointer()` / `Run.rewindFiles()`.
|
|
345
348
|
*/
|
|
346
349
|
private _fileCheckpointer?: t.LocalFileCheckpointer;
|
|
347
350
|
/**
|
|
@@ -364,20 +367,32 @@ export abstract class Graph<
|
|
|
364
367
|
if (this._fileCheckpointer != null) {
|
|
365
368
|
return this._fileCheckpointer;
|
|
366
369
|
}
|
|
367
|
-
if (
|
|
368
|
-
this.toolExecution?.engine !== 'local' ||
|
|
369
|
-
this.toolExecution.local?.fileCheckpointing !== true
|
|
370
|
-
) {
|
|
371
|
-
return undefined;
|
|
372
|
-
}
|
|
373
370
|
// Eagerly create via the bundle factory so the construction path
|
|
374
371
|
// matches the bundle-only callers (and future bundle-internal
|
|
375
372
|
// cleanup hooks fire). The bundle factory itself accepts a pre-
|
|
376
373
|
// supplied checkpointer when present, so re-injecting this one
|
|
377
374
|
// into every ToolNode is idempotent.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
375
|
+
if (
|
|
376
|
+
this.toolExecution?.engine === 'local' &&
|
|
377
|
+
this.toolExecution.local?.fileCheckpointing === true
|
|
378
|
+
) {
|
|
379
|
+
const bundle = createLocalCodingToolBundle(
|
|
380
|
+
this.toolExecution.local ?? {}
|
|
381
|
+
);
|
|
382
|
+
this._fileCheckpointer = bundle.checkpointer;
|
|
383
|
+
return this._fileCheckpointer;
|
|
384
|
+
}
|
|
385
|
+
if (
|
|
386
|
+
this.toolExecution?.engine === 'cloudflare-sandbox' &&
|
|
387
|
+
this.toolExecution.cloudflare?.fileCheckpointing === true
|
|
388
|
+
) {
|
|
389
|
+
const bundle = createCloudflareCodingToolBundle(
|
|
390
|
+
this.toolExecution.cloudflare
|
|
391
|
+
);
|
|
392
|
+
this._fileCheckpointer = bundle.checkpointer;
|
|
393
|
+
return this._fileCheckpointer;
|
|
394
|
+
}
|
|
395
|
+
return undefined;
|
|
381
396
|
}
|
|
382
397
|
}
|
|
383
398
|
|
|
@@ -1318,6 +1333,26 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1318
1333
|
{ force: true }
|
|
1319
1334
|
);
|
|
1320
1335
|
|
|
1336
|
+
const langfuseHandler = createLangfuseHandler({
|
|
1337
|
+
langfuse: agentContext.langfuse,
|
|
1338
|
+
userId: config.configurable?.user_id as string | undefined,
|
|
1339
|
+
sessionId: config.configurable?.thread_id as string | undefined,
|
|
1340
|
+
traceMetadata: {
|
|
1341
|
+
messageId: this.runId,
|
|
1342
|
+
parentMessageId: config.configurable?.requestBody?.parentMessageId,
|
|
1343
|
+
agentId,
|
|
1344
|
+
agentName: agentContext.name,
|
|
1345
|
+
},
|
|
1346
|
+
});
|
|
1347
|
+
const invokeConfig = langfuseHandler
|
|
1348
|
+
? {
|
|
1349
|
+
...config,
|
|
1350
|
+
callbacks: ((config.callbacks as t.ProvidedCallbacks) ?? []).concat(
|
|
1351
|
+
[langfuseHandler]
|
|
1352
|
+
),
|
|
1353
|
+
}
|
|
1354
|
+
: config;
|
|
1355
|
+
|
|
1321
1356
|
try {
|
|
1322
1357
|
result = await attemptInvoke(
|
|
1323
1358
|
{
|
|
@@ -1326,17 +1361,19 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1326
1361
|
provider: agentContext.provider,
|
|
1327
1362
|
context: this,
|
|
1328
1363
|
},
|
|
1329
|
-
|
|
1364
|
+
invokeConfig
|
|
1330
1365
|
);
|
|
1331
1366
|
} catch (primaryError) {
|
|
1332
1367
|
result = await tryFallbackProviders({
|
|
1333
1368
|
fallbacks,
|
|
1334
1369
|
tools: agentContext.tools,
|
|
1335
1370
|
messages: finalMessages,
|
|
1336
|
-
config,
|
|
1371
|
+
config: invokeConfig,
|
|
1337
1372
|
primaryError,
|
|
1338
1373
|
context: this,
|
|
1339
1374
|
});
|
|
1375
|
+
} finally {
|
|
1376
|
+
await disposeLangfuseHandler(langfuseHandler);
|
|
1340
1377
|
}
|
|
1341
1378
|
|
|
1342
1379
|
if (!result) {
|
|
@@ -1562,6 +1599,13 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1562
1599
|
maxDepth: effectiveSubagentDepth,
|
|
1563
1600
|
createChildGraph: (input): StandardGraph => {
|
|
1564
1601
|
const childGraph = new StandardGraph(input);
|
|
1602
|
+
childGraph.hookRegistry = this.hookRegistry;
|
|
1603
|
+
/**
|
|
1604
|
+
* Do not propagate `humanInTheLoop` into the child graph yet:
|
|
1605
|
+
* nested subagent interrupts need a stable child checkpoint and
|
|
1606
|
+
* resume bridge. Child hooks still fire; `ask` decisions fail
|
|
1607
|
+
* closed inside the subagent until that flow is implemented.
|
|
1608
|
+
*/
|
|
1565
1609
|
childGraph.toolOutputReferences = this.toolOutputReferences;
|
|
1566
1610
|
childGraph.eagerEventToolExecution = this.eagerEventToolExecution;
|
|
1567
1611
|
childGraph.toolExecution = this.toolExecution;
|
|
@@ -109,6 +109,44 @@ describe('executeHooks', () => {
|
|
|
109
109
|
consoleWarnSpy.mockRestore();
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
describe('abort listener management', () => {
|
|
113
|
+
it('uses one abort listener for many hooks on one matcher', async () => {
|
|
114
|
+
const registry = new HookRegistry();
|
|
115
|
+
const listenerCounts = new Map<AbortSignal, number>();
|
|
116
|
+
let maxAbortListeners = 0;
|
|
117
|
+
const addEventListenerSpy = jest
|
|
118
|
+
.spyOn(AbortSignal.prototype, 'addEventListener')
|
|
119
|
+
.mockImplementation(function (
|
|
120
|
+
this: AbortSignal,
|
|
121
|
+
type: string,
|
|
122
|
+
_listener: EventListenerOrEventListenerObject | null
|
|
123
|
+
): void {
|
|
124
|
+
if (type !== 'abort') {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const count = (listenerCounts.get(this) ?? 0) + 1;
|
|
128
|
+
listenerCounts.set(this, count);
|
|
129
|
+
maxAbortListeners = Math.max(maxAbortListeners, count);
|
|
130
|
+
});
|
|
131
|
+
const hooks = Array.from({ length: 12 }, () =>
|
|
132
|
+
runStartHook(async (): Promise<RunStartHookOutput> => ({}))
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
registry.register('RunStart', { hooks });
|
|
137
|
+
|
|
138
|
+
await executeHooks({
|
|
139
|
+
registry,
|
|
140
|
+
input: runStartInput(),
|
|
141
|
+
timeoutMs: 1000,
|
|
142
|
+
});
|
|
143
|
+
expect(maxAbortListeners).toBe(1);
|
|
144
|
+
} finally {
|
|
145
|
+
addEventListenerSpy.mockRestore();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
112
150
|
describe('empty matcher set', () => {
|
|
113
151
|
it('returns an empty aggregated result when no matchers are registered', async () => {
|
|
114
152
|
const registry = new HookRegistry();
|
|
@@ -46,6 +46,11 @@ interface HookOutcome {
|
|
|
46
46
|
timedOut: boolean;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
interface AbortRace {
|
|
50
|
+
promise: Promise<never>;
|
|
51
|
+
cleanup: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
function freshResult(): AggregatedHookResult {
|
|
50
55
|
return {
|
|
51
56
|
additionalContexts: [],
|
|
@@ -110,10 +115,10 @@ async function runHook(
|
|
|
110
115
|
hook: WideCallback,
|
|
111
116
|
input: HookInput,
|
|
112
117
|
signal: AbortSignal,
|
|
118
|
+
abortPromise: Promise<never>,
|
|
113
119
|
matcher: WideMatcher
|
|
114
120
|
): Promise<HookOutcome> {
|
|
115
121
|
const hookPromise = Promise.resolve().then(() => hook(input, signal));
|
|
116
|
-
const { promise: abortPromise, cleanup } = makeAbortPromise(signal);
|
|
117
122
|
try {
|
|
118
123
|
const output = await Promise.race([hookPromise, abortPromise]);
|
|
119
124
|
return { matcher, output, error: null, timedOut: false };
|
|
@@ -124,8 +129,22 @@ async function runHook(
|
|
|
124
129
|
error: describeError(err),
|
|
125
130
|
timedOut: isTimeout(err),
|
|
126
131
|
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function runMatcherHooks(
|
|
136
|
+
matcher: WideMatcher,
|
|
137
|
+
input: HookInput,
|
|
138
|
+
signal: AbortSignal
|
|
139
|
+
): Promise<HookOutcome[]> {
|
|
140
|
+
const abortRace: AbortRace = makeAbortPromise(signal);
|
|
141
|
+
const tasks = matcher.hooks.map((hook) =>
|
|
142
|
+
runHook(hook, input, signal, abortRace.promise, matcher)
|
|
143
|
+
);
|
|
144
|
+
try {
|
|
145
|
+
return await Promise.all(tasks);
|
|
127
146
|
} finally {
|
|
128
|
-
cleanup();
|
|
147
|
+
abortRace.cleanup();
|
|
129
148
|
}
|
|
130
149
|
}
|
|
131
150
|
|
|
@@ -373,7 +392,7 @@ export async function executeHooks(
|
|
|
373
392
|
}
|
|
374
393
|
|
|
375
394
|
// --- SYNC CRITICAL SECTION: once-matcher removal must complete before any await ---
|
|
376
|
-
const tasks: Promise<HookOutcome>[] = [];
|
|
395
|
+
const tasks: Promise<HookOutcome[]>[] = [];
|
|
377
396
|
for (const matcher of matchers) {
|
|
378
397
|
if (!matchesQuery(matcher.pattern, matchQuery)) {
|
|
379
398
|
continue;
|
|
@@ -381,18 +400,19 @@ export async function executeHooks(
|
|
|
381
400
|
if (matcher.once === true) {
|
|
382
401
|
registry.removeMatcher(event, matcher, sessionId);
|
|
383
402
|
}
|
|
403
|
+
if (matcher.hooks.length === 0) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
384
406
|
const perHookTimeout = matcher.timeout ?? timeoutMs;
|
|
385
407
|
const matcherSignal = combineSignals(signal, perHookTimeout);
|
|
386
|
-
|
|
387
|
-
tasks.push(runHook(hook, input, matcherSignal, matcher));
|
|
388
|
-
}
|
|
408
|
+
tasks.push(runMatcherHooks(matcher, input, matcherSignal));
|
|
389
409
|
}
|
|
390
410
|
// --- END SYNC CRITICAL SECTION ---
|
|
391
411
|
if (tasks.length === 0) {
|
|
392
412
|
return freshResult();
|
|
393
413
|
}
|
|
394
414
|
|
|
395
|
-
const outcomes = await Promise.all(tasks);
|
|
415
|
+
const outcomes = (await Promise.all(tasks)).flat();
|
|
396
416
|
reportErrors(outcomes, event, logger);
|
|
397
417
|
const aggregated = fold(outcomes);
|
|
398
418
|
/**
|