mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 04:40:45 +08:00
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。 Made-with: Cursor
156 lines
6.4 KiB
Python
156 lines
6.4 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
from typing import Any, Callable
|
|
|
|
from oclaw.platform.config.runtime_paths import runtime_extensions_root
|
|
from .shared_types import GatewayRequestHandlers
|
|
from .validation import error_shape
|
|
|
|
|
|
def _ok(respond, payload: Any) -> None:
|
|
if callable(respond):
|
|
respond(True, payload, None, None)
|
|
|
|
|
|
def _bad(respond, message: str) -> None:
|
|
if callable(respond):
|
|
respond(False, None, error_shape("INVALID_REQUEST", message), None)
|
|
|
|
|
|
def _unavailable(respond, message: str) -> None:
|
|
if callable(respond):
|
|
respond(False, None, error_shape("UNAVAILABLE", message), None)
|
|
|
|
|
|
def _fail(respond, code: str, message: str) -> None:
|
|
if callable(respond):
|
|
respond(False, None, error_shape(code, message), None)
|
|
|
|
|
|
_generate_image_fn: Callable[..., Any] | None = None
|
|
|
|
|
|
def _load_generate_image_fn() -> Callable[..., Any]:
|
|
global _generate_image_fn
|
|
if _generate_image_fn is not None:
|
|
return _generate_image_fn
|
|
file_path = (runtime_extensions_root() / "image-generation-core" / "api.py").resolve()
|
|
if not file_path.exists():
|
|
# Backward compatibility for legacy layout.
|
|
file_path = Path("runtime/extensions/image-generation-core/api.py").resolve()
|
|
spec = importlib.util.spec_from_file_location("gateway_image_generation_core_api", str(file_path))
|
|
if spec is None or spec.loader is None:
|
|
raise RuntimeError("failed to load image-generation-core api module")
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
fn = getattr(module, "generate_image", None)
|
|
if not callable(fn):
|
|
raise RuntimeError("image-generation-core.generate_image is not callable")
|
|
_generate_image_fn = fn
|
|
return fn
|
|
|
|
|
|
def _image_generate_handler(opts: dict[str, Any]) -> None:
|
|
respond = opts.get("respond")
|
|
params = opts.get("params")
|
|
context = opts.get("context")
|
|
if not isinstance(params, dict):
|
|
_bad(respond, "invalid image.generate params")
|
|
return
|
|
prompt = params.get("prompt")
|
|
if not isinstance(prompt, str) or not prompt.strip():
|
|
_bad(respond, "image.generate requires prompt")
|
|
return
|
|
provider = params.get("provider")
|
|
if provider is not None and (not isinstance(provider, str) or not provider.strip()):
|
|
_bad(respond, "image.generate provider must be a non-empty string when provided")
|
|
return
|
|
size = params.get("size")
|
|
if size is not None and (not isinstance(size, str) or not size.strip()):
|
|
_bad(respond, "image.generate size must be a non-empty string when provided")
|
|
return
|
|
quality = params.get("quality")
|
|
if quality is not None and (not isinstance(quality, str) or not quality.strip()):
|
|
_bad(respond, "image.generate quality must be a non-empty string when provided")
|
|
return
|
|
|
|
# Prefer explicit hook so app server can own the runtime.
|
|
hook = context.get("image_generate") if isinstance(context, dict) else None
|
|
if callable(hook):
|
|
try:
|
|
out = hook(params)
|
|
_ok(respond, out if isinstance(out, dict) else {"ok": True, "result": out})
|
|
except Exception as exc:
|
|
_unavailable(respond, str(exc))
|
|
return
|
|
|
|
# Fallback: use image-generation-core with providers passed through context/runtime snapshot.
|
|
providers: list[dict[str, Any]] = []
|
|
if isinstance(context, dict):
|
|
raw = context.get("image_generation_providers")
|
|
if isinstance(raw, list):
|
|
providers = [p for p in raw if isinstance(p, dict)]
|
|
elif callable(context.get("get_runtime_snapshot")):
|
|
try:
|
|
snap = context["get_runtime_snapshot"]()
|
|
if isinstance(snap, dict) and isinstance(snap.get("image_generation_providers"), list):
|
|
providers = [p for p in snap.get("image_generation_providers") if isinstance(p, dict)]
|
|
except Exception:
|
|
providers = []
|
|
# Keep only image-capable providers when capability is declared.
|
|
providers = [
|
|
p
|
|
for p in providers
|
|
if not isinstance(p.get("capabilities"), dict) or bool((p.get("capabilities") or {}).get("image_generation", True))
|
|
]
|
|
if provider and not any(str(p.get("id")) == provider.strip() for p in providers):
|
|
_fail(respond, "NOT_FOUND", f'image provider "{provider.strip()}" is not registered')
|
|
return
|
|
cfg: dict[str, Any] = {}
|
|
if isinstance(context, dict) and isinstance(context.get("config"), dict):
|
|
cfg = dict(context.get("config") or {})
|
|
image_cfg = cfg.get("image") if isinstance(cfg.get("image"), dict) else {}
|
|
default_provider = str((image_cfg or {}).get("defaultProvider") or "").strip()
|
|
priority = [str(x).strip() for x in ((image_cfg or {}).get("providerPriority") or []) if str(x).strip()]
|
|
|
|
ordered = list(providers)
|
|
if not provider:
|
|
if default_provider and any(str(p.get("id") or "") == default_provider for p in ordered):
|
|
ordered.sort(key=lambda p: 0 if str(p.get("id") or "") == default_provider else 1)
|
|
elif priority:
|
|
rank = {pid: idx for idx, pid in enumerate(priority)}
|
|
ordered.sort(key=lambda p: rank.get(str(p.get("id") or ""), 10_000))
|
|
runtime = {"image_generation_providers": ordered}
|
|
try:
|
|
generate_image = _load_generate_image_fn()
|
|
out = generate_image(
|
|
prompt=prompt.strip(),
|
|
provider_id=(provider.strip() if isinstance(provider, str) else None),
|
|
runtime=runtime,
|
|
size=(size.strip() if isinstance(size, str) else None),
|
|
quality=(quality.strip() if isinstance(quality, str) else None),
|
|
)
|
|
if isinstance(out, dict) and out.get("ok") is True:
|
|
_ok(respond, out)
|
|
return
|
|
if isinstance(out, dict):
|
|
err = str(out.get("error") or "image generation failed")
|
|
if err == "no_image_generation_provider_registered":
|
|
_fail(respond, "UNAVAILABLE", err)
|
|
return
|
|
if err == "provider_not_found":
|
|
_fail(respond, "NOT_FOUND", err)
|
|
return
|
|
_fail(respond, "UNAVAILABLE", err)
|
|
return
|
|
_unavailable(respond, "image generation failed")
|
|
except Exception as exc:
|
|
_unavailable(respond, str(exc))
|
|
|
|
|
|
image_handlers: GatewayRequestHandlers = {
|
|
"image.generate": _image_generate_handler,
|
|
}
|
|
|