On the server's own credentials a provider 403 now gets its own code,
server_key_forbidden, with a hint in all four languages: today's free
quota is used up, it resets tomorrow, and users can add their own key
in model settings. The generic "The provider returned an error." line
is left out for this code.
A daily spend cap that blocks a shared key makes every call return 403.
The old hint said the key may lack access to the model or region, which
points users at a setting they cannot change.
A 403 on the user's own key keeps the old hint and the provider's text.
The old export protocol used a single state slot per session: the MCP tool
set exportFormat/exportXml, the browser bridge picked it up on its 2s poll,
and the rendered image came back as one exportData field that whichever
tool call polled first consumed and cleared. Under concurrency this meant:
requests overwrote each other, at most one of N parallel exports succeeded,
a late render could satisfy the next export with the WRONG image, the
browser's leaked 10s fallback timer could silently swallow a successor's
request, and a closed preview tab made every export hang to timeout.
Replace it with a per-session export job queue:
- each export_diagram call enqueues a job (unique id, format, optional
single-page projection) and awaits its own job's promise
- GET /api/state exposes only the head of the queue; the browser renders
jobs strictly one at a time and reports each result/failure BY JOB ID,
which resolves exactly the waiting call; stale ids are ignored
- browser-side fallback timer (30s) is tracked and cleared per job and
only ever fails its own job, so failures advance the queue
- server-side per-job backstop is 120s (DRAWIO_EXPORT_TIMEOUT_MS) since a
queued job also waits for its predecessors
- browser heartbeat (20s staleness) fails enqueues/waiters fast when the
preview tab is gone, instead of hanging to the timeout
- expired-session cleanup now fails waiting jobs and drops queue state
Add tests/export-queue.test.ts covering serialization, per-job routing,
stale-result rejection, fail/timeout queue advance, and the untouched
autosave paths.
The unauthenticated /api/validate-model endpoint should not let callers
raise the token budget; the env var / admin setting alone fixes#883.
Also import the shared constants in the settings registry instead of
duplicating them, and move the setting to the Features group alongside
the other validation settings.
Two related bugs caused Azure OpenAI to pass validation but fail in actual use:
1. validate-model/route.ts used createOpenAI for Azure, while ai-providers.ts
uses createAzure. These differ in URL construction and authentication headers
(api-key vs Authorization: Bearer), so validation did not test the real code
path. Switch to createAzure for consistency.
2. In ai-providers.ts, when a user provided their own API key without a custom
base URL, resourceName was unconditionally set to undefined. This left
createAzure with no endpoint information, resulting in resource not found.
resourceName is an endpoint component, not a credential, so it is safe to
fall back to AZURE_RESOURCE_NAME from the environment whenever no baseURL is
available.
The fallback condition for PNG detection in the MCP export handler was too broad:
it would match any string longer than 100 chars that didn't start with '<', which
includes SVG data URLs (data:image/svg+xml;base64,...).
This caused a race condition where an autosave SVG export response could arrive
while a PNG export was pending, resulting in SVG data being sent to the MCP server
as the PNG export result. The server would then try to write it as binary PNG,
producing a corrupt file (broken image).
Fix: exclude strings starting with 'data:' from the fallback condition, so only
raw base64 strings (without a data URL prefix) match as PNG, while SVG data URLs
are correctly rejected.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.