@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.
- package/dist/cjs/agents/AgentContext.cjs +69 -24
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/events.cjs +2 -1
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/main.cjs +3 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +96 -0
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +5 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +3 -3
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +28 -2
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +107 -34
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +3 -4
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +71 -26
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/events.mjs +2 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/messages/cache.mjs +96 -1
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +6 -3
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +3 -3
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +27 -3
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +108 -35
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +3 -4
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +7 -3
- package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +6 -2
- package/dist/types/messages/cache.d.ts +1 -0
- package/dist/types/tools/CodeExecutor.d.ts +5 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +14 -3
- package/dist/types/types/tools.d.ts +6 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +102 -30
- package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +0 -4
- package/src/agents/__tests__/AgentContext.openrouter.live.test.ts +128 -0
- package/src/agents/__tests__/AgentContext.test.ts +199 -27
- package/src/agents/__tests__/promptCacheLiveHelpers.ts +8 -2
- package/src/events.ts +4 -1
- package/src/messages/cache.ts +143 -0
- package/src/tools/BashExecutor.ts +14 -3
- package/src/tools/BashProgrammaticToolCalling.ts +6 -3
- package/src/tools/CodeExecutor.ts +36 -2
- package/src/tools/ProgrammaticToolCalling.ts +175 -30
- package/src/tools/ToolNode.ts +3 -4
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +321 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +31 -1
- 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('
|
|
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).
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
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(
|
|
155
|
+
expect(content).toHaveLength(1);
|
|
135
156
|
expect(content[0]).toHaveProperty('cache_control');
|
|
136
|
-
expect(
|
|
137
|
-
|
|
138
|
-
|
|
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('
|
|
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('
|
|
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: '
|
|
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: {
|
|
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: '
|
|
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: '
|
|
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 =
|
|
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 (
|
|
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
|
|
package/src/messages/cache.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
...
|
|
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(
|
|
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
|
-
...
|
|
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(
|
|
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();
|