fix(ai): enforce callOptionsSchema at runtime in ToolLoopAgent#14750
Merged
Conversation
4 tasks
lgrammel
reviewed
Apr 27, 2026
Comment on lines
+88
to
+91
| // Validate caller-supplied `options` against `callOptionsSchema` if one | ||
| // was provided. Without this, the schema was effectively dead code: | ||
| // developers wiring untrusted input into `options` would get no runtime | ||
| // protection despite naming the field `callOptionsSchema`. |
lgrammel
reviewed
Apr 27, 2026
Comment on lines
+96
to
+107
| const result = await safeValidateTypes({ | ||
| value: options.options, | ||
| schema: this.settings.callOptionsSchema, | ||
| }); | ||
| if (!result.success) { | ||
| throw new InvalidArgumentError({ | ||
| parameter: 'options', | ||
| value: options.options, | ||
| message: `options failed callOptionsSchema validation: ${result.error.message}`, | ||
| }); | ||
| } | ||
| options = { ...options, options: result.value }; |
Collaborator
There was a problem hiding this comment.
can just call validateTypes and rely on the original parse error. validateTypes supports additional context
etairl
added a commit
to etairl/ai
that referenced
this pull request
Apr 27, 2026
Per review on vercel#14750: drop the explanatory comment block and replace the safeValidateTypes + manual InvalidArgumentError throw with a single validateTypes call. validateTypes already throws TypeValidationError on failure and accepts a context for the field path, which provides the same caller-facing identification without bespoke error wiring. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
lgrammel
approved these changes
Apr 27, 2026
Collaborator
|
@etairl we have changed our security policy to allow only commits with verified signatures. I cannot merge as is. Would it be possible for you to enable commit signing and open a new PR with signed commits? alternatively I can recreate this |
`ToolLoopAgentSettings.callOptionsSchema` was declared and documented as a runtime schema for `options`, but `tool-loop-agent.ts` never invoked it. Any invariant a developer encoded in the schema was silently bypassed at runtime, and unchecked `options` flowed straight into `prepareCall` and any `instructions` template that interpolated them. `ToolLoopAgent.prepareCall` now validates caller-supplied `options` against `callOptionsSchema` (when set) via `safeValidateTypes`, throwing `InvalidArgumentError` on failure before forwarding to `prepareCall` / `generateText` / `streamText`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per review on vercel#14750: drop the explanatory comment block and replace the safeValidateTypes + manual InvalidArgumentError throw with a single validateTypes call. validateTypes already throws TypeValidationError on failure and accepts a context for the field path, which provides the same caller-facing identification without bespoke error wiring. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
d0c5669 to
eeba282
Compare
Contributor
Author
Rebased with signed commits. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
ToolLoopAgentSettings.callOptionsSchemais declared and documented as a runtime schema for caller-suppliedoptions, butToolLoopAgent.prepareCallnever invokes it. Any invariant a developer encodes in that schema is silently bypassed at runtime, and uncheckedoptionsflow straight intoprepareCalland anyinstructionstemplate that interpolates them — defeating both the validation guarantee and any input-shape assumptions downstream code makes.Splitting this out per maintainer feedback on #14749 that
callOptionsSchemaenforcement should be its own dedicated PR.Summary
packages/ai/src/agent/tool-loop-agent.ts: at the top ofprepareCall, whencallOptionsSchemais set and the caller passedoptions, validate viasafeValidateTypes. On failure, throwInvalidArgumentErrorwith the schema's error message. On success, swap the caller-suppliedoptionsfor the validated (parsed) value so any schema transforms or defaults take effect for the rest of the call.packages/ai/src/agent/tool-loop-agent.test.ts: regression tests covering the rejection path (out-of-enum value rejected before reaching the model) and the accept path (in-enum value passes through and the model is invoked normally).The check is gated on
options !== undefined, so existing callers that don't supplyoptionsare unaffected. Only agents that opted intocallOptionsSchemasee new behaviour — and that behaviour is exactly what the field name and docs already promised.Manual Verification
pnpm --filter ai test:node -- src/agent/tool-loop-agent.test.ts— passes, including the two newcallOptionsSchematests.Checklist
pnpm changesetin the project root)Documentation is unchanged: the existing
callOptionsSchemaJSDoc already describes the intended behaviour; this PR makes the runtime match the docs.Future Work
None — this PR makes
callOptionsSchemabehave as documented.Related Issues