Call AI Models
Goal: call the PluginX AI gateway with LFModel — a button that streams an AI summary.
main.js
js
// ==UserScript==
// @name AI Page Summary
// @description Summarize the current page title with AI
// @match https://example.com/*
// @require dom
// @require model
// ==/UserScript==
LFDOM.ready(() => {
const panel = LFDOM.createFloatingRoot({ id: "ai-summary" });
panel.innerHTML =
"<button id='lf-sum'>AI Summary</button><div id='lf-out' style='max-width:280px'></div>";
panel.querySelector("#lf-sum").addEventListener("click", async () => {
const out = panel.querySelector("#lf-out");
out.textContent = "";
try {
const { models } = await LFModel.listModels();
const chatModel = models.find((m) => m.kind === "chat");
for await (const chunk of LFModel.chatStream({
model: chatModel.id,
messages: [{ role: "user", content: `Summarize this page title in one sentence: ${document.title}` }],
})) {
if (chunk.kind === "delta") out.textContent += chunk.data.choices?.[0]?.delta?.content ?? "";
}
} catch (error) {
out.textContent = `Call failed: ${error.errorCode || error.message}`;
}
});
});Key points
listModels()returns the models available to the current account; chat and image models share one list, so pick one withkind === "chat"— do not hard-code model IDschat()always returns the complete result in one go (stream: trueis overridden to false); usechatStream()for incremental outputchatStream()returns an async iterable; for chunks withkind === "delta",chunk.datais an OpenAI stream chunk object and the incremental text ischunk.data.choices[0].delta.content- Errors are thrown from the loop as
LFModelError; branch onerrorCode(e.g.INSUFFICIENT_BALANCE,APP_BUDGET_EXHAUSTED) - Calls count against this app's budget;
cost_points/charge_pointsare canonical (1 CNY = 200 points). Query it withgetBudget()/getUsage() - Calls carry the PluginX login through the background; you cannot act as another app