Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/eslint-config-0-4-0.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 0 additions & 4 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function generateSkillTypes(skills: Array<Skill>): 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}`)
}
Expand All @@ -126,7 +126,7 @@ export function generateSkillTypes(skills: Array<Skill>): 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}`)
}
Expand All @@ -135,14 +135,14 @@ export function generateSkillTypes(skills: Array<Skill>): 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 3 additions & 4 deletions packages/typescript/ai-elevenlabs/src/realtime/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import type {
RealtimeMessage,
RealtimeMode,
RealtimeSessionConfig,
RealtimeStatus,
RealtimeToken,
} from '@tanstack/ai'
import type { InternalLogger } from '@tanstack/ai/adapter-internals'
Expand Down Expand Up @@ -119,15 +118,15 @@ 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' })
},

onDisconnect: () => {
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' })
},

Expand Down Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript/ai-fal/src/adapters/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export class FalImageAdapter<TModel extends FalModel> 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 {
Expand Down
5 changes: 2 additions & 3 deletions packages/typescript/ai-gemini/src/adapters/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@ export class GeminiTextAdapter<
response: {
content: msg.content || '',
},
} as any,
},
})
}

Expand Down Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions packages/typescript/ai-grok/src/adapters/tts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -155,7 +154,7 @@ export function buildTTSRequestBody(options: {

const body: Record<string, unknown> = {
text,
voice_id: (voice as GrokTTSVoice | undefined) ?? 'eve',
voice_id: voice ?? 'eve',
language: modelOptions?.language ?? 'en',
output_format: outputFormat,
}
Expand Down
10 changes: 3 additions & 7 deletions packages/typescript/ai-grok/src/realtime/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type {
RealtimeMessage,
RealtimeMode,
RealtimeSessionConfig,
RealtimeStatus,
RealtimeToken,
} from '@tanstack/ai'
import type { InternalLogger } from '@tanstack/ai/adapter-internals'
Expand Down Expand Up @@ -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()
}
})
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/typescript/ai-openai/src/adapters/tts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? {}),
Expand Down
4 changes: 1 addition & 3 deletions packages/typescript/ai-openai/src/adapters/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
5 changes: 2 additions & 3 deletions packages/typescript/ai-openai/src/realtime/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type {
RealtimeMessage,
RealtimeMode,
RealtimeSessionConfig,
RealtimeStatus,
RealtimeToken,
} from '@tanstack/ai'
import type { InternalLogger } from '@tanstack/ai/adapter-internals'
Expand Down Expand Up @@ -127,7 +126,7 @@ async function createWebRTCConnection(
const dataChannelReady = new Promise<void>((resolve) => {
channel.onopen = () => {
flushPendingEvents()
emit('status_change', { status: 'connected' as RealtimeStatus })
emit('status_change', { status: 'connected' })
resolve()
}
})
Expand Down Expand Up @@ -510,7 +509,7 @@ async function createWebRTCConnection(
audioContext = null
}

emit('status_change', { status: 'idle' as RealtimeStatus })
emit('status_change', { status: 'idle' })
},

async startAudioCapture() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>; TS can't narrow the variant from construction.
return out as unknown as NormalizedStreamEvent
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
13 changes: 5 additions & 8 deletions packages/typescript/ai-react-ui/src/chat-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function ChatInput({
ref={inputRef as React.RefObject<HTMLInputElement>}
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()
Expand All @@ -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'
}}
/>
<button
Expand Down
9 changes: 3 additions & 6 deletions packages/typescript/ai/src/activities/chat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ import type {
ChatMiddleware,
ChatMiddlewareConfig,
ChatMiddlewareContext,
ChatMiddlewarePhase,
} from './middleware/types'
import type { SystemPrompt } from '../../system-prompts'
import type { InternalLogger } from '../../logger/internal-logger'
Expand Down Expand Up @@ -375,9 +374,7 @@ class TextEngine<

// Convert messages to ModelMessage format (handles both UIMessage and ModelMessage input)
// This ensures consistent internal format regardless of what the client sends
this.messages = convertMessagesToModelMessages(
config.params.messages as Array<any>,
)
this.messages = convertMessagesToModelMessages(config.params.messages)

// Initialize lazy tool manager after messages are converted (needs message history for scanning)
this.lazyToolManager = new LazyToolManager(
Expand Down Expand Up @@ -410,7 +407,7 @@ class TextEngine<
// `DevtoolsChatMiddleware` (defined in `@tanstack/ai-event-client` to
// avoid a circular dep). Cast it to `ChatMiddleware` for the runner.
const allMiddleware: Array<ChatMiddleware> = [
devtoolsMiddleware() as ChatMiddleware,
devtoolsMiddleware(),
...(config.middleware || []),
stripToSpecMiddleware(),
]
Expand All @@ -423,7 +420,7 @@ class TextEngine<
// Legacy alias kept on the ctx so middleware that reads
// `ctx.conversationId` keeps working. Always equals `threadId`.
conversationId: this.threadId,
phase: 'init' as ChatMiddlewarePhase,
phase: 'init',
iteration: 0,
chunkIndex: 0,
signal: this.effectiveSignal,
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript/ai/src/activities/chat/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function convertMessagesToModelMessages(
modelMessages.push({
role: 'system' as ModelMessage['role'],
content: (msg as { content: string }).content,
} as ModelMessage)
})
continue
}

Expand Down
Loading
Loading