Skip to content

Plugins: the APIs a design plugin needs, in one pull request - #1392

Open
hoangvu12 wants to merge 8 commits into
zeronsh:zeron/zgxfrom
hoangvu12:plugin-design-apis
Open

hoangvu12 wants to merge 8 commits into
zeronsh:zeron/zgxfrom
hoangvu12:plugin-design-apis

Conversation

@hoangvu12

@hoangvu12 hoangvu12 commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

This replaces seven pull requests with one, as asked, so the plugin APIs can be reviewed together. We're building a design tool as a Luau plugin, and each commit adds one thing its service, its windows or its agent needs. Each commit is one of the old pull requests, in the order they build on each other, and its message says which.

Commit Was What it adds
439a369 #1362 A zeron.tool result with an imageContent array reaches the agent as MCP image content
c3634ae #1363 A manifest can ask for more than 64 MB of service memory (limits.memory_mb), shown on the consent sheet
3b78c67 #1366 zeron.eval runs scripts the plugin's agents write, in its service, behind an "eval" permission
3fe0d99 #1367 zeron.measure_text and zeron.load_font in services, measuring as a window's canvas does (zgx-text, no renderer)
629abf0 #1378 A "fonts" permission grants zgx's Capability::Fonts, so a plugin's windows can call ui.load_font
71a7407 #1379 zeron.fetch with binary = true returns a response's bytes whole, in base64
cc9d259 #1380 A service gets zeron.db, the same SQLite file its windows open (new crates/plugin-db)
b824a40 The engine and apps/zgx pinned to one zgx, the combined zgx pull request

The old pull requests' descriptions still explain each change in full, with their own testing.

Needs zeronsh/zgx#2110

The fonts permission needs Capability::Fonts and the engine needs the zgx-text crate. Both are in zeronsh/zgx#2110, the combined zgx pull request. The last commit pins the engine's zgx and zgx-text, and every zgx crate apps/zgx takes (its [patch] entries too), to that pull request's head on my fork, 99e17852. Once #2110 merges, the pins go back to zeronsh/zgx.

CI

CI on zeron/zgx is red for every pull request right now, from three breakages in the base branch, none of them in these files:

The macOS zgx job also fails GPU tests (create_render_pipeline 'shader fill') on #1382, which only touches v2's template files, so those come from the runner, not from a change.

Testing

On Windows, against zeron/zgx at c153906 with the pins above:

  • cargo check --locked: the engine and zeron-zgx with its tests are clean.
  • cargo test -p zeron-engine --lib plugins: 239 passed. It needs the rusage gate from zgx Windows: browser, terminal, annotation and intro fixes #1219 to compile the engine's tests on Windows, which I applied locally and didn't commit.
  • cargo test -p zeron-mcp --lib: 74 passed. cargo test -p zeron-plugin-db: 5 passed, 1 ignored.
  • zeron-zgx tests filtered to plugins::grants, consent, locale and i18n: 9, 8, 3 and 4 passed.

Two conflicts came up and both kept both sides: #1366 and #1363 add consent-sheet strings, manifest checks and service limits in the same places, and #1380 and #1367 each add a row to the same table in apps/zgx/docs/plugins.md.

A plugin tool's answer reached the agent as one JSON text block, so a base64 PNG was a long string the model read as characters. Now a tool result with a top-level `imageContent` array goes out as MCP image content, after the rest of the result as text. Each entry must be a PNG, JPEG, WebP or GIF of at most 5 MB, and images stop before the reply reaches the 8 MB cap; one left out is named in a last text block. Such a result goes without `structuredContent`: Codex shows the model only that (no images) when it is there, and Claude Code shows it in place of the texts.

Was zeronsh#1362.
A plugin service's Luau heap is 64 MB, and a plugin can't ask for more.
A design tool's service runs out at around 40k nodes.

"limits": { "memory_mb": 256 } gives its service that much, 512 at
most. More than 512, or anything that isn't a whole number from 1 to
512, makes the manifest broken rather than clamped, so the Allow sheet
never shows a number that isn't what runs. The consent record carries
memory_mb only when it's asked, so other plugins' digests don't change.
The Allow sheet names the number on the background line. Raising it asks
again, and lowering it doesn't.

A service with nothing in flight and more than half its memory in use
now runs a full collection. Luau collects only as it allocates and never
before failing an allocation, so the garbage of one big call counted
against the next, which then ran out.

Was zeronsh#1363.
A plugin's service had no way to measure text: it runs in the engine,
which linked no text shaping, and only a window's canvas could measure.
Layout that must work while Zeron is hidden (agent tools) couldn't run.

zeron.measure_text(text, opts) shapes with zgx-gpu's TextServices, the
same shaper and font lookup a window's canvas uses, over the system's
fonts and the app's own. zeron.load_font(path) adds a font file from the
plugin's folder, so the service and a window that loads the same file
with ui.load_font measure alike.

Also:
- Plugins: zeron.load_font says when the plugin has no such file
- Plugins: the engine measures text with zgx-text, without zgx's renderer
- Plugins: zeron.load_font takes a font's bytes in base64

Was zeronsh#1367.
No manifest permission granted zgx's Capability::Fonts, so every plugin
window's ui.load_font was refused, and a window couldn't measure in the
fonts its service loads with zeron.load_font. A loaded font reaches
every window's text, Zeron's own included, so the person is asked.

Was zeronsh#1378.
…in base64

A body crossed into Luau as String::from_utf8_lossy, so every byte that
isn't UTF-8 became U+FFFD: a fetched TTF or PNG came back corrupted, and a
plugin had no way to download one. With { binary = true } the body is the
bytes in base64 (Luau gets results as JSON, which carries text only). The
default stays text.

Was zeronsh#1379.
…ull request

The plugin APIs above need zgx changes that are one pull request now, zeronsh/zgx#2110: Capability::Fonts and ui.load_font for the "fonts" permission, and the zgx-text crate the engine measures with. The engine's zgx and zgx-text and every zgx crate apps/zgx takes (its [patch] entries too) are pinned to its head on the author's fork, 99e17852, so a service and its windows measure with the same zgx. Once #2110 merges, the pins go back to zeronsh/zgx.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant