|
4 | 4 |
|
5 | 5 | ### What changed and why |
6 | 6 |
|
7 | | -- `utils/tool-schema-compat.ts` no longer hoists a ROOT schema's `type` into its combiner |
8 | | - branches. A tool's root `parameters` must stay an object schema: OpenAI-compatible gateways |
9 | | - reject a typeless root with `tools.function.parameters.type is required and must be "object"`, |
10 | | - which is exactly how an Apitopia/Kimi turn died on 2026-08-04. `normalizeNode` now takes an |
11 | | - `isRoot` flag so branch-level hoisting (still correct below the root) is unchanged, and |
12 | | - `ensureRootObjectSchema` guarantees the emitted root is always `{"type":"object", ...}`. |
13 | | -- `mergeRootObjectUnion` now merges the root's OWN `properties`/`required` with the branches' |
14 | | - instead of replacing them. It previously returned `{"properties":{},"type":"object"}` for a root |
15 | | - union that declared its properties at the root — silently sending a tool with zero parameters. |
16 | | - Untyped constraint-only branches (`{ required: [...] }` over root properties) are accepted, and |
17 | | - `required` keeps root entries plus only the names every branch shares. |
18 | | -- Both flavors share one root guarantee: `normalizeToolParametersForMoonshot` is now the OpenAI |
19 | | - normalization plus annotation stripping, rather than a second, divergent root-merge path. |
20 | | -- `api/anthropic-messages.ts` resolves a tool's root parameters through the shared |
21 | | - `resolveRootObjectSchema` before building `input_schema`. `convertTools` reads top-level |
22 | | - `properties`/`required` only, so a tool whose parameters are a root union arrived as |
23 | | - `{"properties":{},"required":[]}` — Claude was told the tool takes no arguments. senpi's own |
24 | | - `monitorSchema` was flattened in July to dodge this, but plugin and MCP tools ship root unions |
25 | | - and cannot be flattened by us, so the conversion itself has to handle them. |
26 | | -- `utils/retry.ts` classifies provider request-shape rejections as NON-retryable, and |
27 | | - `NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN` is renamed `NON_RETRYABLE_PROVIDER_ERROR_PATTERN` |
28 | | - because it no longer covers only limits. Gateways wrap these deterministic rejections in 5xx |
29 | | - envelopes (`500 server_error: Invalid request: tools.function.parameters...`), so matching on |
30 | | - status text alone classified a permanent failure as transient: the identical payload was replayed |
31 | | - on the identical model until the turn died. The patterns are anchored on the |
32 | | - `tools.`/`functions.` request path so unrelated prose mentioning tools stays retryable. |
| 7 | +- `utils/tool-schema-compat.ts` no longer hoists a ROOT schema's `type` into its combiner branches. |
| 8 | + OpenAI-compatible gateways reject a covered object-shaped root when normalization removes its required |
| 9 | + `type: "object"`, which is exactly how an Apitopia/Kimi turn died on 2026-08-04. `normalizeNode` now takes an |
| 10 | + `isRoot` flag so branch-level hoisting (still correct below the root) is unchanged. Plain and object-shaped roots |
| 11 | + receive or retain object typing, while scalar and mixed root unions remain unchanged instead of being mislabeled. |
| 12 | + Root `allOf` is protected from root type hoisting but is not flattened into a synthetic object. |
| 13 | +- `mergeRootObjectUnion` merges object-shaped root `anyOf`/`oneOf` schemas without replacing the root's own |
| 14 | + `properties`/`required`. It previously returned `{"properties":{},"type":"object"}` for a root union that declared |
| 15 | + its properties at the root — silently sending a tool with zero parameters. Untyped constraint-only branches |
| 16 | + (`{ required: [...] }` over root properties) are accepted, and `required` keeps root entries plus only the names |
| 17 | + every branch shares. |
| 18 | +- `normalizeToolParametersForMoonshot` now reuses the same object-root normalization before annotation stripping, |
| 19 | + rather than maintaining a second, divergent root-merge path. |
| 20 | +- `api/anthropic-messages.ts` resolves object-shaped root `anyOf`/`oneOf` parameters through the shared |
| 21 | + `resolveRootObjectSchema` before building `input_schema`. `convertTools` reads top-level `properties`/`required` |
| 22 | + only, so covered root unions previously arrived as `{"properties":{},"required":[]}`. The conversion now merges |
| 23 | + their properties and required names while leaving ordinary object schemas unchanged; non-object unions and root |
| 24 | + `allOf` remain outside this resolver's flattening boundary. |
| 25 | +- `utils/retry.ts` classifies five recognized malformed tool/function schema message forms as NON-retryable, and |
| 26 | + `NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN` is renamed `NON_RETRYABLE_PROVIDER_ERROR_PATTERN` because it no |
| 27 | + longer covers only limits. Gateways can wrap these deterministic rejections in retryable-looking 5xx envelopes, |
| 28 | + so generic status matching replayed an equivalent invalid request on the same model. Four matchers target |
| 29 | + `tools.`/`functions.` request paths; `invalid tool schema` is intentionally broader. Eligible configured |
| 30 | + fallbacks rebuild their own provider-specific request rather than inheriting guaranteed identical bytes. |
33 | 31 |
|
34 | 32 | ### Why this cannot be expressed externally |
35 | 33 |
|
|
0 commit comments