From 8d686324b038f44e0ccb3562d32462ab76717fb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 20:41:32 +0000 Subject: [PATCH 1/2] Add image generation models to TEE_LLM enum Add GROK_2_IMAGE (xAI), SEEDREAM_4_0 and SEEDANCE_4_5 (ByteDance), and GLM_5_2 / GLM_IMAGE (Z.ai) to the TEE_LLM enum so SDK users can access all image generation models already supported by the tee-gateway. Also add GLM_5_2 as the Z.ai text model which was missing entirely. Update the llm_image_generation.py example with a quick reference listing all available image generation models and their billing model. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01XxsSjnuypVUhpDPiEDA1Md --- examples/llm_image_generation.py | 12 ++++++++++++ src/opengradient/types.py | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/examples/llm_image_generation.py b/examples/llm_image_generation.py index 547ed84..68fb378 100644 --- a/examples/llm_image_generation.py +++ b/examples/llm_image_generation.py @@ -32,6 +32,18 @@ async def main(): # The text caption (if any) is in chat_output["content"]; the generated images # are in result.images as data: URIs. Images travel out-of-band and are not part # of the signed output hash. + # + # Available image generation models: + # Google (inline, billed per output token): + # og.TEE_LLM.GEMINI_2_5_FLASH_IMAGE + # og.TEE_LLM.GEMINI_3_1_FLASH_IMAGE + # xAI (flat per-image rate, $0.07/image): + # og.TEE_LLM.GROK_2_IMAGE + # ByteDance (flat per-image rate): + # og.TEE_LLM.SEEDREAM_4_0 ($0.03/image) + # og.TEE_LLM.SEEDANCE_4_5 ($0.05/image) + # Z.ai (flat per-image rate, $0.015/image): + # og.TEE_LLM.GLM_IMAGE result = await llm.chat( model=og.TEE_LLM.GEMINI_3_1_FLASH_IMAGE, messages=messages, diff --git a/src/opengradient/types.py b/src/opengradient/types.py index 2846ad1..f15a591 100644 --- a/src/opengradient/types.py +++ b/src/opengradient/types.py @@ -593,15 +593,39 @@ class TEE_LLM(str, Enum): GROK_4_20_NON_REASONING = "x-ai/grok-4.20-non-reasoning" GROK_CODE_FAST_1 = "x-ai/grok-code-fast-1" + # xAI image-generation models via TEE (Aurora, dedicated /images/generations endpoint). + # Billed at a flat rate per image. Images are returned on ``TextGenerationOutput.images`` + # and ``StreamChunk.images`` as data: URIs and are not part of the signed output hash. + GROK_2_IMAGE = "x-ai/grok-2-image" + # ByteDance Seed models via TEE (BytePlus ModelArk) SEED_1_6 = "bytedance/seed-1.6" SEED_1_8 = "bytedance/seed-1.8" SEED_2_0_LITE = "bytedance/seed-2.0-lite" + # DeepSeek models via TEE (served through BytePlus ModelArk) + DEEPSEEK_V4_FLASH = "bytedance/deepseek-v4-flash" + DEEPSEEK_V4_PRO = "bytedance/deepseek-v4-pro" + + # ByteDance image-generation models via TEE (ModelArk, dedicated /images/generations + # endpoint). Billed at a flat rate per image. Images are returned on + # ``TextGenerationOutput.images`` and ``StreamChunk.images`` as data: URIs. + SEEDREAM_4_0 = "bytedance/seedream-4.0" + SEEDANCE_4_5 = "bytedance/seedance-4.5" + + # Nous Research Hermes models via TEE (Nous Portal) HERMES_4_405B = "nous/hermes-4-405b" HERMES_4_70B = "nous/hermes-4-70b" + # Z.ai GLM models via TEE (Model API, OpenAI-compatible) + GLM_5_2 = "zai/glm-5.2" + + # Z.ai image-generation model via TEE (dedicated /images/generations endpoint). + # Billed at a flat rate per image. Images are returned on + # ``TextGenerationOutput.images`` and ``StreamChunk.images`` as data: URIs. + GLM_IMAGE = "zai/glm-image" + @dataclass class ResponseFormat: From 0afe8391d9a47de0bc1615786b1c32141a1615bc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 21:10:24 +0000 Subject: [PATCH 2/2] Resolve provider image URLs to data: URIs in SDK client Providers like ByteDance Seedance return pre-signed CDN URLs instead of inline base64. _resolve_images() fetches any http(s) URLs and converts them to data: URIs so result.images always contains self-contained base64 regardless of which model generated the image. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01XxsSjnuypVUhpDPiEDA1Md --- src/opengradient/client/llm.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/opengradient/client/llm.py b/src/opengradient/client/llm.py index ecb422e..9155d36 100644 --- a/src/opengradient/client/llm.py +++ b/src/opengradient/client/llm.py @@ -155,6 +155,30 @@ def resolve_tee_connection(self, tee_id: Optional[str] = None) -> ActiveTEE: """ return self._tee.resolve(tee_id) + # ── Image helpers ──────────────────────────────────────────────────── + + @staticmethod + async def _resolve_images(images: Optional[List[str]]) -> Optional[List[str]]: + """Fetch any HTTP/HTTPS image URLs and convert them to data: URIs. + + Providers like ByteDance Seedance return pre-signed CDN URLs instead of + inline base64. This normalises the list so callers always receive data: URIs. + """ + if not images: + return images + resolved: List[str] = [] + async with httpx.AsyncClient(follow_redirects=True, timeout=60) as client: + for img in images: + if img.startswith("http://") or img.startswith("https://"): + resp = await client.get(img) + resp.raise_for_status() + mime = resp.headers.get("content-type", "image/jpeg").split(";")[0].strip() + b64 = base64.b64encode(resp.content).decode("ascii") + resolved.append(f"data:{mime};base64,{b64}") + else: + resolved.append(img) + return resolved + # ── Request helpers ───────────────────────────────────────────────── def _headers(self, settlement_mode: x402SettlementMode) -> Dict[str, str]: @@ -458,7 +482,7 @@ async def _request() -> TextGenerationOutput: data_settlement_blob_id=self._data_settlement_blob_id(response), finish_reason=choices[0].get("finish_reason"), chat_output=message, - images=message.get("images"), + images=await self._resolve_images(message.get("images")), usage=result.get("usage"), tee_signature=result.get("tee_signature"), tee_timestamp=result.get("tee_timestamp"), @@ -497,7 +521,7 @@ async def _chat_tools_as_stream(self, params: _ChatParams, messages: List[Dict]) tee_payment_address=result.tee_payment_address, data_settlement_transaction_hash=result.data_settlement_transaction_hash, data_settlement_blob_id=result.data_settlement_blob_id, - images=result.images, + images=await self._resolve_images(result.images), ) async def _chat_stream(self, params: _ChatParams, messages: List[Dict]) -> AsyncGenerator[StreamChunk, None]: