///|
/// The one contract every model endpoint implements: a remote API, a local server, a
/// scripted fake, or a layer wrapped around another provider. Swapping providers is a
/// change to the constructor line, not to the calling code.
pub(open) trait Provider {
  /// One answer, delivered whole.
  async fn chat(Self, Request) -> Reply raise LlmError
  /// One answer, delivered as it is produced. The callback sees each delta; the
  /// returned reply is the same value `chat` would have returned.
  async fn stream(Self, Request, (Delta) -> Unit) -> Reply raise LlmError
  /// Vectors for `inputs`, one per input and in the same order.
  async fn embed(Self, String, Array[String]) -> Array[Array[Double]] raise LlmError
  /// What this provider serves, when it can say.
  async fn models(Self) -> Array[ModelInfo] raise LlmError
}

///|
/// A provider reference that can be stored in a record or an array.
pub type AnyProvider = &Provider

///|
/// The embeddings of a batch, checked against the batch length. A short answer is a
/// broken endpoint, not a partial success, because the caller indexes vectors by input
/// position.
pub fn check_vectors(
  inputs : Array[String],
  vectors : Array[Array[Double]],
) -> Array[Array[Double]] raise LlmError {
  if vectors.length() != inputs.length() {
    raise Decode(
      "expected " +
      inputs.length().to_string() +
      " vectors, got " +
      vectors.length().to_string(),
    )
  }
  vectors
}