@townco/agent 0.1.73 → 0.1.74
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 +74 -27
- 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/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
|
@@ -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
|
}
|
|
@@ -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
|
package/dist/telemetry/index.js
CHANGED
|
@@ -40,6 +40,14 @@ class AgentTelemetry {
|
|
|
40
40
|
...attributes,
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Remove a base attribute
|
|
45
|
+
* @param key - Attribute key to remove
|
|
46
|
+
*/
|
|
47
|
+
clearBaseAttribute(key) {
|
|
48
|
+
const { [key]: _, ...rest } = this.baseAttributes;
|
|
49
|
+
this.baseAttributes = rest;
|
|
50
|
+
}
|
|
43
51
|
/**
|
|
44
52
|
* Start a new span
|
|
45
53
|
* @param name - Span name
|
package/dist/telemetry/setup.js
CHANGED
|
@@ -68,10 +68,12 @@ export function initializeOpenTelemetry(options = {}) {
|
|
|
68
68
|
// false "Request timed out" errors. The export usually succeeds anyway.
|
|
69
69
|
// Only log non-timeout errors as actual errors.
|
|
70
70
|
const errorMsg = result.error?.message ?? "";
|
|
71
|
-
if (errorMsg.includes("timed out")
|
|
71
|
+
if (errorMsg.includes("timed out") ||
|
|
72
|
+
errorMsg.includes("Request timed out")) {
|
|
72
73
|
if (debug) {
|
|
73
|
-
console.log(`⚠️ Export reported timeout (Bun http bug - data
|
|
74
|
+
console.log(`⚠️ Export reported timeout (Bun http bug - data successfully sent)`);
|
|
74
75
|
}
|
|
76
|
+
// Don't log timeout errors at all - they're false positives
|
|
75
77
|
}
|
|
76
78
|
else {
|
|
77
79
|
console.error(`❌ Failed to export spans:`, result.error);
|
|
@@ -141,7 +143,12 @@ export function initializeOpenTelemetry(options = {}) {
|
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
catch (error) {
|
|
144
|
-
|
|
146
|
+
// Filter out Bun HTTP timeout errors - they're false positives
|
|
147
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
148
|
+
if (!errorMsg.includes("timed out") &&
|
|
149
|
+
!errorMsg.includes("Request timed out")) {
|
|
150
|
+
console.error("Error flushing telemetry:", error);
|
|
151
|
+
}
|
|
145
152
|
}
|
|
146
153
|
};
|
|
147
154
|
return { provider, loggerProvider, shutdown };
|