Migration Guide: Breaking Changes
This document tracks breaking changes shipped in specific NeuroLink
releases — changes to public SDK/CLI surface that require an update on the
consumer side, as opposed to the additive/backward-compatible changes covered
by docs/guides/migration-guide.md and the
auto-generated changelog (see the
GitHub releases page).
Three breaking changes shipped across 9.94.x–9.95.x. Each is intentional and stays as shipped — this document exists so downstream consumers know what changed and how to adapt.
Policy
Per this repository's CLAUDE.md (Critical Rule 5), the public SDK API must
not break existing callers. The three breaking changes below shipped in
9.94.x–9.95.x without an accompanying migration path and are documented here
retroactively. Going forward, any breaking change must ship its migration
path in the same release that introduces it — not documented after the fact.
1. Multimodal file/CSV processing is now fail-loud (v9.94.6)
What changed: generate()/stream() calls that attach files via
input.files (auto-detected) or input.csvFiles (explicit CSV) now throw
on the first file that fails to process, instead of logging a warning and
silently continuing with the files that did succeed.
processUnifiedFilesArray()(src/lib/utils/messageBuilder.ts) throwsErrorFactory.fileProcessingFailed(filename, originalError)on any file ininput.filesthat fails detection/processing.processExplicitCsvFiles()(same file) throwsErrorFactory.csvProcessingFailed(filename, originalError)on any file ininput.csvFilesthat fails to parse.
Both errors are typed NeuroLinkErrors (ERROR_CODES.FILE_PROCESSING_FAILED
/ ERROR_CODES.CSV_PROCESSING_FAILED, see src/lib/utils/errorHandling.ts)
that carry the original error as .originalError and the failing filename in
.context.filename.
Why: the previous log-and-skip behavior silently produced a partial prompt — e.g. attaching 5 files and having one silently drop meant the model answered as if that file never existed, with no signal to the caller or the end user that anything was missing. A model call that appears to succeed but is actually missing part of its input is worse than a call that fails loudly.
Before (9.94.5 and earlier):
// One corrupt/unreadable file among several silently dropped; the request
// still succeeded using only the files that processed cleanly.
const result = await neurolink.generate({
input: { text: "Summarize these", files: ["a.pdf", "corrupt.pdf", "b.pdf"] },
});
// result.content summarizes only a.pdf and b.pdf — no error, no warning
// visible to the caller beyond a log line.
After (9.94.6+):
try {
const result = await neurolink.generate({
input: {
text: "Summarize these",
files: ["a.pdf", "corrupt.pdf", "b.pdf"],
},
});
} catch (error) {
if (
error instanceof NeuroLinkError &&
error.code === "FILE_PROCESSING_FAILED"
) {
// error.context.filename === "corrupt.pdf"
// error.originalError is the underlying cause
}
throw error;
}
How to adapt:
- Wrap multi-file
generate()/stream()calls intry/catchif you were previously relying on partial success. - If you want the old best-effort behavior, pre-validate/pre-filter files
yourself before passing them in
input.files/input.csvFiles, or catch the typed error, drop the failing filename (error.context.filename), and retry without it. - CSV-specific failures (
input.csvFiles) and generic file failures (input.files) now throw different error codes — branch onerror.codeif you need to distinguish them.
2. MessageContent dropped its open index signature (v9.94.6)
What changed: the exported MessageContent type
(src/lib/types/multimodal.ts, re-exported via src/lib/types/index.ts and
the package root) no longer has a [key: string]: unknown index signature.
It's now a closed shape with the concrete fields the pipeline actually reads:
type, text, image, mimeType, data, name, filename,
toolCallId, toolName, args, result, isError, providerOptions.
Why: the open index signature let any typo or unrelated key through
type-checking unchecked (e.g. { type: "text", txt: "..." } compiled fine
and silently produced empty content). Closing it catches that class of bug at
compile time for everyone building MessageContent[] arrays directly.
Before:
import type { MessageContent } from "@juspay/neurolink";
// Compiled fine even though "customField" isn't a real MessageContent key —
// the typo/extra field was silently accepted.
const item: MessageContent = {
type: "text",
text: "hello",
customField: "some app-specific metadata",
};
After:
import type { MessageContent } from "@juspay/neurolink";
// TS2353: Object literal may only specify known properties, and
// 'customField' does not exist in type 'MessageContent'.
const item: MessageContent = {
type: "text",
text: "hello",
customField: "some app-specific metadata", // ❌ no longer type-checks
};
How to adapt:
- If you were attaching arbitrary metadata to a
MessageContentitem, use the dedicatedproviderOptions?: Record<string, unknown>field instead — it's read byMessageBuilderwhen converting toModelMessage[]and is the intended extension point. - If you genuinely need a field NeuroLink doesn't model, open an issue —
MessageContentintentionally stays a closed, hand-maintained shape rather than reopening a blanket index signature (see the#325comment on the type itself). - Runtime behavior is unchanged — this is a compile-time-only break. Plain
JavaScript callers, or TypeScript callers that never annotated a literal
as
MessageContentexplicitly, are unaffected.
3. BatchCommandArgs.file renamed to .promptsFile (v9.95.1)
What changed: the public CLI argument type BatchCommandArgs
(src/lib/types/cli.ts) renamed its file?: string property to
promptsFile?: string.
Why: yargs implicitly registers a positional argument's key as a
recognized flag name too. Keeping the property named file collided with
the unrelated common --file auto-detect flag that the batch command
intentionally does not register — --file on batch would silently pass
strictOptions() without actually doing anything. Renaming the positional's
backing property to promptsFile removes that collision.
Before:
import type { BatchCommandArgs } from "@juspay/neurolink";
function handleBatch(argv: BatchCommandArgs) {
const promptsListPath = argv.file; // ❌ no longer exists
}
After:
import type { BatchCommandArgs } from "@juspay/neurolink";
function handleBatch(argv: BatchCommandArgs) {
const promptsListPath = argv.promptsFile; // ✅
}
How to adapt:
- Update any code that constructs or reads a
BatchCommandArgsobject directly (custom CLI wrappers, tests) to use.promptsFileinstead of.file. - The CLI's own
<promptsFile>positional argument and its behavior are unchanged — this is a type-only rename of the property NeuroLink's ownbatchcommand handler reads internally; end users invokingneurolink batch <promptsFile>on the command line see no difference.