A frontend teammate wired a new free model endpoint into an agent UI. The happy path worked: tokens streamed, the assistant replied, and the demo shipped. Then a user pressed Stop during a long tool call. The UI cleared the pending state, but the stream kept running server-side, the button label changed to Retry, and the live region stayed silent. The model benchmark was fine; the endpoint contract was not.
That failure is why provider selection should start with transport behavior, not benchmark tables. A large free token allowance or free hosted inference is useful only when the endpoint supports the client behaviors a cancel and retry interface depends on: stream start, chunk cadence, abort propagation, rate-limit response shape, and error formatting.
The candidate worth probing is MonkeyCode. The operator describes it as an open-source project with free model access, a 30-million-token free allowance, and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Those availability claims are operator-supplied, so the practical next step is not to trust the marketing line; it is to run a disposable probe against the current endpoint and keep the raw trace.
Run the probe before you build
The smallest useful harness is a single HTML file. It makes two requests: one normal stream, and one stream aborted after 800ms. It records HTTP status, first-byte latency, Retry-After, chunk count, and whether the client receives an AbortError. It posts results to an output element with aria-live=polite so the result is available to a screen reader without having to inspect the console.
<!doctype html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Model endpoint streaming probe</title>
</head>
<body>
<label for='endpoint'>Endpoint URL</label>
<input id='endpoint' value='https://api.example.invalid/v1/chat/completions'>
<label for='model'>Model identifier</label>
<input id='model' value='model-placeholder'>
<label for='key'>API key</label>
<input id='key' type='password' value='key-placeholder'>
<button id='run'>Run streaming probe</button>
<output id='log' aria-live='polite'></output>
<script type='module'>
const endpoint = document.querySelector('#endpoint');
const model = document.querySelector('#model');
const key = document.querySelector('#key');
const log = document.querySelector('#log');
const run = document.querySelector('#run');
function report(message) {
const time = new Date().toISOString().slice(11, 19);
const line = document.createElement('div');
line.textContent = `${time} ${message}`;
log.append(line);
log.scrollTop = log.scrollHeight;
}
async function readStream(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let chunks = 0;
let bytes = 0;
let buffer = '';
const started = performance.now();
while (true) {
const { done, value } = await reader.read();
if (done) break;
bytes += value.byteLength;
chunks += 1;
buffer += decoder.decode(value, { stream: true });
}
const ended = buffer.includes('[DONE]') ? 'done' : 'incomplete';
report(`stream ${ended}: ${chunks} chunks, ${bytes} bytes, ${Math.round(performance.now() - started)}ms`);
return { chunks, bytes, ended };
}
async function requestStream(signal, label) {
const started = performance.now();
const response = await fetch(endpoint.value, {
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${key.value}`,
},
body: JSON.stringify({
model: model.value,
messages: [{ role: 'user', content: 'Reply with exactly: probe-ok' }],
stream: true,
}),
signal,
});
const firstByte = Math.round(performance.now() - started);
const retryAfter = response.headers.get('retry-after');
report(`${label}: HTTP ${response.status}, first byte ${firstByte}ms, Retry-After ${retryAfter ?? 'missing'}`);
if (!response.ok) {
const text = await response.text();
report(`${label} error body: ${text.slice(0, 180)}`);
return null;
}
return response;
}
run.addEventListener('click', async () => {
log.replaceChildren();
try {
const controller = new AbortController();
const response = await requestStream(controller.signal, 'normal');
if (response?.ok) {
await readStream(response);
}
} catch (err) {
report(`normal request failed: ${err.name} ${err.message}`);
}
report('starting abort test');
try {
const abortController = new AbortController();
setTimeout(() => abortController.abort(), 800);
const abortResponse = await requestStream(abortController.signal, 'abort');
if (abortResponse?.ok) {
await readStream(abortResponse);
}
} catch (err) {
if (err.name === 'AbortError') {
report('abort propagated to client as AbortError');
} else {
report(`abort request failed: ${err.name} ${err.message}`);
}
}
});
</script>
</body>
</html>
What to read from the trace
| Signal | What it tells you | UI consequence |
|---|---|---|
| First byte over 800ms | Cold gateway or slow model startup | Show a pending announcement instead of assuming instant streaming |
| Missing AbortError | Stop is cosmetic | The client cannot reliably cancel; you need an explicit cancel endpoint or server timeout |
| 429 with an HTML body | Error parsing will break | Detect content-type before JSON parsing and map failure to a retry state |
| Missing Retry-After | Backoff is guesswork | Use capped exponential backoff and announce retry timing |
The trace is not a benchmark. It is a contract check for the parts of the UI that fail after the demo: loading, cancel, error, and recovery.
Map the endpoint behavior into a typed state machine before writing the component. Use states such as idle, loading, streaming, aborting, error, and retrying. Each transition needs a focus decision and a live-region announcement. If the endpoint returns HTML on rate limit, the error transition must not call JSON.parse on it. If the abort test does not produce an AbortError, the Stop button should be labeled as a local pause rather than a guaranteed cancel.
Do not paste a production key into a browser harness. Use a revoked key or run the probe through a local proxy that injects the key server-side. The probe is intentionally small, but it still sends real requests.
Limits of this probe
- It does not measure model quality, token cost, security, privacy, or long-term availability.
- It makes only two requests; it does not test rate-limit saturation or concurrent load.
- It expects a browser fetch stream. Some endpoints only expose SSE through a server library, so you may need a Node version of the same checks.
- It does not verify the free allowance or server option; check the current provider docs.
Teams that need an SLA, deterministic throughput, or regulatory data handling should not treat a free endpoint as a production dependency. Use it for prototypes and integration-risk testing, then replace it with a contracted endpoint before launch.
Run the probe against the current MonkeyCode free endpoint and keep the raw trace in your integration notes. That trace is more useful than a thumbs-up because it becomes the input to the real state machine and the regression test. To report a failure, include the browser, OS, exact endpoint version, model identifier, and the transition that failed: for example, Chrome 126 plus VoiceOver, abort at 350ms after first chunk, no live-region announcement.
Top comments (0)