feat(agents): add signal tools to zopu agent
This commit is contained in:
@@ -53,6 +53,19 @@ interface ComposedSignal {
|
||||
readonly sources: readonly SignalSourceView[];
|
||||
}
|
||||
|
||||
/**
|
||||
* An admitted user conversation message presented to the agent as candidate
|
||||
* Signal evidence. The agent selects from these by `messageId`; raw text and
|
||||
* timestamps are copied server-side and never accepted from the agent.
|
||||
*/
|
||||
interface ConversationMessageSourceView {
|
||||
readonly messageId: string;
|
||||
readonly clientRequestId: string;
|
||||
readonly rawText: string;
|
||||
readonly submissionId: string | null;
|
||||
readonly createdAt: number;
|
||||
}
|
||||
|
||||
const toSignalView = (doc: Doc<"signals">): SignalView => ({
|
||||
_creationTime: doc._creationTime,
|
||||
_id: doc._id,
|
||||
@@ -78,6 +91,16 @@ const toSourceView = (doc: Doc<"signalSources">): SignalSourceView => ({
|
||||
submissionId: doc.submissionId ?? null,
|
||||
});
|
||||
|
||||
const toConversationMessageSourceView = (
|
||||
doc: Doc<"conversationMessages">
|
||||
): ConversationMessageSourceView => ({
|
||||
clientRequestId: doc.clientRequestId,
|
||||
createdAt: doc.createdAt,
|
||||
messageId: doc.messageId,
|
||||
rawText: doc.rawText,
|
||||
submissionId: doc.submissionId ?? null,
|
||||
});
|
||||
|
||||
/**
|
||||
* Build a deterministic, collision-safe key from the organization, the
|
||||
* conversation, and the ordered message selection. The key is the JSON
|
||||
@@ -348,6 +371,64 @@ export const list = query({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* List the bounded set of admitted user messages in the organization's
|
||||
* conversation that have not yet been consumed as a Signal source, newest
|
||||
* first. These are the candidate messages an agent should select from when
|
||||
* composing a new Signal.
|
||||
*
|
||||
* Authorization: the authenticated identity must be a member of the target
|
||||
* organization. The conversation id equals the organization id; the caller
|
||||
* never supplies tenancy. Cross-organization access is denied.
|
||||
*
|
||||
* Bounded: capped at 100 rows to keep agent evidence selection tractable.
|
||||
*/
|
||||
export const listAdmittedUnused = query({
|
||||
args: {
|
||||
organizationId: v.id("organizations"),
|
||||
},
|
||||
handler: async (
|
||||
ctx,
|
||||
args
|
||||
): Promise<ConversationMessageSourceView[]> => {
|
||||
await requireOrganizationMember(ctx, args.organizationId);
|
||||
const conversationId = args.organizationId;
|
||||
|
||||
const admitted = await ctx.db
|
||||
.query("conversationMessages")
|
||||
.withIndex("by_organization_and_message", (q) =>
|
||||
q
|
||||
.eq("organizationId", args.organizationId)
|
||||
.eq("conversationId", conversationId)
|
||||
)
|
||||
.order("desc")
|
||||
.take(100);
|
||||
|
||||
const candidates = admitted.filter(
|
||||
(message) =>
|
||||
message.role === "user" && message.status === "admitted"
|
||||
);
|
||||
|
||||
const unused: ConversationMessageSourceView[] = [];
|
||||
for (const message of candidates) {
|
||||
const consumed = await ctx.db
|
||||
.query("signalSources")
|
||||
.withIndex("by_organization_and_message", (q) =>
|
||||
q
|
||||
.eq("organizationId", args.organizationId)
|
||||
.eq("conversationId", conversationId)
|
||||
.eq("messageId", message.messageId)
|
||||
)
|
||||
.first();
|
||||
if (!consumed) {
|
||||
unused.push(toConversationMessageSourceView(message));
|
||||
}
|
||||
}
|
||||
|
||||
return unused;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get one Signal by id with its composed sources. Membership in the signal's
|
||||
* organization is required; access to another organization's signal is denied.
|
||||
|
||||
Reference in New Issue
Block a user