image-gen implementation
Shared image runtime
createImageRuntime(ctx) from elowen-plugin-shared/imageRuntime is the supported entry, and Image-gen and Image-edit call it once from an awaited register. It reads ctx.config.provider and asks ctx.provider(id) for that provider's descriptor, which carries only an id, a label and a type. When this installation configures no provider with that id the runtime logs one info message and returns null, so no tool is registered. Otherwise it exposes providerId, provider, model, files, ok, fail and render. runtime.model keeps the trimmed configured model id, including an OpenRouter vendor namespace, or falls back to gpt-image-2.5-sunburst for the connected ChatGPT account and gpt-image-1 for every other provider type; resolveModel stays a private implementation function. A connected ChatGPT account needs no API key.
Rendering and file authority
runtime.render('generate' | 'edit', request, outputPath?, overwrite?) validates the output extension and a boolean overwrite before the provider call, and refuses overwrite without an explicit output path. It then calls the host's ctx.images operation once, maps the reported format to .png, .jpg or .webp with PNG as the default when a response reports none, corrects a mismatched requested extension, and writes the bytes through ctx.host.projectFiles().write with that overwrite. One call is one render and one write, with no polling, extra copy or retry, and the result text carries the saved path instead of sharing the image.
Core keeps both the credential and the file authority: ctx.images authorizes each render against the accounts of the provider the plugin names, and provider or file failures propagate to the tool as errors. Provider identity is not a credential and not an account-availability check, and ctx.images owns image-render authorization. Both image manifests declare the project-files read capability the runtime's file seam needs.
Release requirement and test coverage
The shipped core archive must export and include the imageRuntime subpath and its declarations; the shared API number alone does not establish that a newly added subpath exists there. requiresCore records the release paired with the declared shared API level, 0.30.4 for shared API 14, and the marketplace compatibility gate rejects a candidate whose archived manifest needs a newer core before staging it.
The paired registry tests stage the real plugin bytes through prepareCandidate and load both entries through loadPlugins. Registration fixtures call ctx.provider directly, while the real-loader fixture supplies host.providerDescriptor and a host.projectFiles guard whose read and write throw, so an accidental tool execution cannot pass. A capable source checkout at an unchanged released version is not proof that an older published archive is capable.