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_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.STANDARD),
031  GPT_5_4_NANO("gpt-5.4-nano", 400_000, 128_000, EffortSupport.STANDARD),
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    /** {@code low} / {@code medium} / {@code high} only; higher Helios tiers clamp to high. */
047    STANDARD,
048
049    /** Adds {@code xhigh}; {@code ThinkingLevel.MAX} clamps to xhigh. */
050    EXTENDED,
051
052    /**
053     * Full range {@code none} / {@code low} / {@code medium} / {@code high} / {@code xhigh} /
054     * {@code max} (gpt-5.6 family).
055     */
056    FULL
057  }
058
059  private final String id;
060  private final int contextWindow;
061  private final int maxOutputTokens;
062  private final EffortSupport effortSupport;
063
064  OpenAIModelId(String id, int contextWindow, int maxOutputTokens, EffortSupport effortSupport) {
065    this.id = id;
066    this.contextWindow = contextWindow;
067    this.maxOutputTokens = maxOutputTokens;
068    this.effortSupport = effortSupport;
069  }
070
071  /**
072   * The {@code reasoning.effort} range this model accepts. Drives the {@code ThinkingLevel} → wire
073   * mapping in {@code OpenAIModel}: tiers above the model's ceiling clamp down so requests stay
074   * valid.
075   *
076   * @return the effort-support tier
077   */
078  public EffortSupport effortSupport() {
079    return effortSupport;
080  }
081
082  /**
083   * Whether this model accepts {@code reasoning.effort=xhigh}.
084   *
085   * @return true for {@link EffortSupport#EXTENDED} and {@link EffortSupport#FULL} models
086   * @deprecated use {@link #effortSupport()}; the boolean cannot express the gpt-5.6 family's
087   *     {@code none}/{@code max} support
088   */
089  @Deprecated(since = "2.8.0")
090  public boolean supportsXhighEffort() {
091    return effortSupport != EffortSupport.STANDARD;
092  }
093
094  /**
095   * Returns the API model identifier string.
096   *
097   * @return the model ID used in API requests
098   */
099  public String id() {
100    return id;
101  }
102
103  /**
104   * Returns the context window size in tokens.
105   *
106   * @return the context window size
107   */
108  public int contextWindow() {
109    return contextWindow;
110  }
111
112  /**
113   * Returns the maximum output tokens this model can generate in a single response. Used as the
114   * fallback when {@code ModelConfig.maxOutputTokens()} is unset.
115   *
116   * @return the per-model output ceiling
117   */
118  public int maxOutputTokens() {
119    return maxOutputTokens;
120  }
121
122  /**
123   * Finds an OpenAIModelId by its string identifier.
124   *
125   * @param id the model identifier string
126   * @return the matching OpenAIModelId, or null if not found
127   */
128  public static OpenAIModelId fromId(String id) {
129    if (Strings.isBlank(id)) {
130      return null;
131    }
132    for (var model : values()) {
133      if (model.id.equals(id)) {
134        return model;
135      }
136    }
137    return null;
138  }
139
140  /**
141   * Checks if the given model ID is supported.
142   *
143   * @param id the model identifier string
144   * @return true if the model is supported
145   */
146  public static boolean isSupported(String id) {
147    return fromId(id) != null;
148  }
149}