Files

105 lines
3.2 KiB
TypeScript

import { ConvexError, v } from "convex/values";
import type { Doc, Id } from "./_generated/dataModel";
import { internalMutation, internalQuery } from "./_generated/server";
const agentStatus = v.union(
v.literal("registered"),
v.literal("active"),
v.literal("idle"),
v.literal("disabled")
);
/**
* The stable conversation id used as the Flue `dispatch` conversation id and
* the `conversationAgents.id` uniqueness key.
* Shape: `conversation:<organizationId>:<projectId>:v1`
*/
export const conversationAgentId = (
organizationId: Id<"organizations">,
projectId: Id<"projects">
): string => `conversation:${organizationId}:${projectId}:v1`;
/**
* Register one project-bound conversation agent per Project. Idempotent by the
* stable `conversation:<org>:<project>:v1` key; retries reuse the existing row.
*/
export const register = internalMutation({
args: {
agentType: v.string(),
organizationId: v.id("organizations"),
projectId: v.id("projects"),
runtimeId: v.optional(v.id("projectRuntimes")),
},
handler: async (ctx, args): Promise<Doc<"conversationAgents">> => {
const id = conversationAgentId(args.organizationId, args.projectId);
const existing = await ctx.db
.query("conversationAgents")
.withIndex("by_projectId", (q) => q.eq("projectId", args.projectId))
.unique();
if (existing) {
return existing;
}
const timestamp = Date.now();
const rowId = await ctx.db.insert("conversationAgents", {
agentType: args.agentType,
createdAt: timestamp,
id,
organizationId: args.organizationId,
projectId: args.projectId,
runtimeId: args.runtimeId,
status: "registered",
updatedAt: timestamp,
});
const saved = await ctx.db.get(rowId);
if (!saved) {
throw new ConvexError(
"Conversation agent could not be read after insert"
);
}
return saved;
},
});
export const markStatus = internalMutation({
args: {
agentRowId: v.id("conversationAgents"),
flueConversationId: v.optional(v.string()),
lastEventId: v.optional(v.id("events")),
status: agentStatus,
},
handler: async (ctx, args): Promise<Doc<"conversationAgents">> => {
const agent = await ctx.db.get(args.agentRowId);
if (!agent) {
throw new ConvexError("Conversation agent not found");
}
const patch: Record<string, unknown> = {
status: args.status,
updatedAt: Date.now(),
};
if (args.flueConversationId !== undefined) {
patch.flueConversationId = args.flueConversationId;
}
if (args.lastEventId !== undefined) {
patch.lastEventId = args.lastEventId;
}
await ctx.db.patch(args.agentRowId, patch);
const updated = await ctx.db.get(args.agentRowId);
if (!updated) {
throw new ConvexError(
"Conversation agent could not be read after update"
);
}
return updated;
},
});
export const getForProject = internalQuery({
args: { projectId: v.id("projects") },
handler: async (ctx, args): Promise<Doc<"conversationAgents"> | null> =>
await ctx.db
.query("conversationAgents")
.withIndex("by_projectId", (q) => q.eq("projectId", args.projectId))
.unique(),
});