@librechat/agents 3.1.82 → 3.1.84

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.
Files changed (57) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +69 -24
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/events.cjs +2 -1
  4. package/dist/cjs/events.cjs.map +1 -1
  5. package/dist/cjs/main.cjs +3 -0
  6. package/dist/cjs/main.cjs.map +1 -1
  7. package/dist/cjs/messages/cache.cjs +96 -0
  8. package/dist/cjs/messages/cache.cjs.map +1 -1
  9. package/dist/cjs/tools/BashExecutor.cjs +5 -2
  10. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  11. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +3 -3
  12. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  13. package/dist/cjs/tools/CodeExecutor.cjs +28 -2
  14. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  15. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +107 -34
  16. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  17. package/dist/cjs/tools/ToolNode.cjs +3 -4
  18. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  19. package/dist/esm/agents/AgentContext.mjs +71 -26
  20. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  21. package/dist/esm/events.mjs +2 -1
  22. package/dist/esm/events.mjs.map +1 -1
  23. package/dist/esm/main.mjs +2 -2
  24. package/dist/esm/messages/cache.mjs +96 -1
  25. package/dist/esm/messages/cache.mjs.map +1 -1
  26. package/dist/esm/tools/BashExecutor.mjs +6 -3
  27. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  28. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +3 -3
  29. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  30. package/dist/esm/tools/CodeExecutor.mjs +27 -3
  31. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  32. package/dist/esm/tools/ProgrammaticToolCalling.mjs +108 -35
  33. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  34. package/dist/esm/tools/ToolNode.mjs +3 -4
  35. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  36. package/dist/types/agents/AgentContext.d.ts +7 -3
  37. package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +6 -2
  38. package/dist/types/messages/cache.d.ts +1 -0
  39. package/dist/types/tools/CodeExecutor.d.ts +5 -0
  40. package/dist/types/tools/ProgrammaticToolCalling.d.ts +14 -3
  41. package/dist/types/types/tools.d.ts +6 -0
  42. package/package.json +1 -1
  43. package/src/agents/AgentContext.ts +102 -30
  44. package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +0 -4
  45. package/src/agents/__tests__/AgentContext.openrouter.live.test.ts +128 -0
  46. package/src/agents/__tests__/AgentContext.test.ts +199 -27
  47. package/src/agents/__tests__/promptCacheLiveHelpers.ts +8 -2
  48. package/src/events.ts +4 -1
  49. package/src/messages/cache.ts +143 -0
  50. package/src/tools/BashExecutor.ts +14 -3
  51. package/src/tools/BashProgrammaticToolCalling.ts +6 -3
  52. package/src/tools/CodeExecutor.ts +36 -2
  53. package/src/tools/ProgrammaticToolCalling.ts +175 -30
  54. package/src/tools/ToolNode.ts +3 -4
  55. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +321 -0
  56. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +31 -1
  57. package/src/types/tools.ts +10 -0
@@ -1,7 +1,7 @@
1
1
  // src/agents/__tests__/AgentContext.test.ts
2
- import { HumanMessage } from '@langchain/core/messages';
2
+ import { AIMessage, HumanMessage, ToolMessage } from '@langchain/core/messages';
3
3
  import { AgentContext } from '../AgentContext';
4
- import { Providers } from '@/common';
4
+ import { Constants, Providers } from '@/common';
5
5
  import { addBedrockCacheControl } from '@/messages/cache';
6
6
  import type * as t from '@/types';
7
7
 
@@ -79,7 +79,7 @@ describe('AgentContext', () => {
79
79
  );
80
80
  });
81
81
 
82
- it('marks only stable system text for Anthropic prompt caching', async () => {
82
+ it('moves Anthropic dynamic instructions behind stable history', async () => {
83
83
  const ctx = createBasicContext({
84
84
  agentConfig: {
85
85
  provider: Providers.ANTHROPIC,
@@ -89,18 +89,39 @@ describe('AgentContext', () => {
89
89
  },
90
90
  });
91
91
 
92
- const result = await ctx.systemRunnable!.invoke([]);
92
+ const result = await ctx.systemRunnable!.invoke([
93
+ new HumanMessage('Hello'),
94
+ new HumanMessage('Second'),
95
+ ]);
93
96
  const content = result[0].content as TestSystemContentBlock[];
94
- expect(content).toHaveLength(2);
95
- expect(content[0]).toMatchObject({
96
- type: 'text',
97
- text: 'Stable instructions',
98
- cache_control: { type: 'ephemeral' },
99
- });
100
- expect(content[1]).toEqual({
101
- type: 'text',
102
- text: 'Dynamic instructions',
97
+ expect(content).toEqual([
98
+ {
99
+ type: 'text',
100
+ text: 'Stable instructions',
101
+ cache_control: { type: 'ephemeral' },
102
+ },
103
+ ]);
104
+ expect(result[1].content).toBe('Hello');
105
+ expect(result[2].content).toBe('Dynamic instructions');
106
+ expect(result[3].content).toBe('Second');
107
+ });
108
+
109
+ it('places Anthropic dynamic instructions before a single latest user prompt', async () => {
110
+ const ctx = createBasicContext({
111
+ agentConfig: {
112
+ provider: Providers.ANTHROPIC,
113
+ clientOptions: { model: 'claude-3-5-sonnet', promptCache: true },
114
+ instructions: 'Stable instructions',
115
+ additional_instructions: 'Dynamic instructions',
116
+ },
103
117
  });
118
+
119
+ const result = await ctx.systemRunnable!.invoke([
120
+ new HumanMessage('Latest'),
121
+ ]);
122
+
123
+ expect(result[1].content).toBe('Dynamic instructions');
124
+ expect(result[2].content).toBe('Latest');
104
125
  });
105
126
 
106
127
  it('omits Anthropic cache control when only dynamic system text exists', async () => {
@@ -119,7 +140,7 @@ describe('AgentContext', () => {
119
140
  expect(content[0]).not.toHaveProperty('cache_control');
120
141
  });
121
142
 
122
- it('keeps cross-run summaries in the dynamic Anthropic system tail', async () => {
143
+ it('keeps cross-run summaries in the dynamic Anthropic tail', async () => {
123
144
  const ctx = createBasicContext({
124
145
  agentConfig: {
125
146
  provider: Providers.ANTHROPIC,
@@ -131,12 +152,11 @@ describe('AgentContext', () => {
131
152
 
132
153
  const result = await ctx.systemRunnable!.invoke([]);
133
154
  const content = result[0].content as TestSystemContentBlock[];
134
- expect(content).toHaveLength(2);
155
+ expect(content).toHaveLength(1);
135
156
  expect(content[0]).toHaveProperty('cache_control');
136
- expect(content[1]).toEqual({
137
- type: 'text',
138
- text: '## Conversation Summary\n\nPrior summary',
139
- });
157
+ expect(result[1].content).toBe(
158
+ '## Conversation Summary\n\nPrior summary'
159
+ );
140
160
  });
141
161
 
142
162
  it('places the Bedrock cache point before dynamic system text', async () => {
@@ -198,7 +218,7 @@ describe('AgentContext', () => {
198
218
  );
199
219
  });
200
220
 
201
- it('marks stable OpenRouter system text and keeps first user message stable', async () => {
221
+ it('moves OpenRouter dynamic instructions behind stable history', async () => {
202
222
  const ctx = createBasicContext({
203
223
  agentConfig: {
204
224
  provider: Providers.OPENROUTER,
@@ -223,7 +243,6 @@ describe('AgentContext', () => {
223
243
  cache_control: { type: 'ephemeral' },
224
244
  },
225
245
  ]);
226
- expect(result[1]).toBeInstanceOf(HumanMessage);
227
246
  expect(result[1].content).toBe('Hello');
228
247
  expect(result[2].content).toBe('Dynamic instructions');
229
248
  expect(result[3].content).toBe('Second');
@@ -298,7 +317,7 @@ describe('AgentContext', () => {
298
317
  expect(result[3].content).toBe('Second');
299
318
  });
300
319
 
301
- it('adds OpenRouter body cache points when there is no dynamic tail', async () => {
320
+ it('keeps the first OpenRouter user message before single-turn dynamic instructions', async () => {
302
321
  const ctx = createBasicContext({
303
322
  agentConfig: {
304
323
  provider: Providers.OPENROUTER,
@@ -307,6 +326,126 @@ describe('AgentContext', () => {
307
326
  promptCache: true,
308
327
  },
309
328
  instructions: 'Stable instructions',
329
+ additional_instructions: 'Dynamic instructions',
330
+ },
331
+ });
332
+
333
+ const result = await ctx.systemRunnable!.invoke([
334
+ new HumanMessage('Latest'),
335
+ ]);
336
+
337
+ expect(result[1].content).toBe('Latest');
338
+ expect(result[2].content).toBe('Dynamic instructions');
339
+ });
340
+
341
+ it('caches stable Anthropic history before dynamic instructions', async () => {
342
+ const ctx = createBasicContext({
343
+ agentConfig: {
344
+ provider: Providers.ANTHROPIC,
345
+ clientOptions: {
346
+ model: 'claude-3-5-sonnet',
347
+ promptCache: true,
348
+ },
349
+ instructions: 'Stable instructions',
350
+ additional_instructions: 'Dynamic instructions',
351
+ },
352
+ });
353
+
354
+ const result = await ctx.systemRunnable!.invoke([
355
+ new HumanMessage('First'),
356
+ new AIMessage('Stable assistant history'),
357
+ new HumanMessage('Latest'),
358
+ ]);
359
+ const stableHistory = result[2].content as TestSystemContentBlock[];
360
+
361
+ expect(result[1].content).toBe('First');
362
+ expect(stableHistory[0]).toMatchObject({
363
+ type: 'text',
364
+ text: 'Stable assistant history',
365
+ cache_control: { type: 'ephemeral' },
366
+ });
367
+ expect(result[3].content).toBe('Dynamic instructions');
368
+ expect(result[4].content).toBe('Latest');
369
+ });
370
+
371
+ it('does not place Anthropic dynamic instructions between tool calls and results', async () => {
372
+ const ctx = createBasicContext({
373
+ agentConfig: {
374
+ provider: Providers.ANTHROPIC,
375
+ clientOptions: {
376
+ model: 'claude-3-5-sonnet',
377
+ promptCache: true,
378
+ },
379
+ instructions: 'Stable instructions',
380
+ additional_instructions: 'Dynamic instructions',
381
+ },
382
+ });
383
+
384
+ const result = await ctx.systemRunnable!.invoke([
385
+ new HumanMessage('Use the tool'),
386
+ new AIMessage({
387
+ content: '',
388
+ tool_calls: [
389
+ {
390
+ id: 'call_1',
391
+ name: 'calculator',
392
+ args: { expression: '2+2' },
393
+ type: 'tool_call',
394
+ },
395
+ ],
396
+ }),
397
+ new ToolMessage({
398
+ content: '4',
399
+ name: 'calculator',
400
+ tool_call_id: 'call_1',
401
+ }),
402
+ ]);
403
+
404
+ expect(result[1].content).toBe('Use the tool');
405
+ expect((result[2] as AIMessage).tool_calls?.[0]?.id).toBe('call_1');
406
+ expect(result[3].getType()).toBe('tool');
407
+ expect(result[4].content).toBe('Dynamic instructions');
408
+ });
409
+
410
+ it('caches stable OpenRouter history before dynamic instructions', async () => {
411
+ const ctx = createBasicContext({
412
+ agentConfig: {
413
+ provider: Providers.OPENROUTER,
414
+ clientOptions: {
415
+ model: 'anthropic/claude-haiku-4.5',
416
+ promptCache: true,
417
+ },
418
+ instructions: 'Stable instructions',
419
+ additional_instructions: 'Dynamic instructions',
420
+ },
421
+ });
422
+
423
+ const result = await ctx.systemRunnable!.invoke([
424
+ new HumanMessage('First'),
425
+ new AIMessage('Stable assistant history'),
426
+ new HumanMessage('Latest'),
427
+ ]);
428
+ const stableHistory = result[2].content as TestSystemContentBlock[];
429
+
430
+ expect(result[1].content).toBe('First');
431
+ expect(stableHistory[0]).toMatchObject({
432
+ type: 'text',
433
+ text: 'Stable assistant history',
434
+ cache_control: { type: 'ephemeral' },
435
+ });
436
+ expect(result[3].content).toBe('Dynamic instructions');
437
+ expect(result[4].content).toBe('Latest');
438
+ });
439
+
440
+ it('adds OpenRouter body cache points when there is no dynamic tail', async () => {
441
+ const ctx = createBasicContext({
442
+ agentConfig: {
443
+ provider: Providers.OPENROUTER,
444
+ clientOptions: {
445
+ model: 'google/gemini-3.1-pro-preview',
446
+ promptCache: true,
447
+ },
448
+ instructions: 'Stable instructions',
310
449
  },
311
450
  });
312
451
 
@@ -325,7 +464,7 @@ describe('AgentContext', () => {
325
464
  agentConfig: {
326
465
  provider: Providers.OPENROUTER,
327
466
  clientOptions: {
328
- model: 'anthropic/claude-haiku-4.5',
467
+ model: 'google/gemini-3.1-pro-preview',
329
468
  promptCache: true,
330
469
  },
331
470
  instructions: 'Stable instructions',
@@ -454,7 +593,7 @@ describe('AgentContext', () => {
454
593
  });
455
594
 
456
595
  describe('buildProgrammaticOnlyToolsInstructions', () => {
457
- it('includes code_execution-only tools in system message', () => {
596
+ it('includes code_execution-only tools in system message', async () => {
458
597
  const toolRegistry: t.LCToolRegistry = new Map([
459
598
  [
460
599
  'programmatic_tool',
@@ -467,11 +606,44 @@ describe('AgentContext', () => {
467
606
  ]);
468
607
 
469
608
  const ctx = createBasicContext({
470
- agentConfig: { instructions: 'Base', toolRegistry },
609
+ agentConfig: {
610
+ instructions: 'Base',
611
+ toolDefinitions: [{ name: Constants.BASH_PROGRAMMATIC_TOOL_CALLING }],
612
+ toolRegistry,
613
+ },
471
614
  });
472
615
 
473
616
  const runnable = ctx.systemRunnable;
474
617
  expect(runnable).toBeDefined();
618
+ const result = await runnable!.invoke([]);
619
+ expect(result[0].content).toContain('run_tools_with_bash');
620
+ expect(result[0].content).not.toContain('run_tools_with_code');
621
+ });
622
+
623
+ it('uses Python PTC guidance when only run_tools_with_code is available', async () => {
624
+ const toolRegistry: t.LCToolRegistry = new Map([
625
+ [
626
+ 'programmatic_tool',
627
+ {
628
+ name: 'programmatic_tool',
629
+ description: 'Only callable via code execution',
630
+ allowed_callers: ['code_execution'],
631
+ },
632
+ ],
633
+ ]);
634
+
635
+ const ctx = createBasicContext({
636
+ agentConfig: {
637
+ instructions: 'Base',
638
+ toolDefinitions: [{ name: Constants.PROGRAMMATIC_TOOL_CALLING }],
639
+ toolRegistry,
640
+ },
641
+ });
642
+
643
+ const result = await ctx.systemRunnable!.invoke([]);
644
+ expect(result[0].content).toContain('run_tools_with_code');
645
+ expect(result[0].content).toContain('Python code');
646
+ expect(result[0].content).not.toContain('run_tools_with_bash');
475
647
  });
476
648
 
477
649
  it('excludes direct-callable tools from programmatic section', () => {
@@ -707,7 +879,7 @@ describe('AgentContext', () => {
707
879
  agentConfig: {
708
880
  provider: Providers.OPENROUTER,
709
881
  clientOptions: {
710
- model: 'anthropic/claude-haiku-4.5',
882
+ model: 'google/gemini-3.1-pro-preview',
711
883
  promptCache: true,
712
884
  },
713
885
  instructions: 'Stable',
@@ -733,7 +905,7 @@ describe('AgentContext', () => {
733
905
  agentConfig: {
734
906
  provider: Providers.OPENROUTER,
735
907
  clientOptions: {
736
- model: 'anthropic/claude-haiku-4.5',
908
+ model: 'google/gemini-3.1-pro-preview',
737
909
  promptCache: true,
738
910
  },
739
911
  instructions: 'Stable instructions',
@@ -1,13 +1,18 @@
1
1
  import { expect } from '@jest/globals';
2
2
  import { HumanMessage } from '@langchain/core/messages';
3
3
  import type { UsageMetadata } from '@langchain/core/messages';
4
+ import type { ClientOptions } from '@langchain/openai';
4
5
  import type * as t from '@/types';
5
6
  import { GraphEvents, Providers } from '@/common';
6
7
  import { AgentContext } from '../AgentContext';
7
8
  import { ModelEndHandler } from '@/events';
8
9
  import { Run } from '@/run';
10
+ import type { ChatOpenRouterInput } from '@/llm/openrouter';
9
11
 
10
- type LivePromptCacheProvider = Providers.ANTHROPIC | Providers.BEDROCK;
12
+ type LivePromptCacheProvider =
13
+ | Providers.ANTHROPIC
14
+ | Providers.BEDROCK
15
+ | Providers.OPENROUTER;
11
16
 
12
17
  type PromptCacheExpectedSystemBlock =
13
18
  | { type: 'text'; text: string; cache_control?: { type: 'ephemeral' } }
@@ -15,7 +20,8 @@ type PromptCacheExpectedSystemBlock =
15
20
 
16
21
  type LivePromptCacheClientOptions =
17
22
  | t.ClientOptions
18
- | t.BedrockAnthropicClientOptions;
23
+ | t.BedrockAnthropicClientOptions
24
+ | (ChatOpenRouterInput & { configuration?: ClientOptions });
19
25
 
20
26
  export function buildStableInstructions({
21
27
  nonce,
package/src/events.ts CHANGED
@@ -90,7 +90,10 @@ export class ToolEndHandler implements t.EventHandler {
90
90
  return;
91
91
  }
92
92
 
93
- if (metadata[Constants.PROGRAMMATIC_TOOL_CALLING] === true) {
93
+ if (
94
+ metadata[Constants.PROGRAMMATIC_TOOL_CALLING] === true ||
95
+ metadata[Constants.BASH_PROGRAMMATIC_TOOL_CALLING] === true
96
+ ) {
94
97
  return;
95
98
  }
96
99
 
@@ -240,6 +240,149 @@ function isCachePoint(block: MessageContentComplex): boolean {
240
240
  return 'cachePoint' in block && !('type' in block);
241
241
  }
242
242
 
243
+ function getMessageRole(message: MessageWithContent): string | undefined {
244
+ if (message instanceof BaseMessage) {
245
+ return message.getType();
246
+ }
247
+ if ('role' in message && typeof message.role === 'string') {
248
+ return message.role;
249
+ }
250
+ return undefined;
251
+ }
252
+
253
+ function isCacheableConversationMessage(message: MessageWithContent): boolean {
254
+ const role = getMessageRole(message);
255
+ return (
256
+ role === 'human' || role === 'user' || role === 'ai' || role === 'assistant'
257
+ );
258
+ }
259
+
260
+ function isAssistantConversationMessage(message: MessageWithContent): boolean {
261
+ const role = getMessageRole(message);
262
+ return role === 'ai' || role === 'assistant';
263
+ }
264
+
265
+ function hasCacheMarker(message: MessageWithContent): boolean {
266
+ return (
267
+ Array.isArray(message.content) &&
268
+ message.content.some((block) => 'cache_control' in block)
269
+ );
270
+ }
271
+
272
+ function addCacheControlToRecentMessages<
273
+ T extends AnthropicMessage | BaseMessage,
274
+ >(
275
+ messages: T[],
276
+ maxCachePoints: number,
277
+ canUseMessage: (message: MessageWithContent) => boolean
278
+ ): T[] {
279
+ if (
280
+ !Array.isArray(messages) ||
281
+ messages.length === 0 ||
282
+ maxCachePoints <= 0
283
+ ) {
284
+ return messages;
285
+ }
286
+
287
+ const updatedMessages: T[] = [...messages];
288
+ let cachePointsAdded = 0;
289
+
290
+ for (let i = updatedMessages.length - 1; i >= 0; i--) {
291
+ const originalMessage = updatedMessages[i];
292
+ const content = originalMessage.content;
293
+ const hasArrayContent = Array.isArray(content);
294
+ const canAddCache =
295
+ cachePointsAdded < maxCachePoints && canUseMessage(originalMessage);
296
+
297
+ if (!canAddCache && !hasArrayContent) {
298
+ continue;
299
+ }
300
+
301
+ let workingContent: MessageContentComplex[];
302
+ let modified = false;
303
+
304
+ if (hasArrayContent) {
305
+ const src = content as MessageContentComplex[];
306
+ workingContent = [];
307
+ let lastNonEmptyTextIndex = -1;
308
+
309
+ for (let j = 0; j < src.length; j++) {
310
+ const block = src[j];
311
+ if (isCachePoint(block)) {
312
+ modified = true;
313
+ continue;
314
+ }
315
+
316
+ const cloned = { ...block };
317
+ if ('cache_control' in cloned) {
318
+ delete (cloned as Record<string, unknown>).cache_control;
319
+ modified = true;
320
+ }
321
+
322
+ if ('type' in cloned && cloned.type === 'text') {
323
+ const text = (cloned as { text?: string }).text;
324
+ if (text != null && text.trim() !== '') {
325
+ lastNonEmptyTextIndex = workingContent.length;
326
+ }
327
+ }
328
+ workingContent.push(cloned as MessageContentComplex);
329
+ }
330
+
331
+ if (canAddCache && lastNonEmptyTextIndex >= 0) {
332
+ (
333
+ workingContent[lastNonEmptyTextIndex] as Anthropic.TextBlockParam
334
+ ).cache_control = {
335
+ type: 'ephemeral',
336
+ };
337
+ cachePointsAdded++;
338
+ modified = true;
339
+ }
340
+
341
+ if (!modified) {
342
+ continue;
343
+ }
344
+ } else if (
345
+ typeof content === 'string' &&
346
+ content.trim() !== '' &&
347
+ canAddCache
348
+ ) {
349
+ workingContent = [
350
+ { type: 'text', text: content, cache_control: { type: 'ephemeral' } },
351
+ ] as unknown as MessageContentComplex[];
352
+ cachePointsAdded++;
353
+ } else {
354
+ continue;
355
+ }
356
+
357
+ updatedMessages[i] = cloneMessage(
358
+ originalMessage as MessageWithContent,
359
+ workingContent
360
+ ) as T;
361
+ }
362
+
363
+ return updatedMessages;
364
+ }
365
+
366
+ export function addCacheControlToStablePrefixMessages<
367
+ T extends AnthropicMessage | BaseMessage,
368
+ >(messages: T[], maxCachePoints: number): T[] {
369
+ const assistantMarked = addCacheControlToRecentMessages(
370
+ messages,
371
+ maxCachePoints,
372
+ isAssistantConversationMessage
373
+ );
374
+
375
+ if (assistantMarked.some(hasCacheMarker)) {
376
+ return assistantMarked;
377
+ }
378
+
379
+ return addCacheControlToRecentMessages(
380
+ messages,
381
+ maxCachePoints,
382
+ isCacheableConversationMessage
383
+ );
384
+ }
385
+
243
386
  /**
244
387
  * Checks if a message's content has Anthropic cache_control fields.
245
388
  */
@@ -3,7 +3,12 @@ import fetch, { RequestInit } from 'node-fetch';
3
3
  import { HttpsProxyAgent } from 'https-proxy-agent';
4
4
  import { tool, DynamicStructuredTool } from '@langchain/core/tools';
5
5
  import type * as t from '@/types';
6
- import { emptyOutputMessage, getCodeBaseURL } from './CodeExecutor';
6
+ import {
7
+ emptyOutputMessage,
8
+ buildCodeApiHttpErrorMessage,
9
+ getCodeBaseURL,
10
+ resolveCodeApiAuthHeaders,
11
+ } from './CodeExecutor';
7
12
  import { Constants } from '@/common';
8
13
 
9
14
  config();
@@ -104,6 +109,7 @@ function createBashExecutionTool(
104
109
  ): DynamicStructuredTool {
105
110
  return tool(
106
111
  async (rawInput, config) => {
112
+ const { authHeaders, ...executionParams } = params ?? {};
107
113
  const { command, ...rest } = rawInput as {
108
114
  command: string;
109
115
  args?: string[];
@@ -117,7 +123,7 @@ function createBashExecutionTool(
117
123
  lang: 'bash',
118
124
  code: command,
119
125
  ...rest,
120
- ...params,
126
+ ...executionParams,
121
127
  };
122
128
 
123
129
  /* See `CodeExecutor.ts` for the rationale — `/files/<session_id>`
@@ -137,11 +143,14 @@ function createBashExecutionTool(
137
143
  }
138
144
 
139
145
  try {
146
+ const resolvedAuthHeaders =
147
+ await resolveCodeApiAuthHeaders(authHeaders);
140
148
  const fetchOptions: RequestInit = {
141
149
  method: 'POST',
142
150
  headers: {
143
151
  'Content-Type': 'application/json',
144
152
  'User-Agent': 'LibreChat/1.0',
153
+ ...resolvedAuthHeaders,
145
154
  },
146
155
  body: JSON.stringify(postData),
147
156
  };
@@ -151,7 +160,9 @@ function createBashExecutionTool(
151
160
  }
152
161
  const response = await fetch(EXEC_ENDPOINT, fetchOptions);
153
162
  if (!response.ok) {
154
- throw new Error(`HTTP error! status: ${response.status}`);
163
+ throw new Error(
164
+ await buildCodeApiHttpErrorMessage('POST', EXEC_ENDPOINT, response)
165
+ );
155
166
  }
156
167
 
157
168
  const result: t.ExecuteResult = await response.json();
@@ -312,7 +312,8 @@ export function createBashProgrammaticToolCallingTool(
312
312
  timeout,
313
313
  ...(files && files.length > 0 ? { files } : {}),
314
314
  },
315
- proxy
315
+ proxy,
316
+ initParams.authHeaders
316
317
  );
317
318
 
318
319
  // ====================================================================
@@ -339,7 +340,8 @@ export function createBashProgrammaticToolCallingTool(
339
340
 
340
341
  const toolResults = await executeTools(
341
342
  response.tool_calls ?? [],
342
- toolMap
343
+ toolMap,
344
+ Constants.BASH_PROGRAMMATIC_TOOL_CALLING
343
345
  );
344
346
 
345
347
  response = await makeRequest(
@@ -348,7 +350,8 @@ export function createBashProgrammaticToolCallingTool(
348
350
  continuation_token: response.continuation_token,
349
351
  tool_results: toolResults,
350
352
  },
351
- proxy
353
+ proxy,
354
+ initParams.authHeaders
352
355
  );
353
356
  }
354
357
 
@@ -70,6 +70,34 @@ const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
70
70
 
71
71
  type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
72
72
 
73
+ export async function resolveCodeApiAuthHeaders(
74
+ authHeaders?: t.CodeApiAuthHeaders
75
+ ): Promise<t.CodeApiAuthHeaderMap> {
76
+ if (authHeaders == null) {
77
+ return {};
78
+ }
79
+ if (typeof authHeaders === 'function') {
80
+ return authHeaders();
81
+ }
82
+ return authHeaders;
83
+ }
84
+
85
+ export async function buildCodeApiHttpErrorMessage(
86
+ method: string,
87
+ endpoint: string,
88
+ response: { status: number; text: () => Promise<string> }
89
+ ): Promise<string> {
90
+ let responseBody = '';
91
+ try {
92
+ responseBody = await response.text();
93
+ } catch {
94
+ responseBody = '';
95
+ }
96
+ const body = responseBody.trim();
97
+ const bodySuffix = body === '' ? '' : `, body: ${body.slice(0, 1000)}`;
98
+ return `CodeAPI request failed: ${method} ${endpoint} returned ${response.status}${bodySuffix}`;
99
+ }
100
+
73
101
  export const CodeExecutionToolDescription = `
74
102
  Runs code and returns stdout/stderr output from a stateless execution environment, similar to running scripts in a command-line interface. Each execution is isolated and independent.
75
103
 
@@ -92,6 +120,7 @@ function createCodeExecutionTool(
92
120
  ): DynamicStructuredTool {
93
121
  return tool(
94
122
  async (rawInput, config) => {
123
+ const { authHeaders, ...executionParams } = params ?? {};
95
124
  const { lang, code, ...rest } = rawInput as {
96
125
  lang: SupportedLanguage;
97
126
  code: string;
@@ -111,7 +140,7 @@ function createCodeExecutionTool(
111
140
  lang,
112
141
  code,
113
142
  ...rest,
114
- ...params,
143
+ ...executionParams,
115
144
  };
116
145
 
117
146
  /* File injection: `_injected_files` from ToolNode (set when host
@@ -135,11 +164,14 @@ function createCodeExecutionTool(
135
164
  }
136
165
 
137
166
  try {
167
+ const resolvedAuthHeaders =
168
+ await resolveCodeApiAuthHeaders(authHeaders);
138
169
  const fetchOptions: RequestInit = {
139
170
  method: 'POST',
140
171
  headers: {
141
172
  'Content-Type': 'application/json',
142
173
  'User-Agent': 'LibreChat/1.0',
174
+ ...resolvedAuthHeaders,
143
175
  },
144
176
  body: JSON.stringify(postData),
145
177
  };
@@ -149,7 +181,9 @@ function createCodeExecutionTool(
149
181
  }
150
182
  const response = await fetch(EXEC_ENDPOINT, fetchOptions);
151
183
  if (!response.ok) {
152
- throw new Error(`HTTP error! status: ${response.status}`);
184
+ throw new Error(
185
+ await buildCodeApiHttpErrorMessage('POST', EXEC_ENDPOINT, response)
186
+ );
153
187
  }
154
188
 
155
189
  const result: t.ExecuteResult = await response.json();