feat(ai-vercel)!: require AI SDK v5+ and project tools onto inputSchema - #55
Merged
Merged
Conversation
…ema`
The `ai` peer range moves from ^4 to ^5 || ^6 || ^7; tools are built with
`tool({ inputSchema })` instead of the v4-era `parameters`. Public API is
otherwise unchanged.
The v4 line is a dead end for Google: its final `@ai-sdk/google` (1.2.22)
predates Gemini 3 and has no representation for `thoughtSignature`, which
Gemini 3 requires echoed back on the first `functionCall` part of each step.
Multi-step tool calling therefore fails with "Function call is missing a
thought_signature in functionCall parts", with no opt-out (the requirement
holds even at `thinking_level: minimal`). Support first landed in
`@ai-sdk/google@2.0.3`.
Also corrects the README's "Why pass Zod through (not JSON Schema)?" section,
which described the opposite of what the code does — it has converted via
`z.toJSONSchema()` since the Zod-4 fix.
Claude-Session: https://claude.ai/code/session_01LsHz3ExTnCiZDfqQTjJnTH
Pre-merge cleanup for the 2.0.0 major. Three things still described (or
depended on) the v4 `parameters: ZodSchema` tool shape that this PR removes.
- packages/ai-vercel/package.json: the npm description still claimed the
adapter "passes Zod schemas through directly". It no longer does — it
converts with z.toJSONSchema() and hands the SDK a ready schema via
jsonSchema(). This string is the front door of the package on npm, so it
would have shipped as the first thing a reader of 2.0.0 sees. Rewritten to
state the AI SDK v5+ requirement and the JSON Schema conversion.
- examples/greenfield/graph-editor: pinned ai@^4.0.40 and
@ai-sdk/anthropic@^1, while depending on acture-ai-vercel via workspace:*.
After this PR toAITools emits tools carrying `inputSchema`, but ai@4 reads
`parameters` — so the example's tools would have reached the model with no
schema at all, which is precisely the bug this PR exists to fix. It was also
an unmet peer (ai-vercel peers ^5 || ^6 || ^7). Bumped to the ai@7 line that
the package actually tests against, plus the matching @ai-sdk/anthropic@^4,
and replaced the v4 `maxSteps: 12` idiom with `stopWhen: isStepCount(12)`.
The lockfile is regenerated to match: CI installs with --frozen-lockfile, so
leaving it stale would have failed the build.
- Agent-facing docs described the v4 shape, wrong in both the field name and
the schema type — the shape an agent would have copied:
* .claude/skills/acture-schema-bridge/SKILL.md
* docs/hand-written-assistant-runtime.md (code sample now uses acture's own
toJsonSchema(cmd), which yields { name, description, inputSchema })
* docs/phase-2-reflection.md (a historical reflection, so the original claim
is kept but banner-marked as superseded rather than silently rewritten)
Gates: ai-vercel 11/11 tests, tsc --noEmit clean. Full workspace green
(21 packages, 582 tests). The example — which CI does not typecheck, since
ci.yml filters to ./packages/* — was verified by hand: tsc --noEmit clean and
7/7 tests against the real ai@7 types.
Claude-Session: https://claude.ai/code/session_01D5qgxrtPRcERh22DZMSe3A
thorwhalen
added a commit
that referenced
this pull request
Aug 26, 2026
* feat(forms-rjsf): accept @rjsf 6.x and make the theme injectable `@rjsf/shadcn` — the theme every frontend in this ecosystem wants — is published on the 6.x line only, peering `@rjsf/core@^6`. The adapter's peer range was `^5.20.0`, so the two could not be installed together: an npm peer conflict at install time, before a line of form code got written. The adapter was unusable by exactly the consumer it was written for. Peers move to `^5.20.0 || ^6.0.0` rather than to 6-only. No adapter code was needed to span the two majors, because every API it touches is shape-identical across them: `@rjsf/core`'s default export and `FormProps`, `@rjsf/validator-ajv8`'s default export, and the `schema` / `formData` / `validator` / `liveValidate` / `onSubmit` / children props. The one signature that did change widened rather than moved — `liveValidate` went from `boolean` to `'onChange' | 'onBlur' | boolean`, so the `false` we pass is still valid. This follows the convention set by #55, which declared `ai@^5 || ^6 || ^7` while dev-testing only the newest. Reaching the shadcn theme needed a seam that did not exist. The README already told readers to "pass your own `Form` from the themed package", and there was no prop that accepted one — the core `Form` was imported and rendered directly. `<RjsfForm />` now takes an optional `form` (`ComponentType<FormProps>`, the type every RJSF theme's default export already has), defaulting to `@rjsf/core`'s. That is hard-don't #8's prescribed slot API: `@rjsf/shadcn` is a devDependency for the smoke test only, never a runtime or peer dependency, so no UI kit is bundled. Verification, since a peer range is a promise: - 6.x is what the dev tree installs and CI tests. New smoke test renders and submits through the real `@rjsf/shadcn` Form and asserts the theme actually rendered (its Tailwind classes, not `@rjsf/core`'s bare `form-control`). - 5.x was exercised by hand, once: `@rjsf/{core,utils,validator-ajv8}` pinned to `^5.24.0`, `pnpm install`, `@rjsf/core@5.24.13` resolved — `rjsf-form.test.tsx` 4/4 green and `tsc --noEmit` clean over the source. It is not re-checked per commit, and the tests say so. - It cannot be, in one dev tree: pnpm matches peers by package NAME, so an `npm:`-aliased 5.x install silently binds the 6.x `@rjsf/utils` (measured — `@rjsf+core@5.24.13_@rjsf+utils@6.8.0_...` appears in `node_modules/.pnpm`) and the "5.x matrix" would be 6.x wearing a 5.x label. `packageExtensions`, the documented fix, is not honoured by pnpm 11.1.1 from either `pnpm-workspace.yaml` or `package.json#pnpm`. - A guard pins the declared range against the major actually installed and against the major `@rjsf/shadcn` peers on, so this drift has to be deliberate next time. Mutation-tested three ways (narrow the peer range / ignore the injected form / drift the declared range) — each turns it red. `peerDependencyRules.ignoreMissing: [tailwindcss]` keeps `pnpm peers check` clean: `@rjsf/shadcn` pulls `tailwindcss-animate`, whose peer range is the malformed `">=3.0.0 || insiders"` and is therefore unmet whatever is installed. forms-rjsf 3 -> 11 tests. Workspace: 595 passed across 21 packages, build + typecheck clean. `minor` changeset. Closes #57 Claude-Session: https://claude.ai/code/session_0191cTtmnhFbC2ZKNtgxHXye * fix(forms-rjsf): actually test the 5.x half of the declared peer range Review repair. The PR claimed a 5.x matrix was impossible; it is not, and the gap it left was demonstrable. `packages/forms-rjsf` publishes `@rjsf` peers at `^5.20.0 || ^6.0.0` but nothing automated exercised the 5.x half, so a value that is legal on 6.x and illegal on 5.x shipped green. Reproduced: change `liveValidate={false}` to `liveValidate="onChange"` (6.x widened the type to `'onChange' | 'onBlur' | boolean`) and typecheck + all 17 tests + CI stay green, while the same source against real @rjsf/core@5.24.13 declarations gives `TS2769 ... Type 'string' is not assignable to type 'boolean | undefined'`. A consumer the peer range invites gets a hard compile error nothing here could catch. The impossibility claim was overstated. pnpm matching peers by package name rules out two alias trees inside ONE install; a CI matrix is two INSTALLS. `scripts/pin-rjsf-5x.mjs` rewrites the three `@rjsf` devDependencies to the 5.x clause READ OFF the declared peer range, drops the 6.x-only `@rjsf/shadcn` and its smoke test, and a plain `pnpm install` resolves a plain 5.x tree — no aliases, no packageExtensions. The new `rjsf5` CI job runs it, asserts a real 5.x tree resolved (`--verify` — a job silently testing the wrong major is this bug again), then typechecks and tests. Verified end to end in a throwaway worktree: @rjsf/core@5.24.13 resolved, `tsc --noEmit` clean, `rjsf-form.test.tsx` 4/4 — and red on the `liveValidate` mutation above. The script no-ops if the range ever narrows to 6-only, so the matrix retires with the promise it checks. Three guards added, one strengthened; each mutation-tested: - Hard-don't #8 had no test. Promoting `@rjsf/shadcn` from `devDependencies` to `dependencies` — the obvious "make the theme just work" edit — pulled a UI kit into every consumer's install with nothing red. Now `@rjsf/shadcn` is asserted absent from `dependencies` and `peerDependencies`, and the package is asserted to declare no runtime dependencies at all. Mutation: 2 failed | 15 passed. - `peerDependencyRules.ignoreMissing: [tailwindcss]` disables missing-peer detection workspace-wide and permanently (pnpm honours no scoped form — `tailwindcss-animate>tailwindcss` and `@rjsf/shadcn>tailwindcss` were both tried). Compensating check: no workspace package may declare a `tailwindcss` peer while the suppression is in place, and the block retires itself if the rule is removed. Both halves mutation-tested. - "submits through the theme" passed on an adapter that ignored the injected `form` — @rjsf/core's form submits identically, so only one of the two theme tests guarded the feature. Both now assert theme identity through one `expectThemedInput` helper keyed on @rjsf/core's `form-control` class rather than on a Tailwind class that can move between releases. Mutation (`form ?? Form` -> `Form`): 1 failed -> 2 failed. `FormImpl` is recomputed per render, so a non-stable `form` prop remounts the form and discards in-progress input. Memoizing cannot fix it — the changed identity arrives as the prop, and React reconciles by element type — so the requirement is documented on the prop, in the README and in the palette-design skill instead. README, test docstrings and the skill no longer state the impossibility; they say both majors run in CI, which is now true. forms-rjsf 11 -> 17 tests. Workspace: 601 passed across 21 packages, `pnpm build` / `pnpm typecheck` / `pnpm test` all exit 0, `pnpm peers check` clean. Claude-Session: https://claude.ai/code/session_0191cTtmnhFbC2ZKNtgxHXye
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
acture-ai-vercel@1targets the AI SDK v4 line, which is a dead end for Google.Its final
@ai-sdk/google(1.2.22, published months before Gemini 3 shipped) has norepresentation for Gemini's
thoughtSignature: it is stripped in the response Zodschema, again when tool calls are extracted, and again when the model turn is
re-serialized. Gemini 3 requires that signature to be echoed back on the first
functionCallpart of each step, and rejects the request otherwise:There is no fix on v4 and no way to opt out on Gemini 3 — the requirement holds even at
thinking_level: minimal. Support first landed in@ai-sdk/google@2.0.3(the v5-eraline), which round-trips the signature via
providerOptions.google.Downstream, this was breaking multi-step tool calling in thoremin's in-app assistant.
What
aipeer range^4.0.0->^5.0.0 || ^6.0.0 || ^7.0.0.tool({ inputSchema })— the field the AI SDK has used since v5(it was
parameterson v4). A tool object still carryingparametersreaches themodel with no schema at all, so it can never supply arguments.
toAITools/toToolNameMapkeep their signatures,tier filtering,
[DEPRECATED]banners, wire-safe tool names, and the errors-as-dataexecutecontract.described the opposite of what the code does — it has converted via
z.toJSONSchema()since the Zod-4 fix. Rewritten to explain why we own the conversion.Verification
pnpm typecheckclean againstai@7.0.22.pnpm test— 11/11 pass, including a new assertion thatparametersis nowundefinedand the schema lands oninputSchema.pnpm buildclean;distcontainsinputSchemaand zeroparameters:.completes with no
thought_signatureerror (it failed on v4).Migration
Move your app to
ai@^5or later and bump this package. If you must stay onai@^4,pin
acture-ai-vercel@1.https://claude.ai/code/session_01LsHz3ExTnCiZDfqQTjJnTH