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  // EffortSupport per model (model pages, 2026-08-21): the gpt-5.6 family (incl. sol/terra/luna)
021  // documents none/low/medium/high/xhigh/max; gpt-5.5, gpt-5.4, gpt-5.4-mini and gpt-5.4-nano
022  // document none/low/medium/high/xhigh; o-series and non-reasoning GPT-4.x models are STANDARD
023  // (low..high, no explicit none), so higher tiers clamp.
024  GPT_5_6("gpt-5.6", 1_050_000, 128_000, EffortSupport.FULL),
025  GPT_5_6_SOL("gpt-5.6-sol", 1_050_000, 128_000, EffortSupport.FULL),
026  GPT_5_6_TERRA("gpt-5.6-terra", 1_050_000, 128_000, EffortSupport.FULL),
027  GPT_5_6_LUNA("gpt-5.6-luna", 1_050_000, 128_000, EffortSupport.FULL),
028  GPT_5_5("gpt-5.5", 1_050_000, 128_000, EffortSupport.EXTENDED),
029  GPT_5_4("gpt-5.4", 1_050_000, 128_000, EffortSupport.EXTENDED),
030  GPT_5_4_MINI("gpt-5.4-mini", 400_000, 128_000, EffortSupport.EXTENDED),
031  GPT_5_4_NANO("gpt-5.4-nano", 400_000, 128_000, EffortSupport.EXTENDED),
032  GPT_4_1("gpt-4.1", 1_000_000, 32_000, EffortSupport.STANDARD),
033  GPT_4_1_MINI("gpt-4.1-mini", 1_000_000, 32_000, EffortSupport.STANDARD),
034  GPT_4_1_NANO("gpt-4.1-nano", 1_000_000, 16_000, EffortSupport.STANDARD),
035  GPT_4O("gpt-4o", 128_000, 16_384, EffortSupport.STANDARD),
036  GPT_4O_MINI("gpt-4o-mini", 128_000, 16_384, EffortSupport.STANDARD),
037  O3("o3", 200_000, 100_000, EffortSupport.STANDARD),
038  O4_MINI("o4-mini", 200_000, 100_000, EffortSupport.STANDARD);
039
040  /**
041   * The {@code reasoning.effort} value range a model accepts on the Responses API. Wider tiers are
042   * supersets: {@link #FULL} accepts everything {@link #EXTENDED} does plus {@code none} and {@code
043   * max}.
044   */
045  public enum EffortSupport {
046    /**
047     * {@code low} / {@code medium} / {@code high} only; higher Helios tiers clamp to high and
048     * {@code ThinkingLevel.NONE} omits the {@code reasoning} config.
049     */
050    STANDARD,
051
052    /**
053     * {@code none} / {@code low} / {@code medium} / {@code high} / {@code xhigh} (gpt-5.4 and
054     * gpt-5.5 families); {@code ThinkingLevel.MAX} clamps to xhigh.
055     */
056    EXTENDED,
057
058    /**
059     * Full range {@code none} / {@code low} / {@code medium} / {@code high} / {@code xhigh} /
060     * {@code max} (gpt-5.6 family).
061     */
062    FULL
063  }
064
065  private final String id;
066  private final int contextWindow;
067  private final int maxOutputTokens;
068  private final EffortSupport effortSupport;
069
070  OpenAIModelId(String id, int contextWindow, int maxOutputTokens, EffortSupport effortSupport) {
071    this.id = id;
072    this.contextWindow = contextWindow;
073    this.maxOutputTokens = maxOutputTokens;
074    this.effortSupport = effortSupport;
075  }
076
077  /**
078   * The {@code reasoning.effort} range this model accepts. Drives the {@code ThinkingLevel} → wire
079   * mapping in {@code OpenAIModel}: tiers above the model's ceiling clamp down so requests stay
080   * valid.
081   *
082   * @return the effort-support tier
083   */
084  public EffortSupport effortSupport() {
085    return effortSupport;
086  }
087
088  /**
089   * Whether this model accepts {@code reasoning.effort=xhigh}.
090   *
091   * @return true for {@link EffortSupport#EXTENDED} and {@link EffortSupport#FULL} models
092   * @deprecated use {@link #effortSupport()}; the boolean cannot express the gpt-5.6 family's
093   *     {@code none}/{@code max} support
094   */
095  @Deprecated(since = "2.8.0")
096  public boolean supportsXhighEffort() {
097    return effortSupport != EffortSupport.STANDARD;
098  }
099
100  /**
101   * Returns the API model identifier string.
102   *
103   * @return the model ID used in API requests
104   */
105  public String id() {
106    return id;
107  }
108
109  /**
110   * Returns the context window size in tokens.
111   *
112   * @return the context window size
113   */
114  public int contextWindow() {
115    return contextWindow;
116  }
117
118  /**
119   * Returns the maximum output tokens this model can generate in a single response. Used as the
120   * fallback when {@code ModelConfig.maxOutputTokens()} is unset.
121   *
122   * @return the per-model output ceiling
123   */
124  public int maxOutputTokens() {
125    return maxOutputTokens;
126  }
127
128  /**
129   * Finds an OpenAIModelId by its string identifier.
130   *
131   * @param id the model identifier string
132   * @return the matching OpenAIModelId, or null if not found
133   */
134  public static OpenAIModelId fromId(String id) {
135    if (Strings.isBlank(id)) {
136      return null;
137    }
138    for (var model : values()) {
139      if (model.id.equals(id)) {
140        return model;
141      }
142    }
143    return null;
144  }
145
146  /**
147   * Checks if the given model ID is supported.
148   *
149   * @param id the model identifier string
150   * @return true if the model is supported
151   */
152  public static boolean isSupported(String id) {
153    return fromId(id) != null;
154  }
155}