import { actionSchemaToJsonSchema, } from './actions/index.js';
/** Resolve an agent reference to its bare name (sigils such as `@`/`#` stripped). */
export function resolveAgentName(ref) {
    if (typeof ref === 'string')
        return stripSigil(ref);
    const value = ref.handle ?? ref.name ?? ref.id;
    if (!value) {
        throw new Error('Unable to resolve agent name: reference has no name, handle, or id.');
    }
    return stripSigil(value);
}
/** Resolve an agent reference to its auth token, when one is available (for acting-as sends). */
export function resolveAgentToken(ref) {
    return ref && typeof ref !== 'string' ? ref.token : undefined;
}
function stripSigil(value) {
    return value.startsWith('@') || value.startsWith('#') ? value.slice(1) : value;
}
function isChannelTarget(to) {
    return to.startsWith('#');
}
function deliveryToMode(delivery) {
    if (!delivery)
        return undefined;
    // `immediate` / `next-tool-call` interrupt the running agent; the rest queue.
    return delivery === 'immediate' || delivery === 'next-tool-call' ? 'steer' : 'wait';
}
function buildText(text, mentions) {
    let body = text ?? '';
    if (mentions?.length) {
        const handles = mentions.map((mention) => `@${resolveAgentName(mention)}`);
        const missing = handles.filter((handle) => !body.includes(handle));
        if (missing.length) {
            body = body ? `${missing.join(' ')} ${body}` : missing.join(' ');
        }
    }
    return body;
}
/** Resolve a `thread`/`messageId` reference to a parent message id. */
function resolveMessageId(ref) {
    if (!ref) {
        throw new Error('reply requires a `messageId` or `thread`.');
    }
    if (typeof ref === 'string')
        return ref;
    const value = ref.id ?? ref.threadId;
    if (!value) {
        throw new Error('Unable to resolve a message id from the provided thread reference.');
    }
    return value;
}
export function createEnrichedMessages(base, resolveFrom) {
    const enriched = Object.create(base);
    enriched.send = async (input) => {
        if ('channel' in input && input.channel) {
            return base.send(input);
        }
        const sendInput = input;
        const messages = resolveFrom(sendInput.from);
        const text = buildText(sendInput.text ?? sendInput.msg, sendInput.mentions);
        if (Array.isArray(sendInput.to)) {
            return messages.groupDirect({
                participants: sendInput.to.map(resolveAgentName),
                text,
                attachments: sendInput.attachments,
                mode: sendInput.mode,
                idempotencyKey: sendInput.idempotencyKey,
            });
        }
        if (isChannelTarget(sendInput.to)) {
            return messages.send({
                channel: stripSigil(sendInput.to),
                text,
                blocks: sendInput.blocks,
                attachments: sendInput.attachments,
                mode: sendInput.mode,
                idempotencyKey: sendInput.idempotencyKey,
            });
        }
        return messages.direct({
            to: resolveAgentName(sendInput.to),
            text,
            attachments: sendInput.attachments,
            mode: sendInput.mode,
            idempotencyKey: sendInput.idempotencyKey,
        });
    };
    enriched.reply = async (input) => {
        const messages = resolveFrom(input.from);
        return messages.reply({
            messageId: resolveMessageId(input.messageId ?? input.thread),
            text: input.text,
            blocks: input.blocks,
            idempotencyKey: input.idempotencyKey,
        });
    };
    enriched.react = (async (arg1, arg2) => {
        if (typeof arg1 === 'string') {
            return base.react(arg1, arg2);
        }
        const messageId = typeof arg1.message === 'string' ? arg1.message : arg1.message.id;
        const messages = resolveFrom(arg1.agent);
        return messages.react(messageId, arg1.emoji);
    });
    enriched.dm = async (input) => {
        const messages = resolveFrom(input.from);
        return messages.direct({
            to: resolveAgentName(input.to),
            text: input.text ?? input.msg ?? '',
            attachments: input.attachments,
            mode: input.mode,
            idempotencyKey: input.idempotencyKey,
        });
    };
    return enriched;
}
export function createWorkspaceFacade(messaging, deps) {
    const register = async (agents) => {
        if (!deps) {
            throw new Error('register() is only available on the workspace client.');
        }
        const list = Array.isArray(agents) ? agents : [agents];
        const inputs = list.map((agent) => typeof agent === 'string'
            ? { name: stripSigil(agent) }
            : {
                name: resolveAgentName(agent),
                type: agent.type,
                persona: agent.persona,
                metadata: agent.metadata,
            });
        // Fail fast on in-batch duplicates so a batch can't partially register
        // before the relay rejects a later duplicate name.
        const seen = new Set();
        for (const { name } of inputs) {
            if (seen.has(name)) {
                throw new Error(`Duplicate agent name in register(): "${name}".`);
            }
            seen.add(name);
        }
        const clients = [];
        for (const input of inputs) {
            clients.push(deps.buildAgentClient(await messaging.agents.register(input)));
        }
        return Array.isArray(agents) ? clients : clients[0];
    };
    return {
        info: () => messaging.workspace.info(),
        register: register,
        reconnect: async ({ apiToken }) => {
            if (!deps) {
                throw new Error('reconnect() is only available on the workspace client.');
            }
            return deps.reconnectAgent(apiToken);
        },
    };
}
export function registerFacadeAction(actions, def, wiring) {
    const allowed = def.availableTo?.map(resolveAgentName);
    const policy = allowed || def.policy
        ? async (input, ctx) => {
            if (allowed && !allowed.includes(ctx.caller.name)) {
                return {
                    allowed: false,
                    reason: `${ctx.caller.name} is not permitted to call ${def.name}.`,
                };
            }
            return def.policy ? def.policy(input, ctx) : { allowed: true };
        }
        : undefined;
    const localHandle = actions.register({
        name: def.name,
        description: def.description,
        input: def.input,
        inputSchema: def.inputSchema,
        output: def.output,
        outputSchema: def.outputSchema,
        visibility: def.visibility,
        policy,
        handler: (input, ctx) => def.handler({ input, agent: ctx.caller, ctx }),
    });
    const relayUnsubscribe = wireRelayAction(actions, def, wiring, allowed);
    if (!relayUnsubscribe) {
        return localHandle;
    }
    return {
        unregister: () => {
            relayUnsubscribe();
            localHandle.unregister();
        },
    };
}
/**
 * Register the action descriptor on the relay and subscribe the handler agent
 * to its `action.invoked` events. Returns an unsubscribe callback, or
 * `undefined` when no agent-scoped relay connection is available (in which case
 * the action stays purely in-process, matching legacy behavior).
 */
function wireRelayAction(actions, def, wiring, allowed) {
    const commands = wiring?.messaging.commands;
    if (!wiring || !commands?.agentScoped?.() || !commands.available?.()) {
        return undefined;
    }
    const handlerAgent = wiring.handlerAgent;
    if (!handlerAgent) {
        return undefined;
    }
    // Register the descriptor on the relay (fire-and-forget; failures are logged
    // but do not break local registration).
    void commands
        .register({
        command: def.name,
        description: def.description ?? def.name,
        handlerAgent,
        inputSchema: actionSchemaToJsonSchema(def.inputSchema ?? def.input),
        outputSchema: actionSchemaToJsonSchema(def.outputSchema ?? def.output),
        ...(allowed ? { availableTo: allowed } : {}),
    })
        .catch((error) => {
        console.error(`[agent-relay] failed to register action descriptor "${def.name}":`, error);
    });
    // Subscribe to invocations routed to this handler agent, then open the event
    // stream — `events.on(...)` only registers the handler; the socket is opened
    // by `events.connect()`. Without this the handler never sees `action.invoked`.
    const unsubscribe = wiring.messaging.events.on('actionInvoked', async (event) => {
        if (event.actionName !== def.name) {
            return;
        }
        await handleActionInvoked(actions, commands, def.name, event);
    });
    try {
        wiring.messaging.events.connect();
    }
    catch (error) {
        console.error(`[agent-relay] failed to open the event stream for action "${def.name}":`, error);
    }
    return unsubscribe;
}
async function handleActionInvoked(actions, commands, actionName, event) {
    let input = {};
    try {
        const invocation = await commands.getInvocation(actionName, event.invocationId);
        input = invocation.input ?? {};
    }
    catch (error) {
        console.error(`[agent-relay] failed to load invocation "${event.invocationId}":`, error);
    }
    const result = await actions.invoke({
        name: actionName,
        input,
        caller: { name: event.callerName, type: 'agent' },
    });
    try {
        await commands.completeInvocation(actionName, event.invocationId, result.ok
            ? { output: toOutputRecord(result.output) }
            : { error: result.error?.message ?? 'handler failed' });
    }
    catch (error) {
        console.error(`[agent-relay] failed to complete invocation "${event.invocationId}":`, error);
    }
}
/** Coerce a handler return value into the `output` object the relay accepts. */
function toOutputRecord(output) {
    if (output !== null && typeof output === 'object' && !Array.isArray(output)) {
        return output;
    }
    return { value: output };
}
export function createNotifyHandler(messages, target, options) {
    return async () => {
        const subject = options.subject ? `@${resolveAgentName(options.subject)}` : undefined;
        const label = options.type ?? options.action ?? 'notification';
        const text = options.text ?? [`[${label}]`, subject].filter(Boolean).join(' ');
        await messages.dm({
            to: resolveAgentName(target),
            text,
            mode: deliveryToMode(options.delivery),
        });
    };
}
//# sourceMappingURL=facade.js.map