001/*
002 * Copyright (c) 2026 Singular
003 * SPDX-License-Identifier: MIT
004 */
005
006package ai.singlr.openai;
007
008import ai.singlr.core.common.Strings;
009
010/**
011 * Supported OpenAI model identifiers.
012 *
013 * <p>Each enum constant maps to a specific model available through the Responses API.
014 */
015public enum OpenAIModelId {
016  // maxOutputTokens reflects the documented per-model output ceiling at time of writing —
017  // operators can override per-call via ModelConfig.Builder.withMaxOutputTokens. Reasoning models
018  // (o3, o4-mini) carry higher caps because their output includes reasoning tokens.
019  //
020  // supportsXhighEffort: gpt-5.4 and gpt-5.5 model pages explicitly document `xhigh` as an
021  // accepted value for `reasoning.effort`. The mini/nano variants share their parent's reasoning
022  // family but OpenAI has not published the per-variant value matrix; left false (conservative
023  // clamp to "high") until OpenAI documents otherwise. o-series reasoning models (o3, o4-mini)
024  // pre-date the xhigh tier and are documented to accept low/medium/high only.
025  GPT_5_5("gpt-5.5", 1_050_000, 128_000, true),
026  GPT_5_4("gpt-5.4", 1_050_000, 128_000, true),
027  GPT_5_4_MINI("gpt-5.4-mini", 400_000, 128_000, false),
028  GPT_5_4_NANO("gpt-5.4-nano", 400_000, 128_000, false),
029  GPT_4_1("gpt-4.1", 1_000_000, 32_000, false),
030  GPT_4_1_MINI("gpt-4.1-mini", 1_000_000, 32_000, false),
031  GPT_4_1_NANO("gpt-4.1-nano", 1_000_000, 16_000, false),
032  GPT_4O("gpt-4o", 128_000, 16_384, false),
033  GPT_4O_MINI("gpt-4o-mini", 128_000, 16_384, false),
034  O3("o3", 200_000, 100_000, false),
035  O4_MINI("o4-mini", 200_000, 100_000, false);
036
037  private final String id;
038  private final int contextWindow;
039  private final int maxOutputTokens;
040  private final boolean supportsXhighEffort;
041
042  OpenAIModelId(String id, int contextWindow, int maxOutputTokens, boolean supportsXhighEffort) {
043    this.id = id;
044    this.contextWindow = contextWindow;
045    this.maxOutputTokens = maxOutputTokens;
046    this.supportsXhighEffort = supportsXhighEffort;
047  }
048
049  /**
050   * Whether this model accepts the {@code "xhigh"} value on the {@code reasoning.effort} parameter
051   * of the Responses API. Confirmed against the gpt-5.4 and gpt-5.5 model pages on OpenAI's
052   * developer docs (2026-05-24). Models without confirmed support clamp {@link
053   * ai.singlr.core.model.ThinkingLevel#XHIGH} and {@link ai.singlr.core.model.ThinkingLevel#MAX} to
054   * {@code "high"} so requests stay valid; flip this flag to {@code true} when OpenAI publishes
055   * wider per-model support.
056   *
057   * @return true when {@code reasoning.effort=xhigh} is a valid request value for this model
058   */
059  public boolean supportsXhighEffort() {
060    return supportsXhighEffort;
061  }
062
063  /**
064   * Returns the API model identifier string.
065   *
066   * @return the model ID used in API requests
067   */
068  public String id() {
069    return id;
070  }
071
072  /**
073   * Returns the context window size in tokens.
074   *
075   * @return the context window size
076   */
077  public int contextWindow() {
078    return contextWindow;
079  }
080
081  /**
082   * Returns the maximum output tokens this model can generate in a single response. Used as the
083   * fallback when {@code ModelConfig.maxOutputTokens()} is unset.
084   *
085   * @return the per-model output ceiling
086   */
087  public int maxOutputTokens() {
088    return maxOutputTokens;
089  }
090
091  /**
092   * Finds an OpenAIModelId by its string identifier.
093   *
094   * @param id the model identifier string
095   * @return the matching OpenAIModelId, or null if not found
096   */
097  public static OpenAIModelId fromId(String id) {
098    if (Strings.isBlank(id)) {
099      return null;
100    }
101    for (var model : values()) {
102      if (model.id.equals(id)) {
103        return model;
104      }
105    }
106    return null;
107  }
108
109  /**
110   * Checks if the given model ID is supported.
111   *
112   * @param id the model identifier string
113   * @return true if the model is supported
114   */
115  public static boolean isSupported(String id) {
116    return fromId(id) != null;
117  }
118}