Loading workspace insights... Statistics interval
7 days30 daysLatest CI Pipeline Executions
9d19dd55 chore(types): enable exactOptionalPropertyTypes
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> ace4f2ea refactor(types): ratchet warn-level lint baselines in ai-fal and ai-preact
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> ace4f2ea refactor(types): ratchet warn-level lint baselines in ai-fal and ai-preact
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>