deepagents 1.8.0 → 1.8.1

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.d.cts CHANGED
@@ -1125,17 +1125,47 @@ declare function createSummarizationMiddleware(options: SummarizationMiddlewareO
1125
1125
  }, z$1.core.$strip>, undefined, unknown, readonly (ClientTool | ServerTool)[]>;
1126
1126
  //#endregion
1127
1127
  //#region src/backends/store.d.ts
1128
+ /**
1129
+ * Options for StoreBackend constructor.
1130
+ */
1131
+ interface StoreBackendOptions {
1132
+ /**
1133
+ * Custom namespace for store operations.
1134
+ *
1135
+ * Determines where files are stored in the LangGraph store, enabling
1136
+ * user-scoped, org-scoped, or any custom isolation pattern.
1137
+ *
1138
+ * If not provided, falls back to legacy behavior using assistantId from StateAndStore.
1139
+ *
1140
+ * @example
1141
+ * ```typescript
1142
+ * // User-scoped storage
1143
+ * new StoreBackend(stateAndStore, {
1144
+ * namespace: ["memories", orgId, userId, "filesystem"],
1145
+ * });
1146
+ *
1147
+ * // Org-scoped storage
1148
+ * new StoreBackend(stateAndStore, {
1149
+ * namespace: ["memories", orgId, "filesystem"],
1150
+ * });
1151
+ * ```
1152
+ */
1153
+ namespace?: string[];
1154
+ }
1128
1155
  /**
1129
1156
  * Backend that stores files in LangGraph's BaseStore (persistent).
1130
1157
  *
1131
1158
  * Uses LangGraph's Store for persistent, cross-conversation storage.
1132
1159
  * Files are organized via namespaces and persist across all threads.
1133
1160
  *
1134
- * The namespace can include an optional assistant_id for multi-agent isolation.
1161
+ * The namespace can be customized via a factory function for flexible
1162
+ * isolation patterns (user-scoped, org-scoped, etc.), or falls back
1163
+ * to legacy assistant_id-based isolation.
1135
1164
  */
1136
1165
  declare class StoreBackend implements BackendProtocol {
1137
1166
  private stateAndStore;
1138
- constructor(stateAndStore: StateAndStore);
1167
+ private _namespace;
1168
+ constructor(stateAndStore: StateAndStore, options?: StoreBackendOptions);
1139
1169
  /**
1140
1170
  * Get the store instance.
1141
1171
  *
@@ -1146,9 +1176,11 @@ declare class StoreBackend implements BackendProtocol {
1146
1176
  /**
1147
1177
  * Get the namespace for store operations.
1148
1178
  *
1149
- * If an assistant_id is available in stateAndStore, return
1150
- * [assistant_id, "filesystem"] to provide per-assistant isolation.
1151
- * Otherwise return ["filesystem"].
1179
+ * If a custom namespace was provided, returns it directly.
1180
+ *
1181
+ * Otherwise, falls back to legacy behavior:
1182
+ * - If assistantId is set: [assistantId, "filesystem"]
1183
+ * - Otherwise: ["filesystem"]
1152
1184
  */
1153
1185
  protected getNamespace(): string[];
1154
1186
  /**
@@ -1713,6 +1745,9 @@ declare abstract class BaseSandbox implements SandboxBackendProtocol {
1713
1745
  *
1714
1746
  * Uses downloadFiles() to read, performs string replacement in TypeScript,
1715
1747
  * then uploadFiles() to write back. No runtime needed on the sandbox host.
1748
+ *
1749
+ * Memory-conscious: releases intermediate references early so the GC can
1750
+ * reclaim buffers before the next large allocation is made.
1716
1751
  */
1717
1752
  edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean): Promise<EditResult>;
1718
1753
  }
@@ -2394,5 +2429,5 @@ declare function parseSkillMetadata(skillMdPath: string, source: "user" | "proje
2394
2429
  */
2395
2430
  declare function listSkills(options: ListSkillsOptions): SkillMetadata[];
2396
2431
  //#endregion
2397
- export { type AgentMemoryMiddlewareOptions, type BackendFactory, type BackendProtocol, BaseSandbox, type CompiledSubAgent, CompositeBackend, type CreateDeepAgentParams, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, type DeepAgent, type DeepAgentTypeConfig, type DefaultDeepAgentTypeConfig, type EditResult, type ExecuteResponse, type ExtractSubAgentMiddleware, type FileData, type FileDownloadResponse, type FileInfo, type FileOperationError, type FileUploadResponse, FilesystemBackend, type FilesystemMiddlewareOptions, type FlattenSubAgentMiddleware, GENERAL_PURPOSE_SUBAGENT, type GrepMatch, type InferDeepAgentSubagents, type InferDeepAgentType, type InferStructuredResponse, type InferSubAgentMiddlewareStates, type InferSubagentByName, type InferSubagentReactAgentType, type ListSkillsOptions, type SkillMetadata as LoaderSkillMetadata, LocalShellBackend, type LocalShellBackendOptions, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, type MaybePromise, type MemoryMiddlewareOptions, type MergedDeepAgentState, type ResolveDeepAgentTypeConfig, type SandboxBackendProtocol, type SandboxDeleteOptions, SandboxError, type SandboxErrorCode, type SandboxGetOrCreateOptions, type SandboxInfo, type SandboxListOptions, type SandboxListResponse, type Settings, type SettingsOptions, type SkillMetadata$1 as SkillMetadata, type SkillsMiddlewareOptions, StateBackend, StoreBackend, type SubAgent, type SubAgentMiddlewareOptions, type SupportedResponseFormat, TASK_SYSTEM_PROMPT, type WriteResult, computeSummarizationDefaults, createAgentMemoryMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, createSummarizationMiddleware, filesValue, findProjectRoot, isSandboxBackend, listSkills, parseSkillMetadata };
2432
+ export { type AgentMemoryMiddlewareOptions, type BackendFactory, type BackendProtocol, BaseSandbox, type CompiledSubAgent, CompositeBackend, type CreateDeepAgentParams, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, type DeepAgent, type DeepAgentTypeConfig, type DefaultDeepAgentTypeConfig, type EditResult, type ExecuteResponse, type ExtractSubAgentMiddleware, type FileData, type FileDownloadResponse, type FileInfo, type FileOperationError, type FileUploadResponse, FilesystemBackend, type FilesystemMiddlewareOptions, type FlattenSubAgentMiddleware, GENERAL_PURPOSE_SUBAGENT, type GrepMatch, type InferDeepAgentSubagents, type InferDeepAgentType, type InferStructuredResponse, type InferSubAgentMiddlewareStates, type InferSubagentByName, type InferSubagentReactAgentType, type ListSkillsOptions, type SkillMetadata as LoaderSkillMetadata, LocalShellBackend, type LocalShellBackendOptions, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, type MaybePromise, type MemoryMiddlewareOptions, type MergedDeepAgentState, type ResolveDeepAgentTypeConfig, type SandboxBackendProtocol, type SandboxDeleteOptions, SandboxError, type SandboxErrorCode, type SandboxGetOrCreateOptions, type SandboxInfo, type SandboxListOptions, type SandboxListResponse, type Settings, type SettingsOptions, type SkillMetadata$1 as SkillMetadata, type SkillsMiddlewareOptions, StateBackend, StoreBackend, type StoreBackendOptions, type SubAgent, type SubAgentMiddlewareOptions, type SupportedResponseFormat, TASK_SYSTEM_PROMPT, type WriteResult, computeSummarizationDefaults, createAgentMemoryMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, createSummarizationMiddleware, filesValue, findProjectRoot, isSandboxBackend, listSkills, parseSkillMetadata };
2398
2433
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1125,17 +1125,47 @@ declare function createSummarizationMiddleware(options: SummarizationMiddlewareO
1125
1125
  }, z$1.core.$strip>, undefined, unknown, readonly (ClientTool | ServerTool)[]>;
1126
1126
  //#endregion
1127
1127
  //#region src/backends/store.d.ts
1128
+ /**
1129
+ * Options for StoreBackend constructor.
1130
+ */
1131
+ interface StoreBackendOptions {
1132
+ /**
1133
+ * Custom namespace for store operations.
1134
+ *
1135
+ * Determines where files are stored in the LangGraph store, enabling
1136
+ * user-scoped, org-scoped, or any custom isolation pattern.
1137
+ *
1138
+ * If not provided, falls back to legacy behavior using assistantId from StateAndStore.
1139
+ *
1140
+ * @example
1141
+ * ```typescript
1142
+ * // User-scoped storage
1143
+ * new StoreBackend(stateAndStore, {
1144
+ * namespace: ["memories", orgId, userId, "filesystem"],
1145
+ * });
1146
+ *
1147
+ * // Org-scoped storage
1148
+ * new StoreBackend(stateAndStore, {
1149
+ * namespace: ["memories", orgId, "filesystem"],
1150
+ * });
1151
+ * ```
1152
+ */
1153
+ namespace?: string[];
1154
+ }
1128
1155
  /**
1129
1156
  * Backend that stores files in LangGraph's BaseStore (persistent).
1130
1157
  *
1131
1158
  * Uses LangGraph's Store for persistent, cross-conversation storage.
1132
1159
  * Files are organized via namespaces and persist across all threads.
1133
1160
  *
1134
- * The namespace can include an optional assistant_id for multi-agent isolation.
1161
+ * The namespace can be customized via a factory function for flexible
1162
+ * isolation patterns (user-scoped, org-scoped, etc.), or falls back
1163
+ * to legacy assistant_id-based isolation.
1135
1164
  */
1136
1165
  declare class StoreBackend implements BackendProtocol {
1137
1166
  private stateAndStore;
1138
- constructor(stateAndStore: StateAndStore);
1167
+ private _namespace;
1168
+ constructor(stateAndStore: StateAndStore, options?: StoreBackendOptions);
1139
1169
  /**
1140
1170
  * Get the store instance.
1141
1171
  *
@@ -1146,9 +1176,11 @@ declare class StoreBackend implements BackendProtocol {
1146
1176
  /**
1147
1177
  * Get the namespace for store operations.
1148
1178
  *
1149
- * If an assistant_id is available in stateAndStore, return
1150
- * [assistant_id, "filesystem"] to provide per-assistant isolation.
1151
- * Otherwise return ["filesystem"].
1179
+ * If a custom namespace was provided, returns it directly.
1180
+ *
1181
+ * Otherwise, falls back to legacy behavior:
1182
+ * - If assistantId is set: [assistantId, "filesystem"]
1183
+ * - Otherwise: ["filesystem"]
1152
1184
  */
1153
1185
  protected getNamespace(): string[];
1154
1186
  /**
@@ -1713,6 +1745,9 @@ declare abstract class BaseSandbox implements SandboxBackendProtocol {
1713
1745
  *
1714
1746
  * Uses downloadFiles() to read, performs string replacement in TypeScript,
1715
1747
  * then uploadFiles() to write back. No runtime needed on the sandbox host.
1748
+ *
1749
+ * Memory-conscious: releases intermediate references early so the GC can
1750
+ * reclaim buffers before the next large allocation is made.
1716
1751
  */
1717
1752
  edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean): Promise<EditResult>;
1718
1753
  }
@@ -2394,5 +2429,5 @@ declare function parseSkillMetadata(skillMdPath: string, source: "user" | "proje
2394
2429
  */
2395
2430
  declare function listSkills(options: ListSkillsOptions): SkillMetadata[];
2396
2431
  //#endregion
2397
- export { type AgentMemoryMiddlewareOptions, type BackendFactory, type BackendProtocol, BaseSandbox, type CompiledSubAgent, CompositeBackend, type CreateDeepAgentParams, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, type DeepAgent, type DeepAgentTypeConfig, type DefaultDeepAgentTypeConfig, type EditResult, type ExecuteResponse, type ExtractSubAgentMiddleware, type FileData, type FileDownloadResponse, type FileInfo, type FileOperationError, type FileUploadResponse, FilesystemBackend, type FilesystemMiddlewareOptions, type FlattenSubAgentMiddleware, GENERAL_PURPOSE_SUBAGENT, type GrepMatch, type InferDeepAgentSubagents, type InferDeepAgentType, type InferStructuredResponse, type InferSubAgentMiddlewareStates, type InferSubagentByName, type InferSubagentReactAgentType, type ListSkillsOptions, type SkillMetadata as LoaderSkillMetadata, LocalShellBackend, type LocalShellBackendOptions, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, type MaybePromise, type MemoryMiddlewareOptions, type MergedDeepAgentState, type ResolveDeepAgentTypeConfig, type SandboxBackendProtocol, type SandboxDeleteOptions, SandboxError, type SandboxErrorCode, type SandboxGetOrCreateOptions, type SandboxInfo, type SandboxListOptions, type SandboxListResponse, type Settings, type SettingsOptions, type SkillMetadata$1 as SkillMetadata, type SkillsMiddlewareOptions, StateBackend, StoreBackend, type SubAgent, type SubAgentMiddlewareOptions, type SupportedResponseFormat, TASK_SYSTEM_PROMPT, type WriteResult, computeSummarizationDefaults, createAgentMemoryMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, createSummarizationMiddleware, filesValue, findProjectRoot, isSandboxBackend, listSkills, parseSkillMetadata };
2432
+ export { type AgentMemoryMiddlewareOptions, type BackendFactory, type BackendProtocol, BaseSandbox, type CompiledSubAgent, CompositeBackend, type CreateDeepAgentParams, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, type DeepAgent, type DeepAgentTypeConfig, type DefaultDeepAgentTypeConfig, type EditResult, type ExecuteResponse, type ExtractSubAgentMiddleware, type FileData, type FileDownloadResponse, type FileInfo, type FileOperationError, type FileUploadResponse, FilesystemBackend, type FilesystemMiddlewareOptions, type FlattenSubAgentMiddleware, GENERAL_PURPOSE_SUBAGENT, type GrepMatch, type InferDeepAgentSubagents, type InferDeepAgentType, type InferStructuredResponse, type InferSubAgentMiddlewareStates, type InferSubagentByName, type InferSubagentReactAgentType, type ListSkillsOptions, type SkillMetadata as LoaderSkillMetadata, LocalShellBackend, type LocalShellBackendOptions, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, type MaybePromise, type MemoryMiddlewareOptions, type MergedDeepAgentState, type ResolveDeepAgentTypeConfig, type SandboxBackendProtocol, type SandboxDeleteOptions, SandboxError, type SandboxErrorCode, type SandboxGetOrCreateOptions, type SandboxInfo, type SandboxListOptions, type SandboxListResponse, type Settings, type SettingsOptions, type SkillMetadata$1 as SkillMetadata, type SkillsMiddlewareOptions, StateBackend, StoreBackend, type StoreBackendOptions, type SubAgent, type SubAgentMiddlewareOptions, type SupportedResponseFormat, TASK_SYSTEM_PROMPT, type WriteResult, computeSummarizationDefaults, createAgentMemoryMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, createSummarizationMiddleware, filesValue, findProjectRoot, isSandboxBackend, listSkills, parseSkillMetadata };
2398
2433
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1049,7 +1049,13 @@ function createFilesystemMiddleware(options = {}) {
1049
1049
  message: new ToolMessage({
1050
1050
  content: TOO_LARGE_TOOL_MSG.replace("{tool_call_id}", msg.tool_call_id).replace("{file_path}", evictPath).replace("{content_sample}", contentSample),
1051
1051
  tool_call_id: msg.tool_call_id,
1052
- name: msg.name
1052
+ name: msg.name,
1053
+ id: msg.id,
1054
+ artifact: msg.artifact,
1055
+ status: msg.status,
1056
+ metadata: msg.metadata,
1057
+ additional_kwargs: msg.additional_kwargs,
1058
+ response_metadata: msg.response_metadata
1053
1059
  }),
1054
1060
  filesUpdate: writeResult.filesUpdate
1055
1061
  };
@@ -1327,16 +1333,28 @@ function filterStateForSubagent(state) {
1327
1333
  return filtered;
1328
1334
  }
1329
1335
  /**
1336
+ * Invalid tool message block types
1337
+ */
1338
+ const INVALID_TOOL_MESSAGE_BLOCK_TYPES = [
1339
+ "tool_use",
1340
+ "thinking",
1341
+ "redacted_thinking"
1342
+ ];
1343
+ /**
1330
1344
  * Create Command with filtered state update from subagent result
1331
1345
  */
1332
1346
  function returnCommandWithStateUpdate(result, toolCallId) {
1333
1347
  const stateUpdate = filterStateForSubagent(result);
1334
1348
  const messages = result.messages;
1335
- const lastMessage = messages?.[messages.length - 1];
1349
+ let content = (messages?.[messages.length - 1])?.content || "Task completed";
1350
+ if (Array.isArray(content)) {
1351
+ content = content.filter((block) => !INVALID_TOOL_MESSAGE_BLOCK_TYPES.includes(block.type));
1352
+ if (content.length === 0) content = "Task completed";
1353
+ }
1336
1354
  return new Command({ update: {
1337
1355
  ...stateUpdate,
1338
1356
  messages: [new ToolMessage({
1339
- content: lastMessage?.content || "Task completed",
1357
+ content,
1340
1358
  tool_call_id: toolCallId,
1341
1359
  name: "task"
1342
1360
  })]
@@ -2658,31 +2676,44 @@ function createSummarizationMiddleware(options) {
2658
2676
  return messages.filter((msg) => !isSummaryMessage(msg));
2659
2677
  }
2660
2678
  /**
2661
- * Offload messages to backend.
2679
+ * Offload messages to backend by appending to the history file.
2680
+ *
2681
+ * Uses uploadFiles() directly with raw byte concatenation instead of
2682
+ * edit() to avoid downloading the file twice and performing a full
2683
+ * string search-and-replace. This keeps peak memory at ~2x file size
2684
+ * (existing bytes + combined bytes) instead of ~6x with the old
2685
+ * download → edit(oldContent, newContent) approach.
2662
2686
  */
2663
2687
  async function offloadToBackend(resolvedBackend, messages, state) {
2664
- const path = getHistoryPath(state);
2688
+ const filePath = getHistoryPath(state);
2665
2689
  const filteredMessages = filterSummaryMessages(messages);
2666
2690
  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;
2691
+ const sectionBytes = new TextEncoder().encode(newSection);
2675
2692
  try {
2693
+ let existingBytes = null;
2694
+ if (resolvedBackend.downloadFiles) try {
2695
+ const responses = await resolvedBackend.downloadFiles([filePath]);
2696
+ if (responses.length > 0 && responses[0].content && !responses[0].error) existingBytes = responses[0].content;
2697
+ } catch {}
2676
2698
  let result;
2677
- if (existingContent) result = await resolvedBackend.edit(path, existingContent, combinedContent);
2678
- else result = await resolvedBackend.write(path, combinedContent);
2699
+ if (existingBytes && resolvedBackend.uploadFiles) {
2700
+ const combined = new Uint8Array(existingBytes.byteLength + sectionBytes.byteLength);
2701
+ combined.set(existingBytes, 0);
2702
+ combined.set(sectionBytes, existingBytes.byteLength);
2703
+ const uploadResults = await resolvedBackend.uploadFiles([[filePath, combined]]);
2704
+ result = uploadResults[0].error ? { error: uploadResults[0].error } : { path: filePath };
2705
+ } else if (!existingBytes) result = await resolvedBackend.write(filePath, newSection);
2706
+ else {
2707
+ const existingContent = new TextDecoder().decode(existingBytes);
2708
+ result = await resolvedBackend.edit(filePath, existingContent, existingContent + newSection);
2709
+ }
2679
2710
  if (result.error) {
2680
- console.warn(`Failed to offload conversation history to ${path}: ${result.error}`);
2711
+ console.warn(`Failed to offload conversation history to ${filePath}: ${result.error}`);
2681
2712
  return null;
2682
2713
  }
2683
- return path;
2714
+ return filePath;
2684
2715
  } catch (e) {
2685
- console.warn(`Exception offloading conversation history to ${path}:`, e);
2716
+ console.warn(`Exception offloading conversation history to ${filePath}:`, e);
2686
2717
  return null;
2687
2718
  }
2688
2719
  }
@@ -2872,18 +2903,43 @@ ${summary}
2872
2903
 
2873
2904
  //#endregion
2874
2905
  //#region src/backends/store.ts
2906
+ const NAMESPACE_COMPONENT_RE = /^[A-Za-z0-9\-_.@+:~]+$/;
2907
+ /**
2908
+ * Validate a namespace array.
2909
+ *
2910
+ * Each component must be a non-empty string containing only safe characters:
2911
+ * alphanumeric (a-z, A-Z, 0-9), hyphen (-), underscore (_), dot (.),
2912
+ * at sign (@), plus (+), colon (:), and tilde (~).
2913
+ *
2914
+ * Characters like *, ?, [, ], {, } etc. are rejected to prevent
2915
+ * wildcard or glob injection in store lookups.
2916
+ */
2917
+ function validateNamespace(namespace) {
2918
+ if (namespace.length === 0) throw new Error("Namespace array must not be empty.");
2919
+ for (let i = 0; i < namespace.length; i++) {
2920
+ const component = namespace[i];
2921
+ if (typeof component !== "string") throw new TypeError(`Namespace component at index ${i} must be a string, got ${typeof component}.`);
2922
+ if (!component) throw new Error(`Namespace component at index ${i} must not be empty.`);
2923
+ if (!NAMESPACE_COMPONENT_RE.test(component)) throw new Error(`Namespace component at index ${i} contains disallowed characters: "${component}". Only alphanumeric characters, hyphens, underscores, dots, @, +, colons, and tildes are allowed.`);
2924
+ }
2925
+ return namespace;
2926
+ }
2875
2927
  /**
2876
2928
  * Backend that stores files in LangGraph's BaseStore (persistent).
2877
2929
  *
2878
2930
  * Uses LangGraph's Store for persistent, cross-conversation storage.
2879
2931
  * Files are organized via namespaces and persist across all threads.
2880
2932
  *
2881
- * The namespace can include an optional assistant_id for multi-agent isolation.
2933
+ * The namespace can be customized via a factory function for flexible
2934
+ * isolation patterns (user-scoped, org-scoped, etc.), or falls back
2935
+ * to legacy assistant_id-based isolation.
2882
2936
  */
2883
2937
  var StoreBackend = class {
2884
2938
  stateAndStore;
2885
- constructor(stateAndStore) {
2939
+ _namespace;
2940
+ constructor(stateAndStore, options) {
2886
2941
  this.stateAndStore = stateAndStore;
2942
+ if (options?.namespace) this._namespace = validateNamespace(options.namespace);
2887
2943
  }
2888
2944
  /**
2889
2945
  * Get the store instance.
@@ -2899,15 +2955,17 @@ var StoreBackend = class {
2899
2955
  /**
2900
2956
  * Get the namespace for store operations.
2901
2957
  *
2902
- * If an assistant_id is available in stateAndStore, return
2903
- * [assistant_id, "filesystem"] to provide per-assistant isolation.
2904
- * Otherwise return ["filesystem"].
2958
+ * If a custom namespace was provided, returns it directly.
2959
+ *
2960
+ * Otherwise, falls back to legacy behavior:
2961
+ * - If assistantId is set: [assistantId, "filesystem"]
2962
+ * - Otherwise: ["filesystem"]
2905
2963
  */
2906
2964
  getNamespace() {
2907
- const namespace = "filesystem";
2965
+ if (this._namespace) return this._namespace;
2908
2966
  const assistantId = this.stateAndStore.assistantId;
2909
- if (assistantId) return [assistantId, namespace];
2910
- return [namespace];
2967
+ if (assistantId) return [assistantId, "filesystem"];
2968
+ return ["filesystem"];
2911
2969
  }
2912
2970
  /**
2913
2971
  * Convert a store Item to FileData format.
@@ -4567,17 +4625,85 @@ var BaseSandbox = class {
4567
4625
  *
4568
4626
  * Uses downloadFiles() to read, performs string replacement in TypeScript,
4569
4627
  * then uploadFiles() to write back. No runtime needed on the sandbox host.
4628
+ *
4629
+ * Memory-conscious: releases intermediate references early so the GC can
4630
+ * reclaim buffers before the next large allocation is made.
4570
4631
  */
4571
4632
  async edit(filePath, oldString, newString, replaceAll = false) {
4572
4633
  const results = await this.downloadFiles([filePath]);
4573
4634
  if (results[0].error || !results[0].content) return { error: `Error: File '${filePath}' not found` };
4574
4635
  const text = new TextDecoder().decode(results[0].content);
4575
- const count = text.split(oldString).length - 1;
4576
- if (count === 0) return { error: `String not found in file '${filePath}'` };
4577
- if (count > 1 && !replaceAll) return { error: `Multiple occurrences found in '${filePath}'. Use replaceAll=true to replace all.` };
4578
- const newText = replaceAll ? text.split(oldString).join(newString) : text.replace(oldString, newString);
4579
- const encoder = new TextEncoder();
4580
- const uploadResults = await this.uploadFiles([[filePath, encoder.encode(newText)]]);
4636
+ results[0].content = null;
4637
+ /**
4638
+ * are we editing an empty file?
4639
+ */
4640
+ if (oldString.length === 0) {
4641
+ /**
4642
+ * if the file is not empty, we cannot edit it with an empty oldString
4643
+ */
4644
+ if (text.length !== 0) return { error: "oldString must not be empty unless the file is empty" };
4645
+ /**
4646
+ * if the newString is empty, we can just return the file as is
4647
+ */
4648
+ if (newString.length === 0) return {
4649
+ path: filePath,
4650
+ filesUpdate: null,
4651
+ occurrences: 0
4652
+ };
4653
+ /**
4654
+ * if the newString is not empty, we can edit the file
4655
+ */
4656
+ const encoded = new TextEncoder().encode(newString);
4657
+ const uploadResults = await this.uploadFiles([[filePath, encoded]]);
4658
+ /**
4659
+ * if the upload fails, we return an error
4660
+ */
4661
+ if (uploadResults[0].error) return { error: `Failed to write edited file '${filePath}': ${uploadResults[0].error}` };
4662
+ return {
4663
+ path: filePath,
4664
+ filesUpdate: null,
4665
+ occurrences: 1
4666
+ };
4667
+ }
4668
+ const firstIdx = text.indexOf(oldString);
4669
+ if (firstIdx === -1) return { error: `String not found in file '${filePath}'` };
4670
+ if (oldString === newString) return {
4671
+ path: filePath,
4672
+ filesUpdate: null,
4673
+ occurrences: 1
4674
+ };
4675
+ let newText;
4676
+ let count;
4677
+ if (replaceAll) {
4678
+ newText = text.replaceAll(oldString, newString);
4679
+ /**
4680
+ * Derive count from the length delta to avoid a separate O(n) counting pass
4681
+ */
4682
+ const lenDiff = oldString.length - newString.length;
4683
+ if (lenDiff !== 0) count = (text.length - newText.length) / lenDiff;
4684
+ else {
4685
+ /**
4686
+ * Lengths are equal — count via indexOf (we already found the first)
4687
+ */
4688
+ count = 1;
4689
+ let pos = firstIdx + oldString.length;
4690
+ while (pos <= text.length) {
4691
+ const idx = text.indexOf(oldString, pos);
4692
+ if (idx === -1) break;
4693
+ count++;
4694
+ pos = idx + oldString.length;
4695
+ }
4696
+ }
4697
+ } else {
4698
+ if (text.indexOf(oldString, firstIdx + oldString.length) !== -1) return { error: `Multiple occurrences found in '${filePath}'. Use replaceAll=true to replace all.` };
4699
+ count = 1;
4700
+ /**
4701
+ * Build result from the known index — avoids a redundant search by .replace()
4702
+ */
4703
+ newText = text.slice(0, firstIdx) + newString + text.slice(firstIdx + oldString.length);
4704
+ }
4705
+ const encoded = new TextEncoder().encode(newText);
4706
+ const uploadResults = await this.uploadFiles([[filePath, encoded]]);
4581
4707
  if (uploadResults[0].error) return { error: `Failed to write edited file '${filePath}': ${uploadResults[0].error}` };
4582
4708
  return {
4583
4709
  path: filePath,