Skip to content

chore(types): tighten TypeScript safety β€” strict flags, typed linting, public-API type tests, double-cast ban (#564)#579

Merged
AlemTuzlak merged 20 commits into
mainfrom
564-tighten-typescript-safety-strict-flags-typed-linting-public-api-type-tests
May 20, 2026
Merged

chore(types): tighten TypeScript safety β€” strict flags, typed linting, public-API type tests, double-cast ban (#564)#579
AlemTuzlak merged 20 commits into
mainfrom
564-tighten-typescript-safety-strict-flags-typed-linting-public-api-type-tests

Conversation

@tombeckenham
Copy link
Copy Markdown
Contributor

@tombeckenham tombeckenham commented May 19, 2026

🎯 Changes

Closes #564. End-to-end tightening of the TypeScript surface across the monorepo. Each commit on the branch is a self-contained step; reviewing in commit order is easiest.

Commits

  1. test(types): add public-API type-test coverage for chat() and InferChatMessages (a9c2c6f4)

    • Adds chat-per-model-type-safety + related tests so the public chat/inference surface has type-level coverage before we start tightening underneath it.
  2. chore(types): enable strict TS flags (121de4e1)

    • Turns on noImplicitOverride, noPropertyAccessFromIndexSignature, and noFallthroughCasesInSwitch in the root tsconfig.json.
    • Fixes the violations these surfaced across openai-base, preact/react/solid-ai-devtools, and ai/src.
  3. feat(lint): add typed ESLint rules for streaming/agent-loop correctness (0abc52c9)

    • New typed-linting block in eslint.config.js scoped to packages/typescript/*/src/**/*.{ts,tsx}: no-floating-promises, no-misused-promises, await-thenable, switch-exhaustiveness-check, consistent-type-exports, plus no-explicit-any / no-non-null-assertion / prefer-readonly as warnings.
    • Inverts the base config's @ts-ignore / @ts-expect-error policy: forbids @ts-ignore, requires descriptions on @ts-expect-error (which self-heals once the underlying error disappears).
  4. ci: fail CI on @ts-ignore / @ts-nocheck in library source (0714eea6)

    • Adds scripts/check-ts-suppressions.mjs and wires it into the test pipeline as a backstop in case lint is skipped.
    • Removed later in commit 10 once the coverage gap that made it load-bearing was closed.
  5. chore: add changeset for TypeScript safety tightening (95a0fa17)

    • Patch bump across all 22 published packages.
  6. refactor(types): add brandProviderTool helper and use vendor types directly in Anthropic adapter (68cb3851)

    • New brandProviderTool<T>() helper in @tanstack/ai collapses 16 phantom-brand as unknown as <ProviderTool> casts across Anthropic, Gemini, and OpenRouter tool factories into one audited site. Uses as T (not as unknown as T) so TS's structural overlap check still applies.
    • Anthropic text adapter now imports ContentBlockParam, ThinkingBlockParam, ToolUseBlockParam directly from @anthropic-ai/sdk/resources/messages and pushes block construction into a typed builder. Eliminates the as unknown as AnthropicContentBlock cast that was masking a TS narrowing limitation, not a real type mismatch.
  7. refactor(types): ban as unknown as double-casts in library source (3456bc70)

    • Sweep across 14 packages. Going from ~26 ungated as unknown as casts to 0 ungated + 12 audited + gated.
    • 13 sites reduced to plain as T (cast preserved, overlap check restored).
    • 2 bug fixes where the cast was hiding wrong behavior: Gemini JSON-parse fallback was assigning a string into Record<string, unknown>; Ollama had an unreachable defensive branch casting never to Record<string, unknown>. Both replaced with {}.
    • 12 sites gated with // eslint-disable-next-line no-restricted-syntax -- <reason> after as T failed the overlap check. Reasons cluster around: conditional return types in framework hooks (4Γ— useChat/createChat), DOM/TS-lib limitations (String.fromCharCode.apply(Uint8Array), Uint8Array.fromBase64 Stage-3), discriminated-union construction (OpenRouter NormalizedStreamEvent), RTCErrorEvent duck-type, ElevenLabs webhook-variant duck-type.
    • Adds no-restricted-syntax rule with AST selector TSAsExpression > TSAsExpression[typeAnnotation.type='TSUnknownKeyword'] to prevent regression.
  8. refactor(lint): close typed-linting coverage gap in 4 packages (720ffd61)

    • Four packages (ai, ai-client, ai-svelte, ai-fal) shipped a no-op local eslint.config.js that re-exported root with empty rules. Flat-config evaluates files globs relative to the config-file's directory, so the root's typed-linting block silently didn't apply there.
    • Removes the four no-op configs. Fixes the 12 violations that surfaced: missing case undefined: branches, AG-UI EventType switches where rule's enum-vs-string-literal analysis misfires (added default + scoped eslint-disable with reason), export * β†’ export type *, 3Γ— void-prefix for fire-and-forget promises, 1Γ— async-event-handler wrapped in void (async () => {})() IIFE.
  9. ci(examples): add test:types to all examples and fix surfaced type errors (f2827142)

    • Of 9 examples, only ts-react-media had a test:types script. The other 8 ran "test": "exit 0" and were never typechecked by CI even though every example ships a tsconfig.json with strict mode.
    • All 9 examples now have test:types. ts-vue-chat uses vue-tsc -b, ts-svelte-chat uses svelte-kit sync && svelte-check, the rest use tsc --noEmit. vanilla-chat needed a tsconfig.json (didn't exist).
    • Fixed surfaced errors: ElevenLabs adapter model-id literal-union casts, mergeAgentTools array vs Record shape, per-provider chat() generic erasure (split into ternary), tool handler unknown args, AG-UI stream-chunk discriminator update, dead-code cleanup, generated-file exclusions.
  10. ci: drop check-ts-suppressions.mjs (redundant with ban-ts-comment) (b5247943)

    • The script from commit 4 was a backstop while the 4-package coverage gap kept ban-ts-comment's strict policy from applying uniformly. Commit 8 closes that gap; the script's coverage is now identical to ESLint's, gated by the same CI step. Two enforcement paths for one rule creates divergence risk β€” consolidate on ESLint.

Net public-surface impact

  • One new public export: brandProviderTool from @tanstack/ai (helper for provider-tool factories; type-only at the value level).
  • No public type signatures changed. No behavior changes outside the 2 bug fixes (Gemini and Ollama JSON-parse fallbacks now correctly return {} instead of silently propagating a string into a Record slot).

Verification

  • 14 packages: ESLint, tsc, and unit tests all pass (1828 tests total).
  • 9 examples: test:types exits 0.

βœ… Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

πŸš€ Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

πŸ€– Generated with Claude Code

@tombeckenham tombeckenham linked an issue May 19, 2026 that may be closed by this pull request
20 tasks
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Important

Review skipped

Too many files!

This PR contains 204 files, which is 54 over the limit of 150.

To get a review, narrow the scope:
β€’ coderabbit review --type committed # exclude uncommitted changes
β€’ coderabbit review --dir # limit to a subdirectory
β€’ coderabbit review --base # compare against a closer base

βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c032a52-f8b2-4f60-a3ab-06bfe07bffff

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 144e090 and f839c8a.

πŸ“’ Files selected for processing (204)
  • .changeset/tighten-typescript-safety.md
  • eslint.config.js
  • examples/ts-code-mode-web/package.json
  • examples/ts-code-mode-web/src/lib/reports/create-handler-bindings.ts
  • examples/ts-code-mode-web/src/routes/_home/api.product-codemode.ts
  • examples/ts-group-chat/chat-server/capnweb-rpc.ts
  • examples/ts-group-chat/chat-server/claude-service.ts
  • examples/ts-group-chat/package.json
  • examples/ts-group-chat/src/routes/index.tsx
  • examples/ts-react-chat/package.json
  • examples/ts-react-chat/src/lib/server-audio-adapters.ts
  • examples/ts-react-chat/src/routes/api.tanchat.ts
  • examples/ts-react-chat/src/routes/generations.transcription.tsx
  • examples/ts-react-media/src/components/VideoGenerator.tsx
  • examples/ts-react-search/package.json
  • examples/ts-solid-chat/package.json
  • examples/ts-solid-chat/src/routes/api.chat.ts
  • examples/ts-solid-chat/tsconfig.json
  • examples/ts-svelte-chat/package.json
  • examples/ts-svelte-chat/src/routes/api/chat/+server.ts
  • examples/ts-vue-chat/package.json
  • examples/vanilla-chat/package.json
  • examples/vanilla-chat/tsconfig.json
  • packages/typescript/ai-anthropic/src/adapters/text.ts
  • packages/typescript/ai-anthropic/src/tools/bash-tool.ts
  • packages/typescript/ai-anthropic/src/tools/code-execution-tool.ts
  • packages/typescript/ai-anthropic/src/tools/computer-use-tool.ts
  • packages/typescript/ai-anthropic/src/tools/memory-tool.ts
  • packages/typescript/ai-anthropic/src/tools/text-editor-tool.ts
  • packages/typescript/ai-anthropic/src/tools/web-fetch-tool.ts
  • packages/typescript/ai-anthropic/src/tools/web-search-tool.ts
  • packages/typescript/ai-anthropic/tests/chat-per-model-type-safety.test.ts
  • packages/typescript/ai-client/eslint.config.js
  • packages/typescript/ai-client/src/chat-client.ts
  • packages/typescript/ai-client/src/connection-adapters.ts
  • packages/typescript/ai-client/src/generation-client.ts
  • packages/typescript/ai-client/src/generation-types.ts
  • packages/typescript/ai-client/src/realtime-client.ts
  • packages/typescript/ai-client/src/sse-parser.ts
  • packages/typescript/ai-client/src/video-generation-client.ts
  • packages/typescript/ai-client/tests/chat-client.test.ts
  • packages/typescript/ai-client/tests/infer-chat-messages.test.ts
  • packages/typescript/ai-code-mode-skills/src/code-mode-with-skills.ts
  • packages/typescript/ai-code-mode-skills/src/skills-to-bindings.ts
  • packages/typescript/ai-code-mode-skills/src/storage/memory-storage.ts
  • packages/typescript/ai-code-mode-skills/tests/create-skill-management-tools.test.ts
  • packages/typescript/ai-code-mode-skills/tests/skills-to-bindings.test.ts
  • packages/typescript/ai-code-mode/src/agent-store.ts
  • packages/typescript/ai-code-mode/src/types.ts
  • packages/typescript/ai-devtools/src/components/conversation/ChunksTab.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ConversationHeader.tsx
  • packages/typescript/ai-devtools/src/components/conversation/IterationCard.tsx
  • packages/typescript/ai-devtools/src/components/conversation/IterationTimeline.tsx
  • packages/typescript/ai-devtools/src/components/conversation/MessageCard.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ToolCallDisplay.tsx
  • packages/typescript/ai-devtools/src/components/utils/format.ts
  • packages/typescript/ai-devtools/src/store/ai-context.tsx
  • packages/typescript/ai-elevenlabs/src/adapters/audio.ts
  • packages/typescript/ai-elevenlabs/src/adapters/speech.ts
  • packages/typescript/ai-elevenlabs/src/adapters/transcription.ts
  • packages/typescript/ai-elevenlabs/src/realtime/adapter.ts
  • packages/typescript/ai-elevenlabs/src/realtime/token.ts
  • packages/typescript/ai-elevenlabs/src/utils/client.ts
  • packages/typescript/ai-elevenlabs/tests/realtime-adapter.test.ts
  • packages/typescript/ai-event-client/src/index.ts
  • packages/typescript/ai-fal/eslint.config.ts
  • packages/typescript/ai-fal/src/adapters/audio.ts
  • packages/typescript/ai-fal/src/adapters/image.ts
  • packages/typescript/ai-fal/src/adapters/speech.ts
  • packages/typescript/ai-fal/src/adapters/transcription.ts
  • packages/typescript/ai-fal/src/adapters/video.ts
  • packages/typescript/ai-fal/src/model-meta.ts
  • packages/typescript/ai-fal/src/utils/client.ts
  • packages/typescript/ai-fal/src/video/video-provider-options.ts
  • packages/typescript/ai-fal/tests/utils.test.ts
  • packages/typescript/ai-gemini/src/adapters/audio.ts
  • packages/typescript/ai-gemini/src/adapters/image.ts
  • packages/typescript/ai-gemini/src/adapters/text.ts
  • packages/typescript/ai-gemini/src/adapters/tts.ts
  • packages/typescript/ai-gemini/src/image/image-provider-options.ts
  • packages/typescript/ai-gemini/src/tools/code-execution-tool.ts
  • packages/typescript/ai-gemini/src/tools/computer-use-tool.ts
  • packages/typescript/ai-gemini/src/tools/file-search-tool.ts
  • packages/typescript/ai-gemini/src/tools/function-declaration-tool.ts
  • packages/typescript/ai-gemini/src/tools/google-maps-tool.ts
  • packages/typescript/ai-gemini/src/tools/google-search-retriveal-tool.ts
  • packages/typescript/ai-gemini/src/tools/google-search-tool.ts
  • packages/typescript/ai-gemini/src/tools/url-context-tool.ts
  • packages/typescript/ai-gemini/tests/audio-adapter.test.ts
  • packages/typescript/ai-gemini/tests/chat-per-model-type-safety.test.ts
  • packages/typescript/ai-gemini/tests/gemini-adapter.test.ts
  • packages/typescript/ai-gemini/tests/tts-adapter.test.ts
  • packages/typescript/ai-grok/src/adapters/image.ts
  • packages/typescript/ai-grok/src/adapters/text.ts
  • packages/typescript/ai-grok/src/adapters/transcription.ts
  • packages/typescript/ai-grok/src/realtime/adapter.ts
  • packages/typescript/ai-grok/src/utils/audio.ts
  • packages/typescript/ai-grok/tests/realtime-token.test.ts
  • packages/typescript/ai-groq/src/adapters/text.ts
  • packages/typescript/ai-groq/tests/schema-converter.test.ts
  • packages/typescript/ai-isolate-cloudflare/src/isolate-driver.ts
  • packages/typescript/ai-isolate-cloudflare/src/types.ts
  • packages/typescript/ai-isolate-node/src/error-normalizer.ts
  • packages/typescript/ai-isolate-node/src/isolate-context.ts
  • packages/typescript/ai-isolate-node/tests/escape-attempts.test.ts
  • packages/typescript/ai-isolate-quickjs/src/error-normalizer.ts
  • packages/typescript/ai-isolate-quickjs/src/isolate-context.ts
  • packages/typescript/ai-isolate-quickjs/tests/escape-attempts.test.ts
  • packages/typescript/ai-ollama/src/adapters/text.ts
  • packages/typescript/ai-ollama/src/tools/function-tool.ts
  • packages/typescript/ai-ollama/src/utils/client.ts
  • packages/typescript/ai-openai/src/adapters/image.ts
  • packages/typescript/ai-openai/src/adapters/text-chat-completions.ts
  • packages/typescript/ai-openai/src/adapters/text.ts
  • packages/typescript/ai-openai/src/adapters/transcription.ts
  • packages/typescript/ai-openai/src/adapters/tts.ts
  • packages/typescript/ai-openai/src/adapters/video.ts
  • packages/typescript/ai-openai/src/audio/transcription-provider-options.ts
  • packages/typescript/ai-openai/src/realtime/adapter.ts
  • packages/typescript/ai-openai/tests/chat-per-model-type-safety.test.ts
  • packages/typescript/ai-openai/tests/schema-converter.test.ts
  • packages/typescript/ai-openrouter/src/adapters/image.ts
  • packages/typescript/ai-openrouter/src/adapters/responses-text.ts
  • packages/typescript/ai-openrouter/src/adapters/text.ts
  • packages/typescript/ai-openrouter/src/internal/request-options.ts
  • packages/typescript/ai-openrouter/src/internal/schema-converter.ts
  • packages/typescript/ai-openrouter/src/tools/web-search-tool.ts
  • packages/typescript/ai-openrouter/tests/openrouter-adapter.test.ts
  • packages/typescript/ai-preact/src/types.ts
  • packages/typescript/ai-preact/src/use-chat.ts
  • packages/typescript/ai-react-ui/src/chat-input.tsx
  • packages/typescript/ai-react-ui/src/chat-messages.tsx
  • packages/typescript/ai-react-ui/src/chat.tsx
  • packages/typescript/ai-react-ui/src/tool-approval.tsx
  • packages/typescript/ai-react/src/use-chat.ts
  • packages/typescript/ai-react/src/use-generate-video.ts
  • packages/typescript/ai-react/src/use-generation.ts
  • packages/typescript/ai-react/src/use-realtime-chat.ts
  • packages/typescript/ai-solid-ui/src/chat-input.tsx
  • packages/typescript/ai-solid-ui/src/chat-messages.tsx
  • packages/typescript/ai-solid-ui/src/chat.tsx
  • packages/typescript/ai-solid-ui/src/tool-approval.tsx
  • packages/typescript/ai-solid/src/use-chat.ts
  • packages/typescript/ai-solid/src/use-generate-video.ts
  • packages/typescript/ai-solid/src/use-generation.ts
  • packages/typescript/ai-solid/tests/use-chat-types.test.ts
  • packages/typescript/ai-svelte/eslint.config.js
  • packages/typescript/ai-svelte/src/create-chat.svelte.ts
  • packages/typescript/ai-svelte/src/create-generate-video.svelte.ts
  • packages/typescript/ai-svelte/src/create-generation.svelte.ts
  • packages/typescript/ai-utils/src/base64.ts
  • packages/typescript/ai-utils/src/transforms.ts
  • packages/typescript/ai-vue-ui/src/chat-input.vue
  • packages/typescript/ai-vue-ui/src/chat-message.vue
  • packages/typescript/ai-vue-ui/src/chat-messages.vue
  • packages/typescript/ai-vue-ui/src/chat.vue
  • packages/typescript/ai-vue-ui/src/message-part.vue
  • packages/typescript/ai-vue-ui/src/tool-approval.vue
  • packages/typescript/ai-vue/src/use-chat.ts
  • packages/typescript/ai-vue/src/use-generate-video.ts
  • packages/typescript/ai-vue/src/use-generation.ts
  • packages/typescript/ai/eslint.config.js
  • packages/typescript/ai/src/activities/chat/index.ts
  • packages/typescript/ai/src/activities/chat/messages.ts
  • packages/typescript/ai/src/activities/chat/middleware/types.ts
  • packages/typescript/ai/src/activities/chat/stream/processor.ts
  • packages/typescript/ai/src/activities/chat/stream/strategies.ts
  • packages/typescript/ai/src/activities/chat/tools/schema-converter.ts
  • packages/typescript/ai/src/activities/chat/tools/tool-calls.ts
  • packages/typescript/ai/src/activities/chat/tools/tool-definition.ts
  • packages/typescript/ai/src/activities/generateAudio/index.ts
  • packages/typescript/ai/src/activities/generateImage/index.ts
  • packages/typescript/ai/src/activities/stream-generation-result.ts
  • packages/typescript/ai/src/activities/summarize/chat-stream-summarize.ts
  • packages/typescript/ai/src/activities/summarize/index.ts
  • packages/typescript/ai/src/index.ts
  • packages/typescript/ai/src/middlewares/content-guard.ts
  • packages/typescript/ai/src/middlewares/otel.ts
  • packages/typescript/ai/src/realtime/index.ts
  • packages/typescript/ai/src/tools/provider-tool.ts
  • packages/typescript/ai/src/types.ts
  • packages/typescript/ai/src/utilities/ag-ui-wire.ts
  • packages/typescript/ai/src/utilities/chat-params.ts
  • packages/typescript/ai/tests/chat-per-model-type-safety.test.ts
  • packages/typescript/ai/tests/extend-adapter.test.ts
  • packages/typescript/ai/tests/image-per-model-type-safety.test.ts
  • packages/typescript/ai/tests/logger/internal-logger.test.ts
  • packages/typescript/ai/tests/message-updaters.test.ts
  • packages/typescript/ai/tests/middleware.test.ts
  • packages/typescript/ai/tests/middlewares/fake-otel.ts
  • packages/typescript/ai/tests/middlewares/otel.test.ts
  • packages/typescript/ai/tests/standard-converter.test.ts
  • packages/typescript/ai/tests/stream-to-response.test.ts
  • packages/typescript/ai/tests/test-utils.ts
  • packages/typescript/openai-base/src/adapters/chat-completions-text.ts
  • packages/typescript/openai-base/src/adapters/responses-text.ts
  • packages/typescript/openai-base/src/tools/custom-tool.ts
  • packages/typescript/openai-base/src/tools/file-search-tool.ts
  • packages/typescript/openai-base/src/tools/index.ts
  • packages/typescript/openai-base/src/utils/request-options.ts
  • packages/typescript/openai-base/src/utils/schema-converter.ts
  • packages/typescript/openai-base/tests/chat-completions-text.test.ts
  • packages/typescript/openai-base/tests/schema-converter.test.ts
  • tsconfig.base.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • πŸ” Trigger review
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 564-tighten-typescript-safety-strict-flags-typed-linting-public-api-type-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 19, 2026

View your CI Pipeline Execution β†— for commit f839c8a

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... βœ… Succeeded 4m 4s View β†—
nx run-many --targets=build --exclude=examples/... βœ… Succeeded 1m 6s View β†—

☁️ Nx Cloud last updated this comment at 2026-05-20 04:50:47 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 19, 2026

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@579

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@579

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@579

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@579

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@579

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@579

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@579

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@579

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@579

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@579

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@579

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@579

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@579

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@579

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@579

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@579

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@579

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@579

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@579

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@579

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@579

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@579

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@579

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@579

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@579

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@579

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@579

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@579

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@579

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@579

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@579

commit: f839c8a

tombeckenham and others added 12 commits May 20, 2026 09:27
…atMessages

Lock in the per-model provider-options gating across `chat()` and the
ai-client β†’ useChat type flow before tightening the compiler so
regressions surface as compile failures rather than runtime drift.
Covers issue #564 (Stage 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cessFromIndexSignature, noFallthroughCasesInSwitch

Tighten the compiler baseline per issue #564 by flipping three flags
that are mechanical to absorb. Fix the surfaced fallout:

- Add `override` modifiers to adapter classes whose `kind` / `chatStream`
  / `summarizeStream` members shadow the base abstractions.
- Convert dynamic `obj.foo` reads on `Record<*, *>` / index-signature
  carriers (provider SDK envelopes, JSON-schema walks, devtools wrappers)
  to bracket notation.
- Replace the `Record<string, any>` parameter on
  `makeStructuredOutputCompatible` with `JSONSchema`, and introduce
  `toJsonSchema()` so the Standard-Schema β†’ JSONSchema bridge no longer
  needs `as` casts to bypass index-signature variance.
- Bracket Vue template `$slots.X` lookups so vue-tsc accepts them.

`exactOptionalPropertyTypes` and `useDefineForClassFields` from the
original five-flag set are deferred β€” the former is the dominant
churn (~250 sites) and warrants its own coordinated PR per the issue's
review guidance, and the latter breaks vitest's mock-hoisting transform
in adapter tests that construct classes with field initializers.

Covers issue #564 (Stage 2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a new \`tanstack/ai/typed\` block scoped to packages/typescript/*/src/**
that enables eight typed-linting rules per issue #564:

- error: no-floating-promises, no-misused-promises, await-thenable,
  switch-exhaustiveness-check, consistent-type-exports
- warn:  no-explicit-any, no-non-null-assertion, prefer-readonly

Also inverts ban-ts-comment from the base \`@tanstack/eslint-config\` β€”
\`@ts-ignore\` is now disallowed outright and \`@ts-expect-error\` must
carry a description.

Fixes the fallout each rule surfaced:

- Floating promises: prefix \`sendMessage()\` / \`addToolApprovalResponse()\`
  fire-and-forget calls in the React/Solid UI shells with \`void\`. These
  are intentionally async at the client layer but synchronous from a
  DOM event handler's perspective.
- Misused promises: widen \`errorState.reload\` callback type from
  \`() => void\` to \`() => Promise<void>\` in chat-messages props, since
  the chat client's \`reload()\` is async.
- Exhaustive switches: add \`case undefined:\` co-cases for grok realtime
  events and elevenlabs codec parsing, enumerate the remaining Anthropic
  stop_reason variants, add explicit cases for video/document content
  parts in the openai-base responses adapter, add the missing
  \`thinking\` color in ai-devtools, and extract \`reason\` to a local
  variable in IterationCard so the discriminator stays in scope.
- consistent-type-exports: switch openai-base \`./tool-choice\` to
  \`export type *\`.

Covers issue #564 (Stage 3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `scripts/check-ts-suppressions.mjs` (a one-shot Node walker that
greps every `packages/typescript/<pkg>/src/**.{ts,tsx}` for forbidden
suppressions) plus a `test:no-suppressions` script wired into `test:pr`
and `test:ci`.

`@ts-expect-error` remains allowed β€” the linked typed-ESLint block
requires a description on it, and the variant self-heals when the
underlying error disappears. `@ts-ignore` silently rots.

Issue #564 baseline is zero matches today; adopt the guard so future
regressions surface in CI rather than during incident triage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Patch bump across every workspace package touched by the
strict-flags / typed-linting / suppression-guard work. Public API
surface is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rectly in Anthropic adapter

Collapses 16 `as unknown as <ProviderTool>` phantom-brand casts across Anthropic,
Gemini, and OpenRouter tool factories into a single audited `brandProviderTool<T>()`
helper in @tanstack/ai. The helper uses `as T` (not `as unknown as T`) so TS's
structural-overlap check still applies β€” future refactors that break the brand
contract surface here instead of at distant call sites.

Also refactors the Anthropic text adapter to import `ContentBlockParam`,
`ThinkingBlockParam`, and `ToolUseBlockParam` directly from
`@anthropic-ai/sdk/resources/messages` and pushes thinking-block construction
into a properly-typed builder. Eliminates the `as unknown as AnthropicContentBlock`
cast that was hiding a TS narrowing limitation rather than a real type mismatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sweeps the unaudited `as unknown as <Type>` double-cast pattern out of
library source across 14 packages, then adds a `no-restricted-syntax`
rule to prevent regression.

Why: `as unknown as T` bypasses TypeScript's structural-overlap check
(the safety net that errors when two types don't sufficiently overlap)
β€” equivalent to `@ts-ignore` for type assertions. Plain `as T` keeps
the check, so refactors that break a cast's structural premise surface
at the cast site instead of at distant call sites.

Per-package results:
- 13 sites reduced to plain `as T` (ai, ai-client, ai-fal, ai-utils)
- 2 bug fixes where the cast was hiding real wrong behavior:
  - ai-gemini: JSON.parse fallback was casting a `string` to
    `Record<string, unknown>`. Replaced with `parsedArgs = {}`.
  - ai-ollama: unreachable defensive branch was casting `never` to
    `Record<string, unknown>`. Replaced with `{}` (matches the
    JSON-parse failure fallback in the same function).
- 12 sites gated with `// eslint-disable-next-line no-restricted-syntax
  -- <reason>` after `as T` failed the structural-overlap check.
  Reasons cluster around: conditional return types (4 framework hook
  `useChat`/`createChat`), DOM/TS-lib limitations (`String.fromCharCode
  .apply(Uint8Array)`, `Uint8Array.fromBase64` Stage-3 proposal),
  discriminated-union construction (OpenRouter `NormalizedStreamEvent`
  built field-by-field), and a `RTCErrorEvent` duck-type.

Lint rule scoped to `packages/typescript/*/src/**/*.{ts,tsx}` plus
`src/**/*.{ts,tsx}` to also fire under the 4 packages
(ai, ai-client, ai-svelte, ai-fal) that ship a local
`eslint.config.js` re-exporting root β€” flat-config evaluates `files`
globs relative to the config-file's directory, so the original glob
silently misses those packages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four packages (\`ai\`, \`ai-client\`, \`ai-svelte\`, \`ai-fal\`) shipped a
no-op local \`eslint.config.js\` that re-exported root with empty rules.
Flat-config evaluates \`files\` globs relative to the config-file's
directory, so the root's typed-linting block \`packages/typescript/*/src/**\`
glob silently resolved to nothing in those packages β€” the strict rules
(no-floating-promises, no-misused-promises, switch-exhaustiveness-check,
consistent-type-exports, ban-ts-comment override) didn't apply there.

Removes the four no-op local configs and fixes the violations that
surfaced:

- ai/src/middlewares/otel.ts:118 β€” \`switch (type)\` missing
  \`case undefined:\`. Added with \`[unknown]\` placeholder; dropped the
  now-redundant \`?? 'unknown'\` from the default branch.
- ai/src/activities/summarize/chat-stream-summarize.ts:214 β€”
  \`switch (options.style)\` missing \`undefined\`. Added a case that
  mirrors the existing default-prompt text.
- ai/src/activities/chat/index.ts:695 and stream/processor.ts:456 β€”
  switches on AG-UI \`EventType\` where case labels are string literals
  (\`'TEXT_MESSAGE_START'\` etc.) but the discriminant is the
  \`EventType\` enum. The rule's exhaustiveness analysis treats those as
  unrelated types and flags every variant as unmatched even though the
  default already handles them. Scoped \`eslint-disable-next-line\`
  with reason on each switch.
- ai/src/realtime/index.ts:4 β€” \`export * from './types'\` β†’
  \`export type * from './types'\` (all exports are types).
- ai-client/src/generation-client.ts:162 and
  video-generation-client.ts:188 β€” \`switch (event.type)\` on
  AG-UI \`EventType\`; consumer only handles the generation-lifecycle
  subset (\`CUSTOM\`, \`RUN_FINISHED\`, \`RUN_ERROR\`). Rule's
  \`considerDefaultExhaustiveForUnions\` defaults to false. Added
  \`default: break\` plus \`eslint-disable-next-line\` with reason.
- ai-client/src/realtime-client.ts:171,308,347 β€” fire-and-forget
  promise calls (\`startListening\` kicks off audio capture,
  \`destroy\` calls \`disconnect\`, refresh-token timer). Prefixed with
  \`void\` to make intent explicit.
- ai-client/src/realtime-client.ts:412 β€” \`no-misused-promises\` on
  an async event handler passed to a \`() => void\` slot. Converted
  to a sync arrow with an inner \`void (async () => {...})()\` IIFE;
  hoisted \`tool.execute\` to a local const to preserve narrowing
  across the IIFE boundary.
- ai-fal/src/utils/client.ts:86 β€” \`switch (ext)\` on
  \`string | undefined\`. Added \`case undefined:\` that falls through
  to the existing default \`return 'audio/mpeg'\`.

Doesn't address the warnings (~298 across the four packages, mostly
\`no-explicit-any\` and \`no-non-null-assertion\` β€” both configured as
warnings in the root). Those are a separate cleanup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rors

Before this commit, of 9 examples only \`ts-react-media\` had a
\`test:types\` script. Most ran \`"test": "exit 0"\` and never had
their types checked by CI even though every example ships a \`tsconfig.json\`
with strict mode. That gap is now closed.

For each example:

- \`ts-react-chat\` β€” added \`tsc --noEmit\`. Fixed 4 ElevenLabs
  adapter calls passing a generic \`string\` where a literal-union
  model id was expected (\`as 'eleven_multilingual_v2'\` etc.),
  renamed 4 unused middleware \`ctx\` params to \`_ctx\`, and tightened
  the transcription generation route's fetcher input to the
  \`TranscriptionGenerateInput\` shape from \`@tanstack/ai-client\`.
- \`ts-vue-chat\` β€” added \`vue-tsc -b\` (already used in \`build\`).
  No source fixes needed.
- \`ts-solid-chat\` β€” added \`tsc --noEmit\`. Excluded the generated
  \`src/routeTree.gen.ts\` and three orphan files
  (\`src/lib/stub-adapter.ts\`, \`src/lib/stub-llm.ts\`,
  \`src/utils/demo.tools.ts\` β€” the first two reference outdated
  \`@tanstack/ai\` types; the third imports Vercel's \`ai\` package
  and is unreferenced). Fixed \`api.chat.ts\` to pass the
  \`serverTools\` array directly to \`mergeAgentTools\` instead of
  wrapping it in a \`Record\`.
- \`ts-svelte-chat\` β€” added \`svelte-kit sync && svelte-check\`.
  Same \`mergeAgentTools\` array fix as solid. Split the
  per-provider \`chat()\` call into a ternary so each branch resolves
  to a concrete adapter type (spreading the union erased the
  generic).
- \`vanilla-chat\` β€” created a \`tsconfig.json\` (didn't exist),
  scoped to the example's \`src/**/*.js\` + \`vite.config.ts\` with
  \`allowJs: true\` and \`checkJs: false\` so \`tsc\` doesn't surface
  unrelated JS drift. Added \`tsc --noEmit\`.
- \`ts-group-chat\` β€” added \`tsc --noEmit\`. Removed a dead
  \`private webSocket\` field, replaced an old AG-UI \`'content'\`
  stream-chunk discriminator with \`'TEXT_MESSAGE_CONTENT'\`, and
  typed a tool handler's \`unknown\` args via a single named alias.
- \`ts-react-search\` β€” added \`tsc --noEmit\`. No source fixes
  needed (tsconfig already excludes generated files).
- \`ts-code-mode-web\` β€” added \`tsc --noEmit\`. Renamed two unused
  function params and widened a function's first arg from
  \`AsyncGenerator<StreamChunk>\` to \`AsyncIterable<StreamChunk>\`
  to match \`chat()\`'s return type.

Same anti-pattern (spread of a per-provider \`chat()\` union erasing
the generic) is present in \`ts-react-chat\` but doesn't surface
under its current tsconfig β€” left in place to avoid scope creep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The script was introduced in 0714eea as a backstop for `@ts-ignore` /
`@ts-nocheck` enforcement. Its value-add at the time was that the
typed-linting block in `eslint.config.js` (which configures
`@typescript-eslint/ban-ts-comment` with `'ts-ignore': false` /
`'ts-nocheck': true`) silently didn't apply to four packages β€” `ai`,
`ai-client`, `ai-svelte`, `ai-fal` β€” because each shipped a no-op local
`eslint.config.js` that shifted the `files` glob's resolution context.
Without the script, those four packages were under the base config's
looser policy (allow `@ts-ignore` with a description).

The previous commit (720ffd6) deletes those four local configs, so
the strict `ban-ts-comment` policy now applies uniformly. The script
and ESLint enforce identical rules against identical scopes, both gated
by the same CI step. Keeping both creates divergence risk (regex vs.
AST policies that can drift), so consolidate on ESLint.

Removes:
- `scripts/check-ts-suppressions.mjs`
- `test:no-suppressions` script in root `package.json`
- The `pnpm run test:no-suppressions && ` prefix on `test:pr` and
  `test:ci` β€” `test:eslint` already runs in both and catches the
  same violations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Anthropic per-model type-test: switch to `id`-form aliases
  (`claude-3-5-haiku` / `claude-3-haiku`) after #581 narrowed the
  factory's accepted model union to `id` values rather than `name`.
- Re-add `as unknown as UseChatReturn<TTools, TSchema>` cast on the
  React `useChat` return β€” main's new `activeStructuredPart` memo
  changed the runtime shape, so the conditional return-type seam
  needs the double-cast (the no-restricted-syntax eslint-disable
  was already in place).
- Bracket-notation fixes for `noPropertyAccessFromIndexSignature`
  violations newly surfaced in code that landed on main:
  - `process.env.X` reads in gemini/grok test files
  - `process.env.NODE_ENV` in ai-devtools/src/index.ts
  - `e.name` / `e.message` index-sig reads in
    ai-isolate-cloudflare/src/isolate-driver.ts
  - `Record<string, unknown>` access in message-updaters /
    openai-base / elevenlabs tests
- Cast schema-converter test results to `any` (`const result: any
  = ...`) so dot-chains on the returned JSON Schema satisfy the
  new flag without 50+ bracket rewrites; typed-lint rules don't
  apply to test files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tombeckenham tombeckenham force-pushed the 564-tighten-typescript-safety-strict-flags-typed-linting-public-api-type-tests branch from b524794 to 58a9a90 Compare May 19, 2026 23:41
tombeckenham and others added 5 commits May 20, 2026 10:23
Flips the fifth strict flag from issue #564's original five-flag set
on. With ES2020 target, `useDefineForClassFields` is off by default;
turning it on emits real ES2022 class fields (Object.defineProperty)
instead of [[Set]]-style assignments β€” closer to runtime semantics
and required to keep TS in sync with downstream consumers that
default to true (ES2022+ targets).

Surfaced one test breakage in `ai-openrouter/tests/openrouter-adapter.test.ts`:
the inline `vi.mock` factory returned a class with a `chat` field
initializer, and vitest's mock-hoister mis-rewrote that field
reference because `chat` was also a top-level named import on line 2.
The class-field semantics under `useDefineForClassFields: true`
turned the previously-silent collision into a runtime
"Cannot access '__vi_import_0__' before initialization" error.

Rewrite the mock as a plain constructor function with `this.chat = ...`
assignments. The sibling `openrouter-responses-adapter.test.ts` test
uses the same inline-class pattern but doesn't collide (field is
named `beta`, no matching import) so it's left as-is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…react

The typed-ESLint block leaves `no-explicit-any`,
`no-non-null-assertion`, and `prefer-readonly` at `warn` to ratchet
later. Clears the warnings in the two packages explicitly called out
for cleanup; the remaining ~600 warnings in other packages are a
separate sweep.

ai-fal:
- `utils/client.ts:41` β€” `url.split('?')[0]!.split('#')[0]!` had two
  forbidden non-null assertions guarding `noUncheckedIndexedAccess`.
  Both `String.split` calls return a non-empty array when the
  separator is missing, but TS doesn't know that. Replace with
  `?.split('#')[0] ?? url` β€” falls back to the raw input string if
  the optional chain ever produces undefined.
- `utils/client.ts:137` β€” `view[i]!` inside an indexed `for` loop.
  Rewrite as `for (const byte of view)` so the binding is statically
  non-undefined.
- `model-meta.ts:26,128` β€” fallback branches for the
  `EndpointTypeMap[TModel]` conditional types used
  `Record<string, any>`. Tighten to `Record<string, unknown>` so
  consumers get a value type they have to narrow.
- `adapters/video.ts:140` β€” `catch (error: any)` then `error?.body`,
  `error.message`. Narrow the catch binding to `unknown` and
  introduce a typed local alias for the SDK error shape we read.

ai-preact:
- `addToolResult.output: any` β†’ `output: unknown` in both `types.ts`
  and `use-chat.ts`. The framework genuinely doesn't know the result
  shape β€” `unknown` forces callers to narrow rather than silently
  accepting anything assignable to `any`.

The `<TTools extends ReadonlyArray<AnyClientTool> = any>` defaults on
the hook generics are deliberately kept β€” that pattern is shared by
ai-react, ai-solid, ai-svelte, ai-vue, and ai-preact. Changing the
default in one framework only would diverge the surface, and changing
it everywhere is its own refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flips the final flag from issue #564's original five-flag set on, and
fixes the ~300 errors it surfaces across 26 packages. EOPT
distinguishes `field?: T` (the property may be absent) from
`field?: T | undefined` (may be absent OR explicitly undefined),
so spreading an object whose source field is `T | undefined` into a
target declared `field?: T` is now a type error.

## Fix patterns

Two patterns covered nearly every site. The choice between them is
about semantic intent, not convenience.

**Widen the target** (`field?: T` β†’ `field?: T | undefined`): used
when the field is locally owned and the carry-through-undefined
semantics are correct. Most internal interfaces fall here β€”
`BaseEventContext`-style shared-context types, middleware context
shapes, devtools store interfaces, eval-local row types, etc.

**Conditional spread** (`{ ...(v !== undefined && { field: v }) }`):
used when the target is owned by another package (vendor SDK, AG-UI
event shapes, framework integration options imported from
`@tanstack/ai-client`, etc.) and absence-on-the-wire matters.
Provider adapters (OpenAI, Anthropic, Gemini, Ollama, Grok, fal,
ElevenLabs, OpenRouter, Groq) all use this for vendor request
shapes; framework hooks (ai-react, ai-solid, ai-vue, ai-svelte,
ai-preact) use it for `ChatClientOptions`/`GenerationClientOptions`
pass-throughs.

## Things deliberately NOT widened

- `Tool.inputSchema` / `Tool.outputSchema` and the same fields on
  `ClientTool`. These participate in `InferToolInput` /
  `InferToolOutput` via `infer T extends StandardJSONSchemaV1<…>`.
  Widening to `T | undefined` makes the inference distribute over
  `undefined` and collapses inferred input/output types to `unknown`,
  breaking the `chat()` / `useChat()` type-inference contract that
  the public type-tests in `ai-client/tests/` lock down. Comment
  added at the declaration to flag this.

- Mirror types in `ai-event-client/src/index.ts` (the locally
  redeclared `ContentPartUrlSource`, `ToolCallPart`, `ToolResultPart`,
  `ToolCall`, `ImageUsage`) β€” these must structurally match the
  authoritative declarations in `@tanstack/ai/src/types.ts` to stay
  cross-package assignable. Kept as `field?: T` (no `| undefined`).

## Notable per-package work

- `ai` (71 errors): widened the shared context / middleware /
  activity option types. One `as ChatMiddleware` boundary cast on
  the devtools-middleware structural-equivalent type (sibling
  package, deliberately duplicated to avoid circular dep). Two
  conditional spreads at AG-UI `RUN_ERROR` / image-output wire
  boundaries.
- `ai-event-client` (21 errors): widened `BaseEventContext` and the
  per-event subtype redeclarations of optional fields. Mirror types
  for content parts deliberately not widened (see above).
- `ai-devtools` (31 errors): widened the five store interfaces in
  `store/ai-context.tsx` (`MessagePart`, `Message`, `Chunk`,
  `Iteration`, `Conversation`); removed two now-unnecessary
  `as T[keyof T]` casts.
- `openai-base`, `ai-openai`, `ai-openrouter`, `ai-grok`, `ai-groq`:
  conditional-spread the `RUN_ERROR.code` / nested `error.code` and
  `RUN_FINISHED.usage` wire payloads, plus `signal` and `headers`
  into vendor `RequestOptions`.
- `ai-anthropic`, `ai-gemini`: conditional-spread vendor-SDK request
  shape fields (`ThinkingConfig`, `WebSearchTool20250305`, etc).
  `brandProviderTool` tool factories pass `metadata: config`
  directly β€” `Tool.metadata` was widened to allow `| undefined` so
  the property stays present (smoke tests assert
  `toHaveProperty('metadata')`).
- `ai-isolate-{node,quickjs,cloudflare}`: conditional-spread
  `NormalizedError.stack` / `code` (type owned by `ai-code-mode`,
  intentionally narrow). Local `ExecuteResponse` widened.
- Framework hooks (`ai-react`, `ai-solid`, `ai-vue`, `ai-svelte`,
  `ai-preact`): conditional-spread every optional pass-through to
  `ChatClient` / `GenerationClient` / `VideoGenerationClient`
  constructors and `updateOptions` calls. Block-bodied callback
  wrappers in `ai-react` so the `?.()` chain doesn't widen the
  callback's return type to `void | undefined`.
- `ai-openrouter`: vitest mock-hoister collision under
  `useDefineForClassFields: true` (from the prior commit) needed
  the inline-class mock rewritten as a constructor function β€” same
  fix logic as the `chat = {…}` collision noted there.

## Verification

- `pnpm test:types`: 32 projects, 0 errors.
- `pnpm test:eslint`: 31 projects, 0 errors (warning baselines
  unchanged).
- `pnpm test:lib`: 31 projects, all tests pass.
- `pnpm build`: 31 projects.
- 9 examples: all `test:types` exit 0 (one example, `ts-react-media`,
  needed a local `JobState` discriminant widening + conditional
  spread on a `RequestInit`-style fetch payload).

Covers issue #564 (Stage 3 β€” final flag).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

πŸš€ Changeset Version Preview

22 package(s) bumped directly, 8 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/ai 0.20.0 β†’ 0.20.1 Changeset
@tanstack/ai-anthropic 0.10.0 β†’ 0.10.1 Changeset
@tanstack/ai-code-mode 0.1.15 β†’ 0.1.16 Changeset
@tanstack/ai-code-mode-skills 0.1.15 β†’ 0.1.16 Changeset
@tanstack/ai-devtools-core 0.3.32 β†’ 0.3.33 Changeset
@tanstack/ai-elevenlabs 0.2.7 β†’ 0.2.8 Changeset
@tanstack/ai-fal 0.7.8 β†’ 0.7.9 Changeset
@tanstack/ai-gemini 0.10.7 β†’ 0.10.8 Changeset
@tanstack/ai-grok 0.8.4 β†’ 0.8.5 Changeset
@tanstack/ai-groq 0.2.3 β†’ 0.2.4 Changeset
@tanstack/ai-isolate-node 0.1.15 β†’ 0.1.16 Changeset
@tanstack/ai-isolate-quickjs 0.1.15 β†’ 0.1.16 Changeset
@tanstack/ai-ollama 0.6.18 β†’ 0.6.19 Changeset
@tanstack/ai-openai 0.9.4 β†’ 0.9.5 Changeset
@tanstack/ai-openrouter 0.9.4 β†’ 0.9.5 Changeset
@tanstack/ai-react-ui 0.7.1 β†’ 0.7.2 Changeset
@tanstack/ai-solid-ui 0.6.6 β†’ 0.6.7 Changeset
@tanstack/ai-vue-ui 0.1.39 β†’ 0.1.40 Changeset
@tanstack/openai-base 0.3.3 β†’ 0.3.4 Changeset
@tanstack/preact-ai-devtools 0.1.36 β†’ 0.1.37 Changeset
@tanstack/react-ai-devtools 0.2.36 β†’ 0.2.37 Changeset
@tanstack/solid-ai-devtools 0.2.36 β†’ 0.2.37 Changeset
@tanstack/ai-client 0.11.2 β†’ 0.11.3 Dependent
@tanstack/ai-event-client 0.3.5 β†’ 0.3.6 Dependent
@tanstack/ai-isolate-cloudflare 0.2.6 β†’ 0.2.7 Dependent
@tanstack/ai-preact 0.6.27 β†’ 0.6.28 Dependent
@tanstack/ai-react 0.11.2 β†’ 0.11.3 Dependent
@tanstack/ai-solid 0.10.2 β†’ 0.10.3 Dependent
@tanstack/ai-svelte 0.10.2 β†’ 0.10.3 Dependent
@tanstack/ai-vue 0.10.3 β†’ 0.10.4 Dependent

tombeckenham and others added 3 commits May 20, 2026 13:50
Drop noPropertyAccessFromIndexSignature and exactOptionalPropertyTypes
from tsconfig.base.json β€” both produced ~500 lines of bracket-access /
conditional-spread churn without catching real bugs, and EOPT would
have propagated style preferences to downstream consumers running the
same flag. Two surgical fix-ups restore narrowing the EOPT-removed
explicit guards relied on (realtime-client status/mode dispatch;
gemini functionResponse optional chain).

Re-enable @typescript-eslint/no-non-null-assertion as an error and
refactor all 63 sites across 29 files to avoid `!`:
- Map get-then-has β†’ bind result to local, init if missing
- array[i] in known-bounded loops β†’ for-of / .entries() / optional chain
- arr.shift()! in while β†’ assignment-in-condition pattern
- closure-captured non-null β†’ capture into const outside the closure
  (TS can't propagate narrowing through arrow boundaries)
- vendor SDK "known present" fields β†’ narrowing type predicates on
  .filter, destructure-with-undefined-check, or guarded if-continues

Upgrade @typescript-eslint/prefer-readonly from warn to error and fix
the 18 sites it surfaced. Keep @typescript-eslint/no-explicit-any as a
warning per maintainer preference β€” existing ~165 warnings unchanged;
new introductions surface in editors without blocking CI.

Reduce eslint-disable count from 25 to 23; every remaining disable
now carries an inline reason. File-level disable in schema-converter
.ts replaced with two targeted line-level disables. Dead
\`part.type === 'document'\` branch removed from ai-context.tsx.
\`while (true)\` in SSE/connection adapters rewritten as
\`while (!abortSignal?.aborted)\`. The require-await warning on the
sync in-memory SkillStorage implementation refactored to
\`Promise.resolve()\` wrappers (no async, no disable).

Update changeset to reflect the final rule set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch AudioProviderOptions from a nominal `AudioAdapter<any, any>` match
to a structural `{ '~types': { providerOptions: infer P extends object } }`
extraction. The previous conditional silently collapsed to `object` for
concrete provider options interfaces without an index signature (the
common case), leaving `modelOptions` effectively untyped at activity call
sites. The new form carries the real per-model provider options through,
restores model-specific typing on `modelOptions`, and removes the last
`any` from the audio extraction helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tombeckenham tombeckenham marked this pull request as ready for review May 20, 2026 06:02
@tombeckenham tombeckenham requested a review from a team May 20, 2026 06:02
@AlemTuzlak AlemTuzlak merged commit 2ad137b into main May 20, 2026
10 checks passed
@AlemTuzlak AlemTuzlak deleted the 564-tighten-typescript-safety-strict-flags-typed-linting-public-api-type-tests branch May 20, 2026 10:13
@github-actions github-actions Bot mentioned this pull request May 20, 2026
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.

Tighten TypeScript safety: strict flags, typed linting, public-API type tests

2 participants