mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-21 06:12:17 +03:00
fix(skills): expand shorthand property types in injected tool schemas (#11881)
Every request through a strictly-validating provider (reproduced on opencode-go/glm-5.3-flash) failed with a 400: normalizeInputSchema() wrapped a skill's shorthand property map without expanding string values, so every injected omr_skill_* tool carried an invalid JSON Schema. Closes #11856. Thanks for the root-cause!
This commit is contained in:
1
changelog.d/fixes/11881-skills-shorthand-tool-schema.md
Normal file
1
changelog.d/fixes/11881-skills-shorthand-tool-schema.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(skills):** injected skill tools declared in shorthand (`{"content": "string"}`) now forward valid JSON Schema, unblocking providers that validate tool schemas strictly such as Zhipu GLM on the Console Go tier ([#11881](https://github.com/diegosouzapw/OmniRoute/pull/11881)) — thanks @NoxzRCW
|
||||
@@ -63,9 +63,17 @@ function normalizeInputSchema(input: Record<string, unknown>): Record<string, un
|
||||
if (typeof input.type === "string") {
|
||||
return input;
|
||||
}
|
||||
// Expand shorthand values: skills may declare `{ "content": "string" }`
|
||||
// instead of `{ "content": { "type": "string" } }`. Forwarding the shorthand
|
||||
// verbatim produces invalid JSON Schema, which strict-validating upstreams
|
||||
// (Zhipu GLM behind Console Go) reject with a 400 for the entire request.
|
||||
const properties: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
properties[key] = typeof value === "string" ? { type: value } : value;
|
||||
}
|
||||
return {
|
||||
type: "object",
|
||||
properties: input,
|
||||
properties,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ test("injectSkills renders enabled tools in provider-specific shapes", async ()
|
||||
function: {
|
||||
name: "omr_skill_c2VhcmNoQDEuMC4w", // encodedName("search@1.0.0")
|
||||
description: "search the web",
|
||||
parameters: { type: "object", properties: { query: "string" } },
|
||||
parameters: { type: "object", properties: { query: { type: "string" } } },
|
||||
},
|
||||
});
|
||||
assert.equal(decodeSkillToolName("omr_skill_c2VhcmNoQDEuMC4w"), "search@1.0.0");
|
||||
@@ -90,14 +90,14 @@ test("injectSkills renders enabled tools in provider-specific shapes", async ()
|
||||
{
|
||||
name: "omr_skill_c2VhcmNoQDEuMC4w",
|
||||
description: "search the web",
|
||||
input_schema: { type: "object", properties: { query: "string" } },
|
||||
input_schema: { type: "object", properties: { query: { type: "string" } } },
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(geminiTools, [
|
||||
{
|
||||
name: "omr_skill_c2VhcmNoQDEuMC4w",
|
||||
description: "search the web",
|
||||
parameters: { type: "object", properties: { query: "string" } },
|
||||
parameters: { type: "object", properties: { query: { type: "string" } } },
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(fallbackTools, [openaiTools[0]]);
|
||||
@@ -219,7 +219,7 @@ test("injectSkills auto mode matches message/context semantics and applies score
|
||||
function: {
|
||||
name: encodedName("issueSearch@1.0.0"),
|
||||
description: "search github issues and pull requests",
|
||||
parameters: { type: "object", properties: { query: "string" } },
|
||||
parameters: { type: "object", properties: { query: { type: "string" } } },
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -338,3 +338,60 @@ test("injectSkills auto mode limits selected auto skills and keeps on-mode skill
|
||||
assert.equal(names.includes("alwaysOnUtility@1.0.0"), true);
|
||||
assert.equal(names.filter((name) => name.startsWith("searchSkill")).length, 5);
|
||||
});
|
||||
|
||||
/**
|
||||
* Regression for #11856 — injected skill tools carried a malformed JSON Schema.
|
||||
*
|
||||
* Skills may declare their input in shorthand (`{ "content": "string" }`).
|
||||
* normalizeInputSchema() wrapped that bare property map as
|
||||
* `{ type: "object", properties: { content: "string" } }` without expanding the
|
||||
* shorthand values — and `"string"` is not a JSON Schema object. Zhipu GLM
|
||||
* behind the Console Go tier validates tool schemas strictly and rejected the
|
||||
* whole request with `[1210] Invalid API parameter`, giving a 100% failure rate
|
||||
* on that provider regardless of request content or credentials. Most other
|
||||
* providers tolerate the malformed schema, which is why it surfaced late.
|
||||
*
|
||||
* SkillSchema is `z.record(z.string(), z.unknown())`, so shorthand values pass
|
||||
* validation from every skill source — the skills API, the GitHub collector and
|
||||
* the skillssh marketplace alike.
|
||||
*/
|
||||
test("#11856 injectSkills expands shorthand property types into valid JSON Schema", async () => {
|
||||
await skillRegistry.register({
|
||||
name: "generation",
|
||||
version: "1.0.0",
|
||||
description: "generate content",
|
||||
schema: {
|
||||
input: {
|
||||
content: "string",
|
||||
count: "number",
|
||||
// already-expanded entries must survive untouched
|
||||
options: { type: "object", properties: { tone: { type: "string" } } },
|
||||
},
|
||||
output: { result: "string" },
|
||||
},
|
||||
handler: "generation-handler",
|
||||
enabled: true,
|
||||
apiKeyId: "key-11856",
|
||||
});
|
||||
|
||||
const expected = {
|
||||
type: "object",
|
||||
properties: {
|
||||
content: { type: "string" },
|
||||
count: { type: "number" },
|
||||
options: { type: "object", properties: { tone: { type: "string" } } },
|
||||
},
|
||||
};
|
||||
|
||||
const openaiTools = injectSkills({ provider: "openai", apiKeyId: "key-11856" });
|
||||
assert.deepEqual(
|
||||
(openaiTools[0] as { function: { parameters: unknown } }).function.parameters,
|
||||
expected
|
||||
);
|
||||
|
||||
const claudeTools = injectSkills({ provider: "anthropic", apiKeyId: "key-11856" });
|
||||
assert.deepEqual((claudeTools[0] as { input_schema: unknown }).input_schema, expected);
|
||||
|
||||
const geminiTools = injectSkills({ provider: "google", apiKeyId: "key-11856" });
|
||||
assert.deepEqual((geminiTools[0] as { parameters: unknown }).parameters, expected);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user