Files
accounted/lib/agent/ask/ledger-tools.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

87 lines
3.3 KiB
TypeScript

import type { SupabaseClient } from '@supabase/supabase-js'
import type { AiToolDef } from '@/lib/ai'
import { agentToolRegistry } from '@/lib/agent/tools/registry'
import type { AgentActorContext } from '@/lib/agent/tools/types'
/**
* The read-only tools the single-call assistant may call to answer questions
* about the ledger (audit Option A: "single-call actions over the existing MCP
* tool functions"). This is what makes the console behave like an MCP client:
* the model asks for a report, we run the real tool, feed the JSON back.
*
* The list is the read slice of general.help's old tool whitelist: the
* analytical reports and the lookups across the working set. Write/staging
* tools (categorize, create_invoice, approve_supplier_invoice, stage_year_end,
* …) and the memory-write tools (remember/forget) are deliberately absent: the
* console has no ApprovalCard surface, so it reads and guides only; write
* actions belong to the page-specific intents where a single entity is in
* focus and the user expects a staged card.
*
* The registry is populated by the mcp-server extension at init
* (ensureInitialized). In a core-only build it is empty, so this returns an
* empty list and the assistant answers from the prompt + snapshot alone: the
* graceful, correct degradation.
*/
export const LEDGER_READ_TOOLS: readonly string[] = [
// Reports (the canonical analytical surface)
'gnubok_get_income_statement',
'gnubok_get_balance_sheet',
'gnubok_get_trial_balance',
'gnubok_get_general_ledger',
'gnubok_get_kpi_report',
'gnubok_get_vat_report',
'gnubok_vat_close_check',
'gnubok_get_ar_ledger',
'gnubok_get_supplier_ledger',
'gnubok_get_reconciliation_status',
'gnubok_get_salary_journal',
'gnubok_year_end_readiness',
// Lookups across the working set
'gnubok_query_journal',
'gnubok_list_uncategorized_transactions',
'gnubok_list_transactions_without_documents',
'gnubok_list_invoices',
'gnubok_list_customers',
'gnubok_list_suppliers',
'gnubok_list_supplier_invoices',
'gnubok_list_accounts',
'gnubok_list_fiscal_periods',
'gnubok_list_employees',
'gnubok_list_inbox_items',
'gnubok_list_unmatched_documents',
'gnubok_list_voucher_gaps',
'gnubok_explain_voucher_gap',
'gnubok_get_counterparty_templates',
]
/**
* Build the read-only tool defs for one company/user, bound to the same actor
* context the streaming runtime uses (`agent_chat` + the conversation id, for
* BFL audit). Each def's execute dispatches the real registered tool.
*/
export function buildLedgerTools(
supabase: SupabaseClient,
companyId: string,
userId: string,
conversationId?: string,
): AiToolDef[] {
const actor: AgentActorContext = {
type: 'agent_chat',
...(conversationId ? { id: conversationId } : {}),
}
return agentToolRegistry
.getMany([...LEDGER_READ_TOOLS])
// Belt-and-suspenders on top of the curated whitelist: never expose a tool
// the registry itself marks writable or destructive, even if it somehow
// ended up on the list.
.filter((t) => t.annotations?.readOnlyHint !== false && t.annotations?.destructiveHint !== true)
.map((t) => ({
name: t.name,
description: t.description,
jsonSchema: t.inputSchema,
execute: (args: Record<string, unknown>) =>
t.execute(args, companyId, userId, supabase, actor),
}))
}