Repository navigation
Node.js SDK rejects empty first-page pagination cursors #44264
Description
Self Checks
- I have read the contributing guide and language policy.
- I searched existing issues and pull requests for Node.js pagination and empty cursors.
- This report is in English and includes a local reproduction.
Dify version
Node.js SDK dify-client 3.1.0 from main 8857292ad7b66d558493c3179a2049fd35ddbf9e, Node.js 25.9.0.
Cloud or Self Hosted
Self Hosted (Source). The SDK reproduction uses a loopback HTTP fixture, without a deployed Dify server or model calls.
Steps to reproduce
First-page pagination state is commonly initialized to an empty string. Dify's Service API accepts empty cursor strings: MessageListQuery.first_id, ConversationListQuery.last_id, and ConversationVariablesQuery.last_id all use UUIDStrOrEmpty | None; their handlers normalize an empty cursor to None. The Node.js SDK rejects this value before sending the request.
Build sdks/nodejs-client with vp pack, save the following as repro.mjs in that directory, and run node repro.mjs:
import assert from 'node:assert/strict'
import { createServer } from 'node:http'
import { ChatClient } from './dist/index.js'
const urls = []
const server = createServer((request, response) => {
urls.push(request.url)
response.writeHead(200, { 'content-type': 'application/json' })
response.end('{"data":[]}')
})
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
try {
const client = new ChatClient({ apiKey: 'fixture', baseUrl: `http://127.0.0.1:${server.address().port}`, maxRetries: 0 })
const calls = [
['conversations', () => client.getConversations('fixture', '', 10)],
['messages', () => client.getConversationMessages('fixture', '00000000-0000-0000-0000-000000000001', '', 10)],
['variables', () => client.getConversationVariables('00000000-0000-0000-0000-000000000001', 'fixture', '', 10)],
]
const results = []
for (const [name, call] of calls) {
try { await call(); results.push({ name, result: 'sent' }) }
catch (error) { results.push({ name, result: error.name, message: error.message }) }
}
console.log(JSON.stringify({ results, urls }))
assert.equal(urls.length, 3)
for (const raw of urls) {
const url = new URL(raw, 'http://localhost')
assert.equal(url.searchParams.has('first_id'), false)
assert.equal(url.searchParams.has('last_id'), false)
assert.equal(url.searchParams.get('limit'), '10')
}
} finally {
server.closeAllConnections()
await new Promise(resolve => server.close(resolve))
}Expected Behavior
All three calls request their first page, omitting an empty cursor just as when the argument is omitted or null. Existing non-empty cursors and request limits remain unchanged.
Actual Behavior
All three calls throw ValidationError (lastId/firstId must be a non-empty string when set), and the HTTP fixture receives zero requests.
The method-level ensureOptionalString rejects '' even though the endpoint schemas explicitly support it. This is distinct from #44097 (keyword filters), #44263 (SSE framing), and #44199 (streamed errors).
Verification boundary
I verified the API contract in the current backend query schemas and handlers, and reproduced the SDK's outgoing requests with the local fixture. This is not a deployed-backend or database integration test. A focused fix and tests are prepared; the three empty-cursor regressions fail on unchanged SDK production code and pass with the fix. Whitespace-only cursor validation remains strict.
From Codex
Activity
dosubot commentedon Oct 17, 2026
One additional detail:
getConversationVariablesneeds empty-cursor omission as well as validation handling. It buildslast_idwithlastId ?? undefined, and the HTTP serializer preserves''. By contrast, conversations and messages already omit falsy cursors when building their queries. Simply allowing''through validation would therefore still sendlast_id=for variables. [1] [2] [3]As a caller-side workaround, normalize only the exact empty string to
undefinedbefore passing any of the three cursor arguments:That uses the existing omitted-cursor path without changing non-empty values or masking whitespace-only validation errors. All three methods accept
undefined, and the shared validator skips it while rejecting whitespace-only strings. [1] [2] [4]To reply, just mention @dosu.
Docs are dead. Just use Dosu.