feat: add guarded savings agent tools

This commit is contained in:
Matthias
2026-06-23 21:13:02 +02:00
parent 6e687dc9b5
commit b7dae31fe1
22 changed files with 3510 additions and 73 deletions

View File

@@ -56,6 +56,27 @@ const pendingTanValidator = v.object({
submittedTan: v.optional(v.string()),
});
const accountBalanceStatus = v.union(
v.literal("fresh"),
v.literal("stale"),
v.literal("error"),
);
const accountBalanceValidator = v.object({
_id: v.id("accountBalances"),
_creationTime: v.number(),
userId: v.id("users"),
accountId: v.id("accounts"),
externalId: v.string(),
provider: v.union(v.literal("comdirect"), v.literal("fints")),
balance: v.number(),
currency: v.string(),
asOf: v.optional(v.string()),
fetchedAt: v.number(),
status: accountBalanceStatus,
errorMessage: v.optional(v.string()),
});
export const getBankConfig = internalQuery({
args: { userId: v.id("users") },
returns: v.union(bankConfigValidator, v.null()),
@@ -89,6 +110,33 @@ export const getPendingTan = internalQuery({
},
});
export const listLatestAccountBalances = internalQuery({
args: {
userId: v.id("users"),
includeArchived: v.optional(v.boolean()),
},
returns: v.array(accountBalanceValidator),
handler: async (ctx, args) => {
const accounts = await ctx.db
.query("accounts")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.collect();
const visibleAccountIds = new Set(
accounts
.filter((account) => args.includeArchived || !account.isArchived)
.map((account) => account._id),
);
const balances = await ctx.db
.query("accountBalances")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.collect();
return balances
.filter((balance) => visibleAccountIds.has(balance.accountId))
.sort((a, b) => b.fetchedAt - a.fetchedAt);
},
});
export const upsertBankConfig = internalMutation({
args: {
userId: v.id("users"),
@@ -286,3 +334,52 @@ export const upsertAccountFromProvider = internalMutation({
});
},
});
export const upsertAccountBalance = internalMutation({
args: {
userId: v.id("users"),
accountId: v.id("accounts"),
externalId: v.string(),
provider: v.union(v.literal("comdirect"), v.literal("fints")),
balance: v.number(),
currency: v.string(),
asOf: v.optional(v.string()),
fetchedAt: v.number(),
status: accountBalanceStatus,
errorMessage: v.optional(v.string()),
},
returns: v.id("accountBalances"),
handler: async (ctx, args) => {
const account = await ctx.db.get(args.accountId);
if (!account || account.userId !== args.userId) {
throw new Error("Konto nicht gefunden");
}
const existing = await ctx.db
.query("accountBalances")
.withIndex("by_user_account", (q) =>
q.eq("userId", args.userId).eq("accountId", args.accountId),
)
.unique();
const fields = {
userId: args.userId,
accountId: args.accountId,
externalId: args.externalId,
provider: args.provider,
balance: args.balance,
currency: args.currency,
asOf: args.asOf,
fetchedAt: args.fetchedAt,
status: args.status,
errorMessage: args.errorMessage,
};
if (existing) {
await ctx.db.patch(existing._id, fields);
return existing._id;
}
return await ctx.db.insert("accountBalances", fields);
},
});