@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.
@@ -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 { loadAuthCredentials } from "@townco/core/auth";
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 credentials = loadAuthCredentials();
28
- if (!credentials) {
29
- throw new Error("Not logged in. Run 'town login' first.");
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: credentials.access_token,
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 a new OTEL span for this LLM request.
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
- // Create span within the parent context (invocation span)
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 span = context.with(opts.parentContext, () => telemetry.startSpan(`chat ${opts.model}`, {
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
- if (span) {
118
- spansByRunId.set(runId, span);
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 = span.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 the span.
167
+ * Records token usage and ends both chat and iteration spans.
136
168
  */
137
169
  async handleLLMEnd(output, runId) {
138
- const span = spansByRunId.get(runId);
139
- if (!span)
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, span);
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(span, {
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 = span.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
- telemetry.endSpan(span);
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 the span with error status.
217
+ * Records the error and ends both chat and iteration spans.
180
218
  */
181
219
  async handleLLMError(error, runId) {
182
- const span = spansByRunId.get(runId);
183
- if (!span)
184
- return;
185
- telemetry.endSpan(span, error instanceof Error ? error : new Error(String(error)));
186
- spansByRunId.delete(runId);
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
- const sseResponse = await fetch(`${baseUrl}/events`, {
331
- headers: { "X-Session-ID": sessionId },
332
- signal: sseAbortController.signal,
333
- });
334
- if (!sseResponse.ok || !sseResponse.body) {
335
- throw new Error(`SSE connection failed: HTTP ${sseResponse.status}`);
336
- }
337
- const reader = sseResponse.body.getReader();
338
- const decoder = new TextDecoder();
339
- let buffer = "";
340
- while (true) {
341
- const { done, value } = await reader.read();
342
- if (done)
343
- break;
344
- buffer += decoder.decode(value, { stream: true });
345
- const lines = buffer.split("\n");
346
- buffer = lines.pop() || "";
347
- for (const line of lines) {
348
- if (line.startsWith("data:")) {
349
- const data = line.substring(5).trim();
350
- if (!data)
351
- continue;
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
- // Handle agent_message_chunk - accumulate text
358
- if (update.sessionUpdate === "agent_message_chunk") {
359
- const content = update.content;
360
- if (content?.type === "text" &&
361
- typeof content.text === "string") {
362
- responseText += content.text;
363
- currentMessage.content += content.text;
364
- // Add to contentBlocks - append to last text block or create new one
365
- const lastBlock = currentMessage.contentBlocks[currentMessage.contentBlocks.length - 1];
366
- if (lastBlock && lastBlock.type === "text") {
367
- lastBlock.text += content.text;
368
- }
369
- else {
370
- currentMessage.contentBlocks.push({
371
- type: "text",
372
- text: content.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
- // Handle tool_call - track new tool calls
378
- if (update.sessionUpdate === "tool_call" && update.toolCallId) {
379
- const toolCall = {
380
- id: update.toolCallId,
381
- title: update.title || "Tool call",
382
- prettyName: update._meta?.prettyName,
383
- icon: update._meta?.icon,
384
- status: update.status || "pending",
385
- };
386
- currentMessage.toolCalls.push(toolCall);
387
- toolCallMap.set(update.toolCallId, currentMessage.toolCalls.length - 1);
388
- // Add to contentBlocks for interleaved display
389
- currentMessage.contentBlocks.push({
390
- type: "tool_call",
391
- toolCall,
392
- });
393
- }
394
- // Handle tool_call_update - update existing tool call status
395
- if (update.sessionUpdate === "tool_call_update" &&
396
- update.toolCallId) {
397
- const idx = toolCallMap.get(update.toolCallId);
398
- if (idx !== undefined && currentMessage.toolCalls[idx]) {
399
- if (update.status) {
400
- currentMessage.toolCalls[idx].status =
401
- update.status;
402
- }
403
- // Also update in contentBlocks
404
- const block = currentMessage.contentBlocks.find((b) => b.type === "tool_call" &&
405
- b.toolCall.id === update.toolCallId);
406
- if (block && update.status) {
407
- block.toolCall.status =
408
- update.status;
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
- catch {
414
- // Ignore malformed SSE data
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
@@ -78,3 +78,7 @@ When in doubt, use this tool. Being proactive with task management demonstrates
78
78
  });
79
79
  todoWrite.prettyName = "Todo List";
80
80
  todoWrite.icon = "CheckSquare";
81
+ todoWrite.verbiage = {
82
+ active: "Updating to-do's",
83
+ past: "Updated to-do's",
84
+ };
@@ -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 _exaClient = null;
6
- function getExaClient() {
7
- if (_exaClient) {
8
- return _exaClient;
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
- _exaClient = new Exa(apiKey);
16
- return _exaClient;
18
+ _directExaClient = new Exa(apiKey);
19
+ return _directExaClient;
17
20
  }
18
- export function makeWebSearchTools() {
19
- // WebSearch tool - search the web
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 = getExaClient();
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
- // WebFetch tool - get contents of specific URLs
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 = getExaClient();
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);
@@ -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<{
@@ -4,6 +4,7 @@ export const zBuiltInToolType = z.union([
4
4
  z.literal("todo_write"),
5
5
  z.literal("get_weather"),
6
6
  z.literal("web_search"),
7
+ z.literal("town_web_search"),
7
8
  z.literal("filesystem"),
8
9
  z.literal("generate_image"),
9
10
  z.literal("browser"),
@@ -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