@juno-ai/bind 11.0.0 → 13.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juno-ai/bind",
3
- "version": "11.0.0",
3
+ "version": "13.0.0",
4
4
  "description": "Agent harness: the tool-calling turn kernel, deterministic LLM provider routing with transport-error classification, the streaming-completion watchdog, run mechanics, sub-agent lineage and admission, transcript healing, tool-schema sanitization, the plugin/tool vocabulary, and the skill vocabulary (`./skills`) for progressive knowledge disclosure. MIT-licensed; published to npm from the canonical repo via scripts/publish-bind.ts (docs/bind.md).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -100,6 +100,196 @@
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 non-object
178
+ * `type` the root declared for itself IS rewritten to `"object"` and
179
+ * recorded in the description — measured, such a root is rejected by
180
+ * every model. Nested nodes keep their declared types unless transform
181
+ * #12 applies.
182
+ *
183
+ * Rule (a) is all-or-nothing per keyword, because ONE unusable branch
184
+ * poisons the whole union on xAI even when its siblings are good (0/3 with a
185
+ * `{type:"string"}` sibling next to a valid object branch). Filtering the bad
186
+ * branches out instead is accepted by xAI (3/3) but is NOT what we do — it
187
+ * advertises a narrower tool, telling the model an arm is invalid so it never
188
+ * calls that shape, with nothing to surface the loss. Dropping the keyword
189
+ * leaves the parent's `properties` fully visible, so every shape stays
190
+ * callable and a wrong COMBINATION comes back from dispatch/the remote
191
+ * server as a recoverable tool error.
192
+ *
193
+ * A branch's OWN `anyOf`/`oneOf` disqualifies it too, whatever else it
194
+ * declares: `{type:"object", anyOf:[…]}` as a root union branch is 0/3 on
195
+ * Grok, as is a branch carrying both `allOf` and `anyOf` — the union is what
196
+ * poisons it. Scope is the branch's own keys and never its subtree: a
197
+ * composition on a branch's PROPERTY is accepted 9/9, one or two levels
198
+ * down, so a subtree scan would destroy constraints every provider honours.
199
+ * A branch's own `allOf` is likewise accepted 9/9 (even with a
200
+ * bare-`required` sub-branch), the same intersection-vs-union asymmetry xAI
201
+ * applies to the root — hence `ROOT_UNION_KEYS`, not `COMPOSITION_KEYS`.
202
+ *
203
+ * A root union of `$ref` branches is no longer a loss — transform #14 below
204
+ * inlines the targets so the union survives. What remains is the narrow
205
+ * residue it cannot inline: a target that is not a plain object schema, one
206
+ * whose expanded size exceeds the copy-down ceiling, and a self-referential
207
+ * root pointer. Those branches stay unusable and still drop the keyword.
208
+ *
209
+ * Rule (b) covers EVERY root, not only composition roots. A property sweep
210
+ * over recursive shapes (`__tests__/tool-schema/property/`) found the
211
+ * narrower gate emitting unusable roots for whole classes the fixtures
212
+ * never reached: a scalar or array root (reachable from transform #1
213
+ * collapsing `{type:["string","null"]}`), an annotation-only root, a
214
+ * `$defs`-only root, and a root whose `type` contradicts its `properties` —
215
+ * that last one rejected by all nine models, the only rule here with no
216
+ * tolerant provider. Rule (b) also DROPS a `const` or `$ref` carried by the
217
+ * root: measured `root schema is a const` / `root schema is a $ref`, 400
218
+ * even alongside `type: "object"` and `properties`, and for `$ref` even
219
+ * when the target resolves. Nested `const`/`$ref` are meaningful and
220
+ * untouched.
221
+ *
222
+ * A root `allOf`'s BRANCHES are deliberately not constrained by (a):
223
+ * measured, xAI accepts `$ref`, `not`, nested-`allOf` and even scalar
224
+ * branches under an `allOf` as long as (b) holds — only `anyOf`/`oneOf`
225
+ * branches are validated individually. A bare union root whose branches are
226
+ * all object-carrying is accepted too (xAI infers object-ness from them), so
227
+ * (b) leaves it alone. Compositions nested below the root are untouched.
228
+ *
229
+ * 10. Drop an EMPTY array-valued keyword (`allOf`/`anyOf`/`oneOf`/`prefixItems`)
230
+ * at any depth. xAI rejects one anywhere in the tree — `/properties/a/oneOf:
231
+ * [] has less than 1 item` — and it carries no constraint (an `allOf` of
232
+ * nothing is satisfied by everything). Empty OBJECTS (`properties: {}`,
233
+ * `$defs: {}`) and an empty `required: []` are accepted and left alone;
234
+ * `enum: []` and `required: []` were already omitted by transforms #2/#3.
235
+ * 11. Drop a `$ref` whose LOCAL target does not resolve. xAI resolves `#/…`
236
+ * pointers itself and 400s the whole request on a dangling one
237
+ * (`unresolvable $ref '#/$defs/Nope'`), which a third-party MCP server
238
+ * produces easily by shipping a subschema without its definitions — or by
239
+ * putting `$defs` on a nested node, since `#/$defs/X` is anchored at the
240
+ * document ROOT and a nested `$defs` is unaddressable that way. External
241
+ * refs (`https://…`) and resolvable non-`$defs` pointers (`#/properties/a`)
242
+ * are both accepted by xAI and left alone. Only the `$ref` keyword is
243
+ * dropped, so the rest of the node survives.
244
+ *
245
+ * 12. Give any node carrying `properties` an explicit `type: "object"`. Gemini
246
+ * rejects the type-less form outright — "Unable to submit request because
247
+ * `t` functionDeclaration `parameters.b` schema specified incorrect schema
248
+ * type field. For schema with properties, schema type should be OBJECT" —
249
+ * on gemini-3.5/3.6/3.7-flash, and rejects a CONTRADICTORY declared type
250
+ * (`{type:"string", properties:{…}}`) the same way, so the type is
251
+ * overwritten rather than merely defaulted. It is not composition-specific:
252
+ * a plain property subschema and an `items` schema fail identically. xAI
253
+ * and OpenAI accept every one of those forms, which is why this survived
254
+ * until a property sweep over recursive shapes went looking for it. The
255
+ * root is exempt in Gemini's own validator, but the rule is applied
256
+ * uniformly because it costs nothing and one rule beats two.
257
+ *
258
+ * It is resolved during the node's type resolution, not patched onto the
259
+ * finished node, so `dropEnum` sees the real type — an `enum` left on a
260
+ * node that has just become an object would otherwise be dropped by the
261
+ * NEXT pass instead of this one, breaking idempotence. A discarded
262
+ * contradictory type is folded into the description like transform #1's.
263
+ * This also makes a contradictory root-union BRANCH repairable: it arrives
264
+ * at transform #9 already retyped, so the constraint survives instead of
265
+ * the whole keyword being dropped.
266
+ *
267
+ * 13. Drop a `pattern` that uses a regex construct a provider's validator
268
+ * refuses, echoing it into the description instead. Measured: `(?=`, `(?!`,
269
+ * `(?<=`, `(?<!` AND named groups `(?<name>` are rejected by gpt-5.6-sol
270
+ * and gpt-5.6-terra (`Invalid JSON schema: regex lookaround is not
271
+ * supported` — the message says lookaround, but a named group fails
272
+ * identically, so the giveaway is the `(?<` prefix); a BACKREFERENCE
273
+ * (`\1`–`\9`) is rejected by grok-4.5. Non-capturing `(?:` is accepted
274
+ * everywhere and is deliberately NOT caught. This is the defect that broke
275
+ * Monad's own `spaces__invite_member` on every OpenAI model — a whole-
276
+ * request 400 from one tool's email pattern. The scan tracks backslash
277
+ * escaping and character classes, so a literal `\(\?=` or a `[(?=]` class
278
+ * keeps its pattern: dropping a pattern that would have been accepted
279
+ * costs a real constraint on every provider.
280
+ * 14. Inline a ROOT union branch that is a local `$ref`, so the union survives
281
+ * rule (a) instead of being dropped whole. `z.union([A, B])` renders as
282
+ * `{anyOf:[{$ref},{$ref}], $defs}` and xAI does not resolve a `$ref` union
283
+ * branch (0/3 even with `type: "object"` on the root) — so rule (a) used to
284
+ * drop the keyword, which for a root whose properties live entirely in
285
+ * `$defs` left the tool advertising NO parameters. Inlining produces
286
+ * ordinary object branches, accepted 9/9. Only branches that are not
287
+ * already usable are inlined, one level deep (a `$ref` inside the target
288
+ * resolves on its own, which is what makes a recursive definition
289
+ * terminate here), with branch keywords winning over the target's per
290
+ * 2020-12 `$ref` semantics and a cap for the same reason the copy-down has
291
+ * one. A branch whose target is not a plain object schema stays unusable
292
+ * and still falls through to the drop.
103
293
  *
104
294
  * When (1) or (2) discards information the model could use — a collapsed
105
295
  * union type, or a wholly-dropped `enum` — that constraint is folded into the
@@ -146,5 +336,9 @@
146
336
  * shared by reference, so treat the result as read-only). A non-object root
147
337
  * (boolean schema, or malformed third-party payload) yields an empty object
148
338
  * rather than throwing.
339
+ *
340
+ * Idempotent: `sanitizeToolSchema(sanitizeToolSchema(x))` deep-equals
341
+ * `sanitizeToolSchema(x)`. Transform #8's copy-down depends on that — a repaired
342
+ * branch that a second pass re-emptied would be no repair at all.
149
343
  */
150
344
  export declare function sanitizeToolSchema(schema: Record<string, unknown>): Record<string, unknown>;