import { defineTool } from "@sheason/pi-coding-agent";
import { Type } from "typebox";
import { isRecord } from "../utils.js";
/** Pending response timeouts keyed by message_id. */
const pendingResponseTimeouts = new Map();
function cancelPendingResponseTimeouts(responseIds) {
    for (const rid of responseIds) {
        const pending = pendingResponseTimeouts.get(rid);
        if (pending) {
            clearTimeout(pending.timer);
            pendingResponseTimeouts.delete(rid);
        }
    }
}
function generateMessageId() {
    return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}
const sendSchema = Type.Object({
    agentIds: Type.Union([
        Type.String({ minLength: 1, description: "Single target agent id." }),
        Type.Array(Type.String({ minLength: 1 }), {
            minItems: 1,
            description: "Target agent ids (deduplicated).",
        }),
    ]),
    message: Type.String({ minLength: 1, description: "Message text to deliver to each target." }),
    flush: Type.Optional(Type.Boolean({
        description: "Interrupt running target turn to deliver immediately.",
    })),
    response_timeout: Type.Optional(Type.Number({
        minimum: 1,
        description: "Seconds to wait for a response before warning the sender.",
    })),
    response_ids: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
        description: "Message IDs this message responds to.",
    })),
}, { additionalProperties: false });
const broadcastSchema = Type.Object({
    message: Type.String({ minLength: 1, description: "Message text broadcast to all other agents." }),
}, { additionalProperties: false });
function parseMaybeJsonStringArray(raw) {
    const trimmed = raw.trim();
    if (!trimmed.startsWith("[")) {
        return raw;
    }
    try {
        const parsed = JSON.parse(trimmed);
        if (Array.isArray(parsed) && parsed.every((entry) => typeof entry === "string")) {
            return parsed;
        }
    }
    catch {
        return raw;
    }
    return raw;
}
function prepareSendArguments(args) {
    if (!isRecord(args)) {
        return args;
    }
    const agentIds = typeof args.agentIds === "string" ? parseMaybeJsonStringArray(args.agentIds) : args.agentIds;
    return {
        ...args,
        agentIds,
    };
}
function normalizeTargetIds(raw) {
    const arr = typeof raw === "string" ? [raw] : raw;
    return [...new Set(arr)];
}
function jsonText(obj) {
    return { content: [{ type: "text", text: JSON.stringify(obj) }], details: null };
}
async function resolveReadyTargets(host, senderAgentId, targetIds, message, options) {
    const ready = [];
    for (const id of targetIds) {
        const rt = (await host.ensureAgentStarted?.(id, "agent_message")) ?? host.tryGetAgentRuntime(id);
        if (!rt) {
            return { error: { ok: false, error: "agent runtime not found", agentId: id } };
        }
        const adapter = rt.agentAdapter;
        if (!adapter) {
            if ((await host.deliverMessageToGuest?.(senderAgentId, id, message, options)) === true) {
                ready.push({ id });
                continue;
            }
            return {
                error: {
                    ok: false,
                    error: "Hub agent adapter is not initialized for target",
                    agentId: id,
                },
            };
        }
        ready.push({ id, adapter });
    }
    return ready;
}
export function createAgentMessagingToolDefinitions(getHost, senderAgentId) {
    return [
        defineTool({
            name: "send_message_to_agent",
            label: "send_message_to_agent",
            description: "Queue a message for one or more other hub agents (main or child). Uses agent/<your id> metadata for attribution. Rejects self-target. Set flush=true for high-priority delivery: the target queue is flushed immediately, interrupting an active target turn if needed. Use response_timeout to get a warning if no reply arrives within the given seconds. Use response_ids to indicate which previous messages this is replying to.",
            parameters: sendSchema,
            prepareArguments: prepareSendArguments,
            async execute(_id, params) {
                const { message } = params;
                const targetIds = normalizeTargetIds(params.agentIds);
                if (targetIds.includes(senderAgentId)) {
                    return jsonText({ ok: false, error: "send_message_to_agent cannot target the sender (self)" });
                }
                // Cancel any pending response timeouts for messages this is responding to
                if (params.response_ids && params.response_ids.length > 0) {
                    cancelPendingResponseTimeouts(params.response_ids);
                }
                const host = getHost();
                const unknown = [];
                for (const id of targetIds) {
                    if (!host.getAllMessagingAgentIds().includes(id)) {
                        unknown.push(id);
                    }
                }
                if (unknown.length > 0) {
                    return jsonText({
                        ok: false,
                        error: "unknown agent id(s)",
                        unknown,
                    });
                }
                // Generate a unique message ID for this send
                const messageId = generateMessageId();
                const ready = await resolveReadyTargets(host, senderAgentId, targetIds, message, {
                    messageId,
                    responseIds: params.response_ids,
                });
                if (!Array.isArray(ready)) {
                    return jsonText(ready.error);
                }
                for (const target of ready) {
                    await target.adapter?.enqueueFromAgent(senderAgentId, message, {
                        messageId,
                        responseIds: params.response_ids,
                        responseTimeout: params.response_timeout,
                    });
                }
                // Set up response timeout if requested
                if (params.response_timeout && params.response_timeout > 0) {
                    const timeoutMs = params.response_timeout * 1000;
                    const targetIdsStr = targetIds.join(", ");
                    const timer = setTimeout(async () => {
                        pendingResponseTimeouts.delete(messageId);
                        const senderRt = host.tryGetAgentRuntime(senderAgentId);
                        if (senderRt?.agentAdapter) {
                            await senderRt.agentAdapter.enqueueFromHost("messaging", `[warning] No response received for message ${messageId} to ${targetIdsStr} within ${params.response_timeout}s`);
                        }
                    }, timeoutMs);
                    pendingResponseTimeouts.set(messageId, { timer, senderAgentId });
                }
                if (params.flush !== true) {
                    return jsonText({ ok: true, message_id: messageId, queued: ready.map((target) => target.id) });
                }
                const flush = [];
                for (const target of ready) {
                    if (!target.adapter) {
                        continue;
                    }
                    const result = await target.adapter.flushInputQueue();
                    flush.push({ agentId: target.id, ...result });
                }
                return jsonText({ ok: true, message_id: messageId, queued: ready.map((target) => target.id), flush });
            },
        }),
        defineTool({
            name: "broadcast_message_to_agents",
            label: "broadcast_message_to_agents",
            description: "Queue a message for every other hub agent (all registered agents except the sender). No-op with a clear result when there are no recipients.",
            parameters: broadcastSchema,
            async execute(_id, params) {
                const { message } = params;
                const host = getHost();
                const all = host.getAllMessagingAgentIds();
                const recipients = all.filter((id) => id !== senderAgentId);
                if (recipients.length === 0) {
                    return jsonText({
                        ok: true,
                        queued: [],
                        message: "no other agents to receive broadcast",
                    });
                }
                const ready = await resolveReadyTargets(host, senderAgentId, recipients, message);
                if (!Array.isArray(ready)) {
                    return jsonText(ready.error);
                }
                for (const target of ready) {
                    await target.adapter?.enqueueFromAgent(senderAgentId, message);
                }
                return jsonText({ ok: true, queued: ready.map((target) => target.id) });
            },
        }),
    ];
}
//# sourceMappingURL=agent-messaging-tools.js.map