Troubleshooting
The failures people meet first — a bare import the browser cannot resolve, a local model the browser refuses, no provider, the key warning, missing buttons — and what an error tells you.
On this page
- Overview
- Failed to resolve module specifier "@aparte/core"
- CORS on local BYOK (LM Studio / Ollama)
- ”Provider is not registered”, “No provider selected”
- ”Sending the API key straight from the browser” warning
- [Unknown segment type: …] in a message
- The retry, edit or ⓘ buttons are not there
- The composer toolbar is not showing
- What an error tells you
- Next
Failed to resolve module specifier "@aparte/core"
Section titled “Failed to resolve module specifier "@aparte/core"”The page imports @aparte/core by name with no bundler in front of it, and a browser cannot
resolve a bare name on its own. Use a bundler — Vite, Next, Astro, esbuild, any of them — as every
snippet here assumes, or load the packages from a CDN by URL, as in
From a CDN.
CORS on local BYOK (LM Studio / Ollama)
Section titled “CORS on local BYOK (LM Studio / Ollama)”The server’s log shows the request, and the reply ends in an error such as Failed to fetch
(code NET_ERROR): the browser called the local server itself — http://localhost:1234 for LM Studio, http://localhost:11434 for
Ollama — and refused the answer, because the server did not allow the page’s origin.
- LM Studio: turn on Enable CORS in the server settings, in the Developer tab, then restart the server.
- Ollama: start it with
OLLAMA_ORIGINSset to the page’s origin, or*while you develop.
OLLAMA_ORIGINS=* ollama serve$env:OLLAMA_ORIGINS = "*"
ollama serveIf the Ollama app is running, quit it first, or set OLLAMA_ORIGINS as a user environment
variable and restart the app.
Local models has the whole setup for both, and for llama.cpp.
”Provider is not registered”, “No provider selected”
Section titled “”Provider is not registered”, “No provider selected””[AparteClient] Send dropped: no model is selected. …import { aparteGlobalConfig, AparteClient, AparteDirectTransport } from '@aparte/core';
import { createOpenAICompatProvider, presets } from '@aparte/provider-openai-compat';
aparteGlobalConfig.registerAIProvider(createOpenAICompatProvider(presets.OLLAMA));
aparteGlobalConfig.setModelConfig({ defaultProvider: 'ollama', defaultModel: 'llama3.2' });
aparteGlobalConfig.setTransport(new AparteDirectTransport({ byok: true }));
new AparteClient().start();Neither error appears in the chat: the person’s message gets no reply, the console warns, and
aparte-message-error carries the code. Provider is not registered: the selected
defaultProvider names an id no provider was registered under. No provider selected: nothing chose a model — no setModelConfig, and no
model selector has picked one; aparteGlobalConfig.hasSelectedModel()
is false until one is. A provider registered alone with a single model is selected by itself;
for a list fetched from the server, <aparte-model-selector auto-select persist> picks one. To
keep the composer from sending until a model is chosen,
aparteGlobalConfig.setRequireModelSelection(true).
A page that never calls new AparteClient().start() looks the same from the outside: nothing
streams, because nothing answers the send.
”Sending the API key straight from the browser” warning
Section titled “”Sending the API key straight from the browser” warning”[Aparte] AparteDirectTransport is sending the "<provider>" API key straight from the browser —
it is visible to anyone who opens devtools. …AparteDirectTransport sent a real key from the browser, and says so once per transport. When the key is the
person’s own, or the model is local, say so and the warning stops:
new AparteDirectTransport({ byok: true }); a keyless local provider never triggers it. When the
key is yours, anyone with devtools can read and spend it: send the requests through
your server instead.
[Unknown segment type: …] in a message
Section titled “[Unknown segment type: …] in a message”No renderer claims that segment’s type.
- A type of yours: register a renderer for it with
registerSegmentRenderer, or give the segment afallback, a sentence drawn as text when nothing claims it. Acustomsegment’ssubTypeis not a key of its own: one renderer answers everycustomsegment and reads itssubType(Custom segment types). - A built-in type —
text,thinking,code,tool_call,error— means the built-in renderers were declined, withnew AparteClient({ autoRegister: false }). Drop the option, or register what you need yourself.
The retry, edit or ⓘ buttons are not there
Section titled “The retry, edit or ⓘ buttons are not there”import { aparteGlobalConfig } from '@aparte/core';
aparteGlobalConfig.setBubbleActions({ retry: true, edit: true }); // you run an AparteClient
aparteGlobalConfig.setBubbleActions({ feedback: true, info: true }); // you answer these events
aparteGlobalConfig.setHostHandlers({ attachmentPreview: true }); // you open it on aparte-attachment-previewOnly copy is on by default: the others need something outside the chat to answer them, so they stay off until you say it is there. ⓘ also needs the reply’s token usage: it shows on a reply whose provider reported usage. The bar hides while a reply streams, and a bar with no action in it is not drawn at all. Buttons lists each one.
The composer toolbar is not showing
Section titled “The composer toolbar is not showing”An empty toolbar is hidden: it carries data-empty while it holds nothing visible, and
whitespace or comments do not count. If you put a control in it and still see data-empty, either
the control never arrived — a mistyped slot name, a framework that rendered nothing — or it hides
itself: a child with hidden or data-empty does not count, and <aparte-context> stays
data-empty until the first reply reports its usage. A toolbar
that shows but is unstyled, with your control at the start instead of the end, means the
stylesheet did not load: import @aparte/core/styles.css
(The composer toolbar).
What an error tells you
Section titled “What an error tells you”document.querySelector('aparte-chat')?.addEventListener('aparte-message-error', (event) => {
const { error } = event.detail; // an AparteError
console.error(error.code, error.message, error.httpStatus);
});A failure that reaches the chat — a refused key, a rate limit, a dropped connection — is an
AparteError: its message, a code, the httpStatus when there was one, its
data and the originalError. The message shows it as an error, and the
chat sends aparte-message-error with { messageId, error }. A missing or unregistered provider
is the exception: the send is refused before a reply exists, so nothing appears in the chat and
only the event reports it. A vendor’s HTTP status sets the code:
| Status | code |
|---|---|
| 400 | USAGE_BAD_REQUEST |
| 401, 403 | CONFIG_INVALID_KEY |
| 408 | NET_TIMEOUT |
| 429 | USAGE_RATE_LIMIT |
| 503 | PROVIDER_UNAVAILABLE |
| any other 5xx | PROVIDER_ERROR |
A network failure is NET_ERROR, or NET_OFFLINE when the browser says it is offline; a timeout
is NET_TIMEOUT; no provider selected is CONFIG_NO_PROVIDER, and one not registered
CONFIG_MISSING_KEY. Anything else is UNKNOWN_ERROR — except under a runner that calls the
model elsewhere (createAgentStreamRunner, an AG-UI runner), where a failed or cut run, and a
status the table does not name, is PROVIDER_ERROR. AparteError.from(error) turns any error
into one by the same rules, and AparteError.codeForStatus(status) is the table above, for a
provider of yours. To change how the error looks, use aparteGlobalConfig.setErrorRenderer.