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:
NoxzRCW
2026-08-28 20:49:22 +02:00
committed by GitHub
parent b8c7ee599d
commit c5ebbb733c
3 changed files with 71 additions and 5 deletions

View 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

View File

@@ -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,
};
}

View File

@@ -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);
});