mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 14:22:09 +03:00
This commit is contained in:
committed by
GitHub
parent
8f7172122c
commit
95cdde5f90
@@ -16,6 +16,7 @@
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **fix(translator):** every Gemini/Vertex `functionDeclaration.parameters` is now coerced to an OBJECT-typed schema before cleaning. Clients like GitHub Copilot send some tools (e.g. `terminal_last_command`) whose `parameters` is present but lacks a top-level `type: "object"` (just `{ properties }`, a scalar type, or `{}`); these slipped through `buildGeminiTools`' `params || default` guard and Vertex rejected them with `[400] ... functionDeclaration parameters schema should be of type OBJECT`. Hardens every OpenAI→Gemini tool request (Vertex / antigravity / agy / gemini). (#3357 — thanks @nullbytef0x)
|
||||
- **fix(electron):** clicking "Exit" (or applying an update) now terminates the **whole** server process tree, not just the direct child. The embedded server runs as `omniroute.exe`-as-node (`ELECTRON_RUN_AS_NODE`) and spawns grandchildren (embedded services, MITM proxy, tunnels); on Windows `ChildProcess.kill()` only terminates the direct child, so survivors kept `omniroute.exe` locked — the process "hung in memory" after Exit and updates failed with "file in use". New `killProcessTree()` helper uses `taskkill /PID <pid> /T /F` on Windows (signal-based on POSIX); wired into `stopNextServer`, the `waitForServerExit` force-kill, and `installUpdate`. (#3347 — thanks @Flexible78)
|
||||
- **fix(proxy):** proxy auto-selection is now **opt-in** (new `PROXY_AUTO_SELECT_ENABLED` flag, default off). Previously a single proxy in the registry silently became a global fallback for **all** provider connections (the Step-11 fallback listed every registry proxy, ignoring assignments and per-connection `proxy_enabled`). It now no-ops unless the operator enables the flag. (#3332 — thanks @hertznsk)
|
||||
- **fix(cli):** write the OpenCode config to `~/.config/opencode/opencode.json` on **all** platforms — on Windows OmniRoute wrote to `%APPDATA%\opencode\` but OpenCode reads from `%USERPROFILE%\.config\opencode\` (XDG), so dashboard-saved config silently had no effect. (#3330 — thanks @abdulkadirozyurt)
|
||||
|
||||
@@ -25,6 +25,29 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemini/Vertex requires every functionDeclaration.parameters to be an OBJECT-typed schema
|
||||
* (#3357: "functionDeclaration parameters schema should be of type OBJECT"). Some clients
|
||||
* (e.g. GitHub Copilot's `terminal_last_command`) send a `parameters` that is present but
|
||||
* lacks a top-level `type: "object"` — just `{ properties }`, a scalar/array type, or `{}`.
|
||||
* Coerce the parameters root to an object schema before it is cleaned; a falsy/non-record
|
||||
* schema becomes an empty object schema. Only the top level is touched — nested property
|
||||
* schemas are left to cleanJSONSchemaForAntigravity.
|
||||
*/
|
||||
function toGeminiParametersSchema(raw: unknown): Record<string, unknown> {
|
||||
if (!isRecord(raw)) {
|
||||
return { type: "object", properties: {} };
|
||||
}
|
||||
if (raw.type === "object") {
|
||||
return raw;
|
||||
}
|
||||
return {
|
||||
...raw,
|
||||
type: "object",
|
||||
properties: isRecord(raw.properties) ? raw.properties : {},
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeGeminiToolName(
|
||||
name: string,
|
||||
options: GeminiToolSanitizationOptions = {}
|
||||
@@ -174,9 +197,7 @@ export function buildGeminiTools(
|
||||
functionDeclarations.push({
|
||||
name: sanitizeGeminiToolName(fn.name, options),
|
||||
description: typeof fn.description === "string" ? fn.description : "",
|
||||
parameters: cleanJSONSchemaForAntigravity(
|
||||
fn.parameters || { type: "object", properties: {} }
|
||||
),
|
||||
parameters: cleanJSONSchemaForAntigravity(toGeminiParametersSchema(fn.parameters)),
|
||||
});
|
||||
}
|
||||
continue;
|
||||
@@ -186,9 +207,7 @@ export function buildGeminiTools(
|
||||
functionDeclarations.push({
|
||||
name: sanitizeGeminiToolName(rawTool.name, options),
|
||||
description: typeof rawTool.description === "string" ? rawTool.description : "",
|
||||
parameters: cleanJSONSchemaForAntigravity(
|
||||
rawTool.input_schema || { type: "object", properties: {} }
|
||||
),
|
||||
parameters: cleanJSONSchemaForAntigravity(toGeminiParametersSchema(rawTool.input_schema)),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -202,9 +221,7 @@ export function buildGeminiTools(
|
||||
functionDeclarations.push({
|
||||
name: sanitizeGeminiToolName(fn.name, options),
|
||||
description: typeof fn.description === "string" ? fn.description : "",
|
||||
parameters: cleanJSONSchemaForAntigravity(
|
||||
fn.parameters || { type: "object", properties: {} }
|
||||
),
|
||||
parameters: cleanJSONSchemaForAntigravity(toGeminiParametersSchema(fn.parameters)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
77
tests/unit/gemini-tool-params-object-3357.test.ts
Normal file
77
tests/unit/gemini-tool-params-object-3357.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Regression test for #3357 — Vertex AI function-calling fails with
|
||||
* "functionDeclaration parameters schema should be of type OBJECT".
|
||||
*
|
||||
* Gemini/Vertex requires every functionDeclaration.parameters to be an OBJECT-typed
|
||||
* schema. GitHub Copilot sends some tools (e.g. terminal_last_command) whose `parameters`
|
||||
* is present but lacks a top-level `type: "object"` (just `{ properties }`, a scalar type,
|
||||
* or `{}`). buildGeminiTools() must coerce the function-parameters root to an object schema
|
||||
* before cleaning, otherwise the typeless schema reaches Vertex and 400s.
|
||||
*/
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { buildGeminiTools } from "../../open-sse/translator/helpers/geminiToolsSanitizer.ts";
|
||||
|
||||
function paramsOf(tools: ReturnType<typeof buildGeminiTools>): any {
|
||||
return (tools as any)?.[0]?.functionDeclarations?.[0]?.parameters;
|
||||
}
|
||||
|
||||
describe("buildGeminiTools — function parameters must be an OBJECT schema (#3357)", () => {
|
||||
it("coerces a typeless parameters schema ({properties} only) to type:object, keeping props", () => {
|
||||
const tools = buildGeminiTools([
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "terminal_last_command",
|
||||
parameters: { properties: { cmd: { type: "string" } } },
|
||||
},
|
||||
},
|
||||
]);
|
||||
const params = paramsOf(tools);
|
||||
assert.equal(params.type, "object");
|
||||
assert.ok(params.properties && params.properties.cmd, "original properties preserved");
|
||||
assert.equal(params.properties.cmd.type, "string");
|
||||
});
|
||||
|
||||
it("coerces a scalar-typed parameters schema to type:object", () => {
|
||||
const tools = buildGeminiTools([
|
||||
{ type: "function", function: { name: "weird_tool", parameters: { type: "string" } } },
|
||||
]);
|
||||
assert.equal(paramsOf(tools).type, "object");
|
||||
});
|
||||
|
||||
it("coerces an empty parameters object to type:object", () => {
|
||||
const tools = buildGeminiTools([
|
||||
{ type: "function", function: { name: "no_args", parameters: {} } },
|
||||
]);
|
||||
assert.equal(paramsOf(tools).type, "object");
|
||||
});
|
||||
|
||||
it("defaults missing parameters to a type:object schema", () => {
|
||||
const tools = buildGeminiTools([
|
||||
{ type: "function", function: { name: "bare" } },
|
||||
]);
|
||||
assert.equal(paramsOf(tools).type, "object");
|
||||
});
|
||||
|
||||
it("leaves an already-valid object parameters schema as type:object with its props", () => {
|
||||
const tools = buildGeminiTools([
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "search",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: { query: { type: "string" } },
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const params = paramsOf(tools);
|
||||
assert.equal(params.type, "object");
|
||||
assert.equal(params.properties.query.type, "string");
|
||||
assert.deepEqual(params.required, ["query"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user