@townco/agent 0.1.107 → 0.1.109

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.
@@ -1,3 +1,4 @@
1
+ import * as path from "node:path";
1
2
  import { createLogger } from "../logger.js";
2
3
  import { getSessionContext, hasSessionContext } from "./session-context";
3
4
  const logger = createLogger("e2b-sandbox-manager");
@@ -9,16 +10,64 @@ const sandboxActivity = new Map();
9
10
  const cleanupTimeouts = new Map();
10
11
  // Sandbox timeout in milliseconds (default: 15 minutes)
11
12
  const SANDBOX_TIMEOUT_MS = 15 * 60 * 1000;
13
+ /**
14
+ * Collect environment variables that should be passed to E2B sandbox
15
+ * for tool usage (image generation, etc.)
16
+ */
17
+ function collectToolEnvironmentVars() {
18
+ const envs = {};
19
+ // Gemini API key for image generation
20
+ if (process.env.GEMINI_API_KEY) {
21
+ envs.GEMINI_API_KEY = process.env.GEMINI_API_KEY;
22
+ logger.debug("GEMINI_API_KEY configured for sandbox", {
23
+ keyLength: process.env.GEMINI_API_KEY.length,
24
+ });
25
+ }
26
+ else {
27
+ logger.warn("GEMINI_API_KEY not set - image generation will fail");
28
+ }
29
+ return envs;
30
+ }
31
+ /**
32
+ * Get SessionStorage instance for the current session.
33
+ * Returns null if session storage is not available.
34
+ */
35
+ function getSessionStorage() {
36
+ if (!hasSessionContext()) {
37
+ logger.debug("No session context available for SessionStorage");
38
+ return null;
39
+ }
40
+ const { sessionDir, sessionId } = getSessionContext();
41
+ // Derive agentDir from sessionDir
42
+ // sessionDir = <agentDir>/.sessions/<sessionId>
43
+ // so agentDir = dirname(dirname(sessionDir))
44
+ const agentDir = path.dirname(path.dirname(sessionDir));
45
+ // Extract agent name from agentDir (last path component)
46
+ const agentName = path.basename(agentDir);
47
+ // Import SessionStorage dynamically to avoid circular dependency
48
+ try {
49
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
50
+ const { SessionStorage: SessionStorageClass, } = require("../acp-server/session-storage");
51
+ const storage = new SessionStorageClass(agentDir, agentName);
52
+ logger.debug("Created SessionStorage", { sessionId, agentDir, agentName });
53
+ return storage;
54
+ }
55
+ catch (error) {
56
+ logger.error("Failed to create SessionStorage", { error, sessionId });
57
+ return null;
58
+ }
59
+ }
12
60
  /**
13
61
  * Get or create an E2B sandbox for the current session.
14
62
  * Sandboxes are session-scoped and reused across tool calls.
63
+ * Attempts to reconnect to persisted sandboxes when resuming sessions.
15
64
  */
16
65
  export async function getSessionSandbox(apiKey) {
17
66
  if (!hasSessionContext()) {
18
67
  throw new Error("E2B tools require session context");
19
68
  }
20
69
  const { sessionId } = getSessionContext();
21
- // Check for existing sandbox
70
+ // Check for existing in-memory sandbox
22
71
  let sandbox = sessionSandboxes.get(sessionId);
23
72
  if (sandbox) {
24
73
  // Update activity timestamp and reschedule cleanup
@@ -27,10 +76,91 @@ export async function getSessionSandbox(apiKey) {
27
76
  logger.debug("Reusing existing sandbox", { sessionId });
28
77
  return sandbox;
29
78
  }
79
+ // Try to reconnect to persisted sandbox
80
+ const storage = getSessionStorage();
81
+ const persistedSandboxId = storage?.getSandboxId(sessionId);
82
+ if (persistedSandboxId) {
83
+ logger.info("Attempting to reconnect to persisted sandbox", {
84
+ sessionId,
85
+ sandboxId: persistedSandboxId,
86
+ });
87
+ try {
88
+ const { Sandbox: SandboxClass } = await import("@e2b/code-interpreter");
89
+ sandbox = await SandboxClass.connect(persistedSandboxId, { apiKey });
90
+ logger.info("Successfully reconnected to sandbox", {
91
+ sessionId,
92
+ sandboxId: persistedSandboxId,
93
+ });
94
+ // Store in memory and set up activity tracking
95
+ sessionSandboxes.set(sessionId, sandbox);
96
+ sandboxActivity.set(sessionId, Date.now());
97
+ scheduleCleanup(sessionId);
98
+ return sandbox;
99
+ }
100
+ catch (error) {
101
+ logger.warn("Failed to reconnect to persisted sandbox, will create new one", {
102
+ sessionId,
103
+ sandboxId: persistedSandboxId,
104
+ error,
105
+ });
106
+ // Clear the stale sandboxId from storage
107
+ if (storage) {
108
+ try {
109
+ await storage.updateSandboxId(sessionId, undefined);
110
+ }
111
+ catch (updateError) {
112
+ logger.warn("Failed to clear stale sandboxId", {
113
+ sessionId,
114
+ updateError,
115
+ });
116
+ }
117
+ }
118
+ }
119
+ }
30
120
  // Create new sandbox - dynamic import to avoid loading if not used
31
121
  logger.info("Creating new E2B sandbox", { sessionId });
32
122
  const { Sandbox: SandboxClass } = await import("@e2b/code-interpreter");
33
- sandbox = await SandboxClass.create({ apiKey });
123
+ const envs = collectToolEnvironmentVars();
124
+ // Use custom template with pre-installed packages (google-generativeai, etc.)
125
+ const templateId = process.env.E2B_TEMPLATE_ID;
126
+ const config = {
127
+ apiKey,
128
+ envVars: envs,
129
+ };
130
+ if (templateId) {
131
+ config.template = templateId;
132
+ logger.info("Using custom E2B template", { templateId });
133
+ }
134
+ sandbox = await SandboxClass.create(config);
135
+ logger.info("Created new sandbox", {
136
+ sessionId,
137
+ sandboxId: sandbox.sandboxId,
138
+ });
139
+ // Persist the sandboxId for future reconnection
140
+ if (storage) {
141
+ try {
142
+ await storage.updateSandboxId(sessionId, sandbox.sandboxId);
143
+ logger.info("Persisted sandboxId to storage", {
144
+ sessionId,
145
+ sandboxId: sandbox.sandboxId,
146
+ });
147
+ }
148
+ catch (error) {
149
+ logger.error("Failed to persist sandboxId", { sessionId, error });
150
+ }
151
+ }
152
+ else {
153
+ logger.warn("No storage available to persist sandboxId", { sessionId });
154
+ }
155
+ // Configure Python logging to output to stderr by default
156
+ await sandbox.runCode(`
157
+ import logging
158
+ logging.basicConfig(
159
+ level=logging.INFO,
160
+ format='%(levelname)s - %(name)s - %(message)s',
161
+ stream=__import__('sys').stderr
162
+ )
163
+ `.trim(), { language: "python" });
34
164
  sessionSandboxes.set(sessionId, sandbox);
35
165
  sandboxActivity.set(sessionId, Date.now());
36
166
  // Set up auto-cleanup timeout
@@ -39,6 +169,7 @@ export async function getSessionSandbox(apiKey) {
39
169
  }
40
170
  /**
41
171
  * Explicitly destroy a session's sandbox (called on session end).
172
+ * Also clears the persisted sandboxId from storage.
42
173
  */
43
174
  export async function destroySessionSandbox(sessionId) {
44
175
  const sandbox = sessionSandboxes.get(sessionId);
@@ -58,6 +189,22 @@ export async function destroySessionSandbox(sessionId) {
58
189
  clearTimeout(timeout);
59
190
  cleanupTimeouts.delete(sessionId);
60
191
  }
192
+ // Clear persisted sandboxId from storage
193
+ // We do this without requiring session context since this can be called
194
+ // from HTTP endpoints that don't have session context
195
+ try {
196
+ // Try to get storage if we have context, but don't fail if we don't
197
+ if (hasSessionContext()) {
198
+ const storage = getSessionStorage();
199
+ if (storage) {
200
+ await storage.updateSandboxId(sessionId, undefined);
201
+ logger.debug("Cleared persisted sandboxId", { sessionId });
202
+ }
203
+ }
204
+ }
205
+ catch (error) {
206
+ logger.warn("Failed to clear persisted sandboxId", { sessionId, error });
207
+ }
61
208
  }
62
209
  }
63
210
  /**
@@ -91,6 +238,20 @@ function rescheduleCleanup(sessionId) {
91
238
  export function hasSessionSandbox(sessionId) {
92
239
  return sessionSandboxes.has(sessionId);
93
240
  }
241
+ /**
242
+ * Get an existing sandbox by sessionId without creating a new one.
243
+ * Returns undefined if no sandbox exists for this session.
244
+ * Used by HTTP endpoints that don't have session context.
245
+ */
246
+ export function getExistingSandbox(sessionId) {
247
+ const sandbox = sessionSandboxes.get(sessionId);
248
+ if (sandbox) {
249
+ // Update activity timestamp and reschedule cleanup
250
+ sandboxActivity.set(sessionId, Date.now());
251
+ rescheduleCleanup(sessionId);
252
+ }
253
+ return sandbox;
254
+ }
94
255
  /**
95
256
  * Get the number of active sandboxes (for debugging/monitoring).
96
257
  */
@@ -14,6 +14,7 @@ export const compactionTool = async (ctx) => {
14
14
  // Read settings from callbackSetting
15
15
  const settings = ctx.callbackSetting;
16
16
  const threshold = settings?.threshold ?? 80;
17
+ const forceCompact = settings?.forceCompact ?? false;
17
18
  // Calculate effective token usage including pending tool response if present
18
19
  const toolResponseTokens = ctx.toolResponse?.outputTokens ?? 0;
19
20
  const estimatedTokens = ctx.currentTokens + toolResponseTokens;
@@ -21,7 +22,8 @@ export const compactionTool = async (ctx) => {
21
22
  const paddedTokens = applyTokenPadding(estimatedTokens);
22
23
  const effectivePercentage = (paddedTokens / ctx.maxTokens) * 100;
23
24
  // Check if we should run based on context percentage (including tool response)
24
- if (effectivePercentage < threshold) {
25
+ // Skip threshold check if forceCompact is true (used for error recovery)
26
+ if (!forceCompact && effectivePercentage < threshold) {
25
27
  logger.debug("Context below threshold, skipping compaction", {
26
28
  currentTokens: ctx.currentTokens,
27
29
  toolResponseTokens,
@@ -40,6 +42,15 @@ export const compactionTool = async (ctx) => {
40
42
  },
41
43
  };
42
44
  }
45
+ // Log if force compaction was triggered
46
+ if (forceCompact) {
47
+ logger.info("Force compaction triggered (bypassing threshold check)", {
48
+ currentTokens: ctx.currentTokens,
49
+ toolResponseTokens,
50
+ effectivePercentage: `${effectivePercentage.toFixed(2)}%`,
51
+ threshold,
52
+ });
53
+ }
43
54
  logger.info("Compaction tool triggered", {
44
55
  currentTokens: ctx.currentTokens,
45
56
  toolResponseTokens,
@@ -192,44 +203,46 @@ Please provide your summary based on the conversation above, following this stru
192
203
  const kgMatch = summaryText.match(/<knowledge_graph_analysis_needed>(yes|no)<\/knowledge_graph_analysis_needed>/i);
193
204
  needsKnowledgeGraphAnalysis = kgMatch?.[1]?.toLowerCase() === "yes";
194
205
  logger.info(`Knowledge graph analysis decision: needsAnalysis=${needsKnowledgeGraphAnalysis}, matchFound=${!!kgMatch}`);
195
- if (needsKnowledgeGraphAnalysis) {
196
- const apiUrl = process.env.LIBRARY_API_URL;
197
- const apiKey = process.env.LIBRARY_ROOT_API_KEY;
198
- logger.info("Attempting to dispatch to Library API", {
199
- hasApiUrl: !!apiUrl,
200
- hasApiKey: !!apiKey,
201
- });
202
- if (apiUrl && apiKey) {
203
- const serviceUrl = `${apiUrl}/compaction_analysis`;
204
- // Fire-and-forget - don't block compaction on external service
205
- fetch(serviceUrl, {
206
- method: "POST",
207
- headers: {
208
- "Content-Type": "application/json",
209
- Authorization: `Bearer ${apiKey}`,
210
- },
211
- body: JSON.stringify({
212
- conversation_text: conversationText,
213
- timestamp: Date.now(),
214
- }),
215
- })
216
- .then((response) => {
217
- logger.info("Library API response received", {
218
- status: response.status,
219
- ok: response.ok,
220
- });
221
- })
222
- .catch((error) => logger.error("Failed to send compaction for analysis", {
223
- error: error instanceof Error ? error.message : String(error),
224
- }));
225
- }
226
- else {
227
- logger.warn("Skipping Library API dispatch - missing config", {
228
- hasApiUrl: !!apiUrl,
229
- hasApiKey: !!apiKey,
230
- });
231
- }
232
- }
206
+ // TODO: Re-enable graph analysis dispatch when ready
207
+ // if (needsKnowledgeGraphAnalysis) {
208
+ // const apiUrl = process.env.LIBRARY_API_URL;
209
+ // const apiKey = process.env.LIBRARY_API_KEY;
210
+ // logger.info("Attempting to dispatch to Library API", {
211
+ // hasApiUrl: !!apiUrl,
212
+ // hasApiKey: !!apiKey,
213
+ // });
214
+ // if (apiUrl && apiKey) {
215
+ // const serviceUrl = `${apiUrl}/compaction_analysis`;
216
+ // // Fire-and-forget - don't block compaction on external service
217
+ // fetch(serviceUrl, {
218
+ // method: "POST",
219
+ // headers: {
220
+ // "Content-Type": "application/json",
221
+ // Authorization: `Bearer ${apiKey}`,
222
+ // },
223
+ // body: JSON.stringify({
224
+ // conversation_text: conversationText,
225
+ // timestamp: Date.now(),
226
+ // }),
227
+ // })
228
+ // .then((response) => {
229
+ // logger.info("Library API response received", {
230
+ // status: response.status,
231
+ // ok: response.ok,
232
+ // });
233
+ // })
234
+ // .catch((error) =>
235
+ // logger.error("Failed to send compaction for analysis", {
236
+ // error: error instanceof Error ? error.message : String(error),
237
+ // }),
238
+ // );
239
+ // } else {
240
+ // logger.warn("Skipping Library API dispatch - missing config", {
241
+ // hasApiUrl: !!apiUrl,
242
+ // hasApiKey: !!apiKey,
243
+ // });
244
+ // }
245
+ // }
233
246
  }
234
247
  return {
235
248
  newContextEntry,
@@ -1,5 +1,5 @@
1
- import { type AgentRunner } from "./agent-runner";
2
- export type { AgentRunner };
1
+ import { type AgentRunner, type SessionUpdateNotification } from "./agent-runner";
2
+ export type { AgentRunner, SessionUpdateNotification };
3
3
  export declare const makeRunnerFromDefinition: (definition: {
4
4
  displayName?: string | undefined;
5
5
  version?: string | undefined;
@@ -1,4 +1,4 @@
1
- import { zAgentRunnerParams } from "./agent-runner";
1
+ import { zAgentRunnerParams, } from "./agent-runner";
2
2
  import { LangchainAgent } from "./langchain";
3
3
  export const makeRunnerFromDefinition = (definition) => {
4
4
  const agentRunnerParams = zAgentRunnerParams.safeParse(definition);
@@ -5,21 +5,19 @@ import { context, propagation, trace } from "@opentelemetry/api";
5
5
  import { ensureAuthenticated } from "@townco/core/auth";
6
6
  import { AIMessageChunk, createAgent, ToolMessage, tool, } from "langchain";
7
7
  import { z } from "zod";
8
- import { SUBAGENT_MODE_KEY } from "../../acp-server/adapter";
8
+ import { ContextOverflowError, SUBAGENT_MODE_KEY, } from "../../acp-server/adapter";
9
9
  import { createLogger } from "../../logger.js";
10
10
  import { telemetry } from "../../telemetry/index.js";
11
11
  import { calculateContextSize } from "../../utils/context-size-calculator.js";
12
12
  import { getModelContextWindow } from "../hooks/constants.js";
13
- import { bindGeneratorToAbortSignal, bindGeneratorToSessionContext, getAbortSignal, runWithAbortSignal, } from "../session-context";
13
+ import { isContextOverflowError } from "../hooks/predefined/context-validator.js";
14
+ import { bindGeneratorToAbortSignal, bindGeneratorToEmitUpdate, bindGeneratorToSessionContext, getAbortSignal, runWithAbortSignal, } from "../session-context";
14
15
  import { loadCustomToolModule, } from "../tool-loader.js";
15
16
  import { createModelFromString, detectProvider } from "./model-factory.js";
16
17
  import { makeOtelCallbacks } from "./otel-callbacks.js";
17
- import { makeArtifactsTools } from "./tools/artifacts";
18
18
  import { makeBrowserTools } from "./tools/browser";
19
19
  import { makeDocumentExtractTool } from "./tools/document_extract";
20
20
  import { makeTownE2BTools } from "./tools/e2b";
21
- import { makeFilesystemTools } from "./tools/filesystem";
22
- import { makeGenerateImageTool, makeTownGenerateImageTool, } from "./tools/generate_image";
23
21
  import { SUBAGENT_TOOL_NAME } from "./tools/subagent";
24
22
  import { hashQuery, queryToToolCallId, subagentEvents, } from "./tools/subagent-connections";
25
23
  import { makeTodoWriteTool, TODO_WRITE_TOOL_NAME } from "./tools/todo";
@@ -37,17 +35,13 @@ getWeather.prettyName = "Get Weather";
37
35
  // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
38
36
  getWeather.icon = "Cloud";
39
37
  export const TOOL_REGISTRY = {
40
- artifacts: () => makeArtifactsTools(),
41
38
  todo_write: () => makeTodoWriteTool(), // Factory function to create fresh instance per invocation
42
39
  get_weather: getWeather, // TODO: Convert to factory function for full concurrency safety
43
40
  web_search: () => makeWebSearchTools(),
44
41
  town_web_search: () => makeTownWebSearchTools(),
45
- filesystem: () => makeFilesystemTools(),
46
- generate_image: () => makeGenerateImageTool(),
47
- town_generate_image: () => makeTownGenerateImageTool(),
48
42
  browser: () => makeBrowserTools(),
49
43
  document_extract: () => makeDocumentExtractTool(),
50
- code_sandbox: () => makeTownE2BTools(),
44
+ code_sandbox: makeTownE2BTools,
51
45
  };
52
46
  // ============================================================================
53
47
  // Custom tool loading
@@ -92,6 +86,10 @@ export class LangchainAgent {
92
86
  if (req.abortSignal) {
93
87
  generator = bindGeneratorToAbortSignal(req.abortSignal, generator);
94
88
  }
89
+ // Bind emitUpdate callback if available (for file change events)
90
+ if (req.emitUpdate) {
91
+ generator = bindGeneratorToEmitUpdate(req.emitUpdate, generator);
92
+ }
95
93
  // Set up session context for session-scoped file storage
96
94
  // This allows tools to access getSessionContext() / getToolOutputDir()
97
95
  if (req.agentDir && req.sessionId) {
@@ -290,16 +288,6 @@ export class LangchainAgent {
290
288
  typeof t.modulePath === "string") {
291
289
  customToolPaths.push(t.modulePath);
292
290
  }
293
- else if (type === "filesystem") {
294
- // Note: working_directory is ignored - filesystem tools now use session context
295
- const fsTools = makeFilesystemTools();
296
- // Tag filesystem tools with their group name for tool override filtering
297
- for (const fsTool of fsTools) {
298
- // biome-ignore lint/suspicious/noExplicitAny: Need to add custom property for tool grouping
299
- fsTool.__groupName = "filesystem";
300
- }
301
- enabledTools.push(...fsTools);
302
- }
303
291
  else if (type === "direct") {
304
292
  // Handle direct tool objects (imported in code)
305
293
  // biome-ignore lint/suspicious/noExplicitAny: mlai unsure how to best type this
@@ -320,6 +308,31 @@ export class LangchainAgent {
320
308
  for (const name of builtInNames) {
321
309
  const entry = TOOL_REGISTRY[name];
322
310
  if (!entry) {
311
+ // Provide helpful error messages for removed tools
312
+ // Cast to string to allow checking against removed tool names
313
+ const nameStr = name;
314
+ if (nameStr === "filesystem") {
315
+ throw new Error(`The "filesystem" tool has been removed. Use "code_sandbox" instead.\n\n` +
316
+ `Migration:\n` +
317
+ `- Change tools: ["filesystem"] to tools: ["code_sandbox"]\n` +
318
+ `- Read files: Use Sandbox_ReadFile\n` +
319
+ `- Write files: Use Sandbox_WriteFile\n` +
320
+ `- Search files: Use Sandbox_RunBash with grep/find commands`);
321
+ }
322
+ if (nameStr === "artifacts") {
323
+ throw new Error(`The "artifacts" tool has been removed. Use "code_sandbox" with Sandbox_Share instead.\n\n` +
324
+ `Migration:\n` +
325
+ `- Change tools: ["artifacts"] to tools: ["code_sandbox"]\n` +
326
+ `- Share files: Use Sandbox_Share to upload sandbox files to Supabase Storage`);
327
+ }
328
+ if (nameStr === "generate_image" ||
329
+ nameStr === "town_generate_image") {
330
+ throw new Error(`The "${nameStr}" tool has been removed. Use "code_sandbox" with Sandbox_GenerateImage instead.\n\n` +
331
+ `Migration:\n` +
332
+ `- Change tools: ["${nameStr}"] to tools: ["code_sandbox"]\n` +
333
+ `- Generate images: Use Sandbox_GenerateImage (runs in sandbox)\n` +
334
+ `- Share images: Use Sandbox_Share to make images accessible`);
335
+ }
323
336
  throw new Error(`Unknown built-in tool "${name}"`);
324
337
  }
325
338
  const tagTool = (tool) => {
@@ -1127,10 +1140,6 @@ export class LangchainAgent {
1127
1140
  continue; // Skip the rest of the processing for this chunk
1128
1141
  }
1129
1142
  if (typeof aiMessage.content === "string") {
1130
- console.log("[DEBUG] Yielding agent_message_chunk (string)", {
1131
- content: aiMessage.content.slice(0, 100),
1132
- hasTokenUsage: !!messageTokenUsage,
1133
- });
1134
1143
  const msgToYield = messageTokenUsage
1135
1144
  ? {
1136
1145
  sessionUpdate: "agent_message_chunk",
@@ -1152,16 +1161,8 @@ export class LangchainAgent {
1152
1161
  yield msgToYield;
1153
1162
  }
1154
1163
  else if (Array.isArray(aiMessage.content)) {
1155
- console.log("[DEBUG] Processing array content", {
1156
- partCount: aiMessage.content.length,
1157
- partTypes: aiMessage.content.map((p) => p.type),
1158
- });
1159
1164
  for (const part of aiMessage.content) {
1160
1165
  if (part.type === "text" && typeof part.text === "string") {
1161
- console.log("[DEBUG] Yielding agent_message_chunk (array text)", {
1162
- text: part.text.slice(0, 100),
1163
- hasTokenUsage: !!messageTokenUsage,
1164
- });
1165
1166
  const msgToYield = messageTokenUsage
1166
1167
  ? {
1167
1168
  sessionUpdate: "agent_message_chunk",
@@ -1339,6 +1340,15 @@ export class LangchainAgent {
1339
1340
  subagentEvents.off("messages", onSubagentMessages);
1340
1341
  // Clean up any remaining iteration span
1341
1342
  otelCallbacks?.cleanup();
1343
+ // Check if this is a context overflow error - wrap it for retry handling
1344
+ if (isContextOverflowError(error)) {
1345
+ telemetry.log("warn", "Context overflow detected - will attempt compaction and retry", {
1346
+ error: error instanceof Error ? error.message : String(error),
1347
+ sessionId: req.sessionId,
1348
+ });
1349
+ telemetry.endSpan(invocationSpan);
1350
+ throw new ContextOverflowError(error instanceof Error ? error : new Error(String(error)));
1351
+ }
1342
1352
  // Log error and end span with error status
1343
1353
  telemetry.log("error", "Agent invocation failed", {
1344
1354
  error: error instanceof Error ? error.message : String(error),
@@ -1572,12 +1582,7 @@ export function getToolGroupChildren(toolName) {
1572
1582
  */
1573
1583
  function wrapToolWithTracing(originalTool, getIterationContext, sessionId) {
1574
1584
  // Check if this tool needs session_id injection
1575
- const TOOLS_NEEDING_SESSION_ID = [
1576
- "artifacts_cp",
1577
- "artifacts_del",
1578
- "artifacts_ls",
1579
- "artifacts_url",
1580
- ];
1585
+ const TOOLS_NEEDING_SESSION_ID = [];
1581
1586
  const needsSessionId = TOOLS_NEEDING_SESSION_ID.includes(originalTool.name);
1582
1587
  const wrappedFunc = async (input) => {
1583
1588
  // Capture the abort signal from the current context BEFORE LangChain breaks AsyncLocalStorage
@@ -1,4 +1,8 @@
1
1
  import { z } from "zod";
2
+ /**
3
+ * Get E2B API key from Town proxy (with caching).
4
+ */
5
+ export declare function getTownE2BApiKey(): Promise<string>;
2
6
  /**
3
7
  * Create E2B tools using Town proxy authentication.
4
8
  * Fetches E2B API key from Town server using user credentials.
@@ -51,4 +55,10 @@ export declare function makeTownE2BTools(): readonly [import("langchain").Dynami
51
55
  document_id: string;
52
56
  }, {
53
57
  document_id: string;
58
+ }, string>, import("langchain").DynamicStructuredTool<z.ZodObject<{
59
+ prompt: z.ZodString;
60
+ }, z.core.$strip>, {
61
+ prompt: string;
62
+ }, {
63
+ prompt: string;
54
64
  }, string>];