Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ jobs:

- run: npm test

- run: python3 -m unittest skill/infographic-agent/test_portable_infographic.py

- run: npm run build
1 change: 1 addition & 0 deletions .github/workflows/publish-skill.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
run: |
node --check bin/infographic-agent.js
python3 -m py_compile portable_infographic.py
python3 -m unittest test_portable_infographic.py
npm pack --dry-run

- name: Resolve version & publish state
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [3.2.1] - 2026-07-15

### Fixed

- **Portable Python SDK compatibility**: feature-detect `ImageConfig.image_size` and `ThinkingConfig.thinking_level` so generation works with `google-genai 1.47.0`, the latest release available to Python 3.9. Current SDKs still request the selected resolution and high thinking; 1.47 uses native image resolution and dynamic thinking instead of failing validation before the API call. The supported minimums are now explicit: Python 3.9 and `google-genai 1.47.0`.

## [3.2.0] - 2026-07-15

### Added
Expand Down
2 changes: 1 addition & 1 deletion skill/infographic-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ npx infographic-agent "Top 5 programming languages in 2026"

## 📋 Prerequisites

- **Python 3.8+**
- **Python 3.9+**
- **Gemini API Key**: Get a free key at [Google AI Studio](https://aistudio.google.com/apikey). Set it as:
```bash
export GEMINI_API_KEY="your-key"
Expand Down
2 changes: 1 addition & 1 deletion skill/infographic-agent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: >
then gemini-3.1-flash-lite-image renders it into a PNG. No browser, Playwright, or Chromium dependencies —
the only requirement is Google's GenAI SDK. Fully portable to any agent CLI environment.
metadata:
version: "3.2.0"
version: "3.2.1"
author: "Infographic Agent contributors"
---

Expand Down
6 changes: 3 additions & 3 deletions skill/infographic-agent/bin/infographic-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function findPython() {
if (match) {
const major = parseInt(match[1], 10);
const minor = parseInt(match[2], 10);
if (major >= 3 && minor >= 8) return candidate;
if (major >= 3 && minor >= 9) return candidate;
}
}
}
Expand Down Expand Up @@ -120,7 +120,7 @@ Examples:

if (!python) {
err(
"Python 3.8+ is required but was not found on your PATH.\n\n" +
"Python 3.9+ is required but was not found on your PATH.\n\n" +
" Install Python from https://www.python.org/downloads/ and make sure\n" +
" it is on your PATH, then re-run:\n\n" +
" npx infographic-agent ..."
Expand All @@ -132,7 +132,7 @@ if (!python) {

if (doInstall) {
print("Installing Python dependencies (google-genai, pillow)...");
const result = run(python, ["-m", "pip", "install", "--upgrade", "google-genai", "pillow"]);
const result = run(python, ["-m", "pip", "install", "--upgrade", "google-genai>=1.47.0", "pillow"]);
if (result.status !== 0) process.exit(result.status);

print(`
Expand Down
2 changes: 1 addition & 1 deletion skill/infographic-agent/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "infographic-agent",
"version": "3.2.0",
"version": "3.2.1",
"description": "Generate professional infographic PNGs from text with Gemini — a research agent (gemini-3.5-flash) prepares and validates the prompt, then gemini-3.1-flash-lite-image renders it. No browser/Playwright deps.",
"license": "MIT",
"author": "Ryan Baumann",
Expand Down
32 changes: 28 additions & 4 deletions skill/infographic-agent/portable_infographic.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,11 +637,35 @@ def _normalize_to_png(data: bytes, mime: str):
return data, m


def _config_fields(config_type):
"""Return the declared fields across supported Pydantic SDK versions."""
return getattr(config_type, "model_fields", None) or getattr(config_type, "__fields__", {})


def _image_config(aspect: str, resolution: str):
"""Request image size only when the installed SDK exposes that field."""
fields = _config_fields(types.ImageConfig)
kwargs = {"aspect_ratio": aspect}
if "image_size" in fields:
kwargs["image_size"] = resolution
else:
warn("Installed google-genai does not expose image_size; using the model's native resolution.")
return types.ImageConfig(**kwargs)


def _thinking_config():
"""Use high thinking on current SDKs and dynamic thinking on older SDKs."""
fields = _config_fields(types.ThinkingConfig)
if "thinking_level" in fields:
return types.ThinkingConfig(thinking_level="HIGH", include_thoughts=True)
return types.ThinkingConfig(thinking_budget=-1, include_thoughts=True)


def generate_image(client, prompt: str, aspect: str, image_model: str, resolution: str):
config = types.GenerateContentConfig(
response_modalities=["TEXT", "IMAGE"],
image_config=types.ImageConfig(aspect_ratio=aspect, image_size=resolution),
thinking_config=types.ThinkingConfig(thinking_level="HIGH", include_thoughts=True),
image_config=_image_config(aspect, resolution),
thinking_config=_thinking_config(),
http_options=types.HttpOptions(timeout=180_000),
)
info(f"🎨 Generating the infographic ({image_model}) at {resolution}...")
Expand All @@ -656,8 +680,8 @@ def generate_image(client, prompt: str, aspect: str, image_model: str, resolutio
def refine_image(client, image_bytes: bytes, mime: str, instruction: str, aspect: str, image_model: str, resolution: str):
config = types.GenerateContentConfig(
response_modalities=["TEXT", "IMAGE"],
image_config=types.ImageConfig(aspect_ratio=aspect, image_size=resolution),
thinking_config=types.ThinkingConfig(thinking_level="HIGH", include_thoughts=True),
image_config=_image_config(aspect, resolution),
thinking_config=_thinking_config(),
http_options=types.HttpOptions(timeout=180_000),
)
contents = [
Expand Down
2 changes: 1 addition & 1 deletion skill/infographic-agent/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Runtime dependencies. Install with: pip install -r requirements.txt
# (or: pip install google-genai pillow)
google-genai>=1.0.0 # Gemini SDK — the two-agent research + image pipeline
google-genai>=1.47.0 # Gemini SDK — includes the typed image/thinking config surface tested here
pillow>=10.0.0 # transcodes the model's output to lossless PNG (crisp text)
62 changes: 62 additions & 0 deletions skill/infographic-agent/test_portable_infographic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import importlib.util
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch


SCRIPT_PATH = Path(__file__).with_name("portable_infographic.py")
SPEC = importlib.util.spec_from_file_location("portable_infographic", SCRIPT_PATH)
portable = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(portable)


def config_type(*field_names):
class Config:
model_fields = {name: object() for name in field_names}

def __init__(self, **kwargs):
self.kwargs = kwargs

return Config


class SDKConfigCompatibilityTest(unittest.TestCase):
def test_current_sdk_requests_resolution_and_high_thinking(self):
fake_types = SimpleNamespace(
ImageConfig=config_type("aspect_ratio", "image_size"),
ThinkingConfig=config_type("thinking_level", "include_thoughts"),
)

with patch.object(portable, "types", fake_types):
image = portable._image_config("16:9", "1K")
thinking = portable._thinking_config()

self.assertEqual(image.kwargs, {"aspect_ratio": "16:9", "image_size": "1K"})
self.assertEqual(
thinking.kwargs,
{"thinking_level": "HIGH", "include_thoughts": True},
)

def test_sdk_1_47_falls_back_to_supported_fields(self):
fake_types = SimpleNamespace(
ImageConfig=config_type("aspect_ratio"),
ThinkingConfig=config_type("thinking_budget", "include_thoughts"),
)

with patch.object(portable, "types", fake_types), patch.object(portable, "warn") as warn:
image = portable._image_config("16:9", "1K")
thinking = portable._thinking_config()

self.assertEqual(image.kwargs, {"aspect_ratio": "16:9"})
self.assertEqual(
thinking.kwargs,
{"thinking_budget": -1, "include_thoughts": True},
)
warn.assert_called_once_with(
"Installed google-genai does not expose image_size; using the model's native resolution."
)


if __name__ == "__main__":
unittest.main()
Loading