From 9b0678da1656ea0dc8e8a97b2dd8a09dc58326ce Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 6 Apr 2025 03:28:05 -0400 Subject: [PATCH 01/75] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20refactor:=20OAuth=20?= =?UTF-8?q?Flow=20Signal,=20Type=20Safety,=20Tool=20Progress=20&=20Updated?= =?UTF-8?q?=20Packages=20(#6752)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: bump @librechat/agents and related packages * refactor: update message state for tool calls run step, in case no tool call chunks are received * fix: avoid combining finalized args createContentAggregator for tool calls * chore: bump @librechat/agents to version 2.3.99 * feat: add support for aborting flows with AbortSignal in createFlow methods * fix: improve handling of tool call arguments in useStepHandler * chore: bump @librechat/agents to version 2.4.0 * fix: update flow identifier format for OAuth login in createActionTool to allow uniqueness per run * fix: improve error message handling for aborted flows in FlowStateManager * refactor: allow possible multi-agent cross-over for oauth login * fix: add type safety for Sandpack files in ArtifactCodeEditor --- api/package.json | 10 +- api/server/services/ActionService.js | 27 +- .../Artifacts/ArtifactCodeEditor.tsx | 5 +- client/src/hooks/SSE/useStepHandler.ts | 34 +- package-lock.json | 1823 ++++++++--------- packages/mcp/src/flow/manager.ts | 35 +- 6 files changed, 953 insertions(+), 981 deletions(-) diff --git a/api/package.json b/api/package.json index 2a2c8be6de..644c0ce7a9 100644 --- a/api/package.json +++ b/api/package.json @@ -44,12 +44,12 @@ "@googleapis/youtube": "^20.0.0", "@keyv/mongo": "^2.1.8", "@keyv/redis": "^2.8.1", - "@langchain/community": "^0.3.34", - "@langchain/core": "^0.3.40", - "@langchain/google-genai": "^0.1.11", - "@langchain/google-vertexai": "^0.2.2", + "@langchain/community": "^0.3.39", + "@langchain/core": "^0.3.43", + "@langchain/google-genai": "^0.2.2", + "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.3.95", + "@librechat/agents": "^2.4.0", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", diff --git a/api/server/services/ActionService.js b/api/server/services/ActionService.js index 03157f7ad6..0dc8070f1a 100644 --- a/api/server/services/ActionService.js +++ b/api/server/services/ActionService.js @@ -191,24 +191,30 @@ async function createActionTool({ }; const flowManager = await getFlowStateManager(getLogStores); await flowManager.createFlowWithHandler( - `${identifier}:login`, + `${identifier}:oauth_login:${config.metadata.thread_id}:${config.metadata.run_id}`, 'oauth_login', async () => { sendEvent(res, { event: GraphEvents.ON_RUN_STEP_DELTA, data }); logger.debug('Sent OAuth login request to client', { action_id, identifier }); return true; }, + config?.signal, ); logger.debug('Waiting for OAuth Authorization response', { action_id, identifier }); - const result = await flowManager.createFlow(identifier, 'oauth', { - state: stateToken, - userId: req.user.id, - client_url: metadata.auth.client_url, - redirect_uri: `${process.env.DOMAIN_CLIENT}/api/actions/${action_id}/oauth/callback`, - /** Encrypted values */ - encrypted_oauth_client_id: encrypted.oauth_client_id, - encrypted_oauth_client_secret: encrypted.oauth_client_secret, - }); + const result = await flowManager.createFlow( + identifier, + 'oauth', + { + state: stateToken, + userId: req.user.id, + client_url: metadata.auth.client_url, + redirect_uri: `${process.env.DOMAIN_CLIENT}/api/actions/${action_id}/oauth/callback`, + /** Encrypted values */ + encrypted_oauth_client_id: encrypted.oauth_client_id, + encrypted_oauth_client_secret: encrypted.oauth_client_secret, + }, + config?.signal, + ); logger.debug('Received OAuth Authorization response', { action_id, identifier }); data.delta.auth = undefined; data.delta.expires_at = undefined; @@ -264,6 +270,7 @@ async function createActionTool({ `${identifier}:refresh`, 'oauth_refresh', refreshTokens, + config?.signal, ); metadata.oauth_access_token = refreshData.access_token; if (refreshData.refresh_token) { diff --git a/client/src/components/Artifacts/ArtifactCodeEditor.tsx b/client/src/components/Artifacts/ArtifactCodeEditor.tsx index af0f7bdc8e..c0b62c2916 100644 --- a/client/src/components/Artifacts/ArtifactCodeEditor.tsx +++ b/client/src/components/Artifacts/ArtifactCodeEditor.tsx @@ -5,7 +5,8 @@ import { SandpackCodeEditor, SandpackProvider as StyledProvider, } from '@codesandbox/sandpack-react'; -import { SandpackProviderProps } from '@codesandbox/sandpack-react/unstyled'; +import type { SandpackProviderProps } from '@codesandbox/sandpack-react/unstyled'; +import type { SandpackBundlerFile } from '@codesandbox/sandpack-client'; import type { CodeEditorRef } from '@codesandbox/sandpack-react'; import type { ArtifactFiles, Artifact } from '~/common'; import { useEditArtifact, useGetStartupConfig } from '~/data-provider'; @@ -66,7 +67,7 @@ const CodeEditor = ({ return; } - const currentCode = sandpack.files['/' + fileKey].code; + const currentCode = (sandpack.files['/' + fileKey] as SandpackBundlerFile | undefined)?.code; if (currentCode && artifact.content != null && currentCode.trim() !== artifact.content.trim()) { setCurrentCode(currentCode); diff --git a/client/src/hooks/SSE/useStepHandler.ts b/client/src/hooks/SSE/useStepHandler.ts index b54617d0bb..ec4d797627 100644 --- a/client/src/hooks/SSE/useStepHandler.ts +++ b/client/src/hooks/SSE/useStepHandler.ts @@ -123,11 +123,14 @@ export default function useStepHandler({ } else if (contentType === ContentTypes.TOOL_CALL && 'tool_call' in contentPart) { const existingContent = updatedContent[index] as Agents.ToolCallContent | undefined; const existingToolCall = existingContent?.tool_call; - const toolCallArgs = (contentPart.tool_call.args as unknown as string | undefined) ?? ''; - - const args = finalUpdate - ? contentPart.tool_call.args - : (existingToolCall?.args ?? '') + toolCallArgs; + const toolCallArgs = (contentPart.tool_call as Agents.ToolCall).args; + /** When args are a valid object, they are likely already invoked */ + const args = + finalUpdate || + typeof existingToolCall?.args === 'object' || + typeof toolCallArgs === 'object' + ? contentPart.tool_call.args + : (existingToolCall?.args ?? '') + (toolCallArgs ?? ''); const id = getNonEmptyValue([contentPart.tool_call.id, existingToolCall?.id]) ?? ''; const name = getNonEmptyValue([contentPart.tool_call.name, existingToolCall?.name]) ?? ''; @@ -195,12 +198,31 @@ export default function useStepHandler({ // Store tool call IDs if present if (runStep.stepDetails.type === StepTypes.TOOL_CALLS) { - runStep.stepDetails.tool_calls.forEach((toolCall) => { + let updatedResponse = { ...response }; + (runStep.stepDetails.tool_calls as Agents.ToolCall[]).forEach((toolCall) => { const toolCallId = toolCall.id ?? ''; if ('id' in toolCall && toolCallId) { toolCallIdMap.current.set(runStep.id, toolCallId); } + + const contentPart: Agents.MessageContentComplex = { + type: ContentTypes.TOOL_CALL, + tool_call: { + name: toolCall.name ?? '', + args: toolCall.args, + id: toolCallId, + }, + }; + + updatedResponse = updateContent(updatedResponse, runStep.index, contentPart); }); + + messageMap.current.set(responseMessageId, updatedResponse); + const updatedMessages = messages.map((msg) => + msg.messageId === runStep.runId ? updatedResponse : msg, + ); + + setMessages(updatedMessages); } } else if (event === 'on_agent_update') { const { agent_update } = data as Agents.AgentUpdate; diff --git a/package-lock.json b/package-lock.json index b31e67aeda..88f008db61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,12 +60,12 @@ "@googleapis/youtube": "^20.0.0", "@keyv/mongo": "^2.1.8", "@keyv/redis": "^2.8.1", - "@langchain/community": "^0.3.34", - "@langchain/core": "^0.3.40", - "@langchain/google-genai": "^0.1.11", - "@langchain/google-vertexai": "^0.2.2", + "@langchain/community": "^0.3.39", + "@langchain/core": "^0.3.43", + "@langchain/google-genai": "^0.2.2", + "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.3.95", + "@librechat/agents": "^2.4.0", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", @@ -159,555 +159,11 @@ } }, "api/node_modules/@langchain/community": { - "version": "0.3.34", - "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.34.tgz", - "integrity": "sha512-s0KVulgVIPd90s3m6XZtWrCRGQPWsY93uY62seFMmNhzcyF+I65kKnN04Nbiouthrn/YJ9HB4hW8MJAFuX6RRg==", + "version": "0.3.39", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.39.tgz", + "integrity": "sha512-8sMJD22bUNlQVoh0b8PgRSpO2Hz5zO6BuZeNY3ElgW3cNVXCoywHW7FBmqbwrygGocJQDee76RqiIN0XQunhTg==", "dependencies": { - "@langchain/openai": ">=0.2.0 <0.5.0", - "binary-extensions": "^2.2.0", - "expr-eval": "^2.0.2", - "flat": "^5.0.2", - "js-yaml": "^4.1.0", - "langchain": ">=0.2.3 <0.3.0 || >=0.3.4 <0.4.0", - "langsmith": ">=0.2.8 <0.4.0", - "uuid": "^10.0.0", - "zod": "^3.22.3", - "zod-to-json-schema": "^3.22.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@arcjet/redact": "^v1.0.0-alpha.23", - "@aws-crypto/sha256-js": "^5.0.0", - "@aws-sdk/client-bedrock-agent-runtime": "^3.749.0", - "@aws-sdk/client-bedrock-runtime": "^3.749.0", - "@aws-sdk/client-dynamodb": "^3.749.0", - "@aws-sdk/client-kendra": "^3.749.0", - "@aws-sdk/client-lambda": "^3.749.0", - "@aws-sdk/client-s3": "^3.749.0", - "@aws-sdk/client-sagemaker-runtime": "^3.749.0", - "@aws-sdk/client-sfn": "^3.749.0", - "@aws-sdk/credential-provider-node": "^3.388.0", - "@azure/search-documents": "^12.0.0", - "@azure/storage-blob": "^12.15.0", - "@browserbasehq/sdk": "*", - "@browserbasehq/stagehand": "^1.0.0", - "@clickhouse/client": "^0.2.5", - "@cloudflare/ai": "*", - "@datastax/astra-db-ts": "^1.0.0", - "@elastic/elasticsearch": "^8.4.0", - "@getmetal/metal-sdk": "*", - "@getzep/zep-cloud": "^1.0.6", - "@getzep/zep-js": "^0.9.0", - "@gomomento/sdk": "^1.51.1", - "@gomomento/sdk-core": "^1.51.1", - "@google-ai/generativelanguage": "*", - "@google-cloud/storage": "^6.10.1 || ^7.7.0", - "@gradientai/nodejs-sdk": "^1.2.0", - "@huggingface/inference": "^2.6.4", - "@huggingface/transformers": "^3.2.3", - "@ibm-cloud/watsonx-ai": "*", - "@lancedb/lancedb": "^0.12.0", - "@langchain/core": ">=0.2.21 <0.4.0", - "@layerup/layerup-security": "^1.5.12", - "@libsql/client": "^0.14.0", - "@mendable/firecrawl-js": "^1.4.3", - "@mlc-ai/web-llm": "*", - "@mozilla/readability": "*", - "@neondatabase/serverless": "*", - "@notionhq/client": "^2.2.10", - "@opensearch-project/opensearch": "*", - "@pinecone-database/pinecone": "*", - "@planetscale/database": "^1.8.0", - "@premai/prem-sdk": "^0.3.25", - "@qdrant/js-client-rest": "^1.8.2", - "@raycast/api": "^1.55.2", - "@rockset/client": "^0.9.1", - "@smithy/eventstream-codec": "^2.0.5", - "@smithy/protocol-http": "^3.0.6", - "@smithy/signature-v4": "^2.0.10", - "@smithy/util-utf8": "^2.0.0", - "@spider-cloud/spider-client": "^0.0.21", - "@supabase/supabase-js": "^2.45.0", - "@tensorflow-models/universal-sentence-encoder": "*", - "@tensorflow/tfjs-converter": "*", - "@tensorflow/tfjs-core": "*", - "@upstash/ratelimit": "^1.1.3 || ^2.0.3", - "@upstash/redis": "^1.20.6", - "@upstash/vector": "^1.1.1", - "@vercel/kv": "*", - "@vercel/postgres": "*", - "@writerai/writer-sdk": "^0.40.2", - "@xata.io/client": "^0.28.0", - "@zilliz/milvus2-sdk-node": ">=2.3.5", - "apify-client": "^2.7.1", - "assemblyai": "^4.6.0", - "better-sqlite3": ">=9.4.0 <12.0.0", - "cassandra-driver": "^4.7.2", - "cborg": "^4.1.1", - "cheerio": "^1.0.0-rc.12", - "chromadb": "*", - "closevector-common": "0.1.3", - "closevector-node": "0.1.6", - "closevector-web": "0.1.6", - "cohere-ai": "*", - "convex": "^1.3.1", - "crypto-js": "^4.2.0", - "d3-dsv": "^2.0.0", - "discord.js": "^14.14.1", - "dria": "^0.0.3", - "duck-duck-scrape": "^2.2.5", - "epub2": "^3.0.1", - "fast-xml-parser": "*", - "firebase-admin": "^11.9.0 || ^12.0.0", - "google-auth-library": "*", - "googleapis": "*", - "hnswlib-node": "^3.0.0", - "html-to-text": "^9.0.5", - "ibm-cloud-sdk-core": "*", - "ignore": "^5.2.0", - "interface-datastore": "^8.2.11", - "ioredis": "^5.3.2", - "it-all": "^3.0.4", - "jsdom": "*", - "jsonwebtoken": "^9.0.2", - "llmonitor": "^0.5.9", - "lodash": "^4.17.21", - "lunary": "^0.7.10", - "mammoth": "^1.6.0", - "mongodb": ">=5.2.0", - "mysql2": "^3.9.8", - "neo4j-driver": "*", - "notion-to-md": "^3.1.0", - "officeparser": "^4.0.4", - "openai": "*", - "pdf-parse": "1.1.1", - "pg": "^8.11.0", - "pg-copy-streams": "^6.0.5", - "pickleparser": "^0.2.1", - "playwright": "^1.32.1", - "portkey-ai": "^0.1.11", - "puppeteer": "*", - "pyodide": ">=0.24.1 <0.27.0", - "redis": "*", - "replicate": "*", - "sonix-speech-recognition": "^2.1.1", - "srt-parser-2": "^1.2.3", - "typeorm": "^0.3.20", - "typesense": "^1.5.3", - "usearch": "^1.1.1", - "voy-search": "0.6.2", - "weaviate-ts-client": "*", - "web-auth-library": "^1.0.3", - "word-extractor": "*", - "ws": "^8.14.2", - "youtubei.js": "*" - }, - "peerDependenciesMeta": { - "@arcjet/redact": { - "optional": true - }, - "@aws-crypto/sha256-js": { - "optional": true - }, - "@aws-sdk/client-bedrock-agent-runtime": { - "optional": true - }, - "@aws-sdk/client-bedrock-runtime": { - "optional": true - }, - "@aws-sdk/client-dynamodb": { - "optional": true - }, - "@aws-sdk/client-kendra": { - "optional": true - }, - "@aws-sdk/client-lambda": { - "optional": true - }, - "@aws-sdk/client-s3": { - "optional": true - }, - "@aws-sdk/client-sagemaker-runtime": { - "optional": true - }, - "@aws-sdk/client-sfn": { - "optional": true - }, - "@aws-sdk/credential-provider-node": { - "optional": true - }, - "@aws-sdk/dsql-signer": { - "optional": true - }, - "@azure/search-documents": { - "optional": true - }, - "@azure/storage-blob": { - "optional": true - }, - "@browserbasehq/sdk": { - "optional": true - }, - "@clickhouse/client": { - "optional": true - }, - "@cloudflare/ai": { - "optional": true - }, - "@datastax/astra-db-ts": { - "optional": true - }, - "@elastic/elasticsearch": { - "optional": true - }, - "@getmetal/metal-sdk": { - "optional": true - }, - "@getzep/zep-cloud": { - "optional": true - }, - "@getzep/zep-js": { - "optional": true - }, - "@gomomento/sdk": { - "optional": true - }, - "@gomomento/sdk-core": { - "optional": true - }, - "@google-ai/generativelanguage": { - "optional": true - }, - "@google-cloud/storage": { - "optional": true - }, - "@gradientai/nodejs-sdk": { - "optional": true - }, - "@huggingface/inference": { - "optional": true - }, - "@huggingface/transformers": { - "optional": true - }, - "@lancedb/lancedb": { - "optional": true - }, - "@layerup/layerup-security": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@mendable/firecrawl-js": { - "optional": true - }, - "@mlc-ai/web-llm": { - "optional": true - }, - "@mozilla/readability": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@notionhq/client": { - "optional": true - }, - "@opensearch-project/opensearch": { - "optional": true - }, - "@pinecone-database/pinecone": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@premai/prem-sdk": { - "optional": true - }, - "@qdrant/js-client-rest": { - "optional": true - }, - "@raycast/api": { - "optional": true - }, - "@rockset/client": { - "optional": true - }, - "@smithy/eventstream-codec": { - "optional": true - }, - "@smithy/protocol-http": { - "optional": true - }, - "@smithy/signature-v4": { - "optional": true - }, - "@smithy/util-utf8": { - "optional": true - }, - "@spider-cloud/spider-client": { - "optional": true - }, - "@supabase/supabase-js": { - "optional": true - }, - "@tensorflow-models/universal-sentence-encoder": { - "optional": true - }, - "@tensorflow/tfjs-converter": { - "optional": true - }, - "@tensorflow/tfjs-core": { - "optional": true - }, - "@upstash/ratelimit": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@upstash/vector": { - "optional": true - }, - "@vercel/kv": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@writerai/writer-sdk": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "@zilliz/milvus2-sdk-node": { - "optional": true - }, - "apify-client": { - "optional": true - }, - "assemblyai": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "cassandra-driver": { - "optional": true - }, - "cborg": { - "optional": true - }, - "cheerio": { - "optional": true - }, - "chromadb": { - "optional": true - }, - "closevector-common": { - "optional": true - }, - "closevector-node": { - "optional": true - }, - "closevector-web": { - "optional": true - }, - "cohere-ai": { - "optional": true - }, - "convex": { - "optional": true - }, - "crypto-js": { - "optional": true - }, - "d3-dsv": { - "optional": true - }, - "discord.js": { - "optional": true - }, - "dria": { - "optional": true - }, - "duck-duck-scrape": { - "optional": true - }, - "epub2": { - "optional": true - }, - "fast-xml-parser": { - "optional": true - }, - "firebase-admin": { - "optional": true - }, - "google-auth-library": { - "optional": true - }, - "googleapis": { - "optional": true - }, - "hnswlib-node": { - "optional": true - }, - "html-to-text": { - "optional": true - }, - "ignore": { - "optional": true - }, - "interface-datastore": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "it-all": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "jsonwebtoken": { - "optional": true - }, - "llmonitor": { - "optional": true - }, - "lodash": { - "optional": true - }, - "lunary": { - "optional": true - }, - "mammoth": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "neo4j-driver": { - "optional": true - }, - "notion-to-md": { - "optional": true - }, - "officeparser": { - "optional": true - }, - "pdf-parse": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-copy-streams": { - "optional": true - }, - "pickleparser": { - "optional": true - }, - "playwright": { - "optional": true - }, - "portkey-ai": { - "optional": true - }, - "puppeteer": { - "optional": true - }, - "pyodide": { - "optional": true - }, - "redis": { - "optional": true - }, - "replicate": { - "optional": true - }, - "sonix-speech-recognition": { - "optional": true - }, - "srt-parser-2": { - "optional": true - }, - "typeorm": { - "optional": true - }, - "typesense": { - "optional": true - }, - "usearch": { - "optional": true - }, - "voy-search": { - "optional": true - }, - "weaviate-ts-client": { - "optional": true - }, - "web-auth-library": { - "optional": true - }, - "word-extractor": { - "optional": true - }, - "ws": { - "optional": true - }, - "youtubei.js": { - "optional": true - } - } - }, - "api/node_modules/@librechat/agents": { - "version": "2.3.95", - "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.3.95.tgz", - "integrity": "sha512-+yLo1HVKqh99sGdOSJN4ruYYvzXlIqr+EtVHUkcinRe6UyVYEw10yWWF2q7gHY75nC7RekycU+TxGVD6M/VaxA==", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-sdk/credential-provider-node": "^3.613.0", - "@aws-sdk/types": "^3.609.0", - "@langchain/anthropic": "^0.3.14", - "@langchain/aws": "^0.1.7", - "@langchain/community": "^0.3.35", - "@langchain/core": "^0.3.40", - "@langchain/deepseek": "^0.0.1", - "@langchain/google-genai": "^0.1.11", - "@langchain/google-vertexai": "^0.2.2", - "@langchain/langgraph": "^0.2.49", - "@langchain/mistralai": "^0.0.26", - "@langchain/ollama": "^0.1.5", - "@langchain/openai": "^0.4.2", - "@langchain/xai": "^0.0.2", - "@smithy/eventstream-codec": "^2.2.0", - "@smithy/protocol-http": "^3.0.6", - "@smithy/signature-v4": "^2.0.10", - "@smithy/util-utf8": "^2.0.0", - "dotenv": "^16.4.7", - "https-proxy-agent": "^7.0.6", - "nanoid": "^3.3.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "api/node_modules/@librechat/agents/node_modules/@langchain/community": { - "version": "0.3.37", - "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.37.tgz", - "integrity": "sha512-Ifug3Gc6JHOFNWr0bxT1ie0AUKn3hWkZ4PG+EGqVz8MyeNr68lOhchY4oj51pQCC65KryfZ5CPhXrvbd5Il1GQ==", - "dependencies": { - "@langchain/openai": ">=0.2.0 <0.5.0", + "@langchain/openai": ">=0.2.0 <0.6.0", "binary-extensions": "^2.2.0", "expr-eval": "^2.0.2", "flat": "^5.0.2", @@ -787,6 +243,7 @@ "@zilliz/milvus2-sdk-node": ">=2.3.5", "apify-client": "^2.7.1", "assemblyai": "^4.6.0", + "azion": "^1.11.1", "better-sqlite3": ">=9.4.0 <12.0.0", "cassandra-driver": "^4.7.2", "cborg": "^4.1.1", @@ -1040,6 +497,9 @@ "assemblyai": { "optional": true }, + "azion": { + "optional": true + }, "better-sqlite3": { "optional": true }, @@ -1222,6 +682,23 @@ } } }, + "api/node_modules/@langchain/openai": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.5.4.tgz", + "integrity": "sha512-fBIEgaAMs1OHHjSuOwtonhtFTlRyFETAS9y/4SZxlXV5lLdwEU5OAbfaBTcCR0A58rrlmgt8iPnm2IWn4Onjjw==", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^4.87.3", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.39 <0.4.0" + } + }, "api/node_modules/@types/node": { "version": "18.19.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", @@ -3950,6 +3427,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "optional": true, + "peer": true, "dependencies": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", @@ -3959,7 +3438,9 @@ "node_modules/@aws-crypto/crc32/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true, + "peer": true }, "node_modules/@aws-crypto/crc32c": { "version": "5.2.0", @@ -4067,6 +3548,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", @@ -4076,7 +3559,9 @@ "node_modules/@aws-crypto/util/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true, + "peer": true }, "node_modules/@aws-sdk/client-bedrock-agent-runtime": { "version": "3.758.0", @@ -7267,55 +6752,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.758.0.tgz", - "integrity": "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.758.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.5", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-retry": "^4.0.7", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.7", - "@smithy/util-defaults-mode-node": "^4.0.7", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { "version": "3.734.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.734.0.tgz", @@ -7759,18 +7195,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/client-s3/node_modules/@smithy/url-parser": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", @@ -7908,19 +7332,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", - "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-retry": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.1.tgz", @@ -7983,6 +7394,7 @@ "version": "3.623.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.623.0.tgz", "integrity": "sha512-oEACriysQMnHIVcNp7TD6D1nzgiHfYK0tmMBMbUxgoFuCBkW9g9QYvspHN+S9KgoePfMEXHuPUe9mtG9AH9XeA==", + "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -8031,6 +7443,7 @@ "version": "3.623.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.623.0.tgz", "integrity": "sha512-lMFEXCa6ES/FGV7hpyrppT1PiAkqQb51AbG0zVU3TIgI2IO4XX02uzMUXImRSRqRpGymRCbJCaCs9LtKvS/37Q==", + "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -8083,6 +7496,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8094,6 +7508,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8106,6 +7521,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8117,6 +7533,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "optional": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -8129,6 +7546,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8141,6 +7559,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "optional": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -8153,6 +7572,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8164,6 +7584,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8176,6 +7597,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8187,6 +7609,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "optional": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -8199,6 +7622,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8211,6 +7635,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "optional": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -8223,6 +7648,7 @@ "version": "3.623.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.623.0.tgz", "integrity": "sha512-iJNdx76SOw0YjHAUv8aj3HXzSu3TKI7qSGuR+OGATwA/kpJZDd+4+WYBdGtr8YK+hPrGGqhfecuCkEg805O5iA==", + "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -8273,6 +7699,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8284,6 +7711,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8296,6 +7724,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8307,6 +7736,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "optional": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -8319,6 +7749,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8331,6 +7762,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "optional": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -8343,6 +7775,7 @@ "version": "3.623.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.623.0.tgz", "integrity": "sha512-8Toq3X6trX/67obSdh4K0MFQY4f132bEbr1i0YPDWk/O3KdBt12mLC/sW3aVRnlIs110XMuX9yrWWqJ8fDW10g==", + "optional": true, "dependencies": { "@smithy/core": "^2.3.2", "@smithy/node-config-provider": "^3.1.4", @@ -8362,6 +7795,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8373,6 +7807,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8385,6 +7820,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "optional": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/protocol-http": "^4.1.0", @@ -8403,6 +7839,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8414,6 +7851,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "optional": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -8426,6 +7864,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8437,6 +7876,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8449,6 +7889,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8460,6 +7901,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "optional": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -8500,6 +7942,7 @@ "version": "3.620.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -8514,6 +7957,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8525,6 +7969,7 @@ "version": "3.622.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.622.0.tgz", "integrity": "sha512-VUHbr24Oll1RK3WR8XLUugLpgK9ZuxEm/NVeVqyFts1Ck9gsKpRg1x4eH7L7tW3SJ4TDEQNMbD7/7J+eoL2svg==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/fetch-http-handler": "^3.2.4", @@ -8544,6 +7989,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8556,6 +8002,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8567,6 +8014,7 @@ "version": "3.623.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.623.0.tgz", "integrity": "sha512-kvXA1SwGneqGzFwRZNpESitnmaENHGFFuuTvgGwtMe7mzXWuA/LkXdbiHmdyAzOo0iByKTCD8uetuwh3CXy4Pw==", + "optional": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-http": "3.622.0", @@ -8591,6 +8039,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8602,6 +8051,7 @@ "version": "3.623.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.623.0.tgz", "integrity": "sha512-qDwCOkhbu5PfaQHyuQ+h57HEx3+eFhKdtIw7aISziWkGdFrMe07yIBd7TJqGe4nxXnRF1pfkg05xeOlMId997g==", + "optional": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-http": "3.622.0", @@ -8624,6 +8074,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8635,6 +8086,7 @@ "version": "3.620.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -8650,6 +8102,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8661,6 +8114,7 @@ "version": "3.623.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.623.0.tgz", "integrity": "sha512-70LZhUb3l7cttEsg4A0S4Jq3qrCT/v5Jfyl8F7w1YZJt5zr3oPPcvDJxo/UYckFz4G4/5BhGa99jK8wMlNE9QA==", + "optional": true, "dependencies": { "@aws-sdk/client-sso": "3.623.0", "@aws-sdk/token-providers": "3.614.0", @@ -8678,6 +8132,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8689,6 +8144,7 @@ "version": "3.621.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -8706,6 +8162,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8837,18 +8294,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/util-config-provider": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", @@ -8902,18 +8347,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/middleware-flexible-checksums": { "version": "3.758.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.758.0.tgz", @@ -9275,18 +8708,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/url-parser": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", @@ -9352,19 +8773,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", - "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-stream": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.1.2.tgz", @@ -9413,6 +8821,7 @@ "version": "3.620.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/protocol-http": "^4.1.0", @@ -9427,6 +8836,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -9439,6 +8849,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -9473,22 +8884,11 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/middleware-logger": { "version": "3.609.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/types": "^3.3.0", @@ -9502,6 +8902,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -9513,6 +8914,7 @@ "version": "3.620.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/protocol-http": "^4.1.0", @@ -9527,6 +8929,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -9539,6 +8942,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -9845,18 +9249,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/url-parser": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", @@ -9934,19 +9326,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", - "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-stream": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.1.2.tgz", @@ -10018,22 +9397,11 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-ssec/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/middleware-user-agent": { "version": "3.620.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", @@ -10049,6 +9417,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -10061,6 +9430,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10778,6 +10148,7 @@ "version": "3.614.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/node-config-provider": "^3.1.4", @@ -10794,6 +10165,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10805,6 +10177,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -11065,18 +10438,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/url-parser": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", @@ -11142,19 +10503,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", - "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-stream": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.1.2.tgz", @@ -11273,18 +10621,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/util-buffer-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", @@ -11310,19 +10646,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", - "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/util-uri-escape": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", @@ -11352,6 +10675,7 @@ "version": "3.614.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -11370,6 +10694,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -11416,6 +10741,7 @@ "version": "3.614.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/types": "^3.3.0", @@ -11430,6 +10756,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -11479,18 +10806,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/util-uri-escape": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", @@ -11518,6 +10833,7 @@ "version": "3.609.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/types": "^3.3.0", @@ -11529,6 +10845,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -11540,6 +10857,7 @@ "version": "3.614.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "optional": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/node-config-provider": "^3.1.4", @@ -11562,6 +10880,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -11573,6 +10892,8 @@ "version": "3.259.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "optional": true, + "peer": true, "dependencies": { "tslib": "^2.3.1" } @@ -11590,18 +10911,6 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/xml-builder/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@axe-core/playwright": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.10.1.tgz", @@ -16064,10 +15373,9 @@ "license": "MIT" }, "node_modules/@google/generative-ai": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz", - "integrity": "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==", - "license": "Apache-2.0", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.0.tgz", + "integrity": "sha512-fnEITCGEB7NdX0BhoYZ/cq/7WPZ1QS5IzJJfC3Tg/OwkvBetMiVJciyaan297OvE4B9Jg1xvo0zIazX/9sGu1Q==", "engines": { "node": ">=18.0.0" } @@ -17217,9 +16525,9 @@ } }, "node_modules/@langchain/anthropic": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-0.3.15.tgz", - "integrity": "sha512-Ar2viYcZ64idgV7EtCBCb36tIkNtPAhQRxSaMTWPHGspFgMfvwRoleVri9e90sCpjpS9xhlHsIQ0LlUS/Atsrw==", + "version": "0.3.16", + "resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-0.3.16.tgz", + "integrity": "sha512-O8jhWDsJ175N4p7+ypqMWXMAIT/TRq7NGwMvXGI2IbLPVC7CpjrCZ12yggbYkqzbBDdkVW3bM4fWfINUKWOy2w==", "dependencies": { "@anthropic-ai/sdk": "^0.37.0", "fast-xml-parser": "^4.4.1", @@ -18127,9 +17435,9 @@ } }, "node_modules/@langchain/core": { - "version": "0.3.42", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.42.tgz", - "integrity": "sha512-pT/jC5lqWK3YGDq8dQwgKoa6anqAhMtG1x5JbnrOj9NdaLeBbCKBDQ+/Ykzk3nZ8o+0UMsaXNZo7IVL83VVjHg==", + "version": "0.3.43", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.43.tgz", + "integrity": "sha512-DwiSUwmZqcuOn7j8SFdeOH1nvaUqG7q8qn3LhobdQYEg5PmjLgd2yLr2KzuT/YWMBfjkOR+Di5K6HEdFmouTxg==", "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", @@ -18188,9 +17496,9 @@ } }, "node_modules/@langchain/google-common": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@langchain/google-common/-/google-common-0.2.2.tgz", - "integrity": "sha512-w0811y4rQ/lSUZegcskVzMdSFA8HctWgTZQGjMSfhirVyJTJwJff81IjmQcbjaOyEgY6RA551lMpTPIGvaSl9w==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@langchain/google-common/-/google-common-0.2.3.tgz", + "integrity": "sha512-8jjLeMp3TeFP3kJbIZnQ9DQ7Sm7P86TA5M3A7c5TdWLrrh5VZXigqGM58YxO3OPMcFHhu2jN3ITqPUHwGCMIRw==", "dependencies": { "uuid": "^10.0.0", "zod-to-json-schema": "^3.22.4" @@ -18215,11 +17523,11 @@ } }, "node_modules/@langchain/google-gauth": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@langchain/google-gauth/-/google-gauth-0.2.2.tgz", - "integrity": "sha512-x4tkogcLwJ9VwgY0JF9FSyGAZyzPkoOlBUO5uGTRILcLjR3iNrcWohW6Uy04zadE64w7MLW9d71F8l5ejdjmdA==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@langchain/google-gauth/-/google-gauth-0.2.3.tgz", + "integrity": "sha512-8Bd3yJUntu7RYCwcg5VbjJYyMV3DwrZsbwA0mLPYHxt4k353DuZuiLvR40Vev77cb8GAIXpzvfXA3rHkWFG7nw==", "dependencies": { - "@langchain/google-common": "^0.2.2", + "@langchain/google-common": "^0.2.3", "google-auth-library": "^9.15.1" }, "engines": { @@ -18230,11 +17538,12 @@ } }, "node_modules/@langchain/google-genai": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@langchain/google-genai/-/google-genai-0.1.11.tgz", - "integrity": "sha512-puNFwgW+yY3nI358R4wjG7EsCiCIMH0UaIMGcK/GF44opAlf6pBY9iql4CLVrUto1XBI3EETZuNZ9nOhzpnfgQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@langchain/google-genai/-/google-genai-0.2.2.tgz", + "integrity": "sha512-fEuMoOjNoL9fudJmIu8P+ixCX/7n4eB6UT4/NsZubwtbm6BWCjuJWNNPNoHDX6aboXlabwnkuW9oTUy9ia2Ekg==", "dependencies": { - "@google/generative-ai": "^0.21.0", + "@google/generative-ai": "^0.24.0", + "uuid": "^11.1.0", "zod-to-json-schema": "^3.22.4" }, "engines": { @@ -18244,12 +17553,24 @@ "@langchain/core": ">=0.3.17 <0.4.0" } }, + "node_modules/@langchain/google-genai/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/@langchain/google-vertexai": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@langchain/google-vertexai/-/google-vertexai-0.2.2.tgz", - "integrity": "sha512-DqlKD3VXGTwVS4lWUIYhryqFvTTDfWGulySnv/Gf6t4e3A9pXkapyCpxY4hb6hAGYAKV/55qg7CLMueRXWbpcA==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@langchain/google-vertexai/-/google-vertexai-0.2.3.tgz", + "integrity": "sha512-PO/dBoTsWpC7Q7MZ1CnZFYFV6efFdFsaDpE/p4XgK5QwiBBx3sZucJfR/hIcPXOpRgc+nrmiDyDYxcg855gxvA==", "dependencies": { - "@langchain/google-gauth": "~0.2.2" + "@langchain/google-gauth": "~0.2.3" }, "engines": { "node": ">=18" @@ -18259,9 +17580,9 @@ } }, "node_modules/@langchain/langgraph": { - "version": "0.2.57", - "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.2.57.tgz", - "integrity": "sha512-SolqE+HzwbxEEiqAVgHwE11r9lzjZAnAfEe7MMBUE77TUCaWK3GC0VvDfJMNas53ndlc0KRutmpEa0ODWdhcRQ==", + "version": "0.2.63", + "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.2.63.tgz", + "integrity": "sha512-gmivnxybyBQkR7lpoBz8emrWRt9jFp0csrlDZTI/SlMJ5F+3DvxbsRyH2edI0RPzL7XpiW9QnFGCLMp6wfGblw==", "dependencies": { "@langchain/langgraph-checkpoint": "~0.0.16", "@langchain/langgraph-sdk": "~0.0.32", @@ -18272,7 +17593,13 @@ "node": ">=18" }, "peerDependencies": { - "@langchain/core": ">=0.2.36 <0.3.0 || >=0.3.40 < 0.4.0" + "@langchain/core": ">=0.2.36 <0.3.0 || >=0.3.40 < 0.4.0", + "zod-to-json-schema": "^3.x" + }, + "peerDependenciesMeta": { + "zod-to-json-schema": { + "optional": true + } } }, "node_modules/@langchain/langgraph-checkpoint": { @@ -18302,9 +17629,9 @@ } }, "node_modules/@langchain/langgraph-sdk": { - "version": "0.0.60", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.60.tgz", - "integrity": "sha512-7ndeAdw1afVY72HpKEGw7AyuDlD7U3e4jxaJflxA+PXaFPiE0d/hQYvlPT84YmvqNzJN605hv7YcrOju2573bQ==", + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.62.tgz", + "integrity": "sha512-JJiQwjV5/uVtwiVH/lt+QXhuh0nGhylZSLkMQXc1923TBUC4SHwU0JIKEDqh820PlGNkUu0nODJSAzS/6zPRtQ==", "dependencies": { "@types/json-schema": "^7.0.15", "p-queue": "^6.6.2", @@ -18337,71 +17664,20 @@ } }, "node_modules/@langchain/mistralai": { - "version": "0.0.26", - "resolved": "https://registry.npmjs.org/@langchain/mistralai/-/mistralai-0.0.26.tgz", - "integrity": "sha512-NoXmOTrkHjfCcgWQprbPujCvFktJFPcFTAcJEc4jY0J+PRiwWfhe4Xx2MevWTSV9clWm2Pil454nJ1CYEvh3Ng==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@langchain/mistralai/-/mistralai-0.2.0.tgz", + "integrity": "sha512-VdfbKZopAuSXf/vlXbriGWLK3c7j5s47DoB3S31xpprY2BMSKZZiX9vE9TsgxMfAPuIDPIYcfgU7p1upvTYt8g==", "dependencies": { - "@langchain/core": ">=0.2.16 <0.3.0", - "@mistralai/mistralai": "^0.4.0", + "@mistralai/mistralai": "^1.3.1", "uuid": "^10.0.0", - "zod": "^3.22.4", + "zod": "^3.23.8", "zod-to-json-schema": "^3.22.4" }, "engines": { "node": ">=18" - } - }, - "node_modules/@langchain/mistralai/node_modules/@langchain/core": { - "version": "0.2.36", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.36.tgz", - "integrity": "sha512-qHLvScqERDeH7y2cLuJaSAlMwg3f/3Oc9nayRSXRU2UuaK/SOhI42cxiPLj1FnuHJSmN0rBQFkrLx02gI4mcVg==", - "dependencies": { - "ansi-styles": "^5.0.0", - "camelcase": "6", - "decamelize": "1.2.0", - "js-tiktoken": "^1.0.12", - "langsmith": "^0.1.56-rc.1", - "mustache": "^4.2.0", - "p-queue": "^6.6.2", - "p-retry": "4", - "uuid": "^10.0.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@langchain/mistralai/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@langchain/mistralai/node_modules/langsmith": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.68.tgz", - "integrity": "sha512-otmiysWtVAqzMx3CJ4PrtUBhWRG5Co8Z4o7hSZENPjlit9/j3/vm3TSvbaxpDYakZxtMjhkcJTqrdYFipISEiQ==", - "dependencies": { - "@types/uuid": "^10.0.0", - "commander": "^10.0.1", - "p-queue": "^6.6.2", - "p-retry": "4", - "semver": "^7.6.3", - "uuid": "^10.0.0" }, "peerDependencies": { - "openai": "*" - }, - "peerDependenciesMeta": { - "openai": { - "optional": true - } + "@langchain/core": ">=0.3.7 <0.4.0" } }, "node_modules/@langchain/mistralai/node_modules/uuid": { @@ -18417,9 +17693,9 @@ } }, "node_modules/@langchain/ollama": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@langchain/ollama/-/ollama-0.1.6.tgz", - "integrity": "sha512-hS+xHiRqKpq37eGyZQ0JoTghfNA7hWXK54XbILQ0KVm3v2MMWLKqBAep4LwMLrAr4NE7SIp+SJnQRdsjabR0jw==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@langchain/ollama/-/ollama-0.2.0.tgz", + "integrity": "sha512-jLlYFqt+nbhaJKLakk7lRTWHZJ7wHeJLM6yuv4jToQ8zPzpL//372+MjggDoW0mnw8ofysg1T2C6mEJspKJtiA==", "dependencies": { "ollama": "^0.5.12", "uuid": "^10.0.0", @@ -18542,6 +17818,604 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@librechat/agents": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.0.tgz", + "integrity": "sha512-ijPJw+lMMPJ+Y66xSbh0cCiuODihl0TET3CWAxZweVGqynYqtL8PvPqlxtw+jftmrLjDFV04UM2NiDbzDb87HA==", + "dependencies": { + "@langchain/anthropic": "^0.3.16", + "@langchain/aws": "^0.1.7", + "@langchain/community": "^0.3.39", + "@langchain/core": "^0.3.43", + "@langchain/deepseek": "^0.0.1", + "@langchain/google-genai": "^0.2.2", + "@langchain/google-vertexai": "^0.2.3", + "@langchain/langgraph": "^0.2.62", + "@langchain/mistralai": "^0.2.0", + "@langchain/ollama": "^0.2.0", + "@langchain/openai": "^0.5.4", + "@langchain/xai": "^0.0.2", + "dotenv": "^16.4.7", + "https-proxy-agent": "^7.0.6", + "nanoid": "^3.3.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/community": { + "version": "0.3.39", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.39.tgz", + "integrity": "sha512-8sMJD22bUNlQVoh0b8PgRSpO2Hz5zO6BuZeNY3ElgW3cNVXCoywHW7FBmqbwrygGocJQDee76RqiIN0XQunhTg==", + "dependencies": { + "@langchain/openai": ">=0.2.0 <0.6.0", + "binary-extensions": "^2.2.0", + "expr-eval": "^2.0.2", + "flat": "^5.0.2", + "js-yaml": "^4.1.0", + "langchain": ">=0.2.3 <0.3.0 || >=0.3.4 <0.4.0", + "langsmith": ">=0.2.8 <0.4.0", + "uuid": "^10.0.0", + "zod": "^3.22.3", + "zod-to-json-schema": "^3.22.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@arcjet/redact": "^v1.0.0-alpha.23", + "@aws-crypto/sha256-js": "^5.0.0", + "@aws-sdk/client-bedrock-agent-runtime": "^3.749.0", + "@aws-sdk/client-bedrock-runtime": "^3.749.0", + "@aws-sdk/client-dynamodb": "^3.749.0", + "@aws-sdk/client-kendra": "^3.749.0", + "@aws-sdk/client-lambda": "^3.749.0", + "@aws-sdk/client-s3": "^3.749.0", + "@aws-sdk/client-sagemaker-runtime": "^3.749.0", + "@aws-sdk/client-sfn": "^3.749.0", + "@aws-sdk/credential-provider-node": "^3.388.0", + "@azure/search-documents": "^12.0.0", + "@azure/storage-blob": "^12.15.0", + "@browserbasehq/sdk": "*", + "@browserbasehq/stagehand": "^1.0.0", + "@clickhouse/client": "^0.2.5", + "@cloudflare/ai": "*", + "@datastax/astra-db-ts": "^1.0.0", + "@elastic/elasticsearch": "^8.4.0", + "@getmetal/metal-sdk": "*", + "@getzep/zep-cloud": "^1.0.6", + "@getzep/zep-js": "^0.9.0", + "@gomomento/sdk": "^1.51.1", + "@gomomento/sdk-core": "^1.51.1", + "@google-ai/generativelanguage": "*", + "@google-cloud/storage": "^6.10.1 || ^7.7.0", + "@gradientai/nodejs-sdk": "^1.2.0", + "@huggingface/inference": "^2.6.4", + "@huggingface/transformers": "^3.2.3", + "@ibm-cloud/watsonx-ai": "*", + "@lancedb/lancedb": "^0.12.0", + "@langchain/core": ">=0.2.21 <0.4.0", + "@layerup/layerup-security": "^1.5.12", + "@libsql/client": "^0.14.0", + "@mendable/firecrawl-js": "^1.4.3", + "@mlc-ai/web-llm": "*", + "@mozilla/readability": "*", + "@neondatabase/serverless": "*", + "@notionhq/client": "^2.2.10", + "@opensearch-project/opensearch": "*", + "@pinecone-database/pinecone": "*", + "@planetscale/database": "^1.8.0", + "@premai/prem-sdk": "^0.3.25", + "@qdrant/js-client-rest": "^1.8.2", + "@raycast/api": "^1.55.2", + "@rockset/client": "^0.9.1", + "@smithy/eventstream-codec": "^2.0.5", + "@smithy/protocol-http": "^3.0.6", + "@smithy/signature-v4": "^2.0.10", + "@smithy/util-utf8": "^2.0.0", + "@spider-cloud/spider-client": "^0.0.21", + "@supabase/supabase-js": "^2.45.0", + "@tensorflow-models/universal-sentence-encoder": "*", + "@tensorflow/tfjs-converter": "*", + "@tensorflow/tfjs-core": "*", + "@upstash/ratelimit": "^1.1.3 || ^2.0.3", + "@upstash/redis": "^1.20.6", + "@upstash/vector": "^1.1.1", + "@vercel/kv": "*", + "@vercel/postgres": "*", + "@writerai/writer-sdk": "^0.40.2", + "@xata.io/client": "^0.28.0", + "@zilliz/milvus2-sdk-node": ">=2.3.5", + "apify-client": "^2.7.1", + "assemblyai": "^4.6.0", + "azion": "^1.11.1", + "better-sqlite3": ">=9.4.0 <12.0.0", + "cassandra-driver": "^4.7.2", + "cborg": "^4.1.1", + "cheerio": "^1.0.0-rc.12", + "chromadb": "*", + "closevector-common": "0.1.3", + "closevector-node": "0.1.6", + "closevector-web": "0.1.6", + "cohere-ai": "*", + "convex": "^1.3.1", + "crypto-js": "^4.2.0", + "d3-dsv": "^2.0.0", + "discord.js": "^14.14.1", + "dria": "^0.0.3", + "duck-duck-scrape": "^2.2.5", + "epub2": "^3.0.1", + "fast-xml-parser": "*", + "firebase-admin": "^11.9.0 || ^12.0.0", + "google-auth-library": "*", + "googleapis": "*", + "hnswlib-node": "^3.0.0", + "html-to-text": "^9.0.5", + "ibm-cloud-sdk-core": "*", + "ignore": "^5.2.0", + "interface-datastore": "^8.2.11", + "ioredis": "^5.3.2", + "it-all": "^3.0.4", + "jsdom": "*", + "jsonwebtoken": "^9.0.2", + "llmonitor": "^0.5.9", + "lodash": "^4.17.21", + "lunary": "^0.7.10", + "mammoth": "^1.6.0", + "mariadb": "^3.4.0", + "mem0ai": "^2.1.8", + "mongodb": ">=5.2.0", + "mysql2": "^3.9.8", + "neo4j-driver": "*", + "notion-to-md": "^3.1.0", + "officeparser": "^4.0.4", + "openai": "*", + "pdf-parse": "1.1.1", + "pg": "^8.11.0", + "pg-copy-streams": "^6.0.5", + "pickleparser": "^0.2.1", + "playwright": "^1.32.1", + "portkey-ai": "^0.1.11", + "puppeteer": "*", + "pyodide": ">=0.24.1 <0.27.0", + "redis": "*", + "replicate": "*", + "sonix-speech-recognition": "^2.1.1", + "srt-parser-2": "^1.2.3", + "typeorm": "^0.3.20", + "typesense": "^1.5.3", + "usearch": "^1.1.1", + "voy-search": "0.6.2", + "weaviate-ts-client": "*", + "web-auth-library": "^1.0.3", + "word-extractor": "*", + "ws": "^8.14.2", + "youtubei.js": "*" + }, + "peerDependenciesMeta": { + "@arcjet/redact": { + "optional": true + }, + "@aws-crypto/sha256-js": { + "optional": true + }, + "@aws-sdk/client-bedrock-agent-runtime": { + "optional": true + }, + "@aws-sdk/client-bedrock-runtime": { + "optional": true + }, + "@aws-sdk/client-dynamodb": { + "optional": true + }, + "@aws-sdk/client-kendra": { + "optional": true + }, + "@aws-sdk/client-lambda": { + "optional": true + }, + "@aws-sdk/client-s3": { + "optional": true + }, + "@aws-sdk/client-sagemaker-runtime": { + "optional": true + }, + "@aws-sdk/client-sfn": { + "optional": true + }, + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@aws-sdk/dsql-signer": { + "optional": true + }, + "@azure/search-documents": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@browserbasehq/sdk": { + "optional": true + }, + "@clickhouse/client": { + "optional": true + }, + "@cloudflare/ai": { + "optional": true + }, + "@datastax/astra-db-ts": { + "optional": true + }, + "@elastic/elasticsearch": { + "optional": true + }, + "@getmetal/metal-sdk": { + "optional": true + }, + "@getzep/zep-cloud": { + "optional": true + }, + "@getzep/zep-js": { + "optional": true + }, + "@gomomento/sdk": { + "optional": true + }, + "@gomomento/sdk-core": { + "optional": true + }, + "@google-ai/generativelanguage": { + "optional": true + }, + "@google-cloud/storage": { + "optional": true + }, + "@gradientai/nodejs-sdk": { + "optional": true + }, + "@huggingface/inference": { + "optional": true + }, + "@huggingface/transformers": { + "optional": true + }, + "@lancedb/lancedb": { + "optional": true + }, + "@layerup/layerup-security": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@mendable/firecrawl-js": { + "optional": true + }, + "@mlc-ai/web-llm": { + "optional": true + }, + "@mozilla/readability": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@notionhq/client": { + "optional": true + }, + "@opensearch-project/opensearch": { + "optional": true + }, + "@pinecone-database/pinecone": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@premai/prem-sdk": { + "optional": true + }, + "@qdrant/js-client-rest": { + "optional": true + }, + "@raycast/api": { + "optional": true + }, + "@rockset/client": { + "optional": true + }, + "@smithy/eventstream-codec": { + "optional": true + }, + "@smithy/protocol-http": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "@smithy/util-utf8": { + "optional": true + }, + "@spider-cloud/spider-client": { + "optional": true + }, + "@supabase/supabase-js": { + "optional": true + }, + "@tensorflow-models/universal-sentence-encoder": { + "optional": true + }, + "@tensorflow/tfjs-converter": { + "optional": true + }, + "@tensorflow/tfjs-core": { + "optional": true + }, + "@upstash/ratelimit": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@upstash/vector": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@writerai/writer-sdk": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "@zilliz/milvus2-sdk-node": { + "optional": true + }, + "apify-client": { + "optional": true + }, + "assemblyai": { + "optional": true + }, + "azion": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "cassandra-driver": { + "optional": true + }, + "cborg": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "chromadb": { + "optional": true + }, + "closevector-common": { + "optional": true + }, + "closevector-node": { + "optional": true + }, + "closevector-web": { + "optional": true + }, + "cohere-ai": { + "optional": true + }, + "convex": { + "optional": true + }, + "crypto-js": { + "optional": true + }, + "d3-dsv": { + "optional": true + }, + "discord.js": { + "optional": true + }, + "dria": { + "optional": true + }, + "duck-duck-scrape": { + "optional": true + }, + "epub2": { + "optional": true + }, + "fast-xml-parser": { + "optional": true + }, + "firebase-admin": { + "optional": true + }, + "google-auth-library": { + "optional": true + }, + "googleapis": { + "optional": true + }, + "hnswlib-node": { + "optional": true + }, + "html-to-text": { + "optional": true + }, + "ignore": { + "optional": true + }, + "interface-datastore": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "it-all": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "jsonwebtoken": { + "optional": true + }, + "llmonitor": { + "optional": true + }, + "lodash": { + "optional": true + }, + "lunary": { + "optional": true + }, + "mammoth": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mem0ai": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "neo4j-driver": { + "optional": true + }, + "notion-to-md": { + "optional": true + }, + "officeparser": { + "optional": true + }, + "pdf-parse": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-copy-streams": { + "optional": true + }, + "pickleparser": { + "optional": true + }, + "playwright": { + "optional": true + }, + "portkey-ai": { + "optional": true + }, + "puppeteer": { + "optional": true + }, + "pyodide": { + "optional": true + }, + "redis": { + "optional": true + }, + "replicate": { + "optional": true + }, + "sonix-speech-recognition": { + "optional": true + }, + "srt-parser-2": { + "optional": true + }, + "typeorm": { + "optional": true + }, + "typesense": { + "optional": true + }, + "usearch": { + "optional": true + }, + "voy-search": { + "optional": true + }, + "weaviate-ts-client": { + "optional": true + }, + "web-auth-library": { + "optional": true + }, + "word-extractor": { + "optional": true + }, + "ws": { + "optional": true + }, + "youtubei.js": { + "optional": true + } + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/openai": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.5.4.tgz", + "integrity": "sha512-fBIEgaAMs1OHHjSuOwtonhtFTlRyFETAS9y/4SZxlXV5lLdwEU5OAbfaBTcCR0A58rrlmgt8iPnm2IWn4Onjjw==", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^4.87.3", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.39 <0.4.0" + } + }, + "node_modules/@librechat/agents/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@librechat/agents/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@librechat/agents/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@librechat/backend": { "resolved": "api", "link": true @@ -18765,11 +18639,14 @@ "license": "MIT" }, "node_modules/@mistralai/mistralai": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-0.4.0.tgz", - "integrity": "sha512-KmFzNro1RKxIFh19J3osmUQhucefBBauMXN5fa9doG6dT9OHR/moBvvn+riVlR7c0AVfuxO8Dfa03AyLYYzbyg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.5.2.tgz", + "integrity": "sha512-mBTIDQmuAX9RowMYteZFHJIYlEwDcHzzaxgXzrFtlvH9CkKXK7R1VnZ1sZSe+uLMg0dIXUVdPRUh1SwyFeSqXw==", "dependencies": { - "node-fetch": "^2.6.7" + "zod-to-json-schema": "^3.24.1" + }, + "peerDependencies": { + "zod": ">= 3" } }, "node_modules/@modelcontextprotocol/sdk": { @@ -21699,6 +21576,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21711,6 +21589,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21799,6 +21678,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", + "optional": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", @@ -21814,6 +21694,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21825,6 +21706,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21837,6 +21719,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", + "optional": true, "dependencies": { "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-retry": "^3.0.15", @@ -21857,6 +21740,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21868,6 +21752,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21880,6 +21765,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21891,6 +21777,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "optional": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -21903,6 +21790,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21915,6 +21803,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "optional": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -21927,6 +21816,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", + "optional": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/property-provider": "^3.1.3", @@ -21942,6 +21832,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21953,6 +21844,8 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.2.0.tgz", "integrity": "sha512-8janZoJw85nJmQZc4L8TuePp2pk1nxLgkxIR0TUjKJ5Dkj5oelB9WtiSSGXCQvNsJl0VSTvK/2ueMXxvpa9GVw==", + "optional": true, + "peer": true, "dependencies": { "@aws-crypto/crc32": "3.0.0", "@smithy/types": "^2.12.0", @@ -21964,6 +21857,8 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22074,6 +21969,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "optional": true, "dependencies": { "@smithy/protocol-http": "^4.1.0", "@smithy/querystring-builder": "^3.0.3", @@ -22086,6 +21982,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22098,6 +21995,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22120,22 +22018,11 @@ "node": ">=18.0.0" } }, - "node_modules/@smithy/hash-blob-browser/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@smithy/hash-node": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "@smithy/util-buffer-from": "^3.0.0", @@ -22150,6 +22037,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22161,6 +22049,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22172,6 +22061,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "optional": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -22184,6 +22074,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "optional": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -22218,18 +22109,6 @@ "node": ">=18.0.0" } }, - "node_modules/@smithy/hash-stream-node/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@smithy/hash-stream-node/node_modules/@smithy/util-buffer-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", @@ -22260,6 +22139,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22269,6 +22149,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22313,18 +22194,6 @@ "node": ">=18.0.0" } }, - "node_modules/@smithy/md5-js/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@smithy/md5-js/node_modules/@smithy/util-buffer-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", @@ -22355,6 +22224,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", + "optional": true, "dependencies": { "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", @@ -22368,6 +22238,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22380,6 +22251,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22391,6 +22263,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "optional": true, "dependencies": { "@smithy/middleware-serde": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", @@ -22408,6 +22281,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22419,6 +22293,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22431,6 +22306,7 @@ "version": "3.0.15", "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", + "optional": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/protocol-http": "^4.1.0", @@ -22450,6 +22326,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22462,6 +22339,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22473,6 +22351,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22485,6 +22364,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22497,6 +22377,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22508,6 +22389,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22520,6 +22402,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22531,6 +22414,7 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", + "optional": true, "dependencies": { "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", @@ -22545,6 +22429,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22556,6 +22441,7 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "optional": true, "dependencies": { "@smithy/abort-controller": "^3.1.1", "@smithy/protocol-http": "^4.1.0", @@ -22571,6 +22457,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22583,6 +22470,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22594,6 +22482,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22606,6 +22495,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22617,6 +22507,8 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.3.0.tgz", "integrity": "sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==", + "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" @@ -22629,6 +22521,8 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22640,6 +22534,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "@smithy/util-uri-escape": "^3.0.0", @@ -22653,6 +22548,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22664,6 +22560,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22675,6 +22572,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22687,6 +22585,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22698,6 +22597,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0" }, @@ -22709,6 +22609,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22720,6 +22621,7 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22732,6 +22634,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22743,6 +22646,8 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.3.0.tgz", "integrity": "sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==", + "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "@smithy/types": "^2.12.0", @@ -22760,6 +22665,8 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22771,6 +22678,8 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.2.0.tgz", "integrity": "sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==", + "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" @@ -22783,6 +22692,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "optional": true, "dependencies": { "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-stack": "^3.0.3", @@ -22799,6 +22709,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "optional": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22811,6 +22722,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22833,6 +22745,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "optional": true, "dependencies": { "@smithy/querystring-parser": "^3.0.3", "@smithy/types": "^3.3.0", @@ -22843,6 +22756,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22854,6 +22768,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "optional": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", @@ -22867,6 +22782,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22878,6 +22794,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "optional": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -22890,6 +22807,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "optional": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -22902,6 +22820,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" } @@ -22910,6 +22829,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22933,6 +22853,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22944,6 +22865,7 @@ "version": "3.0.15", "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", + "optional": true, "dependencies": { "@smithy/property-provider": "^3.1.3", "@smithy/smithy-client": "^3.2.0", @@ -22959,6 +22881,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22970,6 +22893,7 @@ "version": "3.0.15", "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", + "optional": true, "dependencies": { "@smithy/config-resolver": "^3.0.5", "@smithy/credential-provider-imds": "^3.2.0", @@ -22987,6 +22911,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22998,6 +22923,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", + "optional": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", @@ -23011,6 +22937,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -23022,6 +22949,8 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.2.0.tgz", "integrity": "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==", + "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -23045,6 +22974,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", + "optional": true, "dependencies": { "@smithy/service-error-classification": "^3.0.3", "@smithy/types": "^3.3.0", @@ -23058,6 +22988,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -23069,6 +23000,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "optional": true, "dependencies": { "@smithy/fetch-http-handler": "^3.2.4", "@smithy/node-http-handler": "^3.1.4", @@ -23087,6 +23019,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -23098,6 +23031,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -23109,6 +23043,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "optional": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -23121,6 +23056,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "optional": true, "dependencies": { "tslib": "^2.6.2" }, @@ -23132,6 +23068,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "optional": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -23144,6 +23081,8 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.2.0.tgz", "integrity": "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==", + "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -23190,18 +23129,6 @@ "node": ">=18.0.0" } }, - "node_modules/@smithy/util-waiter/node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@stitches/core": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", @@ -35531,9 +35458,9 @@ } }, "node_modules/openai": { - "version": "4.80.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.80.1.tgz", - "integrity": "sha512-+6+bbXFwbIE88foZsBEt36bPkgZPdyFN82clAXG61gnHb2gXdZApDyRrcAHqEtpYICywpqaNo57kOm9dtnb7Cw==", + "version": "4.91.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.91.1.tgz", + "integrity": "sha512-DbjrR0hIMQFbxz8+3qBsfPJnh3+I/skPgoSlT7f9eiZuhGBUissPQULNgx6gHNkLoZ3uS0uYS6eXPUdtg4nHzw==", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", diff --git a/packages/mcp/src/flow/manager.ts b/packages/mcp/src/flow/manager.ts index f282bfad8c..0e629cc861 100644 --- a/packages/mcp/src/flow/manager.ts +++ b/packages/mcp/src/flow/manager.ts @@ -55,13 +55,18 @@ export class FlowStateManager { /** * Creates a new flow and waits for its completion */ - async createFlow(flowId: string, type: string, metadata: FlowMetadata = {}): Promise { + async createFlow( + flowId: string, + type: string, + metadata: FlowMetadata = {}, + signal?: AbortSignal, + ): Promise { const flowKey = this.getFlowKey(flowId, type); let existingState = (await this.keyv.get(flowKey)) as FlowState | undefined; if (existingState) { this.logger.debug(`[${flowKey}] Flow already exists`); - return this.monitorFlow(flowKey, type); + return this.monitorFlow(flowKey, type, signal); } await new Promise((resolve) => setTimeout(resolve, 250)); @@ -69,7 +74,7 @@ export class FlowStateManager { existingState = (await this.keyv.get(flowKey)) as FlowState | undefined; if (existingState) { this.logger.debug(`[${flowKey}] Flow exists on 2nd check`); - return this.monitorFlow(flowKey, type); + return this.monitorFlow(flowKey, type, signal); } const initialState: FlowState = { @@ -81,10 +86,10 @@ export class FlowStateManager { this.logger.debug('Creating initial flow state:', flowKey); await this.keyv.set(flowKey, initialState, this.ttl); - return this.monitorFlow(flowKey, type); + return this.monitorFlow(flowKey, type, signal); } - private monitorFlow(flowKey: string, type: string): Promise { + private monitorFlow(flowKey: string, type: string, signal?: AbortSignal): Promise { return new Promise((resolve, reject) => { const checkInterval = 2000; let elapsedTime = 0; @@ -101,6 +106,16 @@ export class FlowStateManager { return; } + if (signal?.aborted) { + clearInterval(intervalId); + this.intervals.delete(intervalId); + this.logger.warn(`[${flowKey}] Flow aborted`); + const message = `${type} flow aborted`; + await this.keyv.delete(flowKey); + reject(new Error(message)); + return; + } + if (flowState.status !== 'PENDING') { clearInterval(intervalId); this.intervals.delete(intervalId); @@ -197,19 +212,19 @@ export class FlowStateManager { * @param flowId - The ID of the flow * @param type - The type of flow * @param handler - Async function to execute if no existing flow is found - * @param metadata - Optional metadata for the flow + * @param signal - Optional AbortSignal to cancel the flow */ async createFlowWithHandler( flowId: string, type: string, handler: () => Promise, - metadata: FlowMetadata = {}, + signal?: AbortSignal, ): Promise { const flowKey = this.getFlowKey(flowId, type); let existingState = (await this.keyv.get(flowKey)) as FlowState | undefined; if (existingState) { this.logger.debug(`[${flowKey}] Flow already exists`); - return this.monitorFlow(flowKey, type); + return this.monitorFlow(flowKey, type, signal); } await new Promise((resolve) => setTimeout(resolve, 250)); @@ -217,13 +232,13 @@ export class FlowStateManager { existingState = (await this.keyv.get(flowKey)) as FlowState | undefined; if (existingState) { this.logger.debug(`[${flowKey}] Flow exists on 2nd check`); - return this.monitorFlow(flowKey, type); + return this.monitorFlow(flowKey, type, signal); } const initialState: FlowState = { type, status: 'PENDING', - metadata, + metadata: {}, createdAt: Date.now(), }; this.logger.debug(`[${flowKey}] Creating initial flow state`); From 175cfe8ffba239b36b3b793857964459a034fbde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Apr 2025 15:17:23 -0400 Subject: [PATCH 02/75] =?UTF-8?q?=F0=9F=93=A6=20chore:=20bump=20vite=20fro?= =?UTF-8?q?m=206.2.3=20to=206.2.5=20(#6745)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.3 to 6.2.5. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v6.2.5/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 6.2.5 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- client/package.json | 2 +- package-lock.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/package.json b/client/package.json index 32c3bc32b5..184e768c5f 100644 --- a/client/package.json +++ b/client/package.json @@ -141,7 +141,7 @@ "tailwindcss": "^3.4.1", "ts-jest": "^29.2.5", "typescript": "^5.3.3", - "vite": "^6.2.3", + "vite": "^6.2.5", "vite-plugin-compression2": "^1.3.3", "vite-plugin-node-polyfills": "^0.23.0", "vite-plugin-pwa": "^0.21.2" diff --git a/package-lock.json b/package-lock.json index 88f008db61..153b969ffd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1250,7 +1250,7 @@ "tailwindcss": "^3.4.1", "ts-jest": "^29.2.5", "typescript": "^5.3.3", - "vite": "^6.2.3", + "vite": "^6.2.5", "vite-plugin-compression2": "^1.3.3", "vite-plugin-node-polyfills": "^0.23.0", "vite-plugin-pwa": "^0.21.2" @@ -42269,9 +42269,9 @@ } }, "node_modules/vite": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.3.tgz", - "integrity": "sha512-IzwM54g4y9JA/xAeBPNaDXiBF8Jsgl3VBQ2YQ/wOY6fyW3xMdSoltIV3Bo59DErdqdE6RxUfv8W69DvUorE4Eg==", + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", + "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", "dev": true, "license": "MIT", "dependencies": { From 4afab52fc5d10fb057ae819286c72da7b1b980e0 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 7 Apr 2025 14:48:11 -0400 Subject: [PATCH 03/75] =?UTF-8?q?=F0=9F=AA=BA=20fix:=20Update=20Role=20Han?= =?UTF-8?q?dling=20due=20to=20New=20Schema=20Shape=20(#6774)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📝 fix: Update translation for shared agent message in English locale * 🪺 fix: Migrate role schema to new nested structure and update permissions handling where missed --- api/models/Role.js | 131 +++++++++++++++++- api/server/routes/roles.js | 30 ++-- .../src/components/Prompts/AdminSettings.tsx | 12 +- .../SidePanel/Agents/AdminSettings.tsx | 12 +- client/src/locales/en/translation.json | 2 +- packages/data-provider/src/roles.ts | 28 +++- 6 files changed, 185 insertions(+), 30 deletions(-) diff --git a/api/models/Role.js b/api/models/Role.js index c4abfedad2..07bf5a2ccb 100644 --- a/api/models/Role.js +++ b/api/models/Role.js @@ -99,6 +99,25 @@ async function updateAccessPermissions(roleName, permissionsUpdate) { const updatedPermissions = { ...currentPermissions }; let hasChanges = false; + const unsetFields = {}; + const permissionTypes = Object.keys(permissionsSchema.shape || {}); + for (const permType of permissionTypes) { + if (role[permType] && typeof role[permType] === 'object') { + logger.info( + `Migrating '${roleName}' role from old schema: found '${permType}' at top level`, + ); + + updatedPermissions[permType] = { + ...updatedPermissions[permType], + ...role[permType], + }; + + unsetFields[permType] = 1; + hasChanges = true; + } + } + + // Process the current updates for (const [permissionType, permissions] of Object.entries(updates)) { const currentTypePermissions = currentPermissions[permissionType] || {}; updatedPermissions[permissionType] = { ...currentTypePermissions }; @@ -115,8 +134,36 @@ async function updateAccessPermissions(roleName, permissionsUpdate) { } if (hasChanges) { - // Update only the permissions field. - await updateRoleByName(roleName, { permissions: updatedPermissions }); + const updateObj = { permissions: updatedPermissions }; + + if (Object.keys(unsetFields).length > 0) { + logger.info( + `Unsetting old schema fields for '${roleName}' role: ${Object.keys(unsetFields).join(', ')}`, + ); + + try { + await Role.updateOne( + { name: roleName }, + { + $set: updateObj, + $unset: unsetFields, + }, + ); + + const cache = getLogStores(CacheKeys.ROLES); + const updatedRole = await Role.findOne({ name: roleName }).select('-__v').lean().exec(); + await cache.set(roleName, updatedRole); + + logger.info(`Updated role '${roleName}' and removed old schema fields`); + } catch (updateError) { + logger.error(`Error during role migration update: ${updateError.message}`); + throw updateError; + } + } else { + // Standard update if no migration needed + await updateRoleByName(roleName, updateObj); + } + logger.info(`Updated '${roleName}' role permissions`); } else { logger.info(`No changes needed for '${roleName}' role permissions`); @@ -155,10 +202,90 @@ const initializeRoles = async function () { } }; +/** + * Migrates roles from old schema to new schema structure. + * This can be called directly to fix existing roles. + * + * @param {string} [roleName] - Optional specific role to migrate. If not provided, migrates all roles. + * @returns {Promise} Number of roles migrated. + */ +const migrateRoleSchema = async function (roleName) { + try { + // Get roles to migrate + let roles; + if (roleName) { + const role = await Role.findOne({ name: roleName }); + roles = role ? [role] : []; + } else { + roles = await Role.find({}); + } + + logger.info(`Migrating ${roles.length} roles to new schema structure`); + let migratedCount = 0; + + for (const role of roles) { + const permissionTypes = Object.keys(permissionsSchema.shape || {}); + const unsetFields = {}; + let hasOldSchema = false; + + // Check for old schema fields + for (const permType of permissionTypes) { + if (role[permType] && typeof role[permType] === 'object') { + hasOldSchema = true; + + // Ensure permissions object exists + role.permissions = role.permissions || {}; + + // Migrate permissions from old location to new + role.permissions[permType] = { + ...role.permissions[permType], + ...role[permType], + }; + + // Mark field for removal + unsetFields[permType] = 1; + } + } + + if (hasOldSchema) { + try { + logger.info(`Migrating role '${role.name}' from old schema structure`); + + // Simple update operation + await Role.updateOne( + { _id: role._id }, + { + $set: { permissions: role.permissions }, + $unset: unsetFields, + }, + ); + + // Refresh cache + const cache = getLogStores(CacheKeys.ROLES); + const updatedRole = await Role.findById(role._id).lean().exec(); + await cache.set(role.name, updatedRole); + + migratedCount++; + logger.info(`Migrated role '${role.name}'`); + } catch (error) { + logger.error(`Failed to migrate role '${role.name}': ${error.message}`); + } + } + } + + logger.info(`Migration complete: ${migratedCount} roles migrated`); + return migratedCount; + } catch (error) { + logger.error(`Role schema migration failed: ${error.message}`); + throw error; + } +}; + module.exports = { Role, getRoleByName, initializeRoles, updateRoleByName, updateAccessPermissions, + migrateRoleSchema, }; diff --git a/api/server/routes/roles.js b/api/server/routes/roles.js index e58ebb6fe7..17768c7de6 100644 --- a/api/server/routes/roles.js +++ b/api/server/routes/roles.js @@ -48,7 +48,7 @@ router.put('/:roleName/prompts', checkAdmin, async (req, res) => { const { roleName: _r } = req.params; // TODO: TEMP, use a better parsing for roleName const roleName = _r.toUpperCase(); - /** @type {TRole['PROMPTS']} */ + /** @type {TRole['permissions']['PROMPTS']} */ const updates = req.body; try { @@ -59,10 +59,16 @@ router.put('/:roleName/prompts', checkAdmin, async (req, res) => { return res.status(404).send({ message: 'Role not found' }); } + const currentPermissions = + role.permissions?.[PermissionTypes.PROMPTS] || role[PermissionTypes.PROMPTS] || {}; + const mergedUpdates = { - [PermissionTypes.PROMPTS]: { - ...role[PermissionTypes.PROMPTS], - ...parsedUpdates, + permissions: { + ...role.permissions, + [PermissionTypes.PROMPTS]: { + ...currentPermissions, + ...parsedUpdates, + }, }, }; @@ -81,7 +87,7 @@ router.put('/:roleName/agents', checkAdmin, async (req, res) => { const { roleName: _r } = req.params; // TODO: TEMP, use a better parsing for roleName const roleName = _r.toUpperCase(); - /** @type {TRole['AGENTS']} */ + /** @type {TRole['permissions']['AGENTS']} */ const updates = req.body; try { @@ -92,17 +98,23 @@ router.put('/:roleName/agents', checkAdmin, async (req, res) => { return res.status(404).send({ message: 'Role not found' }); } + const currentPermissions = + role.permissions?.[PermissionTypes.AGENTS] || role[PermissionTypes.AGENTS] || {}; + const mergedUpdates = { - [PermissionTypes.AGENTS]: { - ...role[PermissionTypes.AGENTS], - ...parsedUpdates, + permissions: { + ...role.permissions, + [PermissionTypes.AGENTS]: { + ...currentPermissions, + ...parsedUpdates, + }, }, }; const updatedRole = await updateRoleByName(roleName, mergedUpdates); res.status(200).send(updatedRole); } catch (error) { - return res.status(400).send({ message: 'Invalid prompt permissions.', error: error.errors }); + return res.status(400).send({ message: 'Invalid agent permissions.', error: error.errors }); } }); diff --git a/client/src/components/Prompts/AdminSettings.tsx b/client/src/components/Prompts/AdminSettings.tsx index 7f019fb343..5311e2b37c 100644 --- a/client/src/components/Prompts/AdminSettings.tsx +++ b/client/src/components/Prompts/AdminSettings.tsx @@ -80,10 +80,10 @@ const AdminSettings = () => { const [selectedRole, setSelectedRole] = useState(SystemRoles.USER); const defaultValues = useMemo(() => { - if (roles?.[selectedRole]) { - return roles[selectedRole][PermissionTypes.PROMPTS]; + if (roles?.[selectedRole]?.permissions) { + return roles[selectedRole].permissions[PermissionTypes.PROMPTS]; } - return roleDefaults[selectedRole][PermissionTypes.PROMPTS]; + return roleDefaults[selectedRole].permissions[PermissionTypes.PROMPTS]; }, [roles, selectedRole]); const { @@ -99,10 +99,10 @@ const AdminSettings = () => { }); useEffect(() => { - if (roles?.[selectedRole]?.[PermissionTypes.PROMPTS]) { - reset(roles[selectedRole][PermissionTypes.PROMPTS]); + if (roles?.[selectedRole]?.permissions?.[PermissionTypes.PROMPTS]) { + reset(roles[selectedRole].permissions[PermissionTypes.PROMPTS]); } else { - reset(roleDefaults[selectedRole][PermissionTypes.PROMPTS]); + reset(roleDefaults[selectedRole].permissions[PermissionTypes.PROMPTS]); } }, [roles, selectedRole, reset]); diff --git a/client/src/components/SidePanel/Agents/AdminSettings.tsx b/client/src/components/SidePanel/Agents/AdminSettings.tsx index 5fb13fd045..cd5c0679fc 100644 --- a/client/src/components/SidePanel/Agents/AdminSettings.tsx +++ b/client/src/components/SidePanel/Agents/AdminSettings.tsx @@ -72,10 +72,10 @@ const AdminSettings = () => { const [selectedRole, setSelectedRole] = useState(SystemRoles.USER); const defaultValues = useMemo(() => { - if (roles?.[selectedRole]) { - return roles[selectedRole][PermissionTypes.AGENTS]; + if (roles?.[selectedRole]?.permissions) { + return roles[selectedRole].permissions[PermissionTypes.AGENTS]; } - return roleDefaults[selectedRole][PermissionTypes.AGENTS]; + return roleDefaults[selectedRole].permissions[PermissionTypes.AGENTS]; }, [roles, selectedRole]); const { @@ -91,10 +91,10 @@ const AdminSettings = () => { }); useEffect(() => { - if (roles?.[selectedRole]?.[PermissionTypes.AGENTS]) { - reset(roles[selectedRole][PermissionTypes.AGENTS]); + if (roles?.[selectedRole]?.permissions?.[PermissionTypes.AGENTS]) { + reset(roles[selectedRole].permissions[PermissionTypes.AGENTS]); } else { - reset(roleDefaults[selectedRole][PermissionTypes.AGENTS]); + reset(roleDefaults[selectedRole].permissions[PermissionTypes.AGENTS]); } }, [roles, selectedRole, reset]); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 13635d453c..88b957d5a7 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -482,7 +482,7 @@ "com_ui_agent_editing_allowed": "Other users can already edit this agent", "com_ui_agent_recursion_limit": "Max Agent Steps", "com_ui_agent_recursion_limit_info": "Limits how many steps the agent can take in a run before giving a final response. Default is 25 steps. A step is either an AI API request or a tool usage round. For example, a basic tool interaction takes 3 steps: initial request, tool usage, and follow-up request.", - "com_ui_agent_shared_to_all": "something needs to go here. was empty", + "com_ui_agent_shared_to_all": "Agent is currently shared to all", "com_ui_agent_var": "{{0}} agent", "com_ui_agents": "Agents", "com_ui_agents_allow_create": "Allow creating Agents", diff --git a/packages/data-provider/src/roles.ts b/packages/data-provider/src/roles.ts index ec863fd94f..ca7d64b206 100644 --- a/packages/data-provider/src/roles.ts +++ b/packages/data-provider/src/roles.ts @@ -74,12 +74,28 @@ export const roleDefaults = defaultRolesSchema.parse({ [SystemRoles.ADMIN]: { name: SystemRoles.ADMIN, permissions: { - [PermissionTypes.PROMPTS]: {}, - [PermissionTypes.BOOKMARKS]: {}, - [PermissionTypes.AGENTS]: {}, - [PermissionTypes.MULTI_CONVO]: {}, - [PermissionTypes.TEMPORARY_CHAT]: {}, - [PermissionTypes.RUN_CODE]: {}, + [PermissionTypes.PROMPTS]: { + [Permissions.SHARED_GLOBAL]: true, + [Permissions.USE]: true, + [Permissions.CREATE]: true, + }, + [PermissionTypes.BOOKMARKS]: { + [Permissions.USE]: true, + }, + [PermissionTypes.AGENTS]: { + [Permissions.SHARED_GLOBAL]: true, + [Permissions.USE]: true, + [Permissions.CREATE]: true, + }, + [PermissionTypes.MULTI_CONVO]: { + [Permissions.USE]: true, + }, + [PermissionTypes.TEMPORARY_CHAT]: { + [Permissions.USE]: true, + }, + [PermissionTypes.RUN_CODE]: { + [Permissions.USE]: true, + }, }, }, [SystemRoles.USER]: { From 018143b5cc91a307647e4f161a82de0a0ee41725 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:57:49 +0200 Subject: [PATCH 04/75] =?UTF-8?q?=F0=9F=97=A8=EF=B8=8F=20fix:=20Show=20Mod?= =?UTF-8?q?elSpec=20Greeting=20(#6770)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/Chat/Landing.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/components/Chat/Landing.tsx b/client/src/components/Chat/Landing.tsx index 484a38b904..bd2945e242 100644 --- a/client/src/components/Chat/Landing.tsx +++ b/client/src/components/Chat/Landing.tsx @@ -52,7 +52,7 @@ export default function Landing({ centerFormOnLanding }: { centerFormOnLanding: }); const name = entity?.name ?? ''; - const description = entity?.description ?? ''; + const description = (entity?.description || conversation?.greeting) ?? ''; const getGreeting = useCallback(() => { if (typeof startupConfig?.interface?.customWelcome === 'string') { @@ -186,8 +186,8 @@ export default function Landing({ centerFormOnLanding }: { centerFormOnLanding: /> )} - {(isAgent || isAssistant) && description && ( -
+ {description && ( +
{description}
)} From 910c73359b81b9e7215a9c89ebf90dc14826c9f9 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 7 Apr 2025 19:16:56 -0400 Subject: [PATCH 05/75] =?UTF-8?q?=F0=9F=94=A6=20feat:=20MCP=20Support=20fo?= =?UTF-8?q?r=20Non-Agent=20Endpoints=20(#6775)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip: mcp select * refactor: Update useAvailableToolsQuery to support generic data types * feat: Enhance MCPSelect to dynamically load server options and improve MultiSelect component styling * WIP: ephemeral agents * wip: Add null check for MCPSelect and improve MultiSelect focus handling * feat: Pass conversationId prop to MCPSelect in BadgeRow to optimize badge rendering * feat: useApplyNewAgentTemplate hook to manage ephemeral agent upon conversation creation * WIP: eph. agent payload * refactor(OpenAIClient): streamline message processing by replacing content handling with parseTextParts function * feat: enhance applyAgentTemplate function to accept source conversation ID for improved template application * feat(parsers): add skipReasoning parameter to parseTextParts for conditional reasoning handling * WIP: first pass, ephemeral agent backend processing * chore: import order * feat: update loadEphemeralAgent and loadAgent functions to accept model_parameters for enhanced agent configuration * feat: add showMCPServers prop to BadgeRow for conditional rendering of MCPSelect, fix react rule violation * feat: enhance MCPSelect with localized placeholder and custom icon, add renderSelectedValues callback * feat: simplify message processing in AnthropicClient by replacing content handling with parseTextParts function * feat: implement useLocalStorage hook for managing MCP values and update MCPSelect to utilize it * chore: remove chatGPTBrowserSchema from endpoint schemas and update types for improved schema management * chore: remove compactChatGPTSchema from endpoint schemas and update types for better schema management * refactor: rename schemas for clarity and improve schema management * feat: extend model detection to include 'codestral' alongside 'mistral' * feat: add endpointType parameter to buildOptions and initializeClient functions * fix: update condition for handling completion in BaseClient to include agents client * refactor: simplify payload parsing logic in AgentClient and remove unused providerParsers * refactor: change useSetRecoilState to useRecoilState for better state management in MCPSelect component * refactor: streamline chat route handlers by consolidating middleware and improving endpoint structure * style: update MCPSelect and MultiSelect components for improved layout in mobile view * v0.7.790 * feat: add getMessageMapMethod to process message text and content in GoogleClient * chore: include LAST_MCP_ key prefix in clearLocalStorage function for proper teardown on logout --- api/app/clients/AnthropicClient.js | 12 +- api/app/clients/BaseClient.js | 3 +- api/app/clients/GoogleClient.js | 17 + api/app/clients/OpenAIClient.js | 12 +- api/models/Agent.js | 53 ++- api/server/controllers/agents/client.js | 54 ++- api/server/middleware/buildEndpointOption.js | 14 +- api/server/routes/agents/chat.js | 33 +- api/server/services/Endpoints/agents/build.js | 8 +- .../services/Endpoints/agents/initialize.js | 7 +- client/src/components/Chat/Input/BadgeRow.tsx | 12 +- client/src/components/Chat/Input/ChatForm.tsx | 8 +- .../src/components/Chat/Input/MCPSelect.tsx | 82 +++++ client/src/components/ui/MCPIcon.tsx | 31 ++ client/src/components/ui/MultiSelect.tsx | 128 +++++++ client/src/components/ui/index.ts | 1 + client/src/data-provider/queries.ts | 8 +- client/src/hooks/Chat/useChatFunctions.ts | 5 +- client/src/hooks/SSE/useEventHandlers.ts | 8 +- client/src/hooks/useLocalStorageAlt.tsx | 61 ++++ client/src/locales/en/translation.json | 8 +- client/src/store/agents.ts | 88 +++++ client/src/store/index.ts | 1 + client/src/utils/localStorage.ts | 1 + package-lock.json | 2 +- packages/data-provider/package.json | 2 +- packages/data-provider/src/config.ts | 4 + packages/data-provider/src/createPayload.ts | 15 +- packages/data-provider/src/parsers.ts | 31 +- packages/data-provider/src/schemas.ts | 311 ++++++++---------- packages/data-provider/src/types.ts | 6 + 31 files changed, 741 insertions(+), 285 deletions(-) create mode 100644 client/src/components/Chat/Input/MCPSelect.tsx create mode 100644 client/src/components/ui/MCPIcon.tsx create mode 100644 client/src/components/ui/MultiSelect.tsx create mode 100644 client/src/hooks/useLocalStorageAlt.tsx create mode 100644 client/src/store/agents.ts diff --git a/api/app/clients/AnthropicClient.js b/api/app/clients/AnthropicClient.js index a1fc03a256..bc2e6042c7 100644 --- a/api/app/clients/AnthropicClient.js +++ b/api/app/clients/AnthropicClient.js @@ -4,6 +4,7 @@ const { Constants, ErrorTypes, EModelEndpoint, + parseTextParts, anthropicSettings, getResponseSender, validateVisionModel, @@ -696,15 +697,8 @@ class AnthropicClient extends BaseClient { if (msg.text != null && msg.text && msg.text.startsWith(':::thinking')) { msg.text = msg.text.replace(/:::thinking.*?:::/gs, '').trim(); } else if (msg.content != null) { - /** @type {import('@librechat/agents').MessageContentComplex} */ - const newContent = []; - for (let part of msg.content) { - if (part.think != null) { - continue; - } - newContent.push(part); - } - msg.content = newContent; + msg.text = parseTextParts(msg.content, true); + delete msg.content; } return msg; diff --git a/api/app/clients/BaseClient.js b/api/app/clients/BaseClient.js index fd1a051832..f89f1b3a8e 100644 --- a/api/app/clients/BaseClient.js +++ b/api/app/clients/BaseClient.js @@ -676,7 +676,8 @@ class BaseClient { responseMessage.text = addSpaceIfNeeded(generation) + completion; } else if ( Array.isArray(completion) && - isParamEndpoint(this.options.endpoint, this.options.endpointType) + (this.clientName === EModelEndpoint.agents || + isParamEndpoint(this.options.endpoint, this.options.endpointType)) ) { responseMessage.text = ''; responseMessage.content = completion; diff --git a/api/app/clients/GoogleClient.js b/api/app/clients/GoogleClient.js index a9831649d4..575065d879 100644 --- a/api/app/clients/GoogleClient.js +++ b/api/app/clients/GoogleClient.js @@ -9,6 +9,7 @@ const { validateVisionModel, getResponseSender, endpointSettings, + parseTextParts, EModelEndpoint, ContentTypes, VisionModes, @@ -774,6 +775,22 @@ class GoogleClient extends BaseClient { return this.usage; } + getMessageMapMethod() { + /** + * @param {TMessage} msg + */ + return (msg) => { + if (msg.text != null && msg.text && msg.text.startsWith(':::thinking')) { + msg.text = msg.text.replace(/:::thinking.*?:::/gs, '').trim(); + } else if (msg.content != null) { + msg.text = parseTextParts(msg.content, true); + delete msg.content; + } + + return msg; + }; + } + /** * Calculates the correct token count for the current user message based on the token count map and API usage. * Edge case: If the calculation results in a negative value, it returns the original estimate. diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index 6b1afa389d..179f5c986e 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -6,6 +6,7 @@ const { Constants, ImageDetail, ContentTypes, + parseTextParts, EModelEndpoint, resolveHeaders, KnownEndpoints, @@ -1121,15 +1122,8 @@ ${convo} if (msg.text != null && msg.text && msg.text.startsWith(':::thinking')) { msg.text = msg.text.replace(/:::thinking.*?:::/gs, '').trim(); } else if (msg.content != null) { - /** @type {import('@librechat/agents').MessageContentComplex} */ - const newContent = []; - for (let part of msg.content) { - if (part.think != null) { - continue; - } - newContent.push(part); - } - msg.content = newContent; + msg.text = parseTextParts(msg.content, true); + delete msg.content; } return msg; diff --git a/api/models/Agent.js b/api/models/Agent.js index 5840c61d7b..7c0a630808 100644 --- a/api/models/Agent.js +++ b/api/models/Agent.js @@ -1,6 +1,8 @@ const mongoose = require('mongoose'); +const { agentSchema } = require('@librechat/data-schemas'); const { SystemRoles } = require('librechat-data-provider'); -const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants; +const { GLOBAL_PROJECT_NAME, EPHEMERAL_AGENT_ID, mcp_delimiter } = + require('librechat-data-provider').Constants; const { CONFIG_STORE, STARTUP_CONFIG } = require('librechat-data-provider').CacheKeys; const { getProjectByName, @@ -9,7 +11,6 @@ const { removeAgentFromAllProjects, } = require('./Project'); const getLogStores = require('~/cache/getLogStores'); -const { agentSchema } = require('@librechat/data-schemas'); const Agent = mongoose.model('agent', agentSchema); @@ -39,9 +40,55 @@ const getAgent = async (searchParameter) => await Agent.findOne(searchParameter) * @param {Object} params * @param {ServerRequest} params.req * @param {string} params.agent_id + * @param {string} params.endpoint + * @param {import('@librechat/agents').ClientOptions} [params.model_parameters] + * @returns {Agent|null} The agent document as a plain object, or null if not found. + */ +const loadEphemeralAgent = ({ req, agent_id, endpoint, model_parameters: _m }) => { + const { model, ...model_parameters } = _m; + /** @type {Record} */ + const availableTools = req.app.locals.availableTools; + const mcpServers = new Set(req.body.ephemeralAgent?.mcp); + /** @type {string[]} */ + const tools = []; + + for (const toolName of Object.keys(availableTools)) { + if (!toolName.includes(mcp_delimiter)) { + continue; + } + const mcpServer = toolName.split(mcp_delimiter)?.[1]; + if (mcpServer && mcpServers.has(mcpServer)) { + tools.push(toolName); + } + } + const instructions = req.body.promptPrefix; + return { + id: agent_id, + instructions, + provider: endpoint, + model_parameters, + model, + tools, + }; +}; + +/** + * Load an agent based on the provided ID + * + * @param {Object} params + * @param {ServerRequest} params.req + * @param {string} params.agent_id + * @param {string} params.endpoint + * @param {import('@librechat/agents').ClientOptions} [params.model_parameters] * @returns {Promise} The agent document as a plain object, or null if not found. */ -const loadAgent = async ({ req, agent_id }) => { +const loadAgent = async ({ req, agent_id, endpoint, model_parameters }) => { + if (!agent_id) { + return null; + } + if (agent_id === EPHEMERAL_AGENT_ID) { + return loadEphemeralAgent({ req, agent_id, endpoint, model_parameters }); + } const agent = await getAgent({ id: agent_id, }); diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index ee23ee1db6..ff98d80a13 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -20,11 +20,9 @@ const { const { Constants, VisionModes, - openAISchema, ContentTypes, EModelEndpoint, KnownEndpoints, - anthropicSchema, isAgentsEndpoint, AgentCapabilities, bedrockInputSchema, @@ -43,11 +41,18 @@ const { createRun } = require('./run'); /** @typedef {import('@librechat/agents').MessageContentComplex} MessageContentComplex */ /** @typedef {import('@langchain/core/runnables').RunnableConfig} RunnableConfig */ -const providerParsers = { - [EModelEndpoint.openAI]: openAISchema.parse, - [EModelEndpoint.azureOpenAI]: openAISchema.parse, - [EModelEndpoint.anthropic]: anthropicSchema.parse, - [EModelEndpoint.bedrock]: bedrockInputSchema.parse, +/** + * @param {ServerRequest} req + * @param {Agent} agent + * @param {string} endpoint + */ +const payloadParser = ({ req, agent, endpoint }) => { + if (isAgentsEndpoint(endpoint)) { + return { model: undefined }; + } else if (endpoint === EModelEndpoint.bedrock) { + return bedrockInputSchema.parse(agent.model_parameters); + } + return req.body.endpointOption.model_parameters; }; const legacyContentEndpoints = new Set([KnownEndpoints.groq, KnownEndpoints.deepseek]); @@ -180,28 +185,19 @@ class AgentClient extends BaseClient { } getSaveOptions() { - const parseOptions = providerParsers[this.options.endpoint]; - let runOptions = - this.options.endpoint === EModelEndpoint.agents - ? { - model: undefined, - // TODO: - // would need to be override settings; otherwise, model needs to be undefined - // model: this.override.model, - // instructions: this.override.instructions, - // additional_instructions: this.override.additional_instructions, - } - : {}; - - if (parseOptions) { - try { - runOptions = parseOptions(this.options.agent.model_parameters); - } catch (error) { - logger.error( - '[api/server/controllers/agents/client.js #getSaveOptions] Error parsing options', - error, - ); - } + // TODO: + // would need to be override settings; otherwise, model needs to be undefined + // model: this.override.model, + // instructions: this.override.instructions, + // additional_instructions: this.override.additional_instructions, + let runOptions = {}; + try { + runOptions = payloadParser(this.options); + } catch (error) { + logger.error( + '[api/server/controllers/agents/client.js #getSaveOptions] Error parsing options', + error, + ); } return removeNullishValues( diff --git a/api/server/middleware/buildEndpointOption.js b/api/server/middleware/buildEndpointOption.js index 041864b025..8394223b5e 100644 --- a/api/server/middleware/buildEndpointOption.js +++ b/api/server/middleware/buildEndpointOption.js @@ -1,6 +1,11 @@ -const { parseCompactConvo, EModelEndpoint, isAgentsEndpoint } = require('librechat-data-provider'); -const { getModelsConfig } = require('~/server/controllers/ModelController'); +const { + parseCompactConvo, + EModelEndpoint, + isAgentsEndpoint, + EndpointURLs, +} = require('librechat-data-provider'); const azureAssistants = require('~/server/services/Endpoints/azureAssistants'); +const { getModelsConfig } = require('~/server/controllers/ModelController'); const assistants = require('~/server/services/Endpoints/assistants'); const gptPlugins = require('~/server/services/Endpoints/gptPlugins'); const { processFiles } = require('~/server/services/Files/process'); @@ -77,8 +82,9 @@ async function buildEndpointOption(req, res, next) { } try { - const isAgents = isAgentsEndpoint(endpoint); - const endpointFn = buildFunction[endpointType ?? endpoint]; + const isAgents = + isAgentsEndpoint(endpoint) || req.baseUrl.startsWith(EndpointURLs[EModelEndpoint.agents]); + const endpointFn = buildFunction[isAgents ? EModelEndpoint.agents : (endpointType ?? endpoint)]; const builder = isAgents ? (...args) => endpointFn(req, ...args) : endpointFn; // TODO: use object params diff --git a/api/server/routes/agents/chat.js b/api/server/routes/agents/chat.js index 42a18d0100..fe50fdc765 100644 --- a/api/server/routes/agents/chat.js +++ b/api/server/routes/agents/chat.js @@ -20,24 +20,33 @@ router.post('/abort', handleAbort()); const checkAgentAccess = generateCheckAccess(PermissionTypes.AGENTS, [Permissions.USE]); +router.use(checkAgentAccess); +router.use(validateConvoAccess); +router.use(buildEndpointOption); +router.use(setHeaders); + +const controller = async (req, res, next) => { + await AgentController(req, res, next, initializeClient, addTitle); +}; + /** - * @route POST / + * @route POST / (regular endpoint) * @desc Chat with an assistant * @access Public * @param {express.Request} req - The request object, containing the request data. * @param {express.Response} res - The response object, used to send back a response. * @returns {void} */ -router.post( - '/', - // validateModel, - checkAgentAccess, - validateConvoAccess, - buildEndpointOption, - setHeaders, - async (req, res, next) => { - await AgentController(req, res, next, initializeClient, addTitle); - }, -); +router.post('/', controller); + +/** + * @route POST /:endpoint (ephemeral agents) + * @desc Chat with an assistant + * @access Public + * @param {express.Request} req - The request object, containing the request data. + * @param {express.Response} res - The response object, used to send back a response. + * @returns {void} + */ +router.post('/:endpoint', controller); module.exports = router; diff --git a/api/server/services/Endpoints/agents/build.js b/api/server/services/Endpoints/agents/build.js index 999cdc16be..77ebbc58dc 100644 --- a/api/server/services/Endpoints/agents/build.js +++ b/api/server/services/Endpoints/agents/build.js @@ -1,12 +1,15 @@ +const { isAgentsEndpoint, Constants } = require('librechat-data-provider'); const { loadAgent } = require('~/models/Agent'); const { logger } = require('~/config'); -const buildOptions = (req, endpoint, parsedBody) => { +const buildOptions = (req, endpoint, parsedBody, endpointType) => { const { spec, iconURL, agent_id, instructions, maxContextTokens, ...model_parameters } = parsedBody; const agentPromise = loadAgent({ req, - agent_id, + agent_id: isAgentsEndpoint(endpoint) ? agent_id : Constants.EPHEMERAL_AGENT_ID, + endpoint, + model_parameters, }).catch((error) => { logger.error(`[/agents/:${agent_id}] Error retrieving agent during build options step`, error); return undefined; @@ -17,6 +20,7 @@ const buildOptions = (req, endpoint, parsedBody) => { iconURL, endpoint, agent_id, + endpointType, instructions, maxContextTokens, model_parameters, diff --git a/api/server/services/Endpoints/agents/initialize.js b/api/server/services/Endpoints/agents/initialize.js index 0a76f906e0..0186541750 100644 --- a/api/server/services/Endpoints/agents/initialize.js +++ b/api/server/services/Endpoints/agents/initialize.js @@ -1,5 +1,6 @@ const { createContentAggregator, Providers } = require('@librechat/agents'); const { + Constants, ErrorTypes, EModelEndpoint, getResponseSender, @@ -322,10 +323,14 @@ const initializeClient = async ({ req, res, endpointOption }) => { agent: primaryConfig, spec: endpointOption.spec, iconURL: endpointOption.iconURL, - endpoint: EModelEndpoint.agents, attachments: primaryConfig.attachments, + endpointType: endpointOption.endpointType, maxContextTokens: primaryConfig.maxContextTokens, resendFiles: primaryConfig.model_parameters?.resendFiles ?? true, + endpoint: + primaryConfig.id === Constants.EPHEMERAL_AGENT_ID + ? primaryConfig.endpoint + : EModelEndpoint.agents, }); return { client }; diff --git a/client/src/components/Chat/Input/BadgeRow.tsx b/client/src/components/Chat/Input/BadgeRow.tsx index 14309acf13..0de28cb347 100644 --- a/client/src/components/Chat/Input/BadgeRow.tsx +++ b/client/src/components/Chat/Input/BadgeRow.tsx @@ -1,4 +1,5 @@ import React, { + memo, useState, useRef, useEffect, @@ -12,11 +13,14 @@ import type { LucideIcon } from 'lucide-react'; import type { BadgeItem } from '~/common'; import { useChatBadges } from '~/hooks'; import { Badge } from '~/components/ui'; +import MCPSelect from './MCPSelect'; import store from '~/store'; interface BadgeRowProps { + showMCPServers?: boolean; onChange: (badges: Pick[]) => void; onToggle?: (badgeId: string, currentActive: boolean) => void; + conversationId?: string | null; isInChat: boolean; } @@ -33,7 +37,8 @@ interface BadgeWrapperProps { const BadgeWrapper = React.memo( forwardRef( ({ badge, isEditing, isInChat, onToggle, onDelete, onMouseDown, badgeRefs }, ref) => { - const isActive = badge.atom ? useRecoilValue(badge.atom) : false; + const atomBadge = useRecoilValue(badge.atom); + const isActive = badge.atom ? atomBadge : false; return (
{ } }; -export function BadgeRow({ onChange, onToggle, isInChat }: BadgeRowProps) { +function BadgeRow({ showMCPServers, conversationId, onChange, onToggle, isInChat }: BadgeRowProps) { const [orderedBadges, setOrderedBadges] = useState([]); const [dragState, dispatch] = useReducer(dragReducer, { draggedBadge: null, @@ -340,6 +345,7 @@ export function BadgeRow({ onChange, onToggle, isInChat }: BadgeRowProps) { />
)} + {showMCPServers === true && } {ghostBadge && (
); } + +export default memo(BadgeRow); diff --git a/client/src/components/Chat/Input/ChatForm.tsx b/client/src/components/Chat/Input/ChatForm.tsx index 8d8714f68f..007c139d76 100644 --- a/client/src/components/Chat/Input/ChatForm.tsx +++ b/client/src/components/Chat/Input/ChatForm.tsx @@ -1,7 +1,7 @@ import { memo, useRef, useMemo, useEffect, useState, useCallback } from 'react'; import { useWatch } from 'react-hook-form'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { Constants, isAssistantsEndpoint } from 'librechat-data-provider'; +import { Constants, isAssistantsEndpoint, isAgentsEndpoint } from 'librechat-data-provider'; import { useChatContext, useChatFormContext, @@ -28,8 +28,8 @@ import CollapseChat from './CollapseChat'; import StreamAudio from './StreamAudio'; import StopButton from './StopButton'; import SendButton from './SendButton'; -import { BadgeRow } from './BadgeRow'; import EditBadges from './EditBadges'; +import BadgeRow from './BadgeRow'; import Mention from './Mention'; import store from '~/store'; @@ -289,7 +289,9 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
setBadges(newBadges)} + showMCPServers={!isAgentsEndpoint(endpoint) && !isAssistantsEndpoint(endpoint)} + conversationId={conversation?.conversationId ?? Constants.NEW_CONVO} + onChange={setBadges} isInChat={ Array.isArray(conversation?.messages) && conversation.messages.length >= 1 } diff --git a/client/src/components/Chat/Input/MCPSelect.tsx b/client/src/components/Chat/Input/MCPSelect.tsx new file mode 100644 index 0000000000..adbdc29750 --- /dev/null +++ b/client/src/components/Chat/Input/MCPSelect.tsx @@ -0,0 +1,82 @@ +import React, { memo, useCallback } from 'react'; +import { useRecoilState } from 'recoil'; +import { Constants, EModelEndpoint, LocalStorageKeys } from 'librechat-data-provider'; +import { useAvailableToolsQuery } from '~/data-provider'; +import useLocalStorage from '~/hooks/useLocalStorageAlt'; +import MultiSelect from '~/components/ui/MultiSelect'; +import { ephemeralAgentByConvoId } from '~/store'; +import MCPIcon from '~/components/ui/MCPIcon'; +import { useLocalize } from '~/hooks'; + +function MCPSelect({ conversationId }: { conversationId?: string | null }) { + const localize = useLocalize(); + const key = conversationId ?? Constants.NEW_CONVO; + const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key)); + const setSelectedValues = useCallback( + (values: string[] | null | undefined) => { + if (!values) { + return; + } + if (!Array.isArray(values)) { + return; + } + setEphemeralAgent((prev) => ({ + ...prev, + mcp: values, + })); + }, + [setEphemeralAgent], + ); + const [mcpValues, setMCPValues] = useLocalStorage( + `${LocalStorageKeys.LAST_MCP_}${key}`, + ephemeralAgent?.mcp ?? [], + setSelectedValues, + ); + const { data: mcpServers } = useAvailableToolsQuery(EModelEndpoint.agents, { + select: (data) => { + const serverNames = new Set(); + data.forEach((tool) => { + if (tool.pluginKey.includes(Constants.mcp_delimiter)) { + const parts = tool.pluginKey.split(Constants.mcp_delimiter); + serverNames.add(parts[parts.length - 1]); + } + }); + return [...serverNames]; + }, + }); + + const renderSelectedValues = useCallback( + (values: string[], placeholder?: string) => { + if (values.length === 0) { + return placeholder || localize('com_ui_select') + '...'; + } + if (values.length === 1) { + return values[0]; + } + return localize('com_ui_x_selected', { 0: values.length }); + }, + [localize], + ); + + if (!mcpServers || mcpServers.length === 0) { + return null; + } + + return ( + } + selectItemsClassName="border border-blue-600/50 bg-blue-500/10 hover:bg-blue-700/10" + selectClassName="group relative inline-flex items-center justify-center md:justify-start gap-1.5 rounded-full border border-border-medium text-sm font-medium transition-shadow md:w-full size-9 p-2 md:p-3 bg-surface-chat shadow-sm hover:bg-surface-hover hover:shadow-md active:shadow-inner" + /> + ); +} + +export default memo(MCPSelect); diff --git a/client/src/components/ui/MCPIcon.tsx b/client/src/components/ui/MCPIcon.tsx new file mode 100644 index 0000000000..ff4ca27350 --- /dev/null +++ b/client/src/components/ui/MCPIcon.tsx @@ -0,0 +1,31 @@ +export default function MCPIcon({ className }: { className?: string }) { + return ( + + + + + + ); +} diff --git a/client/src/components/ui/MultiSelect.tsx b/client/src/components/ui/MultiSelect.tsx new file mode 100644 index 0000000000..9de2780721 --- /dev/null +++ b/client/src/components/ui/MultiSelect.tsx @@ -0,0 +1,128 @@ +import React, { useRef } from 'react'; +import { + Select, + SelectArrow, + SelectItem, + SelectItemCheck, + SelectLabel, + SelectPopover, + SelectProvider, +} from '@ariakit/react'; +import { cn } from '~/utils'; + +interface MultiSelectProps { + items: T[]; + label?: string; + placeholder?: string; + defaultSelectedValues?: T[]; + onSelectedValuesChange?: (values: T[]) => void; + renderSelectedValues?: (values: T[], placeholder?: string) => React.ReactNode; + className?: string; + itemClassName?: string; + labelClassName?: string; + selectClassName?: string; + selectIcon?: React.ReactNode; + popoverClassName?: string; + selectItemsClassName?: string; + selectedValues: T[]; + setSelectedValues: (values: T[]) => void; +} + +function defaultRender(values: T[], placeholder?: string) { + if (values.length === 0) { + return placeholder || 'Select...'; + } + if (values.length === 1) { + return values[0]; + } + return `${values.length} items selected`; +} + +export default function MultiSelect({ + items, + label, + placeholder = 'Select...', + defaultSelectedValues = [], + onSelectedValuesChange, + renderSelectedValues = defaultRender, + className, + selectIcon, + itemClassName, + labelClassName, + selectClassName, + popoverClassName, + selectItemsClassName, + selectedValues = [], + setSelectedValues, +}: MultiSelectProps) { + const selectRef = useRef(null); + // const [selectedValues, setSelectedValues] = React.useState(defaultSelectedValues); + + const handleValueChange = (values: T[]) => { + setSelectedValues(values); + if (onSelectedValuesChange) { + onSelectedValuesChange(values); + } + }; + + return ( +
+ + {label && ( + + {label} + + )} + + + {items.map((value) => ( + + + {value} + + ))} + + +
+ ); +} diff --git a/client/src/components/ui/index.ts b/client/src/components/ui/index.ts index 0e9ee85c5d..8d9eed0c85 100644 --- a/client/src/components/ui/index.ts +++ b/client/src/components/ui/index.ts @@ -27,6 +27,7 @@ export * from './Pagination'; export * from './Progress'; export * from './InputOTP'; export { default as Badge } from './Badge'; +export { default as MCPIcon } from './MCPIcon'; export { default as Combobox } from './Combobox'; export { default as Dropdown } from './Dropdown'; export { default as SplitText } from './SplitText'; diff --git a/client/src/data-provider/queries.ts b/client/src/data-provider/queries.ts index b88d0797e9..96f7a87e5f 100644 --- a/client/src/data-provider/queries.ts +++ b/client/src/data-provider/queries.ts @@ -191,9 +191,10 @@ export const useConversationTagsQuery = ( /** * Hook for getting all available tools for Assistants */ -export const useAvailableToolsQuery = ( +export const useAvailableToolsQuery = ( endpoint: t.AssistantsEndpoint | EModelEndpoint.agents, -): QueryObserverResult => { + config?: UseQueryOptions, +): QueryObserverResult => { const queryClient = useQueryClient(); const endpointsConfig = queryClient.getQueryData([QueryKeys.endpoints]); const keyExpiry = queryClient.getQueryData([QueryKeys.name, endpoint]); @@ -202,7 +203,7 @@ export const useAvailableToolsQuery = ( const enabled = !!endpointsConfig?.[endpoint] && keyProvided; const version: string | number | undefined = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint]; - return useQuery( + return useQuery( [QueryKeys.tools], () => dataService.getAvailableTools(endpoint, version), { @@ -210,6 +211,7 @@ export const useAvailableToolsQuery = ( refetchOnReconnect: false, refetchOnMount: false, enabled, + ...config, }, ); }; diff --git a/client/src/hooks/Chat/useChatFunctions.ts b/client/src/hooks/Chat/useChatFunctions.ts index 7e4c30c8ff..d511a13597 100644 --- a/client/src/hooks/Chat/useChatFunctions.ts +++ b/client/src/hooks/Chat/useChatFunctions.ts @@ -20,10 +20,10 @@ import type { SetterOrUpdater } from 'recoil'; import type { TAskFunction, ExtendedFile } from '~/common'; import useSetFilesToDelete from '~/hooks/Files/useSetFilesToDelete'; import useGetSender from '~/hooks/Conversations/useGetSender'; +import store, { useGetEphemeralAgent } from '~/store'; import { getArtifactsMode } from '~/utils/artifacts'; import { getEndpointField, logger } from '~/utils'; import useUserKey from '~/hooks/Input/useUserKey'; -import store from '~/store'; const logChatRequest = (request: Record) => { logger.log('=====================================\nAsk function called with:'); @@ -64,6 +64,7 @@ export default function useChatFunctions({ setSubmission: SetterOrUpdater; setLatestMessage?: SetterOrUpdater; }) { + const getEphemeralAgent = useGetEphemeralAgent(); const codeArtifacts = useRecoilValue(store.codeArtifacts); const includeShadcnui = useRecoilValue(store.includeShadcnui); const customPromptMode = useRecoilValue(store.customPromptMode); @@ -118,6 +119,7 @@ export default function useChatFunctions({ return; } + const ephemeralAgent = getEphemeralAgent(conversationId ?? Constants.NEW_CONVO); const isEditOrContinue = isEdited || isContinued; let currentMessages: TMessage[] | null = overrideMessages ?? getMessages() ?? []; @@ -297,6 +299,7 @@ export default function useChatFunctions({ isRegenerate, initialResponse, isTemporary, + ephemeralAgent, }; if (isRegenerate) { diff --git a/client/src/hooks/SSE/useEventHandlers.ts b/client/src/hooks/SSE/useEventHandlers.ts index 55d1488197..6eb6941c8f 100644 --- a/client/src/hooks/SSE/useEventHandlers.ts +++ b/client/src/hooks/SSE/useEventHandlers.ts @@ -31,11 +31,11 @@ import { } from '~/utils'; import useAttachmentHandler from '~/hooks/SSE/useAttachmentHandler'; import useContentHandler from '~/hooks/SSE/useContentHandler'; +import store, { useApplyNewAgentTemplate } from '~/store'; import useStepHandler from '~/hooks/SSE/useStepHandler'; import { useAuthContext } from '~/hooks/AuthContext'; import { MESSAGE_UPDATE_INTERVAL } from '~/common'; import { useLiveAnnouncer } from '~/Providers'; -import store from '~/store'; type TSyncData = { sync: boolean; @@ -140,8 +140,9 @@ export default function useEventHandlers({ resetLatestMessage, }: EventHandlerParams) { const queryClient = useQueryClient(); - const setAbortScroll = useSetRecoilState(store.abortScroll); const { announcePolite } = useLiveAnnouncer(); + const applyAgentTemplate = useApplyNewAgentTemplate(); + const setAbortScroll = useSetRecoilState(store.abortScroll); const lastAnnouncementTimeRef = useRef(Date.now()); const { conversationId: paramId } = useParams(); @@ -364,6 +365,9 @@ export default function useEventHandlers({ }); let update = {} as TConversation; + if (conversationId) { + applyAgentTemplate(conversationId, submission.conversation.conversationId); + } if (setConversation && !isAddedRequest) { setConversation((prevState) => { let title = prevState?.title; diff --git a/client/src/hooks/useLocalStorageAlt.tsx b/client/src/hooks/useLocalStorageAlt.tsx new file mode 100644 index 0000000000..465d62c70b --- /dev/null +++ b/client/src/hooks/useLocalStorageAlt.tsx @@ -0,0 +1,61 @@ +/* `useLocalStorage` + * + * Features: + * - JSON Serializing + * - Also value will be updated everywhere, when value updated (via `storage` event) + */ + +import { useEffect, useState } from 'react'; + +export default function useLocalStorage( + key: string, + defaultValue: T, + globalSetState?: (value: T) => void, +): [T, (value: T) => void] { + const [value, setValue] = useState(defaultValue); + + useEffect(() => { + const item = localStorage.getItem(key); + + if (!item) { + localStorage.setItem(key, JSON.stringify(defaultValue)); + } + + const initialValue = item ? JSON.parse(item) : defaultValue; + setValue(initialValue); + if (globalSetState) { + globalSetState(initialValue); + } + + function handler(e: StorageEvent) { + if (e.key !== key) { + return; + } + + const lsi = localStorage.getItem(key); + setValue(JSON.parse(lsi ?? '')); + } + + window.addEventListener('storage', handler); + + return () => { + window.removeEventListener('storage', handler); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [key, globalSetState]); + + const setValueWrap = (value: T) => { + try { + setValue(value); + localStorage.setItem(key, JSON.stringify(value)); + if (typeof window !== 'undefined') { + window.dispatchEvent(new StorageEvent('storage', { key })); + } + globalSetState?.(value); + } catch (e) { + console.error(e); + } + }; + + return [value, setValueWrap]; +} diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 88b957d5a7..67c7f9360b 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -689,6 +689,7 @@ "com_ui_include_shadcnui_agent": "Include shadcn/ui instructions", "com_ui_input": "Input", "com_ui_instructions": "Instructions", + "com_ui_x_selected": "{{0}} selected", "com_ui_late_night": "Happy late night", "com_ui_latest_footer": "Every AI for Everyone.", "com_ui_latest_production_version": "Latest production version", @@ -701,6 +702,7 @@ "com_ui_logo": "{{0}} Logo", "com_ui_manage": "Manage", "com_ui_max_tags": "Maximum number allowed is {{0}}, using latest values.", + "com_ui_mcp_servers": "MCP Servers", "com_ui_mention": "Mention an endpoint, assistant, or preset to quickly switch to it", "com_ui_min_tags": "Cannot remove more values, a minimum of {{0}} are required.", "com_ui_misc": "Misc.", @@ -855,12 +857,6 @@ "com_ui_write": "Writing", "com_ui_yes": "Yes", "com_ui_zoom": "Zoom", - "com_ui_save_badge_changes": "Save badge changes?", - "com_ui_late_night": "Happy late night", - "com_ui_weekend_morning": "Happy weekend", - "com_ui_good_morning": "Good morning", - "com_ui_good_afternoon": "Good afternoon", - "com_ui_good_evening": "Good evening", "com_endpoint_deprecated": "Deprecated", "com_endpoint_deprecated_info": "This endpoint is deprecated and may be removed in future versions, please use the agent endpoint instead", "com_endpoint_deprecated_info_a11y": "The plugin endpoint is deprecated and may be removed in future versions, please use the agent endpoint instead", diff --git a/client/src/store/agents.ts b/client/src/store/agents.ts new file mode 100644 index 0000000000..c539345ec0 --- /dev/null +++ b/client/src/store/agents.ts @@ -0,0 +1,88 @@ +import { Constants } from 'librechat-data-provider'; +import { atomFamily, useRecoilCallback } from 'recoil'; +import type { TEphemeralAgent } from 'librechat-data-provider'; +import { logger } from '~/utils'; + +export const ephemeralAgentByConvoId = atomFamily({ + key: 'ephemeralAgentByConvoId', + default: null, + effects: [ + ({ onSet, node }) => { + onSet(async (newValue) => { + const conversationId = node.key.split('__')[1]?.replaceAll('"', ''); + logger.log('agents', 'Setting ephemeral agent:', { conversationId, newValue }); + }); + }, + ] as const, +}); + +/** + * Creates a callback function to apply the ephemeral agent state + * from the "new" conversation template to a specified conversation ID. + */ +export function useApplyNewAgentTemplate() { + const applyTemplate = useRecoilCallback( + ({ snapshot, set }) => + async (targetId: string, _sourceId: string | null = Constants.NEW_CONVO) => { + const sourceId = _sourceId || Constants.NEW_CONVO; + logger.log('agents', `Attempting to apply template from "${sourceId}" to "${targetId}"`); + + if (targetId === sourceId) { + logger.warn('agents', `Attempted to apply template to itself ("${sourceId}"). Skipping.`); + return; + } + + try { + // 1. Get the current agent state from the "new" conversation template using snapshot + // getPromise reads the value without subscribing + const agentTemplate = await snapshot.getPromise(ephemeralAgentByConvoId(sourceId)); + + // 2. Check if a template state actually exists + if (agentTemplate) { + logger.log('agents', `Applying agent template to "${targetId}":`, agentTemplate); + // 3. Set the state for the target conversation ID using the template value + set(ephemeralAgentByConvoId(targetId), agentTemplate); + } else { + // 4. Handle the case where the "new" template has no agent state (is null) + logger.warn( + 'agents', + `Agent template from "${sourceId}" is null or unset. Setting agent for "${targetId}" to null.`, + ); + // Explicitly set to null (or a default empty state if preferred) + set(ephemeralAgentByConvoId(targetId), null); + // Example: Or set to a default empty state: + // set(ephemeralAgentByConvoId(targetId), { mcp: [] }); + } + } catch (error) { + logger.error( + 'agents', + `Error applying agent template from "${sourceId}" to "${targetId}":`, + error, + ); + set(ephemeralAgentByConvoId(targetId), null); + } + }, + [], + ); + + return applyTemplate; +} + +/** + * Creates a callback function to get the current ephemeral agent state + * for a specified conversation ID without subscribing the component. + * Returns a Loadable object synchronously. + */ +export function useGetEphemeralAgent() { + const getEphemeralAgent = useRecoilCallback( + ({ snapshot }) => + (conversationId: string): TEphemeralAgent | null => { + logger.log('agents', `[useGetEphemeralAgent] Getting loadable for ID: ${conversationId}`); + const agentLoadable = snapshot.getLoadable(ephemeralAgentByConvoId(conversationId)); + return agentLoadable.contents as TEphemeralAgent | null; + }, + [], + ); + + return getEphemeralAgent; +} diff --git a/client/src/store/index.ts b/client/src/store/index.ts index af326ae890..7b7e0f0831 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -12,6 +12,7 @@ import lang from './language'; import settings from './settings'; import misc from './misc'; import isTemporary from './temporary'; +export * from './agents'; export default { ...artifacts, diff --git a/client/src/utils/localStorage.ts b/client/src/utils/localStorage.ts index 3c44551b21..35bd12232f 100644 --- a/client/src/utils/localStorage.ts +++ b/client/src/utils/localStorage.ts @@ -31,6 +31,7 @@ export function clearLocalStorage(skipFirst?: boolean) { return; } if ( + key.startsWith(LocalStorageKeys.LAST_MCP_) || key.startsWith(LocalStorageKeys.ASST_ID_PREFIX) || key.startsWith(LocalStorageKeys.AGENT_ID_PREFIX) || key.startsWith(LocalStorageKeys.LAST_CONVO_SETUP) || diff --git a/package-lock.json b/package-lock.json index 153b969ffd..17e53223c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43615,7 +43615,7 @@ }, "packages/data-provider": { "name": "librechat-data-provider", - "version": "0.7.789", + "version": "0.7.790", "license": "ISC", "dependencies": { "axios": "^1.8.2", diff --git a/packages/data-provider/package.json b/packages/data-provider/package.json index 4326608983..64f120503c 100644 --- a/packages/data-provider/package.json +++ b/packages/data-provider/package.json @@ -1,6 +1,6 @@ { "name": "librechat-data-provider", - "version": "0.7.789", + "version": "0.7.790", "description": "data services for librechat apps", "main": "dist/index.js", "module": "dist/index.es.js", diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index d59d93f6b7..1b8d815a7b 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -1246,6 +1246,8 @@ export enum Constants { GLOBAL_PROJECT_NAME = 'instance', /** Delimiter for MCP tools */ mcp_delimiter = '_mcp_', + /** Placeholder Agent ID for Ephemeral Agents */ + EPHEMERAL_AGENT_ID = 'ephemeral', } export enum LocalStorageKeys { @@ -1281,6 +1283,8 @@ export enum LocalStorageKeys { ENABLE_USER_MSG_MARKDOWN = 'enableUserMsgMarkdown', /** Key for displaying analysis tool code input */ SHOW_ANALYSIS_CODE = 'showAnalysisCode', + /** Last selected MCP values per conversation ID */ + LAST_MCP_ = 'LAST_MCP_', } export enum ForkOptions { diff --git a/packages/data-provider/src/createPayload.ts b/packages/data-provider/src/createPayload.ts index a03d7db032..19d94cec4f 100644 --- a/packages/data-provider/src/createPayload.ts +++ b/packages/data-provider/src/createPayload.ts @@ -3,8 +3,15 @@ import { EndpointURLs } from './config'; import * as s from './schemas'; export default function createPayload(submission: t.TSubmission) { - const { conversation, userMessage, endpointOption, isEdited, isContinued, isTemporary } = - submission; + const { + conversation, + userMessage, + endpointOption, + isEdited, + isContinued, + isTemporary, + ephemeralAgent, + } = submission; const { conversationId } = s.tConvoUpdateSchema.parse(conversation); const { endpoint, endpointType } = endpointOption as { endpoint: s.EModelEndpoint; @@ -12,16 +19,20 @@ export default function createPayload(submission: t.TSubmission) { }; let server = EndpointURLs[endpointType ?? endpoint]; + const isEphemeralAgent = (ephemeralAgent?.mcp?.length ?? 0) > 0 && !s.isAgentsEndpoint(endpoint); if (isEdited && s.isAssistantsEndpoint(endpoint)) { server += '/modify'; } else if (isEdited) { server = server.replace('/ask/', '/edit/'); + } else if (isEphemeralAgent) { + server = `${EndpointURLs[s.EModelEndpoint.agents]}/${endpoint}`; } const payload: t.TPayload = { ...userMessage, ...endpointOption, + ephemeralAgent: isEphemeralAgent ? ephemeralAgent : undefined, isContinued: !!(isEdited && isContinued), conversationId, isTemporary, diff --git a/packages/data-provider/src/parsers.ts b/packages/data-provider/src/parsers.ts index 21040a70d5..a644f7301c 100644 --- a/packages/data-provider/src/parsers.ts +++ b/packages/data-provider/src/parsers.ts @@ -13,8 +13,6 @@ import { // agentsSchema, compactAgentsSchema, compactGoogleSchema, - compactChatGPTSchema, - chatGPTBrowserSchema, compactPluginsSchema, compactAssistantSchema, } from './schemas'; @@ -26,19 +24,19 @@ type EndpointSchema = | typeof openAISchema | typeof googleSchema | typeof anthropicSchema - | typeof chatGPTBrowserSchema | typeof gptPluginsSchema | typeof assistantSchema | typeof compactAgentsSchema | typeof bedrockInputSchema; -const endpointSchemas: Record = { +type EndpointSchemaKey = Exclude; + +const endpointSchemas: Record = { [EModelEndpoint.openAI]: openAISchema, [EModelEndpoint.azureOpenAI]: openAISchema, [EModelEndpoint.custom]: openAISchema, [EModelEndpoint.google]: googleSchema, [EModelEndpoint.anthropic]: anthropicSchema, - [EModelEndpoint.chatGPTBrowser]: chatGPTBrowserSchema, [EModelEndpoint.gptPlugins]: gptPluginsSchema, [EModelEndpoint.assistants]: assistantSchema, [EModelEndpoint.azureAssistants]: assistantSchema, @@ -167,8 +165,8 @@ export const parseConvo = ({ conversation, possibleValues, }: { - endpoint: EModelEndpoint; - endpointType?: EModelEndpoint | null; + endpoint: EndpointSchemaKey; + endpointType?: EndpointSchemaKey | null; conversation: Partial | null; possibleValues?: TPossibleValues; // TODO: POC for default schema @@ -252,7 +250,7 @@ export const getResponseSender = (endpointOption: t.TEndpointOption): string => return modelLabel; } else if (model && extractOmniVersion(model)) { return extractOmniVersion(model); - } else if (model && model.includes('mistral')) { + } else if (model && (model.includes('mistral') || model.includes('codestral'))) { return 'Mistral'; } else if (model && model.includes('gpt-')) { const gptVersion = extractGPTVersion(model); @@ -288,7 +286,7 @@ export const getResponseSender = (endpointOption: t.TEndpointOption): string => return chatGptLabel; } else if (model && extractOmniVersion(model)) { return extractOmniVersion(model); - } else if (model && model.includes('mistral')) { + } else if (model && (model.includes('mistral') || model.includes('codestral'))) { return 'Mistral'; } else if (model && model.includes('gpt-')) { const gptVersion = extractGPTVersion(model); @@ -309,11 +307,10 @@ type CompactEndpointSchema = | typeof compactAgentsSchema | typeof compactGoogleSchema | typeof anthropicSchema - | typeof compactChatGPTSchema | typeof bedrockInputSchema | typeof compactPluginsSchema; -const compactEndpointSchemas: Record = { +const compactEndpointSchemas: Record = { [EModelEndpoint.openAI]: openAISchema, [EModelEndpoint.azureOpenAI]: openAISchema, [EModelEndpoint.custom]: openAISchema, @@ -323,7 +320,6 @@ const compactEndpointSchemas: Record = { [EModelEndpoint.google]: compactGoogleSchema, [EModelEndpoint.bedrock]: bedrockInputSchema, [EModelEndpoint.anthropic]: anthropicSchema, - [EModelEndpoint.chatGPTBrowser]: compactChatGPTSchema, [EModelEndpoint.gptPlugins]: compactPluginsSchema, }; @@ -333,8 +329,8 @@ export const parseCompactConvo = ({ conversation, possibleValues, }: { - endpoint?: EModelEndpoint; - endpointType?: EModelEndpoint | null; + endpoint?: EndpointSchemaKey; + endpointType?: EndpointSchemaKey | null; conversation: Partial; possibleValues?: TPossibleValues; // TODO: POC for default schema @@ -371,7 +367,10 @@ export const parseCompactConvo = ({ return convo; }; -export function parseTextParts(contentParts: a.TMessageContentParts[]): string { +export function parseTextParts( + contentParts: a.TMessageContentParts[], + skipReasoning: boolean = false, +): string { let result = ''; for (const part of contentParts) { @@ -390,7 +389,7 @@ export function parseTextParts(contentParts: a.TMessageContentParts[]): string { result += ' '; } result += textValue; - } else if (part.type === ContentTypes.THINK) { + } else if (part.type === ContentTypes.THINK && !skipReasoning) { const textValue = typeof part.think === 'string' ? part.think : ''; if ( result.length > 0 && diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index 66609ff6ba..b923a1a467 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -752,22 +752,23 @@ export const tConversationTagSchema = z.object({ }); export type TConversationTag = z.infer; -export const googleSchema = tConversationSchema - .pick({ - model: true, - modelLabel: true, - promptPrefix: true, - examples: true, - temperature: true, - maxOutputTokens: true, - artifacts: true, - topP: true, - topK: true, - iconURL: true, - greeting: true, - spec: true, - maxContextTokens: true, - }) +export const googleBaseSchema = tConversationSchema.pick({ + model: true, + modelLabel: true, + promptPrefix: true, + examples: true, + temperature: true, + maxOutputTokens: true, + artifacts: true, + topP: true, + topK: true, + iconURL: true, + greeting: true, + spec: true, + maxContextTokens: true, +}); + +export const googleSchema = googleBaseSchema .transform((obj: Partial) => removeNullishValues(obj)) .catch(() => ({})); @@ -790,36 +791,25 @@ export const googleGenConfigSchema = z .strip() .optional(); -export const chatGPTBrowserSchema = tConversationSchema - .pick({ - model: true, - }) - .transform((obj) => ({ - ...obj, - model: obj.model ?? 'text-davinci-002-render-sha', - })) - .catch(() => ({ - model: 'text-davinci-002-render-sha', - })); +const gptPluginsBaseSchema = tConversationSchema.pick({ + model: true, + modelLabel: true, + chatGptLabel: true, + promptPrefix: true, + temperature: true, + artifacts: true, + top_p: true, + presence_penalty: true, + frequency_penalty: true, + tools: true, + agentOptions: true, + iconURL: true, + greeting: true, + spec: true, + maxContextTokens: true, +}); -export const gptPluginsSchema = tConversationSchema - .pick({ - model: true, - modelLabel: true, - chatGptLabel: true, - promptPrefix: true, - temperature: true, - artifacts: true, - top_p: true, - presence_penalty: true, - frequency_penalty: true, - tools: true, - agentOptions: true, - iconURL: true, - greeting: true, - spec: true, - maxContextTokens: true, - }) +export const gptPluginsSchema = gptPluginsBaseSchema .transform((obj) => { const result = { ...obj, @@ -889,18 +879,19 @@ export function removeNullishValues>( return newObj; } -export const assistantSchema = tConversationSchema - .pick({ - model: true, - assistant_id: true, - instructions: true, - artifacts: true, - promptPrefix: true, - iconURL: true, - greeting: true, - spec: true, - append_current_datetime: true, - }) +const assistantBaseSchema = tConversationSchema.pick({ + model: true, + assistant_id: true, + instructions: true, + artifacts: true, + promptPrefix: true, + iconURL: true, + greeting: true, + spec: true, + append_current_datetime: true, +}); + +export const assistantSchema = assistantBaseSchema .transform((obj) => ({ ...obj, model: obj.model ?? openAISettings.model.default, @@ -923,37 +914,39 @@ export const assistantSchema = tConversationSchema append_current_datetime: false, })); -export const compactAssistantSchema = tConversationSchema - .pick({ - model: true, - assistant_id: true, - instructions: true, - promptPrefix: true, - artifacts: true, - iconURL: true, - greeting: true, - spec: true, - }) +const compactAssistantBaseSchema = tConversationSchema.pick({ + model: true, + assistant_id: true, + instructions: true, + promptPrefix: true, + artifacts: true, + iconURL: true, + greeting: true, + spec: true, +}); + +export const compactAssistantSchema = compactAssistantBaseSchema .transform((obj) => removeNullishValues(obj)) .catch(() => ({})); -export const agentsSchema = tConversationSchema - .pick({ - model: true, - modelLabel: true, - temperature: true, - top_p: true, - presence_penalty: true, - frequency_penalty: true, - resendFiles: true, - imageDetail: true, - agent_id: true, - instructions: true, - promptPrefix: true, - iconURL: true, - greeting: true, - maxContextTokens: true, - }) +export const agentsBaseSchema = tConversationSchema.pick({ + model: true, + modelLabel: true, + temperature: true, + top_p: true, + presence_penalty: true, + frequency_penalty: true, + resendFiles: true, + imageDetail: true, + agent_id: true, + instructions: true, + promptPrefix: true, + iconURL: true, + greeting: true, + maxContextTokens: true, +}); + +export const agentsSchema = agentsBaseSchema .transform((obj) => ({ ...obj, model: obj.model ?? agentsSettings.model.default, @@ -989,46 +982,32 @@ export const agentsSchema = tConversationSchema maxContextTokens: undefined, })); -export const openAISchema = tConversationSchema - .pick({ - model: true, - modelLabel: true, - chatGptLabel: true, - promptPrefix: true, - temperature: true, - top_p: true, - presence_penalty: true, - frequency_penalty: true, - resendFiles: true, - artifacts: true, - imageDetail: true, - stop: true, - iconURL: true, - greeting: true, - spec: true, - maxContextTokens: true, - max_tokens: true, - reasoning_effort: true, - }) +export const openAIBaseSchema = tConversationSchema.pick({ + model: true, + modelLabel: true, + chatGptLabel: true, + promptPrefix: true, + temperature: true, + top_p: true, + presence_penalty: true, + frequency_penalty: true, + resendFiles: true, + artifacts: true, + imageDetail: true, + stop: true, + iconURL: true, + greeting: true, + spec: true, + maxContextTokens: true, + max_tokens: true, + reasoning_effort: true, +}); + +export const openAISchema = openAIBaseSchema .transform((obj: Partial) => removeNullishValues(obj)) .catch(() => ({})); -export const compactGoogleSchema = tConversationSchema - .pick({ - model: true, - modelLabel: true, - promptPrefix: true, - examples: true, - temperature: true, - maxOutputTokens: true, - artifacts: true, - topP: true, - topK: true, - iconURL: true, - greeting: true, - spec: true, - maxContextTokens: true, - }) +export const compactGoogleSchema = googleBaseSchema .transform((obj) => { const newObj: Partial = { ...obj }; if (newObj.temperature === google.temperature.default) { @@ -1048,55 +1027,30 @@ export const compactGoogleSchema = tConversationSchema }) .catch(() => ({})); -export const anthropicSchema = tConversationSchema - .pick({ - model: true, - modelLabel: true, - promptPrefix: true, - temperature: true, - maxOutputTokens: true, - topP: true, - topK: true, - resendFiles: true, - promptCache: true, - thinking: true, - thinkingBudget: true, - artifacts: true, - iconURL: true, - greeting: true, - spec: true, - maxContextTokens: true, - }) +export const anthropicBaseSchema = tConversationSchema.pick({ + model: true, + modelLabel: true, + promptPrefix: true, + temperature: true, + maxOutputTokens: true, + topP: true, + topK: true, + resendFiles: true, + promptCache: true, + thinking: true, + thinkingBudget: true, + artifacts: true, + iconURL: true, + greeting: true, + spec: true, + maxContextTokens: true, +}); + +export const anthropicSchema = anthropicBaseSchema .transform((obj) => removeNullishValues(obj)) .catch(() => ({})); -export const compactChatGPTSchema = tConversationSchema - .pick({ - model: true, - }) - .transform((obj) => { - const newObj: Partial = { ...obj }; - return removeNullishValues(newObj); - }) - .catch(() => ({})); - -export const compactPluginsSchema = tConversationSchema - .pick({ - model: true, - modelLabel: true, - chatGptLabel: true, - promptPrefix: true, - temperature: true, - top_p: true, - presence_penalty: true, - frequency_penalty: true, - tools: true, - agentOptions: true, - iconURL: true, - greeting: true, - spec: true, - maxContextTokens: true, - }) +export const compactPluginsSchema = gptPluginsBaseSchema .transform((obj) => { const newObj: Partial = { ...obj }; if (newObj.modelLabel === null) { @@ -1149,15 +1103,16 @@ export const tBannerSchema = z.object({ }); export type TBanner = z.infer; -export const compactAgentsSchema = tConversationSchema - .pick({ - spec: true, - // model: true, - iconURL: true, - greeting: true, - agent_id: true, - instructions: true, - additional_instructions: true, - }) +export const compactAgentsBaseSchema = tConversationSchema.pick({ + spec: true, + // model: true, + iconURL: true, + greeting: true, + agent_id: true, + instructions: true, + additional_instructions: true, +}); + +export const compactAgentsSchema = compactAgentsBaseSchema .transform((obj) => removeNullishValues(obj)) .catch(() => ({})); diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index f278810a5f..dabe89cdb0 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -41,12 +41,17 @@ export type TEndpointOption = { overrideUserMessageId?: string; }; +export type TEphemeralAgent = { + mcp: string[]; +}; + export type TPayload = Partial & Partial & { isContinued: boolean; conversationId: string | null; messages?: TMessages; isTemporary: boolean; + ephemeralAgent?: TEphemeralAgent | null; }; export type TSubmission = { @@ -63,6 +68,7 @@ export type TSubmission = { conversation: Partial; endpointOption: TEndpointOption; clientTimestamp?: string; + ephemeralAgent?: TEphemeralAgent | null; }; export type EventSubmission = Omit & { initialResponse: TMessage }; From 5d668748f9f52866f92ffb4c77c8c39385fde865 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 8 Apr 2025 23:18:50 -0400 Subject: [PATCH 06/75] =?UTF-8?q?=F0=9F=97=83=EF=B8=8F=20feat:=20Code=20In?= =?UTF-8?q?terpreter=20File=20Persistence=20between=20Sessions=20(#6790)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Enhance FileContainer with customizable button and container styles, onClick button handling, and type override * refactor: Update file type handling to support partial file objects * refactor: Extract download handling into a custom hook for improved reusability * refactor: Replace LogContent with Stdout component and enhance Attachment rendering for added visibility * feat: Update @librechat/agents to version 2.4.1 for referencing generated files in subsequent code interpreter uses * feat: Add support for tab-separated values (TSV) in mime type handling and improve error logging for regex patterns * chore: Update @librechat/agents to version 2.4.11 for better `session_id` instructions when wanting to persist files between executions * chore: Update @librechat/agents to version 2.4.12 for improved functionality * fix: Enhance argument parsing in useParseArgs to support JSON input and improve code extraction * refactor: Update input handling in useAutoSave to require more than one character before saving to local storage --- api/package.json | 2 +- .../Chat/Input/Files/FileContainer.tsx | 32 +++++++++++++++---- .../Chat/Input/Files/FilePreview.tsx | 2 +- .../Messages/Content/Parts/Attachment.tsx | 23 ++++++++++++- .../Messages/Content/Parts/ExecuteCode.tsx | 25 +++++++++++---- .../Chat/Messages/Content/Parts/LogLink.tsx | 9 ++++-- .../Chat/Messages/Content/Parts/Stdout.tsx | 26 +++++++++++++++ client/src/components/svg/Files/FileIcon.tsx | 2 +- client/src/hooks/Input/useAutoSave.ts | 2 +- client/src/utils/files.ts | 1 + package-lock.json | 14 ++++---- packages/data-provider/src/file-config.ts | 5 +-- 12 files changed, 113 insertions(+), 30 deletions(-) create mode 100644 client/src/components/Chat/Messages/Content/Parts/Stdout.tsx diff --git a/api/package.json b/api/package.json index 644c0ce7a9..19bad78cf8 100644 --- a/api/package.json +++ b/api/package.json @@ -49,7 +49,7 @@ "@langchain/google-genai": "^0.2.2", "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.0", + "@librechat/agents": "^2.4.12", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", diff --git a/client/src/components/Chat/Input/Files/FileContainer.tsx b/client/src/components/Chat/Input/Files/FileContainer.tsx index 5131061f87..5a22fef256 100644 --- a/client/src/components/Chat/Input/Files/FileContainer.tsx +++ b/client/src/components/Chat/Input/Files/FileContainer.tsx @@ -1,22 +1,40 @@ import type { TFile } from 'librechat-data-provider'; import type { ExtendedFile } from '~/common'; +import { getFileType, cn } from '~/utils'; import FilePreview from './FilePreview'; import RemoveFile from './RemoveFile'; -import { getFileType } from '~/utils'; const FileContainer = ({ file, + overrideType, + buttonClassName, + containerClassName, onDelete, + onClick, }: { - file: ExtendedFile | TFile; + file: Partial; + overrideType?: string; + buttonClassName?: string; + containerClassName?: string; onDelete?: () => void; + onClick?: React.MouseEventHandler; }) => { - const fileType = getFileType(file.type); + const fileType = getFileType(overrideType ?? file.type); return ( -
-
-
+
+
+ {onDelete && }
); diff --git a/client/src/components/Chat/Input/Files/FilePreview.tsx b/client/src/components/Chat/Input/Files/FilePreview.tsx index 02851119af..31226ac65a 100644 --- a/client/src/components/Chat/Input/Files/FilePreview.tsx +++ b/client/src/components/Chat/Input/Files/FilePreview.tsx @@ -11,7 +11,7 @@ const FilePreview = ({ fileType, className = '', }: { - file?: ExtendedFile | TFile; + file?: Partial; fileType: { paths: React.FC; fill: string; diff --git a/client/src/components/Chat/Messages/Content/Parts/Attachment.tsx b/client/src/components/Chat/Messages/Content/Parts/Attachment.tsx index 8bc3192813..e1aeb86a5e 100644 --- a/client/src/components/Chat/Messages/Content/Parts/Attachment.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/Attachment.tsx @@ -1,6 +1,27 @@ +import { memo } from 'react'; import { imageExtRegex } from 'librechat-data-provider'; import type { TAttachment, TFile, TAttachmentMetadata } from 'librechat-data-provider'; +import FileContainer from '~/components/Chat/Input/Files/FileContainer'; import Image from '~/components/Chat/Messages/Content/Image'; +import { useAttachmentLink } from './LogLink'; + +const FileAttachment = memo(({ attachment }: { attachment: TAttachment }) => { + const { handleDownload } = useAttachmentLink({ + href: attachment.filepath, + filename: attachment.filename, + }); + const extension = attachment.filename.split('.').pop(); + + return ( + + ); +}); export default function Attachment({ attachment }: { attachment?: TAttachment }) { if (!attachment) { @@ -21,5 +42,5 @@ export default function Attachment({ attachment }: { attachment?: TAttachment }) /> ); } - return null; + return ; } diff --git a/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx b/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx index 93fdab434e..40ad8c6a66 100644 --- a/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx @@ -7,7 +7,7 @@ import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite'; import { useProgress, useLocalize } from '~/hooks'; import { CodeInProgress } from './CodeProgress'; import Attachment from './Attachment'; -import LogContent from './LogContent'; +import Stdout from './Stdout'; import store from '~/store'; interface ParsedArgs { @@ -17,8 +17,17 @@ interface ParsedArgs { export function useParseArgs(args: string): ParsedArgs { return useMemo(() => { + let parsedArgs: ParsedArgs | string = args; + try { + parsedArgs = JSON.parse(args); + } catch { + // console.error('Failed to parse args:', e); + } + if (typeof parsedArgs === 'object') { + return parsedArgs; + } const langMatch = args.match(/"lang"\s*:\s*"(\w+)"/); - const codeMatch = args.match(/"code"\s*:\s*"(.+?)(?="\s*,\s*"args"|$)/s); + const codeMatch = args.match(/"code"\s*:\s*"(.+?)(?="\s*,\s*"(session_id|args)"|"\s*})/s); let code = ''; if (codeMatch) { @@ -26,7 +35,7 @@ export function useParseArgs(args: string): ParsedArgs { if (code.endsWith('"}')) { code = code.slice(0, -2); } - code = code.replace(/\\n/g, '\n').replace(/\\/g, ''); + code = code.replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\\\/g, '\\'); } return { @@ -99,15 +108,17 @@ export default function ExecuteCode({ color: 'white', }} > -
-                  
-                
+
)}
)} - {attachments?.map((attachment, index) => )} +
+ {attachments?.map((attachment, index) => ( + + ))} +
); } diff --git a/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx b/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx index c1886e7460..590b6d7b8a 100644 --- a/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx @@ -8,11 +8,11 @@ interface LogLinkProps { children: React.ReactNode; } -const LogLink: React.FC = ({ href, filename, children }) => { +export const useAttachmentLink = ({ href, filename }: Pick) => { const { showToast } = useToastContext(); const { refetch: downloadFile } = useCodeOutputDownload(href); - const handleDownload = async (event: React.MouseEvent) => { + const handleDownload = async (event: React.MouseEvent) => { event.preventDefault(); try { const stream = await downloadFile(); @@ -36,6 +36,11 @@ const LogLink: React.FC = ({ href, filename, children }) => { } }; + return { handleDownload }; +}; + +const LogLink: React.FC = ({ href, filename, children }) => { + const { handleDownload } = useAttachmentLink({ href, filename }); return ( = ({ output = '' }) => { + const processedContent = useMemo(() => { + if (!output) { + return ''; + } + + const parts = output.split('Generated files:'); + return parts[0].trim(); + }, [output]); + + return ( + processedContent && ( +
+        
{processedContent}
+
+ ) + ); +}; + +export default Stdout; diff --git a/client/src/components/svg/Files/FileIcon.tsx b/client/src/components/svg/Files/FileIcon.tsx index 559121efbd..788615171e 100644 --- a/client/src/components/svg/Files/FileIcon.tsx +++ b/client/src/components/svg/Files/FileIcon.tsx @@ -5,7 +5,7 @@ export default function FileIcon({ file, fileType, }: { - file?: ExtendedFile | TFile; + file?: Partial; fileType: { fill: string; paths: React.FC; diff --git a/client/src/hooks/Input/useAutoSave.ts b/client/src/hooks/Input/useAutoSave.ts index bebf54af3f..233a175a53 100644 --- a/client/src/hooks/Input/useAutoSave.ts +++ b/client/src/hooks/Input/useAutoSave.ts @@ -128,7 +128,7 @@ export const useAutoSave = ({ const handleInput = debounce((e: React.ChangeEvent) => { const value = e.target.value; - if (value) { + if (value && value.length > 1) { localStorage.setItem( `${LocalStorageKeys.TEXT_DRAFT}${conversationId}`, encodeBase64(value), diff --git a/client/src/utils/files.ts b/client/src/utils/files.ts index 337d1cc84f..3ca07e51d0 100644 --- a/client/src/utils/files.ts +++ b/client/src/utils/files.ts @@ -48,6 +48,7 @@ export const fileTypes = { title: 'File', }, text: textDocument, + txt: textDocument, // application:, /* Partial matches */ diff --git a/package-lock.json b/package-lock.json index 17e53223c3..158ec0e959 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "@langchain/google-genai": "^0.2.2", "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.0", + "@librechat/agents": "^2.4.12", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", @@ -17629,9 +17629,9 @@ } }, "node_modules/@langchain/langgraph-sdk": { - "version": "0.0.62", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.62.tgz", - "integrity": "sha512-JJiQwjV5/uVtwiVH/lt+QXhuh0nGhylZSLkMQXc1923TBUC4SHwU0JIKEDqh820PlGNkUu0nODJSAzS/6zPRtQ==", + "version": "0.0.65", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.65.tgz", + "integrity": "sha512-Zn1FhiKr/mYa1+W5NcuCPWmdTtJS4UZYu+YVEjxgESd0aMX19FTkqjaSV6tFDcRqHHwlIgHloCSqHLkYWF/Zug==", "dependencies": { "@types/json-schema": "^7.0.15", "p-queue": "^6.6.2", @@ -17819,9 +17819,9 @@ } }, "node_modules/@librechat/agents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.0.tgz", - "integrity": "sha512-ijPJw+lMMPJ+Y66xSbh0cCiuODihl0TET3CWAxZweVGqynYqtL8PvPqlxtw+jftmrLjDFV04UM2NiDbzDb87HA==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.12.tgz", + "integrity": "sha512-m8CEVCjVeQDKXMS0ISG4h4YXU1x51yWGPLCuiNbQI3k+fmDvEFqVuzFi15OhVfoGGQxbKfzGiiF8fsKK2xIKEw==", "dependencies": { "@langchain/anthropic": "^0.3.16", "@langchain/aws": "^0.1.7", diff --git a/packages/data-provider/src/file-config.ts b/packages/data-provider/src/file-config.ts index c380dacf6b..3798b48d4d 100644 --- a/packages/data-provider/src/file-config.ts +++ b/packages/data-provider/src/file-config.ts @@ -112,7 +112,7 @@ export const excelMimeTypes = /^application\/(vnd\.ms-excel|msexcel|x-msexcel|x-ms-excel|x-excel|x-dos_ms_excel|xls|x-xls|vnd\.openxmlformats-officedocument\.spreadsheetml\.sheet)$/; export const textMimeTypes = - /^(text\/(x-c|x-csharp|x-c\+\+|x-java|html|markdown|x-php|x-python|x-script\.python|x-ruby|x-tex|plain|css|vtt|javascript|csv))$/; + /^(text\/(x-c|x-csharp|tab-separated-values|x-c\+\+|x-java|html|markdown|x-php|x-python|x-script\.python|x-ruby|x-tex|plain|css|vtt|javascript|csv))$/; export const applicationMimeTypes = /^(application\/(epub\+zip|csv|json|pdf|x-tar|typescript|vnd\.openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.presentation|spreadsheetml\.sheet)|xml|zip))$/; @@ -152,6 +152,7 @@ export const codeTypeMapping: { [key: string]: string } = { yml: 'application/x-yaml', yaml: 'application/x-yaml', log: 'text/plain', + tsv: 'text/tab-separated-values', }; export const retrievalMimeTypes = [ @@ -230,7 +231,7 @@ export const convertStringsToRegex = (patterns: string[]): RegExp[] => const regex = new RegExp(pattern); acc.push(regex); } catch (error) { - console.error(`Invalid regex pattern "${pattern}" skipped.`); + console.error(`Invalid regex pattern "${pattern}" skipped.`, error); } return acc; }, []); From 24c0433dcf3b84a82eafce055c9076de98efc57b Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 9 Apr 2025 16:11:16 -0400 Subject: [PATCH 07/75] =?UTF-8?q?=F0=9F=96=A5=EF=B8=8F=20feat:=20Code=20In?= =?UTF-8?q?terpreter=20API=20for=20Non-Agent=20Endpoints=20(#6803)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Prevent parsing 'undefined' string in useLocalStorage initialization * feat: first pass, code interpreter badge * feat: Integrate API key authentication and default checked value in Code Interpreter Badge * refactor: Rename showMCPServers to showEphemeralBadges and update related components, memoize values in useChatBadges * refactor: Enhance AttachFileChat to support ephemeral agents in file attachment logic * fix: Add baseURL configuration option to legacy function call * refactor: Update dependency array in useDragHelpers to include handleFiles * refactor: Update isEphemeralAgent function to accept optional endpoint parameter * refactor: Update file handling to support ephemeral agents in AttachFileMenu and useDragHelpers * fix: improve compatibility issues with OpenAI usage field handling in createRun function * refactor: usage field compatibility * fix: ensure mcp servers are no longer "selected" if mcp servers are now unavailable --- api/app/clients/llm/createLLM.js | 1 + api/models/Agent.js | 22 ++-- api/server/controllers/agents/run.js | 14 ++- client/src/components/Chat/Input/BadgeRow.tsx | 20 +++- client/src/components/Chat/Input/ChatForm.tsx | 2 +- .../components/Chat/Input/CodeInterpreter.tsx | 104 ++++++++++++++++++ .../Chat/Input/Files/AttachFileChat.tsx | 11 +- .../Chat/Input/Files/AttachFileMenu.tsx | 4 +- .../src/components/Chat/Input/MCPSelect.tsx | 28 ++++- client/src/components/ui/CheckboxButton.tsx | 62 +++++++++++ client/src/components/ui/MultiSelect.tsx | 5 +- client/src/hooks/Files/useDragHelpers.ts | 28 +++-- client/src/hooks/useChatBadges.ts | 28 +++-- client/src/hooks/useLocalStorageAlt.tsx | 2 +- client/src/utils/localStorage.ts | 1 + packages/data-provider/src/config.ts | 2 + packages/data-provider/src/createPayload.ts | 6 +- packages/data-provider/src/schemas.ts | 16 +++ packages/data-provider/src/types.ts | 3 +- 19 files changed, 311 insertions(+), 48 deletions(-) create mode 100644 client/src/components/Chat/Input/CodeInterpreter.tsx create mode 100644 client/src/components/ui/CheckboxButton.tsx diff --git a/api/app/clients/llm/createLLM.js b/api/app/clients/llm/createLLM.js index 7dc0d40ceb..c8d6666bce 100644 --- a/api/app/clients/llm/createLLM.js +++ b/api/app/clients/llm/createLLM.js @@ -34,6 +34,7 @@ function createLLM({ let credentials = { openAIApiKey }; let configuration = { apiKey: openAIApiKey, + ...(configOptions.basePath && { baseURL: configOptions.basePath }), }; /** @type {AzureOptions} */ diff --git a/api/models/Agent.js b/api/models/Agent.js index 7c0a630808..dba0c40ee9 100644 --- a/api/models/Agent.js +++ b/api/models/Agent.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose'); const { agentSchema } = require('@librechat/data-schemas'); -const { SystemRoles } = require('librechat-data-provider'); +const { SystemRoles, Tools } = require('librechat-data-provider'); const { GLOBAL_PROJECT_NAME, EPHEMERAL_AGENT_ID, mcp_delimiter } = require('librechat-data-provider').Constants; const { CONFIG_STORE, STARTUP_CONFIG } = require('librechat-data-provider').CacheKeys; @@ -51,16 +51,22 @@ const loadEphemeralAgent = ({ req, agent_id, endpoint, model_parameters: _m }) = const mcpServers = new Set(req.body.ephemeralAgent?.mcp); /** @type {string[]} */ const tools = []; + if (req.body.ephemeralAgent?.execute_code === true) { + tools.push(Tools.execute_code); + } - for (const toolName of Object.keys(availableTools)) { - if (!toolName.includes(mcp_delimiter)) { - continue; - } - const mcpServer = toolName.split(mcp_delimiter)?.[1]; - if (mcpServer && mcpServers.has(mcpServer)) { - tools.push(toolName); + if (mcpServers.size > 0) { + for (const toolName of Object.keys(availableTools)) { + if (!toolName.includes(mcp_delimiter)) { + continue; + } + const mcpServer = toolName.split(mcp_delimiter)?.[1]; + if (mcpServer && mcpServers.has(mcpServer)) { + tools.push(toolName); + } } } + const instructions = req.body.promptPrefix; return { id: agent_id, diff --git a/api/server/controllers/agents/run.js b/api/server/controllers/agents/run.js index 2efde5d061..2452e66233 100644 --- a/api/server/controllers/agents/run.js +++ b/api/server/controllers/agents/run.js @@ -11,6 +11,13 @@ const { providerEndpointMap, KnownEndpoints } = require('librechat-data-provider * @typedef {import('@librechat/agents').IState} IState */ +const customProviders = new Set([ + Providers.XAI, + Providers.OLLAMA, + Providers.DEEPSEEK, + Providers.OPENROUTER, +]); + /** * Creates a new Run instance with custom handlers and configuration. * @@ -43,8 +50,11 @@ async function createRun({ agent.model_parameters, ); - /** Resolves Mistral type strictness due to new OpenAI usage field */ - if (agent.endpoint?.toLowerCase().includes(KnownEndpoints.mistral)) { + /** Resolves issues with new OpenAI usage field */ + if ( + customProviders.has(agent.provider) || + (agent.provider === Providers.OPENAI && agent.endpoint !== agent.provider) + ) { llmConfig.streamUsage = false; llmConfig.usage = true; } diff --git a/client/src/components/Chat/Input/BadgeRow.tsx b/client/src/components/Chat/Input/BadgeRow.tsx index 0de28cb347..15e99bd42f 100644 --- a/client/src/components/Chat/Input/BadgeRow.tsx +++ b/client/src/components/Chat/Input/BadgeRow.tsx @@ -10,6 +10,7 @@ import React, { } from 'react'; import { useRecoilValue, useRecoilCallback } from 'recoil'; import type { LucideIcon } from 'lucide-react'; +import CodeInterpreter from './CodeInterpreter'; import type { BadgeItem } from '~/common'; import { useChatBadges } from '~/hooks'; import { Badge } from '~/components/ui'; @@ -17,7 +18,7 @@ import MCPSelect from './MCPSelect'; import store from '~/store'; interface BadgeRowProps { - showMCPServers?: boolean; + showEphemeralBadges?: boolean; onChange: (badges: Pick[]) => void; onToggle?: (badgeId: string, currentActive: boolean) => void; conversationId?: string | null; @@ -131,7 +132,13 @@ const dragReducer = (state: DragState, action: DragAction): DragState => { } }; -function BadgeRow({ showMCPServers, conversationId, onChange, onToggle, isInChat }: BadgeRowProps) { +function BadgeRow({ + showEphemeralBadges, + conversationId, + onChange, + onToggle, + isInChat, +}: BadgeRowProps) { const [orderedBadges, setOrderedBadges] = useState([]); const [dragState, dispatch] = useReducer(dragReducer, { draggedBadge: null, @@ -146,7 +153,7 @@ function BadgeRow({ showMCPServers, conversationId, onChange, onToggle, isInChat const animationFrame = useRef(null); const containerRectRef = useRef(null); - const allBadges = useChatBadges() || []; + const allBadges = useChatBadges(); const isEditing = useRecoilValue(store.isEditingBadges); const badges = useMemo( @@ -345,7 +352,12 @@ function BadgeRow({ showMCPServers, conversationId, onChange, onToggle, isInChat /> )} - {showMCPServers === true && } + {showEphemeralBadges === true && ( + <> + + + + )} {ghostBadge && (
{
{ + return ephemeralAgent?.execute_code ?? false; + }, [ephemeralAgent?.execute_code]); + + const { data } = useVerifyAgentToolAuth( + { toolId: Tools.execute_code }, + { + retry: 1, + }, + ); + const authType = useMemo(() => data?.message ?? false, [data?.message]); + const isAuthenticated = useMemo(() => data?.authenticated ?? false, [data?.authenticated]); + const { methods, onSubmit, isDialogOpen, setIsDialogOpen, handleRevokeApiKey } = + useCodeApiKeyForm({}); + + const setValue = useCallback( + (isChecked: boolean) => { + setEphemeralAgent((prev) => ({ + ...prev, + execute_code: isChecked, + })); + }, + [setEphemeralAgent], + ); + + const [runCode, setRunCode] = useLocalStorage( + `${LocalStorageKeys.LAST_CODE_TOGGLE_}${key}`, + isCodeToggleEnabled, + setValue, + ); + + const handleChange = useCallback( + (isChecked: boolean) => { + if (!isAuthenticated) { + setIsDialogOpen(true); + return; + } + setRunCode(isChecked); + }, + [setRunCode, setIsDialogOpen, isAuthenticated], + ); + + const debouncedChange = useMemo( + () => debounce(handleChange, 50, { leading: true }), + [handleChange], + ); + + if (!canRunCode) { + return null; + } + + return ( + <> + } + /> + + + ); +} + +export default memo(CodeInterpreter); diff --git a/client/src/components/Chat/Input/Files/AttachFileChat.tsx b/client/src/components/Chat/Input/Files/AttachFileChat.tsx index b4b0de1a63..11bca082fe 100644 --- a/client/src/components/Chat/Input/Files/AttachFileChat.tsx +++ b/client/src/components/Chat/Input/Files/AttachFileChat.tsx @@ -1,24 +1,31 @@ import { memo, useMemo } from 'react'; import { useRecoilValue } from 'recoil'; import { + Constants, supportsFiles, mergeFileConfig, isAgentsEndpoint, + isEphemeralAgent, EndpointFileConfig, fileConfig as defaultFileConfig, } from 'librechat-data-provider'; import { useChatContext } from '~/Providers'; import { useGetFileConfig } from '~/data-provider'; +import { ephemeralAgentByConvoId } from '~/store'; import AttachFileMenu from './AttachFileMenu'; import AttachFile from './AttachFile'; -import store from '~/store'; function AttachFileChat({ disableInputs }: { disableInputs: boolean }) { const { conversation } = useChatContext(); const { endpoint: _endpoint, endpointType } = conversation ?? { endpoint: null }; - const isAgents = useMemo(() => isAgentsEndpoint(_endpoint), [_endpoint]); + const key = conversation?.conversationId ?? Constants.NEW_CONVO; + const ephemeralAgent = useRecoilValue(ephemeralAgentByConvoId(key)); + const isAgents = useMemo( + () => isAgentsEndpoint(_endpoint) || isEphemeralAgent(_endpoint, ephemeralAgent), + [_endpoint, ephemeralAgent], + ); const { data: fileConfig = defaultFileConfig } = useGetFileConfig({ select: (data) => mergeFileConfig(data), diff --git a/client/src/components/Chat/Input/Files/AttachFileMenu.tsx b/client/src/components/Chat/Input/Files/AttachFileMenu.tsx index 0f5f8e05a7..f827d39f4a 100644 --- a/client/src/components/Chat/Input/Files/AttachFileMenu.tsx +++ b/client/src/components/Chat/Input/Files/AttachFileMenu.tsx @@ -18,7 +18,9 @@ const AttachFile = ({ disabled }: AttachFileProps) => { const [isPopoverActive, setIsPopoverActive] = useState(false); const [toolResource, setToolResource] = useState(); const { data: endpointsConfig } = useGetEndpointsQuery(); - const { handleFileChange } = useFileHandling(); + const { handleFileChange } = useFileHandling({ + overrideEndpoint: EModelEndpoint.agents, + }); const capabilities = useMemo( () => endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? [], diff --git a/client/src/components/Chat/Input/MCPSelect.tsx b/client/src/components/Chat/Input/MCPSelect.tsx index adbdc29750..270228fe57 100644 --- a/client/src/components/Chat/Input/MCPSelect.tsx +++ b/client/src/components/Chat/Input/MCPSelect.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback } from 'react'; +import React, { memo, useRef, useMemo, useEffect, useCallback } from 'react'; import { useRecoilState } from 'recoil'; import { Constants, EModelEndpoint, LocalStorageKeys } from 'librechat-data-provider'; import { useAvailableToolsQuery } from '~/data-provider'; @@ -10,8 +10,12 @@ import { useLocalize } from '~/hooks'; function MCPSelect({ conversationId }: { conversationId?: string | null }) { const localize = useLocalize(); + const hasSetFetched = useRef(false); const key = conversationId ?? Constants.NEW_CONVO; const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key)); + const mcpState = useMemo(() => { + return ephemeralAgent?.mcp ?? []; + }, [ephemeralAgent?.mcp]); const setSelectedValues = useCallback( (values: string[] | null | undefined) => { if (!values) { @@ -29,10 +33,10 @@ function MCPSelect({ conversationId }: { conversationId?: string | null }) { ); const [mcpValues, setMCPValues] = useLocalStorage( `${LocalStorageKeys.LAST_MCP_}${key}`, - ephemeralAgent?.mcp ?? [], + mcpState, setSelectedValues, ); - const { data: mcpServers } = useAvailableToolsQuery(EModelEndpoint.agents, { + const { data: mcpServers, isFetched } = useAvailableToolsQuery(EModelEndpoint.agents, { select: (data) => { const serverNames = new Set(); data.forEach((tool) => { @@ -45,6 +49,20 @@ function MCPSelect({ conversationId }: { conversationId?: string | null }) { }, }); + useEffect(() => { + if (hasSetFetched.current) { + return; + } + if (!isFetched) { + return; + } + hasSetFetched.current = true; + if ((mcpServers?.length ?? 0) > 0) { + return; + } + setMCPValues([]); + }, [isFetched, setMCPValues, mcpServers?.length]); + const renderSelectedValues = useCallback( (values: string[], placeholder?: string) => { if (values.length === 0) { @@ -70,8 +88,8 @@ function MCPSelect({ conversationId }: { conversationId?: string | null }) { defaultSelectedValues={mcpValues ?? []} renderSelectedValues={renderSelectedValues} placeholder={localize('com_ui_mcp_servers')} - popoverClassName="min-w-[200px]" - className="badge-icon h-full min-w-[150px]" + popoverClassName="min-w-fit" + className="badge-icon min-w-fit" selectIcon={} selectItemsClassName="border border-blue-600/50 bg-blue-500/10 hover:bg-blue-700/10" selectClassName="group relative inline-flex items-center justify-center md:justify-start gap-1.5 rounded-full border border-border-medium text-sm font-medium transition-shadow md:w-full size-9 p-2 md:p-3 bg-surface-chat shadow-sm hover:bg-surface-hover hover:shadow-md active:shadow-inner" diff --git a/client/src/components/ui/CheckboxButton.tsx b/client/src/components/ui/CheckboxButton.tsx new file mode 100644 index 0000000000..988340c061 --- /dev/null +++ b/client/src/components/ui/CheckboxButton.tsx @@ -0,0 +1,62 @@ +import { useEffect } from 'react'; +import { Checkbox, useStoreState, useCheckboxStore } from '@ariakit/react'; +import { cn } from '~/utils'; + +export default function CheckboxButton({ + label, + icon, + setValue, + className, + defaultChecked, + isCheckedClassName, +}: { + label: string; + className?: string; + icon?: React.ReactNode; + defaultChecked?: boolean; + isCheckedClassName?: string; + setValue?: (isChecked: boolean) => void; +}) { + const checkbox = useCheckboxStore(); + const isChecked = useStoreState(checkbox, (state) => state?.value); + const onChange = (e: React.ChangeEvent) => { + e.stopPropagation(); + if (typeof isChecked !== 'boolean') { + return; + } + setValue?.(!isChecked); + }; + useEffect(() => { + if (defaultChecked) { + checkbox.setValue(defaultChecked); + } + }, [defaultChecked, checkbox]); + + return ( + } + > + {/* Icon if provided */} + {icon && {icon}} + + {/* Show the label on larger screens */} + {label} + + ); +} diff --git a/client/src/components/ui/MultiSelect.tsx b/client/src/components/ui/MultiSelect.tsx index 9de2780721..ddbd5c90a4 100644 --- a/client/src/components/ui/MultiSelect.tsx +++ b/client/src/components/ui/MultiSelect.tsx @@ -66,7 +66,7 @@ export default function MultiSelect({ }; return ( -
+
{label && ( @@ -82,9 +82,10 @@ export default function MultiSelect({ selectClassName, selectedValues.length > 0 && selectItemsClassName != null && selectItemsClassName, )} + onChange={(e) => e.stopPropagation()} > {selectIcon && selectIcon} - + {renderSelectedValues(selectedValues, placeholder)} diff --git a/client/src/hooks/Files/useDragHelpers.ts b/client/src/hooks/Files/useDragHelpers.ts index 32cd83f9b1..af4530e622 100644 --- a/client/src/hooks/Files/useDragHelpers.ts +++ b/client/src/hooks/Files/useDragHelpers.ts @@ -4,22 +4,28 @@ import { useRecoilValue } from 'recoil'; import { NativeTypes } from 'react-dnd-html5-backend'; import { useQueryClient } from '@tanstack/react-query'; import { - isAgentsEndpoint, - EModelEndpoint, - AgentCapabilities, + Constants, QueryKeys, + EModelEndpoint, + isAgentsEndpoint, + isEphemeralAgent, + AgentCapabilities, } from 'librechat-data-provider'; import type * as t from 'librechat-data-provider'; import type { DropTargetMonitor } from 'react-dnd'; import useFileHandling from './useFileHandling'; -import store from '~/store'; +import store, { ephemeralAgentByConvoId } from '~/store'; export default function useDragHelpers() { const queryClient = useQueryClient(); - const { handleFiles } = useFileHandling(); const [showModal, setShowModal] = useState(false); const [draggedFiles, setDraggedFiles] = useState([]); const conversation = useRecoilValue(store.conversationByIndex(0)) || undefined; + const key = useMemo( + () => conversation?.conversationId ?? Constants.NEW_CONVO, + [conversation?.conversationId], + ); + const ephemeralAgent = useRecoilValue(ephemeralAgentByConvoId(key)); const handleOptionSelect = (toolResource: string | undefined) => { handleFiles(draggedFiles, toolResource); @@ -28,10 +34,16 @@ export default function useDragHelpers() { }; const isAgents = useMemo( - () => isAgentsEndpoint(conversation?.endpoint), - [conversation?.endpoint], + () => + isAgentsEndpoint(conversation?.endpoint) || + isEphemeralAgent(conversation?.endpoint, ephemeralAgent), + [conversation?.endpoint, ephemeralAgent], ); + const { handleFiles } = useFileHandling({ + overrideEndpoint: isAgents ? EModelEndpoint.agents : undefined, + }); + const [{ canDrop, isOver }, drop] = useDrop( () => ({ accept: [NativeTypes.FILE], @@ -61,7 +73,7 @@ export default function useDragHelpers() { canDrop: monitor.canDrop(), }), }), - [], + [handleFiles], ); return { diff --git a/client/src/hooks/useChatBadges.ts b/client/src/hooks/useChatBadges.ts index 022f3f7a63..abc23ee300 100644 --- a/client/src/hooks/useChatBadges.ts +++ b/client/src/hooks/useChatBadges.ts @@ -1,8 +1,9 @@ +import { useMemo } from 'react'; import { useRecoilCallback } from 'recoil'; import { useRecoilValue } from 'recoil'; import { MessageCircleDashed, Box } from 'lucide-react'; import type { BadgeItem } from '~/common'; -import { useLocalize } from '~/hooks'; +import { useLocalize, TranslationKeys } from '~/hooks'; import store from '~/store'; interface ChatBadgeConfig { @@ -25,15 +26,22 @@ const badgeConfig: ReadonlyArray = [ export default function useChatBadges(): BadgeItem[] { const localize = useLocalize(); const activeBadges = useRecoilValue(store.chatBadges) as Array<{ id: string }>; - const activeBadgeIds = new Set(activeBadges.map((badge) => badge.id)); - - return badgeConfig.map((cfg) => ({ - id: cfg.id, - label: localize(cfg.label), - icon: cfg.icon, - atom: cfg.atom, - isAvailable: activeBadgeIds.has(cfg.id), - })); + const activeBadgeIds = useMemo( + () => new Set(activeBadges.map((badge) => badge.id)), + [activeBadges], + ); + const allBadges = useMemo(() => { + return ( + badgeConfig.map((cfg) => ({ + id: cfg.id, + label: localize(cfg.label as TranslationKeys), + icon: cfg.icon, + atom: cfg.atom, + isAvailable: activeBadgeIds.has(cfg.id), + })) || [] + ); + }, [activeBadgeIds, localize]); + return allBadges; } export function useResetChatBadges() { diff --git a/client/src/hooks/useLocalStorageAlt.tsx b/client/src/hooks/useLocalStorageAlt.tsx index 465d62c70b..ef63198242 100644 --- a/client/src/hooks/useLocalStorageAlt.tsx +++ b/client/src/hooks/useLocalStorageAlt.tsx @@ -21,7 +21,7 @@ export default function useLocalStorage( localStorage.setItem(key, JSON.stringify(defaultValue)); } - const initialValue = item ? JSON.parse(item) : defaultValue; + const initialValue = item && item !== 'undefined' ? JSON.parse(item) : defaultValue; setValue(initialValue); if (globalSetState) { globalSetState(initialValue); diff --git a/client/src/utils/localStorage.ts b/client/src/utils/localStorage.ts index 35bd12232f..cb1dee9403 100644 --- a/client/src/utils/localStorage.ts +++ b/client/src/utils/localStorage.ts @@ -32,6 +32,7 @@ export function clearLocalStorage(skipFirst?: boolean) { } if ( key.startsWith(LocalStorageKeys.LAST_MCP_) || + key.startsWith(LocalStorageKeys.LAST_CODE_TOGGLE_) || key.startsWith(LocalStorageKeys.ASST_ID_PREFIX) || key.startsWith(LocalStorageKeys.AGENT_ID_PREFIX) || key.startsWith(LocalStorageKeys.LAST_CONVO_SETUP) || diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 1b8d815a7b..6e62919526 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -1285,6 +1285,8 @@ export enum LocalStorageKeys { SHOW_ANALYSIS_CODE = 'showAnalysisCode', /** Last selected MCP values per conversation ID */ LAST_MCP_ = 'LAST_MCP_', + /** Last checked toggle for Code Interpreter API per conversation ID */ + LAST_CODE_TOGGLE_ = 'LAST_CODE_TOGGLE_', } export enum ForkOptions { diff --git a/packages/data-provider/src/createPayload.ts b/packages/data-provider/src/createPayload.ts index 19d94cec4f..edd8f0e426 100644 --- a/packages/data-provider/src/createPayload.ts +++ b/packages/data-provider/src/createPayload.ts @@ -19,20 +19,20 @@ export default function createPayload(submission: t.TSubmission) { }; let server = EndpointURLs[endpointType ?? endpoint]; - const isEphemeralAgent = (ephemeralAgent?.mcp?.length ?? 0) > 0 && !s.isAgentsEndpoint(endpoint); + const isEphemeral = s.isEphemeralAgent(endpoint, ephemeralAgent); if (isEdited && s.isAssistantsEndpoint(endpoint)) { server += '/modify'; } else if (isEdited) { server = server.replace('/ask/', '/edit/'); - } else if (isEphemeralAgent) { + } else if (isEphemeral) { server = `${EndpointURLs[s.EModelEndpoint.agents]}/${endpoint}`; } const payload: t.TPayload = { ...userMessage, ...endpointOption, - ephemeralAgent: isEphemeralAgent ? ephemeralAgent : undefined, + ephemeralAgent: isEphemeral ? ephemeralAgent : undefined, isContinued: !!(isEdited && isContinued), conversationId, isTemporary, diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index b923a1a467..f3335a8942 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { Tools } from './types/assistants'; import type { TMessageContentParts, FunctionTool, FunctionToolCall } from './types/assistants'; +import type { TEphemeralAgent } from './types'; import type { TFile } from './types/files'; export const isUUID = z.string().uuid(); @@ -88,6 +89,21 @@ export const isAgentsEndpoint = (_endpoint?: EModelEndpoint.agents | null | stri return endpoint === EModelEndpoint.agents; }; +export const isEphemeralAgent = ( + endpoint?: EModelEndpoint.agents | null | string, + ephemeralAgent?: TEphemeralAgent | null, +) => { + if (!ephemeralAgent) { + return false; + } + if (isAgentsEndpoint(endpoint)) { + return false; + } + const hasMCPSelected = (ephemeralAgent?.mcp?.length ?? 0) > 0; + const hasCodeSelected = (ephemeralAgent?.execute_code ?? false) === true; + return hasMCPSelected || hasCodeSelected; +}; + export const isParamEndpoint = ( endpoint: EModelEndpoint | string, endpointType?: EModelEndpoint | string, diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index dabe89cdb0..1ac3d1f46d 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -42,7 +42,8 @@ export type TEndpointOption = { }; export type TEphemeralAgent = { - mcp: string[]; + mcp?: string[]; + execute_code?: boolean; }; export type TPayload = Partial & From e16a6190a556602289d8c5bb226f540382f25aae Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 9 Apr 2025 18:38:48 -0400 Subject: [PATCH 08/75] =?UTF-8?q?=F0=9F=92=BE=20chore:=20Enhance=20Local?= =?UTF-8?q?=20Storage=20Handling=20and=20Update=20MCP=20SDK=20(#6809)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Update MCP package version and dependencies; refactor ToolContentPart type * refactor: Change module type to commonjs and update rollup configuration, remove unused dev dependency * refactor: Change async calls to synchronous for MCP and FlowStateManager retrieval * chore: Add eslint disable comment for i18next rule in DropdownPopup component * fix: improve statefulness of mcp servers selected if some were removed since last session * feat: implement conversation storage cleanup functions and integrate them into mutation success handlers * feat: enhance storage condition logic in useLocalStorageAlt to prevent unnecessary local storage writes * refactor: streamline local storage update logic in useLocalStorageAlt --- api/config/index.js | 11 +- api/server/controllers/PluginController.js | 2 +- api/server/routes/actions.js | 2 +- api/server/services/ActionService.js | 4 +- api/server/services/AppService.js | 2 +- api/server/services/MCP.js | 2 +- .../components/Chat/Input/CodeInterpreter.tsx | 15 + .../src/components/Chat/Input/MCPSelect.tsx | 59 +- .../Nav/SettingsTabs/Data/ClearChats.tsx | 2 + client/src/components/ui/DropdownPopup.tsx | 1 + client/src/data-provider/Auth/mutations.ts | 2 + client/src/data-provider/mutations.ts | 4 +- client/src/hooks/useLocalStorageAlt.tsx | 16 +- client/src/utils/localStorage.ts | 35 +- package-lock.json | 587 +++++++++--------- packages/mcp/package.json | 13 +- packages/mcp/rollup.config.js | 24 +- packages/mcp/src/types/mcp.ts | 21 +- 18 files changed, 420 insertions(+), 382 deletions(-) diff --git a/api/config/index.js b/api/config/index.js index 8f23e404c8..919919b55f 100644 --- a/api/config/index.js +++ b/api/config/index.js @@ -1,6 +1,7 @@ const axios = require('axios'); const { EventSource } = require('eventsource'); const { Time, CacheKeys } = require('librechat-data-provider'); +const { MCPManager, FlowStateManager } = require('librechat-mcp'); const logger = require('./winston'); global.EventSource = EventSource; @@ -9,11 +10,10 @@ let mcpManager = null; let flowManager = null; /** - * @returns {Promise} + * @returns {MCPManager} */ -async function getMCPManager() { +function getMCPManager() { if (!mcpManager) { - const { MCPManager } = await import('librechat-mcp'); mcpManager = MCPManager.getInstance(logger); } return mcpManager; @@ -21,11 +21,10 @@ async function getMCPManager() { /** * @param {(key: string) => Keyv} getLogStores - * @returns {Promise} + * @returns {FlowStateManager} */ -async function getFlowStateManager(getLogStores) { +function getFlowStateManager(getLogStores) { if (!flowManager) { - const { FlowStateManager } = await import('librechat-mcp'); flowManager = new FlowStateManager(getLogStores(CacheKeys.FLOWS), { ttl: Time.ONE_MINUTE * 3, logger, diff --git a/api/server/controllers/PluginController.js b/api/server/controllers/PluginController.js index 9e87b46289..71e7ed348e 100644 --- a/api/server/controllers/PluginController.js +++ b/api/server/controllers/PluginController.js @@ -108,7 +108,7 @@ const getAvailableTools = async (req, res) => { const pluginManifest = availableTools; const customConfig = await getCustomConfig(); if (customConfig?.mcpServers != null) { - const mcpManager = await getMCPManager(); + const mcpManager = getMCPManager(); await mcpManager.loadManifestTools(pluginManifest); } diff --git a/api/server/routes/actions.js b/api/server/routes/actions.js index 454f4be6c7..28845e3f15 100644 --- a/api/server/routes/actions.js +++ b/api/server/routes/actions.js @@ -20,7 +20,7 @@ router.get('/:action_id/oauth/callback', async (req, res) => { const { action_id } = req.params; const { code, state } = req.query; - const flowManager = await getFlowStateManager(getLogStores); + const flowManager = getFlowStateManager(getLogStores); let identifier = action_id; try { let decodedState; diff --git a/api/server/services/ActionService.js b/api/server/services/ActionService.js index 0dc8070f1a..12e30fd8b9 100644 --- a/api/server/services/ActionService.js +++ b/api/server/services/ActionService.js @@ -189,7 +189,7 @@ async function createActionTool({ expires_at: Date.now() + Time.TWO_MINUTES, }, }; - const flowManager = await getFlowStateManager(getLogStores); + const flowManager = getFlowStateManager(getLogStores); await flowManager.createFlowWithHandler( `${identifier}:oauth_login:${config.metadata.thread_id}:${config.metadata.run_id}`, 'oauth_login', @@ -265,7 +265,7 @@ async function createActionTool({ encrypted_oauth_client_id: encrypted.oauth_client_id, encrypted_oauth_client_secret: encrypted.oauth_client_secret, }); - const flowManager = await getFlowStateManager(getLogStores); + const flowManager = getFlowStateManager(getLogStores); const refreshData = await flowManager.createFlowWithHandler( `${identifier}:refresh`, 'oauth_refresh', diff --git a/api/server/services/AppService.js b/api/server/services/AppService.js index f245c1f737..1ad3aaace6 100644 --- a/api/server/services/AppService.js +++ b/api/server/services/AppService.js @@ -66,7 +66,7 @@ const AppService = async (app) => { }); if (config.mcpServers != null) { - const mcpManager = await getMCPManager(); + const mcpManager = getMCPManager(); await mcpManager.initializeMCP(config.mcpServers, processMCPEnv); await mcpManager.mapAvailableTools(availableTools); } diff --git a/api/server/services/MCP.js b/api/server/services/MCP.js index b64194b07b..0a2711c672 100644 --- a/api/server/services/MCP.js +++ b/api/server/services/MCP.js @@ -49,7 +49,7 @@ async function createMCPTool({ req, toolKey, provider }) { /** @type {(toolArguments: Object | string, config?: GraphRunnableConfig) => Promise} */ const _call = async (toolArguments, config) => { try { - const mcpManager = await getMCPManager(); + const mcpManager = getMCPManager(); const result = await mcpManager.callTool({ serverName, toolName, diff --git a/client/src/components/Chat/Input/CodeInterpreter.tsx b/client/src/components/Chat/Input/CodeInterpreter.tsx index 6bfc367cb1..3f2c282ffe 100644 --- a/client/src/components/Chat/Input/CodeInterpreter.tsx +++ b/client/src/components/Chat/Input/CodeInterpreter.tsx @@ -17,6 +17,20 @@ import useLocalStorage from '~/hooks/useLocalStorageAlt'; import { useVerifyAgentToolAuth } from '~/data-provider'; import { ephemeralAgentByConvoId } from '~/store'; +const storageCondition = (value: unknown, rawCurrentValue?: string | null) => { + if (rawCurrentValue) { + try { + const currentValue = rawCurrentValue?.trim() ?? ''; + if (currentValue === 'true' && value === false) { + return true; + } + } catch (e) { + console.error(e); + } + } + return value !== undefined && value !== null && value !== '' && value !== false; +}; + function CodeInterpreter({ conversationId }: { conversationId?: string | null }) { const localize = useLocalize(); const key = conversationId ?? Constants.NEW_CONVO; @@ -55,6 +69,7 @@ function CodeInterpreter({ conversationId }: { conversationId?: string | null }) `${LocalStorageKeys.LAST_CODE_TOGGLE_}${key}`, isCodeToggleEnabled, setValue, + storageCondition, ); const handleChange = useCallback( diff --git a/client/src/components/Chat/Input/MCPSelect.tsx b/client/src/components/Chat/Input/MCPSelect.tsx index 270228fe57..672713f6d6 100644 --- a/client/src/components/Chat/Input/MCPSelect.tsx +++ b/client/src/components/Chat/Input/MCPSelect.tsx @@ -8,14 +8,43 @@ import { ephemeralAgentByConvoId } from '~/store'; import MCPIcon from '~/components/ui/MCPIcon'; import { useLocalize } from '~/hooks'; +const storageCondition = (value: unknown, rawCurrentValue?: string | null) => { + if (rawCurrentValue) { + try { + const currentValue = rawCurrentValue?.trim() ?? ''; + if (currentValue.length > 2) { + return true; + } + } catch (e) { + console.error(e); + } + } + return Array.isArray(value) && value.length > 0; +}; + function MCPSelect({ conversationId }: { conversationId?: string | null }) { const localize = useLocalize(); - const hasSetFetched = useRef(false); const key = conversationId ?? Constants.NEW_CONVO; + const hasSetFetched = useRef(null); + + const { data: mcpServerSet, isFetched } = useAvailableToolsQuery(EModelEndpoint.agents, { + select: (data) => { + const serverNames = new Set(); + data.forEach((tool) => { + if (tool.pluginKey.includes(Constants.mcp_delimiter)) { + const parts = tool.pluginKey.split(Constants.mcp_delimiter); + serverNames.add(parts[parts.length - 1]); + } + }); + return serverNames; + }, + }); + const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key)); const mcpState = useMemo(() => { return ephemeralAgent?.mcp ?? []; }, [ephemeralAgent?.mcp]); + const setSelectedValues = useCallback( (values: string[] | null | undefined) => { if (!values) { @@ -35,33 +64,23 @@ function MCPSelect({ conversationId }: { conversationId?: string | null }) { `${LocalStorageKeys.LAST_MCP_}${key}`, mcpState, setSelectedValues, + storageCondition, ); - const { data: mcpServers, isFetched } = useAvailableToolsQuery(EModelEndpoint.agents, { - select: (data) => { - const serverNames = new Set(); - data.forEach((tool) => { - if (tool.pluginKey.includes(Constants.mcp_delimiter)) { - const parts = tool.pluginKey.split(Constants.mcp_delimiter); - serverNames.add(parts[parts.length - 1]); - } - }); - return [...serverNames]; - }, - }); useEffect(() => { - if (hasSetFetched.current) { + if (hasSetFetched.current === key) { return; } if (!isFetched) { return; } - hasSetFetched.current = true; - if ((mcpServers?.length ?? 0) > 0) { + hasSetFetched.current = key; + if ((mcpServerSet?.size ?? 0) > 0) { + setMCPValues(mcpValues.filter((mcp) => mcpServerSet?.has(mcp))); return; } setMCPValues([]); - }, [isFetched, setMCPValues, mcpServers?.length]); + }, [isFetched, setMCPValues, mcpServerSet, key, mcpValues]); const renderSelectedValues = useCallback( (values: string[], placeholder?: string) => { @@ -76,7 +95,11 @@ function MCPSelect({ conversationId }: { conversationId?: string | null }) { [localize], ); - if (!mcpServers || mcpServers.length === 0) { + const mcpServers = useMemo(() => { + return Array.from(mcpServerSet ?? []); + }, [mcpServerSet]); + + if (!mcpServerSet || mcpServerSet.size === 0) { return null; } diff --git a/client/src/components/Nav/SettingsTabs/Data/ClearChats.tsx b/client/src/components/Nav/SettingsTabs/Data/ClearChats.tsx index dd7aacc2ab..11e53ba570 100644 --- a/client/src/components/Nav/SettingsTabs/Data/ClearChats.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/ClearChats.tsx @@ -3,6 +3,7 @@ import { useClearConversationsMutation } from 'librechat-data-provider/react-que import { Label, Button, OGDialog, OGDialogTrigger, Spinner } from '~/components'; import { useLocalize, useNewConvo } from '~/hooks'; import OGDialogTemplate from '~/components/ui/OGDialogTemplate'; +import { clearAllConversationStorage } from '~/utils'; export const ClearChats = () => { const localize = useLocalize(); @@ -15,6 +16,7 @@ export const ClearChats = () => { {}, { onSuccess: () => { + clearAllConversationStorage(); newConversation(); }, }, diff --git a/client/src/components/ui/DropdownPopup.tsx b/client/src/components/ui/DropdownPopup.tsx index 8645d59736..919557eacb 100644 --- a/client/src/components/ui/DropdownPopup.tsx +++ b/client/src/components/ui/DropdownPopup.tsx @@ -83,6 +83,7 @@ const DropdownPopup: React.FC = ({ )} {item.label} {item.kbd != null && ( + // eslint-disable-next-line i18next/no-literal-string ⌘{item.kbd} diff --git a/client/src/data-provider/Auth/mutations.ts b/client/src/data-provider/Auth/mutations.ts index eb09868ec6..88fdcf06f1 100644 --- a/client/src/data-provider/Auth/mutations.ts +++ b/client/src/data-provider/Auth/mutations.ts @@ -4,6 +4,7 @@ import { MutationKeys, QueryKeys, dataService, request } from 'librechat-data-pr import type { UseMutationResult } from '@tanstack/react-query'; import type * as t from 'librechat-data-provider'; import useClearStates from '~/hooks/Config/useClearStates'; +import { clearAllConversationStorage } from '~/utils'; import store from '~/store'; /* login/logout */ @@ -79,6 +80,7 @@ export const useDeleteUserMutation = ( onSuccess: (...args) => { resetDefaultPreset(); clearStates(); + clearAllConversationStorage(); queryClient.removeQueries(); options?.onSuccess?.(...args); }, diff --git a/client/src/data-provider/mutations.ts b/client/src/data-provider/mutations.ts index 60f65eeeec..fb772f7a0d 100644 --- a/client/src/data-provider/mutations.ts +++ b/client/src/data-provider/mutations.ts @@ -18,6 +18,7 @@ import { updateConvoFields, updateConversation, deleteConversation, + clearConversationStorage, } from '~/utils'; export type TGenTitleMutation = UseMutationResult< @@ -562,6 +563,7 @@ export const useDeleteConversationMutation = ( const current = queryClient.getQueryData([QueryKeys.allConversations]); refetch({ refetchPage: (page, index) => index === (current?.pages.length ?? 1) - 1 }); onSuccess?.(_data, vars, context); + clearConversationStorage(conversationId); }, ..._options, }, @@ -897,7 +899,7 @@ export const useUploadAssistantAvatarMutation = ( unknown // context > => { return useMutation([MutationKeys.assistantAvatarUpload], { - // eslint-disable-next-line @typescript-eslint/no-unused-vars + mutationFn: ({ postCreation, ...variables }: t.AssistantAvatarVariables) => dataService.uploadAssistantAvatar(variables), ...(options || {}), diff --git a/client/src/hooks/useLocalStorageAlt.tsx b/client/src/hooks/useLocalStorageAlt.tsx index ef63198242..81b3e637ec 100644 --- a/client/src/hooks/useLocalStorageAlt.tsx +++ b/client/src/hooks/useLocalStorageAlt.tsx @@ -11,13 +11,16 @@ export default function useLocalStorage( key: string, defaultValue: T, globalSetState?: (value: T) => void, + storageCondition?: (value: T, rawCurrentValue?: string | null) => boolean, ): [T, (value: T) => void] { const [value, setValue] = useState(defaultValue); useEffect(() => { const item = localStorage.getItem(key); - if (!item) { + if (!item && !storageCondition) { + localStorage.setItem(key, JSON.stringify(defaultValue)); + } else if (!item && storageCondition && storageCondition(defaultValue)) { localStorage.setItem(key, JSON.stringify(defaultValue)); } @@ -47,9 +50,14 @@ export default function useLocalStorage( const setValueWrap = (value: T) => { try { setValue(value); - localStorage.setItem(key, JSON.stringify(value)); - if (typeof window !== 'undefined') { - window.dispatchEvent(new StorageEvent('storage', { key })); + const storeLocal = () => { + localStorage.setItem(key, JSON.stringify(value)); + window?.dispatchEvent(new StorageEvent('storage', { key })); + }; + if (!storageCondition) { + storeLocal(); + } else if (storageCondition(value, localStorage.getItem(key))) { + storeLocal(); } globalSetState?.(value); } catch (e) { diff --git a/client/src/utils/localStorage.ts b/client/src/utils/localStorage.ts index cb1dee9403..d1c9d1acf1 100644 --- a/client/src/utils/localStorage.ts +++ b/client/src/utils/localStorage.ts @@ -1,4 +1,4 @@ -import { LocalStorageKeys, TConversation } from 'librechat-data-provider'; +import { LocalStorageKeys, TConversation, isUUID } from 'librechat-data-provider'; export function getLocalStorageItems() { const items = { @@ -45,3 +45,36 @@ export function clearLocalStorage(skipFirst?: boolean) { } }); } + +export function clearConversationStorage(conversationId?: string | null) { + if (!conversationId) { + return; + } + if (!isUUID.safeParse(conversationId)?.success) { + console.warn( + `Conversation ID ${conversationId} is not a valid UUID. Skipping local storage cleanup.`, + ); + return; + } + const keys = Object.keys(localStorage); + keys.forEach((key) => { + if (key.includes(conversationId)) { + localStorage.removeItem(key); + } + }); +} +export function clearAllConversationStorage() { + const keys = Object.keys(localStorage); + keys.forEach((key) => { + if ( + key.startsWith(LocalStorageKeys.LAST_MCP_) || + key.startsWith(LocalStorageKeys.LAST_CODE_TOGGLE_) || + key.startsWith(LocalStorageKeys.TEXT_DRAFT) || + key.startsWith(LocalStorageKeys.ASST_ID_PREFIX) || + key.startsWith(LocalStorageKeys.AGENT_ID_PREFIX) || + key.startsWith(LocalStorageKeys.LAST_CONVO_SETUP) + ) { + localStorage.removeItem(key); + } + }); +} diff --git a/package-lock.json b/package-lock.json index 158ec0e959..a28245e651 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18649,304 +18649,6 @@ "zod": ">= 3" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz", - "integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==", - "dependencies": { - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.3", - "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^4.1.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/express": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", - "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.0.1", - "content-disposition": "^1.0.0", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "^1.2.1", - "debug": "4.3.6", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "^2.0.0", - "fresh": "2.0.0", - "http-errors": "2.0.0", - "merge-descriptors": "^2.0.0", - "methods": "~1.1.2", - "mime-types": "^3.0.0", - "on-finished": "2.4.1", - "once": "1.4.0", - "parseurl": "~1.3.3", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "router": "^2.0.0", - "safe-buffer": "5.2.1", - "send": "^1.1.0", - "serve-static": "^2.1.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "^2.0.0", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", @@ -36071,14 +35773,6 @@ "node": ">= 6" } }, - "node_modules/pkce-challenge": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz", - "integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==", - "engines": { - "node": ">=16.20.0" - } - }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -43978,10 +43672,10 @@ }, "packages/mcp": { "name": "librechat-mcp", - "version": "1.1.0", + "version": "1.2.0", "license": "ISC", "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", + "@modelcontextprotocol/sdk": "^1.9.0", "diff": "^7.0.0", "eventsource": "^3.0.2", "express": "^4.21.2" @@ -44010,7 +43704,6 @@ "rollup": "^4.22.4", "rollup-plugin-generate-package-json": "^3.2.0", "rollup-plugin-peer-deps-external": "^2.2.4", - "rollup-plugin-typescript2": "^0.35.0", "ts-node": "^10.9.2", "typescript": "^5.0.4" }, @@ -44018,6 +43711,98 @@ "keyv": "^4.5.4" } }, + "packages/mcp/node_modules/@modelcontextprotocol/sdk": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz", + "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==", + "dependencies": { + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.3", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "packages/mcp/node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "packages/mcp/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "packages/mcp/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "packages/mcp/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -44027,6 +43812,49 @@ "balanced-match": "^1.0.0" } }, + "packages/mcp/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "packages/mcp/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "packages/mcp/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "packages/mcp/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, "packages/mcp/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -44047,6 +43875,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/mcp/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "packages/mcp/node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -44062,6 +43901,44 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "packages/mcp/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "packages/mcp/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/mcp/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "packages/mcp/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "packages/mcp/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -44077,6 +43954,50 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/mcp/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "packages/mcp/node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "engines": { + "node": ">=16.20.0" + } + }, + "packages/mcp/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "packages/mcp/node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "packages/mcp/node_modules/rimraf": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", @@ -44091,6 +44012,54 @@ "funding": { "url": "https://github.com/sponsors/isaacs" } + }, + "packages/mcp/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "packages/mcp/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "packages/mcp/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } } } } diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 098efa8b5d..fcb17f7e69 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,21 +1,21 @@ { "name": "librechat-mcp", - "version": "1.1.0", - "type": "module", + "version": "1.2.0", + "type": "commonjs", "description": "MCP services for LibreChat", "main": "dist/index.js", "module": "dist/index.es.js", "types": "./dist/types/index.d.ts", "exports": { ".": { - "import": "./dist/index.es.js", + "require": "./dist/index.js", "types": "./dist/types/index.d.ts" } }, "scripts": { "clean": "rimraf dist", - "build": "npm run clean && rollup -c --configPlugin=@rollup/plugin-typescript", - "build:watch": "rollup -c -w --configPlugin=@rollup/plugin-typescript", + "build": "npm run clean && rollup -c --bundleConfigAsCjs", + "build:watch": "rollup -c -w --bundleConfigAsCjs", "test": "jest --coverage --watch", "test:ci": "jest --coverage --ci", "verify": "npm run test:ci", @@ -60,7 +60,6 @@ "rollup": "^4.22.4", "rollup-plugin-generate-package-json": "^3.2.0", "rollup-plugin-peer-deps-external": "^2.2.4", - "rollup-plugin-typescript2": "^0.35.0", "ts-node": "^10.9.2", "typescript": "^5.0.4" }, @@ -68,7 +67,7 @@ "registry": "https://registry.npmjs.org/" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", + "@modelcontextprotocol/sdk": "^1.9.0", "diff": "^7.0.0", "eventsource": "^3.0.2", "express": "^4.21.2" diff --git a/packages/mcp/rollup.config.js b/packages/mcp/rollup.config.js index 6bf8df24b2..6e17d6ee2f 100644 --- a/packages/mcp/rollup.config.js +++ b/packages/mcp/rollup.config.js @@ -1,11 +1,11 @@ // rollup.config.js -import typescript from 'rollup-plugin-typescript2'; -import resolve from '@rollup/plugin-node-resolve'; -import peerDepsExternal from 'rollup-plugin-peer-deps-external'; -import commonjs from '@rollup/plugin-commonjs'; -import replace from '@rollup/plugin-replace'; -import terser from '@rollup/plugin-terser'; import { readFileSync } from 'fs'; +import terser from '@rollup/plugin-terser'; +import replace from '@rollup/plugin-replace'; +import commonjs from '@rollup/plugin-commonjs'; +import resolve from '@rollup/plugin-node-resolve'; +import typescript from '@rollup/plugin-typescript'; +import peerDepsExternal from 'rollup-plugin-peer-deps-external'; const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')); @@ -24,16 +24,18 @@ const plugins = [ }), typescript({ tsconfig: './tsconfig.json', - useTsconfigDeclarationDir: true, + outDir: './dist', + sourceMap: true, + inlineSourceMap: true, }), terser(), ]; -const esmBuild = { +const cjsBuild = { input: 'src/index.ts', output: { - file: pkg.module, - format: 'esm', + file: pkg.main, + format: 'cjs', sourcemap: true, exports: 'named', }, @@ -42,4 +44,4 @@ const esmBuild = { plugins, }; -export default esmBuild; +export default cjsBuild; diff --git a/packages/mcp/src/types/mcp.ts b/packages/mcp/src/types/mcp.ts index 106eb04e13..1ab99ff675 100644 --- a/packages/mcp/src/types/mcp.ts +++ b/packages/mcp/src/types/mcp.ts @@ -8,6 +8,7 @@ import { } from 'librechat-data-provider'; import type { JsonSchemaType, TPlugin } from 'librechat-data-provider'; import { ToolSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'; +import type * as t from '@modelcontextprotocol/sdk/types.js'; export type StdioOptions = z.infer; export type WebSocketOptions = z.infer; @@ -44,25 +45,7 @@ export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'err export type MCPTool = z.infer; export type MCPToolListResponse = z.infer; -export type ToolContentPart = - | { - type: 'text'; - text: string; - } - | { - type: 'image'; - data: string; - mimeType: string; - } - | { - type: 'resource'; - resource: { - uri: string; - mimeType?: string; - text?: string; - blob?: string; - }; - }; +export type ToolContentPart = t.TextContent | t.ImageContent | t.EmbeddedResource | t.AudioContent; export type ImageContent = Extract; export type MCPToolCallResponse = | undefined From 12f4dbb8c5603e30cc9928bdcceeafa4922389d7 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Thu, 10 Apr 2025 15:37:23 -0400 Subject: [PATCH 09/75] =?UTF-8?q?=E2=9A=A1=20feat:=20Self-hosted=20Artifac?= =?UTF-8?q?ts=20Static=20Bundler=20URL=20(#6827)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * v0.7.791 * feat: configuration via `SANDPACK_STATIC_BUNDLER_URL` env var and update bundlerURL logic in Artifact components * fix: update minimum length requirement for auth fields from 10 to 1 character --- api/server/routes/config.js | 1 + client/src/components/Artifacts/ArtifactCodeEditor.tsx | 4 ++-- client/src/components/Artifacts/ArtifactPreview.tsx | 10 +++++++--- client/src/components/Plugins/Store/PluginAuthForm.tsx | 4 ++-- package-lock.json | 2 +- packages/data-provider/package.json | 2 +- packages/data-provider/src/config.ts | 1 + 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/api/server/routes/config.js b/api/server/routes/config.js index e1e8ba763b..ebafb05c30 100644 --- a/api/server/routes/config.js +++ b/api/server/routes/config.js @@ -82,6 +82,7 @@ router.get('/', async function (req, res) { analyticsGtmId: process.env.ANALYTICS_GTM_ID, instanceProjectId: instanceProject._id.toString(), bundlerURL: process.env.SANDPACK_BUNDLER_URL, + staticBundlerURL: process.env.SANDPACK_STATIC_BUNDLER_URL, }; if (ldap) { diff --git a/client/src/components/Artifacts/ArtifactCodeEditor.tsx b/client/src/components/Artifacts/ArtifactCodeEditor.tsx index c0b62c2916..ecec01cdbe 100644 --- a/client/src/components/Artifacts/ArtifactCodeEditor.tsx +++ b/client/src/components/Artifacts/ArtifactCodeEditor.tsx @@ -132,9 +132,9 @@ export const ArtifactCodeEditor = memo(function ({ } return { ...sharedOptions, - bundlerURL: config.bundlerURL, + bundlerURL: template === 'static' ? config.staticBundlerURL : config.bundlerURL, }; - }, [config]); + }, [config, template]); if (Object.keys(files).length === 0) { return null; diff --git a/client/src/components/Artifacts/ArtifactPreview.tsx b/client/src/components/Artifacts/ArtifactPreview.tsx index d3d147929f..fdf435c3be 100644 --- a/client/src/components/Artifacts/ArtifactPreview.tsx +++ b/client/src/components/Artifacts/ArtifactPreview.tsx @@ -46,11 +46,15 @@ export const ArtifactPreview = memo(function ({ if (!config) { return sharedOptions; } - return { + const _options: typeof sharedOptions = { ...sharedOptions, - bundlerURL: config.bundlerURL, + bundlerURL: template === 'static' ? config.staticBundlerURL : config.bundlerURL, }; - }, [config]); + + return _options; + }, [config, template]); + + console.log(options); if (Object.keys(artifactFiles).length === 0) { return null; diff --git a/client/src/components/Plugins/Store/PluginAuthForm.tsx b/client/src/components/Plugins/Store/PluginAuthForm.tsx index c2d1817107..2275bbe96f 100644 --- a/client/src/components/Plugins/Store/PluginAuthForm.tsx +++ b/client/src/components/Plugins/Store/PluginAuthForm.tsx @@ -59,8 +59,8 @@ function PluginAuthForm({ plugin, onSubmit, isEntityTool }: TPluginAuthFormProps {...register(authField, { required: `${config.label} is required.`, minLength: { - value: 10, - message: `${config.label} must be at least 10 characters long`, + value: 1, + message: `${config.label} must be at least 1 character long`, }, })} className="flex h-10 max-h-10 w-full resize-none rounded-md border border-gray-200 bg-transparent px-3 py-2 text-sm text-gray-700 shadow-[0_0_10px_rgba(0,0,0,0.05)] outline-none placeholder:text-gray-400 focus:border-gray-400 focus:bg-gray-50 focus:outline-none focus:ring-0 focus:ring-gray-400 focus:ring-opacity-0 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 focus:dark:bg-gray-600 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0" diff --git a/package-lock.json b/package-lock.json index a28245e651..ec692aaa14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43309,7 +43309,7 @@ }, "packages/data-provider": { "name": "librechat-data-provider", - "version": "0.7.790", + "version": "0.7.791", "license": "ISC", "dependencies": { "axios": "^1.8.2", diff --git a/packages/data-provider/package.json b/packages/data-provider/package.json index 64f120503c..6437be5f11 100644 --- a/packages/data-provider/package.json +++ b/packages/data-provider/package.json @@ -1,6 +1,6 @@ { "name": "librechat-data-provider", - "version": "0.7.790", + "version": "0.7.791", "description": "data services for librechat apps", "main": "dist/index.js", "module": "dist/index.es.js", diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 6e62919526..bfb1694088 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -541,6 +541,7 @@ export type TStartupConfig = { analyticsGtmId?: string; instanceProjectId: string; bundlerURL?: string; + staticBundlerURL?: string; }; export enum OCRStrategy { From 1e6b1b9554601179e612123435c9ae394367d5ce Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 11 Apr 2025 00:42:32 -0400 Subject: [PATCH 10/75] =?UTF-8?q?=F0=9F=90=B3=20feat:=20Add=20Jemalloc=20a?= =?UTF-8?q?nd=20UV=20to=20Docker=20Builds=20(#6836)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add `uv` for extended MCP support in Dockerfiles * feat: Install jemalloc and set environment variable to use it --- Dockerfile | 12 ++++++++++-- Dockerfile.multi | 7 +++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index d9113eb650..e16b18a5d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,15 @@ # Base node image FROM node:20-alpine AS node -RUN apk --no-cache add curl +# Install jemalloc +RUN apk add --no-cache jemalloc + +# Set environment variable to use jemalloc +ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2 + +# Add `uv` for extended MCP support +COPY --from=ghcr.io/astral-sh/uv:0.6.13 /uv /uvx /bin/ +RUN uv --version RUN mkdir -p /app && chown node:node /app WORKDIR /app @@ -38,4 +46,4 @@ CMD ["npm", "run", "backend"] # WORKDIR /usr/share/nginx/html # COPY --from=node /app/client/dist /usr/share/nginx/html # COPY client/nginx.conf /etc/nginx/conf.d/default.conf -# ENTRYPOINT ["nginx", "-g", "daemon off;"] +# ENTRYPOINT ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/Dockerfile.multi b/Dockerfile.multi index 40721137bb..594ead8b03 100644 --- a/Dockerfile.multi +++ b/Dockerfile.multi @@ -3,6 +3,10 @@ # Base for all builds FROM node:20-alpine AS base-min +# Install jemalloc +RUN apk add --no-cache jemalloc +# Set environment variable to use jemalloc +ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2 WORKDIR /app RUN apk --no-cache add curl RUN npm config set fetch-retry-maxtimeout 600000 && \ @@ -50,6 +54,9 @@ RUN npm run build # API setup (including client dist) FROM base-min AS api-build +# Add `uv` for extended MCP support +COPY --from=ghcr.io/astral-sh/uv:0.6.13 /uv /uvx /bin/ +RUN uv --version WORKDIR /app # Install only production deps RUN npm ci --omit=dev From 37964975c1ead270deb2a71a3fa2dfeae2e25b6e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sat, 12 Apr 2025 18:46:36 -0400 Subject: [PATCH 11/75] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20Improve=20Agen?= =?UTF-8?q?ts=20Memory=20Usage,=20Bump=20Keyv,=20Grok=203=20(#6850)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: remove unused redis file * chore: bump keyv dependencies, and update related imports * refactor: Implement IoRedis client for rate limiting across middleware, as node-redis via keyv not compatible * fix: Set max listeners to expected amount * WIP: memory improvements * refactor: Simplify getAbortData assignment in createAbortController * refactor: Update getAbortData to use WeakRef for content management * WIP: memory improvements in agent chat requests * refactor: Enhance memory management with finalization registry and cleanup functions * refactor: Simplify domainParser calls by removing unnecessary request parameter * refactor: Update parameter types for action tools and agent loading functions to use minimal configs * refactor: Simplify domainParser tests by removing unnecessary request parameter * refactor: Simplify domainParser call by removing unnecessary request parameter * refactor: Enhance client disposal by nullifying additional properties to improve memory management * refactor: Improve title generation by adding abort controller and timeout handling, consolidate request cleanup * refactor: Update checkIdleConnections to skip current user when checking for idle connections if passed * refactor: Update createMCPTool to derive userId from config and handle abort signals * refactor: Introduce createTokenCounter function and update tokenCounter usage; enhance disposeClient to reset Graph values * refactor: Update getMCPManager to accept userId parameter for improved idle connection handling * refactor: Extract logToolError function for improved error handling in AgentClient * refactor: Update disposeClient to clear handlerRegistry and graphRunnable references in client.run * refactor: Extract createHandleNewToken function to streamline token handling in initializeClient * chore: bump @librechat/agents * refactor: Improve timeout handling in addTitle function for better error management * refactor: Introduce createFetch instead of using class method * refactor: Enhance client disposal and request data handling in AskController and EditController * refactor: Update import statements for AnthropicClient and OpenAIClient to use specific paths * refactor: Use WeakRef for response handling in SplitStreamHandler to prevent memory leaks * refactor: Simplify client disposal and rename getReqData to processReqData in AskController and EditController * refactor: Improve logging structure and parameter handling in OpenAIClient * refactor: Remove unused GraphEvents and improve stream event handling in AnthropicClient and OpenAIClient * refactor: Simplify client initialization in AskController and EditController * refactor: Remove unused mock functions and implement in-memory store for KeyvMongo * chore: Update dependencies in package-lock.json to latest versions * refactor: Await token usage recording in OpenAIClient to ensure proper async handling * refactor: Remove handleAbort route from multiple endpoints and enhance client disposal logic * refactor: Enhance abort controller logic by managing abortKey more effectively * refactor: Add newConversation handling in useEventHandlers for improved conversation management * fix: dropparams * refactor: Use optional chaining for safer access to request properties in BaseClient * refactor: Move client disposal and request data processing logic to cleanup module for better organization * refactor: Remove aborted request check from addTitle function for cleaner logic * feat: Add Grok 3 model pricing and update tests for new models * chore: Remove trace warnings and inspect flags from backend start script used for debugging * refactor: Replace user identifier handling with userId for consistency across controllers, use UserId in clientRegistry * refactor: Enhance client disposal logic to prevent memory leaks by clearing additional references * chore: Update @librechat/agents to version 2.4.14 in package.json and package-lock.json --- api/app/clients/AnthropicClient.js | 17 +- api/app/clients/BaseClient.js | 50 +- api/app/clients/ChatGPTClient.js | 4 +- api/app/clients/OpenAIClient.js | 31 +- api/app/clients/PluginsClient.js | 10 +- api/app/clients/generators.js | 60 ++ api/app/clients/tools/util/handleTools.js | 2 +- api/cache/getLogStores.js | 2 +- api/cache/ioredisClient.js | 92 ++++ api/cache/keyvFiles.js | 6 +- api/cache/keyvMongo.js | 2 +- api/cache/keyvRedis.js | 15 +- api/cache/redis.js | 4 - api/config/index.js | 6 +- api/models/tx.js | 4 + api/models/tx.spec.js | 22 + api/package.json | 10 +- api/server/cleanup.js | 240 ++++++++ api/server/controllers/AskController.js | 259 ++++++--- api/server/controllers/EditController.js | 202 +++++-- api/server/controllers/agents/client.js | 50 +- api/server/controllers/agents/request.js | 178 +++++- api/server/middleware/abortMiddleware.js | 196 ++++++- api/server/middleware/checkBan.js | 2 +- .../middleware/limiters/importLimiters.js | 9 +- .../middleware/limiters/loginLimiter.js | 10 +- .../middleware/limiters/messageLimiters.js | 9 +- .../middleware/limiters/registerLimiter.js | 10 +- .../limiters/resetPasswordLimiter.js | 10 +- api/server/middleware/limiters/sttLimiters.js | 9 +- .../middleware/limiters/toolCallLimiter.js | 10 +- api/server/middleware/limiters/ttsLimiters.js | 9 +- .../middleware/limiters/uploadLimiters.js | 9 +- .../middleware/limiters/verifyEmailLimiter.js | 10 +- api/server/routes/agents/actions.js | 4 +- api/server/routes/agents/chat.js | 2 - api/server/routes/ask/addToCache.js | 2 +- api/server/routes/ask/anthropic.js | 2 - api/server/routes/ask/custom.js | 3 - api/server/routes/ask/google.js | 3 - api/server/routes/ask/gptPlugins.js | 4 +- api/server/routes/ask/openAI.js | 1 - api/server/routes/assistants/actions.js | 4 +- api/server/routes/bedrock/chat.js | 1 - api/server/routes/edit/anthropic.js | 3 - api/server/routes/edit/custom.js | 2 - api/server/routes/edit/google.js | 3 - api/server/routes/edit/gptPlugins.js | 5 +- api/server/routes/edit/openAI.js | 2 - api/server/routes/search.js | 2 +- api/server/services/ActionService.js | 21 +- api/server/services/ActionService.spec.js | 33 +- .../services/Endpoints/agents/initialize.js | 10 +- api/server/services/Endpoints/agents/title.js | 78 ++- .../Endpoints/anthropic/initialize.js | 2 +- .../services/Endpoints/anthropic/title.js | 5 - .../services/Endpoints/custom/initialize.js | 2 +- .../services/Endpoints/openAI/initialize.js | 15 +- api/server/services/Endpoints/openAI/title.js | 5 - api/server/services/MCP.js | 12 +- api/server/services/ToolService.js | 10 +- api/server/socialLogins.js | 4 +- api/test/__mocks__/KeyvMongo.js | 30 +- api/utils/tokens.js | 5 + api/utils/tokens.spec.js | 54 +- client/src/hooks/SSE/useEventHandlers.ts | 12 + package-lock.json | 514 ++++++++++++------ packages/mcp/src/manager.ts | 5 +- 68 files changed, 1796 insertions(+), 623 deletions(-) create mode 100644 api/app/clients/generators.js create mode 100644 api/cache/ioredisClient.js delete mode 100644 api/cache/redis.js create mode 100644 api/server/cleanup.js diff --git a/api/app/clients/AnthropicClient.js b/api/app/clients/AnthropicClient.js index bc2e6042c7..ebd94ca9b1 100644 --- a/api/app/clients/AnthropicClient.js +++ b/api/app/clients/AnthropicClient.js @@ -9,7 +9,7 @@ const { getResponseSender, validateVisionModel, } = require('librechat-data-provider'); -const { SplitStreamHandler: _Handler, GraphEvents } = require('@librechat/agents'); +const { SplitStreamHandler: _Handler } = require('@librechat/agents'); const { truncateText, formatMessage, @@ -26,10 +26,11 @@ const { const { getModelMaxTokens, getModelMaxOutputTokens, matchModelName } = require('~/utils'); const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens'); const { encodeAndFormat } = require('~/server/services/Files/images/encode'); +const { createFetch, createStreamEventHandlers } = require('./generators'); const Tokenizer = require('~/server/services/Tokenizer'); -const { logger, sendEvent } = require('~/config'); const { sleep } = require('~/server/utils'); const BaseClient = require('./BaseClient'); +const { logger } = require('~/config'); const HUMAN_PROMPT = '\n\nHuman:'; const AI_PROMPT = '\n\nAssistant:'; @@ -184,7 +185,10 @@ class AnthropicClient extends BaseClient { getClient(requestOptions) { /** @type {Anthropic.ClientOptions} */ const options = { - fetch: this.fetch, + fetch: createFetch({ + directEndpoint: this.options.directEndpoint, + reverseProxyUrl: this.options.reverseProxyUrl, + }), apiKey: this.apiKey, }; @@ -795,14 +799,11 @@ class AnthropicClient extends BaseClient { } logger.debug('[AnthropicClient]', { ...requestOptions }); + const handlers = createStreamEventHandlers(this.options.res); this.streamHandler = new SplitStreamHandler({ accumulate: true, runId: this.responseMessageId, - handlers: { - [GraphEvents.ON_RUN_STEP]: (event) => sendEvent(this.options.res, event), - [GraphEvents.ON_MESSAGE_DELTA]: (event) => sendEvent(this.options.res, event), - [GraphEvents.ON_REASONING_DELTA]: (event) => sendEvent(this.options.res, event), - }, + handlers, }); let intermediateReply = this.streamHandler.tokens; diff --git a/api/app/clients/BaseClient.js b/api/app/clients/BaseClient.js index f89f1b3a8e..0c9a6e4d79 100644 --- a/api/app/clients/BaseClient.js +++ b/api/app/clients/BaseClient.js @@ -28,15 +28,10 @@ class BaseClient { month: 'long', day: 'numeric', }); - this.fetch = this.fetch.bind(this); /** @type {boolean} */ this.skipSaveConvo = false; /** @type {boolean} */ this.skipSaveUserMessage = false; - /** @type {ClientDatabaseSavePromise} */ - this.userMessagePromise; - /** @type {ClientDatabaseSavePromise} */ - this.responsePromise; /** @type {string} */ this.user; /** @type {string} */ @@ -564,6 +559,8 @@ class BaseClient { } async sendMessage(message, opts = {}) { + /** @type {Promise} */ + let userMessagePromise; const { user, head, isEdited, conversationId, responseMessageId, saveOptions, userMessage } = await this.handleStartMethods(message, opts); @@ -625,11 +622,11 @@ class BaseClient { } if (!isEdited && !this.skipSaveUserMessage) { - this.userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user); + userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user); this.savedMessageIds.add(userMessage.messageId); if (typeof opts?.getReqData === 'function') { opts.getReqData({ - userMessagePromise: this.userMessagePromise, + userMessagePromise, }); } } @@ -655,7 +652,9 @@ class BaseClient { /** @type {string|string[]|undefined} */ const completion = await this.sendCompletion(payload, opts); - this.abortController.requestCompleted = true; + if (this.abortController) { + this.abortController.requestCompleted = true; + } /** @type {TMessage} */ const responseMessage = { @@ -703,7 +702,13 @@ class BaseClient { if (usage != null && Number(usage[this.outputTokensKey]) > 0) { responseMessage.tokenCount = usage[this.outputTokensKey]; completionTokens = responseMessage.tokenCount; - await this.updateUserMessageTokenCount({ usage, tokenCountMap, userMessage, opts }); + await this.updateUserMessageTokenCount({ + usage, + tokenCountMap, + userMessage, + userMessagePromise, + opts, + }); } else { responseMessage.tokenCount = this.getTokenCountForResponse(responseMessage); completionTokens = responseMessage.tokenCount; @@ -712,8 +717,8 @@ class BaseClient { await this.recordTokenUsage({ promptTokens, completionTokens, usage }); } - if (this.userMessagePromise) { - await this.userMessagePromise; + if (userMessagePromise) { + await userMessagePromise; } if (this.artifactPromises) { @@ -728,7 +733,11 @@ class BaseClient { } } - this.responsePromise = this.saveMessageToDatabase(responseMessage, saveOptions, user); + responseMessage.databasePromise = this.saveMessageToDatabase( + responseMessage, + saveOptions, + user, + ); this.savedMessageIds.add(responseMessage.messageId); delete responseMessage.tokenCount; return responseMessage; @@ -749,9 +758,16 @@ class BaseClient { * @param {StreamUsage} params.usage * @param {Record} params.tokenCountMap * @param {TMessage} params.userMessage + * @param {Promise} params.userMessagePromise * @param {object} params.opts */ - async updateUserMessageTokenCount({ usage, tokenCountMap, userMessage, opts }) { + async updateUserMessageTokenCount({ + usage, + tokenCountMap, + userMessage, + userMessagePromise, + opts, + }) { /** @type {boolean} */ const shouldUpdateCount = this.calculateCurrentTokenCount != null && @@ -787,7 +803,7 @@ class BaseClient { Note: we update the user message to be sure it gets the calculated token count; though `AskController` saves the user message, EditController does not */ - await this.userMessagePromise; + await userMessagePromise; await this.updateMessageInDatabase({ messageId: userMessage.messageId, tokenCount: userMessageTokenCount, @@ -853,7 +869,7 @@ class BaseClient { } const savedMessage = await saveMessage( - this.options.req, + this.options?.req, { ...message, endpoint: this.options.endpoint, @@ -877,7 +893,7 @@ class BaseClient { const existingConvo = this.fetchedConvo === true ? null - : await getConvo(this.options.req?.user?.id, message.conversationId); + : await getConvo(this.options?.req?.user?.id, message.conversationId); const unsetFields = {}; const exceptions = new Set(['spec', 'iconURL']); @@ -897,7 +913,7 @@ class BaseClient { } } - const conversation = await saveConvo(this.options.req, fieldsToKeep, { + const conversation = await saveConvo(this.options?.req, fieldsToKeep, { context: 'api/app/clients/BaseClient.js - saveMessageToDatabase #saveConvo', unsetFields, }); diff --git a/api/app/clients/ChatGPTClient.js b/api/app/clients/ChatGPTClient.js index 5450300a17..07b2fa97bb 100644 --- a/api/app/clients/ChatGPTClient.js +++ b/api/app/clients/ChatGPTClient.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const crypto = require('crypto'); const { CohereClient } = require('cohere-ai'); const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source'); @@ -339,7 +339,7 @@ class ChatGPTClient extends BaseClient { opts.body = JSON.stringify(modelOptions); if (modelOptions.stream) { - // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { try { let done = false; diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index 179f5c986e..8c58d70f70 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -1,7 +1,7 @@ const OpenAI = require('openai'); const { OllamaClient } = require('./OllamaClient'); const { HttpsProxyAgent } = require('https-proxy-agent'); -const { SplitStreamHandler, GraphEvents } = require('@librechat/agents'); +const { SplitStreamHandler } = require('@librechat/agents'); const { Constants, ImageDetail, @@ -32,17 +32,18 @@ const { createContextHandlers, } = require('./prompts'); const { encodeAndFormat } = require('~/server/services/Files/images/encode'); +const { createFetch, createStreamEventHandlers } = require('./generators'); const { addSpaceIfNeeded, isEnabled, sleep } = require('~/server/utils'); const Tokenizer = require('~/server/services/Tokenizer'); const { spendTokens } = require('~/models/spendTokens'); const { handleOpenAIErrors } = require('./tools/util'); const { createLLM, RunManager } = require('./llm'); -const { logger, sendEvent } = require('~/config'); const ChatGPTClient = require('./ChatGPTClient'); const { summaryBuffer } = require('./memory'); const { runTitleChain } = require('./chains'); const { tokenSplit } = require('./document'); const BaseClient = require('./BaseClient'); +const { logger } = require('~/config'); class OpenAIClient extends BaseClient { constructor(apiKey, options = {}) { @@ -609,7 +610,7 @@ class OpenAIClient extends BaseClient { return result.trim(); } - logger.debug('[OpenAIClient] sendCompletion: result', result); + logger.debug('[OpenAIClient] sendCompletion: result', { ...result }); if (this.isChatCompletion) { reply = result.choices[0].message.content; @@ -818,7 +819,7 @@ ${convo} const completionTokens = this.getTokenCount(title); - this.recordTokenUsage({ promptTokens, completionTokens, context: 'title' }); + await this.recordTokenUsage({ promptTokens, completionTokens, context: 'title' }); } catch (e) { logger.error( '[OpenAIClient] There was an issue generating the title with the completion method', @@ -1245,7 +1246,10 @@ ${convo} let chatCompletion; /** @type {OpenAI} */ const openai = new OpenAI({ - fetch: this.fetch, + fetch: createFetch({ + directEndpoint: this.options.directEndpoint, + reverseProxyUrl: this.options.reverseProxyUrl, + }), apiKey: this.apiKey, ...opts, }); @@ -1275,12 +1279,13 @@ ${convo} } if (this.options.addParams && typeof this.options.addParams === 'object') { + const addParams = { ...this.options.addParams }; modelOptions = { ...modelOptions, - ...this.options.addParams, + ...addParams, }; logger.debug('[OpenAIClient] chatCompletion: added params', { - addParams: this.options.addParams, + addParams: addParams, modelOptions, }); } @@ -1309,11 +1314,12 @@ ${convo} } if (this.options.dropParams && Array.isArray(this.options.dropParams)) { - this.options.dropParams.forEach((param) => { + const dropParams = [...this.options.dropParams]; + dropParams.forEach((param) => { delete modelOptions[param]; }); logger.debug('[OpenAIClient] chatCompletion: dropped params', { - dropParams: this.options.dropParams, + dropParams: dropParams, modelOptions, }); } @@ -1355,15 +1361,12 @@ ${convo} delete modelOptions.reasoning_effort; } + const handlers = createStreamEventHandlers(this.options.res); this.streamHandler = new SplitStreamHandler({ reasoningKey, accumulate: true, runId: this.responseMessageId, - handlers: { - [GraphEvents.ON_RUN_STEP]: (event) => sendEvent(this.options.res, event), - [GraphEvents.ON_MESSAGE_DELTA]: (event) => sendEvent(this.options.res, event), - [GraphEvents.ON_REASONING_DELTA]: (event) => sendEvent(this.options.res, event), - }, + handlers, }); intermediateReply = this.streamHandler.tokens; diff --git a/api/app/clients/PluginsClient.js b/api/app/clients/PluginsClient.js index 60f8703e0f..d0ffe2ef75 100644 --- a/api/app/clients/PluginsClient.js +++ b/api/app/clients/PluginsClient.js @@ -252,12 +252,14 @@ class PluginsClient extends OpenAIClient { await this.recordTokenUsage(responseMessage); } - this.responsePromise = this.saveMessageToDatabase(responseMessage, saveOptions, user); + const databasePromise = this.saveMessageToDatabase(responseMessage, saveOptions, user); delete responseMessage.tokenCount; - return { ...responseMessage, ...result }; + return { ...responseMessage, ...result, databasePromise }; } async sendMessage(message, opts = {}) { + /** @type {Promise} */ + let userMessagePromise; /** @type {{ filteredTools: string[], includedTools: string[] }} */ const { filteredTools = [], includedTools = [] } = this.options.req.app.locals; @@ -327,10 +329,10 @@ class PluginsClient extends OpenAIClient { } if (!this.skipSaveUserMessage) { - this.userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user); + userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user); if (typeof opts?.getReqData === 'function') { opts.getReqData({ - userMessagePromise: this.userMessagePromise, + userMessagePromise, }); } } diff --git a/api/app/clients/generators.js b/api/app/clients/generators.js new file mode 100644 index 0000000000..4d3988bf34 --- /dev/null +++ b/api/app/clients/generators.js @@ -0,0 +1,60 @@ +const { GraphEvents } = require('@librechat/agents'); +const { logger, sendEvent } = require('~/config'); + +/** + * Makes a function to make HTTP request and logs the process. + * @param {Object} params + * @param {boolean} [params.directEndpoint] - Whether to use a direct endpoint. + * @param {string} [params.reverseProxyUrl] - The reverse proxy URL to use for the request. + * @returns {Promise} - A promise that resolves to the response of the fetch request. + */ +function createFetch({ directEndpoint = false, reverseProxyUrl = '' }) { + /** + * Makes an HTTP request and logs the process. + * @param {RequestInfo} url - The URL to make the request to. Can be a string or a Request object. + * @param {RequestInit} [init] - Optional init options for the request. + * @returns {Promise} - A promise that resolves to the response of the fetch request. + */ + return async (_url, init) => { + let url = _url; + if (directEndpoint) { + url = reverseProxyUrl; + } + logger.debug(`Making request to ${url}`); + if (typeof Bun !== 'undefined') { + return await fetch(url, init); + } + return await fetch(url, init); + }; +} + +// Add this at the module level outside the class +/** + * Creates event handlers for stream events that don't capture client references + * @param {Object} res - The response object to send events to + * @returns {Object} Object containing handler functions + */ +function createStreamEventHandlers(res) { + return { + [GraphEvents.ON_RUN_STEP]: (event) => { + if (res) { + sendEvent(res, event); + } + }, + [GraphEvents.ON_MESSAGE_DELTA]: (event) => { + if (res) { + sendEvent(res, event); + } + }, + [GraphEvents.ON_REASONING_DELTA]: (event) => { + if (res) { + sendEvent(res, event); + } + }, + }; +} + +module.exports = { + createFetch, + createStreamEventHandlers, +}; diff --git a/api/app/clients/tools/util/handleTools.js b/api/app/clients/tools/util/handleTools.js index 063d6e0327..8ce9d7bc74 100644 --- a/api/app/clients/tools/util/handleTools.js +++ b/api/app/clients/tools/util/handleTools.js @@ -123,7 +123,7 @@ const getAuthFields = (toolKey) => { * * @param {object} object * @param {string} object.user - * @param {Agent} [object.agent] + * @param {Pick} [object.agent] * @param {string} [object.model] * @param {EModelEndpoint} [object.endpoint] * @param {LoadToolOptions} [object.options] diff --git a/api/cache/getLogStores.js b/api/cache/getLogStores.js index 6d5ea15a7b..e652cfdee3 100644 --- a/api/cache/getLogStores.js +++ b/api/cache/getLogStores.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const { CacheKeys, ViolationTypes, Time } = require('librechat-data-provider'); const { logFile, violationFile } = require('./keyvFiles'); const { math, isEnabled } = require('~/server/utils'); diff --git a/api/cache/ioredisClient.js b/api/cache/ioredisClient.js new file mode 100644 index 0000000000..cd48459ab4 --- /dev/null +++ b/api/cache/ioredisClient.js @@ -0,0 +1,92 @@ +const fs = require('fs'); +const Redis = require('ioredis'); +const { isEnabled } = require('~/server/utils'); +const logger = require('~/config/winston'); + +const { REDIS_URI, USE_REDIS, USE_REDIS_CLUSTER, REDIS_CA, REDIS_MAX_LISTENERS } = process.env; + +/** @type {import('ioredis').Redis | import('ioredis').Cluster} */ +let ioredisClient; +const redis_max_listeners = Number(REDIS_MAX_LISTENERS) || 40; + +function mapURI(uri) { + const regex = + /^(?:(?\w+):\/\/)?(?:(?[^:@]+)(?::(?[^@]+))?@)?(?[\w.-]+)(?::(?\d{1,5}))?$/; + const match = uri.match(regex); + + if (match) { + const { scheme, user, password, host, port } = match.groups; + + return { + scheme: scheme || 'none', + user: user || null, + password: password || null, + host: host || null, + port: port || null, + }; + } else { + const parts = uri.split(':'); + if (parts.length === 2) { + return { + scheme: 'none', + user: null, + password: null, + host: parts[0], + port: parts[1], + }; + } + + return { + scheme: 'none', + user: null, + password: null, + host: uri, + port: null, + }; + } +} + +if (REDIS_URI && isEnabled(USE_REDIS)) { + let redisOptions = null; + + if (REDIS_CA) { + const ca = fs.readFileSync(REDIS_CA); + redisOptions = { tls: { ca } }; + } + + if (isEnabled(USE_REDIS_CLUSTER)) { + const hosts = REDIS_URI.split(',').map((item) => { + var value = mapURI(item); + + return { + host: value.host, + port: value.port, + }; + }); + ioredisClient = new Redis.Cluster(hosts, { redisOptions }); + } else { + ioredisClient = new Redis(REDIS_URI, redisOptions); + } + + ioredisClient.on('ready', () => { + logger.info('IoRedis connection ready'); + }); + ioredisClient.on('reconnecting', () => { + logger.info('IoRedis connection reconnecting'); + }); + ioredisClient.on('end', () => { + logger.info('IoRedis connection ended'); + }); + ioredisClient.on('close', () => { + logger.info('IoRedis connection closed'); + }); + ioredisClient.on('error', (err) => logger.error('IoRedis connection error:', err)); + ioredisClient.setMaxListeners(redis_max_listeners); + logger.info( + '[Optional] IoRedis initialized for rate limiters. If you have issues, disable Redis or restart the server.', + ); +} else { + logger.info('[Optional] IoRedis not initialized for rate limiters.'); +} + +module.exports = ioredisClient; diff --git a/api/cache/keyvFiles.js b/api/cache/keyvFiles.js index f969174b7d..1476b60cb8 100644 --- a/api/cache/keyvFiles.js +++ b/api/cache/keyvFiles.js @@ -1,11 +1,9 @@ const { KeyvFile } = require('keyv-file'); -const logFile = new KeyvFile({ filename: './data/logs.json' }); -const pendingReqFile = new KeyvFile({ filename: './data/pendingReqCache.json' }); -const violationFile = new KeyvFile({ filename: './data/violations.json' }); +const logFile = new KeyvFile({ filename: './data/logs.json' }).setMaxListeners(20); +const violationFile = new KeyvFile({ filename: './data/violations.json' }).setMaxListeners(20); module.exports = { logFile, - pendingReqFile, violationFile, }; diff --git a/api/cache/keyvMongo.js b/api/cache/keyvMongo.js index 8f5b9fd8d8..3321be56fe 100644 --- a/api/cache/keyvMongo.js +++ b/api/cache/keyvMongo.js @@ -1,4 +1,4 @@ -const KeyvMongo = require('@keyv/mongo'); +const { KeyvMongo } = require('@keyv/mongo'); const { logger } = require('~/config'); const { MONGO_URI } = process.env ?? {}; diff --git a/api/cache/keyvRedis.js b/api/cache/keyvRedis.js index 992e789ae3..0c830422e1 100644 --- a/api/cache/keyvRedis.js +++ b/api/cache/keyvRedis.js @@ -1,6 +1,6 @@ const fs = require('fs'); const ioredis = require('ioredis'); -const KeyvRedis = require('@keyv/redis'); +const KeyvRedis = require('@keyv/redis').default; const { isEnabled } = require('~/server/utils'); const logger = require('~/config/winston'); @@ -50,6 +50,7 @@ function mapURI(uri) { if (REDIS_URI && isEnabled(USE_REDIS)) { let redisOptions = null; + /** @type {import('@keyv/redis').KeyvRedisOptions} */ let keyvOpts = { useRedisSets: false, keyPrefix: redis_prefix, @@ -74,6 +75,18 @@ if (REDIS_URI && isEnabled(USE_REDIS)) { } else { keyvRedis = new KeyvRedis(REDIS_URI, keyvOpts); } + keyvRedis.on('ready', () => { + logger.info('KeyvRedis connection ready'); + }); + keyvRedis.on('reconnecting', () => { + logger.info('KeyvRedis connection reconnecting'); + }); + keyvRedis.on('end', () => { + logger.info('KeyvRedis connection ended'); + }); + keyvRedis.on('close', () => { + logger.info('KeyvRedis connection closed'); + }); keyvRedis.on('error', (err) => logger.error('KeyvRedis connection error:', err)); keyvRedis.setMaxListeners(redis_max_listeners); logger.info( diff --git a/api/cache/redis.js b/api/cache/redis.js deleted file mode 100644 index adf291d02b..0000000000 --- a/api/cache/redis.js +++ /dev/null @@ -1,4 +0,0 @@ -const Redis = require('ioredis'); -const { REDIS_URI } = process.env ?? {}; -const redis = new Redis.Cluster(REDIS_URI); -module.exports = redis; diff --git a/api/config/index.js b/api/config/index.js index 919919b55f..57bc45fe1a 100644 --- a/api/config/index.js +++ b/api/config/index.js @@ -6,15 +6,19 @@ const logger = require('./winston'); global.EventSource = EventSource; +/** @type {MCPManager} */ let mcpManager = null; let flowManager = null; /** + * @param {string} [userId] - Optional user ID, to avoid disconnecting the current user. * @returns {MCPManager} */ -function getMCPManager() { +function getMCPManager(userId) { if (!mcpManager) { mcpManager = MCPManager.getInstance(logger); + } else { + mcpManager.checkIdleConnections(userId); } return mcpManager; } diff --git a/api/models/tx.js b/api/models/tx.js index 41003e665c..5099e37f47 100644 --- a/api/models/tx.js +++ b/api/models/tx.js @@ -123,6 +123,10 @@ const tokenValues = Object.assign( 'grok-2-1212': { prompt: 2.0, completion: 10.0 }, 'grok-2-latest': { prompt: 2.0, completion: 10.0 }, 'grok-2': { prompt: 2.0, completion: 10.0 }, + 'grok-3-mini-fast': { prompt: 0.4, completion: 4 }, + 'grok-3-mini': { prompt: 0.3, completion: 0.5 }, + 'grok-3-fast': { prompt: 5.0, completion: 25.0 }, + 'grok-3': { prompt: 3.0, completion: 15.0 }, 'grok-beta': { prompt: 5.0, completion: 15.0 }, 'mistral-large': { prompt: 2.0, completion: 6.0 }, 'pixtral-large': { prompt: 2.0, completion: 6.0 }, diff --git a/api/models/tx.spec.js b/api/models/tx.spec.js index f612e222bb..f759e658f0 100644 --- a/api/models/tx.spec.js +++ b/api/models/tx.spec.js @@ -507,5 +507,27 @@ describe('Grok Model Tests - Pricing', () => { expect(getMultiplier({ model: 'grok-beta', tokenType: 'prompt' })).toBe(5.0); expect(getMultiplier({ model: 'grok-beta', tokenType: 'completion' })).toBe(15.0); }); + + test('should return correct prompt and completion rates for Grok 3 models', () => { + expect(getMultiplier({ model: 'grok-3', tokenType: 'prompt' })).toBe(3.0); + expect(getMultiplier({ model: 'grok-3', tokenType: 'completion' })).toBe(15.0); + expect(getMultiplier({ model: 'grok-3-fast', tokenType: 'prompt' })).toBe(5.0); + expect(getMultiplier({ model: 'grok-3-fast', tokenType: 'completion' })).toBe(25.0); + expect(getMultiplier({ model: 'grok-3-mini', tokenType: 'prompt' })).toBe(0.3); + expect(getMultiplier({ model: 'grok-3-mini', tokenType: 'completion' })).toBe(0.5); + expect(getMultiplier({ model: 'grok-3-mini-fast', tokenType: 'prompt' })).toBe(0.4); + expect(getMultiplier({ model: 'grok-3-mini-fast', tokenType: 'completion' })).toBe(4.0); + }); + + test('should return correct prompt and completion rates for Grok 3 models with prefixes', () => { + expect(getMultiplier({ model: 'xai/grok-3', tokenType: 'prompt' })).toBe(3.0); + expect(getMultiplier({ model: 'xai/grok-3', tokenType: 'completion' })).toBe(15.0); + expect(getMultiplier({ model: 'xai/grok-3-fast', tokenType: 'prompt' })).toBe(5.0); + expect(getMultiplier({ model: 'xai/grok-3-fast', tokenType: 'completion' })).toBe(25.0); + expect(getMultiplier({ model: 'xai/grok-3-mini', tokenType: 'prompt' })).toBe(0.3); + expect(getMultiplier({ model: 'xai/grok-3-mini', tokenType: 'completion' })).toBe(0.5); + expect(getMultiplier({ model: 'xai/grok-3-mini-fast', tokenType: 'prompt' })).toBe(0.4); + expect(getMultiplier({ model: 'xai/grok-3-mini-fast', tokenType: 'completion' })).toBe(4.0); + }); }); }); diff --git a/api/package.json b/api/package.json index 19bad78cf8..73b415ad79 100644 --- a/api/package.json +++ b/api/package.json @@ -42,14 +42,14 @@ "@azure/storage-blob": "^12.26.0", "@google/generative-ai": "^0.23.0", "@googleapis/youtube": "^20.0.0", - "@keyv/mongo": "^2.1.8", - "@keyv/redis": "^2.8.1", + "@keyv/mongo": "^3.0.1", + "@keyv/redis": "^4.3.3", "@langchain/community": "^0.3.39", "@langchain/core": "^0.3.43", "@langchain/google-genai": "^0.2.2", "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.12", + "@librechat/agents": "^2.4.14", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", @@ -76,8 +76,8 @@ "ioredis": "^5.3.2", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", - "keyv": "^4.5.4", - "keyv-file": "^0.2.0", + "keyv": "^5.3.2", + "keyv-file": "^5.1.2", "klona": "^2.0.6", "librechat-data-provider": "*", "librechat-mcp": "*", diff --git a/api/server/cleanup.js b/api/server/cleanup.js new file mode 100644 index 0000000000..508d2e8129 --- /dev/null +++ b/api/server/cleanup.js @@ -0,0 +1,240 @@ +const { logger } = require('~/config'); + +// WeakMap to hold temporary data associated with requests +const requestDataMap = new WeakMap(); + +const FinalizationRegistry = global.FinalizationRegistry || null; + +/** + * FinalizationRegistry to clean up client objects when they are garbage collected. + * This is used to prevent memory leaks and ensure that client objects are + * properly disposed of when they are no longer needed. + * The registry holds a weak reference to the client object and a cleanup + * callback that is called when the client object is garbage collected. + * The callback can be used to perform any necessary cleanup operations, + * such as removing event listeners or freeing up resources. + */ +const clientRegistry = FinalizationRegistry + ? new FinalizationRegistry((heldValue) => { + try { + // This will run when the client is garbage collected + if (heldValue && heldValue.userId) { + logger.debug(`[FinalizationRegistry] Cleaning up client for user ${heldValue.userId}`); + } else { + logger.debug('[FinalizationRegistry] Cleaning up client'); + } + } catch (e) { + // Ignore errors + } + }) + : null; + +/** + * Cleans up the client object by removing references to its properties. + * This is useful for preventing memory leaks and ensuring that the client + * and its properties can be garbage collected when it is no longer needed. + */ +function disposeClient(client) { + if (!client) { + return; + } + + try { + if (client.user) { + client.user = null; + } + if (client.apiKey) { + client.apiKey = null; + } + if (client.azure) { + client.azure = null; + } + if (client.conversationId) { + client.conversationId = null; + } + if (client.responseMessageId) { + client.responseMessageId = null; + } + if (client.clientName) { + client.clientName = null; + } + if (client.sender) { + client.sender = null; + } + if (client.model) { + client.model = null; + } + if (client.maxContextTokens) { + client.maxContextTokens = null; + } + if (client.contextStrategy) { + client.contextStrategy = null; + } + if (client.currentDateString) { + client.currentDateString = null; + } + if (client.inputTokensKey) { + client.inputTokensKey = null; + } + if (client.outputTokensKey) { + client.outputTokensKey = null; + } + if (client.run) { + // Break circular references in run + if (client.run.Graph) { + client.run.Graph.resetValues(); + client.run.Graph.handlerRegistry = null; + client.run.Graph.runId = null; + client.run.Graph.tools = null; + client.run.Graph.signal = null; + client.run.Graph.config = null; + client.run.Graph.toolEnd = null; + client.run.Graph.toolMap = null; + client.run.Graph.provider = null; + client.run.Graph.streamBuffer = null; + client.run.Graph.clientOptions = null; + client.run.Graph.graphState = null; + client.run.Graph.boundModel = null; + client.run.Graph.systemMessage = null; + client.run.Graph.reasoningKey = null; + client.run.Graph.messages = null; + client.run.Graph.contentData = null; + client.run.Graph.stepKeyIds = null; + client.run.Graph.contentIndexMap = null; + client.run.Graph.toolCallStepIds = null; + client.run.Graph.messageIdsByStepKey = null; + client.run.Graph.messageStepHasToolCalls = null; + client.run.Graph.prelimMessageIdsByStepKey = null; + client.run.Graph.currentTokenType = null; + client.run.Graph.lastToken = null; + client.run.Graph.tokenTypeSwitch = null; + client.run.Graph.indexTokenCountMap = null; + client.run.Graph.currentUsage = null; + client.run.Graph.tokenCounter = null; + client.run.Graph.maxContextTokens = null; + client.run.Graph.pruneMessages = null; + client.run.Graph.lastStreamCall = null; + client.run.Graph.startIndex = null; + client.run.Graph = null; + } + if (client.run.handlerRegistry) { + client.run.handlerRegistry = null; + } + if (client.run.graphRunnable) { + if (client.run.graphRunnable.channels) { + client.run.graphRunnable.channels = null; + } + if (client.run.graphRunnable.nodes) { + client.run.graphRunnable.nodes = null; + } + if (client.run.graphRunnable.lc_kwargs) { + client.run.graphRunnable.lc_kwargs = null; + } + if (client.run.graphRunnable.builder?.nodes) { + client.run.graphRunnable.builder.nodes = null; + client.run.graphRunnable.builder = null; + } + client.run.graphRunnable = null; + } + client.run = null; + } + if (client.sendMessage) { + client.sendMessage = null; + } + if (client.savedMessageIds) { + client.savedMessageIds.clear(); + client.savedMessageIds = null; + } + if (client.currentMessages) { + client.currentMessages = null; + } + if (client.streamHandler) { + client.streamHandler = null; + } + if (client.contentParts) { + client.contentParts = null; + } + if (client.abortController) { + client.abortController = null; + } + if (client.collectedUsage) { + client.collectedUsage = null; + } + if (client.indexTokenCountMap) { + client.indexTokenCountMap = null; + } + if (client.agentConfigs) { + client.agentConfigs = null; + } + if (client.artifactPromises) { + client.artifactPromises = null; + } + if (client.usage) { + client.usage = null; + } + if (typeof client.dispose === 'function') { + client.dispose(); + } + if (client.options) { + if (client.options.req) { + client.options.req = null; + } + if (client.options.res) { + client.options.res = null; + } + if (client.options.attachments) { + client.options.attachments = null; + } + if (client.options.agent) { + client.options.agent = null; + } + } + client.options = null; + } catch (e) { + // Ignore errors during disposal + } +} + +function processReqData(data = {}, context) { + let { + abortKey, + userMessage, + userMessagePromise, + responseMessageId, + promptTokens, + conversationId, + userMessageId, + } = context; + for (const key in data) { + if (key === 'userMessage') { + userMessage = data[key]; + userMessageId = data[key].messageId; + } else if (key === 'userMessagePromise') { + userMessagePromise = data[key]; + } else if (key === 'responseMessageId') { + responseMessageId = data[key]; + } else if (key === 'promptTokens') { + promptTokens = data[key]; + } else if (key === 'abortKey') { + abortKey = data[key]; + } else if (!conversationId && key === 'conversationId') { + conversationId = data[key]; + } + } + return { + abortKey, + userMessage, + userMessagePromise, + responseMessageId, + promptTokens, + conversationId, + userMessageId, + }; +} + +module.exports = { + disposeClient, + requestDataMap, + clientRegistry, + processReqData, +}; diff --git a/api/server/controllers/AskController.js b/api/server/controllers/AskController.js index 2df6f34ede..8904a57220 100644 --- a/api/server/controllers/AskController.js +++ b/api/server/controllers/AskController.js @@ -1,5 +1,15 @@ const { getResponseSender, Constants } = require('librechat-data-provider'); -const { createAbortController, handleAbortError } = require('~/server/middleware'); +const { + handleAbortError, + createAbortController, + cleanupAbortController, +} = require('~/server/middleware'); +const { + disposeClient, + processReqData, + clientRegistry, + requestDataMap, +} = require('~/server/cleanup'); const { sendMessage, createOnProgress } = require('~/server/utils'); const { saveMessage } = require('~/models'); const { logger } = require('~/config'); @@ -14,90 +24,162 @@ const AskController = async (req, res, next, initializeClient, addTitle) => { overrideParentMessageId = null, } = req.body; + let client = null; + let abortKey = null; + let cleanupHandlers = []; + let clientRef = null; + logger.debug('[AskController]', { text, conversationId, ...endpointOption, - modelsConfig: endpointOption.modelsConfig ? 'exists' : '', + modelsConfig: endpointOption?.modelsConfig ? 'exists' : '', }); - let userMessage; - let userMessagePromise; - let promptTokens; - let userMessageId; - let responseMessageId; + let userMessage = null; + let userMessagePromise = null; + let promptTokens = null; + let userMessageId = null; + let responseMessageId = null; + let getAbortData = null; + const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model, modelDisplayLabel, }); - const newConvo = !conversationId; - const user = req.user.id; + const initialConversationId = conversationId; + const newConvo = !initialConversationId; + const userId = req.user.id; - const getReqData = (data = {}) => { - for (let key in data) { - if (key === 'userMessage') { - userMessage = data[key]; - userMessageId = data[key].messageId; - } else if (key === 'userMessagePromise') { - userMessagePromise = data[key]; - } else if (key === 'responseMessageId') { - responseMessageId = data[key]; - } else if (key === 'promptTokens') { - promptTokens = data[key]; - } else if (!conversationId && key === 'conversationId') { - conversationId = data[key]; - } - } + let reqDataContext = { + userMessage, + userMessagePromise, + responseMessageId, + promptTokens, + conversationId, + userMessageId, }; - let getText; + const updateReqData = (data = {}) => { + reqDataContext = processReqData(data, reqDataContext); + abortKey = reqDataContext.abortKey; + userMessage = reqDataContext.userMessage; + userMessagePromise = reqDataContext.userMessagePromise; + responseMessageId = reqDataContext.responseMessageId; + promptTokens = reqDataContext.promptTokens; + conversationId = reqDataContext.conversationId; + userMessageId = reqDataContext.userMessageId; + }; + + let { onProgress: progressCallback, getPartialText } = createOnProgress(); + + const performCleanup = () => { + logger.debug('[AskController] Performing cleanup'); + if (Array.isArray(cleanupHandlers)) { + for (const handler of cleanupHandlers) { + try { + if (typeof handler === 'function') { + handler(); + } + } catch (e) { + // Ignore + } + } + } + + if (abortKey) { + logger.debug('[AskController] Cleaning up abort controller'); + cleanupAbortController(abortKey); + abortKey = null; + } + + if (client) { + disposeClient(client); + client = null; + } + + reqDataContext = null; + userMessage = null; + userMessagePromise = null; + promptTokens = null; + getAbortData = null; + progressCallback = null; + endpointOption = null; + cleanupHandlers = null; + addTitle = null; + + if (requestDataMap.has(req)) { + requestDataMap.delete(req); + } + logger.debug('[AskController] Cleanup completed'); + }; try { - const { client } = await initializeClient({ req, res, endpointOption }); - const { onProgress: progressCallback, getPartialText } = createOnProgress(); + ({ client } = await initializeClient({ req, res, endpointOption })); + if (clientRegistry && client) { + clientRegistry.register(client, { userId }, client); + } - getText = client.getStreamText != null ? client.getStreamText.bind(client) : getPartialText; + if (client) { + requestDataMap.set(req, { client }); + } - const getAbortData = () => ({ - sender, - conversationId, - userMessagePromise, - messageId: responseMessageId, - parentMessageId: overrideParentMessageId ?? userMessageId, - text: getText(), - userMessage, - promptTokens, - }); + clientRef = new WeakRef(client); - const { abortController, onStart } = createAbortController(req, res, getAbortData, getReqData); + getAbortData = () => { + const currentClient = clientRef.deref(); + const currentText = + currentClient?.getStreamText != null ? currentClient.getStreamText() : getPartialText(); - res.on('close', () => { + return { + sender, + conversationId, + messageId: reqDataContext.responseMessageId, + parentMessageId: overrideParentMessageId ?? userMessageId, + text: currentText, + userMessage: userMessage, + userMessagePromise: userMessagePromise, + promptTokens: reqDataContext.promptTokens, + }; + }; + + const { onStart, abortController } = createAbortController( + req, + res, + getAbortData, + updateReqData, + ); + + const closeHandler = () => { logger.debug('[AskController] Request closed'); - if (!abortController) { - return; - } else if (abortController.signal.aborted) { - return; - } else if (abortController.requestCompleted) { + if (!abortController || abortController.signal.aborted || abortController.requestCompleted) { return; } - abortController.abort(); logger.debug('[AskController] Request aborted on close'); + }; + + res.on('close', closeHandler); + cleanupHandlers.push(() => { + try { + res.removeListener('close', closeHandler); + } catch (e) { + // Ignore + } }); const messageOptions = { - user, + user: userId, parentMessageId, - conversationId, + conversationId: reqDataContext.conversationId, overrideParentMessageId, - getReqData, + getReqData: updateReqData, onStart, abortController, progressCallback, progressOptions: { res, - // parentMessageId: overrideParentMessageId || userMessageId, }, }; @@ -105,59 +187,94 @@ const AskController = async (req, res, next, initializeClient, addTitle) => { let response = await client.sendMessage(text, messageOptions); response.endpoint = endpointOption.endpoint; - const { conversation = {} } = await client.responsePromise; + const databasePromise = response.databasePromise; + delete response.databasePromise; + + const { conversation: convoData = {} } = await databasePromise; + const conversation = { ...convoData }; conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; - if (client.options.attachments) { - userMessage.files = client.options.attachments; - conversation.model = endpointOption.modelOptions.model; - delete userMessage.image_urls; + const latestUserMessage = reqDataContext.userMessage; + + if (client?.options?.attachments && latestUserMessage) { + latestUserMessage.files = client.options.attachments; + if (endpointOption?.modelOptions?.model) { + conversation.model = endpointOption.modelOptions.model; + } + delete latestUserMessage.image_urls; } if (!abortController.signal.aborted) { + const finalResponseMessage = { ...response }; + sendMessage(res, { final: true, conversation, title: conversation.title, - requestMessage: userMessage, - responseMessage: response, + requestMessage: latestUserMessage, + responseMessage: finalResponseMessage, }); res.end(); - if (!client.savedMessageIds.has(response.messageId)) { + if (client?.savedMessageIds && !client.savedMessageIds.has(response.messageId)) { await saveMessage( req, - { ...response, user }, + { ...finalResponseMessage, user: userId }, { context: 'api/server/controllers/AskController.js - response end' }, ); } } - if (!client.skipSaveUserMessage) { - await saveMessage(req, userMessage, { + if (!client?.skipSaveUserMessage && latestUserMessage) { + await saveMessage(req, latestUserMessage, { context: 'api/server/controllers/AskController.js - don\'t skip saving user message', }); } - if (addTitle && parentMessageId === Constants.NO_PARENT && newConvo) { + if (typeof addTitle === 'function' && parentMessageId === Constants.NO_PARENT && newConvo) { addTitle(req, { text, - response, + response: { ...response }, client, - }); + }) + .then(() => { + logger.debug('[AskController] Title generation started'); + }) + .catch((err) => { + logger.error('[AskController] Error in title generation', err); + }) + .finally(() => { + logger.debug('[AskController] Title generation completed'); + performCleanup(); + }); + } else { + performCleanup(); } } catch (error) { - const partialText = getText && getText(); + logger.error('[AskController] Error handling request', error); + let partialText = ''; + try { + const currentClient = clientRef.deref(); + partialText = + currentClient?.getStreamText != null ? currentClient.getStreamText() : getPartialText(); + } catch (getTextError) { + logger.error('[AskController] Error calling getText() during error handling', getTextError); + } + handleAbortError(res, req, error, { sender, partialText, - conversationId, - messageId: responseMessageId, - parentMessageId: overrideParentMessageId ?? userMessageId ?? parentMessageId, - }).catch((err) => { - logger.error('[AskController] Error in `handleAbortError`', err); - }); + conversationId: reqDataContext.conversationId, + messageId: reqDataContext.responseMessageId, + parentMessageId: overrideParentMessageId ?? reqDataContext.userMessageId ?? parentMessageId, + }) + .catch((err) => { + logger.error('[AskController] Error in `handleAbortError` during catch block', err); + }) + .finally(() => { + performCleanup(); + }); } }; diff --git a/api/server/controllers/EditController.js b/api/server/controllers/EditController.js index 1de9725722..e7ebc0785c 100644 --- a/api/server/controllers/EditController.js +++ b/api/server/controllers/EditController.js @@ -1,5 +1,15 @@ const { getResponseSender } = require('librechat-data-provider'); -const { createAbortController, handleAbortError } = require('~/server/middleware'); +const { + handleAbortError, + createAbortController, + cleanupAbortController, +} = require('~/server/middleware'); +const { + disposeClient, + processReqData, + clientRegistry, + requestDataMap, +} = require('~/server/cleanup'); const { sendMessage, createOnProgress } = require('~/server/utils'); const { saveMessage } = require('~/models'); const { logger } = require('~/config'); @@ -17,6 +27,11 @@ const EditController = async (req, res, next, initializeClient) => { overrideParentMessageId = null, } = req.body; + let client = null; + let abortKey = null; + let cleanupHandlers = []; + let clientRef = null; // Declare clientRef here + logger.debug('[EditController]', { text, generation, @@ -26,123 +41,204 @@ const EditController = async (req, res, next, initializeClient) => { modelsConfig: endpointOption.modelsConfig ? 'exists' : '', }); - let userMessage; - let userMessagePromise; - let promptTokens; + let userMessage = null; + let userMessagePromise = null; + let promptTokens = null; + let getAbortData = null; + const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model, modelDisplayLabel, }); const userMessageId = parentMessageId; - const user = req.user.id; + const userId = req.user.id; - const getReqData = (data = {}) => { - for (let key in data) { - if (key === 'userMessage') { - userMessage = data[key]; - } else if (key === 'userMessagePromise') { - userMessagePromise = data[key]; - } else if (key === 'responseMessageId') { - responseMessageId = data[key]; - } else if (key === 'promptTokens') { - promptTokens = data[key]; - } - } + let reqDataContext = { userMessage, userMessagePromise, responseMessageId, promptTokens }; + + const updateReqData = (data = {}) => { + reqDataContext = processReqData(data, reqDataContext); + abortKey = reqDataContext.abortKey; + userMessage = reqDataContext.userMessage; + userMessagePromise = reqDataContext.userMessagePromise; + responseMessageId = reqDataContext.responseMessageId; + promptTokens = reqDataContext.promptTokens; }; - const { onProgress: progressCallback, getPartialText } = createOnProgress({ + let { onProgress: progressCallback, getPartialText } = createOnProgress({ generation, }); - let getText; + const performCleanup = () => { + logger.debug('[EditController] Performing cleanup'); + if (Array.isArray(cleanupHandlers)) { + for (const handler of cleanupHandlers) { + try { + if (typeof handler === 'function') { + handler(); + } + } catch (e) { + // Ignore + } + } + } + + if (abortKey) { + logger.debug('[AskController] Cleaning up abort controller'); + cleanupAbortController(abortKey); + abortKey = null; + } + + if (client) { + disposeClient(client); + client = null; + } + + reqDataContext = null; + userMessage = null; + userMessagePromise = null; + promptTokens = null; + getAbortData = null; + progressCallback = null; + endpointOption = null; + cleanupHandlers = null; + + if (requestDataMap.has(req)) { + requestDataMap.delete(req); + } + logger.debug('[EditController] Cleanup completed'); + }; try { - const { client } = await initializeClient({ req, res, endpointOption }); + ({ client } = await initializeClient({ req, res, endpointOption })); - getText = client.getStreamText != null ? client.getStreamText.bind(client) : getPartialText; + if (clientRegistry && client) { + clientRegistry.register(client, { userId }, client); + } - const getAbortData = () => ({ - conversationId, - userMessagePromise, - messageId: responseMessageId, - sender, - parentMessageId: overrideParentMessageId ?? userMessageId, - text: getText(), - userMessage, - promptTokens, - }); + if (client) { + requestDataMap.set(req, { client }); + } - const { abortController, onStart } = createAbortController(req, res, getAbortData, getReqData); + clientRef = new WeakRef(client); - res.on('close', () => { + getAbortData = () => { + const currentClient = clientRef.deref(); + const currentText = + currentClient?.getStreamText != null ? currentClient.getStreamText() : getPartialText(); + + return { + sender, + conversationId, + messageId: reqDataContext.responseMessageId, + parentMessageId: overrideParentMessageId ?? userMessageId, + text: currentText, + userMessage: userMessage, + userMessagePromise: userMessagePromise, + promptTokens: reqDataContext.promptTokens, + }; + }; + + const { onStart, abortController } = createAbortController( + req, + res, + getAbortData, + updateReqData, + ); + + const closeHandler = () => { logger.debug('[EditController] Request closed'); - if (!abortController) { - return; - } else if (abortController.signal.aborted) { - return; - } else if (abortController.requestCompleted) { + if (!abortController || abortController.signal.aborted || abortController.requestCompleted) { return; } - abortController.abort(); logger.debug('[EditController] Request aborted on close'); + }; + + res.on('close', closeHandler); + cleanupHandlers.push(() => { + try { + res.removeListener('close', closeHandler); + } catch (e) { + // Ignore + } }); let response = await client.sendMessage(text, { - user, + user: userId, generation, isContinued, isEdited: true, conversationId, parentMessageId, - responseMessageId, + responseMessageId: reqDataContext.responseMessageId, overrideParentMessageId, - getReqData, + getReqData: updateReqData, onStart, abortController, progressCallback, progressOptions: { res, - // parentMessageId: overrideParentMessageId || userMessageId, }, }); - const { conversation = {} } = await client.responsePromise; + const databasePromise = response.databasePromise; + delete response.databasePromise; + + const { conversation: convoData = {} } = await databasePromise; + const conversation = { ...convoData }; conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; - if (client.options.attachments) { + if (client?.options?.attachments && endpointOption?.modelOptions?.model) { conversation.model = endpointOption.modelOptions.model; } if (!abortController.signal.aborted) { + const finalUserMessage = reqDataContext.userMessage; + const finalResponseMessage = { ...response }; + sendMessage(res, { final: true, conversation, title: conversation.title, - requestMessage: userMessage, - responseMessage: response, + requestMessage: finalUserMessage, + responseMessage: finalResponseMessage, }); res.end(); await saveMessage( req, - { ...response, user }, + { ...finalResponseMessage, user: userId }, { context: 'api/server/controllers/EditController.js - response end' }, ); } + + performCleanup(); } catch (error) { - const partialText = getText(); + logger.error('[EditController] Error handling request', error); + let partialText = ''; + try { + const currentClient = clientRef.deref(); + partialText = + currentClient?.getStreamText != null ? currentClient.getStreamText() : getPartialText(); + } catch (getTextError) { + logger.error('[EditController] Error calling getText() during error handling', getTextError); + } + handleAbortError(res, req, error, { sender, partialText, conversationId, - messageId: responseMessageId, + messageId: reqDataContext.responseMessageId, parentMessageId: overrideParentMessageId ?? userMessageId ?? parentMessageId, - }).catch((err) => { - logger.error('[EditController] Error in `handleAbortError`', err); - }); + }) + .catch((err) => { + logger.error('[EditController] Error in `handleAbortError` during catch block', err); + }) + .finally(() => { + performCleanup(); + }); } }; diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index ff98d80a13..a0de98c5f1 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -63,6 +63,21 @@ const noSystemModelRegex = [/\bo1\b/gi]; // const { getFormattedMemories } = require('~/models/Memory'); // const { getCurrentDateTime } = require('~/utils'); +function createTokenCounter(encoding) { + return (message) => { + const countTokens = (text) => Tokenizer.getTokenCount(text, encoding); + return getTokenCountForMessage(message, countTokens); + }; +} + +function logToolError(graph, error, toolId) { + logger.error( + '[api/server/controllers/agents/client.js #chatCompletion] Tool Error', + error, + toolId, + ); +} + class AgentClient extends BaseClient { constructor(options = {}) { super(null, options); @@ -535,6 +550,10 @@ class AgentClient extends BaseClient { } async chatCompletion({ payload, abortController = null }) { + /** @type {Partial & { version: 'v1' | 'v2'; run_id?: string; streamMode: string }} */ + let config; + /** @type {ReturnType} */ + let run; try { if (!abortController) { abortController = new AbortController(); @@ -632,11 +651,11 @@ class AgentClient extends BaseClient { /** @type {TCustomConfig['endpoints']['agents']} */ const agentsEConfig = this.options.req.app.locals[EModelEndpoint.agents]; - /** @type {Partial & { version: 'v1' | 'v2'; run_id?: string; streamMode: string }} */ - const config = { + config = { configurable: { thread_id: this.conversationId, last_agent_index: this.agentConfigs?.size ?? 0, + user_id: this.user ?? this.options.req.user?.id, hide_sequential_outputs: this.options.agent.hide_sequential_outputs, }, recursionLimit: agentsEConfig?.recursionLimit, @@ -655,15 +674,6 @@ class AgentClient extends BaseClient { initialMessages = formatContentStrings(initialMessages); } - /** @type {ReturnType} */ - let run; - const countTokens = ((text) => this.getTokenCount(text)).bind(this); - - /** @type {(message: BaseMessage) => number} */ - const tokenCounter = (message) => { - return getTokenCountForMessage(message, countTokens); - }; - /** * * @param {Agent} agent @@ -767,19 +777,14 @@ class AgentClient extends BaseClient { run.Graph.contentData = contentData; } + const encoding = this.getEncoding(); await run.processStream({ messages }, config, { keepContent: i !== 0, - tokenCounter, + tokenCounter: createTokenCounter(encoding), indexTokenCountMap: currentIndexCountMap, maxContextTokens: agent.maxContextTokens, callbacks: { - [Callback.TOOL_ERROR]: (graph, error, toolId) => { - logger.error( - '[api/server/controllers/agents/client.js #chatCompletion] Tool Error', - error, - toolId, - ); - }, + [Callback.TOOL_ERROR]: logToolError, }, }); }; @@ -809,6 +814,8 @@ class AgentClient extends BaseClient { break; } } + const encoding = this.getEncoding(); + const tokenCounter = createTokenCounter(encoding); for (const [agentId, agent] of this.agentConfigs) { if (abortController.signal.aborted === true) { break; @@ -917,7 +924,7 @@ class AgentClient extends BaseClient { * @param {string} params.text * @param {string} params.conversationId */ - async titleConvo({ text }) { + async titleConvo({ text, abortController }) { if (!this.run) { throw new Error('Run not initialized'); } @@ -950,6 +957,7 @@ class AgentClient extends BaseClient { contentParts: this.contentParts, clientOptions, chainOptions: { + signal: abortController.signal, callbacks: [ { handleLLMEnd, @@ -975,7 +983,7 @@ class AgentClient extends BaseClient { }; }); - this.recordCollectedUsage({ + await this.recordCollectedUsage({ model: clientOptions.model, context: 'title', collectedUsage, diff --git a/api/server/controllers/agents/request.js b/api/server/controllers/agents/request.js index 91277d5bc4..80b70a7450 100644 --- a/api/server/controllers/agents/request.js +++ b/api/server/controllers/agents/request.js @@ -1,5 +1,10 @@ const { Constants } = require('librechat-data-provider'); -const { createAbortController, handleAbortError } = require('~/server/middleware'); +const { + handleAbortError, + createAbortController, + cleanupAbortController, +} = require('~/server/middleware'); +const { disposeClient, clientRegistry, requestDataMap } = require('~/server/cleanup'); const { sendMessage } = require('~/server/utils'); const { saveMessage } = require('~/models'); const { logger } = require('~/config'); @@ -14,16 +19,22 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => { } = req.body; let sender; + let abortKey; let userMessage; let promptTokens; let userMessageId; let responseMessageId; let userMessagePromise; + let getAbortData; + let client = null; + // Initialize as an array + let cleanupHandlers = []; const newConvo = !conversationId; - const user = req.user.id; + const userId = req.user.id; - const getReqData = (data = {}) => { + // Create handler to avoid capturing the entire parent scope + let getReqData = (data = {}) => { for (let key in data) { if (key === 'userMessage') { userMessage = data[key]; @@ -36,30 +47,96 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => { promptTokens = data[key]; } else if (key === 'sender') { sender = data[key]; + } else if (key === 'abortKey') { + abortKey = data[key]; } else if (!conversationId && key === 'conversationId') { conversationId = data[key]; } } }; + // Create a function to handle final cleanup + const performCleanup = () => { + logger.debug('[AgentController] Performing cleanup'); + // Make sure cleanupHandlers is an array before iterating + if (Array.isArray(cleanupHandlers)) { + // Execute all cleanup handlers + for (const handler of cleanupHandlers) { + try { + if (typeof handler === 'function') { + handler(); + } + } catch (e) { + // Ignore cleanup errors + } + } + } + + // Clean up abort controller + if (abortKey) { + logger.debug('[AgentController] Cleaning up abort controller'); + cleanupAbortController(abortKey); + } + + // Dispose client properly + if (client) { + disposeClient(client); + } + + // Clear all references + client = null; + getReqData = null; + userMessage = null; + getAbortData = null; + endpointOption.agent = null; + endpointOption = null; + cleanupHandlers = null; + userMessagePromise = null; + + // Clear request data map + if (requestDataMap.has(req)) { + requestDataMap.delete(req); + } + logger.debug('[AgentController] Cleanup completed'); + }; + try { /** @type {{ client: TAgentClient }} */ - const { client } = await initializeClient({ req, res, endpointOption }); + const result = await initializeClient({ req, res, endpointOption }); + client = result.client; - const getAbortData = () => ({ - sender, - userMessage, - promptTokens, - conversationId, - userMessagePromise, - messageId: responseMessageId, - content: client.getContentParts(), - parentMessageId: overrideParentMessageId ?? userMessageId, - }); + // Register client with finalization registry if available + if (clientRegistry) { + clientRegistry.register(client, { userId }, client); + } + + // Store request data in WeakMap keyed by req object + requestDataMap.set(req, { client }); + + // Use WeakRef to allow GC but still access content if it exists + const contentRef = new WeakRef(client.contentParts || []); + + // Minimize closure scope - only capture small primitives and WeakRef + getAbortData = () => { + // Dereference WeakRef each time + const content = contentRef.deref(); + + return { + sender, + content: content || [], + userMessage, + promptTokens, + conversationId, + userMessagePromise, + messageId: responseMessageId, + parentMessageId: overrideParentMessageId ?? userMessageId, + }; + }; const { abortController, onStart } = createAbortController(req, res, getAbortData, getReqData); - res.on('close', () => { + // Simple handler to avoid capturing scope + const closeHandler = () => { logger.debug('[AgentController] Request closed'); if (!abortController) { return; @@ -71,10 +148,19 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => { abortController.abort(); logger.debug('[AgentController] Request aborted on close'); + }; + + res.on('close', closeHandler); + cleanupHandlers.push(() => { + try { + res.removeListener('close', closeHandler); + } catch (e) { + // Ignore + } }); const messageOptions = { - user, + user: userId, onStart, getReqData, conversationId, @@ -83,69 +169,103 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => { overrideParentMessageId, progressOptions: { res, - // parentMessageId: overrideParentMessageId || userMessageId, }, }; let response = await client.sendMessage(text, messageOptions); - response.endpoint = endpointOption.endpoint; - const { conversation = {} } = await client.responsePromise; + // Extract what we need and immediately break reference + const messageId = response.messageId; + const endpoint = endpointOption.endpoint; + response.endpoint = endpoint; + + // Store database promise locally + const databasePromise = response.databasePromise; + delete response.databasePromise; + + // Resolve database-related data + const { conversation: convoData = {} } = await databasePromise; + const conversation = { ...convoData }; conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; - if (req.body.files && client.options.attachments) { + // Process files if needed + if (req.body.files && client.options?.attachments) { userMessage.files = []; const messageFiles = new Set(req.body.files.map((file) => file.file_id)); for (let attachment of client.options.attachments) { if (messageFiles.has(attachment.file_id)) { - userMessage.files.push(attachment); + userMessage.files.push({ ...attachment }); } } delete userMessage.image_urls; } + // Only send if not aborted if (!abortController.signal.aborted) { + // Create a new response object with minimal copies + const finalResponse = { ...response }; + sendMessage(res, { final: true, conversation, title: conversation.title, requestMessage: userMessage, - responseMessage: response, + responseMessage: finalResponse, }); res.end(); - if (!client.savedMessageIds.has(response.messageId)) { + // Save the message if needed + if (client.savedMessageIds && !client.savedMessageIds.has(messageId)) { await saveMessage( req, - { ...response, user }, + { ...finalResponse, user: userId }, { context: 'api/server/controllers/agents/request.js - response end' }, ); } } + // Save user message if needed if (!client.skipSaveUserMessage) { await saveMessage(req, userMessage, { context: 'api/server/controllers/agents/request.js - don\'t skip saving user message', }); } + // Add title if needed - extract minimal data if (addTitle && parentMessageId === Constants.NO_PARENT && newConvo) { addTitle(req, { text, - response, + response: { ...response }, client, - }); + }) + .then(() => { + logger.debug('[AgentController] Title generation started'); + }) + .catch((err) => { + logger.error('[AgentController] Error in title generation', err); + }) + .finally(() => { + logger.debug('[AgentController] Title generation completed'); + performCleanup(); + }); + } else { + performCleanup(); } } catch (error) { + // Handle error without capturing much scope handleAbortError(res, req, error, { conversationId, sender, messageId: responseMessageId, parentMessageId: overrideParentMessageId ?? userMessageId ?? parentMessageId, - }).catch((err) => { - logger.error('[api/server/controllers/agents/request] Error in `handleAbortError`', err); - }); + }) + .catch((err) => { + logger.error('[api/server/controllers/agents/request] Error in `handleAbortError`', err); + }) + .finally(() => { + performCleanup(); + }); } }; diff --git a/api/server/middleware/abortMiddleware.js b/api/server/middleware/abortMiddleware.js index ccc4ed0439..b10f6586f9 100644 --- a/api/server/middleware/abortMiddleware.js +++ b/api/server/middleware/abortMiddleware.js @@ -1,3 +1,4 @@ +// abortMiddleware.js const { isAssistantsEndpoint, ErrorTypes } = require('librechat-data-provider'); const { sendMessage, sendError, countTokens, isEnabled } = require('~/server/utils'); const { truncateText, smartTruncateText } = require('~/app/clients/prompts'); @@ -8,6 +9,68 @@ const { saveMessage, getConvo } = require('~/models'); const { abortRun } = require('./abortRun'); const { logger } = require('~/config'); +const abortDataMap = new WeakMap(); + +function cleanupAbortController(abortKey) { + if (!abortControllers.has(abortKey)) { + return false; + } + + const { abortController } = abortControllers.get(abortKey); + + if (!abortController) { + abortControllers.delete(abortKey); + return true; + } + + // 1. Check if this controller has any composed signals and clean them up + try { + // This creates a temporary composed signal to use for cleanup + const composedSignal = AbortSignal.any([abortController.signal]); + + // Get all event types - in practice, AbortSignal typically only uses 'abort' + const eventTypes = ['abort']; + + // First, execute a dummy listener removal to handle potential composed signals + for (const eventType of eventTypes) { + const dummyHandler = () => {}; + composedSignal.addEventListener(eventType, dummyHandler); + composedSignal.removeEventListener(eventType, dummyHandler); + + const listeners = composedSignal.listeners?.(eventType) || []; + for (const listener of listeners) { + composedSignal.removeEventListener(eventType, listener); + } + } + } catch (e) { + logger.debug(`Error cleaning up composed signals: ${e}`); + } + + // 2. Abort the controller if not already aborted + if (!abortController.signal.aborted) { + abortController.abort(); + } + + // 3. Remove from registry + abortControllers.delete(abortKey); + + // 4. Clean up any data stored in the WeakMap + if (abortDataMap.has(abortController)) { + abortDataMap.delete(abortController); + } + + // 5. Clean up function references on the controller + if (abortController.getAbortData) { + abortController.getAbortData = null; + } + + if (abortController.abortCompletion) { + abortController.abortCompletion = null; + } + + return true; +} + async function abortMessage(req, res) { let { abortKey, endpoint } = req.body; @@ -29,19 +92,19 @@ async function abortMessage(req, res) { if (!abortController) { return res.status(204).send({ message: 'Request not found' }); } - const finalEvent = await abortController.abortCompletion(); + + const finalEvent = await abortController.abortCompletion?.(); logger.debug( `[abortMessage] ID: ${req.user.id} | ${req.user.email} | Aborted request: ` + JSON.stringify({ abortKey }), ); - abortControllers.delete(abortKey); + cleanupAbortController(abortKey); if (res.headersSent && finalEvent) { return sendMessage(res, finalEvent); } res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(finalEvent)); } @@ -62,8 +125,48 @@ const createAbortController = (req, res, getAbortData, getReqData) => { const abortController = new AbortController(); const { endpointOption } = req.body; + // Store minimal data in WeakMap to avoid circular references + abortDataMap.set(abortController, { + getAbortDataFn: getAbortData, + userId: req.user.id, + endpoint: endpointOption.endpoint, + iconURL: endpointOption.iconURL, + model: endpointOption.modelOptions?.model || endpointOption.model_parameters?.model, + }); + + // Replace the direct function reference with a wrapper that uses WeakMap abortController.getAbortData = function () { - return getAbortData(); + const data = abortDataMap.get(this); + if (!data || typeof data.getAbortDataFn !== 'function') { + return {}; + } + + try { + const result = data.getAbortDataFn(); + + // Create a copy without circular references + const cleanResult = { ...result }; + + // If userMessagePromise exists, break its reference to client + if ( + cleanResult.userMessagePromise && + typeof cleanResult.userMessagePromise.then === 'function' + ) { + // Create a new promise that fulfills with the same result but doesn't reference the original + const originalPromise = cleanResult.userMessagePromise; + cleanResult.userMessagePromise = new Promise((resolve, reject) => { + originalPromise.then( + (result) => resolve({ ...result }), + (error) => reject(error), + ); + }); + } + + return cleanResult; + } catch (err) { + logger.error('[abortController.getAbortData] Error:', err); + return {}; + } }; /** @@ -74,6 +177,7 @@ const createAbortController = (req, res, getAbortData, getReqData) => { sendMessage(res, { message: userMessage, created: true }); const abortKey = userMessage?.conversationId ?? req.user.id; + getReqData({ abortKey }); const prevRequest = abortControllers.get(abortKey); const { overrideUserMessageId } = req?.body ?? {}; @@ -81,34 +185,74 @@ const createAbortController = (req, res, getAbortData, getReqData) => { const data = prevRequest.abortController.getAbortData(); getReqData({ userMessage: data?.userMessage }); const addedAbortKey = `${abortKey}:${responseMessageId}`; - abortControllers.set(addedAbortKey, { abortController, ...endpointOption }); - res.on('finish', function () { - abortControllers.delete(addedAbortKey); - }); + + // Store minimal options + const minimalOptions = { + endpoint: endpointOption.endpoint, + iconURL: endpointOption.iconURL, + model: endpointOption.modelOptions?.model || endpointOption.model_parameters?.model, + }; + + abortControllers.set(addedAbortKey, { abortController, ...minimalOptions }); + + // Use a simple function for cleanup to avoid capturing context + const cleanupHandler = () => { + try { + cleanupAbortController(addedAbortKey); + } catch (e) { + // Ignore cleanup errors + } + }; + + res.on('finish', cleanupHandler); return; } - abortControllers.set(abortKey, { abortController, ...endpointOption }); + // Store minimal options + const minimalOptions = { + endpoint: endpointOption.endpoint, + iconURL: endpointOption.iconURL, + model: endpointOption.modelOptions?.model || endpointOption.model_parameters?.model, + }; - res.on('finish', function () { - abortControllers.delete(abortKey); - }); + abortControllers.set(abortKey, { abortController, ...minimalOptions }); + + // Use a simple function for cleanup to avoid capturing context + const cleanupHandler = () => { + try { + cleanupAbortController(abortKey); + } catch (e) { + // Ignore cleanup errors + } + }; + + res.on('finish', cleanupHandler); }; + // Define abortCompletion without capturing the entire parent scope abortController.abortCompletion = async function () { - abortController.abort(); + this.abort(); + + // Get data from WeakMap + const ctrlData = abortDataMap.get(this); + if (!ctrlData || !ctrlData.getAbortDataFn) { + return { final: true, conversation: {}, title: 'New Chat' }; + } + + // Get abort data using stored function const { conversationId, userMessage, userMessagePromise, promptTokens, ...responseData } = - getAbortData(); + ctrlData.getAbortDataFn(); + const completionTokens = await countTokens(responseData?.text ?? ''); - const user = req.user.id; + const user = ctrlData.userId; const responseMessage = { ...responseData, conversationId, finish_reason: 'incomplete', - endpoint: endpointOption.endpoint, - iconURL: endpointOption.iconURL, - model: endpointOption.modelOptions?.model ?? endpointOption.model_parameters?.model, + endpoint: ctrlData.endpoint, + iconURL: ctrlData.iconURL, + model: ctrlData.modelOptions?.model ?? ctrlData.model_parameters?.model, unfinished: false, error: false, isCreatedByUser: false, @@ -130,10 +274,12 @@ const createAbortController = (req, res, getAbortData, getReqData) => { if (userMessagePromise) { const resolved = await userMessagePromise; conversation = resolved?.conversation; + // Break reference to promise + resolved.conversation = null; } if (!conversation) { - conversation = await getConvo(req.user.id, conversationId); + conversation = await getConvo(user, conversationId); } return { @@ -218,11 +364,12 @@ const handleAbortError = async (res, req, error, data) => { }; } + // Create a simple callback without capturing parent scope const callback = async () => { - if (abortControllers.has(conversationId)) { - const { abortController } = abortControllers.get(conversationId); - abortController.abort(); - abortControllers.delete(conversationId); + try { + cleanupAbortController(conversationId); + } catch (e) { + // Ignore cleanup errors } }; @@ -243,6 +390,7 @@ const handleAbortError = async (res, req, error, data) => { module.exports = { handleAbort, - createAbortController, handleAbortError, + createAbortController, + cleanupAbortController, }; diff --git a/api/server/middleware/checkBan.js b/api/server/middleware/checkBan.js index 67540bb009..4e0593192a 100644 --- a/api/server/middleware/checkBan.js +++ b/api/server/middleware/checkBan.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const uap = require('ua-parser-js'); const { ViolationTypes } = require('librechat-data-provider'); const { isEnabled, removePorts } = require('~/server/utils'); diff --git a/api/server/middleware/limiters/importLimiters.js b/api/server/middleware/limiters/importLimiters.js index 5e50046a30..f353f5e996 100644 --- a/api/server/middleware/limiters/importLimiters.js +++ b/api/server/middleware/limiters/importLimiters.js @@ -1,10 +1,9 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); +const ioredisClient = require('~/cache/ioredisClient'); const logViolation = require('~/cache/logViolation'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); const getEnvironmentVariables = () => { @@ -67,11 +66,9 @@ const createImportLimiters = () => { }, }; - if (isEnabled(process.env.USE_REDIS)) { + if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for import rate limiters.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); + const sendCommand = (...args) => ioredisClient.call(...args); const ipStore = new RedisStore({ sendCommand, prefix: 'import_ip_limiter:', diff --git a/api/server/middleware/limiters/loginLimiter.js b/api/server/middleware/limiters/loginLimiter.js index 8cf10ccb12..d57af29414 100644 --- a/api/server/middleware/limiters/loginLimiter.js +++ b/api/server/middleware/limiters/loginLimiter.js @@ -1,8 +1,7 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { removePorts, isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); +const ioredisClient = require('~/cache/ioredisClient'); const { logViolation } = require('~/cache'); const { logger } = require('~/config'); @@ -31,13 +30,10 @@ const limiterOptions = { keyGenerator: removePorts, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for login rate limiter.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); const store = new RedisStore({ - sendCommand, + sendCommand: (...args) => ioredisClient.call(...args), prefix: 'login_limiter:', }); limiterOptions.store = store; diff --git a/api/server/middleware/limiters/messageLimiters.js b/api/server/middleware/limiters/messageLimiters.js index fe4f75a9c6..4191c9fe7c 100644 --- a/api/server/middleware/limiters/messageLimiters.js +++ b/api/server/middleware/limiters/messageLimiters.js @@ -1,9 +1,8 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const denyRequest = require('~/server/middleware/denyRequest'); +const ioredisClient = require('~/cache/ioredisClient'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logViolation } = require('~/cache'); const { logger } = require('~/config'); @@ -63,11 +62,9 @@ const userLimiterOptions = { }, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for message rate limiters.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); + const sendCommand = (...args) => ioredisClient.call(...args); const ipStore = new RedisStore({ sendCommand, prefix: 'message_ip_limiter:', diff --git a/api/server/middleware/limiters/registerLimiter.js b/api/server/middleware/limiters/registerLimiter.js index f9bf1215cd..7d38b3044e 100644 --- a/api/server/middleware/limiters/registerLimiter.js +++ b/api/server/middleware/limiters/registerLimiter.js @@ -1,8 +1,7 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { removePorts, isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); +const ioredisClient = require('~/cache/ioredisClient'); const { logViolation } = require('~/cache'); const { logger } = require('~/config'); @@ -31,13 +30,10 @@ const limiterOptions = { keyGenerator: removePorts, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for register rate limiter.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); const store = new RedisStore({ - sendCommand, + sendCommand: (...args) => ioredisClient.call(...args), prefix: 'register_limiter:', }); limiterOptions.store = store; diff --git a/api/server/middleware/limiters/resetPasswordLimiter.js b/api/server/middleware/limiters/resetPasswordLimiter.js index 9f56bd7949..673b23e8e5 100644 --- a/api/server/middleware/limiters/resetPasswordLimiter.js +++ b/api/server/middleware/limiters/resetPasswordLimiter.js @@ -1,9 +1,8 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); const { removePorts, isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); +const ioredisClient = require('~/cache/ioredisClient'); const { logViolation } = require('~/cache'); const { logger } = require('~/config'); @@ -36,13 +35,10 @@ const limiterOptions = { keyGenerator: removePorts, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for reset password rate limiter.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); const store = new RedisStore({ - sendCommand, + sendCommand: (...args) => ioredisClient.call(...args), prefix: 'reset_password_limiter:', }); limiterOptions.store = store; diff --git a/api/server/middleware/limiters/sttLimiters.js b/api/server/middleware/limiters/sttLimiters.js index f9304637c4..72ed3af6a3 100644 --- a/api/server/middleware/limiters/sttLimiters.js +++ b/api/server/middleware/limiters/sttLimiters.js @@ -1,10 +1,9 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); +const ioredisClient = require('~/cache/ioredisClient'); const logViolation = require('~/cache/logViolation'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); const getEnvironmentVariables = () => { @@ -67,11 +66,9 @@ const createSTTLimiters = () => { }, }; - if (isEnabled(process.env.USE_REDIS)) { + if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for STT rate limiters.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); + const sendCommand = (...args) => ioredisClient.call(...args); const ipStore = new RedisStore({ sendCommand, prefix: 'stt_ip_limiter:', diff --git a/api/server/middleware/limiters/toolCallLimiter.js b/api/server/middleware/limiters/toolCallLimiter.js index 7a867b5bcd..482744a3e9 100644 --- a/api/server/middleware/limiters/toolCallLimiter.js +++ b/api/server/middleware/limiters/toolCallLimiter.js @@ -1,10 +1,9 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); +const ioredisClient = require('~/cache/ioredisClient'); const logViolation = require('~/cache/logViolation'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); const handler = async (req, res) => { @@ -29,13 +28,10 @@ const limiterOptions = { }, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for tool call rate limiter.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); const store = new RedisStore({ - sendCommand, + sendCommand: (...args) => ioredisClient.call(...args), prefix: 'tool_call_limiter:', }); limiterOptions.store = store; diff --git a/api/server/middleware/limiters/ttsLimiters.js b/api/server/middleware/limiters/ttsLimiters.js index e13aaf48c3..9054a6beb1 100644 --- a/api/server/middleware/limiters/ttsLimiters.js +++ b/api/server/middleware/limiters/ttsLimiters.js @@ -1,10 +1,9 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); +const ioredisClient = require('~/cache/ioredisClient'); const logViolation = require('~/cache/logViolation'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); const getEnvironmentVariables = () => { @@ -67,11 +66,9 @@ const createTTSLimiters = () => { }, }; - if (isEnabled(process.env.USE_REDIS)) { + if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for TTS rate limiters.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); + const sendCommand = (...args) => ioredisClient.call(...args); const ipStore = new RedisStore({ sendCommand, prefix: 'tts_ip_limiter:', diff --git a/api/server/middleware/limiters/uploadLimiters.js b/api/server/middleware/limiters/uploadLimiters.js index 9fffface61..d9049f898e 100644 --- a/api/server/middleware/limiters/uploadLimiters.js +++ b/api/server/middleware/limiters/uploadLimiters.js @@ -1,10 +1,9 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); +const ioredisClient = require('~/cache/ioredisClient'); const logViolation = require('~/cache/logViolation'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); const getEnvironmentVariables = () => { @@ -72,11 +71,9 @@ const createFileLimiters = () => { }, }; - if (isEnabled(process.env.USE_REDIS)) { + if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for file upload rate limiters.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); + const sendCommand = (...args) => ioredisClient.call(...args); const ipStore = new RedisStore({ sendCommand, prefix: 'file_upload_ip_limiter:', diff --git a/api/server/middleware/limiters/verifyEmailLimiter.js b/api/server/middleware/limiters/verifyEmailLimiter.js index 0b245afbd1..73bfa2daf3 100644 --- a/api/server/middleware/limiters/verifyEmailLimiter.js +++ b/api/server/middleware/limiters/verifyEmailLimiter.js @@ -1,9 +1,8 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); const { removePorts, isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); +const ioredisClient = require('~/cache/ioredisClient'); const { logViolation } = require('~/cache'); const { logger } = require('~/config'); @@ -36,13 +35,10 @@ const limiterOptions = { keyGenerator: removePorts, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for verify email rate limiter.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); const store = new RedisStore({ - sendCommand, + sendCommand: (...args) => ioredisClient.call(...args), prefix: 'verify_email_limiter:', }); limiterOptions.store = store; diff --git a/api/server/routes/agents/actions.js b/api/server/routes/agents/actions.js index 786f44dd8e..5413bc1d68 100644 --- a/api/server/routes/agents/actions.js +++ b/api/server/routes/agents/actions.js @@ -58,7 +58,7 @@ router.post('/:agent_id', async (req, res) => { } let { domain } = metadata; - domain = await domainParser(req, domain, true); + domain = await domainParser(domain, true); if (!domain) { return res.status(400).json({ message: 'No domain provided' }); @@ -164,7 +164,7 @@ router.delete('/:agent_id/:action_id', async (req, res) => { return true; }); - domain = await domainParser(req, domain, true); + domain = await domainParser(domain, true); if (!domain) { return res.status(400).json({ message: 'No domain provided' }); diff --git a/api/server/routes/agents/chat.js b/api/server/routes/agents/chat.js index fe50fdc765..ef66ef7896 100644 --- a/api/server/routes/agents/chat.js +++ b/api/server/routes/agents/chat.js @@ -2,7 +2,6 @@ const express = require('express'); const { PermissionTypes, Permissions } = require('librechat-data-provider'); const { setHeaders, - handleAbort, moderateText, // validateModel, generateCheckAccess, @@ -16,7 +15,6 @@ const addTitle = require('~/server/services/Endpoints/agents/title'); const router = express.Router(); router.use(moderateText); -router.post('/abort', handleAbort()); const checkAgentAccess = generateCheckAccess(PermissionTypes.AGENTS, [Permissions.USE]); diff --git a/api/server/routes/ask/addToCache.js b/api/server/routes/ask/addToCache.js index 6e21edd2b8..a2f427098f 100644 --- a/api/server/routes/ask/addToCache.js +++ b/api/server/routes/ask/addToCache.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const { KeyvFile } = require('keyv-file'); const { logger } = require('~/config'); diff --git a/api/server/routes/ask/anthropic.js b/api/server/routes/ask/anthropic.js index a08d1d2570..afe1720d84 100644 --- a/api/server/routes/ask/anthropic.js +++ b/api/server/routes/ask/anthropic.js @@ -11,8 +11,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/ask/custom.js b/api/server/routes/ask/custom.js index 668a9902cb..8fc343cf17 100644 --- a/api/server/routes/ask/custom.js +++ b/api/server/routes/ask/custom.js @@ -3,7 +3,6 @@ const AskController = require('~/server/controllers/AskController'); const { initializeClient } = require('~/server/services/Endpoints/custom'); const { addTitle } = require('~/server/services/Endpoints/openAI'); const { - handleAbort, setHeaders, validateModel, validateEndpoint, @@ -12,8 +11,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/ask/google.js b/api/server/routes/ask/google.js index 2b3378bf6c..16c7e265f4 100644 --- a/api/server/routes/ask/google.js +++ b/api/server/routes/ask/google.js @@ -3,7 +3,6 @@ const AskController = require('~/server/controllers/AskController'); const { initializeClient, addTitle } = require('~/server/services/Endpoints/google'); const { setHeaders, - handleAbort, validateModel, validateEndpoint, buildEndpointOption, @@ -11,8 +10,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/ask/gptPlugins.js b/api/server/routes/ask/gptPlugins.js index 036654f845..a40022848a 100644 --- a/api/server/routes/ask/gptPlugins.js +++ b/api/server/routes/ask/gptPlugins.js @@ -20,7 +20,6 @@ const { logger } = require('~/config'); const router = express.Router(); router.use(moderateText); -router.post('/abort', handleAbort()); router.post( '/', @@ -196,7 +195,8 @@ router.post( logger.debug('[/ask/gptPlugins]', response); - const { conversation = {} } = await client.responsePromise; + const { conversation = {} } = await response.databasePromise; + delete response.databasePromise; conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; diff --git a/api/server/routes/ask/openAI.js b/api/server/routes/ask/openAI.js index 5083a08b10..dadf00def4 100644 --- a/api/server/routes/ask/openAI.js +++ b/api/server/routes/ask/openAI.js @@ -12,7 +12,6 @@ const { const router = express.Router(); router.use(moderateText); -router.post('/abort', handleAbort()); router.post( '/', diff --git a/api/server/routes/assistants/actions.js b/api/server/routes/assistants/actions.js index 9f4db5d6b8..3dc3923503 100644 --- a/api/server/routes/assistants/actions.js +++ b/api/server/routes/assistants/actions.js @@ -36,7 +36,7 @@ router.post('/:assistant_id', async (req, res) => { } let { domain } = metadata; - domain = await domainParser(req, domain, true); + domain = await domainParser(domain, true); if (!domain) { return res.status(400).json({ message: 'No domain provided' }); @@ -172,7 +172,7 @@ router.delete('/:assistant_id/:action_id/:model', async (req, res) => { return true; }); - domain = await domainParser(req, domain, true); + domain = await domainParser(domain, true); if (!domain) { return res.status(400).json({ message: 'No domain provided' }); diff --git a/api/server/routes/bedrock/chat.js b/api/server/routes/bedrock/chat.js index 11db89f07e..263ca96002 100644 --- a/api/server/routes/bedrock/chat.js +++ b/api/server/routes/bedrock/chat.js @@ -14,7 +14,6 @@ const AgentController = require('~/server/controllers/agents/request'); const addTitle = require('~/server/services/Endpoints/agents/title'); router.use(moderateText); -router.post('/abort', handleAbort()); /** * @route POST / diff --git a/api/server/routes/edit/anthropic.js b/api/server/routes/edit/anthropic.js index c7bf128d7c..704a9f4ea4 100644 --- a/api/server/routes/edit/anthropic.js +++ b/api/server/routes/edit/anthropic.js @@ -3,7 +3,6 @@ const EditController = require('~/server/controllers/EditController'); const { initializeClient } = require('~/server/services/Endpoints/anthropic'); const { setHeaders, - handleAbort, validateModel, validateEndpoint, buildEndpointOption, @@ -11,8 +10,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/edit/custom.js b/api/server/routes/edit/custom.js index 0bf97ba180..a6fd804763 100644 --- a/api/server/routes/edit/custom.js +++ b/api/server/routes/edit/custom.js @@ -12,8 +12,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/edit/google.js b/api/server/routes/edit/google.js index 7482f11b4c..187f4f6158 100644 --- a/api/server/routes/edit/google.js +++ b/api/server/routes/edit/google.js @@ -3,7 +3,6 @@ const EditController = require('~/server/controllers/EditController'); const { initializeClient } = require('~/server/services/Endpoints/google'); const { setHeaders, - handleAbort, validateModel, validateEndpoint, buildEndpointOption, @@ -11,8 +10,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/edit/gptPlugins.js b/api/server/routes/edit/gptPlugins.js index 5547a1fcdf..94d9b91d0b 100644 --- a/api/server/routes/edit/gptPlugins.js +++ b/api/server/routes/edit/gptPlugins.js @@ -2,7 +2,6 @@ const express = require('express'); const { getResponseSender } = require('librechat-data-provider'); const { setHeaders, - handleAbort, moderateText, validateModel, handleAbortError, @@ -19,7 +18,6 @@ const { logger } = require('~/config'); const router = express.Router(); router.use(moderateText); -router.post('/abort', handleAbort()); router.post( '/', @@ -173,7 +171,8 @@ router.post( logger.debug('[/edit/gptPlugins] CLIENT RESPONSE', response); - const { conversation = {} } = await client.responsePromise; + const { conversation = {} } = await response.databasePromise; + delete response.databasePromise; conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; diff --git a/api/server/routes/edit/openAI.js b/api/server/routes/edit/openAI.js index ae26b235c7..ee25a42ee3 100644 --- a/api/server/routes/edit/openAI.js +++ b/api/server/routes/edit/openAI.js @@ -2,7 +2,6 @@ const express = require('express'); const EditController = require('~/server/controllers/EditController'); const { initializeClient } = require('~/server/services/Endpoints/openAI'); const { - handleAbort, setHeaders, validateModel, validateEndpoint, @@ -12,7 +11,6 @@ const { const router = express.Router(); router.use(moderateText); -router.post('/abort', handleAbort()); router.post( '/', diff --git a/api/server/routes/search.js b/api/server/routes/search.js index 68cff7532b..0341a241df 100644 --- a/api/server/routes/search.js +++ b/api/server/routes/search.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const express = require('express'); const { MeiliSearch } = require('meilisearch'); const { Conversation, getConvosQueried } = require('~/models/Conversation'); diff --git a/api/server/services/ActionService.js b/api/server/services/ActionService.js index 12e30fd8b9..8b13e2609f 100644 --- a/api/server/services/ActionService.js +++ b/api/server/services/ActionService.js @@ -50,7 +50,7 @@ const validateAndUpdateTool = async ({ req, tool, assistant_id }) => { return null; } - const parsedDomain = await domainParser(req, domain, true); + const parsedDomain = await domainParser(domain, true); if (!parsedDomain) { return null; @@ -66,12 +66,11 @@ const validateAndUpdateTool = async ({ req, tool, assistant_id }) => { * * Necessary due to `[a-zA-Z0-9_-]*` Regex Validation, limited to a 64-character maximum. * - * @param {Express.Request} req - The Express Request object. * @param {string} domain - The domain name to encode/decode. * @param {boolean} inverse - False to decode from base64, true to encode to base64. * @returns {Promise} Encoded or decoded domain string. */ -async function domainParser(req, domain, inverse = false) { +async function domainParser(domain, inverse = false) { if (!domain) { return; } @@ -122,7 +121,7 @@ async function loadActionSets(searchParams) { * Creates a general tool for an entire action set. * * @param {Object} params - The parameters for loading action sets. - * @param {ServerRequest} params.req + * @param {string} params.userId * @param {ServerResponse} params.res * @param {Action} params.action - The action set. Necessary for decrypting authentication values. * @param {ActionRequest} params.requestBuilder - The ActionRequest builder class to execute the API call. @@ -133,7 +132,7 @@ async function loadActionSets(searchParams) { * @returns { Promise unknown}> } An object with `_call` method to execute the tool input. */ async function createActionTool({ - req, + userId, res, action, requestBuilder, @@ -154,7 +153,7 @@ async function createActionTool({ try { if (metadata.auth.type === AuthTypeEnum.OAuth && metadata.auth.authorization_url) { const action_id = action.action_id; - const identifier = `${req.user.id}:${action.action_id}`; + const identifier = `${userId}:${action.action_id}`; const requestLogin = async () => { const { args: _args, stepId, ...toolCall } = config.toolCall ?? {}; if (!stepId) { @@ -162,7 +161,7 @@ async function createActionTool({ } const statePayload = { nonce: nanoid(), - user: req.user.id, + user: userId, action_id, }; @@ -206,7 +205,7 @@ async function createActionTool({ 'oauth', { state: stateToken, - userId: req.user.id, + userId: userId, client_url: metadata.auth.client_url, redirect_uri: `${process.env.DOMAIN_CLIENT}/api/actions/${action_id}/oauth/callback`, /** Encrypted values */ @@ -232,10 +231,10 @@ async function createActionTool({ }; const tokenPromises = []; - tokenPromises.push(findToken({ userId: req.user.id, type: 'oauth', identifier })); + tokenPromises.push(findToken({ userId, type: 'oauth', identifier })); tokenPromises.push( findToken({ - userId: req.user.id, + userId, type: 'oauth_refresh', identifier: `${identifier}:refresh`, }), @@ -258,9 +257,9 @@ async function createActionTool({ const refresh_token = await decryptV2(refreshTokenData.token); const refreshTokens = async () => await refreshAccessToken({ + userId, identifier, refresh_token, - userId: req.user.id, client_url: metadata.auth.client_url, encrypted_oauth_client_id: encrypted.oauth_client_id, encrypted_oauth_client_secret: encrypted.oauth_client_secret, diff --git a/api/server/services/ActionService.spec.js b/api/server/services/ActionService.spec.js index 8f9d67a9d1..f3b4423197 100644 --- a/api/server/services/ActionService.spec.js +++ b/api/server/services/ActionService.spec.js @@ -78,20 +78,20 @@ describe('domainParser', () => { // Non-azure request it('does not return domain as is if not azure', async () => { const domain = `example.com${actionDomainSeparator}test${actionDomainSeparator}`; - const result1 = await domainParser(reqNoAzure, domain, false); - const result2 = await domainParser(reqNoAzure, domain, true); + const result1 = await domainParser(domain, false); + const result2 = await domainParser(domain, true); expect(result1).not.toEqual(domain); expect(result2).not.toEqual(domain); }); // Test for Empty or Null Inputs it('returns undefined for null domain input', async () => { - const result = await domainParser(req, null, true); + const result = await domainParser(null, true); expect(result).toBeUndefined(); }); it('returns undefined for empty domain input', async () => { - const result = await domainParser(req, '', true); + const result = await domainParser('', true); expect(result).toBeUndefined(); }); @@ -102,7 +102,7 @@ describe('domainParser', () => { .toString('base64') .substring(0, Constants.ENCODED_DOMAIN_LENGTH); - await domainParser(req, domain, true); + await domainParser(domain, true); const cachedValue = await globalCache[encodedDomain]; expect(cachedValue).toEqual(Buffer.from(domain).toString('base64')); @@ -112,14 +112,14 @@ describe('domainParser', () => { it('encodes domain exactly at threshold without modification', async () => { const domain = 'a'.repeat(Constants.ENCODED_DOMAIN_LENGTH - TLD.length) + TLD; const expected = domain.replace(/\./g, actionDomainSeparator); - const result = await domainParser(req, domain, true); + const result = await domainParser(domain, true); expect(result).toEqual(expected); }); it('encodes domain just below threshold without modification', async () => { const domain = 'a'.repeat(Constants.ENCODED_DOMAIN_LENGTH - 1 - TLD.length) + TLD; const expected = domain.replace(/\./g, actionDomainSeparator); - const result = await domainParser(req, domain, true); + const result = await domainParser(domain, true); expect(result).toEqual(expected); }); @@ -129,7 +129,7 @@ describe('domainParser', () => { const encodedDomain = Buffer.from(unicodeDomain) .toString('base64') .substring(0, Constants.ENCODED_DOMAIN_LENGTH); - const result = await domainParser(req, unicodeDomain, true); + const result = await domainParser(unicodeDomain, true); expect(result).toEqual(encodedDomain); }); @@ -139,7 +139,6 @@ describe('domainParser', () => { globalCache[encodedDomain.substring(0, Constants.ENCODED_DOMAIN_LENGTH)] = encodedDomain; // Simulate caching const result = await domainParser( - req, encodedDomain.substring(0, Constants.ENCODED_DOMAIN_LENGTH), false, ); @@ -150,27 +149,27 @@ describe('domainParser', () => { it('returns domain with replaced separators if no cached domain exists', async () => { const domain = 'example.com'; const withSeparator = domain.replace(/\./g, actionDomainSeparator); - const result = await domainParser(req, withSeparator, false); + const result = await domainParser(withSeparator, false); expect(result).toEqual(domain); }); it('returns domain with replaced separators when inverse is false and under encoding length', async () => { const domain = 'examp.com'; const withSeparator = domain.replace(/\./g, actionDomainSeparator); - const result = await domainParser(req, withSeparator, false); + const result = await domainParser(withSeparator, false); expect(result).toEqual(domain); }); it('replaces periods with actionDomainSeparator when inverse is true and under encoding length', async () => { const domain = 'examp.com'; const expected = domain.replace(/\./g, actionDomainSeparator); - const result = await domainParser(req, domain, true); + const result = await domainParser(domain, true); expect(result).toEqual(expected); }); it('encodes domain when length is above threshold and inverse is true', async () => { const domain = 'a'.repeat(Constants.ENCODED_DOMAIN_LENGTH + 1).concat('.com'); - const result = await domainParser(req, domain, true); + const result = await domainParser(domain, true); expect(result).not.toEqual(domain); expect(result.length).toBeLessThanOrEqual(Constants.ENCODED_DOMAIN_LENGTH); }); @@ -180,20 +179,20 @@ describe('domainParser', () => { const encodedDomain = Buffer.from( originalDomain.replace(/\./g, actionDomainSeparator), ).toString('base64'); - const result = await domainParser(req, encodedDomain, false); + const result = await domainParser(encodedDomain, false); expect(result).toEqual(encodedDomain); }); it('decodes encoded value if cached and encoded value is provided, and inverse is false', async () => { const originalDomain = 'example.com'; - const encodedDomain = await domainParser(req, originalDomain, true); - const result = await domainParser(req, encodedDomain, false); + const encodedDomain = await domainParser(originalDomain, true); + const result = await domainParser(encodedDomain, false); expect(result).toEqual(originalDomain); }); it('handles invalid base64 encoded values gracefully', async () => { const invalidBase64Domain = 'not_base64_encoded'; - const result = await domainParser(req, invalidBase64Domain, false); + const result = await domainParser(invalidBase64Domain, false); expect(result).toEqual(invalidBase64Domain); }); }); diff --git a/api/server/services/Endpoints/agents/initialize.js b/api/server/services/Endpoints/agents/initialize.js index 0186541750..eaff058bf0 100644 --- a/api/server/services/Endpoints/agents/initialize.js +++ b/api/server/services/Endpoints/agents/initialize.js @@ -159,14 +159,20 @@ const initializeAgentOptions = async ({ currentFiles, agent.tool_resources, ); + + const provider = agent.provider; const { tools, toolContextMap } = await loadAgentTools({ req, res, - agent, + agent: { + id: agent.id, + tools: agent.tools, + provider, + model: agent.model, + }, tool_resources, }); - const provider = agent.provider; agent.endpoint = provider; let getOptions = providerConfigMap[provider]; if (!getOptions && providerConfigMap[provider.toLowerCase()] != null) { diff --git a/api/server/services/Endpoints/agents/title.js b/api/server/services/Endpoints/agents/title.js index f25746582e..ab171bc79d 100644 --- a/api/server/services/Endpoints/agents/title.js +++ b/api/server/services/Endpoints/agents/title.js @@ -2,7 +2,11 @@ const { CacheKeys } = require('librechat-data-provider'); const getLogStores = require('~/cache/getLogStores'); const { isEnabled } = require('~/server/utils'); const { saveConvo } = require('~/models'); +const { logger } = require('~/config'); +/** + * Add title to conversation in a way that avoids memory retention + */ const addTitle = async (req, { text, response, client }) => { const { TITLE_CONVO = true } = process.env ?? {}; if (!isEnabled(TITLE_CONVO)) { @@ -13,37 +17,55 @@ const addTitle = async (req, { text, response, client }) => { return; } - // If the request was aborted, don't generate the title. - if (client.abortController.signal.aborted) { - return; - } - const titleCache = getLogStores(CacheKeys.GEN_TITLE); const key = `${req.user.id}-${response.conversationId}`; - const responseText = - response?.content && Array.isArray(response?.content) - ? response.content.reduce((acc, block) => { - if (block?.type === 'text') { - return acc + block.text; - } - return acc; - }, '') - : (response?.content ?? response?.text ?? ''); + /** @type {NodeJS.Timeout} */ + let timeoutId; + try { + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => reject(new Error('Title generation timeout')), 25000); + }).catch((error) => { + logger.error('Title error:', error); + }); - const title = await client.titleConvo({ - text, - responseText, - conversationId: response.conversationId, - }); - await titleCache.set(key, title, 120000); - await saveConvo( - req, - { - conversationId: response.conversationId, - title, - }, - { context: 'api/server/services/Endpoints/agents/title.js' }, - ); + let titlePromise; + let abortController = new AbortController(); + if (client && typeof client.titleConvo === 'function') { + titlePromise = Promise.race([ + client + .titleConvo({ + text, + abortController, + }) + .catch((error) => { + logger.error('Client title error:', error); + }), + timeoutPromise, + ]); + } else { + return; + } + + const title = await titlePromise; + if (!abortController.signal.aborted) { + abortController.abort(); + } + if (timeoutId) { + clearTimeout(timeoutId); + } + + await titleCache.set(key, title, 120000); + await saveConvo( + req, + { + conversationId: response.conversationId, + title, + }, + { context: 'api/server/services/Endpoints/agents/title.js' }, + ); + } catch (error) { + logger.error('Error generating title:', error); + } }; module.exports = addTitle; diff --git a/api/server/services/Endpoints/anthropic/initialize.js b/api/server/services/Endpoints/anthropic/initialize.js index 6c89eff463..d4c6dd1795 100644 --- a/api/server/services/Endpoints/anthropic/initialize.js +++ b/api/server/services/Endpoints/anthropic/initialize.js @@ -1,7 +1,7 @@ const { EModelEndpoint } = require('librechat-data-provider'); const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getLLMConfig } = require('~/server/services/Endpoints/anthropic/llm'); -const { AnthropicClient } = require('~/app'); +const AnthropicClient = require('~/app/clients/AnthropicClient'); const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => { const { ANTHROPIC_API_KEY, ANTHROPIC_REVERSE_PROXY, PROXY } = process.env; diff --git a/api/server/services/Endpoints/anthropic/title.js b/api/server/services/Endpoints/anthropic/title.js index 5c477632d2..0f9a5e97d0 100644 --- a/api/server/services/Endpoints/anthropic/title.js +++ b/api/server/services/Endpoints/anthropic/title.js @@ -13,11 +13,6 @@ const addTitle = async (req, { text, response, client }) => { return; } - // If the request was aborted, don't generate the title. - if (client.abortController.signal.aborted) { - return; - } - const titleCache = getLogStores(CacheKeys.GEN_TITLE); const key = `${req.user.id}-${response.conversationId}`; diff --git a/api/server/services/Endpoints/custom/initialize.js b/api/server/services/Endpoints/custom/initialize.js index e98ec71980..c44b9e0e3e 100644 --- a/api/server/services/Endpoints/custom/initialize.js +++ b/api/server/services/Endpoints/custom/initialize.js @@ -11,8 +11,8 @@ const { getLLMConfig } = require('~/server/services/Endpoints/openAI/llm'); const { getCustomEndpointConfig } = require('~/server/services/Config'); const { fetchModels } = require('~/server/services/ModelService'); const { isUserProvided, sleep } = require('~/server/utils'); +const OpenAIClient = require('~/app/clients/OpenAIClient'); const getLogStores = require('~/cache/getLogStores'); -const { OpenAIClient } = require('~/app'); const { PROXY } = process.env; diff --git a/api/server/services/Endpoints/openAI/initialize.js b/api/server/services/Endpoints/openAI/initialize.js index 4d358cef1a..1661f9e6c6 100644 --- a/api/server/services/Endpoints/openAI/initialize.js +++ b/api/server/services/Endpoints/openAI/initialize.js @@ -7,8 +7,14 @@ const { const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getLLMConfig } = require('~/server/services/Endpoints/openAI/llm'); const { isEnabled, isUserProvided, sleep } = require('~/server/utils'); +const OpenAIClient = require('~/app/clients/OpenAIClient'); const { getAzureCredentials } = require('~/utils'); -const { OpenAIClient } = require('~/app'); + +function createHandleNewToken(streamRate) { + async () => { + await sleep(streamRate); + }; +} const initializeClient = async ({ req, @@ -140,14 +146,13 @@ const initializeClient = async ({ clientOptions = Object.assign({ modelOptions }, clientOptions); clientOptions.modelOptions.user = req.user.id; const options = getLLMConfig(apiKey, clientOptions); - if (!clientOptions.streamRate) { + const streamRate = clientOptions.streamRate; + if (!streamRate) { return options; } options.llmConfig.callbacks = [ { - handleLLMNewToken: async () => { - await sleep(clientOptions.streamRate); - }, + handleLLMNewToken: createHandleNewToken(streamRate), }, ]; return options; diff --git a/api/server/services/Endpoints/openAI/title.js b/api/server/services/Endpoints/openAI/title.js index 35291c5e31..3b9e9c82b7 100644 --- a/api/server/services/Endpoints/openAI/title.js +++ b/api/server/services/Endpoints/openAI/title.js @@ -13,11 +13,6 @@ const addTitle = async (req, { text, response, client }) => { return; } - // If the request was aborted and is not azure, don't generate the title. - if (!client.azure && client.abortController.signal.aborted) { - return; - } - const titleCache = getLogStores(CacheKeys.GEN_TITLE); const key = `${req.user.id}-${response.conversationId}`; diff --git a/api/server/services/MCP.js b/api/server/services/MCP.js index 0a2711c672..5c5bd9677d 100644 --- a/api/server/services/MCP.js +++ b/api/server/services/MCP.js @@ -37,9 +37,8 @@ async function createMCPTool({ req, toolKey, provider }) { } const [toolName, serverName] = toolKey.split(Constants.mcp_delimiter); - const userId = req.user?.id; - if (!userId) { + if (!req.user?.id) { logger.error( `[MCP][${serverName}][${toolName}] User ID not found on request. Cannot create tool.`, ); @@ -49,15 +48,16 @@ async function createMCPTool({ req, toolKey, provider }) { /** @type {(toolArguments: Object | string, config?: GraphRunnableConfig) => Promise} */ const _call = async (toolArguments, config) => { try { - const mcpManager = getMCPManager(); + const derivedSignal = config?.signal ? AbortSignal.any([config.signal]) : undefined; + const mcpManager = getMCPManager(config?.userId); const result = await mcpManager.callTool({ serverName, toolName, provider, toolArguments, options: { - userId, - signal: config?.signal, + userId: config?.configurable?.user_id, + signal: derivedSignal, }, }); @@ -70,7 +70,7 @@ async function createMCPTool({ req, toolKey, provider }) { return result; } catch (error) { logger.error( - `[MCP][User: ${userId}][${serverName}] Error calling "${toolName}" MCP tool:`, + `[MCP][User: ${config?.userId}][${serverName}] Error calling "${toolName}" MCP tool:`, error, ); throw new Error( diff --git a/api/server/services/ToolService.js b/api/server/services/ToolService.js index fca26ffcfe..046d4e9bfe 100644 --- a/api/server/services/ToolService.js +++ b/api/server/services/ToolService.js @@ -334,7 +334,7 @@ async function processRequiredActions(client, requiredActions) { const domainMap = new Map(); for (const action of actionSets) { - const domain = await domainParser(client.req, action.metadata.domain, true); + const domain = await domainParser(action.metadata.domain, true); domainMap.set(domain, action); // Check if domain is allowed @@ -404,7 +404,7 @@ async function processRequiredActions(client, requiredActions) { // We've already decrypted the metadata, so we can pass it directly tool = await createActionTool({ - req: client.req, + userId: client.req.user.id, res: client.res, action, requestBuilder, @@ -458,7 +458,7 @@ async function processRequiredActions(client, requiredActions) { * @param {Object} params - Run params containing user and request information. * @param {ServerRequest} params.req - The request object. * @param {ServerResponse} params.res - The request object. - * @param {Agent} params.agent - The agent to load tools for. + * @param {Pick} The agent tools. */ @@ -570,7 +570,7 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey }) const domainMap = new Map(); for (const action of actionSets) { - const domain = await domainParser(req, action.metadata.domain, true); + const domain = await domainParser(action.metadata.domain, true); domainMap.set(domain, action); // Check if domain is allowed (do this once per action set) @@ -639,7 +639,7 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey }) if (requestBuilder) { const tool = await createActionTool({ - req, + userId: req.user.id, res, action, requestBuilder, diff --git a/api/server/socialLogins.js b/api/server/socialLogins.js index af80a3b880..0eb44514d3 100644 --- a/api/server/socialLogins.js +++ b/api/server/socialLogins.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const passport = require('passport'); const session = require('express-session'); const MemoryStore = require('memorystore')(session); @@ -53,7 +53,7 @@ const configureSocialLogins = (app) => { if (isEnabled(process.env.USE_REDIS)) { logger.debug('Using Redis for session storage in OpenID...'); const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; + const client = keyv.opts.store.client; sessionOptions.store = new RedisStore({ client, prefix: 'openid_session' }); } else { sessionOptions.store = new MemoryStore({ diff --git a/api/test/__mocks__/KeyvMongo.js b/api/test/__mocks__/KeyvMongo.js index f88bc144be..f990ae3899 100644 --- a/api/test/__mocks__/KeyvMongo.js +++ b/api/test/__mocks__/KeyvMongo.js @@ -1,6 +1,3 @@ -const mockGet = jest.fn(); -const mockSet = jest.fn(); - jest.mock('@keyv/mongo', () => { const EventEmitter = require('events'); class KeyvMongo extends EventEmitter { @@ -20,11 +17,32 @@ jest.mock('@keyv/mongo', () => { ...url, ...options, }; + + // In-memory store for tests + this.store = new Map(); } - get = mockGet; - set = mockSet; + async get(key) { + return this.store.get(key); + } + + async set(key, value, ttl) { + this.store.set(key, value); + return true; + } + + async delete(key) { + return this.store.delete(key); + } + + async clear() { + this.store.clear(); + return true; + } } - return KeyvMongo; + // Create a store factory function for the test suite + const store = () => new KeyvMongo(); + + return { KeyvMongo }; }); diff --git a/api/utils/tokens.js b/api/utils/tokens.js index 2982aedcb8..1f648b34f1 100644 --- a/api/utils/tokens.js +++ b/api/utils/tokens.js @@ -196,6 +196,7 @@ const bedrockModels = { }; const xAIModels = { + grok: 131072, 'grok-beta': 131072, 'grok-vision-beta': 8192, 'grok-2': 131072, @@ -204,6 +205,10 @@ const xAIModels = { 'grok-2-vision': 32768, 'grok-2-vision-latest': 32768, 'grok-2-vision-1212': 32768, + 'grok-3': 131072, + 'grok-3-fast': 131072, + 'grok-3-mini': 131072, + 'grok-3-mini-fast': 131072, }; const aggregateModels = { ...openAIModels, ...googleModels, ...bedrockModels, ...xAIModels }; diff --git a/api/utils/tokens.spec.js b/api/utils/tokens.spec.js index e5ae21b646..9960b8f5f5 100644 --- a/api/utils/tokens.spec.js +++ b/api/utils/tokens.spec.js @@ -517,18 +517,30 @@ describe('Grok Model Tests - Tokens', () => { expect(getModelMaxTokens('grok-2-latest')).toBe(131072); }); + test('should return correct tokens for Grok 3 series models', () => { + expect(getModelMaxTokens('grok-3')).toBe(131072); + expect(getModelMaxTokens('grok-3-fast')).toBe(131072); + expect(getModelMaxTokens('grok-3-mini')).toBe(131072); + expect(getModelMaxTokens('grok-3-mini-fast')).toBe(131072); + }); + test('should handle partial matches for Grok models with prefixes', () => { // Vision models should match before general models - expect(getModelMaxTokens('openai/grok-2-vision-1212')).toBe(32768); - expect(getModelMaxTokens('openai/grok-2-vision')).toBe(32768); - expect(getModelMaxTokens('openai/grok-2-vision-latest')).toBe(32768); + expect(getModelMaxTokens('xai/grok-2-vision-1212')).toBe(32768); + expect(getModelMaxTokens('xai/grok-2-vision')).toBe(32768); + expect(getModelMaxTokens('xai/grok-2-vision-latest')).toBe(32768); // Beta models - expect(getModelMaxTokens('openai/grok-vision-beta')).toBe(8192); - expect(getModelMaxTokens('openai/grok-beta')).toBe(131072); + expect(getModelMaxTokens('xai/grok-vision-beta')).toBe(8192); + expect(getModelMaxTokens('xai/grok-beta')).toBe(131072); // Text models - expect(getModelMaxTokens('openai/grok-2-1212')).toBe(131072); - expect(getModelMaxTokens('openai/grok-2')).toBe(131072); - expect(getModelMaxTokens('openai/grok-2-latest')).toBe(131072); + expect(getModelMaxTokens('xai/grok-2-1212')).toBe(131072); + expect(getModelMaxTokens('xai/grok-2')).toBe(131072); + expect(getModelMaxTokens('xai/grok-2-latest')).toBe(131072); + // Grok 3 models + expect(getModelMaxTokens('xai/grok-3')).toBe(131072); + expect(getModelMaxTokens('xai/grok-3-fast')).toBe(131072); + expect(getModelMaxTokens('xai/grok-3-mini')).toBe(131072); + expect(getModelMaxTokens('xai/grok-3-mini-fast')).toBe(131072); }); }); @@ -545,20 +557,30 @@ describe('Grok Model Tests - Tokens', () => { expect(matchModelName('grok-2-1212')).toBe('grok-2-1212'); expect(matchModelName('grok-2')).toBe('grok-2'); expect(matchModelName('grok-2-latest')).toBe('grok-2-latest'); + // Grok 3 models + expect(matchModelName('grok-3')).toBe('grok-3'); + expect(matchModelName('grok-3-fast')).toBe('grok-3-fast'); + expect(matchModelName('grok-3-mini')).toBe('grok-3-mini'); + expect(matchModelName('grok-3-mini-fast')).toBe('grok-3-mini-fast'); }); test('should match Grok model variations with prefixes', () => { // Vision models should match before general models - expect(matchModelName('openai/grok-2-vision-1212')).toBe('grok-2-vision-1212'); - expect(matchModelName('openai/grok-2-vision')).toBe('grok-2-vision'); - expect(matchModelName('openai/grok-2-vision-latest')).toBe('grok-2-vision-latest'); + expect(matchModelName('xai/grok-2-vision-1212')).toBe('grok-2-vision-1212'); + expect(matchModelName('xai/grok-2-vision')).toBe('grok-2-vision'); + expect(matchModelName('xai/grok-2-vision-latest')).toBe('grok-2-vision-latest'); // Beta models - expect(matchModelName('openai/grok-vision-beta')).toBe('grok-vision-beta'); - expect(matchModelName('openai/grok-beta')).toBe('grok-beta'); + expect(matchModelName('xai/grok-vision-beta')).toBe('grok-vision-beta'); + expect(matchModelName('xai/grok-beta')).toBe('grok-beta'); // Text models - expect(matchModelName('openai/grok-2-1212')).toBe('grok-2-1212'); - expect(matchModelName('openai/grok-2')).toBe('grok-2'); - expect(matchModelName('openai/grok-2-latest')).toBe('grok-2-latest'); + expect(matchModelName('xai/grok-2-1212')).toBe('grok-2-1212'); + expect(matchModelName('xai/grok-2')).toBe('grok-2'); + expect(matchModelName('xai/grok-2-latest')).toBe('grok-2-latest'); + // Grok 3 models + expect(matchModelName('xai/grok-3')).toBe('grok-3'); + expect(matchModelName('xai/grok-3-fast')).toBe('grok-3-fast'); + expect(matchModelName('xai/grok-3-mini')).toBe('grok-3-mini'); + expect(matchModelName('xai/grok-3-mini-fast')).toBe('grok-3-mini-fast'); }); }); }); diff --git a/client/src/hooks/SSE/useEventHandlers.ts b/client/src/hooks/SSE/useEventHandlers.ts index 6eb6941c8f..75a918fb57 100644 --- a/client/src/hooks/SSE/useEventHandlers.ts +++ b/client/src/hooks/SSE/useEventHandlers.ts @@ -11,6 +11,7 @@ import { tMessageSchema, tConvoUpdateSchema, ContentTypes, + isAssistantsEndpoint, } from 'librechat-data-provider'; import type { TMessage, @@ -622,6 +623,17 @@ export default function useEventHandlers({ const { endpoint: _endpoint, endpointType } = (submission.conversation as TConversation | null) ?? {}; const endpoint = endpointType ?? _endpoint; + if (!isAssistantsEndpoint(endpoint)) { + if (newConversation) { + newConversation({ + template: { conversationId: conversationId || v4() }, + preset: tPresetSchema.parse(submission.conversation), + }); + } + setIsSubmitting(false); + return; + } + try { const response = await fetch(`${EndpointURLs[endpoint ?? '']}/abort`, { method: 'POST', diff --git a/package-lock.json b/package-lock.json index ec692aaa14..1e98d035cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,14 +58,14 @@ "@azure/storage-blob": "^12.26.0", "@google/generative-ai": "^0.23.0", "@googleapis/youtube": "^20.0.0", - "@keyv/mongo": "^2.1.8", - "@keyv/redis": "^2.8.1", + "@keyv/mongo": "^3.0.1", + "@keyv/redis": "^4.3.3", "@langchain/community": "^0.3.39", "@langchain/core": "^0.3.43", "@langchain/google-genai": "^0.2.2", "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.12", + "@librechat/agents": "^2.4.14", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", @@ -92,8 +92,8 @@ "ioredis": "^5.3.2", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", - "keyv": "^4.5.4", - "keyv-file": "^0.2.0", + "keyv": "^5.3.2", + "keyv-file": "^5.1.2", "klona": "^2.0.6", "librechat-data-provider": "*", "librechat-mcp": "*", @@ -158,6 +158,27 @@ "node": ">=18.0.0" } }, + "api/node_modules/@keyv/mongo": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@keyv/mongo/-/mongo-3.0.1.tgz", + "integrity": "sha512-dNVIhm68mh/CAySN6q7o1vyBRdBRt41nrJXIDlqLhnXOo4l8IAY1Vj5BlTkUfw1BDLuZ9zjb+g1lr/BMRdzNdg==", + "dependencies": { + "mongodb": "^6.8.0" + } + }, + "api/node_modules/@keyv/redis": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-4.3.3.tgz", + "integrity": "sha512-J/uhvKu/Qfh11yMUs+9KdcGCLmWFd3vMxtDVQh2j9cOcnrpnM5jE1xU+K1/kI89czSVEdeMyqTC9gGNtwi3JEQ==", + "dependencies": { + "cluster-key-slot": "^1.1.2", + "keyv": "^5.3.2", + "redis": "^4.7.0" + }, + "engines": { + "node": ">= 18" + } + }, "api/node_modules/@langchain/community": { "version": "0.3.39", "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.39.tgz", @@ -825,6 +846,29 @@ "node": ">= 14" } }, + "api/node_modules/keyv": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz", + "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", + "dependencies": { + "@keyv/serialize": "^1.0.3" + } + }, + "api/node_modules/keyv-file": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-5.1.2.tgz", + "integrity": "sha512-Sx5W55HeSbmsX4BfanJwaJZd3xePoQKxfuysvIhZ3JTPoSeZjApFO1QnuXGVy9hDXpmztS5mm39wlBFOUalVgw==", + "dependencies": { + "@keyv/serialize": "^1.0.1", + "fs-extra": "^4.0.1", + "tslib": "^1.14.1" + } + }, + "api/node_modules/keyv-file/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "api/node_modules/mongodb": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz", @@ -3369,12 +3413,12 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -5384,6 +5428,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.623.0.tgz", "integrity": "sha512-kGYnTzXTMGdjko5+GZ1PvWvfXA7quiOp5iMo5gbh5b55pzIdc918MHN0pvaqplVGWYlaFJF4YzxUT5Nbxd7Xeg==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -5436,6 +5481,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -5448,6 +5494,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -5461,6 +5508,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -5473,6 +5521,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -5486,6 +5535,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -5499,6 +5549,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -7395,6 +7446,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.623.0.tgz", "integrity": "sha512-oEACriysQMnHIVcNp7TD6D1nzgiHfYK0tmMBMbUxgoFuCBkW9g9QYvspHN+S9KgoePfMEXHuPUe9mtG9AH9XeA==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -7444,6 +7496,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.623.0.tgz", "integrity": "sha512-lMFEXCa6ES/FGV7hpyrppT1PiAkqQb51AbG0zVU3TIgI2IO4XX02uzMUXImRSRqRpGymRCbJCaCs9LtKvS/37Q==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -7497,6 +7550,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7509,6 +7563,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7522,6 +7577,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7534,6 +7590,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -7547,6 +7604,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7560,6 +7618,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -7573,6 +7632,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7585,6 +7645,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7598,6 +7659,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7610,6 +7672,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -7623,6 +7686,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7636,6 +7700,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -7649,6 +7714,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.623.0.tgz", "integrity": "sha512-iJNdx76SOw0YjHAUv8aj3HXzSu3TKI7qSGuR+OGATwA/kpJZDd+4+WYBdGtr8YK+hPrGGqhfecuCkEg805O5iA==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -7700,6 +7766,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7712,6 +7779,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7725,6 +7793,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7737,6 +7806,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -7750,6 +7820,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7763,6 +7834,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -7776,6 +7848,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.623.0.tgz", "integrity": "sha512-8Toq3X6trX/67obSdh4K0MFQY4f132bEbr1i0YPDWk/O3KdBt12mLC/sW3aVRnlIs110XMuX9yrWWqJ8fDW10g==", "optional": true, + "peer": true, "dependencies": { "@smithy/core": "^2.3.2", "@smithy/node-config-provider": "^3.1.4", @@ -7796,6 +7869,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7808,6 +7882,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7821,6 +7896,7 @@ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/protocol-http": "^4.1.0", @@ -7840,6 +7916,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7852,6 +7929,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -7865,6 +7943,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7877,6 +7956,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7890,6 +7970,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7902,6 +7983,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -7915,6 +7997,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.623.0.tgz", "integrity": "sha512-sXU2KtWpFzIzE4iffSIUbl4mgbeN1Rta6BnuKtS3rrVrryku9akAxY//pulbsIsYfXRzOwZzULsa+cxQN00lrw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/client-cognito-identity": "3.623.0", "@aws-sdk/types": "3.609.0", @@ -7931,6 +8014,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7943,6 +8027,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -7958,6 +8043,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7970,6 +8056,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.622.0.tgz", "integrity": "sha512-VUHbr24Oll1RK3WR8XLUugLpgK9ZuxEm/NVeVqyFts1Ck9gsKpRg1x4eH7L7tW3SJ4TDEQNMbD7/7J+eoL2svg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/fetch-http-handler": "^3.2.4", @@ -7990,6 +8077,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8003,6 +8091,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8015,6 +8104,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.623.0.tgz", "integrity": "sha512-kvXA1SwGneqGzFwRZNpESitnmaENHGFFuuTvgGwtMe7mzXWuA/LkXdbiHmdyAzOo0iByKTCD8uetuwh3CXy4Pw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-http": "3.622.0", @@ -8040,6 +8130,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8052,6 +8143,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.623.0.tgz", "integrity": "sha512-qDwCOkhbu5PfaQHyuQ+h57HEx3+eFhKdtIw7aISziWkGdFrMe07yIBd7TJqGe4nxXnRF1pfkg05xeOlMId997g==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-http": "3.622.0", @@ -8075,6 +8167,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8087,6 +8180,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -8103,6 +8197,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8115,6 +8210,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.623.0.tgz", "integrity": "sha512-70LZhUb3l7cttEsg4A0S4Jq3qrCT/v5Jfyl8F7w1YZJt5zr3oPPcvDJxo/UYckFz4G4/5BhGa99jK8wMlNE9QA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/client-sso": "3.623.0", "@aws-sdk/token-providers": "3.614.0", @@ -8133,6 +8229,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8145,6 +8242,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -8163,6 +8261,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8175,6 +8274,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.623.0.tgz", "integrity": "sha512-abtlH1hkVWAkzuOX79Q47l0ztWOV2Q7l7J4JwQgzEQm7+zCk5iUAiwqKyDzr+ByCyo4I3IWFjy+e1gBdL7rXQQ==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/client-cognito-identity": "3.623.0", "@aws-sdk/client-sso": "3.623.0", @@ -8202,6 +8302,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8822,6 +8923,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/protocol-http": "^4.1.0", @@ -8837,6 +8939,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8850,6 +8953,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8889,6 +8993,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/types": "^3.3.0", @@ -8903,6 +9008,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8915,6 +9021,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/protocol-http": "^4.1.0", @@ -8930,6 +9037,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8943,6 +9051,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -9402,6 +9511,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", @@ -9418,6 +9528,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -9431,6 +9542,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10149,6 +10261,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/node-config-provider": "^3.1.4", @@ -10166,6 +10279,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10178,6 +10292,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -10676,6 +10791,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -10695,6 +10811,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10742,6 +10859,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/types": "^3.3.0", @@ -10757,6 +10875,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10834,6 +10953,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/types": "^3.3.0", @@ -10846,6 +10966,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10858,6 +10979,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/node-config-provider": "^3.1.4", @@ -10881,6 +11003,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -16440,9 +16563,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -16453,75 +16576,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@keyv/mongo": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@keyv/mongo/-/mongo-2.2.8.tgz", - "integrity": "sha512-2y8RXQDzCUzvhkzjH0bj4+Ur9Ce+x9PjNrV6KnGGpRocexFKVgOYexIegnEc/DBy6HhNyqUlgWOpuFfnhpmF+A==", + "node_modules/@keyv/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", "dependencies": { - "mongodb": "^4.5.0", - "pify": "^5.0.0" - } - }, - "node_modules/@keyv/mongo/node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@keyv/mongo/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/@keyv/mongo/node_modules/mongodb": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.2.tgz", - "integrity": "sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==", - "dependencies": { - "bson": "^4.7.2", - "mongodb-connection-string-url": "^2.6.0", - "socks": "^2.7.1" - }, - "engines": { - "node": ">=12.9.0" - }, - "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", - "@mongodb-js/saslprep": "^1.1.0" - } - }, - "node_modules/@keyv/redis": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-2.8.4.tgz", - "integrity": "sha512-osO4C+i+Gi844wHjvXuHwhl+sDx3289Of309ZlLcj6SJReTLmPXzNiVR81N88wOu5aC+lVFdmx9FUQkkjdbPRQ==", - "dependencies": { - "ioredis": "^5.3.2" - }, - "engines": { - "node": ">= 14" + "buffer": "^6.0.3" } }, "node_modules/@langchain/anthropic": { @@ -17629,9 +17689,9 @@ } }, "node_modules/@langchain/langgraph-sdk": { - "version": "0.0.65", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.65.tgz", - "integrity": "sha512-Zn1FhiKr/mYa1+W5NcuCPWmdTtJS4UZYu+YVEjxgESd0aMX19FTkqjaSV6tFDcRqHHwlIgHloCSqHLkYWF/Zug==", + "version": "0.0.66", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.66.tgz", + "integrity": "sha512-l0V4yfKXhHaTRK/1bKMfZ14k3wWZu27DWTlCUnbYJvdo7os5srhONgPCOqQgpazhi5EhXbW2EVgeu/wLW2zH6Q==", "dependencies": { "@types/json-schema": "^7.0.15", "p-queue": "^6.6.2", @@ -17819,9 +17879,9 @@ } }, "node_modules/@librechat/agents": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.12.tgz", - "integrity": "sha512-m8CEVCjVeQDKXMS0ISG4h4YXU1x51yWGPLCuiNbQI3k+fmDvEFqVuzFi15OhVfoGGQxbKfzGiiF8fsKK2xIKEw==", + "version": "2.4.14", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.14.tgz", + "integrity": "sha512-EYtjjl7/Xb94UZh+6S8XHcbyuqAYrVi41aFk9UFV+8TRTN16ZK/K0zhPix1SGeA8J60Xs3Y1HkZBJdbGMi9X2Q==", "dependencies": { "@langchain/anthropic": "^0.3.16", "@langchain/aws": "^0.1.7", @@ -17844,9 +17904,9 @@ } }, "node_modules/@librechat/agents/node_modules/@langchain/community": { - "version": "0.3.39", - "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.39.tgz", - "integrity": "sha512-8sMJD22bUNlQVoh0b8PgRSpO2Hz5zO6BuZeNY3ElgW3cNVXCoywHW7FBmqbwrygGocJQDee76RqiIN0XQunhTg==", + "version": "0.3.40", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.40.tgz", + "integrity": "sha512-UvpEebdFKJsjFBKeUOvvYHOEFsUcjZnyU1qNirtDajwjzTJlszXtv+Mq8F6w5mJsznpI9x7ZMNzAqydVxMG5hA==", "dependencies": { "@langchain/openai": ">=0.2.0 <0.6.0", "binary-extensions": "^2.2.0", @@ -18368,9 +18428,9 @@ } }, "node_modules/@librechat/agents/node_modules/@langchain/openai": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.5.4.tgz", - "integrity": "sha512-fBIEgaAMs1OHHjSuOwtonhtFTlRyFETAS9y/4SZxlXV5lLdwEU5OAbfaBTcCR0A58rrlmgt8iPnm2IWn4Onjjw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.5.5.tgz", + "integrity": "sha512-QwdZrWcx6FB+UMKQ6+a0M9ZXzeUnZCwXP7ltqCCycPzdfiwxg3TQ6WkSefdEyiPpJcVVq/9HZSxrzGmf18QGyw==", "dependencies": { "js-tiktoken": "^1.0.12", "openai": "^4.87.3", @@ -20752,6 +20812,64 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" } }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz", @@ -21279,6 +21397,7 @@ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21292,6 +21411,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21381,6 +21501,7 @@ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", "optional": true, + "peer": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", @@ -21397,6 +21518,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21409,6 +21531,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21422,6 +21545,7 @@ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", "optional": true, + "peer": true, "dependencies": { "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-retry": "^3.0.15", @@ -21443,6 +21567,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21455,6 +21580,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21468,6 +21594,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21480,6 +21607,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -21493,6 +21621,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21506,6 +21635,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -21519,6 +21649,7 @@ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", "optional": true, + "peer": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/property-provider": "^3.1.3", @@ -21535,6 +21666,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21672,6 +21804,7 @@ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", "optional": true, + "peer": true, "dependencies": { "@smithy/protocol-http": "^4.1.0", "@smithy/querystring-builder": "^3.0.3", @@ -21685,6 +21818,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21698,6 +21832,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21725,6 +21860,7 @@ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "@smithy/util-buffer-from": "^3.0.0", @@ -21740,6 +21876,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21752,6 +21889,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21764,6 +21902,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -21777,6 +21916,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -21842,6 +21982,7 @@ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21852,6 +21993,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21927,6 +22069,7 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", "optional": true, + "peer": true, "dependencies": { "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", @@ -21941,6 +22084,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21954,6 +22098,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21966,6 +22111,7 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", "optional": true, + "peer": true, "dependencies": { "@smithy/middleware-serde": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", @@ -21984,6 +22130,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21996,6 +22143,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22009,6 +22157,7 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/protocol-http": "^4.1.0", @@ -22029,6 +22178,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22042,6 +22192,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22054,6 +22205,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22067,6 +22219,7 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22080,6 +22233,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22092,6 +22246,7 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22105,6 +22260,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22117,6 +22273,7 @@ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", @@ -22132,6 +22289,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22144,6 +22302,7 @@ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", "optional": true, + "peer": true, "dependencies": { "@smithy/abort-controller": "^3.1.1", "@smithy/protocol-http": "^4.1.0", @@ -22160,6 +22319,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22173,6 +22333,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22185,6 +22346,7 @@ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22198,6 +22360,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22237,6 +22400,7 @@ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "@smithy/util-uri-escape": "^3.0.0", @@ -22251,6 +22415,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22263,6 +22428,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22275,6 +22441,7 @@ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22288,6 +22455,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22300,6 +22468,7 @@ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0" }, @@ -22312,6 +22481,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22324,6 +22494,7 @@ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22337,6 +22508,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22395,6 +22567,7 @@ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", "optional": true, + "peer": true, "dependencies": { "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-stack": "^3.0.3", @@ -22412,6 +22585,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22425,6 +22599,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22448,6 +22623,7 @@ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", "optional": true, + "peer": true, "dependencies": { "@smithy/querystring-parser": "^3.0.3", "@smithy/types": "^3.3.0", @@ -22459,6 +22635,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22471,6 +22648,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", @@ -22485,6 +22663,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22497,6 +22676,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -22510,6 +22690,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -22523,6 +22704,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" } @@ -22532,6 +22714,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22556,6 +22739,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22568,6 +22752,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", "optional": true, + "peer": true, "dependencies": { "@smithy/property-provider": "^3.1.3", "@smithy/smithy-client": "^3.2.0", @@ -22584,6 +22769,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22596,6 +22782,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", "optional": true, + "peer": true, "dependencies": { "@smithy/config-resolver": "^3.0.5", "@smithy/credential-provider-imds": "^3.2.0", @@ -22614,6 +22801,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22626,6 +22814,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", "optional": true, + "peer": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", @@ -22640,6 +22829,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22677,6 +22867,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", "optional": true, + "peer": true, "dependencies": { "@smithy/service-error-classification": "^3.0.3", "@smithy/types": "^3.3.0", @@ -22691,6 +22882,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22703,6 +22895,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", "optional": true, + "peer": true, "dependencies": { "@smithy/fetch-http-handler": "^3.2.4", "@smithy/node-http-handler": "^3.1.4", @@ -22722,6 +22915,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22734,6 +22928,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22746,6 +22941,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -22759,6 +22955,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22771,6 +22968,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -23555,15 +23753,6 @@ "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" }, - "node_modules/@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", - "dependencies": { - "@types/node": "*", - "@types/webidl-conversions": "*" - } - }, "node_modules/@types/winston": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", @@ -24859,9 +25048,9 @@ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, "node_modules/bignumber.js": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.2.1.tgz", + "integrity": "sha512-+NzaKgOUvInq9TIUZ1+DRspzf/HApkCwD4btfuasFTdrfnOxqx853TgDpMolp+uv4RpRp7bPcEU2zKr9+fRmyw==", "engines": { "node": "*" } @@ -26896,6 +27085,29 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "peer": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", @@ -28907,6 +29119,14 @@ "node": ">=12" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -30156,6 +30376,8 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "optional": true, + "peer": true, "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -30167,7 +30389,9 @@ "node_modules/ip-address/node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "optional": true, + "peer": true }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -30901,9 +31125,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -31729,7 +31953,9 @@ "node_modules/jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "optional": true, + "peer": true }, "node_modules/jschardet": { "version": "3.1.4", @@ -31976,21 +32202,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/keyv-file": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-0.2.0.tgz", - "integrity": "sha512-zUQ11eZRmilEUpV1gJSj8mBAHjyXpleQo1iCS0khb+GFRhiPfwavWgn4eDUKNlOyMZzmExnISl8HE1hNbim0gw==", - "dependencies": { - "debug": "^4.1.1", - "fs-extra": "^4.0.1", - "tslib": "^1.9.3" - } - }, - "node_modules/keyv-file/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -32997,15 +33208,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -34489,15 +34697,6 @@ "node": "*" } }, - "node_modules/mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, "node_modules/moo-color": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", @@ -35754,17 +35953,6 @@ "node": ">=0.10" } }, - "node_modules/pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -38078,6 +38266,22 @@ "node": ">=8" } }, + "node_modules/redis": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.0", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, "node_modules/redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", @@ -39713,6 +39917,8 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "peer": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -39728,6 +39934,8 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "optional": true, + "peer": true, "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -40919,6 +41127,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "devOptional": true, "dependencies": { "punycode": "^2.1.1" }, @@ -42316,6 +42525,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "devOptional": true, "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" diff --git a/packages/mcp/src/manager.ts b/packages/mcp/src/manager.ts index 83a5f8fe90..d5c5a3138f 100644 --- a/packages/mcp/src/manager.ts +++ b/packages/mcp/src/manager.ts @@ -151,11 +151,14 @@ export class MCPManager { } /** Check for and disconnect idle connections */ - private checkIdleConnections(): void { + private checkIdleConnections(currentUserId?: string): void { const now = Date.now(); // Iterate through all users to check for idle ones for (const [userId, lastActivity] of this.userLastActivity.entries()) { + if (currentUserId && currentUserId === userId) { + continue; + } if (now - lastActivity > this.USER_CONNECTION_IDLE_TIMEOUT) { this.logger.info( `[MCP][User: ${userId}] User idle for too long. Disconnecting all connections...`, From 339882eea47799686cb012db16379d860149d11e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sat, 12 Apr 2025 20:53:38 -0400 Subject: [PATCH 12/75] =?UTF-8?q?=F0=9F=92=BE=20refactor:=20Enhance=20Memo?= =?UTF-8?q?ry=20In=20Image=20Encodings=20&=20Client=20Disposal=20(#6852)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 💾 chore: Clear Additional Properties in `disposeClient` * refactor: stream handling and base64 conversion in encode.js to better free memory --- api/server/cleanup.js | 144 +++++++++++++++++++++ api/server/services/Files/images/encode.js | 87 ++++++++----- 2 files changed, 197 insertions(+), 34 deletions(-) diff --git a/api/server/cleanup.js b/api/server/cleanup.js index 508d2e8129..6a9abb8180 100644 --- a/api/server/cleanup.js +++ b/api/server/cleanup.js @@ -55,6 +55,9 @@ function disposeClient(client) { if (client.responseMessageId) { client.responseMessageId = null; } + if (client.message_file_map) { + client.message_file_map = null; + } if (client.clientName) { client.clientName = null; } @@ -79,6 +82,147 @@ function disposeClient(client) { if (client.outputTokensKey) { client.outputTokensKey = null; } + if (client.skipSaveUserMessage !== undefined) { + client.skipSaveUserMessage = null; + } + if (client.visionMode) { + client.visionMode = null; + } + if (client.continued !== undefined) { + client.continued = null; + } + if (client.fetchedConvo !== undefined) { + client.fetchedConvo = null; + } + if (client.previous_summary) { + client.previous_summary = null; + } + if (client.metadata) { + client.metadata = null; + } + if (client.isVisionModel) { + client.isVisionModel = null; + } + if (client.isChatCompletion !== undefined) { + client.isChatCompletion = null; + } + if (client.contextHandlers) { + client.contextHandlers = null; + } + if (client.augmentedPrompt) { + client.augmentedPrompt = null; + } + if (client.systemMessage) { + client.systemMessage = null; + } + if (client.azureEndpoint) { + client.azureEndpoint = null; + } + if (client.langchainProxy) { + client.langchainProxy = null; + } + if (client.isOmni !== undefined) { + client.isOmni = null; + } + if (client.runManager) { + client.runManager = null; + } + // Properties specific to AnthropicClient + if (client.message_start) { + client.message_start = null; + } + if (client.message_delta) { + client.message_delta = null; + } + if (client.isClaude3 !== undefined) { + client.isClaude3 = null; + } + if (client.useMessages !== undefined) { + client.useMessages = null; + } + if (client.isLegacyOutput !== undefined) { + client.isLegacyOutput = null; + } + if (client.supportsCacheControl !== undefined) { + client.supportsCacheControl = null; + } + // Properties specific to GoogleClient + if (client.serviceKey) { + client.serviceKey = null; + } + if (client.project_id) { + client.project_id = null; + } + if (client.client_email) { + client.client_email = null; + } + if (client.private_key) { + client.private_key = null; + } + if (client.access_token) { + client.access_token = null; + } + if (client.reverseProxyUrl) { + client.reverseProxyUrl = null; + } + if (client.authHeader) { + client.authHeader = null; + } + if (client.isGenerativeModel !== undefined) { + client.isGenerativeModel = null; + } + // Properties specific to OpenAIClient + if (client.ChatGPTClient) { + client.ChatGPTClient = null; + } + if (client.completionsUrl) { + client.completionsUrl = null; + } + if (client.shouldSummarize !== undefined) { + client.shouldSummarize = null; + } + if (client.isOllama !== undefined) { + client.isOllama = null; + } + if (client.FORCE_PROMPT !== undefined) { + client.FORCE_PROMPT = null; + } + if (client.isChatGptModel !== undefined) { + client.isChatGptModel = null; + } + if (client.isUnofficialChatGptModel !== undefined) { + client.isUnofficialChatGptModel = null; + } + if (client.useOpenRouter !== undefined) { + client.useOpenRouter = null; + } + if (client.startToken) { + client.startToken = null; + } + if (client.endToken) { + client.endToken = null; + } + if (client.userLabel) { + client.userLabel = null; + } + if (client.chatGptLabel) { + client.chatGptLabel = null; + } + if (client.modelLabel) { + client.modelLabel = null; + } + if (client.modelOptions) { + client.modelOptions = null; + } + if (client.defaultVisionModel) { + client.defaultVisionModel = null; + } + if (client.maxPromptTokens) { + client.maxPromptTokens = null; + } + if (client.maxResponseTokens) { + client.maxResponseTokens = null; + } if (client.run) { // Break circular references in run if (client.run.Graph) { diff --git a/api/server/services/Files/images/encode.js b/api/server/services/Files/images/encode.js index f733a0d6d6..154941fd89 100644 --- a/api/server/services/Files/images/encode.js +++ b/api/server/services/Files/images/encode.js @@ -10,6 +10,44 @@ const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { logAxiosError } = require('~/utils'); const { logger } = require('~/config'); +/** + * Converts a readable stream to a base64 encoded string. + * + * @param {NodeJS.ReadableStream} stream - The readable stream to convert. + * @param {boolean} [destroyStream=true] - Whether to destroy the stream after processing. + * @returns {Promise} - Promise resolving to the base64 encoded content. + */ +async function streamToBase64(stream, destroyStream = true) { + return new Promise((resolve, reject) => { + const chunks = []; + + stream.on('data', (chunk) => { + chunks.push(chunk); + }); + + stream.on('end', () => { + try { + const buffer = Buffer.concat(chunks); + const base64Data = buffer.toString('base64'); + chunks.length = 0; // Clear the array + resolve(base64Data); + } catch (err) { + reject(err); + } + }); + + stream.on('error', (error) => { + chunks.length = 0; + reject(error); + }); + }).finally(() => { + // Clean up the stream if required + if (destroyStream && stream.destroy && typeof stream.destroy === 'function') { + stream.destroy(); + } + }); +} + /** * Fetches an image from a URL and returns its base64 representation. * @@ -23,7 +61,9 @@ async function fetchImageToBase64(url) { const response = await axios.get(url, { responseType: 'arraybuffer', }); - return Buffer.from(response.data).toString('base64'); + const base64Data = Buffer.from(response.data).toString('base64'); + response.data = null; + return base64Data; } catch (error) { const message = 'Error fetching image to convert to base64'; throw new Error(logAxiosError({ message, error })); @@ -89,38 +129,15 @@ async function encodeAndFormat(req, files, endpoint, mode) { if (blobStorageSources.has(source)) { try { const downloadStream = encodingMethods[source].getDownloadStream; - const stream = await downloadStream(req, file.filepath); - const streamPromise = new Promise((resolve, reject) => { - /** @type {Uint8Array[]} */ - const chunks = []; - stream.on('readable', () => { - let chunk; - while (null !== (chunk = stream.read())) { - chunks.push(chunk); - } - }); - - stream.on('end', () => { - const buffer = Buffer.concat(chunks); - const base64Data = buffer.toString('base64'); - resolve(base64Data); - }); - stream.on('error', (error) => { - reject(error); - }); - }); - const base64Data = await streamPromise; + let stream = await downloadStream(req, file.filepath); + let base64Data = await streamToBase64(stream); + stream = null; promises.push([file, base64Data]); + base64Data = null; continue; } catch (error) { - logger.error( - `Error processing blob storage file stream for ${file.name} base64 payload:`, - error, - ); - continue; + // Error handling code } - - /* Google & Anthropic don't support passing URLs to payload */ } else if (source !== FileSources.local && base64Only.has(endpoint)) { const [_file, imageURL] = await preparePayload(req, file); promises.push([_file, await fetchImageToBase64(imageURL)]); @@ -137,6 +154,7 @@ async function encodeAndFormat(req, files, endpoint, mode) { /** @type {Array<[MongoFile, string]>} */ const formattedImages = await Promise.all(promises); + promises.length = 0; for (const [file, imageContent] of formattedImages) { const fileMetadata = { @@ -169,8 +187,8 @@ async function encodeAndFormat(req, files, endpoint, mode) { }; if (mode === VisionModes.agents) { - result.image_urls.push(imagePart); - result.files.push(fileMetadata); + result.image_urls.push({ ...imagePart }); + result.files.push({ ...fileMetadata }); continue; } @@ -192,10 +210,11 @@ async function encodeAndFormat(req, files, endpoint, mode) { delete imagePart.image_url; } - result.image_urls.push(imagePart); - result.files.push(fileMetadata); + result.image_urls.push({ ...imagePart }); + result.files.push({ ...fileMetadata }); } - return result; + formattedImages.length = 0; + return { ...result }; } module.exports = { From 64bd373bc87659d561dbfed6e02e8f0607a5c5f1 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 13 Apr 2025 23:01:55 -0400 Subject: [PATCH 13/75] =?UTF-8?q?=F0=9F=94=A7=20fix:=20Keyv=20and=20Proxy?= =?UTF-8?q?=20Issues,=20and=20More=20Memory=20Optimizations=20(#6867)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update @librechat/agents dependency to version 2.4.15 * refactor: Prevent memory leaks by nullifying boundModel.client in disposeClient function * fix: use of proxy, use undici * chore: update @librechat/agents dependency to version 2.4.16 * Revert "fix: use of proxy, use undici" This reverts commit 83153cd58268e8018ae960d232bd17413983d667. * fix: ensure fetch is imported for HTTP requests * fix: replace direct OpenAI import with CustomOpenAIClient from @librechat/agents * fix: update keyv peer dependency to version 5.3.2 * fix: update keyv dependency to version 5.3.2 * refactor: replace KeyvMongo with custom implementation and update flow state manager usage * fix: update @librechat/agents dependency to version 2.4.17 * ci: update OpenAIClient tests to use CustomOpenAIClient from @librechat/agents * refactor: remove KeyvMongo mock and related dependencies --- api/app/clients/OpenAIClient.js | 3 +- api/app/clients/generators.js | 1 + api/app/clients/specs/BaseClient.test.js | 2 +- api/app/clients/specs/OpenAIClient.test.js | 73 +-- api/cache/keyvMongo.js | 269 +++++++- api/config/index.js | 6 +- api/jest.config.js | 1 - api/package.json | 3 +- api/server/cleanup.js | 3 + api/server/controllers/agents/client.js | 2 + api/server/routes/actions.js | 5 +- api/server/services/ActionService.js | 7 +- api/test/__mocks__/KeyvMongo.js | 48 -- package-lock.json | 684 ++------------------- packages/data-schemas/package.json | 2 +- packages/mcp/package.json | 2 +- packages/mcp/src/flow/manager.spec.ts | 2 +- packages/mcp/src/flow/manager.ts | 5 +- 18 files changed, 375 insertions(+), 743 deletions(-) delete mode 100644 api/test/__mocks__/KeyvMongo.js diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index 8c58d70f70..6cebdff146 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -1,7 +1,6 @@ -const OpenAI = require('openai'); const { OllamaClient } = require('./OllamaClient'); const { HttpsProxyAgent } = require('https-proxy-agent'); -const { SplitStreamHandler } = require('@librechat/agents'); +const { SplitStreamHandler, CustomOpenAIClient: OpenAI } = require('@librechat/agents'); const { Constants, ImageDetail, diff --git a/api/app/clients/generators.js b/api/app/clients/generators.js index 4d3988bf34..971ac16da2 100644 --- a/api/app/clients/generators.js +++ b/api/app/clients/generators.js @@ -1,3 +1,4 @@ +const fetch = require('node-fetch'); const { GraphEvents } = require('@librechat/agents'); const { logger, sendEvent } = require('~/config'); diff --git a/api/app/clients/specs/BaseClient.test.js b/api/app/clients/specs/BaseClient.test.js index c9be50d3de..d620d5f647 100644 --- a/api/app/clients/specs/BaseClient.test.js +++ b/api/app/clients/specs/BaseClient.test.js @@ -32,7 +32,7 @@ jest.mock('~/models', () => ({ const { getConvo, saveConvo } = require('~/models'); -jest.mock('@langchain/openai', () => { +jest.mock('@librechat/agents', () => { return { ChatOpenAI: jest.fn().mockImplementation(() => { return {}; diff --git a/api/app/clients/specs/OpenAIClient.test.js b/api/app/clients/specs/OpenAIClient.test.js index adc290486a..579f636eef 100644 --- a/api/app/clients/specs/OpenAIClient.test.js +++ b/api/app/clients/specs/OpenAIClient.test.js @@ -1,9 +1,7 @@ jest.mock('~/cache/getLogStores'); require('dotenv').config(); -const OpenAI = require('openai'); -const getLogStores = require('~/cache/getLogStores'); const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source'); -const { genAzureChatCompletion } = require('~/utils/azureUtils'); +const getLogStores = require('~/cache/getLogStores'); const OpenAIClient = require('../OpenAIClient'); jest.mock('meilisearch'); @@ -36,19 +34,21 @@ jest.mock('~/models', () => ({ updateFileUsage: jest.fn(), })); -jest.mock('@langchain/openai', () => { - return { - ChatOpenAI: jest.fn().mockImplementation(() => { - return {}; - }), - }; +// Import the actual module but mock specific parts +const agents = jest.requireActual('@librechat/agents'); +const { CustomOpenAIClient } = agents; + +// Also mock ChatOpenAI to prevent real API calls +agents.ChatOpenAI = jest.fn().mockImplementation(() => { + return {}; +}); +agents.AzureChatOpenAI = jest.fn().mockImplementation(() => { + return {}; }); -jest.mock('openai'); - -jest.spyOn(OpenAI, 'constructor').mockImplementation(function (...options) { - // We can add additional logic here if needed - return new OpenAI(...options); +// Mock only the CustomOpenAIClient constructor +jest.spyOn(CustomOpenAIClient, 'constructor').mockImplementation(function (...options) { + return new CustomOpenAIClient(...options); }); const finalChatCompletion = jest.fn().mockResolvedValue({ @@ -120,7 +120,13 @@ const create = jest.fn().mockResolvedValue({ ], }); -OpenAI.mockImplementation(() => ({ +// Mock the implementation of CustomOpenAIClient instances +jest.spyOn(CustomOpenAIClient.prototype, 'constructor').mockImplementation(function () { + return this; +}); + +// Create a mock for the CustomOpenAIClient class +const mockCustomOpenAIClient = jest.fn().mockImplementation(() => ({ beta: { chat: { completions: { @@ -135,6 +141,8 @@ OpenAI.mockImplementation(() => ({ }, })); +CustomOpenAIClient.mockImplementation = mockCustomOpenAIClient; + describe('OpenAIClient', () => { beforeEach(() => { const mockCache = { @@ -559,41 +567,6 @@ describe('OpenAIClient', () => { expect(requestBody).toHaveProperty('model'); expect(requestBody.model).toBe(model); }); - - it('[Azure OpenAI] should call chatCompletion and OpenAI.stream with correct args', async () => { - // Set a default model - process.env.AZURE_OPENAI_DEFAULT_MODEL = 'gpt4-turbo'; - - const onProgress = jest.fn().mockImplementation(() => ({})); - client.azure = defaultAzureOptions; - const chatCompletion = jest.spyOn(client, 'chatCompletion'); - await client.sendMessage('Hi mom!', { - replaceOptions: true, - ...defaultOptions, - modelOptions: { model: 'gpt4-turbo', stream: true }, - onProgress, - azure: defaultAzureOptions, - }); - - expect(chatCompletion).toHaveBeenCalled(); - expect(chatCompletion.mock.calls.length).toBe(1); - - const chatCompletionArgs = chatCompletion.mock.calls[0][0]; - const { payload } = chatCompletionArgs; - - expect(payload[0].role).toBe('user'); - expect(payload[0].content).toBe('Hi mom!'); - - // Azure OpenAI does not use the model property, and will error if it's passed - // This check ensures the model property is not present - const streamArgs = stream.mock.calls[0][0]; - expect(streamArgs).not.toHaveProperty('model'); - - // Check if the baseURL is correct - const constructorArgs = OpenAI.mock.calls[0][0]; - const expectedURL = genAzureChatCompletion(defaultAzureOptions).split('/chat')[0]; - expect(constructorArgs.baseURL).toBe(expectedURL); - }); }); describe('checkVisionRequest functionality', () => { diff --git a/api/cache/keyvMongo.js b/api/cache/keyvMongo.js index 3321be56fe..1606e98eb8 100644 --- a/api/cache/keyvMongo.js +++ b/api/cache/keyvMongo.js @@ -1,9 +1,272 @@ -const { KeyvMongo } = require('@keyv/mongo'); +// api/cache/keyvMongo.js +const mongoose = require('mongoose'); +const EventEmitter = require('events'); +const { GridFSBucket } = require('mongodb'); const { logger } = require('~/config'); -const { MONGO_URI } = process.env ?? {}; +const storeMap = new Map(); + +class KeyvMongoCustom extends EventEmitter { + constructor(url, options = {}) { + super(); + + url = url || {}; + if (typeof url === 'string') { + url = { url }; + } + if (url.uri) { + url = { url: url.uri, ...url }; + } + + this.opts = { + url: 'mongodb://127.0.0.1:27017', + collection: 'keyv', + ...url, + ...options, + }; + + this.ttlSupport = false; + + // Filter valid options + const keyvMongoKeys = new Set([ + 'url', + 'collection', + 'namespace', + 'serialize', + 'deserialize', + 'uri', + 'useGridFS', + 'dialect', + ]); + this.opts = Object.fromEntries(Object.entries(this.opts).filter(([k]) => keyvMongoKeys.has(k))); + } + + // Helper to access the store WITHOUT storing a promise on the instance + _getClient() { + const storeKey = `${this.opts.collection}:${this.opts.useGridFS ? 'gridfs' : 'collection'}`; + + // If we already have the store initialized, return it directly + if (storeMap.has(storeKey)) { + return Promise.resolve(storeMap.get(storeKey)); + } + + // Check mongoose connection state + if (mongoose.connection.readyState !== 1) { + return Promise.reject( + new Error('Mongoose connection not ready. Ensure connectDb() is called first.'), + ); + } + + try { + const db = mongoose.connection.db; + let client; + + if (this.opts.useGridFS) { + const bucket = new GridFSBucket(db, { + readPreference: this.opts.readPreference, + bucketName: this.opts.collection, + }); + const store = db.collection(`${this.opts.collection}.files`); + client = { bucket, store, db }; + } else { + const collection = this.opts.collection || 'keyv'; + const store = db.collection(collection); + client = { store, db }; + } + + storeMap.set(storeKey, client); + return Promise.resolve(client); + } catch (error) { + this.emit('error', error); + return Promise.reject(error); + } + } + + async get(key) { + const client = await this._getClient(); + + if (this.opts.useGridFS) { + await client.store.updateOne( + { + filename: key, + }, + { + $set: { + 'metadata.lastAccessed': new Date(), + }, + }, + ); + + const stream = client.bucket.openDownloadStreamByName(key); + + return new Promise((resolve) => { + const resp = []; + stream.on('error', () => { + resolve(undefined); + }); + + stream.on('end', () => { + const data = Buffer.concat(resp).toString('utf8'); + resolve(data); + }); + + stream.on('data', (chunk) => { + resp.push(chunk); + }); + }); + } + + const document = await client.store.findOne({ key: { $eq: key } }); + + if (!document) { + return undefined; + } + + return document.value; + } + + async getMany(keys) { + const client = await this._getClient(); + + if (this.opts.useGridFS) { + const promises = []; + for (const key of keys) { + promises.push(this.get(key)); + } + + const values = await Promise.allSettled(promises); + const data = []; + for (const value of values) { + data.push(value.value); + } + + return data; + } + + const values = await client.store + .find({ key: { $in: keys } }) + .project({ _id: 0, value: 1, key: 1 }) + .toArray(); + + const results = [...keys]; + let i = 0; + for (const key of keys) { + const rowIndex = values.findIndex((row) => row.key === key); + results[i] = rowIndex > -1 ? values[rowIndex].value : undefined; + i++; + } + + return results; + } + + async set(key, value, ttl) { + const client = await this._getClient(); + const expiresAt = typeof ttl === 'number' ? new Date(Date.now() + ttl) : null; + + if (this.opts.useGridFS) { + const stream = client.bucket.openUploadStream(key, { + metadata: { + expiresAt, + lastAccessed: new Date(), + }, + }); + + return new Promise((resolve) => { + stream.on('finish', () => { + resolve(stream); + }); + stream.end(value); + }); + } + + await client.store.updateOne( + { key: { $eq: key } }, + { $set: { key, value, expiresAt } }, + { upsert: true }, + ); + } + + async delete(key) { + if (typeof key !== 'string') { + return false; + } + + const client = await this._getClient(); + + if (this.opts.useGridFS) { + try { + const bucket = new GridFSBucket(client.db, { + bucketName: this.opts.collection, + }); + const files = await bucket.find({ filename: key }).toArray(); + await client.bucket.delete(files[0]._id); + return true; + } catch { + return false; + } + } + + const object = await client.store.deleteOne({ key: { $eq: key } }); + return object.deletedCount > 0; + } + + async deleteMany(keys) { + const client = await this._getClient(); + + if (this.opts.useGridFS) { + const bucket = new GridFSBucket(client.db, { + bucketName: this.opts.collection, + }); + const files = await bucket.find({ filename: { $in: keys } }).toArray(); + if (files.length === 0) { + return false; + } + + await Promise.all(files.map(async (file) => client.bucket.delete(file._id))); + return true; + } + + const object = await client.store.deleteMany({ key: { $in: keys } }); + return object.deletedCount > 0; + } + + async clear() { + const client = await this._getClient(); + + if (this.opts.useGridFS) { + try { + await client.bucket.drop(); + } catch (error) { + // Throw error if not "namespace not found" error + if (!(error.code === 26)) { + throw error; + } + } + } + + await client.store.deleteMany({ + key: { $regex: this.namespace ? `^${this.namespace}:*` : '' }, + }); + } + + async has(key) { + const client = await this._getClient(); + const filter = { [this.opts.useGridFS ? 'filename' : 'key']: { $eq: key } }; + const document = await client.store.countDocuments(filter, { limit: 1 }); + return document !== 0; + } + + // No-op disconnect + async disconnect() { + // This is a no-op since we don't want to close the shared mongoose connection + return true; + } +} + +const keyvMongo = new KeyvMongoCustom({ + collection: 'logs', +}); -const keyvMongo = new KeyvMongo(MONGO_URI, { collection: 'logs' }); keyvMongo.on('error', (err) => logger.error('KeyvMongo connection error:', err)); module.exports = keyvMongo; diff --git a/api/config/index.js b/api/config/index.js index 57bc45fe1a..e238f700be 100644 --- a/api/config/index.js +++ b/api/config/index.js @@ -24,12 +24,12 @@ function getMCPManager(userId) { } /** - * @param {(key: string) => Keyv} getLogStores + * @param {Keyv} flowsCache * @returns {FlowStateManager} */ -function getFlowStateManager(getLogStores) { +function getFlowStateManager(flowsCache) { if (!flowManager) { - flowManager = new FlowStateManager(getLogStores(CacheKeys.FLOWS), { + flowManager = new FlowStateManager(flowsCache, { ttl: Time.ONE_MINUTE * 3, logger, }); diff --git a/api/jest.config.js b/api/jest.config.js index ec44bd7f56..2df7790b7b 100644 --- a/api/jest.config.js +++ b/api/jest.config.js @@ -5,7 +5,6 @@ module.exports = { coverageDirectory: 'coverage', setupFiles: [ './test/jestSetup.js', - './test/__mocks__/KeyvMongo.js', './test/__mocks__/logger.js', './test/__mocks__/fetchEventSource.js', ], diff --git a/api/package.json b/api/package.json index 73b415ad79..33fb734122 100644 --- a/api/package.json +++ b/api/package.json @@ -42,14 +42,13 @@ "@azure/storage-blob": "^12.26.0", "@google/generative-ai": "^0.23.0", "@googleapis/youtube": "^20.0.0", - "@keyv/mongo": "^3.0.1", "@keyv/redis": "^4.3.3", "@langchain/community": "^0.3.39", "@langchain/core": "^0.3.43", "@langchain/google-genai": "^0.2.2", "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.14", + "@librechat/agents": "^2.4.17", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", diff --git a/api/server/cleanup.js b/api/server/cleanup.js index 6a9abb8180..6d5b77196a 100644 --- a/api/server/cleanup.js +++ b/api/server/cleanup.js @@ -238,6 +238,9 @@ function disposeClient(client) { client.run.Graph.streamBuffer = null; client.run.Graph.clientOptions = null; client.run.Graph.graphState = null; + if (client.run.Graph.boundModel?.client) { + client.run.Graph.boundModel.client = null; + } client.run.Graph.boundModel = null; client.run.Graph.systemMessage = null; client.run.Graph.reasoningKey = null; diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index a0de98c5f1..8a128bcdba 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -787,6 +787,8 @@ class AgentClient extends BaseClient { [Callback.TOOL_ERROR]: logToolError, }, }); + + config.signal = null; }; await runAgent(this.options.agent, initialMessages); diff --git a/api/server/routes/actions.js b/api/server/routes/actions.js index 28845e3f15..dc474d1a67 100644 --- a/api/server/routes/actions.js +++ b/api/server/routes/actions.js @@ -1,5 +1,6 @@ const express = require('express'); const jwt = require('jsonwebtoken'); +const { CacheKeys } = require('librechat-data-provider'); const { getAccessToken } = require('~/server/services/TokenService'); const { logger, getFlowStateManager } = require('~/config'); const { getLogStores } = require('~/cache'); @@ -19,8 +20,8 @@ const JWT_SECRET = process.env.JWT_SECRET; router.get('/:action_id/oauth/callback', async (req, res) => { const { action_id } = req.params; const { code, state } = req.query; - - const flowManager = getFlowStateManager(getLogStores); + const flowsCache = getLogStores(CacheKeys.FLOWS); + const flowManager = getFlowStateManager(flowsCache); let identifier = action_id; try { let decodedState; diff --git a/api/server/services/ActionService.js b/api/server/services/ActionService.js index 8b13e2609f..1255b3db49 100644 --- a/api/server/services/ActionService.js +++ b/api/server/services/ActionService.js @@ -74,7 +74,6 @@ async function domainParser(domain, inverse = false) { if (!domain) { return; } - const domainsCache = getLogStores(CacheKeys.ENCODED_DOMAINS); const cachedDomain = await domainsCache.get(domain); if (inverse && cachedDomain) { @@ -188,7 +187,8 @@ async function createActionTool({ expires_at: Date.now() + Time.TWO_MINUTES, }, }; - const flowManager = getFlowStateManager(getLogStores); + const flowsCache = getLogStores(CacheKeys.FLOWS); + const flowManager = getFlowStateManager(flowsCache); await flowManager.createFlowWithHandler( `${identifier}:oauth_login:${config.metadata.thread_id}:${config.metadata.run_id}`, 'oauth_login', @@ -264,7 +264,8 @@ async function createActionTool({ encrypted_oauth_client_id: encrypted.oauth_client_id, encrypted_oauth_client_secret: encrypted.oauth_client_secret, }); - const flowManager = getFlowStateManager(getLogStores); + const flowsCache = getLogStores(CacheKeys.FLOWS); + const flowManager = getFlowStateManager(flowsCache); const refreshData = await flowManager.createFlowWithHandler( `${identifier}:refresh`, 'oauth_refresh', diff --git a/api/test/__mocks__/KeyvMongo.js b/api/test/__mocks__/KeyvMongo.js deleted file mode 100644 index f990ae3899..0000000000 --- a/api/test/__mocks__/KeyvMongo.js +++ /dev/null @@ -1,48 +0,0 @@ -jest.mock('@keyv/mongo', () => { - const EventEmitter = require('events'); - class KeyvMongo extends EventEmitter { - constructor(url = 'mongodb://127.0.0.1:27017', options) { - super(); - this.ttlSupport = false; - url = url ?? {}; - if (typeof url === 'string') { - url = { url }; - } - if (url.uri) { - url = { url: url.uri, ...url }; - } - this.opts = { - url, - collection: 'keyv', - ...url, - ...options, - }; - - // In-memory store for tests - this.store = new Map(); - } - - async get(key) { - return this.store.get(key); - } - - async set(key, value, ttl) { - this.store.set(key, value); - return true; - } - - async delete(key) { - return this.store.delete(key); - } - - async clear() { - this.store.clear(); - return true; - } - } - - // Create a store factory function for the test suite - const store = () => new KeyvMongo(); - - return { KeyvMongo }; -}); diff --git a/package-lock.json b/package-lock.json index 1e98d035cb..b60145153f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,14 +58,13 @@ "@azure/storage-blob": "^12.26.0", "@google/generative-ai": "^0.23.0", "@googleapis/youtube": "^20.0.0", - "@keyv/mongo": "^3.0.1", "@keyv/redis": "^4.3.3", "@langchain/community": "^0.3.39", "@langchain/core": "^0.3.43", "@langchain/google-genai": "^0.2.2", "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.14", + "@librechat/agents": "^2.4.17", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", @@ -158,14 +157,6 @@ "node": ">=18.0.0" } }, - "api/node_modules/@keyv/mongo": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@keyv/mongo/-/mongo-3.0.1.tgz", - "integrity": "sha512-dNVIhm68mh/CAySN6q7o1vyBRdBRt41nrJXIDlqLhnXOo4l8IAY1Vj5BlTkUfw1BDLuZ9zjb+g1lr/BMRdzNdg==", - "dependencies": { - "mongodb": "^6.8.0" - } - }, "api/node_modules/@keyv/redis": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-4.3.3.tgz", @@ -720,6 +711,31 @@ "@langchain/core": ">=0.3.39 <0.4.0" } }, + "api/node_modules/@librechat/agents": { + "version": "2.4.17", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.17.tgz", + "integrity": "sha512-lGgOqovIqzaFtO3wUe8LShckJYmFGxa/RAn1edUxmQgK76F4QK53POFivzQhYUxso9z4SNvu1b8q/+vq7lWYaw==", + "dependencies": { + "@langchain/anthropic": "^0.3.16", + "@langchain/aws": "^0.1.7", + "@langchain/community": "^0.3.39", + "@langchain/core": "^0.3.43", + "@langchain/deepseek": "^0.0.1", + "@langchain/google-genai": "^0.2.2", + "@langchain/google-vertexai": "^0.2.3", + "@langchain/langgraph": "^0.2.62", + "@langchain/mistralai": "^0.2.0", + "@langchain/ollama": "^0.2.0", + "@langchain/openai": "^0.5.4", + "@langchain/xai": "^0.0.2", + "dotenv": "^16.4.7", + "https-proxy-agent": "^7.0.6", + "nanoid": "^3.3.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, "api/node_modules/@types/node": { "version": "18.19.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", @@ -846,14 +862,6 @@ "node": ">= 14" } }, - "api/node_modules/keyv": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz", - "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", - "dependencies": { - "@keyv/serialize": "^1.0.3" - } - }, "api/node_modules/keyv-file": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-5.1.2.tgz", @@ -17878,604 +17886,6 @@ "@lezer/common": "^1.0.0" } }, - "node_modules/@librechat/agents": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.14.tgz", - "integrity": "sha512-EYtjjl7/Xb94UZh+6S8XHcbyuqAYrVi41aFk9UFV+8TRTN16ZK/K0zhPix1SGeA8J60Xs3Y1HkZBJdbGMi9X2Q==", - "dependencies": { - "@langchain/anthropic": "^0.3.16", - "@langchain/aws": "^0.1.7", - "@langchain/community": "^0.3.39", - "@langchain/core": "^0.3.43", - "@langchain/deepseek": "^0.0.1", - "@langchain/google-genai": "^0.2.2", - "@langchain/google-vertexai": "^0.2.3", - "@langchain/langgraph": "^0.2.62", - "@langchain/mistralai": "^0.2.0", - "@langchain/ollama": "^0.2.0", - "@langchain/openai": "^0.5.4", - "@langchain/xai": "^0.0.2", - "dotenv": "^16.4.7", - "https-proxy-agent": "^7.0.6", - "nanoid": "^3.3.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@librechat/agents/node_modules/@langchain/community": { - "version": "0.3.40", - "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.40.tgz", - "integrity": "sha512-UvpEebdFKJsjFBKeUOvvYHOEFsUcjZnyU1qNirtDajwjzTJlszXtv+Mq8F6w5mJsznpI9x7ZMNzAqydVxMG5hA==", - "dependencies": { - "@langchain/openai": ">=0.2.0 <0.6.0", - "binary-extensions": "^2.2.0", - "expr-eval": "^2.0.2", - "flat": "^5.0.2", - "js-yaml": "^4.1.0", - "langchain": ">=0.2.3 <0.3.0 || >=0.3.4 <0.4.0", - "langsmith": ">=0.2.8 <0.4.0", - "uuid": "^10.0.0", - "zod": "^3.22.3", - "zod-to-json-schema": "^3.22.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@arcjet/redact": "^v1.0.0-alpha.23", - "@aws-crypto/sha256-js": "^5.0.0", - "@aws-sdk/client-bedrock-agent-runtime": "^3.749.0", - "@aws-sdk/client-bedrock-runtime": "^3.749.0", - "@aws-sdk/client-dynamodb": "^3.749.0", - "@aws-sdk/client-kendra": "^3.749.0", - "@aws-sdk/client-lambda": "^3.749.0", - "@aws-sdk/client-s3": "^3.749.0", - "@aws-sdk/client-sagemaker-runtime": "^3.749.0", - "@aws-sdk/client-sfn": "^3.749.0", - "@aws-sdk/credential-provider-node": "^3.388.0", - "@azure/search-documents": "^12.0.0", - "@azure/storage-blob": "^12.15.0", - "@browserbasehq/sdk": "*", - "@browserbasehq/stagehand": "^1.0.0", - "@clickhouse/client": "^0.2.5", - "@cloudflare/ai": "*", - "@datastax/astra-db-ts": "^1.0.0", - "@elastic/elasticsearch": "^8.4.0", - "@getmetal/metal-sdk": "*", - "@getzep/zep-cloud": "^1.0.6", - "@getzep/zep-js": "^0.9.0", - "@gomomento/sdk": "^1.51.1", - "@gomomento/sdk-core": "^1.51.1", - "@google-ai/generativelanguage": "*", - "@google-cloud/storage": "^6.10.1 || ^7.7.0", - "@gradientai/nodejs-sdk": "^1.2.0", - "@huggingface/inference": "^2.6.4", - "@huggingface/transformers": "^3.2.3", - "@ibm-cloud/watsonx-ai": "*", - "@lancedb/lancedb": "^0.12.0", - "@langchain/core": ">=0.2.21 <0.4.0", - "@layerup/layerup-security": "^1.5.12", - "@libsql/client": "^0.14.0", - "@mendable/firecrawl-js": "^1.4.3", - "@mlc-ai/web-llm": "*", - "@mozilla/readability": "*", - "@neondatabase/serverless": "*", - "@notionhq/client": "^2.2.10", - "@opensearch-project/opensearch": "*", - "@pinecone-database/pinecone": "*", - "@planetscale/database": "^1.8.0", - "@premai/prem-sdk": "^0.3.25", - "@qdrant/js-client-rest": "^1.8.2", - "@raycast/api": "^1.55.2", - "@rockset/client": "^0.9.1", - "@smithy/eventstream-codec": "^2.0.5", - "@smithy/protocol-http": "^3.0.6", - "@smithy/signature-v4": "^2.0.10", - "@smithy/util-utf8": "^2.0.0", - "@spider-cloud/spider-client": "^0.0.21", - "@supabase/supabase-js": "^2.45.0", - "@tensorflow-models/universal-sentence-encoder": "*", - "@tensorflow/tfjs-converter": "*", - "@tensorflow/tfjs-core": "*", - "@upstash/ratelimit": "^1.1.3 || ^2.0.3", - "@upstash/redis": "^1.20.6", - "@upstash/vector": "^1.1.1", - "@vercel/kv": "*", - "@vercel/postgres": "*", - "@writerai/writer-sdk": "^0.40.2", - "@xata.io/client": "^0.28.0", - "@zilliz/milvus2-sdk-node": ">=2.3.5", - "apify-client": "^2.7.1", - "assemblyai": "^4.6.0", - "azion": "^1.11.1", - "better-sqlite3": ">=9.4.0 <12.0.0", - "cassandra-driver": "^4.7.2", - "cborg": "^4.1.1", - "cheerio": "^1.0.0-rc.12", - "chromadb": "*", - "closevector-common": "0.1.3", - "closevector-node": "0.1.6", - "closevector-web": "0.1.6", - "cohere-ai": "*", - "convex": "^1.3.1", - "crypto-js": "^4.2.0", - "d3-dsv": "^2.0.0", - "discord.js": "^14.14.1", - "dria": "^0.0.3", - "duck-duck-scrape": "^2.2.5", - "epub2": "^3.0.1", - "fast-xml-parser": "*", - "firebase-admin": "^11.9.0 || ^12.0.0", - "google-auth-library": "*", - "googleapis": "*", - "hnswlib-node": "^3.0.0", - "html-to-text": "^9.0.5", - "ibm-cloud-sdk-core": "*", - "ignore": "^5.2.0", - "interface-datastore": "^8.2.11", - "ioredis": "^5.3.2", - "it-all": "^3.0.4", - "jsdom": "*", - "jsonwebtoken": "^9.0.2", - "llmonitor": "^0.5.9", - "lodash": "^4.17.21", - "lunary": "^0.7.10", - "mammoth": "^1.6.0", - "mariadb": "^3.4.0", - "mem0ai": "^2.1.8", - "mongodb": ">=5.2.0", - "mysql2": "^3.9.8", - "neo4j-driver": "*", - "notion-to-md": "^3.1.0", - "officeparser": "^4.0.4", - "openai": "*", - "pdf-parse": "1.1.1", - "pg": "^8.11.0", - "pg-copy-streams": "^6.0.5", - "pickleparser": "^0.2.1", - "playwright": "^1.32.1", - "portkey-ai": "^0.1.11", - "puppeteer": "*", - "pyodide": ">=0.24.1 <0.27.0", - "redis": "*", - "replicate": "*", - "sonix-speech-recognition": "^2.1.1", - "srt-parser-2": "^1.2.3", - "typeorm": "^0.3.20", - "typesense": "^1.5.3", - "usearch": "^1.1.1", - "voy-search": "0.6.2", - "weaviate-ts-client": "*", - "web-auth-library": "^1.0.3", - "word-extractor": "*", - "ws": "^8.14.2", - "youtubei.js": "*" - }, - "peerDependenciesMeta": { - "@arcjet/redact": { - "optional": true - }, - "@aws-crypto/sha256-js": { - "optional": true - }, - "@aws-sdk/client-bedrock-agent-runtime": { - "optional": true - }, - "@aws-sdk/client-bedrock-runtime": { - "optional": true - }, - "@aws-sdk/client-dynamodb": { - "optional": true - }, - "@aws-sdk/client-kendra": { - "optional": true - }, - "@aws-sdk/client-lambda": { - "optional": true - }, - "@aws-sdk/client-s3": { - "optional": true - }, - "@aws-sdk/client-sagemaker-runtime": { - "optional": true - }, - "@aws-sdk/client-sfn": { - "optional": true - }, - "@aws-sdk/credential-provider-node": { - "optional": true - }, - "@aws-sdk/dsql-signer": { - "optional": true - }, - "@azure/search-documents": { - "optional": true - }, - "@azure/storage-blob": { - "optional": true - }, - "@browserbasehq/sdk": { - "optional": true - }, - "@clickhouse/client": { - "optional": true - }, - "@cloudflare/ai": { - "optional": true - }, - "@datastax/astra-db-ts": { - "optional": true - }, - "@elastic/elasticsearch": { - "optional": true - }, - "@getmetal/metal-sdk": { - "optional": true - }, - "@getzep/zep-cloud": { - "optional": true - }, - "@getzep/zep-js": { - "optional": true - }, - "@gomomento/sdk": { - "optional": true - }, - "@gomomento/sdk-core": { - "optional": true - }, - "@google-ai/generativelanguage": { - "optional": true - }, - "@google-cloud/storage": { - "optional": true - }, - "@gradientai/nodejs-sdk": { - "optional": true - }, - "@huggingface/inference": { - "optional": true - }, - "@huggingface/transformers": { - "optional": true - }, - "@lancedb/lancedb": { - "optional": true - }, - "@layerup/layerup-security": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@mendable/firecrawl-js": { - "optional": true - }, - "@mlc-ai/web-llm": { - "optional": true - }, - "@mozilla/readability": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@notionhq/client": { - "optional": true - }, - "@opensearch-project/opensearch": { - "optional": true - }, - "@pinecone-database/pinecone": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@premai/prem-sdk": { - "optional": true - }, - "@qdrant/js-client-rest": { - "optional": true - }, - "@raycast/api": { - "optional": true - }, - "@rockset/client": { - "optional": true - }, - "@smithy/eventstream-codec": { - "optional": true - }, - "@smithy/protocol-http": { - "optional": true - }, - "@smithy/signature-v4": { - "optional": true - }, - "@smithy/util-utf8": { - "optional": true - }, - "@spider-cloud/spider-client": { - "optional": true - }, - "@supabase/supabase-js": { - "optional": true - }, - "@tensorflow-models/universal-sentence-encoder": { - "optional": true - }, - "@tensorflow/tfjs-converter": { - "optional": true - }, - "@tensorflow/tfjs-core": { - "optional": true - }, - "@upstash/ratelimit": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@upstash/vector": { - "optional": true - }, - "@vercel/kv": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@writerai/writer-sdk": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "@zilliz/milvus2-sdk-node": { - "optional": true - }, - "apify-client": { - "optional": true - }, - "assemblyai": { - "optional": true - }, - "azion": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "cassandra-driver": { - "optional": true - }, - "cborg": { - "optional": true - }, - "cheerio": { - "optional": true - }, - "chromadb": { - "optional": true - }, - "closevector-common": { - "optional": true - }, - "closevector-node": { - "optional": true - }, - "closevector-web": { - "optional": true - }, - "cohere-ai": { - "optional": true - }, - "convex": { - "optional": true - }, - "crypto-js": { - "optional": true - }, - "d3-dsv": { - "optional": true - }, - "discord.js": { - "optional": true - }, - "dria": { - "optional": true - }, - "duck-duck-scrape": { - "optional": true - }, - "epub2": { - "optional": true - }, - "fast-xml-parser": { - "optional": true - }, - "firebase-admin": { - "optional": true - }, - "google-auth-library": { - "optional": true - }, - "googleapis": { - "optional": true - }, - "hnswlib-node": { - "optional": true - }, - "html-to-text": { - "optional": true - }, - "ignore": { - "optional": true - }, - "interface-datastore": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "it-all": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "jsonwebtoken": { - "optional": true - }, - "llmonitor": { - "optional": true - }, - "lodash": { - "optional": true - }, - "lunary": { - "optional": true - }, - "mammoth": { - "optional": true - }, - "mariadb": { - "optional": true - }, - "mem0ai": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "neo4j-driver": { - "optional": true - }, - "notion-to-md": { - "optional": true - }, - "officeparser": { - "optional": true - }, - "pdf-parse": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-copy-streams": { - "optional": true - }, - "pickleparser": { - "optional": true - }, - "playwright": { - "optional": true - }, - "portkey-ai": { - "optional": true - }, - "puppeteer": { - "optional": true - }, - "pyodide": { - "optional": true - }, - "redis": { - "optional": true - }, - "replicate": { - "optional": true - }, - "sonix-speech-recognition": { - "optional": true - }, - "srt-parser-2": { - "optional": true - }, - "typeorm": { - "optional": true - }, - "typesense": { - "optional": true - }, - "usearch": { - "optional": true - }, - "voy-search": { - "optional": true - }, - "weaviate-ts-client": { - "optional": true - }, - "web-auth-library": { - "optional": true - }, - "word-extractor": { - "optional": true - }, - "ws": { - "optional": true - }, - "youtubei.js": { - "optional": true - } - } - }, - "node_modules/@librechat/agents/node_modules/@langchain/openai": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.5.5.tgz", - "integrity": "sha512-QwdZrWcx6FB+UMKQ6+a0M9ZXzeUnZCwXP7ltqCCycPzdfiwxg3TQ6WkSefdEyiPpJcVVq/9HZSxrzGmf18QGyw==", - "dependencies": { - "js-tiktoken": "^1.0.12", - "openai": "^4.87.3", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@langchain/core": ">=0.3.39 <0.4.0" - } - }, - "node_modules/@librechat/agents/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@librechat/agents/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@librechat/agents/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@librechat/backend": { "resolved": "api", "link": true @@ -18698,6 +18108,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@microsoft/eslint-formatter-sarif/node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/@mistralai/mistralai": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.5.2.tgz", @@ -28807,6 +28226,15 @@ "node": ">=16" } }, + "node_modules/flat-cache/node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/flatted": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", @@ -32035,7 +31463,8 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -32195,11 +31624,11 @@ } }, "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz", + "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", "dependencies": { - "json-buffer": "3.0.1" + "@keyv/serialize": "^1.0.3" } }, "node_modules/kleur": { @@ -37234,6 +36663,15 @@ "dev": true, "license": "MIT" }, + "node_modules/prettier-eslint/node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/prettier-eslint/node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -43684,7 +43122,7 @@ "typescript": "^5.0.4" }, "peerDependencies": { - "keyv": "^4.5.4" + "keyv": "^5.3.2" } }, "packages/data-schemas/node_modules/@types/whatwg-url": { @@ -43918,7 +43356,7 @@ "typescript": "^5.0.4" }, "peerDependencies": { - "keyv": "^4.5.4" + "keyv": "^5.3.2" } }, "packages/mcp/node_modules/@modelcontextprotocol/sdk": { diff --git a/packages/data-schemas/package.json b/packages/data-schemas/package.json index 51f77aa9fe..2f35c69c9f 100644 --- a/packages/data-schemas/package.json +++ b/packages/data-schemas/package.json @@ -62,7 +62,7 @@ "mongoose": "^8.12.1" }, "peerDependencies": { - "keyv": "^4.5.4" + "keyv": "^5.3.2" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/mcp/package.json b/packages/mcp/package.json index fcb17f7e69..6e084dd811 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -73,6 +73,6 @@ "express": "^4.21.2" }, "peerDependencies": { - "keyv": "^4.5.4" + "keyv": "^5.3.2" } } diff --git a/packages/mcp/src/flow/manager.spec.ts b/packages/mcp/src/flow/manager.spec.ts index 2d8897747a..1f1714509b 100644 --- a/packages/mcp/src/flow/manager.spec.ts +++ b/packages/mcp/src/flow/manager.spec.ts @@ -1,5 +1,5 @@ import { FlowStateManager } from './manager'; -import Keyv from 'keyv'; +import { Keyv } from 'keyv'; import type { FlowState } from './types'; // Create a mock class without extending Keyv diff --git a/packages/mcp/src/flow/manager.ts b/packages/mcp/src/flow/manager.ts index 0e629cc861..6a421b8d18 100644 --- a/packages/mcp/src/flow/manager.ts +++ b/packages/mcp/src/flow/manager.ts @@ -1,4 +1,5 @@ -import Keyv from 'keyv'; +import { Keyv } from 'keyv'; +import type { StoredDataNoRaw } from 'keyv'; import type { Logger } from 'winston'; import type { FlowState, FlowMetadata, FlowManagerOptions } from './types'; @@ -202,7 +203,7 @@ export class FlowStateManager { /** * Gets current flow state */ - async getFlowState(flowId: string, type: string): Promise | null> { + async getFlowState(flowId: string, type: string): Promise> | null> { const flowKey = this.getFlowKey(flowId, type); return this.keyv.get(flowKey); } From 52b3ed54ca3a212a7447aae95223f2790d8d35e0 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 14 Apr 2025 14:55:59 -0400 Subject: [PATCH 14/75] =?UTF-8?q?=F0=9F=A4=96=20feat:=20GPT-4.1=20(#6880)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Agent Builder setting not applying in useSideNavLinks * fix: Remove unused type imports in useSideNavLinks * feat: gpt-4.1 * fix: Update getCacheMultiplier and getMultiplier tests to use dynamic token values * feat: Add gpt-4.1 to the list of vision models * chore: Bump version of librechat-data-provider to 0.7.792 --- api/models/tx.js | 9 + api/models/tx.spec.js | 178 +++++++++++++++--- api/utils/tokens.js | 3 + api/utils/tokens.spec.js | 56 ++++++ client/src/components/SidePanel/SidePanel.tsx | 4 - client/src/hooks/Nav/useSideNavLinks.ts | 17 +- package-lock.json | 2 +- packages/data-provider/package.json | 2 +- packages/data-provider/src/config.ts | 1 + 9 files changed, 224 insertions(+), 48 deletions(-) diff --git a/api/models/tx.js b/api/models/tx.js index 5099e37f47..c141cd0d2d 100644 --- a/api/models/tx.js +++ b/api/models/tx.js @@ -80,6 +80,9 @@ const tokenValues = Object.assign( 'o1-mini': { prompt: 1.1, completion: 4.4 }, 'o1-preview': { prompt: 15, completion: 60 }, o1: { prompt: 15, completion: 60 }, + 'gpt-4.1-nano': { prompt: 0.1, completion: 0.4 }, + 'gpt-4.1-mini': { prompt: 0.4, completion: 1.6 }, + 'gpt-4.1': { prompt: 2, completion: 8 }, 'gpt-4.5': { prompt: 75, completion: 150 }, 'gpt-4o-mini': { prompt: 0.15, completion: 0.6 }, 'gpt-4o': { prompt: 2.5, completion: 10 }, @@ -183,6 +186,12 @@ const getValueKey = (model, endpoint) => { return 'o1'; } else if (modelName.includes('gpt-4.5')) { return 'gpt-4.5'; + } else if (modelName.includes('gpt-4.1-nano')) { + return 'gpt-4.1-nano'; + } else if (modelName.includes('gpt-4.1-mini')) { + return 'gpt-4.1-mini'; + } else if (modelName.includes('gpt-4.1')) { + return 'gpt-4.1'; } else if (modelName.includes('gpt-4o-2024-05-13')) { return 'gpt-4o-2024-05-13'; } else if (modelName.includes('gpt-4o-mini')) { diff --git a/api/models/tx.spec.js b/api/models/tx.spec.js index f759e658f0..c2b4326ab6 100644 --- a/api/models/tx.spec.js +++ b/api/models/tx.spec.js @@ -60,6 +60,30 @@ describe('getValueKey', () => { expect(getValueKey('gpt-4.5-0125')).toBe('gpt-4.5'); }); + it('should return "gpt-4.1" for model type of "gpt-4.1"', () => { + expect(getValueKey('gpt-4.1-preview')).toBe('gpt-4.1'); + expect(getValueKey('gpt-4.1-2024-08-06')).toBe('gpt-4.1'); + expect(getValueKey('gpt-4.1-2024-08-06-0718')).toBe('gpt-4.1'); + expect(getValueKey('openai/gpt-4.1')).toBe('gpt-4.1'); + expect(getValueKey('openai/gpt-4.1-2024-08-06')).toBe('gpt-4.1'); + expect(getValueKey('gpt-4.1-turbo')).toBe('gpt-4.1'); + expect(getValueKey('gpt-4.1-0125')).toBe('gpt-4.1'); + }); + + it('should return "gpt-4.1-mini" for model type of "gpt-4.1-mini"', () => { + expect(getValueKey('gpt-4.1-mini-preview')).toBe('gpt-4.1-mini'); + expect(getValueKey('gpt-4.1-mini-2024-08-06')).toBe('gpt-4.1-mini'); + expect(getValueKey('openai/gpt-4.1-mini')).toBe('gpt-4.1-mini'); + expect(getValueKey('gpt-4.1-mini-0125')).toBe('gpt-4.1-mini'); + }); + + it('should return "gpt-4.1-nano" for model type of "gpt-4.1-nano"', () => { + expect(getValueKey('gpt-4.1-nano-preview')).toBe('gpt-4.1-nano'); + expect(getValueKey('gpt-4.1-nano-2024-08-06')).toBe('gpt-4.1-nano'); + expect(getValueKey('openai/gpt-4.1-nano')).toBe('gpt-4.1-nano'); + expect(getValueKey('gpt-4.1-nano-0125')).toBe('gpt-4.1-nano'); + }); + it('should return "gpt-4o" for model type of "gpt-4o"', () => { expect(getValueKey('gpt-4o-2024-08-06')).toBe('gpt-4o'); expect(getValueKey('gpt-4o-2024-08-06-0718')).toBe('gpt-4o'); @@ -185,6 +209,52 @@ describe('getMultiplier', () => { ); }); + it('should return the correct multiplier for gpt-4.1', () => { + const valueKey = getValueKey('gpt-4.1-2024-08-06'); + expect(getMultiplier({ valueKey, tokenType: 'prompt' })).toBe(tokenValues['gpt-4.1'].prompt); + expect(getMultiplier({ valueKey, tokenType: 'completion' })).toBe( + tokenValues['gpt-4.1'].completion, + ); + expect(getMultiplier({ model: 'gpt-4.1-preview', tokenType: 'prompt' })).toBe( + tokenValues['gpt-4.1'].prompt, + ); + expect(getMultiplier({ model: 'openai/gpt-4.1', tokenType: 'completion' })).toBe( + tokenValues['gpt-4.1'].completion, + ); + }); + + it('should return the correct multiplier for gpt-4.1-mini', () => { + const valueKey = getValueKey('gpt-4.1-mini-2024-08-06'); + expect(getMultiplier({ valueKey, tokenType: 'prompt' })).toBe( + tokenValues['gpt-4.1-mini'].prompt, + ); + expect(getMultiplier({ valueKey, tokenType: 'completion' })).toBe( + tokenValues['gpt-4.1-mini'].completion, + ); + expect(getMultiplier({ model: 'gpt-4.1-mini-preview', tokenType: 'prompt' })).toBe( + tokenValues['gpt-4.1-mini'].prompt, + ); + expect(getMultiplier({ model: 'openai/gpt-4.1-mini', tokenType: 'completion' })).toBe( + tokenValues['gpt-4.1-mini'].completion, + ); + }); + + it('should return the correct multiplier for gpt-4.1-nano', () => { + const valueKey = getValueKey('gpt-4.1-nano-2024-08-06'); + expect(getMultiplier({ valueKey, tokenType: 'prompt' })).toBe( + tokenValues['gpt-4.1-nano'].prompt, + ); + expect(getMultiplier({ valueKey, tokenType: 'completion' })).toBe( + tokenValues['gpt-4.1-nano'].completion, + ); + expect(getMultiplier({ model: 'gpt-4.1-nano-preview', tokenType: 'prompt' })).toBe( + tokenValues['gpt-4.1-nano'].prompt, + ); + expect(getMultiplier({ model: 'openai/gpt-4.1-nano', tokenType: 'completion' })).toBe( + tokenValues['gpt-4.1-nano'].completion, + ); + }); + it('should return the correct multiplier for gpt-4o-mini', () => { const valueKey = getValueKey('gpt-4o-mini-2024-07-18'); expect(getMultiplier({ valueKey, tokenType: 'prompt' })).toBe( @@ -348,9 +418,11 @@ describe('getCacheMultiplier', () => { it('should derive the valueKey from the model if not provided', () => { expect(getCacheMultiplier({ cacheType: 'write', model: 'claude-3-5-sonnet-20240620' })).toBe( - 3.75, + cacheTokenValues['claude-3-5-sonnet'].write, + ); + expect(getCacheMultiplier({ cacheType: 'read', model: 'claude-3-haiku-20240307' })).toBe( + cacheTokenValues['claude-3-haiku'].read, ); - expect(getCacheMultiplier({ cacheType: 'read', model: 'claude-3-haiku-20240307' })).toBe(0.03); }); it('should return null if only model or cacheType is missing', () => { @@ -371,10 +443,10 @@ describe('getCacheMultiplier', () => { }; expect( getCacheMultiplier({ model: 'custom-model', cacheType: 'write', endpointTokenConfig }), - ).toBe(5); + ).toBe(endpointTokenConfig['custom-model'].write); expect( getCacheMultiplier({ model: 'custom-model', cacheType: 'read', endpointTokenConfig }), - ).toBe(1); + ).toBe(endpointTokenConfig['custom-model'].read); }); it('should return null if model is not found in endpointTokenConfig', () => { @@ -395,13 +467,13 @@ describe('getCacheMultiplier', () => { model: 'bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0', cacheType: 'write', }), - ).toBe(3.75); + ).toBe(cacheTokenValues['claude-3-5-sonnet'].write); expect( getCacheMultiplier({ model: 'bedrock/anthropic.claude-3-haiku-20240307-v1:0', cacheType: 'read', }), - ).toBe(0.03); + ).toBe(cacheTokenValues['claude-3-haiku'].read); }); }); @@ -488,46 +560,92 @@ describe('Grok Model Tests - Pricing', () => { test('should return correct prompt and completion rates for Grok vision models', () => { const models = ['grok-2-vision-1212', 'grok-2-vision', 'grok-2-vision-latest']; models.forEach((model) => { - expect(getMultiplier({ model, tokenType: 'prompt' })).toBe(2.0); - expect(getMultiplier({ model, tokenType: 'completion' })).toBe(10.0); + expect(getMultiplier({ model, tokenType: 'prompt' })).toBe( + tokenValues['grok-2-vision'].prompt, + ); + expect(getMultiplier({ model, tokenType: 'completion' })).toBe( + tokenValues['grok-2-vision'].completion, + ); }); }); test('should return correct prompt and completion rates for Grok text models', () => { const models = ['grok-2-1212', 'grok-2', 'grok-2-latest']; models.forEach((model) => { - expect(getMultiplier({ model, tokenType: 'prompt' })).toBe(2.0); - expect(getMultiplier({ model, tokenType: 'completion' })).toBe(10.0); + expect(getMultiplier({ model, tokenType: 'prompt' })).toBe(tokenValues['grok-2'].prompt); + expect(getMultiplier({ model, tokenType: 'completion' })).toBe( + tokenValues['grok-2'].completion, + ); }); }); test('should return correct prompt and completion rates for Grok beta models', () => { - expect(getMultiplier({ model: 'grok-vision-beta', tokenType: 'prompt' })).toBe(5.0); - expect(getMultiplier({ model: 'grok-vision-beta', tokenType: 'completion' })).toBe(15.0); - expect(getMultiplier({ model: 'grok-beta', tokenType: 'prompt' })).toBe(5.0); - expect(getMultiplier({ model: 'grok-beta', tokenType: 'completion' })).toBe(15.0); + expect(getMultiplier({ model: 'grok-vision-beta', tokenType: 'prompt' })).toBe( + tokenValues['grok-vision-beta'].prompt, + ); + expect(getMultiplier({ model: 'grok-vision-beta', tokenType: 'completion' })).toBe( + tokenValues['grok-vision-beta'].completion, + ); + expect(getMultiplier({ model: 'grok-beta', tokenType: 'prompt' })).toBe( + tokenValues['grok-beta'].prompt, + ); + expect(getMultiplier({ model: 'grok-beta', tokenType: 'completion' })).toBe( + tokenValues['grok-beta'].completion, + ); }); test('should return correct prompt and completion rates for Grok 3 models', () => { - expect(getMultiplier({ model: 'grok-3', tokenType: 'prompt' })).toBe(3.0); - expect(getMultiplier({ model: 'grok-3', tokenType: 'completion' })).toBe(15.0); - expect(getMultiplier({ model: 'grok-3-fast', tokenType: 'prompt' })).toBe(5.0); - expect(getMultiplier({ model: 'grok-3-fast', tokenType: 'completion' })).toBe(25.0); - expect(getMultiplier({ model: 'grok-3-mini', tokenType: 'prompt' })).toBe(0.3); - expect(getMultiplier({ model: 'grok-3-mini', tokenType: 'completion' })).toBe(0.5); - expect(getMultiplier({ model: 'grok-3-mini-fast', tokenType: 'prompt' })).toBe(0.4); - expect(getMultiplier({ model: 'grok-3-mini-fast', tokenType: 'completion' })).toBe(4.0); + expect(getMultiplier({ model: 'grok-3', tokenType: 'prompt' })).toBe( + tokenValues['grok-3'].prompt, + ); + expect(getMultiplier({ model: 'grok-3', tokenType: 'completion' })).toBe( + tokenValues['grok-3'].completion, + ); + expect(getMultiplier({ model: 'grok-3-fast', tokenType: 'prompt' })).toBe( + tokenValues['grok-3-fast'].prompt, + ); + expect(getMultiplier({ model: 'grok-3-fast', tokenType: 'completion' })).toBe( + tokenValues['grok-3-fast'].completion, + ); + expect(getMultiplier({ model: 'grok-3-mini', tokenType: 'prompt' })).toBe( + tokenValues['grok-3-mini'].prompt, + ); + expect(getMultiplier({ model: 'grok-3-mini', tokenType: 'completion' })).toBe( + tokenValues['grok-3-mini'].completion, + ); + expect(getMultiplier({ model: 'grok-3-mini-fast', tokenType: 'prompt' })).toBe( + tokenValues['grok-3-mini-fast'].prompt, + ); + expect(getMultiplier({ model: 'grok-3-mini-fast', tokenType: 'completion' })).toBe( + tokenValues['grok-3-mini-fast'].completion, + ); }); test('should return correct prompt and completion rates for Grok 3 models with prefixes', () => { - expect(getMultiplier({ model: 'xai/grok-3', tokenType: 'prompt' })).toBe(3.0); - expect(getMultiplier({ model: 'xai/grok-3', tokenType: 'completion' })).toBe(15.0); - expect(getMultiplier({ model: 'xai/grok-3-fast', tokenType: 'prompt' })).toBe(5.0); - expect(getMultiplier({ model: 'xai/grok-3-fast', tokenType: 'completion' })).toBe(25.0); - expect(getMultiplier({ model: 'xai/grok-3-mini', tokenType: 'prompt' })).toBe(0.3); - expect(getMultiplier({ model: 'xai/grok-3-mini', tokenType: 'completion' })).toBe(0.5); - expect(getMultiplier({ model: 'xai/grok-3-mini-fast', tokenType: 'prompt' })).toBe(0.4); - expect(getMultiplier({ model: 'xai/grok-3-mini-fast', tokenType: 'completion' })).toBe(4.0); + expect(getMultiplier({ model: 'xai/grok-3', tokenType: 'prompt' })).toBe( + tokenValues['grok-3'].prompt, + ); + expect(getMultiplier({ model: 'xai/grok-3', tokenType: 'completion' })).toBe( + tokenValues['grok-3'].completion, + ); + expect(getMultiplier({ model: 'xai/grok-3-fast', tokenType: 'prompt' })).toBe( + tokenValues['grok-3-fast'].prompt, + ); + expect(getMultiplier({ model: 'xai/grok-3-fast', tokenType: 'completion' })).toBe( + tokenValues['grok-3-fast'].completion, + ); + expect(getMultiplier({ model: 'xai/grok-3-mini', tokenType: 'prompt' })).toBe( + tokenValues['grok-3-mini'].prompt, + ); + expect(getMultiplier({ model: 'xai/grok-3-mini', tokenType: 'completion' })).toBe( + tokenValues['grok-3-mini'].completion, + ); + expect(getMultiplier({ model: 'xai/grok-3-mini-fast', tokenType: 'prompt' })).toBe( + tokenValues['grok-3-mini-fast'].prompt, + ); + expect(getMultiplier({ model: 'xai/grok-3-mini-fast', tokenType: 'completion' })).toBe( + tokenValues['grok-3-mini-fast'].completion, + ); }); }); }); diff --git a/api/utils/tokens.js b/api/utils/tokens.js index 1f648b34f1..0e505b00da 100644 --- a/api/utils/tokens.js +++ b/api/utils/tokens.js @@ -14,6 +14,9 @@ const openAIModels = { 'gpt-4-1106': 127500, // -500 from max 'gpt-4-0125': 127500, // -500 from max 'gpt-4.5': 127500, // -500 from max + 'gpt-4.1': 1047576, + 'gpt-4.1-mini': 1047576, + 'gpt-4.1-nano': 1047576, 'gpt-4o': 127500, // -500 from max 'gpt-4o-mini': 127500, // -500 from max 'gpt-4o-2024-05-13': 127500, // -500 from max diff --git a/api/utils/tokens.spec.js b/api/utils/tokens.spec.js index 9960b8f5f5..6bb4967cb4 100644 --- a/api/utils/tokens.spec.js +++ b/api/utils/tokens.spec.js @@ -113,6 +113,43 @@ describe('getModelMaxTokens', () => { ); }); + test('should return correct tokens for gpt-4.1 matches', () => { + expect(getModelMaxTokens('gpt-4.1')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-4.1']); + expect(getModelMaxTokens('gpt-4.1-preview')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-4.1'], + ); + expect(getModelMaxTokens('openai/gpt-4.1')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-4.1'], + ); + expect(getModelMaxTokens('gpt-4.1-2024-08-06')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-4.1'], + ); + }); + + test('should return correct tokens for gpt-4.1-mini matches', () => { + expect(getModelMaxTokens('gpt-4.1-mini')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-4.1-mini'], + ); + expect(getModelMaxTokens('gpt-4.1-mini-preview')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-4.1-mini'], + ); + expect(getModelMaxTokens('openai/gpt-4.1-mini')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-4.1-mini'], + ); + }); + + test('should return correct tokens for gpt-4.1-nano matches', () => { + expect(getModelMaxTokens('gpt-4.1-nano')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-4.1-nano'], + ); + expect(getModelMaxTokens('gpt-4.1-nano-preview')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-4.1-nano'], + ); + expect(getModelMaxTokens('openai/gpt-4.1-nano')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-4.1-nano'], + ); + }); + test('should return correct tokens for Anthropic models', () => { const models = [ 'claude-2.1', @@ -355,6 +392,25 @@ describe('matchModelName', () => { expect(matchModelName('gpt-4-0125-vision-preview')).toBe('gpt-4-0125'); }); + it('should return the closest matching key for gpt-4.1 matches', () => { + expect(matchModelName('openai/gpt-4.1')).toBe('gpt-4.1'); + expect(matchModelName('gpt-4.1-preview')).toBe('gpt-4.1'); + expect(matchModelName('gpt-4.1-2024-08-06')).toBe('gpt-4.1'); + expect(matchModelName('gpt-4.1-2024-08-06-0718')).toBe('gpt-4.1'); + }); + + it('should return the closest matching key for gpt-4.1-mini matches', () => { + expect(matchModelName('openai/gpt-4.1-mini')).toBe('gpt-4.1-mini'); + expect(matchModelName('gpt-4.1-mini-preview')).toBe('gpt-4.1-mini'); + expect(matchModelName('gpt-4.1-mini-2024-08-06')).toBe('gpt-4.1-mini'); + }); + + it('should return the closest matching key for gpt-4.1-nano matches', () => { + expect(matchModelName('openai/gpt-4.1-nano')).toBe('gpt-4.1-nano'); + expect(matchModelName('gpt-4.1-nano-preview')).toBe('gpt-4.1-nano'); + expect(matchModelName('gpt-4.1-nano-2024-08-06')).toBe('gpt-4.1-nano'); + }); + // Tests for Google models it('should return the exact model name if it exists in maxTokensMap - Google models', () => { expect(matchModelName('text-bison-32k', EModelEndpoint.google)).toBe('text-bison-32k'); diff --git a/client/src/components/SidePanel/SidePanel.tsx b/client/src/components/SidePanel/SidePanel.tsx index dc2015314b..48080a612c 100644 --- a/client/src/components/SidePanel/SidePanel.tsx +++ b/client/src/components/SidePanel/SidePanel.tsx @@ -62,8 +62,6 @@ const SidePanel = ({ () => getEndpointField(endpointsConfig, endpoint, 'type'), [endpoint, endpointsConfig], ); - const assistants = useMemo(() => endpointsConfig?.[endpoint ?? ''], [endpoint, endpointsConfig]); - const agents = useMemo(() => endpointsConfig?.[endpoint ?? ''], [endpoint, endpointsConfig]); const userProvidesKey = useMemo( () => !!(endpointsConfig?.[endpoint ?? '']?.userProvide ?? false), @@ -84,10 +82,8 @@ const SidePanel = ({ }, []); const Links = useSideNavLinks({ - agents, endpoint, hidePanel, - assistants, keyProvided, endpointType, interfaceConfig, diff --git a/client/src/hooks/Nav/useSideNavLinks.ts b/client/src/hooks/Nav/useSideNavLinks.ts index 885cdecc30..167983a7ec 100644 --- a/client/src/hooks/Nav/useSideNavLinks.ts +++ b/client/src/hooks/Nav/useSideNavLinks.ts @@ -8,7 +8,7 @@ import { EModelEndpoint, Permissions, } from 'librechat-data-provider'; -import type { TConfig, TInterfaceConfig, TEndpointsConfig } from 'librechat-data-provider'; +import type { TInterfaceConfig, TEndpointsConfig } from 'librechat-data-provider'; import type { NavLink } from '~/common'; import AgentPanelSwitch from '~/components/SidePanel/Agents/AgentPanelSwitch'; import BookmarkPanel from '~/components/SidePanel/Bookmarks/BookmarkPanel'; @@ -21,8 +21,6 @@ import { useHasAccess } from '~/hooks'; export default function useSideNavLinks({ hidePanel, - assistants, - agents, keyProvided, endpoint, endpointType, @@ -30,8 +28,6 @@ export default function useSideNavLinks({ endpointsConfig, }: { hidePanel: () => void; - assistants?: TConfig | null; - agents?: TConfig | null; keyProvided: boolean; endpoint?: EModelEndpoint | null; endpointType?: EModelEndpoint | null; @@ -59,8 +55,8 @@ export default function useSideNavLinks({ const links: NavLink[] = []; if ( isAssistantsEndpoint(endpoint) && - assistants && - assistants.disableBuilder !== true && + endpointsConfig?.[EModelEndpoint.assistants] && + endpointsConfig[EModelEndpoint.assistants].disableBuilder !== true && keyProvided ) { links.push({ @@ -76,8 +72,7 @@ export default function useSideNavLinks({ endpointsConfig?.[EModelEndpoint.agents] && hasAccessToAgents && hasAccessToCreateAgents && - agents && - agents.disableBuilder !== true + endpointsConfig[EModelEndpoint.agents].disableBuilder !== true ) { links.push({ title: 'com_sidepanel_agent_builder', @@ -141,13 +136,11 @@ export default function useSideNavLinks({ return links; }, [ - endpointsConfig?.[EModelEndpoint.agents], + endpointsConfig, interfaceConfig.parameters, keyProvided, - assistants, endpointType, endpoint, - agents, hasAccessToAgents, hasAccessToPrompts, hasAccessToBookmarks, diff --git a/package-lock.json b/package-lock.json index b60145153f..15ea82e83a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42957,7 +42957,7 @@ }, "packages/data-provider": { "name": "librechat-data-provider", - "version": "0.7.791", + "version": "0.7.792", "license": "ISC", "dependencies": { "axios": "^1.8.2", diff --git a/packages/data-provider/package.json b/packages/data-provider/package.json index 6437be5f11..cd5e740b70 100644 --- a/packages/data-provider/package.json +++ b/packages/data-provider/package.json @@ -1,6 +1,6 @@ { "name": "librechat-data-provider", - "version": "0.7.791", + "version": "0.7.792", "description": "data services for librechat apps", "main": "dist/index.js", "module": "dist/index.es.js", diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index bfb1694088..fcbaef074f 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -857,6 +857,7 @@ export const visionModels = [ 'gpt-4-turbo', 'gpt-4-vision', 'o1', + 'gpt-4.1', 'gpt-4.5', 'llava', 'llava-13b', From c49f883e1ac38ea9b7d229cd97e089e5f233dd23 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 14 Apr 2025 22:38:35 -0400 Subject: [PATCH 15/75] =?UTF-8?q?=F0=9F=94=81=20refactor:=20Token=20Event?= =?UTF-8?q?=20Handler=20and=20Standardize=20`maxTokens`=20Key=20(#6886)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: agent token handling to use createHandleLLMNewToken for improved closure * refactor: update llmConfig to use maxTokens instead of max_tokens for consistency --- api/app/clients/generators.js | 10 ++++++++++ api/server/services/Endpoints/bedrock/options.js | 9 ++------- api/server/services/Endpoints/custom/initialize.js | 7 +++---- api/server/services/Endpoints/openAI/initialize.js | 11 +++-------- api/server/services/Endpoints/openAI/llm.js | 6 ++++++ 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/api/app/clients/generators.js b/api/app/clients/generators.js index 971ac16da2..9814cac7a5 100644 --- a/api/app/clients/generators.js +++ b/api/app/clients/generators.js @@ -1,6 +1,7 @@ const fetch = require('node-fetch'); const { GraphEvents } = require('@librechat/agents'); const { logger, sendEvent } = require('~/config'); +const { sleep } = require('~/server/utils'); /** * Makes a function to make HTTP request and logs the process. @@ -55,7 +56,16 @@ function createStreamEventHandlers(res) { }; } +function createHandleLLMNewToken(streamRate) { + return async () => { + if (streamRate) { + await sleep(streamRate); + } + }; +} + module.exports = { createFetch, + createHandleLLMNewToken, createStreamEventHandlers, }; diff --git a/api/server/services/Endpoints/bedrock/options.js b/api/server/services/Endpoints/bedrock/options.js index 6740ae882e..1936a8f483 100644 --- a/api/server/services/Endpoints/bedrock/options.js +++ b/api/server/services/Endpoints/bedrock/options.js @@ -8,7 +8,7 @@ const { removeNullishValues, } = require('librechat-data-provider'); const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService'); -const { sleep } = require('~/server/utils'); +const { createHandleLLMNewToken } = require('~/app/clients/generators'); const getOptions = async ({ req, overrideModel, endpointOption }) => { const { @@ -90,12 +90,7 @@ const getOptions = async ({ req, overrideModel, endpointOption }) => { llmConfig.callbacks = [ { - handleLLMNewToken: async () => { - if (!streamRate) { - return; - } - await sleep(streamRate); - }, + handleLLMNewToken: createHandleLLMNewToken(streamRate), }, ]; diff --git a/api/server/services/Endpoints/custom/initialize.js b/api/server/services/Endpoints/custom/initialize.js index c44b9e0e3e..592440db54 100644 --- a/api/server/services/Endpoints/custom/initialize.js +++ b/api/server/services/Endpoints/custom/initialize.js @@ -9,9 +9,10 @@ const { Providers } = require('@librechat/agents'); const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getLLMConfig } = require('~/server/services/Endpoints/openAI/llm'); const { getCustomEndpointConfig } = require('~/server/services/Config'); +const { createHandleLLMNewToken } = require('~/app/clients/generators'); const { fetchModels } = require('~/server/services/ModelService'); -const { isUserProvided, sleep } = require('~/server/utils'); const OpenAIClient = require('~/app/clients/OpenAIClient'); +const { isUserProvided } = require('~/server/utils'); const getLogStores = require('~/cache/getLogStores'); const { PROXY } = process.env; @@ -148,9 +149,7 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid } options.llmConfig.callbacks = [ { - handleLLMNewToken: async () => { - await sleep(customOptions.streamRate); - }, + handleLLMNewToken: createHandleLLMNewToken(clientOptions.streamRate), }, ]; return options; diff --git a/api/server/services/Endpoints/openAI/initialize.js b/api/server/services/Endpoints/openAI/initialize.js index 1661f9e6c6..714ed5a1e6 100644 --- a/api/server/services/Endpoints/openAI/initialize.js +++ b/api/server/services/Endpoints/openAI/initialize.js @@ -6,16 +6,11 @@ const { } = require('librechat-data-provider'); const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getLLMConfig } = require('~/server/services/Endpoints/openAI/llm'); -const { isEnabled, isUserProvided, sleep } = require('~/server/utils'); +const { createHandleLLMNewToken } = require('~/app/clients/generators'); +const { isEnabled, isUserProvided } = require('~/server/utils'); const OpenAIClient = require('~/app/clients/OpenAIClient'); const { getAzureCredentials } = require('~/utils'); -function createHandleNewToken(streamRate) { - async () => { - await sleep(streamRate); - }; -} - const initializeClient = async ({ req, res, @@ -152,7 +147,7 @@ const initializeClient = async ({ } options.llmConfig.callbacks = [ { - handleLLMNewToken: createHandleNewToken(streamRate), + handleLLMNewToken: createHandleLLMNewToken(streamRate), }, ]; return options; diff --git a/api/server/services/Endpoints/openAI/llm.js b/api/server/services/Endpoints/openAI/llm.js index a8aeeb5b9d..fad9139a16 100644 --- a/api/server/services/Endpoints/openAI/llm.js +++ b/api/server/services/Endpoints/openAI/llm.js @@ -153,6 +153,12 @@ function getLLMConfig(apiKey, options = {}, endpoint = null) { delete llmConfig.reasoning_effort; } + if (llmConfig?.['max_tokens'] != null) { + /** @type {number} */ + llmConfig.maxTokens = llmConfig['max_tokens']; + delete llmConfig['max_tokens']; + } + return { /** @type {OpenAIClientOptions} */ llmConfig, From 5d56f48879aa465f92db34f9268057b31b017623 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Tue, 15 Apr 2025 04:39:01 +0200 Subject: [PATCH 16/75] =?UTF-8?q?=F0=9F=91=8B=20feat:=20remove=20Edge=20TT?= =?UTF-8?q?S=20(#6885)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: remove Edge TTS * remove the remaining edge code * chore: cleanup * chore: cleanup package-lock --- client/package.json | 1 - client/src/common/types.ts | 1 - client/src/components/Audio/TTS.tsx | 96 +------ client/src/components/Audio/Voices.tsx | 30 +-- .../components/Chat/Messages/MessageAudio.tsx | 3 +- .../Speech/TTS/EngineTTSDropdown.tsx | 6 +- .../SettingsTabs/Speech/TTS/VoiceDropdown.tsx | 7 +- client/src/hooks/Audio/index.ts | 1 - client/src/hooks/Audio/useTTSEdge.ts | 100 ------- client/src/hooks/Input/useTextToSpeech.ts | 17 +- client/src/hooks/Input/useTextToSpeechEdge.ts | 249 ------------------ client/src/locales/en/translation.json | 7 - package-lock.json | 92 ++++--- 13 files changed, 63 insertions(+), 547 deletions(-) delete mode 100644 client/src/hooks/Audio/useTTSEdge.ts delete mode 100644 client/src/hooks/Input/useTextToSpeechEdge.ts diff --git a/client/package.json b/client/package.json index 184e768c5f..db82362a18 100644 --- a/client/package.json +++ b/client/package.json @@ -73,7 +73,6 @@ "lodash": "^4.17.21", "lucide-react": "^0.394.0", "match-sorter": "^6.3.4", - "msedge-tts": "^2.0.0", "qrcode.react": "^4.2.0", "rc-input-number": "^7.4.2", "react": "^18.2.0", diff --git a/client/src/common/types.ts b/client/src/common/types.ts index ce47a4667b..3551436ee8 100644 --- a/client/src/common/types.ts +++ b/client/src/common/types.ts @@ -29,7 +29,6 @@ export enum STTEndpoints { export enum TTSEndpoints { browser = 'browser', - edge = 'edge', external = 'external', } diff --git a/client/src/components/Audio/TTS.tsx b/client/src/components/Audio/TTS.tsx index 14c6346b0f..3ceacb7f8d 100644 --- a/client/src/components/Audio/TTS.tsx +++ b/client/src/components/Audio/TTS.tsx @@ -2,9 +2,8 @@ import { useEffect, useMemo } from 'react'; import { useRecoilValue } from 'recoil'; import type { TMessageAudio } from '~/common'; -import { useLocalize, useTTSBrowser, useTTSEdge, useTTSExternal } from '~/hooks'; -import { VolumeIcon, VolumeMuteIcon, Spinner } from '~/components/svg'; -import { useToastContext } from '~/Providers/ToastContext'; +import { useLocalize, useTTSBrowser, useTTSExternal } from '~/hooks'; +import { VolumeIcon, VolumeMuteIcon, Spinner } from '~/components'; import { logger } from '~/utils'; import store from '~/store'; @@ -85,97 +84,6 @@ export function BrowserTTS({ isLast, index, messageId, content, className }: TMe ); } -export function EdgeTTS({ isLast, index, messageId, content, className }: TMessageAudio) { - const localize = useLocalize(); - const playbackRate = useRecoilValue(store.playbackRate); - const isBrowserSupported = useMemo( - () => typeof MediaSource !== 'undefined' && MediaSource.isTypeSupported('audio/mpeg'), - [], - ); - - const { showToast } = useToastContext(); - const { toggleSpeech, isSpeaking, isLoading, audioRef } = useTTSEdge({ - isLast, - index, - messageId, - content, - }); - - const renderIcon = (size: string) => { - if (isLoading === true) { - return ; - } - - if (isSpeaking === true) { - return ; - } - - return ; - }; - - useEffect(() => { - const messageAudio = document.getElementById(`audio-${messageId}`) as HTMLAudioElement | null; - if (!messageAudio) { - return; - } - if (playbackRate != null && playbackRate > 0 && messageAudio.playbackRate !== playbackRate) { - messageAudio.playbackRate = playbackRate; - } - }, [audioRef, isSpeaking, playbackRate, messageId]); - - logger.log( - 'MessageAudio: audioRef.current?.src, audioRef.current', - audioRef.current?.src, - audioRef.current, - ); - - return ( - <> - - {isBrowserSupported ? ( -
); diff --git a/client/src/components/Bookmarks/BookmarkItem.tsx b/client/src/components/Bookmarks/BookmarkItem.tsx index 92a6df0b54..60698a3165 100644 --- a/client/src/components/Bookmarks/BookmarkItem.tsx +++ b/client/src/components/Bookmarks/BookmarkItem.tsx @@ -34,19 +34,22 @@ const BookmarkItem: FC = ({ tag, selected, handleSubmit, icon, .. if (icon != null) { return icon; } + if (isLoading) { return ; } + if (selected) { return ; } + return ; }; return ( { - const { title: _t, ...convo } = conversation ?? ({} as TConversation); setAddedConvo({ ...convo, @@ -42,7 +41,7 @@ function AddMultiConvo() { role="button" onClick={clickHandler} data-testid="parameters-button" - className="inline-flex size-10 flex-shrink-0 items-center justify-center rounded-lg border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary" + className="inline-flex size-10 flex-shrink-0 items-center justify-center rounded-xl border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary" > diff --git a/client/src/components/Chat/ExportAndShareMenu.tsx b/client/src/components/Chat/ExportAndShareMenu.tsx index 954393abaa..7f37ebbc72 100644 --- a/client/src/components/Chat/ExportAndShareMenu.tsx +++ b/client/src/components/Chat/ExportAndShareMenu.tsx @@ -79,7 +79,7 @@ export default function ExportAndShareMenu({ { () => conversation?.endpointType ?? conversation?.endpoint, [conversation?.endpointType, conversation?.endpoint], ); + const conversationId = useMemo( + () => conversation?.conversationId ?? Constants.NEW_CONVO, + [conversation?.conversationId], + ); - const isRTL = useMemo(() => chatDirection === 'rtl', [chatDirection.toLowerCase()]); + const isRTL = useMemo( + () => (chatDirection != null ? chatDirection?.toLowerCase() === 'rtl' : false), + [chatDirection], + ); const invalidAssistant = useMemo( () => isAssistantsEndpoint(endpoint) && @@ -110,10 +117,10 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => { }, [isCollapsed]); useAutoSave({ - conversationId: conversation?.conversationId, - textAreaRef, files, setFiles, + textAreaRef, + conversationId, }); const { submitMessage, submitPrompt } = useSubmitMessage(); @@ -166,7 +173,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => { const handleSaveBadges = useCallback(() => { setIsEditingBadges(false); setBackupBadges([]); - }, []); + }, [setIsEditingBadges, setBackupBadges]); const handleCancelBadges = useCallback(() => { if (backupBadges.length > 0) { @@ -174,7 +181,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => { } setIsEditingBadges(false); setBackupBadges([]); - }, [backupBadges, setBadges]); + }, [backupBadges, setBadges, setIsEditingBadges]); const isMoreThanThreeRows = visualRowCount > 3; @@ -195,8 +202,9 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => { 'mx-auto flex flex-row gap-3 sm:px-2', maximizeChatSpace ? 'w-full max-w-full' : 'md:max-w-3xl xl:max-w-4xl', centerFormOnLanding && - (!conversation?.conversationId || conversation?.conversationId === Constants.NEW_CONVO) && - !isSubmitting + (conversationId == null || conversationId === Constants.NEW_CONVO) && + !isSubmitting && + conversation?.messages?.length === 0 ? 'transition-all duration-200 sm:mb-28' : 'sm:mb-10', )} @@ -290,7 +298,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
= 1 diff --git a/client/src/components/Chat/Menus/BookmarkMenu.tsx b/client/src/components/Chat/Menus/BookmarkMenu.tsx index 326d1dcd4a..48945a577e 100644 --- a/client/src/components/Chat/Menus/BookmarkMenu.tsx +++ b/client/src/components/Chat/Menus/BookmarkMenu.tsx @@ -170,7 +170,7 @@ const BookmarkMenu: FC = () => { id="bookmark-menu-button" aria-label={localize('com_ui_bookmarks_add')} className={cn( - 'mt-text-sm flex size-10 flex-shrink-0 items-center justify-center gap-2 rounded-lg border border-border-light text-sm transition-colors duration-200 hover:bg-surface-hover', + 'mt-text-sm flex size-10 flex-shrink-0 items-center justify-center gap-2 rounded-xl border border-border-light text-sm transition-colors duration-200 hover:bg-surface-hover', isMenuOpen ? 'bg-surface-hover' : '', )} data-testid="bookmark-menu" diff --git a/client/src/components/Chat/Menus/PresetsMenu.tsx b/client/src/components/Chat/Menus/PresetsMenu.tsx index 224a006d59..a0d0790f99 100644 --- a/client/src/components/Chat/Menus/PresetsMenu.tsx +++ b/client/src/components/Chat/Menus/PresetsMenu.tsx @@ -30,7 +30,7 @@ const PresetsMenu: FC = () => { tabIndex={0} role="button" data-testid="presets-button" - className="inline-flex size-10 flex-shrink-0 items-center justify-center rounded-lg border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary" + className="inline-flex size-10 flex-shrink-0 items-center justify-center rounded-xl border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary" > diff --git a/client/src/components/Chat/Messages/SearchButtons.tsx b/client/src/components/Chat/Messages/SearchButtons.tsx index c9feada2fa..9707767ad6 100644 --- a/client/src/components/Chat/Messages/SearchButtons.tsx +++ b/client/src/components/Chat/Messages/SearchButtons.tsx @@ -1,8 +1,8 @@ import { Link } from 'lucide-react'; import type { TMessage } from 'librechat-data-provider'; import { useLocalize, useNavigateToConvo } from '~/hooks'; +import { findConversationInInfinite } from '~/utils'; import { useSearchContext } from '~/Providers'; -import { getConversationById } from '~/utils'; export default function SearchButtons({ message }: { message: TMessage }) { const localize = useLocalize(); @@ -17,7 +17,7 @@ export default function SearchButtons({ message }: { message: TMessage }) { const clickHandler = (event: React.MouseEvent) => { event.preventDefault(); - const conversation = getConversationById(searchQueryRes?.data, conversationId); + const conversation = findConversationInInfinite(searchQueryRes?.data, conversationId); if (!conversation) { return; } diff --git a/client/src/components/Chat/Messages/SearchMessage.tsx b/client/src/components/Chat/Messages/SearchMessage.tsx index 30db3ac092..c7ac2c69c3 100644 --- a/client/src/components/Chat/Messages/SearchMessage.tsx +++ b/client/src/components/Chat/Messages/SearchMessage.tsx @@ -10,7 +10,30 @@ import SubRow from './SubRow'; import { cn } from '~/utils'; import store from '~/store'; -export default function Message({ message }: Pick) { +const MessageAvatar = ({ iconData }: { iconData: TMessageIcon }) => ( +
+
+
+ +
+
+
+); + +const MessageBody = ({ message, messageLabel, fontSize }) => ( +
+
{messageLabel}
+ + + + + +
+); + +export default function SearchMessage({ message }: Pick) { const UsernameDisplay = useRecoilValue(store.UsernameDisplay); const fontSize = useRecoilValue(store.fontSize); const { user } = useAuthContext(); @@ -18,60 +41,42 @@ export default function Message({ message }: Pick) { const iconData: TMessageIcon = useMemo( () => ({ - endpoint: message?.endpoint, - model: message?.model, + endpoint: message?.endpoint ?? '', + model: message?.model ?? '', iconURL: message?.iconURL ?? '', - isCreatedByUser: message?.isCreatedByUser, + isCreatedByUser: message?.isCreatedByUser ?? false, }), - [message?.model, message?.iconURL, message?.endpoint, message?.isCreatedByUser], + [message?.endpoint, message?.model, message?.iconURL, message?.isCreatedByUser], ); + const messageLabel = useMemo(() => { + if (message?.isCreatedByUser) { + return UsernameDisplay + ? (user?.name ?? '') || (user?.username ?? '') + : localize('com_user_message'); + } + return message?.sender ?? ''; + }, [ + message?.isCreatedByUser, + message?.sender, + UsernameDisplay, + user?.name, + user?.username, + localize, + ]); + if (!message) { return null; } - const { isCreatedByUser } = message; - - let messageLabel = ''; - if (isCreatedByUser) { - messageLabel = UsernameDisplay - ? (user?.name ?? '') || (user?.username ?? '') - : localize('com_user_message'); - } else { - messageLabel = message.sender ?? ''; - } - return ( - <> -
-
-
-
-
-
-
- -
-
-
-
-
-
{messageLabel}
-
-
- -
-
- - - - -
-
+
+
+
+ +
- +
); } diff --git a/client/src/components/Chat/TemporaryChat.tsx b/client/src/components/Chat/TemporaryChat.tsx index 13c05bee6c..25985e8192 100644 --- a/client/src/components/Chat/TemporaryChat.tsx +++ b/client/src/components/Chat/TemporaryChat.tsx @@ -37,34 +37,28 @@ export function TemporaryChat() { return (
-
- - {temporaryBadge.icon && ( - - )} - - } - /> -
+ className={cn( + 'inline-flex size-10 flex-shrink-0 items-center justify-center rounded-xl border border-border-light text-text-primary transition-all ease-in-out hover:bg-surface-tertiary', + isTemporary + ? 'bg-surface-active shadow-md' + : 'bg-transparent shadow-sm hover:bg-surface-hover hover:shadow-md', + 'active:shadow-inner', + )} + > + {temporaryBadge.icon && ( + + )} + + } + />
); } diff --git a/client/src/components/Conversations/Conversations.tsx b/client/src/components/Conversations/Conversations.tsx index 67d79c704c..cec289405a 100644 --- a/client/src/components/Conversations/Conversations.tsx +++ b/client/src/components/Conversations/Conversations.tsx @@ -1,67 +1,203 @@ -import { useMemo, memo } from 'react'; +import { useMemo, memo, type FC, useCallback } from 'react'; +import throttle from 'lodash/throttle'; import { parseISO, isToday } from 'date-fns'; +import { List, AutoSizer, CellMeasurer, CellMeasurerCache } from 'react-virtualized'; +import { useLocalize, TranslationKeys, useMediaQuery } from '~/hooks'; import { TConversation } from 'librechat-data-provider'; -import { useLocalize, TranslationKeys } from '~/hooks'; import { groupConversationsByDate } from '~/utils'; +import { Spinner } from '~/components/svg'; import Convo from './Convo'; -const Conversations = ({ - conversations, - moveToTop, - toggleNav, -}: { +interface ConversationsProps { conversations: Array; moveToTop: () => void; toggleNav: () => void; -}) => { + containerRef: React.RefObject; + loadMoreConversations: () => void; + isFetchingNextPage: boolean; + isSearchLoading: boolean; +} + +const LoadingSpinner = memo(() => ( + +)); + +const DateLabel: FC<{ groupName: string }> = memo(({ groupName }) => { const localize = useLocalize(); - const groupedConversations = useMemo( - () => groupConversationsByDate(conversations), - [conversations], + return ( +
+ {localize(groupName as TranslationKeys) || groupName} +
); +}); + +DateLabel.displayName = 'DateLabel'; + +type FlattenedItem = + | { type: 'header'; groupName: string } + | { type: 'convo'; convo: TConversation }; + +const MemoizedConvo = memo( + ({ + conversation, + retainView, + toggleNav, + isLatestConvo, + }: { + conversation: TConversation; + retainView: () => void; + toggleNav: () => void; + isLatestConvo: boolean; + }) => { + return ( + + ); + }, + (prevProps, nextProps) => { + return ( + prevProps.conversation.conversationId === nextProps.conversation.conversationId && + prevProps.conversation.title === nextProps.conversation.title && + prevProps.isLatestConvo === nextProps.isLatestConvo && + prevProps.conversation.endpoint === nextProps.conversation.endpoint + ); + }, +); + +const Conversations: FC = ({ + conversations: rawConversations, + moveToTop, + toggleNav, + containerRef, + loadMoreConversations, + isFetchingNextPage, + isSearchLoading, +}) => { + const isSmallScreen = useMediaQuery('(max-width: 768px)'); + const convoHeight = isSmallScreen ? 44 : 34; + + const filteredConversations = useMemo( + () => rawConversations.filter(Boolean) as TConversation[], + [rawConversations], + ); + + const groupedConversations = useMemo( + () => groupConversationsByDate(filteredConversations), + [filteredConversations], + ); + const firstTodayConvoId = useMemo( () => - conversations.find((convo) => convo && convo.updatedAt && isToday(parseISO(convo.updatedAt))) - ?.conversationId, - [conversations], + filteredConversations.find((convo) => convo.updatedAt && isToday(parseISO(convo.updatedAt))) + ?.conversationId ?? undefined, + [filteredConversations], + ); + + const flattenedItems = useMemo(() => { + const items: FlattenedItem[] = []; + groupedConversations.forEach(([groupName, convos]) => { + items.push({ type: 'header', groupName }); + items.push(...convos.map((convo) => ({ type: 'convo' as const, convo }))); + }); + return items; + }, [groupedConversations]); + + const cache = useMemo( + () => + new CellMeasurerCache({ + fixedWidth: true, + defaultHeight: convoHeight, + keyMapper: (index) => { + const item = flattenedItems[index]; + return item.type === 'header' ? `header-${index}` : `convo-${item.convo.conversationId}`; + }, + }), + [flattenedItems, convoHeight], + ); + + const rowRenderer = useCallback( + ({ index, key, parent, style }) => { + const item = flattenedItems[index]; + return ( + + {({ registerChild }) => ( +
+ {item.type === 'header' ? ( + + ) : ( + + )} +
+ )} +
+ ); + }, + [cache, flattenedItems, firstTodayConvoId, moveToTop, toggleNav], + ); + + const getRowHeight = useCallback( + ({ index }: { index: number }) => cache.getHeight(index, 0), + [cache], + ); + + const throttledLoadMore = useMemo( + () => throttle(loadMoreConversations, 300), + [loadMoreConversations], + ); + + const handleRowsRendered = useCallback( + ({ stopIndex }: { stopIndex: number }) => { + if (stopIndex >= flattenedItems.length - 2) { + throttledLoadMore(); + } + }, + [flattenedItems.length, throttledLoadMore], ); return ( -
-
- - {groupedConversations.map(([groupName, convos]) => ( -
-
- {localize(groupName as TranslationKeys) || groupName} -
- {convos.map((convo, i) => ( - - ))} -
+ {isSearchLoading ? ( +
+ + Loading... +
+ ) : ( +
+ + {({ width, height }) => ( + } + width={width} + height={height} + deferredMeasurementCache={cache} + rowCount={flattenedItems.length} + rowHeight={getRowHeight} + rowRenderer={rowRenderer} + overscanRowCount={10} + className="outline-none" + style={{ outline: 'none' }} + role="list" + aria-label="Conversations" + onRowsRendered={handleRowsRendered} /> -
- ))} - -
+ )} + +
+ )} + {isFetchingNextPage && !isSearchLoading && ( +
+ +
+ )}
); }; diff --git a/client/src/components/Conversations/Convo.tsx b/client/src/components/Conversations/Convo.tsx index b0e6e06636..b31cfb5a55 100644 --- a/client/src/components/Conversations/Convo.tsx +++ b/client/src/components/Conversations/Convo.tsx @@ -1,28 +1,26 @@ -import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; import { useRecoilValue } from 'recoil'; -import { Check, X } from 'lucide-react'; import { useParams } from 'react-router-dom'; import { Constants } from 'librechat-data-provider'; -import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react'; import type { TConversation } from 'librechat-data-provider'; import { useNavigateToConvo, useMediaQuery, useLocalize } from '~/hooks'; import { useUpdateConversationMutation } from '~/data-provider'; import EndpointIcon from '~/components/Endpoints/EndpointIcon'; import { useGetEndpointsQuery } from '~/data-provider'; import { NotificationSeverity } from '~/common'; -import { useToastContext } from '~/Providers'; import { ConvoOptions } from './ConvoOptions'; +import { useToastContext } from '~/Providers'; +import RenameForm from './RenameForm'; +import ConvoLink from './ConvoLink'; import { cn } from '~/utils'; import store from '~/store'; -type KeyEvent = KeyboardEvent; - -type ConversationProps = { +interface ConversationProps { conversation: TConversation; retainView: () => void; toggleNav: () => void; isLatestConvo: boolean; -}; +} export default function Conversation({ conversation, @@ -31,27 +29,81 @@ export default function Conversation({ isLatestConvo, }: ConversationProps) { const params = useParams(); + const localize = useLocalize(); + const { showToast } = useToastContext(); const currentConvoId = useMemo(() => params.conversationId, [params.conversationId]); const updateConvoMutation = useUpdateConversationMutation(currentConvoId ?? ''); const activeConvos = useRecoilValue(store.allConversationsSelector); const { data: endpointsConfig } = useGetEndpointsQuery(); const { navigateWithLastTools } = useNavigateToConvo(); - const { showToast } = useToastContext(); - const { conversationId, title } = conversation; - const inputRef = useRef(null); - const [titleInput, setTitleInput] = useState(title); + const isSmallScreen = useMediaQuery('(max-width: 768px)'); + const { conversationId, title = '' } = conversation; + + const [titleInput, setTitleInput] = useState(title || ''); const [renaming, setRenaming] = useState(false); const [isPopoverActive, setIsPopoverActive] = useState(false); - const isSmallScreen = useMediaQuery('(max-width: 768px)'); - const localize = useLocalize(); - const clickHandler = async (event: MouseEvent) => { - if (event.button === 0 && (event.ctrlKey || event.metaKey)) { - toggleNav(); + const previousTitle = useRef(title); + + useEffect(() => { + if (title !== previousTitle.current) { + setTitleInput(title as string); + previousTitle.current = title; + } + }, [title]); + + const isActiveConvo = useMemo(() => { + if (conversationId === Constants.NEW_CONVO) { + return currentConvoId === Constants.NEW_CONVO; + } + + if (currentConvoId !== Constants.NEW_CONVO) { + return currentConvoId === conversationId; + } else { + const latestConvo = activeConvos?.[0]; + return latestConvo === conversationId; + } + }, [currentConvoId, conversationId, activeConvos]); + + const handleRename = () => { + setIsPopoverActive(false); + setTitleInput(title as string); + setRenaming(true); + }; + + const handleRenameSubmit = async (newTitle: string) => { + if (!conversationId || newTitle === title) { + setRenaming(false); return; } - event.preventDefault(); + try { + await updateConvoMutation.mutateAsync({ + conversationId, + title: newTitle.trim() || localize('com_ui_untitled'), + }); + setRenaming(false); + } catch (error) { + setTitleInput(title as string); + showToast({ + message: localize('com_ui_rename_failed'), + severity: NotificationSeverity.ERROR, + showIcon: true, + }); + setRenaming(false); + } + }; + + const handleCancelRename = () => { + setTitleInput(title as string); + setRenaming(false); + }; + + const handleNavigation = (ctrlOrMetaKey: boolean) => { + if (ctrlOrMetaKey) { + toggleNav(); + return; + } if (currentConvoId === conversationId || isPopoverActive) { return; @@ -59,138 +111,68 @@ export default function Conversation({ toggleNav(); - // set document title if (typeof title === 'string' && title.length > 0) { document.title = title; } - /* Note: Latest Message should not be reset if existing convo */ + navigateWithLastTools( conversation, !(conversationId ?? '') || conversationId === Constants.NEW_CONVO, ); }; - const renameHandler = useCallback(() => { - setIsPopoverActive(false); - setTitleInput(title); - setRenaming(true); - }, [title]); - - useEffect(() => { - if (renaming && inputRef.current) { - inputRef.current.focus(); - } - }, [renaming]); - - const onRename = useCallback( - (e: MouseEvent | FocusEvent | KeyEvent) => { - e.preventDefault(); - setRenaming(false); - if (titleInput === title) { - return; - } - if (typeof conversationId !== 'string' || conversationId === '') { - return; - } - - updateConvoMutation.mutate( - { conversationId, title: titleInput ?? '' }, - { - onError: () => { - setTitleInput(title); - showToast({ - message: 'Failed to rename conversation', - severity: NotificationSeverity.ERROR, - showIcon: true, - }); - }, - }, - ); - }, - [title, titleInput, conversationId, showToast, updateConvoMutation], - ); - - const handleKeyDown = useCallback( - (e: KeyEvent) => { - if (e.key === 'Escape') { - setTitleInput(title); - setRenaming(false); - } else if (e.key === 'Enter') { - onRename(e); - } - }, - [title, onRename], - ); - - const cancelRename = useCallback( - (e: MouseEvent) => { - e.preventDefault(); - setTitleInput(title); - setRenaming(false); - }, - [title], - ); - - const isActiveConvo: boolean = useMemo( - () => - currentConvoId === conversationId || - (isLatestConvo && - currentConvoId === 'new' && - activeConvos[0] != null && - activeConvos[0] !== 'new'), - [currentConvoId, conversationId, isLatestConvo, activeConvos], - ); + const convoOptionsProps = { + title, + retainView, + renameHandler: handleRename, + isActiveConvo, + conversationId, + isPopoverActive, + setIsPopoverActive, + }; return (
{ + if (renaming) { + return; + } + if (e.button === 0) { + handleNavigation(e.ctrlKey || e.metaKey); + } + }} + onKeyDown={(e) => { + if (renaming) { + return; + } + if (e.key === 'Enter') { + handleNavigation(false); + } + }} + style={{ cursor: renaming ? 'default' : 'pointer' }} + data-testid="convo-item" > {renaming ? ( -
- setTitleInput(e.target.value)} - onKeyDown={handleKeyDown} - aria-label={`${localize('com_ui_rename')} ${localize('com_ui_chat')}`} - /> -
- - -
-
+ ) : ( -
-
{ - e.preventDefault(); - e.stopPropagation(); - setTitleInput(title); - setRenaming(true); - }} - > - {title} -
- {isActiveConvo ? ( -
- ) : ( -
- )} - + )}
- {!renaming && ( - - )} + {!renaming && }
); diff --git a/client/src/components/Conversations/ConvoLink.tsx b/client/src/components/Conversations/ConvoLink.tsx new file mode 100644 index 0000000000..4e8d5888e4 --- /dev/null +++ b/client/src/components/Conversations/ConvoLink.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { cn } from '~/utils'; + +interface ConvoLinkProps { + isActiveConvo: boolean; + title: string | null; + onRename: () => void; + isSmallScreen: boolean; + localize: (key: any, options?: any) => string; + children: React.ReactNode; +} + +const ConvoLink: React.FC = ({ + isActiveConvo, + title, + onRename, + isSmallScreen, + localize, + children, +}) => { + return ( +
+ {children} +
{ + if (isSmallScreen) { + return; + } + e.preventDefault(); + e.stopPropagation(); + onRename(); + }} + role="button" + aria-label={isSmallScreen ? undefined : localize('com_ui_double_click_to_rename')} + > + {title || localize('com_ui_untitled')} +
+ + ); +}; + +export default ConvoLink; diff --git a/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx b/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx index c9b697ece3..d367dd3963 100644 --- a/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx +++ b/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx @@ -1,12 +1,17 @@ -import { useState, useId, useRef, memo } from 'react'; +import { useState, useId, useRef, memo, useCallback, useMemo } from 'react'; import * as Menu from '@ariakit/react/menu'; +import { useParams, useNavigate } from 'react-router-dom'; import { Ellipsis, Share2, Copy, Archive, Pen, Trash } from 'lucide-react'; import type { MouseEvent } from 'react'; -import type * as t from '~/common'; -import { useDuplicateConversationMutation, useGetStartupConfig } from '~/data-provider'; -import { useLocalize, useArchiveHandler, useNavigateToConvo } from '~/hooks'; +import { + useDuplicateConversationMutation, + useGetStartupConfig, + useArchiveConvoMutation, +} from '~/data-provider'; +import { useLocalize, useNavigateToConvo, useNewConvo } from '~/hooks'; import { useToastContext, useChatContext } from '~/Providers'; -import { DropdownPopup } from '~/components/ui'; +import { DropdownPopup, Spinner } from '~/components'; +import { NotificationSeverity } from '~/common'; import DeleteButton from './DeleteButton'; import ShareButton from './ShareButton'; import { cn } from '~/utils'; @@ -31,14 +36,49 @@ function ConvoOptions({ const localize = useLocalize(); const { index } = useChatContext(); const { data: startupConfig } = useGetStartupConfig(); - const archiveHandler = useArchiveHandler(conversationId, true, retainView); const { navigateToConvo } = useNavigateToConvo(index); const { showToast } = useToastContext(); + + const navigate = useNavigate(); + const { conversationId: currentConvoId } = useParams(); + const { newConversation } = useNewConvo(); + const shareButtonRef = useRef(null); const deleteButtonRef = useRef(null); const [showShareDialog, setShowShareDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const archiveConvoMutation = useArchiveConvoMutation(); + + const archiveHandler = async () => { + const convoId = conversationId ?? ''; + + if (!convoId) { + return; + } + + archiveConvoMutation.mutate( + { conversationId: convoId, isArchived: true }, + { + onSuccess: () => { + if (currentConvoId === convoId || currentConvoId === 'new') { + newConversation(); + navigate('/c/new', { replace: true }); + } + retainView(); + setIsPopoverActive(false); + }, + onError: () => { + showToast({ + message: localize('com_ui_archive_error'), + severity: NotificationSeverity.ERROR, + showIcon: true, + }); + }, + }, + ); + }; + const duplicateConversation = useDuplicateConversationMutation({ onSuccess: (data) => { navigateToConvo(data.conversation); @@ -46,6 +86,7 @@ function ConvoOptions({ message: localize('com_ui_duplication_success'), status: 'success', }); + setIsPopoverActive(false); }, onMutate: () => { showToast({ @@ -61,56 +102,118 @@ function ConvoOptions({ }, }); - const shareHandler = () => { + const isDuplicateLoading = duplicateConversation.isLoading; + const isArchiveLoading = archiveConvoMutation.isLoading; + + const handleShareClick = useCallback(() => { setShowShareDialog(true); - }; + }, []); - const deleteHandler = () => { + const handleDeleteClick = useCallback(() => { setShowDeleteDialog(true); - }; + }, []); - const duplicateHandler = () => { - setIsPopoverActive(false); + const handleArchiveClick = useCallback(async () => { + const convoId = conversationId ?? ''; + if (!convoId) { + return; + } + + archiveConvoMutation.mutate( + { conversationId: convoId, isArchived: true }, + { + onSuccess: () => { + if (currentConvoId === convoId || currentConvoId === 'new') { + newConversation(); + navigate('/c/new', { replace: true }); + } + retainView(); + setIsPopoverActive(false); + }, + onError: () => { + showToast({ + message: localize('com_ui_archive_error'), + severity: NotificationSeverity.ERROR, + showIcon: true, + }); + }, + }, + ); + }, [ + conversationId, + currentConvoId, + archiveConvoMutation, + navigate, + newConversation, + retainView, + setIsPopoverActive, + showToast, + localize, + ]); + + const handleDuplicateClick = useCallback(() => { duplicateConversation.mutate({ conversationId: conversationId ?? '', }); - }; + }, [conversationId, duplicateConversation]); - const dropdownItems: t.MenuItemProps[] = [ - { - label: localize('com_ui_share'), - onClick: shareHandler, - icon: , - show: startupConfig && startupConfig.sharedLinksEnabled, - /** NOTE: THE FOLLOWING PROPS ARE REQUIRED FOR MENU ITEMS THAT OPEN DIALOGS */ - hideOnClick: false, - ref: shareButtonRef, - render: (props) => + +
+ ); } @@ -84,7 +101,7 @@ export default function DeleteButton({ setShowDeleteDialog, triggerRef, }: DeleteButtonProps) { - if (showDeleteDialog === undefined && setShowDeleteDialog === undefined) { + if (showDeleteDialog === undefined || setShowDeleteDialog === undefined) { return null; } @@ -93,8 +110,9 @@ export default function DeleteButton({ } return ( - + void; + onSubmit: (title: string) => void; + onCancel: () => void; + localize: (key: any, options?: any) => string; +} + +const RenameForm: React.FC = ({ + titleInput, + setTitleInput, + onSubmit, + onCancel, + localize, +}) => { + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + } + }, []); + + const handleKeyDown = (e: KeyboardEvent) => { + switch (e.key) { + case 'Escape': + onCancel(); + break; + case 'Enter': + onSubmit(titleInput); + break; + } + }; + + return ( +
+ setTitleInput(e.target.value)} + onKeyDown={handleKeyDown} + onBlur={() => onSubmit(titleInput)} + maxLength={100} + aria-label={localize('com_ui_new_conversation_title')} + /> +
+ + +
+
+ ); +}; + +export default RenameForm; diff --git a/client/src/components/Nav/AccountSettings.tsx b/client/src/components/Nav/AccountSettings.tsx index 70bf90d7c4..7d4e6576c2 100644 --- a/client/src/components/Nav/AccountSettings.tsx +++ b/client/src/components/Nav/AccountSettings.tsx @@ -30,7 +30,7 @@ function AccountSettings() {
diff --git a/client/src/components/Nav/Bookmarks/BookmarkNav.tsx b/client/src/components/Nav/Bookmarks/BookmarkNav.tsx index c1de20f1ad..fa8ab06d6a 100644 --- a/client/src/components/Nav/Bookmarks/BookmarkNav.tsx +++ b/client/src/components/Nav/Bookmarks/BookmarkNav.tsx @@ -33,7 +33,7 @@ const BookmarkNav: FC = ({ tags, setTags, isSmallScreen }: Boo data-testid="bookmark-menu" >
-
+
{tags.length > 0 ? (
- + {data && conversation && ( tag.count > 0) }}> { - const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, - }); - - return render( - - - - - -