fix: address Copilot review round 3 — conjunctive keywords, unflattenable allOf, branch inference
- rootUnion: a branch restating a constraint the root states differently is declined (keywords at one level are conjunctive and no intersection is computed here), while annotations may disagree freely. An `allOf` with a member it cannot fold in — JSON Schema's boolean form, or a $ref whose constraints are unknown rather than absent — is left intact instead of merged-and-stripped, which would turn an unsatisfiable schema into a fillable form. - rootUnion: `declaresAnyFields` counts the raw composition members, so `hasInputFields` reports fields for a union the resolver declines and an App tool is never auto-invoked with empty arguments. - SchemaForm: the picker opens on the branch the supplied values identify (an App deep link overlays its args, so they can name another branch), and a branch switch no longer carries a value the outgoing branch declared — the branches may type the same name differently. - TUI: a base property some branch redeclares is offered in EVERY branch's section under that branch's inherited or specialized declaration, so a branch that does not redeclare it can still supply a required root argument. Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
84cd0c10f0
commit
1a271e4931
2 changed files with 442 additions and 69 deletions
|
|
@ -118,6 +118,22 @@ describe("hasInputFields with root composition (#2123)", () => {
|
|||
).toBe(true);
|
||||
});
|
||||
|
||||
it("sees fields on a union the form declines to flatten", () => {
|
||||
// Otherwise an App tool with such a schema is treated as input-free and
|
||||
// invoked immediately with `{}`.
|
||||
expect(
|
||||
hasInputFields(
|
||||
tool({
|
||||
type: "object",
|
||||
anyOf: [
|
||||
{ type: "object", properties: { a: { type: "string" } } },
|
||||
{ $ref: "#/$defs/Other" },
|
||||
],
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("still reports no fields for a bare object schema", () => {
|
||||
expect(hasInputFields(tool({ type: "object" }))).toBe(false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Tool } from "@modelcontextprotocol/client";
|
||||
import { resolveRootUnion } from "@inspector/core/json/rootUnion.js";
|
||||
import { declaresAnyFields } from "@inspector/core/json/rootUnion.js";
|
||||
|
||||
/**
|
||||
* Returns the display label for an MCP entity that follows the BaseMetadata
|
||||
|
|
@ -17,19 +17,16 @@ export function resolveDisplayLabel(name: string, title?: string): string {
|
|||
* one place so the definition of "has fields" stays consistent if it ever
|
||||
* grows to consider `additionalProperties` etc.
|
||||
*
|
||||
* Root composition is resolved first, since a schema declaring its fields on a
|
||||
* root `allOf`/`oneOf`/`anyOf` has none of its own (#2123) — an App tool with
|
||||
* such a schema would otherwise launch with empty arguments rather than asking
|
||||
* for them.
|
||||
* Root composition counts, since a schema declaring its fields on a root
|
||||
* `allOf`/`oneOf`/`anyOf` has none of its own (#2123) — an App tool with such a
|
||||
* schema would otherwise launch with empty arguments rather than asking for
|
||||
* them. Counted from the composition members directly rather than from a
|
||||
* resolved union, so a schema whose composition the form declines to flatten
|
||||
* still reports the fields it has: it renders fewer controls, not none, and
|
||||
* auto-invoking it would be wrong either way.
|
||||
*/
|
||||
export function hasInputFields(tool: Tool): boolean {
|
||||
const { base, branches } = resolveRootUnion(tool.inputSchema);
|
||||
return (
|
||||
Object.keys(base.properties ?? {}).length > 0 ||
|
||||
branches.some(
|
||||
(branch) => Object.keys(branch.schema.properties ?? {}).length > 0,
|
||||
)
|
||||
);
|
||||
return declaresAnyFields(tool.inputSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue