105 lines
3.2 KiB
TypeScript
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(),
|
|
});
|