Skip to content

feat(ai-vercel)!: require AI SDK v5+ and project tools onto inputSchema - #55

Merged
thorwhalen merged 3 commits into
mainfrom
claude/ai-sdk-v7
Jul 12, 2026
Merged

thorwhalen merged 3 commits into
mainfrom
claude/ai-sdk-v7

Conversation

@thorwhalen

Copy link
Copy Markdown
Member

Why

acture-ai-vercel@1 targets 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 no
representation for Gemini's thoughtSignature: it is stripped in the response Zod
schema, 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
functionCall part of each step, and rejects the request otherwise:

Function call is missing a thought_signature in functionCall parts.

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-era
line), which round-trips the signature via providerOptions.google.

Downstream, this was breaking multi-step tool calling in thoremin's in-app assistant.

What

  • Breaking: ai peer range ^4.0.0 -> ^5.0.0 || ^6.0.0 || ^7.0.0.
  • Tools are built with tool({ inputSchema }) — the field the AI SDK has used since v5
    (it was parameters on v4). A tool object still carrying parameters reaches the
    model with no schema at all, so it can never supply arguments.
  • Public API is otherwise unchanged: toAITools / toToolNameMap keep their signatures,
    tier filtering, [DEPRECATED] banners, wire-safe tool names, and the errors-as-data
    execute contract.
  • README: corrects the "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. Rewritten to explain why we own the conversion.

Verification

  • pnpm typecheck clean against ai@7.0.22.
  • pnpm test — 11/11 pass, including a new assertion that parameters is now
    undefined and the schema lands on inputSchema.
  • pnpm build clean; dist contains inputSchema and zero parameters:.
  • Verified end-to-end downstream: a 5-step sequential Gemini 3.5 Flash tool loop now
    completes with no thought_signature error (it failed on v4).

Migration

Move your app to ai@^5 or later and bump this package. If you must stay on ai@^4,
pin acture-ai-vercel@1.

https://claude.ai/code/session_01LsHz3ExTnCiZDfqQTjJnTH

…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
thorwhalen merged commit 157948f into main Jul 12, 2026
1 check passed
@thorwhalen
thorwhalen deleted the claude/ai-sdk-v7 branch July 12, 2026 17:02
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant