001/*
002 * Copyright (c) 2026 Singular
003 * SPDX-License-Identifier: MIT
004 */
005
006package ai.singlr.openai;
007
008import ai.singlr.core.common.Strings;
009import ai.singlr.core.model.Model;
010import ai.singlr.core.model.ModelConfig;
011import ai.singlr.core.model.ModelProvider;
012
013/**
014 * ModelProvider implementation for OpenAI's Responses API.
015 *
016 * <p>Supports every {@link OpenAIModelId} out of the box. When {@link ModelConfig#baseUrl()} is set
017 * — pointing at Azure OpenAI, an OpenAI-compatible proxy (LiteLLM, vLLM, Ollama), or Vertex AI —
018 * any non-blank {@code modelId} is accepted. The string is used verbatim as the {@code model} field
019 * in the request body, which Azure OpenAI maps to the deployment name. Context-window and
020 * max-output-tokens metadata default to {@code 0} ("unknown") for unrecognised ids; callers can
021 * override output tokens via {@link ModelConfig.Builder#withMaxOutputTokens(Integer)}.
022 */
023public class OpenAIProvider implements ModelProvider {
024
025  private static final String PROVIDER_NAME = "openai";
026
027  @Override
028  public String name() {
029    return PROVIDER_NAME;
030  }
031
032  @Override
033  public Model create(String modelId, ModelConfig config) {
034    var known = OpenAIModelId.fromId(modelId);
035    if (known != null) {
036      return new OpenAIModel(known, config);
037    }
038    if (!Strings.isBlank(config.baseUrl())) {
039      return new OpenAIModel(modelId, config);
040    }
041    throw new IllegalArgumentException(
042        "Unsupported model: "
043            + modelId
044            + ". Set ModelConfig.baseUrl for custom endpoints (Azure, proxy, Vertex).");
045  }
046
047  @Override
048  public boolean supports(String modelId) {
049    return OpenAIModelId.isSupported(modelId);
050  }
051}