Skip to content

chore(eslint): adopt @tanstack/eslint-config@0.4.0 and prune local overrides#607

Merged
AlemTuzlak merged 1 commit into
mainfrom
eslint-config-0-4-0-cleanup
May 21, 2026
Merged

chore(eslint): adopt @tanstack/eslint-config@0.4.0 and prune local overrides#607
AlemTuzlak merged 1 commit into
mainfrom
eslint-config-0-4-0-cleanup

Conversation

@tombeckenham
Copy link
Copy Markdown
Contributor

@tombeckenham tombeckenham commented May 21, 2026

Summary

  • Bumps @tanstack/eslint-config from 0.3.3 to 0.4.0.
  • Removes the local override layer that has either gone dead upstream or no longer corresponds to real violations.
  • Cleans up ~25 unnecessary type assertions that the upgraded typescript-eslint (via no-unnecessary-type-assertion) now catches.

Changes to eslint.config.js

Before:

rules: {
  'no-case-declarations': 'off',
  'no-shadow': 'off',
  'unused-imports/no-unused-imports': 'warn',
  'pnpm/enforce-catalog': 'off',
  'pnpm/json-enforce-catalog': 'off',
},

After:

rules: {
  'unused-imports/no-unused-imports': 'warn',
},
  • pnpm/* — dead. eslint-plugin-pnpm was removed upstream in 0.3.1.
  • no-case-declarations — no current source actually violates the upstream error setting.
  • no-shadow — upstream is warn, not error. One legitimate shadow surfaces as a warning in activities/chat/index.ts; left as-is (warn-only, doesn't block CI).

The additive typed-linting and no-double-as blocks from #564 are untouched.

Source-file fixes

The upgrade surfaced 26 no-unnecessary-type-assertion errors across 10 publishable packages. All but one were resolved by eslint --fix (purely removing redundant as X). The remaining one — utilities/ag-ui-wire.ts — was a deliberately defensive cast handling ModelMessage-shaped runtime input that doesn't match the declared UIMessage.parts type; preserved with a targeted eslint-disable-next-line plus a reason comment.

No public-API or runtime-behavior changes.

Test plan

  • pnpm nx run-many --target=test:eslint --exclude=examples/**,testing/** — 45/45 pass
  • pnpm nx run-many --target=test:types --exclude=examples/**,testing/** — 49/49 pass
  • pnpm nx run-many --target=build --exclude=examples/**,testing/** — 31/31 pass
  • CI green

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Updated ESLint configuration to version 0.4.0.
    • Removed unnecessary TypeScript type assertions across the codebase for improved type safety and code quality.

Review Change Stack

…errides

- Bump @tanstack/eslint-config 0.3.3 → 0.4.0.
- Drop dead pnpm/* override rules (upstream removed eslint-plugin-pnpm
  in 0.3.1).
- Drop no-case-declarations and no-shadow overrides — neither has real
  violations under the upgraded ruleset (no-shadow stays as upstream
  warn).
- Remove ~25 unnecessary type assertions surfaced by the upgraded
  typescript-eslint via no-unnecessary-type-assertion.
- Preserve one deliberately defensive cast in ag-ui-wire.ts with an
  inline opt-out + reason.

No public-API or runtime-behavior changes.

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

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

Upgrades @tanstack/eslint-config to 0.4.0 and removes ~25 unnecessary TypeScript type assertions across adapter and core packages now caught by stricter typescript-eslint rules, preserving one intentional defensive cast in ag-ui-wire.ts with an explicit opt-out comment.

Changes

ESLint configuration upgrade and type assertion cleanup

Layer / File(s) Summary
Configuration and dependency updates
package.json, eslint.config.js, .changeset/eslint-config-0-4-0.md
@tanstack/eslint-config bumped to 0.4.0; local rule overrides for no-case-declarations, no-shadow, pnpm/enforce-catalog, and pnpm/json-enforce-catalog removed from eslint.config.js; changeset documents the scope and notes one preserved defensive cast.
Type assertion cleanup in code-mode type generation
packages/typescript/ai-code-mode-skills/src/generate-skill-types.ts, packages/typescript/ai-code-mode/src/type-generator/json-schema-to-ts.ts
Explicit as object casts removed from Object.keys() calls when checking for non-empty schema properties in skill and JSON schema type generation.
RealtimeStatus type assertion cleanup across realtime adapters
packages/typescript/ai-elevenlabs/src/realtime/adapter.ts, packages/typescript/ai-grok/src/realtime/adapter.ts, packages/typescript/ai-openai/src/realtime/adapter.ts
Redundant as RealtimeStatus casts removed from status_change event emissions; adapters now emit plain 'connected', 'idle', and 'error' string literals instead of typed assertions.
Type assertion cleanup in model adapter implementations
packages/typescript/ai-fal/src/adapters/image.ts, packages/typescript/ai-gemini/src/adapters/text.ts, packages/typescript/ai-grok/src/adapters/tts.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-openrouter/src/adapters/responses-text.ts, packages/typescript/ai-openrouter/src/tools/web-search-tool.ts, packages/typescript/ai-react-ui/src/chat-input.tsx
Unnecessary type casts removed from FAL image validation, Gemini message/options handling, Grok TTS voice fallback, OpenAI transcription/TTS/video response mapping, OpenRouter stream normalization, web-search tool config, and React UI DOM event handlers.
Type assertion cleanup in chat activity and middleware composition
packages/typescript/ai/src/activities/chat/index.ts, packages/typescript/ai/src/activities/chat/messages.ts, packages/typescript/ai/src/activities/chat/middleware/compose.ts, packages/typescript/ai/src/activities/chat/stream/processor.ts, packages/typescript/ai/src/activities/chat/tools/schema-converter.ts
Type casts removed from TextEngine message conversion, devtools middleware registration, middleware phase initialization, stream chunk instrumentation (keys, type extraction, event payloads), and user message mapping; ESLint suppression comment removed from Standard Schema guard.
Type assertion cleanup in utilities and observability middleware
packages/typescript/ai/src/activities/generateImage/index.ts, packages/typescript/ai/src/extend-adapter.ts, packages/typescript/ai/src/middlewares/content-guard.ts, packages/typescript/ai/src/middlewares/otel.ts, packages/typescript/ai/src/strip-to-spec-middleware.ts, packages/typescript/ai/src/utilities/ag-ui-wire.ts
Type casts removed from image generation event payloads, model options defaults, content guard delta filtering, OpenTelemetry span callback invocations (7 call sites across onAfterToolCall, onError, onAbort, onFinish), and RUN_ERROR detection; one intentional defensive cast in ag-ui-wire.ts preserved via inline eslint-disable comment with reason.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • TanStack/ai#511: Type assertions and wire serialization updates in packages/typescript/ai/src/utilities/ag-ui-wire.ts are adjacent to the main PR's cleanup of the same module, making them code-level related.

Suggested reviewers

  • crutchcorn

Poem

🐰 Assertions once held sway,
Now the linter finds a way,
Cast them off to cleaner skies,
One defensive guard we prize!
TypeScript flows with lighter stride. 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main changes: adopting the newer ESLint config and pruning local overrides. It is clear, specific, and reflects the primary objectives.
Description check ✅ Passed The PR description is comprehensive and well-structured. It covers all template sections with detailed context about the eslint-config upgrade, explains the rationale for removing each rule override, documents the source-file fixes across multiple packages, and includes a test plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch eslint-config-0-4-0-cleanup

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.

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

10 package(s) bumped directly, 15 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/ai 0.20.1 → 0.20.2 Changeset
@tanstack/ai-code-mode 0.1.16 → 0.1.17 Changeset
@tanstack/ai-code-mode-skills 0.1.16 → 0.1.17 Changeset
@tanstack/ai-elevenlabs 0.2.8 → 0.2.9 Changeset
@tanstack/ai-fal 0.7.9 → 0.7.10 Changeset
@tanstack/ai-gemini 0.10.8 → 0.10.9 Changeset
@tanstack/ai-grok 0.8.5 → 0.8.6 Changeset
@tanstack/ai-openai 0.9.5 → 0.9.6 Changeset
@tanstack/ai-openrouter 0.9.5 → 0.9.6 Changeset
@tanstack/ai-react-ui 0.7.2 → 0.7.3 Changeset
@tanstack/ai-client 0.11.3 → 0.11.4 Dependent
@tanstack/ai-devtools-core 0.3.33 → 0.3.34 Dependent
@tanstack/ai-event-client 0.3.6 → 0.3.7 Dependent
@tanstack/ai-isolate-cloudflare 0.2.7 → 0.2.8 Dependent
@tanstack/ai-isolate-node 0.1.16 → 0.1.17 Dependent
@tanstack/ai-isolate-quickjs 0.1.16 → 0.1.17 Dependent
@tanstack/ai-preact 0.6.28 → 0.6.29 Dependent
@tanstack/ai-react 0.11.3 → 0.11.4 Dependent
@tanstack/ai-solid 0.10.3 → 0.10.4 Dependent
@tanstack/ai-svelte 0.10.3 → 0.10.4 Dependent
@tanstack/ai-vue 0.10.4 → 0.10.5 Dependent
@tanstack/ai-vue-ui 0.1.40 → 0.1.41 Dependent
@tanstack/preact-ai-devtools 0.1.37 → 0.1.38 Dependent
@tanstack/react-ai-devtools 0.2.37 → 0.2.38 Dependent
@tanstack/solid-ai-devtools 0.2.37 → 0.2.38 Dependent

@socket-security
Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​tanstack/​eslint-config@​0.4.01001006888100

View full report

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 21, 2026

View your CI Pipeline Execution ↗ for commit e1e24b6

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 1m 11s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-21 02:26:52 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 21, 2026

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-anthropic

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

@tanstack/ai-client

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

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-skills

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

@tanstack/ai-devtools-core

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

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-groq

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

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-openrouter

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-utils

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

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/openai-base

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

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

commit: e1e24b6

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/typescript/ai-fal/src/adapters/image.ts (1)

134-141: ⚡ Quick win

Remove redundant type assertion.

Line 138 now validates that img.url is a string via typeof img.url === 'string', which narrows the type within the guarded block. The cast on line 140 is now redundant and can be removed.

♻️ Proposed fix
   } else if (
     img &&
     typeof img === 'object' &&
     'url' in img &&
     typeof img.url === 'string'
   ) {
-    url = (img as { url: string }).url
+    url = img.url
   } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/typescript/ai-fal/src/adapters/image.ts` around lines 134 - 141,
Remove the redundant type assertion when extracting the URL: inside the guarded
branch that checks img && typeof img === 'object' && 'url' in img && typeof
img.url === 'string', assign url directly from img.url (no cast). Update the
assignment that currently uses (img as { url: string }).url to simply use
img.url so the narrowed type is relied upon.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/typescript/ai-fal/src/adapters/image.ts`:
- Around line 134-141: Remove the redundant type assertion when extracting the
URL: inside the guarded branch that checks img && typeof img === 'object' &&
'url' in img && typeof img.url === 'string', assign url directly from img.url
(no cast). Update the assignment that currently uses (img as { url: string
}).url to simply use img.url so the narrowed type is relied upon.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0eac3316-cce1-4a1a-a7ba-a8b697aee8b8

📥 Commits

Reviewing files that changed from the base of the PR and between 1e4df18 and e1e24b6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .changeset/eslint-config-0-4-0.md
  • eslint.config.js
  • package.json
  • packages/typescript/ai-code-mode-skills/src/generate-skill-types.ts
  • packages/typescript/ai-code-mode/src/type-generator/json-schema-to-ts.ts
  • packages/typescript/ai-elevenlabs/src/realtime/adapter.ts
  • packages/typescript/ai-fal/src/adapters/image.ts
  • packages/typescript/ai-gemini/src/adapters/text.ts
  • packages/typescript/ai-grok/src/adapters/tts.ts
  • packages/typescript/ai-grok/src/realtime/adapter.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/realtime/adapter.ts
  • packages/typescript/ai-openrouter/src/adapters/responses-text.ts
  • packages/typescript/ai-openrouter/src/tools/web-search-tool.ts
  • packages/typescript/ai-react-ui/src/chat-input.tsx
  • packages/typescript/ai/src/activities/chat/index.ts
  • packages/typescript/ai/src/activities/chat/messages.ts
  • packages/typescript/ai/src/activities/chat/middleware/compose.ts
  • packages/typescript/ai/src/activities/chat/stream/processor.ts
  • packages/typescript/ai/src/activities/chat/tools/schema-converter.ts
  • packages/typescript/ai/src/activities/generateImage/index.ts
  • packages/typescript/ai/src/extend-adapter.ts
  • packages/typescript/ai/src/middlewares/content-guard.ts
  • packages/typescript/ai/src/middlewares/otel.ts
  • packages/typescript/ai/src/strip-to-spec-middleware.ts
  • packages/typescript/ai/src/utilities/ag-ui-wire.ts
💤 Files with no reviewable changes (2)
  • packages/typescript/ai/src/activities/chat/tools/schema-converter.ts
  • eslint.config.js

@tombeckenham tombeckenham requested a review from a team May 21, 2026 02:40
@AlemTuzlak AlemTuzlak merged commit ec1393d into main May 21, 2026
10 checks passed
@AlemTuzlak AlemTuzlak deleted the eslint-config-0-4-0-cleanup branch May 21, 2026 03:41
@github-actions github-actions Bot mentioned this pull request May 21, 2026
tombeckenham added a commit that referenced this pull request May 21, 2026
Surfaced by tsc after merging origin/main (the eslint-config 0.4.0
bump in #607 strengthens unused-import detection). The type is
re-exported elsewhere and not referenced inside this file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tombeckenham added a commit that referenced this pull request May 21, 2026
- Remove unnecessary `as object` cast in compose.ts; the upgraded
  typescript-eslint (via #607) flags it as unnecessary because
  `Object.keys` already accepts the original type.
- Update the openrouter `chat() entrypoint with strict transformation`
  test: with Critical #1 (skip agent loop when tools.length === 0),
  the engine no longer consumes the streaming mock for an empty agent
  pass — the structured-output payload now arrives via the same
  streaming mock that previously held the placeholder 'ok' delta.
  Move the JSON payload into the streaming mock and assert via
  `responseFormat` presence instead of `stream === false`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AlemTuzlak added a commit that referenced this pull request May 21, 2026
* feat(ai): structured-output middleware coverage (closes #390)

Middleware now wraps the final structured-output provider call in
`chat({ outputSchema })` for both Promise<T> and streaming variants.

- Add `'structuredOutput'` to `ChatMiddlewarePhase` and set it on
  `ChatMiddlewareContext` for the duration of the final structured-output
  adapter call.
- Add optional `ChatMiddleware.onStructuredOutputConfig` hook receiving a
  `StructuredOutputMiddlewareConfig` (with the JSON Schema) which may
  return a partial to transform the config before the final call.
- Export new `StructuredOutputMiddlewareConfig` type extending
  `ChatMiddlewareConfig` with `outputSchema: JSONSchema`.
- `onChunk` now observes chunks from the final structured-output call;
  `onFinish` fires once at the end of the whole `chat()` invocation
  after finalization completes.
- Remove the previous `RUN_STARTED`/`RUN_FINISHED` suppression hack in
  `runStreamingStructuredOutput`; engine now emits exactly one outer
  pair around the whole run.

* test(ai-e2e): add structured-output x middleware spec

Adds a Playwright spec that exercises the structured-output finalization
path end-to-end with middleware attached, plus a route + fixture for the
mocked LLM call and a phase-capture helper for asserting middleware
phase transitions.

* docs(ai): document onStructuredOutputConfig hook and structuredOutput phase

Updates middleware and structured-outputs skills, public docs, and
regenerated TypeDoc reference for the new `structuredOutput` middleware
phase and the `onStructuredOutputConfig` hook.

* fix(ai): address CR findings on structured-output middleware coverage

- Type: onConfig / onStructuredOutputConfig Promise return allows null
- Error diagnostics: preserve cause + code on validation failures, with
  smarter message extraction for plain-object errors (Standard Schema)
- Streaming consumers see RUN_ERROR on finalization failure (missing
  result or validation), guarded against double-emission
- Synth structured-output.start carries threadId
- Abort signal checked inside runStructuredFinalization for-await loop
- runAgenticStructuredOutput rethrow preserves cause + code
- Skill examples use (ctx, info) signature for onFinish/onError
- Docs Mermaid diagram includes structuredOutput phase branch
- Docs prose acknowledges 3 onConfig firings (init + beforeModel +
  structuredOutput boundary)
- Docs add FinishInfo table marking info.usage explicitly optional
- Changeset reflects suppression-hack relocation (not removal)
- Test comment corrected (synthesis still happens)
- E2E spec title honest about stream:true; docblock notes scope
- kind=phase GET no longer gated behind OTEL_TEST_ENABLED

Call-site enumeration (Procedure 2.8):
- finalizationError gained cause?: unknown. Readers updated:
  * TextEngine terminal hook chooser — propagates cause via Error({ cause })
    and surfaces code as a non-enumerable Object.defineProperty.
  * runAgenticStructuredOutput — same treatment when re-throwing.
  * getFinalizationError return type widened to include cause.
- runStructuredFinalization gained a post-loop synthetic RUN_ERROR yield
  path, gated on yieldChunks and a new runErrorYielded flag. The streaming
  consumer in runStreamingStructuredOutputImpl iterates engine.run() and
  propagates the new chunk transparently — no consumer-side changes.
- Synthesized structured-output.start gained threadId — passive readers,
  no behavioral impact.

* fix(ai): address Round 2 CR findings on structured-output middleware

- Mid-finalization abort routes through onAbort (not onError):
  skip missing-result attribution when isCancelled()
- Finalization chunks no longer pollute agent-loop state:
  removed handleStreamChunk(chunk) call in runStructuredFinalization;
  targeted updates only for structured-output.complete + RUN_ERROR + RUN_FINISHED.usage
- Synth RUN_ERROR for empty-stream case is preceded by a synth
  structured-output.start so client-side StructuredOutputPart routing works

Call-site enumeration:
- handleStreamChunk removal: accumulatedContent (now agent-loop only;
  info.content stays clean of JSON deltas), finishedEvent /
  lastFinishReason (finalization no longer overwrites the agent loop's
  real finish reason), currentMessageId (unchanged path), currentThinking*
  (no thinking pollution), earlyTermination (irrelevant — finalization
  is already terminating). Explicit branches still capture
  structured-output.complete, RUN_ERROR (finalizationError), and
  RUN_FINISHED.usage (runOnUsage).
- isCancelled() early-return + chooser gating: run()'s finally block
  fires onAbort when !terminalHookCalled && isCancelled(). The terminal-
  hook chooser at the end of the try-block now additionally skips when
  isCancelled() so it can't pre-empt the finally with a stray onFinish.
- Pre-synth-start before synth-RUN_ERROR: uses the same
  buildSynthesizedStart() + pipeThroughMiddleware path as the in-loop
  synth, gated on !startEmitted so we never double-emit.

* fix(ai): align onFinish info docs with implementation

- Docs/skill no longer claim onFinish.info.usage reflects the full
  run including finalization tokens. The Round 2 fix correctly
  segregated finalization state; info.* reflects the agent loop's
  terminal state only.
- Add unit test pinning the documented semantics: tools-less
  structured-output run gives info.usage=undefined, finishReason=null,
  content='', while onUsage fires once for finalization tokens.

* fix(ai): clarify tools on StructuredOutputMiddlewareConfig is not forwarded to the structured-output adapter call

Round 4 CR finding: tools is structurally inherited from ChatMiddlewareConfig
but the engine omits tools from structuredCallOptions.chatOptions. Document
the caveat in the type JSDoc and the public middleware reference so middleware
authors don't expect tools transformation at this boundary to take effect.

* fix(ai): align runStreamingStructuredOutput JSDoc with implementation

Round 5 CR finding: the JSDoc claimed "Validates the parsed object against
the original Standard Schema" but the implementation explicitly defers
validation to the consumer (via `void outputSchema`). Update the JSDoc to
honestly describe the streaming-path validation policy and call out the
deliberate asymmetry with `runAgenticStructuredOutput` (which does validate).

* fix(docs): clarify server-side validation is path-dependent (streaming vs Promise<T>)

Round 6 CR finding: docs/structured-outputs/overview.md claimed "Server-side
validation against your schema is always authoritative" but the streaming
path (chat({ outputSchema, stream: true })) deliberately defers validation
to the consumer. Update the prose to reflect the actual path-dependent
behavior — agentic Promise<T> validates server-side; streaming forwards
the adapter event verbatim and consumers validate downstream.

* fix(ai): apply Procedure 3 bucket-(c) audit promotions

Bucket (c) Promotion Audit (cr-loop final step) flagged 4 items as load-bearing on the structured-output subject this PR makes authoritative:

- PROMOTE_TO_A: runAgenticStructuredOutput was calling convertSchemaToJsonSchema without forStructuredOutput: true while runStreamingStructuredOutput did. Same Zod schema produced different JSON Schema depending on stream mode. Both paths now use the strict converter, eliminating the divergence.

- PROMOTE_TO_B (3 trivial fixes on PR-adjacent surfaces):
  - fallbackStructuredOutputStream's IDs prefixed 'mock-' in production code; renamed to 'fallback-' to stop leaking test-style identifiers into user-visible run/thread/message IDs for Anthropic/Gemini/Ollama structured-output runs.
  - fallbackStructuredOutputStream's RUN_ERROR chunk was missing threadId while sibling RUN_STARTED and RUN_FINISHED carried it; added for consumer correlation.
  - chat() JSDoc example used chunk.type === 'content' (wrong); changed to 'TEXT_MESSAGE_CONTENT'.

26 other bucket-(c) items confirmed STAY_IN_C (pre-existing, not subject-load-bearing) and are reported to the loop-exit follow-up list. 1 item (gpt-5.2 model existence) REFUTED.

* ci: apply automated fixes

* fix(ai): address PR #600 review — critical + important findings

- Skip agent loop when finalStructuredOutput is set and tools.length === 0
  to avoid a wasted chatStream round-trip before finalization (Critical #1).
- Omit `tools` structurally from StructuredOutputMiddlewareConfig — the
  field was inherited but silently discarded at the provider boundary
  (Important #2).
- Preserve Standard Schema `issues[]` on validation failures via a new
  exported StandardSchemaValidationError carried as `error.cause`
  (Important #4).
- Preserve the original adapter error (stack, cause, provider properties)
  on the fallbackStructuredOutputStream path via an onAdapterError
  callback (Important #5).
- Add tests for messages-transform via onStructuredOutputConfig and for
  mid-finalization abort routing through onAbort (Important #6, #7).
- Strip transitional source comments ("in Task 7", "closes issue #390").

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

* chore(examples): add /verify-pr600 page to ts-react-chat

Adds a diagnostics page in the ts-react-chat example that exercises all
four PR #600 review fixes against the real chat() engine using inline
mock adapters. No API keys required — click "Run verification" and see
pass/fail per scenario with observed values.

- POST /api/verify-pr600 runs the four scenarios server-side.
- /verify-pr600 page calls the endpoint and renders results.
- Header nav gains a "Diagnostics" section linking to the page.

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

* chore(examples): replace verify-pr600 with real-provider repros

The mock-based verify-pr600 page only validated my own fixes; it didn't
prove anything about real provider wire formats. Replace with two
real-provider verifications:

- New /issue-390-repro page runs the exact gist from the issue reporter
  (@imsherrill) against geminiText('gemini-2.5-flash') and surfaces the
  middleware logs + per-phase chunk counts. Fix is verified iff the
  middleware observed any chunks with ctx.phase === 'structuredOutput'.
- Existing /generations/structured-output page now instruments every
  request with a counter middleware. Counts surface via a JSON field
  (non-streaming) or a trailing CUSTOM `phase-counts` event (streaming).
  Works across all configured providers (OpenAI/Anthropic via OpenRouter,
  Gemini via OpenRouter, Grok, Groq).

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

* chore(examples): drop /issue-390-repro page

The counter middleware on /generations/structured-output already
demonstrates the PR #600 fix against real providers — the dedicated
single-shot repro page is redundant.

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

* fix(ai): drop unused ChatMiddlewarePhase import after merge

Surfaced by tsc after merging origin/main (the eslint-config 0.4.0
bump in #607 strengthens unused-import detection). The type is
re-exported elsewhere and not referenced inside this file.

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

* ci: apply automated fixes

* fix: address CI lint + openrouter test failures

- Remove unnecessary `as object` cast in compose.ts; the upgraded
  typescript-eslint (via #607) flags it as unnecessary because
  `Object.keys` already accepts the original type.
- Update the openrouter `chat() entrypoint with strict transformation`
  test: with Critical #1 (skip agent loop when tools.length === 0),
  the engine no longer consumes the streaming mock for an empty agent
  pass — the structured-output payload now arrives via the same
  streaming mock that previously held the placeholder 'ok' delta.
  Move the JSON payload into the streaming mock and assert via
  `responseFormat` presence instead of `stream === false`.

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

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants