mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-28 19:05:31 +02:00
Refactor agents api integration
This commit is contained in:
parent
0e31098d58
commit
a02c830fb0
14 changed files with 131 additions and 1121 deletions
|
|
@ -1,26 +1,16 @@
|
|||
'use server';
|
||||
import { convertFromAgenticAPIChatMessages } from "../lib/types/agents_api_types";
|
||||
import { AgenticAPIChatRequest } from "../lib/types/agents_api_types";
|
||||
import { WorkflowAgent } from "../lib/types/workflow_types";
|
||||
import { EmbeddingRecord } from "../lib/types/datasource_types";
|
||||
import { WebpageCrawlResponse } from "../lib/types/tool_types";
|
||||
import { GetInformationToolResult } from "../lib/types/tool_types";
|
||||
import { EmbeddingDoc } from "../lib/types/datasource_types";
|
||||
import { generateObject, generateText, embed } from "ai";
|
||||
import { dataSourceDocsCollection, dataSourcesCollection, embeddingsCollection, webpagesCollection } from "../lib/mongodb";
|
||||
import { webpagesCollection } from "../lib/mongodb";
|
||||
import { z } from 'zod';
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import FirecrawlApp, { ScrapeResponse } from '@mendable/firecrawl-js';
|
||||
import { embeddingModel } from "../lib/embedding";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { Claims, getSession } from "@auth0/nextjs-auth0";
|
||||
import { callClientToolWebhook, getAgenticApiResponse, mockToolResponse, runRAGToolCall } from "../lib/utils";
|
||||
import { getAgenticApiResponse } from "../lib/utils";
|
||||
import { check_query_limit } from "../lib/rate_limiting";
|
||||
import { QueryLimitError } from "../lib/client_utils";
|
||||
import { projectAuthCheck } from "./project_actions";
|
||||
import { qdrantClient } from "../lib/qdrant";
|
||||
import { ObjectId } from "mongodb";
|
||||
import { TestProfile } from "../lib/types/testing_types";
|
||||
|
||||
const crawler = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY || '' });
|
||||
|
||||
|
|
@ -98,96 +88,3 @@ export async function getAssistantResponse(
|
|||
rawResponse: response.rawAPIResponse,
|
||||
};
|
||||
}
|
||||
|
||||
export async function suggestToolResponse(toolId: string, projectId: string, messages: z.infer<typeof apiV1.ChatMessage>[], mockInstructions: string): Promise<string> {
|
||||
await projectAuthCheck(projectId);
|
||||
if (!await check_query_limit(projectId)) {
|
||||
throw new QueryLimitError();
|
||||
}
|
||||
|
||||
return await mockToolResponse(toolId, messages, mockInstructions);
|
||||
}
|
||||
|
||||
export async function getInformationTool(
|
||||
projectId: string,
|
||||
query: string,
|
||||
sourceIds: string[],
|
||||
returnType: z.infer<typeof WorkflowAgent>['ragReturnType'],
|
||||
k: number,
|
||||
): Promise<z.infer<typeof GetInformationToolResult>> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
return await runRAGToolCall(projectId, query, sourceIds, returnType, k);
|
||||
}
|
||||
|
||||
export async function simulateUserResponse(
|
||||
projectId: string,
|
||||
messages: z.infer<typeof apiV1.ChatMessage>[],
|
||||
scenario: string,
|
||||
): Promise<string> {
|
||||
await projectAuthCheck(projectId);
|
||||
if (!await check_query_limit(projectId)) {
|
||||
throw new QueryLimitError();
|
||||
}
|
||||
|
||||
const scenarioPrompt = `
|
||||
# Your Specific Task:
|
||||
|
||||
## Context:
|
||||
|
||||
Here is a scenario:
|
||||
|
||||
Scenario:
|
||||
<START_SCENARIO>
|
||||
{{scenario}}
|
||||
<END_SCENARIO>
|
||||
|
||||
## Task definition:
|
||||
|
||||
Pretend to be a user reaching out to customer support. Chat with the
|
||||
customer support assistant, assuming your issue is based on this scenario.
|
||||
Ask follow-up questions and make it real-world like. Don't do dummy
|
||||
conversations. Your conversation should be a maximum of 5 user turns.
|
||||
|
||||
As output, simply provide your (user) turn of conversation.
|
||||
|
||||
After you are done with the chat, keep replying with a single word EXIT
|
||||
in all capitals.
|
||||
`;
|
||||
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// flip message assistant / user message
|
||||
// roles from chat messages
|
||||
// use only text response messages
|
||||
const flippedMessages: { role: 'user' | 'assistant', content: string }[] = messages
|
||||
.filter(m => m.role == 'assistant' || m.role == 'user')
|
||||
.map(m => ({
|
||||
role: m.role == 'assistant' ? 'user' : 'assistant',
|
||||
content: m.content || '',
|
||||
}));
|
||||
|
||||
// simulate user call
|
||||
let prompt;
|
||||
prompt = scenarioPrompt
|
||||
.replace('{{scenario}}', scenario);
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai("gpt-4o"),
|
||||
system: prompt || '',
|
||||
messages: flippedMessages,
|
||||
});
|
||||
|
||||
return text.replace(/\. EXIT$/, '.');
|
||||
}
|
||||
|
||||
export async function executeClientTool(
|
||||
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number],
|
||||
messages: z.infer<typeof apiV1.ChatMessage>[],
|
||||
projectId: string,
|
||||
): Promise<unknown> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
const result = await callClientToolWebhook(toolCall, messages, projectId);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import { WorkflowTool } from "../lib/types/workflow_types";
|
|||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
import { projectAuthCheck } from "./project_actions";
|
||||
import { callMcpTool } from "../lib/utils";
|
||||
import { projectsCollection } from "../lib/mongodb";
|
||||
import { Project } from "../lib/types/project_types";
|
||||
|
||||
|
|
@ -72,11 +71,4 @@ export async function updateMcpServers(projectId: string, mcpServers: z.infer<ty
|
|||
await projectsCollection.updateOne({
|
||||
_id: projectId,
|
||||
}, { $set: { mcpServers } });
|
||||
}
|
||||
|
||||
export async function executeMcpTool(projectId: string, mcpServerName: string, toolName: string, parameters: Record<string, unknown>): Promise<unknown> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
const result = await callMcpTool(projectId, mcpServerName, toolName, parameters);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -4,10 +4,9 @@ import { z } from "zod";
|
|||
import { ObjectId } from "mongodb";
|
||||
import { authCheck } from "../../utils";
|
||||
import { ApiRequest, ApiResponse } from "../../../../lib/types/types";
|
||||
import { AgenticAPIChatRequest, AgenticAPIChatMessage, convertFromAgenticApiToApiMessages, convertFromApiToAgenticApiMessages, convertWorkflowToAgenticAPI } from "../../../../lib/types/agents_api_types";
|
||||
import { getAgenticApiResponse, callClientToolWebhook, runRAGToolCall, mockToolResponse, callMcpTool } from "../../../../lib/utils";
|
||||
import { AgenticAPIChatRequest, convertFromAgenticApiToApiMessages, convertFromApiToAgenticApiMessages, convertWorkflowToAgenticAPI } from "../../../../lib/types/agents_api_types";
|
||||
import { getAgenticApiResponse } from "../../../../lib/utils";
|
||||
import { check_query_limit } from "../../../../lib/rate_limiting";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { PrefixLogger } from "../../../../lib/utils";
|
||||
import { TestProfile } from "@/app/lib/types/testing_types";
|
||||
|
||||
|
|
@ -70,7 +69,7 @@ export async function POST(
|
|||
logger.log(`Workflow ${workflowId} not found for project ${projectId}`);
|
||||
return Response.json({ error: "Workflow not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
|
||||
// if test profile is provided in the request, use it
|
||||
let testProfile: z.infer<typeof TestProfile> | null = null;
|
||||
if (result.data.testProfileId) {
|
||||
|
|
@ -84,138 +83,29 @@ export async function POST(
|
|||
}
|
||||
}
|
||||
|
||||
// if profile has a context available, overwrite the system message in the request (if there is one)
|
||||
let currentMessages = reqMessages;
|
||||
if (testProfile?.context) {
|
||||
// if there is a system message, overwrite it
|
||||
const systemMessageIndex = reqMessages.findIndex(m => m.role === "system");
|
||||
if (systemMessageIndex !== -1) {
|
||||
currentMessages[systemMessageIndex].content = testProfile.context;
|
||||
} else {
|
||||
// if there is no system message, add one
|
||||
currentMessages.unshift({ role: "system", content: testProfile.context });
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_TURNS = result.data.maxTurns ?? 3;
|
||||
let currentState: unknown = reqState ?? { last_agent_name: workflow.agents[0].name };
|
||||
let turns = 0;
|
||||
let hasToolCalls = false;
|
||||
|
||||
do {
|
||||
hasToolCalls = false;
|
||||
// get assistant response
|
||||
const { agents, tools, prompts, startAgent } = convertWorkflowToAgenticAPI(workflow);
|
||||
const request: z.infer<typeof AgenticAPIChatRequest> = {
|
||||
messages: convertFromApiToAgenticApiMessages(currentMessages),
|
||||
state: currentState,
|
||||
agents,
|
||||
tools,
|
||||
prompts,
|
||||
startAgent,
|
||||
};
|
||||
// get assistant response
|
||||
const { agents, tools, prompts, startAgent } = convertWorkflowToAgenticAPI(workflow);
|
||||
const request: z.infer<typeof AgenticAPIChatRequest> = {
|
||||
messages: convertFromApiToAgenticApiMessages(reqMessages),
|
||||
state: currentState,
|
||||
agents,
|
||||
tools,
|
||||
prompts,
|
||||
startAgent,
|
||||
testProfile: testProfile ?? undefined,
|
||||
mcpServers: project.mcpServers ?? undefined,
|
||||
toolWebhookUrl: project.webhookUrl ?? undefined,
|
||||
};
|
||||
|
||||
console.log(`turn ${turns}: sending agentic request from /chat api`, JSON.stringify(request, null, 2));
|
||||
logger.log(`Processing turn ${turns} for conversation`);
|
||||
const { messages: agenticMessages, state } = await getAgenticApiResponse(request);
|
||||
|
||||
const newMessages = convertFromAgenticApiToApiMessages(agenticMessages);
|
||||
currentState = state;
|
||||
|
||||
// if tool calls are to be skipped, return immediately
|
||||
if (result.data.skipToolCalls) {
|
||||
logger.log('Skipping tool calls as requested');
|
||||
const responseBody: z.infer<typeof ApiResponse> = {
|
||||
messages: newMessages,
|
||||
state: currentState,
|
||||
};
|
||||
return Response.json(responseBody);
|
||||
}
|
||||
|
||||
// get last message to check for tool calls
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage?.role === "assistant" &&
|
||||
'tool_calls' in lastMessage &&
|
||||
lastMessage.tool_calls?.length > 0) {
|
||||
hasToolCalls = true;
|
||||
const toolCallResultMessages: z.infer<typeof apiV1.ToolMessage>[] = [];
|
||||
|
||||
// Process tool calls
|
||||
for (const toolCall of lastMessage.tool_calls) {
|
||||
let result: unknown;
|
||||
if (toolCall.function.name === "getArticleInfo") {
|
||||
logger.log(`Running RAG tool call for agent ${lastMessage.agenticSender}`);
|
||||
// find the source ids attached to this agent in the workflow
|
||||
const agent = workflow.agents.find(a => a.name === lastMessage.agenticSender);
|
||||
if (!agent) {
|
||||
return Response.json({ error: "Agent not found" }, { status: 404 });
|
||||
}
|
||||
const sourceIds = agent.ragDataSources;
|
||||
if (!sourceIds) {
|
||||
return Response.json({ error: "Agent has no data sources" }, { status: 404 });
|
||||
}
|
||||
try {
|
||||
result = await runRAGToolCall(projectId, toolCall.function.arguments, sourceIds, agent.ragReturnType, agent.ragK);
|
||||
logger.log(`RAG tool call completed for agent ${lastMessage.agenticSender}`);
|
||||
} catch (e) {
|
||||
logger.log(`Error running RAG tool call: ${e}`);
|
||||
return Response.json({ error: "Error running RAG tool call" }, { status: 500 });
|
||||
}
|
||||
} else {
|
||||
logger.log(`Processing tool call ${toolCall.function.name}`);
|
||||
|
||||
try {
|
||||
// if tool is supposed to be mocked, mock it
|
||||
const workflowTool = workflow.tools.find(t => t.name === toolCall.function.name);
|
||||
if (testProfile?.mockTools || workflowTool?.mockTool) {
|
||||
logger.log(`Mocking tool call ${toolCall.function.name}`);
|
||||
result = await mockToolResponse(toolCall.id, currentMessages, testProfile?.mockPrompt || workflowTool?.mockInstructions || '');
|
||||
} else if (workflowTool?.isMcp) {
|
||||
// else run the tool call by calling the MCP tool
|
||||
logger.log(`Calling MCP tool: ${toolCall.function.name}`);
|
||||
result = await callMcpTool(projectId, workflowTool.mcpServerName ?? 'default', toolCall.function.name, JSON.parse(toolCall.function.arguments));
|
||||
} else {
|
||||
// else run the tool call by calling the client tool webhook
|
||||
logger.log(`Running client tool webhook for tool ${toolCall.function.name}`);
|
||||
result = await callClientToolWebhook(
|
||||
toolCall,
|
||||
currentMessages,
|
||||
projectId,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.log(`Error in tool call ${toolCall.function.name}: ${e}`);
|
||||
return Response.json({ error: `Error in tool call ${toolCall.function.name}` }, { status: 500 });
|
||||
}
|
||||
logger.log(`Tool call ${toolCall.function.name} completed`);
|
||||
}
|
||||
|
||||
toolCallResultMessages.push({
|
||||
role: "tool",
|
||||
tool_call_id: toolCall.id,
|
||||
content: JSON.stringify(result),
|
||||
tool_name: toolCall.function.name,
|
||||
});
|
||||
}
|
||||
|
||||
// Add new messages to the conversation
|
||||
currentMessages = [...currentMessages, ...newMessages, ...toolCallResultMessages];
|
||||
} else {
|
||||
// No tool calls, just add the new messages
|
||||
currentMessages = [...currentMessages, ...newMessages];
|
||||
}
|
||||
|
||||
turns++;
|
||||
if (turns >= MAX_TURNS && hasToolCalls) {
|
||||
logger.log(`Max turns (${MAX_TURNS}) reached for conversation`);
|
||||
return Response.json({ error: "Max turns reached" }, { status: 429 });
|
||||
}
|
||||
|
||||
} while (hasToolCalls);
|
||||
const { messages: agenticMessages, state } = await getAgenticApiResponse(request);
|
||||
const newMessages = convertFromAgenticApiToApiMessages(agenticMessages);
|
||||
const newState = state;
|
||||
|
||||
const responseBody: z.infer<typeof ApiResponse> = {
|
||||
messages: currentMessages.slice(reqMessages.length),
|
||||
state: currentState,
|
||||
messages: newMessages,
|
||||
state: newState,
|
||||
};
|
||||
return Response.json(responseBody);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,13 +8,10 @@ import { convertFromAgenticAPIChatMessages } from "../../../../../../lib/types/a
|
|||
import { convertToAgenticAPIChatMessages } from "../../../../../../lib/types/agents_api_types";
|
||||
import { convertWorkflowToAgenticAPI } from "../../../../../../lib/types/agents_api_types";
|
||||
import { AgenticAPIChatRequest } from "../../../../../../lib/types/agents_api_types";
|
||||
import { callClientToolWebhook, getAgenticApiResponse, runRAGToolCall, mockToolResponse, callMcpTool } from "../../../../../../lib/utils";
|
||||
import { getAgenticApiResponse } from "../../../../../../lib/utils";
|
||||
import { check_query_limit } from "../../../../../../lib/rate_limiting";
|
||||
import { PrefixLogger } from "../../../../../../lib/utils";
|
||||
|
||||
// Add max turns constant at the top with other constants
|
||||
const MAX_TURNS = 3;
|
||||
|
||||
// get next turn / agent response
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
|
|
@ -23,7 +20,7 @@ export async function POST(
|
|||
return await authCheck(req, async (session) => {
|
||||
const { chatId } = await params;
|
||||
const logger = new PrefixLogger(`widget-chat:${chatId}`);
|
||||
|
||||
|
||||
logger.log(`Processing turn request for chat ${chatId}`);
|
||||
|
||||
// check query limit
|
||||
|
|
@ -95,109 +92,32 @@ export async function POST(
|
|||
// get assistant response
|
||||
const { agents, tools, prompts, startAgent } = convertWorkflowToAgenticAPI(workflow);
|
||||
const unsavedMessages: z.infer<typeof apiV1.ChatMessage>[] = [userMessage];
|
||||
let resolvingToolCalls = true;
|
||||
let state: unknown = chat.agenticState ?? {last_agent_name: startAgent};
|
||||
let turns = 0; // Add turns counter
|
||||
let state: unknown = chat.agenticState ?? { last_agent_name: startAgent };
|
||||
|
||||
while (resolvingToolCalls) {
|
||||
if (turns >= MAX_TURNS) {
|
||||
logger.log(`Max turns (${MAX_TURNS}) reached for chat ${chatId}`);
|
||||
throw new Error("Max turns reached");
|
||||
}
|
||||
turns++;
|
||||
|
||||
const request: z.infer<typeof AgenticAPIChatRequest> = {
|
||||
messages: convertToAgenticAPIChatMessages([systemMessage, ...messages, ...unsavedMessages]),
|
||||
state,
|
||||
agents,
|
||||
tools,
|
||||
prompts,
|
||||
startAgent,
|
||||
};
|
||||
logger.log(`Turn ${turns}: sending agentic request`);
|
||||
const response = await getAgenticApiResponse(request);
|
||||
state = response.state;
|
||||
if (response.messages.length === 0) {
|
||||
throw new Error("No messages returned from assistant");
|
||||
}
|
||||
const convertedMessages = convertFromAgenticAPIChatMessages(response.messages);
|
||||
unsavedMessages.push(...convertedMessages.map(m => ({
|
||||
...m,
|
||||
version: 'v1' as const,
|
||||
chatId,
|
||||
createdAt: new Date().toISOString(),
|
||||
})));
|
||||
|
||||
// if the last messages is tool call, execute them
|
||||
const lastMessage = convertedMessages[convertedMessages.length - 1];
|
||||
if (lastMessage.role === 'assistant' && 'tool_calls' in lastMessage) {
|
||||
logger.log(`Processing ${lastMessage.tool_calls.length} tool calls`);
|
||||
const toolCallResults = await Promise.all(lastMessage.tool_calls.map(async toolCall => {
|
||||
logger.log(`Executing tool call: ${toolCall.function.name}`);
|
||||
try {
|
||||
if (toolCall.function.name === "getArticleInfo") {
|
||||
logger.log(`Processing RAG tool call for agent ${lastMessage.agenticSender}`);
|
||||
const agent = workflow.agents.find(a => a.name === lastMessage.agenticSender);
|
||||
if (!agent || !agent.ragDataSources) {
|
||||
throw new Error("Agent not found or has no data sources");
|
||||
}
|
||||
return await runRAGToolCall(
|
||||
session.projectId,
|
||||
toolCall.function.arguments,
|
||||
agent.ragDataSources,
|
||||
agent.ragReturnType,
|
||||
agent.ragK
|
||||
);
|
||||
}
|
||||
|
||||
const workflowTool = workflow.tools.find(t => t.name === toolCall.function.name);
|
||||
if (workflowTool?.mockTool) {
|
||||
logger.log(`Using mock response for tool: ${toolCall.function.name}`);
|
||||
return await mockToolResponse(
|
||||
toolCall.id,
|
||||
[...messages, ...unsavedMessages],
|
||||
workflowTool.mockInstructions || ''
|
||||
);
|
||||
} else if (workflowTool?.isMcp) {
|
||||
logger.log(`Calling MCP tool: ${toolCall.function.name}`);
|
||||
return await callMcpTool(
|
||||
session.projectId,
|
||||
workflowTool.mcpServerName ?? 'default',
|
||||
toolCall.function.name,
|
||||
JSON.parse(toolCall.function.arguments)
|
||||
);
|
||||
} else {
|
||||
logger.log(`Calling webhook for tool: ${toolCall.function.name}`);
|
||||
return await callClientToolWebhook(
|
||||
toolCall,
|
||||
[...messages, ...unsavedMessages],
|
||||
session.projectId,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log(`Error executing tool call ${toolCall.id}: ${error}`);
|
||||
return { error: "Tool execution failed" };
|
||||
}
|
||||
}));
|
||||
unsavedMessages.push(...toolCallResults.map((result, index) => ({
|
||||
version: 'v1' as const,
|
||||
chatId,
|
||||
createdAt: new Date().toISOString(),
|
||||
role: 'tool' as const,
|
||||
tool_call_id: lastMessage.tool_calls[index].id,
|
||||
tool_name: lastMessage.tool_calls[index].function.name,
|
||||
content: JSON.stringify(result),
|
||||
})));
|
||||
} else {
|
||||
// ensure that the last message is from an assistant
|
||||
// and is of an external type
|
||||
if (lastMessage.role !== 'assistant' || lastMessage.agenticResponseType !== 'external') {
|
||||
throw new Error("Last message is not from an assistant and is not of an external type");
|
||||
}
|
||||
resolvingToolCalls = false;
|
||||
break;
|
||||
}
|
||||
const request: z.infer<typeof AgenticAPIChatRequest> = {
|
||||
messages: convertToAgenticAPIChatMessages([systemMessage, ...messages, ...unsavedMessages]),
|
||||
state,
|
||||
agents,
|
||||
tools,
|
||||
prompts,
|
||||
startAgent,
|
||||
mcpServers: projectSettings.mcpServers ?? undefined,
|
||||
toolWebhookUrl: projectSettings.webhookUrl ?? undefined,
|
||||
testProfile: undefined,
|
||||
};
|
||||
logger.log(`Sending agentic request`);
|
||||
const response = await getAgenticApiResponse(request);
|
||||
state = response.state;
|
||||
if (response.messages.length === 0) {
|
||||
throw new Error("No messages returned from assistant");
|
||||
}
|
||||
const convertedMessages = convertFromAgenticAPIChatMessages(response.messages);
|
||||
unsavedMessages.push(...convertedMessages.map(m => ({
|
||||
...m,
|
||||
version: 'v1' as const,
|
||||
chatId,
|
||||
createdAt: new Date().toISOString(),
|
||||
})));
|
||||
|
||||
logger.log(`Saving ${unsavedMessages.length} new messages and updating chat state`);
|
||||
await chatMessagesCollection.insertMany(unsavedMessages);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { z } from "zod";
|
||||
import { ConnectedEntity, sanitizeTextWithMentions, Workflow, WorkflowAgent, WorkflowPrompt, WorkflowTool } from "./workflow_types";
|
||||
import { sanitizeTextWithMentions, Workflow, WorkflowAgent, WorkflowPrompt, WorkflowTool } from "./workflow_types";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { ApiMessage } from "./types";
|
||||
import { TestProfile } from "./testing_types";
|
||||
import { MCPServer } from "./types";
|
||||
|
||||
export const AgenticAPIChatMessage = z.object({
|
||||
role: z.union([z.literal('user'), z.literal('assistant'), z.literal('tool'), z.literal('system')]),
|
||||
|
|
@ -30,12 +32,8 @@ export const AgenticAPIAgent = WorkflowAgent
|
|||
locked: true,
|
||||
toggleAble: true,
|
||||
global: true,
|
||||
ragDataSources: true,
|
||||
ragReturnType: true,
|
||||
ragK: true,
|
||||
})
|
||||
.extend({
|
||||
hasRagSources: z.boolean().default(false).optional(),
|
||||
tools: z.array(z.string()),
|
||||
prompts: z.array(z.string()),
|
||||
connectedAgents: z.array(z.string()),
|
||||
|
|
@ -43,10 +41,10 @@ export const AgenticAPIAgent = WorkflowAgent
|
|||
|
||||
export const AgenticAPIPrompt = WorkflowPrompt;
|
||||
|
||||
export const AgenticAPITool = WorkflowTool.omit({
|
||||
mockTool: true,
|
||||
autoSubmitMockedResponse: true,
|
||||
});
|
||||
export const AgenticAPITool = WorkflowTool
|
||||
.omit({
|
||||
autoSubmitMockedResponse: true,
|
||||
})
|
||||
|
||||
export const AgenticAPIChatRequest = z.object({
|
||||
messages: z.array(AgenticAPIChatMessage),
|
||||
|
|
@ -55,6 +53,9 @@ export const AgenticAPIChatRequest = z.object({
|
|||
tools: z.array(AgenticAPITool),
|
||||
prompts: z.array(WorkflowPrompt),
|
||||
startAgent: z.string(),
|
||||
testProfile: TestProfile.optional(),
|
||||
mcpServers: z.array(MCPServer).optional(),
|
||||
toolWebhookUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
export const AgenticAPIChatResponse = z.object({
|
||||
|
|
@ -82,8 +83,10 @@ export function convertWorkflowToAgenticAPI(workflow: z.infer<typeof Workflow>):
|
|||
description: agent.description,
|
||||
instructions: sanitized,
|
||||
model: agent.model,
|
||||
hasRagSources: agent.ragDataSources ? agent.ragDataSources.length > 0 : false,
|
||||
controlType: agent.controlType,
|
||||
ragDataSources: agent.ragDataSources,
|
||||
ragK: agent.ragK,
|
||||
ragReturnType: agent.ragReturnType,
|
||||
tools: entities.filter(e => e.type == 'tool').map(e => e.name),
|
||||
prompts: entities.filter(e => e.type == 'prompt').map(e => e.name),
|
||||
connectedAgents: entities.filter(e => e.type === 'agent').map(e => e.name),
|
||||
|
|
@ -91,10 +94,8 @@ export function convertWorkflowToAgenticAPI(workflow: z.infer<typeof Workflow>):
|
|||
return agenticAgent;
|
||||
}),
|
||||
tools: workflow.tools.map(tool => {
|
||||
const { mockTool, autoSubmitMockedResponse, ...rest } = tool;
|
||||
return {
|
||||
...rest,
|
||||
};
|
||||
const { autoSubmitMockedResponse, ...rest } = tool;
|
||||
return rest;
|
||||
}),
|
||||
prompts: workflow.prompts
|
||||
.map(p => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { z } from "zod";
|
||||
import { MCPServer } from "./types";
|
||||
|
||||
export const Project = z.object({
|
||||
_id: z.string().uuid(),
|
||||
|
|
@ -12,10 +13,7 @@ export const Project = z.object({
|
|||
publishedWorkflowId: z.string().optional(),
|
||||
nextWorkflowNumber: z.number().optional(),
|
||||
testRunCounter: z.number().default(0),
|
||||
mcpServers: z.array(z.object({
|
||||
name: z.string(),
|
||||
url: z.string(),
|
||||
})).optional(),
|
||||
mcpServers: z.array(MCPServer).optional(),
|
||||
});
|
||||
|
||||
export const ProjectMember = z.object({
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ import { CoreMessage, ToolCallPart } from "ai";
|
|||
import { z } from "zod";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
|
||||
export const MCPServer = z.object({
|
||||
name: z.string(),
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export const PlaygroundChat = z.object({
|
||||
createdAt: z.string().datetime(),
|
||||
projectId: z.string(),
|
||||
|
|
|
|||
|
|
@ -1,136 +1,8 @@
|
|||
import { convertFromAgenticAPIChatMessages } from "./types/agents_api_types";
|
||||
import { ClientToolCallRequest } from "./types/tool_types";
|
||||
import { ClientToolCallJwt, GetInformationToolResult } from "./types/tool_types";
|
||||
import { ClientToolCallRequestBody } from "./types/tool_types";
|
||||
import { AgenticAPIChatResponse } from "./types/agents_api_types";
|
||||
import { AgenticAPIChatRequest } from "./types/agents_api_types";
|
||||
import { Workflow, WorkflowAgent } from "./types/workflow_types";
|
||||
import { AgenticAPIChatMessage } from "./types/agents_api_types";
|
||||
import { AgenticAPIChatResponse, AgenticAPIChatRequest, AgenticAPIChatMessage } from "./types/agents_api_types";
|
||||
import { z } from "zod";
|
||||
import { dataSourceDocsCollection, dataSourcesCollection, projectsCollection } from "./mongodb";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { SignJWT } from "jose";
|
||||
import crypto from "crypto";
|
||||
import { ObjectId } from "mongodb";
|
||||
import { embeddingModel } from "./embedding";
|
||||
import { embed, generateObject } from "ai";
|
||||
import { qdrantClient } from "./qdrant";
|
||||
import { EmbeddingRecord } from "./types/datasource_types";
|
||||
import { generateObject } from "ai";
|
||||
import { ApiMessage } from "./types/types";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
|
||||
export async function callMcpTool(
|
||||
projectId: string,
|
||||
mcpServerName: string,
|
||||
toolName: string,
|
||||
parameters: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
const project = await projectsCollection.findOne({
|
||||
"_id": projectId,
|
||||
});
|
||||
if (!project) {
|
||||
throw new Error('Project not found');
|
||||
}
|
||||
|
||||
const mcpServer = project.mcpServers?.find(s => s.name === mcpServerName);
|
||||
if (!mcpServer) {
|
||||
throw new Error('MCP server not found');
|
||||
}
|
||||
|
||||
const transport = new SSEClientTransport(new URL(mcpServer.url));
|
||||
|
||||
const client = new Client(
|
||||
{
|
||||
name: "rowboat-client",
|
||||
version: "1.0.0"
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
prompts: {},
|
||||
resources: {},
|
||||
tools: {}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
await client.connect(transport);
|
||||
|
||||
const result = await client.callTool({
|
||||
name: toolName,
|
||||
arguments: parameters,
|
||||
});
|
||||
|
||||
await client.close();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function callClientToolWebhook(
|
||||
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number],
|
||||
messages: z.infer<typeof ApiMessage>[],
|
||||
projectId: string,
|
||||
): Promise<unknown> {
|
||||
const project = await projectsCollection.findOne({
|
||||
"_id": projectId,
|
||||
});
|
||||
if (!project) {
|
||||
throw new Error('Project not found');
|
||||
}
|
||||
|
||||
if (!project.webhookUrl) {
|
||||
throw new Error('Webhook URL not found');
|
||||
}
|
||||
|
||||
// prepare request body
|
||||
const content = JSON.stringify({
|
||||
toolCall,
|
||||
messages,
|
||||
} as z.infer<typeof ClientToolCallRequestBody>);
|
||||
const requestId = crypto.randomUUID();
|
||||
const bodyHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(content, 'utf8')
|
||||
.digest('hex');
|
||||
|
||||
// sign request
|
||||
const jwt = await new SignJWT({
|
||||
requestId,
|
||||
projectId,
|
||||
bodyHash,
|
||||
} as z.infer<typeof ClientToolCallJwt>)
|
||||
.setProtectedHeader({
|
||||
alg: 'HS256',
|
||||
typ: 'JWT',
|
||||
})
|
||||
.setIssuer('rowboat')
|
||||
.setAudience(project.webhookUrl)
|
||||
.setSubject(`tool-call-${toolCall.id}`)
|
||||
.setJti(requestId)
|
||||
.setIssuedAt()
|
||||
.setExpirationTime("5 minutes")
|
||||
.sign(new TextEncoder().encode(project.secret));
|
||||
|
||||
// make request
|
||||
const request: z.infer<typeof ClientToolCallRequest> = {
|
||||
requestId,
|
||||
content,
|
||||
};
|
||||
const response = await fetch(project.webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-signature-jwt': jwt,
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to call webhook: ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
const responseBody = await response.json();
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
export async function getAgenticApiResponse(
|
||||
request: z.infer<typeof AgenticAPIChatRequest>,
|
||||
|
|
@ -163,85 +35,6 @@ export async function getAgenticApiResponse(
|
|||
};
|
||||
}
|
||||
|
||||
export async function runRAGToolCall(
|
||||
projectId: string,
|
||||
query: string,
|
||||
sourceIds: string[],
|
||||
returnType: z.infer<typeof WorkflowAgent>['ragReturnType'],
|
||||
k: number,
|
||||
): Promise<z.infer<typeof GetInformationToolResult>> {
|
||||
// create embedding for question
|
||||
const embedResult = await embed({
|
||||
model: embeddingModel,
|
||||
value: query,
|
||||
});
|
||||
|
||||
// fetch all data sources for this project
|
||||
const sources = await dataSourcesCollection.find({
|
||||
projectId: projectId,
|
||||
active: true,
|
||||
}).toArray();
|
||||
const validSourceIds = sources
|
||||
.filter(s => sourceIds.includes(s._id.toString())) // id should be in sourceIds
|
||||
.filter(s => s.active) // should be active
|
||||
.map(s => s._id.toString());
|
||||
|
||||
// if no sources found, return empty response
|
||||
if (validSourceIds.length === 0) {
|
||||
return {
|
||||
results: [],
|
||||
};
|
||||
}
|
||||
|
||||
// perform qdrant vector search
|
||||
const qdrantResults = await qdrantClient.query("embeddings", {
|
||||
query: embedResult.embedding,
|
||||
filter: {
|
||||
must: [
|
||||
{ key: "projectId", match: { value: projectId } },
|
||||
{ key: "sourceId", match: { any: validSourceIds } },
|
||||
],
|
||||
},
|
||||
limit: k,
|
||||
with_payload: true,
|
||||
});
|
||||
|
||||
// if return type is chunks, return the chunks
|
||||
let results = qdrantResults.points.map((point) => {
|
||||
const { title, name, content, docId, sourceId } = point.payload as z.infer<typeof EmbeddingRecord>['payload'];
|
||||
return {
|
||||
title,
|
||||
name,
|
||||
content,
|
||||
docId,
|
||||
sourceId,
|
||||
};
|
||||
});
|
||||
|
||||
if (returnType === 'chunks') {
|
||||
return {
|
||||
results,
|
||||
};
|
||||
}
|
||||
|
||||
// otherwise, fetch the doc contents from mongodb
|
||||
const docs = await dataSourceDocsCollection.find({
|
||||
_id: { $in: results.map(r => new ObjectId(r.docId)) },
|
||||
}).toArray();
|
||||
|
||||
// map the results to the docs
|
||||
results = results.map(r => {
|
||||
const doc = docs.find(d => d._id.toString() === r.docId);
|
||||
return {
|
||||
...r,
|
||||
content: doc?.content || '',
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
results,
|
||||
};
|
||||
}
|
||||
// create a PrefixLogger class that wraps console.log with a prefix
|
||||
// and allows chaining with a parent logger
|
||||
export class PrefixLogger {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
import { useState } from "react";
|
||||
import { z } from "zod";
|
||||
import { PlaygroundChat } from "../../../lib/types/types";
|
||||
import { MCPServer, PlaygroundChat } from "../../../lib/types/types";
|
||||
import { Workflow } from "../../../lib/types/workflow_types";
|
||||
import { Chat } from "./chat";
|
||||
import { ActionButton, Pane } from "../workflow/pane";
|
||||
|
|
@ -17,11 +17,15 @@ export function App({
|
|||
projectId,
|
||||
workflow,
|
||||
messageSubscriber,
|
||||
mcpServerUrls,
|
||||
toolWebhookUrl,
|
||||
}: {
|
||||
hidden?: boolean;
|
||||
projectId: string;
|
||||
workflow: z.infer<typeof Workflow>;
|
||||
messageSubscriber?: (messages: z.infer<typeof apiV1.ChatMessage>[]) => void;
|
||||
mcpServerUrls: Array<z.infer<typeof MCPServer>>;
|
||||
toolWebhookUrl: string;
|
||||
}) {
|
||||
const [counter, setCounter] = useState<number>(0);
|
||||
const [testProfile, setTestProfile] = useState<z.infer<typeof TestProfile> | null>(null);
|
||||
|
|
@ -84,6 +88,8 @@ export function App({
|
|||
onTestProfileChange={handleTestProfileChange}
|
||||
systemMessage={systemMessage}
|
||||
onSystemMessageChange={handleSystemMessageChange}
|
||||
mcpServerUrls={mcpServerUrls}
|
||||
toolWebhookUrl={toolWebhookUrl}
|
||||
/>
|
||||
</div>
|
||||
</Pane>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
'use client';
|
||||
import { getAssistantResponse, simulateUserResponse } from "../../../actions/actions";
|
||||
import { getAssistantResponse } from "../../../actions/actions";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Messages } from "./messages";
|
||||
import z from "zod";
|
||||
import { PlaygroundChat } from "../../../lib/types/types";
|
||||
import { MCPServer, PlaygroundChat } from "../../../lib/types/types";
|
||||
import { convertToAgenticAPIChatMessages } from "../../../lib/types/agents_api_types";
|
||||
import { convertWorkflowToAgenticAPI } from "../../../lib/types/agents_api_types";
|
||||
import { AgenticAPIChatRequest } from "../../../lib/types/agents_api_types";
|
||||
|
|
@ -15,7 +15,7 @@ import { CopyAsJsonButton } from "./copy-as-json-button";
|
|||
import { TestProfile } from "@/app/lib/types/testing_types";
|
||||
import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector";
|
||||
import { WithStringId } from "@/app/lib/types/types";
|
||||
import { XCircleIcon, XIcon } from "lucide-react";
|
||||
import { XIcon } from "lucide-react";
|
||||
|
||||
export function Chat({
|
||||
chat,
|
||||
|
|
@ -26,6 +26,8 @@ export function Chat({
|
|||
onTestProfileChange,
|
||||
systemMessage,
|
||||
onSystemMessageChange,
|
||||
mcpServerUrls,
|
||||
toolWebhookUrl,
|
||||
}: {
|
||||
chat: z.infer<typeof PlaygroundChat>;
|
||||
projectId: string;
|
||||
|
|
@ -35,6 +37,8 @@ export function Chat({
|
|||
onTestProfileChange: (profile: WithStringId<z.infer<typeof TestProfile>> | null) => void;
|
||||
systemMessage: string;
|
||||
onSystemMessageChange: (message: string) => void;
|
||||
mcpServerUrls: Array<z.infer<typeof MCPServer>>;
|
||||
toolWebhookUrl: string;
|
||||
}) {
|
||||
const [messages, setMessages] = useState<z.infer<typeof apiV1.ChatMessage>[]>(chat.messages);
|
||||
const [loadingAssistantResponse, setLoadingAssistantResponse] = useState<boolean>(false);
|
||||
|
|
@ -68,15 +72,6 @@ export function Chat({
|
|||
setFetchResponseError(null);
|
||||
}
|
||||
|
||||
function handleToolCallResults(results: z.infer<typeof apiV1.ToolMessage>[]) {
|
||||
setMessages([...messages, ...results.map((result) => ({
|
||||
...result,
|
||||
version: 'v1' as const,
|
||||
chatId: '',
|
||||
createdAt: new Date().toISOString(),
|
||||
}))]);
|
||||
}
|
||||
|
||||
// reset state when workflow changes
|
||||
useEffect(() => {
|
||||
setMessages([]);
|
||||
|
|
@ -113,6 +108,9 @@ export function Chat({
|
|||
tools,
|
||||
prompts,
|
||||
startAgent,
|
||||
mcpServers: mcpServerUrls,
|
||||
toolWebhookUrl: toolWebhookUrl,
|
||||
testProfile: testProfile ?? undefined,
|
||||
};
|
||||
setLastAgenticRequest(null);
|
||||
setLastAgenticResponse(null);
|
||||
|
|
@ -164,105 +162,7 @@ export function Chat({
|
|||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [chat.simulated, messages, projectId, agenticState, workflow, fetchResponseError, systemMessage, simulationComplete]);
|
||||
|
||||
// simulate user turn
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
|
||||
async function process() {
|
||||
if (chat.simulationScenario === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch next user prompt
|
||||
setLoadingUserResponse(true);
|
||||
try {
|
||||
|
||||
const response = await simulateUserResponse(projectId, messages, chat.simulationScenario)
|
||||
if (ignore) {
|
||||
return;
|
||||
}
|
||||
if (simulationComplete) {
|
||||
return;
|
||||
}
|
||||
if (response.trim() === 'EXIT') {
|
||||
setSimulationComplete(true);
|
||||
return;
|
||||
}
|
||||
setMessages([...messages, {
|
||||
role: 'user',
|
||||
content: response,
|
||||
version: 'v1' as const,
|
||||
chatId: '',
|
||||
createdAt: new Date().toISOString(),
|
||||
}]);
|
||||
setFetchResponseError(null);
|
||||
} catch (err) {
|
||||
setFetchResponseError(`Failed to simulate user response: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
||||
} finally {
|
||||
setLoadingUserResponse(false);
|
||||
}
|
||||
}
|
||||
|
||||
// proceed only if chat is simulated
|
||||
if (!chat.simulated) {
|
||||
return;
|
||||
}
|
||||
|
||||
// dont proceed if simulation is complete
|
||||
if (chat.simulated && simulationComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if there are no messages yet OR
|
||||
// check if the last message is an assistant
|
||||
// message containing a text response. If so,
|
||||
// call the simulate user turn api to fetch
|
||||
// user response
|
||||
let last = messages[messages.length - 1];
|
||||
if (last && last.role !== 'assistant') {
|
||||
return;
|
||||
}
|
||||
if (last && 'tool_calls' in last) {
|
||||
return;
|
||||
}
|
||||
|
||||
process();
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [chat.simulated, messages, projectId, simulationComplete, chat.simulationScenario]);
|
||||
|
||||
// save chat on every assistant message
|
||||
// useEffect(() => {
|
||||
// let ignore = false;
|
||||
|
||||
// function process() {
|
||||
// savePlaygroundChat(projectId, {
|
||||
// ...chat,
|
||||
// messages,
|
||||
// simulationComplete,
|
||||
// agenticState,
|
||||
// }, chatId)
|
||||
// .then((insertedChatId) => {
|
||||
// if (!chatId) {
|
||||
// setChatId(insertedChatId);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (messages.length === 0) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const lastMessage = messages[messages.length - 1];
|
||||
// if (lastMessage && lastMessage.role !== 'assistant') {
|
||||
// return;
|
||||
// }
|
||||
// process();
|
||||
// }, [chatId, chat, messages, projectId, simulationComplete, agenticState]);
|
||||
}, [chat.simulated, messages, projectId, agenticState, workflow, fetchResponseError, systemMessage, simulationComplete, mcpServerUrls, toolWebhookUrl, testProfile]);
|
||||
|
||||
const handleCopyChat = () => {
|
||||
const jsonString = JSON.stringify({
|
||||
|
|
@ -303,7 +203,6 @@ export function Chat({
|
|||
projectId={projectId}
|
||||
messages={messages}
|
||||
toolCallResults={toolCallResults}
|
||||
handleToolCallResults={handleToolCallResults}
|
||||
loadingAssistantResponse={loadingAssistantResponse}
|
||||
loadingUserResponse={loadingUserResponse}
|
||||
workflow={workflow}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
'use client';
|
||||
import { Button, Spinner, Textarea } from "@heroui/react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Spinner } from "@heroui/react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import z from "zod";
|
||||
import { Workflow } from "../../../lib/types/workflow_types";
|
||||
import { WorkflowTool } from "../../../lib/types/workflow_types";
|
||||
import { GetInformationToolResult } from "../../../lib/types/tool_types";
|
||||
import { executeClientTool, getInformationTool, suggestToolResponse } from "../../../actions/actions";
|
||||
import MarkdownContent from "../../../lib/components/markdown-content";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { EditableField } from "../../../lib/components/editable-field";
|
||||
import { MessageSquareIcon, EllipsisIcon, CircleCheckIcon, ChevronRightIcon, ChevronDownIcon, XIcon } from "lucide-react";
|
||||
import { TestProfile } from "@/app/lib/types/testing_types";
|
||||
import { executeMcpTool } from "@/app/actions/mcp_actions";
|
||||
|
||||
function UserMessage({ content }: { content: string }) {
|
||||
return <div className="self-end ml-[30%] flex flex-col">
|
||||
|
|
@ -88,7 +85,6 @@ function UserMessageLoading() {
|
|||
function ToolCalls({
|
||||
toolCalls,
|
||||
results,
|
||||
handleResults,
|
||||
projectId,
|
||||
messages,
|
||||
sender,
|
||||
|
|
@ -98,7 +94,6 @@ function ToolCalls({
|
|||
}: {
|
||||
toolCalls: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'];
|
||||
results: Record<string, z.infer<typeof apiV1.ToolMessage>>;
|
||||
handleResults: (results: z.infer<typeof apiV1.ToolMessage>[]) => void;
|
||||
projectId: string;
|
||||
messages: z.infer<typeof apiV1.ChatMessage>[];
|
||||
sender: string | null | undefined;
|
||||
|
|
@ -108,21 +103,12 @@ function ToolCalls({
|
|||
}) {
|
||||
const resultsMap: Record<string, z.infer<typeof apiV1.ToolMessage>> = {};
|
||||
|
||||
function handleToolCallResult(result: z.infer<typeof apiV1.ToolMessage>) {
|
||||
resultsMap[result.tool_call_id] = result;
|
||||
if (Object.keys(resultsMap).length === toolCalls.length) {
|
||||
const results = Object.values(resultsMap);
|
||||
handleResults(results);
|
||||
}
|
||||
}
|
||||
|
||||
return <div className="flex flex-col gap-4">
|
||||
{toolCalls.map(toolCall => {
|
||||
return <ToolCall
|
||||
key={toolCall.id}
|
||||
toolCall={toolCall}
|
||||
result={results[toolCall.id]}
|
||||
handleResult={handleToolCallResult}
|
||||
projectId={projectId}
|
||||
messages={messages}
|
||||
sender={sender}
|
||||
|
|
@ -137,7 +123,6 @@ function ToolCalls({
|
|||
function ToolCall({
|
||||
toolCall,
|
||||
result,
|
||||
handleResult,
|
||||
projectId,
|
||||
messages,
|
||||
sender,
|
||||
|
|
@ -147,7 +132,6 @@ function ToolCall({
|
|||
}: {
|
||||
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number];
|
||||
result: z.infer<typeof apiV1.ToolMessage> | undefined;
|
||||
handleResult: (result: z.infer<typeof apiV1.ToolMessage>) => void;
|
||||
projectId: string;
|
||||
messages: z.infer<typeof apiV1.ChatMessage>[];
|
||||
sender: string | null | undefined;
|
||||
|
|
@ -163,63 +147,17 @@ function ToolCall({
|
|||
}
|
||||
}
|
||||
|
||||
switch (toolCall.function.name) {
|
||||
case 'getArticleInfo':
|
||||
return <GetInformationToolCall
|
||||
toolCall={toolCall}
|
||||
result={result}
|
||||
handleResult={handleResult}
|
||||
projectId={projectId}
|
||||
messages={messages}
|
||||
sender={sender}
|
||||
workflow={workflow}
|
||||
/>;
|
||||
default:
|
||||
if (toolCall.function.name.startsWith('transfer_to_')) {
|
||||
return <TransferToAgentToolCall
|
||||
toolCall={toolCall}
|
||||
result={result}
|
||||
handleResult={handleResult}
|
||||
projectId={projectId}
|
||||
messages={messages}
|
||||
sender={sender}
|
||||
/>;
|
||||
}
|
||||
if (!matchingWorkflowTool ||
|
||||
matchingWorkflowTool.mockTool ||
|
||||
(testProfile && testProfile.mockTools)) {
|
||||
return <MockToolCall
|
||||
toolCall={toolCall}
|
||||
result={result}
|
||||
handleResult={handleResult}
|
||||
projectId={projectId}
|
||||
messages={messages}
|
||||
sender={sender}
|
||||
testProfile={testProfile}
|
||||
workflowTool={matchingWorkflowTool}
|
||||
systemMessage={systemMessage}
|
||||
/>;
|
||||
}
|
||||
if (matchingWorkflowTool?.isMcp) {
|
||||
return <McpToolCall
|
||||
toolCall={toolCall}
|
||||
workflowTool={matchingWorkflowTool}
|
||||
result={result}
|
||||
handleResult={handleResult}
|
||||
projectId={projectId}
|
||||
messages={messages}
|
||||
sender={sender}
|
||||
/>;
|
||||
}
|
||||
return <ClientToolCall
|
||||
toolCall={toolCall}
|
||||
result={result}
|
||||
handleResult={handleResult}
|
||||
projectId={projectId}
|
||||
messages={messages}
|
||||
sender={sender}
|
||||
/>;
|
||||
if (toolCall.function.name.startsWith('transfer_to_')) {
|
||||
return <TransferToAgentToolCall
|
||||
result={result}
|
||||
sender={sender}
|
||||
/>;
|
||||
}
|
||||
return <ClientToolCall
|
||||
toolCall={toolCall}
|
||||
result={result}
|
||||
sender={sender}
|
||||
/>;
|
||||
}
|
||||
|
||||
function ToolCallHeader({
|
||||
|
|
@ -240,105 +178,11 @@ function ToolCallHeader({
|
|||
</div>;
|
||||
}
|
||||
|
||||
function GetInformationToolCall({
|
||||
toolCall,
|
||||
result: availableResult,
|
||||
handleResult,
|
||||
projectId,
|
||||
messages,
|
||||
sender,
|
||||
workflow,
|
||||
}: {
|
||||
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number];
|
||||
result: z.infer<typeof apiV1.ToolMessage> | undefined;
|
||||
handleResult: (result: z.infer<typeof apiV1.ToolMessage>) => void;
|
||||
projectId: string;
|
||||
messages: z.infer<typeof apiV1.ChatMessage>[];
|
||||
sender: string | null | undefined;
|
||||
workflow: z.infer<typeof Workflow>;
|
||||
}) {
|
||||
const [result, setResult] = useState<z.infer<typeof apiV1.ToolMessage> | undefined>(availableResult);
|
||||
const args = JSON.parse(toolCall.function.arguments) as { question: string };
|
||||
let typedResult: z.infer<typeof GetInformationToolResult> | undefined;
|
||||
if (result) {
|
||||
typedResult = JSON.parse(result.content) as z.infer<typeof GetInformationToolResult>;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
let ignore = false;
|
||||
|
||||
async function process() {
|
||||
const result: z.infer<typeof apiV1.ToolMessage> = {
|
||||
role: 'tool',
|
||||
tool_call_id: toolCall.id,
|
||||
tool_name: toolCall.function.name,
|
||||
content: '',
|
||||
};
|
||||
// find target agent
|
||||
const agent = workflow.agents.find(agent => agent.name == sender);
|
||||
if (!agent || !agent.ragDataSources) {
|
||||
result.content = JSON.stringify({
|
||||
results: [],
|
||||
});
|
||||
} else {
|
||||
const matches = await getInformationTool(projectId, args.question, agent.ragDataSources, agent.ragReturnType, agent.ragK);
|
||||
if (ignore) {
|
||||
return;
|
||||
}
|
||||
result.content = JSON.stringify(matches);
|
||||
}
|
||||
setResult(result);
|
||||
handleResult(result);
|
||||
}
|
||||
process();
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [result, toolCall.id, toolCall.function.name, projectId, args.question, workflow.agents, sender, handleResult]);
|
||||
|
||||
return <div className="flex flex-col gap-1">
|
||||
{sender && <div className='text-gray-500 text-sm ml-3'>{sender}</div>}
|
||||
<div className='border border-gray-300 p-2 rounded-lg rounded-bl-none flex flex-col gap-2 mr-[30%]'>
|
||||
<ToolCallHeader toolCall={toolCall} result={result} />
|
||||
|
||||
<div className='mt-1'>
|
||||
{result ? 'Fetched' : 'Fetch'} information for question: <span className='font-mono font-semibold'>{args['question']}</span>
|
||||
{result && <div className='flex flex-col gap-2 mt-2 pt-2 border-t border-t-gray-200'>
|
||||
{typedResult && typedResult.results.length === 0 && <div>No matches found.</div>}
|
||||
{typedResult && typedResult.results.length > 0 && <ul className="list-disc ml-6">
|
||||
{typedResult.results.map((result, index) => {
|
||||
return <li key={'' + index} className="mb-2">
|
||||
<ExpandableContent
|
||||
label={result.title || result.name}
|
||||
content={result.content}
|
||||
expanded={false}
|
||||
/>
|
||||
</li>
|
||||
})}
|
||||
</ul>}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function TransferToAgentToolCall({
|
||||
toolCall,
|
||||
result: availableResult,
|
||||
handleResult,
|
||||
projectId,
|
||||
messages,
|
||||
sender,
|
||||
}: {
|
||||
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number];
|
||||
result: z.infer<typeof apiV1.ToolMessage> | undefined;
|
||||
handleResult: (result: z.infer<typeof apiV1.ToolMessage>) => void;
|
||||
projectId: string;
|
||||
messages: z.infer<typeof apiV1.ChatMessage>[];
|
||||
sender: string | null | undefined;
|
||||
}) {
|
||||
const typedResult = availableResult ? JSON.parse(availableResult.content) as { assistant: string } : undefined;
|
||||
|
|
@ -355,282 +199,28 @@ function TransferToAgentToolCall({
|
|||
</div>;
|
||||
}
|
||||
|
||||
function McpToolCall({
|
||||
toolCall,
|
||||
result: availableResult,
|
||||
handleResult,
|
||||
projectId,
|
||||
messages,
|
||||
sender,
|
||||
workflowTool,
|
||||
}: {
|
||||
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number];
|
||||
result: z.infer<typeof apiV1.ToolMessage> | undefined;
|
||||
handleResult: (result: z.infer<typeof apiV1.ToolMessage>) => void;
|
||||
projectId: string;
|
||||
messages: z.infer<typeof apiV1.ChatMessage>[];
|
||||
sender: string | null | undefined;
|
||||
workflowTool: z.infer<typeof WorkflowTool>;
|
||||
}) {
|
||||
const [result, setResult] = useState<z.infer<typeof apiV1.ToolMessage> | undefined>(availableResult);
|
||||
|
||||
useEffect(() => {
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
let ignore = false;
|
||||
|
||||
async function process() {
|
||||
let response;
|
||||
try {
|
||||
response = await executeMcpTool(
|
||||
projectId,
|
||||
workflowTool.mcpServerName || '',
|
||||
workflowTool.name,
|
||||
JSON.parse(toolCall.function.arguments),
|
||||
);
|
||||
} catch (e) {
|
||||
response = {
|
||||
error: (e as Error).message,
|
||||
};
|
||||
}
|
||||
if (ignore) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result: z.infer<typeof apiV1.ToolMessage> = {
|
||||
role: 'tool',
|
||||
tool_call_id: toolCall.id,
|
||||
tool_name: toolCall.function.name,
|
||||
content: JSON.stringify(response),
|
||||
};
|
||||
setResult(result);
|
||||
handleResult(result);
|
||||
}
|
||||
process();
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [result, toolCall, projectId, messages, handleResult, workflowTool.mcpServerName, workflowTool.name]);
|
||||
|
||||
return <div className="flex flex-col gap-1">
|
||||
{sender && <div className='text-gray-500 text-sm ml-3'>{sender}</div>}
|
||||
<div className='border border-gray-300 p-2 pt-2 rounded-lg rounded-bl-none flex flex-col gap-2 mr-[30%]'>
|
||||
<ToolCallHeader toolCall={toolCall} result={result} />
|
||||
|
||||
<div className='flex flex-col gap-2'>
|
||||
<ExpandableContent label='Params' content={toolCall.function.arguments} expanded={false} />
|
||||
{result && <ExpandableContent label='Result' content={result.content} expanded={false} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function ClientToolCall({
|
||||
toolCall,
|
||||
result: availableResult,
|
||||
handleResult,
|
||||
projectId,
|
||||
messages,
|
||||
sender,
|
||||
}: {
|
||||
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number];
|
||||
result: z.infer<typeof apiV1.ToolMessage> | undefined;
|
||||
handleResult: (result: z.infer<typeof apiV1.ToolMessage>) => void;
|
||||
projectId: string;
|
||||
messages: z.infer<typeof apiV1.ChatMessage>[];
|
||||
sender: string | null | undefined;
|
||||
}) {
|
||||
const [result, setResult] = useState<z.infer<typeof apiV1.ToolMessage> | undefined>(availableResult);
|
||||
|
||||
useEffect(() => {
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
let ignore = false;
|
||||
|
||||
async function process() {
|
||||
let response;
|
||||
try {
|
||||
response = await executeClientTool(
|
||||
toolCall,
|
||||
messages,
|
||||
projectId,
|
||||
);
|
||||
} catch (e) {
|
||||
response = {
|
||||
error: (e as Error).message,
|
||||
};
|
||||
}
|
||||
if (ignore) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result: z.infer<typeof apiV1.ToolMessage> = {
|
||||
role: 'tool',
|
||||
tool_call_id: toolCall.id,
|
||||
tool_name: toolCall.function.name,
|
||||
content: JSON.stringify(response),
|
||||
};
|
||||
setResult(result);
|
||||
handleResult(result);
|
||||
}
|
||||
process();
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [result, toolCall, projectId, messages, handleResult]);
|
||||
|
||||
return <div className="flex flex-col gap-1">
|
||||
{sender && <div className='text-gray-500 text-sm ml-3'>{sender}</div>}
|
||||
<div className='border border-gray-300 p-2 pt-2 rounded-lg rounded-bl-none flex flex-col gap-2 mr-[30%]'>
|
||||
<ToolCallHeader toolCall={toolCall} result={result} />
|
||||
<ToolCallHeader toolCall={toolCall} result={availableResult} />
|
||||
|
||||
<div className='flex flex-col gap-2'>
|
||||
<ExpandableContent label='Params' content={toolCall.function.arguments} expanded={false} />
|
||||
{result && <ExpandableContent label='Result' content={result.content} expanded={false} />}
|
||||
{availableResult && <ExpandableContent label='Result' content={availableResult.content} expanded={false} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function MockToolCall({
|
||||
toolCall,
|
||||
result: availableResult,
|
||||
handleResult,
|
||||
projectId,
|
||||
messages,
|
||||
sender,
|
||||
testProfile = null,
|
||||
workflowTool,
|
||||
systemMessage,
|
||||
}: {
|
||||
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number];
|
||||
result: z.infer<typeof apiV1.ToolMessage> | undefined;
|
||||
handleResult: (result: z.infer<typeof apiV1.ToolMessage>) => void;
|
||||
projectId: string;
|
||||
messages: z.infer<typeof apiV1.ChatMessage>[];
|
||||
sender: string | null | undefined;
|
||||
testProfile: z.infer<typeof TestProfile> | null;
|
||||
workflowTool: z.infer<typeof WorkflowTool> | undefined;
|
||||
systemMessage: string | undefined;
|
||||
}) {
|
||||
const [result, setResult] = useState<z.infer<typeof apiV1.ToolMessage> | undefined>(availableResult);
|
||||
const [response, setResponse] = useState('');
|
||||
const [generatingResponse, setGeneratingResponse] = useState(false);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(response);
|
||||
} catch (e) {
|
||||
alert('Invalid JSON');
|
||||
return;
|
||||
}
|
||||
const result: z.infer<typeof apiV1.ToolMessage> = {
|
||||
role: 'tool',
|
||||
tool_call_id: toolCall.id,
|
||||
tool_name: toolCall.function.name,
|
||||
content: JSON.stringify(parsed),
|
||||
};
|
||||
setResult(result);
|
||||
handleResult(result);
|
||||
}, [toolCall.id, toolCall.function.name, handleResult, response]);
|
||||
|
||||
useEffect(() => {
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
if (response) {
|
||||
return;
|
||||
}
|
||||
let ignore = false;
|
||||
|
||||
async function process() {
|
||||
setGeneratingResponse(true);
|
||||
|
||||
const response = await suggestToolResponse(
|
||||
toolCall.id,
|
||||
projectId,
|
||||
[{
|
||||
role: 'system',
|
||||
content: systemMessage || '',
|
||||
createdAt: new Date().toISOString(),
|
||||
version: 'v1',
|
||||
chatId: '',
|
||||
}, ...messages],
|
||||
testProfile?.mockPrompt || workflowTool?.mockInstructions || '',
|
||||
);
|
||||
if (ignore) {
|
||||
return;
|
||||
}
|
||||
setResponse(response);
|
||||
setGeneratingResponse(false);
|
||||
}
|
||||
process();
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [result, response, toolCall.id, projectId, messages, testProfile, systemMessage, workflowTool?.mockInstructions]);
|
||||
|
||||
// auto submit if autoSubmitMockedResponse is true
|
||||
useEffect(() => {
|
||||
if (!workflowTool?.autoSubmitMockedResponse) {
|
||||
return;
|
||||
}
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
if (response) {
|
||||
handleSubmit();
|
||||
}
|
||||
}, [workflowTool?.autoSubmitMockedResponse, response, handleSubmit, result]);
|
||||
|
||||
return <div className="flex flex-col gap-1">
|
||||
{sender && <div className='text-gray-500 dark:text-gray-400 text-xs ml-3'>{sender}</div>}
|
||||
<div className='border border-gray-300 dark:border-gray-700 p-2 pt-2 rounded-lg rounded-bl-none flex flex-col gap-2 mr-[30%] bg-white dark:bg-gray-900'>
|
||||
<div className="flex items-center gap-2">
|
||||
{!result && <Spinner size="sm" />}
|
||||
{result && <CircleCheckIcon size={16} className="text-gray-500 dark:text-gray-400" />}
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||
Function Call: <code className='bg-gray-100 dark:bg-neutral-800 px-2 py-0.5 rounded font-mono'>{toolCall.function.name}</code>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2'>
|
||||
<ExpandableContent label='Params' content={toolCall.function.arguments} expanded={false} />
|
||||
{result && <ExpandableContent label='Result' content={result.content} expanded={false} />}
|
||||
</div>
|
||||
|
||||
{!result && !workflowTool?.autoSubmitMockedResponse && <div className='flex flex-col gap-2 mt-2'>
|
||||
<div>Response:</div>
|
||||
<Textarea
|
||||
maxRows={10}
|
||||
placeholder='{}'
|
||||
variant="bordered"
|
||||
value={response}
|
||||
disabled={generatingResponse}
|
||||
onValueChange={(value) => setResponse(value)}
|
||||
className='font-mono'
|
||||
size="sm"
|
||||
>
|
||||
</Textarea>
|
||||
<Button
|
||||
onPress={handleSubmit}
|
||||
disabled={generatingResponse}
|
||||
isLoading={generatingResponse}
|
||||
size="sm"
|
||||
>
|
||||
Submit result
|
||||
</Button>
|
||||
</div>}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function ExpandableContent({
|
||||
label,
|
||||
content,
|
||||
|
|
@ -701,7 +291,6 @@ export function Messages({
|
|||
projectId,
|
||||
messages,
|
||||
toolCallResults,
|
||||
handleToolCallResults,
|
||||
loadingAssistantResponse,
|
||||
loadingUserResponse,
|
||||
workflow,
|
||||
|
|
@ -712,7 +301,6 @@ export function Messages({
|
|||
projectId: string;
|
||||
messages: z.infer<typeof apiV1.ChatMessage>[];
|
||||
toolCallResults: Record<string, z.infer<typeof apiV1.ToolMessage>>;
|
||||
handleToolCallResults: (results: z.infer<typeof apiV1.ToolMessage>[]) => void;
|
||||
loadingAssistantResponse: boolean;
|
||||
loadingUserResponse: boolean;
|
||||
workflow: z.infer<typeof Workflow>;
|
||||
|
|
@ -742,7 +330,6 @@ export function Messages({
|
|||
key={index}
|
||||
toolCalls={message.tool_calls}
|
||||
results={toolCallResults}
|
||||
handleResults={handleToolCallResults}
|
||||
projectId={projectId}
|
||||
messages={messages}
|
||||
sender={message.agenticSender}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"use client";
|
||||
import { WithStringId } from "../../../lib/types/types";
|
||||
import { MCPServer, WithStringId } from "../../../lib/types/types";
|
||||
import { Workflow } from "../../../lib/types/workflow_types";
|
||||
import { DataSource } from "../../../lib/types/datasource_types";
|
||||
import { z } from "zod";
|
||||
|
|
@ -13,9 +13,13 @@ import { listDataSources } from "../../../actions/datasource_actions";
|
|||
export function App({
|
||||
projectId,
|
||||
useRag,
|
||||
mcpServerUrls,
|
||||
toolWebhookUrl,
|
||||
}: {
|
||||
projectId: string;
|
||||
useRag: boolean;
|
||||
mcpServerUrls: Array<z.infer<typeof MCPServer>>;
|
||||
toolWebhookUrl: string;
|
||||
}) {
|
||||
const [selectorKey, setSelectorKey] = useState(0);
|
||||
const [workflow, setWorkflow] = useState<WithStringId<z.infer<typeof Workflow>> | null>(null);
|
||||
|
|
@ -108,6 +112,8 @@ export function App({
|
|||
handleShowSelector={handleShowSelector}
|
||||
handleCloneVersion={handleCloneVersion}
|
||||
useRag={useRag}
|
||||
mcpServerUrls={mcpServerUrls}
|
||||
toolWebhookUrl={toolWebhookUrl}
|
||||
/>}
|
||||
</>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Metadata } from "next";
|
||||
import { App } from "./app";
|
||||
import { USE_RAG } from "@/app/lib/feature_flags";
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
import { projectsCollection } from "@/app/lib/mongodb";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Workflow"
|
||||
|
|
@ -13,8 +13,18 @@ export default async function Page({
|
|||
}: {
|
||||
params: { projectId: string };
|
||||
}) {
|
||||
const project = await projectsCollection.findOne({
|
||||
_id: params.projectId,
|
||||
});
|
||||
if (!project) {
|
||||
notFound();
|
||||
}
|
||||
const toolWebhookUrl = project.webhookUrl ?? '';
|
||||
|
||||
return <App
|
||||
projectId={params.projectId}
|
||||
useRag={USE_RAG}
|
||||
mcpServerUrls={project.mcpServers ?? []}
|
||||
toolWebhookUrl={toolWebhookUrl}
|
||||
/>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"use client";
|
||||
import { WithStringId } from "../../../lib/types/types";
|
||||
import { MCPServer, WithStringId } from "../../../lib/types/types";
|
||||
import { Workflow } from "../../../lib/types/workflow_types";
|
||||
import { WorkflowTool } from "../../../lib/types/workflow_types";
|
||||
import { WorkflowPrompt } from "../../../lib/types/workflow_types";
|
||||
|
|
@ -559,6 +559,8 @@ export function WorkflowEditor({
|
|||
handleShowSelector,
|
||||
handleCloneVersion,
|
||||
useRag,
|
||||
mcpServerUrls,
|
||||
toolWebhookUrl,
|
||||
}: {
|
||||
dataSources: WithStringId<z.infer<typeof DataSource>>[];
|
||||
workflow: WithStringId<z.infer<typeof Workflow>>;
|
||||
|
|
@ -566,6 +568,8 @@ export function WorkflowEditor({
|
|||
handleShowSelector: () => void;
|
||||
handleCloneVersion: (workflowId: string) => void;
|
||||
useRag: boolean;
|
||||
mcpServerUrls: Array<z.infer<typeof MCPServer>>;
|
||||
toolWebhookUrl: string;
|
||||
}) {
|
||||
const [state, dispatch] = useReducer<Reducer<State, Action>>(reducer, {
|
||||
patches: [],
|
||||
|
|
@ -911,6 +915,8 @@ export function WorkflowEditor({
|
|||
projectId={state.present.workflow.projectId}
|
||||
workflow={state.present.workflow}
|
||||
messageSubscriber={updateChatMessages}
|
||||
mcpServerUrls={mcpServerUrls}
|
||||
toolWebhookUrl={toolWebhookUrl}
|
||||
/>
|
||||
{state.present.selection?.type === "agent" && <AgentConfig
|
||||
key={state.present.selection.name}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue