/**
 * src/mesh/delegate.ts
 *
 * `mesh.delegate()` — intent-based dispatch to the best available agent.
 *
 * @experimental — sprint-585. Public API may change before v1.x freeze.
 *
 * Replaces manual `withCompute()` boilerplate with a one-liner:
 *
 *     const r = await meshDelegate({ need: "summarize the spec" }, dispatcher);
 *
 * Responsibilities:
 *   1. Classify need → AgentKind (via dispatcher.classify or injected hint).
 *   2. Apply capability attenuation if a parent token is present.
 *   3. Forward to dispatcher with budget/deadline guards.
 *   4. Build a delegation chain link for audit.
 *
 * Streaming (`onChunk`) is a best-effort surface — the dispatcher must opt-in
 * by exposing a `dispatchStream` (not part of base contract). When unavailable,
 * the final result is delivered as a single chunk after completion.
 */
import { attenuateScope, buildToken, } from "./attenuation.js";
import { DefaultMeshDispatcher } from "./dispatcher.js";
// ─── Defaults ────────────────────────────────────────────────────────────────
const DEFAULT_DEADLINE_MS = 30_000;
const DEFAULT_MAX_COST_EUR = 0.01;
const SELF_ID = "mesh:caller";
// ─── Helpers ─────────────────────────────────────────────────────────────────
function stringifyContext(ctx) {
    if (ctx === undefined)
        return undefined;
    if (typeof ctx === "string")
        return ctx;
    try {
        return JSON.stringify(ctx);
    }
    catch {
        return String(ctx);
    }
}
function isDispatcherWithClassify(d) {
    return typeof d.classify === "function";
}
function generateChildId() {
    // Tiny non-cryptographic id — child-id is for audit, not auth.
    // Production: replace with ULID/UUID from caller env.
    return `mesh-child-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e6).toString(36)}`;
}
/**
 * Compute child scope from parent token + caller-requested capabilities.
 * Returns the issued child token (parented to caller's parentId) and the
 * effective MeshCapabilityScope (post-intersection).
 */
function issueChildToken(parentToken, requestedCapabilities, requestedBudgetEur, childId) {
    const parentScope = {
        actions: parentToken.scope,
        budgetEur: parentToken.budgetEur,
        expiresAt: parentToken.expiresAt ? new Date(parentToken.expiresAt) : undefined,
    };
    const requestedScope = {
        actions: requestedCapabilities ?? parentToken.scope,
        budgetEur: requestedBudgetEur,
    };
    const childScope = attenuateScope(parentScope, requestedScope);
    const token = buildToken(childScope, childId);
    return { token, scope: childScope };
}
// ─── Errors ──────────────────────────────────────────────────────────────────
export class DelegateAttenuationError extends Error {
    name = "DelegateAttenuationError";
}
// ─── Public API ──────────────────────────────────────────────────────────────
/**
 * Delegate a need to the best available agent.
 *
 * Routing priority (when `kind` not pre-set):
 *  1. Dispatcher's `classify(need)` hook if present.
 *  2. Heuristic — see `defaultClassifier`.
 *
 * Capability attenuation:
 *  - If `parentToken` is provided, child scope = parent ∩ requested.
 *  - Empty intersection raises {@link DelegateAttenuationError}.
 *
 * Budget guards:
 *  - When `parentToken.budgetEur < max_cost_eur`, the effective max is parent's.
 *  - Negative budget or deadline → RangeError.
 */
export async function meshDelegate(options, dispatcher) {
    if (!options || typeof options.need !== "string" || options.need.length === 0) {
        throw new TypeError("meshDelegate: `need` is required and must be a non-empty string");
    }
    if (!dispatcher || typeof dispatcher.dispatch !== "function") {
        throw new TypeError("meshDelegate: `dispatcher` must implement MeshDispatcher");
    }
    const deadlineMs = options.deadline_ms ?? DEFAULT_DEADLINE_MS;
    if (!Number.isFinite(deadlineMs) || deadlineMs <= 0) {
        throw new RangeError(`meshDelegate: deadline_ms must be > 0, got ${deadlineMs}`);
    }
    let maxCostEur = options.max_cost_eur ?? DEFAULT_MAX_COST_EUR;
    if (!Number.isFinite(maxCostEur) || maxCostEur < 0) {
        throw new RangeError(`meshDelegate: max_cost_eur must be ≥ 0, got ${maxCostEur}`);
    }
    // ── 1. Classify ──────────────────────────────────────────────────────────
    const kind = options.kind ??
        (isDispatcherWithClassify(dispatcher) ? dispatcher.classify(options.need) : "llm-router");
    // ── 2. Attenuation (if parent token given) ───────────────────────────────
    const childId = options.childId ?? generateChildId();
    let childToken;
    let effectiveScope;
    if (options.parentToken) {
        const issued = issueChildToken(options.parentToken, options.capabilities, maxCostEur, childId);
        if (issued.scope.actions.length === 0 && (options.capabilities?.length ?? 0) > 0) {
            throw new DelegateAttenuationError("meshDelegate: requested capabilities are not granted by parent scope");
        }
        childToken = issued.token;
        effectiveScope = issued.scope.actions;
        // Enforce parent budget cap.
        maxCostEur = Math.min(maxCostEur, issued.scope.budgetEur);
    }
    else if (options.capabilities) {
        effectiveScope = options.capabilities;
    }
    // ── 3. Dispatch ──────────────────────────────────────────────────────────
    const intent = {
        kind,
        need: options.need,
        context: stringifyContext(options.context),
        deadline_ms: deadlineMs,
        max_cost_eur: maxCostEur,
        toolName: options.toolName,
        toolArgs: options.toolArgs,
    };
    const dispatched = await dispatcher.dispatch(intent);
    // ── 4. Audit chain ───────────────────────────────────────────────────────
    const parentIdForLink = options.parentToken?.parentId ?? SELF_ID;
    const link = {
        from: parentIdForLink,
        to: childId,
        scope: effectiveScope ?? [],
        budget: maxCostEur,
    };
    // ── 5. Stream (best-effort, single-shot when streaming not supported) ────
    if (options.onChunk) {
        options.onChunk({ text: dispatched.output, done: true, agentKind: kind });
    }
    const result = {
        output: dispatched.output,
        agentKind: kind,
        delegationChain: [link],
        costEur: dispatched.costEur,
        durationMs: dispatched.durationMs,
        truncated: dispatched.truncated,
        childToken,
    };
    return result;
}
/**
 * Factory variant — binds a dispatcher so callers can call `delegate(opts)`
 * without re-passing the dispatcher every time.
 *
 * Useful for DI in long-lived services.
 */
export function createMeshDelegate(dispatcher) {
    return (options) => meshDelegate(options, dispatcher);
}
/**
 * Re-export DefaultMeshDispatcher as a convenience so callers can do:
 *
 *     import { meshDelegate, DefaultMeshDispatcher } from "@vauban-org/agent-sdk";
 */
export { DefaultMeshDispatcher };
//# sourceMappingURL=delegate.js.map