Simplify oclaw product surface: drop niche specialists and admin noise.

Remove stock/image/video specialists, Gmail watcher, CocoLoop market, desktop packaging, and extra memory plugins; disable dynamic agents and hide Session Monitor / API grants / Admin Audit from nav.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-11 01:34:10 +08:00
parent efa72df362
commit cf31fd104d
290 changed files with 127 additions and 64576 deletions

View file

@ -141,20 +141,19 @@ class AdminSkillsApiTests(unittest.TestCase):
gb = g.json() or {}
self.assertTrue(gb.get("ok"))
self.assertIn("market_provider", gb)
self.assertIn(str(gb.get("market_provider") or ""), {"clawhub", "cocoloop"})
self.assertIn(str(gb.get("market_provider") or ""), {"clawhub"})
s = self.client.post(
"/admin/api/skills/mode",
json={"prompt_in_system": True, "toolcall_enabled": False, "market_provider": "cocoloop"},
json={"prompt_in_system": True, "market_provider": "cocoloop"},
headers=self._h(),
)
self.assertEqual(s.status_code, 200, s.text)
sb = s.json() or {}
self.assertTrue(sb.get("ok"))
self.assertTrue(bool(sb.get("prompt_in_system")))
self.assertFalse(bool(sb.get("toolcall_enabled")))
self.assertEqual(str(sb.get("market_provider") or ""), "cocoloop")
self.assertEqual(str(sb.get("market_provider") or ""), "clawhub")
g2 = self.client.get("/admin/api/skills/mode", headers=self._h())
self.assertEqual((g2.json() or {}).get("market_provider"), "cocoloop")
self.assertEqual((g2.json() or {}).get("market_provider"), "clawhub")
def test_skills_effective_dashboard(self) -> None:
c = self.client.post(

View file

@ -1,60 +0,0 @@
"""Image specialist lane must use ``AIA_IMAGE_EXPERT_*``, not ``AIA_OCR_*``."""
from __future__ import annotations
from svc.llm.image_legacy_client import send_legacy_image_messages
def test_legacy_fails_when_only_ocr_env_configured(monkeypatch: object) -> None:
monkeypatch.setenv("AIA_OCR_API_KEY", "k-ocr")
monkeypatch.setenv("AIA_OCR_BASE_URL", "https://ocr.example/v1")
monkeypatch.setenv("AIA_OCR_MODEL", "ocr-model")
for k in ("AIA_IMAGE_EXPERT_API_KEY", "AIA_IMAGE_EXPERT_BASE_URL", "AIA_IMAGE_EXPERT_MODEL"):
monkeypatch.delenv(k, raising=False)
out = send_legacy_image_messages(
images=["https://example.com/x.png"],
prompt="hi",
model=None,
api_key=None,
base_url=None,
)
assert out.get("ok") is False
err = str(out.get("error") or "")
assert "AIA_IMAGE_EXPERT" in err
def test_legacy_accepts_explicit_kwargs_without_expert_env(monkeypatch: object) -> None:
"""Explicit api_key/base_url/model override env (for scripts/tests only)."""
for k in (
"AIA_OCR_API_KEY",
"AIA_OCR_BASE_URL",
"AIA_IMAGE_EXPERT_API_KEY",
"AIA_IMAGE_EXPERT_BASE_URL",
):
monkeypatch.delenv(k, raising=False)
recorded: dict[str, object] = {}
def fake_post(client, *, url: str, headers: dict[str, str], payload: dict[str, object]) -> object:
recorded["url"] = url
class R:
status_code = 200
def json(_self): # noqa: PLR6301
return {"choices": [{"message": {"content": [{"text": "ok"}]}}]}
return R()
import svc.llm.image_legacy_client as mod
monkeypatch.setattr(mod, "post_with_retry", fake_post)
out = send_legacy_image_messages(
images=["https://example.com/x.png"],
prompt="hello",
model="m-special",
api_key="explicit-key",
base_url="https://gw.example/expert/v1",
)
assert out.get("ok") is True
assert "/chat/completions" in str(recorded.get("url") or "")

View file

@ -1,388 +0,0 @@
from __future__ import annotations
import json
import pytest
from svc.llm.image_http_common import (
dashscope_multimodal_http_ok,
dashscope_native_multimodal_url_from_compatible_base,
extract_text_and_images,
)
from svc.llm.image_http_common import build_extract_diag_empty
from svc.llm.image_legacy_client import (
collect_legacy_lane_images_from_attachments,
collect_legacy_lane_images_with_session_fallback,
legacy_image_assistant_body_with_placeholder,
legacy_image_turn_bundle,
normalize_legacy_output_image_urls,
parse_message_attachments_json,
)
from svc.llm.image_legacy_client import _http_content_blocks
from svc.llm.image_legacy_client import _openai_compatible_vision_content
def test_openai_compatible_vision_has_type_per_part() -> None:
"""compatible-mode /chat/completions expects type on each content element."""
b = _openai_compatible_vision_content(
["https://example.invalid/a.png", "data:image/png;base64,abcd"],
"prompt",
)
assert len(b) == 3
assert b[0]["type"] == "image_url" and "url" in b[0]["image_url"]
assert b[1]["type"] == "image_url"
assert b[2]["type"] == "text" and b[2]["text"] == "prompt"
def test_http_content_blocks_multi_image_then_text() -> None:
"""DashScope samples: one user message, content = N × {"image": url} then {"text": ...}."""
b = _http_content_blocks(
[
"https://example.invalid/a.png",
"https://example.invalid/b.png",
],
"合成说明",
typed=False,
)
assert b == [
{"image": "https://example.invalid/a.png"},
{"image": "https://example.invalid/b.png"},
{"text": "合成说明"},
]
def test_normalize_legacy_output_dict_image_parts() -> None:
u = "https://dashscope-result-sz.oss-cn-shenzhen.aliyuncs.com/x.png?Expires=1"
assert normalize_legacy_output_image_urls([{"image": u}]) == [u]
assert normalize_legacy_output_image_urls([{"image_url": {"url": u}}]) == [u]
def test_legacy_turn_bundle_coerces_dict_images_to_attachments() -> None:
ok, text, att = legacy_image_turn_bundle(
{"ok": True, "text": "", "images": [{"image": "https://example.invalid/a.png"}]}
)
assert ok is True
assert len(att) == 1
assert att[0]["type"] == "image_url"
assert att[0]["url"] == "https://example.invalid/a.png"
def test_dashscope_native_url_from_compatible_base() -> None:
assert (
dashscope_native_multimodal_url_from_compatible_base(
"https://dashscope.aliyuncs.com/compatible-mode/v1"
)
== "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
)
assert (
dashscope_native_multimodal_url_from_compatible_base("https://example.com/openai/v1") is None
)
def test_collect_legacy_lane_images_image_url() -> None:
atts = [{"type": "image_url", "url": "https://example.invalid/x.png"}]
assert collect_legacy_lane_images_from_attachments(atts) == ["https://example.invalid/x.png"]
def test_collect_legacy_lane_images_raw_base64() -> None:
atts = [{"type": "input_image", "mime": "image/png", "image_base64": "SGVsbG8="}]
got = collect_legacy_lane_images_from_attachments(atts)
assert len(got) == 1
assert got[0].startswith("data:image/png;base64,")
def test_legacy_turn_bundle_text_only_success() -> None:
ok, text, att = legacy_image_turn_bundle({"ok": True, "text": "caption only", "images": []})
assert ok is True
assert text == "caption only"
assert att == []
def test_legacy_image_assistant_placeholder_zh_en() -> None:
produced = [{"type": "image_ref", "attachment_id": "a" * 64}]
assert "附件" in legacy_image_assistant_body_with_placeholder(
lang="zh", body_text="", produced=produced
)
assert "attachment" in legacy_image_assistant_body_with_placeholder(
lang="en", body_text="", produced=produced
).lower()
assert legacy_image_assistant_body_with_placeholder(lang="zh", body_text="x", produced=produced) == "x"
def test_legacy_turn_bundle_upstream_error() -> None:
ok, text, att = legacy_image_turn_bundle({"ok": False, "error": "rate"})
assert ok is False
assert "rate" in text
assert att == []
def test_extract_harvests_nested_https_under_message() -> None:
url = "https://cdn.example.invalid/generated.png"
text, images = extract_text_and_images(
{
"choices": [
{
"message": {
"role": "assistant",
"content": None,
"metadata": {"preview_image": url},
}
}
]
}
)
assert text == ""
assert images == [url]
def test_build_extract_diag_top_level_openai_choices() -> None:
d = build_extract_diag_empty(
{
"choices": [],
"model": "x",
}
)
assert d.get("choices_len") == 0
def test_legacy_turn_bundle_includes_provider_redacted() -> None:
ok, msg, att = legacy_image_turn_bundle(
{
"ok": True,
"text": "",
"images": [],
"extract_diag": {"choices_len": 0},
"provider_response_redacted": '{"choices":[]}',
}
)
assert ok is False
assert "provider_json=" in msg
assert att == []
def test_legacy_turn_bundle_empty_ok_response_fails() -> None:
ok, text, att = legacy_image_turn_bundle({"ok": True, "text": "", "images": []})
assert ok is False
assert att == []
def test_extract_text_and_images_dashscope_output_wrapper() -> None:
"""Native multimodal HTTP wraps ``choices`` under ``output`` (not top-level OpenAI shape)."""
url = "https://dashscope-result-hz.oss-cn-hangzhou.aliyuncs.com/x.png?Expires=1"
payload = {
"status_code": 200,
"request_id": "959afba6-544e-487e-b58a-6bd9fea97xxx",
"code": "",
"message": "",
"output": {
"text": None,
"finish_reason": None,
"choices": [
{
"finish_reason": "stop",
"message": {
"role": "assistant",
"content": [{"image": url}],
},
}
],
"audio": None,
},
"usage": {
"input_tokens": 0,
"output_tokens": 0,
"image_count": 1,
"width": 2048,
"height": 2048,
},
}
text, images = extract_text_and_images(payload)
assert text == ""
assert images == [url]
assert dashscope_multimodal_http_ok(payload)[0] is True
def test_extract_text_and_images_content_dict_not_list() -> None:
"""Some gateways return a single object for ``message.content`` instead of an array."""
url = "https://dashscope-result-hz.oss-cn-hangzhou.aliyuncs.com/out.png"
text, images = extract_text_and_images(
{
"output": {
"choices": [
{
"message": {
"role": "assistant",
"content": {"image": url},
}
}
]
}
}
)
assert text == ""
assert images == [url]
def test_extract_text_and_images_messages_fallback() -> None:
text, images = extract_text_and_images(
{
"output": {
"messages": [
{"role": "user", "content": "x"},
{
"role": "assistant",
"content": [{"image": "https://example.invalid/gen.png"}],
},
]
}
}
)
assert images == ["https://example.invalid/gen.png"]
def test_extract_text_and_images_typed_image_url_part() -> None:
text, images = extract_text_and_images(
{
"choices": [
{
"message": {
"content": [
{
"type": "image_url",
"image_url": {"url": "https://example.invalid/v.png"},
}
]
}
}
]
}
)
assert images == ["https://example.invalid/v.png"]
def test_dashscope_envelope_rejects_non_success_code() -> None:
ok, msg = dashscope_multimodal_http_ok({"code": "InvalidParameter", "message": "bad"})
assert ok is False
assert "bad" in msg
def test_extract_text_and_images_openai_top_level_unchanged() -> None:
text, images = extract_text_and_images(
{
"choices": [
{
"message": {
"content": [
{"type": "text", "text": "hi"},
{"image": "https://example.invalid/a.jpg"},
]
}
}
]
}
)
assert "hi" in text
assert images == ["https://example.invalid/a.jpg"]
def test_parse_message_attachments_json_string_list() -> None:
raw = json.dumps([{"type": "image_url", "url": "https://example.invalid/z.png"}])
got = parse_message_attachments_json(raw)
assert len(got) == 1 and got[0]["url"] == "https://example.invalid/z.png"
class _FakeHistMsg:
__slots__ = ("role", "attachments")
def __init__(self, role: str, attachments: object) -> None:
self.role = role
self.attachments = attachments
class _FakeHistStore:
def __init__(self, msgs: list[_FakeHistMsg]) -> None:
self._msgs = msgs
def get_messages(self, session_id: str, limit: int = 200) -> list[_FakeHistMsg]:
_ = session_id
return self._msgs[-limit:] if len(self._msgs) > limit else list(self._msgs)
def test_session_fallback_prefers_latest_assistant_images() -> None:
"""Newest assistant row with images wins over older user uploads."""
msgs = [
_FakeHistMsg(
"user",
json.dumps([{"type": "image_url", "url": "https://example.invalid/old-user.png"}]),
),
_FakeHistMsg(
"assistant",
json.dumps([{"type": "image_url", "url": "https://example.invalid/from-assistant.png"}]),
),
_FakeHistMsg("user", "null"),
]
store = _FakeHistStore(msgs)
imgs, src = collect_legacy_lane_images_with_session_fallback(
store=store,
session_id="s1",
attachments=[],
)
assert src == "assistant_history"
assert imgs == ["https://example.invalid/from-assistant.png"]
def test_session_fallback_user_history_when_no_assistant_images() -> None:
msgs = [
_FakeHistMsg(
"user",
json.dumps([{"type": "image_url", "url": "https://example.invalid/only-user.png"}]),
),
_FakeHistMsg("assistant", "[]"),
_FakeHistMsg("user", "null"),
]
store = _FakeHistStore(msgs)
imgs, src = collect_legacy_lane_images_with_session_fallback(
store=store,
session_id="s1",
attachments=[],
)
assert src == "user_history"
assert imgs == ["https://example.invalid/only-user.png"]
def test_session_fallback_disabled_env(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("AIA_IMAGE_SPECIALIST_SESSION_IMAGE_FALLBACK", "0")
msgs = [
_FakeHistMsg(
"assistant",
json.dumps([{"type": "image_url", "url": "https://example.invalid/a.png"}]),
),
_FakeHistMsg("user", "null"),
]
store = _FakeHistStore(msgs)
imgs, src = collect_legacy_lane_images_with_session_fallback(
store=store,
session_id="s1",
attachments=[],
)
assert src == ""
assert imgs == []
def test_session_fallback_current_attachments_skip_history(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("AIA_IMAGE_SPECIALIST_SESSION_IMAGE_FALLBACK", raising=False)
msgs = [
_FakeHistMsg(
"assistant",
json.dumps([{"type": "image_url", "url": "https://example.invalid/hist.png"}]),
),
_FakeHistMsg("user", "null"),
]
store = _FakeHistStore(msgs)
imgs, src = collect_legacy_lane_images_with_session_fallback(
store=store,
session_id="s1",
attachments=[{"type": "image_url", "url": "https://example.invalid/current.png"}],
)
assert src == "current"
assert imgs == ["https://example.invalid/current.png"]

View file

@ -509,7 +509,7 @@ def test_gateway_comprehensive_mode_has_manager_final_pass(monkeypatch: pytest.M
assert out.reply_text == "final_from_manager"
def test_gateway_comprehensive_mode_dynamic_agent_dispatches_instruction_only(monkeypatch: pytest.MonkeyPatch) -> None:
def test_gateway_comprehensive_mode_dynamic_agent_falls_back_to_generalist(monkeypatch: pytest.MonkeyPatch) -> None:
class Store:
def get_setting(self, _k: str) -> str:
return ""
@ -538,21 +538,26 @@ def test_gateway_comprehensive_mode_dynamic_agent_dispatches_instruction_only(mo
"runtime.gateway.get_manager_prompt_prebuild",
lambda **kwargs: {
"manager_context": "manager",
"allowed_fixed": ("generalist", "ops", "memory", "image"),
"allowed_fixed_quoted": '"generalist", "ops", "memory", "image"',
"allowed_fixed": ("generalist", "ops", "memory"),
"allowed_fixed_quoted": '"generalist", "ops", "memory"',
},
)
monkeypatch.setattr("runtime.gateway.build_ephemeral_executor", lambda *args, **kwargs: _Exec(model=object()))
captured: dict = {}
def _run_agent_core(**kwargs):
data = kwargs.get("data")
captured["exec_text"] = getattr(getattr(data, "msg", None), "text", None)
return SimpleNamespace(outcome=SimpleNamespace(final_text="dynamic_specialist_answer"))
return SimpleNamespace(outcome=SimpleNamespace(final_text="generalist_answer", turn_uuid="t1"))
monkeypatch.setattr("runtime.gateway.run_agent_core", _run_agent_core)
chosen: dict[str, str] = {}
def _factory(sid: str):
chosen["sid"] = sid
return _Exec(model=object())
gw = OclawGateway(store=Store())
msg = StandardMessage(
session_id="sid-dyn-1",
@ -564,9 +569,11 @@ def test_gateway_comprehensive_mode_dynamic_agent_dispatches_instruction_only(mo
attachments=[],
metadata={"interaction_mode": "comprehensive"},
)
out = gw.handle_turn(msg=msg, lang="en", executor=_Exec(model=_ManagerModel()))
out = gw.handle_turn(msg=msg, lang="en", executor=_Exec(model=_ManagerModel()), specialist_executor_factory=_factory)
assert out.interaction_mode == "comprehensive"
assert out.selected_specialist == "dyn:sql"
assert out.selected_specialist == "generalist"
assert chosen.get("sid") == "generalist"
assert out.dispatch_reason == "dynamic_agent_disabled_fallback"
assert captured.get("exec_text") == "Write a SQL query to compute daily active users."

View file

@ -1,18 +0,0 @@
from __future__ import annotations
from runtime.hooks.gmail_watcher import start_gmail_watcher
def test_start_gmail_watcher_top_level_hooks_disabled() -> None:
r = start_gmail_watcher({"hooks": {"enabled": False, "internal": {"enabled": True}}})
assert r.started is False and r.reason == "hooks not enabled"
def test_start_gmail_watcher_internal_disabled() -> None:
r = start_gmail_watcher({"hooks": {"internal": {"enabled": False}}})
assert r.started is False and r.reason == "hooks not enabled"
def test_start_gmail_watcher_no_account() -> None:
r = start_gmail_watcher({"hooks": {"internal": {"enabled": True}, "gmail": {}}})
assert r.started is False and r.reason == "no gmail account configured"

View file

@ -1,99 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass, field
from runtime.hooks.gmail_watcher import GmailWatcherResult
from runtime.hooks.gmail_watcher_lifecycle import start_gmail_watcher_with_logs
@dataclass
class _Log:
infos: list[str] = field(default_factory=list)
warns: list[str] = field(default_factory=list)
errors: list[str] = field(default_factory=list)
def info(self, msg: str) -> None:
self.infos.append(msg)
def warn(self, msg: str) -> None:
self.warns.append(msg)
def error(self, msg: str) -> None:
self.errors.append(msg)
def test_gmail_lifecycle_logs_startup_success() -> None:
log = _Log()
start_gmail_watcher_with_logs(
cfg={},
log=log,
starter=lambda _cfg: GmailWatcherResult(started=True),
)
assert log.infos == ["gmail watcher started"]
assert not log.warns
assert not log.errors
def test_gmail_lifecycle_logs_actionable_reason() -> None:
log = _Log()
start_gmail_watcher_with_logs(
cfg={},
log=log,
starter=lambda _cfg: GmailWatcherResult(started=False, reason="auth failed"),
)
assert log.warns == ["gmail watcher not started: auth failed"]
def test_gmail_lifecycle_suppresses_expected_reason() -> None:
log = _Log()
start_gmail_watcher_with_logs(
cfg={},
log=log,
starter=lambda _cfg: GmailWatcherResult(started=False, reason="hooks not enabled"),
)
assert not log.warns
def test_gmail_lifecycle_supports_skip_callback_oclaw_env(monkeypatch) -> None:
monkeypatch.setenv("OCLAW_SKIP_GMAIL_WATCHER", "1")
monkeypatch.delenv("OPENCLAW_SKIP_GMAIL_WATCHER", raising=False)
log = _Log()
called = {"skip": 0}
start_gmail_watcher_with_logs(
cfg={},
log=log,
on_skipped=lambda: called.__setitem__("skip", called["skip"] + 1),
starter=lambda _cfg: GmailWatcherResult(started=True),
)
assert called["skip"] == 1
assert not log.infos
def test_gmail_lifecycle_supports_skip_callback(monkeypatch) -> None:
monkeypatch.delenv("OCLAW_SKIP_GMAIL_WATCHER", raising=False)
monkeypatch.setenv("OPENCLAW_SKIP_GMAIL_WATCHER", "1")
log = _Log()
called = {"skip": 0}
start_gmail_watcher_with_logs(
cfg={},
log=log,
on_skipped=lambda: called.__setitem__("skip", called["skip"] + 1),
starter=lambda _cfg: GmailWatcherResult(started=True),
)
assert called["skip"] == 1
assert not log.infos
def test_gmail_lifecycle_logs_startup_error() -> None:
log = _Log()
def _raise(_cfg):
raise RuntimeError("boom")
start_gmail_watcher_with_logs(
cfg={},
log=log,
starter=_raise,
)
assert log.errors == ["gmail watcher failed to start: boom"]

View file

@ -1,29 +0,0 @@
from __future__ import annotations
from pathlib import Path
from runtime.hooks_runtime import _reset_hooks_runtime_state_for_test, initialize_hooks_runtime
def test_initialize_hooks_runtime_invokes_gmail_lifecycle(monkeypatch, tmp_path: Path) -> None:
_reset_hooks_runtime_state_for_test()
calls: list[object] = []
def _cap(*, cfg, log, on_skipped=None, starter=None):
calls.append(cfg)
monkeypatch.setattr(
"runtime.hooks.gmail_watcher_lifecycle.start_gmail_watcher_with_logs",
_cap,
)
bundled = tmp_path / "bundled"
bundled.mkdir()
monkeypatch.setattr("runtime.hooks_runtime.runtime_hooks_bundled_root", lambda: str(bundled))
monkeypatch.setattr("runtime.hooks.merge_skill_hook_dirs.discover_workspace_skill_manifests", lambda: ())
ws = tmp_path / "ws"
ws.mkdir()
cfg = {"hooks": {"internal": {"enabled": True}}}
initialize_hooks_runtime(cfg=cfg, workspace_dir=str(ws))
assert len(calls) == 1
assert isinstance(calls[0], dict)

View file

@ -106,15 +106,15 @@ def test_interaction_mode_normalization_supports_legacy_values() -> None:
def test_requested_specialist_normalization_defaults_to_generalist() -> None:
assert normalize_requested_specialist("ops") == "ops"
# "stock" is a dynamic specialist discovered from runtime workspaces.
assert normalize_requested_specialist("stock") == "stock"
assert normalize_requested_specialist("image") == "image"
# Unknown ids fall back to generalist.
# Removed specialists map to generalist.
assert normalize_requested_specialist("stock") == "generalist"
assert normalize_requested_specialist("image") == "generalist"
assert normalize_requested_specialist("video") == "generalist"
assert normalize_requested_specialist("memory") == "memory"
assert normalize_requested_specialist("unknown") == "generalist"
def test_router_video_expert_sync_despite_long_attachments() -> None:
def test_router_removed_video_expert_uses_normal_routing() -> None:
long_text = "x" * 150
msg = StandardMessage(
session_id="s1",
@ -127,9 +127,8 @@ def test_router_video_expert_sync_despite_long_attachments() -> None:
metadata={"interaction_mode": "expert", "selected_specialist": "video"},
)
d = decide_route(msg)
assert d.mode == "sync_direct"
assert d.reason == "video_expert_legacy_lane"
assert d.requested_specialist == "video"
assert d.requested_specialist == "generalist"
assert d.reason != "video_expert_legacy_lane"
def test_router_carries_interaction_mode_and_requested_specialist() -> None:

View file

@ -78,6 +78,7 @@ def test_skill_market_install_tool_provider_arg_overrides_setting(monkeypatch, t
tool = skill_market_install_tool()
result = tool.handler({"slug": "demo", "provider": "cocoloop", "version": "latest"})
assert bool(result.get("ok")) is True
assert captured["provider"] == "cocoloop"
# CocoLoop market removed; provider args normalize to clawhub.
assert captured["provider"] == "clawhub"
assert captured["skills_root"].replace("\\", "/").endswith("/skills/_workspace")

View file

@ -1,35 +0,0 @@
from __future__ import annotations
from runtime import skills_market
def test_get_market_adapter_clawhub_default() -> None:
a = skills_market.get_market_adapter("clawhub")
assert a.provider == "clawhub"
def test_get_market_adapter_cocoloop() -> None:
a = skills_market.get_market_adapter("cocoloop")
assert a.provider == "cocoloop"
def test_get_market_adapter_cocoloop_alias() -> None:
a = skills_market.get_market_adapter("cocoloop-cn")
assert a.provider == "cocoloop"
def test_cocoloop_resolve_archive_url(monkeypatch) -> None:
def _fake_detail(slug: str) -> dict: # noqa: ANN001
return {
"source": "cocoloop",
"slug": slug,
"latestVersion": "1.0.0",
"archiveUrl": "https://dl.example/bss/skills/demo.zip",
"versions": [{"version": "1.0.0", "archiveUrl": "https://dl.example/bss/skills/demo.zip"}],
}
monkeypatch.setattr("runtime.skills_market.cocoloop_get_skill_detail", _fake_detail)
a = skills_market.CocoloopMarketAdapter()
url, ver = a.resolve_archive_url(slug="demo", version=None)
assert url.endswith("demo.zip")
assert ver == "1.0.0"

View file

@ -1,257 +0,0 @@
from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock, patch
from svc.llm.video_generation_client import (
_effective_video_model_for_request,
_i2v_first_frame_input_style,
dashscope_api_root_from_base_url,
legacy_video_assistant_body_with_placeholder,
legacy_video_turn_bundle,
materialize_video_output_attachments,
send_video_generation_request,
)
def test_dashscope_api_root_strips_compatible_suffix() -> None:
assert (
dashscope_api_root_from_base_url("https://dashscope.aliyuncs.com/compatible-mode/v1")
== "https://dashscope.aliyuncs.com"
)
def test_send_video_generation_missing_config() -> None:
r = send_video_generation_request(prompt="hello", api_key="", base_url="", model="wan2.2-t2v-plus")
assert r["ok"] is False
assert "missing" in str(r.get("error") or "").lower()
def test_send_video_generation_poll_succeeds() -> None:
post_resp = MagicMock()
post_resp.status_code = 200
post_resp.json.return_value = {"output": {"task_id": "tid-1", "task_status": "PENDING"}}
get_pending = MagicMock()
get_pending.status_code = 200
get_pending.json.return_value = {"output": {"task_id": "tid-1", "task_status": "PENDING"}}
get_ok = MagicMock()
get_ok.status_code = 200
get_ok.json.return_value = {
"output": {
"task_id": "tid-1",
"task_status": "SUCCEEDED",
"video_url": "https://example.invalid/out.mp4",
}
}
clients: list[MagicMock] = []
def _client_factory(*_a, **_k):
inst = MagicMock()
inst.__enter__ = lambda *_x: inst
inst.__exit__ = lambda *_x: None
if len(clients) == 1:
inst.get.side_effect = [get_pending, get_ok]
clients.append(inst)
return inst
with patch("svc.llm.video_generation_client.httpx.Client", side_effect=_client_factory):
with patch(
"svc.llm.video_generation_client.post_with_retry",
return_value=post_resp,
):
with patch("svc.llm.video_generation_client.time.sleep"):
r = send_video_generation_request(
prompt="cat",
model="wan2.2-t2v-plus",
api_key="sk-test",
base_url="https://dashscope.aliyuncs.com",
)
assert r["ok"] is True
assert r.get("video_urls") == ["https://example.invalid/out.mp4"]
assert len(clients) == 2
assert clients[1].get.call_count == 2
def test_legacy_video_turn_bundle_ok_without_materialize() -> None:
ok, text, att = legacy_video_turn_bundle(
{"ok": True, "text": "done", "video_urls": ["https://example.invalid/x.mp4"]}
)
assert ok is True
assert text == "done"
assert len(att) >= 1
assert att[0].get("type") == "video_ref"
def test_legacy_video_placeholder_zh() -> None:
produced = [{"type": "video_ref", "attachment_id": "a" * 64}]
s = legacy_video_assistant_body_with_placeholder(lang="zh", body_text="", produced=produced)
assert "视频" in s
def test_materialize_video_output_attachments_empty() -> None:
assert materialize_video_output_attachments([], max_videos=1) == []
def test_effective_video_model_coerces_t2v_to_i2v_when_image() -> None:
assert _effective_video_model_for_request("wan2.6-t2v-flash", img_url="https://x/a.png") == "wan2.6-i2v-flash"
assert _effective_video_model_for_request("wan2.6-i2v-flash", img_url="https://x/a.png") == "wan2.6-i2v-flash"
assert _effective_video_model_for_request("wan2.6-t2v-flash", img_url=None) == "wan2.6-t2v-flash"
assert _effective_video_model_for_request("wan2.6-t2v-flash", img_url="") == "wan2.6-t2v-flash"
assert _effective_video_model_for_request("wan2.7-t2v", img_url="https://x/a.png") == "wan2.7-i2v"
def test_i2v_input_style_auto(monkeypatch) -> None:
monkeypatch.delenv("AIA_VIDEO_I2V_INPUT_STYLE", raising=False)
assert _i2v_first_frame_input_style(model_name="wan2.7-i2v") == "media"
assert _i2v_first_frame_input_style(model_name="WAN2.7-i2v-plus") == "media"
assert _i2v_first_frame_input_style(model_name="wan2.6-i2v-flash") == "img_url"
def test_send_video_coerces_payload_model_when_t2v_plus_img(monkeypatch) -> None:
monkeypatch.delenv("AIA_VIDEO_EXPERT_I2V_MODEL", raising=False)
monkeypatch.delenv("AIA_VIDEO_EXPERT_DISABLE_I2V_MODEL_COERCION", raising=False)
post_resp = MagicMock()
post_resp.status_code = 200
post_resp.json.return_value = {
"output": {
"task_id": "tid-i2v",
"task_status": "SUCCEEDED",
"video_url": "https://example.invalid/i2v.mp4",
}
}
inst = MagicMock()
inst.__enter__ = lambda *_x: inst
inst.__exit__ = lambda *_x: None
captured: dict[str, Any] = {}
def _capture_post(_client, **kw):
p = kw.get("payload")
if isinstance(p, dict):
captured.clear()
captured.update(p)
return post_resp
with patch("svc.llm.video_generation_client.httpx.Client", return_value=inst):
with patch("svc.llm.video_generation_client.post_with_retry", side_effect=_capture_post):
r = send_video_generation_request(
prompt="motion",
model="wan2.6-t2v-flash",
api_key="k",
base_url="https://dashscope.aliyuncs.com",
img_url="https://example.invalid/frame.png",
)
assert r["ok"] is True
assert captured.get("model") == "wan2.6-i2v-flash"
def test_send_video_includes_img_url_in_payload() -> None:
post_resp = MagicMock()
post_resp.status_code = 200
post_resp.json.return_value = {
"output": {
"task_id": "tid-i2v",
"task_status": "SUCCEEDED",
"video_url": "https://example.invalid/i2v.mp4",
}
}
inst = MagicMock()
inst.__enter__ = lambda *_x: inst
inst.__exit__ = lambda *_x: None
captured: dict[str, Any] = {}
def _capture_post(_client, **kw):
p = kw.get("payload")
if isinstance(p, dict):
captured.clear()
captured.update(p)
return post_resp
with patch("svc.llm.video_generation_client.httpx.Client", return_value=inst):
with patch("svc.llm.video_generation_client.post_with_retry", side_effect=_capture_post):
r = send_video_generation_request(
prompt="motion",
model="wan2.6-i2v-flash",
api_key="k",
base_url="https://dashscope.aliyuncs.com",
img_url="https://example.invalid/frame.png",
)
assert r["ok"] is True
inp = captured.get("input")
assert isinstance(inp, dict)
assert inp.get("img_url") == "https://example.invalid/frame.png"
assert inp.get("media") is None
def test_send_video_wan27_uses_media_first_frame(monkeypatch) -> None:
monkeypatch.delenv("AIA_VIDEO_I2V_INPUT_STYLE", raising=False)
post_resp = MagicMock()
post_resp.status_code = 200
post_resp.json.return_value = {
"output": {
"task_id": "tid-i2v",
"task_status": "SUCCEEDED",
"video_url": "https://example.invalid/i2v.mp4",
}
}
inst = MagicMock()
inst.__enter__ = lambda *_x: inst
inst.__exit__ = lambda *_x: None
captured: dict[str, Any] = {}
def _capture_post(_client, **kw):
p = kw.get("payload")
if isinstance(p, dict):
captured.clear()
captured.update(p)
return post_resp
u = "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/wpimhv/rap.png"
with patch("svc.llm.video_generation_client.httpx.Client", return_value=inst):
with patch("svc.llm.video_generation_client.post_with_retry", side_effect=_capture_post):
r = send_video_generation_request(
prompt="你好",
model="wan2.7-i2v",
api_key="k",
base_url="https://dashscope.aliyuncs.com",
img_url=u,
)
assert r["ok"] is True
inp = captured.get("input")
assert isinstance(inp, dict)
assert inp.get("img_url") is None
media = inp.get("media")
assert isinstance(media, list) and len(media) >= 1
assert media[0].get("type") == "first_frame"
assert media[0].get("url") == u
def test_send_immediate_succeeded_on_submit() -> None:
post_resp = MagicMock()
post_resp.status_code = 200
post_resp.json.return_value = {
"output": {
"task_id": "tid-x",
"task_status": "SUCCEEDED",
"video_url": "https://example.invalid/immediate.mp4",
}
}
inst = MagicMock()
inst.__enter__ = lambda *_x: inst
inst.__exit__ = lambda *_x: None
with patch("svc.llm.video_generation_client.httpx.Client", return_value=inst):
with patch(
"svc.llm.video_generation_client.post_with_retry",
return_value=post_resp,
):
r = send_video_generation_request(
prompt="x",
model="m",
api_key="k",
base_url="https://dashscope.aliyuncs.com",
)
assert r["ok"] is True
assert r["video_urls"] == ["https://example.invalid/immediate.mp4"]