Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const NATIVE_AUTH_HEADERS = [
'authorization', 'x-api-key', 'api-key', 'anthropic-api-key',
];

function stripClaudeOneMMarker(model) {
return model.replace(/\[1m\]$/i, '');
}

function safeHeaders(headers, strip) {
const dynamicStrip = new Set(strip);
for (const token of String(headers.connection || '').split(',')) {
Expand Down Expand Up @@ -106,7 +110,9 @@ async function startRouterServer(options) {

const selected = typeof body?.model === 'string' ? body.model : '';
const isKenari = selected.startsWith('kenari/');
const id = isKenari ? selected.slice('kenari/'.length) : selected;
const id = isKenari
? stripClaudeOneMMarker(selected.slice('kenari/'.length))
: selected;
const model = isKenari ? models.get(id) : null;
if (isKenari && (!id || !model)) {
replyJson(res, 400, `unknown or unavailable Kenari model "${selected}"`);
Expand Down Expand Up @@ -146,6 +152,18 @@ async function startRouterServer(options) {
}, (upstreamRes) => {
const responseHeaders = safeHeaders(upstreamRes.headers, RESPONSE_STRIP);
res.writeHead(upstreamRes.statusCode || 502, responseHeaders);
const terminateDownstream = (error) => {
if (!res.destroyed) res.destroy(error);
};
upstreamRes.on('aborted', () => {
terminateDownstream(new Error(`${isKenari ? 'Kenari' : 'native'} upstream response aborted`));
});
upstreamRes.on('error', terminateDownstream);
upstreamRes.on('close', () => {
if (!upstreamRes.complete) {
terminateDownstream(new Error(`${isKenari ? 'Kenari' : 'native'} upstream response closed early`));
}
});
upstreamRes.pipe(res);
res.on('close', () => {
if (!res.writableEnded) upstreamRes.destroy();
Expand Down
77 changes: 77 additions & 0 deletions test/v2-router.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,83 @@ test('router isolates native and Kenari model, auth, headers, and token rewrites
assert.ok(router.childPid > 0);
});

test('router strips Claude 1m markers from Kenari models before lookup and forwarding', async (t) => {
const seen = [];
const base = await upstream(t, async (req, res) => {
seen.push(await collect(req));
res.writeHead(200, { 'content-type': 'application/json' });
res.end('{"ok":true}');
});
const router = await startRouter({
nativeBase: base,
kenariBase: base,
credential: 'kn-secret',
catalog: { models: [{ id: 'minimax-m3' }] },
});
t.after(() => router.close());

for (const marker of ['[1m]', '[1M]']) {
const response = await fetch(router.url + '/v1/messages', {
method: 'POST',
body: JSON.stringify({ model: `kenari/minimax-m3${marker}` }),
});
assert.equal(response.status, 200);
assert.deepEqual(await response.json(), { ok: true });
}

const native = await fetch(router.url + '/v1/messages', {
method: 'POST',
body: JSON.stringify({ model: 'claude-opus-5[1m]' }),
});
assert.equal(native.status, 200);
assert.deepEqual(await native.json(), { ok: true });

const malformed = await fetch(router.url + '/v1/messages', {
method: 'POST',
body: JSON.stringify({ model: 'kenari/minimax-m3[1m]-extra' }),
});
assert.equal(malformed.status, 400);
assert.deepEqual(
seen.map((body) => body.model),
['minimax-m3', 'minimax-m3', 'claude-opus-5[1m]'],
);
});

test('router terminates the client response when an upstream stream aborts', async (t) => {
const base = await upstream(t, (_req, res) => {
res.writeHead(200, { 'content-type': 'text/event-stream' });
res.write('data: partial\n\n');
setImmediate(() => res.socket.destroy());
});
const router = await startRouter({
nativeBase: base,
kenariBase: base,
catalog: { models: [] },
});
t.after(() => router.close());

const response = await fetch(router.url + '/v1/messages', {
method: 'POST',
body: JSON.stringify({ model: 'claude-opus-5' }),
});
assert.equal(response.status, 200);

let timeout;
const outcome = await Promise.race([
response.text().then(
() => ({ completed: true }),
(error) => ({ error }),
),
new Promise((resolve) => {
timeout = setTimeout(() => resolve({ timedOut: true }), 1_000);
}),
]);
clearTimeout(timeout);

assert.equal(outcome.timedOut, undefined, 'aborted stream must not remain open');
assert.ok(outcome.error, 'aborted stream must reject the downstream reader');
});

test('router fails closed for unknown or logged-out Kenari model', async (t) => {
let requests = 0;
const base = await upstream(t, (_req, res) => { requests += 1; res.end(); });
Expand Down
Loading