@townco/agent 0.1.82 → 0.1.84

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 (38) hide show
  1. package/dist/acp-server/adapter.js +150 -49
  2. package/dist/acp-server/http.js +56 -1
  3. package/dist/acp-server/session-storage.d.ts +44 -12
  4. package/dist/acp-server/session-storage.js +153 -59
  5. package/dist/definition/index.d.ts +2 -2
  6. package/dist/definition/index.js +1 -1
  7. package/dist/runner/agent-runner.d.ts +4 -2
  8. package/dist/runner/hooks/executor.d.ts +1 -0
  9. package/dist/runner/hooks/executor.js +18 -2
  10. package/dist/runner/hooks/predefined/compaction-tool.js +3 -2
  11. package/dist/runner/hooks/predefined/tool-response-compactor.d.ts +0 -4
  12. package/dist/runner/hooks/predefined/tool-response-compactor.js +30 -16
  13. package/dist/runner/hooks/types.d.ts +4 -5
  14. package/dist/runner/langchain/index.d.ts +1 -0
  15. package/dist/runner/langchain/index.js +156 -33
  16. package/dist/runner/langchain/tools/artifacts.d.ts +68 -0
  17. package/dist/runner/langchain/tools/artifacts.js +466 -0
  18. package/dist/runner/langchain/tools/browser.js +15 -3
  19. package/dist/runner/langchain/tools/filesystem.d.ts +8 -4
  20. package/dist/runner/langchain/tools/filesystem.js +118 -82
  21. package/dist/runner/langchain/tools/generate_image.d.ts +19 -0
  22. package/dist/runner/langchain/tools/generate_image.js +54 -14
  23. package/dist/runner/langchain/tools/subagent.js +2 -2
  24. package/dist/runner/langchain/tools/todo.js +3 -0
  25. package/dist/runner/langchain/tools/web_search.js +6 -0
  26. package/dist/runner/session-context.d.ts +40 -0
  27. package/dist/runner/session-context.js +69 -0
  28. package/dist/runner/tools.d.ts +2 -2
  29. package/dist/runner/tools.js +2 -0
  30. package/dist/scaffold/project-scaffold.js +7 -3
  31. package/dist/telemetry/setup.js +1 -1
  32. package/dist/templates/index.d.ts +1 -1
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/dist/utils/context-size-calculator.d.ts +1 -10
  35. package/dist/utils/context-size-calculator.js +1 -12
  36. package/dist/utils/token-counter.js +2 -2
  37. package/package.json +10 -10
  38. package/templates/index.ts +1 -1
@@ -1,17 +1,21 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import * as path from "node:path";
1
3
  import { MultiServerMCPClient } from "@langchain/mcp-adapters";
2
4
  import { context, propagation, trace } from "@opentelemetry/api";
3
- import { getShedAuth } from "@townco/core/auth";
5
+ import { ensureAuthenticated } from "@townco/core/auth";
4
6
  import { AIMessageChunk, createAgent, ToolMessage, tool, } from "langchain";
5
7
  import { z } from "zod";
6
8
  import { SUBAGENT_MODE_KEY } from "../../acp-server/adapter";
7
9
  import { createLogger } from "../../logger.js";
8
10
  import { telemetry } from "../../telemetry/index.js";
11
+ import { bindGeneratorToSessionContext } from "../session-context";
9
12
  import { loadCustomToolModule, } from "../tool-loader.js";
10
13
  import { createModelFromString, detectProvider } from "./model-factory.js";
11
14
  import { makeOtelCallbacks } from "./otel-callbacks.js";
15
+ import { makeArtifactsTools } from "./tools/artifacts";
12
16
  import { makeBrowserTools } from "./tools/browser";
13
17
  import { makeFilesystemTools } from "./tools/filesystem";
14
- import { makeGenerateImageTool } from "./tools/generate_image";
18
+ import { makeGenerateImageTool, makeTownGenerateImageTool, } from "./tools/generate_image";
15
19
  import { SUBAGENT_TOOL_NAME } from "./tools/subagent";
16
20
  import { hashQuery, queryToToolCallId, subagentEvents, } from "./tools/subagent-connections";
17
21
  import { makeTodoWriteTool, TODO_WRITE_TOOL_NAME } from "./tools/todo";
@@ -24,15 +28,19 @@ const getWeather = tool(({ city }) => `It's always sunny in ${city}!`, {
24
28
  city: z.string(),
25
29
  }),
26
30
  });
31
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
27
32
  getWeather.prettyName = "Get Weather";
33
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
28
34
  getWeather.icon = "Cloud";
29
35
  export const TOOL_REGISTRY = {
36
+ artifacts: () => makeArtifactsTools(),
30
37
  todo_write: () => makeTodoWriteTool(), // Factory function to create fresh instance per invocation
31
38
  get_weather: getWeather, // TODO: Convert to factory function for full concurrency safety
32
39
  web_search: () => makeWebSearchTools(),
33
40
  town_web_search: () => makeTownWebSearchTools(),
34
- filesystem: () => makeFilesystemTools(process.cwd()),
41
+ filesystem: () => makeFilesystemTools(),
35
42
  generate_image: () => makeGenerateImageTool(),
43
+ town_generate_image: () => makeTownGenerateImageTool(),
36
44
  browser: () => makeBrowserTools(),
37
45
  };
38
46
  // ============================================================================
@@ -44,7 +52,9 @@ function toLangchainTool(resolved) {
44
52
  description: resolved.description,
45
53
  schema: resolved.schema,
46
54
  });
55
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
47
56
  t.prettyName = resolved.prettyName;
57
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
48
58
  t.icon = resolved.icon;
49
59
  return t;
50
60
  }
@@ -70,6 +80,26 @@ export class LangchainAgent {
70
80
  return telemetry.bindGeneratorToContext(sessionAttributes, generator);
71
81
  }
72
82
  async *invokeInternal(req) {
83
+ // Set up session context for session-scoped file storage
84
+ // This allows tools to access getSessionContext() / getToolOutputDir()
85
+ if (req.agentDir && req.sessionId) {
86
+ const sessionDir = path.join(req.agentDir, ".sessions", req.sessionId);
87
+ const artifactsDir = path.join(sessionDir, "artifacts");
88
+ // Ensure artifacts directory exists
89
+ await mkdir(artifactsDir, { recursive: true });
90
+ // Bind the generator to session context so every iteration has access
91
+ const sessionContext = {
92
+ sessionId: req.sessionId,
93
+ sessionDir,
94
+ artifactsDir,
95
+ };
96
+ const boundGenerator = bindGeneratorToSessionContext(sessionContext, this.invokeWithContext(req));
97
+ return yield* boundGenerator;
98
+ }
99
+ // Fallback: no session context (e.g., missing agentDir)
100
+ return yield* this.invokeWithContext(req);
101
+ }
102
+ async *invokeWithContext(req) {
73
103
  // Derive the parent OTEL context for this invocation.
74
104
  // If this is a subagent and the parent process propagated an OTEL trace
75
105
  // context via sessionMeta.otelTraceContext, use that as the parent;
@@ -136,6 +166,7 @@ export class LangchainAgent {
136
166
  // Helper to get next subagent update (returns immediately if queued, otherwise waits)
137
167
  const waitForSubagentUpdate = () => {
138
168
  if (subagentUpdateQueue.length > 0) {
169
+ // biome-ignore lint/style/noNonNullAssertion: We check length > 0, so shift() will return a value
139
170
  return Promise.resolve(subagentUpdateQueue.shift());
140
171
  }
141
172
  return new Promise((resolve) => {
@@ -146,6 +177,8 @@ export class LangchainAgent {
146
177
  async function* yieldPendingSubagentUpdates() {
147
178
  while (subagentUpdateQueue.length > 0) {
148
179
  const update = subagentUpdateQueue.shift();
180
+ if (!update)
181
+ continue;
149
182
  _logger.info("Yielding queued subagent connection update", {
150
183
  toolCallId: update.toolCallId,
151
184
  subagentPort: update.port,
@@ -164,6 +197,8 @@ export class LangchainAgent {
164
197
  // Also yield any pending messages updates
165
198
  while (subagentMessagesQueue.length > 0) {
166
199
  const messagesUpdate = subagentMessagesQueue.shift();
200
+ if (!messagesUpdate)
201
+ continue;
167
202
  _logger.info("Yielding queued subagent messages update", {
168
203
  toolCallId: messagesUpdate.toolCallId,
169
204
  messageCount: messagesUpdate.messages.length,
@@ -246,11 +281,11 @@ export class LangchainAgent {
246
281
  customToolPaths.push(t.modulePath);
247
282
  }
248
283
  else if (type === "filesystem") {
249
- const wd = t.working_directory ??
250
- process.cwd();
251
- const fsTools = makeFilesystemTools(wd);
284
+ // Note: working_directory is ignored - filesystem tools now use session context
285
+ const fsTools = makeFilesystemTools();
252
286
  // Tag filesystem tools with their group name for tool override filtering
253
287
  for (const fsTool of fsTools) {
288
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom property for tool grouping
254
289
  fsTool.__groupName = "filesystem";
255
290
  }
256
291
  enabledTools.push(...fsTools);
@@ -263,7 +298,9 @@ export class LangchainAgent {
263
298
  description: t.description,
264
299
  schema: t.schema,
265
300
  });
301
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
266
302
  addedTool.prettyName = t.prettyName;
303
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
267
304
  addedTool.icon = t.icon;
268
305
  enabledTools.push(addedTool);
269
306
  }
@@ -279,6 +316,7 @@ export class LangchainAgent {
279
316
  // Track which built-in group produced this tool so overrides can
280
317
  // filter by the original config name (e.g. "web_search" filters
281
318
  // both WebSearch and WebFetch helpers).
319
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom property for tool grouping
282
320
  tool.__groupName = name;
283
321
  };
284
322
  if (typeof entry === "function") {
@@ -318,7 +356,8 @@ export class LangchainAgent {
318
356
  // MCP tools - calculate overhead separately
319
357
  let mcpOverheadTokens = 0;
320
358
  if ((this.definition.mcps?.length ?? 0) > 0) {
321
- const mcpTools = await makeMcpToolsClient(this.definition.mcps).getTools();
359
+ const client = await makeMcpToolsClient(this.definition.mcps);
360
+ const mcpTools = await client.getTools();
322
361
  const mcpToolMetadata = mcpTools.map(extractToolMetadata);
323
362
  mcpOverheadTokens = estimateAllToolsOverhead(mcpToolMetadata);
324
363
  enabledTools.push(...mcpTools);
@@ -409,22 +448,42 @@ export class LangchainAgent {
409
448
  reduction: `${((1 - compactedTokens / outputTokens) * 100).toFixed(1)}%`,
410
449
  totalCumulativeTokens: cumulativeToolOutputTokens,
411
450
  });
412
- return typeof result === "string"
413
- ? modifiedOutput.content
414
- : JSON.stringify(modifiedOutput);
451
+ // Include compaction metadata in the output for the adapter to extract
452
+ // Also include original content so adapter can store it
453
+ const originalContentStr = typeof rawOutput === "object" &&
454
+ rawOutput !== null &&
455
+ "content" in rawOutput
456
+ ? String(rawOutput.content)
457
+ : JSON.stringify(rawOutput);
458
+ const outputWithMeta = {
459
+ ...modifiedOutput,
460
+ _compactionMeta: {
461
+ action: hookResult.metadata.action,
462
+ originalTokens: hookResult.metadata.originalTokens,
463
+ finalTokens: hookResult.metadata.finalTokens,
464
+ tokensSaved: hookResult.metadata.tokensSaved,
465
+ originalContent: originalContentStr,
466
+ },
467
+ };
468
+ // Always return JSON string to preserve metadata
469
+ return JSON.stringify(outputWithMeta);
415
470
  }
416
471
  // No compaction happened, count original size
417
472
  cumulativeToolOutputTokens += outputTokens;
418
473
  return result;
419
474
  };
420
475
  // Create new tool with wrapped function
476
+ // biome-ignore lint/suspicious/noExplicitAny: Need to pass function with dynamic signature
421
477
  const wrappedTool = tool(wrappedFunc, {
422
478
  name: originalTool.name,
423
479
  description: originalTool.description,
480
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing internal schema property
424
481
  schema: originalTool.schema,
425
482
  });
426
483
  // Preserve metadata
484
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
427
485
  wrappedTool.prettyName = originalTool.prettyName;
486
+ // biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
428
487
  wrappedTool.icon = originalTool.icon;
429
488
  return wrappedTool;
430
489
  });
@@ -434,15 +493,16 @@ export class LangchainAgent {
434
493
  const filteredTools = isSubagent
435
494
  ? wrappedTools.filter((t) => t.name !== TODO_WRITE_TOOL_NAME && t.name !== SUBAGENT_TOOL_NAME)
436
495
  : wrappedTools;
437
- // Wrap tools with tracing so each tool executes within its own span context.
496
+ // Wrap tools with tracing and session_id injection (combined in one wrapper)
438
497
  // This ensures subagent spans are children of the Task tool span.
439
498
  // Pass the context getter so tools can nest under the current iteration span.
440
499
  let finalTools = filteredTools.map((t) => wrapToolWithTracing(t, otelCallbacks?.getCurrentIterationContext ??
441
- (() => invocationContext)));
500
+ (() => invocationContext), req.sessionId));
442
501
  // Apply tool overrides if provided (Town Hall comparison feature)
443
502
  if (req.configOverrides?.tools && req.configOverrides.tools.length > 0) {
444
503
  const allowedToolNames = new Set(req.configOverrides.tools);
445
504
  finalTools = finalTools.filter((t) => {
505
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing custom property for tool grouping
446
506
  const groupName = t.__groupName;
447
507
  return (allowedToolNames.has(groupName ?? "") ||
448
508
  allowedToolNames.has(t.name));
@@ -490,26 +550,28 @@ export class LangchainAgent {
490
550
  // LangChain uses image_url type with data URL format
491
551
  return blocks
492
552
  .map((block) => {
493
- if (block.type === "text") {
553
+ const typedBlock = block;
554
+ if (typedBlock.type === "text") {
494
555
  return {
495
556
  type: "text",
496
- text: block.text,
557
+ text: typedBlock.text,
497
558
  };
498
559
  }
499
- else if (block.type === "image") {
560
+ else if (typedBlock.type === "image") {
500
561
  // Extract base64 data and media type from various formats
501
562
  let base64Data;
502
563
  let mediaType = "image/png";
503
564
  // Check if it has the source format (Claude API format)
504
- if ("source" in block && block.source) {
505
- base64Data = block.source.data;
506
- mediaType = block.source.media_type || "image/png";
565
+ if ("source" in typedBlock && typedBlock.source) {
566
+ const source = typedBlock.source;
567
+ base64Data = source.data;
568
+ mediaType = source.media_type || "image/png";
507
569
  }
508
570
  // ACP format: { type: "image", data: "...", mimeType: "..." }
509
- else if ("data" in block && block.data) {
510
- base64Data = block.data;
511
- if (block.mimeType) {
512
- const mt = block.mimeType.toLowerCase();
571
+ else if ("data" in typedBlock && typedBlock.data) {
572
+ base64Data = typedBlock.data;
573
+ if (typedBlock.mimeType) {
574
+ const mt = typedBlock.mimeType.toLowerCase();
513
575
  if (mt === "image/jpeg" || mt === "image/jpg") {
514
576
  mediaType = "image/jpeg";
515
577
  }
@@ -536,7 +598,7 @@ export class LangchainAgent {
536
598
  }
537
599
  return null;
538
600
  })
539
- .filter(Boolean);
601
+ .filter((item) => item !== null);
540
602
  };
541
603
  if (req.contextMessages && req.contextMessages.length > 0) {
542
604
  // Use context messages (already resolved from context entries)
@@ -667,6 +729,7 @@ export class LangchainAgent {
667
729
  }
668
730
  // Also yield any queued subagent updates before processing stream item
669
731
  yield* yieldPendingSubagentUpdates();
732
+ // biome-ignore lint/suspicious/noExplicitAny: LangChain stream items are tuples with dynamic types
670
733
  const [streamMode, chunk] = streamItem;
671
734
  if (streamMode === "updates") {
672
735
  const updatesChunk = modelRequestSchema.safeParse(chunk);
@@ -741,8 +804,11 @@ export class LangchainAgent {
741
804
  // continue;
742
805
  //}
743
806
  const matchingTool = finalTools.find((t) => t.name === toolCall.name);
807
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing custom property on LangChain tool
744
808
  let prettyName = matchingTool?.prettyName;
809
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing custom property on LangChain tool
745
810
  const icon = matchingTool?.icon;
811
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing custom property on LangChain tool
746
812
  const verbiage = matchingTool?.verbiage;
747
813
  // For the Task tool, use the displayName (or agentName as fallback) as the prettyName
748
814
  if (toolCall.name === SUBAGENT_TOOL_NAME &&
@@ -756,6 +822,7 @@ export class LangchainAgent {
756
822
  const taskTool = this.definition.tools?.find((t) => typeof t === "object" &&
757
823
  t.type === "direct" &&
758
824
  t.name === SUBAGENT_TOOL_NAME);
825
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing custom property on tool definition
759
826
  const subagentConfigs = taskTool?.subagentConfigs;
760
827
  const subagentConfig = subagentConfigs?.find((config) => config.agentName === agentName);
761
828
  prettyName = subagentConfig?.displayName ?? agentName;
@@ -987,6 +1054,40 @@ export class LangchainAgent {
987
1054
  _meta: { messageId: req.messageId },
988
1055
  });
989
1056
  // Buffer tool output separately
1057
+ // Check if the content contains compaction metadata and extract it
1058
+ let rawOutput = {
1059
+ content: aiMessage.content,
1060
+ };
1061
+ let compactionMeta;
1062
+ try {
1063
+ const parsed = JSON.parse(aiMessage.content);
1064
+ if (typeof parsed === "object" &&
1065
+ parsed !== null &&
1066
+ "_compactionMeta" in parsed) {
1067
+ // Extract compaction metadata to top level of rawOutput
1068
+ const { _compactionMeta, ...contentWithoutMeta } = parsed;
1069
+ compactionMeta = _compactionMeta;
1070
+ rawOutput = {
1071
+ content: JSON.stringify(contentWithoutMeta),
1072
+ _compactionMeta,
1073
+ };
1074
+ }
1075
+ }
1076
+ catch {
1077
+ // Not valid JSON, use original content
1078
+ }
1079
+ // For content display, use cleaned version if compaction occurred
1080
+ let displayContent = aiMessage.content;
1081
+ if (compactionMeta) {
1082
+ try {
1083
+ const parsed = JSON.parse(aiMessage.content);
1084
+ const { _compactionMeta: _, ...cleanParsed } = parsed;
1085
+ displayContent = JSON.stringify(cleanParsed);
1086
+ }
1087
+ catch {
1088
+ // Keep original if parsing fails
1089
+ }
1090
+ }
990
1091
  pendingToolCallNotifications.push({
991
1092
  sessionUpdate: "tool_output",
992
1093
  toolCallId: aiMessage.tool_call_id,
@@ -995,11 +1096,11 @@ export class LangchainAgent {
995
1096
  type: "content",
996
1097
  content: {
997
1098
  type: "text",
998
- text: aiMessage.content,
1099
+ text: displayContent,
999
1100
  },
1000
1101
  },
1001
1102
  ],
1002
- rawOutput: { content: aiMessage.content },
1103
+ rawOutput,
1003
1104
  _meta: { messageId: req.messageId },
1004
1105
  });
1005
1106
  // Flush tool outputs after buffering
@@ -1069,11 +1170,11 @@ const modelRequestSchema = z.object({
1069
1170
  messages: z.array(z.any()),
1070
1171
  }),
1071
1172
  });
1072
- const makeMcpToolsClient = (mcpConfigs) => {
1073
- const mcpServers = mcpConfigs?.map((config) => {
1173
+ const makeMcpToolsClient = async (mcpConfigs) => {
1174
+ const mcpServers = await Promise.all((mcpConfigs ?? []).map(async (config) => {
1074
1175
  if (typeof config === "string") {
1075
1176
  // String configs use the centralized MCP proxy with auth
1076
- const shedAuth = getShedAuth();
1177
+ const shedAuth = await ensureAuthenticated();
1077
1178
  if (!shedAuth) {
1078
1179
  throw new Error("Not logged in. Run 'town login' or set SHED_API_KEY to use cloud MCP servers.");
1079
1180
  }
@@ -1105,7 +1206,7 @@ const makeMcpToolsClient = (mcpConfigs) => {
1105
1206
  args: config.args ?? [],
1106
1207
  },
1107
1208
  ];
1108
- });
1209
+ }));
1109
1210
  const client = new MultiServerMCPClient({
1110
1211
  // Global tool configuration options
1111
1212
  // Whether to throw on errors if a tool fails to load (optional, default: true)
@@ -1172,16 +1273,33 @@ I've found some existing telemetry code. Let me mark the first todo as in_progre
1172
1273
  // Re-export subagent tool utility
1173
1274
  export { makeSubagentsTool } from "./tools/subagent.js";
1174
1275
  /**
1175
- * Wraps a LangChain tool with OpenTelemetry tracing.
1276
+ * Wraps a LangChain tool with OpenTelemetry tracing and session_id injection.
1176
1277
  * This ensures the tool executes within its own span context,
1177
1278
  * so any child operations (like subagent spawning) become children
1178
1279
  * of the tool span rather than the parent invocation span.
1179
1280
  * @param originalTool The tool to wrap
1180
1281
  * @param getIterationContext Function that returns the current iteration context
1282
+ * @param sessionId Optional session ID to inject for artifact tools
1181
1283
  */
1182
- function wrapToolWithTracing(originalTool, getIterationContext) {
1284
+ function wrapToolWithTracing(originalTool, getIterationContext, sessionId) {
1285
+ // Check if this tool needs session_id injection
1286
+ const TOOLS_NEEDING_SESSION_ID = [
1287
+ "artifacts_cp",
1288
+ "artifacts_del",
1289
+ "artifacts_ls",
1290
+ "artifacts_url",
1291
+ ];
1292
+ const needsSessionId = TOOLS_NEEDING_SESSION_ID.includes(originalTool.name);
1183
1293
  const wrappedFunc = async (input) => {
1184
- const toolInputJson = JSON.stringify(input);
1294
+ // Inject session_id if needed
1295
+ let finalInput = input;
1296
+ if (needsSessionId && sessionId) {
1297
+ finalInput = {
1298
+ ...input,
1299
+ session_id: sessionId,
1300
+ };
1301
+ }
1302
+ const toolInputJson = JSON.stringify(finalInput);
1185
1303
  // CRITICAL: Get the iteration context synchronously when the tool is invoked.
1186
1304
  // We must capture both the context AND the parent span at this moment.
1187
1305
  const iterationContext = getIterationContext();
@@ -1203,7 +1321,7 @@ function wrapToolWithTracing(originalTool, getIterationContext) {
1203
1321
  : iterationContext;
1204
1322
  try {
1205
1323
  // Execute within the tool span's context
1206
- const result = await context.with(spanContext, () => originalTool.invoke(input));
1324
+ const result = await context.with(spanContext, () => originalTool.invoke(finalInput));
1207
1325
  const resultStr = typeof result === "string" ? result : JSON.stringify(result);
1208
1326
  if (toolSpan) {
1209
1327
  telemetry.setSpanAttributes(toolSpan, {
@@ -1222,14 +1340,19 @@ function wrapToolWithTracing(originalTool, getIterationContext) {
1222
1340
  });
1223
1341
  };
1224
1342
  // Create new tool with wrapped function
1343
+ // biome-ignore lint/suspicious/noExplicitAny: Need to pass function with dynamic signature
1225
1344
  const wrappedTool = tool(wrappedFunc, {
1226
1345
  name: originalTool.name,
1227
1346
  description: originalTool.description,
1347
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing internal schema property
1228
1348
  schema: originalTool.schema,
1229
1349
  });
1230
1350
  // Preserve metadata
1351
+ // biome-ignore lint/suspicious/noExplicitAny: Need to copy custom properties between LangChain tools
1231
1352
  wrappedTool.prettyName = originalTool.prettyName;
1353
+ // biome-ignore lint/suspicious/noExplicitAny: Need to copy custom properties between LangChain tools
1232
1354
  wrappedTool.icon = originalTool.icon;
1355
+ // biome-ignore lint/suspicious/noExplicitAny: Need to copy custom properties between LangChain tools
1233
1356
  wrappedTool.__groupName = originalTool.__groupName;
1234
1357
  return wrappedTool;
1235
1358
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Supabase Storage-backed artifacts tool for agent backend
3
+ *
4
+ * Provides file storage capabilities using Supabase Storage with the following operations:
5
+ * - artifacts_cp: Copy files to/from Supabase Storage
6
+ * - artifacts_del: Delete files from Supabase Storage
7
+ * - artifacts_ls: List files in Supabase Storage
8
+ * - artifacts_url: Generate signed URLs
9
+ *
10
+ * Storage keys are scoped by: <deploying_user>/<agent_name>/<session_id>/<file_path>
11
+ */
12
+ import { z } from "zod";
13
+ /**
14
+ * Factory function to create the artifacts tools
15
+ * Returns an array of all four artifact management tools
16
+ */
17
+ export declare function makeArtifactsTools(): (import("langchain").DynamicStructuredTool<z.ZodObject<{
18
+ session_id: z.ZodOptional<z.ZodString>;
19
+ source: z.ZodString;
20
+ destination: z.ZodString;
21
+ direction: z.ZodEnum<{
22
+ upload: "upload";
23
+ download: "download";
24
+ }>;
25
+ }, z.core.$strip>, {
26
+ session_id: string;
27
+ source: string;
28
+ destination: string;
29
+ direction: "upload" | "download";
30
+ }, {
31
+ source: string;
32
+ destination: string;
33
+ direction: "upload" | "download";
34
+ session_id?: string | undefined;
35
+ }, string> | import("langchain").DynamicStructuredTool<z.ZodObject<{
36
+ session_id: z.ZodOptional<z.ZodString>;
37
+ path: z.ZodString;
38
+ }, z.core.$strip>, {
39
+ session_id: string;
40
+ path: string;
41
+ }, {
42
+ path: string;
43
+ session_id?: string | undefined;
44
+ }, string> | import("langchain").DynamicStructuredTool<z.ZodObject<{
45
+ session_id: z.ZodOptional<z.ZodString>;
46
+ path: z.ZodOptional<z.ZodString>;
47
+ recursive: z.ZodOptional<z.ZodBoolean>;
48
+ }, z.core.$strip>, {
49
+ session_id: string;
50
+ path?: string;
51
+ recursive?: boolean;
52
+ }, {
53
+ session_id?: string | undefined;
54
+ path?: string | undefined;
55
+ recursive?: boolean | undefined;
56
+ }, string> | import("langchain").DynamicStructuredTool<z.ZodObject<{
57
+ session_id: z.ZodOptional<z.ZodString>;
58
+ path: z.ZodString;
59
+ expires_in: z.ZodOptional<z.ZodNumber>;
60
+ }, z.core.$strip>, {
61
+ session_id: string;
62
+ path: string;
63
+ expires_in?: number;
64
+ }, {
65
+ path: string;
66
+ session_id?: string | undefined;
67
+ expires_in?: number | undefined;
68
+ }, string>)[];