@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.
- package/dist/acp-server/adapter.js +150 -49
- package/dist/acp-server/http.js +56 -1
- package/dist/acp-server/session-storage.d.ts +44 -12
- package/dist/acp-server/session-storage.js +153 -59
- package/dist/definition/index.d.ts +2 -2
- package/dist/definition/index.js +1 -1
- package/dist/runner/agent-runner.d.ts +4 -2
- package/dist/runner/hooks/executor.d.ts +1 -0
- package/dist/runner/hooks/executor.js +18 -2
- package/dist/runner/hooks/predefined/compaction-tool.js +3 -2
- package/dist/runner/hooks/predefined/tool-response-compactor.d.ts +0 -4
- package/dist/runner/hooks/predefined/tool-response-compactor.js +30 -16
- package/dist/runner/hooks/types.d.ts +4 -5
- package/dist/runner/langchain/index.d.ts +1 -0
- package/dist/runner/langchain/index.js +156 -33
- package/dist/runner/langchain/tools/artifacts.d.ts +68 -0
- package/dist/runner/langchain/tools/artifacts.js +466 -0
- package/dist/runner/langchain/tools/browser.js +15 -3
- package/dist/runner/langchain/tools/filesystem.d.ts +8 -4
- package/dist/runner/langchain/tools/filesystem.js +118 -82
- package/dist/runner/langchain/tools/generate_image.d.ts +19 -0
- package/dist/runner/langchain/tools/generate_image.js +54 -14
- package/dist/runner/langchain/tools/subagent.js +2 -2
- package/dist/runner/langchain/tools/todo.js +3 -0
- package/dist/runner/langchain/tools/web_search.js +6 -0
- package/dist/runner/session-context.d.ts +40 -0
- package/dist/runner/session-context.js +69 -0
- package/dist/runner/tools.d.ts +2 -2
- package/dist/runner/tools.js +2 -0
- package/dist/scaffold/project-scaffold.js +7 -3
- package/dist/telemetry/setup.js +1 -1
- package/dist/templates/index.d.ts +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/context-size-calculator.d.ts +1 -10
- package/dist/utils/context-size-calculator.js +1 -12
- package/dist/utils/token-counter.js +2 -2
- package/package.json +10 -10
- 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 {
|
|
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(
|
|
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
|
-
|
|
250
|
-
|
|
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
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
|
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
|
-
|
|
553
|
+
const typedBlock = block;
|
|
554
|
+
if (typedBlock.type === "text") {
|
|
494
555
|
return {
|
|
495
556
|
type: "text",
|
|
496
|
-
text:
|
|
557
|
+
text: typedBlock.text,
|
|
497
558
|
};
|
|
498
559
|
}
|
|
499
|
-
else if (
|
|
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
|
|
505
|
-
|
|
506
|
-
|
|
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
|
|
510
|
-
base64Data =
|
|
511
|
-
if (
|
|
512
|
-
const mt =
|
|
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(
|
|
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:
|
|
1099
|
+
text: displayContent,
|
|
999
1100
|
},
|
|
1000
1101
|
},
|
|
1001
1102
|
],
|
|
1002
|
-
rawOutput
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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>)[];
|