Add gpt-image-2.5-flare (new default) and gpt-image-2.5-sunburst, released 2026-09-08. Both support custom WxH sizes, transparent backgrounds and the new xhigh/max quality tiers; gpt-image-2 falls back to high when an extended tier is selected. Retire gpt-image-1 and gpt-image-1.5 ahead of their 2026-10-23 and 2026-12-01 shutdown dates. That drops the input_fidelity parameter with them: gpt-image-2 and 2.5 always use high input fidelity and error if it is sent, so no shipped model can use it. With every remaining model accepting custom sizes, the custom_size capability flag became constant-true; removing it collapses resolve_size to a validation call and drops its unused model argument. BREAKING CHANGE: saved workflows selecting gpt-image-1 or gpt-image-1.5 must have their model widget re-picked.
76 lines
2.6 KiB
Python
76 lines
2.6 KiB
Python
"""OpenAI API key resolution, model catalog, and client construction.
|
|
|
|
Public entry points: resolve_api_key(), get_client(), friendly_error(); plus the
|
|
MODELS list and DEFAULT_MODEL constant.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from openai import OpenAI
|
|
|
|
log = logging.getLogger("imagent")
|
|
|
|
# config.json lives at the repo root, next to this package directory.
|
|
_CONFIG_PATH = Path(__file__).resolve().parent.parent / "config.json"
|
|
|
|
# Current OpenAI image models, verified 2026-09-13. Dated snapshots, -mini and
|
|
# models with an announced shutdown date are intentionally omitted.
|
|
MODELS = [
|
|
"gpt-image-2.5-flare",
|
|
"gpt-image-2.5-sunburst",
|
|
"gpt-image-2",
|
|
]
|
|
DEFAULT_MODEL = "gpt-image-2.5-flare"
|
|
|
|
_PLACEHOLDERS = {"", "sk-...", "your_openai_api_key_here"}
|
|
|
|
|
|
def resolve_api_key() -> str | None:
|
|
"""Return the API key from env (preferred) or config.json, else None."""
|
|
env_key = os.environ.get("OPENAI_API_KEY")
|
|
if env_key and env_key not in _PLACEHOLDERS:
|
|
return env_key
|
|
try:
|
|
if _CONFIG_PATH.exists():
|
|
cfg = json.loads(_CONFIG_PATH.read_text())
|
|
file_key = cfg.get("OPENAI_API_KEY", "")
|
|
if isinstance(file_key, str) and file_key and file_key not in _PLACEHOLDERS:
|
|
return file_key
|
|
except (json.JSONDecodeError, OSError) as exc:
|
|
log.warning("imagent: could not read %s: %s", _CONFIG_PATH, exc)
|
|
return None
|
|
|
|
|
|
def get_client() -> "OpenAI | None":
|
|
"""Construct an OpenAI client, or None if no key is available."""
|
|
key = resolve_api_key()
|
|
if not key:
|
|
return None
|
|
from openai import OpenAI
|
|
return OpenAI(api_key=key)
|
|
|
|
|
|
def friendly_error(exc: Exception) -> str:
|
|
"""Turn an OpenAI exception into an actionable message.
|
|
|
|
Prefers the exception's HTTP status_code (OpenAI APIError exposes it) and
|
|
falls back to anchored substrings, so unrelated text containing 'verif' or
|
|
'429' is not misclassified.
|
|
"""
|
|
msg = str(exc)
|
|
lower = msg.lower()
|
|
status = getattr(exc, "status_code", None)
|
|
is_verif = "organization verification" in lower or "must be verified" in lower
|
|
if status == 403 or "error code: 403" in lower or is_verif:
|
|
return ("Error: 403 — your OpenAI org may need API Organization "
|
|
"Verification before gpt-image models can be used. Details: " + msg)
|
|
if status == 429 or "error code: 429" in lower:
|
|
return "Error: 429 — rate limit / quota exceeded. Details: " + msg
|
|
return f"Error: {msg}"
|