Skip to content

動作

每個視窗層級的動作都會變成一個名為 {winId}__{action} 的 MCP 工具。用 win.actions().add(name, title) 建立它,逐步調整,最後以 .on(handler) 收尾。

建構器選項

方法效果
.description(s)tools/list 中提供人類可讀的描述
.params::<T>()T: JsonSchema + DeserializeOwned 產生 inputSchema
.returns::<R>()貢獻 outputSchema 中的 result 子結構描述
.read_only() .destructive() .idempotent() .open_world()MCP 註記提示
.available(bool)tools/list 中的初始可見性

處理器形式

.on(..) 接受六種形態。用兩個問題來選擇:它需要參數嗎? 以及 它需要宿主環境 &mut Cx 嗎?

形式執行於適用於
on_ui(|| …)主執行緒純 UI 邏輯,無參數
on_ui(|p| …)主執行緒帶參數的純 UI 邏輯
on_ui(|cx| …)主執行緒需要操作真正的元件,無參數
on_ui(|p, cx| …)主執行緒需要操作真正的元件,帶參數
|| async { … }tokio非同步工作,無參數
|p| async { … }tokio帶參數的非同步工作
rust
#[derive(Deserialize, JsonSchema)]
struct Add { text: String }

#[derive(Serialize, JsonSchema)]
struct Stats { total: usize, done: usize }

win.actions()
    .add("add", "Add a todo")
    .description("Append a new open item.")
    .params::<Add>()      // → inputSchema
    .returns::<Stats>()   // → outputSchema
    .on(on_ui(move |p: Add, _cx: &mut Cx| {
        let text = p.text.trim().to_string();
        if text.is_empty() {
            return Err(ActionError::field("text", "must not be empty"));
        }
        // …mutate real state, then let the read side mirror it…
        Ok(Outcome::message("added").with(Stats { total: 1, done: 0 }))
    }));

回傳結果

可以回傳 &strStringOutcomeOutcome::message(..).with(value) 會附帶結構化資料,讓 Agent 直接解析,而不必重新讀取整份快照。

非同步處理器與主執行緒

Important

async 處理器在 tokio 上執行,因此不會收到 &mut Cx。若要在 await 之後操作 UI,請擷取一個 Ui<Cx>(來自 app.ui())並跳回主執行緒:

rust
let ui = app.ui();
win.actions().add("report", "Build a report").on(move || {
    let ui = ui.clone();
    async move {
        let data = fetch_from_network().await;          // off-thread work
        let n = ui.run(move |cx| apply(cx, data)).await; // back on the main thread
        Ok::<_, ActionError>(Outcome::message(format!("{n} rows")))
    }
});

在執行期改變動作

.on(..) 會回傳一個 ActionHandle。保留它,就能隱藏當下不該被 Agent 呼叫的工具——工具清單會重新計算,並通知客戶端:

rust
let clear = win.actions().add("clear_done", "Clear completed")
    .available(false)                       // hidden until useful
    .on(on_ui(|| Ok::<_, ActionError>("cleared")));

// In sync(), as state changes:
clear.set_available(self.items.iter().any(|t| t.done));

ActionHandle 另外還有 set_titleset_descriptionremove

為何值得這樣做

一個只會看到真正可呼叫的工具的 Agent,不必去猜測,也不會把一輪對話浪費在註定失敗的動作上。