Skill: pgyppp/coze-image (version 1.0.0)

--- name: coze-image description: Generate images using Coze AI platform. Supports text-to-image generation with automatic Base64 encoding for inline preview. Use when you need to create images from text prompts. metadata: { "openclaw": { "env": [ { "name": "IMAGE_API_TOKEN", "description": "Coze API authentication token", "required": true }, { "name": "IMAGE_API_URL", "description": "Coze stream_run endpoint", "default": "https://6fj9k4p9x3.coze.site/stream_run" }, { "name": "IMAGE_API_PROJECT_ID", "description": "Coze project ID", "default": "7621854258107039796" }, { "name": "IMAGE_API_SESSION_ID", "description": "Coze session ID", "default": "mT8SQeCGgTMZNBsJEiRuN" }, { "name": "IMAGE_API_TIMEOUT", "description": "Request timeout in seconds", "default": "60" }, ], }, "clawhub": { "version": "1.0.0", "author": "OpenClaw Community", "license": "MIT", "tags": ["image", "generation", "coze", "ai", "text-to-image"], "repository": "https://github.com/openclaw/skills", }, } --- # Coze Image Generation Skill Generate images from text prompts using the Coze AI platform. This skill handles the complete workflow: submitting prompts, parsing SSE responses, downloading images, and returning Base64-encoded data URIs for inline display. ## Usage ### Basic Usage ```python from coze_image_skill import run result = run({ "text": "一只可爱的小猫，毛茸茸的，大眼睛，坐在窗台上", "api_token": "your_coze_api_token" }) # Result contains: # - image: data:image/jpeg;base64,... (inline Base64) # - mime_type: image/jpeg # - filename: generated-image.jpeg # - source_url: original image URL ``` ### With Custom Configuration ```python result = run({ "prompt": "a cute orange cat playing on grass, sunny day", "api_token": "your_token", "project_id": "your_project_id", "session_id": "your_session_id", "timeout": 90, "include_debug": True }) ``` ## Environment Variables Set these in your OpenClaw configuration or `.env` file: | Variable | Description | Default | |----------|-------------|---------| | `IMAGE_API_TOKEN` | Coze API authentication token | Required | | `IMAGE_API_URL` | Coze stream_run endpoint | `https://6fj9k4p9x3.coze.site/stream_run` | | `IMAGE_API_PROJECT_ID` | Coze project ID | `7621854258107039796` | | `IMAGE_API_SESSION_ID` | Coze session ID | `mT8SQeCGgTMZNBsJEiRuN` | | `IMAGE_API_TIMEOUT` | Request timeout in seconds | `60` | ## Parameters | Parameter | Type | Description | |-----------|------|-------------| | `text` or `prompt` | string | Image generation prompt (required) | | `api_token` | string | Coze API token (or use env var) | | `project_id` | string | Coze project ID (or use env var) | | `session_id` | string | Coze session ID (or use env var) | | `timeout` | int | Request timeout in seconds | | `include_debug` | bool | Include debug info in response | | `strict` | bool | Raise exceptions instead of returning error object | ## Response Format ### Success ```json { "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg...", "mime_type": "image/jpeg", "filename": "generated-image.jpeg", "source_url": "https://..." } ``` ### Error ```json { "error": "Error message describing what went wrong", "image": null, "mime_type": null, "filename": null, "source_url": null } ``` ## Features - **SSE Streaming**: Handles Coze's Server-Sent Events response format - **Auto Download**: Automatically downloads generated images and converts to Base64 - **Error Handling**: Graceful error handling with structured error responses - **Flexible Auth**: Supports both inline token and environment variables - **Debug Mode**: Optional debug output for troubleshooting ## Setup on ClawHub 1. Install the skill via ClawHub: ```bash openclaw skills install coze-image ``` 2. Configure your API token: ```bash openclaw config set IMAGE_API_TOKEN your_token_here ``` 3. Generate your first image: ``` Generate a picture of a sunset over the ocean ``` ## Troubleshooting ### "Image URL not found in SSE response" This means the Coze project returned text instead of an image. Make sure: - Your Coze bot has an image generation plugin enabled - The workflow is configured to return images - The prompt is appropriate for image generation ### Authentication Errors - Verify your API token is valid and not expired - Check that the token has permission to access the project - Ensure environment variables are set correctly ### Timeout Errors - Increase the timeout parameter (default 60s) - Check your network connection - The image generation may be taking longer than expected ## License MIT License - See license file for details. ## Support For issues or questions, please open an issue on the ClawHub repository.

path: agents/openai.yaml
content: interface:
  display_name: "Coze Image Skill"
  short_description: "Return Coze-generated images as Base64 for OpenClaw."
  default_prompt: "Use $coze-image-skill to call a Coze image project and return the generated image as an inline Base64 data URI."

policy:
  allow_implicit_invocation: true


path: package.json
content: {
  "name": "coze-image",
  "version": "1.0.0",
  "description": "Generate images using Coze AI platform with automatic Base64 encoding",
  "author": "OpenClaw Community",
  "license": "MIT",
  "keywords": [
    "openclaw",
    "skill",
    "image",
    "generation",
    "coze",
    "ai",
    "text-to-image"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/openclaw/skills.git",
    "directory": "coze-image"
  },
  "engines": {
    "python": ">=3.8",
    "openclaw": ">=1.0.0"
  },
  "dependencies": {
    "requests": "*"
  },
  "clawhub": {
    "type": "skill",
    "entryPoint": "coze_image_skill.py",
    "skillName": "coze-image",
    "category": "media",
    "autoUpdate": true
  }
}


path: README.md
content: # Coze Image Skill for OpenClaw

Generate images from text prompts using the Coze AI platform.

## Installation

### Via ClawHub (Recommended)

```bash
# Install ClawHub CLI if not already installed
npm install -g clawhub

# Install the skill
clawhub install coze-image

# Or sync to latest version
clawhub sync coze-image
```

### Manual Installation

Copy the skill folder to your OpenClaw skills directory:

```bash
# Windows
cp -r coze-image %APPDATA%\npm\node_modules\openclaw\skills\

# macOS/Linux
cp -r coze-image ~/.openclaw/skills/
```

## Configuration

Set the following environment variables in your OpenClaw configuration:

```bash
openclaw config set IMAGE_API_TOKEN your_coze_api_token
```

Optional configuration:

```bash
openclaw config set IMAGE_API_URL https://6fj9k4p9x3.coze.site/stream_run
openclaw config set IMAGE_API_PROJECT_ID 7621854258107039796
openclaw config set IMAGE_API_SESSION_ID mT8SQeCGgTMZNBsJEiRuN
openclaw config set IMAGE_API_TIMEOUT 60
```

## Usage

Once installed, you can generate images by simply asking:

```
Generate a picture of a cute cat playing on grass
```

Or use the skill directly:

```python
from coze_image_skill import run

result = run({
    "text": "a beautiful sunset over the ocean",
    "api_token": "your_token"  # Optional if env var is set
})

# Result contains Base64-encoded image
print(result["image"])  # data:image/jpeg;base64,...
```

## Features

- Text-to-image generation via Coze AI
- Automatic Base64 encoding for inline preview
- Support for custom Coze projects
- Graceful error handling
- Debug mode for troubleshooting

## Requirements

- Python 3.8+
- `requests` library
- Coze API access token

## License

MIT License

## Support

For issues or questions, please visit the ClawHub repository or OpenClaw community.


path: references/payload-shape.md
content: # Coze Image Skill Payload

## Success

```json
{
  "image": "data:image/png;base64,AAAA...",
  "mime_type": "image/png",
  "filename": "generated-image.png",
  "source_url": "https://..."
}
```

## Failure

```json
{
  "error": "Image URL not found in SSE response. Upstream assistant replied: ...",
  "image": null,
  "mime_type": null,
  "filename": null,
  "source_url": null
}
```


path: scripts/coze_image_skill.py
content: import base64
import json
import os
import re
from typing import Any, Dict, Optional, Tuple

import requests


class CozeImageSkill:
    def __init__(
        self,
        api_token: str,
        *,
        url: str,
        project_id: str,
        session_id: str,
        timeout: int = 60,
    ) -> None:
        self.url = url
        self.project_id = project_id
        self.session_id = session_id
        self.timeout = timeout
        self.headers = {
            "Authorization": f"Bearer {api_token}",
            "Content-Type": "application/json",
            "Accept": "text/event-stream",
        }

    def _build_payload(self, prompt: str) -> Dict[str, Any]:
        return {
            "content": {
                "query": {
                    "prompt": [
                        {
                            "type": "text",
                            "content": {
                                "text": prompt.strip(),
                            },
                        }
                    ]
                }
            },
            "type": "query",
            "session_id": self.session_id,
            "project_id": self.project_id,
        }

    def _find_url_in_string(self, value: str) -> Optional[str]:
        match = re.search(r"https?://\S+", value)
        if not match:
            return None
        return match.group(0).rstrip("),]}>\"'")

    def _extract_image_url(self, value: Any) -> Optional[str]:
        if isinstance(value, str):
            return self._find_url_in_string(value)

        if isinstance(value, list):
            for item in value:
                found = self._extract_image_url(item)
                if found:
                    return found
            return None

        if isinstance(value, dict):
            # Check direct URL fields
            for key in ("url", "image_url", "imageUrl"):
                nested = value.get(key)
                if isinstance(nested, str) and nested.startswith("http"):
                    return nested

            # Special handling for tool_response.result which may contain URL text
            tool_response = value.get("tool_response")
            if isinstance(tool_response, dict):
                result = tool_response.get("result")
                if isinstance(result, str):
                    # Look for URL pattern in result text (e.g., "图片生成成功，图片 URL: https://...")
                    found = self._find_url_in_string(result)
                    if found:
                        return found

            # Recurse into common nested fields
            for key in ("content", "output", "result", "data", "tool_result", "tool_response"):
                found = self._extract_image_url(value.get(key))
                if found:
                    return found

        return None

    def _stream_for_image_url(self, prompt: str) -> Tuple[str, str]:
        response = requests.post(
            self.url,
            headers=self.headers,
            json=self._build_payload(prompt),
            stream=True,
            timeout=self.timeout,
        )
        response.raise_for_status()

        answer_chunks = []

        for line in response.iter_lines(decode_unicode=True):
            if not line or not line.startswith("data:"):
                continue

            payload = line[5:].strip()
            if not payload or payload == "[DONE]":
                continue

            try:
                event_data = json.loads(payload)
            except json.JSONDecodeError:
                continue

            content = event_data.get("content")
            if not isinstance(content, dict):
                content = event_data.get("data", {}).get("content", {})

            answer_piece = content.get("answer") if isinstance(content, dict) else None
            if isinstance(answer_piece, str):
                answer_chunks.append(answer_piece)

            if event_data.get("type") == "error":
                raise RuntimeError(str(content.get("error") if isinstance(content, dict) else "Upstream SSE returned an error event"))

            image_url = self._extract_image_url(event_data)
            if image_url:
                return image_url, "".join(answer_chunks).strip()

        assistant_text = "".join(answer_chunks).strip()
        if assistant_text:
            raise RuntimeError(f"Image URL not found in SSE response. Upstream assistant replied: {assistant_text}")
        raise RuntimeError("Image URL not found in SSE response")

    def _download_as_data_uri(self, image_url: str) -> Tuple[str, str]:
        response = requests.get(image_url, timeout=self.timeout)
        response.raise_for_status()
        mime_type = response.headers.get("Content-Type", "image/png").split(";")[0].strip()
        image_base64 = base64.b64encode(response.content).decode("utf-8")
        return f"data:{mime_type};base64,{image_base64}", mime_type

    def generate(self, prompt: str) -> Dict[str, str]:
        if not prompt or not prompt.strip():
            raise ValueError("prompt is required")

        image_url, answer_text = self._stream_for_image_url(prompt)
        image_data_uri, mime_type = self._download_as_data_uri(image_url)
        extension = mime_type.split("/")[-1] if "/" in mime_type else "png"
        result = {
            "image": image_data_uri,
            "mime_type": mime_type,
            "filename": f"generated-image.{extension}",
            "source_url": image_url,
        }
        if answer_text:
            result["answer_text"] = answer_text
        return result


def _bool_param(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        return value.strip().lower() in {"1", "true", "yes", "on"}
    return bool(value)


def run(params: Dict[str, Any]) -> Dict[str, Any]:
    prompt = params.get("text") or params.get("prompt") or ""
    api_token = params.get("api_token") or os.environ.get("IMAGE_API_TOKEN")
    api_url = params.get("api_url") or os.environ.get("IMAGE_API_URL", "https://6fj9k4p9x3.coze.site/stream_run")
    project_id = params.get("project_id") or os.environ.get("IMAGE_API_PROJECT_ID", "7621854258107039796")
    session_id = params.get("session_id") or os.environ.get("IMAGE_API_SESSION_ID", "mT8SQeCGgTMZNBsJEiRuN")
    timeout = int(params.get("timeout") or os.environ.get("IMAGE_API_TIMEOUT", "60"))
    include_debug = _bool_param(params.get("include_debug"))
    strict_mode = _bool_param(params.get("strict"))

    try:
        if not api_token:
            raise RuntimeError("Missing IMAGE_API_TOKEN environment variable or params['api_token']")
        if not project_id:
            raise RuntimeError("Missing IMAGE_API_PROJECT_ID environment variable or params['project_id']")
        if not session_id:
            raise RuntimeError("Missing IMAGE_API_SESSION_ID environment variable or params['session_id']")

        client = CozeImageSkill(
            api_token,
            url=api_url,
            project_id=project_id,
            session_id=session_id,
            timeout=timeout,
        )
        result = client.generate(prompt)
        if include_debug:
            result["debug"] = {
                "api_url": api_url,
                "project_id": project_id,
                "session_id": session_id,
            }
        return result
    except Exception as exc:
        if strict_mode:
            raise
        return {
            "error": str(exc),
            "image": None,
            "mime_type": None,
            "filename": None,
            "source_url": None,
        }


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)