/**
 * ToolProvider — abstraction over tool dispatch.
 *
 * v2.4 shipped tools as a flat array on the agent (registered via
 * `agent.tool(t)` / `agent.tools(arr)`). That model breaks down once
 * production agents need:
 *   1. Permission gating per-tool, per-caller (read-only roles, etc.)
 *   2. Per-skill tool gating (only show the active skill's tools to
 *      the LLM each turn)
 *   3. Composable filters (a `withReadonly` decorator over a `withSkill`
 *      decorator over the base tool list)
 *
 * `ToolProvider` is the answer: a chainable abstraction over "what
 * tools does the LLM see right now?". The agent asks the provider
 * each iteration; the provider returns the visible tool set computed
 * from whatever predicates / role gates / skill filters the consumer
 * composed.
 *
 * Pattern: Strategy (GoF) — each ToolProvider is a strategy for
 *          "compute the visible tool list given current context".
 *          Decorator (GoF) — `gatedTools(inner, predicate)` wraps any
 *          provider with an additional filter, mirroring how `withRetry`
 *          / `withFallback` decorate `LLMProvider`.
 * Role:    tools/ layer (L7) dispatch primitive. Agent calls `provider.list(ctx)`
 *          each iteration to compute the visible tool set.
 * Emits:   N/A (pure compute; permission denials emit elsewhere via the
 *          permission subsystem).
 *
 * @example  Static tool list (90% case — what `.tools(arr)` does today)
 *   const provider = staticTools([weather, lookupOrder]);
 *
 * @example  Read-only enforcement (role-based gate)
 *   const readOnlyProvider = gatedTools(
 *     staticTools([weather, lookupOrder, processRefund]),
 *     (toolName) => policy.isAllowed(toolName),
 *   );
 *
 * @example  Skill-gated dispatch (only active skill's tools visible)
 *   const skillGated = gatedTools(
 *     staticTools(allTools),
 *     (toolName, ctx) => ctx.activeSkillId
 *       ? skillsToolMap[ctx.activeSkillId].includes(toolName)
 *       : alwaysVisible.includes(toolName),
 *   );
 *
 * @example  Stack: read-only over skill-gated
 *   const provider = gatedTools(
 *     gatedTools(staticTools(allTools), readOnlyPredicate),
 *     skillGatePredicate,
 *   );
 */
import type { Tool } from './tools.js';
/**
 * Read-only context the provider receives each iteration. Pure data
 * — providers MUST NOT mutate. Used by gating predicates to inspect
 * the current activation state.
 */
export interface ToolDispatchContext {
    /** Current ReAct iteration (1-based). */
    readonly iteration: number;
    /**
     * The id of the currently-activated Skill, if any.
     * Set by `read_skill(id)` activation; cleared between turns.
     * Used by `autoActivate`-driven per-skill tool gating.
     *
     * **This is the last `read_skill` activation, not the graph's position.** It is
     * filled from the tail of `activatedInjectionIds`, which only `read_skill` ever
     * appends to. For "which skills are actually loaded right now", including the ones
     * an entry rule or a `skillGraph()` edge activated, read {@link activeSkillIds}.
     */
    readonly activeSkillId?: string;
    /**
     * **Every skill active on this iteration** — the real active set, however each one
     * got there: a `read_skill` activation, an entry rule, a `skillGraph()` route, a
     * decision-tree leaf. Added in 8.7.0.
     *
     * `activeSkillId` answers "what did the model most recently open"; this answers
     * "what is loaded". A provider that wants to follow the graph's POSITION — which
     * `skillScopedTools`' own docs said was impossible — reads this one:
     *
     * ```ts
     * const graphScoped = (id: string, tools: Tool[]): ToolProvider => ({
     *   id: `graph-scoped:${id}`,
     *   list: (ctx) => (ctx.activeSkillIds?.includes(id) ? tools : []),
     * });
     * ```
     *
     * Optional for forward-compat: a provider written before 8.7.0 sees `undefined` and
     * behaves exactly as it always did.
     */
    readonly activeSkillIds?: readonly string[];
    /**
     * Caller identity tuple — passed through from `agent.run({ identity })`.
     * Permission predicates can role-check based on `identity.principal`
     * or `identity.tenant`.
     */
    readonly identity?: {
        readonly tenant?: string;
        readonly principal?: string;
        readonly conversationId: string;
    };
    /**
     * The identity the CALLER named in `run({ identity })` — the same fact a
     * tool's `ToolExecutionContext.identity` carries. **Absent when the run's
     * identity was derived** (from a `sessionId`, or the per-run default), which
     * {@link identity} is not: it falls back to `{ conversationId: sessionId }` or
     * `{ conversationId: runId }`, and a derived tuple is not an authenticated
     * identity — a session id is caller data.
     *
     * A provider that keys anything live on the caller (a client per
     * conversation, `mcpClientPool`'s provider) reads only this one.
     */
    readonly callerIdentity?: {
        readonly tenant?: string;
        readonly principal?: string;
        readonly conversationId: string;
    };
    /**
     * Optional abort signal propagated from the agent's `run({ env })` /
     * AbortController. Async providers (network discovery, MCP catalog
     * fetch, registry pull) MUST honor this — abandon the in-flight
     * request when the agent is cancelled mid-discovery, otherwise the
     * provider holds the run open past abort. Sync providers can ignore
     * it.
     */
    readonly signal?: AbortSignal;
}
/**
 * The provider interface. A `ToolProvider` answers ONE question per
 * iteration: "what tools should the LLM see right now?"
 *
 * Implementations are PURE — given the same context, return the same
 * tool list (no observable mutation; reentrant; safe under concurrent
 * calls).
 *
 * **Sync vs async.** Most providers (`staticTools`, `gatedTools`,
 * `skillScopedTools`) compute the answer synchronously and return
 * `readonly Tool[]` — the agent's hot path skips the await microtask
 * entirely via a runtime `instanceof Promise` check. Discovery-style
 * providers (MCP catalog fetch, registry pull, dynamic skill resolution)
 * may return `Promise<readonly Tool[]>`; the agent awaits only when
 * the value is actually a Promise. Sync providers pay zero overhead.
 *
 * **Caching.** The agent calls `list(ctx)` once per iteration. For
 * expensive lookups (network calls, hub queries), the provider is
 * responsible for caching — typically TTL- or iteration-keyed. The
 * framework deliberately does NOT cache for you because the cache
 * key depends on which fields of `ctx` matter to your provider
 * (e.g., per-skill vs per-tenant vs per-iteration).
 *
 * **Errors.** A throwing or rejecting provider emits
 * `agentfootprint.tools.discovery_failed` and aborts the iteration —
 * the run continues only if a configured `reliability` rule routes
 * the error (`fail-fast`, `retry`, etc.). Discovery failure is loud
 * by design; silently dropping tools mid-conversation produces
 * non-deterministic agent behavior that's harder to debug than a
 * crash.
 */
export interface ToolProvider {
    /**
     * Return the tool list visible to the LLM for the current iteration.
     * Sync return is the fast path; Promise return is supported for
     * discovery-style providers. The returned array MUST be a NEW
     * reference each call (the agent compares for change detection).
     * Order is preserved — the LLM may use position as a hint when tool
     * descriptions are ambiguous.
     */
    list(ctx: ToolDispatchContext): readonly Tool[] | Promise<readonly Tool[]>;
    /**
     * Optional: stable id for observability / debugging. Defaults to
     * `'static'` for `staticTools`, `'gated'` for `gatedTools`. Custom
     * implementations should set their own id — surfaces on
     * `agentfootprint.tools.discovery_failed.providerId` so consumers
     * can route alerts to the right hub adapter.
     */
    readonly id?: string;
}
/** Predicate for `gatedTools` — runs per tool, per iteration. */
export type ToolGatePredicate = (toolName: string, ctx: ToolDispatchContext) => boolean;
