mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-30 20:39:46 +02:00
move rb widget apis to /api/widget/v1
This commit is contained in:
parent
7f571092a2
commit
573188afb0
10 changed files with 106 additions and 83 deletions
|
|
@ -15,7 +15,7 @@ import crypto from 'crypto';
|
||||||
import { SignJWT } from "jose";
|
import { SignJWT } from "jose";
|
||||||
import { Claims, getSession } from "@auth0/nextjs-auth0";
|
import { Claims, getSession } from "@auth0/nextjs-auth0";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { baseWorkflow } from "./lib/utils";
|
import { baseWorkflow, callClientToolWebhook, getAgenticApiResponse } from "./lib/utils";
|
||||||
|
|
||||||
const crawler = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY || '' });
|
const crawler = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY || '' });
|
||||||
|
|
||||||
|
|
@ -466,25 +466,8 @@ export async function getAssistantResponse(
|
||||||
}> {
|
}> {
|
||||||
await projectAuthCheck(projectId);
|
await projectAuthCheck(projectId);
|
||||||
|
|
||||||
// call agentic api
|
const response = await getAgenticApiResponse(request);
|
||||||
const response = await fetch(process.env.AGENTIC_API_URL + '/chat', {
|
return response;
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
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();
|
|
||||||
const result: z.infer<typeof AgenticAPIChatResponse> = responseJson;
|
|
||||||
return {
|
|
||||||
messages: convertFromAgenticAPIChatMessages(result.messages),
|
|
||||||
state: result.state,
|
|
||||||
rawAPIResponse: result,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCopilotResponse(
|
export async function getCopilotResponse(
|
||||||
|
|
@ -916,61 +899,6 @@ export async function executeClientTool(
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
await projectAuthCheck(projectId);
|
await projectAuthCheck(projectId);
|
||||||
|
|
||||||
const project = await projectsCollection.findOne({
|
const result = await callClientToolWebhook(toolCall, projectId);
|
||||||
"_id": projectId,
|
return result;
|
||||||
});
|
|
||||||
if (!project) {
|
|
||||||
throw new Error('Project not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!project.webhookUrl) {
|
|
||||||
throw new Error('Webhook URL not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare request body
|
|
||||||
const content = JSON.stringify({
|
|
||||||
toolCall,
|
|
||||||
} as z.infer<typeof ClientToolCallRequestBody>);
|
|
||||||
const requestId = crypto.randomUUID();
|
|
||||||
const bodyHash = crypto
|
|
||||||
.createHash('sha256')
|
|
||||||
.update(content, 'utf8')
|
|
||||||
.digest('hex');
|
|
||||||
|
|
||||||
// sign request
|
|
||||||
const jwt = await new SignJWT({
|
|
||||||
requestId,
|
|
||||||
projectId,
|
|
||||||
bodyHash,
|
|
||||||
} as z.infer<typeof ClientToolCallJwt>)
|
|
||||||
.setProtectedHeader({
|
|
||||||
alg: 'HS256',
|
|
||||||
typ: 'JWT',
|
|
||||||
})
|
|
||||||
.setIssuer('rowboat')
|
|
||||||
.setAudience(project.webhookUrl)
|
|
||||||
.setSubject(`tool-call-${toolCall.id}`)
|
|
||||||
.setJti(requestId)
|
|
||||||
.setIssuedAt()
|
|
||||||
.setExpirationTime("5 minutes")
|
|
||||||
.sign(new TextEncoder().encode(project.secret));
|
|
||||||
|
|
||||||
// make request
|
|
||||||
const request: z.infer<typeof ClientToolCallRequest> = {
|
|
||||||
requestId,
|
|
||||||
content,
|
|
||||||
};
|
|
||||||
const response = await fetch(project.webhookUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'x-signature-jwt': jwt,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to call webhook: ${response.status}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
const responseBody = await response.json();
|
|
||||||
return responseBody;
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,8 +4,8 @@ import { agentWorkflowsCollection, db, projectsCollection } from "@/app/lib/mong
|
||||||
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 { AgenticAPIChatRequest, convertToAgenticAPIChatMessages, convertToCoreMessages, convertWorkflowToAgenticAPI } from "@/app/lib/types";
|
import { AgenticAPIChatRequest, convertToAgenticAPIChatMessages, convertWorkflowToAgenticAPI } from "@/app/lib/types";
|
||||||
import { executeClientTool, getAssistantResponse } from "@/app/actions";
|
import { callClientToolWebhook, getAgenticApiResponse } from "@/app/lib/utils";
|
||||||
|
|
||||||
const chatsCollection = db.collection<z.infer<typeof apiV1.Chat>>("chats");
|
const chatsCollection = db.collection<z.infer<typeof apiV1.Chat>>("chats");
|
||||||
const chatMessagesCollection = db.collection<z.infer<typeof apiV1.ChatMessage>>("chatMessages");
|
const chatMessagesCollection = db.collection<z.infer<typeof apiV1.ChatMessage>>("chatMessages");
|
||||||
|
|
@ -91,7 +91,7 @@ export async function POST(
|
||||||
startAgent,
|
startAgent,
|
||||||
};
|
};
|
||||||
console.log("turn: sending agentic request", JSON.stringify(request, null, 2));
|
console.log("turn: sending agentic request", JSON.stringify(request, null, 2));
|
||||||
const response = await getAssistantResponse(session.projectId, request);
|
const response = await getAgenticApiResponse(request);
|
||||||
state = response.state;
|
state = response.state;
|
||||||
if (response.messages.length === 0) {
|
if (response.messages.length === 0) {
|
||||||
throw new Error("No messages returned from assistant");
|
throw new Error("No messages returned from assistant");
|
||||||
|
|
@ -111,7 +111,7 @@ export async function POST(
|
||||||
const toolCallResults = await Promise.all(lastMessage.tool_calls.map(async toolCall => {
|
const toolCallResults = await Promise.all(lastMessage.tool_calls.map(async toolCall => {
|
||||||
console.log('executing tool call', toolCall);
|
console.log('executing tool call', toolCall);
|
||||||
try {
|
try {
|
||||||
return await executeClientTool(toolCall, session.projectId);
|
return await callClientToolWebhook(toolCall, session.projectId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error executing tool call ${toolCall.id}:`, error);
|
console.error(`Error executing tool call ${toolCall.id}:`, error);
|
||||||
return { error: "Tool execution failed" };
|
return { error: "Tool execution failed" };
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
import { Workflow } from "@/app/lib/types";
|
import { AgenticAPIChatRequest, AgenticAPIChatResponse, ClientToolCallJwt, ClientToolCallRequest, ClientToolCallRequestBody, convertFromAgenticAPIChatMessages, Workflow } from "@/app/lib/types";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { projectsCollection } from "./mongodb";
|
||||||
|
import { apiV1 } from "rowboat-shared";
|
||||||
|
import { SignJWT } from "jose";
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
export const baseWorkflow: z.infer<typeof Workflow> = {
|
export const baseWorkflow: z.infer<typeof Workflow> = {
|
||||||
projectId: "",
|
projectId: "",
|
||||||
|
|
@ -100,4 +104,95 @@ You are an helpful customer support assistant
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tools: [],
|
tools: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function callClientToolWebhook(
|
||||||
|
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number],
|
||||||
|
projectId: string,
|
||||||
|
): Promise<unknown> {
|
||||||
|
const project = await projectsCollection.findOne({
|
||||||
|
"_id": projectId,
|
||||||
|
});
|
||||||
|
if (!project) {
|
||||||
|
throw new Error('Project not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!project.webhookUrl) {
|
||||||
|
throw new Error('Webhook URL not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare request body
|
||||||
|
const content = JSON.stringify({
|
||||||
|
toolCall,
|
||||||
|
} as z.infer<typeof ClientToolCallRequestBody>);
|
||||||
|
const requestId = crypto.randomUUID();
|
||||||
|
const bodyHash = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(content, 'utf8')
|
||||||
|
.digest('hex');
|
||||||
|
|
||||||
|
// sign request
|
||||||
|
const jwt = await new SignJWT({
|
||||||
|
requestId,
|
||||||
|
projectId,
|
||||||
|
bodyHash,
|
||||||
|
} as z.infer<typeof ClientToolCallJwt>)
|
||||||
|
.setProtectedHeader({
|
||||||
|
alg: 'HS256',
|
||||||
|
typ: 'JWT',
|
||||||
|
})
|
||||||
|
.setIssuer('rowboat')
|
||||||
|
.setAudience(project.webhookUrl)
|
||||||
|
.setSubject(`tool-call-${toolCall.id}`)
|
||||||
|
.setJti(requestId)
|
||||||
|
.setIssuedAt()
|
||||||
|
.setExpirationTime("5 minutes")
|
||||||
|
.sign(new TextEncoder().encode(project.secret));
|
||||||
|
|
||||||
|
// make request
|
||||||
|
const request: z.infer<typeof ClientToolCallRequest> = {
|
||||||
|
requestId,
|
||||||
|
content,
|
||||||
|
};
|
||||||
|
const response = await fetch(project.webhookUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-signature-jwt': jwt,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to call webhook: ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const responseBody = await response.json();
|
||||||
|
return responseBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAgenticApiResponse(
|
||||||
|
request: z.infer<typeof AgenticAPIChatRequest>,
|
||||||
|
): Promise<{
|
||||||
|
messages: z.infer<typeof apiV1.ChatMessage>[],
|
||||||
|
state: unknown,
|
||||||
|
rawAPIResponse: unknown,
|
||||||
|
}> {
|
||||||
|
// call agentic api
|
||||||
|
const response = await fetch(process.env.AGENTIC_API_URL + '/chat', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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();
|
||||||
|
const result: z.infer<typeof AgenticAPIChatResponse> = responseJson;
|
||||||
|
return {
|
||||||
|
messages: convertFromAgenticAPIChatMessages(result.messages),
|
||||||
|
state: result.state,
|
||||||
|
rawAPIResponse: result,
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue