deepagents 1.14.1 → 1.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,14 +21,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  }) : target, mod));
22
22
  //#endregion
23
23
  let langchain = require("langchain");
24
+ let _langchain_core_errors = require("@langchain/core/errors");
24
25
  let _langchain_langgraph = require("@langchain/langgraph");
25
26
  let zod_v4 = require("zod/v4");
26
27
  let micromatch = require("micromatch");
27
28
  micromatch = __toESM(micromatch, 1);
29
+ let node_crypto = require("node:crypto");
28
30
  let _langchain_core_messages = require("@langchain/core/messages");
29
31
  let zod = require("zod");
30
32
  zod = __toESM(zod, 1);
31
- let _langchain_core_errors = require("@langchain/core/errors");
32
33
  let langchain_chat_models_universal = require("langchain/chat_models/universal");
33
34
  let yaml = require("yaml");
34
35
  yaml = __toESM(yaml, 1);
@@ -657,9 +658,18 @@ function adaptBackendProtocol(backend) {
657
658
  return result;
658
659
  }
659
660
  };
660
- const routePrefixes = backend.routePrefixes;
661
- if (Array.isArray(routePrefixes)) Object.defineProperty(adapted, "routePrefixes", {
662
- value: routePrefixes,
661
+ const identityMarkers = [["routePrefixes", Array.isArray], ["backendKind", (value) => typeof value === "string"]];
662
+ for (const [key, isValid] of identityMarkers) {
663
+ const value = backend[key];
664
+ if (isValid(value)) Object.defineProperty(adapted, key, {
665
+ value,
666
+ enumerable: true,
667
+ configurable: true
668
+ });
669
+ }
670
+ const resolveBackendForPath = backend.resolveBackendForPath;
671
+ if (typeof resolveBackendForPath === "function") Object.defineProperty(adapted, "resolveBackendForPath", {
672
+ value: (path) => resolveBackendForPath.call(backend, path),
663
673
  enumerable: true,
664
674
  configurable: true
665
675
  });
@@ -810,8 +820,14 @@ const PREGEL_READ_KEY = "__pregel_read";
810
820
  * for the middleware to apply via Command.
811
821
  */
812
822
  var StateBackend = class {
823
+ /** Class identity discriminant for {@link StateBackend.isInstance}. */
824
+ backendKind = "state";
813
825
  runtime;
814
826
  fileFormat;
827
+ /** Duck-typed so this survives the plain-object copies `adaptBackendProtocol` makes — a class check wouldn't. */
828
+ static isInstance(backend) {
829
+ return typeof backend === "object" && backend !== null && backend.backendKind === "state";
830
+ }
815
831
  constructor(runtimeOrOptions, options) {
816
832
  if (runtimeOrOptions != null && typeof runtimeOrOptions === "object" && "state" in runtimeOrOptions) {
817
833
  this.runtime = runtimeOrOptions;
@@ -1111,6 +1127,466 @@ var StateBackend = class {
1111
1127
  }
1112
1128
  };
1113
1129
  //#endregion
1130
+ //#region src/middleware/blobOffload.ts
1131
+ /**
1132
+ * Content-addressed offload of binary `read_file` and inline `HumanMessage`
1133
+ * blocks to the backend.
1134
+ *
1135
+ * Binary blocks are written to `/blobs/<sha256>` and replaced in state with a
1136
+ * `deepagents_blob` reference. Model requests are rehydrated from the backend
1137
+ * (or a per-run cache), so checkpoints never carry the base64 payload.
1138
+ */
1139
+ /** Content block key holding the SHA-256 digest of an offloaded payload. */
1140
+ const BLOB_REF_KEY = "deepagents_blob";
1141
+ const MISSING_BLOB_TEXT = "[Binary content from an earlier read_file call is no longer available. Re-read the file if you still need it.]";
1142
+ const DIGEST_RE = /^[0-9a-f]{64}$/;
1143
+ const BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
1144
+ /** Whether `reference` is a well-formed digest string. */
1145
+ function isDigest(reference) {
1146
+ return typeof reference === "string" && DIGEST_RE.test(reference);
1147
+ }
1148
+ function isValidBase64(value) {
1149
+ return value.length > 0 && value.length % 4 === 0 && BASE64_RE.test(value);
1150
+ }
1151
+ function sha256Hex(bytes) {
1152
+ return (0, node_crypto.createHash)("sha256").update(bytes).digest("hex");
1153
+ }
1154
+ const DEFAULT_BLOB_CACHE_BYTES = 268435456;
1155
+ /**
1156
+ * Content-addressed cache of base64 payloads, bounded by total payload size
1157
+ * (LRU-evicted past the bound). Safe to share across threads — every key is
1158
+ * a SHA-256 digest of its own value, so a hit is only reachable by a caller
1159
+ * who already has that exact reference, and an eviction just forces a
1160
+ * re-fetch from the backend, never a wrong result.
1161
+ */
1162
+ var BlobCache = class extends Map {
1163
+ maxBytes;
1164
+ totalBytes = 0;
1165
+ constructor(maxBytes = DEFAULT_BLOB_CACHE_BYTES) {
1166
+ super();
1167
+ this.maxBytes = maxBytes;
1168
+ }
1169
+ /** Returns the cached payload for `digest`, marking it most recently used. */
1170
+ get(digest) {
1171
+ const payload = super.get(digest);
1172
+ if (payload === void 0) return void 0;
1173
+ super.delete(digest);
1174
+ super.set(digest, payload);
1175
+ return payload;
1176
+ }
1177
+ /** Caches `payload`, evicting least-recently-used entries past the size bound. */
1178
+ set(digest, payload) {
1179
+ if (payload.length > this.maxBytes) return this;
1180
+ const previous = super.get(digest);
1181
+ if (previous !== void 0) {
1182
+ this.totalBytes -= previous.length;
1183
+ super.delete(digest);
1184
+ }
1185
+ super.set(digest, payload);
1186
+ this.totalBytes += payload.length;
1187
+ while (this.totalBytes > this.maxBytes) {
1188
+ const oldestDigest = this.keys().next().value;
1189
+ if (oldestDigest === void 0) break;
1190
+ const oldest = super.get(oldestDigest);
1191
+ super.delete(oldestDigest);
1192
+ this.totalBytes -= oldest?.length ?? 0;
1193
+ }
1194
+ return this;
1195
+ }
1196
+ };
1197
+ function blobPath(prefix, digest) {
1198
+ return `${prefix}/${digest}`;
1199
+ }
1200
+ function contentBlocks(message) {
1201
+ const content = message?.content;
1202
+ return Array.isArray(content) ? content : null;
1203
+ }
1204
+ /** Map each inline base64 payload found in `messages` to its digest and decoded bytes. */
1205
+ function pendingBlobs(messages) {
1206
+ const pending = /* @__PURE__ */ new Map();
1207
+ for (const message of messages) {
1208
+ const blocks = contentBlocks(message);
1209
+ if (!blocks) continue;
1210
+ for (const block of blocks) {
1211
+ if (block == null || typeof block !== "object") continue;
1212
+ const payload = block.data;
1213
+ if (typeof payload !== "string" || pending.has(payload) || !isValidBase64(payload)) continue;
1214
+ let raw;
1215
+ try {
1216
+ raw = new Uint8Array(Buffer.from(payload, "base64"));
1217
+ } catch {
1218
+ continue;
1219
+ }
1220
+ pending.set(payload, {
1221
+ digest: sha256Hex(raw),
1222
+ raw
1223
+ });
1224
+ }
1225
+ }
1226
+ return pending;
1227
+ }
1228
+ /** Replace payloads found in `digests` with blob references; returns `messages` unchanged if nothing matched. */
1229
+ function stubMessages(messages, digests) {
1230
+ return messages.map((message) => {
1231
+ const blocks = contentBlocks(message);
1232
+ if (!blocks) return message;
1233
+ let changed = false;
1234
+ const newContent = blocks.map((block) => {
1235
+ const digest = typeof block.data === "string" ? digests.get(block.data) : void 0;
1236
+ if (digest === void 0) return block;
1237
+ changed = true;
1238
+ const { data: _data, ...rest } = block;
1239
+ return {
1240
+ ...rest,
1241
+ [BLOB_REF_KEY]: digest
1242
+ };
1243
+ });
1244
+ if (!changed) return message;
1245
+ return withContent(message, newContent);
1246
+ });
1247
+ }
1248
+ /**
1249
+ * Upload inline binary payloads found in `messages` and return messages carrying blob
1250
+ * references instead. Payloads that fail to upload stay inline — offload is best-effort.
1251
+ */
1252
+ async function offloadMessages(messages, backend, prefix, cache) {
1253
+ const pending = pendingBlobs(messages);
1254
+ if (pending.size === 0) return [...messages];
1255
+ if (typeof backend.uploadFiles !== "function") {
1256
+ console.warn("Backend does not support uploadFiles; binary read_file content stays inline");
1257
+ return [...messages];
1258
+ }
1259
+ const entries = [...pending.entries()];
1260
+ let responses;
1261
+ try {
1262
+ responses = await backend.uploadFiles(entries.map(([, { digest, raw }]) => [blobPath(prefix, digest), raw]));
1263
+ } catch (error) {
1264
+ console.warn("Failed to offload binary read_file content; keeping it inline", error);
1265
+ return [...messages];
1266
+ }
1267
+ const digests = /* @__PURE__ */ new Map();
1268
+ entries.forEach(([payload, { digest }], index) => {
1269
+ if (responses[index]?.error == null) {
1270
+ digests.set(payload, digest);
1271
+ cache.set(digest, payload);
1272
+ }
1273
+ });
1274
+ return stubMessages(messages, digests);
1275
+ }
1276
+ /** Apply `offloadMessages` to a tool result (a `ToolMessage`, or a `Command`-shaped update). */
1277
+ async function offloadToolResult(result, backend, prefix, cache) {
1278
+ if (langchain.ToolMessage.isInstance(result)) return (await offloadMessages([result], backend, prefix, cache))[0];
1279
+ const update = result?.update;
1280
+ if (update && Array.isArray(update.messages)) {
1281
+ const messages = await offloadMessages(update.messages, backend, prefix, cache);
1282
+ return new _langchain_langgraph.Command({
1283
+ ...result,
1284
+ update: {
1285
+ ...update,
1286
+ messages
1287
+ }
1288
+ });
1289
+ }
1290
+ return result;
1291
+ }
1292
+ /** Return `HumanMessage`s with ids after the last `AIMessage`, preferring a version already queued in `pending`. */
1293
+ function humanCandidates({ stateMessages, pending }) {
1294
+ let lastAiIndex = -1;
1295
+ stateMessages.forEach((message, index) => {
1296
+ if (langchain.AIMessage.isInstance(message)) lastAiIndex = index;
1297
+ });
1298
+ const queued = /* @__PURE__ */ new Map();
1299
+ for (const message of pending) {
1300
+ const id = message?.id;
1301
+ if (typeof id === "string") queued.set(id, message);
1302
+ }
1303
+ const candidates = [];
1304
+ for (const message of stateMessages.slice(lastAiIndex + 1)) {
1305
+ if (!langchain.HumanMessage.isInstance(message) || typeof message.id !== "string") continue;
1306
+ candidates.push(queued.get(message.id) ?? message);
1307
+ }
1308
+ return candidates;
1309
+ }
1310
+ function mergeReplacements({ pending, offloaded, candidates }) {
1311
+ const replaced = offloaded.filter((next, index) => next !== candidates[index]);
1312
+ const replacedIds = new Set(replaced.map((message) => message?.id));
1313
+ return [...pending.filter((message) => !replacedIds.has(message?.id)), ...replaced];
1314
+ }
1315
+ /** Return `pending` plus stubbed replacements for `HumanMessage`s in state carrying inline media. */
1316
+ async function offloadHumanMessages({ stateMessages, pending }, backend, prefix, cache) {
1317
+ const candidates = humanCandidates({
1318
+ stateMessages,
1319
+ pending
1320
+ });
1321
+ if (candidates.length === 0) return [...pending];
1322
+ return mergeReplacements({
1323
+ pending,
1324
+ offloaded: await offloadMessages(candidates, backend, prefix, cache),
1325
+ candidates
1326
+ });
1327
+ }
1328
+ /** Collect every well-formed `deepagents_blob` digest referenced in `messages`, deduplicated. */
1329
+ function referencedDigests(messages) {
1330
+ const digests = /* @__PURE__ */ new Set();
1331
+ for (const message of messages) {
1332
+ const blocks = contentBlocks(message);
1333
+ if (!blocks) continue;
1334
+ for (const block of blocks) {
1335
+ const ref = block?.[BLOB_REF_KEY];
1336
+ if (isDigest(ref)) digests.add(ref);
1337
+ }
1338
+ }
1339
+ return [...digests];
1340
+ }
1341
+ function cachedPayloads(digests, cache) {
1342
+ const payloads = /* @__PURE__ */ new Map();
1343
+ const missing = [];
1344
+ for (const digest of digests) {
1345
+ const payload = cache.get(digest);
1346
+ if (payload !== void 0) payloads.set(digest, payload);
1347
+ else missing.push(digest);
1348
+ }
1349
+ return {
1350
+ payloads,
1351
+ missing
1352
+ };
1353
+ }
1354
+ /** Verify each downloaded blob's integrity before trusting it — blobs live on an agent-writable backend. */
1355
+ function acceptDownloads(missing, responses, payloads, cache) {
1356
+ missing.forEach((digest, index) => {
1357
+ const response = responses[index];
1358
+ if (!response || response.error != null || response.content == null) return;
1359
+ if (sha256Hex(response.content) !== digest) return;
1360
+ const payload = Buffer.from(response.content).toString("base64");
1361
+ cache.set(digest, payload);
1362
+ payloads.set(digest, payload);
1363
+ });
1364
+ }
1365
+ function messageHasRefs(message) {
1366
+ const blocks = contentBlocks(message);
1367
+ return blocks != null && blocks.some((block) => block != null && typeof block === "object" && "deepagents_blob" in block);
1368
+ }
1369
+ function withContent(message, content) {
1370
+ if (langchain.ToolMessage.isInstance(message)) return new langchain.ToolMessage({
1371
+ content,
1372
+ tool_call_id: message.tool_call_id,
1373
+ name: message.name,
1374
+ id: message.id,
1375
+ artifact: message.artifact,
1376
+ status: message.status,
1377
+ additional_kwargs: message.additional_kwargs,
1378
+ response_metadata: message.response_metadata,
1379
+ metadata: message.metadata
1380
+ });
1381
+ if (langchain.HumanMessage.isInstance(message)) return new langchain.HumanMessage({
1382
+ content,
1383
+ id: message.id,
1384
+ additional_kwargs: message.additional_kwargs,
1385
+ response_metadata: message.response_metadata
1386
+ });
1387
+ if (langchain.AIMessage.isInstance(message)) return new langchain.AIMessage({
1388
+ content,
1389
+ id: message.id,
1390
+ tool_calls: message.tool_calls,
1391
+ invalid_tool_calls: message.invalid_tool_calls,
1392
+ usage_metadata: message.usage_metadata,
1393
+ additional_kwargs: message.additional_kwargs,
1394
+ response_metadata: message.response_metadata
1395
+ });
1396
+ if (langchain.SystemMessage.isInstance(message)) return new langchain.SystemMessage({
1397
+ content,
1398
+ id: message.id,
1399
+ additional_kwargs: message.additional_kwargs,
1400
+ response_metadata: message.response_metadata
1401
+ });
1402
+ console.warn(`blobOffload: left a blob reference unresolved on an unsupported message type (${message?.constructor?.name})`);
1403
+ return message;
1404
+ }
1405
+ /** Restore base64 payloads for blob references; a reference with no matching payload becomes a text notice. */
1406
+ function restorePayloads(messages, payloads) {
1407
+ const hydrate = (block) => {
1408
+ const ref = block[BLOB_REF_KEY];
1409
+ if (ref === void 0) return block;
1410
+ const payload = isDigest(ref) ? payloads.get(ref) : void 0;
1411
+ if (payload === void 0) return {
1412
+ type: "text",
1413
+ text: MISSING_BLOB_TEXT
1414
+ };
1415
+ const { [BLOB_REF_KEY]: _ref, ...rest } = block;
1416
+ return {
1417
+ ...rest,
1418
+ data: payload
1419
+ };
1420
+ };
1421
+ return messages.map((message) => {
1422
+ if (!messageHasRefs(message)) return message;
1423
+ return withContent(message, (contentBlocks(message) ?? []).map(hydrate));
1424
+ });
1425
+ }
1426
+ /**
1427
+ * Restore base64 payloads for blob references ahead of a model call.
1428
+ *
1429
+ * Only the returned array is used for the request — callers must not persist
1430
+ * the result back into state, or the checkpoint-size benefit of offloading is
1431
+ * lost the next time state is saved.
1432
+ */
1433
+ async function hydrateMessages(messages, backend, prefix, cache) {
1434
+ if (!messages.some(messageHasRefs)) return [...messages];
1435
+ const { payloads, missing } = cachedPayloads(referencedDigests(messages), cache);
1436
+ if (missing.length > 0) if (typeof backend.downloadFiles !== "function") console.warn("Backend does not support downloadFiles; offloaded read_file content is unavailable");
1437
+ else {
1438
+ let responses = [];
1439
+ try {
1440
+ responses = await backend.downloadFiles(missing.map((digest) => blobPath(prefix, digest)));
1441
+ } catch (error) {
1442
+ console.warn("Failed to load offloaded read_file content", error);
1443
+ }
1444
+ acceptDownloads(missing, responses, payloads, cache);
1445
+ }
1446
+ return restorePayloads(messages, payloads);
1447
+ }
1448
+ //#endregion
1449
+ //#region src/middleware/unsupportedContent.ts
1450
+ const PDF_MIME_TYPE = "application/pdf";
1451
+ /** Content block types `read_file` may emit that require multimodal model support. */
1452
+ const MULTIMODAL_BLOCK_TYPES = /* @__PURE__ */ new Set([
1453
+ "image",
1454
+ "audio",
1455
+ "video",
1456
+ "file"
1457
+ ]);
1458
+ const PROFILE_FIELD_BY_BLOCK_TYPE = {
1459
+ image: "imageInputs",
1460
+ audio: "audioInputs",
1461
+ video: "videoInputs"
1462
+ };
1463
+ const TOOL_MESSAGE_FIELD_BY_BLOCK_TYPE = { image: "imageToolMessage" };
1464
+ function hasInlineData(block) {
1465
+ return block.data != null || block.source_type === "base64";
1466
+ }
1467
+ function blockMimeType(block) {
1468
+ const mimeType = block.mimeType ?? block.mime_type;
1469
+ return typeof mimeType === "string" ? mimeType : void 0;
1470
+ }
1471
+ /**
1472
+ * Unlike the other block types, a missing `fileMimeTypes` defaults to
1473
+ * unsupported rather than supported: generic file support varies far more
1474
+ * across models than image/audio/video, so there's no safe assumption to
1475
+ * fall back on absent real profile data.
1476
+ */
1477
+ function fileBlockSupported(block, profile, inToolMessage) {
1478
+ if (!hasInlineData(block)) return true;
1479
+ const mimeType = blockMimeType(block);
1480
+ if (mimeType === PDF_MIME_TYPE) {
1481
+ if (inToolMessage && profile.pdfToolMessage !== void 0) return profile.pdfToolMessage !== false;
1482
+ return profile.pdfInputs !== false;
1483
+ }
1484
+ if (mimeType == null) return false;
1485
+ const { fileMimeTypes } = profile;
1486
+ return Array.isArray(fileMimeTypes) && fileMimeTypes.includes(mimeType);
1487
+ }
1488
+ /**
1489
+ * Whether the model accepts a multimodal block.
1490
+ *
1491
+ * Missing profile fields default to supported, since profile coverage is
1492
+ * incomplete — except `file` blocks, where a missing `fileMimeTypes`
1493
+ * defaults to unsupported (see `fileBlockSupported`).
1494
+ */
1495
+ function multimodalBlockSupported(block, profile, inToolMessage) {
1496
+ if (block.type === "file") return fileBlockSupported(block, profile, inToolMessage);
1497
+ const field = PROFILE_FIELD_BY_BLOCK_TYPE[block.type];
1498
+ if (field == null) return true;
1499
+ if (inToolMessage) {
1500
+ const toolField = TOOL_MESSAGE_FIELD_BY_BLOCK_TYPE[block.type];
1501
+ if (toolField != null && profile[toolField] !== void 0) return profile[toolField] !== false;
1502
+ }
1503
+ return profile[field] !== false;
1504
+ }
1505
+ function placeholder(block, path) {
1506
+ return {
1507
+ type: "text",
1508
+ text: `[read_file: ${path ?? "the requested file"} was not attached because this model does not support ${block.type} content (${blockMimeType(block) ?? "unknown"}).]`
1509
+ };
1510
+ }
1511
+ function readFilePaths(messages) {
1512
+ const paths = /* @__PURE__ */ new Map();
1513
+ for (const message of messages) {
1514
+ if (!_langchain_core_messages.AIMessage.isInstance(message)) continue;
1515
+ for (const toolCall of message.tool_calls ?? []) {
1516
+ const path = toolCall.args?.file_path;
1517
+ if (toolCall.id != null && typeof path === "string") paths.set(toolCall.id, path);
1518
+ }
1519
+ }
1520
+ return paths;
1521
+ }
1522
+ /**
1523
+ * Without this, a request carrying content the model can't accept (e.g. a
1524
+ * `.zip` file sent to a model without file support) fails with a
1525
+ * non-retryable 400, and since the content stays in history, every later
1526
+ * turn fails the same way. Only the request is changed; messages in state
1527
+ * are left untouched, so switching to a model that accepts the content
1528
+ * sends it again.
1529
+ */
1530
+ function scrubUnsupportedMultimodalContent(messages, model) {
1531
+ const rawProfile = model?.profile;
1532
+ const profile = rawProfile != null && typeof rawProfile === "object" ? rawProfile : {};
1533
+ let paths;
1534
+ let changed = false;
1535
+ const result = messages.map((message) => {
1536
+ const isTool = _langchain_core_messages.ToolMessage.isInstance(message);
1537
+ if (!isTool && !_langchain_core_messages.HumanMessage.isInstance(message)) return message;
1538
+ let messageChanged = false;
1539
+ const content = message.contentBlocks.map((block) => {
1540
+ if (block == null || typeof block !== "object" || !MULTIMODAL_BLOCK_TYPES.has(block.type) || multimodalBlockSupported(block, profile, isTool)) return block;
1541
+ messageChanged = true;
1542
+ paths ??= readFilePaths(messages);
1543
+ return placeholder(block, isTool ? paths.get(message.tool_call_id) : void 0);
1544
+ });
1545
+ if (!messageChanged) return message;
1546
+ changed = true;
1547
+ if (isTool) return new _langchain_core_messages.ToolMessage({
1548
+ content,
1549
+ tool_call_id: message.tool_call_id,
1550
+ name: message.name,
1551
+ id: message.id,
1552
+ artifact: message.artifact,
1553
+ status: message.status,
1554
+ metadata: message.metadata,
1555
+ additional_kwargs: message.additional_kwargs,
1556
+ response_metadata: message.response_metadata
1557
+ });
1558
+ return new _langchain_core_messages.HumanMessage({
1559
+ content,
1560
+ name: message.name,
1561
+ id: message.id,
1562
+ additional_kwargs: message.additional_kwargs,
1563
+ response_metadata: message.response_metadata
1564
+ });
1565
+ });
1566
+ return changed ? result : messages;
1567
+ }
1568
+ /**
1569
+ * Support is read from `model.profile` (see
1570
+ * https://docs.langchain.com/oss/javascript/langchain/models#model-profiles).
1571
+ *
1572
+ * Place this middleware last in the agent's `middleware` list, so that if
1573
+ * some other middleware changes `request.model`, this one sees the model
1574
+ * that will actually receive the request.
1575
+ */
1576
+ function createUnsupportedContentMiddleware() {
1577
+ return (0, langchain.createMiddleware)({
1578
+ name: "UnsupportedContentMiddleware",
1579
+ wrapModelCall: async (request, handler) => {
1580
+ const messages = scrubUnsupportedMultimodalContent(request.messages, request.model);
1581
+ if (messages === request.messages) return handler(request);
1582
+ return handler({
1583
+ ...request,
1584
+ messages
1585
+ });
1586
+ }
1587
+ });
1588
+ }
1589
+ //#endregion
1114
1590
  //#region src/permissions/enforce.ts
1115
1591
  /**
1116
1592
  * Validate permission rule paths at setup time. Throws if any path is
@@ -1201,6 +1677,10 @@ var CompositeBackend = class {
1201
1677
  static isInstance(backend) {
1202
1678
  return typeof backend === "object" && backend !== null && Array.isArray(backend.routePrefixes);
1203
1679
  }
1680
+ /** Returns the backend for `path`, with the matched prefix stripped from the returned path. */
1681
+ resolveBackendForPath(path) {
1682
+ return this.getBackendAndKey(path);
1683
+ }
1204
1684
  /**
1205
1685
  * Determine which backend handles this key and strip prefix.
1206
1686
  *
@@ -1805,6 +2285,8 @@ const GREP_TRUNCATION_NOTE = "Note: the search stopped early because it hit the
1805
2285
  * Set to null to disable the cap.
1806
2286
  */
1807
2287
  const DEFAULT_GREP_MAX_COUNT = 1e3;
2288
+ /** Path prefix offloaded binary content blobs are written under. */
2289
+ const BLOBS_PREFIX = "/blobs";
1808
2290
  /**
1809
2291
  * Message template for evicted tool results.
1810
2292
  */
@@ -2587,6 +3069,68 @@ function allPathsScopedToRoutes(permissions, backend) {
2587
3069
  return path === normalizedRoute.slice(0, -1) || path.startsWith(normalizedRoute);
2588
3070
  })));
2589
3071
  }
3072
+ /** Whether `path` resolves to a `StateBackend`, recursing through composite routing. */
3073
+ function routesToStateBackend(backend, path) {
3074
+ if (CompositeBackend.isInstance(backend)) {
3075
+ const [routed, routedPath] = backend.resolveBackendForPath(path);
3076
+ return routesToStateBackend(routed, routedPath);
3077
+ }
3078
+ return StateBackend.isInstance(backend);
3079
+ }
3080
+ /**
3081
+ * Whether `response` is the `{ structuredResponse, messages }` shape a
3082
+ * provider-strategy structured-output call can return instead of a plain
3083
+ * `AIMessage`. The agent framework only recognizes this shape at the
3084
+ * outermost `wrapModelCall` return value — returning a `Command` instead
3085
+ * discards `structuredResponse` with no way to carry it through the Command.
3086
+ */
3087
+ function hasStructuredResponse(response) {
3088
+ return typeof response === "object" && response !== null && "structuredResponse" in response && "messages" in response;
3089
+ }
3090
+ const REJECTED_FILE_CONTENT_TEXT = "Unsupported content. The file may be invalid, too large, or of an unsupported mime-type.";
3091
+ /**
3092
+ * No LangChain error class captures "the provider rejected this request"
3093
+ * generically, so this checks the raw HTTP status providers' client SDKs set
3094
+ * on their errors instead. `ContextOverflowError` is excluded because it's a
3095
+ * different, already-handled problem, and retrying it with stripped file
3096
+ * content wouldn't fix an oversized prompt.
3097
+ */
3098
+ function isRejectedRequestError(error) {
3099
+ if (_langchain_core_errors.ContextOverflowError.isInstance(error)) return false;
3100
+ return typeof error === "object" && error !== null && "status" in error && error.status === 400;
3101
+ }
3102
+ /**
3103
+ * Only messages carrying multimodal blocks after the last `AIMessage` are
3104
+ * candidates for replacement, since anything earlier was already accepted by
3105
+ * the model in a prior turn. Returns `messages` unchanged if nothing
3106
+ * matched, so the caller can tell there was nothing to blame for the
3107
+ * rejection.
3108
+ */
3109
+ function replaceRejectedFileContent(messages) {
3110
+ let lastResponseIndex = -1;
3111
+ for (let i = messages.length - 1; i >= 0; i--) if (langchain.AIMessage.isInstance(messages[i])) {
3112
+ lastResponseIndex = i;
3113
+ break;
3114
+ }
3115
+ let changed = false;
3116
+ const result = messages.map((message, index) => {
3117
+ if (index <= lastResponseIndex || !langchain.ToolMessage.isInstance(message) || message.name !== "read_file") return message;
3118
+ if (!message.contentBlocks.some((block) => MULTIMODAL_BLOCK_TYPES.has(block.type))) return message;
3119
+ changed = true;
3120
+ return new langchain.ToolMessage({
3121
+ content: REJECTED_FILE_CONTENT_TEXT,
3122
+ tool_call_id: message.tool_call_id,
3123
+ name: message.name,
3124
+ id: message.id,
3125
+ artifact: message.artifact,
3126
+ status: message.status,
3127
+ metadata: message.metadata,
3128
+ additional_kwargs: message.additional_kwargs,
3129
+ response_metadata: message.response_metadata
3130
+ });
3131
+ });
3132
+ return changed ? result : messages;
3133
+ }
2590
3134
  /**
2591
3135
  * Create middleware that provides built-in filesystem tools and optional custom
2592
3136
  * prompt guidance.
@@ -2613,7 +3157,10 @@ function allPathsScopedToRoutes(permissions, backend) {
2613
3157
  * ```
2614
3158
  */
2615
3159
  function createFilesystemMiddleware(options = {}) {
2616
- const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4, humanMessageTokenLimitBeforeEvict = 5e4, permissions = [], tools: filesystemTools = null, grepMaxCount = DEFAULT_GREP_MAX_COUNT } = options;
3160
+ const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4, humanMessageTokenLimitBeforeEvict = 5e4, permissions = [], tools: filesystemTools = null, grepMaxCount = DEFAULT_GREP_MAX_COUNT, offloadBinaryContent = false } = options;
3161
+ const backendIsStateBackend = StateBackend.isInstance(backend);
3162
+ if (offloadBinaryContent && backendIsStateBackend) console.warn("offloadBinaryContent has no effect: the backend is a StateBackend, which keeps files as part of checkpointed state.");
3163
+ const blobCache = offloadBinaryContent && !backendIsStateBackend ? new BlobCache() : null;
2617
3164
  const enabledFilesystemTools = normalizeFilesystemTools(filesystemTools);
2618
3165
  const executeToolEnabled = enabledFilesystemTools == null || enabledFilesystemTools.has("execute");
2619
3166
  if (permissions.length > 0) validatePermissionPaths(permissions);
@@ -2750,18 +3297,59 @@ function createFilesystemMiddleware(options = {}) {
2750
3297
  return msg;
2751
3298
  });
2752
3299
  }
2753
- return handler({
3300
+ let offloadedHuman = [];
3301
+ if (blobCache && !routesToStateBackend(resolvedBackend, `${BLOBS_PREFIX}/`)) {
3302
+ offloadedHuman = await offloadHumanMessages({
3303
+ stateMessages: request.state.messages ?? [],
3304
+ pending: []
3305
+ }, resolvedBackend, BLOBS_PREFIX, blobCache);
3306
+ messages = await hydrateMessages(messages, resolvedBackend, BLOBS_PREFIX, blobCache);
3307
+ }
3308
+ const finalRequest = {
2754
3309
  ...request,
2755
3310
  tools,
2756
3311
  messages,
2757
3312
  systemMessage: newSystemMessage
2758
- });
3313
+ };
3314
+ const foldOffload = (response, extra = []) => {
3315
+ if (offloadedHuman.length === 0 && extra.length === 0) return response;
3316
+ if (hasStructuredResponse(response)) return {
3317
+ ...response,
3318
+ messages: [
3319
+ ...extra,
3320
+ ...offloadedHuman,
3321
+ ...response.messages
3322
+ ]
3323
+ };
3324
+ return new _langchain_langgraph.Command({ update: { messages: extra.length === 0 ? offloadedHuman : [
3325
+ ...extra,
3326
+ ...offloadedHuman,
3327
+ response
3328
+ ] } });
3329
+ };
3330
+ try {
3331
+ return foldOffload(await handler(finalRequest));
3332
+ } catch (error) {
3333
+ if (!isRejectedRequestError(error)) throw error;
3334
+ const replaced = replaceRejectedFileContent(finalRequest.messages);
3335
+ if (replaced === finalRequest.messages) throw error;
3336
+ return foldOffload(await handler({
3337
+ ...finalRequest,
3338
+ messages: replaced
3339
+ }), replaced.filter((msg, i) => msg !== finalRequest.messages[i]));
3340
+ }
2759
3341
  },
2760
3342
  wrapToolCall: async (request, handler) => {
2761
- if (!toolTokenLimitBeforeEvict) return handler(request);
2762
3343
  const toolName = request.toolCall?.name;
2763
- if (toolName && TOOLS_EXCLUDED_FROM_EVICTION.includes(toolName)) return handler(request);
2764
- const result = await handler(request);
3344
+ let result = await handler(request);
3345
+ if (blobCache && toolName === "read_file") {
3346
+ const resolvedBackend = await resolveBackend(backend, {
3347
+ ...request.runtime,
3348
+ state: request.state
3349
+ });
3350
+ if (!routesToStateBackend(resolvedBackend, `${BLOBS_PREFIX}/`)) result = await offloadToolResult(result, resolvedBackend, BLOBS_PREFIX, /* @__PURE__ */ new Map());
3351
+ }
3352
+ if (!toolTokenLimitBeforeEvict || toolName && TOOLS_EXCLUDED_FROM_EVICTION.includes(toolName)) return result;
2765
3353
  if (langchain.ToolMessage.isInstance(result)) {
2766
3354
  const processed = await processToolMessage(result, request.runtime, request.state, request.toolCall?.id);
2767
3355
  if (processed.filesUpdate) return new _langchain_langgraph.Command({ update: {
@@ -3805,6 +4393,7 @@ function createSubAgent(spec, options) {
3805
4393
  if (!spec.tools) throw new Error(`SubAgent '${spec.name}' must specify 'tools'`);
3806
4394
  const middleware = [...spec.middleware ?? []];
3807
4395
  if (spec.interruptOn) middleware.push((0, langchain.humanInTheLoopMiddleware)({ interruptOn: spec.interruptOn }));
4396
+ if (!middleware.some((m) => m.name === "UnsupportedContentMiddleware")) middleware.push(createUnsupportedContentMiddleware());
3808
4397
  const selectedResponseFormat = options?.responseFormat ?? spec.responseFormat;
3809
4398
  return (0, langchain.createAgent)({
3810
4399
  model: spec.model,
@@ -6724,6 +7313,7 @@ function createDeepAgent(params = {}) {
6724
7313
  ]);
6725
7314
  if (subagentProfile.excludedMiddleware.size > 0) subagentMiddleware = subagentMiddleware.filter((middleware) => !subagentProfile.excludedMiddleware.has(middleware.name));
6726
7315
  if (subagentProfile.excludedTools.size > 0) subagentMiddleware.push(createToolExclusionMiddleware(subagentProfile.excludedTools));
7316
+ if (!subagentProfile.excludedMiddleware.has("UnsupportedContentMiddleware") && !subagentMiddleware.some((middleware) => middleware.name === "UnsupportedContentMiddleware")) subagentMiddleware.push(createUnsupportedContentMiddleware());
6727
7317
  return subagentMiddleware;
6728
7318
  };
6729
7319
  const normalizeSubagentSpec = (input) => ({
@@ -6786,6 +7376,7 @@ function createDeepAgent(params = {}) {
6786
7376
  middleware = middleware.filter((entry) => !excluded.has(entry.name));
6787
7377
  }
6788
7378
  if (harnessProfile.excludedTools.size > 0) middleware.push(createToolExclusionMiddleware(harnessProfile.excludedTools));
7379
+ if (!harnessProfile.excludedMiddleware.has("UnsupportedContentMiddleware") && !middleware.some((m) => m.name === "UnsupportedContentMiddleware")) middleware.push(createUnsupportedContentMiddleware());
6789
7380
  /**
6790
7381
  * Return as DeepAgent with proper DeepAgentTypeConfig
6791
7382
  * - Response: InferStructuredResponse<TResponse> (unwraps ToolStrategy<T>/ProviderStrategy<T> → T)
@@ -8993,6 +9584,12 @@ Object.defineProperty(exports, "MAX_SKILL_NAME_LENGTH", {
8993
9584
  return MAX_SKILL_NAME_LENGTH;
8994
9585
  }
8995
9586
  });
9587
+ Object.defineProperty(exports, "MULTIMODAL_BLOCK_TYPES", {
9588
+ enumerable: true,
9589
+ get: function() {
9590
+ return MULTIMODAL_BLOCK_TYPES;
9591
+ }
9592
+ });
8996
9593
  Object.defineProperty(exports, "REQUIRED_MIDDLEWARE_NAMES", {
8997
9594
  enumerable: true,
8998
9595
  get: function() {
@@ -9131,6 +9728,12 @@ Object.defineProperty(exports, "createSummarizationMiddleware", {
9131
9728
  return createSummarizationMiddleware;
9132
9729
  }
9133
9730
  });
9731
+ Object.defineProperty(exports, "createUnsupportedContentMiddleware", {
9732
+ enumerable: true,
9733
+ get: function() {
9734
+ return createUnsupportedContentMiddleware;
9735
+ }
9736
+ });
9134
9737
  Object.defineProperty(exports, "filesValue", {
9135
9738
  enumerable: true,
9136
9739
  get: function() {
@@ -9185,6 +9788,12 @@ Object.defineProperty(exports, "isTextMimeType", {
9185
9788
  return isTextMimeType;
9186
9789
  }
9187
9790
  });
9791
+ Object.defineProperty(exports, "multimodalBlockSupported", {
9792
+ enumerable: true,
9793
+ get: function() {
9794
+ return multimodalBlockSupported;
9795
+ }
9796
+ });
9188
9797
  Object.defineProperty(exports, "normalizeReadPagination", {
9189
9798
  enumerable: true,
9190
9799
  get: function() {
@@ -9215,6 +9824,12 @@ Object.defineProperty(exports, "resolveBackend", {
9215
9824
  return resolveBackend;
9216
9825
  }
9217
9826
  });
9827
+ Object.defineProperty(exports, "scrubUnsupportedMultimodalContent", {
9828
+ enumerable: true,
9829
+ get: function() {
9830
+ return scrubUnsupportedMultimodalContent;
9831
+ }
9832
+ });
9218
9833
  Object.defineProperty(exports, "serializeProfile", {
9219
9834
  enumerable: true,
9220
9835
  get: function() {
@@ -9228,4 +9843,4 @@ Object.defineProperty(exports, "skillsMetadataValue", {
9228
9843
  }
9229
9844
  });
9230
9845
 
9231
- //# sourceMappingURL=langsmith-Bhg_Q4ZO.cjs.map
9846
+ //# sourceMappingURL=langsmith-CjOWZAS2.cjs.map