Skip to content

fix(tools): coerce limit/offset in ai-sdk schemas#1202

Open
haroon0x wants to merge 1 commit into
supermemoryai:mainfrom
haroon0x:fix/ai-sdk-coerce-number
Open

fix(tools): coerce limit/offset in ai-sdk schemas#1202
haroon0x wants to merge 1 commit into
supermemoryai:mainfrom
haroon0x:fix/ai-sdk-coerce-number

Conversation

@haroon0x

@haroon0x haroon0x commented Jul 5, 2026

Copy link
Copy Markdown

Fixes #1191
searchMemories and documentList tool calls fail with
tool call validation failed: [/limit: expected number, but got string]
when the LLM emits limit/offset as JSON strings (e.g. "10").

Changes all five numeric param schemas in packages/tools/src/ai-sdk.ts
from z.number() to z.coerce.number():

  • searchMemories.limit (strict + non-strict)
  • documentList.limit (strict + non-strict)
  • documentList.offset

Behavior

  • "10"10; real numbers pass through unchanged
  • undefined still triggers .default() / stays undefined for optional offset
  • Non-numeric strings ("abc") are still rejected (NaN fails validation)
  • The JSON schema advertised to the LLM is unchanged — both zod's
    toJSONSchema and the AI SDK's schema conversion still emit
    "type": "number" with defaults intact, so the model-facing contract
    is identical

Known z.coerce trade-off: ""/null coerce to 0 instead of erroring.
This is standard zod semantics and documentList already guards with
limit || DEFAULT_VALUES.limit.

Testing

  • Built the package and parsed string inputs through the built tools'
    actual inputSchema via the AI SDK validation path — coerces correctly
    in both strict and non-strict modes
  • Package vitest suite: identical results to main (52 passed; the 2
    failing files fail on main too — one needs SUPERMEMORY_API_KEY,
    one has a pre-existing broken import)
  • tsc --noEmit error count identical to main; biome clean

Note: the OpenAI tools codepath (src/openai/tools.ts) has a related
gap (hand-written JSON schema, no coercion) — intentionally out of
scope; will be filed as a separate issue.


Session Details

  • Session: View Session
  • Requested by: Unknown
  • Address comments on this PR. Add (aside) to your comment to have me ignore it.

@vorflux vorflux Bot left a comment

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.

Summary

Reviewed — found 1 issue(s). This change updates the AI SDK tool schemas in packages/tools/src/ai-sdk.ts to coerce limit and offset inputs to numbers. The review focused on validation behavior and found that z.coerce.number() broadens accepted inputs beyond numeric strings.

Findings

packages/tools/src/ai-sdk.ts

  1. z.coerce.number() accepts null and empty strings as 0, so optional limit inputs can bypass defaults or validation and produce empty/invalid searches.

Verdict

⚠️ Changes requested. Please narrow the coercion so only intended numeric values or numeric strings are accepted, while null and empty strings are either treated as omitted before defaults apply or rejected.


Review with Vorflux

.describe(PARAMETER_DESCRIPTIONS.includeFullDocs),
limit: strict
? z
? z.coerce

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.

z.coerce.number() applies Number(...) before .default(), so inputs like null or "" parse as 0 instead of using DEFAULT_VALUES.limit or being rejected. AI tool calls often emit optional fields as null; here that 0 can be passed to client.search.execute, causing empty or invalid searches. Consider preprocessing only numeric strings while mapping null/empty strings to undefined before the default applies, or rejecting them explicitly.

@vorflux

vorflux Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Testing

Targeted schema/runtime validation and package-level regression checks were run by the testing subagent. Numeric-string coercion works for searchMemoriesTool.limit, documentListTool.limit, and documentListTool.offset, but an edge case was found where z.coerce.number() also accepts null and empty strings as 0.

Commands run:

bun /tmp/pr-1202-ai-sdk-coerce-check.ts
bunx vitest run src/tools.test.ts -t 'new tool operations'
bunx vitest run src/tools.test.ts -t 'client initialization'
bun run build

Result:

bun /tmp/pr-1202-ai-sdk-coerce-check.ts: PASS
- searchMemoriesTool coerces limit: "7" to 7 in strict and non-strict modes.
- documentListTool coerces limit: "12" and offset: "3" to numbers in strict and non-strict modes.
- Defaults still apply when omitted.
- "not-a-number" is rejected.

bunx vitest run src/tools.test.ts -t 'new tool operations': PASS — 5 passed.
bunx vitest run src/tools.test.ts -t 'client initialization': PASS — 3 passed.
bun run build: PASS — package built successfully.

Adversarial probe:
search limit empty string -> { limit: 0 }
search limit null -> { limit: 0 }
document limit empty string -> { limit: 0 }
document offset empty string -> { offset: 0 }
document offset null -> { offset: 0 }

Verdict

⚠️ Issues found. The intended numeric-string coercion passes, but the schema now also treats null and empty-string values as 0, which likely broadens validation beyond the intended behavior.

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.

fix(ai-sdk): use z.coerce.number() for limit/offset params to handle string inputs from LLMs

1 participant