diff --git a/crates/op-orchestrator/src/lib.rs b/crates/op-orchestrator/src/lib.rs index ad9cafc35..76ed3a5f1 100644 --- a/crates/op-orchestrator/src/lib.rs +++ b/crates/op-orchestrator/src/lib.rs @@ -56,5 +56,8 @@ pub use intent::classify_intent; pub use model_profile::{resolve_model_profile, ModelProfile, ModelTier}; pub use prompt::build_orchestrator_prompt; pub use run::Orchestrator; -pub use stub_providers::{SkippedPreValidator, SkippedScreenshotProvider, SkippedVisionLlmClient}; +pub use stub_providers::{ + SkippedPreValidator, SkippedScreenshotProvider, SkippedVisionLlmClient, + SkippedVisualRefProvider, +}; pub use types::*; diff --git a/crates/op-orchestrator/src/stub_providers.rs b/crates/op-orchestrator/src/stub_providers.rs index 1abbcc721..1c0aed3c3 100644 --- a/crates/op-orchestrator/src/stub_providers.rs +++ b/crates/op-orchestrator/src/stub_providers.rs @@ -1,5 +1,5 @@ //! Production-visible stub provider implementations for the vision -//! validation pipeline (S3c). +//! validation pipeline (S3c) and visual-ref pipeline (S4). //! //! Hosts use these when the real implementations are not yet wired — //! `op-design-lint` integration is pending host wiring, screenshot @@ -14,6 +14,8 @@ //! loop exits before any vision round. //! - `SkippedVisionLlmClient::validate` → `VisionResponse::Skipped` //! (defensive in case `capture_root_frame` ever returns `Some`). +//! - `SkippedVisualRefProvider::render_html_to_screenshot` → `None`, +//! so the visual-ref pipeline skips immediately to plain orchestration. //! //! Net result: the Progress stream emits `ValidationStarted` → //! `ValidationPreCheckDone { applied: 0, .. }` → @@ -23,7 +25,7 @@ use crate::types::{ DocSink, PreValidationResult, PreValidator, ScreenshotProvider, VisionCallRequest, - VisionLlmClient, VisionResponse, + VisionLlmClient, VisionResponse, VisualRefProvider, }; /// Stub `PreValidator` — always returns zero fixes; no side effects. @@ -53,3 +55,13 @@ impl VisionLlmClient for SkippedVisionLlmClient { VisionResponse::Skipped { reason: None } } } + +/// Stub `VisualRefProvider` — always returns `None`. Visual-ref pipeline +/// short-circuits to plain orchestration without rendering any HTML. +pub struct SkippedVisualRefProvider; + +impl VisualRefProvider for SkippedVisualRefProvider { + fn render_html_to_screenshot(&self, _html: &str, _width: f64, _height: f64) -> Option { + None + } +} diff --git a/crates/op-orchestrator/src/types.rs b/crates/op-orchestrator/src/types.rs index f068306e0..cb22a8cab 100644 --- a/crates/op-orchestrator/src/types.rs +++ b/crates/op-orchestrator/src/types.rs @@ -160,6 +160,23 @@ pub struct ValidationProviders<'a> { pub system_prompt: String, } +// ── S4: VisualRefProvider ───────────────────────────────────────────────────── + +/// Visual-reference rendering outlet. +/// +/// host 实现把 HTML 字符串渲染成 base64 PNG 截图;stub 返回 `None` +/// 表示"跳过视觉参考阶段"。 +/// +/// Port of the `renderHtmlToScreenshot` call-site shape in +/// `visual-ref-orchestrator.ts:108-122` + spec §4.5. +pub trait VisualRefProvider: Send + Sync { + /// 将 HTML 字符串渲染为给定像素尺寸的截图,返回 base64 PNG; + /// `None` 表示不可用 / 跳过。 + fn render_html_to_screenshot(&self, html: &str, width: f64, height: f64) -> Option; +} + +// ── S4 end ──────────────────────────────────────────────────────────────────── + // ── S3c end ─────────────────────────────────────────────────────────────────── /// 廉价可克隆的中止句柄(`Arc` 语义)。 @@ -564,6 +581,42 @@ mod tests { } } + // ── Task A2: VisualRefProvider trait + SkippedVisualRefProvider stub ───────── + + /// `SkippedVisualRefProvider` returns `None` for any input. + #[test] + fn skipped_visual_ref_provider_returns_none() { + use crate::stub_providers::SkippedVisualRefProvider; + let p = SkippedVisualRefProvider; + assert!(p + .render_html_to_screenshot("", 1280.0, 800.0) + .is_none()); + assert!(p.render_html_to_screenshot("", 0.0, 0.0).is_none()); + assert!(p + .render_html_to_screenshot("Hello", 390.0, 844.0) + .is_none()); + } + + /// `VisualRefProvider` trait is `Send + Sync`. + #[test] + fn visual_ref_provider_is_send_sync() { + use crate::types::VisualRefProvider; + fn assert_send_sync() {} + assert_send_sync::(); + } + + /// `op_orchestrator::SkippedVisualRefProvider` resolves from a host-style import. + #[test] + fn skipped_visual_ref_provider_resolves_from_crate_root() { + use crate::{SkippedVisualRefProvider, VisualRefProvider}; + let p: &dyn VisualRefProvider = &SkippedVisualRefProvider; + assert!(p + .render_html_to_screenshot("

test

", 800.0, 600.0) + .is_none()); + } + + // ── Task A2 end ─────────────────────────────────────────────────────────────── + /// `DesignRequest.validation_enabled` defaults to `true` when omitted from JSON. #[test] fn design_request_validation_enabled_defaults_true() {