deepagents 1.7.5 → 1.8.0

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.
package/dist/index.js CHANGED
@@ -1,18 +1,19 @@
1
- import { AIMessage, HumanMessage, SystemMessage, ToolMessage, anthropicPromptCachingMiddleware, createAgent, createMiddleware, humanInTheLoopMiddleware, summarizationMiddleware, todoListMiddleware, tool } from "langchain";
1
+ import { AIMessage, HumanMessage, SystemMessage, ToolMessage, anthropicPromptCachingMiddleware, countTokensApproximately, createAgent, createMiddleware, humanInTheLoopMiddleware, todoListMiddleware, tool } from "langchain";
2
2
  import { Runnable } from "@langchain/core/runnables";
3
3
  import { Command, REMOVE_ALL_MESSAGES, ReducedValue, StateSchema, getCurrentTaskInput, isCommand } from "@langchain/langgraph";
4
4
  import { z } from "zod/v4";
5
5
  import micromatch from "micromatch";
6
6
  import { basename } from "path";
7
- import { HumanMessage as HumanMessage$1, RemoveMessage } from "@langchain/core/messages";
7
+ import { HumanMessage as HumanMessage$1, RemoveMessage, getBufferString } from "@langchain/core/messages";
8
8
  import { z as z$1 } from "zod";
9
9
  import yaml from "yaml";
10
- import "uuid";
11
- import "langchain/chat_models/universal";
10
+ import { v4 } from "uuid";
11
+ import { ContextOverflowError } from "@langchain/core/errors";
12
+ import { initChatModel } from "langchain/chat_models/universal";
12
13
  import fs from "node:fs/promises";
13
14
  import fs$1 from "node:fs";
14
15
  import path from "node:path";
15
- import { spawn } from "node:child_process";
16
+ import cp, { spawn } from "node:child_process";
16
17
  import fg from "fast-glob";
17
18
  import os from "node:os";
18
19
 
@@ -219,7 +220,7 @@ function performStringReplacement(content, oldString, newString, replaceAll) {
219
220
  if (oldString === "") return "Error: oldString cannot be empty when file has content";
220
221
  const occurrences = content.split(oldString).length - 1;
221
222
  if (occurrences === 0) return `Error: String not found in file: '${oldString}'`;
222
- if (occurrences > 1 && !replaceAll) return `Error: String '${oldString}' appears ${occurrences} times in file. Use replace_all=True to replace all instances, or provide a more specific string with surrounding context.`;
223
+ if (occurrences > 1 && !replaceAll) return `Error: String '${oldString}' has multiple occurrences (appears ${occurrences} times) in file. Use replace_all=True to replace all instances, or provide a more specific string with surrounding context.`;
223
224
  return [content.split(oldString).join(newString), occurrences];
224
225
  }
225
226
  /**
@@ -868,7 +869,7 @@ function createWriteFileTool(backend, options) {
868
869
  description: customDescription || WRITE_FILE_TOOL_DESCRIPTION,
869
870
  schema: z.object({
870
871
  file_path: z.string().describe("Absolute path to the file to write"),
871
- content: z.string().describe("Content to write to the file")
872
+ content: z.string().default("").describe("Content to write to the file")
872
873
  })
873
874
  });
874
875
  }
@@ -1357,7 +1358,8 @@ function getSubagents(options) {
1357
1358
  model: defaultModel,
1358
1359
  systemPrompt: DEFAULT_SUBAGENT_PROMPT,
1359
1360
  tools: defaultTools,
1360
- middleware: generalPurposeMiddleware
1361
+ middleware: generalPurposeMiddleware,
1362
+ name: "general-purpose"
1361
1363
  });
1362
1364
  subagentDescriptions.push(`- general-purpose: ${DEFAULT_GENERAL_PURPOSE_DESCRIPTION}`);
1363
1365
  }
@@ -1372,7 +1374,8 @@ function getSubagents(options) {
1372
1374
  model: agentParams.model ?? defaultModel,
1373
1375
  systemPrompt: agentParams.systemPrompt,
1374
1376
  tools: agentParams.tools ?? defaultTools,
1375
- middleware
1377
+ middleware,
1378
+ name: agentParams.name
1376
1379
  });
1377
1380
  }
1378
1381
  }
@@ -2234,6 +2237,76 @@ function createSkillsMiddleware(options) {
2234
2237
  * For simple use cases without backend offloading, use `summarizationMiddleware`
2235
2238
  * from `langchain` directly.
2236
2239
  */
2240
+ const DEFAULT_MESSAGES_TO_KEEP = 20;
2241
+ const DEFAULT_TRIM_TOKEN_LIMIT = 4e3;
2242
+ const FALLBACK_TRIGGER = {
2243
+ type: "tokens",
2244
+ value: 17e4
2245
+ };
2246
+ const FALLBACK_KEEP = {
2247
+ type: "messages",
2248
+ value: 6
2249
+ };
2250
+ const FALLBACK_TRUNCATE_ARGS = {
2251
+ trigger: {
2252
+ type: "messages",
2253
+ value: 20
2254
+ },
2255
+ keep: {
2256
+ type: "messages",
2257
+ value: 20
2258
+ }
2259
+ };
2260
+ const PROFILE_TRIGGER = {
2261
+ type: "fraction",
2262
+ value: .85
2263
+ };
2264
+ const PROFILE_KEEP = {
2265
+ type: "fraction",
2266
+ value: .1
2267
+ };
2268
+ const PROFILE_TRUNCATE_ARGS = {
2269
+ trigger: {
2270
+ type: "fraction",
2271
+ value: .85
2272
+ },
2273
+ keep: {
2274
+ type: "fraction",
2275
+ value: .1
2276
+ }
2277
+ };
2278
+ /**
2279
+ * Compute summarization defaults based on model profile.
2280
+ * Mirrors Python's `_compute_summarization_defaults`.
2281
+ *
2282
+ * If the model has a profile with `maxInputTokens`, uses fraction-based
2283
+ * settings. Otherwise, uses fixed token/message counts.
2284
+ *
2285
+ * @param resolvedModel - The resolved chat model instance.
2286
+ */
2287
+ function computeSummarizationDefaults(resolvedModel) {
2288
+ if (resolvedModel.profile && typeof resolvedModel.profile === "object" && "maxInputTokens" in resolvedModel.profile && typeof resolvedModel.profile.maxInputTokens === "number") return {
2289
+ trigger: PROFILE_TRIGGER,
2290
+ keep: PROFILE_KEEP,
2291
+ truncateArgsSettings: PROFILE_TRUNCATE_ARGS
2292
+ };
2293
+ return {
2294
+ trigger: FALLBACK_TRIGGER,
2295
+ keep: FALLBACK_KEEP,
2296
+ truncateArgsSettings: FALLBACK_TRUNCATE_ARGS
2297
+ };
2298
+ }
2299
+ const DEFAULT_SUMMARY_PROMPT = `You are a conversation summarizer. Your task is to create a concise summary of the conversation that captures:
2300
+ 1. The main topics discussed
2301
+ 2. Key decisions or conclusions reached
2302
+ 3. Any important context that would be needed for continuing the conversation
2303
+
2304
+ Keep the summary focused and informative. Do not include unnecessary details.
2305
+
2306
+ Conversation to summarize:
2307
+ {conversation}
2308
+
2309
+ Summary:`;
2237
2310
  /**
2238
2311
  * Zod schema for a summarization event that tracks what was summarized and
2239
2312
  * where the cutoff is.
@@ -2254,6 +2327,548 @@ const SummarizationStateSchema = z$1.object({
2254
2327
  _summarizationSessionId: z$1.string().optional(),
2255
2328
  _summarizationEvent: SummarizationEventSchema.optional()
2256
2329
  });
2330
+ /**
2331
+ * Check if a message is a previous summarization message.
2332
+ * Summary messages are HumanMessage objects with lc_source='summarization' in additional_kwargs.
2333
+ */
2334
+ function isSummaryMessage(msg) {
2335
+ if (!HumanMessage.isInstance(msg)) return false;
2336
+ return msg.additional_kwargs?.lc_source === "summarization";
2337
+ }
2338
+ /**
2339
+ * Create summarization middleware with backend support for conversation history offloading.
2340
+ *
2341
+ * This middleware:
2342
+ * 1. Monitors conversation length against configured thresholds
2343
+ * 2. When triggered, offloads old messages to backend storage
2344
+ * 3. Generates a summary of offloaded messages
2345
+ * 4. Replaces old messages with the summary, preserving recent context
2346
+ *
2347
+ * @param options - Configuration options
2348
+ * @returns AgentMiddleware for summarization and history offloading
2349
+ */
2350
+ function createSummarizationMiddleware(options) {
2351
+ const { model, backend, summaryPrompt = DEFAULT_SUMMARY_PROMPT, trimTokensToSummarize = DEFAULT_TRIM_TOKEN_LIMIT, historyPathPrefix = "/conversation_history" } = options;
2352
+ let trigger = options.trigger;
2353
+ let keep = options.keep ?? {
2354
+ type: "messages",
2355
+ value: DEFAULT_MESSAGES_TO_KEEP
2356
+ };
2357
+ let truncateArgsSettings = options.truncateArgsSettings;
2358
+ let defaultsComputed = trigger != null;
2359
+ let truncateTrigger = truncateArgsSettings?.trigger;
2360
+ let truncateKeep = truncateArgsSettings?.keep ?? {
2361
+ type: "messages",
2362
+ value: 20
2363
+ };
2364
+ let maxArgLength = truncateArgsSettings?.maxLength ?? 2e3;
2365
+ let truncationText = truncateArgsSettings?.truncationText ?? "...(argument truncated)";
2366
+ /**
2367
+ * Lazily compute defaults from model profile when trigger was not provided.
2368
+ * Called once when the model is first resolved.
2369
+ */
2370
+ function applyModelDefaults(resolvedModel) {
2371
+ if (defaultsComputed) return;
2372
+ defaultsComputed = true;
2373
+ const defaults = computeSummarizationDefaults(resolvedModel);
2374
+ trigger = defaults.trigger;
2375
+ keep = options.keep ?? defaults.keep;
2376
+ if (!options.truncateArgsSettings) {
2377
+ truncateArgsSettings = defaults.truncateArgsSettings;
2378
+ truncateTrigger = defaults.truncateArgsSettings.trigger;
2379
+ truncateKeep = defaults.truncateArgsSettings.keep ?? {
2380
+ type: "messages",
2381
+ value: 20
2382
+ };
2383
+ maxArgLength = defaults.truncateArgsSettings.maxLength ?? 2e3;
2384
+ truncationText = defaults.truncateArgsSettings.truncationText ?? "...(argument truncated)";
2385
+ }
2386
+ }
2387
+ let sessionId = null;
2388
+ let tokenEstimationMultiplier = 1;
2389
+ /**
2390
+ * Resolve backend from instance or factory.
2391
+ */
2392
+ function getBackend(state) {
2393
+ if (typeof backend === "function") return backend({ state });
2394
+ return backend;
2395
+ }
2396
+ /**
2397
+ * Get or create session ID for history file naming.
2398
+ */
2399
+ function getSessionId(state) {
2400
+ if (state._summarizationSessionId) return state._summarizationSessionId;
2401
+ if (!sessionId) sessionId = `session_${v4().substring(0, 8)}`;
2402
+ return sessionId;
2403
+ }
2404
+ /**
2405
+ * Get the history file path.
2406
+ */
2407
+ function getHistoryPath(state) {
2408
+ return `${historyPathPrefix}/${getSessionId(state)}.md`;
2409
+ }
2410
+ /**
2411
+ * Cached resolved model to avoid repeated initChatModel calls
2412
+ */
2413
+ let cachedModel = void 0;
2414
+ /**
2415
+ * Resolve the chat model.
2416
+ * Uses initChatModel to support any model provider from a string name.
2417
+ * The resolved model is cached for subsequent calls.
2418
+ */
2419
+ async function getChatModel() {
2420
+ if (cachedModel) return cachedModel;
2421
+ if (typeof model === "string") cachedModel = await initChatModel(model);
2422
+ else cachedModel = model;
2423
+ return cachedModel;
2424
+ }
2425
+ /**
2426
+ * Get the max input tokens from the model's profile.
2427
+ * Similar to Python's _get_profile_limits.
2428
+ *
2429
+ * When the profile is unavailable, returns undefined. In that case the
2430
+ * middleware uses fixed token/message-count fallback defaults for
2431
+ * trigger/keep, and relies on the ContextOverflowError catch as a
2432
+ * safety net if the prompt still exceeds the model's actual limit.
2433
+ */
2434
+ function getMaxInputTokens(resolvedModel) {
2435
+ const profile = resolvedModel.profile;
2436
+ if (profile && typeof profile === "object" && "maxInputTokens" in profile && typeof profile.maxInputTokens === "number") return profile.maxInputTokens;
2437
+ }
2438
+ /**
2439
+ * Check if summarization should be triggered.
2440
+ */
2441
+ function shouldSummarize(messages, totalTokens, maxInputTokens) {
2442
+ if (!trigger) return false;
2443
+ const adjustedTokens = totalTokens * tokenEstimationMultiplier;
2444
+ const triggers = Array.isArray(trigger) ? trigger : [trigger];
2445
+ for (const t of triggers) {
2446
+ if (t.type === "messages" && messages.length >= t.value) return true;
2447
+ if (t.type === "tokens" && adjustedTokens >= t.value) return true;
2448
+ if (t.type === "fraction" && maxInputTokens) {
2449
+ if (adjustedTokens >= Math.floor(maxInputTokens * t.value)) return true;
2450
+ }
2451
+ }
2452
+ return false;
2453
+ }
2454
+ /**
2455
+ * Find a safe cutoff point that doesn't split AI/Tool message pairs.
2456
+ *
2457
+ * If the message at `cutoffIndex` is a ToolMessage, this adjusts the boundary
2458
+ * so that related AI and Tool messages stay together. Two strategies are used:
2459
+ *
2460
+ * 1. **Move backward** to include the AIMessage that produced the tool calls,
2461
+ * keeping the pair in the preserved set. Preferred when it doesn't move
2462
+ * the cutoff too far back.
2463
+ *
2464
+ * 2. **Advance forward** past all consecutive ToolMessages, putting the entire
2465
+ * pair into the summarized set. Used when moving backward would preserve
2466
+ * too many messages (e.g., a single AIMessage made 20+ tool calls).
2467
+ */
2468
+ function findSafeCutoffPoint(messages, cutoffIndex) {
2469
+ if (cutoffIndex >= messages.length || !ToolMessage.isInstance(messages[cutoffIndex])) return cutoffIndex;
2470
+ let forwardIdx = cutoffIndex;
2471
+ while (forwardIdx < messages.length && ToolMessage.isInstance(messages[forwardIdx])) forwardIdx++;
2472
+ const toolCallIds = /* @__PURE__ */ new Set();
2473
+ for (let i = cutoffIndex; i < forwardIdx; i++) {
2474
+ const toolMsg = messages[i];
2475
+ if (toolMsg.tool_call_id) toolCallIds.add(toolMsg.tool_call_id);
2476
+ }
2477
+ let backwardIdx = null;
2478
+ for (let i = cutoffIndex - 1; i >= 0; i--) {
2479
+ const msg = messages[i];
2480
+ if (AIMessage.isInstance(msg) && msg.tool_calls) {
2481
+ const aiToolCallIds = new Set(msg.tool_calls.map((tc) => tc.id).filter((id) => id != null));
2482
+ for (const id of toolCallIds) if (aiToolCallIds.has(id)) {
2483
+ backwardIdx = i;
2484
+ break;
2485
+ }
2486
+ if (backwardIdx !== null) break;
2487
+ }
2488
+ }
2489
+ if (backwardIdx === null) return forwardIdx;
2490
+ if (cutoffIndex - backwardIdx > cutoffIndex / 2 && cutoffIndex > 2) return forwardIdx;
2491
+ return backwardIdx;
2492
+ }
2493
+ /**
2494
+ * Determine cutoff index for messages to summarize.
2495
+ * Messages at index < cutoff will be summarized.
2496
+ * Messages at index >= cutoff will be preserved.
2497
+ *
2498
+ * Uses findSafeCutoffPoint to ensure tool call/result pairs stay together.
2499
+ */
2500
+ function determineCutoffIndex(messages, maxInputTokens) {
2501
+ let rawCutoff;
2502
+ if (keep.type === "messages") {
2503
+ if (messages.length <= keep.value) return 0;
2504
+ rawCutoff = messages.length - keep.value;
2505
+ } else if (keep.type === "tokens" || keep.type === "fraction") {
2506
+ const targetTokenCount = keep.type === "fraction" && maxInputTokens ? Math.floor(maxInputTokens * keep.value) : keep.value;
2507
+ let tokensKept = 0;
2508
+ rawCutoff = 0;
2509
+ for (let i = messages.length - 1; i >= 0; i--) {
2510
+ const msgTokens = countTokensApproximately([messages[i]]);
2511
+ if (tokensKept + msgTokens > targetTokenCount) {
2512
+ rawCutoff = i + 1;
2513
+ break;
2514
+ }
2515
+ tokensKept += msgTokens;
2516
+ }
2517
+ } else return 0;
2518
+ return findSafeCutoffPoint(messages, rawCutoff);
2519
+ }
2520
+ /**
2521
+ * Check if argument truncation should be triggered.
2522
+ */
2523
+ function shouldTruncateArgs(messages, totalTokens, maxInputTokens) {
2524
+ if (!truncateTrigger) return false;
2525
+ const adjustedTokens = totalTokens * tokenEstimationMultiplier;
2526
+ if (truncateTrigger.type === "messages") return messages.length >= truncateTrigger.value;
2527
+ if (truncateTrigger.type === "tokens") return adjustedTokens >= truncateTrigger.value;
2528
+ if (truncateTrigger.type === "fraction" && maxInputTokens) return adjustedTokens >= Math.floor(maxInputTokens * truncateTrigger.value);
2529
+ return false;
2530
+ }
2531
+ /**
2532
+ * Determine cutoff index for argument truncation.
2533
+ * Uses findSafeCutoffPoint to ensure tool call/result pairs stay together.
2534
+ */
2535
+ function determineTruncateCutoffIndex(messages, maxInputTokens) {
2536
+ let rawCutoff;
2537
+ if (truncateKeep.type === "messages") {
2538
+ if (messages.length <= truncateKeep.value) return messages.length;
2539
+ rawCutoff = messages.length - truncateKeep.value;
2540
+ } else if (truncateKeep.type === "tokens" || truncateKeep.type === "fraction") {
2541
+ const targetTokenCount = truncateKeep.type === "fraction" && maxInputTokens ? Math.floor(maxInputTokens * truncateKeep.value) : truncateKeep.value;
2542
+ let tokensKept = 0;
2543
+ rawCutoff = 0;
2544
+ for (let i = messages.length - 1; i >= 0; i--) {
2545
+ const msgTokens = countTokensApproximately([messages[i]]);
2546
+ if (tokensKept + msgTokens > targetTokenCount) {
2547
+ rawCutoff = i + 1;
2548
+ break;
2549
+ }
2550
+ tokensKept += msgTokens;
2551
+ }
2552
+ } else return messages.length;
2553
+ return findSafeCutoffPoint(messages, rawCutoff);
2554
+ }
2555
+ /**
2556
+ * Count tokens including system message and tools, matching Python's approach.
2557
+ * This gives a more accurate picture of what actually gets sent to the model.
2558
+ */
2559
+ function countTotalTokens(messages, systemMessage, tools) {
2560
+ return countTokensApproximately(systemMessage && SystemMessage.isInstance(systemMessage) ? [systemMessage, ...messages] : [...messages], tools && Array.isArray(tools) && tools.length > 0 ? tools : null);
2561
+ }
2562
+ /**
2563
+ * Truncate ToolMessage content so that the total payload fits within the
2564
+ * model's context window. Each ToolMessage gets an equal share of the
2565
+ * remaining token budget after accounting for non-tool messages, system
2566
+ * message, and tool schemas.
2567
+ *
2568
+ * This is critical for conversations where a single AIMessage triggers
2569
+ * many tool calls whose results collectively exceed the context window.
2570
+ * Without this, findSafeCutoffPoint cannot split the AI/Tool group and
2571
+ * summarization would discard everything, causing the model to re-call
2572
+ * the same tools in an infinite loop.
2573
+ */
2574
+ function compactToolResults(messages, maxInputTokens, systemMessage, tools) {
2575
+ const toolMessageIndices = [];
2576
+ for (let i = 0; i < messages.length; i++) if (ToolMessage.isInstance(messages[i])) toolMessageIndices.push(i);
2577
+ if (toolMessageIndices.length === 0) return {
2578
+ messages,
2579
+ modified: false
2580
+ };
2581
+ const overheadTokens = countTotalTokens(messages.filter((m) => !ToolMessage.isInstance(m)), systemMessage, tools);
2582
+ const adjustedMax = maxInputTokens / tokenEstimationMultiplier;
2583
+ const budgetForTools = Math.max(adjustedMax * .7 - overheadTokens, 1e3);
2584
+ const perToolBudgetChars = Math.floor(budgetForTools / toolMessageIndices.length) * 4;
2585
+ let modified = false;
2586
+ const result = [...messages];
2587
+ for (const idx of toolMessageIndices) {
2588
+ const msg = messages[idx];
2589
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
2590
+ if (content.length > perToolBudgetChars) {
2591
+ result[idx] = new ToolMessage({
2592
+ content: content.substring(0, perToolBudgetChars) + "\n...(result truncated)",
2593
+ tool_call_id: msg.tool_call_id,
2594
+ name: msg.name
2595
+ });
2596
+ modified = true;
2597
+ }
2598
+ }
2599
+ return {
2600
+ messages: result,
2601
+ modified
2602
+ };
2603
+ }
2604
+ /**
2605
+ * Truncate large tool arguments in old messages.
2606
+ */
2607
+ function truncateArgs(messages, maxInputTokens, systemMessage, tools) {
2608
+ if (!shouldTruncateArgs(messages, countTotalTokens(messages, systemMessage, tools), maxInputTokens)) return {
2609
+ messages,
2610
+ modified: false
2611
+ };
2612
+ const cutoffIndex = determineTruncateCutoffIndex(messages, maxInputTokens);
2613
+ if (cutoffIndex >= messages.length) return {
2614
+ messages,
2615
+ modified: false
2616
+ };
2617
+ const truncatedMessages = [];
2618
+ let modified = false;
2619
+ for (let i = 0; i < messages.length; i++) {
2620
+ const msg = messages[i];
2621
+ if (i < cutoffIndex && AIMessage.isInstance(msg) && msg.tool_calls) {
2622
+ const truncatedToolCalls = msg.tool_calls.map((toolCall) => {
2623
+ const args = toolCall.args || {};
2624
+ const truncatedArgs = {};
2625
+ let toolModified = false;
2626
+ for (const [key, value] of Object.entries(args)) if (typeof value === "string" && value.length > maxArgLength && (toolCall.name === "write_file" || toolCall.name === "edit_file")) {
2627
+ truncatedArgs[key] = value.substring(0, 20) + truncationText;
2628
+ toolModified = true;
2629
+ } else truncatedArgs[key] = value;
2630
+ if (toolModified) {
2631
+ modified = true;
2632
+ return {
2633
+ ...toolCall,
2634
+ args: truncatedArgs
2635
+ };
2636
+ }
2637
+ return toolCall;
2638
+ });
2639
+ if (modified) {
2640
+ const truncatedMsg = new AIMessage({
2641
+ content: msg.content,
2642
+ tool_calls: truncatedToolCalls,
2643
+ additional_kwargs: msg.additional_kwargs
2644
+ });
2645
+ truncatedMessages.push(truncatedMsg);
2646
+ } else truncatedMessages.push(msg);
2647
+ } else truncatedMessages.push(msg);
2648
+ }
2649
+ return {
2650
+ messages: truncatedMessages,
2651
+ modified
2652
+ };
2653
+ }
2654
+ /**
2655
+ * Filter out previous summary messages.
2656
+ */
2657
+ function filterSummaryMessages(messages) {
2658
+ return messages.filter((msg) => !isSummaryMessage(msg));
2659
+ }
2660
+ /**
2661
+ * Offload messages to backend.
2662
+ */
2663
+ async function offloadToBackend(resolvedBackend, messages, state) {
2664
+ const path = getHistoryPath(state);
2665
+ const filteredMessages = filterSummaryMessages(messages);
2666
+ const newSection = `## Summarized at ${(/* @__PURE__ */ new Date()).toISOString()}\n\n${getBufferString(filteredMessages)}\n\n`;
2667
+ let existingContent = "";
2668
+ try {
2669
+ if (resolvedBackend.downloadFiles) {
2670
+ const responses = await resolvedBackend.downloadFiles([path]);
2671
+ if (responses.length > 0 && responses[0].content && !responses[0].error) existingContent = new TextDecoder().decode(responses[0].content);
2672
+ }
2673
+ } catch {}
2674
+ const combinedContent = existingContent + newSection;
2675
+ try {
2676
+ let result;
2677
+ if (existingContent) result = await resolvedBackend.edit(path, existingContent, combinedContent);
2678
+ else result = await resolvedBackend.write(path, combinedContent);
2679
+ if (result.error) {
2680
+ console.warn(`Failed to offload conversation history to ${path}: ${result.error}`);
2681
+ return null;
2682
+ }
2683
+ return path;
2684
+ } catch (e) {
2685
+ console.warn(`Exception offloading conversation history to ${path}:`, e);
2686
+ return null;
2687
+ }
2688
+ }
2689
+ /**
2690
+ * Create summary of messages.
2691
+ */
2692
+ async function createSummary(messages, chatModel) {
2693
+ let messagesToSummarize = messages;
2694
+ if (countTokensApproximately(messages) > trimTokensToSummarize) {
2695
+ let kept = 0;
2696
+ const trimmedMessages = [];
2697
+ for (let i = messages.length - 1; i >= 0; i--) {
2698
+ const msgTokens = countTokensApproximately([messages[i]]);
2699
+ if (kept + msgTokens > trimTokensToSummarize) break;
2700
+ trimmedMessages.unshift(messages[i]);
2701
+ kept += msgTokens;
2702
+ }
2703
+ messagesToSummarize = trimmedMessages;
2704
+ }
2705
+ const conversation = getBufferString(messagesToSummarize);
2706
+ const prompt = summaryPrompt.replace("{conversation}", conversation);
2707
+ const response = await chatModel.invoke([new HumanMessage({ content: prompt })]);
2708
+ return typeof response.content === "string" ? response.content : JSON.stringify(response.content);
2709
+ }
2710
+ /**
2711
+ * Build the summary message with file path reference.
2712
+ */
2713
+ function buildSummaryMessage(summary, filePath) {
2714
+ let content;
2715
+ if (filePath) content = `You are in the middle of a conversation that has been summarized.
2716
+
2717
+ The full conversation history has been saved to ${filePath} should you need to refer back to it for details.
2718
+
2719
+ A condensed summary follows:
2720
+
2721
+ <summary>
2722
+ ${summary}
2723
+ </summary>`;
2724
+ else content = `Here is a summary of the conversation to date:\n\n${summary}`;
2725
+ return new HumanMessage({
2726
+ content,
2727
+ additional_kwargs: { lc_source: "summarization" }
2728
+ });
2729
+ }
2730
+ /**
2731
+ * Reconstruct the effective message list based on any previous summarization event.
2732
+ *
2733
+ * After summarization, instead of using all messages from state, we use the summary
2734
+ * message plus messages after the cutoff index. This avoids full state rewrites.
2735
+ */
2736
+ function getEffectiveMessages(messages, state) {
2737
+ const event = state._summarizationEvent;
2738
+ if (!event) return messages;
2739
+ const result = [event.summaryMessage];
2740
+ result.push(...messages.slice(event.cutoffIndex));
2741
+ return result;
2742
+ }
2743
+ /**
2744
+ * Summarize a set of messages using the given model and build the
2745
+ * summary message + backend offload. Returns the summary message,
2746
+ * the file path, and the state cutoff index.
2747
+ */
2748
+ async function summarizeMessages(messagesToSummarize, resolvedModel, state, previousCutoffIndex, cutoffIndex) {
2749
+ const filePath = await offloadToBackend(getBackend(state), messagesToSummarize, state);
2750
+ if (filePath === null) console.warn(`[SummarizationMiddleware] Backend offload failed during summarization. Proceeding with summary generation.`);
2751
+ return {
2752
+ summaryMessage: buildSummaryMessage(await createSummary(messagesToSummarize, resolvedModel), filePath),
2753
+ filePath,
2754
+ stateCutoffIndex: previousCutoffIndex != null ? previousCutoffIndex + cutoffIndex - 1 : cutoffIndex
2755
+ };
2756
+ }
2757
+ /**
2758
+ * Check if an error (possibly wrapped in MiddlewareError layers) is a
2759
+ * ContextOverflowError by walking the `cause` chain.
2760
+ */
2761
+ function isContextOverflow(err) {
2762
+ let cause = err;
2763
+ while (cause != null) {
2764
+ if (ContextOverflowError.isInstance(cause)) return true;
2765
+ cause = typeof cause === "object" && cause !== null && "cause" in cause ? cause.cause : void 0;
2766
+ }
2767
+ return false;
2768
+ }
2769
+ async function performSummarization(request, handler, truncatedMessages, resolvedModel, maxInputTokens) {
2770
+ const cutoffIndex = determineCutoffIndex(truncatedMessages, maxInputTokens);
2771
+ if (cutoffIndex <= 0) return handler({
2772
+ ...request,
2773
+ messages: truncatedMessages
2774
+ });
2775
+ const messagesToSummarize = truncatedMessages.slice(0, cutoffIndex);
2776
+ const preservedMessages = truncatedMessages.slice(cutoffIndex);
2777
+ if (preservedMessages.length === 0 && maxInputTokens) {
2778
+ const compact = compactToolResults(truncatedMessages, maxInputTokens, request.systemMessage, request.tools);
2779
+ if (compact.modified) try {
2780
+ return await handler({
2781
+ ...request,
2782
+ messages: compact.messages
2783
+ });
2784
+ } catch (err) {
2785
+ if (!isContextOverflow(err)) throw err;
2786
+ }
2787
+ }
2788
+ const previousEvent = request.state._summarizationEvent;
2789
+ const previousCutoffIndex = previousEvent != null ? previousEvent.cutoffIndex : void 0;
2790
+ const { summaryMessage, filePath, stateCutoffIndex } = await summarizeMessages(messagesToSummarize, resolvedModel, request.state, previousCutoffIndex, cutoffIndex);
2791
+ let modifiedMessages = [summaryMessage, ...preservedMessages];
2792
+ const modifiedTokens = countTotalTokens(modifiedMessages, request.systemMessage, request.tools);
2793
+ let finalStateCutoffIndex = stateCutoffIndex;
2794
+ let finalSummaryMessage = summaryMessage;
2795
+ let finalFilePath = filePath;
2796
+ try {
2797
+ await handler({
2798
+ ...request,
2799
+ messages: modifiedMessages
2800
+ });
2801
+ } catch (err) {
2802
+ if (!isContextOverflow(err)) throw err;
2803
+ if (maxInputTokens && modifiedTokens > 0) {
2804
+ const observedRatio = maxInputTokens / modifiedTokens;
2805
+ if (observedRatio > tokenEstimationMultiplier) tokenEstimationMultiplier = observedRatio * 1.1;
2806
+ }
2807
+ const reSumResult = await summarizeMessages([...messagesToSummarize, ...preservedMessages], resolvedModel, request.state, previousCutoffIndex, truncatedMessages.length);
2808
+ finalSummaryMessage = reSumResult.summaryMessage;
2809
+ finalFilePath = reSumResult.filePath;
2810
+ finalStateCutoffIndex = reSumResult.stateCutoffIndex;
2811
+ modifiedMessages = [reSumResult.summaryMessage];
2812
+ await handler({
2813
+ ...request,
2814
+ messages: modifiedMessages
2815
+ });
2816
+ }
2817
+ return new Command({ update: {
2818
+ _summarizationEvent: {
2819
+ cutoffIndex: finalStateCutoffIndex,
2820
+ summaryMessage: finalSummaryMessage,
2821
+ filePath: finalFilePath
2822
+ },
2823
+ _summarizationSessionId: getSessionId(request.state)
2824
+ } });
2825
+ }
2826
+ return createMiddleware({
2827
+ name: "SummarizationMiddleware",
2828
+ stateSchema: SummarizationStateSchema,
2829
+ async wrapModelCall(request, handler) {
2830
+ const effectiveMessages = getEffectiveMessages(request.messages ?? [], request.state);
2831
+ if (effectiveMessages.length === 0) return handler(request);
2832
+ /**
2833
+ * Resolve the chat model and get max input tokens from its profile.
2834
+ */
2835
+ const resolvedModel = await getChatModel();
2836
+ const maxInputTokens = getMaxInputTokens(resolvedModel);
2837
+ applyModelDefaults(resolvedModel);
2838
+ /**
2839
+ * Step 1: Truncate args if configured
2840
+ */
2841
+ const { messages: truncatedMessages } = truncateArgs(effectiveMessages, maxInputTokens, request.systemMessage, request.tools);
2842
+ /**
2843
+ * Step 2: Check if summarization should happen.
2844
+ * Count tokens including system message and tools to match what's
2845
+ * actually sent to the model (matching Python implementation).
2846
+ */
2847
+ const totalTokens = countTotalTokens(truncatedMessages, request.systemMessage, request.tools);
2848
+ /**
2849
+ * If no summarization needed, try passing through.
2850
+ * If the handler throws a ContextOverflowError, fall back to
2851
+ * emergency summarization (matching Python's behavior).
2852
+ */
2853
+ if (!shouldSummarize(truncatedMessages, totalTokens, maxInputTokens)) try {
2854
+ return await handler({
2855
+ ...request,
2856
+ messages: truncatedMessages
2857
+ });
2858
+ } catch (err) {
2859
+ if (!isContextOverflow(err)) throw err;
2860
+ if (maxInputTokens && totalTokens > 0) {
2861
+ const observedRatio = maxInputTokens / totalTokens;
2862
+ if (observedRatio > tokenEstimationMultiplier) tokenEstimationMultiplier = observedRatio * 1.1;
2863
+ }
2864
+ }
2865
+ /**
2866
+ * Step 3: Perform summarization
2867
+ */
2868
+ return performSummarization(request, handler, truncatedMessages, resolvedModel, maxInputTokens);
2869
+ }
2870
+ });
2871
+ }
2257
2872
 
2258
2873
  //#endregion
2259
2874
  //#region src/backends/store.ts
@@ -3336,6 +3951,311 @@ var CompositeBackend = class {
3336
3951
  }
3337
3952
  };
3338
3953
 
3954
+ //#endregion
3955
+ //#region src/backends/local-shell.ts
3956
+ /**
3957
+ * LocalShellBackend: Node.js implementation of the filesystem backend with unrestricted local shell execution.
3958
+ *
3959
+ * This backend extends FilesystemBackend to add shell command execution on the local
3960
+ * host system. It provides NO sandboxing or isolation - all operations run directly
3961
+ * on the host machine with full system access.
3962
+ *
3963
+ * @module
3964
+ */
3965
+ /**
3966
+ * Filesystem backend with unrestricted local shell command execution.
3967
+ *
3968
+ * This backend extends FilesystemBackend to add shell command execution
3969
+ * capabilities. Commands are executed directly on the host system without any
3970
+ * sandboxing, process isolation, or security restrictions.
3971
+ *
3972
+ * **Security Warning:**
3973
+ * This backend grants agents BOTH direct filesystem access AND unrestricted
3974
+ * shell execution on your local machine. Use with extreme caution and only in
3975
+ * appropriate environments.
3976
+ *
3977
+ * **Appropriate use cases:**
3978
+ * - Local development CLIs (coding assistants, development tools)
3979
+ * - Personal development environments where you trust the agent's code
3980
+ * - CI/CD pipelines with proper secret management
3981
+ *
3982
+ * **Inappropriate use cases:**
3983
+ * - Production environments (e.g., web servers, APIs, multi-tenant systems)
3984
+ * - Processing untrusted user input or executing untrusted code
3985
+ *
3986
+ * Use StateBackend, StoreBackend, or extend BaseSandbox for production.
3987
+ *
3988
+ * @example
3989
+ * ```typescript
3990
+ * import { LocalShellBackend } from "@langchain/deepagents";
3991
+ *
3992
+ * // Create backend with explicit environment
3993
+ * const backend = new LocalShellBackend({
3994
+ * rootDir: "/home/user/project",
3995
+ * env: { PATH: "/usr/bin:/bin" },
3996
+ * });
3997
+ *
3998
+ * // Execute shell commands (runs directly on host)
3999
+ * const result = await backend.execute("ls -la");
4000
+ * console.log(result.output);
4001
+ * console.log(result.exitCode);
4002
+ *
4003
+ * // Use filesystem operations (inherited from FilesystemBackend)
4004
+ * const content = await backend.read("/README.md");
4005
+ * await backend.write("/output.txt", "Hello world");
4006
+ *
4007
+ * // Inherit all environment variables
4008
+ * const backend2 = new LocalShellBackend({
4009
+ * rootDir: "/home/user/project",
4010
+ * inheritEnv: true,
4011
+ * });
4012
+ * ```
4013
+ */
4014
+ var LocalShellBackend = class LocalShellBackend extends FilesystemBackend {
4015
+ #timeout;
4016
+ #maxOutputBytes;
4017
+ #env;
4018
+ #sandboxId;
4019
+ #initialized = false;
4020
+ constructor(options = {}) {
4021
+ const { rootDir, virtualMode = false, timeout = 120, maxOutputBytes = 1e5, env, inheritEnv = false } = options;
4022
+ super({
4023
+ rootDir,
4024
+ virtualMode,
4025
+ maxFileSizeMb: 10
4026
+ });
4027
+ this.#timeout = timeout;
4028
+ this.#maxOutputBytes = maxOutputBytes;
4029
+ const bytes = new Uint8Array(4);
4030
+ crypto.getRandomValues(bytes);
4031
+ this.#sandboxId = `local-${[...bytes].map((b) => b.toString(16).padStart(2, "0")).join("")}`;
4032
+ if (inheritEnv) {
4033
+ this.#env = { ...process.env };
4034
+ if (env) Object.assign(this.#env, env);
4035
+ } else this.#env = env ?? {};
4036
+ }
4037
+ /** Unique identifier for this backend instance (format: "local-{random_hex}"). */
4038
+ get id() {
4039
+ return this.#sandboxId;
4040
+ }
4041
+ /** Whether the backend has been initialized and is ready to use. */
4042
+ get isInitialized() {
4043
+ return this.#initialized;
4044
+ }
4045
+ /** Alias for `isInitialized`, matching the standard sandbox interface. */
4046
+ get isRunning() {
4047
+ return this.#initialized;
4048
+ }
4049
+ /**
4050
+ * Initialize the backend by ensuring the rootDir exists.
4051
+ *
4052
+ * Creates the rootDir (and any parent directories) if it does not already
4053
+ * exist. Safe to call on an existing directory. Must be called before
4054
+ * `execute()`, or use the static `LocalShellBackend.create()` factory.
4055
+ *
4056
+ * @throws {SandboxError} If already initialized (`ALREADY_INITIALIZED`)
4057
+ */
4058
+ async initialize() {
4059
+ if (this.#initialized) throw new SandboxError("Backend is already initialized. Each LocalShellBackend instance can only be initialized once.", "ALREADY_INITIALIZED");
4060
+ await fs.mkdir(this.cwd, { recursive: true });
4061
+ this.#initialized = true;
4062
+ }
4063
+ /**
4064
+ * Mark the backend as no longer running.
4065
+ *
4066
+ * For local shell backends there is no remote resource to tear down,
4067
+ * so this simply flips the `isRunning` / `isInitialized` flag.
4068
+ */
4069
+ async close() {
4070
+ this.#initialized = false;
4071
+ }
4072
+ /**
4073
+ * Read a file, adapting error messages to the standard sandbox format.
4074
+ */
4075
+ async read(filePath, offset = 0, limit = 500) {
4076
+ const result = await super.read(filePath, offset, limit);
4077
+ if (typeof result === "string" && result.startsWith("Error reading file") && result.includes("ENOENT")) return `Error: File '${filePath}' not found`;
4078
+ return result;
4079
+ }
4080
+ /**
4081
+ * Edit a file, adapting error messages to the standard sandbox format.
4082
+ */
4083
+ async edit(filePath, oldString, newString, replaceAll = false) {
4084
+ const result = await super.edit(filePath, oldString, newString, replaceAll);
4085
+ if (result.error?.includes("ENOENT")) return {
4086
+ ...result,
4087
+ error: `Error: File '${filePath}' not found`
4088
+ };
4089
+ return result;
4090
+ }
4091
+ /**
4092
+ * List directory contents, returning paths relative to rootDir.
4093
+ */
4094
+ async lsInfo(dirPath) {
4095
+ const results = await super.lsInfo(dirPath);
4096
+ if (this.virtualMode) return results;
4097
+ const cwdPrefix = this.cwd.endsWith(path.sep) ? this.cwd : this.cwd + path.sep;
4098
+ return results.map((info) => ({
4099
+ ...info,
4100
+ path: info.path.startsWith(cwdPrefix) ? info.path.slice(cwdPrefix.length) : info.path
4101
+ }));
4102
+ }
4103
+ /**
4104
+ * Glob matching that returns relative paths and includes directories.
4105
+ */
4106
+ async globInfo(pattern, searchPath = "/") {
4107
+ if (pattern.startsWith("/")) pattern = pattern.substring(1);
4108
+ const resolvedSearchPath = searchPath === "/" || searchPath === "" ? this.cwd : this.virtualMode ? path.resolve(this.cwd, searchPath.replace(/^\//, "")) : path.resolve(this.cwd, searchPath);
4109
+ try {
4110
+ if (!(await fs.stat(resolvedSearchPath)).isDirectory()) return [];
4111
+ } catch {
4112
+ return [];
4113
+ }
4114
+ const formatPath = (rel) => this.virtualMode ? `/${rel}` : rel;
4115
+ const globOpts = {
4116
+ cwd: resolvedSearchPath,
4117
+ absolute: false,
4118
+ dot: true
4119
+ };
4120
+ const [fileMatches, dirMatches] = await Promise.all([fg(pattern, {
4121
+ ...globOpts,
4122
+ onlyFiles: true
4123
+ }), fg(pattern, {
4124
+ ...globOpts,
4125
+ onlyDirectories: true
4126
+ })]);
4127
+ const statFile = async (match) => {
4128
+ try {
4129
+ const entryStat = await fs.stat(path.join(resolvedSearchPath, match));
4130
+ if (entryStat.isFile()) return {
4131
+ path: formatPath(match),
4132
+ is_dir: false,
4133
+ size: entryStat.size,
4134
+ modified_at: entryStat.mtime.toISOString()
4135
+ };
4136
+ } catch {}
4137
+ return null;
4138
+ };
4139
+ const statDir = async (match) => {
4140
+ try {
4141
+ const entryStat = await fs.stat(path.join(resolvedSearchPath, match));
4142
+ if (entryStat.isDirectory()) return {
4143
+ path: formatPath(match),
4144
+ is_dir: true,
4145
+ size: 0,
4146
+ modified_at: entryStat.mtime.toISOString()
4147
+ };
4148
+ } catch {}
4149
+ return null;
4150
+ };
4151
+ const [fileInfos, dirInfos] = await Promise.all([Promise.all(fileMatches.map(statFile)), Promise.all(dirMatches.map(statDir))]);
4152
+ const results = [...fileInfos, ...dirInfos].filter((info) => info !== null);
4153
+ results.sort((a, b) => a.path.localeCompare(b.path));
4154
+ return results;
4155
+ }
4156
+ /**
4157
+ * Execute a shell command directly on the host system.
4158
+ *
4159
+ * Commands are executed directly on your host system using `spawn()`
4160
+ * with `shell: true`. There is NO sandboxing, isolation, or security
4161
+ * restrictions. The command runs with your user's full permissions.
4162
+ *
4163
+ * The command is executed using the system shell with the working directory
4164
+ * set to the backend's rootDir. Stdout and stderr are combined into a single
4165
+ * output stream, with stderr lines prefixed with `[stderr]`.
4166
+ *
4167
+ * @param command - Shell command string to execute
4168
+ * @returns ExecuteResponse containing output, exit code, and truncation flag
4169
+ */
4170
+ async execute(command) {
4171
+ if (!command || typeof command !== "string") return {
4172
+ output: "Error: Command must be a non-empty string.",
4173
+ exitCode: 1,
4174
+ truncated: false
4175
+ };
4176
+ return new Promise((resolve) => {
4177
+ let stdout = "";
4178
+ let stderr = "";
4179
+ let timedOut = false;
4180
+ const child = cp.spawn(command, {
4181
+ shell: true,
4182
+ env: this.#env,
4183
+ cwd: this.cwd
4184
+ });
4185
+ const timer = setTimeout(() => {
4186
+ timedOut = true;
4187
+ child.kill("SIGTERM");
4188
+ }, this.#timeout * 1e3);
4189
+ child.stdout.on("data", (data) => {
4190
+ stdout += data.toString();
4191
+ });
4192
+ child.stderr.on("data", (data) => {
4193
+ stderr += data.toString();
4194
+ });
4195
+ child.on("error", (err) => {
4196
+ clearTimeout(timer);
4197
+ resolve({
4198
+ output: `Error executing command: ${err.message}`,
4199
+ exitCode: 1,
4200
+ truncated: false
4201
+ });
4202
+ });
4203
+ child.on("close", (code, signal) => {
4204
+ clearTimeout(timer);
4205
+ if (timedOut || signal === "SIGTERM") {
4206
+ resolve({
4207
+ output: `Error: Command timed out after ${this.#timeout.toFixed(1)} seconds.`,
4208
+ exitCode: 124,
4209
+ truncated: false
4210
+ });
4211
+ return;
4212
+ }
4213
+ const outputParts = [];
4214
+ if (stdout) outputParts.push(stdout);
4215
+ if (stderr) {
4216
+ const stderrLines = stderr.trim().split("\n");
4217
+ outputParts.push(...stderrLines.map((line) => `[stderr] ${line}`));
4218
+ }
4219
+ let output = outputParts.length > 0 ? outputParts.join("\n") : "<no output>";
4220
+ let truncated = false;
4221
+ if (output.length > this.#maxOutputBytes) {
4222
+ output = output.slice(0, this.#maxOutputBytes);
4223
+ output += `\n\n... Output truncated at ${this.#maxOutputBytes} bytes.`;
4224
+ truncated = true;
4225
+ }
4226
+ const exitCode = code ?? 1;
4227
+ if (exitCode !== 0) output = `${output.trimEnd()}\n\nExit code: ${exitCode}`;
4228
+ resolve({
4229
+ output,
4230
+ exitCode,
4231
+ truncated
4232
+ });
4233
+ });
4234
+ });
4235
+ }
4236
+ /**
4237
+ * Create and initialize a new LocalShellBackend in one step.
4238
+ *
4239
+ * This is the recommended way to create a backend when the rootDir may
4240
+ * not exist yet. It combines construction and initialization (ensuring
4241
+ * rootDir exists) into a single async operation.
4242
+ *
4243
+ * @param options - Configuration options for the backend
4244
+ * @returns An initialized and ready-to-use backend
4245
+ */
4246
+ static async create(options = {}) {
4247
+ const { initialFiles, ...backendOptions } = options;
4248
+ const backend = new LocalShellBackend(backendOptions);
4249
+ await backend.initialize();
4250
+ if (initialFiles) {
4251
+ const encoder = new TextEncoder();
4252
+ const files = Object.entries(initialFiles).map(([filePath, content]) => [filePath, encoder.encode(content)]);
4253
+ await backend.uploadFiles(files);
4254
+ }
4255
+ return backend;
4256
+ }
4257
+ };
4258
+
3339
4259
  //#endregion
3340
4260
  //#region src/backends/sandbox.ts
3341
4261
  /**
@@ -3677,7 +4597,7 @@ const BASE_PROMPT = `In order to complete the objective that the user asks of yo
3677
4597
  * - Todo management (todoListMiddleware)
3678
4598
  * - Filesystem tools (createFilesystemMiddleware)
3679
4599
  * - Subagent delegation (createSubAgentMiddleware)
3680
- * - Conversation summarization (summarizationMiddleware)
4600
+ * - Conversation summarization (createSummarizationMiddleware) with backend offloading
3681
4601
  * - Prompt caching (anthropicPromptCachingMiddleware)
3682
4602
  * - Tool call patching (createPatchToolCallsMiddleware)
3683
4603
  * - Human-in-the-loop (humanInTheLoopMiddleware) - optional
@@ -3765,21 +4685,26 @@ function createDeepAgent(params = {}) {
3765
4685
  /**
3766
4686
  * Middleware for custom subagents (does NOT include skills from main agent).
3767
4687
  * Custom subagents must define their own `skills` property to get skills.
4688
+ *
4689
+ * Uses createSummarizationMiddleware (deepagents version) with backend support
4690
+ * and auto-computed defaults from model profile, matching Python's create_deep_agent.
4691
+ * When trigger is not provided, defaults are lazily computed:
4692
+ * - With model profile: fraction-based (trigger=0.85, keep=0.10)
4693
+ * - Without profile: fixed (trigger=170k tokens, keep=6 messages)
3768
4694
  */
3769
4695
  const subagentMiddleware = [
3770
4696
  todoListMiddleware(),
3771
4697
  createFilesystemMiddleware({ backend: filesystemBackend }),
3772
- summarizationMiddleware({
4698
+ createSummarizationMiddleware({
3773
4699
  model,
3774
- trigger: { tokens: 17e4 },
3775
- keep: { messages: 6 }
4700
+ backend: filesystemBackend
3776
4701
  }),
3777
4702
  anthropicPromptCachingMiddleware({ unsupportedModelBehavior: "ignore" }),
3778
4703
  createPatchToolCallsMiddleware()
3779
4704
  ];
3780
4705
  /**
3781
4706
  * Return as DeepAgent with proper DeepAgentTypeConfig
3782
- * - Response: TResponse (from responseFormat parameter)
4707
+ * - Response: InferStructuredResponse<TResponse> (unwraps ToolStrategy<T>/ProviderStrategy<T> → T)
3783
4708
  * - State: undefined (state comes from middleware)
3784
4709
  * - Context: ContextSchema
3785
4710
  * - Middleware: AllMiddleware (built-in + custom + subagent middleware for state inference)
@@ -3803,10 +4728,9 @@ function createDeepAgent(params = {}) {
3803
4728
  subagents: processedSubagents,
3804
4729
  generalPurposeAgent: true
3805
4730
  }),
3806
- summarizationMiddleware({
4731
+ createSummarizationMiddleware({
3807
4732
  model,
3808
- trigger: { tokens: 17e4 },
3809
- keep: { messages: 6 }
4733
+ backend: filesystemBackend
3810
4734
  }),
3811
4735
  anthropicPromptCachingMiddleware({ unsupportedModelBehavior: "ignore" }),
3812
4736
  createPatchToolCallsMiddleware()
@@ -3816,7 +4740,7 @@ function createDeepAgent(params = {}) {
3816
4740
  ...interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : [],
3817
4741
  ...customMiddleware
3818
4742
  ],
3819
- responseFormat,
4743
+ ...responseFormat != null && { responseFormat },
3820
4744
  contextSchema,
3821
4745
  checkpointer,
3822
4746
  store,
@@ -4369,5 +5293,5 @@ function listSkills(options) {
4369
5293
  }
4370
5294
 
4371
5295
  //#endregion
4372
- export { BaseSandbox, CompositeBackend, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, FilesystemBackend, GENERAL_PURPOSE_SUBAGENT, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, SandboxError, StateBackend, StoreBackend, TASK_SYSTEM_PROMPT, createAgentMemoryMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, filesValue, findProjectRoot, isSandboxBackend, listSkills, parseSkillMetadata };
5296
+ export { BaseSandbox, CompositeBackend, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, FilesystemBackend, GENERAL_PURPOSE_SUBAGENT, LocalShellBackend, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, SandboxError, StateBackend, StoreBackend, TASK_SYSTEM_PROMPT, computeSummarizationDefaults, createAgentMemoryMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, createSummarizationMiddleware, filesValue, findProjectRoot, isSandboxBackend, listSkills, parseSkillMetadata };
4373
5297
  //# sourceMappingURL=index.js.map