`isOfferable` in `core/json/rootUnion.ts` checked that a merged branch carries
at least one property and that every property value is readable, but never that
each name in `required` resolves to a property a form can render. A branch like
`{ properties: { kind }, required: ["kind", "payload"] }` was therefore offered
in the picker, rendered no control for `payload`, and had the submit-time check
report it missing permanently — the exact dead end the function's own comment
already reasons through for a `false`-schema required field, one case short.
Judge the merge, so a branch requiring only names the ROOT declares —
`anyOf: [{ required: ["email"] }, …]` — stays offerable as before. `hasOwn`
rather than `in`, since an argument legally named `constructor` would otherwise
resolve through the prototype and read as declared.
Declining every branch of a union leaves the web form with neither a picker nor
a field, which is a different dead end. `SchemaForm` now seeds the raw-JSON
editor (#2151) in that one case — a root union that resolved to no branches and
no properties — so the arguments stay expressible. Seeded rather than forced, so
the switch still works, and re-seeded during render (never in an effect) because
the form is reused across tools rather than remounted. A tool that genuinely
takes no arguments is untouched. The TUI has no such editor; there the union is
simply not offered, which is already better than a section that can never be
submitted.
Adds a `record_shipment_by` preset and wires it into the
`root-union-schemas-http.json` showcase so the shape is reproducible by hand,
with docs/test-servers.md covering it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PxYT4eKdnx1yRw7x4bW7Ts
Signed-off-by: cliffhall <cliff@futurescale.com>
91 lines
2.3 KiB
JSON
91 lines
2.3 KiB
JSON
{
|
|
"serverInfo": {
|
|
"name": "root-union-schemas-showcase",
|
|
"version": "1.0.0"
|
|
},
|
|
"tools": [
|
|
{ "preset": "echo" },
|
|
{ "preset": "get_weather" },
|
|
{ "preset": "record_shipment_by" }
|
|
],
|
|
"rawToolSchemas": {
|
|
"echo": {
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"message": { "type": "string" }
|
|
},
|
|
"required": ["message"],
|
|
"anyOf": [
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"kind": { "type": "string", "const": "email" },
|
|
"address": { "type": "string" }
|
|
},
|
|
"required": ["kind", "address"]
|
|
},
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"kind": { "type": "string", "const": "sms" },
|
|
"phone": { "type": "string" }
|
|
},
|
|
"required": ["kind", "phone"]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"record_shipment_by": {
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"discriminator": { "propertyName": "by" },
|
|
"oneOf": [
|
|
{
|
|
"type": "object",
|
|
"title": "By address",
|
|
"properties": { "by": { "type": "string", "const": "address" } },
|
|
"required": ["by", "address"]
|
|
},
|
|
{
|
|
"type": "object",
|
|
"title": "By tracking number",
|
|
"properties": { "by": { "type": "string", "const": "tracking" } },
|
|
"required": ["by", "tracking"]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"get_weather": {
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"discriminator": { "propertyName": "by" },
|
|
"oneOf": [
|
|
{
|
|
"type": "object",
|
|
"title": "By city",
|
|
"properties": {
|
|
"by": { "type": "string", "const": "city" },
|
|
"city": { "type": "string" }
|
|
},
|
|
"required": ["by", "city"]
|
|
},
|
|
{
|
|
"type": "object",
|
|
"title": "By coordinates",
|
|
"properties": {
|
|
"by": { "type": "string", "const": "coords" },
|
|
"lat": { "type": "number" },
|
|
"lon": { "type": "number" }
|
|
},
|
|
"required": ["by", "lat", "lon"]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"transport": {
|
|
"type": "streamable-http",
|
|
"port": 6604
|
|
}
|
|
}
|