///|
/// Effect-scoped execution context handed to `ToolProvider::execute`.
/// `effect_id` is the reducer-assigned identity of this `ExecuteTool` effect —
/// the exact id a later cancellation carries, so a provider owning
/// interruptible work keys its in-flight state by it.
pub struct ToolCallContext {
  effect_id : @kernel.EffectId
  call_id : @kernel.CallId
  name : @kernel.ToolName
  arguments : Json
  /// Register the cancellable facet for THIS effect. Providers that cannot
  /// be interrupted simply never call it; the runtime then reports
  /// `NotPropagated` for the effect, exactly as a ports-only agent does
  /// today. The registrar is valid only during the execute call it
  /// arrived in.
  register_cancel : (&ToolCancellable) -> Unit
  scope : @kernel.InvocationScope?
  parent_effect_id : @kernel.EffectId?
  root_effect_id : @kernel.EffectId
  priv tools_ : () -> Array[@kernel.ToolDef]
  priv invoke_ : (async (String, Json) -> @kernel.InvocationResult)?
  priv active_ : () -> Bool
}

///|
/// Framework construction seam. An ordinary manually constructed context has
/// no invocation authority. Supplied callbacks are local to this effect scope.
pub fn ToolCallContext::ToolCallContext(
  effect_id~ : @kernel.EffectId,
  call_id~ : @kernel.CallId,
  name~ : @kernel.ToolName,
  arguments~ : Json,
  register_cancel~ : (&ToolCancellable) -> Unit,
  scope? : @kernel.InvocationScope,
  parent_effect_id? : @kernel.EffectId,
  root_effect_id? : @kernel.EffectId,
  tools~ : () -> Array[@kernel.ToolDef] = fn() { [] },
  invoke? : async (String, Json) -> @kernel.InvocationResult,
  active~ : () -> Bool = fn() { true },
) -> ToolCallContext {
  {
    effect_id,
    call_id,
    name,
    arguments,
    scope,
    parent_effect_id,
    root_effect_id: root_effect_id.unwrap_or(effect_id),
    register_cancel: handler => if active() { register_cancel(handler) },
    tools_: tools,
    invoke_: invoke,
    active_: active,
  }
}

///|
pub fn ToolCallContext::tools(self : ToolCallContext) -> Array[@kernel.ToolDef] {
  if (self.active_)() {
    (self.tools_)()
  } else {
    []
  }
}

///|
pub async fn ToolCallContext::invoke(
  self : ToolCallContext,
  name : String,
  arguments : Json,
) -> @kernel.InvocationResult raise @error.RuntimeError {
  if !(self.active_)() {
    return Interrupted(
      reason="tool invocation scope has ended",
      cancellation=AlreadySettled,
      side_effects_possible=false,
    )
  }
  match self.invoke_ {
    Some(invoke) =>
      invoke(name, arguments) catch {
        error => raise @error.RuntimeError::InvocationFailed(error.to_string())
      }
    None =>
      Interrupted(
        reason="tool invocation scope is unavailable",
        cancellation=NotPropagated,
        side_effects_possible=false,
      )
  }
}

///|
/// Cancellation facet a `ToolProvider` may also implement for effects it
/// owns. Register via `ToolCallContext.register_cancel` inside `execute`;
/// the runtime routes a later cancel back through the same `effect_id`.
///
/// Idempotent per execution. `Propagated` means only that a signal was sent;
/// `Stopped` requires confirmation that owned work has stopped (not rollback).
/// Return `AlreadySettled` for previously completed work. Core bounds cleanup
/// attempts and retains unresolved registrations on failure or timeout so
/// shutdown can retry; never disguise an error as confirmed termination.
pub(open) trait ToolCancellable {
  async fn cancel(
    Self,
    run_id~ : @kernel.RunId,
    effect_id : @kernel.EffectId,
    reason : @kernel.CancelReason,
  ) -> @kernel.CancelDisposition
}

///|
/// `ToolProvider`: declare tools via `list_tools()`, execute via `execute()`.
///
/// Agent binds each definition's owner to the declaring manifest. Providers
/// declare their own execution policy and exposure; these are not inferred
/// from names or provenance. `execute` returns the canonical ToolOutcome.
///
/// A composing tool must reserve a policy strong enough for its children:
/// Parallel cannot invoke Sequential/Exclusive; Sequential cannot invoke
/// Exclusive. Such requests are rejected before starting, not upgraded at
/// runtime. The parent's scheduling slot covers the whole joined subtree.
pub(open) trait ToolProvider {
  /// Read once per static assembly or dynamic publication. Agent deep-copies
  /// definitions and replaces owner with the manifest id. Choose Sequential
  /// for isolated execution; Parallel explicitly permits concurrent effects.
  /// Exposure defaults to Direct and operation hooks may only narrow it.
  fn list_tools(Self) -> Array[@kernel.ToolDef]

  /// Execute one tool call described by `ctx`: map the internal result to
  /// `Success` or `ToolReportedError` for business failures the model should
  /// see, and let posoco's catch-all turn a raised `RuntimeError` into
  /// `RuntimeFailure`. Interruptible work registers its `ToolCancellable` facet
  /// via `ctx.register_cancel` before starting.
  async fn execute(Self, ctx : ToolCallContext) -> @kernel.ToolOutcome raise @error.RuntimeError
}