Skip to content

fix(ai): detect callable ArkType schemas in convertSchemaToJsonSchema (#276)#662

Merged
AlemTuzlak merged 1 commit into
mainfrom
fix/276-arktype-callable-schema
May 30, 2026
Merged

fix(ai): detect callable ArkType schemas in convertSchemaToJsonSchema (#276)#662
AlemTuzlak merged 1 commit into
mainfrom
fix/276-arktype-callable-schema

Conversation

@AlemTuzlak
Copy link
Copy Markdown
Contributor

@AlemTuzlak AlemTuzlak commented May 30, 2026

Fixes #276

convertSchemaToJsonSchema returned the raw ArkType validator (a function) instead of a JSON Schema object, so providers that validate the tool/output schema (e.g. OpenRouter) rejected it.

Root cause

ArkType's type() returns a callable function with ~standard attached as a property — not a plain object. Both Standard Schema detection guards (isStandardJSONSchema, isStandardSchema) required typeof schema === 'object', which is false for a function. ArkType schemas therefore matched neither branch and fell through to the JSONSchema pass-through, which returned the function untouched. The same guard also gated tool input/output validation, so validation was silently skipped for ArkType tools.

Verified empirically against the reporter's version (arktype 2.1.28): typeof type({...}) === 'function', and ~standard.jsonSchema.input() is present and works — so the only needed change is widening the guards.

Fix

  • Add an isPropertyCarrier helper that accepts both 'object' and 'function' (non-null).
  • Route isStandardJSONSchema and isStandardSchema through it. Rewrote isStandardJSONSchema cast-free, which also drops the eslint-disable and turns a latent null.jsonSchema crash into a clean false.

Because tool input/output conversion/validation and structured output all route through these two functions, ArkType now works end-to-end for tools and structured output too — previously validation was silently skipped.

Tests

  • Unit (packages/ai/tests/standard-converter.test.ts): the reporter's exact repro plus guard detection, optional fields, structured-output transformation, and direct parseWithStandardSchema / validateWithStandardSchema coverage with ArkType.
  • E2E (testing/e2e/tests/arktype-tool-wire.spec.ts + route): drives the OpenRouter chat adapter with an ArkType-schema function tool against aimock and inspects the journal to assert the converted JSON Schema reaches the wire (pre-fix the function-valued schema serialized to {}). Deterministic — no recorded LLM fixture needed.

Verification

  • @tanstack/ai unit suite green (63 tests in the converter spec); test:types clean; test:eslint 0 errors for the touched file.
  • E2E arktype — tool schema wire format passes.
  • test:sherif and Prettier clean.

A patch changeset is included.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed detection and validation of ArkType callable schemas in tool definitions. Previously, callable ArkType schemas were incorrectly handled due to overly strict schema type validation guards, causing improper conversion to JSON Schema format. The detection logic has been updated to properly recognize and convert all ArkType schema types, including callable variants.

Review Change Stack

…#276)

ArkType's type() returns a callable function with ~standard attached, not a
plain object. The Standard Schema detection guards required
`typeof schema === 'object'`, so ArkType schemas matched neither branch and
fell through the JSONSchema pass-through, returning the raw validator function
instead of a converted JSON Schema object. This also meant tool input/output
validation silently skipped ArkType schemas.

Add an isPropertyCarrier helper that accepts both objects and callable
functions, and route both isStandardJSONSchema and isStandardSchema through it.
Add unit coverage (conversion, guards, structured output, parse/validate) plus
an end-to-end wire-format regression asserting the converted JSON Schema
reaches the provider.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 30, 2026

📝 Walkthrough

Walkthrough

This PR fixes convertSchemaToJsonSchema to properly handle ArkType callable schemas by relaxing schema-type guards to accept both objects and functions as Standard Schema carriers. It adds comprehensive unit tests validating schema detection and conversion, e2e integration infrastructure with a new /api/arktype-tool-wire endpoint, and dependency documentation.

Changes

ArkType Callable Schema Support

Layer / File(s) Summary
Schema detection guards for callable validators
packages/ai/src/activities/chat/tools/schema-converter.ts
Introduces isPropertyCarrier() helper that accepts objects and functions as property carriers, enabling isStandardJSONSchema and isStandardSchema to recognize ArkType callables with attached ~standard metadata instead of restricting to plain objects only.
Unit test coverage for callable schemas
packages/ai/tests/standard-converter.test.ts
Adds test cases for convertSchemaToJsonSchema, isStandardJSONSchema, isStandardSchema, parseWithStandardSchema, and validateWithStandardSchema with ArkType callables, verifying JSON Schema conversion, optional field handling, structured output transformation, and validation/parsing workflows.
Dependency and changelog updates
packages/ai/package.json, testing/e2e/package.json, .changeset/arktype-callable-schema.md
Adds arktype ^2.1.28 to packages/ai devDependencies and testing/e2e dependencies; documents the fix to schema-type guards in a changeset patch bump.
E2E endpoint route registration
testing/e2e/src/routeTree.gen.ts
Registers new /api/arktype-tool-wire route in TanStack Router with route imports, type mappings, and module augmentations across FileRoutesByFullPath, FileRoutesByTo, FileRoutesById, and FileRouteTypes unions.
E2E endpoint handler and integration test
testing/e2e/src/routes/api.arktype-tool-wire.ts, testing/e2e/tests/arktype-tool-wire.spec.ts
Implements /api/arktype-tool-wire POST handler that initializes an OpenRouter adapter and executes chat with an ArkType-based tool to verify schema serialization; includes e2e test asserting the tool parameters are properly converted to JSON Schema in the provider request.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Callables and objects hop together, no more strife,
Type guards now see both forms in Standard life,
ArkType schemas dance through chat with pride,
JSON schemas flow from end to end, unified!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing detection of callable ArkType schemas in convertSchemaToJsonSchema, with the issue reference.
Description check ✅ Passed The PR description comprehensively covers changes, root cause analysis, the fix approach, test coverage, and verification results, aligning well with the template structure.
Linked Issues check ✅ Passed Code changes fully address issue #276 by implementing detection of callable ArkType schemas, enabling proper JSON Schema conversion and validation that downstream providers can accept.
Out of Scope Changes check ✅ Passed All changes directly support fixing ArkType schema handling: core fix in schema-converter.ts, comprehensive unit and E2E tests, dependency additions, changeset, and generated route file for E2E testing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 fix/276-arktype-callable-schema

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

1 package(s) bumped directly, 18 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/ai 0.23.0 → 0.23.1 Changeset
@tanstack/ai-client 0.14.0 → 0.14.1 Dependent
@tanstack/ai-code-mode 0.1.23 → 0.1.24 Dependent
@tanstack/ai-code-mode-skills 0.1.23 → 0.1.24 Dependent
@tanstack/ai-devtools-core 0.4.1 → 0.4.2 Dependent
@tanstack/ai-event-client 0.4.1 → 0.4.2 Dependent
@tanstack/ai-fal 0.7.16 → 0.7.17 Dependent
@tanstack/ai-isolate-cloudflare 0.2.14 → 0.2.15 Dependent
@tanstack/ai-isolate-node 0.1.23 → 0.1.24 Dependent
@tanstack/ai-isolate-quickjs 0.1.23 → 0.1.24 Dependent
@tanstack/ai-preact 0.7.0 → 0.7.1 Dependent
@tanstack/ai-react 0.13.0 → 0.13.1 Dependent
@tanstack/ai-solid 0.11.0 → 0.11.1 Dependent
@tanstack/ai-svelte 0.11.0 → 0.11.1 Dependent
@tanstack/ai-vue 0.11.0 → 0.11.1 Dependent
@tanstack/ai-vue-ui 0.2.7 → 0.2.8 Dependent
@tanstack/preact-ai-devtools 0.1.44 → 0.1.45 Dependent
@tanstack/react-ai-devtools 0.2.44 → 0.2.45 Dependent
@tanstack/solid-ai-devtools 0.2.44 → 0.2.45 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
Addedarktype@​2.2.010010010085100

View full report

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 30, 2026

View your CI Pipeline Execution ↗ for commit 32b980d

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

☁️ Nx Cloud last updated this comment at 2026-05-30 17:46:02 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 30, 2026

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-anthropic

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

@tanstack/ai-client

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

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-skills

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

@tanstack/ai-devtools-core

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

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-groq

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

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-openrouter

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-utils

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

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/openai-base

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

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

commit: 32b980d

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
testing/e2e/src/routes/api.arktype-tool-wire.ts (1)

73-79: 💤 Low value

Consider using appropriate HTTP status code for errors.

The error handler returns status 200 even when an error occurs. While this might be intentional for the test endpoint to ensure the test framework can always read the response, it's unconventional and could mask failures in test infrastructure.

🤖 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 `@testing/e2e/src/routes/api.arktype-tool-wire.ts` around lines 73 - 79, The
error branch currently always returns HTTP 200; change it to return an
appropriate error status (e.g., use (error as any).status || 500) instead of 200
so failures surface correctly: update the Response creation that
JSON.stringifies { ok:false, error: ... } to set the status to the error's
status when available or 500 as a default, and preserve the JSON content-type
header.
🤖 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.

Inline comments:
In `@testing/e2e/src/routes/api.arktype-tool-wire.ts`:
- Line 3: The import is using the package root (createOpenRouterText from
'`@tanstack/ai-openrouter`') which prevents tree-shaking; update the import to use
the adapters subpath (import createOpenRouterText from
'`@tanstack/ai-openrouter/adapters`') so the provider adapter is
tree-shakeable—locate the createOpenRouterText import and replace the root
package import with the '/adapters' subpath.
- Line 51: The model argument to createOpenRouterText is being force-cast with
"as never", which bypasses its generic type OpenRouterTextModels; remove the "as
never" cast and pass 'openai/gpt-4o' directly (or, if necessary, cast to the
correct OpenRouterTextModels member) so the call to createOpenRouterText<TModel
extends OpenRouterTextModels>(model: TModel, ...) retains proper type safety.

---

Nitpick comments:
In `@testing/e2e/src/routes/api.arktype-tool-wire.ts`:
- Around line 73-79: The error branch currently always returns HTTP 200; change
it to return an appropriate error status (e.g., use (error as any).status ||
500) instead of 200 so failures surface correctly: update the Response creation
that JSON.stringifies { ok:false, error: ... } to set the status to the error's
status when available or 500 as a default, and preserve the JSON content-type
header.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c86cd97b-9806-4b7d-a80f-055ecc5b3901

📥 Commits

Reviewing files that changed from the base of the PR and between 548e113 and 32b980d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • .changeset/arktype-callable-schema.md
  • packages/ai/package.json
  • packages/ai/src/activities/chat/tools/schema-converter.ts
  • packages/ai/tests/standard-converter.test.ts
  • testing/e2e/package.json
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.arktype-tool-wire.ts
  • testing/e2e/tests/arktype-tool-wire.spec.ts

@@ -0,0 +1,89 @@
import { createFileRoute } from '@tanstack/react-router'
import { chat, createChatOptions, toolDefinition } from '@tanstack/ai'
import { createOpenRouterText } from '@tanstack/ai-openrouter'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Prefer /adapters subpath import for tree-shakeable provider adapters.

As per coding guidelines, provider adapters should be imported from the /adapters subpath rather than the root package for better tree-shaking.

♻️ Proposed fix
-import { createOpenRouterText } from '`@tanstack/ai-openrouter`'
+import { createOpenRouterText } from '`@tanstack/ai-openrouter/adapters`'

As per coding guidelines: "Import tree-shakeable provider adapters from /adapters subpath (e.g., @tanstack/ai-openai/adapters) rather than default imports"

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { createOpenRouterText } from '@tanstack/ai-openrouter'
import { createOpenRouterText } from '`@tanstack/ai-openrouter/adapters`'
🤖 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 `@testing/e2e/src/routes/api.arktype-tool-wire.ts` at line 3, The import is
using the package root (createOpenRouterText from '`@tanstack/ai-openrouter`')
which prevents tree-shaking; update the import to use the adapters subpath
(import createOpenRouterText from '`@tanstack/ai-openrouter/adapters`') so the
provider adapter is tree-shakeable—locate the createOpenRouterText import and
replace the root package import with the '/adapters' subpath.

}

const adapter = createOpenRouterText(
'openai/gpt-4o' as never,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the type signature of createOpenRouterText to determine the correct model type
ast-grep --pattern 'function createOpenRouterText($$$) {
  $$$
}'

# Also search for type definitions
rg -nP --type=ts 'createOpenRouterText.*\(' -A 5

Repository: TanStack/ai

Length of output: 5883


Avoid as never on the createOpenRouterText model argument

createOpenRouterText is generic over OpenRouterTextModels (createOpenRouterText<TModel extends OpenRouterTextModels>(model: TModel, apiKey: string, ...)), so as never bypasses the intended type safety. Remove the cast and pass 'openai/gpt-4o' directly (or cast to the correct OpenRouterTextModels member instead).

🤖 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 `@testing/e2e/src/routes/api.arktype-tool-wire.ts` at line 51, The model
argument to createOpenRouterText is being force-cast with "as never", which
bypasses its generic type OpenRouterTextModels; remove the "as never" cast and
pass 'openai/gpt-4o' directly (or, if necessary, cast to the correct
OpenRouterTextModels member) so the call to createOpenRouterText<TModel extends
OpenRouterTextModels>(model: TModel, ...) retains proper type safety.

@AlemTuzlak AlemTuzlak merged commit 94bb9c0 into main May 30, 2026
10 checks passed
@AlemTuzlak AlemTuzlak deleted the fix/276-arktype-callable-schema branch May 30, 2026 18:06
@github-actions github-actions Bot mentioned this pull request May 30, 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.

convertSchemaToJsonSchema works incorrectly for arktype

1 participant