From a52c82489ecd92eca91367b6920626d396cb9bf5 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 10 Jun 2026 16:10:59 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B7=20fix:=20Reject=20Client-Supplied?= =?UTF-8?q?=20Subagent=20Configuration=20(#13660)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applyModelSpecEphemeralAgent.test.ts | 2 +- .../api/src/agents/__tests__/load.spec.ts | 37 ++++++++----------- packages/api/src/agents/added.ts | 20 +++------- packages/api/src/agents/load.ts | 6 --- packages/api/src/agents/subagents.ts | 27 -------------- packages/data-provider/src/types.ts | 3 +- 6 files changed, 23 insertions(+), 72 deletions(-) delete mode 100644 packages/api/src/agents/subagents.ts diff --git a/client/src/utils/__tests__/applyModelSpecEphemeralAgent.test.ts b/client/src/utils/__tests__/applyModelSpecEphemeralAgent.test.ts index 821df6aa87..f6e9411270 100644 --- a/client/src/utils/__tests__/applyModelSpecEphemeralAgent.test.ts +++ b/client/src/utils/__tests__/applyModelSpecEphemeralAgent.test.ts @@ -126,7 +126,7 @@ describe('applyModelSpecEphemeralAgent', () => { applyModelSpecEphemeralAgent({ convoId: null, modelSpec, updateEphemeralAgent }); const agent = updateEphemeralAgent.mock.calls[0][1] as TEphemeralAgent; - expect(agent.subagents).toBeUndefined(); + expect('subagents' in agent).toBe(false); }); }); diff --git a/packages/api/src/agents/__tests__/load.spec.ts b/packages/api/src/agents/__tests__/load.spec.ts index b2b9b6b3e9..332f278873 100644 --- a/packages/api/src/agents/__tests__/load.spec.ts +++ b/packages/api/src/agents/__tests__/load.spec.ts @@ -1,11 +1,12 @@ import mongoose from 'mongoose'; import { v4 as uuidv4 } from 'uuid'; import { MongoMemoryServer } from 'mongodb-memory-server'; +import { Constants, FileSources } from 'librechat-data-provider'; import { agentSchema, createMethods } from '@librechat/data-schemas'; -import { Constants, FileSources, MAX_SUBAGENTS } from 'librechat-data-provider'; import type { Agent as LibreChatAgent, AgentModelParameters, + TEphemeralAgent, TConversation, } from 'librechat-data-provider'; import type { LoadAgentParams, LoadAgentDeps } from '../load'; @@ -266,7 +267,9 @@ describe('loadAgent', () => { req: { user: { id: 'user123' }, body: { - ephemeralAgent: { subagents: { enabled: false, agent_ids: ['agent_tampered'] } }, + ephemeralAgent: { + subagents: { enabled: false, agent_ids: ['agent_tampered'] }, + } as unknown as TEphemeralAgent, }, config: { config: {}, @@ -294,6 +297,7 @@ describe('loadAgent', () => { expect(result?.skills_enabled).toBe(true); expect(result?.skills).toBeUndefined(); + expect(result?.subagents).toBeUndefined(); }); test('should initialize an empty allowlist for ephemeral model spec skill names', async () => { @@ -368,9 +372,8 @@ describe('loadAgent', () => { expect(result?.subagents).toEqual(subagents); }); - test('should discard oversized request subagent ids for ephemeral agents', async () => { + test('should ignore request subagents for ephemeral agents', async () => { const { EPHEMERAL_AGENT_ID } = Constants; - const oversized = Array.from({ length: MAX_SUBAGENTS + 1 }, (_, index) => `agent_${index}`); const result = await loadAgent( { @@ -378,8 +381,8 @@ describe('loadAgent', () => { user: { id: 'user123' }, body: { ephemeralAgent: { - subagents: { enabled: true, allowSelf: false, agent_ids: oversized }, - }, + subagents: { enabled: true, allowSelf: true, agent_ids: ['agent_other'] }, + } as unknown as TEphemeralAgent, }, }, agent_id: EPHEMERAL_AGENT_ID as string, @@ -389,12 +392,11 @@ describe('loadAgent', () => { deps, ); - expect(result?.subagents).toEqual({ enabled: true, allowSelf: false }); + expect(result?.subagents).toBeUndefined(); }); - test('should preserve request subagents when added agent mirrors ephemeral primary tools', async () => { + test('should ignore request subagents when added agent mirrors ephemeral primary tools', async () => { const { EPHEMERAL_AGENT_ID } = Constants; - const subagents = { enabled: true, allowSelf: true, agent_ids: [] }; const result = await loadAddedAgent( { @@ -409,7 +411,7 @@ describe('loadAgent', () => { conversation: { endpoint: 'openai', model: 'gpt-4', - ephemeralAgent: { subagents }, + ephemeralAgent: { subagents: { enabled: true, allowSelf: true, agent_ids: [] } }, } as unknown as TConversation, primaryAgent: { id: EPHEMERAL_AGENT_ID as string, tools: ['web_search'] } as LibreChatAgent, }, @@ -417,13 +419,10 @@ describe('loadAgent', () => { ); expect(result?.tools).toEqual(['web_search']); - expect(result?.subagents).toEqual(subagents); + expect(result?.subagents).toBeUndefined(); }); - test('should discard oversized request subagent ids for mirrored added agents', async () => { - const { EPHEMERAL_AGENT_ID } = Constants; - const oversized = Array.from({ length: MAX_SUBAGENTS + 1 }, (_, index) => `agent_${index}`); - + test('should ignore request subagents for added ephemeral agents', async () => { const result = await loadAddedAgent( { req: { @@ -437,17 +436,13 @@ describe('loadAgent', () => { conversation: { endpoint: 'openai', model: 'gpt-4', - ephemeralAgent: { - subagents: { enabled: true, allowSelf: false, agent_ids: oversized }, - }, + ephemeralAgent: { subagents: { enabled: true, allowSelf: true, agent_ids: [] } }, } as unknown as TConversation, - primaryAgent: { id: EPHEMERAL_AGENT_ID as string, tools: ['web_search'] } as LibreChatAgent, }, deps, ); - expect(result?.tools).toEqual(['web_search']); - expect(result?.subagents).toEqual({ enabled: true, allowSelf: false }); + expect(result?.subagents).toBeUndefined(); }); test('should enable full skill scope for added ephemeral model spec with skills true', async () => { diff --git a/packages/api/src/agents/added.ts b/packages/api/src/agents/added.ts index 8c440156c5..829f5285d3 100644 --- a/packages/api/src/agents/added.ts +++ b/packages/api/src/agents/added.ts @@ -7,15 +7,9 @@ import { appendAgentIdSuffix, encodeEphemeralAgentId, } from 'librechat-data-provider'; -import type { - Agent, - TConversation, - TModelSpec, - AgentSubagentsConfig, -} from 'librechat-data-provider'; +import type { Agent, TConversation, TModelSpec } from 'librechat-data-provider'; import type { AppConfig } from '@librechat/data-schemas'; import { getCustomEndpointConfig } from '~/app/config'; -import { sanitizeRequestSubagents } from './subagents'; const { mcp_all, mcp_delimiter } = Constants; @@ -43,11 +37,9 @@ function applyModelSpecSkills( function applyModelSpecSubagents( result: Record, modelSpec: Pick | null | undefined, - ephemeralAgent?: { subagents?: AgentSubagentsConfig }, ): void { - const subagents = modelSpec?.subagents ?? sanitizeRequestSubagents(ephemeralAgent?.subagents); - if (subagents) { - result.subagents = subagents; + if (modelSpec?.subagents) { + result.subagents = modelSpec.subagents; } } @@ -105,7 +97,6 @@ export async function loadAddedAgent( file_search?: boolean; web_search?: boolean; artifacts?: unknown; - subagents?: AgentSubagentsConfig; }; [key: string]: unknown; }; @@ -123,7 +114,6 @@ export async function loadAddedAgent( file_search?: boolean; web_search?: boolean; artifacts?: unknown; - subagents?: AgentSubagentsConfig; } | undefined; @@ -160,7 +150,7 @@ export async function loadAddedAgent( tools: [...primaryAgent.tools], }; applyModelSpecSkills(result, modelSpec); - applyModelSpecSubagents(result, modelSpec, ephemeralAgent); + applyModelSpecSubagents(result, modelSpec); return result as unknown as Agent; } @@ -254,7 +244,7 @@ export async function loadAddedAgent( if (ephemeralAgent?.artifacts != null && ephemeralAgent.artifacts) { result.artifacts = ephemeralAgent.artifacts; } - applyModelSpecSubagents(result, modelSpec, ephemeralAgent); + applyModelSpecSubagents(result, modelSpec); applyModelSpecSkills(result, modelSpec); return result as unknown as Agent; diff --git a/packages/api/src/agents/load.ts b/packages/api/src/agents/load.ts index 3d85fb2b74..83e6e2832b 100644 --- a/packages/api/src/agents/load.ts +++ b/packages/api/src/agents/load.ts @@ -14,7 +14,6 @@ import type { } from 'librechat-data-provider'; import type { AppConfig } from '@librechat/data-schemas'; import { getCustomEndpointConfig } from '~/app/config'; -import { sanitizeRequestSubagents } from './subagents'; const { mcp_all, mcp_delimiter } = Constants; type ModelParametersWithPromptPrefix = AgentModelParameters & { promptPrefix?: string | null }; @@ -138,11 +137,6 @@ export async function loadEphemeralAgent( } if (modelSpec?.subagents) { result.subagents = modelSpec.subagents; - } else { - const requestSubagents = sanitizeRequestSubagents(ephemeralAgent?.subagents); - if (requestSubagents) { - result.subagents = requestSubagents; - } } if (modelSpec && Object.prototype.hasOwnProperty.call(modelSpec, 'skills')) { if (modelSpec.skills === true) { diff --git a/packages/api/src/agents/subagents.ts b/packages/api/src/agents/subagents.ts deleted file mode 100644 index 0353397781..0000000000 --- a/packages/api/src/agents/subagents.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { MAX_SUBAGENTS } from 'librechat-data-provider'; -import type { AgentSubagentsConfig } from 'librechat-data-provider'; - -export function sanitizeRequestSubagents( - subagents?: AgentSubagentsConfig | null, -): AgentSubagentsConfig | undefined { - if (!subagents || typeof subagents !== 'object') { - return undefined; - } - - const sanitized: AgentSubagentsConfig = {}; - if (typeof subagents.enabled === 'boolean') { - sanitized.enabled = subagents.enabled; - } - if (typeof subagents.allowSelf === 'boolean') { - sanitized.allowSelf = subagents.allowSelf; - } - if ( - Array.isArray(subagents.agent_ids) && - subagents.agent_ids.length <= MAX_SUBAGENTS && - subagents.agent_ids.every((agentId) => typeof agentId === 'string') - ) { - sanitized.agent_ids = subagents.agent_ids; - } - - return Object.keys(sanitized).length > 0 ? sanitized : undefined; -} diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index a7f51a5ee6..ac069b210d 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -10,11 +10,11 @@ import type { ReasoningResponseKey, ReasoningParameterFormat, } from './schemas'; -import type { Agent, AgentSubagentsConfig } from './types/assistants'; import type { RefillIntervalUnit } from './balance'; import type { SettingDefinition } from './generate'; import type { TMinimalFeedback } from './feedback'; import type { ContentTypes } from './types/runs'; +import type { Agent } from './types/assistants'; export * from './schemas'; @@ -107,7 +107,6 @@ export type TEphemeralAgent = { execute_code?: boolean; artifacts?: string; skills?: boolean; - subagents?: AgentSubagentsConfig; }; export type TPayload = Partial &