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