@juno-ai/bind 10.0.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -100,6 +100,106 @@
100
100
  * `properties` is an open per-database map with no fixed sub-schema), and the
101
101
  * key name is never changed so the model still emits the right argument key.
102
102
  * Confirmed live: `x-ai/grok-4.3` 400→200, Gemini/Claude unaffected.
103
+ * 8. Resolve a COMPOSITION BRANCH's `required` against the branch's own
104
+ * `properties` UNIONED with the enclosing node's `properties`, and — at the
105
+ * parameters ROOT only — make the surviving branch self-sufficient by copying
106
+ * the referenced property subschemas down into it and setting
107
+ * `type: "object"`. This is the narrow, measured exception to transform #3.
108
+ * The ordinary way to say "pass exactly one of these" puts the properties on
109
+ * the parent and only `required` in each branch:
110
+ *
111
+ * { type: "object", properties: { query: …, memoryItemId: … },
112
+ * oneOf: [ { required: ["query"] }, { required: ["memoryItemId"] } ] }
113
+ *
114
+ * which is how JSON Schema composition works, and which transform #3 alone
115
+ * sanitized to `oneOf: [{}, {}]` — a root xAI (Grok) hard-rejects
116
+ * (`tool parameter root must be an object type (root schema is an
117
+ * anyOf/oneOf union with a non-object branch)`, failing EVERY tool in the
118
+ * request), and which Gemini/OpenAI accept while silently losing the
119
+ * constraint, letting the model send both parameters or neither. Measured on
120
+ * xAI for branches of a ROOT union: `{type:"object", …}` or `{properties:…}`
121
+ * is accepted with EITHER alone sufficing; a bare `{required:[…]}` or `{}` is
122
+ * a 400. Hence the copy-down, which also makes the branch idempotent under
123
+ * this sanitizer (a second pass finds every `required` name in the branch's
124
+ * OWN `properties` and changes nothing).
125
+ *
126
+ * Two limits are load-bearing, both measured:
127
+ * - The parent scope is the ENCLOSING node only, and only for
128
+ * `allOf`/`anyOf`/`oneOf` elements. Widening transform #3 to resolve
129
+ * `required` against ALL ancestors at every node reintroduces the hard
130
+ * Gemini 400 that transform #3 exists to prevent ("required fields
131
+ * ['value'] are not defined in the schema properties" — still true on
132
+ * gemini-3.5/3.6/3.7-flash). Every non-branch node keeps transform #3
133
+ * exactly as it is, and so do `if`/`then`/`else` and `dependentSchemas`,
134
+ * which are not composition arrays.
135
+ * - The copy-down is ROOT-ONLY. The identical bare-`required` union nested
136
+ * under a property is accepted by all nine measured models including
137
+ * every Grok version, so a nested composition keeps its `required` (that
138
+ * is the fix) and is otherwise left alone — no provider constrains it,
139
+ * and churning it risks the transform-#5 `allOf` flatten.
140
+ * Only the names in the branch's own `required` are copied, so a branch does
141
+ * not inherit the parent's whole property set — and a single ceiling
142
+ * (`MAX_ROOT_COPY_DOWN`) bounds the copy-down across ALL branches of one
143
+ * root composition, because the per-branch bound does not bound N branches
144
+ * × P names. Past the ceiling the repair is skipped, leaving the branches
145
+ * bare for rule (a) below to drop.
146
+ * 9. Guarantee the parameters ROOT is one xAI will accept, as a final pass.
147
+ * xAI rejects a root that is not an object type, and the rejection takes the
148
+ * whole request (every tool) with it. Two distinct rules, each measured
149
+ * against grok-4.3/4.5/4.6 (and confirmed inert on the six Gemini/OpenAI
150
+ * models) through OpenRouter with `provider.allow_fallbacks: false` and one
151
+ * tool per request:
152
+ *
153
+ * a. Every element of a root `anyOf`/`oneOf` must be a plain object that
154
+ * is object-shaped, and must not be `{}`. A branch that declares a
155
+ * `type` is judged on that alone (`type: "object"` passes, anything
156
+ * else fails even when the branch also carries `properties` — measured
157
+ * 0/3, identical to a bare `{type:"string"}`); a branch with no `type`
158
+ * passes on `properties`. This holds even when the root ITSELF declares
159
+ * `type: "object"` and `properties` — an object root does not excuse a
160
+ * non-object branch. Rule (a) runs BEFORE the transform-#5 flatten,
161
+ * since that flatten refuses to merge an `allOf` while a union sibling
162
+ * is present; dropping the union afterwards would strand an `allOf`
163
+ * that the next pass would merge, breaking idempotence. A
164
+ * branch that still fails after #8 cannot be repaired (its `required`
165
+ * names something no `properties` map declares — the parent supplies it
166
+ * only via `patternProperties`/`additionalProperties`, or not at all —
167
+ * or it is a `$ref`/scalar/boolean branch), so the whole keyword is
168
+ * dropped. Losing one tool's constraint is strictly better than losing
169
+ * every tool in the request.
170
+ * b. A root carrying a composition keyword but declaring neither `type`
171
+ * nor `properties` is rejected on its OWN account, whatever the branches
172
+ * look like: `{allOf:[{$ref:…}], $defs:…}` and
173
+ * `{allOf:[{type:"object",…}, {not:…}]}` are both 0/3 on Grok. Adding
174
+ * `type: "object"` to such a root makes exactly those cases pass 9/9
175
+ * without touching the composition — so the un-flattenable root `allOf`
176
+ * that transform #5 deliberately preserves is RESCUED rather than
177
+ * discarded, and it stops being the silent 400 it is today. A `type` the
178
+ * schema declared for itself is never overridden.
179
+ *
180
+ * Rule (a) is all-or-nothing per keyword, because ONE unusable branch
181
+ * poisons the whole union on xAI even when its siblings are good (0/3 with a
182
+ * `{type:"string"}` sibling next to a valid object branch). Filtering the bad
183
+ * branches out instead is accepted by xAI (3/3) but is NOT what we do — it
184
+ * advertises a narrower tool, telling the model an arm is invalid so it never
185
+ * calls that shape, with nothing to surface the loss. Dropping the keyword
186
+ * leaves the parent's `properties` fully visible, so every shape stays
187
+ * callable and a wrong COMBINATION comes back from dispatch/the remote
188
+ * server as a recoverable tool error.
189
+ *
190
+ * Two shapes are known losses, both measured and both unavoidable here:
191
+ * a root union of `$ref` branches (xAI does not resolve them — 0/3 even with
192
+ * an object root), and a root union whose branch is itself a composition
193
+ * (`{oneOf:[{anyOf:[…]}]}` — 0/3 even with `type:"object"` stamped on the
194
+ * root). Both drop to a root that is accepted but advertises no properties.
195
+ * Recovering the first means inlining same-document `$ref`s before rule (a).
196
+ *
197
+ * A root `allOf`'s BRANCHES are deliberately not constrained by (a):
198
+ * measured, xAI accepts `$ref`, `not`, nested-`allOf` and even scalar
199
+ * branches under an `allOf` as long as (b) holds — only `anyOf`/`oneOf`
200
+ * branches are validated individually. A bare union root whose branches are
201
+ * all object-carrying is accepted too (xAI infers object-ness from them), so
202
+ * (b) leaves it alone. Compositions nested below the root are untouched.
103
203
  *
104
204
  * When (1) or (2) discards information the model could use — a collapsed
105
205
  * union type, or a wholly-dropped `enum` — that constraint is folded into the
@@ -146,5 +246,9 @@
146
246
  * shared by reference, so treat the result as read-only). A non-object root
147
247
  * (boolean schema, or malformed third-party payload) yields an empty object
148
248
  * rather than throwing.
249
+ *
250
+ * Idempotent: `sanitizeToolSchema(sanitizeToolSchema(x))` deep-equals
251
+ * `sanitizeToolSchema(x)`. Transform #8's copy-down depends on that — a repaired
252
+ * branch that a second pass re-emptied would be no repair at all.
149
253
  */
150
254
  export declare function sanitizeToolSchema(schema: Record<string, unknown>): Record<string, unknown>;