P1: host-agnostic quercus-core with agent loop, providers, and tool schemas
- Core types: Rational time, EntityId, ActionBatch, PngBytes (no Pillow dep) - Config: ~/.quercus/config.toml with 0600 perms, env var override for CI - LLMProvider: Claude (anthropic SDK) and OpenAI-compatible endpoint (covers custom gateways and llama.cpp server mode); graceful degradation without API keys - Tool schemas: 26 curated tools with JSON Schema and read_only flags - AgentLoop: sync dialogue loop with confirmation gate (read-only batches bypass, rejected mutations are reported back to the LLM), PNG frame feedback for multimodal providers - Session log: JSONL recording with frame sha256 hashes, replayable - HostAdapter ABC (signatures only, aligned with OPP/1 method families) - Mock LLM provider (scripted/replay/record) for network-free CI Tests: 69 passed via uv run pytest (no network access)
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
"""HostAdapter ABC:只定义接口形状,不可实例化,方法签名齐全。"""
|
||||
import pytest
|
||||
|
||||
from quercus_core.host.adapter import HostAdapter
|
||||
from quercus_core.types import Action, ActionBatch, Limits, Rational
|
||||
|
||||
|
||||
def test_abc_cannot_instantiate():
|
||||
with pytest.raises(TypeError):
|
||||
HostAdapter() # type: ignore[abstract]
|
||||
|
||||
|
||||
def test_abstract_methods_declared():
|
||||
assert {"capabilities", "limits", "get_frame", "get_thumbnails", "execute", "undo_last"} <= set(
|
||||
HostAdapter.__abstractmethods__
|
||||
)
|
||||
|
||||
|
||||
def test_missing_abstract_method_raises():
|
||||
class Partial(HostAdapter):
|
||||
def capabilities(self):
|
||||
return set()
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
Partial() # type: ignore[abstract]
|
||||
|
||||
|
||||
def test_concrete_subclass_works():
|
||||
class Impl(HostAdapter):
|
||||
def capabilities(self):
|
||||
return set()
|
||||
|
||||
def limits(self):
|
||||
return Limits()
|
||||
|
||||
def open_project(self, path): ...
|
||||
|
||||
def save_project(self, path=None): ...
|
||||
|
||||
def get_project_overview(self): ...
|
||||
|
||||
def probe_media(self, path): ...
|
||||
|
||||
def import_footage(self, paths): ...
|
||||
|
||||
def get_timeline_structure(self, seq): ...
|
||||
|
||||
def get_frame(self, target, time, max_size): ...
|
||||
|
||||
def get_thumbnails(self, target, range, count): ...
|
||||
|
||||
def get_audio_levels(self, seq, range, resolution): ...
|
||||
|
||||
def play(self, speed=1.0): ...
|
||||
|
||||
def pause(self): ...
|
||||
|
||||
def seek(self, time): ...
|
||||
|
||||
def get_state(self): ...
|
||||
|
||||
def export(self, seq, output, preset): ...
|
||||
|
||||
def execute(self, batch): ...
|
||||
|
||||
def undo_last(self): ...
|
||||
|
||||
impl = Impl()
|
||||
assert impl.capabilities() == set()
|
||||
assert impl.limits().max_frame_rate == 8.0
|
||||
# execute 接受 ActionBatch 语义
|
||||
impl.execute(ActionBatch(label="t", actions=[Action(tool="play", params={})]))
|
||||
Reference in New Issue
Block a user