mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
📌 fix: Stabilize Agent Prompt Cache Prefix (#12907)
* fix: stabilize agent prompt cache prefix * chore: refresh agents sdk lockfile integrity * test: format agent memory assertion * test: type agent context fixtures * fix: preserve MCP instruction precedence * fix: reuse resolved conversation anchor * fix: keep resumable startup immediate
This commit is contained in:
parent
5b5e2b0286
commit
f3e1201ae7
23 changed files with 564 additions and 122 deletions
|
|
@ -813,11 +813,28 @@ class BaseClient {
|
|||
endpointType: options.endpointType,
|
||||
...endpointOptions,
|
||||
};
|
||||
const conversationCreatedAt = options?.req?.conversationCreatedAt;
|
||||
const createdAtOnInsert =
|
||||
conversationCreatedAt != null ? new Date(conversationCreatedAt) : undefined;
|
||||
const validCreatedAtOnInsert =
|
||||
createdAtOnInsert && !Number.isNaN(createdAtOnInsert.getTime())
|
||||
? createdAtOnInsert
|
||||
: undefined;
|
||||
|
||||
const existingConvo =
|
||||
this.fetchedConvo === true
|
||||
? null
|
||||
: await db.getConvo(options?.req?.user?.id, message.conversationId);
|
||||
const req = options?.req;
|
||||
const skippedExistingConvoLookup = this.fetchedConvo === true;
|
||||
const hasResolvedConversation =
|
||||
req != null && Object.prototype.hasOwnProperty.call(req, 'resolvedConversation');
|
||||
let existingConvo = null;
|
||||
if (!skippedExistingConvoLookup && hasResolvedConversation) {
|
||||
existingConvo = req.resolvedConversation;
|
||||
} else if (!skippedExistingConvoLookup) {
|
||||
existingConvo = await db.getConvo(req?.user?.id, message.conversationId);
|
||||
}
|
||||
if (hasResolvedConversation) {
|
||||
delete req.resolvedConversation;
|
||||
}
|
||||
const shouldSetCreatedAtOnInsert = !skippedExistingConvoLookup && existingConvo == null;
|
||||
|
||||
const unsetFields = {};
|
||||
const exceptions = new Set(['spec', 'iconURL']);
|
||||
|
|
@ -847,6 +864,7 @@ class BaseClient {
|
|||
const conversation = await db.saveConvo(reqCtx, fieldsToKeep, {
|
||||
context: 'api/app/clients/BaseClient.js - saveMessageToDatabase #saveConvo',
|
||||
unsetFields,
|
||||
createdAtOnInsert: shouldSetCreatedAtOnInsert ? validCreatedAtOnInsert : undefined,
|
||||
});
|
||||
|
||||
return { message: savedMessage, conversation };
|
||||
|
|
|
|||
|
|
@ -952,6 +952,45 @@ describe('BaseClient', () => {
|
|||
saveConvo.mockReset();
|
||||
});
|
||||
|
||||
test('saveMessageToDatabase reuses conversation resolved on the request', async () => {
|
||||
const existingConvo = {
|
||||
conversationId: 'cached-convo-id',
|
||||
endpoint: 'openai',
|
||||
endpointType: 'openai',
|
||||
temperature: 0.7,
|
||||
};
|
||||
const user = { id: 'user-id' };
|
||||
const req = { user, resolvedConversation: existingConvo };
|
||||
|
||||
getConvo.mockClear();
|
||||
saveMessage.mockResolvedValue({ messageId: 'msg-1' });
|
||||
saveConvo.mockResolvedValue(existingConvo);
|
||||
|
||||
TestClient = initializeFakeClient(apiKey, { ...options, endpoint: 'openai', req }, []);
|
||||
|
||||
await TestClient.saveMessageToDatabase(
|
||||
{
|
||||
messageId: 'msg-1',
|
||||
conversationId: existingConvo.conversationId,
|
||||
isCreatedByUser: true,
|
||||
text: 'hi',
|
||||
},
|
||||
{ endpoint: 'openai' },
|
||||
user,
|
||||
);
|
||||
|
||||
expect(getConvo).not.toHaveBeenCalled();
|
||||
expect(req).not.toHaveProperty('resolvedConversation');
|
||||
expect(TestClient.fetchedConvo).toBe(true);
|
||||
expect(saveConvo).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
expect.objectContaining({ conversationId: existingConvo.conversationId }),
|
||||
expect.objectContaining({
|
||||
unsetFields: expect.objectContaining({ temperature: 1 }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('userMessagePromise is awaited before saving response message', async () => {
|
||||
// Mock the saveMessageToDatabase method
|
||||
TestClient.saveMessageToDatabase = jest.fn().mockImplementation(() => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const {
|
|||
loadWebSearchAuth,
|
||||
buildImageToolContext,
|
||||
buildWebSearchContext,
|
||||
buildWebSearchDynamicContext,
|
||||
} = require('@librechat/api');
|
||||
const {
|
||||
Tools,
|
||||
|
|
@ -150,7 +151,7 @@ const getAuthFields = (toolKey) => {
|
|||
* @param {AppConfig['webSearch']} [params.webSearch]
|
||||
* @param {AppConfig['fileStrategy']} [params.fileStrategy]
|
||||
* @param {AppConfig['imageOutputType']} [params.imageOutputType]
|
||||
* @returns {Promise<{ loadedTools: Tool[], toolContextMap: Object<string, any> } | Record<string,Tool>>}
|
||||
* @returns {Promise<{ loadedTools: Tool[], toolContextMap: Object<string, any>, dynamicToolContextMap?: Object<string, any> } | Record<string,Tool>>}
|
||||
*/
|
||||
const loadTools = async ({
|
||||
user,
|
||||
|
|
@ -180,7 +181,7 @@ const loadTools = async ({
|
|||
};
|
||||
|
||||
const customConstructors = {
|
||||
image_gen_oai: async (toolContextMap) => {
|
||||
image_gen_oai: async (_toolContextMap, dynamicToolContextMap) => {
|
||||
const authFields = getAuthFields('image_gen_oai');
|
||||
const authValues = await loadAuthValues({ userId: user, authFields });
|
||||
const imageFiles = options.tool_resources?.[EToolResources.image_edit]?.files ?? [];
|
||||
|
|
@ -190,7 +191,7 @@ const loadTools = async ({
|
|||
contextDescription: 'image editing',
|
||||
});
|
||||
if (toolContext) {
|
||||
toolContextMap.image_edit_oai = toolContext;
|
||||
dynamicToolContextMap.image_edit_oai = toolContext;
|
||||
}
|
||||
return createOpenAIImageTools({
|
||||
...authValues,
|
||||
|
|
@ -201,7 +202,7 @@ const loadTools = async ({
|
|||
imageFiles,
|
||||
});
|
||||
},
|
||||
gemini_image_gen: async (toolContextMap) => {
|
||||
gemini_image_gen: async (_toolContextMap, dynamicToolContextMap) => {
|
||||
const authFields = getAuthFields('gemini_image_gen');
|
||||
const authValues = await loadAuthValues({ userId: user, authFields, throwError: false });
|
||||
const imageFiles = options.tool_resources?.[EToolResources.image_edit]?.files ?? [];
|
||||
|
|
@ -211,7 +212,7 @@ const loadTools = async ({
|
|||
contextDescription: 'image context',
|
||||
});
|
||||
if (toolContext) {
|
||||
toolContextMap.gemini_image_gen = toolContext;
|
||||
dynamicToolContextMap.gemini_image_gen = toolContext;
|
||||
}
|
||||
return createGeminiImageTool({
|
||||
...authValues,
|
||||
|
|
@ -249,6 +250,8 @@ const loadTools = async ({
|
|||
|
||||
/** @type {Record<string, string>} */
|
||||
const toolContextMap = {};
|
||||
/** @type {Record<string, string>} */
|
||||
const dynamicToolContextMap = {};
|
||||
/**
|
||||
* @type {import('@librechat/agents').CodeEnvFile[] | undefined}
|
||||
* Captured by the `execute_code` factory when files are primed. Surfaced
|
||||
|
|
@ -274,7 +277,7 @@ const loadTools = async ({
|
|||
agentId: agent?.id,
|
||||
});
|
||||
if (toolContext) {
|
||||
toolContextMap[tool] = toolContext;
|
||||
dynamicToolContextMap[tool] = toolContext;
|
||||
}
|
||||
if (files?.length) {
|
||||
primedCodeFiles = files;
|
||||
|
|
@ -289,7 +292,7 @@ const loadTools = async ({
|
|||
agentId: agent?.id,
|
||||
});
|
||||
if (toolContext) {
|
||||
toolContextMap[tool] = toolContext;
|
||||
dynamicToolContextMap[tool] = toolContext;
|
||||
}
|
||||
|
||||
/** @type {boolean | undefined} Check if user has FILE_CITATIONS permission */
|
||||
|
|
@ -325,6 +328,9 @@ const loadTools = async ({
|
|||
const { onSearchResults, onGetHighlights } = options?.[Tools.web_search] ?? {};
|
||||
requestedTools[tool] = async () => {
|
||||
toolContextMap[tool] = buildWebSearchContext();
|
||||
dynamicToolContextMap[tool] = buildWebSearchDynamicContext(
|
||||
options.req?.conversationCreatedAt,
|
||||
);
|
||||
return createSearchTool({
|
||||
...result.authResult,
|
||||
onSearchResults,
|
||||
|
|
@ -374,7 +380,7 @@ const loadTools = async ({
|
|||
if (!requestedTools[toolKey]) {
|
||||
let cached;
|
||||
requestedTools[toolKey] = async () => {
|
||||
cached ??= customConstructors[toolKey](toolContextMap);
|
||||
cached ??= customConstructors[toolKey](toolContextMap, dynamicToolContextMap);
|
||||
return cached;
|
||||
};
|
||||
}
|
||||
|
|
@ -486,7 +492,7 @@ const loadTools = async ({
|
|||
}
|
||||
}
|
||||
loadedTools.push(...(await Promise.all(mcpToolPromises)).flatMap((plugin) => plugin || []));
|
||||
return { loadedTools, toolContextMap, primedCodeFiles };
|
||||
return { loadedTools, toolContextMap, dynamicToolContextMap, primedCodeFiles };
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
"@google/genai": "^1.19.0",
|
||||
"@keyv/redis": "^4.3.3",
|
||||
"@langchain/core": "^0.3.80",
|
||||
"@librechat/agents": "^3.1.74",
|
||||
"@librechat/agents": "^3.1.75",
|
||||
"@librechat/api": "*",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||
|
|
|
|||
|
|
@ -246,25 +246,19 @@ class AgentClient extends BaseClient {
|
|||
/** @type {number | undefined} */
|
||||
let promptTokens;
|
||||
|
||||
/**
|
||||
* Extract base instructions for all agents (combines instructions + additional_instructions).
|
||||
* This must be done before applying context to preserve the original agent configuration.
|
||||
*/
|
||||
const extractBaseInstructions = (agent) => {
|
||||
const baseInstructions = [agent.instructions ?? '', agent.additional_instructions ?? '']
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
.trim();
|
||||
agent.instructions = baseInstructions;
|
||||
/** Normalize instruction fields before applying per-run context. */
|
||||
const normalizeInstructions = (agent) => {
|
||||
agent.instructions = agent.instructions?.trim() || undefined;
|
||||
agent.additional_instructions = agent.additional_instructions?.trim() || undefined;
|
||||
return agent;
|
||||
};
|
||||
|
||||
/** Collect all agents for unified processing, extracting base instructions during collection */
|
||||
/** Collect all agents for unified processing while preserving stable/dynamic instruction fields. */
|
||||
const allAgents = [
|
||||
{ agent: extractBaseInstructions(this.options.agent), agentId: this.options.agent.id },
|
||||
{ agent: normalizeInstructions(this.options.agent), agentId: this.options.agent.id },
|
||||
...(this.agentConfigs?.size > 0
|
||||
? Array.from(this.agentConfigs.entries()).map(([agentId, agent]) => ({
|
||||
agent: extractBaseInstructions(agent),
|
||||
agent: normalizeInstructions(agent),
|
||||
agentId,
|
||||
}))
|
||||
: []),
|
||||
|
|
@ -425,7 +419,8 @@ class AgentClient extends BaseClient {
|
|||
|
||||
/**
|
||||
* Apply context to all agents.
|
||||
* Each agent gets: run context + their own base instructions + their own MCP instructions.
|
||||
* Stable agent/MCP instructions stay on `instructions`; shared runtime context
|
||||
* is appended to `additional_instructions` as the dynamic system tail.
|
||||
*
|
||||
* NOTE: This intentionally mutates agent objects in place. The agentConfigs Map
|
||||
* holds references to config objects that will be passed to the graph runtime.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,12 @@ jest.mock('@librechat/api', () => ({
|
|||
checkAccess: jest.fn(),
|
||||
initializeAgent: jest.fn(),
|
||||
createMemoryProcessor: jest.fn(),
|
||||
isMemoryAgentEnabled: jest.fn((config) => {
|
||||
if (!config || config.disabled === true) return false;
|
||||
const agent = config.agent;
|
||||
if (agent?.enabled !== true) return false;
|
||||
return Boolean(agent.id || (agent.provider && agent.model));
|
||||
}),
|
||||
loadAgent: jest.fn(),
|
||||
}));
|
||||
|
||||
|
|
@ -1965,13 +1971,16 @@ describe('AgentClient - titleConvo', () => {
|
|||
expect(client.useMemory).toHaveBeenCalled();
|
||||
|
||||
expect(client.options.agent.instructions).toContain('Primary agent instructions');
|
||||
expect(client.options.agent.instructions).toContain(memoryContent);
|
||||
expect(client.options.agent.instructions).not.toContain(memoryContent);
|
||||
expect(client.options.agent.additional_instructions).toContain(memoryContent);
|
||||
|
||||
expect(parallelAgent1.instructions).toContain('Parallel agent 1 instructions');
|
||||
expect(parallelAgent1.instructions).not.toContain(memoryContent);
|
||||
expect(parallelAgent1.additional_instructions ?? '').not.toContain(memoryContent);
|
||||
|
||||
expect(parallelAgent2.instructions).toContain('Parallel agent 2 instructions');
|
||||
expect(parallelAgent2.instructions).not.toContain(memoryContent);
|
||||
expect(parallelAgent2.additional_instructions ?? '').not.toContain(memoryContent);
|
||||
});
|
||||
|
||||
it('should pass memory context to parallel agents when automatic memory updates are enabled', async () => {
|
||||
|
|
@ -2006,9 +2015,13 @@ describe('AgentClient - titleConvo', () => {
|
|||
additional_instructions: null,
|
||||
});
|
||||
|
||||
expect(client.options.agent.instructions).toContain(memoryContent);
|
||||
expect(client.options.agent.instructions).toContain('Primary agent instructions');
|
||||
expect(client.options.agent.instructions).not.toContain(memoryContent);
|
||||
expect(client.options.agent.additional_instructions).toContain(memoryContent);
|
||||
|
||||
expect(parallelAgent.instructions).toContain('Parallel agent instructions');
|
||||
expect(parallelAgent.instructions).toContain(memoryContent);
|
||||
expect(parallelAgent.instructions).not.toContain(memoryContent);
|
||||
expect(parallelAgent.additional_instructions).toContain(memoryContent);
|
||||
});
|
||||
|
||||
it('should not modify parallel agents when no memory context is available', async () => {
|
||||
|
|
@ -2070,8 +2083,11 @@ describe('AgentClient - titleConvo', () => {
|
|||
additional_instructions: null,
|
||||
});
|
||||
|
||||
expect(client.options.agent.instructions).toContain(memoryContent);
|
||||
expect(client.options.agent.additional_instructions).toContain(memoryContent);
|
||||
expect(parallelAgentNoInstructions.instructions).toBeUndefined();
|
||||
expect(parallelAgentNoInstructions.additional_instructions ?? '').not.toContain(
|
||||
memoryContent,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not modify agentConfigs when none exist', async () => {
|
||||
|
|
@ -2097,7 +2113,7 @@ describe('AgentClient - titleConvo', () => {
|
|||
}),
|
||||
).resolves.not.toThrow();
|
||||
|
||||
expect(client.options.agent.instructions).toContain(memoryContent);
|
||||
expect(client.options.agent.additional_instructions).toContain(memoryContent);
|
||||
});
|
||||
|
||||
it('should handle empty agentConfigs map', async () => {
|
||||
|
|
@ -2123,7 +2139,7 @@ describe('AgentClient - titleConvo', () => {
|
|||
}),
|
||||
).resolves.not.toThrow();
|
||||
|
||||
expect(client.options.agent.instructions).toContain(memoryContent);
|
||||
expect(client.options.agent.additional_instructions).toContain(memoryContent);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const {
|
|||
const { disposeClient, clientRegistry, requestDataMap } = require('~/server/cleanup');
|
||||
const { handleAbortError } = require('~/server/middleware');
|
||||
const { logViolation } = require('~/cache');
|
||||
const { saveMessage } = require('~/models');
|
||||
const { saveMessage, getConvo } = require('~/models');
|
||||
|
||||
function createCloseHandler(abortController) {
|
||||
return function (manual) {
|
||||
|
|
@ -32,6 +32,48 @@ function createCloseHandler(abortController) {
|
|||
};
|
||||
}
|
||||
|
||||
function toValidISOString(value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
||||
}
|
||||
|
||||
async function resolveConversationCreatedAt({ userId, conversationId, isNewConvo }) {
|
||||
if (isNewConvo) {
|
||||
return { createdAt: new Date().toISOString(), conversation: undefined };
|
||||
}
|
||||
|
||||
try {
|
||||
const conversation = await getConvo(userId, conversationId);
|
||||
return {
|
||||
conversation,
|
||||
createdAt: toValidISOString(conversation?.createdAt) ?? new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
logger.warn('[AgentController] Failed to resolve conversation timestamp anchor', {
|
||||
conversationId,
|
||||
error: error?.message ?? error,
|
||||
});
|
||||
return { createdAt: new Date().toISOString(), conversation: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
async function attachConversationCreatedAt(req, { userId, conversationId, isNewConvo }) {
|
||||
req.body.conversationId = conversationId;
|
||||
const resolved = await resolveConversationCreatedAt({
|
||||
userId,
|
||||
conversationId,
|
||||
isNewConvo,
|
||||
});
|
||||
req.conversationCreatedAt = resolved.createdAt;
|
||||
if (!isNewConvo && resolved.conversation !== undefined) {
|
||||
req.resolvedConversation = resolved.conversation ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumable Agent Controller - Generation runs independently of HTTP connection.
|
||||
* Returns streamId immediately, client subscribes separately via SSE.
|
||||
|
|
@ -60,9 +102,10 @@ const ResumableAgentController = async (req, res, next, initializeClient, addTit
|
|||
|
||||
// Generate conversationId upfront if not provided - streamId === conversationId always
|
||||
// Treat "new" as a placeholder that needs a real UUID (frontend may send "new" for new convos)
|
||||
const conversationId =
|
||||
!reqConversationId || reqConversationId === 'new' ? crypto.randomUUID() : reqConversationId;
|
||||
const isNewConvo = !reqConversationId || reqConversationId === 'new';
|
||||
const conversationId = isNewConvo ? crypto.randomUUID() : reqConversationId;
|
||||
const streamId = conversationId;
|
||||
req.body.conversationId = conversationId;
|
||||
|
||||
let client = null;
|
||||
|
||||
|
|
@ -82,6 +125,8 @@ const ResumableAgentController = async (req, res, next, initializeClient, addTit
|
|||
// This is critical: tool loading (MCP OAuth) may emit events that the client needs to receive
|
||||
res.json({ streamId, conversationId, status: 'started' });
|
||||
|
||||
await attachConversationCreatedAt(req, { userId, conversationId, isNewConvo });
|
||||
|
||||
// Note: We no longer use res.on('close') to abort since we send JSON immediately.
|
||||
// The response closes normally after res.json(), which is not an abort condition.
|
||||
// Abort handling is done through GenerationJobManager via the SSE stream connection.
|
||||
|
|
@ -268,7 +313,6 @@ const ResumableAgentController = async (req, res, next, initializeClient, addTit
|
|||
|
||||
// Check abort state BEFORE calling completeJob (which triggers abort signal for cleanup)
|
||||
const wasAbortedBeforeComplete = job.abortController.signal.aborted;
|
||||
const isNewConvo = !reqConversationId || reqConversationId === 'new';
|
||||
const shouldGenerateTitle =
|
||||
addTitle &&
|
||||
parentMessageId === Constants.NO_PARENT &&
|
||||
|
|
@ -453,8 +497,8 @@ const _LegacyAgentController = async (req, res, next, initializeClient, addTitle
|
|||
|
||||
// Generate conversationId upfront if not provided - streamId === conversationId always
|
||||
// Treat "new" as a placeholder that needs a real UUID (frontend may send "new" for new convos)
|
||||
const conversationId =
|
||||
!reqConversationId || reqConversationId === 'new' ? crypto.randomUUID() : reqConversationId;
|
||||
const isNewConvo = !reqConversationId || reqConversationId === 'new';
|
||||
const conversationId = isNewConvo ? crypto.randomUUID() : reqConversationId;
|
||||
const streamId = conversationId;
|
||||
|
||||
let userMessage;
|
||||
|
|
@ -464,9 +508,10 @@ const _LegacyAgentController = async (req, res, next, initializeClient, addTitle
|
|||
let cleanupHandlers = [];
|
||||
|
||||
// Match the same logic used for conversationId generation above
|
||||
const isNewConvo = !reqConversationId || reqConversationId === 'new';
|
||||
const userId = req.user.id;
|
||||
|
||||
await attachConversationCreatedAt(req, { userId, conversationId, isNewConvo });
|
||||
|
||||
// Create handler to avoid capturing the entire parent scope
|
||||
let getReqData = (data = {}) => {
|
||||
for (let key in data) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const {
|
|||
GenerationJobManager,
|
||||
isActionDomainAllowed,
|
||||
buildWebSearchContext,
|
||||
buildWebSearchDynamicContext,
|
||||
buildImageToolContext,
|
||||
buildToolClassification,
|
||||
buildOAuthToolCallName,
|
||||
|
|
@ -489,6 +490,7 @@ async function processRequiredActions(client, requiredActions) {
|
|||
* @returns {Promise<{
|
||||
* tools?: StructuredTool[];
|
||||
* toolContextMap?: Record<string, unknown>;
|
||||
* dynamicToolContextMap?: Record<string, unknown>;
|
||||
* userMCPAuthMap?: Record<string, Record<string, string>>;
|
||||
* toolRegistry?: Map<string, import('~/utils/toolClassification').LCTool>;
|
||||
* hasDeferredTools?: boolean;
|
||||
|
|
@ -779,12 +781,17 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
|
||||
/** @type {Record<string, string>} */
|
||||
const toolContextMap = {};
|
||||
/** @type {Record<string, string>} */
|
||||
const dynamicToolContextMap = {};
|
||||
const hasWebSearch = filteredTools.includes(Tools.web_search);
|
||||
const hasFileSearch = filteredTools.includes(Tools.file_search);
|
||||
const hasExecuteCode = filteredTools.includes(Tools.execute_code);
|
||||
|
||||
if (hasWebSearch) {
|
||||
toolContextMap[Tools.web_search] = buildWebSearchContext();
|
||||
dynamicToolContextMap[Tools.web_search] = buildWebSearchDynamicContext(
|
||||
req.conversationCreatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -803,7 +810,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
agentId: agent.id,
|
||||
});
|
||||
if (toolContext) {
|
||||
toolContextMap[Tools.execute_code] = toolContext;
|
||||
dynamicToolContextMap[Tools.execute_code] = toolContext;
|
||||
}
|
||||
if (files?.length) {
|
||||
primedCodeFiles = files;
|
||||
|
|
@ -821,7 +828,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
agentId: agent.id,
|
||||
});
|
||||
if (toolContext) {
|
||||
toolContextMap[Tools.file_search] = toolContext;
|
||||
dynamicToolContextMap[Tools.file_search] = toolContext;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[loadToolDefinitionsWrapper] Error priming search files:', error);
|
||||
|
|
@ -840,7 +847,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
contextDescription: 'image editing',
|
||||
});
|
||||
if (toolContext) {
|
||||
toolContextMap.image_edit_oai = toolContext;
|
||||
dynamicToolContextMap.image_edit_oai = toolContext;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -851,7 +858,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
contextDescription: 'image context',
|
||||
});
|
||||
if (toolContext) {
|
||||
toolContextMap.gemini_image_gen = toolContext;
|
||||
dynamicToolContextMap.gemini_image_gen = toolContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -860,6 +867,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
toolRegistry,
|
||||
userMCPAuthMap,
|
||||
toolContextMap,
|
||||
dynamicToolContextMap,
|
||||
toolDefinitions,
|
||||
hasDeferredTools,
|
||||
actionsEnabled,
|
||||
|
|
@ -962,7 +970,7 @@ async function loadAgentTools({
|
|||
});
|
||||
}
|
||||
|
||||
const { loadedTools, toolContextMap, primedCodeFiles } = await loadTools({
|
||||
const { loadedTools, toolContextMap, dynamicToolContextMap, primedCodeFiles } = await loadTools({
|
||||
agent,
|
||||
signal,
|
||||
userMCPAuthMap,
|
||||
|
|
@ -1047,6 +1055,7 @@ async function loadAgentTools({
|
|||
toolRegistry,
|
||||
userMCPAuthMap,
|
||||
toolContextMap,
|
||||
dynamicToolContextMap,
|
||||
toolDefinitions,
|
||||
hasDeferredTools,
|
||||
actionsEnabled,
|
||||
|
|
@ -1064,6 +1073,7 @@ async function loadAgentTools({
|
|||
toolRegistry,
|
||||
userMCPAuthMap,
|
||||
toolContextMap,
|
||||
dynamicToolContextMap,
|
||||
toolDefinitions,
|
||||
hasDeferredTools,
|
||||
actionsEnabled,
|
||||
|
|
@ -1187,6 +1197,7 @@ async function loadAgentTools({
|
|||
return {
|
||||
toolRegistry,
|
||||
toolContextMap,
|
||||
dynamicToolContextMap,
|
||||
userMCPAuthMap,
|
||||
toolDefinitions,
|
||||
hasDeferredTools,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue