mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-19 18:35:18 +02:00
port agents runtime to ts
This commit is contained in:
parent
07b561f3c6
commit
d22af5e4ec
22 changed files with 1514 additions and 769 deletions
|
|
@ -1,6 +1,4 @@
|
||||||
'use server';
|
'use server';
|
||||||
import { AgenticAPIInitStreamResponse } from "../lib/types/agents_api_types";
|
|
||||||
import { AgenticAPIChatRequest } from "../lib/types/agents_api_types";
|
|
||||||
import { WebpageCrawlResponse } from "../lib/types/tool_types";
|
import { WebpageCrawlResponse } from "../lib/types/tool_types";
|
||||||
import { webpagesCollection } from "../lib/mongodb";
|
import { webpagesCollection } from "../lib/mongodb";
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
@ -10,6 +8,8 @@ import { check_query_limit } from "../lib/rate_limiting";
|
||||||
import { QueryLimitError } from "../lib/client_utils";
|
import { QueryLimitError } from "../lib/client_utils";
|
||||||
import { projectAuthCheck } from "./project_actions";
|
import { projectAuthCheck } from "./project_actions";
|
||||||
import { authorizeUserAction } from "./billing_actions";
|
import { authorizeUserAction } from "./billing_actions";
|
||||||
|
import { Workflow, WorkflowTool } from "../lib/types/workflow_types";
|
||||||
|
import { Message } from "@/app/lib/types/types";
|
||||||
|
|
||||||
const crawler = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY || '' });
|
const crawler = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY || '' });
|
||||||
|
|
||||||
|
|
@ -57,14 +57,18 @@ export async function scrapeWebpage(url: string): Promise<z.infer<typeof Webpage
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAssistantResponseStreamId(request: z.infer<typeof AgenticAPIChatRequest>): Promise<z.infer<typeof AgenticAPIInitStreamResponse> | { billingError: string }> {
|
export async function getAssistantResponseStreamId(
|
||||||
await projectAuthCheck(request.projectId);
|
workflow: z.infer<typeof Workflow>,
|
||||||
if (!await check_query_limit(request.projectId)) {
|
projectTools: z.infer<typeof WorkflowTool>[],
|
||||||
|
messages: z.infer<typeof Message>[],
|
||||||
|
): Promise<{ streamId: string } | { billingError: string }> {
|
||||||
|
await projectAuthCheck(workflow.projectId);
|
||||||
|
if (!await check_query_limit(workflow.projectId)) {
|
||||||
throw new QueryLimitError();
|
throw new QueryLimitError();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check billing authorization
|
// Check billing authorization
|
||||||
const agentModels = request.agents.reduce((acc, agent) => {
|
const agentModels = workflow.agents.reduce((acc, agent) => {
|
||||||
acc.push(agent.model);
|
acc.push(agent.model);
|
||||||
return acc;
|
return acc;
|
||||||
}, [] as string[]);
|
}, [] as string[]);
|
||||||
|
|
@ -78,6 +82,6 @@ export async function getAssistantResponseStreamId(request: z.infer<typeof Agent
|
||||||
return { billingError: error || 'Billing error' };
|
return { billingError: error || 'Billing error' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await getAgenticResponseStreamId(request);
|
const response = await getAgenticResponseStreamId(workflow, projectTools, messages);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
@ -1,43 +1,11 @@
|
||||||
"use server";
|
"use server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { WorkflowTool } from "../lib/types/workflow_types";
|
import { WorkflowTool } from "../lib/types/workflow_types";
|
||||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
||||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
||||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
||||||
import { projectAuthCheck } from "./project_actions";
|
import { projectAuthCheck } from "./project_actions";
|
||||||
import { projectsCollection, agentWorkflowsCollection } from "../lib/mongodb";
|
import { projectsCollection, agentWorkflowsCollection } from "../lib/mongodb";
|
||||||
import { Project } from "../lib/types/project_types";
|
import { Project } from "../lib/types/project_types";
|
||||||
import { MCPServer, McpServerTool, convertMcpServerToolToWorkflowTool } from "../lib/types/types";
|
import { MCPServer, McpServerTool, convertMcpServerToolToWorkflowTool } from "../lib/types/types";
|
||||||
|
import { getMcpClient } from "../lib/mcp";
|
||||||
async function getMcpClient(serverUrl: string, serverName: string): Promise<Client> {
|
|
||||||
let client: Client | undefined = undefined;
|
|
||||||
const baseUrl = new URL(serverUrl);
|
|
||||||
|
|
||||||
// Try to connect using Streamable HTTP transport
|
|
||||||
try {
|
|
||||||
client = new Client({
|
|
||||||
name: 'streamable-http-client',
|
|
||||||
version: '1.0.0'
|
|
||||||
});
|
|
||||||
const transport = new StreamableHTTPClientTransport(
|
|
||||||
new URL(baseUrl)
|
|
||||||
);
|
|
||||||
await client.connect(transport);
|
|
||||||
console.log(`[MCP] Connected using Streamable HTTP transport to ${serverName}`);
|
|
||||||
return client;
|
|
||||||
} catch (error) {
|
|
||||||
// If that fails with a 4xx error, try the older SSE transport
|
|
||||||
console.log(`[MCP] Streamable HTTP connection failed, falling back to SSE transport for ${serverName}`);
|
|
||||||
client = new Client({
|
|
||||||
name: 'sse-client',
|
|
||||||
version: '1.0.0'
|
|
||||||
});
|
|
||||||
const sseTransport = new SSEClientTransport(baseUrl);
|
|
||||||
await client.connect(sseTransport);
|
|
||||||
console.log(`[MCP] Connected using SSE transport to ${serverName}`);
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchMcpTools(projectId: string): Promise<z.infer<typeof WorkflowTool>[]> {
|
export async function fetchMcpTools(projectId: string): Promise<z.infer<typeof WorkflowTool>[]> {
|
||||||
await projectAuthCheck(projectId);
|
await projectAuthCheck(projectId);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
import { getCustomerIdForProject, logUsage } from "@/app/lib/billing";
|
import { getCustomerIdForProject, logUsage } from "@/app/lib/billing";
|
||||||
import { USE_BILLING } from "@/app/lib/feature_flags";
|
import { USE_BILLING } from "@/app/lib/feature_flags";
|
||||||
import { redisClient } from "@/app/lib/redis";
|
import { redisClient } from "@/app/lib/redis";
|
||||||
import { AgenticAPIChatMessage, AgenticAPIChatRequest, convertFromAgenticAPIChatMessages } from "@/app/lib/types/agents_api_types";
|
import { Workflow, WorkflowTool } from "@/app/lib/types/workflow_types";
|
||||||
import { createParser, type EventSourceMessage } from 'eventsource-parser';
|
import { streamResponse } from "@/app/lib/agents";
|
||||||
|
import { Message } from "@/app/lib/types/types";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const PayloadSchema = z.object({
|
||||||
|
workflow: Workflow,
|
||||||
|
projectTools: z.array(WorkflowTool),
|
||||||
|
messages: z.array(Message),
|
||||||
|
});
|
||||||
|
|
||||||
export async function GET(request: Request, props: { params: Promise<{ streamId: string }> }) {
|
export async function GET(request: Request, props: { params: Promise<{ streamId: string }> }) {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
|
|
@ -13,85 +21,42 @@ export async function GET(request: Request, props: { params: Promise<{ streamId:
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the payload
|
// parse the payload
|
||||||
const parsedPayload = AgenticAPIChatRequest.parse(JSON.parse(payload));
|
const { workflow, projectTools, messages } = PayloadSchema.parse(JSON.parse(payload));
|
||||||
|
console.log('payload', payload);
|
||||||
|
|
||||||
// fetch billing customer id
|
// fetch billing customer id
|
||||||
let billingCustomerId: string | null = null;
|
let billingCustomerId: string | null = null;
|
||||||
if (USE_BILLING) {
|
if (USE_BILLING) {
|
||||||
billingCustomerId = await getCustomerIdForProject(parsedPayload.projectId);
|
billingCustomerId = await getCustomerIdForProject(workflow.projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the upstream SSE stream.
|
|
||||||
const upstreamResponse = await fetch(`${process.env.AGENTS_API_URL}/chat_stream`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${process.env.AGENTS_API_KEY || 'test'}`,
|
|
||||||
},
|
|
||||||
cache: 'no-store',
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the upstream request fails, return a 502 Bad Gateway.
|
|
||||||
if (!upstreamResponse.ok || !upstreamResponse.body) {
|
|
||||||
return new Response("Error connecting to upstream SSE stream", { status: 502 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = upstreamResponse.body.getReader();
|
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
|
let messageCount = 0;
|
||||||
|
|
||||||
const stream = new ReadableStream({
|
const stream = new ReadableStream({
|
||||||
async start(controller) {
|
async start(controller) {
|
||||||
let messageCount = 0;
|
|
||||||
|
|
||||||
function emitEvent(event: EventSourceMessage) {
|
|
||||||
// Re-emit the event in SSE format
|
|
||||||
let eventString = '';
|
|
||||||
if (event.id) eventString += `id: ${event.id}\n`;
|
|
||||||
if (event.event) eventString += `event: ${event.event}\n`;
|
|
||||||
if (event.data) eventString += `data: ${event.data}\n`;
|
|
||||||
eventString += '\n';
|
|
||||||
|
|
||||||
controller.enqueue(encoder.encode(eventString));
|
|
||||||
}
|
|
||||||
|
|
||||||
const parser = createParser({
|
|
||||||
onEvent(event: EventSourceMessage) {
|
|
||||||
if (event.event !== 'message') {
|
|
||||||
emitEvent(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse message
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
const msg = AgenticAPIChatMessage.parse(data);
|
|
||||||
const parsedMsg = convertFromAgenticAPIChatMessages([msg])[0];
|
|
||||||
|
|
||||||
// increment the message count if this is an assistant message
|
|
||||||
if (parsedMsg.role === 'assistant') {
|
|
||||||
messageCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// emit the event
|
|
||||||
emitEvent(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (true) {
|
// Iterate over the generator
|
||||||
const { done, value } = await reader.read();
|
for await (const event of streamResponse(workflow, projectTools, messages)) {
|
||||||
if (done) break;
|
// Check if this is a message event (has role property)
|
||||||
|
if ('role' in event) {
|
||||||
// Feed the chunk to the parser
|
if (event.role === 'assistant') {
|
||||||
parser.feed(new TextDecoder().decode(value));
|
messageCount++;
|
||||||
|
}
|
||||||
|
controller.enqueue(encoder.encode(`event: message\ndata: ${JSON.stringify(event)}\n\n`));
|
||||||
|
} else {
|
||||||
|
controller.enqueue(encoder.encode(`event: done\ndata: ${JSON.stringify(event)}\n\n`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.close();
|
controller.close();
|
||||||
|
|
||||||
|
// Log billing usage
|
||||||
if (USE_BILLING && billingCustomerId) {
|
if (USE_BILLING && billingCustomerId) {
|
||||||
await logUsage(billingCustomerId, {
|
await logUsage(billingCustomerId, {
|
||||||
type: "agent_messages",
|
type: "agent_messages",
|
||||||
amount: messageCount,
|
amount: messageCount,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing stream:', error);
|
console.error('Error processing stream:', error);
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,13 @@ import { z } from "zod";
|
||||||
import { ObjectId } from "mongodb";
|
import { ObjectId } from "mongodb";
|
||||||
import { authCheck } from "../../utils";
|
import { authCheck } from "../../utils";
|
||||||
import { ApiRequest, ApiResponse } from "../../../../lib/types/types";
|
import { ApiRequest, ApiResponse } from "../../../../lib/types/types";
|
||||||
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 { check_query_limit } from "../../../../lib/rate_limiting";
|
||||||
import { PrefixLogger } from "../../../../lib/utils";
|
import { PrefixLogger } from "../../../../lib/utils";
|
||||||
import { TestProfile } from "@/app/lib/types/testing_types";
|
import { TestProfile } from "@/app/lib/types/testing_types";
|
||||||
import { fetchProjectMcpTools } from "@/app/lib/project_tools";
|
import { fetchProjectMcpTools } from "@/app/lib/project_tools";
|
||||||
import { authorize, getCustomerIdForProject, logUsage } from "@/app/lib/billing";
|
import { authorize, getCustomerIdForProject, logUsage } from "@/app/lib/billing";
|
||||||
import { USE_BILLING } from "@/app/lib/feature_flags";
|
import { USE_BILLING } from "@/app/lib/feature_flags";
|
||||||
|
import { getResponse } from "@/app/lib/agents";
|
||||||
|
|
||||||
// get next turn / agent response
|
// get next turn / agent response
|
||||||
export async function POST(
|
export async function POST(
|
||||||
|
|
@ -52,7 +51,6 @@ export async function POST(
|
||||||
return Response.json({ error: `Invalid request body: ${result.error.message}` }, { status: 400 });
|
return Response.json({ error: `Invalid request body: ${result.error.message}` }, { status: 400 });
|
||||||
}
|
}
|
||||||
const reqMessages = result.data.messages;
|
const reqMessages = result.data.messages;
|
||||||
const reqState = result.data.state;
|
|
||||||
|
|
||||||
// fetch published workflow id
|
// fetch published workflow id
|
||||||
const project = await projectsCollection.findOne({
|
const project = await projectsCollection.findOne({
|
||||||
|
|
@ -112,34 +110,12 @@ export async function POST(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentState: unknown = reqState ?? { last_agent_name: workflow.agents[0].name };
|
|
||||||
|
|
||||||
// get assistant response
|
// get assistant response
|
||||||
const { agents, tools, prompts, startAgent } = convertWorkflowToAgenticAPI(workflow, projectTools);
|
const { messages } = await getResponse(workflow, projectTools, reqMessages);
|
||||||
const request: z.infer<typeof AgenticAPIChatRequest> = {
|
|
||||||
projectId,
|
|
||||||
messages: convertFromApiToAgenticApiMessages(reqMessages),
|
|
||||||
state: currentState,
|
|
||||||
agents,
|
|
||||||
tools,
|
|
||||||
prompts,
|
|
||||||
startAgent,
|
|
||||||
testProfile: testProfile ?? undefined,
|
|
||||||
mcpServers: (project.mcpServers ?? []).map(server => ({
|
|
||||||
name: server.name,
|
|
||||||
serverUrl: server.serverUrl ?? '',
|
|
||||||
isReady: server.isReady ?? false
|
|
||||||
})),
|
|
||||||
toolWebhookUrl: project.webhookUrl ?? '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const { messages: agenticMessages, state } = await getAgenticApiResponse(request);
|
|
||||||
const newMessages = convertFromAgenticApiToApiMessages(agenticMessages);
|
|
||||||
const newState = state;
|
|
||||||
|
|
||||||
// log billing usage
|
// log billing usage
|
||||||
if (USE_BILLING && billingCustomerId) {
|
if (USE_BILLING && billingCustomerId) {
|
||||||
const agentMessageCount = newMessages.filter(m => m.role === 'assistant').length;
|
const agentMessageCount = messages.filter(m => m.role === 'assistant').length;
|
||||||
await logUsage(billingCustomerId, {
|
await logUsage(billingCustomerId, {
|
||||||
type: 'agent_messages',
|
type: 'agent_messages',
|
||||||
amount: agentMessageCount,
|
amount: agentMessageCount,
|
||||||
|
|
@ -147,8 +123,7 @@ export async function POST(
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseBody: z.infer<typeof ApiResponse> = {
|
const responseBody: z.infer<typeof ApiResponse> = {
|
||||||
messages: newMessages,
|
messages,
|
||||||
state: newState,
|
|
||||||
};
|
};
|
||||||
return Response.json(responseBody);
|
return Response.json(responseBody);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,108 @@ import { agentWorkflowsCollection, projectsCollection, chatsCollection, chatMess
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ObjectId, WithId } from "mongodb";
|
import { ObjectId, WithId } from "mongodb";
|
||||||
import { authCheck } from "../../../utils";
|
import { authCheck } from "../../../utils";
|
||||||
import { convertFromAgenticAPIChatMessages } from "../../../../../../lib/types/agents_api_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";
|
|
||||||
import { getAgenticApiResponse } from "../../../../../../lib/utils";
|
|
||||||
import { check_query_limit } from "../../../../../../lib/rate_limiting";
|
import { check_query_limit } from "../../../../../../lib/rate_limiting";
|
||||||
import { PrefixLogger } from "../../../../../../lib/utils";
|
import { PrefixLogger } from "../../../../../../lib/utils";
|
||||||
import { fetchProjectMcpTools } from "@/app/lib/project_tools";
|
import { fetchProjectMcpTools } from "@/app/lib/project_tools";
|
||||||
import { authorize, getCustomerIdForProject, logUsage } from "@/app/lib/billing";
|
import { authorize, getCustomerIdForProject, logUsage } from "@/app/lib/billing";
|
||||||
import { USE_BILLING } from "@/app/lib/feature_flags";
|
import { USE_BILLING } from "@/app/lib/feature_flags";
|
||||||
|
import { getResponse } from "@/app/lib/agents";
|
||||||
|
import { Message, AssistantMessage, AssistantMessageWithToolCalls, ToolMessage } from "@/app/lib/types/types";
|
||||||
|
|
||||||
|
function convert(messages: z.infer<typeof apiV1.ChatMessage>[]): z.infer<typeof Message>[] {
|
||||||
|
const result: z.infer<typeof Message>[] = [];
|
||||||
|
for (const m of messages) {
|
||||||
|
if (m.role === 'assistant') {
|
||||||
|
if ('tool_calls' in m) {
|
||||||
|
result.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: null,
|
||||||
|
agentName: m.agenticSender ?? '',
|
||||||
|
toolCalls: m.tool_calls.map((t: any) => ({
|
||||||
|
function: {
|
||||||
|
name: t.function.name,
|
||||||
|
arguments: t.function.arguments,
|
||||||
|
},
|
||||||
|
type: 'function',
|
||||||
|
id: t.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: m.content,
|
||||||
|
agentName: m.agenticSender ?? '',
|
||||||
|
responseType: m.agenticResponseType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (m.role === 'tool') {
|
||||||
|
result.push({
|
||||||
|
role: 'tool',
|
||||||
|
content: m.content,
|
||||||
|
toolCallId: m.tool_call_id,
|
||||||
|
toolName: m.tool_name,
|
||||||
|
});
|
||||||
|
} else if (m.role === 'system') {
|
||||||
|
result.push({
|
||||||
|
role: 'system',
|
||||||
|
content: m.content,
|
||||||
|
});
|
||||||
|
} else if (m.role === 'user') {
|
||||||
|
result.push({
|
||||||
|
role: 'user',
|
||||||
|
content: m.content,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertBack(messages: z.infer<typeof AssistantMessage | typeof AssistantMessageWithToolCalls | typeof ToolMessage>[]): z.infer<typeof apiV1.ChatMessage>[] {
|
||||||
|
const result: z.infer<typeof apiV1.ChatMessage>[] = [];
|
||||||
|
for (const m of messages) {
|
||||||
|
if (m.role === 'assistant') {
|
||||||
|
if ('toolCalls' in m) {
|
||||||
|
result.push({
|
||||||
|
version: 'v1',
|
||||||
|
chatId: '',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
role: 'assistant',
|
||||||
|
agenticSender: m.agentName,
|
||||||
|
agenticResponseType: 'external',
|
||||||
|
tool_calls: m.toolCalls.map((t: any) => ({
|
||||||
|
function: {
|
||||||
|
name: t.function.name,
|
||||||
|
arguments: t.function.arguments,
|
||||||
|
},
|
||||||
|
type: 'function',
|
||||||
|
id: t.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result.push({
|
||||||
|
version: 'v1',
|
||||||
|
chatId: '',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
role: 'assistant',
|
||||||
|
content: m.content,
|
||||||
|
agenticSender: m.agentName,
|
||||||
|
agenticResponseType: m.responseType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (m.role === 'tool') {
|
||||||
|
result.push({
|
||||||
|
version: 'v1',
|
||||||
|
chatId: '',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
role: 'tool',
|
||||||
|
content: m.content,
|
||||||
|
tool_call_id: m.toolCallId,
|
||||||
|
tool_name: m.toolName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// get next turn / agent response
|
// get next turn / agent response
|
||||||
export async function POST(
|
export async function POST(
|
||||||
|
|
@ -119,47 +211,23 @@ export async function POST(
|
||||||
}
|
}
|
||||||
|
|
||||||
// get assistant response
|
// get assistant response
|
||||||
const { agents, tools, prompts, startAgent } = convertWorkflowToAgenticAPI(workflow, projectTools);
|
const inMessages: z.infer<typeof Message>[] = convert(messages);
|
||||||
const unsavedMessages: z.infer<typeof apiV1.ChatMessage>[] = [userMessage];
|
inMessages.push(userMessage);
|
||||||
let state: unknown = chat.agenticState ?? { last_agent_name: startAgent };
|
|
||||||
|
|
||||||
const request: z.infer<typeof AgenticAPIChatRequest> = {
|
const { messages: responseMessages } = await getResponse(workflow, projectTools, [systemMessage, ...inMessages]);
|
||||||
projectId: session.projectId,
|
const convertedResponseMessages = convertBack(responseMessages);
|
||||||
messages: convertToAgenticAPIChatMessages([systemMessage, ...messages, ...unsavedMessages]),
|
const unsavedMessages = [
|
||||||
state,
|
userMessage,
|
||||||
agents,
|
...convertedResponseMessages,
|
||||||
tools,
|
];
|
||||||
prompts,
|
|
||||||
startAgent,
|
|
||||||
mcpServers: (projectSettings.mcpServers ?? []).map(server => ({
|
|
||||||
name: server.name,
|
|
||||||
serverUrl: server.serverUrl || '',
|
|
||||||
isReady: server.isReady
|
|
||||||
})),
|
|
||||||
toolWebhookUrl: projectSettings.webhookUrl ?? '',
|
|
||||||
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`);
|
logger.log(`Saving ${unsavedMessages.length} new messages and updating chat state`);
|
||||||
await chatMessagesCollection.insertMany(unsavedMessages);
|
await chatMessagesCollection.insertMany(unsavedMessages);
|
||||||
await chatsCollection.updateOne({ _id: new ObjectId(chatId) }, { $set: { agenticState: state } });
|
await chatsCollection.updateOne({ _id: new ObjectId(chatId) }, { $set: { agenticState: chat.agenticState } });
|
||||||
|
|
||||||
// log billing usage
|
// log billing usage
|
||||||
if (USE_BILLING && billingCustomerId) {
|
if (USE_BILLING && billingCustomerId) {
|
||||||
const agentMessageCount = convertedMessages.filter(m => m.role === 'assistant').length;
|
const agentMessageCount = convertedResponseMessages.filter(m => m.role === 'assistant').length;
|
||||||
await logUsage(billingCustomerId, {
|
await logUsage(billingCustomerId, {
|
||||||
type: 'agent_messages',
|
type: 'agent_messages',
|
||||||
amount: agentMessageCount,
|
amount: agentMessageCount,
|
||||||
|
|
|
||||||
109
apps/rowboat/app/lib/agent_instructions.ts
Normal file
109
apps/rowboat/app/lib/agent_instructions.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* Instructions for agents that use RAG (Retrieval Augmented Generation)
|
||||||
|
*/
|
||||||
|
export const RAG_INSTRUCTIONS = (ragToolName: string): string => `
|
||||||
|
# Instructions about using the article retrieval tool
|
||||||
|
- Where relevant, use the articles tool: ${ragToolName} to fetch articles with knowledge relevant to the query and use its contents to respond to the user.
|
||||||
|
- Do not send a separate message first asking the user to wait while you look up information. Immediately fetch the articles and respond to the user with the answer to their query.
|
||||||
|
- Do not make up information. If the article's contents do not have the answer, give up control of the chat (or transfer to your parent agent, as per your transfer instructions). Do not say anything to the user.
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructions for child agents that are aware of parent agents
|
||||||
|
* These instructions guide agents that can transfer control to parent agents
|
||||||
|
*/
|
||||||
|
export const TRANSFER_PARENT_AWARE_INSTRUCTIONS = (candidateParentsNameDescriptionTools: string): string => `
|
||||||
|
# Instructions about using your parent agents
|
||||||
|
You have the following candidate parent agents that you can transfer the chat to, using the appropriate tool calls for the transfer:
|
||||||
|
${candidateParentsNameDescriptionTools}.
|
||||||
|
|
||||||
|
## Notes:
|
||||||
|
- During runtime, you will be provided with a tool call for exactly one of these parent agents that you can use. Use that tool call to transfer the chat to the parent agent in case you are unable to handle the chat (e.g. if it is not in your scope of instructions).
|
||||||
|
- Transfer the chat to the appropriate agent, based on the chat history and / or the user's request.
|
||||||
|
- When you transfer the chat to another agent, you should not provide any response to the user. For example, do not say 'Transferring chat to X agent' or anything like that. Just invoke the tool call to transfer to the other agent.
|
||||||
|
- Do NOT ever mention the existence of other agents. For example, do not say 'Please check with X agent for details regarding processing times.' or anything like that.
|
||||||
|
- If any other agent transfers the chat to you without responding to the user, it means that they don't know how to help. Do not transfer the chat to back to the same agent in this case. In such cases, you should transfer to the escalation agent using the appropriate tool call. Never ask the user to contact support.
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructions for child agents that give up control to parent agents
|
||||||
|
* These instructions guide agents that need to relinquish control to parent agents
|
||||||
|
*/
|
||||||
|
export const TRANSFER_GIVE_UP_CONTROL_INSTRUCTIONS = (candidateParentsNameDescriptionTools: string): string => `
|
||||||
|
# Instructions about giving up chat control
|
||||||
|
If you are unable to handle the chat (e.g. if it is not in your scope of instructions), you should use the tool call provided to give up control of the chat.
|
||||||
|
${candidateParentsNameDescriptionTools}
|
||||||
|
|
||||||
|
## Notes:
|
||||||
|
- When you give up control of the chat, you should not provide any response to the user. Just invoke the tool call to give up control.
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructions for parent agents that need to transfer the chat to other specialized (children) agents
|
||||||
|
* These instructions guide parent agents in delegating tasks to specialized child agents
|
||||||
|
*/
|
||||||
|
export const TRANSFER_CHILDREN_INSTRUCTIONS = (otherAgentNameDescriptionsTools: string): string => `
|
||||||
|
# Instructions about using other specialized agents
|
||||||
|
You have the following specialized agents that you can transfer the chat to, using the appropriate tool calls for the transfer:
|
||||||
|
${otherAgentNameDescriptionsTools}
|
||||||
|
|
||||||
|
## Notes:
|
||||||
|
- Transfer the chat to the appropriate agent, based on the chat history and / or the user's request.
|
||||||
|
- When you transfer the chat to another agent, you should not provide any response to the user. For example, do not say 'Transferring chat to X agent' or anything like that. Just invoke the tool call to transfer to the other agent.
|
||||||
|
- Do NOT ever mention the existence of other agents. For example, do not say 'Please check with X agent for details regarding processing times.' or anything like that.
|
||||||
|
- If any other agent transfers the chat to you without responding to the user, it means that they don't know how to help. Do not transfer the chat to back to the same agent in this case. In such cases, you should transfer to the escalation agent using the appropriate tool call. Never ask the user to contact support.
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional instruction for escalation agent when called due to an error
|
||||||
|
* These instructions are used when other agents are unable to handle the chat
|
||||||
|
*/
|
||||||
|
export const ERROR_ESCALATION_AGENT_INSTRUCTIONS = `
|
||||||
|
# Context
|
||||||
|
The rest of the parts of the chatbot were unable to handle the chat. Hence, the chat has been escalated to you. In addition to your other instructions, tell the user that you are having trouble handling the chat - say "I'm having trouble helping with your request. Sorry about that.". Remember you are a part of the chatbot as well.
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal system message formatting
|
||||||
|
* Template for system-wide context and instructions
|
||||||
|
*/
|
||||||
|
export const SYSTEM_MESSAGE = (systemMessage: string): string => `
|
||||||
|
# Additional System-Wide Context or Instructions:
|
||||||
|
${systemMessage}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructions for non-repeat child transfer
|
||||||
|
* Critical rules for handling agent transfers and handoffs to prevent circular transfers
|
||||||
|
*/
|
||||||
|
export const CHILD_TRANSFER_RELATED_INSTRUCTIONS = `
|
||||||
|
# Critical Rules for Agent Transfers and Handoffs
|
||||||
|
|
||||||
|
- SEQUENTIAL TRANSFERS AND RESPONSES:
|
||||||
|
1. BEFORE transferring to any agent:
|
||||||
|
- Plan your complete sequence of needed transfers
|
||||||
|
- Document which responses you need to collect
|
||||||
|
|
||||||
|
2. DURING transfers:
|
||||||
|
- Transfer to only ONE agent at a time
|
||||||
|
- Wait for that agent's COMPLETE response and then proceed with the next agent
|
||||||
|
- Store the response for later use
|
||||||
|
- Only then proceed with the next transfer
|
||||||
|
- Never attempt parallel or simultaneous transfers
|
||||||
|
- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent (a handoff). You must only put out 1 transfer related tool call in one output.
|
||||||
|
|
||||||
|
3. AFTER receiving a response:
|
||||||
|
- Do not transfer to another agent until you've processed the current response
|
||||||
|
- If you need to transfer to another agent, wait for your current processing to complete
|
||||||
|
- Never transfer back to an agent that has already responded
|
||||||
|
|
||||||
|
- COMPLETION REQUIREMENTS:
|
||||||
|
- Never provide final response until ALL required agents have been consulted
|
||||||
|
- Never attempt to get multiple responses in parallel
|
||||||
|
- If a transfer is rejected due to multiple handoffs:
|
||||||
|
1. Complete current response processing
|
||||||
|
2. Then retry the transfer as next in sequence
|
||||||
|
3. Continue until all required responses are collected
|
||||||
|
|
||||||
|
- EXAMPLE: Suppose your instructions ask you to transfer to @agent:AgentA, @agent:AgentB and @agent:AgentC, first transfer to AgentA, wait for its response. Then transfer to AgentB, wait for its response. Then transfer to AgentC, wait for its response. Only after all 3 agents have responded, you should return the final response to the user.
|
||||||
|
`;
|
||||||
909
apps/rowboat/app/lib/agents.ts
Normal file
909
apps/rowboat/app/lib/agents.ts
Normal file
|
|
@ -0,0 +1,909 @@
|
||||||
|
// External dependencies
|
||||||
|
import { Agent, AgentInputItem, run, tool, Tool } from "@openai/agents";
|
||||||
|
import { RECOMMENDED_PROMPT_PREFIX } from "@openai/agents-core/extensions";
|
||||||
|
import { aisdk } from "@openai/agents-extensions";
|
||||||
|
import { createOpenAI } from "@ai-sdk/openai";
|
||||||
|
import { CoreMessage, embed, generateText } from "ai";
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// Internal dependencies
|
||||||
|
import { embeddingModel } from '../lib/embedding';
|
||||||
|
import { getMcpClient } from "./mcp";
|
||||||
|
import { dataSourceDocsCollection, dataSourcesCollection } from "./mongodb";
|
||||||
|
import { qdrantClient } from '../lib/qdrant';
|
||||||
|
import { EmbeddingRecord } from "./types/datasource_types";
|
||||||
|
import { ConnectedEntity, sanitizeTextWithMentions, Workflow, WorkflowAgent, WorkflowPrompt, WorkflowTool } from "./types/workflow_types";
|
||||||
|
import { CHILD_TRANSFER_RELATED_INSTRUCTIONS, RAG_INSTRUCTIONS } from "./agent_instructions";
|
||||||
|
import { PrefixLogger } from "./utils";
|
||||||
|
import { Message, AssistantMessage, AssistantMessageWithToolCalls, ToolMessage } from "./types/types";
|
||||||
|
|
||||||
|
const PROVIDER_API_KEY = process.env.PROVIDER_API_KEY || process.env.OPENAI_API_KEY || '';
|
||||||
|
const PROVIDER_BASE_URL = process.env.PROVIDER_BASE_URL || undefined;
|
||||||
|
const MODEL = process.env.PROVIDER_DEFAULT_MODEL || 'gpt-4o';
|
||||||
|
|
||||||
|
const openai = createOpenAI({
|
||||||
|
apiKey: PROVIDER_API_KEY,
|
||||||
|
baseURL: PROVIDER_BASE_URL,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUsage = z.object({
|
||||||
|
tokens: z.object({
|
||||||
|
total: z.number(),
|
||||||
|
prompt: z.number(),
|
||||||
|
completion: z.number(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ZOutMessage = z.union([
|
||||||
|
AssistantMessage,
|
||||||
|
AssistantMessageWithToolCalls,
|
||||||
|
ToolMessage,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Helper to handle mock tool responses
|
||||||
|
async function invokeMockTool(
|
||||||
|
logger: PrefixLogger,
|
||||||
|
toolName: string,
|
||||||
|
args: string,
|
||||||
|
description: string,
|
||||||
|
mockInstructions: string
|
||||||
|
): Promise<string> {
|
||||||
|
logger = logger.child(`invokeMockTool`);
|
||||||
|
logger.log(`toolName: ${toolName}`);
|
||||||
|
logger.log(`args: ${args}`);
|
||||||
|
logger.log(`description: ${description}`);
|
||||||
|
logger.log(`mockInstructions: ${mockInstructions}`);
|
||||||
|
|
||||||
|
const messages: CoreMessage[] = [{
|
||||||
|
role: "system" as const,
|
||||||
|
content: `You are simulating the execution of a tool called '${toolName}'. Here is the description of the tool: ${description}. Here are the instructions for the mock tool: ${mockInstructions}. Generate a realistic response as if the tool was actually executed with the given parameters.`
|
||||||
|
}, {
|
||||||
|
role: "user" as const,
|
||||||
|
content: `Generate a realistic response for the tool '${toolName}' with these parameters: ${args}. The response should be concise and focused on what the tool would actually return.`
|
||||||
|
}];
|
||||||
|
|
||||||
|
const { text } = await generateText({
|
||||||
|
model: openai(MODEL),
|
||||||
|
messages,
|
||||||
|
});
|
||||||
|
logger.log(`generated text: ${text}`);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to handle RAG tool calls
|
||||||
|
async function invokeRagTool(
|
||||||
|
logger: PrefixLogger,
|
||||||
|
projectId: string,
|
||||||
|
query: string,
|
||||||
|
sourceIds: string[],
|
||||||
|
returnType: 'chunks' | 'content',
|
||||||
|
k: number
|
||||||
|
): Promise<{
|
||||||
|
title: string;
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
docId: string;
|
||||||
|
sourceId: string;
|
||||||
|
}[]> {
|
||||||
|
logger = logger.child(`invokeRagTool`);
|
||||||
|
logger.log(`projectId: ${projectId}`);
|
||||||
|
logger.log(`query: ${query}`);
|
||||||
|
logger.log(`sourceIds: ${sourceIds.join(', ')}`);
|
||||||
|
logger.log(`returnType: ${returnType}`);
|
||||||
|
logger.log(`k: ${k}`);
|
||||||
|
|
||||||
|
// Create embedding for question
|
||||||
|
const { embedding } = 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());
|
||||||
|
logger.log(`valid source ids: ${validSourceIds.join(', ')}`);
|
||||||
|
|
||||||
|
// if no sources found, return empty response
|
||||||
|
if (validSourceIds.length === 0) {
|
||||||
|
logger.log(`no valid source ids found, returning empty response`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform vector search
|
||||||
|
const qdrantResults = await qdrantClient.query("embeddings", {
|
||||||
|
query: embedding,
|
||||||
|
filter: {
|
||||||
|
must: [
|
||||||
|
{ key: "projectId", match: { value: projectId } },
|
||||||
|
{ key: "sourceId", match: { any: validSourceIds } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
limit: k,
|
||||||
|
with_payload: true,
|
||||||
|
});
|
||||||
|
logger.log(`found ${qdrantResults.points.length} results`);
|
||||||
|
|
||||||
|
// 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') {
|
||||||
|
logger.log(`returning 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();
|
||||||
|
logger.log(`fetched docs: ${docs.length}`);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to handle MCP tool calls
|
||||||
|
async function invokeMcpTool(
|
||||||
|
logger: PrefixLogger,
|
||||||
|
projectId: string,
|
||||||
|
name: string,
|
||||||
|
input: any,
|
||||||
|
mcpServerURL: string,
|
||||||
|
mcpServerName: string
|
||||||
|
) {
|
||||||
|
logger = logger.child(`invokeMcpTool`);
|
||||||
|
logger.log(`projectId: ${projectId}`);
|
||||||
|
logger.log(`name: ${name}`);
|
||||||
|
logger.log(`input: ${JSON.stringify(input)}`);
|
||||||
|
logger.log(`mcpServerURL: ${mcpServerURL}`);
|
||||||
|
logger.log(`mcpServerName: ${mcpServerName}`);
|
||||||
|
|
||||||
|
const client = await getMcpClient(mcpServerURL, mcpServerName || '');
|
||||||
|
const result = await client.callTool({
|
||||||
|
name,
|
||||||
|
arguments: input,
|
||||||
|
});
|
||||||
|
logger.log(`mcp tool result: ${JSON.stringify(result)}`);
|
||||||
|
await client.close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to create RAG tool
|
||||||
|
function createRagTool(
|
||||||
|
logger: PrefixLogger,
|
||||||
|
config: z.infer<typeof WorkflowAgent>,
|
||||||
|
projectId: string
|
||||||
|
): Tool {
|
||||||
|
if (!config.ragDataSources?.length) {
|
||||||
|
throw new Error(`data sources not found for agent ${config.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tool({
|
||||||
|
name: "rag_search",
|
||||||
|
description: config.description,
|
||||||
|
parameters: z.object({
|
||||||
|
query: z.string().describe("The query to search for")
|
||||||
|
}),
|
||||||
|
async execute(input: { query: string }) {
|
||||||
|
const results = await invokeRagTool(
|
||||||
|
logger,
|
||||||
|
projectId,
|
||||||
|
input.query,
|
||||||
|
config.ragDataSources || [],
|
||||||
|
config.ragReturnType || 'chunks',
|
||||||
|
config.ragK || 3
|
||||||
|
);
|
||||||
|
return JSON.stringify({
|
||||||
|
results,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to create a mock tool
|
||||||
|
function createMockTool(
|
||||||
|
logger: PrefixLogger,
|
||||||
|
config: z.infer<typeof WorkflowTool>,
|
||||||
|
): Tool {
|
||||||
|
return tool({
|
||||||
|
name: config.name,
|
||||||
|
description: config.description,
|
||||||
|
parameters: z.object({
|
||||||
|
query: z.string().describe("The query to search for")
|
||||||
|
}),
|
||||||
|
async execute(input: { query: string }) {
|
||||||
|
try {
|
||||||
|
const result = await invokeMockTool(
|
||||||
|
logger,
|
||||||
|
config.name,
|
||||||
|
JSON.stringify(input),
|
||||||
|
config.description,
|
||||||
|
config.mockInstructions || ''
|
||||||
|
);
|
||||||
|
return JSON.stringify({
|
||||||
|
result,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.log(`Error executing mock tool ${config.name}:`, error);
|
||||||
|
return JSON.stringify({
|
||||||
|
error: `Mock tool execution failed: ${error}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to create an mcp tool
|
||||||
|
function createMcpTool(
|
||||||
|
logger: PrefixLogger,
|
||||||
|
config: z.infer<typeof WorkflowTool>,
|
||||||
|
projectId: string
|
||||||
|
): Tool {
|
||||||
|
const { name, description, parameters, mcpServerName, mcpServerURL } = config;
|
||||||
|
|
||||||
|
return tool({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
strict: false,
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: parameters.properties,
|
||||||
|
required: parameters.required || [],
|
||||||
|
additionalProperties: true,
|
||||||
|
},
|
||||||
|
async execute(input: any) {
|
||||||
|
try {
|
||||||
|
const result = await invokeMcpTool(logger, projectId, name, input, mcpServerURL || '', mcpServerName || '');
|
||||||
|
return JSON.stringify({
|
||||||
|
result,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.log(`Error executing mcp tool ${name}:`, error);
|
||||||
|
return JSON.stringify({
|
||||||
|
error: `Tool execution failed: ${error}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to create an agent
|
||||||
|
function createAgent(
|
||||||
|
logger: PrefixLogger,
|
||||||
|
config: z.infer<typeof WorkflowAgent>,
|
||||||
|
tools: Record<string, Tool>,
|
||||||
|
projectTools: z.infer<typeof WorkflowTool>[],
|
||||||
|
workflow: z.infer<typeof Workflow>,
|
||||||
|
promptConfig: Record<string, z.infer<typeof WorkflowPrompt>>,
|
||||||
|
): { agent: Agent, entities: z.infer<typeof ConnectedEntity>[] } {
|
||||||
|
const agentLogger = logger.child(`createAgent: ${config.name}`);
|
||||||
|
|
||||||
|
// Combine instructions and examples
|
||||||
|
let instructions = `${RECOMMENDED_PROMPT_PREFIX}
|
||||||
|
|
||||||
|
## Your Name
|
||||||
|
${config.name}
|
||||||
|
|
||||||
|
## Description
|
||||||
|
${config.description}
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
${config.instructions}
|
||||||
|
|
||||||
|
${config.examples ? ('# Examples\n' + config.examples) : ''}
|
||||||
|
|
||||||
|
${'-'.repeat(100)}
|
||||||
|
|
||||||
|
${CHILD_TRANSFER_RELATED_INSTRUCTIONS}
|
||||||
|
`;
|
||||||
|
|
||||||
|
let { sanitized, entities } = sanitizeTextWithMentions(instructions, workflow, projectTools);
|
||||||
|
agentLogger.log(`instructions: ${JSON.stringify(sanitized)}`);
|
||||||
|
agentLogger.log(`mentions: ${JSON.stringify(entities)}`);
|
||||||
|
|
||||||
|
// // add prompts to instructions
|
||||||
|
// for (const e of entities) {
|
||||||
|
// if (e.type === 'prompt') {
|
||||||
|
// const prompt = promptConfig[e.name];
|
||||||
|
// if (prompt) {
|
||||||
|
// compiledInstructions = compiledInstructions + '\n\n# ' + prompt.name + '\n' + prompt.prompt;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
const agentTools = entities.filter(e => e.type === 'tool').map(e => tools[e.name]).filter(Boolean) as Tool[];
|
||||||
|
|
||||||
|
// Add RAG tool if needed
|
||||||
|
if (config.ragDataSources?.length) {
|
||||||
|
const ragTool = createRagTool(logger, config, workflow.projectId);
|
||||||
|
agentTools.push(ragTool);
|
||||||
|
|
||||||
|
// update instructions to include RAG instructions
|
||||||
|
sanitized = sanitized + '\n\n' + ('-'.repeat(100)) + '\n\n' + RAG_INSTRUCTIONS(ragTool.name);
|
||||||
|
agentLogger.log(`added rag instructions`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the agent
|
||||||
|
const agent = new Agent({
|
||||||
|
name: config.name,
|
||||||
|
instructions: sanitized,
|
||||||
|
tools: agentTools,
|
||||||
|
model: aisdk(openai(config.model)),
|
||||||
|
// model: config.model,
|
||||||
|
modelSettings: {
|
||||||
|
temperature: 0.0,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
agentLogger.log(`created agent`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
agent,
|
||||||
|
entities,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert messages to agent input items
|
||||||
|
function convertMsgsInput(messages: z.infer<typeof Message>[]): AgentInputItem[] {
|
||||||
|
const msgs: AgentInputItem[] = [];
|
||||||
|
|
||||||
|
for (const msg of messages) {
|
||||||
|
if (msg.role === 'assistant' && msg.content) {
|
||||||
|
msgs.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: [{
|
||||||
|
type: 'output_text',
|
||||||
|
text: `Sender agent: ${msg.agentName}\nContent: ${msg.content}`,
|
||||||
|
}],
|
||||||
|
status: 'completed',
|
||||||
|
});
|
||||||
|
} else if (msg.role === 'user') {
|
||||||
|
msgs.push({
|
||||||
|
role: 'user',
|
||||||
|
content: msg.content,
|
||||||
|
});
|
||||||
|
} else if (msg.role === 'system') {
|
||||||
|
msgs.push({
|
||||||
|
role: 'system',
|
||||||
|
content: msg.content,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return msgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to determine the next agent name based on control settings
|
||||||
|
function getNextAgentName(
|
||||||
|
logger: PrefixLogger,
|
||||||
|
stack: string[],
|
||||||
|
agentConfig: Record<string, z.infer<typeof WorkflowAgent>>,
|
||||||
|
workflow: z.infer<typeof Workflow>,
|
||||||
|
): string {
|
||||||
|
logger = logger.child(`getNextAgentName`);
|
||||||
|
logger.log(`stack: ${stack.join(', ')}`);
|
||||||
|
|
||||||
|
// get the last agent from the stack
|
||||||
|
// if stack is empty, use the start agent
|
||||||
|
const lastAgentName = stack.pop() || workflow.startAgent;
|
||||||
|
|
||||||
|
return lastAgentName;
|
||||||
|
|
||||||
|
// TODO: control-type logic is being ignored for now
|
||||||
|
// if control type is retain, return last agent
|
||||||
|
// const lastAgentName = stack.pop() || workflow.startAgent;
|
||||||
|
// const lastAgentConfig = agentConfig[lastAgentName];
|
||||||
|
// if (!lastAgentConfig) {
|
||||||
|
// logger.log(`last agent ${lastAgentName} not found in agent config, returning start agent: ${workflow.startAgent}`);
|
||||||
|
// return workflow.startAgent;
|
||||||
|
// }
|
||||||
|
// switch (lastAgentConfig.controlType) {
|
||||||
|
// case 'retain':
|
||||||
|
// logger.log(`last agent ${lastAgentName} control type is retain, returning last agent: ${lastAgentName}`);
|
||||||
|
// return lastAgentName;
|
||||||
|
// case 'relinquish_to_parent':
|
||||||
|
// const parentAgentName = stack.pop() || workflow.startAgent;
|
||||||
|
// logger.log(`last agent ${lastAgentName} control type is relinquish_to_parent, returning most recent parent: ${parentAgentName}`);
|
||||||
|
// return parentAgentName;
|
||||||
|
// case 'relinquish_to_start':
|
||||||
|
// logger.log(`last agent ${lastAgentName} control type is relinquish_to_start, returning start agent: ${workflow.startAgent}`);
|
||||||
|
// return workflow.startAgent;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logs an event and then yields it
|
||||||
|
async function* emitEvent(
|
||||||
|
logger: PrefixLogger,
|
||||||
|
event: z.infer<typeof ZOutMessage> | z.infer<typeof ZUsage>,
|
||||||
|
): AsyncIterable<z.infer<typeof ZOutMessage> | z.infer<typeof ZUsage>> {
|
||||||
|
logger.log(`-> emitting event: ${JSON.stringify(event)}`);
|
||||||
|
yield event;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emits an agent -> agent transfer event
|
||||||
|
function createTransferEvents(
|
||||||
|
fromAgent: string,
|
||||||
|
toAgent: string,
|
||||||
|
): [z.infer<typeof AssistantMessageWithToolCalls>, z.infer<typeof ToolMessage>] {
|
||||||
|
const toolCallId = crypto.randomUUID();
|
||||||
|
const m1: z.infer<typeof Message> = {
|
||||||
|
role: 'assistant',
|
||||||
|
content: null,
|
||||||
|
toolCalls: [{
|
||||||
|
id: toolCallId,
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: 'transfer_to_agent',
|
||||||
|
arguments: JSON.stringify({ assistant: toAgent }),
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
agentName: fromAgent,
|
||||||
|
};
|
||||||
|
|
||||||
|
const m2: z.infer<typeof Message> = {
|
||||||
|
role: 'tool',
|
||||||
|
content: JSON.stringify({ assistant: toAgent }),
|
||||||
|
toolCallId: toolCallId,
|
||||||
|
toolName: 'transfer_to_agent',
|
||||||
|
};
|
||||||
|
|
||||||
|
return [m1, m2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracks agent to agent transfer counts
|
||||||
|
class AgentTransferCounter {
|
||||||
|
private calls: Record<string, number> = {};
|
||||||
|
|
||||||
|
increment(fromAgent: string, toAgent: string): void {
|
||||||
|
const key = `${fromAgent}:${toAgent}`;
|
||||||
|
this.calls[key] = (this.calls[key] || 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(fromAgent: string, toAgent: string): number {
|
||||||
|
const key = `${fromAgent}:${toAgent}`;
|
||||||
|
return this.calls[key] || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsageTracker {
|
||||||
|
private usage: {
|
||||||
|
total: number;
|
||||||
|
prompt: number;
|
||||||
|
completion: number;
|
||||||
|
} = { total: 0, prompt: 0, completion: 0 };
|
||||||
|
|
||||||
|
increment(total: number, prompt: number, completion: number): void {
|
||||||
|
this.usage.total += total;
|
||||||
|
this.usage.prompt += prompt;
|
||||||
|
this.usage.completion += completion;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(): { total: number, prompt: number, completion: number } {
|
||||||
|
return this.usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
asEvent(): z.infer<typeof ZUsage> {
|
||||||
|
return {
|
||||||
|
tokens: this.usage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureSystemMessage(logger: PrefixLogger, messages: z.infer<typeof Message>[]) {
|
||||||
|
logger = logger.child(`ensureSystemMessage`);
|
||||||
|
|
||||||
|
// ensure that a system message is set
|
||||||
|
if (messages.length > 0 && messages[0]?.role !== 'system') {
|
||||||
|
messages.unshift({
|
||||||
|
role: 'system',
|
||||||
|
content: 'You are a helpful assistant.',
|
||||||
|
});
|
||||||
|
logger.log(`added system message: ${messages[0]?.content}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that system message isn't blank
|
||||||
|
if (messages.length > 0 && messages[0]?.role === 'system' && !messages[0].content) {
|
||||||
|
messages[0].content = 'You are a helpful assistant.';
|
||||||
|
logger.log(`updated system message: ${messages[0].content}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapConfig(workflow: z.infer<typeof Workflow>, projectTools: z.infer<typeof WorkflowTool>[]): {
|
||||||
|
agentConfig: Record<string, z.infer<typeof WorkflowAgent>>;
|
||||||
|
toolConfig: Record<string, z.infer<typeof WorkflowTool>>;
|
||||||
|
promptConfig: Record<string, z.infer<typeof WorkflowPrompt>>;
|
||||||
|
} {
|
||||||
|
const agentConfig: Record<string, z.infer<typeof WorkflowAgent>> = workflow.agents.reduce((acc, agent) => ({
|
||||||
|
...acc,
|
||||||
|
[agent.name]: agent
|
||||||
|
}), {});
|
||||||
|
const toolConfig: Record<string, z.infer<typeof WorkflowTool>> = [
|
||||||
|
...workflow.tools,
|
||||||
|
...projectTools,
|
||||||
|
].reduce((acc, tool) => ({
|
||||||
|
...acc,
|
||||||
|
[tool.name]: tool
|
||||||
|
}), {});
|
||||||
|
const promptConfig: Record<string, z.infer<typeof WorkflowPrompt>> = workflow.prompts.reduce((acc, prompt) => ({
|
||||||
|
...acc,
|
||||||
|
[prompt.name]: prompt
|
||||||
|
}), {});
|
||||||
|
return { agentConfig, toolConfig, promptConfig };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function* emitGreetingTurn(logger: PrefixLogger, workflow: z.infer<typeof Workflow>): AsyncIterable<z.infer<typeof ZOutMessage> | z.infer<typeof ZUsage>> {
|
||||||
|
// find the greeting prompt
|
||||||
|
const prompt = workflow.prompts.find(p => p.type === 'greeting')?.prompt || 'How can I help you today?';
|
||||||
|
logger.log(`greeting turn: ${prompt}`);
|
||||||
|
|
||||||
|
// emit greeting turn
|
||||||
|
yield* emitEvent(logger, {
|
||||||
|
role: 'assistant',
|
||||||
|
content: prompt,
|
||||||
|
agentName: workflow.startAgent,
|
||||||
|
responseType: 'external',
|
||||||
|
});
|
||||||
|
|
||||||
|
// emit final usage information
|
||||||
|
yield* emitEvent(logger, new UsageTracker().asEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAgentCallStack(messages: z.infer<typeof Message>[]): string[] {
|
||||||
|
const stack: string[] = [];
|
||||||
|
for (const msg of messages) {
|
||||||
|
if (msg.role === 'assistant' && msg.agentName) {
|
||||||
|
// skip duplicate entries
|
||||||
|
if (stack.length > 0 && stack[stack.length - 1] === msg.agentName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// add to stack
|
||||||
|
stack.push(msg.agentName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTools(logger: PrefixLogger, workflow: z.infer<typeof Workflow>, toolConfig: Record<string, z.infer<typeof WorkflowTool>>): Record<string, Tool> {
|
||||||
|
const tools: Record<string, Tool> = {};
|
||||||
|
for (const [toolName, config] of Object.entries(toolConfig)) {
|
||||||
|
if (config.isMcp) {
|
||||||
|
tools[toolName] = createMcpTool(logger, config, workflow.projectId);
|
||||||
|
logger.log(`created mcp tool: ${toolName}`);
|
||||||
|
} else if (config.mockTool) {
|
||||||
|
tools[toolName] = createMockTool(logger, config);
|
||||||
|
logger.log(`created mock tool: ${toolName}`);
|
||||||
|
} else {
|
||||||
|
logger.log(`unsupported tool type: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tools;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAgents(
|
||||||
|
logger: PrefixLogger,
|
||||||
|
workflow: z.infer<typeof Workflow>,
|
||||||
|
agentConfig: Record<string, z.infer<typeof WorkflowAgent>>,
|
||||||
|
tools: Record<string, Tool>,
|
||||||
|
projectTools: z.infer<typeof WorkflowTool>[],
|
||||||
|
promptConfig: Record<string, z.infer<typeof WorkflowPrompt>>,
|
||||||
|
): { agents: Record<string, Agent>, mentions: Record<string, z.infer<typeof ConnectedEntity>[]> } {
|
||||||
|
const agents: Record<string, Agent> = {};
|
||||||
|
const mentions: Record<string, z.infer<typeof ConnectedEntity>[]> = {};
|
||||||
|
|
||||||
|
// create agents
|
||||||
|
for (const [agentName, config] of Object.entries(agentConfig)) {
|
||||||
|
const { agent, entities } = createAgent(
|
||||||
|
logger,
|
||||||
|
config,
|
||||||
|
tools,
|
||||||
|
projectTools,
|
||||||
|
workflow,
|
||||||
|
promptConfig,
|
||||||
|
);
|
||||||
|
agents[agentName] = agent;
|
||||||
|
mentions[agentName] = entities;
|
||||||
|
logger.log(`created agent: ${agentName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set handoffs
|
||||||
|
for (const [agentName, agent] of Object.entries(agents)) {
|
||||||
|
const connectedAgentNames = (mentions[agentName] || []).filter(e => e.type === 'agent').map(e => e.name);
|
||||||
|
agent.handoffs = connectedAgentNames.map(e => agents[e]).filter(Boolean) as Agent[];
|
||||||
|
logger.log(`set handoffs for ${agentName}: ${connectedAgentNames.join(',')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { agents, mentions };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function to stream an agentic response
|
||||||
|
// using OpenAI Agents SDK
|
||||||
|
export async function* streamResponse(
|
||||||
|
workflow: z.infer<typeof Workflow>,
|
||||||
|
projectTools: z.infer<typeof WorkflowTool>[],
|
||||||
|
messages: z.infer<typeof Message>[],
|
||||||
|
): AsyncIterable<z.infer<typeof ZOutMessage> | z.infer<typeof ZUsage>> {
|
||||||
|
// set up logging
|
||||||
|
let logger = new PrefixLogger(`agent-loop`)
|
||||||
|
logger.log('projectId', workflow.projectId);
|
||||||
|
logger.log('workflow', workflow.name);
|
||||||
|
|
||||||
|
// ensure valid system message
|
||||||
|
ensureSystemMessage(logger, messages);
|
||||||
|
|
||||||
|
// if there is only a system message, emit greeting turn and return
|
||||||
|
if (messages.length === 1 && messages[0]?.role === 'system') {
|
||||||
|
yield* emitGreetingTurn(logger, workflow);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create map of agent, tool and prompt configs
|
||||||
|
const { agentConfig, toolConfig, promptConfig } = mapConfig(workflow, projectTools);
|
||||||
|
|
||||||
|
// create agent call stack from input messages
|
||||||
|
const stack = createAgentCallStack(messages);
|
||||||
|
|
||||||
|
// create tools
|
||||||
|
const tools = createTools(logger, workflow, toolConfig);
|
||||||
|
|
||||||
|
// create agents
|
||||||
|
const { agents } = createAgents(logger, workflow, agentConfig, tools, projectTools, promptConfig);
|
||||||
|
|
||||||
|
// track agent to agent calls
|
||||||
|
const transferCounter = new AgentTransferCounter();
|
||||||
|
|
||||||
|
// track usage
|
||||||
|
const usageTracker = new UsageTracker();
|
||||||
|
|
||||||
|
// get next agent name
|
||||||
|
let agentName = getNextAgentName(logger, stack, agentConfig, workflow);
|
||||||
|
|
||||||
|
// set up initial state for loop
|
||||||
|
logger.log('@@ starting agent turn @@');
|
||||||
|
let iter = 0;
|
||||||
|
const turnMsgs: z.infer<typeof Message>[] = [...messages];
|
||||||
|
|
||||||
|
// loop indefinitely
|
||||||
|
turnLoop: while (true) {
|
||||||
|
// increment loop counter
|
||||||
|
iter++;
|
||||||
|
|
||||||
|
// set up logging
|
||||||
|
const loopLogger = logger.child(`iter-${iter}`);
|
||||||
|
|
||||||
|
// log agent info
|
||||||
|
loopLogger.log(`agent name: ${agentName}`);
|
||||||
|
loopLogger.log(`stack: ${stack.join(', ')}`);
|
||||||
|
if (!agents[agentName]) {
|
||||||
|
throw new Error(`agent not found in agent config!`);
|
||||||
|
}
|
||||||
|
const agent: Agent = agents[agentName]!;
|
||||||
|
|
||||||
|
// convert messages to agents sdk compatible input
|
||||||
|
const inputs = convertMsgsInput(turnMsgs);
|
||||||
|
|
||||||
|
// run the agent
|
||||||
|
const result = await run(agent, inputs, {
|
||||||
|
stream: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle streaming events
|
||||||
|
for await (const event of result) {
|
||||||
|
const eventLogger = loopLogger.child(event.type);
|
||||||
|
// eventLogger.log(`----------> event: ${JSON.stringify(event)}`);
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case 'raw_model_stream_event':
|
||||||
|
if (event.data.type === 'response_done') {
|
||||||
|
for (const output of event.data.response.output) {
|
||||||
|
// handle tool call invocation
|
||||||
|
// except for transfer_to_* tool calls
|
||||||
|
if (output.type === 'function_call' && !output.name.startsWith('transfer_to')) {
|
||||||
|
const m: z.infer<typeof Message> = {
|
||||||
|
role: 'assistant',
|
||||||
|
content: null,
|
||||||
|
toolCalls: [{
|
||||||
|
id: output.callId,
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: output.name,
|
||||||
|
arguments: output.arguments,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
agentName: agentName,
|
||||||
|
};
|
||||||
|
|
||||||
|
// add message to turn
|
||||||
|
turnMsgs.push(m);
|
||||||
|
|
||||||
|
// emit event
|
||||||
|
yield* emitEvent(eventLogger, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update usage information
|
||||||
|
usageTracker.increment(
|
||||||
|
event.data.response.usage.totalTokens,
|
||||||
|
event.data.response.usage.inputTokens,
|
||||||
|
event.data.response.usage.outputTokens
|
||||||
|
);
|
||||||
|
eventLogger.log(`updated usage information: ${JSON.stringify(usageTracker.get())}`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'run_item_stream_event':
|
||||||
|
// handle handoff event
|
||||||
|
if (event.name === 'handoff_occurred' && event.item.type === 'handoff_output_item') {
|
||||||
|
// skip if its the same agent
|
||||||
|
if (agentName === event.item.targetAgent.name) {
|
||||||
|
eventLogger.log(`skipping handoff to same agent: ${agentName}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit transfer tool call invocation
|
||||||
|
const [transferStart, transferComplete] = createTransferEvents(agentName, event.item.targetAgent.name);
|
||||||
|
|
||||||
|
// add messages to turn
|
||||||
|
turnMsgs.push(transferStart);
|
||||||
|
turnMsgs.push(transferComplete);
|
||||||
|
|
||||||
|
// emit events
|
||||||
|
yield* emitEvent(eventLogger, transferStart);
|
||||||
|
yield* emitEvent(eventLogger, transferComplete);
|
||||||
|
|
||||||
|
// update transfer counter
|
||||||
|
transferCounter.increment(agentName, event.item.targetAgent.name);
|
||||||
|
|
||||||
|
// add current agent to stack
|
||||||
|
stack.push(agentName);
|
||||||
|
|
||||||
|
// set this as the new agent name
|
||||||
|
agentName = event.item.targetAgent.name;
|
||||||
|
loopLogger.log(`switched to agent: ${agentName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle tool call result
|
||||||
|
if (event.item.type === 'tool_call_output_item' &&
|
||||||
|
event.item.rawItem.type === 'function_call_result' &&
|
||||||
|
event.item.rawItem.status === 'completed' &&
|
||||||
|
event.item.rawItem.output.type === 'text') {
|
||||||
|
const m: z.infer<typeof Message> = {
|
||||||
|
role: 'tool',
|
||||||
|
content: event.item.rawItem.output.text,
|
||||||
|
toolCallId: event.item.rawItem.callId,
|
||||||
|
toolName: event.item.rawItem.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
// add message to turn
|
||||||
|
turnMsgs.push(m);
|
||||||
|
|
||||||
|
// emit event
|
||||||
|
yield* emitEvent(eventLogger, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle model response message output
|
||||||
|
if (event.item.type === 'message_output_item' &&
|
||||||
|
event.item.rawItem.type === 'message' &&
|
||||||
|
event.item.rawItem.status === 'completed') {
|
||||||
|
// check response visibility
|
||||||
|
const isInternal = agentConfig[agentName]?.outputVisibility === 'internal';
|
||||||
|
for (const content of event.item.rawItem.content) {
|
||||||
|
if (content.type === 'output_text') {
|
||||||
|
// create message
|
||||||
|
const msg: z.infer<typeof Message> = {
|
||||||
|
role: 'assistant',
|
||||||
|
content: content.text,
|
||||||
|
agentName: agentName,
|
||||||
|
responseType: isInternal ? 'internal' : 'external',
|
||||||
|
};
|
||||||
|
|
||||||
|
// add message to turn
|
||||||
|
turnMsgs.push(msg);
|
||||||
|
|
||||||
|
// emit event
|
||||||
|
yield* emitEvent(eventLogger, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this is an internal agent, switch to previous agent
|
||||||
|
if (isInternal) {
|
||||||
|
const current = agentName;
|
||||||
|
agentName = getNextAgentName(logger, stack, agentConfig, workflow);
|
||||||
|
|
||||||
|
// emit transfer tool call invocation
|
||||||
|
const [transferStart, transferComplete] = createTransferEvents(current, agentName);
|
||||||
|
|
||||||
|
// add messages to turn
|
||||||
|
turnMsgs.push(transferStart);
|
||||||
|
turnMsgs.push(transferComplete);
|
||||||
|
|
||||||
|
// emit events
|
||||||
|
yield* emitEvent(eventLogger, transferStart);
|
||||||
|
yield* emitEvent(eventLogger, transferComplete);
|
||||||
|
|
||||||
|
// update transfer counter
|
||||||
|
transferCounter.increment(current, agentName);
|
||||||
|
|
||||||
|
// add current agent to stack
|
||||||
|
stack.push(current);
|
||||||
|
|
||||||
|
// set this as the new agent name
|
||||||
|
loopLogger.log(`switched to agent (reason: internal agent put out a message): ${agentName}`);
|
||||||
|
|
||||||
|
// run the turn from the previous agent
|
||||||
|
continue turnLoop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the last message was a text response by a user-facing agent, complete the turn
|
||||||
|
// loopLogger.log(`iter end, turnMsgs: ${JSON.stringify(turnMsgs)}, agentName: ${agentName}`);
|
||||||
|
const lastMessage = turnMsgs[turnMsgs.length - 1];
|
||||||
|
if (agentConfig[agentName]?.outputVisibility === 'user_facing' &&
|
||||||
|
lastMessage?.role === 'assistant' &&
|
||||||
|
lastMessage?.content !== null &&
|
||||||
|
lastMessage?.agentName === agentName
|
||||||
|
) {
|
||||||
|
loopLogger.log(`last message was by a user_facing agent, breaking out of parent loop`);
|
||||||
|
break turnLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit usage information
|
||||||
|
yield* emitEvent(logger, usageTracker.asEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is a sync version of streamResponse
|
||||||
|
export async function getResponse(
|
||||||
|
workflow: z.infer<typeof Workflow>,
|
||||||
|
projectTools: z.infer<typeof WorkflowTool>[],
|
||||||
|
messages: z.infer<typeof Message>[],
|
||||||
|
): Promise<{
|
||||||
|
messages: z.infer<typeof ZOutMessage>[],
|
||||||
|
usage: z.infer<typeof ZUsage>,
|
||||||
|
}> {
|
||||||
|
const out: z.infer<typeof ZOutMessage>[] = [];
|
||||||
|
let usage: z.infer<typeof ZUsage> = {
|
||||||
|
tokens: {
|
||||||
|
total: 0,
|
||||||
|
prompt: 0,
|
||||||
|
completion: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for await (const event of streamResponse(workflow, projectTools, messages)) {
|
||||||
|
if ('role' in event && event.role === 'assistant') {
|
||||||
|
out.push(event);
|
||||||
|
}
|
||||||
|
if ('tokens' in event) {
|
||||||
|
usage = event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { messages: out, usage };
|
||||||
|
}
|
||||||
32
apps/rowboat/app/lib/mcp.ts
Normal file
32
apps/rowboat/app/lib/mcp.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||||
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||||
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||||
|
|
||||||
|
// Helper to get MCP client
|
||||||
|
export async function getMcpClient(serverUrl: string, serverName: string): Promise<Client> {
|
||||||
|
let client: Client | undefined = undefined;
|
||||||
|
const baseUrl = new URL(serverUrl);
|
||||||
|
|
||||||
|
// Try to connect using Streamable HTTP transport
|
||||||
|
try {
|
||||||
|
client = new Client({
|
||||||
|
name: 'streamable-http-client',
|
||||||
|
version: '1.0.0'
|
||||||
|
});
|
||||||
|
const transport = new StreamableHTTPClientTransport(baseUrl);
|
||||||
|
await client.connect(transport);
|
||||||
|
console.log(`[MCP] Connected using Streamable HTTP transport to ${serverName}`);
|
||||||
|
return client;
|
||||||
|
} catch (error) {
|
||||||
|
// If that fails with a 4xx error, try the older SSE transport
|
||||||
|
console.log(`[MCP] Streamable HTTP connection failed, falling back to SSE transport for ${serverName}`);
|
||||||
|
client = new Client({
|
||||||
|
name: 'sse-client',
|
||||||
|
version: '1.0.0'
|
||||||
|
});
|
||||||
|
const sseTransport = new SSEClientTransport(baseUrl);
|
||||||
|
await client.connect(sseTransport);
|
||||||
|
console.log(`[MCP] Connected using SSE transport to ${serverName}`);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,334 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
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, MCPServerMinimal } from "./types";
|
|
||||||
import { mergeProjectTools } from "./project_types";
|
|
||||||
|
|
||||||
export const AgenticAPIChatMessage = z.object({
|
|
||||||
role: z.union([z.literal('user'), z.literal('assistant'), z.literal('tool'), z.literal('system')]),
|
|
||||||
content: z.string().nullable(),
|
|
||||||
tool_calls: z.array(z.object({
|
|
||||||
id: z.string(),
|
|
||||||
function: z.object({
|
|
||||||
name: z.string(),
|
|
||||||
arguments: z.string(),
|
|
||||||
}),
|
|
||||||
type: z.literal('function'),
|
|
||||||
})).nullable(),
|
|
||||||
tool_call_id: z.string().nullable(),
|
|
||||||
tool_name: z.string().nullable(),
|
|
||||||
sender: z.string().nullable(),
|
|
||||||
response_type: z.union([
|
|
||||||
z.literal('internal'),
|
|
||||||
z.literal('external'),
|
|
||||||
]).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AgenticAPIAgent = WorkflowAgent
|
|
||||||
.omit({
|
|
||||||
disabled: true,
|
|
||||||
examples: true,
|
|
||||||
locked: true,
|
|
||||||
toggleAble: true,
|
|
||||||
global: true,
|
|
||||||
})
|
|
||||||
.extend({
|
|
||||||
tools: z.array(z.string()),
|
|
||||||
prompts: z.array(z.string()),
|
|
||||||
connectedAgents: z.array(z.string()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AgenticAPIPrompt = WorkflowPrompt;
|
|
||||||
|
|
||||||
export const AgenticAPITool = WorkflowTool
|
|
||||||
.omit({
|
|
||||||
autoSubmitMockedResponse: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const AgenticAPIChatRequest = z.object({
|
|
||||||
projectId: z.string(),
|
|
||||||
messages: z.array(AgenticAPIChatMessage),
|
|
||||||
state: z.unknown(),
|
|
||||||
agents: z.array(AgenticAPIAgent),
|
|
||||||
tools: z.array(AgenticAPITool),
|
|
||||||
prompts: z.array(WorkflowPrompt),
|
|
||||||
startAgent: z.string(),
|
|
||||||
testProfile: TestProfile.optional(),
|
|
||||||
mcpServers: z.array(MCPServerMinimal),
|
|
||||||
toolWebhookUrl: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AgenticAPIChatResponse = z.object({
|
|
||||||
messages: z.array(AgenticAPIChatMessage),
|
|
||||||
state: z.unknown(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AgenticAPIInitStreamResponse = z.object({
|
|
||||||
streamId: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function convertWorkflowToAgenticAPI(workflow: z.infer<typeof Workflow>, projectTools: z.infer<typeof WorkflowTool>[]): {
|
|
||||||
agents: z.infer<typeof AgenticAPIAgent>[];
|
|
||||||
tools: z.infer<typeof AgenticAPITool>[];
|
|
||||||
prompts: z.infer<typeof AgenticAPIPrompt>[];
|
|
||||||
startAgent: string;
|
|
||||||
} {
|
|
||||||
const mergedTools = mergeProjectTools(workflow.tools, projectTools);
|
|
||||||
|
|
||||||
return {
|
|
||||||
agents: workflow.agents
|
|
||||||
.filter(agent => !agent.disabled)
|
|
||||||
.map(agent => {
|
|
||||||
const compiledInstructions = agent.instructions +
|
|
||||||
(agent.examples ? '\n\n# Examples\n' + agent.examples : '');
|
|
||||||
const { sanitized, entities } = sanitizeTextWithMentions(compiledInstructions, workflow, mergedTools);
|
|
||||||
|
|
||||||
const agenticAgent: z.infer<typeof AgenticAPIAgent> = {
|
|
||||||
name: agent.name,
|
|
||||||
type: agent.type,
|
|
||||||
description: agent.description,
|
|
||||||
instructions: sanitized,
|
|
||||||
model: agent.model,
|
|
||||||
controlType: agent.controlType,
|
|
||||||
ragDataSources: agent.ragDataSources,
|
|
||||||
ragK: agent.ragK,
|
|
||||||
ragReturnType: agent.ragReturnType,
|
|
||||||
outputVisibility: agent.outputVisibility,
|
|
||||||
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),
|
|
||||||
maxCallsPerParentAgent: agent.maxCallsPerParentAgent,
|
|
||||||
};
|
|
||||||
return agenticAgent;
|
|
||||||
}),
|
|
||||||
tools: mergedTools,
|
|
||||||
prompts: workflow.prompts
|
|
||||||
.map(p => {
|
|
||||||
const { sanitized } = sanitizeTextWithMentions(p.prompt, workflow, mergedTools);
|
|
||||||
return {
|
|
||||||
...p,
|
|
||||||
prompt: sanitized,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
startAgent: workflow.startAgent,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertToAgenticAPIChatMessages(messages: z.infer<typeof apiV1.ChatMessage>[]): z.infer<typeof AgenticAPIChatMessage>[] {
|
|
||||||
const converted: z.infer<typeof AgenticAPIChatMessage>[] = [];
|
|
||||||
|
|
||||||
for (const m of messages) {
|
|
||||||
const baseMessage: z.infer<typeof AgenticAPIChatMessage> = {
|
|
||||||
content: null,
|
|
||||||
role: m.role,
|
|
||||||
sender: null,
|
|
||||||
tool_calls: null,
|
|
||||||
tool_call_id: null,
|
|
||||||
tool_name: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (m.role) {
|
|
||||||
case 'system':
|
|
||||||
converted.push({
|
|
||||||
...baseMessage,
|
|
||||||
content: m.content,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'user':
|
|
||||||
converted.push({
|
|
||||||
...baseMessage,
|
|
||||||
content: m.content,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'assistant':
|
|
||||||
if ('tool_calls' in m) {
|
|
||||||
converted.push({
|
|
||||||
...baseMessage,
|
|
||||||
tool_calls: m.tool_calls,
|
|
||||||
sender: m.agenticSender ?? null,
|
|
||||||
response_type: m.agenticResponseType,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
converted.push({
|
|
||||||
...baseMessage,
|
|
||||||
content: m.content,
|
|
||||||
sender: m.agenticSender ?? null,
|
|
||||||
response_type: m.agenticResponseType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'tool':
|
|
||||||
converted.push({
|
|
||||||
...baseMessage,
|
|
||||||
content: m.content,
|
|
||||||
tool_call_id: m.tool_call_id,
|
|
||||||
tool_name: m.tool_name,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return converted;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertFromAgenticAPIChatMessages(messages: z.infer<typeof AgenticAPIChatMessage>[]): z.infer<typeof apiV1.ChatMessage>[] {
|
|
||||||
const converted: z.infer<typeof apiV1.ChatMessage>[] = [];
|
|
||||||
|
|
||||||
for (const m of messages) {
|
|
||||||
const baseMessage = {
|
|
||||||
version: 'v1' as const,
|
|
||||||
chatId: '',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
switch (m.role) {
|
|
||||||
case 'user':
|
|
||||||
converted.push({
|
|
||||||
...baseMessage,
|
|
||||||
role: 'user',
|
|
||||||
content: m.content ?? '',
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'assistant':
|
|
||||||
if (m.tool_calls) {
|
|
||||||
// TODO: handle tool calls
|
|
||||||
converted.push({
|
|
||||||
...baseMessage,
|
|
||||||
role: 'assistant',
|
|
||||||
tool_calls: m.tool_calls,
|
|
||||||
agenticSender: m.sender ?? undefined,
|
|
||||||
agenticResponseType: m.response_type ?? 'internal',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
converted.push({
|
|
||||||
...baseMessage,
|
|
||||||
role: 'assistant',
|
|
||||||
content: m.content ?? '',
|
|
||||||
agenticSender: m.sender ?? undefined,
|
|
||||||
agenticResponseType: m.response_type ?? 'internal',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'tool':
|
|
||||||
converted.push({
|
|
||||||
...baseMessage,
|
|
||||||
role: 'tool',
|
|
||||||
content: m.content ?? '',
|
|
||||||
tool_call_id: m.tool_call_id ?? '',
|
|
||||||
tool_name: m.tool_name ?? '',
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return converted;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertFromApiToAgenticApiMessages(messages: z.infer<typeof ApiMessage>[]): z.infer<typeof AgenticAPIChatMessage>[] {
|
|
||||||
return messages.map(m => {
|
|
||||||
switch (m.role) {
|
|
||||||
case 'system':
|
|
||||||
return {
|
|
||||||
role: 'system',
|
|
||||||
content: m.content,
|
|
||||||
tool_calls: null,
|
|
||||||
tool_call_id: null,
|
|
||||||
tool_name: null,
|
|
||||||
sender: null,
|
|
||||||
};
|
|
||||||
case 'user':
|
|
||||||
return {
|
|
||||||
role: 'user',
|
|
||||||
content: m.content,
|
|
||||||
tool_calls: null,
|
|
||||||
tool_call_id: null,
|
|
||||||
tool_name: null,
|
|
||||||
sender: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
case 'assistant':
|
|
||||||
if ('tool_calls' in m) {
|
|
||||||
return {
|
|
||||||
role: 'assistant',
|
|
||||||
content: m.content ?? null,
|
|
||||||
tool_calls: m.tool_calls,
|
|
||||||
tool_call_id: null,
|
|
||||||
tool_name: null,
|
|
||||||
sender: m.agenticSender ?? null,
|
|
||||||
response_type: m.agenticResponseType ?? 'external',
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
role: 'assistant',
|
|
||||||
content: m.content ?? null,
|
|
||||||
sender: m.agenticSender ?? null,
|
|
||||||
response_type: m.agenticResponseType ?? 'external',
|
|
||||||
tool_call_id: null,
|
|
||||||
tool_calls: null,
|
|
||||||
tool_name: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'tool':
|
|
||||||
return {
|
|
||||||
role: 'tool',
|
|
||||||
content: m.content ?? null,
|
|
||||||
tool_calls: null,
|
|
||||||
tool_call_id: m.tool_call_id ?? null,
|
|
||||||
tool_name: m.tool_name ?? null,
|
|
||||||
sender: null,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
role: "user",
|
|
||||||
content: "foo",
|
|
||||||
tool_calls: null,
|
|
||||||
tool_call_id: null,
|
|
||||||
tool_name: null,
|
|
||||||
sender: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertFromAgenticApiToApiMessages(messages: z.infer<typeof AgenticAPIChatMessage>[]): z.infer<typeof ApiMessage>[] {
|
|
||||||
const converted: z.infer<typeof ApiMessage>[] = [];
|
|
||||||
|
|
||||||
for (const m of messages) {
|
|
||||||
switch (m.role) {
|
|
||||||
case 'user':
|
|
||||||
converted.push({
|
|
||||||
role: 'user',
|
|
||||||
content: m.content ?? '',
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'assistant':
|
|
||||||
if (m.tool_calls) {
|
|
||||||
converted.push({
|
|
||||||
role: 'assistant',
|
|
||||||
tool_calls: m.tool_calls,
|
|
||||||
agenticSender: m.sender ?? undefined,
|
|
||||||
agenticResponseType: m.response_type ?? 'internal',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
converted.push({
|
|
||||||
role: 'assistant',
|
|
||||||
content: m.content ?? '',
|
|
||||||
agenticSender: m.sender ?? undefined,
|
|
||||||
agenticResponseType: m.response_type ?? 'internal',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'tool':
|
|
||||||
converted.push({
|
|
||||||
role: 'tool',
|
|
||||||
content: m.content ?? '',
|
|
||||||
tool_call_id: m.tool_call_id ?? '',
|
|
||||||
tool_name: m.tool_name ?? '',
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return converted;
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Workflow } from "./workflow_types";
|
import { Workflow } from "./workflow_types";
|
||||||
import { apiV1 } from "rowboat-shared"
|
|
||||||
import { AgenticAPIChatMessage } from "./agents_api_types";
|
|
||||||
import { convertToAgenticAPIChatMessages } from "./agents_api_types";
|
|
||||||
import { DataSource } from "./datasource_types";
|
import { DataSource } from "./datasource_types";
|
||||||
|
import { Message } from "./types";
|
||||||
|
|
||||||
// Create a filtered version of DataSource for copilot
|
// Create a filtered version of DataSource for copilot
|
||||||
export const CopilotDataSource = z.object({
|
export const CopilotDataSource = z.object({
|
||||||
|
|
@ -70,7 +68,7 @@ export const CopilotApiMessage = z.object({
|
||||||
export const CopilotChatContext = z.union([
|
export const CopilotChatContext = z.union([
|
||||||
z.object({
|
z.object({
|
||||||
type: z.literal('chat'),
|
type: z.literal('chat'),
|
||||||
messages: z.array(apiV1.ChatMessage),
|
messages: z.array(Message),
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
type: z.literal('agent'),
|
type: z.literal('agent'),
|
||||||
|
|
@ -88,7 +86,7 @@ export const CopilotChatContext = z.union([
|
||||||
export const CopilotApiChatContext = z.union([
|
export const CopilotApiChatContext = z.union([
|
||||||
z.object({
|
z.object({
|
||||||
type: z.literal('chat'),
|
type: z.literal('chat'),
|
||||||
messages: z.array(AgenticAPIChatMessage),
|
messages: z.array(Message),
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
type: z.literal('agent'),
|
type: z.literal('agent'),
|
||||||
|
|
@ -124,7 +122,7 @@ export function convertToCopilotApiChatContext(context: z.infer<typeof CopilotCh
|
||||||
case 'chat':
|
case 'chat':
|
||||||
return {
|
return {
|
||||||
type: 'chat',
|
type: 'chat',
|
||||||
messages: convertToAgenticAPIChatMessages(context.messages),
|
messages: context.messages,
|
||||||
};
|
};
|
||||||
case 'agent':
|
case 'agent':
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,52 @@
|
||||||
import { CoreMessage, ToolCallPart } from "ai";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { apiV1 } from "rowboat-shared";
|
|
||||||
import { WorkflowTool } from "./workflow_types";
|
import { WorkflowTool } from "./workflow_types";
|
||||||
|
|
||||||
|
export const SystemMessage = z.object({
|
||||||
|
role: z.literal("system"),
|
||||||
|
content: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UserMessage = z.object({
|
||||||
|
role: z.literal("user"),
|
||||||
|
content: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AssistantMessage = z.object({
|
||||||
|
role: z.literal("assistant"),
|
||||||
|
content: z.string(),
|
||||||
|
agentName: z.string().nullable(),
|
||||||
|
responseType: z.enum(['internal', 'external']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AssistantMessageWithToolCalls = z.object({
|
||||||
|
role: z.literal("assistant"),
|
||||||
|
content: z.null(),
|
||||||
|
toolCalls: z.array(z.object({
|
||||||
|
id: z.string(),
|
||||||
|
type: z.literal("function"),
|
||||||
|
function: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
arguments: z.string(),
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
agentName: z.string().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ToolMessage = z.object({
|
||||||
|
role: z.literal("tool"),
|
||||||
|
content: z.string(),
|
||||||
|
toolCallId: z.string(),
|
||||||
|
toolName: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Message = z.union([
|
||||||
|
SystemMessage,
|
||||||
|
UserMessage,
|
||||||
|
AssistantMessage,
|
||||||
|
AssistantMessageWithToolCalls,
|
||||||
|
ToolMessage,
|
||||||
|
]);
|
||||||
|
|
||||||
export const McpToolInputSchema = z.object({
|
export const McpToolInputSchema = z.object({
|
||||||
type: z.literal('object'),
|
type: z.literal('object'),
|
||||||
properties: z.record(z.object({
|
properties: z.record(z.object({
|
||||||
|
|
@ -89,7 +133,7 @@ export const PlaygroundChat = z.object({
|
||||||
createdAt: z.string().datetime(),
|
createdAt: z.string().datetime(),
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
messages: z.array(apiV1.ChatMessage),
|
messages: z.array(Message),
|
||||||
simulated: z.boolean().default(false).optional(),
|
simulated: z.boolean().default(false).optional(),
|
||||||
simulationScenario: z.string().optional(),
|
simulationScenario: z.string().optional(),
|
||||||
simulationComplete: z.boolean().default(false).optional(),
|
simulationComplete: z.boolean().default(false).optional(),
|
||||||
|
|
@ -111,90 +155,15 @@ export const ChatClientId = z.object({
|
||||||
|
|
||||||
export type WithStringId<T> = T & { _id: string };
|
export type WithStringId<T> = T & { _id: string };
|
||||||
|
|
||||||
export function convertToCoreMessages(messages: z.infer<typeof apiV1.ChatMessage>[]): CoreMessage[] {
|
|
||||||
// convert to core messages
|
|
||||||
const coreMessages: CoreMessage[] = [];
|
|
||||||
for (const m of messages) {
|
|
||||||
switch (m.role) {
|
|
||||||
case 'system':
|
|
||||||
coreMessages.push({
|
|
||||||
role: 'system',
|
|
||||||
content: m.content,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'user':
|
|
||||||
coreMessages.push({
|
|
||||||
role: 'user',
|
|
||||||
content: m.content,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'assistant':
|
|
||||||
if ('tool_calls' in m) {
|
|
||||||
const toolCallParts: ToolCallPart[] = m.tool_calls.map((toolCall) => ({
|
|
||||||
type: 'tool-call',
|
|
||||||
toolCallId: toolCall.id,
|
|
||||||
toolName: toolCall.function.name,
|
|
||||||
args: JSON.parse(toolCall.function.arguments),
|
|
||||||
}));
|
|
||||||
if (m.content) {
|
|
||||||
coreMessages.push({
|
|
||||||
role: 'assistant',
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: m.content,
|
|
||||||
},
|
|
||||||
...toolCallParts,
|
|
||||||
]
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
coreMessages.push({
|
|
||||||
role: 'assistant',
|
|
||||||
content: toolCallParts,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
coreMessages.push({
|
|
||||||
role: 'assistant',
|
|
||||||
content: m.content,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'tool':
|
|
||||||
coreMessages.push({
|
|
||||||
role: 'tool',
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'tool-result',
|
|
||||||
toolCallId: m.tool_call_id,
|
|
||||||
toolName: m.tool_name,
|
|
||||||
result: JSON.parse(m.content),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return coreMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ApiMessage = z.union([
|
|
||||||
apiV1.SystemMessage,
|
|
||||||
apiV1.UserMessage,
|
|
||||||
apiV1.AssistantMessage,
|
|
||||||
apiV1.AssistantMessageWithToolCalls,
|
|
||||||
apiV1.ToolMessage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const ApiRequest = z.object({
|
export const ApiRequest = z.object({
|
||||||
messages: z.array(ApiMessage),
|
messages: z.array(Message),
|
||||||
state: z.unknown(),
|
state: z.unknown(),
|
||||||
workflowId: z.string().nullable().optional(),
|
workflowId: z.string().nullable().optional(),
|
||||||
testProfileId: z.string().nullable().optional(),
|
testProfileId: z.string().nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ApiResponse = z.object({
|
export const ApiResponse = z.object({
|
||||||
messages: z.array(ApiMessage),
|
messages: z.array(Message),
|
||||||
state: z.unknown(),
|
state: z.unknown(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ import { z } from "zod";
|
||||||
export const WorkflowAgent = z.object({
|
export const WorkflowAgent = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
order: z.number().int().optional(),
|
order: z.number().int().optional(),
|
||||||
type: z.union([
|
type: z.enum([
|
||||||
z.literal('conversation'),
|
'conversation',
|
||||||
z.literal('post_process'),
|
'post_process',
|
||||||
z.literal('escalation'),
|
'escalation',
|
||||||
]),
|
]),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
disabled: z.boolean().default(false).optional(),
|
disabled: z.boolean().default(false).optional(),
|
||||||
|
|
@ -16,18 +16,22 @@ export const WorkflowAgent = z.object({
|
||||||
toggleAble: z.boolean().default(true).describe('Whether this agent can be enabled or disabled').optional(),
|
toggleAble: z.boolean().default(true).describe('Whether this agent can be enabled or disabled').optional(),
|
||||||
global: z.boolean().default(false).describe('Whether this agent is a global agent, in which case it cannot be connected to other agents').optional(),
|
global: z.boolean().default(false).describe('Whether this agent is a global agent, in which case it cannot be connected to other agents').optional(),
|
||||||
ragDataSources: z.array(z.string()).optional(),
|
ragDataSources: z.array(z.string()).optional(),
|
||||||
ragReturnType: z.union([z.literal('chunks'), z.literal('content')]).default('chunks'),
|
ragReturnType: z.enum(['chunks', 'content']).default('chunks'),
|
||||||
ragK: z.number().default(3),
|
ragK: z.number().default(3),
|
||||||
outputVisibility: z.union([z.literal('user_facing'), z.literal('internal')]).default('user_facing').optional(),
|
outputVisibility: z.enum(['user_facing', 'internal']).default('user_facing').optional(),
|
||||||
controlType: z.union([z.literal('retain'), z.literal('relinquish_to_parent'), z.literal('relinquish_to_start')]).default('retain').describe('Whether this agent retains control after a turn, relinquishes to the parent agent, or relinquishes to the start agent'),
|
controlType: z.enum([
|
||||||
|
'retain',
|
||||||
|
'relinquish_to_parent',
|
||||||
|
'relinquish_to_start',
|
||||||
|
]).default('retain').describe('Whether this agent retains control after a turn, relinquishes to the parent agent, or relinquishes to the start agent'),
|
||||||
maxCallsPerParentAgent: z.number().default(3).describe('Maximum number of times this agent can be called by a parent agent in a single turn').optional(),
|
maxCallsPerParentAgent: z.number().default(3).describe('Maximum number of times this agent can be called by a parent agent in a single turn').optional(),
|
||||||
});
|
});
|
||||||
export const WorkflowPrompt = z.object({
|
export const WorkflowPrompt = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
type: z.union([
|
type: z.enum([
|
||||||
z.literal('base_prompt'),
|
'base_prompt',
|
||||||
z.literal('style_prompt'),
|
'style_prompt',
|
||||||
z.literal('greeting'),
|
'greeting',
|
||||||
]),
|
]),
|
||||||
prompt: z.string(),
|
prompt: z.string(),
|
||||||
});
|
});
|
||||||
|
|
@ -39,29 +43,9 @@ export const WorkflowTool = z.object({
|
||||||
mockInstructions: z.string().optional(),
|
mockInstructions: z.string().optional(),
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
type: z.literal('object'),
|
type: z.literal('object'),
|
||||||
properties: z.record(z.object({
|
properties: z.record(z.string(), z.any()),
|
||||||
type: z.string(),
|
required: z.array(z.string()).optional(),
|
||||||
description: z.string(),
|
additionalProperties: z.boolean().optional(),
|
||||||
enum: z.array(z.any()).optional(),
|
|
||||||
default: z.any().optional(),
|
|
||||||
minimum: z.number().optional(),
|
|
||||||
maximum: z.number().optional(),
|
|
||||||
items: z.any().optional(), // For array types
|
|
||||||
format: z.string().optional(),
|
|
||||||
pattern: z.string().optional(),
|
|
||||||
minLength: z.number().optional(),
|
|
||||||
maxLength: z.number().optional(),
|
|
||||||
minItems: z.number().optional(),
|
|
||||||
maxItems: z.number().optional(),
|
|
||||||
uniqueItems: z.boolean().optional(),
|
|
||||||
multipleOf: z.number().optional(),
|
|
||||||
examples: z.array(z.any()).optional(),
|
|
||||||
})),
|
|
||||||
required: z.array(z.string()).default([]),
|
|
||||||
}).default({
|
|
||||||
type: 'object',
|
|
||||||
properties: {},
|
|
||||||
required: [],
|
|
||||||
}),
|
}),
|
||||||
isMcp: z.boolean().default(false).optional(),
|
isMcp: z.boolean().default(false).optional(),
|
||||||
isLibrary: z.boolean().default(false).optional(),
|
isLibrary: z.boolean().default(false).optional(),
|
||||||
|
|
@ -90,7 +74,7 @@ export const WorkflowTemplate = Workflow
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ConnectedEntity = z.object({
|
export const ConnectedEntity = z.object({
|
||||||
type: z.union([z.literal('tool'), z.literal('prompt'), z.literal('agent')]),
|
type: z.enum(['tool', 'prompt', 'agent']),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,23 @@
|
||||||
import { AgenticAPIChatResponse, AgenticAPIChatRequest, AgenticAPIChatMessage, AgenticAPIInitStreamResponse } from "./types/agents_api_types";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { generateObject } from "ai";
|
import { generateObject } from "ai";
|
||||||
import { ApiMessage } from "./types/types";
|
|
||||||
import { openai } from "@ai-sdk/openai";
|
import { openai } from "@ai-sdk/openai";
|
||||||
import { redisClient } from "./redis";
|
import { redisClient } from "./redis";
|
||||||
|
import { Workflow, WorkflowTool } from "./types/workflow_types";
|
||||||
export async function getAgenticApiResponse(
|
import { Message } from "./types/types";
|
||||||
request: z.infer<typeof AgenticAPIChatRequest>,
|
|
||||||
): Promise<{
|
|
||||||
messages: z.infer<typeof AgenticAPIChatMessage>[],
|
|
||||||
state: unknown,
|
|
||||||
rawAPIResponse: unknown,
|
|
||||||
}> {
|
|
||||||
// call agentic api
|
|
||||||
console.log(`sending agentic api request`, JSON.stringify(request));
|
|
||||||
const response = await fetch(process.env.AGENTS_API_URL + '/chat', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${process.env.AGENTS_API_KEY || 'test'}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error('Failed to call agentic api', response);
|
|
||||||
throw new Error(`Failed to call agentic api: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
const responseJson = await response.json();
|
|
||||||
console.log(`received agentic api response`, JSON.stringify(responseJson));
|
|
||||||
const result: z.infer<typeof AgenticAPIChatResponse> = responseJson;
|
|
||||||
return {
|
|
||||||
messages: result.messages,
|
|
||||||
state: result.state,
|
|
||||||
rawAPIResponse: result,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAgenticResponseStreamId(
|
export async function getAgenticResponseStreamId(
|
||||||
request: z.infer<typeof AgenticAPIChatRequest>,
|
workflow: z.infer<typeof Workflow>,
|
||||||
): Promise<z.infer<typeof AgenticAPIInitStreamResponse>> {
|
projectTools: z.infer<typeof WorkflowTool>[],
|
||||||
|
messages: z.infer<typeof Message>[],
|
||||||
|
): Promise<{
|
||||||
|
streamId: string,
|
||||||
|
}> {
|
||||||
// serialize the request
|
// serialize the request
|
||||||
const payload = JSON.stringify(request);
|
const payload = JSON.stringify({
|
||||||
|
workflow,
|
||||||
|
projectTools,
|
||||||
|
messages,
|
||||||
|
});
|
||||||
|
|
||||||
// create a uuid for the stream
|
// create a uuid for the stream
|
||||||
const streamId = crypto.randomUUID();
|
const streamId = crypto.randomUUID();
|
||||||
|
|
@ -80,7 +57,7 @@ export class PrefixLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function mockToolResponse(toolId: string, messages: z.infer<typeof ApiMessage>[], mockInstructions: string): Promise<string> {
|
export async function mockToolResponse(toolId: string, messages: z.infer<typeof Message>[], mockInstructions: string): Promise<string> {
|
||||||
const prompt = `Given below is a chat between a user and a customer support assistant.
|
const prompt = `Given below is a chat between a user and a customer support assistant.
|
||||||
The assistant has requested a tool call with ID {{toolID}}.
|
The assistant has requested a tool call with ID {{toolID}}.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { WithStringId } from "../../../lib/types/types";
|
import { WithStringId } from "../../../lib/types/types";
|
||||||
import { AgenticAPITool } from "../../../lib/types/agents_api_types";
|
|
||||||
import { WorkflowPrompt, WorkflowAgent, Workflow, WorkflowTool } from "../../../lib/types/workflow_types";
|
import { WorkflowPrompt, WorkflowAgent, Workflow, WorkflowTool } from "../../../lib/types/workflow_types";
|
||||||
import { DataSource } from "../../../lib/types/datasource_types";
|
import { DataSource } from "../../../lib/types/datasource_types";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -56,7 +55,7 @@ export function AgentConfig({
|
||||||
agent: z.infer<typeof WorkflowAgent>,
|
agent: z.infer<typeof WorkflowAgent>,
|
||||||
usedAgentNames: Set<string>,
|
usedAgentNames: Set<string>,
|
||||||
agents: z.infer<typeof WorkflowAgent>[],
|
agents: z.infer<typeof WorkflowAgent>[],
|
||||||
tools: z.infer<typeof AgenticAPITool>[],
|
tools: z.infer<typeof WorkflowTool>[],
|
||||||
projectTools: z.infer<typeof WorkflowTool>[],
|
projectTools: z.infer<typeof WorkflowTool>[],
|
||||||
prompts: z.infer<typeof WorkflowPrompt>[],
|
prompts: z.infer<typeof WorkflowPrompt>[],
|
||||||
dataSources: WithStringId<z.infer<typeof DataSource>>[],
|
dataSources: WithStringId<z.infer<typeof DataSource>>[],
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { useState, useCallback, useRef } from "react";
|
import { useState, useCallback, useRef } from "react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { MCPServer, PlaygroundChat } from "@/app/lib/types/types";
|
import { MCPServer, Message, PlaygroundChat } from "@/app/lib/types/types";
|
||||||
import { Workflow, WorkflowTool } from "@/app/lib/types/workflow_types";
|
import { Workflow, WorkflowTool } from "@/app/lib/types/workflow_types";
|
||||||
import { Chat } from "./components/chat";
|
import { Chat } from "./components/chat";
|
||||||
import { Panel } from "@/components/common/panel-common";
|
import { Panel } from "@/components/common/panel-common";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Tooltip } from "@heroui/react";
|
import { Tooltip } from "@heroui/react";
|
||||||
import { apiV1 } from "rowboat-shared";
|
|
||||||
import { TestProfile } from "@/app/lib/types/testing_types";
|
import { TestProfile } from "@/app/lib/types/testing_types";
|
||||||
import { WithStringId } from "@/app/lib/types/types";
|
import { WithStringId } from "@/app/lib/types/types";
|
||||||
import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector";
|
import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector";
|
||||||
|
|
@ -31,7 +30,7 @@ export function App({
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
workflow: z.infer<typeof Workflow>;
|
workflow: z.infer<typeof Workflow>;
|
||||||
messageSubscriber?: (messages: z.infer<typeof apiV1.ChatMessage>[]) => void;
|
messageSubscriber?: (messages: z.infer<typeof Message>[]) => void;
|
||||||
mcpServerUrls: Array<z.infer<typeof MCPServer>>;
|
mcpServerUrls: Array<z.infer<typeof MCPServer>>;
|
||||||
toolWebhookUrl: string;
|
toolWebhookUrl: string;
|
||||||
isInitialState?: boolean;
|
isInitialState?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,10 @@ import { useEffect, useRef, useState, useCallback } from "react";
|
||||||
import { getAssistantResponseStreamId } from "@/app/actions/actions";
|
import { getAssistantResponseStreamId } from "@/app/actions/actions";
|
||||||
import { Messages } from "./messages";
|
import { Messages } from "./messages";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { MCPServer, PlaygroundChat } from "@/app/lib/types/types";
|
import { MCPServer, Message, PlaygroundChat, ToolMessage } from "@/app/lib/types/types";
|
||||||
import { AgenticAPIChatMessage, convertFromAgenticAPIChatMessages, convertToAgenticAPIChatMessages } from "@/app/lib/types/agents_api_types";
|
|
||||||
import { convertWorkflowToAgenticAPI } from "@/app/lib/types/agents_api_types";
|
|
||||||
import { AgenticAPIChatRequest } from "@/app/lib/types/agents_api_types";
|
|
||||||
import { Workflow, WorkflowTool } from "@/app/lib/types/workflow_types";
|
import { Workflow, WorkflowTool } from "@/app/lib/types/workflow_types";
|
||||||
import { ComposeBoxPlayground } from "@/components/common/compose-box-playground";
|
import { ComposeBoxPlayground } from "@/components/common/compose-box-playground";
|
||||||
import { Button } from "@heroui/react";
|
import { Button } from "@heroui/react";
|
||||||
import { apiV1 } from "rowboat-shared";
|
|
||||||
import { TestProfile } from "@/app/lib/types/testing_types";
|
import { TestProfile } from "@/app/lib/types/testing_types";
|
||||||
import { WithStringId } from "@/app/lib/types/types";
|
import { WithStringId } from "@/app/lib/types/types";
|
||||||
import { ProfileContextBox } from "./profile-context-box";
|
import { ProfileContextBox } from "./profile-context-box";
|
||||||
|
|
@ -35,7 +31,7 @@ export function Chat({
|
||||||
chat: z.infer<typeof PlaygroundChat>;
|
chat: z.infer<typeof PlaygroundChat>;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
workflow: z.infer<typeof Workflow>;
|
workflow: z.infer<typeof Workflow>;
|
||||||
messageSubscriber?: (messages: z.infer<typeof apiV1.ChatMessage>[]) => void;
|
messageSubscriber?: (messages: z.infer<typeof Message>[]) => void;
|
||||||
testProfile?: z.infer<typeof TestProfile> | null;
|
testProfile?: z.infer<typeof TestProfile> | null;
|
||||||
onTestProfileChange: (profile: WithStringId<z.infer<typeof TestProfile>> | null) => void;
|
onTestProfileChange: (profile: WithStringId<z.infer<typeof TestProfile>> | null) => void;
|
||||||
systemMessage: string;
|
systemMessage: string;
|
||||||
|
|
@ -46,16 +42,13 @@ export function Chat({
|
||||||
showDebugMessages?: boolean;
|
showDebugMessages?: boolean;
|
||||||
projectTools: z.infer<typeof WorkflowTool>[];
|
projectTools: z.infer<typeof WorkflowTool>[];
|
||||||
}) {
|
}) {
|
||||||
const [messages, setMessages] = useState<z.infer<typeof apiV1.ChatMessage>[]>(chat.messages);
|
const [messages, setMessages] = useState<z.infer<typeof Message>[]>(chat.messages);
|
||||||
const [loadingAssistantResponse, setLoadingAssistantResponse] = useState<boolean>(false);
|
const [loadingAssistantResponse, setLoadingAssistantResponse] = useState<boolean>(false);
|
||||||
const [agenticState, setAgenticState] = useState<unknown>(chat.agenticState || {
|
|
||||||
last_agent_name: workflow.startAgent,
|
|
||||||
});
|
|
||||||
const [fetchResponseError, setFetchResponseError] = useState<string | null>(null);
|
const [fetchResponseError, setFetchResponseError] = useState<string | null>(null);
|
||||||
const [billingError, setBillingError] = useState<string | null>(null);
|
const [billingError, setBillingError] = useState<string | null>(null);
|
||||||
const [lastAgenticRequest, setLastAgenticRequest] = useState<unknown | null>(null);
|
const [lastAgenticRequest, setLastAgenticRequest] = useState<unknown | null>(null);
|
||||||
const [lastAgenticResponse, setLastAgenticResponse] = useState<unknown | null>(null);
|
const [lastAgenticResponse, setLastAgenticResponse] = useState<unknown | null>(null);
|
||||||
const [optimisticMessages, setOptimisticMessages] = useState<z.infer<typeof apiV1.ChatMessage>[]>(chat.messages);
|
const [optimisticMessages, setOptimisticMessages] = useState<z.infer<typeof Message>[]>(chat.messages);
|
||||||
const [isLastInteracted, setIsLastInteracted] = useState(false);
|
const [isLastInteracted, setIsLastInteracted] = useState(false);
|
||||||
|
|
||||||
const getCopyContent = useCallback(() => {
|
const getCopyContent = useCallback(() => {
|
||||||
|
|
@ -80,20 +73,17 @@ export function Chat({
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
// collect published tool call results
|
// collect published tool call results
|
||||||
const toolCallResults: Record<string, z.infer<typeof apiV1.ToolMessage>> = {};
|
const toolCallResults: Record<string, z.infer<typeof ToolMessage>> = {};
|
||||||
optimisticMessages
|
optimisticMessages
|
||||||
.filter((message) => message.role == 'tool')
|
.filter((message) => message.role == 'tool')
|
||||||
.forEach((message) => {
|
.forEach((message) => {
|
||||||
toolCallResults[message.tool_call_id] = message;
|
toolCallResults[message.toolCallId] = message;
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleUserMessage(prompt: string) {
|
function handleUserMessage(prompt: string) {
|
||||||
const updatedMessages: z.infer<typeof apiV1.ChatMessage>[] = [...messages, {
|
const updatedMessages: z.infer<typeof Message>[] = [...messages, {
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: prompt,
|
content: prompt,
|
||||||
version: 'v1',
|
|
||||||
chatId: '',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
}];
|
}];
|
||||||
setMessages(updatedMessages);
|
setMessages(updatedMessages);
|
||||||
setFetchResponseError(null);
|
setFetchResponseError(null);
|
||||||
|
|
@ -103,9 +93,6 @@ export function Chat({
|
||||||
// reset state when workflow changes
|
// reset state when workflow changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
setAgenticState({
|
|
||||||
last_agent_name: workflow.startAgent,
|
|
||||||
});
|
|
||||||
}, [workflow]);
|
}, [workflow]);
|
||||||
|
|
||||||
// publish messages to subscriber
|
// publish messages to subscriber
|
||||||
|
|
@ -119,7 +106,7 @@ export function Chat({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let ignore = false;
|
let ignore = false;
|
||||||
let eventSource: EventSource | null = null;
|
let eventSource: EventSource | null = null;
|
||||||
let msgs: z.infer<typeof apiV1.ChatMessage>[] = [];
|
let msgs: z.infer<typeof Message>[] = [];
|
||||||
|
|
||||||
async function process() {
|
async function process() {
|
||||||
setLoadingAssistantResponse(true);
|
setLoadingAssistantResponse(true);
|
||||||
|
|
@ -129,36 +116,19 @@ export function Chat({
|
||||||
setLastAgenticRequest(null);
|
setLastAgenticRequest(null);
|
||||||
setLastAgenticResponse(null);
|
setLastAgenticResponse(null);
|
||||||
|
|
||||||
const { agents, tools, prompts, startAgent } = convertWorkflowToAgenticAPI(workflow, projectTools);
|
|
||||||
const request: z.infer<typeof AgenticAPIChatRequest> = {
|
|
||||||
projectId,
|
|
||||||
messages: convertToAgenticAPIChatMessages([{
|
|
||||||
role: 'system',
|
|
||||||
content: systemMessage || '',
|
|
||||||
version: 'v1' as const,
|
|
||||||
chatId: '',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
}, ...messages]),
|
|
||||||
state: agenticState,
|
|
||||||
agents,
|
|
||||||
tools,
|
|
||||||
prompts,
|
|
||||||
startAgent,
|
|
||||||
mcpServers: mcpServerUrls.map(server => ({
|
|
||||||
name: server.name,
|
|
||||||
serverUrl: server.serverUrl || '',
|
|
||||||
isReady: server.isReady
|
|
||||||
})),
|
|
||||||
toolWebhookUrl: toolWebhookUrl,
|
|
||||||
testProfile: testProfile ?? undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Store the full request object
|
|
||||||
setLastAgenticRequest(request);
|
|
||||||
|
|
||||||
let streamId: string | null = null;
|
let streamId: string | null = null;
|
||||||
try {
|
try {
|
||||||
const response = await getAssistantResponseStreamId(request);
|
const response = await getAssistantResponseStreamId(
|
||||||
|
workflow,
|
||||||
|
projectTools,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: systemMessage || '',
|
||||||
|
},
|
||||||
|
...messages,
|
||||||
|
],
|
||||||
|
);
|
||||||
if (ignore) {
|
if (ignore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -190,8 +160,7 @@ export function Chat({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
const msg = AgenticAPIChatMessage.parse(data);
|
const parsedMsg = Message.parse(data);
|
||||||
const parsedMsg = convertFromAgenticAPIChatMessages([msg])[0];
|
|
||||||
msgs.push(parsedMsg);
|
msgs.push(parsedMsg);
|
||||||
setOptimisticMessages(prev => [...prev, parsedMsg]);
|
setOptimisticMessages(prev => [...prev, parsedMsg]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -207,7 +176,6 @@ export function Chat({
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = JSON.parse(event.data);
|
const parsed = JSON.parse(event.data);
|
||||||
setAgenticState(parsed.state);
|
|
||||||
|
|
||||||
// Combine state and collected messages in the response
|
// Combine state and collected messages in the response
|
||||||
setLastAgenticResponse({
|
setLastAgenticResponse({
|
||||||
|
|
@ -267,7 +235,6 @@ export function Chat({
|
||||||
}, [
|
}, [
|
||||||
messages,
|
messages,
|
||||||
projectId,
|
projectId,
|
||||||
agenticState,
|
|
||||||
workflow,
|
workflow,
|
||||||
systemMessage,
|
systemMessage,
|
||||||
mcpServerUrls,
|
mcpServerUrls,
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import z from "zod";
|
||||||
import { Workflow } from "@/app/lib/types/workflow_types";
|
import { Workflow } from "@/app/lib/types/workflow_types";
|
||||||
import { WorkflowTool } from "@/app/lib/types/workflow_types";
|
import { WorkflowTool } from "@/app/lib/types/workflow_types";
|
||||||
import MarkdownContent from "@/app/lib/components/markdown-content";
|
import MarkdownContent from "@/app/lib/components/markdown-content";
|
||||||
import { apiV1 } from "rowboat-shared";
|
|
||||||
import { MessageSquareIcon, EllipsisIcon, CircleCheckIcon, ChevronRightIcon, ChevronDownIcon, ChevronUpIcon, XIcon, PlusIcon, CodeIcon, CheckCircleIcon, FileTextIcon } from "lucide-react";
|
import { MessageSquareIcon, EllipsisIcon, CircleCheckIcon, ChevronRightIcon, ChevronDownIcon, ChevronUpIcon, XIcon, PlusIcon, CodeIcon, CheckCircleIcon, FileTextIcon } from "lucide-react";
|
||||||
import { TestProfile } from "@/app/lib/types/testing_types";
|
import { TestProfile } from "@/app/lib/types/testing_types";
|
||||||
import { ProfileContextBox } from "./profile-context-box";
|
import { ProfileContextBox } from "./profile-context-box";
|
||||||
|
import { Message, ToolMessage, AssistantMessageWithToolCalls } from "@/app/lib/types/types";
|
||||||
|
|
||||||
function UserMessage({ content }: { content: string }) {
|
function UserMessage({ content }: { content: string }) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -140,10 +140,10 @@ function ToolCalls({
|
||||||
systemMessage,
|
systemMessage,
|
||||||
delta
|
delta
|
||||||
}: {
|
}: {
|
||||||
toolCalls: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'];
|
toolCalls: z.infer<typeof AssistantMessageWithToolCalls>['toolCalls'];
|
||||||
results: Record<string, z.infer<typeof apiV1.ToolMessage>>;
|
results: Record<string, z.infer<typeof ToolMessage>>;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
messages: z.infer<typeof apiV1.ChatMessage>[];
|
messages: z.infer<typeof Message>[];
|
||||||
sender: string | null | undefined;
|
sender: string | null | undefined;
|
||||||
workflow: z.infer<typeof Workflow>;
|
workflow: z.infer<typeof Workflow>;
|
||||||
testProfile: z.infer<typeof TestProfile> | null;
|
testProfile: z.infer<typeof TestProfile> | null;
|
||||||
|
|
@ -171,8 +171,8 @@ function ToolCall({
|
||||||
workflow,
|
workflow,
|
||||||
delta
|
delta
|
||||||
}: {
|
}: {
|
||||||
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number];
|
toolCall: z.infer<typeof AssistantMessageWithToolCalls>['toolCalls'][number];
|
||||||
result: z.infer<typeof apiV1.ToolMessage> | undefined;
|
result: z.infer<typeof ToolMessage> | undefined;
|
||||||
sender: string | null | undefined;
|
sender: string | null | undefined;
|
||||||
workflow: z.infer<typeof Workflow>;
|
workflow: z.infer<typeof Workflow>;
|
||||||
delta: number;
|
delta: number;
|
||||||
|
|
@ -206,7 +206,7 @@ function TransferToAgentToolCall({
|
||||||
sender,
|
sender,
|
||||||
delta
|
delta
|
||||||
}: {
|
}: {
|
||||||
result: z.infer<typeof apiV1.ToolMessage> | undefined;
|
result: z.infer<typeof ToolMessage> | undefined;
|
||||||
sender: string | null | undefined;
|
sender: string | null | undefined;
|
||||||
delta: number;
|
delta: number;
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -238,8 +238,8 @@ function ClientToolCall({
|
||||||
workflow,
|
workflow,
|
||||||
delta
|
delta
|
||||||
}: {
|
}: {
|
||||||
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number];
|
toolCall: z.infer<typeof AssistantMessageWithToolCalls>['toolCalls'][number];
|
||||||
result: z.infer<typeof apiV1.ToolMessage> | undefined;
|
result: z.infer<typeof ToolMessage> | undefined;
|
||||||
sender: string | null | undefined;
|
sender: string | null | undefined;
|
||||||
workflow: z.infer<typeof Workflow>;
|
workflow: z.infer<typeof Workflow>;
|
||||||
delta: number;
|
delta: number;
|
||||||
|
|
@ -350,8 +350,8 @@ export function Messages({
|
||||||
showDebugMessages = true,
|
showDebugMessages = true,
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
messages: z.infer<typeof apiV1.ChatMessage>[];
|
messages: z.infer<typeof Message>[];
|
||||||
toolCallResults: Record<string, z.infer<typeof apiV1.ToolMessage>>;
|
toolCallResults: Record<string, z.infer<typeof ToolMessage>>;
|
||||||
loadingAssistantResponse: boolean;
|
loadingAssistantResponse: boolean;
|
||||||
workflow: z.infer<typeof Workflow>;
|
workflow: z.infer<typeof Workflow>;
|
||||||
testProfile: z.infer<typeof TestProfile> | null;
|
testProfile: z.infer<typeof TestProfile> | null;
|
||||||
|
|
@ -368,28 +368,28 @@ export function Messages({
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
}, [messages, loadingAssistantResponse]);
|
}, [messages, loadingAssistantResponse]);
|
||||||
|
|
||||||
const renderMessage = (message: z.infer<typeof apiV1.ChatMessage>, index: number) => {
|
const renderMessage = (message: z.infer<typeof Message>, index: number) => {
|
||||||
const isConsecutive = index > 0 && messages[index - 1].role === message.role;
|
|
||||||
|
|
||||||
if (message.role === 'assistant') {
|
if (message.role === 'assistant') {
|
||||||
let latency = new Date(message.createdAt).getTime() - lastUserMessageTimestamp;
|
// TODO: add latency support
|
||||||
if (!userMessageSeen) {
|
// let latency = new Date(message.createdAt).getTime() - lastUserMessageTimestamp;
|
||||||
latency = 0;
|
// if (!userMessageSeen) {
|
||||||
}
|
// latency = 0;
|
||||||
|
// }
|
||||||
|
let latency = 0;
|
||||||
|
|
||||||
// First check for tool calls
|
// First check for tool calls
|
||||||
if ('tool_calls' in message && message.tool_calls) {
|
if ('toolCalls' in message) {
|
||||||
// Skip tool calls if debug mode is off
|
// Skip tool calls if debug mode is off
|
||||||
if (!showDebugMessages) {
|
if (!showDebugMessages) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ToolCalls
|
<ToolCalls
|
||||||
toolCalls={message.tool_calls}
|
toolCalls={message.toolCalls}
|
||||||
results={toolCallResults}
|
results={toolCallResults}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
sender={message.agenticSender ?? ''}
|
sender={message.agentName ?? ''}
|
||||||
workflow={workflow}
|
workflow={workflow}
|
||||||
testProfile={testProfile}
|
testProfile={testProfile}
|
||||||
systemMessage={systemMessage}
|
systemMessage={systemMessage}
|
||||||
|
|
@ -399,7 +399,7 @@ export function Messages({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then check for internal messages
|
// Then check for internal messages
|
||||||
if (message.agenticResponseType === 'internal') {
|
if (message.content && message.responseType === 'internal') {
|
||||||
// Skip internal messages if debug mode is off
|
// Skip internal messages if debug mode is off
|
||||||
if (!showDebugMessages) {
|
if (!showDebugMessages) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -407,7 +407,7 @@ export function Messages({
|
||||||
return (
|
return (
|
||||||
<InternalAssistantMessage
|
<InternalAssistantMessage
|
||||||
content={message.content ?? ''}
|
content={message.content ?? ''}
|
||||||
sender={message.agenticSender ?? ''}
|
sender={message.agentName ?? ''}
|
||||||
latency={latency}
|
latency={latency}
|
||||||
delta={latency}
|
delta={latency}
|
||||||
/>
|
/>
|
||||||
|
|
@ -418,14 +418,15 @@ export function Messages({
|
||||||
return (
|
return (
|
||||||
<AssistantMessage
|
<AssistantMessage
|
||||||
content={message.content ?? ''}
|
content={message.content ?? ''}
|
||||||
sender={message.agenticSender ?? ''}
|
sender={message.agentName ?? ''}
|
||||||
latency={latency}
|
latency={latency}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.role === 'user' && typeof message.content === 'string') {
|
if (message.role === 'user') {
|
||||||
lastUserMessageTimestamp = new Date(message.createdAt).getTime();
|
// TODO: add latency support
|
||||||
|
// lastUserMessageTimestamp = new Date(message.createdAt).getTime();
|
||||||
userMessageSeen = true;
|
userMessageSeen = true;
|
||||||
return <UserMessage content={message.content} />;
|
return <UserMessage content={message.content} />;
|
||||||
}
|
}
|
||||||
|
|
@ -433,12 +434,12 @@ export function Messages({
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAgentTransition = (message: z.infer<typeof apiV1.ChatMessage>) => {
|
const isAgentTransition = (message: z.infer<typeof Message>) => {
|
||||||
return message.role === 'assistant' && 'tool_calls' in message && Array.isArray(message.tool_calls) && message.tool_calls.some(tc => tc.function.name.startsWith('transfer_to_'));
|
return message.role === 'assistant' && 'toolCalls' in message && Array.isArray(message.toolCalls) && message.toolCalls.some(tc => tc.function.name.startsWith('transfer_to_'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAssistantMessage = (message: z.infer<typeof apiV1.ChatMessage>) => {
|
const isAssistantMessage = (message: z.infer<typeof Message>) => {
|
||||||
return message.role === 'assistant' && (!('tool_calls' in message) || !Array.isArray(message.tool_calls) || !message.tool_calls.some(tc => tc.function.name.startsWith('transfer_to_')));
|
return message.role === 'assistant' && (!('toolCalls' in message) || !Array.isArray(message.toolCalls) || !message.toolCalls.some(tc => tc.function.name.startsWith('transfer_to_')));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (showSystemMessage) {
|
if (showSystemMessage) {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { useState } from 'react';
|
||||||
import { Tabs, Tab } from '@/components/ui/tabs';
|
import { Tabs, Tab } from '@/components/ui/tabs';
|
||||||
import { HostedServers } from './HostedServers';
|
import { HostedServers } from './HostedServers';
|
||||||
import { CustomServers } from './CustomServers';
|
import { CustomServers } from './CustomServers';
|
||||||
import { WebhookConfig } from './WebhookConfig';
|
|
||||||
import type { Key } from 'react';
|
import type { Key } from 'react';
|
||||||
|
|
||||||
export function ToolsConfig() {
|
export function ToolsConfig() {
|
||||||
|
|
@ -40,11 +39,6 @@ export function ToolsConfig() {
|
||||||
<CustomServers />
|
<CustomServers />
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab key="webhook" title="Webhook">
|
|
||||||
<div className="mt-4 p-6">
|
|
||||||
<WebhookConfig />
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { AgenticAPITool } from "../../../lib/types/agents_api_types";
|
|
||||||
import { WorkflowPrompt, WorkflowAgent, WorkflowTool } from "../../../lib/types/workflow_types";
|
import { WorkflowPrompt, WorkflowAgent, WorkflowTool } from "../../../lib/types/workflow_types";
|
||||||
import { Dropdown, DropdownItem, DropdownTrigger, DropdownMenu } from "@heroui/react";
|
import { Dropdown, DropdownItem, DropdownTrigger, DropdownMenu } from "@heroui/react";
|
||||||
import { useRef, useEffect, useState } from "react";
|
import { useRef, useEffect, useState } from "react";
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
import React, { useReducer, Reducer, useState, useCallback, useEffect, useRef, createContext, useContext } from "react";
|
import React, { useReducer, Reducer, useState, useCallback, useEffect, useRef, createContext, useContext } from "react";
|
||||||
import { MCPServer, WithStringId } from "../../../lib/types/types";
|
import { MCPServer, Message, WithStringId } from "../../../lib/types/types";
|
||||||
import { Workflow, WorkflowTool, WorkflowPrompt, WorkflowAgent } from "../../../lib/types/workflow_types";
|
import { Workflow, WorkflowTool, WorkflowPrompt, WorkflowAgent } from "../../../lib/types/workflow_types";
|
||||||
import { DataSource } from "../../../lib/types/datasource_types";
|
import { DataSource } from "../../../lib/types/datasource_types";
|
||||||
import { produce, applyPatches, enablePatches, produceWithPatches, Patch } from 'immer';
|
import { produce, applyPatches, enablePatches, produceWithPatches, Patch } from 'immer';
|
||||||
|
|
@ -20,7 +20,6 @@ import {
|
||||||
ResizablePanelGroup,
|
ResizablePanelGroup,
|
||||||
} from "@/components/ui/resizable"
|
} from "@/components/ui/resizable"
|
||||||
import { Copilot } from "../copilot/app";
|
import { Copilot } from "../copilot/app";
|
||||||
import { apiV1 } from "rowboat-shared";
|
|
||||||
import { publishWorkflow, renameWorkflow, saveWorkflow } from "../../../actions/workflow_actions";
|
import { publishWorkflow, renameWorkflow, saveWorkflow } from "../../../actions/workflow_actions";
|
||||||
import { PublishedBadge } from "./published_badge";
|
import { PublishedBadge } from "./published_badge";
|
||||||
import { BackIcon, HamburgerIcon, WorkflowIcon } from "../../../lib/components/icons";
|
import { BackIcon, HamburgerIcon, WorkflowIcon } from "../../../lib/components/icons";
|
||||||
|
|
@ -597,8 +596,8 @@ export function WorkflowEditor({
|
||||||
lastUpdatedAt: workflow.lastUpdatedAt,
|
lastUpdatedAt: workflow.lastUpdatedAt,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const [chatMessages, setChatMessages] = useState<z.infer<typeof apiV1.ChatMessage>[]>([]);
|
const [chatMessages, setChatMessages] = useState<z.infer<typeof Message>[]>([]);
|
||||||
const updateChatMessages = useCallback((messages: z.infer<typeof apiV1.ChatMessage>[]) => {
|
const updateChatMessages = useCallback((messages: z.infer<typeof Message>[]) => {
|
||||||
setChatMessages(messages);
|
setChatMessages(messages);
|
||||||
}, []);
|
}, []);
|
||||||
const saveQueue = useRef<z.infer<typeof Workflow>[]>([]);
|
const saveQueue = useRef<z.infer<typeof Workflow>[]>([]);
|
||||||
|
|
|
||||||
175
apps/rowboat/package-lock.json
generated
175
apps/rowboat/package-lock.json
generated
|
|
@ -25,6 +25,8 @@
|
||||||
"@langchain/textsplitters": "^0.1.0",
|
"@langchain/textsplitters": "^0.1.0",
|
||||||
"@mendable/firecrawl-js": "^1.0.3",
|
"@mendable/firecrawl-js": "^1.0.3",
|
||||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
|
"@openai/agents": "^0.0.9",
|
||||||
|
"@openai/agents-extensions": "^0.0.9",
|
||||||
"@primer/react": "^37.27.0",
|
"@primer/react": "^37.27.0",
|
||||||
"@qdrant/js-client-rest": "^1.13.0",
|
"@qdrant/js-client-rest": "^1.13.0",
|
||||||
"ai": "^4.3.13",
|
"ai": "^4.3.13",
|
||||||
|
|
@ -4666,6 +4668,155 @@
|
||||||
"integrity": "sha512-iFrvar5SOMtKFOSjYvs4z9UlLqDdJbMx0mgISLcPedv+g0ac5sgeETLGtipHCVIae6HJPclNEH5aCyD1RZaEHw==",
|
"integrity": "sha512-iFrvar5SOMtKFOSjYvs4z9UlLqDdJbMx0mgISLcPedv+g0ac5sgeETLGtipHCVIae6HJPclNEH5aCyD1RZaEHw==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/@openai/agents": {
|
||||||
|
"version": "0.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.0.9.tgz",
|
||||||
|
"integrity": "sha512-JAZLqovH4MLGflwm7BZKjqW7ejhfGAoS7eyXkgfXh4oh/DtWbMr5hmK/Ha0jeqb1+xKY5ULbmikKDTPmHflW7g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@openai/agents-core": "0.0.9",
|
||||||
|
"@openai/agents-openai": "0.0.9",
|
||||||
|
"@openai/agents-realtime": "0.0.9",
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"openai": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/agents-core": {
|
||||||
|
"version": "0.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/agents-core/-/agents-core-0.0.9.tgz",
|
||||||
|
"integrity": "sha512-n7vftCMIBNNbhHSs6SAr0mU99YDD8CH2wRoGZ016nqgl1X9SZsfdQyZvpMMypWrGQ+bqke+jUtXCVnOhKXISFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@openai/zod": "npm:zod@^3.25.40",
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"openai": "^5.0.1"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "^1.12.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.40"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"zod": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/agents-core/node_modules/openai": {
|
||||||
|
"version": "5.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/openai/-/openai-5.7.0.tgz",
|
||||||
|
"integrity": "sha512-zXWawZl6J/P5Wz57/nKzVT3kJQZvogfuyuNVCdEp4/XU2UNrjL7SsuNpWAyLZbo6HVymwmnfno9toVzBhelygA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"openai": "bin/cli"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ws": "^8.18.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"ws": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"zod": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/agents-extensions": {
|
||||||
|
"version": "0.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/agents-extensions/-/agents-extensions-0.0.9.tgz",
|
||||||
|
"integrity": "sha512-r8gSAd9gZOSlY/c79dWToi5e1BnBrCCIY/gZ+j84rWl+hRumhjzMQcXenzbw6y3ng45UkmIubIUK3VBO2SSIPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "^1.1.3",
|
||||||
|
"@openai/zod": "npm:zod@^3.25.40",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
|
"debug": "^4.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@openai/agents": "0.0.9",
|
||||||
|
"ws": "^8.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/agents-openai": {
|
||||||
|
"version": "0.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/agents-openai/-/agents-openai-0.0.9.tgz",
|
||||||
|
"integrity": "sha512-JfTuFaswJUmzVVEEseH+uQHLeHv3ED+X8E0pNE868FwKe1+vd9elzD9uCqRolMAtkBfk8AemHzkZlYl2nuG1sg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@openai/agents-core": "0.0.9",
|
||||||
|
"@openai/zod": "npm:zod@^3.25.40",
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"openai": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/agents-openai/node_modules/openai": {
|
||||||
|
"version": "5.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/openai/-/openai-5.7.0.tgz",
|
||||||
|
"integrity": "sha512-zXWawZl6J/P5Wz57/nKzVT3kJQZvogfuyuNVCdEp4/XU2UNrjL7SsuNpWAyLZbo6HVymwmnfno9toVzBhelygA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"openai": "bin/cli"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ws": "^8.18.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"ws": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"zod": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/agents-realtime": {
|
||||||
|
"version": "0.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/agents-realtime/-/agents-realtime-0.0.9.tgz",
|
||||||
|
"integrity": "sha512-WpAoYG3zOq1U7ljyERxChMXOgnzaRaHqbU4gPMQUmEBD48MHpA0uro6VZTk83Vs6J8JfuS+fGxF11WiR2UlTCg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@openai/agents-core": "0.0.9",
|
||||||
|
"@openai/zod": "npm:zod@^3.25.40",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"ws": "^8.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/agents/node_modules/openai": {
|
||||||
|
"version": "5.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/openai/-/openai-5.7.0.tgz",
|
||||||
|
"integrity": "sha512-zXWawZl6J/P5Wz57/nKzVT3kJQZvogfuyuNVCdEp4/XU2UNrjL7SsuNpWAyLZbo6HVymwmnfno9toVzBhelygA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"openai": "bin/cli"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ws": "^8.18.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"ws": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"zod": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/zod": {
|
||||||
|
"name": "zod",
|
||||||
|
"version": "3.25.67",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
|
||||||
|
"integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@opentelemetry/api": {
|
"node_modules/@opentelemetry/api": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||||
|
|
@ -7560,6 +7711,15 @@
|
||||||
"@types/webidl-conversions": "*"
|
"@types/webidl-conversions": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||||
|
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.35.0",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
|
||||||
|
|
@ -16455,10 +16615,10 @@
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.18.0",
|
"version": "8.18.2",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
|
||||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
|
||||||
"peer": true,
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -16503,9 +16663,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.24.2",
|
"version": "3.25.67",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
|
||||||
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
|
"integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@
|
||||||
"@langchain/textsplitters": "^0.1.0",
|
"@langchain/textsplitters": "^0.1.0",
|
||||||
"@mendable/firecrawl-js": "^1.0.3",
|
"@mendable/firecrawl-js": "^1.0.3",
|
||||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
|
"@openai/agents": "^0.0.9",
|
||||||
|
"@openai/agents-extensions": "^0.0.9",
|
||||||
"@primer/react": "^37.27.0",
|
"@primer/react": "^37.27.0",
|
||||||
"@qdrant/js-client-rest": "^1.13.0",
|
"@qdrant/js-client-rest": "^1.13.0",
|
||||||
"ai": "^4.3.13",
|
"ai": "^4.3.13",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue