Restrict Vercel Node stream path to OpenAI chat to avoid Gemini empty responses

This commit is contained in:
CJACK.
2026-04-03 11:28:54 +08:00
parent a28e833f33
commit fabdba48c3
2 changed files with 36 additions and 2 deletions

View File

@@ -59,8 +59,9 @@ async function handler(req, res) {
return;
}
// Keep all non-stream behavior on Go side to avoid compatibility regressions.
if (!toBool(payload.stream)) {
// Keep all non-stream behavior and non-OpenAI-chat paths on Go side to avoid
// protocol-shape regressions (e.g. Gemini/Claude clients expecting their own formats).
if (!toBool(payload.stream) || !isNodeStreamSupportedPath(req.url || '')) {
await proxyToGo(req, res, rawBody);
return;
}
@@ -76,6 +77,23 @@ function isVercelRuntime() {
return asString(process.env.VERCEL) !== '' || asString(process.env.NOW_REGION) !== '';
}
function isNodeStreamSupportedPath(rawURL) {
const path = extractPathname(rawURL);
return path === '/v1/chat/completions';
}
function extractPathname(rawURL) {
const text = asString(rawURL);
if (!text) {
return '';
}
const q = text.indexOf('?');
if (q >= 0) {
return text.slice(0, q);
}
return text;
}
module.exports = handler;
module.exports.__test = {
@@ -89,4 +107,6 @@ module.exports.__test = {
boolDefaultTrue,
filterIncrementalToolCallDeltasByAllowed,
estimateTokens,
isNodeStreamSupportedPath,
extractPathname,
};

View File

@@ -18,6 +18,8 @@ const {
boolDefaultTrue,
filterIncrementalToolCallDeltasByAllowed,
shouldSkipPath,
isNodeStreamSupportedPath,
extractPathname,
} = handler.__test;
test('chat-stream exposes parser test hooks', () => {
@@ -225,3 +227,15 @@ test('shouldSkipPath skips dynamic response/fragments/*/status paths only', () =
assert.equal(shouldSkipPath('response/fragments/8/status'), true);
assert.equal(shouldSkipPath('response/status'), false);
});
test('node stream path guard only allows /v1/chat/completions', () => {
assert.equal(isNodeStreamSupportedPath('/v1/chat/completions'), true);
assert.equal(isNodeStreamSupportedPath('/v1/chat/completions?x=1'), true);
assert.equal(isNodeStreamSupportedPath('/v1beta/models/gemini-2.5-flash:streamGenerateContent'), false);
assert.equal(isNodeStreamSupportedPath('/anthropic/v1/messages'), false);
});
test('extractPathname strips query only', () => {
assert.equal(extractPathname('/v1/chat/completions?stream=true'), '/v1/chat/completions');
assert.equal(extractPathname('/v1beta/models/gemini-2.5-flash:streamGenerateContent?key=1'), '/v1beta/models/gemini-2.5-flash:streamGenerateContent');
});