/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import fs from "node:fs";
import path from "node:path";
import { logger } from "../logger.js";
import { getConfigDir } from "../config/index.js";
import { GoogleCalendarClient } from "../calendar/google-calendar.js";
import { GmailEmailClient } from "../email/gmail-email.js";
import { queryStockData } from "../tools/stock-data.js";
import { executeMemoryCommand } from "../tools/memory.js";
import { resolveSerpApiKey } from "../serpapi-credentials.js";
export const TOOLS = [
    {
        type: "function",
        name: "manage_calendar",
        description: "Manage Google Calendar with one consolidated tool: list, create, update, delete events, and find free time. Confirm with the owner before creating, updating, or deleting events.",
        parameters: {
            type: "object",
            properties: {
                action: { type: "string", enum: ["list_events", "create_event", "update_event", "delete_event", "find_free_time"], description: "Calendar action to perform." },
                event_id: { type: "string", description: "Event ID for update_event or delete_event." },
                summary: { type: "string", description: "Event title/summary for create_event or update_event." },
                description: { type: "string", description: "Event description." },
                location: { type: "string", description: "Event location." },
                start: { type: "string", description: "Start datetime in ISO 8601 format with timezone offset." },
                end: { type: "string", description: "End datetime in ISO 8601 format with timezone offset." },
                recurrence: { type: "string", description: "RRULE recurrence string for create_event." },
                attendees: { type: "array", items: { type: "string" }, description: "Attendee email addresses." },
                max_results: { type: "number", description: "Maximum events/free slots to return, clamped to 1-10." },
            },
            required: ["action"],
        },
    },
    {
        type: "function",
        name: "web_search",
        description: "General Google web search. Use for general questions, weather, sports scores, places, facts, definitions, people, or anything you do not have a more specific category for. Returns answer_box / knowledge_graph / top organic results.",
        parameters: {
            type: "object",
            properties: {
                query: { type: "string", description: "The search query." },
                num: { type: "number", description: "Number of organic results to return, clamped to 1-5. Defaults to 3." },
            },
            required: ["query"],
        },
    },
    {
        type: "function",
        name: "news_search",
        description: "Google News search. Use for recent news, current events, and time-sensitive headlines. Returns top news items with title, source, date, and snippet.",
        parameters: {
            type: "object",
            properties: {
                query: { type: "string", description: "The news search query." },
                num: { type: "number", description: "Number of news results, clamped to 1-5. Defaults to 3." },
            },
            required: ["query"],
        },
    },
    {
        type: "function",
        name: "hotel_search",
        description: "Google Hotels search via SerpAPI. Use for hotel availability, prices, and ratings on specific dates. Requires destination plus check-in and check-out dates.",
        parameters: {
            type: "object",
            properties: {
                query: { type: "string", description: "Destination, e.g. 'Tokyo', 'Hyatt Regency San Francisco', 'hotels near Eiffel Tower'." },
                check_in_date: { type: "string", description: "Check-in date in YYYY-MM-DD format." },
                check_out_date: { type: "string", description: "Check-out date in YYYY-MM-DD format." },
                adults: { type: "number", description: "Number of adults, clamped to 1-10. Defaults to 2." },
                currency: { type: "string", description: "ISO 4217 currency code, e.g. USD, CNY, JPY. Defaults to USD." },
                num: { type: "number", description: "Number of hotel results to return, clamped to 1-5. Defaults to 5." },
            },
            required: ["query", "check_in_date", "check_out_date"],
        },
    },
    {
        type: "function",
        name: "flight_search",
        description: "Google Flights search via SerpAPI. Use for flight availability and prices between two airports. Use IATA codes for departure_id and arrival_id (e.g. SFO, JFK, PEK). Return type defaults to one-way.",
        parameters: {
            type: "object",
            properties: {
                departure_id: { type: "string", description: "Departure airport IATA code, e.g. SFO, JFK, PEK." },
                arrival_id: { type: "string", description: "Arrival airport IATA code, e.g. NRT, LAX, SHA." },
                outbound_date: { type: "string", description: "Outbound date in YYYY-MM-DD format." },
                return_date: { type: "string", description: "Return date in YYYY-MM-DD format. Required for round-trip." },
                trip_type: { type: "string", enum: ["one_way", "round_trip"], description: "Trip type. Defaults to one_way." },
                adults: { type: "number", description: "Number of adults, clamped to 1-9. Defaults to 1." },
                currency: { type: "string", description: "ISO 4217 currency code. Defaults to USD." },
                num: { type: "number", description: "Number of flight options to return, clamped to 1-5. Defaults to 5." },
            },
            required: ["departure_id", "arrival_id", "outbound_date"],
        },
    },
    {
        type: "function",
        name: "finance_search",
        description: "Google Finance lookup via SerpAPI. Use for indices, currencies, crypto, or when get_stock_quote does not cover the market. The query must be a SerpAPI Finance ticker, e.g. 'AAPL:NASDAQ', '600519:SHA', '.INX:INDEXSP', 'BTC-USD'.",
        parameters: {
            type: "object",
            properties: {
                query: { type: "string", description: "SerpAPI Finance ticker. Format <symbol>:<exchange>, e.g. AAPL:NASDAQ, MSFT:NASDAQ, 600519:SHA, .INX:INDEXSP." },
            },
            required: ["query"],
        },
    },
    {
        type: "function",
        name: "local_search",
        description: "Search nearby points of interest on Google (restaurants, banks, ATMs, cafes, hospitals, gas stations, hotels nearby, etc.). Returns the local 3-pack-style results with name, type, address, rating, reviews, phone, and hours. Always ask the owner for a location if you do not already know it.",
        parameters: {
            type: "object",
            properties: {
                query: { type: "string", description: "What to look for, e.g. 'ramen', 'ATM', 'pediatrician', 'gas station'." },
                location: { type: "string", description: "Free-text location for the search, e.g. 'Austin, Texas, United States' or 'Shibuya, Tokyo'. Required." },
                num: { type: "number", description: "Number of local results to return, clamped to 1-5. Defaults to 3." },
            },
            required: ["query", "location"],
        },
    },
    {
        type: "function",
        name: "manage_email",
        description: "Minimal Gmail tool for voice: list recent inbox emails (titles + snippets only) or send a plain-text email. Always confirm with the owner before sending.",
        parameters: {
            type: "object",
            properties: {
                action: { type: "string", enum: ["list", "send"], description: "Email action: list recent inbox messages, or send a new plain-text email." },
                unread_only: { type: "boolean", description: "For list: only include unread emails. Defaults to false." },
                num: { type: "number", description: "For list: number of recent messages to return, clamped to 1-10. Defaults to 5." },
                to: { type: "string", description: "For send: recipient email address." },
                subject: { type: "string", description: "For send: email subject." },
                body: { type: "string", description: "For send: plain-text email body." },
            },
            required: ["action"],
        },
    },
    {
        type: "function",
        name: "memory",
        description: "Persistent memory storage under /memories. Use to view, create, edit, rename, or delete memory files. Confirm with the owner before creating, editing, renaming, or deleting memories.",
        parameters: {
            type: "object",
            properties: {
                command: { type: "string", enum: ["view", "create", "str_replace", "insert", "delete", "rename"], description: "Memory command to execute." },
                path: { type: "string", description: "Path starting with /memories, e.g. /memories/notes.txt." },
                file_text: { type: "string", description: "File content for create." },
                old_str: { type: "string", description: "Text to find for str_replace." },
                new_str: { type: "string", description: "Replacement text for str_replace." },
                insert_line: { type: "number", description: "0-indexed line number for insert." },
                insert_text: { type: "string", description: "Text to insert." },
                view_range: { type: "array", items: { type: "number" }, minItems: 2, maxItems: 2, description: "Line range [start, end], -1 for end of file." },
                old_path: { type: "string", description: "Source path for rename." },
                new_path: { type: "string", description: "Destination path for rename." },
            },
            required: ["command"],
        },
    },
    {
        type: "function",
        name: "get_stock_quote",
        description: "Get a real-time stock quote for one or more tickers across A-shares, HK, and US markets. Returns current price, change, and percent change only — no history or fundamentals. Use finance_search if you need richer Google Finance data (e.g. crypto, indices, FX).",
        parameters: {
            type: "object",
            properties: {
                symbols: { type: "string", description: "One ticker or a comma-separated list. Examples: 'AAPL', '600519', '0700.HK', 'AAPL,TSLA'." },
            },
            required: ["symbols"],
        },
    },
    {
        type: "function",
        name: "send_telegram_message",
        description: "Send a Telegram message to the owner. Use when the owner asks to send a text, note, or reminder to themselves or to notify them about something.",
        parameters: { type: "object", properties: { message: { type: "string" } }, required: ["message"] },
    },
    {
        type: "function",
        name: "set_reminder",
        description: "Set a quick reminder by creating a calendar event at the specified timezone-aware ISO 8601 time.",
        parameters: { type: "object", properties: { reminder: { type: "string" }, time: { type: "string", description: "Absolute reminder time in ISO 8601 format with timezone offset." } }, required: ["reminder", "time"] },
    },
];
let _calendarClient = null;
function getCalendarClient(config) {
    if (!_calendarClient)
        _calendarClient = new GoogleCalendarClient(config);
    return _calendarClient;
}
let _emailClient = null;
function getEmailClient(config) {
    if (!_emailClient)
        _emailClient = new GmailEmailClient(config);
    return _emailClient;
}
function clampInt(value, min, max, fallback) {
    const parsed = typeof value === "number" ? value : Number(value);
    if (!Number.isFinite(parsed))
        return fallback;
    return Math.min(max, Math.max(min, Math.trunc(parsed)));
}
function requireIsoDateTime(value, field) {
    if (!value || !/^\d{4}-\d{2}-\d{2}T/.test(value) || !/(Z|[+-]\d{2}:\d{2})$/.test(value)) {
        throw new Error(`${field} must be an ISO 8601 datetime with timezone offset`);
    }
    const parsed = new Date(value);
    if (Number.isNaN(parsed.getTime())) {
        throw new Error(`${field} is not a valid datetime`);
    }
    return parsed;
}
const SENSITIVE_FIELDS = new Set(["body", "html_body", "message", "to", "cc", "bcc", "subject", "snippet", "file_text", "old_str", "new_str", "insert_text"]);
const SENSITIVE_TOOLS = new Set(["manage_email", "send_telegram_message", "memory"]);
export function redactRealtimeToolPayload(toolName, value) {
    if (value === null || value === undefined)
        return value;
    if (typeof value !== "object")
        return SENSITIVE_TOOLS.has(toolName) ? "[redacted]" : value;
    if (Array.isArray(value))
        return value.map((item) => redactRealtimeToolPayload(toolName, item));
    const out = {};
    for (const [key, child] of Object.entries(value)) {
        if (SENSITIVE_TOOLS.has(toolName) || SENSITIVE_FIELDS.has(key)) {
            out[key] = typeof child === "string" ? `[redacted:${child.length} chars]` : "[redacted]";
        }
        else {
            out[key] = redactRealtimeToolPayload(toolName, child);
        }
    }
    return out;
}
async function manageCalendar(config, args) {
    const cal = getCalendarClient(config);
    const action = args.action ?? "list_events";
    switch (action) {
        case "list_events": {
            const timeMin = args.start ? requireIsoDateTime(args.start, "start") : new Date();
            const timeMax = args.end ? requireIsoDateTime(args.end, "end") : new Date(timeMin.getTime() + 7 * 86400000);
            const maxResults = clampInt(args.max_results, 1, 10, 5);
            const events = await cal.listUpcomingEvents(timeMin, timeMax, maxResults);
            return events.map((e) => ({
                id: e.id,
                summary: e.summary,
                start: e.start?.dateTime || e.start?.date,
                end: e.end?.dateTime || e.end?.date,
                location: e.location || null,
                description: e.description || null,
            }));
        }
        case "create_event": {
            if (!args.summary || !args.start || !args.end)
                return { error: "summary, start, and end are required for create_event" };
            const event = await cal.createEvent({
                summary: args.summary,
                description: args.description || "",
                location: args.location || undefined,
                start: requireIsoDateTime(args.start, "start"),
                end: requireIsoDateTime(args.end, "end"),
                recurrence: args.recurrence ? [args.recurrence] : undefined,
                attendees: args.attendees,
            });
            return { id: event.id, summary: event.summary, start: event.start?.dateTime, end: event.end?.dateTime, location: event.location, htmlLink: event.htmlLink };
        }
        case "update_event": {
            if (!args.event_id)
                return { error: "event_id is required for update_event" };
            const updates = {};
            if (args.summary)
                updates.summary = args.summary;
            if (args.description)
                updates.description = args.description;
            if (args.location)
                updates.location = args.location;
            if (args.start)
                updates.start = requireIsoDateTime(args.start, "start");
            if (args.end)
                updates.end = requireIsoDateTime(args.end, "end");
            if (args.attendees)
                updates.attendees = args.attendees;
            const event = await cal.updateEvent(args.event_id, updates);
            return { id: event.id, summary: event.summary, start: event.start?.dateTime || event.start?.date, end: event.end?.dateTime || event.end?.date, location: event.location, updated: true };
        }
        case "delete_event": {
            if (!args.event_id)
                return { error: "event_id is required for delete_event" };
            await cal.deleteEvent(args.event_id);
            return { deleted: true, eventId: args.event_id };
        }
        case "find_free_time": {
            const timeMin = args.start ? requireIsoDateTime(args.start, "start") : new Date();
            const timeMax = args.end ? requireIsoDateTime(args.end, "end") : new Date(timeMin.getTime() + 86400000);
            return (await cal.findFreeTime(timeMin, timeMax)).slice(0, clampInt(args.max_results, 1, 10, 5));
        }
        default:
            return { error: `Unknown calendar action: ${action}` };
    }
}
async function manageEmail(config, args) {
    const email = getEmailClient(config);
    const action = args.action ?? "list";
    switch (action) {
        case "list": {
            const maxResults = clampInt(args.num, 1, 10, 5);
            const messages = await email.listEmails({ label: "INBOX", unreadOnly: args.unread_only ?? false, maxResults });
            return messages.map((m) => ({ from: m.from, subject: m.subject, date: m.date, unread: m.labels?.includes("UNREAD") ?? false, snippet: m.snippet }));
        }
        case "send": {
            if (!args.to || !args.subject || !args.body)
                return { error: "to, subject, and body are required for send" };
            const result = await email.sendEmail({ to: args.to, subject: args.subject, body: args.body });
            return { sent: true, messageId: result.id, to: args.to, subject: args.subject };
        }
        default:
            return { error: `Unknown email action: ${action}` };
    }
}
function memory(args) {
    const result = executeMemoryCommand(args);
    const text = result.content.filter((item) => item.type === "text").map((item) => item.text).join("\n");
    return result.isError ? { error: text } : { result: text };
}
/**
 * Pick the SerpAPI HTTP base URL based on the shape of the key. Mirrors the
 * routing in `src/agent/runtime-surface.ts#buildSerpApiMcpUrl` but for the
 * REST `/search` endpoint instead of the MCP endpoint:
 *
 *   - 32 lowercase hex chars: per-agent APIM key, served by Azure APIM
 *     (`https://visionclaw-serpapi.azure-api.net/search`).
 *   - Otherwise: a direct serpapi.com key (legacy / shared / BYO), served by
 *     SerpAPI itself (`https://serpapi.com/search`).
 */
function buildSerpApiSearchBaseUrl(apiKey) {
    if (/^[0-9a-f]{32}$/.test(apiKey)) {
        return "https://visionclaw-serpapi.azure-api.net/search";
    }
    return "https://serpapi.com/search";
}
function stringifyError(value) {
    if (typeof value === "string")
        return value;
    if (value && typeof value === "object") {
        try {
            return JSON.stringify(value);
        }
        catch {
            return "Unknown error";
        }
    }
    return String(value);
}
async function callSerpApi(serpApiKey, params) {
    const baseUrl = buildSerpApiSearchBaseUrl(serpApiKey);
    const usp = new URLSearchParams({ ...params, api_key: serpApiKey });
    const res = await fetch(`${baseUrl}?${usp.toString()}`, { headers: { Accept: "application/json" } });
    if (!res.ok)
        throw new Error(`SerpAPI error: ${res.status} ${await res.text()}`);
    return await res.json();
}
export async function webSearch(serpApiKey, args) {
    const num = clampInt(args.num, 1, 5, 3);
    const data = await callSerpApi(serpApiKey, { engine: "google", q: args.query, num: String(num) });
    const results = [];
    if (data.answer_box) {
        const ab = data.answer_box;
        results.push({ answer: ab.answer || ab.snippet || ab.result, title: ab.title });
    }
    if (data.knowledge_graph) {
        const kg = data.knowledge_graph;
        results.push({ entity: kg.title, description: kg.description });
    }
    const organic = (data.organic_results ?? [])
        .slice(0, num)
        .map((r) => ({ title: r.title, snippet: r.snippet, link: r.link }));
    if (organic.length)
        results.push(...organic);
    if (data.weather_result) {
        const w = data.weather_result;
        results.push({ weather: true, location: w.location, temperature: w.temperature, unit: w.unit, description: w.description, humidity: w.humidity, wind: w.wind });
    }
    if (data.sports_results)
        results.push({ sports: true, ...data.sports_results });
    return results.length ? results : { message: "No results found" };
}
export async function newsSearch(serpApiKey, args) {
    const num = clampInt(args.num, 1, 5, 3);
    const data = await callSerpApi(serpApiKey, { engine: "google_news", q: args.query });
    const items = (data.news_results ?? [])
        .slice(0, num)
        .map((n) => ({
        title: n.title,
        source: n.source?.name ?? n.source,
        date: n.date,
        snippet: n.snippet,
        link: n.link,
    }));
    return items.length ? items : { message: "No news results found" };
}
export async function hotelSearch(serpApiKey, args) {
    const num = clampInt(args.num, 1, 5, 5);
    const adults = clampInt(args.adults, 1, 10, 2);
    const data = await callSerpApi(serpApiKey, {
        engine: "google_hotels",
        q: args.query,
        check_in_date: args.check_in_date,
        check_out_date: args.check_out_date,
        adults: String(adults),
        currency: args.currency || "USD",
    });
    if (data.error)
        return { error: stringifyError(data.error) };
    const properties = (data.properties ?? [])
        .slice(0, num)
        .map((p) => ({
        name: p.name,
        type: p.type,
        rate_per_night: p.rate_per_night?.lowest,
        total_rate: p.total_rate?.lowest,
        currency: p.rate_per_night?.currency,
        overall_rating: p.overall_rating,
        reviews: p.reviews,
        check_in_time: p.check_in_time,
        check_out_time: p.check_out_time,
        link: p.link,
    }));
    return properties.length ? properties : { message: "No hotels found" };
}
export async function flightSearch(serpApiKey, args) {
    const num = clampInt(args.num, 1, 5, 5);
    const adults = clampInt(args.adults, 1, 9, 1);
    const tripType = args.trip_type === "round_trip" ? "1" : "2"; // 1 = round-trip, 2 = one-way in SerpAPI
    if (args.trip_type === "round_trip" && !args.return_date) {
        return { error: "return_date is required when trip_type is round_trip" };
    }
    const params = {
        engine: "google_flights",
        departure_id: args.departure_id,
        arrival_id: args.arrival_id,
        outbound_date: args.outbound_date,
        adults: String(adults),
        currency: args.currency || "USD",
        type: tripType,
    };
    if (args.return_date)
        params.return_date = args.return_date;
    const data = await callSerpApi(serpApiKey, params);
    if (data.error)
        return { error: stringifyError(data.error) };
    const best = data.best_flights ?? [];
    const other = data.other_flights ?? [];
    const all = [...best, ...other].slice(0, num).map((opt) => {
        const legs = (opt.flights ?? []).map((leg) => ({
            airline: leg.airline,
            flight_number: leg.flight_number,
            departure_airport: leg.departure_airport?.id,
            departure_time: leg.departure_airport?.time,
            arrival_airport: leg.arrival_airport?.id,
            arrival_time: leg.arrival_airport?.time,
            duration_min: leg.duration,
        }));
        return {
            price: opt.price,
            total_duration_min: opt.total_duration,
            stops: Math.max(0, legs.length - 1),
            legs,
        };
    });
    return all.length ? all : { message: "No flights found" };
}
export async function localSearch(serpApiKey, args) {
    if (!args.location?.trim())
        return { error: "location is required" };
    const num = clampInt(args.num, 1, 5, 3);
    const data = await callSerpApi(serpApiKey, {
        engine: "google_local",
        q: args.query,
        location: args.location,
    });
    if (data.error)
        return { error: stringifyError(data.error) };
    // `local_results` is sometimes an array, sometimes { places: [...] } — handle both.
    const lrRaw = data.local_results;
    const list = Array.isArray(lrRaw)
        ? lrRaw
        : Array.isArray(lrRaw?.places)
            ? (lrRaw.places)
            : [];
    const items = list.slice(0, num).map((p) => ({
        title: p.title,
        type: p.type,
        address: p.address,
        rating: p.rating,
        reviews: p.reviews,
        price: p.price,
        phone: p.phone,
        hours: p.hours,
        description: p.description,
        gps_coordinates: p.gps_coordinates,
        link: p.links ?? p.website,
    }));
    return items.length ? items : { message: "No local results found" };
}
export async function financeSearch(serpApiKey, args) {
    const data = await callSerpApi(serpApiKey, { engine: "google_finance", q: args.query });
    if (data.error)
        return { message: stringifyError(data.error) };
    const summary = data.summary;
    const news = (data.news_results ?? []).slice(0, 3).map((n) => ({
        title: n.title,
        source: n.source?.name ?? n.source,
        date: n.date,
        link: n.link,
    }));
    return {
        ticker: summary?.title,
        stock: summary?.stock,
        exchange: summary?.exchange,
        price: summary?.price,
        currency: summary?.currency,
        price_movement: summary?.price_movement,
        date: summary?.date,
        market: summary?.market,
        news,
    };
}
async function getStockQuote(args) {
    if (!args.symbols?.trim())
        return { error: "symbols is required, e.g. 'AAPL' or 'AAPL,TSLA'" };
    const result = await queryStockData({ action: "quote", symbols: args.symbols, market: "auto" });
    const text = result.content
        .filter((item) => item.type === "text")
        .map((item) => item.text)
        .join("\n");
    if (result.isError)
        return { error: text };
    let parsed;
    try {
        parsed = JSON.parse(text);
    }
    catch {
        return { result: text };
    }
    // queryStockData("quote") returns { quotes: [...], timestamp }.
    // For voice we only return the essentials per quote.
    const quotes = parsed?.quotes;
    if (!Array.isArray(quotes))
        return parsed;
    return quotes.map((q) => {
        const r = q;
        return {
            symbol: r.symbol,
            name: r.name,
            market: r.market,
            price: r.price,
            change: r.change,
            change_pct: r.change_pct,
            prev_close: r.prev_close,
        };
    });
}
async function sendTelegramMessage(botToken, chatId, args) {
    const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ chat_id: chatId, text: args.message }),
    });
    if (!res.ok)
        throw new Error(`Telegram API error: ${res.status} ${await res.text()}`);
    return { sent: true, message: args.message };
}
async function setReminder(config, args) {
    const start = requireIsoDateTime(args.time, "time");
    const endTime = new Date(start.getTime() + 15 * 60 * 1000).toISOString();
    return await manageCalendar(config, { action: "create_event", summary: `Reminder: ${args.reminder}`, start: args.time, end: endTime, description: `Voice reminder set: ${args.reminder}` });
}
export async function executeTool(config, name, args) {
    logger.system(`[Realtime] Executing tool: ${name}`);
    try {
        switch (name) {
            case "manage_calendar": return await manageCalendar(config, args);
            case "web_search":
            case "news_search":
            case "hotel_search":
            case "flight_search":
            case "finance_search":
            case "local_search": {
                const serpKey = resolveSerpApiKey(config);
                if (!serpKey)
                    return { error: "SerpAPI key not configured" };
                switch (name) {
                    case "web_search": return await webSearch(serpKey, args);
                    case "news_search": return await newsSearch(serpKey, args);
                    case "hotel_search": return await hotelSearch(serpKey, args);
                    case "flight_search": return await flightSearch(serpKey, args);
                    case "finance_search": return await financeSearch(serpKey, args);
                    case "local_search": return await localSearch(serpKey, args);
                    default: return { error: `Unknown search tool: ${String(name)}` };
                }
            }
            case "manage_email": return await manageEmail(config, args);
            case "memory": return memory(args);
            case "get_stock_quote": return await getStockQuote(args);
            case "send_telegram_message": {
                const bt = config.channels.telegram?.botToken;
                let chatId = config.channels.telegram?.chatId;
                if (!chatId) {
                    try {
                        const ownerPath = path.join(getConfigDir(), "owner.json");
                        if (fs.existsSync(ownerPath)) {
                            const ownerChatId = JSON.parse(fs.readFileSync(ownerPath, "utf-8")).telegramChatId;
                            chatId = typeof ownerChatId === "number" || typeof ownerChatId === "string" ? String(ownerChatId) : "";
                        }
                    }
                    catch { /* ignore */ }
                }
                if (!bt || !chatId)
                    return { error: "Telegram not configured" };
                return await sendTelegramMessage(bt, chatId, args);
            }
            case "set_reminder": return await setReminder(config, args);
            default: return { error: `Unknown tool: ${name}` };
        }
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        logger.warn(`[Realtime] Tool error (${name}): ${msg}`);
        return { error: msg };
    }
}
//# sourceMappingURL=tools.js.map