🎬 fix: Code Session Context In Event Driven Mode (#11673)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run

* fix: Update parseTextParts to handle undefined content parts

- Modified the parseTextParts function to accept an array of content parts that may include undefined values.
- Implemented optional chaining to safely check for the type of each part, preventing potential runtime errors when accessing properties of undefined elements.

* refactor: Tool Call Configuration with Session Context

- Added support for including session ID and injected files in the tool call configuration when a code session context is present.
- Improved handling of tool call configurations to accommodate additional context data, enhancing the functionality of the tool execution handler.

* chore: Update @librechat/agents to version 3.1.37 in package.json and package-lock.json

* test: Add unit tests for createToolExecuteHandler

- Introduced a new test suite for the createToolExecuteHandler function, validating the handling of session context in tool calls.
- Added tests to ensure correct passing of session IDs and injected files based on the presence of codeSessionContext.
- Included scenarios for handling multiple tool calls and ensuring non-code execution tools are unaffected by session context.

* test: Update createToolExecuteHandler tests for session context handling

- Renamed test to clarify that it checks for the absence of session context in non-code-execution tools.
- Updated assertions to ensure that session_id and _injected_files are undefined when non-code-execution tools are invoked, enhancing test accuracy.
This commit is contained in:
Danny Avila 2026-02-07 03:09:55 -05:00 committed by GitHub
parent 968e97b4d2
commit a771d70b10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 294 additions and 17 deletions

View file

@ -1,6 +1,8 @@
import { replaceSpecialVars, parseCompactConvo } from '../src/parsers';
import { replaceSpecialVars, parseCompactConvo, parseTextParts } from '../src/parsers';
import { specialVariables } from '../src/config';
import { EModelEndpoint } from '../src/schemas';
import { ContentTypes } from '../src/types/runs';
import type { TMessageContentParts } from '../src/types/assistants';
import type { TUser, TConversation } from '../src/types';
// Mock dayjs module with consistent date/time values regardless of environment
@ -141,7 +143,7 @@ describe('parseCompactConvo', () => {
});
expect(result).not.toBeNull();
expect(result?.iconURL).toBeUndefined();
expect(result?.['iconURL']).toBeUndefined();
expect(result?.model).toBe('gpt-4');
});
@ -159,7 +161,7 @@ describe('parseCompactConvo', () => {
});
expect(result).not.toBeNull();
expect(result?.iconURL).toBeUndefined();
expect(result?.['iconURL']).toBeUndefined();
expect(result?.agent_id).toBe('agent_123');
});
@ -177,7 +179,7 @@ describe('parseCompactConvo', () => {
});
expect(result).not.toBeNull();
expect(result?.iconURL).toBeUndefined();
expect(result?.['iconURL']).toBeUndefined();
expect(result?.model).toBe('claude-3-opus');
});
@ -195,7 +197,7 @@ describe('parseCompactConvo', () => {
});
expect(result).not.toBeNull();
expect(result?.iconURL).toBeUndefined();
expect(result?.['iconURL']).toBeUndefined();
expect(result?.model).toBe('gemini-pro');
});
@ -213,7 +215,7 @@ describe('parseCompactConvo', () => {
});
expect(result).not.toBeNull();
expect(result?.iconURL).toBeUndefined();
expect(result?.['iconURL']).toBeUndefined();
expect(result?.assistant_id).toBe('asst_123');
});
@ -234,7 +236,7 @@ describe('parseCompactConvo', () => {
});
expect(result).not.toBeNull();
expect(result?.iconURL).toBeUndefined();
expect(result?.['iconURL']).toBeUndefined();
expect(result?.model).toBe('gpt-4');
expect(result?.temperature).toBe(0.7);
expect(result?.top_p).toBe(0.9);
@ -254,8 +256,94 @@ describe('parseCompactConvo', () => {
});
expect(result).not.toBeNull();
expect(result?.iconURL).toBeUndefined();
expect(result?.['iconURL']).toBeUndefined();
expect(result?.model).toBe('gpt-4');
});
});
});
describe('parseTextParts', () => {
test('should concatenate text parts', () => {
const parts: TMessageContentParts[] = [
{ type: ContentTypes.TEXT, text: 'Hello' },
{ type: ContentTypes.TEXT, text: 'World' },
];
expect(parseTextParts(parts)).toBe('Hello World');
});
test('should handle text parts with object-style text values', () => {
const parts: TMessageContentParts[] = [
{ type: ContentTypes.TEXT, text: { value: 'structured text' } },
];
expect(parseTextParts(parts)).toBe('structured text');
});
test('should include think parts by default', () => {
const parts: TMessageContentParts[] = [
{ type: ContentTypes.TEXT, text: 'Answer:' },
{ type: ContentTypes.THINK, think: 'reasoning step' },
];
expect(parseTextParts(parts)).toBe('Answer: reasoning step');
});
test('should skip think parts when skipReasoning is true', () => {
const parts: TMessageContentParts[] = [
{ type: ContentTypes.THINK, think: 'internal reasoning' },
{ type: ContentTypes.TEXT, text: 'visible answer' },
];
expect(parseTextParts(parts, true)).toBe('visible answer');
});
test('should skip non-text/think part types', () => {
const parts: TMessageContentParts[] = [
{ type: ContentTypes.TEXT, text: 'before' },
{ type: ContentTypes.IMAGE_FILE } as TMessageContentParts,
{ type: ContentTypes.TEXT, text: 'after' },
];
expect(parseTextParts(parts)).toBe('before after');
});
test('should handle undefined elements in the content parts array', () => {
const parts: Array<TMessageContentParts | undefined> = [
{ type: ContentTypes.TEXT, text: 'first' },
undefined,
{ type: ContentTypes.TEXT, text: 'third' },
];
expect(parseTextParts(parts)).toBe('first third');
});
test('should handle multiple consecutive undefined elements', () => {
const parts: Array<TMessageContentParts | undefined> = [
undefined,
undefined,
{ type: ContentTypes.TEXT, text: 'only text' },
undefined,
];
expect(parseTextParts(parts)).toBe('only text');
});
test('should handle an array of all undefined elements', () => {
const parts: Array<TMessageContentParts | undefined> = [undefined, undefined, undefined];
expect(parseTextParts(parts)).toBe('');
});
test('should handle parts with missing type property', () => {
const parts: Array<TMessageContentParts | undefined> = [
{ text: 'no type field' } as unknown as TMessageContentParts,
{ type: ContentTypes.TEXT, text: 'valid' },
];
expect(parseTextParts(parts)).toBe('valid');
});
test('should return empty string for empty array', () => {
expect(parseTextParts([])).toBe('');
});
test('should not add extra spaces when parts already have spacing', () => {
const parts: TMessageContentParts[] = [
{ type: ContentTypes.TEXT, text: 'Hello ' },
{ type: ContentTypes.TEXT, text: 'World' },
];
expect(parseTextParts(parts)).toBe('Hello World');
});
});

View file

@ -349,13 +349,13 @@ export const parseCompactConvo = ({
};
export function parseTextParts(
contentParts: a.TMessageContentParts[],
contentParts: Array<a.TMessageContentParts | undefined>,
skipReasoning: boolean = false,
): string {
let result = '';
for (const part of contentParts) {
if (!part.type) {
if (!part?.type) {
continue;
}
if (part.type === ContentTypes.TEXT) {