mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 03:42:21 +03:00
fix(zed-hosted): map developer role and Gemini enums for Zed proxy (#13543)
Zed-hosted requests are re-mapped to the narrower enums the Zed proxy validates. Responses input items with `role: "developer"` go back to `system` (#13362). For Gemini, `safetySettings[].threshold: "OFF"` becomes `BLOCK_NONE` and `functionCallingConfig.mode` `VALIDATED`/`AUTO`/`ANY`/`NONE` become Zed's lowercase `auto`/`any`/`none` (#13363). Chosen over #13541, which carried the identical commit. Maintainer changes: the inline mapping used five `as any` casts, which `no-explicit-any` rejects in `open-sse/`. It is now two typed helpers (`adaptGeminiRequestForZed`, `adaptResponsesRequestForZed`, exposed through `__test__`) plus a small mode table; behavior is unchanged. Added `tests/unit/zed-hosted-proxy-enums-13362.test.ts` (4 cases). Zed suites 47/47; `check:open-sse-typecheck` clean. Validated in one consolidated batch of this series (37 PRs boarded together on `release/v3.8.51`): `typecheck:core`, `check:open-sse-typecheck` and `check:dashboard-typecheck` clean; ESLint clean on every changed file; file-size, complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync and migration-numbering gates green (only the pre-existing `open-sse/utils/stream.ts` file-size red remains, inherited from the base); 3,743 focused `node:test` cases plus 34 vitest cases green. Thanks @KooshaPari!
This commit is contained in:
committed by
GitHub
parent
2d1a281d12
commit
4745fca27d
@@ -81,6 +81,49 @@ function normalizeZedProvider(value: unknown, model: unknown): ZedProviderName {
|
||||
return ZED_PROVIDER.openai;
|
||||
}
|
||||
|
||||
function asMutableRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
// Zed's Google proxy enums are narrower than Google's own (#13363): the safety
|
||||
// threshold only accepts BLOCK_NONE (not "OFF"), and FunctionCallingMode is
|
||||
// lowercase auto/any/none (no VALIDATED).
|
||||
const ZED_FUNCTION_CALLING_MODES: Record<string, string> = {
|
||||
VALIDATED: "auto",
|
||||
AUTO: "auto",
|
||||
ANY: "any",
|
||||
NONE: "none",
|
||||
};
|
||||
|
||||
function adaptGeminiRequestForZed(request: unknown): unknown {
|
||||
const record = asMutableRecord(request);
|
||||
if (!record) return request;
|
||||
if (Array.isArray(record.safetySettings)) {
|
||||
for (const entry of record.safetySettings) {
|
||||
const setting = asMutableRecord(entry);
|
||||
if (setting?.threshold === "OFF") setting.threshold = "BLOCK_NONE";
|
||||
}
|
||||
}
|
||||
const callingConfig = asMutableRecord(asMutableRecord(record.toolConfig)?.functionCallingConfig);
|
||||
const mappedMode = callingConfig
|
||||
? ZED_FUNCTION_CALLING_MODES[String(callingConfig.mode || "").toUpperCase()]
|
||||
: undefined;
|
||||
if (callingConfig && mappedMode) callingConfig.mode = mappedMode;
|
||||
return request;
|
||||
}
|
||||
|
||||
// Zed's OpenAI proxy Role enum only has user/assistant/system/tool — no
|
||||
// "developer" (#13362) — so developer-role input items go back to system.
|
||||
function adaptResponsesRequestForZed(request: unknown): unknown {
|
||||
const input = asMutableRecord(request)?.input;
|
||||
if (!Array.isArray(input)) return request;
|
||||
for (const entry of input) {
|
||||
const item = asMutableRecord(entry);
|
||||
if (item?.role === "developer") item.role = "system";
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
function buildProviderRequest(
|
||||
provider: ZedProviderName,
|
||||
model: string,
|
||||
@@ -92,10 +135,14 @@ function buildProviderRequest(
|
||||
return openaiToClaudeRequest(model, body, true);
|
||||
}
|
||||
if (provider === ZED_PROVIDER.google) {
|
||||
return openaiToGeminiRequest(model, body as Record<string, unknown>, true, credentials);
|
||||
return adaptGeminiRequestForZed(
|
||||
openaiToGeminiRequest(model, body as Record<string, unknown>, true, credentials)
|
||||
);
|
||||
}
|
||||
if (provider === ZED_PROVIDER.openai) {
|
||||
return openaiToOpenAIResponsesRequest(model, body, true, credentials);
|
||||
return adaptResponsesRequestForZed(
|
||||
openaiToOpenAIResponsesRequest(model, body, true, credentials)
|
||||
);
|
||||
}
|
||||
return {
|
||||
...(body as Record<string, unknown>),
|
||||
@@ -551,6 +598,8 @@ export class ZedHostedExecutor extends BaseExecutor {
|
||||
export default ZedHostedExecutor;
|
||||
|
||||
export const __test__ = {
|
||||
adaptGeminiRequestForZed,
|
||||
adaptResponsesRequestForZed,
|
||||
normalizeZedProvider,
|
||||
unwrapZedLine,
|
||||
wrapZedCompletionStream,
|
||||
|
||||
57
tests/unit/zed-hosted-proxy-enums-13362.test.ts
Normal file
57
tests/unit/zed-hosted-proxy-enums-13362.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { __test__ } = await import("../../open-sse/executors/zed-hosted.ts");
|
||||
const { adaptGeminiRequestForZed, adaptResponsesRequestForZed } = __test__;
|
||||
|
||||
// #13362 / #13363: Zed's hosted proxy validates narrower enums than the upstream
|
||||
// APIs, so requests translated for Google/OpenAI must be re-mapped before dispatch.
|
||||
|
||||
test("#13363 Gemini safety threshold OFF becomes BLOCK_NONE for Zed", () => {
|
||||
const request = {
|
||||
safetySettings: [
|
||||
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "OFF" },
|
||||
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_ONLY_HIGH" },
|
||||
],
|
||||
};
|
||||
adaptGeminiRequestForZed(request);
|
||||
assert.deepEqual(
|
||||
request.safetySettings.map((s) => s.threshold),
|
||||
["BLOCK_NONE", "BLOCK_ONLY_HIGH"]
|
||||
);
|
||||
});
|
||||
|
||||
test("#13363 Gemini function-calling modes map to Zed's lowercase enum", () => {
|
||||
for (const [mode, expected] of [
|
||||
["VALIDATED", "auto"],
|
||||
["AUTO", "auto"],
|
||||
["ANY", "any"],
|
||||
["NONE", "none"],
|
||||
]) {
|
||||
const request = { toolConfig: { functionCallingConfig: { mode } } };
|
||||
adaptGeminiRequestForZed(request);
|
||||
assert.equal(request.toolConfig.functionCallingConfig.mode, expected, mode);
|
||||
}
|
||||
});
|
||||
|
||||
test("#13363 Gemini request without safety or tool config passes through untouched", () => {
|
||||
const request = { contents: [{ role: "user", parts: [{ text: "hi" }] }] };
|
||||
const before = JSON.stringify(request);
|
||||
assert.equal(adaptGeminiRequestForZed(request), request);
|
||||
assert.equal(JSON.stringify(request), before);
|
||||
assert.equal(adaptGeminiRequestForZed(null), null);
|
||||
});
|
||||
|
||||
test("#13362 Responses developer-role input items become system for Zed", () => {
|
||||
const request = {
|
||||
input: [
|
||||
{ role: "developer", content: "be terse" },
|
||||
{ role: "user", content: "hello" },
|
||||
],
|
||||
};
|
||||
adaptResponsesRequestForZed(request);
|
||||
assert.deepEqual(
|
||||
request.input.map((item) => item.role),
|
||||
["system", "user"]
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user