fix: address Copilot review — const precedence, TUI variant model, decline both-keyword unions
- rootUnion: decline a schema carrying BOTH oneOf and anyOf rather than reading one and silently dropping the other's constraints. - SchemaForm: handle `const` before every type dispatch and for every constant type, so a numeric/boolean constant, or a schema carrying both `const` and `enum`, can no longer offer a forbidden value. - collectSchemaDefaults / TUI initialValue: `const` outranks `default`, which is an annotation a schema may set to something its const rejects. - convertToolParameters: resolve from the first branch's merged schema, so a branch's specialization of a root-declared property is what types the arg. - TUI: ink-form keys values by field name across the whole form, so two branches' `kind` were one field and the later section's initial value decided what the earlier submitted. Branch fields now render under prefixed names behind a Variant select, and decodeFormValues translates back on submit, dropping every branch but the chosen one. Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
5933162dd0
commit
2782ed75d1
2 changed files with 370 additions and 68 deletions
|
|
@ -104,6 +104,25 @@ describe("JSON Utils", () => {
|
|||
).toEqual({ note: "hi", count: 42, enabled: true });
|
||||
});
|
||||
|
||||
it("prefers a branch's specialization of a root property (#2123)", () => {
|
||||
const specializing: Tool = {
|
||||
name: "specializing",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
// The root declares the name but constrains nothing; the branch is
|
||||
// what says it is a number.
|
||||
properties: { count: {} },
|
||||
anyOf: [
|
||||
{ type: "object", properties: { count: { type: "number" } } },
|
||||
{ type: "object", properties: { other: { type: "string" } } },
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(convertToolParameters(specializing, { count: "3" })).toEqual({
|
||||
count: 3,
|
||||
});
|
||||
});
|
||||
|
||||
it("coerces a value whose schema lives on a root allOf branch (#2123)", () => {
|
||||
const allOfTool: Tool = {
|
||||
name: "allof-tool",
|
||||
|
|
|
|||
|
|
@ -63,13 +63,17 @@ describe("resolveRootUnion", () => {
|
|||
expect(branches[0].schema.anyOf).toBeUndefined();
|
||||
});
|
||||
|
||||
it("prefers oneOf when a schema carries both", () => {
|
||||
const { branches } = resolveRootUnion({
|
||||
it("declines a schema carrying both oneOf and anyOf", () => {
|
||||
// Independent keywords a value satisfies together, so reading one and
|
||||
// dropping the other would build a form missing real constraints.
|
||||
const { base, branches } = resolveRootUnion({
|
||||
type: "object",
|
||||
properties: { note: { type: "string" } },
|
||||
oneOf: [EMAIL],
|
||||
anyOf: [EMAIL, SMS],
|
||||
});
|
||||
expect(branches).toHaveLength(1);
|
||||
expect(branches).toEqual([]);
|
||||
expect(Object.keys(base.properties ?? {})).toEqual(["note"]);
|
||||
});
|
||||
|
||||
it("merges allOf branches unconditionally", () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue