diff --git a/api/server/services/AssistantService.js b/api/server/services/AssistantService.js
index f9f613acf5..6ab14bad4f 100644
--- a/api/server/services/AssistantService.js
+++ b/api/server/services/AssistantService.js
@@ -286,6 +286,9 @@ function createInProgressHandler(openai, thread_id, messages) {
openai.seenCompletedMessages.add(message_id);
const message = await openai.beta.threads.messages.retrieve(thread_id, message_id);
+ if (!message?.content?.length) {
+ return;
+ }
messages.push(message);
let messageIndex = openai.mappedOrder.get(step.id);
diff --git a/api/server/services/Runs/RunManager.js b/api/server/services/Runs/RunManager.js
index b7f7400c3d..c8deeb9264 100644
--- a/api/server/services/Runs/RunManager.js
+++ b/api/server/services/Runs/RunManager.js
@@ -1,3 +1,4 @@
+const { ToolCallTypes } = require('librechat-data-provider');
const { logger } = require('~/config');
/**
@@ -18,6 +19,53 @@ const { logger } = require('~/config');
* @property {Function} handleStep - Handles a run step based on its status.
*/
+/**
+ * Generates a signature string for a given tool call object. This signature includes
+ * the tool call's id, type, and other distinguishing features based on its type.
+ *
+ * @param {ToolCall} toolCall The tool call object for which to generate a signature.
+ * @returns {string} The generated signature for the tool call.
+ */
+function getToolCallSignature(toolCall) {
+ if (toolCall.type === ToolCallTypes.CODE_INTERPRETER) {
+ const inputLength = toolCall.code_interpreter?.input?.length ?? 0;
+ const outputsLength = toolCall.code_interpreter?.outputs?.length ?? 0;
+ return `${toolCall.id}-${toolCall.type}-${inputLength}-${outputsLength}`;
+ }
+ if (toolCall.type === ToolCallTypes.RETRIEVAL) {
+ return `${toolCall.id}-${toolCall.type}`;
+ }
+ if (toolCall.type === ToolCallTypes.FUNCTION) {
+ const argsLength = toolCall.function?.arguments?.length ?? 0;
+ const hasOutput = toolCall.function?.output ? 1 : 0;
+ return `${toolCall.id}-${toolCall.type}-${argsLength}-${hasOutput}`;
+ }
+
+ return `${toolCall.id}-unknown-type`;
+}
+
+/**
+ * Generates a signature based on the specifics of the step details.
+ * This function supports 'message_creation' and 'tool_calls' types, and returns a default signature
+ * for any other type or in case the details are undefined.
+ *
+ * @param {MessageCreationStepDetails | ToolCallsStepDetails | undefined} details - The detailed content of the step, which can be undefined.
+ * @returns {string} A signature string derived from the content of step details.
+ */
+function getDetailsSignature(details) {
+ if (!details) {
+ return 'undefined-details';
+ }
+
+ if (details.type === 'message_creation') {
+ return `${details.type}-${details.message_creation.message_id}`;
+ } else if (details.type === 'tool_calls') {
+ const toolCallsSignature = details.tool_calls.map(getToolCallSignature).join('|');
+ return `${details.type}-${toolCallsSignature}`;
+ }
+ return 'unknown-type';
+}
+
/**
* Manages the retrieval and processing of run steps based on run status.
*/
@@ -55,12 +103,14 @@ class RunManager {
);
const steps = _steps.sort((a, b) => a.created_at - b.created_at);
for (const [i, step] of steps.entries()) {
- if (!final && this.seenSteps.has(`${step.id}-${step.status}`)) {
+ const detailsSignature = getDetailsSignature(step.step_details);
+ const stepKey = `${step.id}-${step.status}-${detailsSignature}`;
+ if (!final && this.seenSteps.has(stepKey)) {
continue;
}
const isLast = i === steps.length - 1;
- this.seenSteps.add(`${step.id}-${step.status}`);
+ this.seenSteps.add(stepKey);
this.stepsByStatus[runStatus] = this.stepsByStatus[runStatus] || [];
const currentStepPromise = (async () => {
diff --git a/client/src/components/Chat/Messages/Content/ContentParts.tsx b/client/src/components/Chat/Messages/Content/ContentParts.tsx
index d897d6f2c3..e436ea1a81 100644
--- a/client/src/components/Chat/Messages/Content/ContentParts.tsx
+++ b/client/src/components/Chat/Messages/Content/ContentParts.tsx
@@ -1,12 +1,10 @@
import { Suspense } from 'react';
-// import type { ContentPart } from 'librechat-data-provider';
+import type { TMessageContentParts } from 'librechat-data-provider';
import { UnfinishedMessage } from './MessageContent';
import { DelayedRender } from '~/components/ui';
import Part from './Part';
-// Content Component
const ContentParts = ({
- edit,
error,
unfinished,
isSubmitting,
@@ -17,15 +15,16 @@ const ContentParts = ({
any) => {
if (error) {
// return ;
- } else if (edit) {
- // return ;
} else {
const { message } = props;
const { messageId } = message;
return (
<>
- {content.map((part, idx) => {
+ {content.map((part: TMessageContentParts | undefined, idx: number) => {
+ if (!part) {
+ return null;
+ }
return (
p !== undefined);
-
setMessages([...messages, response]);
};
}