@townco/agent 0.1.73 → 0.1.75
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 +44 -5
- package/dist/acp-server/http.js +29 -13
- package/dist/runner/agent-runner.d.ts +11 -1
- package/dist/runner/hooks/predefined/tool-response-compactor.js +73 -19
- package/dist/runner/langchain/index.js +80 -31
- package/dist/runner/langchain/model-factory.js +6 -9
- package/dist/runner/langchain/otel-callbacks.d.ts +7 -1
- package/dist/runner/langchain/otel-callbacks.js +80 -20
- package/dist/runner/langchain/tools/filesystem.js +15 -0
- package/dist/runner/langchain/tools/subagent.js +89 -79
- package/dist/runner/langchain/tools/todo.js +4 -0
- package/dist/runner/langchain/tools/web_search.d.ts +24 -0
- package/dist/runner/langchain/tools/web_search.js +42 -11
- package/dist/runner/tool-loader.d.ts +10 -0
- package/dist/runner/tool-loader.js +1 -0
- package/dist/runner/tools.d.ts +2 -2
- package/dist/runner/tools.js +1 -0
- package/dist/telemetry/index.d.ts +5 -0
- package/dist/telemetry/index.js +8 -0
- package/dist/telemetry/setup.js +10 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/tool.d.ts +5 -0
- package/dist/utils/tool.js +1 -0
- package/package.json +6 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ChatAnthropic } from "@langchain/anthropic";
|
|
2
2
|
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
3
3
|
import { ChatVertexAI } from "@langchain/google-vertexai";
|
|
4
|
-
import {
|
|
4
|
+
import { getShedAuth } from "@townco/core/auth";
|
|
5
5
|
import { createLogger } from "../../logger.js";
|
|
6
6
|
const logger = createLogger("model-factory");
|
|
7
7
|
/**
|
|
@@ -24,17 +24,14 @@ export function createModelFromString(modelString) {
|
|
|
24
24
|
// Check for town- prefix for proxied models via shed
|
|
25
25
|
if (modelString.startsWith("town-")) {
|
|
26
26
|
const actualModel = modelString.slice(5); // strip "town-"
|
|
27
|
-
const
|
|
28
|
-
if (!
|
|
29
|
-
throw new Error("Not logged in. Run 'town login'
|
|
27
|
+
const shedAuth = getShedAuth();
|
|
28
|
+
if (!shedAuth) {
|
|
29
|
+
throw new Error("Not logged in. Run 'town login' or set SHED_API_KEY.");
|
|
30
30
|
}
|
|
31
|
-
const shedUrl = credentials.shed_url ??
|
|
32
|
-
process.env.TOWN_SHED_URL ??
|
|
33
|
-
"http://localhost:3000";
|
|
34
31
|
return new ChatAnthropic({
|
|
35
32
|
model: actualModel,
|
|
36
|
-
anthropicApiUrl: `${shedUrl}/api/anthropic`,
|
|
37
|
-
apiKey:
|
|
33
|
+
anthropicApiUrl: `${shedAuth.shedUrl}/api/anthropic`,
|
|
34
|
+
apiKey: shedAuth.accessToken,
|
|
38
35
|
});
|
|
39
36
|
}
|
|
40
37
|
// Check if the model string uses provider prefix format
|
|
@@ -4,6 +4,9 @@ export interface OtelCallbackOptions {
|
|
|
4
4
|
provider: string;
|
|
5
5
|
model: string;
|
|
6
6
|
parentContext: Context;
|
|
7
|
+
iterationIndexRef: {
|
|
8
|
+
current: number;
|
|
9
|
+
};
|
|
7
10
|
}
|
|
8
11
|
/**
|
|
9
12
|
* Creates OpenTelemetry callback handlers for LangChain LLM calls.
|
|
@@ -15,4 +18,7 @@ export interface OtelCallbackOptions {
|
|
|
15
18
|
* @param opts.parentContext - The parent OTEL context to create child spans under
|
|
16
19
|
* @returns CallbackHandlerMethods object that can be passed to LangChain
|
|
17
20
|
*/
|
|
18
|
-
export declare function makeOtelCallbacks(opts: OtelCallbackOptions): CallbackHandlerMethods
|
|
21
|
+
export declare function makeOtelCallbacks(opts: OtelCallbackOptions): CallbackHandlerMethods & {
|
|
22
|
+
cleanup: () => void;
|
|
23
|
+
getCurrentIterationContext: () => Context;
|
|
24
|
+
};
|
|
@@ -5,11 +5,15 @@ import { telemetry } from "../../telemetry/index.js";
|
|
|
5
5
|
* Creates spans for each LLM request to track model invocations and token usage.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
* Map to store active spans by their LangChain run ID
|
|
8
|
+
* Map to store active chat spans by their LangChain run ID
|
|
9
9
|
*
|
|
10
10
|
* There's a memory leak opportunity here, but we are OK with that for now.
|
|
11
11
|
*/
|
|
12
12
|
const spansByRunId = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Map to store active iteration spans by their LangChain run ID
|
|
15
|
+
*/
|
|
16
|
+
const iterationSpansByRunId = new Map();
|
|
13
17
|
/**
|
|
14
18
|
* Serializes LangChain messages to a JSON string for span attributes.
|
|
15
19
|
* Extracts role and content from each message.
|
|
@@ -88,19 +92,41 @@ function serializeOutput(output) {
|
|
|
88
92
|
* @returns CallbackHandlerMethods object that can be passed to LangChain
|
|
89
93
|
*/
|
|
90
94
|
export function makeOtelCallbacks(opts) {
|
|
95
|
+
// Track the iteration span for this specific invocation
|
|
96
|
+
let localIterationSpan = null;
|
|
97
|
+
let currentIterationContext = opts.parentContext;
|
|
91
98
|
return {
|
|
92
99
|
/**
|
|
93
100
|
* Called when a chat model/LLM request starts.
|
|
94
|
-
* Creates
|
|
101
|
+
* Creates iteration span first, then chat span as its child.
|
|
95
102
|
*/
|
|
96
103
|
async handleChatModelStart(_llm, messages, runId, _parentRunId, _extraParams, tags, _metadata) {
|
|
97
104
|
// Extract system prompt and serialize messages
|
|
98
105
|
const systemPrompt = extractSystemPrompt(messages);
|
|
99
106
|
const serializedMessages = serializeMessages(messages);
|
|
100
|
-
//
|
|
107
|
+
// Close previous iteration span if it exists (tools from previous iteration are done)
|
|
108
|
+
if (localIterationSpan) {
|
|
109
|
+
telemetry.endSpan(localIterationSpan);
|
|
110
|
+
localIterationSpan = null;
|
|
111
|
+
}
|
|
112
|
+
// Create iteration span within the parent context (invocation span)
|
|
113
|
+
const iterationIndex = opts.iterationIndexRef.current;
|
|
114
|
+
const iterationSpan = context.with(opts.parentContext, () => telemetry.startSpan("agent.iteration", {
|
|
115
|
+
"agent.iteration.index": iterationIndex,
|
|
116
|
+
"langchain.run_id": runId,
|
|
117
|
+
}));
|
|
118
|
+
// Track as current iteration span (will be closed when next iteration starts or invocation ends)
|
|
119
|
+
localIterationSpan = iterationSpan;
|
|
120
|
+
// Create context with iteration span as active
|
|
121
|
+
const iterationContext = iterationSpan
|
|
122
|
+
? trace.setSpan(opts.parentContext, iterationSpan)
|
|
123
|
+
: opts.parentContext;
|
|
124
|
+
// Store current iteration context so tools can use it
|
|
125
|
+
currentIterationContext = iterationContext;
|
|
126
|
+
// Create chat span as child of iteration span
|
|
101
127
|
// Following OpenTelemetry GenAI semantic conventions:
|
|
102
128
|
// https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/
|
|
103
|
-
const
|
|
129
|
+
const chatSpan = context.with(iterationContext, () => telemetry.startSpan(`chat ${opts.model}`, {
|
|
104
130
|
"gen_ai.operation.name": "chat",
|
|
105
131
|
"gen_ai.provider.name": opts.provider,
|
|
106
132
|
"gen_ai.request.model": opts.model,
|
|
@@ -114,10 +140,14 @@ export function makeOtelCallbacks(opts) {
|
|
|
114
140
|
? { "langchain.tags": tags.join(",") }
|
|
115
141
|
: {}),
|
|
116
142
|
}));
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
// Store both spans
|
|
144
|
+
if (iterationSpan) {
|
|
145
|
+
iterationSpansByRunId.set(runId, iterationSpan);
|
|
146
|
+
}
|
|
147
|
+
if (chatSpan) {
|
|
148
|
+
spansByRunId.set(runId, chatSpan);
|
|
119
149
|
// Emit log for LLM request with trace context
|
|
120
|
-
const spanContext =
|
|
150
|
+
const spanContext = chatSpan.spanContext();
|
|
121
151
|
telemetry.log("info", "LLM Request", {
|
|
122
152
|
"gen_ai.operation.name": "chat",
|
|
123
153
|
"gen_ai.provider.name": opts.provider,
|
|
@@ -129,14 +159,16 @@ export function makeOtelCallbacks(opts) {
|
|
|
129
159
|
span_id: spanContext.spanId,
|
|
130
160
|
});
|
|
131
161
|
}
|
|
162
|
+
// Increment iteration index for next LLM call
|
|
163
|
+
opts.iterationIndexRef.current++;
|
|
132
164
|
},
|
|
133
165
|
/**
|
|
134
166
|
* Called when an LLM request completes successfully.
|
|
135
|
-
* Records token usage and ends
|
|
167
|
+
* Records token usage and ends both chat and iteration spans.
|
|
136
168
|
*/
|
|
137
169
|
async handleLLMEnd(output, runId) {
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
170
|
+
const chatSpan = spansByRunId.get(runId);
|
|
171
|
+
if (!chatSpan)
|
|
140
172
|
return;
|
|
141
173
|
// Extract token usage from LLM output
|
|
142
174
|
// The structure varies by provider but LangChain normalizes it
|
|
@@ -147,15 +179,15 @@ export function makeOtelCallbacks(opts) {
|
|
|
147
179
|
(tokenUsage.totalTokens != null
|
|
148
180
|
? tokenUsage.totalTokens - inputTokens
|
|
149
181
|
: 0);
|
|
150
|
-
telemetry.recordTokenUsage(inputTokens, outputTokens,
|
|
182
|
+
telemetry.recordTokenUsage(inputTokens, outputTokens, chatSpan);
|
|
151
183
|
}
|
|
152
184
|
// Serialize output and attach to span
|
|
153
185
|
const serializedOutput = serializeOutput(output);
|
|
154
|
-
telemetry.setSpanAttributes(
|
|
186
|
+
telemetry.setSpanAttributes(chatSpan, {
|
|
155
187
|
"gen_ai.output.messages": serializedOutput,
|
|
156
188
|
});
|
|
157
189
|
// Emit log for LLM response with trace context
|
|
158
|
-
const spanContext =
|
|
190
|
+
const spanContext = chatSpan.spanContext();
|
|
159
191
|
telemetry.log("info", "LLM Response", {
|
|
160
192
|
"gen_ai.operation.name": "chat",
|
|
161
193
|
"gen_ai.output.messages": serializedOutput,
|
|
@@ -171,19 +203,47 @@ export function makeOtelCallbacks(opts) {
|
|
|
171
203
|
trace_id: spanContext.traceId,
|
|
172
204
|
span_id: spanContext.spanId,
|
|
173
205
|
});
|
|
174
|
-
|
|
206
|
+
// End chat span
|
|
207
|
+
telemetry.endSpan(chatSpan);
|
|
175
208
|
spansByRunId.delete(runId);
|
|
209
|
+
// DON'T close iteration span here - tools will execute after this
|
|
210
|
+
// The iteration span will be closed when:
|
|
211
|
+
// 1. Next iteration starts (in handleChatModelStart)
|
|
212
|
+
// 2. Invocation ends (caller must close remaining span)
|
|
213
|
+
iterationSpansByRunId.delete(runId);
|
|
176
214
|
},
|
|
177
215
|
/**
|
|
178
216
|
* Called when an LLM request fails with an error.
|
|
179
|
-
* Records the error and ends
|
|
217
|
+
* Records the error and ends both chat and iteration spans.
|
|
180
218
|
*/
|
|
181
219
|
async handleLLMError(error, runId) {
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
220
|
+
const chatSpan = spansByRunId.get(runId);
|
|
221
|
+
if (chatSpan) {
|
|
222
|
+
telemetry.endSpan(chatSpan, error instanceof Error ? error : new Error(String(error)));
|
|
223
|
+
spansByRunId.delete(runId);
|
|
224
|
+
}
|
|
225
|
+
// Close iteration span on error
|
|
226
|
+
if (localIterationSpan) {
|
|
227
|
+
telemetry.endSpan(localIterationSpan, error instanceof Error ? error : new Error(String(error)));
|
|
228
|
+
localIterationSpan = null;
|
|
229
|
+
}
|
|
230
|
+
iterationSpansByRunId.delete(runId);
|
|
231
|
+
},
|
|
232
|
+
/**
|
|
233
|
+
* Get the current iteration context for tool span creation
|
|
234
|
+
*/
|
|
235
|
+
getCurrentIterationContext() {
|
|
236
|
+
return currentIterationContext;
|
|
237
|
+
},
|
|
238
|
+
/**
|
|
239
|
+
* Cleanup function to close any remaining iteration span
|
|
240
|
+
* Should be called when invocation completes
|
|
241
|
+
*/
|
|
242
|
+
cleanup() {
|
|
243
|
+
if (localIterationSpan) {
|
|
244
|
+
telemetry.endSpan(localIterationSpan);
|
|
245
|
+
localIterationSpan = null;
|
|
246
|
+
}
|
|
187
247
|
},
|
|
188
248
|
};
|
|
189
249
|
}
|
|
@@ -190,6 +190,11 @@ export function makeFilesystemTools(workingDirectory) {
|
|
|
190
190
|
});
|
|
191
191
|
grep.prettyName = "Codebase Search";
|
|
192
192
|
grep.icon = "Search";
|
|
193
|
+
grep.verbiage = {
|
|
194
|
+
active: "Searching for {query}",
|
|
195
|
+
past: "Searched for {query}",
|
|
196
|
+
paramKey: "pattern",
|
|
197
|
+
};
|
|
193
198
|
const read = tool(async ({ file_path, offset, limit }) => {
|
|
194
199
|
await ensureSandbox(resolvedWd);
|
|
195
200
|
assertAbsolutePath(file_path, "file_path");
|
|
@@ -234,6 +239,11 @@ export function makeFilesystemTools(workingDirectory) {
|
|
|
234
239
|
});
|
|
235
240
|
read.prettyName = "Read File";
|
|
236
241
|
read.icon = "FileText";
|
|
242
|
+
read.verbiage = {
|
|
243
|
+
active: "Reading {file}",
|
|
244
|
+
past: "Read {file}",
|
|
245
|
+
paramKey: "file_path",
|
|
246
|
+
};
|
|
237
247
|
const write = tool(async ({ file_path, content }) => {
|
|
238
248
|
await ensureSandbox(resolvedWd);
|
|
239
249
|
assertAbsolutePath(file_path, "file_path");
|
|
@@ -263,5 +273,10 @@ export function makeFilesystemTools(workingDirectory) {
|
|
|
263
273
|
});
|
|
264
274
|
write.prettyName = "Write File";
|
|
265
275
|
write.icon = "Edit";
|
|
276
|
+
write.verbiage = {
|
|
277
|
+
active: "Writing {file}",
|
|
278
|
+
past: "Wrote {file}",
|
|
279
|
+
paramKey: "file_path",
|
|
280
|
+
};
|
|
266
281
|
return [grep, read, write];
|
|
267
282
|
}
|
|
@@ -327,95 +327,105 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query)
|
|
|
327
327
|
// Map of tool call IDs to their indices in toolCalls array
|
|
328
328
|
const toolCallMap = new Map();
|
|
329
329
|
const ssePromise = (async () => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
try {
|
|
353
|
-
const message = JSON.parse(data);
|
|
354
|
-
const update = message.params?.update;
|
|
355
|
-
if (message.method !== "session/update" || !update)
|
|
330
|
+
try {
|
|
331
|
+
const sseResponse = await fetch(`${baseUrl}/events`, {
|
|
332
|
+
headers: { "X-Session-ID": sessionId },
|
|
333
|
+
signal: sseAbortController.signal,
|
|
334
|
+
});
|
|
335
|
+
if (!sseResponse.ok || !sseResponse.body) {
|
|
336
|
+
throw new Error(`SSE connection failed: HTTP ${sseResponse.status}`);
|
|
337
|
+
}
|
|
338
|
+
const reader = sseResponse.body.getReader();
|
|
339
|
+
const decoder = new TextDecoder();
|
|
340
|
+
let buffer = "";
|
|
341
|
+
while (true) {
|
|
342
|
+
const { done, value } = await reader.read();
|
|
343
|
+
if (done)
|
|
344
|
+
break;
|
|
345
|
+
buffer += decoder.decode(value, { stream: true });
|
|
346
|
+
const lines = buffer.split("\n");
|
|
347
|
+
buffer = lines.pop() || "";
|
|
348
|
+
for (const line of lines) {
|
|
349
|
+
if (line.startsWith("data:")) {
|
|
350
|
+
const data = line.substring(5).trim();
|
|
351
|
+
if (!data)
|
|
356
352
|
continue;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
currentMessage.contentBlocks.
|
|
371
|
-
|
|
372
|
-
text
|
|
373
|
-
}
|
|
353
|
+
try {
|
|
354
|
+
const message = JSON.parse(data);
|
|
355
|
+
const update = message.params?.update;
|
|
356
|
+
if (message.method !== "session/update" || !update)
|
|
357
|
+
continue;
|
|
358
|
+
// Handle agent_message_chunk - accumulate text
|
|
359
|
+
if (update.sessionUpdate === "agent_message_chunk") {
|
|
360
|
+
const content = update.content;
|
|
361
|
+
if (content?.type === "text" &&
|
|
362
|
+
typeof content.text === "string") {
|
|
363
|
+
responseText += content.text;
|
|
364
|
+
currentMessage.content += content.text;
|
|
365
|
+
// Add to contentBlocks - append to last text block or create new one
|
|
366
|
+
const lastBlock = currentMessage.contentBlocks[currentMessage.contentBlocks.length - 1];
|
|
367
|
+
if (lastBlock && lastBlock.type === "text") {
|
|
368
|
+
lastBlock.text += content.text;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
currentMessage.contentBlocks.push({
|
|
372
|
+
type: "text",
|
|
373
|
+
text: content.text,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
374
376
|
}
|
|
375
377
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
378
|
+
// Handle tool_call - track new tool calls
|
|
379
|
+
if (update.sessionUpdate === "tool_call" && update.toolCallId) {
|
|
380
|
+
const toolCall = {
|
|
381
|
+
id: update.toolCallId,
|
|
382
|
+
title: update.title || "Tool call",
|
|
383
|
+
prettyName: update._meta?.prettyName,
|
|
384
|
+
icon: update._meta?.icon,
|
|
385
|
+
status: update.status ||
|
|
386
|
+
"pending",
|
|
387
|
+
};
|
|
388
|
+
currentMessage.toolCalls.push(toolCall);
|
|
389
|
+
toolCallMap.set(update.toolCallId, currentMessage.toolCalls.length - 1);
|
|
390
|
+
// Add to contentBlocks for interleaved display
|
|
391
|
+
currentMessage.contentBlocks.push({
|
|
392
|
+
type: "tool_call",
|
|
393
|
+
toolCall,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
// Handle tool_call_update - update existing tool call status
|
|
397
|
+
if (update.sessionUpdate === "tool_call_update" &&
|
|
398
|
+
update.toolCallId) {
|
|
399
|
+
const idx = toolCallMap.get(update.toolCallId);
|
|
400
|
+
if (idx !== undefined && currentMessage.toolCalls[idx]) {
|
|
401
|
+
if (update.status) {
|
|
402
|
+
currentMessage.toolCalls[idx].status =
|
|
403
|
+
update.status;
|
|
404
|
+
}
|
|
405
|
+
// Also update in contentBlocks
|
|
406
|
+
const block = currentMessage.contentBlocks.find((b) => b.type === "tool_call" &&
|
|
407
|
+
b.toolCall.id === update.toolCallId);
|
|
408
|
+
if (block && update.status) {
|
|
409
|
+
block.toolCall.status =
|
|
410
|
+
update.status;
|
|
411
|
+
}
|
|
409
412
|
}
|
|
410
413
|
}
|
|
411
414
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
+
catch {
|
|
416
|
+
// Ignore malformed SSE data
|
|
417
|
+
}
|
|
415
418
|
}
|
|
416
419
|
}
|
|
417
420
|
}
|
|
418
421
|
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
// Ignore AbortError - this is expected when we abort the SSE connection
|
|
424
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
throw error;
|
|
428
|
+
}
|
|
419
429
|
})();
|
|
420
430
|
// Step 4: Send the prompt with timeout
|
|
421
431
|
const timeoutMs = 5 * 60 * 1000; // 5 minutes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
/** Create web search tools using direct EXA_API_KEY */
|
|
2
3
|
export declare function makeWebSearchTools(): readonly [import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
3
4
|
query: z.ZodString;
|
|
4
5
|
}, z.core.$strip>, {
|
|
@@ -21,3 +22,26 @@ export declare function makeWebSearchTools(): readonly [import("langchain").Dyna
|
|
|
21
22
|
url: string;
|
|
22
23
|
prompt: string;
|
|
23
24
|
}, string>];
|
|
25
|
+
/** Create web search tools using Town proxy */
|
|
26
|
+
export declare function makeTownWebSearchTools(): readonly [import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
27
|
+
query: z.ZodString;
|
|
28
|
+
}, z.core.$strip>, {
|
|
29
|
+
query: string;
|
|
30
|
+
}, {
|
|
31
|
+
query: string;
|
|
32
|
+
}, string | import("exa-js").SearchResponse<{
|
|
33
|
+
numResults: number;
|
|
34
|
+
type: "auto";
|
|
35
|
+
text: {
|
|
36
|
+
maxCharacters: number;
|
|
37
|
+
};
|
|
38
|
+
}>>, import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
39
|
+
url: z.ZodString;
|
|
40
|
+
prompt: z.ZodString;
|
|
41
|
+
}, z.core.$strip>, {
|
|
42
|
+
url: string;
|
|
43
|
+
prompt: string;
|
|
44
|
+
}, {
|
|
45
|
+
url: string;
|
|
46
|
+
prompt: string;
|
|
47
|
+
}, string>];
|
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { getShedAuth } from "@townco/core/auth";
|
|
2
3
|
import Exa from "exa-js";
|
|
3
4
|
import { tool } from "langchain";
|
|
4
5
|
import { z } from "zod";
|
|
5
|
-
let
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
let _directExaClient = null;
|
|
7
|
+
let _townExaClient = null;
|
|
8
|
+
/** Get Exa client using direct EXA_API_KEY environment variable */
|
|
9
|
+
function getDirectExaClient() {
|
|
10
|
+
if (_directExaClient) {
|
|
11
|
+
return _directExaClient;
|
|
9
12
|
}
|
|
10
13
|
const apiKey = process.env.EXA_API_KEY;
|
|
11
14
|
if (!apiKey) {
|
|
12
15
|
throw new Error("EXA_API_KEY environment variable is required to use the web_search tool. " +
|
|
13
16
|
"Please set it to your Exa API key from https://exa.ai");
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
return
|
|
18
|
+
_directExaClient = new Exa(apiKey);
|
|
19
|
+
return _directExaClient;
|
|
17
20
|
}
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
/** Get Exa client using Town proxy with authenticated credentials */
|
|
22
|
+
function getTownExaClient() {
|
|
23
|
+
if (_townExaClient) {
|
|
24
|
+
return _townExaClient;
|
|
25
|
+
}
|
|
26
|
+
const shedAuth = getShedAuth();
|
|
27
|
+
if (!shedAuth) {
|
|
28
|
+
throw new Error("Not logged in. Run 'town login' or set SHED_API_KEY to use the town_web_search tool.");
|
|
29
|
+
}
|
|
30
|
+
_townExaClient = new Exa(shedAuth.accessToken, `${shedAuth.shedUrl}/api/exa`);
|
|
31
|
+
return _townExaClient;
|
|
32
|
+
}
|
|
33
|
+
function makeWebSearchToolsInternal(getClient) {
|
|
20
34
|
const webSearch = tool(async ({ query }) => {
|
|
21
|
-
const client =
|
|
35
|
+
const client = getClient();
|
|
22
36
|
const result = await client.searchAndContents(query, {
|
|
23
37
|
numResults: 5,
|
|
24
38
|
type: "auto",
|
|
@@ -48,9 +62,13 @@ export function makeWebSearchTools() {
|
|
|
48
62
|
});
|
|
49
63
|
webSearch.prettyName = "Web Search";
|
|
50
64
|
webSearch.icon = "Globe";
|
|
51
|
-
|
|
65
|
+
webSearch.verbiage = {
|
|
66
|
+
active: "Searching the web for {query}",
|
|
67
|
+
past: "Searched the web for {query}",
|
|
68
|
+
paramKey: "query",
|
|
69
|
+
};
|
|
52
70
|
const webFetch = tool(async ({ url, prompt }) => {
|
|
53
|
-
const client =
|
|
71
|
+
const client = getClient();
|
|
54
72
|
try {
|
|
55
73
|
const result = await client.getContents([url], {
|
|
56
74
|
text: true,
|
|
@@ -122,8 +140,21 @@ export function makeWebSearchTools() {
|
|
|
122
140
|
});
|
|
123
141
|
webFetch.prettyName = "Web Fetch";
|
|
124
142
|
webFetch.icon = "Link";
|
|
143
|
+
webFetch.verbiage = {
|
|
144
|
+
active: "Fetching {url}",
|
|
145
|
+
past: "Fetched {url}",
|
|
146
|
+
paramKey: "url",
|
|
147
|
+
};
|
|
125
148
|
return [webSearch, webFetch];
|
|
126
149
|
}
|
|
150
|
+
/** Create web search tools using direct EXA_API_KEY */
|
|
151
|
+
export function makeWebSearchTools() {
|
|
152
|
+
return makeWebSearchToolsInternal(getDirectExaClient);
|
|
153
|
+
}
|
|
154
|
+
/** Create web search tools using Town proxy */
|
|
155
|
+
export function makeTownWebSearchTools() {
|
|
156
|
+
return makeWebSearchToolsInternal(getTownExaClient);
|
|
157
|
+
}
|
|
127
158
|
function buildWebFetchUserMessage(pageContent, prompt) {
|
|
128
159
|
return `Web page content:
|
|
129
160
|
---
|
|
@@ -6,6 +6,11 @@ export type CustomToolModule = {
|
|
|
6
6
|
description: string;
|
|
7
7
|
prettyName?: string;
|
|
8
8
|
icon?: string;
|
|
9
|
+
verbiage?: {
|
|
10
|
+
active: string;
|
|
11
|
+
past: string;
|
|
12
|
+
paramKey?: string;
|
|
13
|
+
};
|
|
9
14
|
};
|
|
10
15
|
export type ResolvedCustomTool = {
|
|
11
16
|
fn: (input: unknown) => unknown | Promise<unknown>;
|
|
@@ -14,5 +19,10 @@ export type ResolvedCustomTool = {
|
|
|
14
19
|
description: string;
|
|
15
20
|
prettyName?: string;
|
|
16
21
|
icon?: string;
|
|
22
|
+
verbiage?: {
|
|
23
|
+
active: string;
|
|
24
|
+
past: string;
|
|
25
|
+
paramKey?: string;
|
|
26
|
+
};
|
|
17
27
|
};
|
|
18
28
|
export declare function loadCustomToolModule(modulePath: string): Promise<ResolvedCustomTool>;
|
|
@@ -37,6 +37,7 @@ export async function loadCustomToolModule(modulePath) {
|
|
|
37
37
|
description: mod.description,
|
|
38
38
|
...(mod.prettyName && { prettyName: mod.prettyName }),
|
|
39
39
|
...(mod.icon && { icon: mod.icon }),
|
|
40
|
+
...(mod.verbiage && { verbiage: mod.verbiage }),
|
|
40
41
|
};
|
|
41
42
|
// Cache the resolved tool
|
|
42
43
|
customToolCache.set(modulePath, resolved);
|
package/dist/runner/tools.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/** Built-in tool types. */
|
|
3
|
-
export declare const zBuiltInToolType: z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">, z.ZodLiteral<"browser">]>;
|
|
3
|
+
export declare const zBuiltInToolType: z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"town_web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">, z.ZodLiteral<"browser">]>;
|
|
4
4
|
/** Subagent configuration schema for Task tools. */
|
|
5
5
|
export declare const zSubagentConfig: z.ZodObject<{
|
|
6
6
|
agentName: z.ZodString;
|
|
@@ -23,7 +23,7 @@ declare const zDirectTool: z.ZodObject<{
|
|
|
23
23
|
}, z.core.$strip>>>;
|
|
24
24
|
}, z.core.$strip>;
|
|
25
25
|
/** Tool type - can be a built-in tool string or custom tool object. */
|
|
26
|
-
export declare const zToolType: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">, z.ZodLiteral<"browser">]>, z.ZodObject<{
|
|
26
|
+
export declare const zToolType: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"town_web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">, z.ZodLiteral<"browser">]>, z.ZodObject<{
|
|
27
27
|
type: z.ZodLiteral<"custom">;
|
|
28
28
|
modulePath: z.ZodString;
|
|
29
29
|
}, z.core.$strip>, z.ZodObject<{
|
package/dist/runner/tools.js
CHANGED
|
@@ -28,6 +28,11 @@ declare class AgentTelemetry {
|
|
|
28
28
|
* @param attributes - Attributes to merge with existing base attributes
|
|
29
29
|
*/
|
|
30
30
|
setBaseAttributes(attributes: Record<string, string | number>): void;
|
|
31
|
+
/**
|
|
32
|
+
* Remove a base attribute
|
|
33
|
+
* @param key - Attribute key to remove
|
|
34
|
+
*/
|
|
35
|
+
clearBaseAttribute(key: string): void;
|
|
31
36
|
/**
|
|
32
37
|
* Start a new span
|
|
33
38
|
* @param name - Span name
|