diff --git a/.changeset/eslint-config-0-4-0.md b/.changeset/eslint-config-0-4-0.md new file mode 100644 index 000000000..65dd237b1 --- /dev/null +++ b/.changeset/eslint-config-0-4-0.md @@ -0,0 +1,22 @@ +--- +'@tanstack/ai': patch +'@tanstack/ai-code-mode': patch +'@tanstack/ai-code-mode-skills': patch +'@tanstack/ai-elevenlabs': patch +'@tanstack/ai-fal': patch +'@tanstack/ai-gemini': patch +'@tanstack/ai-grok': patch +'@tanstack/ai-openai': patch +'@tanstack/ai-openrouter': patch +'@tanstack/ai-react-ui': patch +--- + +Adopt `@tanstack/eslint-config@0.4.0` and clean up the local override layer. + +- Bump `@tanstack/eslint-config` from `0.3.3` to `0.4.0`. +- Drop dead `pnpm/enforce-catalog` and `pnpm/json-enforce-catalog` disables (upstream removed `eslint-plugin-pnpm` in `0.3.1`). +- Drop the `no-case-declarations: off` override — no current source actually violates it. +- Drop the `no-shadow: off` override — upstream sets it to `warn`, so it surfaces in editors without blocking CI. +- Remove ~25 unnecessary type assertions across the publishable packages that the upgraded `typescript-eslint` now catches via `no-unnecessary-type-assertion`. One deliberately defensive cast in `ag-ui-wire.ts` is preserved with an inline opt-out and a reason comment. + +No public-API or runtime-behavior changes. diff --git a/eslint.config.js b/eslint.config.js index 982bac57b..58ee5aaf3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -12,11 +12,7 @@ const config = [ 'unused-imports': unusedImports, }, rules: { - 'no-case-declarations': 'off', - 'no-shadow': 'off', 'unused-imports/no-unused-imports': 'warn', - 'pnpm/enforce-catalog': 'off', - 'pnpm/json-enforce-catalog': 'off', }, }, { diff --git a/package.json b/package.json index dd91cc05e..3a71835b7 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@changesets/cli": "^2.30.0", "@faker-js/faker": "^10.1.0", "@svitejs/changesets-changelog-github-compact": "^1.2.0", - "@tanstack/eslint-config": "0.3.3", + "@tanstack/eslint-config": "0.4.0", "@tanstack/typedoc-config": "0.3.1", "@tanstack/vite-config": "0.4.1", "@types/node": "^24.10.1", diff --git a/packages/typescript/ai-code-mode-skills/src/generate-skill-types.ts b/packages/typescript/ai-code-mode-skills/src/generate-skill-types.ts index 4e4da85bf..0faf2795b 100644 --- a/packages/typescript/ai-code-mode-skills/src/generate-skill-types.ts +++ b/packages/typescript/ai-code-mode-skills/src/generate-skill-types.ts @@ -116,7 +116,7 @@ export function generateSkillTypes(skills: Array): string { if ( skill.inputSchema.type === 'object' && skill.inputSchema.properties && - Object.keys(skill.inputSchema.properties as object).length > 0 + Object.keys(skill.inputSchema.properties).length > 0 ) { declarations.push(`interface ${inputTypeName} ${inputType}`) } @@ -126,7 +126,7 @@ export function generateSkillTypes(skills: Array): string { if ( skill.outputSchema.type === 'object' && skill.outputSchema.properties && - Object.keys(skill.outputSchema.properties as object).length > 0 + Object.keys(skill.outputSchema.properties).length > 0 ) { declarations.push(`interface ${outputTypeName} ${outputType}`) } @@ -135,14 +135,14 @@ export function generateSkillTypes(skills: Array): string { const inputRef = skill.inputSchema.type === 'object' && skill.inputSchema.properties && - Object.keys(skill.inputSchema.properties as object).length > 0 + Object.keys(skill.inputSchema.properties).length > 0 ? inputTypeName : inputType const outputRef = skill.outputSchema.type === 'object' && skill.outputSchema.properties && - Object.keys(skill.outputSchema.properties as object).length > 0 + Object.keys(skill.outputSchema.properties).length > 0 ? outputTypeName : outputType diff --git a/packages/typescript/ai-code-mode/src/type-generator/json-schema-to-ts.ts b/packages/typescript/ai-code-mode/src/type-generator/json-schema-to-ts.ts index c18d73372..9b1bb2f26 100644 --- a/packages/typescript/ai-code-mode/src/type-generator/json-schema-to-ts.ts +++ b/packages/typescript/ai-code-mode/src/type-generator/json-schema-to-ts.ts @@ -84,7 +84,7 @@ export function jsonSchemaToTypeScript( if ( schema.type === 'object' && schema.properties && - Object.keys(schema.properties as object).length > 0 + Object.keys(schema.properties).length > 0 ) { return { name: typeName, diff --git a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts index 616efd79a..e9a857442 100644 --- a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts +++ b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts @@ -8,7 +8,6 @@ import type { RealtimeMessage, RealtimeMode, RealtimeSessionConfig, - RealtimeStatus, RealtimeToken, } from '@tanstack/ai' import type { InternalLogger } from '@tanstack/ai/adapter-internals' @@ -119,7 +118,7 @@ async function createElevenLabsConnection( logger.provider(`provider=elevenlabs direction=in type=connect`, { frame: { type: 'connect' }, }) - emit('status_change', { status: 'connected' as RealtimeStatus }) + emit('status_change', { status: 'connected' }) emit('mode_change', { mode: 'listening' }) }, @@ -127,7 +126,7 @@ async function createElevenLabsConnection( logger.provider(`provider=elevenlabs direction=in type=disconnect`, { frame: { type: 'disconnect' }, }) - emit('status_change', { status: 'idle' as RealtimeStatus }) + emit('status_change', { status: 'idle' }) emit('mode_change', { mode: 'idle' }) }, @@ -205,7 +204,7 @@ async function createElevenLabsConnection( await conversation.endSession() conversation = null } - emit('status_change', { status: 'idle' as RealtimeStatus }) + emit('status_change', { status: 'idle' }) }, async startAudioCapture() { diff --git a/packages/typescript/ai-fal/src/adapters/image.ts b/packages/typescript/ai-fal/src/adapters/image.ts index 66c2a6117..11dcbaeb2 100644 --- a/packages/typescript/ai-fal/src/adapters/image.ts +++ b/packages/typescript/ai-fal/src/adapters/image.ts @@ -135,7 +135,7 @@ export class FalImageAdapter extends BaseImageAdapter< img && typeof img === 'object' && 'url' in img && - typeof (img as { url: unknown }).url === 'string' + typeof img.url === 'string' ) { url = (img as { url: string }).url } else { diff --git a/packages/typescript/ai-gemini/src/adapters/text.ts b/packages/typescript/ai-gemini/src/adapters/text.ts index 7c6cebdf0..b8ff1cbf5 100644 --- a/packages/typescript/ai-gemini/src/adapters/text.ts +++ b/packages/typescript/ai-gemini/src/adapters/text.ts @@ -750,7 +750,7 @@ export class GeminiTextAdapter< response: { content: msg.content || '', }, - } as any, + }, }) } @@ -829,8 +829,7 @@ export class GeminiTextAdapter< // local `thinkingLevel?: keyof typeof ThinkingLevel` type doesn't leak // into the SDK config object via the `...modelOpts` spread — we re-add a // properly-typed `ThinkingConfig` below. - const { thinkingConfig, ...modelOpts } = - options.modelOptions ?? ({} as GeminiTextProviderOptions) + const { thinkingConfig, ...modelOpts } = options.modelOptions ?? {} // Build the thinkingConfig payload only when the caller actually supplied // one. Our local `thinkingLevel` is typed as `keyof typeof ThinkingLevel` // (string union) so users can pass plain strings; the SDK target is the diff --git a/packages/typescript/ai-grok/src/adapters/tts.ts b/packages/typescript/ai-grok/src/adapters/tts.ts index b421a5463..268856432 100644 --- a/packages/typescript/ai-grok/src/adapters/tts.ts +++ b/packages/typescript/ai-grok/src/adapters/tts.ts @@ -5,7 +5,6 @@ import type { GrokTTSModel } from '../model-meta' import type { GrokTTSCodec, GrokTTSProviderOptions, - GrokTTSVoice, } from '../audio/tts-provider-options' const DEFAULT_GROK_BASE_URL = 'https://api.x.ai/v1' @@ -155,7 +154,7 @@ export function buildTTSRequestBody(options: { const body: Record = { text, - voice_id: (voice as GrokTTSVoice | undefined) ?? 'eve', + voice_id: voice ?? 'eve', language: modelOptions?.language ?? 'en', output_format: outputFormat, } diff --git a/packages/typescript/ai-grok/src/realtime/adapter.ts b/packages/typescript/ai-grok/src/realtime/adapter.ts index c63653aab..b17b36431 100644 --- a/packages/typescript/ai-grok/src/realtime/adapter.ts +++ b/packages/typescript/ai-grok/src/realtime/adapter.ts @@ -7,7 +7,6 @@ import type { RealtimeMessage, RealtimeMode, RealtimeSessionConfig, - RealtimeStatus, RealtimeToken, } from '@tanstack/ai' import type { InternalLogger } from '@tanstack/ai/adapter-internals' @@ -210,7 +209,7 @@ async function createWebRTCConnection( // don't attempt a redundant reject on an already-settled promise. rejectDataChannelReady = null flushPendingEvents() - emit('status_change', { status: 'connected' as RealtimeStatus }) + emit('status_change', { status: 'connected' }) resolve() } }) @@ -297,10 +296,7 @@ async function createWebRTCConnection( // guard listeners would see two `idle` events per disconnect. if (!isTornDown) { emit('status_change', { - status: - state === 'failed' - ? ('error' as RealtimeStatus) - : ('idle' as RealtimeStatus), + status: state === 'failed' ? 'error' : 'idle', }) } if (!dataChannelOpened) { @@ -961,7 +957,7 @@ async function createWebRTCConnection( // every cleanup site stays in sync (input analyser, output analyser, // output source, audio element, etc.). await teardownConnection() - emit('status_change', { status: 'idle' as RealtimeStatus }) + emit('status_change', { status: 'idle' }) }, async startAudioCapture() { diff --git a/packages/typescript/ai-openai/src/adapters/transcription.ts b/packages/typescript/ai-openai/src/adapters/transcription.ts index 0f2ffeca5..060dbf0e8 100644 --- a/packages/typescript/ai-openai/src/adapters/transcription.ts +++ b/packages/typescript/ai-openai/src/adapters/transcription.ts @@ -186,7 +186,7 @@ export class OpenAITranscriptionAdapter< format?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt', ): OpenAI_SDK.Audio.TranscriptionCreateParams['response_format'] { if (!format) return 'json' - return format as OpenAI_SDK.Audio.TranscriptionCreateParams['response_format'] + return format } } diff --git a/packages/typescript/ai-openai/src/adapters/tts.ts b/packages/typescript/ai-openai/src/adapters/tts.ts index 53a160262..fb29088a5 100644 --- a/packages/typescript/ai-openai/src/adapters/tts.ts +++ b/packages/typescript/ai-openai/src/adapters/tts.ts @@ -65,7 +65,7 @@ export class OpenAITTSAdapter< const request: OpenAI_SDK.Audio.SpeechCreateParams = { model, input: text, - voice: (voice || 'alloy') as OpenAI_SDK.Audio.SpeechCreateParams['voice'], + voice: voice || 'alloy', response_format: format, ...(speed !== undefined && { speed }), ...(modelOptions ?? {}), diff --git a/packages/typescript/ai-openai/src/adapters/video.ts b/packages/typescript/ai-openai/src/adapters/video.ts index 247445f02..2bb9df046 100644 --- a/packages/typescript/ai-openai/src/adapters/video.ts +++ b/packages/typescript/ai-openai/src/adapters/video.ts @@ -100,9 +100,7 @@ export class OpenAIVideoAdapter< // `VideoCreateParams.size` is `size?: VideoSize` (no `| undefined`), so we // narrow before assignment instead of casting from a `T | undefined` source. if (size) { - request.size = size as NonNullable< - OpenAI_SDK.Videos.VideoCreateParams['size'] - > + request.size = size } else if (modelOptions?.size) { request.size = modelOptions.size } diff --git a/packages/typescript/ai-openai/src/realtime/adapter.ts b/packages/typescript/ai-openai/src/realtime/adapter.ts index 554b3356b..5f8a5c09c 100644 --- a/packages/typescript/ai-openai/src/realtime/adapter.ts +++ b/packages/typescript/ai-openai/src/realtime/adapter.ts @@ -7,7 +7,6 @@ import type { RealtimeMessage, RealtimeMode, RealtimeSessionConfig, - RealtimeStatus, RealtimeToken, } from '@tanstack/ai' import type { InternalLogger } from '@tanstack/ai/adapter-internals' @@ -127,7 +126,7 @@ async function createWebRTCConnection( const dataChannelReady = new Promise((resolve) => { channel.onopen = () => { flushPendingEvents() - emit('status_change', { status: 'connected' as RealtimeStatus }) + emit('status_change', { status: 'connected' }) resolve() } }) @@ -510,7 +509,7 @@ async function createWebRTCConnection( audioContext = null } - emit('status_change', { status: 'idle' as RealtimeStatus }) + emit('status_change', { status: 'idle' }) }, async startAudioCapture() { diff --git a/packages/typescript/ai-openrouter/src/adapters/responses-text.ts b/packages/typescript/ai-openrouter/src/adapters/responses-text.ts index 37d221931..76b27a4a8 100644 --- a/packages/typescript/ai-openrouter/src/adapters/responses-text.ts +++ b/packages/typescript/ai-openrouter/src/adapters/responses-text.ts @@ -1823,9 +1823,7 @@ function normalizeStreamEvent(event: StreamEvents): NormalizedStreamEvent { } if ('part' in raw) out.part = raw.part out.type = - typeof raw['type'] === 'string' - ? raw['type'] - : (e.type as string) || 'unknown' + typeof raw['type'] === 'string' ? raw['type'] : e.type || 'unknown' // eslint-disable-next-line no-restricted-syntax -- NormalizedStreamEvent is a discriminated union built field-by-field from Record; TS can't narrow the variant from construction. return out as unknown as NormalizedStreamEvent } diff --git a/packages/typescript/ai-openrouter/src/tools/web-search-tool.ts b/packages/typescript/ai-openrouter/src/tools/web-search-tool.ts index 6fcc1870a..bc47d33de 100644 --- a/packages/typescript/ai-openrouter/src/tools/web-search-tool.ts +++ b/packages/typescript/ai-openrouter/src/tools/web-search-tool.ts @@ -57,7 +57,7 @@ export function convertWebSearchToolToAdapterFormat( } return { type: 'web_search', - web_search: metadata.web_search as WebSearchToolConfig['web_search'], + web_search: metadata.web_search, } } diff --git a/packages/typescript/ai-react-ui/src/chat-input.tsx b/packages/typescript/ai-react-ui/src/chat-input.tsx index dfacc0f68..d90cdcd5a 100644 --- a/packages/typescript/ai-react-ui/src/chat-input.tsx +++ b/packages/typescript/ai-react-ui/src/chat-input.tsx @@ -105,7 +105,7 @@ export function ChatInput({ ref={inputRef as React.RefObject} type="text" value={value} - onChange={(e) => setValue((e.target as HTMLInputElement).value)} + onChange={(e) => setValue(e.target.value)} onKeyDown={(e) => { if (submitOnEnter && e.key === 'Enter') { e.preventDefault() @@ -127,15 +127,12 @@ export function ChatInput({ transition: 'all 0.2s', }} onFocus={(e) => { - ;(e.target as HTMLInputElement).style.borderColor = - 'rgba(249, 115, 22, 0.4)' - ;(e.target as HTMLInputElement).style.boxShadow = - '0 0 0 2px rgba(249, 115, 22, 0.2)' + e.target.style.borderColor = 'rgba(249, 115, 22, 0.4)' + e.target.style.boxShadow = '0 0 0 2px rgba(249, 115, 22, 0.2)' }} onBlur={(e) => { - ;(e.target as HTMLInputElement).style.borderColor = - 'rgba(255, 255, 255, 0.1)' - ;(e.target as HTMLInputElement).style.boxShadow = 'none' + e.target.style.borderColor = 'rgba(255, 255, 255, 0.1)' + e.target.style.boxShadow = 'none' }} />