@townco/agent 0.1.88 → 0.1.101

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.
Files changed (32) hide show
  1. package/dist/acp-server/adapter.d.ts +49 -0
  2. package/dist/acp-server/adapter.js +693 -5
  3. package/dist/acp-server/http.d.ts +7 -0
  4. package/dist/acp-server/http.js +53 -6
  5. package/dist/definition/index.d.ts +29 -0
  6. package/dist/definition/index.js +24 -0
  7. package/dist/runner/agent-runner.d.ts +16 -1
  8. package/dist/runner/agent-runner.js +2 -1
  9. package/dist/runner/e2b-sandbox-manager.d.ts +18 -0
  10. package/dist/runner/e2b-sandbox-manager.js +99 -0
  11. package/dist/runner/hooks/executor.d.ts +3 -1
  12. package/dist/runner/hooks/executor.js +21 -1
  13. package/dist/runner/hooks/predefined/compaction-tool.js +67 -2
  14. package/dist/runner/hooks/types.d.ts +5 -0
  15. package/dist/runner/index.d.ts +11 -0
  16. package/dist/runner/langchain/index.d.ts +10 -0
  17. package/dist/runner/langchain/index.js +227 -7
  18. package/dist/runner/langchain/model-factory.js +28 -1
  19. package/dist/runner/langchain/tools/artifacts.js +6 -3
  20. package/dist/runner/langchain/tools/e2b.d.ts +54 -0
  21. package/dist/runner/langchain/tools/e2b.js +360 -0
  22. package/dist/runner/langchain/tools/filesystem.js +63 -0
  23. package/dist/runner/langchain/tools/subagent.d.ts +8 -0
  24. package/dist/runner/langchain/tools/subagent.js +76 -4
  25. package/dist/runner/langchain/tools/web_search.d.ts +36 -14
  26. package/dist/runner/langchain/tools/web_search.js +33 -2
  27. package/dist/runner/session-context.d.ts +20 -0
  28. package/dist/runner/session-context.js +54 -0
  29. package/dist/runner/tools.d.ts +2 -2
  30. package/dist/runner/tools.js +1 -0
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +8 -7
@@ -8,13 +8,14 @@ import { z } from "zod";
8
8
  import { SUBAGENT_MODE_KEY } from "../../acp-server/adapter";
9
9
  import { createLogger } from "../../logger.js";
10
10
  import { telemetry } from "../../telemetry/index.js";
11
- import { bindGeneratorToSessionContext } from "../session-context";
11
+ import { bindGeneratorToAbortSignal, bindGeneratorToSessionContext, getAbortSignal, runWithAbortSignal, } from "../session-context";
12
12
  import { loadCustomToolModule, } from "../tool-loader.js";
13
13
  import { createModelFromString, detectProvider } from "./model-factory.js";
14
14
  import { makeOtelCallbacks } from "./otel-callbacks.js";
15
15
  import { makeArtifactsTools } from "./tools/artifacts";
16
16
  import { makeBrowserTools } from "./tools/browser";
17
17
  import { makeDocumentExtractTool } from "./tools/document_extract";
18
+ import { makeTownE2BTools } from "./tools/e2b";
18
19
  import { makeFilesystemTools } from "./tools/filesystem";
19
20
  import { makeGenerateImageTool, makeTownGenerateImageTool, } from "./tools/generate_image";
20
21
  import { SUBAGENT_TOOL_NAME } from "./tools/subagent";
@@ -44,6 +45,7 @@ export const TOOL_REGISTRY = {
44
45
  town_generate_image: () => makeTownGenerateImageTool(),
45
46
  browser: () => makeBrowserTools(),
46
47
  document_extract: () => makeDocumentExtractTool(),
48
+ town_e2b: () => makeTownE2BTools(),
47
49
  };
48
50
  // ============================================================================
49
51
  // Custom tool loading
@@ -82,6 +84,12 @@ export class LangchainAgent {
82
84
  return telemetry.bindGeneratorToContext(sessionAttributes, generator);
83
85
  }
84
86
  async *invokeInternal(req) {
87
+ // Start with the base generator
88
+ let generator = this.invokeWithContext(req);
89
+ // Bind abort signal if available (for cancellation support)
90
+ if (req.abortSignal) {
91
+ generator = bindGeneratorToAbortSignal(req.abortSignal, generator);
92
+ }
85
93
  // Set up session context for session-scoped file storage
86
94
  // This allows tools to access getSessionContext() / getToolOutputDir()
87
95
  if (req.agentDir && req.sessionId) {
@@ -95,11 +103,9 @@ export class LangchainAgent {
95
103
  sessionDir,
96
104
  artifactsDir,
97
105
  };
98
- const boundGenerator = bindGeneratorToSessionContext(sessionContext, this.invokeWithContext(req));
99
- return yield* boundGenerator;
106
+ generator = bindGeneratorToSessionContext(sessionContext, generator);
100
107
  }
101
- // Fallback: no session context (e.g., missing agentDir)
102
- return yield* this.invokeWithContext(req);
108
+ return yield* generator;
103
109
  }
104
110
  async *invokeWithContext(req) {
105
111
  // Derive the parent OTEL context for this invocation.
@@ -384,6 +390,10 @@ export class LangchainAgent {
384
390
  const noSession = req.sessionMeta?.[SUBAGENT_MODE_KEY] === true; // Subagents don't have session storage
385
391
  // Track cumulative tool output tokens in this turn for proper context calculation
386
392
  let cumulativeToolOutputTokens = 0;
393
+ // Counter for subagent calls - used to create unique source ID ranges
394
+ // Each subagent call gets a unique offset (1000, 2000, 3000, etc.)
395
+ // to ensure sources never conflict with parent's sources (typically < 100)
396
+ let subagentCallCounter = 0;
387
397
  let wrappedTools = enabledTools;
388
398
  if (hasToolResponseHook && !noSession) {
389
399
  const { countToolResultTokens } = await import("../../utils/token-counter.js");
@@ -426,6 +436,7 @@ export class LangchainAgent {
426
436
  maxTokens,
427
437
  percentage: (currentTokens / maxTokens) * 100,
428
438
  model: this.definition.model,
439
+ agent: this.definition,
429
440
  toolResponse: {
430
441
  toolCallId: "pending",
431
442
  toolName: originalTool.name,
@@ -510,6 +521,65 @@ export class LangchainAgent {
510
521
  allowedToolNames.has(t.name));
511
522
  });
512
523
  }
524
+ // Wrap the subagent tool to re-number citation sources
525
+ // This ensures sources from subagents get unique IDs that don't conflict
526
+ // with the parent agent's own sources or other subagent calls
527
+ // Each subagent call gets a unique ID range (1000+, 2000+, etc.)
528
+ finalTools = finalTools.map((t) => {
529
+ if (t.name !== SUBAGENT_TOOL_NAME) {
530
+ return t;
531
+ }
532
+ const wrappedFunc = async (input) => {
533
+ const result = (await t.invoke(input));
534
+ // Check if result has sources to re-number
535
+ if (!result ||
536
+ typeof result !== "object" ||
537
+ !Array.isArray(result.sources) ||
538
+ result.sources.length === 0) {
539
+ return result;
540
+ }
541
+ // Increment subagent call counter and calculate base offset
542
+ subagentCallCounter++;
543
+ const baseOffset = subagentCallCounter * 1000;
544
+ let sourceIndex = 0;
545
+ // Create ID mapping and re-number sources with offset
546
+ const idMapping = new Map();
547
+ const renumberedSources = result.sources.map((source) => {
548
+ sourceIndex++;
549
+ const newId = String(baseOffset + sourceIndex);
550
+ idMapping.set(source.id, newId);
551
+ return { ...source, id: newId };
552
+ });
553
+ // Update citation references in the text [[oldId]] -> [[newId]]
554
+ let updatedText = result.text;
555
+ for (const [oldId, newId] of idMapping) {
556
+ const pattern = new RegExp(`\\[\\[${oldId}\\]\\]`, "g");
557
+ updatedText = updatedText.replace(pattern, `[[${newId}]]`);
558
+ }
559
+ _logger.info("Re-numbered subagent citation sources", {
560
+ subagentCall: subagentCallCounter,
561
+ originalCount: result.sources.length,
562
+ idRange: `${baseOffset + 1}-${baseOffset + sourceIndex}`,
563
+ });
564
+ return { text: updatedText, sources: renumberedSources };
565
+ };
566
+ // Create new tool with wrapped function
567
+ // biome-ignore lint/suspicious/noExplicitAny: Need to pass function with dynamic signature
568
+ const wrappedTool = tool(wrappedFunc, {
569
+ name: t.name,
570
+ description: t.description,
571
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing internal schema property
572
+ schema: t.schema,
573
+ });
574
+ // Preserve metadata
575
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
576
+ wrappedTool.prettyName = t.prettyName;
577
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
578
+ wrappedTool.icon = t.icon;
579
+ // biome-ignore lint/suspicious/noExplicitAny: Need to preserve subagentConfigs for metadata
580
+ wrappedTool.subagentConfigs = t.subagentConfigs;
581
+ return wrappedTool;
582
+ });
513
583
  // Create the model instance using the factory
514
584
  // This detects the provider from the model string:
515
585
  // - "gemini-2.0-flash" → Google Generative AI
@@ -532,6 +602,32 @@ export class LangchainAgent {
532
602
  if (hasTodoWrite) {
533
603
  agentConfig.systemPrompt = `${agentConfig.systemPrompt ?? ""}\n\n${TODO_WRITE_INSTRUCTIONS}`;
534
604
  }
605
+ // Inject citation instructions when web search tools are enabled
606
+ const hasWebSearch = builtInNames.includes("web_search") ||
607
+ builtInNames.includes("town_web_search");
608
+ if (hasWebSearch) {
609
+ agentConfig.systemPrompt = `${agentConfig.systemPrompt ?? ""}\n\n${CITATION_INSTRUCTIONS}`;
610
+ }
611
+ // Inject citation instructions when library/document retrieval tools are enabled
612
+ // These are typically MCP tools for searching and retrieving internal documents
613
+ const hasLibraryTools = finalTools.some((t) => t.name.startsWith("library__") ||
614
+ t.name.includes("get_document") ||
615
+ t.name.includes("retrieve_document") ||
616
+ t.name.includes("search_document"));
617
+ if (hasLibraryTools && !hasWebSearch) {
618
+ // Only add if not already covered by web search instructions
619
+ agentConfig.systemPrompt = `${agentConfig.systemPrompt ?? ""}\n\n${DOCUMENT_CITATION_INSTRUCTIONS}`;
620
+ }
621
+ else if (hasLibraryTools && hasWebSearch) {
622
+ // Append document-specific instructions to existing citation instructions
623
+ agentConfig.systemPrompt = `${agentConfig.systemPrompt ?? ""}\n\n${DOCUMENT_CITATION_ADDENDUM}`;
624
+ }
625
+ // Inject subagent citation instructions when agent has subagent tools
626
+ // This tells the parent agent to preserve citations from subagent responses
627
+ const hasSubagentTool = finalTools.some((t) => t.name === SUBAGENT_TOOL_NAME);
628
+ if (hasSubagentTool) {
629
+ agentConfig.systemPrompt = `${agentConfig.systemPrompt ?? ""}\n\n${SUBAGENT_CITATION_INSTRUCTIONS}`;
630
+ }
535
631
  // Process template variables in system prompt and inject current date/time
536
632
  if (agentConfig.systemPrompt) {
537
633
  const currentDateTime = getCurrentDateTimeString();
@@ -540,6 +636,18 @@ export class LangchainAgent {
540
636
  // Replace {{.CurrentDateTime}} template variable (alias)
541
637
  agentConfig.systemPrompt = agentConfig.systemPrompt.replace(/\{\{\.CurrentDateTime\}\}/g, currentDateTime);
542
638
  }
639
+ // Apply prompt parameters from request (user-selected per-message options)
640
+ if (req.promptParameters && this.definition.promptParameters) {
641
+ for (const param of this.definition.promptParameters) {
642
+ const selectedOptionId = req.promptParameters[param.id];
643
+ if (selectedOptionId) {
644
+ const option = param.options.find((o) => o.id === selectedOptionId);
645
+ if (option?.systemPromptAddendum) {
646
+ agentConfig.systemPrompt = `${agentConfig.systemPrompt ?? ""}\n\n${option.systemPromptAddendum}`;
647
+ }
648
+ }
649
+ }
650
+ }
543
651
  const agent = createAgent(agentConfig);
544
652
  // Build messages from context history if available, otherwise use just the prompt
545
653
  // Type includes tool messages for sending tool results
@@ -687,11 +795,17 @@ export class LangchainAgent {
687
795
  }
688
796
  // Create the stream within the invocation context so AsyncLocalStorage
689
797
  // propagates the context to all tool executions and callbacks
798
+ _logger.info("Starting agent.stream", {
799
+ messageCount: messages.length,
800
+ effectiveModel,
801
+ sessionId: req.sessionId,
802
+ });
690
803
  const stream = context.with(invocationContext, () => agent.stream({ messages }, {
691
804
  streamMode: ["updates", "messages"],
692
805
  recursionLimit: 200,
693
806
  callbacks: [otelCallbacks],
694
807
  }));
808
+ _logger.info("agent.stream created, starting iteration");
695
809
  // Use manual iteration with Promise.race to interleave stream events with subagent updates
696
810
  const streamIterator = (await stream)[Symbol.asyncIterator]();
697
811
  let streamDone = false;
@@ -961,6 +1075,10 @@ export class LangchainAgent {
961
1075
  continue; // Skip the rest of the processing for this chunk
962
1076
  }
963
1077
  if (typeof aiMessage.content === "string") {
1078
+ console.log("[DEBUG] Yielding agent_message_chunk (string)", {
1079
+ content: aiMessage.content.slice(0, 100),
1080
+ hasTokenUsage: !!messageTokenUsage,
1081
+ });
964
1082
  const msgToYield = messageTokenUsage
965
1083
  ? {
966
1084
  sessionUpdate: "agent_message_chunk",
@@ -982,8 +1100,16 @@ export class LangchainAgent {
982
1100
  yield msgToYield;
983
1101
  }
984
1102
  else if (Array.isArray(aiMessage.content)) {
1103
+ console.log("[DEBUG] Processing array content", {
1104
+ partCount: aiMessage.content.length,
1105
+ partTypes: aiMessage.content.map((p) => p.type),
1106
+ });
985
1107
  for (const part of aiMessage.content) {
986
1108
  if (part.type === "text" && typeof part.text === "string") {
1109
+ console.log("[DEBUG] Yielding agent_message_chunk (array text)", {
1110
+ text: part.text.slice(0, 100),
1111
+ hasTokenUsage: !!messageTokenUsage,
1112
+ });
987
1113
  const msgToYield = messageTokenUsage
988
1114
  ? {
989
1115
  sessionUpdate: "agent_message_chunk",
@@ -1280,6 +1406,61 @@ I've found some existing telemetry code. Let me mark the first todo as in_progre
1280
1406
  [Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]
1281
1407
  </example>
1282
1408
  `.trim();
1409
+ // System prompt instructions to inject when WebSearch/WebFetch tools are enabled
1410
+ const CITATION_INSTRUCTIONS = `
1411
+ # Citations
1412
+ When referencing information from web search results or fetched web pages, you MUST cite your sources using double-bracket notation: [[1]], [[2]], etc.
1413
+
1414
+ Each number corresponds to a source from your search results. Always cite when:
1415
+ - Quoting or paraphrasing information from a source
1416
+ - Stating facts or statistics from search results
1417
+ - Referencing specific claims from fetched pages
1418
+
1419
+ Example:
1420
+ "According to recent reports, the company's revenue increased by 15% [[1]]. This growth was primarily driven by their expansion into new markets [[2]]."
1421
+
1422
+ Place citations immediately after the relevant claim or at the end of the sentence. Use multiple citations if information comes from multiple sources.
1423
+ `.trim();
1424
+ // System prompt instructions for agents that use document retrieval tools (library, internal docs, etc.)
1425
+ const DOCUMENT_CITATION_INSTRUCTIONS = `
1426
+ # Citations
1427
+ When referencing information from retrieved documents, you MUST cite your sources using double-bracket notation: [[1]], [[2]], etc.
1428
+
1429
+ The document_id from each retrieved document serves as its citation number. Always cite when:
1430
+ - Quoting or paraphrasing information from a document
1431
+ - Stating facts or data from retrieved documents
1432
+ - Referencing specific claims or findings
1433
+
1434
+ Example:
1435
+ If you retrieve a document with document_id 19448 titled "Q3 Financial Report", cite it as:
1436
+ "Revenue grew by 20% in Q3 [[19448]], exceeding analyst expectations."
1437
+
1438
+ When summarizing multiple documents, cite each source appropriately:
1439
+ "The product roadmap [[19448]] aligns with the strategy outlined in the board memo [[19449]]."
1440
+ `.trim();
1441
+ // Addendum for agents that have both web search and document retrieval tools
1442
+ const DOCUMENT_CITATION_ADDENDUM = `
1443
+ # Document Citations
1444
+ In addition to web search citations, when referencing information from retrieved internal documents, cite using the document_id:
1445
+ - Retrieved documents include a document_id field - use this as the citation number
1446
+ - Example: "According to the internal memo [[19448]], the project is on track."
1447
+ `.trim();
1448
+ // System prompt instructions for agents that use subagents with web search capabilities
1449
+ const SUBAGENT_CITATION_INSTRUCTIONS = `
1450
+ # Preserving Citations from Subagents
1451
+ When subagents return results that include citations (in the format [[N]]), you MUST preserve these citations in your final response. The subagent's output includes a "sources" array that maps citation numbers to their source URLs.
1452
+
1453
+ Important guidelines:
1454
+ - When summarizing or quoting information from a subagent's response, preserve the original citation numbers exactly as they appear (e.g., [[1001]], [[1002]])
1455
+ - These citation numbers link to sources that will be displayed to the user
1456
+ - Do not remove or renumber citations - the numbers are already unique across all subagent responses
1457
+ - If you paraphrase information that had a citation, include that citation with your paraphrase
1458
+ - Facts, statistics, quotes, and specific claims from subagent responses should retain their citations
1459
+
1460
+ Example:
1461
+ If a subagent returns: "Revenue increased 15% [[1001]] driven by cloud growth [[1002]]"
1462
+ Your response should preserve those citations: "The company saw 15% revenue growth [[1001]], primarily from cloud services [[1002]]."
1463
+ `.trim();
1283
1464
  /**
1284
1465
  * Returns a human-readable string of the current date and time.
1285
1466
  * Example: "Monday, December 9, 2024 at 2:30 PM PST"
@@ -1299,6 +1480,35 @@ const getCurrentDateTimeString = () => {
1299
1480
  };
1300
1481
  // Re-export subagent tool utility
1301
1482
  export { makeSubagentsTool } from "./tools/subagent.js";
1483
+ /**
1484
+ * Get metadata for children tools of a built-in tool group.
1485
+ * This dynamically loads the tool factory and extracts metadata from each tool.
1486
+ * Returns undefined if the tool is not a group (returns single tool or not found).
1487
+ */
1488
+ export function getToolGroupChildren(toolName) {
1489
+ const entry = TOOL_REGISTRY[toolName];
1490
+ if (!entry) {
1491
+ return undefined;
1492
+ }
1493
+ // If it's a function, call it to get the tool(s)
1494
+ if (typeof entry === "function") {
1495
+ const result = entry();
1496
+ // Check if it returns an array (tool group)
1497
+ if (Array.isArray(result)) {
1498
+ return result.map((tool) => ({
1499
+ name: tool.name,
1500
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing custom property on LangChain tool
1501
+ prettyName: tool.prettyName || tool.name,
1502
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing custom property on LangChain tool
1503
+ icon: tool.icon,
1504
+ }));
1505
+ }
1506
+ // Single tool factory - not a group
1507
+ return undefined;
1508
+ }
1509
+ // Direct tool reference - not a group
1510
+ return undefined;
1511
+ }
1302
1512
  /**
1303
1513
  * Wraps a LangChain tool with OpenTelemetry tracing and session_id injection.
1304
1514
  * This ensures the tool executes within its own span context,
@@ -1318,6 +1528,12 @@ function wrapToolWithTracing(originalTool, getIterationContext, sessionId) {
1318
1528
  ];
1319
1529
  const needsSessionId = TOOLS_NEEDING_SESSION_ID.includes(originalTool.name);
1320
1530
  const wrappedFunc = async (input) => {
1531
+ // Capture the abort signal from the current context BEFORE LangChain breaks AsyncLocalStorage
1532
+ const abortSignal = getAbortSignal();
1533
+ // Check if already cancelled before starting tool
1534
+ if (abortSignal?.aborted) {
1535
+ throw new Error(`Tool ${originalTool.name} cancelled before execution`);
1536
+ }
1321
1537
  // Inject session_id if needed
1322
1538
  let finalInput = input;
1323
1539
  if (needsSessionId && sessionId) {
@@ -1347,8 +1563,12 @@ function wrapToolWithTracing(originalTool, getIterationContext, sessionId) {
1347
1563
  ? trace.setSpan(iterationContext, toolSpan)
1348
1564
  : iterationContext;
1349
1565
  try {
1350
- // Execute within the tool span's context
1351
- const result = await context.with(spanContext, () => originalTool.invoke(finalInput));
1566
+ // Execute within the tool span's context AND with abort signal context restored
1567
+ const executeWithContext = () => context.with(spanContext, () => originalTool.invoke(finalInput));
1568
+ // Wrap with abort signal context if available
1569
+ const result = abortSignal
1570
+ ? await runWithAbortSignal(abortSignal, executeWithContext)
1571
+ : await executeWithContext();
1352
1572
  const resultStr = typeof result === "string" ? result : JSON.stringify(result);
1353
1573
  if (toolSpan) {
1354
1574
  telemetry.setSpanAttributes(toolSpan, {
@@ -1,3 +1,5 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
1
3
  import { ChatAnthropic } from "@langchain/anthropic";
2
4
  import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
3
5
  import { ChatVertexAI } from "@langchain/google-vertexai";
@@ -104,11 +106,36 @@ export function createModelFromString(modelString) {
104
106
  case "openai":
105
107
  throw new Error("OpenAI provider is not yet supported. Please install @langchain/openai and add implementation.");
106
108
  case "anthropic":
107
- case "claude":
109
+ case "claude": {
110
+ // Read the expected key from .env file for comparison
111
+ const envPath = path.resolve(process.cwd(), ".env");
112
+ let expectedKey = "";
113
+ try {
114
+ const envContents = fs.readFileSync(envPath, "utf8");
115
+ const match = envContents.match(/ANTHROPIC_API_KEY="?([^"\n]+)"?/);
116
+ if (match) {
117
+ expectedKey = match[1] || "";
118
+ }
119
+ }
120
+ catch {
121
+ expectedKey = "[could not read .env]";
122
+ }
123
+ const actualKey = process.env.ANTHROPIC_API_KEY || "";
124
+ console.log("[DEBUG] Creating Anthropic model", {
125
+ modelName,
126
+ hasApiKey: !!actualKey,
127
+ keysMatch: actualKey === expectedKey,
128
+ actualKeyLength: actualKey.length,
129
+ expectedKeyLength: expectedKey.length,
130
+ actualKeyPrefix: actualKey.slice(0, 15),
131
+ expectedKeyPrefix: expectedKey.slice(0, 15),
132
+ cwd: process.cwd(),
133
+ });
108
134
  return new ChatAnthropic({
109
135
  model: modelName,
110
136
  // Use default Anthropic settings
111
137
  });
138
+ }
112
139
  default:
113
140
  // Fallback to Anthropic for unknown providers (backward compatibility)
114
141
  logger.warn(`Unknown provider "${provider}" in model string "${modelString}". Defaulting to Anthropic.`);
@@ -31,7 +31,10 @@ import { z } from "zod";
31
31
  function getArtifactsKeyPrefix() {
32
32
  // 1. Explicit override (highest priority)
33
33
  const explicitPrefix = process.env.ARTIFACTS_KEY_PREFIX;
34
- if (explicitPrefix) {
34
+ // Check for valid prefix (not empty, not just quotes from env parsing)
35
+ if (explicitPrefix &&
36
+ explicitPrefix !== '""' &&
37
+ explicitPrefix.trim() !== "") {
35
38
  return explicitPrefix.replace(/\/+$/, ""); // Remove trailing slashes
36
39
  }
37
40
  // 2. Check deployment vs local mode
@@ -393,8 +396,8 @@ const artifactsUrl = tool(async ({ session_id, path, }) => {
393
396
  try {
394
397
  validateStoragePath(path);
395
398
  const storageKey = buildStorageKey(session_id, path);
396
- const _url = await generateSignedUrl(storageKey);
397
- return `Signed URL for ${path}`;
399
+ const url = await generateSignedUrl(storageKey);
400
+ return `Signed URL for ${path}:\n${url}`;
398
401
  }
399
402
  catch (error) {
400
403
  return handleStorageError(error, `Failed to generate URL for ${path}`);
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Create E2B tools using Town proxy authentication.
4
+ * Fetches E2B API key from Town server using user credentials.
5
+ */
6
+ export declare function makeTownE2BTools(): readonly [import("langchain").DynamicStructuredTool<z.ZodObject<{
7
+ code: z.ZodString;
8
+ language: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
9
+ javascript: "javascript";
10
+ python: "python";
11
+ }>>>;
12
+ }, z.core.$strip>, {
13
+ code: string;
14
+ language: "javascript" | "python";
15
+ }, {
16
+ code: string;
17
+ language?: "javascript" | "python" | undefined;
18
+ }, string>, import("langchain").DynamicStructuredTool<z.ZodObject<{
19
+ command: z.ZodString;
20
+ }, z.core.$strip>, {
21
+ command: string;
22
+ }, {
23
+ command: string;
24
+ }, string>, import("langchain").DynamicStructuredTool<z.ZodObject<{
25
+ path: z.ZodString;
26
+ }, z.core.$strip>, {
27
+ path: string;
28
+ }, {
29
+ path: string;
30
+ }, string>, import("langchain").DynamicStructuredTool<z.ZodObject<{
31
+ path: z.ZodString;
32
+ content: z.ZodString;
33
+ }, z.core.$strip>, {
34
+ path: string;
35
+ content: string;
36
+ }, {
37
+ path: string;
38
+ content: string;
39
+ }, string>, import("langchain").DynamicStructuredTool<z.ZodObject<{
40
+ sandboxPath: z.ZodString;
41
+ fileName: z.ZodOptional<z.ZodString>;
42
+ }, z.core.$strip>, {
43
+ sandboxPath: string;
44
+ fileName?: string | undefined;
45
+ }, {
46
+ sandboxPath: string;
47
+ fileName?: string | undefined;
48
+ }, string>, import("langchain").DynamicStructuredTool<z.ZodObject<{
49
+ document_id: z.ZodString;
50
+ }, z.core.$strip>, {
51
+ document_id: string;
52
+ }, {
53
+ document_id: string;
54
+ }, string>];